From 999e76e598c043ff2f1923cb13574076639ab84e Mon Sep 17 00:00:00 2001 From: Jetz Date: Fri, 22 Aug 2025 09:14:07 -0400 Subject: [PATCH 001/230] Refactor event generation, add `set event` command. --- .../adventure/data/AdventureEventData.java | 204 +++++++++--------- .../src/forge/adventure/scene/InnScene.java | 22 +- .../stage/ConsoleCommandInterpreter.java | 26 ++- .../src/forge/adventure/stage/MapStage.java | 10 + .../util/AdventureEventController.java | 68 +++--- 5 files changed, 194 insertions(+), 136 deletions(-) diff --git a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java index 703800b4788..fa96f36809d 100644 --- a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java +++ b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java @@ -23,14 +23,15 @@ import forge.util.Aggregates; import forge.util.IterableUtil; import forge.util.MyRandom; import forge.util.StreamUtil; -import org.apache.commons.lang3.tuple.Pair; +import java.io.Serial; import java.io.Serializable; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; public class AdventureEventData implements Serializable { + @Serial private static final long serialVersionUID = 1L; public transient BoosterDraft draft; public AdventureEventParticipant[] participants; @@ -68,6 +69,7 @@ public class AdventureEventData implements Serializable { style = other.style; random.setSeed(eventSeed); eventStatus = other.eventStatus; + format = other.format; registeredDeck = other.registeredDeck; isDraftComplete = other.isDraftComplete; description = other.description; @@ -94,49 +96,48 @@ public class AdventureEventData implements Serializable { } public AdventureEventData(Long seed, AdventureEventController.EventFormat selectedFormat) { + this(seed, selectedFormat, AdventureEventController.EventStyle.Bracket, pickCardBlockByFormat(selectedFormat)); + } + + public AdventureEventData(Long seed, AdventureEventController.EventFormat selectedFormat, AdventureEventController.EventStyle style) { + this(seed, selectedFormat, style, pickCardBlockByFormat(selectedFormat)); + } + + public AdventureEventData(Long seed, AdventureEventController.EventFormat selectedFormat, AdventureEventController.EventStyle style, CardBlock cardBlock) { setEventSeed(seed); eventStatus = AdventureEventController.EventStatus.Available; registeredDeck = new Deck(); format = selectedFormat; + this.cardBlock = cardBlock; + if (cardBlock == null) + return; + cardBlockName = cardBlock.getName(); if (format == AdventureEventController.EventFormat.Draft) { - cardBlock = pickWeightedCardBlock(); - if (cardBlock == null) - return; - cardBlockName = cardBlock.getName(); - //Below all to be fully generated in later release rewardPacks = getRewardPacks(3); generateParticipants(7); - if (cardBlock != null) { - packConfiguration = getBoosterConfiguration(cardBlock); + packConfiguration = getBoosterConfiguration(cardBlock); - rewards = new AdventureEventData.AdventureEventReward[4]; - AdventureEventData.AdventureEventReward r0 = new AdventureEventData.AdventureEventReward(); - AdventureEventData.AdventureEventReward r1 = new AdventureEventData.AdventureEventReward(); - AdventureEventData.AdventureEventReward r2 = new AdventureEventData.AdventureEventReward(); - AdventureEventData.AdventureEventReward r3 = new AdventureEventData.AdventureEventReward(); - r0.minWins = 0; - r0.maxWins = 0; - r0.cardRewards = new Deck[]{rewardPacks[0]}; - rewards[0] = r0; - r1.minWins = 1; - r1.maxWins = 3; - r1.cardRewards = new Deck[]{rewardPacks[1], rewardPacks[2]}; - rewards[1] = r1; - r2.minWins = 2; - r2.maxWins = 3; - r2.itemRewards = new String[]{"Challenge Coin"}; - rewards[2] = r2; - } + rewards = new AdventureEventReward[4]; + AdventureEventReward r0 = new AdventureEventReward(); + AdventureEventReward r1 = new AdventureEventReward(); + AdventureEventReward r2 = new AdventureEventReward(); + AdventureEventReward r3 = new AdventureEventReward(); + r0.minWins = 0; + r0.maxWins = 0; + r0.cardRewards = new Deck[]{rewardPacks[0]}; + rewards[0] = r0; + r1.minWins = 1; + r1.maxWins = 3; + r1.cardRewards = new Deck[]{rewardPacks[1], rewardPacks[2]}; + rewards[1] = r1; + r2.minWins = 2; + r2.maxWins = 3; + r2.itemRewards = new String[]{"Challenge Coin"}; + rewards[2] = r2; } else if (format == AdventureEventController.EventFormat.Jumpstart) { int numPacksToPickFrom = 6; generateParticipants(7); - - cardBlock = pickJumpstartCardBlock(); - if (cardBlock == null) - return; - cardBlockName = cardBlock.getName(); - jumpstartBoosters = AdventureEventController.instance().getJumpstartBoosters(cardBlock, numPacksToPickFrom); packConfiguration = new String[]{cardBlock.getLandSet().getCode(), cardBlock.getLandSet().getCode(), cardBlock.getLandSet().getCode()}; @@ -227,11 +228,10 @@ public class AdventureEventData implements Serializable { //3. If no matching color found and need more packs, add any available at random. if (packConfiguration.length > chosenPacks.size() && colorAdded.isEmpty() && !availableOptions.isEmpty()) { chosenPacks.add(Aggregates.removeRandom(availableOptions)); - colorAdded = ""; } else { done = colorAdded.isEmpty() || packConfiguration.length <= chosenPacks.size(); - colorAdded = ""; } + colorAdded = ""; } participant.registeredDeck = new Deck(); @@ -272,6 +272,16 @@ public class AdventureEventData implements Serializable { rewards[3] = r3; //r3 will be the selected card packs } + + switch (style) { + case Swiss: + case Bracket: + this.rounds = (participants.length / 2) - 1; + break; + case RoundRobin: + this.rounds = participants.length - 1; + break; + } } public void setEventSeed(long seed) { @@ -303,6 +313,15 @@ public class AdventureEventData implements Serializable { return draft; } + private static CardBlock pickCardBlockByFormat(AdventureEventController.EventFormat format) { + return switch (format) { + case Draft -> pickWeightedCardBlock(); + case Jumpstart -> pickJumpstartCardBlock(); + case Constructed -> null; + case Sealed -> null; + }; + } + private static final Predicate filterPioneer = FModel.getFormats().getPioneer().editionLegalPredicate; private static final Predicate filterModern = FModel.getFormats().getModern().editionLegalPredicate; private static final Predicate filterVintage = FModel.getFormats().getVintage().editionLegalPredicate; @@ -323,11 +342,11 @@ public class AdventureEventData implements Serializable { return rolledFilter; } + private static final Set POWER_NINE = Set.of("Black Lotus", "Mox Emerald", "Mox Pearl", "Mox Ruby", "Mox Sapphire", "Mox Jet", "Ancestral Recall", "Timetwister", "Time Walk"); - private CardBlock pickWeightedCardBlock() { + private static CardBlock pickWeightedCardBlock() { CardEdition.Collection editions = FModel.getMagicDb().getEditions(); ConfigData configData = Config.instance().getConfigData(); - Iterable src = FModel.getBlocks(); //all blocks Predicate filter = CardEdition.Predicates.CAN_MAKE_BOOSTER.and(selectSetPool()); if(configData.restrictedEvents != null) { @@ -351,52 +370,41 @@ public class AdventureEventData implements Serializable { .filter(CardEdition::hasBoosterTemplate) .forEach(allEditions::add); - List legalBlocks = new ArrayList<>(); - for (CardBlock b : src) { // for each block - if (b.getSets().isEmpty() || (b.getCntBoostersDraft() < 1)) - continue; - boolean isOkay = true; - for (CardEdition c : b.getSets()) { - if (!allEditions.contains(c)) { - isOkay = false; - break; - } - if (!c.hasBoosterTemplate()) { - isOkay = false; - break; - } else { - final List> slots = c.getBoosterTemplate().getSlots(); - int boosterSize = 0; - for (Pair slot : slots) { - boosterSize += slot.getRight(); - } - isOkay = boosterSize > 11; - } - for (PrintSheet ps : c.getPrintSheetsBySection()) { - //exclude block with sets containing P9 cards.. - if (ps.containsCardNamed("Black Lotus", 1) - || ps.containsCardNamed("Mox Emerald", 1) - || ps.containsCardNamed("Mox Pearl", 1) - || ps.containsCardNamed("Mox Ruby", 1) - || ps.containsCardNamed("Mox Sapphire", 1) - || ps.containsCardNamed("Mox Jet", 1) - || ps.containsCardNamed("Ancestral Recall", 1) - || ps.containsCardNamed("Timetwister", 1) - || ps.containsCardNamed("Time Walk", 1)) { - isOkay = false; - break; - } - } - } - if (isOkay) { - legalBlocks.add(b); - } - } + List legalBlocks = getValidDraftBlocks(allEditions); return legalBlocks.isEmpty() ? null : Aggregates.random(legalBlocks); } - private CardBlock pickJumpstartCardBlock() { + public static List getValidDraftBlocks(List validEditions) { + List legalBlocks = new ArrayList<>(); + for (CardBlock b : FModel.getBlocks()) { // for each block + if (b.getSets().isEmpty() || (b.getCntBoostersDraft() < 1)) + continue; + if (!isValidDraftBlock(b, validEditions)) + continue; + legalBlocks.add(b); + } + return legalBlocks; + } + + private static boolean isValidDraftBlock(CardBlock b, List validEditions) { + for (CardEdition c : b.getSets()) { + if (!validEditions.contains(c)) + return false; + if (!c.hasBoosterTemplate()) + return false; + if(c.getBoosterTemplate().getNumberOfCardsExpected() <= 11) + return false; + for (PrintSheet ps : c.getPrintSheetsBySection()) { + //exclude block with sets containing P9 cards. + if(!Collections.disjoint(ps.toNameLookup().keySet(), POWER_NINE)) + return false; + } + } + return true; + } + + private static CardBlock pickJumpstartCardBlock() { Iterable src = FModel.getBlocks(); //all blocks List legalBlocks = new ArrayList<>(); ConfigData configData = Config.instance().getConfigData(); @@ -584,29 +592,13 @@ public class AdventureEventData implements Serializable { eventStatus = AdventureEventController.EventStatus.Awarded; } - public String getPairingDescription() { - switch (eventRules.pairingStyle) { - case Swiss: - return "swiss"; - case SwissWithCut: - return "swiss (with cut)"; - case RoundRobin: - return "round robin"; - case SingleElimination: - return "single elimination"; - case DoubleElimination: - return "double elimination"; - } - return ""; - } - public String getDescription(PointOfInterestChanges changes) { float townPriceModifier = changes == null ? 1f : changes.getTownPriceModifier(); - if (format.equals(AdventureEventController.EventFormat.Draft)) { + if (format == AdventureEventController.EventFormat.Draft) { description = "Event Type: Booster Draft\n"; description += "Block: " + getCardBlock() + "\n"; description += "Boosters: " + String.join(", ", packConfiguration) + "\n"; - description += "Competition Style: " + participants.length + " players, matches played as best of " + eventRules.gamesPerMatch + ", " + (getPairingDescription()) + "\n\n"; + description += "Competition Style: " + participants.length + " players, matches played as best of " + eventRules.gamesPerMatch + ", " + (eventRules.getPairingDescription()) + "\n\n"; description += String.format("Pay 1 Entry Fee\n- Gold %d[][+Gold][BLACK]\n- Mana Shards %d[][+Shards][BLACK]\n", Math.round(eventRules.goldToEnter * townPriceModifier), Math.round(eventRules.shardsToEnter * townPriceModifier)); if (eventRules.acceptsBronzeChallengeCoin) { description += "- Bronze Challenge Coin [][+BronzeChallengeCoin][BLACK]\n\n"; @@ -618,10 +610,10 @@ public class AdventureEventData implements Serializable { description += "\n"; } description += String.format("Prizes\nChampion: Keep drafted deck\n2+ round wins: Challenge Coin \n1+ round wins: %s Booster, %s Booster\n0 round wins: %s Booster", rewardPacks[0].getComment(), rewardPacks[1].getComment(), rewardPacks[2].getComment()); - } else if (format.equals(AdventureEventController.EventFormat.Jumpstart)) { + } else if (format == AdventureEventController.EventFormat.Jumpstart) { description = "Event Type: Jumpstart\n"; description += "Block: " + getCardBlock() + "\n"; - description += "Competition Style: " + participants.length + " players, matches played as best of " + eventRules.gamesPerMatch + ", " + (getPairingDescription()) + "\n\n"; + description += "Competition Style: " + participants.length + " players, matches played as best of " + eventRules.gamesPerMatch + ", " + (eventRules.getPairingDescription()) + "\n\n"; description += String.format("Pay 1 Entry Fee\n- Gold %d[][+Gold][BLACK]\n- Mana Shards %d[][+Shards][BLACK]\n", Math.round(eventRules.goldToEnter * townPriceModifier), Math.round(eventRules.shardsToEnter * townPriceModifier)); if (eventRules.acceptsBronzeChallengeCoin) { description += "- Bronze Challenge Coin [][+BronzeChallengeCoin][BLACK]\n\n"; @@ -640,6 +632,7 @@ public class AdventureEventData implements Serializable { public static class AdventureEventParticipant implements Serializable, Comparable { + @Serial private static final long serialVersionUID = 1L; private transient EnemySprite sprite; String enemyDataName; @@ -727,6 +720,7 @@ public class AdventureEventData implements Serializable { } public static class AdventureEventRules implements Serializable { + @Serial private static final long serialVersionUID = -2902188278147984885L; public int goldToEnter; public int shardsToEnter; @@ -786,9 +780,20 @@ public class AdventureEventData implements Serializable { goldToEnter = baseGoldEntry; shardsToEnter = baseShardEntry; } + + public String getPairingDescription() { + return switch (pairingStyle) { + case Swiss -> "swiss"; + case SwissWithCut -> "swiss (with cut)"; + case RoundRobin -> "round robin"; + case SingleElimination -> "single elimination"; + case DoubleElimination -> "double elimination"; + }; + } } public static class AdventureEventMatch implements Serializable { + @Serial private static final long serialVersionUID = 1L; public AdventureEventParticipant p1; public AdventureEventParticipant p2; @@ -797,6 +802,7 @@ public class AdventureEventData implements Serializable { } public static class AdventureEventReward implements Serializable { + @Serial private final static long serialVersionUID = -2605375040895115477L; public int minWins = -1; public int maxWins = -1; @@ -806,7 +812,7 @@ public class AdventureEventData implements Serializable { public boolean isNoSell = false; } - enum PairingStyle { + public enum PairingStyle { SingleElimination, DoubleElimination, Swiss, diff --git a/forge-gui-mobile/src/forge/adventure/scene/InnScene.java b/forge-gui-mobile/src/forge/adventure/scene/InnScene.java index 3b2b391b10f..4aa6346212a 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/InnScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/InnScene.java @@ -11,6 +11,7 @@ import forge.adventure.stage.GameHUD; import forge.adventure.util.AdventureEventController; import forge.adventure.util.Controls; import forge.adventure.util.Current; +import forge.model.CardBlock; /** * Scene for the Inn in towns @@ -34,7 +35,7 @@ public class InnScene extends UIScene { localObjectId = objectId; if (lastGameScene != null) object.lastGameScene=lastGameScene; - getLocalEvent(); + initLocalEvent(); return object; } @@ -109,7 +110,7 @@ public class InnScene extends UIScene { tempHitPointCost.setDisabled(!purchaseable); tempHitPointCost.setText("[+GoldCoin] " + tempHealthCost); - getLocalEvent(); + initLocalEvent(); if (localEvent == null){ eventDescription.setText("[GREY]No events at this time"); event.setDisabled(true); @@ -148,9 +149,7 @@ public class InnScene extends UIScene { Forge.switchScene(ShopScene.instance()); } - - - private static void getLocalEvent() { + private static void initLocalEvent() { localEvent = null; for (AdventureEventData data : AdventurePlayer.current().getEvents()){ if (data.sourceID.equals(localPointOfInterestId) && data.eventOrigin == localObjectId){ @@ -158,7 +157,18 @@ public class InnScene extends UIScene { return; } } - localEvent = AdventureEventController.instance().createEvent(AdventureEventController.EventStyle.Bracket, localPointOfInterestId, localObjectId, changes); + AdventureEventController controller = AdventureEventController.instance(); + localEvent = controller.createEvent(AdventureEventController.EventStyle.Bracket, localPointOfInterestId); + if(localEvent != null) + controller.initializeEvent(localEvent, localPointOfInterestId, localObjectId, changes); + } + + public static void replaceLocalEvent(AdventureEventController.EventFormat format, CardBlock cardBlock) { + AdventurePlayer.current().getEvents().removeIf((data) -> data.sourceID.equals(localPointOfInterestId) && data.eventOrigin == localObjectId); + AdventureEventController controller = AdventureEventController.instance(); + localEvent = controller.createEvent(AdventureEventController.EventStyle.Bracket, format, cardBlock, localPointOfInterestId); + if(localEvent != null) + controller.initializeEvent(localEvent, localPointOfInterestId, localObjectId, changes); } private void startEvent(){ diff --git a/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java b/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java index e19f72e1ffe..da2727776d5 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java +++ b/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java @@ -8,7 +8,7 @@ import forge.StaticData; import forge.adventure.character.PlayerSprite; import forge.adventure.data.*; import forge.adventure.pointofintrest.PointOfInterest; -import forge.adventure.scene.InventoryScene; +import forge.adventure.scene.InnScene; import forge.adventure.util.AdventureEventController; import forge.adventure.util.Current; import forge.adventure.util.Paths; @@ -21,7 +21,10 @@ import forge.deck.DeckProxy; import forge.game.GameType; import forge.gui.FThreads; import forge.item.PaperCard; +import forge.model.CardBlock; +import forge.model.FModel; import forge.screens.CoverScreen; +import forge.util.Aggregates; import java.util.ArrayList; import java.util.Arrays; @@ -510,5 +513,26 @@ public class ConsoleCommandInterpreter { } return message; }); + registerCommand(new String[]{"set", "event"}, s -> { + if(s.length < 1) return "Command needs 1 parameter: Block or edition name. "; + String blockName = s[0]; + if(MapStage.getInstance().findLocalInn() == null) + return "Must be used within a town with an inn."; + CardBlock eventCardBlock = FModel.getBlocks().find(b -> b.getName().equalsIgnoreCase(blockName)); + if(eventCardBlock == null) { + CardEdition edition = FModel.getMagicDb().getEditions().find(e -> e.getCode().equalsIgnoreCase(blockName) || e.getName().equalsIgnoreCase(blockName)); + if(edition == null) + return "Unable to find edition or block: " + blockName; + eventCardBlock = Aggregates.random(AdventureEventData.getValidDraftBlocks(List.of(edition))); + if(eventCardBlock == null) + return "Unable to find a valid event block that exclusively contains edition " + edition.getName(); + } + AdventureEventController.EventFormat eventFormat = s.length > 1 ? AdventureEventController.EventFormat.smartValueOf(s[1]) + : eventCardBlock.getName().contains("Jumpstart") ? AdventureEventController.EventFormat.Jumpstart : AdventureEventController.EventFormat.Draft; + if(eventFormat == null) + return "Unknown event format: " + s[1]; + InnScene.replaceLocalEvent(eventFormat, eventCardBlock); + return "Replaced local event with " + eventFormat.name() + " - " + eventCardBlock.getName(); + }); } } diff --git a/forge-gui-mobile/src/forge/adventure/stage/MapStage.java b/forge-gui-mobile/src/forge/adventure/stage/MapStage.java index ad96a87f1f5..3b408bcd475 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/MapStage.java +++ b/forge-gui-mobile/src/forge/adventure/stage/MapStage.java @@ -260,6 +260,7 @@ public class MapStage extends GameStage { spawnClassified.clear(); sourceMapMatch.clear(); enemies.clear(); + localInnID = -1; for (MapLayer layer : map.getLayers()) { if (layer.getProperties().containsKey("spriteLayer") && layer.getProperties().get("spriteLayer", boolean.class)) { spriteLayer = layer; @@ -578,6 +579,7 @@ public class MapStage extends GameStage { //TODO: Ability to move them (using a sequence such as "UULU" for up, up, left, up). break; case "inn": + localInnID = id; addMapActor(obj, new OnCollide(() -> Forge.switchScene(InnScene.instance(TileMapScene.instance(), TileMapScene.instance().rootPoint.getID(), changes, id)))); break; case "spellsmith": @@ -750,6 +752,14 @@ public class MapStage extends GameStage { } } + //We could track MapObject IDs more generally but for now this is the only one we might need. + private int localInnID = -1; + public InnScene findLocalInn() { + if(localInnID == -1) + return null; + return InnScene.instance(TileMapScene.instance(), TileMapScene.instance().rootPoint.getID(), changes, localInnID); + } + public boolean exitDungeon(boolean defeated) { AdventureQuestController.instance().updateQuestsLeave(); clearIsInMap(); diff --git a/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java b/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java index 2e6c1c1d1d5..bc57fc631ac 100644 --- a/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java +++ b/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java @@ -31,7 +31,13 @@ public class AdventureEventController implements Serializable { Draft, Sealed, Jumpstart, - Constructed + Constructed; + + public static EventFormat smartValueOf(String name) { + return Arrays.stream(EventFormat.values()) + .filter(e -> e.name().equalsIgnoreCase(name)) + .findFirst().orElse(null); + } } public enum EventStyle { @@ -78,12 +84,39 @@ public class AdventureEventController implements Serializable { object = null; } - public AdventureEventData createEvent(EventStyle style, String pointID, int eventOrigin, PointOfInterestChanges changes) { + public AdventureEventData createEvent(EventStyle style, String pointID) { if (nextEventDate.containsKey(pointID) && nextEventDate.get(pointID) >= LocalDate.now().toEpochDay()) { //No event currently available here return null; } + long eventSeed = getEventSeed(pointID); + Random random = new Random(eventSeed); + + AdventureEventData e; + // TODO After a certain amount of wins, stop offering jump start events + if (random.nextInt(10) <= 2) { + e = new AdventureEventData(eventSeed, EventFormat.Jumpstart, style); + } else { + e = new AdventureEventData(eventSeed, EventFormat.Draft, style); + } + + if (e.cardBlock == null) { + //covers cases where (somehow) editions that do not match the event style have been picked up + return null; + } + return e; + } + + public AdventureEventData createEvent(EventStyle style, EventFormat format, CardBlock cardBlock, String pointID) { + long eventSeed = getEventSeed(pointID); + AdventureEventData e = new AdventureEventData(eventSeed, format, style, cardBlock); + if(e.cardBlock == null) + return null; + return e; + } + + private static long getEventSeed(String pointID) { long eventSeed; long timeSeed = LocalDate.now().toEpochDay(); long placeSeed = Long.parseLong(pointID.replaceAll("[^0-9]", "")); @@ -94,40 +127,16 @@ public class AdventureEventController implements Serializable { } else { eventSeed = timeSeed + placeSeed; } + return eventSeed; + } - Random random = new Random(eventSeed); - - AdventureEventData e; - - // TODO After a certain amount of wins, stop offering jump start events - if (random.nextInt(10) <= 2) { - e = new AdventureEventData(eventSeed, EventFormat.Jumpstart); - } else { - e = new AdventureEventData(eventSeed, EventFormat.Draft); - } - - if (e.cardBlock == null) { - //covers cases where (somehow) editions that do not match the event style have been picked up - return null; - } + public void initializeEvent(AdventureEventData e, String pointID, int eventOrigin, PointOfInterestChanges changes) { e.sourceID = pointID; e.eventOrigin = eventOrigin; e.eventRules = new AdventureEventData.AdventureEventRules(e.format, changes == null ? 1f : changes.getTownPriceModifier()); - e.style = style; - - switch (style) { - case Swiss: - case Bracket: - e.rounds = (e.participants.length / 2) - 1; - break; - case RoundRobin: - e.rounds = e.participants.length - 1; - break; - } AdventurePlayer.current().addEvent(e); nextEventDate.put(pointID, LocalDate.now().toEpochDay() + new Random().nextInt(2)); //next local event availability date - return e; } public Deck generateBooster(String setCode) { @@ -141,7 +150,6 @@ public class AdventureEventController implements Serializable { } public Deck generateBoosterByColor(String color) { - List cards = BoosterPack.fromColor(color).getCards(); Deck output = new Deck(); output.getMain().add(cards); From bee208f1d50d1c13543996d371a1417004bdcf99 Mon Sep 17 00:00:00 2001 From: Jetz Date: Fri, 22 Aug 2025 09:22:16 -0400 Subject: [PATCH 002/230] Add toNameLookup to print sheet, remove containsCardName. --- .../src/main/java/forge/card/PrintSheet.java | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/forge-core/src/main/java/forge/card/PrintSheet.java b/forge-core/src/main/java/forge/card/PrintSheet.java index a714e84341b..db3f1293863 100644 --- a/forge-core/src/main/java/forge/card/PrintSheet.java +++ b/forge-core/src/main/java/forge/card/PrintSheet.java @@ -9,9 +9,7 @@ import forge.util.storage.StorageExtendable; import forge.util.storage.StorageReaderFileSections; import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; import java.util.Map.Entry; import java.util.function.Predicate; @@ -93,19 +91,6 @@ public class PrintSheet { return fetchRoulette(sum + 1, roulette, toSkip); // start over from beginning, in case last cards were to skip } - public boolean containsCardNamed(String name,int atLeast) { - int count=0; - for (Entry kv : cardsWithWeights) { - for (int i = 0; i < kv.getValue(); i++) { - if(kv.getKey().getName().equals(name)) - { - count++; - if(count>=atLeast)return true; - } - } - } - return false; - } public String getName() { return name; } @@ -147,6 +132,10 @@ public class PrintSheet { return cardsWithWeights.toFlatList(); } + public Map toNameLookup() { + return cardsWithWeights.toNameLookup(); + } + public static class Reader extends StorageReaderFileSections { public Reader(File file) { super(file, PrintSheet::getName); From 4abc96c045a30ed800d050b2aa8fdd15dd7bb6de Mon Sep 17 00:00:00 2001 From: Jetz Date: Mon, 25 Aug 2025 08:34:43 -0400 Subject: [PATCH 003/230] Fix rebase issue --- .../src/forge/adventure/stage/ConsoleCommandInterpreter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java b/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java index da2727776d5..40a7743d82b 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java +++ b/forge-gui-mobile/src/forge/adventure/stage/ConsoleCommandInterpreter.java @@ -9,6 +9,7 @@ import forge.adventure.character.PlayerSprite; import forge.adventure.data.*; import forge.adventure.pointofintrest.PointOfInterest; import forge.adventure.scene.InnScene; +import forge.adventure.scene.InventoryScene; import forge.adventure.util.AdventureEventController; import forge.adventure.util.Current; import forge.adventure.util.Paths; From 10a68c27d5aa8cffb46e268f21d6998fa353142e Mon Sep 17 00:00:00 2001 From: tool4ever Date: Thu, 18 Sep 2025 16:36:25 +0000 Subject: [PATCH 004/230] Update omarthis_ghostfire_initiate.txt --- forge-gui/res/cardsfolder/o/omarthis_ghostfire_initiate.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/o/omarthis_ghostfire_initiate.txt b/forge-gui/res/cardsfolder/o/omarthis_ghostfire_initiate.txt index 36d52ee68db..824e4d54701 100644 --- a/forge-gui/res/cardsfolder/o/omarthis_ghostfire_initiate.txt +++ b/forge-gui/res/cardsfolder/o/omarthis_ghostfire_initiate.txt @@ -8,7 +8,7 @@ T:Mode$ CounterAddedOnce | ValidCard$ Creature.Other+inZoneBattlefield+Colorless SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigManifest | TriggerDescription$ When NICKNAME dies, manifest a number of cards from the top of your library equal to the number of counters on it. SVar:TrigManifest:DB$ Manifest | Amount$ Y -SVar:Y:TriggeredCard$CardCounters.P1P1 +SVar:Y:TriggeredCard$CardCounters.ALL DeckHas:Ability$Counters DeckHints:Ability$Counters Oracle:Omarthis, Ghostfire Initiate enters with X +1/+1 counters on it.\nWhenever you put one or more +1/+1 counters on another colorless creature, you may put a +1/+1 counter on Omarthis.\nWhen Omarthis dies, manifest a number of cards from the top of your library equal to the number of counters on it. From 398917d010af7842ce33402231c3123f9bf65101 Mon Sep 17 00:00:00 2001 From: Eradev Date: Thu, 18 Sep 2025 13:15:07 -0400 Subject: [PATCH 005/230] Support named abilities (#8464) * Support named abilities * Check if spell was cast from a named ability --- forge-game/src/main/java/forge/game/ForgeScript.java | 2 ++ .../main/java/forge/game/ability/AbilityFactory.java | 4 ++++ .../main/java/forge/game/ability/AbilityUtils.java | 8 ++++++++ .../java/forge/game/ability/effects/PlayEffect.java | 4 ++++ .../java/forge/game/spellability/SpellAbility.java | 10 ++++++++++ .../src/main/java/forge/game/trigger/Trigger.java | 12 ++++++++++++ 6 files changed, 40 insertions(+) diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index 358c597814c..5ce1b116732 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -412,6 +412,8 @@ public class ForgeScript { return !sa.isPwAbility() && !sa.getRestrictions().isSorcerySpeed(); } return true; + } else if(property.startsWith("NamedAbility")) { + return sa.getName().equals(property.substring(12)); } else if (sa.getHostCard() != null) { return sa.getHostCard().hasProperty(property, sourceController, source, spellAbility); } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index f4d580cdf92..f0c89aa25d2 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -239,6 +239,10 @@ public final class AbilityFactory { spellAbility.putParam("PrecostDesc", "Exhaust — "); } + if (mapParams.containsKey("Named")) { + spellAbility.setName(mapParams.get("Named")); + } + // ********************************************* // set universal properties of the SpellAbility 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 94d1d10f119..78d13487b62 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1870,6 +1870,14 @@ public class AbilityUtils { } return doXMath(v, expr, c, ctb); } + + // Count$FromNamedAbility[abilityName].. + if (sq[0].startsWith("FromNamedAbility")) { + String abilityNamed = sq[0].substring(16); + SpellAbility trigSA = sa.getHostCard().getCastSA(); + boolean fromNamedAbility = trigSA != null && trigSA.getName().equals(abilityNamed); + return doXMath(calculateAmount(c, sq[fromNamedAbility ? 1 : 2], ctb), expr, c, ctb); + } } else { // fallback if ctb isn't a spellability if (sq[0].startsWith("LastStateBattlefield")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java index 5bc43048acc..514b5c2bd16 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java @@ -428,6 +428,10 @@ public class PlayEffect extends SpellAbilityEffect { tgtSA.getTargetRestrictions().setMandatory(true); } + if (sa.hasParam("Named")) { + tgtSA.setName(sa.getName()); + } + // can't be done later if (sa.hasParam("ReplaceGraveyard")) { if (!sa.hasParam("ReplaceGraveyardValid") 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 92d94c496df..399e6ebee96 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -174,6 +174,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private CardZoneTable changeZoneTable; private Map loseLifeMap; + private String name = ""; + public CardCollection getLastStateBattlefield() { return lastStateBattlefield; } @@ -2675,4 +2677,12 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public void clearOptionalKeywordAmount() { optionalKeywordAmount.clear(); } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-game/src/main/java/forge/game/trigger/Trigger.java index dbcd5ee48de..926e12d18f8 100644 --- a/forge-game/src/main/java/forge/game/trigger/Trigger.java +++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java @@ -392,6 +392,10 @@ public abstract class Trigger extends TriggerReplacementBase { } } + if (condition == null) { + return true; + } + if ("LifePaid".equals(condition)) { final SpellAbility trigSA = (SpellAbility) runParams.get(AbilityKey.SpellAbility); if (trigSA != null && trigSA.getAmountLifePaid() <= 0) { @@ -442,7 +446,15 @@ public abstract class Trigger extends TriggerReplacementBase { if (game.getCombat().getAttackersAndDefenders().values().containsAll(attacker.getOpponents())) { return false; } + } else if (condition.startsWith("FromNamedAbility")) { + var rest = condition.substring(16); + final SpellAbility trigSA = (SpellAbility) runParams.get(AbilityKey.Cause); + + if (trigSA != null && !trigSA.getName().equals(rest)) { + return false; + } } + return true; } From 9b8020bb30d140a69e14f8eafa0b9caae91185fb Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Thu, 18 Sep 2025 21:01:42 +0200 Subject: [PATCH 006/230] Fix copied SA using old replacingObjects --- .../forge/game/ability/effects/SacrificeEffect.java | 11 +++++------ .../java/forge/game/spellability/SpellAbility.java | 3 +++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java index 313261f7459..4fe536aae8f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SacrificeEffect.java @@ -92,12 +92,11 @@ public class SacrificeEffect extends SpellAbilityEffect { CardZoneTable zoneMovements = AbilityKey.addCardZoneTableParams(params, sa); if (valid.equals("Self") && game.getZoneOf(host) != null) { - if (host.getController().equals(activator) && game.getZoneOf(host).is(ZoneType.Battlefield)) { - if (!optional || activator.getController().confirmAction(sa, null, - Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getName()), null)) { - if (game.getAction().sacrifice(new CardCollection(host), sa, true, params) != null && remSacrificed) { - host.addRemembered(host); - } + if (host.getController().equals(activator) && game.getZoneOf(host).is(ZoneType.Battlefield) && + (!optional || activator.getController().confirmAction(sa, null, + Localizer.getInstance().getMessage("lblDoYouWantSacrificeThis", host.getName()), null))) { + if (game.getAction().sacrifice(new CardCollection(host), sa, true, params) != null && remSacrificed) { + host.addRemembered(host); } } } else { 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 399e6ebee96..2f91be78f11 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1248,6 +1248,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit clone.mayChooseNewTargets = false; clone.triggeringObjects = AbilityKey.newMap(this.triggeringObjects); + if (!lki) { + clone.replacingObjects = AbilityKey.newMap(); + } clone.setPayCosts(getPayCosts().copy()); if (manaPart != null) { From cb7fc3df4e455392f8c2521e5636e0b1fbed9f99 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Fri, 19 Sep 2025 10:32:18 +0800 Subject: [PATCH 007/230] fix gap and visibility on horizontal layout --- .../screens/match/views/VPlayerPanel.java | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java b/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java index 16e7a92061a..d6362537b99 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java @@ -51,6 +51,12 @@ public class VPlayerPanel extends FContainer { return FSkinColor.get(Colors.CLR_INACTIVE).alphaColor(0.5f); } + private static FSkinColor getAltDisplayAreaBackColor() { + if (Forge.isMobileAdventureMode) + return FSkinColor.get(Colors.ADV_CLR_PHASE_INACTIVE_ENABLED).alphaColor(0.3f); + return FSkinColor.get(Colors.CLR_PHASE_INACTIVE_ENABLED).alphaColor(0.3f); + } + private static FSkinColor getDeliriumHighlight() { if (Forge.isMobileAdventureMode) return FSkinColor.get(Colors.ADV_CLR_PHASE_ACTIVE_ENABLED).alphaColor(0.5f); @@ -307,6 +313,8 @@ public class VPlayerPanel extends FContainer { field.update(true); } else if (zoneType == ZoneType.Command) { commandZone.update(); + if (selectedTab != null && Forge.isHorizontalTabLayout()) + updateTabLayout(initW, initH); } else { if (zoneTabs.containsKey(zoneType)) zoneTabs.get(zoneType).update(); @@ -426,7 +434,11 @@ public class VPlayerPanel extends FContainer { } } } - x = avatar.getRight(); + updateTabLayout(width, height); + } + + private void updateTabLayout(float width, float height) { + float x = avatar.getRight(); phaseIndicator.resetFont(); phaseIndicator.setBounds(x, 0, avatar.getWidth() * 0.6f, height); x += phaseIndicator.getWidth(); @@ -461,15 +473,18 @@ public class VPlayerPanel extends FContainer { } prefWidth = width / mod; if (Forge.isHorizontalTabLayout()) { - field.setBounds(x, 0, width - (avatarWidth / 16f), height); - updateFieldDisplayArea(width); + field.setBounds(x, 0, width - avatarWidth, height); + field.getRow1().setWidth(width - (commandZoneCount > 0 ? commandZone.getWidth() + (avatarWidth * commandZoneCount) : avatarWidth)); + field.getRow2().setWidth(width - (avatarWidth / 4f) - (selectedTab == null ? 0 : selectedTab.getIdealWidth(prefWidth) + 1) - avatarWidth * mod); } else field.setBounds(x, 0, fieldWidth, height); x = width - displayAreaWidth - avatarWidth; for (InfoTab tab : tabs) { if (Forge.isHorizontalTabLayout()) { - updateTabDisplayArea(tab, width, height); + float w = tab.getIdealWidth(prefWidth); + float h = height / 2f; + tab.setDisplayBounds(width - w - avatarWidth, isBottomPlayer ? h : 0, w, h); } else { tab.setDisplayBounds(x, 0, displayAreaWidth, height); } @@ -483,21 +498,11 @@ public class VPlayerPanel extends FContainer { } } - private void updateFieldDisplayArea(float width) { - field.getRow1().setWidth(width - (avatarWidth / 8f) - (commandZoneCount > 0 ? commandZoneWidth + 1 : 0)); - field.getRow2().setWidth(width - (avatarWidth / 8f) - (selectedTab == null ? 0 : selectedTab.getIdealWidth(prefWidth) + 1) - avatarWidth * mod); - } - - private void updateTabDisplayArea(InfoTab tab, float width, float height) { - float w = tab.getIdealWidth(prefWidth); - float h = height / 2f; - tab.setDisplayBounds(width - w - avatarWidth, isBottomPlayer ? h : 0, w, h); - } - @Override public void drawBackground(Graphics g) { float y; InfoTab infoTab = selectedTab; + float pad = Forge.isHorizontalTabLayout() ? avatarWidth / 16f : 0f; if (infoTab != null) { //draw background and border for selected zone if needed VDisplayArea selectedDisplayArea = infoTab.getDisplayArea(); float x = selectedDisplayArea == null ? 0 : selectedDisplayArea.getLeft(); @@ -505,7 +510,9 @@ public class VPlayerPanel extends FContainer { float top = selectedDisplayArea == null ? 0 : selectedDisplayArea.getTop(); float h = selectedDisplayArea == null ? 0 : selectedDisplayArea.getHeight(); float bottom = selectedDisplayArea == null ? 0 : selectedDisplayArea.getBottom(); - g.fillRect(getDisplayAreaBackColor(), x, top, w, h); + g.fillRect(Forge.isHorizontalTabLayout() ? getAltDisplayAreaBackColor() : getDisplayAreaBackColor(), x - pad, top, w + pad, h + pad); + if (Forge.isHorizontalTabLayout()) + g.drawLine(1, MatchScreen.getBorderColor(), x, isFlipped() ? bottom : top, x + w, isFlipped() ? bottom : top); if (Forge.isLandscapeMode()) { g.drawLine(1, MatchScreen.getBorderColor(), x, top, x, bottom); @@ -523,6 +530,8 @@ public class VPlayerPanel extends FContainer { float x = commandZone.getLeft(); y = commandZone.getTop(); g.drawLine(1, MatchScreen.getBorderColor(), x, y, x, y + commandZone.getHeight()); + /*if (Forge.isHorizontalTabLayout()) + g.fillRect(getAltDisplayAreaBackColor(), x - pad, y, commandZoneWidth + pad, commandZone.getHeight() + pad);*/ if (isFlipped()) { y += commandZone.getHeight(); } @@ -923,10 +932,8 @@ public class VPlayerPanel extends FContainer { @Override public void update() { super.update(); - if (selectedTab != null && Forge.isHorizontalTabLayout()) { - updateFieldDisplayArea(initW); - updateTabDisplayArea(selectedTab, initW, initH); - } + if (selectedTab != null && Forge.isHorizontalTabLayout()) + updateTabLayout(initW, initH); } } From 1105b3fcbd9710e3a733452079b2b0c7d44d2365 Mon Sep 17 00:00:00 2001 From: Renato Filipe Vidal Santos <45150760+dracontes@users.noreply.github.com> Date: Fri, 19 Sep 2025 05:40:42 +0100 Subject: [PATCH 008/230] Add files via upload --- forge-gui/res/cardsfolder/a/airbending_lesson.txt | 4 ++-- forge-gui/res/cardsfolder/b/badgermole.txt | 2 +- .../res/cardsfolder/b/behold_the_sinister_six.txt | 2 +- .../cardsfolder/b/bumi_eclectic_earthbender.txt | 2 +- forge-gui/res/cardsfolder/c/chlorophant.txt | 2 +- .../res/cardsfolder/d/dai_li_indoctrination.txt | 2 +- .../res/cardsfolder/e/earth_village_ruffians.txt | 2 +- .../res/cardsfolder/e/earthbending_student.txt | 2 +- forge-gui/res/cardsfolder/e/electros_bolt.txt | 2 +- forge-gui/res/cardsfolder/f/fire_lord_zuko.txt | 2 +- .../res/cardsfolder/f/fire_nation_attacks.txt | 2 +- forge-gui/res/cardsfolder/f/fire_sages.txt | 2 +- forge-gui/res/cardsfolder/h/haru_hidden_talent.txt | 2 +- .../res/cardsfolder/i/inner_demons_gangsters.txt | 2 +- .../res/cardsfolder/n/name_sticker_goblin.txt | 2 +- .../p/peter_parker_amazing_spider_man.txt | 3 +-- .../res/cardsfolder/r/rebellious_captives.txt | 2 +- .../res/cardsfolder/r/rough_rhino_cavalry.txt | 2 +- .../res/cardsfolder/t/toph_the_blind_bandit.txt | 2 +- .../cardsfolder/t/toph_the_first_metalbender.txt | 2 +- forge-gui/res/cardsfolder/v/vindictive_warden.txt | 2 +- .../tokenscripts/g_1_1_forest_dryad_squirrel.txt | 2 +- .../tokenscripts/r_2_2_soldier_firebending_1.txt | 14 +++++++------- 23 files changed, 30 insertions(+), 31 deletions(-) diff --git a/forge-gui/res/cardsfolder/a/airbending_lesson.txt b/forge-gui/res/cardsfolder/a/airbending_lesson.txt index 3e422f67f9a..9f904f830d1 100644 --- a/forge-gui/res/cardsfolder/a/airbending_lesson.txt +++ b/forge-gui/res/cardsfolder/a/airbending_lesson.txt @@ -1,6 +1,6 @@ Name:Airbending Lesson ManaCost:2 W Types:Instant Lesson -A:SP$ Airbend | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | SubAbility$ DBDraw | SpellDescription$ Airbend target nonland permanent. (Exile it. While it’s exiled, its owner may cast it for {2} rather than its mana cost.) +A:SP$ Airbend | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | SubAbility$ DBDraw | SpellDescription$ Airbend target nonland permanent. (Exile it. While it's exiled, its owner may cast it for {2} rather than its mana cost.) SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SpellDescription$ Draw a card. -Oracle:Airbend target nonland permanent. (Exile it. While it’s exiled, its owner may cast it for {2} rather than its mana cost.)\nDraw a card. +Oracle:Airbend target nonland permanent. (Exile it. While it's exiled, its owner may cast it for {2} rather than its mana cost.)\nDraw a card. diff --git a/forge-gui/res/cardsfolder/b/badgermole.txt b/forge-gui/res/cardsfolder/b/badgermole.txt index 3ef8bff4dee..9e24582a0b9 100644 --- a/forge-gui/res/cardsfolder/b/badgermole.txt +++ b/forge-gui/res/cardsfolder/b/badgermole.txt @@ -6,4 +6,4 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S SVar:TrigEarthbend:DB$ Earthbend | Num$ 2 S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddKeyword$ Trample | Description$ Creatures you control with +1/+1 counters on them have trample. DeckHas:Ability$Counters -Oracle:When this creature enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)\nCreatures you control with +1/+1 counters on them have trample. \ No newline at end of file +Oracle:When this creature enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)\nCreatures you control with +1/+1 counters on them have trample. diff --git a/forge-gui/res/cardsfolder/b/behold_the_sinister_six.txt b/forge-gui/res/cardsfolder/b/behold_the_sinister_six.txt index 57ae6b8e815..b014a9c2f84 100644 --- a/forge-gui/res/cardsfolder/b/behold_the_sinister_six.txt +++ b/forge-gui/res/cardsfolder/b/behold_the_sinister_six.txt @@ -3,4 +3,4 @@ ManaCost:6 B Types:Sorcery A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose up to six target creature cards with different names in your graveyard | ValidTgts$ Creature.YouOwn | TargetsWithDifferentNames$ True | TargetMin$ 0 | TargetMax$ 6 | SpellDescription$ Return up to six target creature cards with different names from your graveyard to the battlefield. DeckHas:Ability$Graveyard -Oracle:Return up to six target creature cards with different names from your graveyard to the battlefield. \ No newline at end of file +Oracle:Return up to six target creature cards with different names from your graveyard to the battlefield. diff --git a/forge-gui/res/cardsfolder/b/bumi_eclectic_earthbender.txt b/forge-gui/res/cardsfolder/b/bumi_eclectic_earthbender.txt index 8559b76944e..99d21634e24 100644 --- a/forge-gui/res/cardsfolder/b/bumi_eclectic_earthbender.txt +++ b/forge-gui/res/cardsfolder/b/bumi_eclectic_earthbender.txt @@ -7,4 +7,4 @@ SVar:TrigEarthbend:DB$ Earthbend | Num$ 1 T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPutCounterAll | TriggerDescription$ Whenever NICKNAME attacks, put two +1/+1 counters on each land creature you control. SVar:TrigPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.Land+YouCtrl | CounterType$ P1P1 | CounterNum$ 2 DeckHas:Ability$Counters -Oracle:When Bumi enters, earthbend 1. (Target land you control becomes a 0/0 creature with haste that's still a land. Put a +1/+1 counter on it. When it dies or is exiled, return it to the battlefield tapped.)\nWhenever Bumi attacks, put two +1/+1 counters on each land creature you control. \ No newline at end of file +Oracle:When Bumi enters, earthbend 1. (Target land you control becomes a 0/0 creature with haste that's still a land. Put a +1/+1 counter on it. When it dies or is exiled, return it to the battlefield tapped.)\nWhenever Bumi attacks, put two +1/+1 counters on each land creature you control. diff --git a/forge-gui/res/cardsfolder/c/chlorophant.txt b/forge-gui/res/cardsfolder/c/chlorophant.txt index e63ccf172c3..ce01a6059f5 100644 --- a/forge-gui/res/cardsfolder/c/chlorophant.txt +++ b/forge-gui/res/cardsfolder/c/chlorophant.txt @@ -6,4 +6,4 @@ T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | O S:Mode$ Continuous | Affected$ Card.Self | AddTrigger$ TrigCounter | Condition$ Threshold | Description$ Threshold — As long as there are seven or more cards in your graveyard, CARDNAME has "At the beginning of your upkeep, you may put another +1/+1 counter on CARDNAME." SVar:TrigCounter:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ GimmeSome | TriggerDescription$ At the beginning of your upkeep, you may put another +1/+1 counter on CARDNAME. SVar:GimmeSome:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 -Oracle:At the beginning of your upkeep, you may put a +1/+1 counter on Chlorophant.\nThreshold — As long as there are seven or more cards in your graveyard,, Chlorophant has "At the beginning of your upkeep, you may put another +1/+1 counter on Chlorophant." +Oracle:At the beginning of your upkeep, you may put a +1/+1 counter on Chlorophant.\nThreshold — As long as there are seven or more cards in your graveyard, Chlorophant has "At the beginning of your upkeep, you may put another +1/+1 counter on Chlorophant." diff --git a/forge-gui/res/cardsfolder/d/dai_li_indoctrination.txt b/forge-gui/res/cardsfolder/d/dai_li_indoctrination.txt index dadf19be943..142cb3da795 100644 --- a/forge-gui/res/cardsfolder/d/dai_li_indoctrination.txt +++ b/forge-gui/res/cardsfolder/d/dai_li_indoctrination.txt @@ -5,4 +5,4 @@ A:SP$ Charm | Choices$ DBDiscard,DBEarthbend SVar:DBDiscard:DB$ Discard | ValidTgts$ Opponent | Mode$ RevealYouChoose | DiscardValid$ Permanent.nonLand | NumCards$ 1 | SpellDescription$ Target opponent reveals their hand. You choose a nonland permanent card from it. That player discards that card. SVar:DBEarthbend:DB$ Earthbend | Num$ 2 | SpellDescription$ Earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) DeckHas:Ability$Discard|Counters -Oracle:Choose one —\n• Target opponent reveals their hand. You choose a nonland permanent card from it. That player discards that card.\n• Earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) \ No newline at end of file +Oracle:Choose one —\n• Target opponent reveals their hand. You choose a nonland permanent card from it. That player discards that card.\n• Earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) diff --git a/forge-gui/res/cardsfolder/e/earth_village_ruffians.txt b/forge-gui/res/cardsfolder/e/earth_village_ruffians.txt index 57a170376c6..5a62a08b27a 100644 --- a/forge-gui/res/cardsfolder/e/earth_village_ruffians.txt +++ b/forge-gui/res/cardsfolder/e/earth_village_ruffians.txt @@ -5,4 +5,4 @@ PT:3/1 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigEarthbend | TriggerDescription$ When this creature dies, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) SVar:TrigEarthbend:DB$ Earthbend | Num$ 2 DeckHas:Ability$Counters -Oracle:When this creature dies, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) \ No newline at end of file +Oracle:When this creature dies, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) diff --git a/forge-gui/res/cardsfolder/e/earthbending_student.txt b/forge-gui/res/cardsfolder/e/earthbending_student.txt index fa0766dd8d9..509f0de1705 100644 --- a/forge-gui/res/cardsfolder/e/earthbending_student.txt +++ b/forge-gui/res/cardsfolder/e/earthbending_student.txt @@ -6,4 +6,4 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S SVar:TrigEarthbend:DB$ Earthbend | Num$ 2 S:Mode$ Continuous | Affected$ Creature.Land+YouCtrl | AddKeyword$ Vigilance | Description$ Land creatures you control have vigilance. (Attacking doesn't cause them to tap.) DeckHas:Ability$Counters -Oracle:When this creature enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)\nLand creatures you control have vigilance. (Attacking doesn't cause them to tap.) \ No newline at end of file +Oracle:When this creature enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)\nLand creatures you control have vigilance. (Attacking doesn't cause them to tap.) diff --git a/forge-gui/res/cardsfolder/e/electros_bolt.txt b/forge-gui/res/cardsfolder/e/electros_bolt.txt index fbdba1ff4af..88186bc7674 100644 --- a/forge-gui/res/cardsfolder/e/electros_bolt.txt +++ b/forge-gui/res/cardsfolder/e/electros_bolt.txt @@ -3,5 +3,5 @@ ManaCost:2 R Types:Sorcery A:SP$ DealDamage | ValidTgts$ Creature | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to target creature. K:Mayhem:1 R -Oracle:Electro’s Bolt deals 4 damage to target creature.\nMayhem {1}{R} (You may cast this card from your graveyard for {1}{R} if you discarded it this turn. Timing rules still apply.) +Oracle:Electro's Bolt deals 4 damage to target creature.\nMayhem {1}{R} (You may cast this card from your graveyard for {1}{R} if you discarded it this turn. Timing rules still apply.) diff --git a/forge-gui/res/cardsfolder/f/fire_lord_zuko.txt b/forge-gui/res/cardsfolder/f/fire_lord_zuko.txt index 2bb84bf5350..a4dee053bb0 100644 --- a/forge-gui/res/cardsfolder/f/fire_lord_zuko.txt +++ b/forge-gui/res/cardsfolder/f/fire_lord_zuko.txt @@ -7,4 +7,4 @@ T:Mode$ SpellCast | ValidCard$ Card.wasCastFromExile | ValidActivatingPlayer$ Yo T:Mode$ ChangesZone | Origin$ Exile | Destination$ Battlefield | ValidCard$ Permanent.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounterAll | Secondary$ True | TriggerDescription$ Whenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control. SVar:TrigPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 SVar:X:Count$CardPower -Oracle:Firebending X, where X is Fire Lord Zuko's power. (Whenever this creature attacks, add X {R}. This mana lasts until end of combat.)\nWhenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control. \ No newline at end of file +Oracle:Firebending X, where X is Fire Lord Zuko's power. (Whenever this creature attacks, add X {R}. This mana lasts until end of combat.)\nWhenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control. diff --git a/forge-gui/res/cardsfolder/f/fire_nation_attacks.txt b/forge-gui/res/cardsfolder/f/fire_nation_attacks.txt index ccc85675cee..9d3da9e3b14 100644 --- a/forge-gui/res/cardsfolder/f/fire_nation_attacks.txt +++ b/forge-gui/res/cardsfolder/f/fire_nation_attacks.txt @@ -3,4 +3,4 @@ ManaCost:4 R Types:Instant A:SP$ Token | TokenAmount$ 2 | TokenScript$ r_2_2_soldier_firebending_1 | TokenOwner$ You | SpellDescription$ Create two 2/2 red Soldier creature tokens with firebending 1. (Whenever a creature with firebending 1 attacks, add {R}. This mana lasts until end of combat.) K:Flashback:8 R -Oracle:Create two 2/2 red Soldier creature tokens with firebending 1. (Whenever a creature with firebending 1 attacks, add {R}. This mana lasts until end of combat.)\nFlashback {8}{R} (You may may cast this card from your graveyard for its flashback cost. Then exile it.) \ No newline at end of file +Oracle:Create two 2/2 red Soldier creature tokens with firebending 1. (Whenever a creature with firebending 1 attacks, add {R}. This mana lasts until end of combat.)\nFlashback {8}{R} (You may may cast this card from your graveyard for its flashback cost. Then exile it.) diff --git a/forge-gui/res/cardsfolder/f/fire_sages.txt b/forge-gui/res/cardsfolder/f/fire_sages.txt index 6d5863da493..ea44ae2df50 100644 --- a/forge-gui/res/cardsfolder/f/fire_sages.txt +++ b/forge-gui/res/cardsfolder/f/fire_sages.txt @@ -4,4 +4,4 @@ Types:Creature Human Cleric PT:2/2 K:Firebending:1 A:AB$ PutCounter | Cost$ 1 R R | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on this creature. -Oracle:Firebending 1 (Whenever this creature attacks, add {R}. This mana lasts until end of combat.)\n{1}{R}{R}: Put a +1/+1 counter on this creature. \ No newline at end of file +Oracle:Firebending 1 (Whenever this creature attacks, add {R}. This mana lasts until end of combat.)\n{1}{R}{R}: Put a +1/+1 counter on this creature. diff --git a/forge-gui/res/cardsfolder/h/haru_hidden_talent.txt b/forge-gui/res/cardsfolder/h/haru_hidden_talent.txt index 4519ac3ce57..7ad83ae1778 100644 --- a/forge-gui/res/cardsfolder/h/haru_hidden_talent.txt +++ b/forge-gui/res/cardsfolder/h/haru_hidden_talent.txt @@ -6,4 +6,4 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Ally.O SVar:TrigEarthbend:DB$ Earthbend | Num$ 1 DeckHas:Ability$Counters DeckNeeds:Type$Ally -Oracle:Whenever another Ally you control enters, earthbend 1. (Target land you control becomes a 0/0 creature with haste that's still a land. Put a +1/+1 counter on it. When it dies or is exiled, return it to the battlefield tapped.) \ No newline at end of file +Oracle:Whenever another Ally you control enters, earthbend 1. (Target land you control becomes a 0/0 creature with haste that's still a land. Put a +1/+1 counter on it. When it dies or is exiled, return it to the battlefield tapped.) diff --git a/forge-gui/res/cardsfolder/i/inner_demons_gangsters.txt b/forge-gui/res/cardsfolder/i/inner_demons_gangsters.txt index 65adf772af2..1ba0bccaea5 100644 --- a/forge-gui/res/cardsfolder/i/inner_demons_gangsters.txt +++ b/forge-gui/res/cardsfolder/i/inner_demons_gangsters.txt @@ -2,5 +2,5 @@ Name:Inner Demons Gangsters ManaCost:3 B Types:Creature Human Rogue Villain PT:3/4 -A:AB$ Pump | Cost$ Discard<1/Card> | Defined$ Self | NumAtt$ +1 | KW$ Menace | SorcerySpeed$ True | SpellDescription$ This creature gets +1/+0 and gains menace until end of turn. Activate only as a sorcery. (It can't be blocked except by two or more creatures.) +A:AB$ Pump | Cost$ Discard<1/Card> | Defined$ Self | NumAtt$ +1 | KW$ Menace | SorcerySpeed$ True | SpellDescription$ This creature gets +1/+0 and gains menace until end of turn. Activate only as a sorcery. (It can't be blocked except by two or more creatures.) Oracle:Discard a card: This creature gets +1/+0 and gains menace until end of turn. Activate only as a sorcery. (It can't be blocked except by two or more creatures.) diff --git a/forge-gui/res/cardsfolder/n/name_sticker_goblin.txt b/forge-gui/res/cardsfolder/n/name_sticker_goblin.txt index 8c30691d0c9..cf0d2543e4d 100644 --- a/forge-gui/res/cardsfolder/n/name_sticker_goblin.txt +++ b/forge-gui/res/cardsfolder/n/name_sticker_goblin.txt @@ -7,4 +7,4 @@ SVar:TrigRollDice:DB$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-6:DBMana1,7-1 SVar:DBMana1:DB$ Mana | Produced$ R | Amount$ 4 | SpellDescription$ 1-6 VERT Add {R}{R}{R}{R}. SVar:DBMana2:DB$ Mana | Produced$ R | Amount$ 5 | SpellDescription$ 7-14 VERT Add {R}{R}{R}{R}{R}. SVar:DBMana3:DB$ Mana | Produced$ R | Amount$ 6 | SpellDescription$ 15-20 VERT Add {R}{R}{R}{R}{R}{R}. -Oracle:When this creature enters from anywhere other than a graveyard or exile, if it’s on the battlefield and you control 9 or fewer creatures named “Name Sticker” Goblin, roll a 20-sided die.\n1-6 | Add {R}{R}{R}{R}.\n7-14 | Add {R}{R}{R}{R}{R}.\n15-20 | Add {R}{R}{R}{R}{R}{R}. +Oracle:When this creature enters from anywhere other than a graveyard or exile, if it's on the battlefield and you control 9 or fewer creatures named "Name Sticker" Goblin, roll a 20-sided die.\n1-6 | Add {R}{R}{R}{R}.\n7-14 | Add {R}{R}{R}{R}{R}.\n15-20 | Add {R}{R}{R}{R}{R}{R}. diff --git a/forge-gui/res/cardsfolder/p/peter_parker_amazing_spider_man.txt b/forge-gui/res/cardsfolder/p/peter_parker_amazing_spider_man.txt index baee9c1c6d9..7d3b1cdfc9e 100644 --- a/forge-gui/res/cardsfolder/p/peter_parker_amazing_spider_man.txt +++ b/forge-gui/res/cardsfolder/p/peter_parker_amazing_spider_man.txt @@ -17,5 +17,4 @@ PT:4/4 K:Vigilance K:Reach S:Mode$ Continuous | Affected$ Legendary.YouCtrl+wasCast+nonColorless | AffectedZone$ Stack | AddKeyword$ Web-slinging:G W U:Spell.Legendary+nonColorless | Description$ Each legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}. (You may cast a spell for its web-slinging cost if you also return a tapped creature you control to its owner's hand.) -Oracle:Vigilance, reach\nEach legendary spell you cast that’s one or more colors has web-slinging {G}{W}{U}. (You may cast a spell for its web-slinging cost if you also return a tapped creature you control to its owner's hand.) - +Oracle:Vigilance, reach\nEach legendary spell you cast that's one or more colors has web-slinging {G}{W}{U}. (You may cast a spell for its web-slinging cost if you also return a tapped creature you control to its owner's hand.) diff --git a/forge-gui/res/cardsfolder/r/rebellious_captives.txt b/forge-gui/res/cardsfolder/r/rebellious_captives.txt index 4c62071a175..7918b91107f 100644 --- a/forge-gui/res/cardsfolder/r/rebellious_captives.txt +++ b/forge-gui/res/cardsfolder/r/rebellious_captives.txt @@ -5,4 +5,4 @@ PT:2/2 A:AB$ PutCounter | Cost$ 6 | Defined$ Self | CounterType$ P1P1 | CounterNum$ 2 | Exhaust$ True | SubAbility$ DBEarthbend | SpellDescription$ Put two +1/+1 counters on this creature, then earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped. Activate each exhaust ability only once.) SVar:DBEarthbend:DB$ Earthbend | Num$ 2 DeckHas:Ability$Counters -Oracle:Exhaust — {6}: Put two +1/+1 counters on this creature, then earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped. Activate each exhaust ability only once.) \ No newline at end of file +Oracle:Exhaust — {6}: Put two +1/+1 counters on this creature, then earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped. Activate each exhaust ability only once.) diff --git a/forge-gui/res/cardsfolder/r/rough_rhino_cavalry.txt b/forge-gui/res/cardsfolder/r/rough_rhino_cavalry.txt index eb523510f0b..47c43cb9c77 100644 --- a/forge-gui/res/cardsfolder/r/rough_rhino_cavalry.txt +++ b/forge-gui/res/cardsfolder/r/rough_rhino_cavalry.txt @@ -6,4 +6,4 @@ K:Firebending:2 A:AB$ PutCounter | Cost$ 8 | Defined$ Self | CounterType$ P1P1 | CounterNum$ 2 | Exhaust$ True | SubAbility$ DBPump | SpellDescription$ Put two +1/+1 counters on this creature. It gains trample until end of turn. (Activate each exhaust ability only once.) SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Trample DeckHas:Ability$Counters -Oracle:Firebending 2 (Whenever this creature attacks, add {R}{R}. This mana lasts until end of combat.)\nExhaust — {8}: Put two +1/+1 counters on this creature. It gains trample until end of turn. (Activate each exhaust ability only once.) \ No newline at end of file +Oracle:Firebending 2 (Whenever this creature attacks, add {R}{R}. This mana lasts until end of combat.)\nExhaust — {8}: Put two +1/+1 counters on this creature. It gains trample until end of turn. (Activate each exhaust ability only once.) diff --git a/forge-gui/res/cardsfolder/t/toph_the_blind_bandit.txt b/forge-gui/res/cardsfolder/t/toph_the_blind_bandit.txt index 8e47200f006..285d343ae23 100644 --- a/forge-gui/res/cardsfolder/t/toph_the_blind_bandit.txt +++ b/forge-gui/res/cardsfolder/t/toph_the_blind_bandit.txt @@ -7,4 +7,4 @@ SVar:TrigEarthbend:DB$ Earthbend | Num$ 2 S:Mode$ Continuous | CharacteristicDefining$ True | SetPower$ X | Description$ NICKNAME's power is equal to the number of +1/+1 counters on lands you control. SVar:X:Count$Valid Land.YouCtrl$CardCounters.P1P1 DeckHas:Ability$Counters -Oracle:When Toph enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)\nToph's power is equal to the number of +1/+1 counters on lands you control. \ No newline at end of file +Oracle:When Toph enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)\nToph's power is equal to the number of +1/+1 counters on lands you control. diff --git a/forge-gui/res/cardsfolder/t/toph_the_first_metalbender.txt b/forge-gui/res/cardsfolder/t/toph_the_first_metalbender.txt index 4500e47bb67..f2a3cba7f80 100644 --- a/forge-gui/res/cardsfolder/t/toph_the_first_metalbender.txt +++ b/forge-gui/res/cardsfolder/t/toph_the_first_metalbender.txt @@ -6,4 +6,4 @@ S:Mode$ Continuous | Affected$ Artifact.!token+YouCtrl | AddType$ Land | Descrip T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigEarthbend | TriggerDescription$ At the beginning of your end step, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) SVar:TrigEarthbend:DB$ Earthbend | Num$ 2 DeckHas:Ability$Counters -Oracle:Nontoken artifacts you control are lands in addition to their other types. (They don't gain the ability to {T} for mana.)\nAt the beginning of your end step, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) \ No newline at end of file +Oracle:Nontoken artifacts you control are lands in addition to their other types. (They don't gain the ability to {T} for mana.)\nAt the beginning of your end step, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) diff --git a/forge-gui/res/cardsfolder/v/vindictive_warden.txt b/forge-gui/res/cardsfolder/v/vindictive_warden.txt index 01bac8a17b1..423b2a75186 100644 --- a/forge-gui/res/cardsfolder/v/vindictive_warden.txt +++ b/forge-gui/res/cardsfolder/v/vindictive_warden.txt @@ -5,4 +5,4 @@ PT:2/3 K:Menace K:Firebending:1 A:AB$ DealDamage | Cost$ 3 | Defined$ Player.Opponent | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to each opponent. -Oracle:Menace (This creature can't be blocked except by two or more creatures.)\nFirebending 1 (Whenever this creature attacks, add {R}. This mana lasts until end of combat.)\n{3}: This creature deals 1 damage to each opponent. \ No newline at end of file +Oracle:Menace (This creature can't be blocked except by two or more creatures.)\nFirebending 1 (Whenever this creature attacks, add {R}. This mana lasts until end of combat.)\n{3}: This creature deals 1 damage to each opponent. diff --git a/forge-gui/res/tokenscripts/g_1_1_forest_dryad_squirrel.txt b/forge-gui/res/tokenscripts/g_1_1_forest_dryad_squirrel.txt index 0f73be0f5ca..b2c5e0c0e6d 100644 --- a/forge-gui/res/tokenscripts/g_1_1_forest_dryad_squirrel.txt +++ b/forge-gui/res/tokenscripts/g_1_1_forest_dryad_squirrel.txt @@ -3,4 +3,4 @@ ManaCost:no cost Colors:green Types:Land Creature Forest Dryad Squirrel PT:1/1 -Oracle:({T}: Add {G}.) \ No newline at end of file +Oracle:({T}: Add {G}.) diff --git a/forge-gui/res/tokenscripts/r_2_2_soldier_firebending_1.txt b/forge-gui/res/tokenscripts/r_2_2_soldier_firebending_1.txt index cb6bdbd40dc..49f8996b9bd 100644 --- a/forge-gui/res/tokenscripts/r_2_2_soldier_firebending_1.txt +++ b/forge-gui/res/tokenscripts/r_2_2_soldier_firebending_1.txt @@ -1,7 +1,7 @@ -Name:Soldier Token -ManaCost:no cost -Colors:red -Types:Creature Soldier -PT:2/2 -K:Firebending:1 -Oracle:Firebending 1 (Whenever this token attacks, add {R}. This mana lasts until end of combat.) \ No newline at end of file +Name:Soldier Token +ManaCost:no cost +Colors:red +Types:Creature Soldier +PT:2/2 +K:Firebending:1 +Oracle:Firebending 1 (Whenever this token attacks, add {R}. This mana lasts until end of combat.) From a72df6ad16875c61328eeb2f2c500339e9fb5bd0 Mon Sep 17 00:00:00 2001 From: kevlahnota Date: Fri, 19 Sep 2025 13:19:50 +0800 Subject: [PATCH 009/230] updateRarityFilterOdds (#8746) --- .../planarconquest/ConquestPreferences.java | 3 ++- .../planarconquest/ConquestUtil.java | 3 +-- .../src/main/java/forge/model/FModel.java | 27 +++++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestPreferences.java b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestPreferences.java index ac7a8485c4a..317a2bbfffc 100644 --- a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestPreferences.java +++ b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestPreferences.java @@ -21,6 +21,7 @@ import java.io.Serializable; import forge.localinstance.properties.ForgeConstants; import forge.localinstance.properties.PreferencesStore; +import forge.model.FModel; @SuppressWarnings("serial") public class ConquestPreferences extends PreferencesStore implements Serializable { @@ -74,7 +75,7 @@ public class ConquestPreferences extends PreferencesStore odds = Maps.newEnumMap(CardRarity.class); if (prefs.getPrefBoolean(CQPref.AETHER_USE_DEFAULT_RARITY_ODDS)) { diff --git a/forge-gui/src/main/java/forge/model/FModel.java b/forge-gui/src/main/java/forge/model/FModel.java index e8ae20c95d4..b33b448ae1d 100644 --- a/forge-gui/src/main/java/forge/model/FModel.java +++ b/forge-gui/src/main/java/forge/model/FModel.java @@ -220,17 +220,12 @@ public final class FModel { for (final CardBlock b : blocks) { magicDb.getBlockLands().add(b.getLandSet().getCode()); } - questPreferences = new QuestPreferences(); - conquestPreferences = new ConquestPreferences(); - netPreferences = new ForgeNetPreferences(); fantasyBlocks = new StorageBase<>("Custom blocks", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "fantasyblocks.txt", magicDb.getEditions())); themedChaosDrafts = new StorageBase<>("Themed Chaos Drafts", new ThemedChaosDraft.Reader(ForgeConstants.BLOCK_DATA_DIR + "chaosdraftthemes.txt")); planes = new StorageBase<>("Conquest planes", new ConquestPlane.Reader(ForgeConstants.CONQUEST_PLANES_DIR + "planes.txt")); Map standardWorlds = new QuestWorld.Reader(ForgeConstants.QUEST_WORLD_DIR + "worlds.txt").readAll(); Map customWorlds = new QuestWorld.Reader(ForgeConstants.USER_QUEST_WORLD_DIR + "customworlds.txt").readAll(); - for (QuestWorld world:customWorlds.values()){ - world.setCustom(true); - } + customWorlds.values().forEach(world -> world.setCustom(true)); standardWorlds.putAll(customWorlds); worlds = new StorageBase<>("Quest worlds", null, standardWorlds); @@ -240,14 +235,9 @@ public final class FModel { FThreads.invokeInEdtLater(() -> progressBar.setDescription(Localizer.getInstance().getMessage("splash.loading.decks"))); } - decks = new CardCollections(); - quest = new QuestController(); - conquest = new ConquestController(); - CardPreferences.load(); DeckPreferences.load(); ItemManagerConfig.load(); - ConquestUtil.updateRarityFilterOdds(); achievements = Maps.newHashMap(); achievements.put(GameType.Constructed, new ConstructedAchievements()); @@ -300,10 +290,14 @@ public final class FModel { } public static QuestController getQuest() { + if (quest == null) + quest = new QuestController(); return quest; } public static ConquestController getConquest() { + if (conquest == null) + conquest = new ConquestController(); return conquest; } @@ -435,6 +429,8 @@ public final class FModel { return preferences; } public static ForgeNetPreferences getNetPreferences() { + if (netPreferences == null) + netPreferences = new ForgeNetPreferences(); return netPreferences; } @@ -466,10 +462,17 @@ public final class FModel { } public static QuestPreferences getQuestPreferences() { + if (questPreferences == null) + questPreferences = new QuestPreferences(); return questPreferences; } public static ConquestPreferences getConquestPreferences() { + if (conquestPreferences == null) { + conquestPreferences = new ConquestPreferences(); + // initialize on first call... + ConquestUtil.updateRarityFilterOdds(conquestPreferences); + } return conquestPreferences; } @@ -489,6 +492,8 @@ public final class FModel { } public static CardCollections getDecks() { + if (decks == null) + decks = new CardCollections(); return decks; } From 9bc6ab747d9e11361c120cb98f007dcb87a31964 Mon Sep 17 00:00:00 2001 From: Chris H Date: Fri, 19 Sep 2025 09:11:15 -0400 Subject: [PATCH 010/230] Update Venom, Eddie Brock's power and toughness --- forge-gui/res/cardsfolder/v/venom_eddie_brock.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/v/venom_eddie_brock.txt b/forge-gui/res/cardsfolder/v/venom_eddie_brock.txt index d9ddc26fae9..cef924879ed 100644 --- a/forge-gui/res/cardsfolder/v/venom_eddie_brock.txt +++ b/forge-gui/res/cardsfolder/v/venom_eddie_brock.txt @@ -1,7 +1,7 @@ Name:Venom, Eddie Brock ManaCost:4 B B Types:Legendary Creature Symbiote Villain -PT:3/3 +PT:6/4 K:Menace T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.Other | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever another creature dies, put a +1/+1 counter on NICKNAME. If that creature was a Villain, draw a card. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw From 6ec6d64cb2dd58edd129c5a8be9e9c7762980e53 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Fri, 19 Sep 2025 22:06:38 +0800 Subject: [PATCH 011/230] update FModel, add Icon Overlay for Horizontal tabs --- .../screens/match/views/VPlayerPanel.java | 23 +++ .../src/main/java/forge/model/FModel.java | 188 +++++++++--------- 2 files changed, 117 insertions(+), 94 deletions(-) diff --git a/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java b/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java index d6362537b99..477ad165d8c 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java @@ -498,6 +498,24 @@ public class VPlayerPanel extends FContainer { } } + @Override + protected void drawOverlay(Graphics g) { + if (Forge.isHorizontalTabLayout()) { + InfoTab infoTab = selectedTab; + if (infoTab != null) { + VDisplayArea selectedDisplayArea = infoTab.getDisplayArea(); + if (selectedDisplayArea != null && selectedDisplayArea.getCount() > 0) { + float scale = avatarWidth / 2f; + float x = selectedDisplayArea.getLeft(); + float y = selectedDisplayArea.getBottom() - scale; + g.fillRect(getAltDisplayAreaBackColor(), x, y, scale, scale); + infoTab.icon.draw(g, x, y, scale, scale); + } + } + } + super.drawOverlay(g); + } + @Override public void drawBackground(Graphics g) { float y; @@ -1069,6 +1087,11 @@ public class VPlayerPanel extends FContainer { @Override public float getIdealWidth(float pref) { + if (getDisplayArea() instanceof VCardDisplayArea vCardDisplayArea) { + float cardWidth = vCardDisplayArea.getCardWidth(vCardDisplayArea.getHeight()); + float size = vCardDisplayArea.getCount(); + return Math.min(cardWidth * size, pref); + } return pref; } diff --git a/forge-gui/src/main/java/forge/model/FModel.java b/forge-gui/src/main/java/forge/model/FModel.java index b33b448ae1d..6232a69d3d2 100644 --- a/forge-gui/src/main/java/forge/model/FModel.java +++ b/forge-gui/src/main/java/forge/model/FModel.java @@ -99,19 +99,19 @@ public final class FModel { private static IStorage worlds; private static GameFormat.Collection formats; private static ItemPool uniqueCardsNoAlt, allCardsNoAlt, planechaseCards, archenemyCards, - brawlCommander, oathbreakerCommander, tinyLeadersCommander, commanderPool, - avatarPool, conspiracyPool, dungeonPool, attractionPool, contraptionPool; + brawlCommander, oathbreakerCommander, tinyLeadersCommander, commanderPool, + avatarPool, conspiracyPool, dungeonPool, attractionPool, contraptionPool; public static void initialize(final IProgressBar progressBar, Function adjustPrefs) { initialize(progressBar, adjustPrefs, false); } public static void initialize(final IProgressBar progressBar, Function adjustPrefs, boolean isSimTest) { ImageKeys.initializeDirs( - ForgeConstants.CACHE_CARD_PICS_DIR, ForgeConstants.CACHE_CARD_PICS_SUBDIR, - ForgeConstants.CACHE_TOKEN_PICS_DIR, ForgeConstants.CACHE_ICON_PICS_DIR, - ForgeConstants.CACHE_BOOSTER_PICS_DIR, ForgeConstants.CACHE_FATPACK_PICS_DIR, - ForgeConstants.CACHE_BOOSTERBOX_PICS_DIR, ForgeConstants.CACHE_PRECON_PICS_DIR, - ForgeConstants.CACHE_TOURNAMENTPACK_PICS_DIR); + ForgeConstants.CACHE_CARD_PICS_DIR, ForgeConstants.CACHE_CARD_PICS_SUBDIR, + ForgeConstants.CACHE_TOKEN_PICS_DIR, ForgeConstants.CACHE_ICON_PICS_DIR, + ForgeConstants.CACHE_BOOSTER_PICS_DIR, ForgeConstants.CACHE_FATPACK_PICS_DIR, + ForgeConstants.CACHE_BOOSTERBOX_PICS_DIR, ForgeConstants.CACHE_PRECON_PICS_DIR, + ForgeConstants.CACHE_TOURNAMENTPACK_PICS_DIR); // Instantiate preferences: quest and regular // Preferences are initialized first so that the splash screen can be translated. @@ -198,15 +198,12 @@ public final class FModel { ForgePreferences.DEV_MODE = preferences.getPrefBoolean(FPref.DEV_MODE_ENABLED); ForgePreferences.UPLOAD_DRAFT = ForgePreferences.NET_CONN; - formats = new GameFormat.Collection(new GameFormat.Reader( new File(ForgeConstants.FORMATS_DATA_DIR), - new File(ForgeConstants.USER_FORMATS_DIR), preferences.getPrefBoolean(FPref.LOAD_ARCHIVED_FORMATS))); - - magicDb.setStandardPredicate(formats.getStandard().getFilterRules()); - magicDb.setPioneerPredicate(formats.getPioneer().getFilterRules()); - magicDb.setModernPredicate(formats.getModern().getFilterRules()); - magicDb.setCommanderPredicate(formats.get("Commander").getFilterRules()); - magicDb.setOathbreakerPredicate(formats.get("Oathbreaker").getFilterRules()); - magicDb.setBrawlPredicate(formats.get("Brawl").getFilterRules()); + magicDb.setStandardPredicate(getFormats().getStandard().getFilterRules()); + magicDb.setPioneerPredicate(getFormats().getPioneer().getFilterRules()); + magicDb.setModernPredicate(getFormats().getModern().getFilterRules()); + magicDb.setCommanderPredicate(getFormats().get("Commander").getFilterRules()); + magicDb.setOathbreakerPredicate(getFormats().get("Oathbreaker").getFilterRules()); + magicDb.setBrawlPredicate(getFormats().get("Brawl").getFilterRules()); magicDb.setFilteredHandsEnabled(preferences.getPrefBoolean(FPref.FILTERED_HANDS)); try { @@ -215,20 +212,6 @@ public final class FModel { magicDb.setMulliganRule(MulliganDefs.MulliganRule.London); } - blocks = new StorageBase<>("Block definitions", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "blocks.txt", magicDb.getEditions())); - // SetblockLands - for (final CardBlock b : blocks) { - magicDb.getBlockLands().add(b.getLandSet().getCode()); - } - fantasyBlocks = new StorageBase<>("Custom blocks", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "fantasyblocks.txt", magicDb.getEditions())); - themedChaosDrafts = new StorageBase<>("Themed Chaos Drafts", new ThemedChaosDraft.Reader(ForgeConstants.BLOCK_DATA_DIR + "chaosdraftthemes.txt")); - planes = new StorageBase<>("Conquest planes", new ConquestPlane.Reader(ForgeConstants.CONQUEST_PLANES_DIR + "planes.txt")); - Map standardWorlds = new QuestWorld.Reader(ForgeConstants.QUEST_WORLD_DIR + "worlds.txt").readAll(); - Map customWorlds = new QuestWorld.Reader(ForgeConstants.USER_QUEST_WORLD_DIR + "customworlds.txt").readAll(); - customWorlds.values().forEach(world -> world.setCustom(true)); - standardWorlds.putAll(customWorlds); - worlds = new StorageBase<>("Quest worlds", null, standardWorlds); - Spell.setPerformanceMode(preferences.getPrefBoolean(FPref.PERFORMANCE_MODE)); if (progressBar != null) { @@ -239,15 +222,6 @@ public final class FModel { DeckPreferences.load(); ItemManagerConfig.load(); - achievements = Maps.newHashMap(); - achievements.put(GameType.Constructed, new ConstructedAchievements()); - achievements.put(GameType.Draft, new DraftAchievements()); - achievements.put(GameType.Sealed, new SealedAchievements()); - achievements.put(GameType.Quest, new QuestAchievements()); - achievements.put(GameType.PlanarConquest, new PlanarConquestAchievements()); - achievements.put(GameType.Puzzle, new PuzzleAchievements()); - achievements.put(GameType.Adventure, new AdventureAchievements()); - // Preload AI profiles AiProfileUtil.loadAllProfiles(ForgeConstants.AI_PROFILE_DIR); AiProfileUtil.setAiSideboardingMode(AiProfileUtil.AISideboardingMode.normalizedValueOf(FModel.getPreferences().getPref(FPref.MATCH_AI_SIDEBOARDING_MODE))); @@ -265,25 +239,26 @@ public final class FModel { return; // Don't preload ItemPool on mobile port with less than 5GB RAM // Common ItemPool to preload - allCardsNoAlt = getAllCardsNoAlt(); - archenemyCards = getArchenemyCards(); - planechaseCards = getPlanechaseCards(); - attractionPool = getAttractionPool(); - contraptionPool = getContraptionPool(); + getAllCardsNoAlt(); + getArchenemyCards(); + getPlanechaseCards(); + getAttractionPool(); + getContraptionPool(); if (GuiBase.getInterface().isLibgdxPort()) { // Preload mobile Itempool - uniqueCardsNoAlt = getUniqueCardsNoAlt(); + getUniqueCardsNoAlt(); } else { // Preload Desktop Itempool - commanderPool = getCommanderPool(); - brawlCommander = getBrawlCommander(); - tinyLeadersCommander = getTinyLeadersCommander(); - avatarPool = getAvatarPool(); - conspiracyPool = getConspiracyPool(); + getCommanderPool(); + getOathbreakerCommander(); + getBrawlCommander(); + getTinyLeadersCommander(); + getAvatarPool(); + getConspiracyPool(); } } - private static boolean deckGenMatrixLoaded=false; + private static boolean deckGenMatrixLoaded = false; public static boolean isdeckGenMatrixLoaded(){ return deckGenMatrixLoaded; @@ -303,90 +278,89 @@ public final class FModel { public static ItemPool getUniqueCardsNoAlt() { if (uniqueCardsNoAlt == null) - return ItemPool.createFrom(getMagicDb().getCommonCards().getUniqueCardsNoAlt(), PaperCard.class); + uniqueCardsNoAlt = ItemPool.createFrom(getMagicDb().getCommonCards().getUniqueCardsNoAlt(), PaperCard.class); return uniqueCardsNoAlt; } public static ItemPool getAllCardsNoAlt() { if (allCardsNoAlt == null) - return ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(), PaperCard.class); + allCardsNoAlt = ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(), PaperCard.class); return allCardsNoAlt; } public static ItemPool getArchenemyCards() { if (archenemyCards == null) - return ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_SCHEME)), PaperCard.class); + archenemyCards = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_SCHEME)), PaperCard.class); return archenemyCards; } public static ItemPool getPlanechaseCards() { if (planechaseCards == null) - return ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_PLANE_OR_PHENOMENON)), PaperCard.class); + planechaseCards = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_PLANE_OR_PHENOMENON)), PaperCard.class); return planechaseCards; } public static ItemPool getBrawlCommander() { if (brawlCommander == null) { - return ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt( - FModel.getFormats().get("Brawl").getFilterPrinted() - .and(PaperCardPredicates.fromRules(CardRulesPredicates.CAN_BE_BRAWL_COMMANDER)) - ), PaperCard.class); + brawlCommander = ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt( + FModel.getFormats().get("Brawl").getFilterPrinted() + .and(PaperCardPredicates.fromRules(CardRulesPredicates.CAN_BE_BRAWL_COMMANDER))), PaperCard.class); } return brawlCommander; } public static ItemPool getOathbreakerCommander() { if (oathbreakerCommander == null) - return ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(PaperCardPredicates.fromRules( - CardRulesPredicates.CAN_BE_OATHBREAKER.or(CardRulesPredicates.CAN_BE_SIGNATURE_SPELL))), PaperCard.class); + oathbreakerCommander = ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(PaperCardPredicates.fromRules( + CardRulesPredicates.CAN_BE_OATHBREAKER.or(CardRulesPredicates.CAN_BE_SIGNATURE_SPELL))), PaperCard.class); return oathbreakerCommander; } public static ItemPool getTinyLeadersCommander() { if (tinyLeadersCommander == null) - return ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(PaperCardPredicates.fromRules( - CardRulesPredicates.CAN_BE_TINY_LEADERS_COMMANDER)), PaperCard.class); + tinyLeadersCommander = ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(PaperCardPredicates.fromRules( + CardRulesPredicates.CAN_BE_TINY_LEADERS_COMMANDER)), PaperCard.class); return tinyLeadersCommander; } public static ItemPool getCommanderPool() { if (commanderPool == null) - return ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(PaperCardPredicates.CAN_BE_COMMANDER), PaperCard.class); + commanderPool = ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(PaperCardPredicates.CAN_BE_COMMANDER), PaperCard.class); return commanderPool; } public static ItemPool getAvatarPool() { if (avatarPool == null) - return ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( - CardRulesPredicates.IS_VANGUARD)), PaperCard.class); + avatarPool = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( + CardRulesPredicates.IS_VANGUARD)), PaperCard.class); return avatarPool; } public static ItemPool getConspiracyPool() { if (conspiracyPool == null) - return ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( - CardRulesPredicates.IS_CONSPIRACY)), PaperCard.class); + conspiracyPool = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( + CardRulesPredicates.IS_CONSPIRACY)), PaperCard.class); return conspiracyPool; } public static ItemPool getDungeonPool() { if (dungeonPool == null) - return ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( - CardRulesPredicates.IS_DUNGEON)), PaperCard.class); + dungeonPool = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( + CardRulesPredicates.IS_DUNGEON)), PaperCard.class); return dungeonPool; } public static ItemPool getAttractionPool() { if (attractionPool == null) - return ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( - CardRulesPredicates.IS_ATTRACTION)), PaperCard.class); + attractionPool = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( + CardRulesPredicates.IS_ATTRACTION)), PaperCard.class); return attractionPool; } public static ItemPool getContraptionPool() { - if(contraptionPool == null) - return ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( - CardRulesPredicates.IS_CONTRAPTION)), PaperCard.class); + if (contraptionPool == null) + contraptionPool = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( + CardRulesPredicates.IS_CONTRAPTION)), PaperCard.class); return contraptionPool; } @@ -435,29 +409,38 @@ public final class FModel { } public static AchievementCollection getAchievements(GameType gameType) { - switch (gameType) { // Translate gameType to appropriate type if needed - case Constructed: - case Draft: - case Sealed: - case Quest: - case PlanarConquest: - case Puzzle: - case Adventure: - break; - case AdventureEvent: - gameType = GameType.Adventure; - break; - case QuestDraft: - gameType = GameType.Quest; - break; - default: - gameType = GameType.Constructed; - break; + if (achievements == null) { + achievements = Maps.newHashMap(); + achievements.put(GameType.Constructed, new ConstructedAchievements()); + achievements.put(GameType.Draft, new DraftAchievements()); + achievements.put(GameType.Sealed, new SealedAchievements()); + achievements.put(GameType.Quest, new QuestAchievements()); + achievements.put(GameType.PlanarConquest, new PlanarConquestAchievements()); + achievements.put(GameType.Puzzle, new PuzzleAchievements()); + achievements.put(GameType.Adventure, new AdventureAchievements()); } - return achievements.get(gameType); + + // Translate gameType to appropriate type if needed + return switch (gameType) { + case Constructed, Draft, Sealed, Quest, PlanarConquest, Puzzle, Adventure -> achievements.get(gameType); + case AdventureEvent -> achievements.get(GameType.Adventure); + case QuestDraft -> achievements.get(GameType.Quest); + default -> achievements.get(GameType.Constructed); + }; } public static IStorage getBlocks() { + if (blocks == null) { + blocks = new StorageBase<>("Block definitions", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "blocks.txt", magicDb.getEditions())); + // SetblockLands + for (final CardBlock b : blocks) { + try { + magicDb.getBlockLands().add(b.getLandSet().getCode()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } return blocks; } @@ -498,22 +481,39 @@ public final class FModel { } public static IStorage getPlanes() { + if (planes == null) + planes = new StorageBase<>("Conquest planes", new ConquestPlane.Reader(ForgeConstants.CONQUEST_PLANES_DIR + "planes.txt")); return planes; } public static IStorage getWorlds() { + if (worlds == null) { + Map standardWorlds = new QuestWorld.Reader(ForgeConstants.QUEST_WORLD_DIR + "worlds.txt").readAll(); + Map customWorlds = new QuestWorld.Reader(ForgeConstants.USER_QUEST_WORLD_DIR + "customworlds.txt").readAll(); + customWorlds.values().forEach(world -> world.setCustom(true)); + standardWorlds.putAll(customWorlds); + worlds = new StorageBase<>("Quest worlds", null, standardWorlds); + } return worlds; } public static GameFormat.Collection getFormats() { + if (formats == null) { + formats = new GameFormat.Collection(new GameFormat.Reader( new File(ForgeConstants.FORMATS_DATA_DIR), + new File(ForgeConstants.USER_FORMATS_DIR), preferences.getPrefBoolean(FPref.LOAD_ARCHIVED_FORMATS))); + } return formats; } public static IStorage getFantasyBlocks() { + if (fantasyBlocks == null) + fantasyBlocks = new StorageBase<>("Custom blocks", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "fantasyblocks.txt", magicDb.getEditions())); return fantasyBlocks; } public static IStorage getThemedChaosDrafts() { + if (themedChaosDrafts == null) + themedChaosDrafts = new StorageBase<>("Themed Chaos Drafts", new ThemedChaosDraft.Reader(ForgeConstants.BLOCK_DATA_DIR + "chaosdraftthemes.txt")); return themedChaosDrafts; } From 90bd0c73d0ced11cadab1e4bd6d6cbb02f8b5ff7 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 20 Sep 2025 14:32:30 +0200 Subject: [PATCH 012/230] Transform: all DFC can transform now --- .../src/main/java/forge/card/CardRules.java | 16 +--------------- .../src/main/java/forge/item/PaperToken.java | 2 +- .../src/main/java/forge/game/GameAction.java | 4 ---- .../main/java/forge/game/GameActionUtil.java | 3 --- .../ability/effects/CopyPermanentEffect.java | 13 ++++--------- .../src/main/java/forge/game/card/Card.java | 17 ++++++++--------- .../java/forge/game/card/CardCopyService.java | 10 +--------- .../main/java/forge/game/card/CardFactory.java | 12 +++--------- 8 files changed, 18 insertions(+), 59 deletions(-) diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index 4e1b6de6b48..2a9bb30ba4b 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -168,21 +168,7 @@ public final class CardRules implements ICardCharacteristics { } public boolean isTransformable() { - if (CardSplitType.Transform == getSplitType()) { - return true; - } - if (CardSplitType.Modal != getSplitType()) { - return false; - } - for (ICardFace face : getAllFaces()) { - for (String spell : face.getAbilities()) { - if (spell.contains("AB$ SetState") && spell.contains("Mode$ Transform")) { - return true; - } - } - // TODO check keywords if needed - } - return false; + return CardSplitType.Transform == getSplitType() || CardSplitType.Modal == getSplitType(); } public ICardFace getWSpecialize() { diff --git a/forge-core/src/main/java/forge/item/PaperToken.java b/forge-core/src/main/java/forge/item/PaperToken.java index 76f94634093..f60c5befe5c 100644 --- a/forge-core/src/main/java/forge/item/PaperToken.java +++ b/forge-core/src/main/java/forge/item/PaperToken.java @@ -156,7 +156,7 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard { return false; CardSplitType cst = this.cardRules.getSplitType(); //expand this on future for other tokens that has other backsides besides transform.. - return cst == CardSplitType.Transform; + return cst == CardSplitType.Transform || cst == CardSplitType.Modal; } @Override diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index efae785c646..b759be95564 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -220,10 +220,6 @@ public class GameAction { //copied.setGamePieceType(GamePieceType.COPIED_SPELL); } - if (c.isTransformed()) { - copied.incrementTransformedTimestamp(); - } - if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) { copied.setCastSA(cause); copied.setSplitStateToPlayAbility(cause); diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index b5a8fe681e1..b7e2ec6d469 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -993,9 +993,6 @@ public final class GameActionUtil { oldCard.setBackSide(false); oldCard.setState(oldCard.getFaceupCardStateName(), true); oldCard.unanimateBestow(); - if (ability.isDisturb() || ability.hasParam("CastTransformed")) { - oldCard.undoIncrementTransformedTimestamp(); - } if (ability.hasParam("Prototype")) { oldCard.removeCloneState(oldCard.getPrototypeTimestamp()); diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java index 7345e824bb1..771909901ab 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java @@ -287,22 +287,17 @@ public class CopyPermanentEffect extends TokenEffectBase { int id = newOwner == null ? 0 : newOwner.getGame().nextCardId(); // need to create a physical card first, i need the original card faces copy = CardFactory.getCard(original.getPaperCard(), newOwner, id, host.getGame()); + + copy.setStates(CardFactory.getCloneStates(original, copy, sa)); + // force update the now set State if (original.isTransformable()) { + copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true); // 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield, // the resulting token is a transforming token that has both a front face and a back face. // The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent. // If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up. // This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect. copy.setBackSide(original.isBackSide()); - if (original.isTransformed()) { - copy.incrementTransformedTimestamp(); - } - } - - copy.setStates(CardFactory.getCloneStates(original, copy, sa)); - // force update the now set State - if (original.isTransformable()) { - copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true); } else { copy.setState(copy.getCurrentStateName(), true, true); } 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 3264e0ee5ae..246b405263b 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -257,7 +257,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr private long worldTimestamp = -1; private long bestowTimestamp = -1; - private long transformedTimestamp = 0; + private long transformedTimestamp = -1; private long prototypeTimestamp = -1; private long mutatedTimestamp = -1; private int timesMutated = 0; @@ -425,8 +425,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr public long getPrototypeTimestamp() { return prototypeTimestamp; } public long getTransformedTimestamp() { return transformedTimestamp; } - public void incrementTransformedTimestamp() { this.transformedTimestamp++; } - public void undoIncrementTransformedTimestamp() { this.transformedTimestamp--; } + public void setTransformedTimestamp(long ts) { this.transformedTimestamp = ts; } // The following methods are used to selectively update certain view components (text, // P/T, card types) in order to avoid card flickering due to aggressive full update @@ -696,7 +695,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr final Map runParams = AbilityKey.mapFromCard(this); getGame().getTriggerHandler().runTrigger(TriggerType.Transformed, runParams, false); } - incrementTransformedTimestamp(); + setTransformedTimestamp(ts); return retResult; } else if (mode.equals("Flip")) { @@ -1070,7 +1069,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr } public final boolean isDoubleFaced() { - return isTransformable() || isMeldable() || isModal(); + return isTransformable() || isMeldable(); } public final boolean isFlipCard() { @@ -1132,7 +1131,10 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr } public final boolean isTransformed() { - return getTransformedTimestamp() != 0; + if (isMeldable() || hasMergedCard()) { + return false; + } + return this.isTransformable() && isBackSide(); } public final boolean isFlipped() { @@ -7638,9 +7640,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr if (sa.isBestow()) { animateBestow(); } - if (sa.isDisturb() || sa.hasParam("CastTransformed")) { - incrementTransformedTimestamp(); - } if (sa.hasParam("Prototype") && prototypeTimestamp == -1) { long next = game.getNextTimestamp(); addCloneState(CardFactory.getCloneStates(this, this, sa), next); diff --git a/forge-game/src/main/java/forge/game/card/CardCopyService.java b/forge-game/src/main/java/forge/game/card/CardCopyService.java index 3cd81c703ce..ebf6c3a6009 100644 --- a/forge-game/src/main/java/forge/game/card/CardCopyService.java +++ b/forge-game/src/main/java/forge/game/card/CardCopyService.java @@ -131,9 +131,7 @@ public class CardCopyService { c.setState(in.getCurrentStateName(), false); c.setRules(in.getRules()); - if (in.isTransformed()) { - c.incrementTransformedTimestamp(); - } + c.setBackSide(in.isBackSide()); return c; } @@ -168,9 +166,6 @@ public class CardCopyService { // The characteristics of its front and back face are determined by the copiable values of the same face of the spell it is a copy of, as modified by any other copy effects. // If the spell it is a copy of has its back face up, the copy is created with its back face up. The token that’s put onto the battlefield as that spell resolves is a transforming token. to.setBackSide(copyFrom.isBackSide()); - if (copyFrom.isTransformed()) { - to.incrementTransformedTimestamp(); - } } else if (fromIsTransformedCard) { copyState(copyFrom, copyFrom.getCurrentStateName(), to, CardStateName.Original); } else { @@ -274,9 +269,6 @@ public class CardCopyService { } newCopy.setFlipped(copyFrom.isFlipped()); newCopy.setBackSide(copyFrom.isBackSide()); - if (copyFrom.isTransformed()) { - newCopy.incrementTransformedTimestamp(); - } if (newCopy.hasAlternateState()) { newCopy.setState(copyFrom.getCurrentStateName(), false, true); } diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index c902781cfaa..690b1535942 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -87,22 +87,16 @@ public class CardFactory { // need to create a physical card first, i need the original card faces final Card copy = getCard(original.getPaperCard(), controller, id, game); + copy.setStates(getCloneStates(original, copy, sourceSA)); + // force update the now set State if (original.isTransformable()) { + copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true); // 707.8a If an effect creates a token that is a copy of a transforming permanent or a transforming double-faced card not on the battlefield, // the resulting token is a transforming token that has both a front face and a back face. // The characteristics of each face are determined by the copiable values of the same face of the permanent it is a copy of, as modified by any other copy effects that apply to that permanent. // If the token is a copy of a transforming permanent with its back face up, the token enters the battlefield with its back face up. // This rule does not apply to tokens that are created with their own set of characteristics and enter the battlefield as a copy of a transforming permanent due to a replacement effect. copy.setBackSide(original.isBackSide()); - if (original.isTransformed()) { - copy.incrementTransformedTimestamp(); - } - } - - copy.setStates(getCloneStates(original, copy, sourceSA)); - // force update the now set State - if (original.isTransformable()) { - copy.setState(original.isTransformed() ? CardStateName.Backside : CardStateName.Original, true, true); } else { copy.setState(copy.getCurrentStateName(), true, true); } From 35e6268a95117105d6d885820056fa9e70f94e8d Mon Sep 17 00:00:00 2001 From: Renato Filipe Vidal Santos <45150760+dracontes@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:58:28 +0100 Subject: [PATCH 013/230] Add files via upload (#8754) --- forge-gui/res/cardsfolder/f/finale_of_revelation.txt | 2 +- .../flamewar_brash_veteran_flamewar_streetwise_operative.txt | 2 +- forge-gui/res/cardsfolder/l/last_march_of_the_ents.txt | 2 +- forge-gui/res/cardsfolder/r/rikku_resourceful_guardian.txt | 2 +- forge-gui/res/cardsfolder/s/skanos_dragon_vassal.txt | 2 +- forge-gui/res/cardsfolder/s/sword_point_diplomacy.txt | 4 ++-- forge-gui/res/cardsfolder/s/sylvan_library.txt | 2 +- forge-gui/res/cardsfolder/t/themberchaud.txt | 2 +- forge-gui/res/cardsfolder/t/thundermaw_hellkite.txt | 4 ++-- forge-gui/res/cardsfolder/v/virtuss_maneuver.txt | 2 +- forge-gui/res/cardsfolder/w/white_mages_staff.txt | 2 +- forge-gui/res/cardsfolder/z/zurs_weirding.txt | 4 ++-- 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/forge-gui/res/cardsfolder/f/finale_of_revelation.txt b/forge-gui/res/cardsfolder/f/finale_of_revelation.txt index bf83f925a5b..4fd11280949 100644 --- a/forge-gui/res/cardsfolder/f/finale_of_revelation.txt +++ b/forge-gui/res/cardsfolder/f/finale_of_revelation.txt @@ -6,6 +6,6 @@ SVar:DBDraw:DB$ Draw | NumCards$ X | SubAbility$ DBUntap | AILogic$ ConsiderPrim SVar:DBUntap:DB$ Untap | UntapUpTo$ True | UntapType$ Land | Amount$ 5 | SubAbility$ DBEffect | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10 SVar:DBEffect:DB$ Effect | StaticAbilities$ STHandSize | Duration$ Permanent | SubAbility$ DBChange | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10 SVar:STHandSize:Mode$ Continuous | Affected$ You | SetMaxHandSize$ Unlimited | Description$ You have no maximum hand size. -SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | StackDescription$ Exile CARDNAME +SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | StackDescription$ Exile CARDNAME. SVar:X:Count$xPaid Oracle:Draw X cards. If X is 10 or more, instead shuffle your graveyard into your library, draw X cards, untap up to five lands, and you have no maximum hand size for the rest of the game.\nExile Finale of Revelation. diff --git a/forge-gui/res/cardsfolder/f/flamewar_brash_veteran_flamewar_streetwise_operative.txt b/forge-gui/res/cardsfolder/f/flamewar_brash_veteran_flamewar_streetwise_operative.txt index 9b9f9e64a99..604ab696c0c 100644 --- a/forge-gui/res/cardsfolder/f/flamewar_brash_veteran_flamewar_streetwise_operative.txt +++ b/forge-gui/res/cardsfolder/f/flamewar_brash_veteran_flamewar_streetwise_operative.txt @@ -4,7 +4,7 @@ Types:Legendary Artifact Creature Robot PT:3/2 K:More Than Meets the Eye:B R A:AB$ PutCounter | Cost$ Sac<1/Artifact.Other/another artifact> | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | SorcerySpeed$ True | SubAbility$ DBConvert | AILogic$ AristocratCounters | SpellDescription$ Put a +1/+1 counter on NICKNAME and convert it. Activate only as a sorcery. -SVar:DBConvert:DB$ SetState | Mode$ Transform | StackDescription$ Convert NICKNAME +SVar:DBConvert:DB$ SetState | Mode$ Transform | StackDescription$ Convert NICKNAME. A:AB$ ChangeZoneAll | Cost$ 1 Discard<1/Hand> | ChangeType$ Card.YouOwn+counters_GE1_INTEL | Origin$ Exile | Destination$ Hand | SpellDescription$ Put all exiled cards you own with intel counters on them into your hand. DeckHints:Ability$Counters DeckHas:Ability$Sacrifice|Discard|Counters diff --git a/forge-gui/res/cardsfolder/l/last_march_of_the_ents.txt b/forge-gui/res/cardsfolder/l/last_march_of_the_ents.txt index 0645823806d..fed7511d6b4 100644 --- a/forge-gui/res/cardsfolder/l/last_march_of_the_ents.txt +++ b/forge-gui/res/cardsfolder/l/last_march_of_the_ents.txt @@ -3,7 +3,7 @@ ManaCost:6 G G Types:Sorcery R:Event$ Counter | ValidCard$ Card.Self | ValidSA$ Spell | Layer$ CantHappen | Description$ This spell can't be countered. A:SP$ Draw | Defined$ You | NumCards$ X | SubAbility$ CheatBattlefield | SpellDescription$ Draw cards equal to the greatest toughness among creatures you control, then put any number of creature cards from your hand onto the battlefield. -SVar:CheatBattlefield:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature | ChangeNum$ Y | StackDescription$ {p:You} puts any number of creature cards from their hand onto the battlefield +SVar:CheatBattlefield:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature | ChangeNum$ Y | StackDescription$ {p:You} puts any number of creature cards from their hand onto the battlefield. SVar:X:Count$Valid Creature.YouCtrl$GreatestToughness SVar:Y:Count$ValidHand Creature.YouCtrl DeckHints:Type$Wall|Plant|Treefolk diff --git a/forge-gui/res/cardsfolder/r/rikku_resourceful_guardian.txt b/forge-gui/res/cardsfolder/r/rikku_resourceful_guardian.txt index 4c156177fa4..bbe692ee87d 100644 --- a/forge-gui/res/cardsfolder/r/rikku_resourceful_guardian.txt +++ b/forge-gui/res/cardsfolder/r/rikku_resourceful_guardian.txt @@ -7,4 +7,4 @@ SVar:TrigEffect:DB$ Effect | RememberObjects$ TriggeredObjectLKICopy | StaticAbi SVar:BlockRestrict:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | ValidBlocker$ Creature.OppCtrl | Description$ That creature can't be blocked by creatures your opponents control. A:AB$ Pump | Cost$ 1 T | PrecostDesc$ Steal — | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | SubAbility$ DBMove | SorcerySpeed$ True | StackDescription$ SpellDescription | SpellDescription$ Move a counter from target creature an opponent controls onto target creature you control. Activate only as a sorcery. SVar:DBMove:DB$ MoveCounter | Source$ ParentTarget | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control to move counter to | CounterType$ Any | CounterNum$ 1 | StackDescription$ None -Oracle:Whenever you put one or more counters on a creature, until end of turn, that creature can't be blocked by creatures your opponents control.\nSteal — {1}, {T}: Move a counter from target creature an opponent controls onto target creature you control. Activate only as a sorcery.\n +Oracle:Whenever you put one or more counters on a creature, until end of turn, that creature can't be blocked by creatures your opponents control.\nSteal — {1}, {T}: Move a counter from target creature an opponent controls onto target creature you control. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/s/skanos_dragon_vassal.txt b/forge-gui/res/cardsfolder/s/skanos_dragon_vassal.txt index c4ab79c229a..3c7fa084fe3 100644 --- a/forge-gui/res/cardsfolder/s/skanos_dragon_vassal.txt +++ b/forge-gui/res/cardsfolder/s/skanos_dragon_vassal.txt @@ -73,7 +73,7 @@ ManaCost:4 G G Types:Legendary Creature Dragon Ranger PT:6/6 K:Vigilance -T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$Whenever CARDNAME attacks, untap another target attacking creature. It gets +X/+0 until end of turn, where X is NICKNAME's power. +T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ Whenever CARDNAME attacks, untap another target attacking creature. It gets +X/+0 until end of turn, where X is NICKNAME's power. SVar:TrigUntap:DB$ Untap | ValidTgts$ Creature.attacking+Other | TgtPrompt$ Select another target attacking creature | SubAbility$ DBPump SVar:DBPump:DB$ Pump | Defined$ Targeted | NumAtt$ +X SVar:X:Count$CardPower diff --git a/forge-gui/res/cardsfolder/s/sword_point_diplomacy.txt b/forge-gui/res/cardsfolder/s/sword_point_diplomacy.txt index 82c929dc202..646845b7761 100644 --- a/forge-gui/res/cardsfolder/s/sword_point_diplomacy.txt +++ b/forge-gui/res/cardsfolder/s/sword_point_diplomacy.txt @@ -1,9 +1,9 @@ Name:Sword-Point Diplomacy ManaCost:2 B Types:Sorcery -A:SP$ PeekAndReveal | PeekAmount$ 3 | NoPeek$ True | RememberRevealed$ True | SubAbility$ DBRepeat | SpellDescription$ Reveal the top three cards of your library. For each of those cards, put that card into your hand unless any opponent pays 3 life. Then exile the rest. +A:SP$ PeekAndReveal | PeekAmount$ 3 | NoPeek$ True | RememberRevealed$ True | SubAbility$ DBRepeat | StackDescription$ REP Reveal_{p:You} reveals & of your_of their & put_{p:You} puts & into your_into their & Then exile_Then {p:You} exiles | SpellDescription$ Reveal the top three cards of your library. For each of those cards, put that card into your hand unless any opponent pays 3 life. Then exile the rest. SVar:DBRepeat:DB$ RepeatEach | RepeatSubAbility$ DBChangeZone | RepeatCards$ Card.IsRemembered | ChooseOrder$ True | Zone$ Library | UseImprinted$ True | SubAbility$ PutIntoHand -SVar:DBChangeZone:DB$ ChangeZone | Defined$ Imprinted | Origin$ Library | Destination$ Hand | UnlessCost$ PayLife<3> | UnlessPayer$ Opponent | ForgetChanged$ True | StackDescription$ Put [{c:Imprinted}] into the hand +SVar:DBChangeZone:DB$ ChangeZone | Defined$ Imprinted | Origin$ Library | Destination$ Hand | UnlessCost$ PayLife<3> | UnlessPayer$ Opponent | ForgetChanged$ True SVar:PutIntoHand:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Exile | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Reveal the top three cards of your library. For each of those cards, put that card into your hand unless any opponent pays 3 life. Then exile the rest. diff --git a/forge-gui/res/cardsfolder/s/sylvan_library.txt b/forge-gui/res/cardsfolder/s/sylvan_library.txt index a81f123f95f..c2b9db9b3a6 100644 --- a/forge-gui/res/cardsfolder/s/sylvan_library.txt +++ b/forge-gui/res/cardsfolder/s/sylvan_library.txt @@ -4,7 +4,7 @@ Types:Enchantment T:Mode$ Phase | Phase$ Draw | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ At the beginning of your draw step, you may draw two additional cards. If you do, choose two cards in your hand drawn this turn. For each of those cards, pay 4 life or put the card on top of your library. SVar:TrigDraw:AB$ ChooseCard | ChoiceZone$ Hand | Choices$ Card.YouOwn+DrawnThisTurn | Cost$ Draw<2/You> | Amount$ 2 | Mandatory$ True | AILogic$ Worst | SubAbility$ DBPayOrReturn | NoReveal$ True SVar:DBPayOrReturn:DB$ RepeatEach | RepeatCards$ Card.ChosenCard | Zone$ Hand | RepeatSubAbility$ DBReplace | SubAbility$ DBCleanup | ChooseOrder$ True -SVar:DBReplace:DB$ ChangeZone | Origin$ Hand | Destination$ Library | ChangeType$ Card.ChosenCard | UnlessCost$ PayLife<4> | Mandatory$ True | StackDescription$ Put {c:Imprinted} on top of your library | UnlessPayer$ TriggeredPlayer | NoReveal$ True +SVar:DBReplace:DB$ ChangeZone | Origin$ Hand | Destination$ Library | ChangeType$ Card.ChosenCard | UnlessCost$ PayLife<4> | Mandatory$ True | UnlessPayer$ TriggeredPlayer | NoReveal$ True SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True AI:RemoveDeck:All Oracle:At the beginning of your draw step, you may draw two additional cards. If you do, choose two cards in your hand drawn this turn. For each of those cards, pay 4 life or put the card on top of your library. diff --git a/forge-gui/res/cardsfolder/t/themberchaud.txt b/forge-gui/res/cardsfolder/t/themberchaud.txt index b2911e5fe24..b4f02da2d45 100644 --- a/forge-gui/res/cardsfolder/t/themberchaud.txt +++ b/forge-gui/res/cardsfolder/t/themberchaud.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Dragon PT:5/5 K:Trample T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDamageAllNonFlyers | TriggerDescription$ When CARDNAME enters, he deals X damage to each other creature without flying and each player, where X is the number of Mountains you control. -SVar:TrigDamageAllNonFlyers:DB$ DamageAll | NumDmg$ X | ValidCards$ Creature.Other+withoutFlying | ValidPlayers$ Player | ValidDescription$ each other creature without flying and each player +SVar:TrigDamageAllNonFlyers:DB$ DamageAll | NumDmg$ X | ValidCards$ Creature.Other+withoutFlying | ValidPlayers$ Player S:Mode$ OptionalAttackCost | ValidCard$ Card.Self | Trigger$ TrigPump | Cost$ Exert<1/CARDNAME> | Description$ You may exert CARDNAME as it attacks. When you do, he gains flying until end of turn. (An exerted creature won't untap during your next untap step.) SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ Flying | SpellDescription$ When you do, he gains flying until end of turn. SVar:X:Count$Valid Mountain.YouCtrl diff --git a/forge-gui/res/cardsfolder/t/thundermaw_hellkite.txt b/forge-gui/res/cardsfolder/t/thundermaw_hellkite.txt index dc442c80b8a..23552a6ea0b 100644 --- a/forge-gui/res/cardsfolder/t/thundermaw_hellkite.txt +++ b/forge-gui/res/cardsfolder/t/thundermaw_hellkite.txt @@ -5,6 +5,6 @@ PT:5/5 K:Flying K:Haste T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ ThunderDamage | TriggerDescription$ When CARDNAME enters, it deals 1 damage to each creature with flying your opponents control. Tap those creatures. -SVar:ThunderDamage:DB$ DamageAll | NumDmg$ 1 | ValidCards$ Creature.withFlying+OppCtrl | ValidDescription$ each creature with flying you don't control | SubAbility$ ThunderTap -SVar:ThunderTap:DB$ TapAll | ValidCards$ Creature.withFlying+OppCtrl | ValidDescription$ each creature with flying you don't control +SVar:ThunderDamage:DB$ DamageAll | NumDmg$ 1 | ValidCards$ Creature.withFlying+OppCtrl | SubAbility$ ThunderTap +SVar:ThunderTap:DB$ TapAll | ValidCards$ Creature.withFlying+OppCtrl Oracle:Flying\nHaste (This creature can attack and {T} as soon as it comes under your control.)\nWhen Thundermaw Hellkite enters, it deals 1 damage to each creature with flying your opponents control. Tap those creatures. diff --git a/forge-gui/res/cardsfolder/v/virtuss_maneuver.txt b/forge-gui/res/cardsfolder/v/virtuss_maneuver.txt index ec3fc71676a..32a7bdea77b 100644 --- a/forge-gui/res/cardsfolder/v/virtuss_maneuver.txt +++ b/forge-gui/res/cardsfolder/v/virtuss_maneuver.txt @@ -3,7 +3,7 @@ ManaCost:2 B Types:Sorcery A:SP$ AssignGroup | Defined$ Player | Choices$ DBToHand,DBSacrifice | AILogic$ FriendOrFoe | SpellDescription$ For each player, choose friend or foe. Each friend returns a creature card from their graveyard to their hand. Each foe sacrifices a creature they control. SVar:DBToHand:DB$ ChangeZone | DefinedPlayer$ Remembered | ChangeType$ Creature | Hidden$ True | Mandatory$ True | Origin$ Graveyard | Destination$ Hand | SpellDescription$ Each friend returns a creature card from their graveyard to their hand -SVar:DBSacrifice:DB$ Sacrifice | Defined$ Remembered | SacValid$ Creature | SacMessage$ creature | StackDescription$ SpellDescription | SpellDescription$ Each foe sacrifices a creature they control. +SVar:DBSacrifice:DB$ Sacrifice | Defined$ Remembered | SacValid$ Creature | SacMessage$ creature | StackDescription$ SpellDescription | SpellDescription$ Each foe sacrifices a creature they control SVar:NeedsToPlayVar:Z GE2 SVar:Z:SVar$Z1/Plus.Z2 SVar:Z1:Count$ValidGraveyard Creature.YourTeamCtrl diff --git a/forge-gui/res/cardsfolder/w/white_mages_staff.txt b/forge-gui/res/cardsfolder/w/white_mages_staff.txt index 3f24d19d5b6..c1c86a4c1c0 100644 --- a/forge-gui/res/cardsfolder/w/white_mages_staff.txt +++ b/forge-gui/res/cardsfolder/w/white_mages_staff.txt @@ -8,4 +8,4 @@ SVar:TrigAttack:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigGainLife | T SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 SVar:AE:SVar:HasAttackEffect:TRUE DeckHas:Ability$LifeGain|Token & Type$Cleric -Oracle:Job select (When this Equipment enters, create a 1/1 colorless Hero creature token, then attach this to it.)\nEquipped creature gets +1/+1, has "Whenever this creature attacks, you gain 1 life," and is a Cleric in addition to its other types.\nEquip {3} ({3}: Attach to target creature you control. Equip only as a sorcery) +Oracle:Job select (When this Equipment enters, create a 1/1 colorless Hero creature token, then attach this to it.)\nEquipped creature gets +1/+1, has "Whenever this creature attacks, you gain 1 life," and is a Cleric in addition to its other types.\nEquip {3} ({3}: Attach to target creature you control. Equip only as a sorcery.) diff --git a/forge-gui/res/cardsfolder/z/zurs_weirding.txt b/forge-gui/res/cardsfolder/z/zurs_weirding.txt index 9b13cca8e4f..914d35bad81 100644 --- a/forge-gui/res/cardsfolder/z/zurs_weirding.txt +++ b/forge-gui/res/cardsfolder/z/zurs_weirding.txt @@ -4,8 +4,8 @@ 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 | 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:DBMill:DB$ Dig | Defined$ ReplacedPlayer | DestinationZone$ Graveyard | DigNum$ 1 | ChangeNum$ All | SubAbility$ DBDraw | UnlessPayer$ NonReplacedPlayer | UnlessCost$ PayLife<2> | UnlessSwitched$ True | UnlessResolveSubs$ WhenNotPaid +SVar:DBDraw:DB$ Draw | Defined$ ReplacedPlayer | NumCards$ 1 SVar:NonStackingEffect:True AI:RemoveDeck:All Oracle:Players play with their hands revealed.\nIf 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. From ac492fac357e74903890e078c1c5f7571de0867c Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 21 Sep 2025 08:47:28 +0800 Subject: [PATCH 014/230] use Supplier for FModel for thread safety --- .../src/main/java/forge/model/FModel.java | 312 +++++++----------- 1 file changed, 120 insertions(+), 192 deletions(-) diff --git a/forge-gui/src/main/java/forge/model/FModel.java b/forge-gui/src/main/java/forge/model/FModel.java index 6232a69d3d2..dca8793a240 100644 --- a/forge-gui/src/main/java/forge/model/FModel.java +++ b/forge-gui/src/main/java/forge/model/FModel.java @@ -17,6 +17,8 @@ */ package forge.model; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; import com.google.common.collect.Maps; import forge.*; import forge.CardStorageReader.ProgressObserver; @@ -75,32 +77,82 @@ import java.util.function.Function; public final class FModel { private FModel() { } //don't allow creating instance - private static StaticData magicDb; - - private static QuestPreferences questPreferences; - private static ConquestPreferences conquestPreferences; + private static CardStorageReader reader, tokenReader, customReader, customTokenReader; + private static final Supplier magicDb = Suppliers.memoize(() -> + new StaticData(reader, tokenReader, customReader, customTokenReader, ForgeConstants.EDITIONS_DIR, + ForgeConstants.USER_CUSTOM_EDITIONS_DIR, ForgeConstants.BLOCK_DATA_DIR, ForgeConstants.SETLOOKUP_DIR, + getPreferences().getPref(FPref.UI_PREFERRED_ART), + getPreferences().getPrefBoolean(FPref.UI_LOAD_UNKNOWN_CARDS), + getPreferences().getPrefBoolean(FPref.UI_LOAD_NONLEGAL_CARDS), + getPreferences().getPrefBoolean(FPref.ALLOW_CUSTOM_CARDS_IN_DECKS_CONFORMANCE), + getPreferences().getPrefBoolean(FPref.UI_SMART_CARD_ART), + GuiBase.getInterface().isLibgdxPort())); + private static final Supplier questPreferences = Suppliers.memoize(QuestPreferences::new); + private static final Supplier conquestPreferences = Suppliers.memoize(() -> { + final ConquestPreferences cp = new ConquestPreferences(); + ConquestUtil.updateRarityFilterOdds(cp); + return cp; + }); private static ForgePreferences preferences; - private static ForgeNetPreferences netPreferences; - private static Map achievements; + private static final Supplier netPreferences = Suppliers.memoize(ForgeNetPreferences::new); + private static final Supplier> achievements = Suppliers.memoize(() -> { + final Map a = Maps.newHashMap(); + a.put(GameType.Constructed, new ConstructedAchievements()); + a.put(GameType.Draft, new DraftAchievements()); + a.put(GameType.Sealed, new SealedAchievements()); + a.put(GameType.Quest, new QuestAchievements()); + a.put(GameType.PlanarConquest, new PlanarConquestAchievements()); + a.put(GameType.Puzzle, new PuzzleAchievements()); + a.put(GameType.Adventure, new AdventureAchievements()); + return a; + }); // Someone should take care of 2 gauntlets here private static TournamentData tournamentData; private static GauntletData gauntletData; - private static GauntletMini gauntlet; + private static final Supplier gauntletMini = Suppliers.memoize(GauntletMini::new); - private static QuestController quest; - private static ConquestController conquest; - private static CardCollections decks; + private static final Supplier quest = Suppliers.memoize(QuestController::new); + private static final Supplier conquest = Suppliers.memoize(ConquestController::new); + private static final Supplier decks = Suppliers.memoize(CardCollections::new); - private static IStorage blocks; - private static IStorage fantasyBlocks; - private static IStorage themedChaosDrafts; - private static IStorage planes; - private static IStorage worlds; - private static GameFormat.Collection formats; - private static ItemPool uniqueCardsNoAlt, allCardsNoAlt, planechaseCards, archenemyCards, - brawlCommander, oathbreakerCommander, tinyLeadersCommander, commanderPool, - avatarPool, conspiracyPool, dungeonPool, attractionPool, contraptionPool; + private static final Supplier> blocks = Suppliers.memoize(() -> { + final IStorage cb = new StorageBase<>("Block definitions", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "blocks.txt", getMagicDb().getEditions())); + // SetblockLands + for (final CardBlock b : cb) { + try { + getMagicDb().getBlockLands().add(b.getLandSet().getCode()); + } catch (Exception e) { + e.printStackTrace(); + } + } + return cb; + }); + private static final Supplier> fantasyBlocks = Suppliers.memoize(() -> new StorageBase<>("Custom blocks", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "fantasyblocks.txt", getMagicDb().getEditions()))); + private static final Supplier> themedChaosDrafts = Suppliers.memoize(() -> new StorageBase<>("Themed Chaos Drafts", new ThemedChaosDraft.Reader(ForgeConstants.BLOCK_DATA_DIR + "chaosdraftthemes.txt"))); + private static final Supplier> planes = Suppliers.memoize(() -> new StorageBase<>("Conquest planes", new ConquestPlane.Reader(ForgeConstants.CONQUEST_PLANES_DIR + "planes.txt"))); + private static final Supplier> worlds = Suppliers.memoize(() -> { + final Map standardWorlds = new QuestWorld.Reader(ForgeConstants.QUEST_WORLD_DIR + "worlds.txt").readAll(); + final Map customWorlds = new QuestWorld.Reader(ForgeConstants.USER_QUEST_WORLD_DIR + "customworlds.txt").readAll(); + customWorlds.values().forEach(world -> world.setCustom(true)); + standardWorlds.putAll(customWorlds); + final IStorage w = new StorageBase<>("Quest worlds", null, standardWorlds); + return w; + }); + private static final Supplier formats = Suppliers.memoize(() -> new GameFormat.Collection(new GameFormat.Reader( new File(ForgeConstants.FORMATS_DATA_DIR), new File(ForgeConstants.USER_FORMATS_DIR), preferences.getPrefBoolean(FPref.LOAD_ARCHIVED_FORMATS)))); + private static final Supplier> uniqueCardsNoAlt = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getCommonCards().getUniqueCardsNoAlt(), PaperCard.class)); + private static final Supplier> allCardsNoAlt = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(), PaperCard.class)); + private static final Supplier> planechaseCards = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_PLANE_OR_PHENOMENON)), PaperCard.class)); + private static final Supplier> archenemyCards = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_SCHEME)), PaperCard.class)); + private static final Supplier> brawlCommander = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(getFormats().get("Brawl").getFilterPrinted().and(PaperCardPredicates.fromRules(CardRulesPredicates.CAN_BE_BRAWL_COMMANDER))), PaperCard.class)); + private static final Supplier> oathbreakerCommander = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(PaperCardPredicates.fromRules(CardRulesPredicates.CAN_BE_OATHBREAKER.or(CardRulesPredicates.CAN_BE_SIGNATURE_SPELL))), PaperCard.class)); + private static final Supplier> tinyLeadersCommander = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(PaperCardPredicates.fromRules(CardRulesPredicates.CAN_BE_TINY_LEADERS_COMMANDER)), PaperCard.class)); + private static final Supplier> commanderPool = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(PaperCardPredicates.CAN_BE_COMMANDER), PaperCard.class)); + private static final Supplier> avatarPool = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_VANGUARD)), PaperCard.class)); + private static final Supplier> conspiracyPool = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_CONSPIRACY)), PaperCard.class)); + private static final Supplier> dungeonPool = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_DUNGEON)), PaperCard.class)); + private static final Supplier> attractionPool = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_ATTRACTION)), PaperCard.class)); + private static final Supplier> contraptionPool = Suppliers.memoize(() -> ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_CONTRAPTION)), PaperCard.class)); public static void initialize(final IProgressBar progressBar, Function adjustPrefs) { initialize(progressBar, adjustPrefs, false); @@ -126,8 +178,8 @@ public final class FModel { throw new RuntimeException(exn); } - Lang.createInstance(FModel.getPreferences().getPref(FPref.UI_LANGUAGE)); - Localizer.getInstance().initialize(FModel.getPreferences().getPref(FPref.UI_LANGUAGE), ForgeConstants.LANG_DIR); + Lang.createInstance(getPreferences().getPref(FPref.UI_LANGUAGE)); + Localizer.getInstance().initialize(getPreferences().getPref(FPref.UI_LANGUAGE), ForgeConstants.LANG_DIR); final ProgressObserver progressBarBridge = (progressBar == null) ? ProgressObserver.emptyObserver : new ProgressObserver() { @@ -154,17 +206,17 @@ public final class FModel { // Load card database // Lazy loading currently disabled - final CardStorageReader reader = new CardStorageReader(ForgeConstants.CARD_DATA_DIR, progressBarBridge, + reader = new CardStorageReader(ForgeConstants.CARD_DATA_DIR, progressBarBridge, false); - final CardStorageReader tokenReader = new CardStorageReader(ForgeConstants.TOKEN_DATA_DIR, progressBarBridge, + tokenReader = new CardStorageReader(ForgeConstants.TOKEN_DATA_DIR, progressBarBridge, false); - CardStorageReader customReader; + try { customReader = new CardStorageReader(ForgeConstants.USER_CUSTOM_CARDS_DIR, progressBarBridge, false); } catch (Exception e) { customReader = null; } - CardStorageReader customTokenReader; + try { customTokenReader = new CardStorageReader(ForgeConstants.USER_CUSTOM_TOKENS_DIR, progressBarBridge, false); } catch (Exception e) { @@ -174,15 +226,6 @@ public final class FModel { // Do this first so PaperCards see the real preference CardTranslation.preloadTranslation(preferences.getPref(FPref.UI_LANGUAGE), ForgeConstants.LANG_DIR); - magicDb = new StaticData(reader, tokenReader, customReader, customTokenReader, ForgeConstants.EDITIONS_DIR, - ForgeConstants.USER_CUSTOM_EDITIONS_DIR, ForgeConstants.BLOCK_DATA_DIR, ForgeConstants.SETLOOKUP_DIR, - FModel.getPreferences().getPref(FPref.UI_PREFERRED_ART), - FModel.getPreferences().getPrefBoolean(FPref.UI_LOAD_UNKNOWN_CARDS), - FModel.getPreferences().getPrefBoolean(FPref.UI_LOAD_NONLEGAL_CARDS), - FModel.getPreferences().getPrefBoolean(FPref.ALLOW_CUSTOM_CARDS_IN_DECKS_CONFORMANCE), - FModel.getPreferences().getPrefBoolean(FPref.UI_SMART_CARD_ART) - ); - // Create profile dirs if they don't already exist for (final String dname : ForgeConstants.PROFILE_DIRS) { final File path = new File(dname); @@ -198,18 +241,18 @@ public final class FModel { ForgePreferences.DEV_MODE = preferences.getPrefBoolean(FPref.DEV_MODE_ENABLED); ForgePreferences.UPLOAD_DRAFT = ForgePreferences.NET_CONN; - magicDb.setStandardPredicate(getFormats().getStandard().getFilterRules()); - magicDb.setPioneerPredicate(getFormats().getPioneer().getFilterRules()); - magicDb.setModernPredicate(getFormats().getModern().getFilterRules()); - magicDb.setCommanderPredicate(getFormats().get("Commander").getFilterRules()); - magicDb.setOathbreakerPredicate(getFormats().get("Oathbreaker").getFilterRules()); - magicDb.setBrawlPredicate(getFormats().get("Brawl").getFilterRules()); + getMagicDb().setStandardPredicate(getFormats().getStandard().getFilterRules()); + getMagicDb().setPioneerPredicate(getFormats().getPioneer().getFilterRules()); + getMagicDb().setModernPredicate(getFormats().getModern().getFilterRules()); + getMagicDb().setCommanderPredicate(getFormats().get("Commander").getFilterRules()); + getMagicDb().setOathbreakerPredicate(getFormats().get("Oathbreaker").getFilterRules()); + getMagicDb().setBrawlPredicate(getFormats().get("Brawl").getFilterRules()); - magicDb.setFilteredHandsEnabled(preferences.getPrefBoolean(FPref.FILTERED_HANDS)); + getMagicDb().setFilteredHandsEnabled(preferences.getPrefBoolean(FPref.FILTERED_HANDS)); try { - magicDb.setMulliganRule(MulliganDefs.MulliganRule.valueOf(preferences.getPref(FPref.MULLIGAN_RULE))); + getMagicDb().setMulliganRule(MulliganDefs.MulliganRule.valueOf(preferences.getPref(FPref.MULLIGAN_RULE))); } catch(Exception e) { - magicDb.setMulliganRule(MulliganDefs.MulliganRule.London); + getMagicDb().setMulliganRule(MulliganDefs.MulliganRule.London); } Spell.setPerformanceMode(preferences.getPrefBoolean(FPref.PERFORMANCE_MODE)); @@ -224,38 +267,16 @@ public final class FModel { // Preload AI profiles AiProfileUtil.loadAllProfiles(ForgeConstants.AI_PROFILE_DIR); - AiProfileUtil.setAiSideboardingMode(AiProfileUtil.AISideboardingMode.normalizedValueOf(FModel.getPreferences().getPref(FPref.MATCH_AI_SIDEBOARDING_MODE))); + AiProfileUtil.setAiSideboardingMode(AiProfileUtil.AISideboardingMode.normalizedValueOf(getPreferences().getPref(FPref.MATCH_AI_SIDEBOARDING_MODE))); // Generate Deck Gen matrix - if(FModel.getPreferences().getPrefBoolean(FPref.DECKGEN_CARDBASED)) { + if(getPreferences().getPrefBoolean(FPref.DECKGEN_CARDBASED)) { boolean commanderDeckGenMatrixLoaded=CardRelationMatrixGenerator.initialize(); deckGenMatrixLoaded=CardArchetypeLDAGenerator.initialize(); if(!commanderDeckGenMatrixLoaded){ deckGenMatrixLoaded=false; } } - - if (GuiBase.getInterface().isLibgdxPort() && GuiBase.getDeviceRAM() < 5000) - return; // Don't preload ItemPool on mobile port with less than 5GB RAM - - // Common ItemPool to preload - getAllCardsNoAlt(); - getArchenemyCards(); - getPlanechaseCards(); - getAttractionPool(); - getContraptionPool(); - if (GuiBase.getInterface().isLibgdxPort()) { - // Preload mobile Itempool - getUniqueCardsNoAlt(); - } else { - // Preload Desktop Itempool - getCommanderPool(); - getOathbreakerCommander(); - getBrawlCommander(); - getTinyLeadersCommander(); - getAvatarPool(); - getConspiracyPool(); - } } private static boolean deckGenMatrixLoaded = false; @@ -265,103 +286,63 @@ public final class FModel { } public static QuestController getQuest() { - if (quest == null) - quest = new QuestController(); - return quest; + return quest.get(); } public static ConquestController getConquest() { - if (conquest == null) - conquest = new ConquestController(); - return conquest; + return conquest.get(); } public static ItemPool getUniqueCardsNoAlt() { - if (uniqueCardsNoAlt == null) - uniqueCardsNoAlt = ItemPool.createFrom(getMagicDb().getCommonCards().getUniqueCardsNoAlt(), PaperCard.class); - return uniqueCardsNoAlt; + return uniqueCardsNoAlt.get(); } public static ItemPool getAllCardsNoAlt() { - if (allCardsNoAlt == null) - allCardsNoAlt = ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(), PaperCard.class); - return allCardsNoAlt; + return allCardsNoAlt.get(); } public static ItemPool getArchenemyCards() { - if (archenemyCards == null) - archenemyCards = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_SCHEME)), PaperCard.class); - return archenemyCards; + return archenemyCards.get(); } public static ItemPool getPlanechaseCards() { - if (planechaseCards == null) - planechaseCards = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules(CardRulesPredicates.IS_PLANE_OR_PHENOMENON)), PaperCard.class); - return planechaseCards; + return planechaseCards.get(); } public static ItemPool getBrawlCommander() { - if (brawlCommander == null) { - brawlCommander = ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt( - FModel.getFormats().get("Brawl").getFilterPrinted() - .and(PaperCardPredicates.fromRules(CardRulesPredicates.CAN_BE_BRAWL_COMMANDER))), PaperCard.class); - } - return brawlCommander; + return brawlCommander.get(); } public static ItemPool getOathbreakerCommander() { - if (oathbreakerCommander == null) - oathbreakerCommander = ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(PaperCardPredicates.fromRules( - CardRulesPredicates.CAN_BE_OATHBREAKER.or(CardRulesPredicates.CAN_BE_SIGNATURE_SPELL))), PaperCard.class); - return oathbreakerCommander; + return oathbreakerCommander.get(); } public static ItemPool getTinyLeadersCommander() { - if (tinyLeadersCommander == null) - tinyLeadersCommander = ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(PaperCardPredicates.fromRules( - CardRulesPredicates.CAN_BE_TINY_LEADERS_COMMANDER)), PaperCard.class); - return tinyLeadersCommander; + return tinyLeadersCommander.get(); } public static ItemPool getCommanderPool() { - if (commanderPool == null) - commanderPool = ItemPool.createFrom(getMagicDb().getCommonCards().getAllCardsNoAlt(PaperCardPredicates.CAN_BE_COMMANDER), PaperCard.class); - return commanderPool; + return commanderPool.get(); } public static ItemPool getAvatarPool() { - if (avatarPool == null) - avatarPool = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( - CardRulesPredicates.IS_VANGUARD)), PaperCard.class); - return avatarPool; + return avatarPool.get(); } public static ItemPool getConspiracyPool() { - if (conspiracyPool == null) - conspiracyPool = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( - CardRulesPredicates.IS_CONSPIRACY)), PaperCard.class); - return conspiracyPool; + return conspiracyPool.get(); } public static ItemPool getDungeonPool() { - if (dungeonPool == null) - dungeonPool = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( - CardRulesPredicates.IS_DUNGEON)), PaperCard.class); - return dungeonPool; + return dungeonPool.get(); } public static ItemPool getAttractionPool() { - if (attractionPool == null) - attractionPool = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( - CardRulesPredicates.IS_ATTRACTION)), PaperCard.class); - return attractionPool; + return attractionPool.get(); } public static ItemPool getContraptionPool() { - if (contraptionPool == null) - contraptionPool = ItemPool.createFrom(getMagicDb().getVariantCards().getAllCards(PaperCardPredicates.fromRules( - CardRulesPredicates.IS_CONTRAPTION)), PaperCard.class); - return contraptionPool; + return contraptionPool.get(); } private static boolean keywordsLoaded = false; @@ -396,67 +377,36 @@ public final class FModel { } public static StaticData getMagicDb() { - return magicDb; + return magicDb.get(); } public static ForgePreferences getPreferences() { return preferences; } public static ForgeNetPreferences getNetPreferences() { - if (netPreferences == null) - netPreferences = new ForgeNetPreferences(); - return netPreferences; + return netPreferences.get(); } public static AchievementCollection getAchievements(GameType gameType) { - if (achievements == null) { - achievements = Maps.newHashMap(); - achievements.put(GameType.Constructed, new ConstructedAchievements()); - achievements.put(GameType.Draft, new DraftAchievements()); - achievements.put(GameType.Sealed, new SealedAchievements()); - achievements.put(GameType.Quest, new QuestAchievements()); - achievements.put(GameType.PlanarConquest, new PlanarConquestAchievements()); - achievements.put(GameType.Puzzle, new PuzzleAchievements()); - achievements.put(GameType.Adventure, new AdventureAchievements()); - } - // Translate gameType to appropriate type if needed return switch (gameType) { - case Constructed, Draft, Sealed, Quest, PlanarConquest, Puzzle, Adventure -> achievements.get(gameType); - case AdventureEvent -> achievements.get(GameType.Adventure); - case QuestDraft -> achievements.get(GameType.Quest); - default -> achievements.get(GameType.Constructed); + case Constructed, Draft, Sealed, Quest, PlanarConquest, Puzzle, Adventure -> achievements.get().get(gameType); + case AdventureEvent -> achievements.get().get(GameType.Adventure); + case QuestDraft -> achievements.get().get(GameType.Quest); + default -> achievements.get().get(GameType.Constructed); }; } public static IStorage getBlocks() { - if (blocks == null) { - blocks = new StorageBase<>("Block definitions", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "blocks.txt", magicDb.getEditions())); - // SetblockLands - for (final CardBlock b : blocks) { - try { - magicDb.getBlockLands().add(b.getLandSet().getCode()); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - return blocks; + return blocks.get(); } public static QuestPreferences getQuestPreferences() { - if (questPreferences == null) - questPreferences = new QuestPreferences(); - return questPreferences; + return questPreferences.get(); } public static ConquestPreferences getConquestPreferences() { - if (conquestPreferences == null) { - conquestPreferences = new ConquestPreferences(); - // initialize on first call... - ConquestUtil.updateRarityFilterOdds(conquestPreferences); - } - return conquestPreferences; + return conquestPreferences.get(); } public static GauntletData getGauntletData() { @@ -468,56 +418,34 @@ public final class FModel { } public static GauntletMini getGauntletMini() { - if (gauntlet == null) { - gauntlet = new GauntletMini(); - } - return gauntlet; + return gauntletMini.get(); } public static CardCollections getDecks() { - if (decks == null) - decks = new CardCollections(); - return decks; + return decks.get(); } public static IStorage getPlanes() { - if (planes == null) - planes = new StorageBase<>("Conquest planes", new ConquestPlane.Reader(ForgeConstants.CONQUEST_PLANES_DIR + "planes.txt")); - return planes; + return planes.get(); } public static IStorage getWorlds() { - if (worlds == null) { - Map standardWorlds = new QuestWorld.Reader(ForgeConstants.QUEST_WORLD_DIR + "worlds.txt").readAll(); - Map customWorlds = new QuestWorld.Reader(ForgeConstants.USER_QUEST_WORLD_DIR + "customworlds.txt").readAll(); - customWorlds.values().forEach(world -> world.setCustom(true)); - standardWorlds.putAll(customWorlds); - worlds = new StorageBase<>("Quest worlds", null, standardWorlds); - } - return worlds; + return worlds.get(); } public static GameFormat.Collection getFormats() { - if (formats == null) { - formats = new GameFormat.Collection(new GameFormat.Reader( new File(ForgeConstants.FORMATS_DATA_DIR), - new File(ForgeConstants.USER_FORMATS_DIR), preferences.getPrefBoolean(FPref.LOAD_ARCHIVED_FORMATS))); - } - return formats; + return formats.get(); } public static IStorage getFantasyBlocks() { - if (fantasyBlocks == null) - fantasyBlocks = new StorageBase<>("Custom blocks", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "fantasyblocks.txt", magicDb.getEditions())); - return fantasyBlocks; + return fantasyBlocks.get(); } public static IStorage getThemedChaosDrafts() { - if (themedChaosDrafts == null) - themedChaosDrafts = new StorageBase<>("Themed Chaos Drafts", new ThemedChaosDraft.Reader(ForgeConstants.BLOCK_DATA_DIR + "chaosdraftthemes.txt")); - return themedChaosDrafts; + return themedChaosDrafts.get(); } public static TournamentData getTournamentData() { return tournamentData; } - public static void setTournamentData(TournamentData tournamentData) { FModel.tournamentData = tournamentData; } + public static void setTournamentData(TournamentData tournamentData0) { tournamentData = tournamentData0; } } From bea5ffb0a843ac00af80424a329ec15be70f70c2 Mon Sep 17 00:00:00 2001 From: kevlahnota Date: Sun, 21 Sep 2025 08:51:30 +0800 Subject: [PATCH 015/230] Update FModel.java unused var --- forge-gui/src/main/java/forge/model/FModel.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/forge-gui/src/main/java/forge/model/FModel.java b/forge-gui/src/main/java/forge/model/FModel.java index dca8793a240..7ae9a94341c 100644 --- a/forge-gui/src/main/java/forge/model/FModel.java +++ b/forge-gui/src/main/java/forge/model/FModel.java @@ -85,8 +85,7 @@ public final class FModel { getPreferences().getPrefBoolean(FPref.UI_LOAD_UNKNOWN_CARDS), getPreferences().getPrefBoolean(FPref.UI_LOAD_NONLEGAL_CARDS), getPreferences().getPrefBoolean(FPref.ALLOW_CUSTOM_CARDS_IN_DECKS_CONFORMANCE), - getPreferences().getPrefBoolean(FPref.UI_SMART_CARD_ART), - GuiBase.getInterface().isLibgdxPort())); + getPreferences().getPrefBoolean(FPref.UI_SMART_CARD_ART))); private static final Supplier questPreferences = Suppliers.memoize(QuestPreferences::new); private static final Supplier conquestPreferences = Suppliers.memoize(() -> { final ConquestPreferences cp = new ConquestPreferences(); From a7568b08450638bb73f89b4efadb3acdd29558fe Mon Sep 17 00:00:00 2001 From: Renato Filipe Vidal Santos <45150760+dracontes@users.noreply.github.com> Date: Sun, 21 Sep 2025 08:30:43 +0100 Subject: [PATCH 016/230] Cleanup 2025-09-21 --- forge-gui/res/cardsfolder/a/appa_aangs_companion.txt | 4 ++-- forge-gui/res/cardsfolder/f/fire_nation_attacks.txt | 2 +- .../res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/cardsfolder/a/appa_aangs_companion.txt b/forge-gui/res/cardsfolder/a/appa_aangs_companion.txt index 5cfc5cb267b..ecebfb61baf 100644 --- a/forge-gui/res/cardsfolder/a/appa_aangs_companion.txt +++ b/forge-gui/res/cardsfolder/a/appa_aangs_companion.txt @@ -3,7 +3,7 @@ ManaCost:3 W Types:Legendary Creature Bison Ally PT:2/4 K:Flying -T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever NICKNAME attacks, another target attacking creature without flying gains flying until until end of turn. +T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever NICKNAME attacks, another target attacking creature without flying gains flying until end of turn. SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.Other+attacking+withoutFlying | TgtPrompt$ Select another target attacking creature without flying | KW$ Flying SVar:HasAttackEffect:TRUE -Oracle:Flying (This creature can't be blocked except by creatures with flying or reach.)\nWhenever Appa attacks, another target attacking creature without flying gains flying until until end of turn. +Oracle:Flying (This creature can't be blocked except by creatures with flying or reach.)\nWhenever Appa attacks, another target attacking creature without flying gains flying until end of turn. diff --git a/forge-gui/res/cardsfolder/f/fire_nation_attacks.txt b/forge-gui/res/cardsfolder/f/fire_nation_attacks.txt index 9d3da9e3b14..e737dd00894 100644 --- a/forge-gui/res/cardsfolder/f/fire_nation_attacks.txt +++ b/forge-gui/res/cardsfolder/f/fire_nation_attacks.txt @@ -3,4 +3,4 @@ ManaCost:4 R Types:Instant A:SP$ Token | TokenAmount$ 2 | TokenScript$ r_2_2_soldier_firebending_1 | TokenOwner$ You | SpellDescription$ Create two 2/2 red Soldier creature tokens with firebending 1. (Whenever a creature with firebending 1 attacks, add {R}. This mana lasts until end of combat.) K:Flashback:8 R -Oracle:Create two 2/2 red Soldier creature tokens with firebending 1. (Whenever a creature with firebending 1 attacks, add {R}. This mana lasts until end of combat.)\nFlashback {8}{R} (You may may cast this card from your graveyard for its flashback cost. Then exile it.) +Oracle:Create two 2/2 red Soldier creature tokens with firebending 1. (Whenever a creature with firebending 1 attacks, add {R}. This mana lasts until end of combat.)\nFlashback {8}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.) diff --git a/forge-gui/res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt b/forge-gui/res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt index 680c2cf38a8..dbf399f6d6b 100644 --- a/forge-gui/res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt +++ b/forge-gui/res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt @@ -8,7 +8,7 @@ SVar:DBExile:DB$ ChangeZone | ValidTgts$ Opponent | ChangeType$ Card.NamedCard | SVar:DBTransform:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBReturn | SpellDescription$ Exile this Saga, then return it to the battlefield transformed under your control. SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | Transformed$ True | GainControl$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -Oracle: (As this Saga enters and after your draw step, add a lore counter.)\nI — Destroy all creatures.\nII — Choose a card name. Search target opponent's graveyard, hand, and library for up to four cards with that name and exile them. Then that player shuffles.\nIII — Exile this Saga, then return it to the battlefield transformed under your control. +Oracle:(As this Saga enters and after your draw step, add a lore counter.)\nI — Destroy all creatures.\nII — Choose a card name. Search target opponent's graveyard, hand, and library for up to four cards with that name and exile them. Then that player shuffles.\nIII — Exile this Saga, then return it to the battlefield transformed under your control. ALTERNATE From b50cdea5df8566af1d3e700a1d7913721f739484 Mon Sep 17 00:00:00 2001 From: kevlahnota Date: Mon, 22 Sep 2025 06:37:26 +0800 Subject: [PATCH 017/230] Update drop_tower.txt --- forge-gui/res/cardsfolder/d/drop_tower.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/d/drop_tower.txt b/forge-gui/res/cardsfolder/d/drop_tower.txt index ed5b5ada6db..329ae1b915a 100644 --- a/forge-gui/res/cardsfolder/d/drop_tower.txt +++ b/forge-gui/res/cardsfolder/d/drop_tower.txt @@ -5,7 +5,7 @@ K:Visit:TrigPump SVar:TrigPump:DB$ Effect | StaticAbilities$ Pump | ValidTgts$ Creature | Triggers$ ExileSelf | RememberObjects$ Targeted | SpellDescription$ Target creature gains flying until end of turn, or until any player rolls a 1, whichever comes first. SVar:Pump:Mode$ Continuous | Affected$ Card.IsRemembered | AddKeyword$ Flying | Description$ This creature gains flying until end of turn, or until any player rolls a 1. SVar:ExileSelf:Mode$ RolledDie | Execute$ TrigRemove | ValidResult$ EQ1 | Static$ True -SVar:TrigRemove:DB$ ChangeZone | Origin$ Command | Defined$ Self | Destination$ Exiled +SVar:TrigRemove:DB$ ChangeZone | Origin$ Command | Defined$ Self | Destination$ Exile Oracle:Visit — Target creature gains flying until end of turn, or until any player rolls a 1, whichever comes first. # --- VARIANTS --- From 8f7a77354aab65df1e125a3709162e660e825086 Mon Sep 17 00:00:00 2001 From: Chris H Date: Sun, 21 Sep 2025 19:07:55 -0400 Subject: [PATCH 018/230] Update apt-get so latest libxml2-utils is available --- .github/workflows/maven-publish.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 808043bb503..877b16b0f1e 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -129,7 +129,9 @@ jobs: makeLatest: true - name: 🔧 Install XML tools - run: sudo apt-get install -y libxml2-utils + run: | + sudo apt-get update + sudo apt-get install -y libxml2-utils - name: 🔼 Bump versionCode in root POM id: bump_version From a2c798ba865792c1d858c1594cb314bb904646e6 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 21 Sep 2025 23:31:36 +0000 Subject: [PATCH 019/230] [maven-release-plugin] prepare release forge-2.0.06 --- adventure-editor/pom.xml | 2 +- forge-ai/pom.xml | 2 +- forge-core/pom.xml | 2 +- forge-game/pom.xml | 2 +- forge-gui-android/pom.xml | 2 +- forge-gui-desktop/pom.xml | 2 +- forge-gui-ios/pom.xml | 2 +- forge-gui-mobile-dev/pom.xml | 2 +- forge-gui-mobile/pom.xml | 2 +- forge-gui/pom.xml | 2 +- forge-installer/pom.xml | 2 +- forge-lda/pom.xml | 2 +- pom.xml | 4 ++-- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/adventure-editor/pom.xml b/adventure-editor/pom.xml index 542d52eb86c..4c79b3ea0b0 100644 --- a/adventure-editor/pom.xml +++ b/adventure-editor/pom.xml @@ -3,7 +3,7 @@ forge forge - ${revision} + 2.0.06 4.0.0 diff --git a/forge-ai/pom.xml b/forge-ai/pom.xml index b51460f8ce7..6ba8b474ba6 100644 --- a/forge-ai/pom.xml +++ b/forge-ai/pom.xml @@ -6,7 +6,7 @@ forge forge - ${revision} + 2.0.06 forge-ai diff --git a/forge-core/pom.xml b/forge-core/pom.xml index 4649f07e8ea..431caf3d3ef 100644 --- a/forge-core/pom.xml +++ b/forge-core/pom.xml @@ -6,7 +6,7 @@ forge forge - ${revision} + 2.0.06 forge-core diff --git a/forge-game/pom.xml b/forge-game/pom.xml index d4c360abda8..cd0c5952ed1 100644 --- a/forge-game/pom.xml +++ b/forge-game/pom.xml @@ -6,7 +6,7 @@ forge forge - ${revision} + 2.0.06 forge-game diff --git a/forge-gui-android/pom.xml b/forge-gui-android/pom.xml index 62059c01379..0626b6ffeaf 100644 --- a/forge-gui-android/pom.xml +++ b/forge-gui-android/pom.xml @@ -23,7 +23,7 @@ forge forge - ${revision} + 2.0.06 forge-gui-android diff --git a/forge-gui-desktop/pom.xml b/forge-gui-desktop/pom.xml index 4e6b5c911a9..c3d04e8f2e4 100644 --- a/forge-gui-desktop/pom.xml +++ b/forge-gui-desktop/pom.xml @@ -4,7 +4,7 @@ forge forge - ${revision} + 2.0.06 forge-gui-desktop diff --git a/forge-gui-ios/pom.xml b/forge-gui-ios/pom.xml index 6b62913cf8a..b09249da91b 100644 --- a/forge-gui-ios/pom.xml +++ b/forge-gui-ios/pom.xml @@ -11,7 +11,7 @@ forge forge - ${revision} + 2.0.06 forge-gui-ios diff --git a/forge-gui-mobile-dev/pom.xml b/forge-gui-mobile-dev/pom.xml index 1c287064cdb..155b6efe24d 100644 --- a/forge-gui-mobile-dev/pom.xml +++ b/forge-gui-mobile-dev/pom.xml @@ -9,7 +9,7 @@ forge forge - ${revision} + 2.0.06 forge-gui-mobile-dev diff --git a/forge-gui-mobile/pom.xml b/forge-gui-mobile/pom.xml index a9ea7c2fdde..8eaac263430 100644 --- a/forge-gui-mobile/pom.xml +++ b/forge-gui-mobile/pom.xml @@ -4,7 +4,7 @@ forge forge - ${revision} + 2.0.06 forge-gui-mobile diff --git a/forge-gui/pom.xml b/forge-gui/pom.xml index 140fe370904..e21d5682229 100644 --- a/forge-gui/pom.xml +++ b/forge-gui/pom.xml @@ -4,7 +4,7 @@ forge forge - ${revision} + 2.0.06 forge-gui diff --git a/forge-installer/pom.xml b/forge-installer/pom.xml index 27fdd666499..fb7a0054005 100644 --- a/forge-installer/pom.xml +++ b/forge-installer/pom.xml @@ -4,7 +4,7 @@ forge forge - ${revision} + 2.0.06 forge-installer diff --git a/forge-lda/pom.xml b/forge-lda/pom.xml index 7cc9377a9d1..c354ec0f326 100644 --- a/forge-lda/pom.xml +++ b/forge-lda/pom.xml @@ -4,7 +4,7 @@ forge forge - ${revision} + 2.0.06 forge-lda diff --git a/pom.xml b/pom.xml index 87d70446855..22541706e77 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ forge pom Forge Parent - ${revision} + 2.0.06 Forge lets you play the card game Magic: The Gathering against a computer opponent using all of the rules. @@ -43,7 +43,7 @@ scm:git:https://github.com/Card-Forge/forge.git scm:git:https://github.com/Card-Forge/forge.git - HEAD + forge-2.0.06 From 54f41a28cd39cd80dbe16869feec00ad8bb10f3f Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 21 Sep 2025 23:31:38 +0000 Subject: [PATCH 020/230] [maven-release-plugin] prepare for next development iteration --- adventure-editor/pom.xml | 2 +- forge-ai/pom.xml | 2 +- forge-core/pom.xml | 2 +- forge-game/pom.xml | 2 +- forge-gui-android/pom.xml | 2 +- forge-gui-desktop/pom.xml | 2 +- forge-gui-ios/pom.xml | 2 +- forge-gui-mobile-dev/pom.xml | 2 +- forge-gui-mobile/pom.xml | 2 +- forge-gui/pom.xml | 2 +- forge-installer/pom.xml | 2 +- forge-lda/pom.xml | 2 +- pom.xml | 4 ++-- 13 files changed, 14 insertions(+), 14 deletions(-) diff --git a/adventure-editor/pom.xml b/adventure-editor/pom.xml index 4c79b3ea0b0..f8e21f10959 100644 --- a/adventure-editor/pom.xml +++ b/adventure-editor/pom.xml @@ -3,7 +3,7 @@ forge forge - 2.0.06 + 2.0.07-SNAPSHOT 4.0.0 diff --git a/forge-ai/pom.xml b/forge-ai/pom.xml index 6ba8b474ba6..5d9c42cc10b 100644 --- a/forge-ai/pom.xml +++ b/forge-ai/pom.xml @@ -6,7 +6,7 @@ forge forge - 2.0.06 + 2.0.07-SNAPSHOT forge-ai diff --git a/forge-core/pom.xml b/forge-core/pom.xml index 431caf3d3ef..fa9aa85e79d 100644 --- a/forge-core/pom.xml +++ b/forge-core/pom.xml @@ -6,7 +6,7 @@ forge forge - 2.0.06 + 2.0.07-SNAPSHOT forge-core diff --git a/forge-game/pom.xml b/forge-game/pom.xml index cd0c5952ed1..9935aaf6869 100644 --- a/forge-game/pom.xml +++ b/forge-game/pom.xml @@ -6,7 +6,7 @@ forge forge - 2.0.06 + 2.0.07-SNAPSHOT forge-game diff --git a/forge-gui-android/pom.xml b/forge-gui-android/pom.xml index 0626b6ffeaf..87be2d52639 100644 --- a/forge-gui-android/pom.xml +++ b/forge-gui-android/pom.xml @@ -23,7 +23,7 @@ forge forge - 2.0.06 + 2.0.07-SNAPSHOT forge-gui-android diff --git a/forge-gui-desktop/pom.xml b/forge-gui-desktop/pom.xml index c3d04e8f2e4..ef8689270e4 100644 --- a/forge-gui-desktop/pom.xml +++ b/forge-gui-desktop/pom.xml @@ -4,7 +4,7 @@ forge forge - 2.0.06 + 2.0.07-SNAPSHOT forge-gui-desktop diff --git a/forge-gui-ios/pom.xml b/forge-gui-ios/pom.xml index b09249da91b..b4a4dabb846 100644 --- a/forge-gui-ios/pom.xml +++ b/forge-gui-ios/pom.xml @@ -11,7 +11,7 @@ forge forge - 2.0.06 + 2.0.07-SNAPSHOT forge-gui-ios diff --git a/forge-gui-mobile-dev/pom.xml b/forge-gui-mobile-dev/pom.xml index 155b6efe24d..90f4f179d95 100644 --- a/forge-gui-mobile-dev/pom.xml +++ b/forge-gui-mobile-dev/pom.xml @@ -9,7 +9,7 @@ forge forge - 2.0.06 + 2.0.07-SNAPSHOT forge-gui-mobile-dev diff --git a/forge-gui-mobile/pom.xml b/forge-gui-mobile/pom.xml index 8eaac263430..6a64b96c0c0 100644 --- a/forge-gui-mobile/pom.xml +++ b/forge-gui-mobile/pom.xml @@ -4,7 +4,7 @@ forge forge - 2.0.06 + 2.0.07-SNAPSHOT forge-gui-mobile diff --git a/forge-gui/pom.xml b/forge-gui/pom.xml index e21d5682229..32470332e03 100644 --- a/forge-gui/pom.xml +++ b/forge-gui/pom.xml @@ -4,7 +4,7 @@ forge forge - 2.0.06 + 2.0.07-SNAPSHOT forge-gui diff --git a/forge-installer/pom.xml b/forge-installer/pom.xml index fb7a0054005..00bae7832ae 100644 --- a/forge-installer/pom.xml +++ b/forge-installer/pom.xml @@ -4,7 +4,7 @@ forge forge - 2.0.06 + 2.0.07-SNAPSHOT forge-installer diff --git a/forge-lda/pom.xml b/forge-lda/pom.xml index c354ec0f326..ca72f2be079 100644 --- a/forge-lda/pom.xml +++ b/forge-lda/pom.xml @@ -4,7 +4,7 @@ forge forge - 2.0.06 + 2.0.07-SNAPSHOT forge-lda diff --git a/pom.xml b/pom.xml index 22541706e77..3580a61cd89 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ forge pom Forge Parent - 2.0.06 + 2.0.07-SNAPSHOT Forge lets you play the card game Magic: The Gathering against a computer opponent using all of the rules. @@ -43,7 +43,7 @@ scm:git:https://github.com/Card-Forge/forge.git scm:git:https://github.com/Card-Forge/forge.git - forge-2.0.06 + HEAD From a6395373f71301a4f59a96e28c370c57229997b3 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 21 Sep 2025 23:40:14 +0000 Subject: [PATCH 021/230] Restore POM files for preparation of next release --- adventure-editor/pom.xml | 2 +- forge-ai/pom.xml | 2 +- forge-core/pom.xml | 2 +- forge-game/pom.xml | 2 +- forge-gui-android/pom.xml | 2 +- forge-gui-desktop/pom.xml | 12 ++++++------ forge-gui-ios/pom.xml | 2 +- forge-gui-mobile-dev/pom.xml | 2 +- forge-gui-mobile/pom.xml | 2 +- forge-gui/pom.xml | 2 +- forge-installer/pom.xml | 2 +- forge-lda/pom.xml | 2 +- pom.xml | 2 +- 13 files changed, 18 insertions(+), 18 deletions(-) diff --git a/adventure-editor/pom.xml b/adventure-editor/pom.xml index f8e21f10959..542d52eb86c 100644 --- a/adventure-editor/pom.xml +++ b/adventure-editor/pom.xml @@ -3,7 +3,7 @@ forge forge - 2.0.07-SNAPSHOT + ${revision} 4.0.0 diff --git a/forge-ai/pom.xml b/forge-ai/pom.xml index 5d9c42cc10b..b51460f8ce7 100644 --- a/forge-ai/pom.xml +++ b/forge-ai/pom.xml @@ -6,7 +6,7 @@ forge forge - 2.0.07-SNAPSHOT + ${revision} forge-ai diff --git a/forge-core/pom.xml b/forge-core/pom.xml index fa9aa85e79d..4649f07e8ea 100644 --- a/forge-core/pom.xml +++ b/forge-core/pom.xml @@ -6,7 +6,7 @@ forge forge - 2.0.07-SNAPSHOT + ${revision} forge-core diff --git a/forge-game/pom.xml b/forge-game/pom.xml index 9935aaf6869..d4c360abda8 100644 --- a/forge-game/pom.xml +++ b/forge-game/pom.xml @@ -6,7 +6,7 @@ forge forge - 2.0.07-SNAPSHOT + ${revision} forge-game diff --git a/forge-gui-android/pom.xml b/forge-gui-android/pom.xml index 87be2d52639..62059c01379 100644 --- a/forge-gui-android/pom.xml +++ b/forge-gui-android/pom.xml @@ -23,7 +23,7 @@ forge forge - 2.0.07-SNAPSHOT + ${revision} forge-gui-android diff --git a/forge-gui-desktop/pom.xml b/forge-gui-desktop/pom.xml index ef8689270e4..e8c5220721e 100644 --- a/forge-gui-desktop/pom.xml +++ b/forge-gui-desktop/pom.xml @@ -4,7 +4,7 @@ forge forge - 2.0.07-SNAPSHOT + ${revision} forge-gui-desktop @@ -340,7 +340,7 @@ try { org.powermock powermock-module-testng-common - 2.0.9 + ${revision} com.beust @@ -380,7 +380,7 @@ try { org.powermock powermock-module-testng - 2.0.9 + ${revision} test @@ -392,7 +392,7 @@ try { org.powermock powermock-core - 2.0.9 + ${revision} test @@ -408,7 +408,7 @@ try { org.powermock powermock-api-mockito2 - 2.0.9 + ${revision} test @@ -420,7 +420,7 @@ try { org.powermock powermock-api-support - 2.0.9 + ${revision} test diff --git a/forge-gui-ios/pom.xml b/forge-gui-ios/pom.xml index b4a4dabb846..6b62913cf8a 100644 --- a/forge-gui-ios/pom.xml +++ b/forge-gui-ios/pom.xml @@ -11,7 +11,7 @@ forge forge - 2.0.07-SNAPSHOT + ${revision} forge-gui-ios diff --git a/forge-gui-mobile-dev/pom.xml b/forge-gui-mobile-dev/pom.xml index 90f4f179d95..1c287064cdb 100644 --- a/forge-gui-mobile-dev/pom.xml +++ b/forge-gui-mobile-dev/pom.xml @@ -9,7 +9,7 @@ forge forge - 2.0.07-SNAPSHOT + ${revision} forge-gui-mobile-dev diff --git a/forge-gui-mobile/pom.xml b/forge-gui-mobile/pom.xml index 6a64b96c0c0..a9ea7c2fdde 100644 --- a/forge-gui-mobile/pom.xml +++ b/forge-gui-mobile/pom.xml @@ -4,7 +4,7 @@ forge forge - 2.0.07-SNAPSHOT + ${revision} forge-gui-mobile diff --git a/forge-gui/pom.xml b/forge-gui/pom.xml index 32470332e03..140fe370904 100644 --- a/forge-gui/pom.xml +++ b/forge-gui/pom.xml @@ -4,7 +4,7 @@ forge forge - 2.0.07-SNAPSHOT + ${revision} forge-gui diff --git a/forge-installer/pom.xml b/forge-installer/pom.xml index 00bae7832ae..27fdd666499 100644 --- a/forge-installer/pom.xml +++ b/forge-installer/pom.xml @@ -4,7 +4,7 @@ forge forge - 2.0.07-SNAPSHOT + ${revision} forge-installer diff --git a/forge-lda/pom.xml b/forge-lda/pom.xml index ca72f2be079..7cc9377a9d1 100644 --- a/forge-lda/pom.xml +++ b/forge-lda/pom.xml @@ -4,7 +4,7 @@ forge forge - 2.0.07-SNAPSHOT + ${revision} forge-lda diff --git a/pom.xml b/pom.xml index 3580a61cd89..a5202d65929 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ src/main/config 17 - 2.0.06 + 2.0.07 -SNAPSHOT From a4378a20e5418a7449c9d1f435392561d9c86197 Mon Sep 17 00:00:00 2001 From: Chris H Date: Sun, 21 Sep 2025 20:46:30 -0400 Subject: [PATCH 022/230] Manually fixing version touches --- forge-gui-desktop/pom.xml | 10 +++++----- pom.xml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/forge-gui-desktop/pom.xml b/forge-gui-desktop/pom.xml index e8c5220721e..4e6b5c911a9 100644 --- a/forge-gui-desktop/pom.xml +++ b/forge-gui-desktop/pom.xml @@ -340,7 +340,7 @@ try { org.powermock powermock-module-testng-common - ${revision} + 2.0.9 com.beust @@ -380,7 +380,7 @@ try { org.powermock powermock-module-testng - ${revision} + 2.0.9 test @@ -392,7 +392,7 @@ try { org.powermock powermock-core - ${revision} + 2.0.9 test @@ -408,7 +408,7 @@ try { org.powermock powermock-api-mockito2 - ${revision} + 2.0.9 test @@ -420,7 +420,7 @@ try { org.powermock powermock-api-support - ${revision} + 2.0.9 test diff --git a/pom.xml b/pom.xml index a5202d65929..cd533232ab3 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ forge pom Forge Parent - 2.0.07-SNAPSHOT + ${revision} Forge lets you play the card game Magic: The Gathering against a computer opponent using all of the rules. From 50b7543933296d5f432fe720e7be46740f9496a0 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 21 Sep 2025 14:43:00 +0200 Subject: [PATCH 023/230] ColorEnum: use ITranslatable interface --- forge-core/src/main/java/forge/card/MagicColor.java | 12 ++++++------ .../src/main/java/forge/deck/DeckRecognizer.java | 6 +++--- forge-core/src/main/java/forge/item/IPaperCard.java | 5 ----- .../src/main/java/forge/util/ITranslatable.java | 8 +++----- forge-game/src/main/java/forge/game/card/Card.java | 4 ++-- .../src/main/java/forge/game/card/CardState.java | 5 +++-- .../src/main/java/forge/game/card/CardView.java | 4 ++-- .../java/forge/game/event/GameEventDoorChanged.java | 3 +-- .../forge/game/replacement/ReplacementEffect.java | 2 +- .../java/forge/game/spellability/SpellAbility.java | 2 +- .../java/forge/game/staticability/StaticAbility.java | 2 +- .../src/main/java/forge/game/trigger/Trigger.java | 4 ++-- .../src/main/java/forge/gui/card/CardDetailUtil.java | 2 +- 13 files changed, 26 insertions(+), 33 deletions(-) diff --git a/forge-core/src/main/java/forge/card/MagicColor.java b/forge-core/src/main/java/forge/card/MagicColor.java index 941a6a821f0..0eb9a7b6470 100644 --- a/forge-core/src/main/java/forge/card/MagicColor.java +++ b/forge-core/src/main/java/forge/card/MagicColor.java @@ -1,6 +1,8 @@ package forge.card; import com.google.common.collect.ImmutableList; + +import forge.util.ITranslatable; import forge.util.Localizer; /** @@ -157,7 +159,7 @@ public final class MagicColor { } } - public enum Color { + public enum Color implements ITranslatable { WHITE(Constant.WHITE, MagicColor.WHITE, "W", "lblWhite"), BLUE(Constant.BLUE, MagicColor.BLUE, "U", "lblBlue"), BLACK(Constant.BLACK, MagicColor.BLACK, "B", "lblBlack"), @@ -188,6 +190,7 @@ public final class MagicColor { } } + @Override public String getName() { return name; } @@ -195,7 +198,8 @@ public final class MagicColor { return shortName; } - public String getLocalizedName() { + @Override + public String getTranslatedName() { return Localizer.getInstance().getMessage(label); } @@ -205,10 +209,6 @@ public final class MagicColor { public String getSymbol() { return symbol; } - @Override - public String toString() { - return getLocalizedName(); - } } } diff --git a/forge-core/src/main/java/forge/deck/DeckRecognizer.java b/forge-core/src/main/java/forge/deck/DeckRecognizer.java index 0e486c85929..1723a1942e4 100644 --- a/forge-core/src/main/java/forge/deck/DeckRecognizer.java +++ b/forge-core/src/main/java/forge/deck/DeckRecognizer.java @@ -994,7 +994,7 @@ public class DeckRecognizer { private static String getMagicColourLabel(MagicColor.Color magicColor) { if (magicColor == null) // Multicolour return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour")); - return String.format("%s %s", magicColor.getLocalizedName(), magicColor.getSymbol()); + return String.format("%s %s", magicColor.getTranslatedName(), magicColor.getSymbol()); } private static final HashMap manaSymbolsMap = new HashMap() {{ @@ -1013,8 +1013,8 @@ public class DeckRecognizer { if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS || magicColor1 == MagicColor.Color.COLORLESS) return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2)); - String localisedName1 = magicColor1.getLocalizedName(); - String localisedName2 = magicColor2.getLocalizedName(); + String localisedName1 = magicColor1.getTranslatedName(); + String localisedName2 = magicColor2.getTranslatedName(); String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColorMask() | magicColor2.getColorMask()); return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol); } diff --git a/forge-core/src/main/java/forge/item/IPaperCard.java b/forge-core/src/main/java/forge/item/IPaperCard.java index 9d16618c2c4..af12c23c78e 100644 --- a/forge-core/src/main/java/forge/item/IPaperCard.java +++ b/forge-core/src/main/java/forge/item/IPaperCard.java @@ -52,9 +52,4 @@ public interface IPaperCard extends InventoryItem, Serializable { default String getUntranslatedType() { return getRules().getType().toString(); } - - @Override - default String getUntranslatedOracle() { - return getRules().getOracleText(); - } } \ No newline at end of file diff --git a/forge-core/src/main/java/forge/util/ITranslatable.java b/forge-core/src/main/java/forge/util/ITranslatable.java index 6bbba27afb3..6f75fcd96cc 100644 --- a/forge-core/src/main/java/forge/util/ITranslatable.java +++ b/forge-core/src/main/java/forge/util/ITranslatable.java @@ -10,13 +10,11 @@ public interface ITranslatable extends IHasName { default String getUntranslatedName() { return getName(); } + default String getTranslatedName() { + return getName(); + } default String getUntranslatedType() { return ""; } - - default String getUntranslatedOracle() { - return ""; - } - } 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 246b405263b..776f9f6a0ae 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -7849,8 +7849,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr return currentState.getUntranslatedType(); } @Override - public String getUntranslatedOracle() { - return currentState.getUntranslatedOracle(); + public String getTranslatedName() { + return CardTranslation.getTranslatedName(this); } @Override diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index bdc892d36b0..57f9cf39896 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -40,6 +40,7 @@ import forge.game.spellability.SpellAbilityPredicates; import forge.game.spellability.SpellPermanent; import forge.game.staticability.StaticAbility; import forge.game.trigger.Trigger; +import forge.util.CardTranslation; import forge.util.ITranslatable; import forge.util.IterableUtil; import forge.util.collect.FCollection; @@ -946,7 +947,7 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable { } @Override - public String getUntranslatedOracle() { - return getOracleText(); + public String getTranslatedName() { + return CardTranslation.getTranslatedName(this); } } diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index ab00531d2e3..52fbf98b263 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -1841,8 +1841,8 @@ public class CardView extends GameEntityView { } @Override - public String getUntranslatedOracle() { - return getOracleText(); + public String getTranslatedName() { + return CardTranslation.getTranslatedName(this); } } diff --git a/forge-game/src/main/java/forge/game/event/GameEventDoorChanged.java b/forge-game/src/main/java/forge/game/event/GameEventDoorChanged.java index 1b5d0ba7874..bf846625b1f 100644 --- a/forge-game/src/main/java/forge/game/event/GameEventDoorChanged.java +++ b/forge-game/src/main/java/forge/game/event/GameEventDoorChanged.java @@ -3,7 +3,6 @@ package forge.game.event; import forge.card.CardStateName; import forge.game.card.Card; import forge.game.player.Player; -import forge.util.CardTranslation; import forge.util.Lang; public record GameEventDoorChanged(Player activatingPlayer, Card card, CardStateName state, boolean unlock) implements GameEvent { @@ -15,7 +14,7 @@ public record GameEventDoorChanged(Player activatingPlayer, Card card, CardState @Override public String toString() { - String doorName = CardTranslation.getTranslatedName(card.getState(state)); + String doorName = card.getState(state).getTranslatedName(); StringBuilder sb = new StringBuilder(); sb.append(activatingPlayer); diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java index f81dbb8f47d..a1c82f30a5e 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java @@ -242,7 +242,7 @@ public abstract class ReplacementEffect extends TriggerReplacementBase { String desc = AbilityUtils.applyDescriptionTextChangeEffects(getParam("Description"), this); ITranslatable nameSource = getHostName(this); desc = CardTranslation.translateMultipleDescriptionText(desc, nameSource); - String translatedName = CardTranslation.getTranslatedName(nameSource); + String translatedName = nameSource.getTranslatedName(); desc = TextUtil.fastReplace(desc, "CARDNAME", translatedName); desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(translatedName)); if (desc.contains("EFFECTSOURCE")) { 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 2f91be78f11..f404a3bb148 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1121,7 +1121,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit if (node.getHostCard() != null && !desc.isEmpty()) { ITranslatable nameSource = getHostName(node); desc = CardTranslation.translateMultipleDescriptionText(desc, nameSource); - String translatedName = CardTranslation.getTranslatedName(nameSource); + String translatedName = nameSource.getTranslatedName(); desc = TextUtil.fastReplace(desc, "CARDNAME", translatedName); desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(translatedName)); if (node.getOriginalHost() != null) { diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java index 957f10a494f..1d362b75a98 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -206,7 +206,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone if (hasParam("Description") && !this.isSuppressed()) { ITranslatable nameSource = getHostName(this); String desc = CardTranslation.translateSingleDescriptionText(getParam("Description"), nameSource); - String translatedName = CardTranslation.getTranslatedName(nameSource); + String translatedName = nameSource.getTranslatedName(); desc = TextUtil.fastReplace(desc, "CARDNAME", translatedName); desc = TextUtil.fastReplace(desc, "NICKNAME", Lang.getInstance().getNickName(translatedName)); diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-game/src/main/java/forge/game/trigger/Trigger.java index 926e12d18f8..21e3ce718ac 100644 --- a/forge-game/src/main/java/forge/game/trigger/Trigger.java +++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java @@ -124,7 +124,7 @@ public abstract class Trigger extends TriggerReplacementBase { String desc = getParam("TriggerDescription"); if (!desc.contains("ABILITY")) { desc = CardTranslation.translateSingleDescriptionText(getParam("TriggerDescription"), nameSource); - String translatedName = CardTranslation.getTranslatedName(nameSource); + String translatedName = nameSource.getTranslatedName(); desc = TextUtil.fastReplace(desc,"CARDNAME", translatedName); desc = TextUtil.fastReplace(desc,"NICKNAME", Lang.getInstance().getNickName(translatedName)); if (desc.contains("ORIGINALHOST") && this.getOriginalHost() != null) { @@ -218,7 +218,7 @@ public abstract class Trigger extends TriggerReplacementBase { result = TextUtil.fastReplace(result, "ABILITY", saDesc); result = CardTranslation.translateMultipleDescriptionText(result, sa.getHostCard()); - String translatedName = CardTranslation.getTranslatedName(sa.getHostCard()); + String translatedName = sa.getHostCard().getTranslatedName(); result = TextUtil.fastReplace(result,"CARDNAME", translatedName); result = TextUtil.fastReplace(result,"NICKNAME", Lang.getInstance().getNickName(translatedName)); } 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 988e762e765..335f37dcb50 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java @@ -451,7 +451,7 @@ public class CardDetailUtil { if (card.getMarkedColors() != null && !card.getMarkedColors().isColorless()) { area.append("\n"); area.append("(").append(Localizer.getInstance().getMessage("lblSelected")).append(": "); - area.append(Lang.joinHomogenous(card.getMarkedColors().stream().map(MagicColor.Color::getLocalizedName).collect(Collectors.toList()))); + area.append(Lang.joinHomogenous(card.getMarkedColors().stream().map(MagicColor.Color::getTranslatedName).collect(Collectors.toList()))); area.append(")"); } From da52bc17e873f5ca74f7bcf8ed640c9bd782d1b9 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Mon, 22 Sep 2025 16:16:57 +0200 Subject: [PATCH 024/230] GameEventAttachment: flip oldTarget and newTarget param (#8761) * GameEventAttachment: flip oldTarget and newTarget param --------- Co-authored-by: tool4EvEr --- forge-game/src/main/java/forge/game/GameEntity.java | 3 --- .../main/java/forge/game/event/GameEventCardAttachment.java | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-game/src/main/java/forge/game/GameEntity.java index fabfe363313..7e51c00234f 100644 --- a/forge-game/src/main/java/forge/game/GameEntity.java +++ b/forge-game/src/main/java/forge/game/GameEntity.java @@ -34,7 +34,6 @@ import forge.game.card.CardCollectionView; import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CounterType; -import forge.game.event.GameEventCardAttachment; import forge.game.keyword.Keyword; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; @@ -196,14 +195,12 @@ public abstract class GameEntity extends GameObject implements IIdentifiable { public final void addAttachedCard(final Card c) { if (attachedCards.add(c)) { updateAttachedCards(); - getGame().fireEvent(new GameEventCardAttachment(c, null, this)); } } public final void removeAttachedCard(final Card c) { if (attachedCards.remove(c)) { updateAttachedCards(); - getGame().fireEvent(new GameEventCardAttachment(c, this, null)); } } diff --git a/forge-game/src/main/java/forge/game/event/GameEventCardAttachment.java b/forge-game/src/main/java/forge/game/event/GameEventCardAttachment.java index 4430a9f9357..c6acf10358b 100644 --- a/forge-game/src/main/java/forge/game/event/GameEventCardAttachment.java +++ b/forge-game/src/main/java/forge/game/event/GameEventCardAttachment.java @@ -3,7 +3,7 @@ package forge.game.event; import forge.game.GameEntity; import forge.game.card.Card; -public record GameEventCardAttachment(Card equipment, GameEntity newTarget, GameEntity oldEntity) implements GameEvent { +public record GameEventCardAttachment(Card equipment, GameEntity oldEntity, GameEntity newTarget) implements GameEvent { @Override public T visit(IGameEventVisitor visitor) { From f6bc6d6742d158913a0265a01ce000a48239781d Mon Sep 17 00:00:00 2001 From: squee1968 <105706641+squee1968@users.noreply.github.com> Date: Tue, 23 Sep 2025 03:00:33 -0500 Subject: [PATCH 025/230] Fix Lady Octopus, Inspired Inventor (#8766) * Fix Lady Octopus, Inspired Inventor --- forge-gui/res/cardsfolder/l/lady_octopus_inspired_inventor.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/l/lady_octopus_inspired_inventor.txt b/forge-gui/res/cardsfolder/l/lady_octopus_inspired_inventor.txt index b12afee52b3..2c6bace341c 100644 --- a/forge-gui/res/cardsfolder/l/lady_octopus_inspired_inventor.txt +++ b/forge-gui/res/cardsfolder/l/lady_octopus_inspired_inventor.txt @@ -5,6 +5,6 @@ PT:0/2 T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 1 | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you draw your first or second card each turn, put an ingenuity counter on NICKNAME. T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 2 | TriggerZones$ Battlefield | Execute$ TrigPutCounter | Secondary$ True | TriggerDescription$ Whenever you draw your first or second card each turn, put an ingenuity counter on NICKNAME. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ INGENUITY | CounterNum$ 1 -A:AB$ Play | Cost$ T | Valid$ Artifact | ValidSA$ Spell.cmcLEX | ValidZone$ Hand | WithoutManaCost$ True | Amount$ 1 | Controller$ You | Optional$ True | SpellDescription$ You may cast an artifact spell from your hand with mana value less than or equal to the number of ingenuity counters on NICKNAME without paying its mana cost. +A:AB$ Play | Cost$ T | Valid$ Artifact.YouOwn | ValidSA$ Spell.cmcLEX | ValidZone$ Hand | WithoutManaCost$ True | Amount$ 1 | Controller$ You | Optional$ True | SpellDescription$ You may cast an artifact spell from your hand with mana value less than or equal to the number of ingenuity counters on NICKNAME without paying its mana cost. SVar:X:Count$CardCounters.INGENUITY Oracle:Whenever you draw your first or second card each turn, put an ingenuity counter on Lady Octopus.\n{T}: You may cast an artifact spell from your hand with mana value less than or equal to the number of ingenuity counters on Lady Octopus without paying its mana cost. From 40629a72a9a3ba0da6e6d947fef5050583838007 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Tue, 23 Sep 2025 19:09:15 +0800 Subject: [PATCH 026/230] update Sentry, update oshi, update android-all, npe prevention --- forge-game/pom.xml | 2 +- forge-gui-android/pom.xml | 8 ++++---- forge-gui-mobile-dev/pom.xml | 2 +- .../src/forge/itemmanager/views/ImageView.java | 18 ++++++++++++++++-- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/forge-game/pom.xml b/forge-game/pom.xml index d4c360abda8..ce1a3dd95a5 100644 --- a/forge-game/pom.xml +++ b/forge-game/pom.xml @@ -32,7 +32,7 @@ io.sentry sentry-logback - 8.19.1 + 8.21.1 org.jgrapht diff --git a/forge-gui-android/pom.xml b/forge-gui-android/pom.xml index 62059c01379..0de3daae9eb 100644 --- a/forge-gui-android/pom.xml +++ b/forge-gui-android/pom.xml @@ -97,7 +97,7 @@ org.robolectric android-all - 15-robolectric-12650502 + 15-robolectric-13954326 provided @@ -156,7 +156,7 @@ io.sentry sentry-android - 8.19.1 + 8.21.1 aar @@ -177,7 +177,7 @@ io.sentry sentry-android-core - 8.19.1 + 8.21.1 aar @@ -201,7 +201,7 @@ io.sentry sentry-android-ndk - 8.19.1 + 8.21.1 aar diff --git a/forge-gui-mobile-dev/pom.xml b/forge-gui-mobile-dev/pom.xml index 1c287064cdb..34ac5a2cfca 100644 --- a/forge-gui-mobile-dev/pom.xml +++ b/forge-gui-mobile-dev/pom.xml @@ -242,7 +242,7 @@ com.github.oshi oshi-core - 6.8.3 + 6.9.0 diff --git a/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java b/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java index 18b921f1bae..324a6460923 100644 --- a/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java +++ b/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java @@ -92,7 +92,12 @@ public class ImageView extends ItemView { private T get(int index) { synchronized (lock) { - return internalList.get(index); + try { + // TODO: Find cause why index is invalid on some cases... + return internalList.get(index); + } catch (Exception e) { + return null; + } } } @@ -579,6 +584,8 @@ public class ImageView extends ItemView { maxPileHeight = 0; for (int j = 0; j < group.piles.size(); j++) { Pile pile = group.piles.get(j); + if (pile == null) + continue; y = pileY; for (int k = 0; k < pile.items.size(); k++) { ItemInfo itemInfo = pile.items.get(k); @@ -588,7 +595,10 @@ public class ImageView extends ItemView { itemInfo.setBounds(x, y, itemWidth, itemHeight); y += dy; } - pile.items.get(pile.items.size() - 1).pos = CardStackPosition.Top; + ItemInfo itemInfo = pile.items.get(pile.items.size() - 1); + if (itemInfo == null) + continue; + itemInfo.pos = CardStackPosition.Top; pileHeight = y + itemHeight - dy - pileY; if (pileHeight > maxPileHeight) { maxPileHeight = pileHeight; @@ -705,9 +715,13 @@ public class ImageView extends ItemView { float relX = x + group.getScrollLeft() - group.getLeft(); float relY = y + getScrollValue(); Pile pile = group.piles.get(j); + if (pile == null) + continue; if (pile.contains(relX, relY)) { for (int k = pile.items.size() - 1; k >= 0; k--) { ItemInfo item = pile.items.get(k); + if (item == null) + continue; if (item.contains(relX, relY)) { return item; } From ab3b00cb656a85881c0a72beae5496bb134a0590 Mon Sep 17 00:00:00 2001 From: Renato Filipe Vidal Santos <45150760+dracontes@users.noreply.github.com> Date: Tue, 23 Sep 2025 17:26:25 +0100 Subject: [PATCH 027/230] Cleanup 2025-09-23 --- .../cardsfolder/a/avatar_aang_aang_master_of_elements.txt | 2 +- forge-gui/res/cardsfolder/b/bagel_and_schmear.txt | 4 ++-- forge-gui/res/cardsfolder/b/ballad_of_the_black_flag.txt | 4 ++-- forge-gui/res/cardsfolder/b/bloodpyre_elemental.txt | 2 +- forge-gui/res/cardsfolder/b/bound_determined.txt | 4 ++-- forge-gui/res/cardsfolder/c/cloakwood_swarmkeeper.txt | 4 ++-- forge-gui/res/cardsfolder/c/corpse_traders.txt | 2 +- forge-gui/res/cardsfolder/d/daily_bugle_building.txt | 2 +- forge-gui/res/cardsfolder/d/draining_whelk.txt | 4 ++-- forge-gui/res/cardsfolder/e/eliminate_the_impossible.txt | 4 ++-- forge-gui/res/cardsfolder/e/eye_for_an_eye.txt | 4 ++-- .../res/cardsfolder/f/flaxen_intruder_welcome_home.txt | 4 ++-- forge-gui/res/cardsfolder/f/frankensteins_monster.txt | 4 ++-- forge-gui/res/cardsfolder/g/gryffs_boon.txt | 4 ++-- forge-gui/res/cardsfolder/i/ignoble_soldier.txt | 4 ++-- forge-gui/res/cardsfolder/i/immersturm_skullcairn.txt | 2 +- forge-gui/res/cardsfolder/i/izzet_steam_maze.txt | 4 ++-- forge-gui/res/cardsfolder/l/luminescent_rain.txt | 4 ++-- forge-gui/res/cardsfolder/m/march_toward_perfection.txt | 2 +- forge-gui/res/cardsfolder/m/mindwhip_sliver.txt | 2 +- forge-gui/res/cardsfolder/m/molten_collapse.txt | 4 ++-- .../res/cardsfolder/o/ob_nixilis_of_the_black_oath.txt | 4 ++-- forge-gui/res/cardsfolder/p/perish_the_thought.txt | 4 ++-- forge-gui/res/cardsfolder/p/pilfering_imp.txt | 2 +- forge-gui/res/cardsfolder/r/rakshasa_vizier.txt | 4 ++-- .../cardsfolder/rebalanced/a-zar_ojanen_scion_of_efrava.txt | 6 +++--- forge-gui/res/cardsfolder/s/shimatsu_the_bloodcloaked.txt | 4 ++-- forge-gui/res/cardsfolder/s/skygames.txt | 2 +- forge-gui/res/cardsfolder/s/smoldering_tar.txt | 2 +- forge-gui/res/cardsfolder/s/squees_revenge.txt | 4 ++-- forge-gui/res/cardsfolder/s/stalking_yeti.txt | 2 +- .../cardsfolder/s/startled_awake_persistent_nightmare.txt | 2 +- forge-gui/res/cardsfolder/s/stonebinders_familiar.txt | 4 ++-- forge-gui/res/cardsfolder/s/surtland_frostpyre.txt | 2 +- forge-gui/res/cardsfolder/t/tahngarths_glare.txt | 4 ++-- forge-gui/res/cardsfolder/t/the_soul_stone.txt | 2 +- .../t/titania_voice_of_gaea_titania_gaea_incarnate.txt | 4 ++-- forge-gui/res/cardsfolder/t/transmogrifying_wand.txt | 2 +- forge-gui/res/cardsfolder/t/trap_essence.txt | 4 ++-- forge-gui/res/cardsfolder/t/trickery_charm.txt | 4 ++-- forge-gui/res/cardsfolder/u/undercity_necrolisk.txt | 2 +- forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt | 4 ++-- forge-gui/res/cardsfolder/v/ventifact_bottle.txt | 2 +- forge-gui/res/cardsfolder/w/woodland_champion.txt | 4 ++-- forge-gui/res/cardsfolder/z/zar_ojanen_scion_of_efrava.txt | 4 ++-- .../tokenscripts/g_x_x_treefolk_warrior_total_forests.txt | 4 ++-- 46 files changed, 76 insertions(+), 76 deletions(-) diff --git a/forge-gui/res/cardsfolder/a/avatar_aang_aang_master_of_elements.txt b/forge-gui/res/cardsfolder/a/avatar_aang_aang_master_of_elements.txt index a3072cef049..8e4e9a77b46 100644 --- a/forge-gui/res/cardsfolder/a/avatar_aang_aang_master_of_elements.txt +++ b/forge-gui/res/cardsfolder/a/avatar_aang_aang_master_of_elements.txt @@ -7,7 +7,7 @@ K:Firebending:2 T:Mode$ ElementalBend | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever you waterbend, earthbend, firebend, or airbend, draw a card. Then if you've done all four this turn, transform CARDNAME. SVar:TrigDraw:DB$ Draw | SubAbility$ DBTransform SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform | ConditionCheckSVar$ X -X:Count$AllFourBend.1.0 +SVar:X:Count$AllFourBend.1.0 Oracle:Flying, firebending 2\nWhenever you waterbend, earthbend, firebend, or airbend, draw a card. Then if you've done all four this turn, transform Avatar Aang. ALTERNATE diff --git a/forge-gui/res/cardsfolder/b/bagel_and_schmear.txt b/forge-gui/res/cardsfolder/b/bagel_and_schmear.txt index ecaa11ff2e2..66285ca9f29 100644 --- a/forge-gui/res/cardsfolder/b/bagel_and_schmear.txt +++ b/forge-gui/res/cardsfolder/b/bagel_and_schmear.txt @@ -1,8 +1,8 @@ Name:Bagel and Schmear ManaCost:1 Types:Artifact Food -A:AB$ PutCounter | PreCostDesc$ Share — | Cost$ W T Sac<1/CARDNAME> | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw | SorcerySpeed$ True | SpellDescription$ Put a +1/+1 counter on up to one target creature. Draw a card. Activate only as a sorcery. -A:AB$ GainLife | PreCostDesc$ Nosh — | Cost$ 2 T Sac<1/CARDNAME> | LifeAmount$ 3 | SubAbility$ DBDraw | SpellDescription$ You gain 3 life and draw a card. +A:AB$ PutCounter | PrecostDesc$ Share — | Cost$ W T Sac<1/CARDNAME> | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw | SorcerySpeed$ True | SpellDescription$ Put a +1/+1 counter on up to one target creature. Draw a card. Activate only as a sorcery. +A:AB$ GainLife | PrecostDesc$ Nosh — | Cost$ 2 T Sac<1/CARDNAME> | LifeAmount$ 3 | SubAbility$ DBDraw | SpellDescription$ You gain 3 life and draw a card. SVar:DBDraw:DB$ Draw DeckHas:Ability$LifeGain|Sacrifice|Counters Oracle:Share — {W}, {T}, Sacrifice this artifact: Put a +1/+1 counter on up to one target creature. Draw a card. Activate only as a sorcery.\nNosh — {2}, {T}, Sacrifice this artifact: You gain 3 life and draw a card. diff --git a/forge-gui/res/cardsfolder/b/ballad_of_the_black_flag.txt b/forge-gui/res/cardsfolder/b/ballad_of_the_black_flag.txt index e0135e23caa..214905940b1 100644 --- a/forge-gui/res/cardsfolder/b/ballad_of_the_black_flag.txt +++ b/forge-gui/res/cardsfolder/b/ballad_of_the_black_flag.txt @@ -5,6 +5,6 @@ K:Chapter:4:DBMill,DBMill,DBMill,DBCostReduction SVar:DBMill:DB$ Mill | NumCards$ 3 | Defined$ You | Imprint$ True | SubAbility$ DBChangeZone SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard,Exile | Destination$ Hand | ChangeType$ Card.Historic+YouOwn+IsImprinted | Hidden$ True | Optional$ True | SubAbility$ DBCleanup | SpellDescription$ Mill three cards. You may put a historic card from among them into your hand. (Artifacts, legendaries, and Sagas are historic.) SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True -SVar:DBCostReduction:DB$ Effect | StaticAbilities$ ReduceSPcost -SVar:ReduceSPcost:Mode$ ReduceCost | ValidCard$ Card.Historic | Type$ Spell | Activator$ You | Amount$ 2 | Description$ Historic spells you cast this turn cost {2} less to cast. +SVar:DBCostReduction:DB$ Effect | StaticAbilities$ ReduceSPCost +SVar:ReduceSPCost:Mode$ ReduceCost | ValidCard$ Card.Historic | Type$ Spell | Activator$ You | Amount$ 2 | Description$ Historic spells you cast this turn cost {2} less to cast. Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.)\nI, II, III — Mill three cards. You may put a historic card from among them into your hand. (Artifacts, legendaries, and Sagas are historic.)\nIV — Historic spells you cast this turn cost {2} less to cast. diff --git a/forge-gui/res/cardsfolder/b/bloodpyre_elemental.txt b/forge-gui/res/cardsfolder/b/bloodpyre_elemental.txt index 0025d902e7d..fcc0db5613d 100644 --- a/forge-gui/res/cardsfolder/b/bloodpyre_elemental.txt +++ b/forge-gui/res/cardsfolder/b/bloodpyre_elemental.txt @@ -2,6 +2,6 @@ Name:Bloodpyre Elemental ManaCost:4 R Types:Creature Elemental PT:4/1 -A:AB$ DealDamage | Cost$ Sac<1/CARDNAME> | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 4 | SorcerySpeed$ True | SpellDescription$ It deals 4 damage to target creature. Activate only any time you could cast a sorcery. +A:AB$ DealDamage | Cost$ Sac<1/CARDNAME> | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 4 | SorcerySpeed$ True | SpellDescription$ It deals 4 damage to target creature. Activate only as a sorcery. DeckHas:Ability$Sacrifice Oracle:Sacrifice Bloodpyre Elemental: It deals 4 damage to target creature. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/b/bound_determined.txt b/forge-gui/res/cardsfolder/b/bound_determined.txt index 4930864f9da..6ae44ecde59 100644 --- a/forge-gui/res/cardsfolder/b/bound_determined.txt +++ b/forge-gui/res/cardsfolder/b/bound_determined.txt @@ -15,7 +15,7 @@ ALTERNATE Name:Determined ManaCost:G U Types:Instant -A:SP$ Effect | ReplacementEffects$ CantbeCountered | SubAbility$ DBDraw | SpellDescription$ Other spells you control can't be countered this turn. Draw a card. -SVar:CantbeCountered:Event$ Counter | ValidSA$ Spell.YouCtrl | Layer$ CantHappen | Description$ Other spells you control can't be countered this turn. +A:SP$ Effect | ReplacementEffects$ CantBeCountered | SubAbility$ DBDraw | SpellDescription$ Other spells you control can't be countered this turn. Draw a card. +SVar:CantBeCountered:Event$ Counter | ValidSA$ Spell.YouCtrl | Layer$ CantHappen | Description$ Other spells you control can't be countered this turn. SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 Oracle:Other spells you control can't be countered this turn.\nDraw a card. diff --git a/forge-gui/res/cardsfolder/c/cloakwood_swarmkeeper.txt b/forge-gui/res/cardsfolder/c/cloakwood_swarmkeeper.txt index 16eeb056faa..e542d0eb53f 100644 --- a/forge-gui/res/cardsfolder/c/cloakwood_swarmkeeper.txt +++ b/forge-gui/res/cardsfolder/c/cloakwood_swarmkeeper.txt @@ -2,8 +2,8 @@ Name:Cloakwood Swarmkeeper ManaCost:G Types:Creature Elf Ranger PT:1/1 -T:Mode$ ChangesZoneAll | ValidCards$ Card.token+YouCtrl | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigPutcounter | TriggerDescription$ Gathered Swarm — Whenever one or more tokens you control enter, put a +1/+1 counter on CARDNAME. -SVar:TrigPutcounter:DB$ PutCounter | CounterType$ P1P1 | Defined$ Self | CounterNum$ 1 +T:Mode$ ChangesZoneAll | ValidCards$ Card.token+YouCtrl | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Gathered Swarm — Whenever one or more tokens you control enter, put a +1/+1 counter on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | Defined$ Self | CounterNum$ 1 DeckHints:Ability$Token DeckHas:Ability$Counters Oracle:Gathered Swarm — Whenever one or more tokens you control enter, put a +1/+1 counter on Cloakwood Swarmkeeper. diff --git a/forge-gui/res/cardsfolder/c/corpse_traders.txt b/forge-gui/res/cardsfolder/c/corpse_traders.txt index 27aadf2f91b..bbc5544c279 100644 --- a/forge-gui/res/cardsfolder/c/corpse_traders.txt +++ b/forge-gui/res/cardsfolder/c/corpse_traders.txt @@ -2,6 +2,6 @@ Name:Corpse Traders ManaCost:3 B Types:Creature Human Rogue PT:3/3 -A:AB$ Discard | Cost$ 2 B Sac<1/Creature> | ValidTgts$ Player | Mode$ RevealYouChoose | NumCards$ 1 | SorcerySpeed$ True | SpellDescription$ Target player reveals their hand. You choose a card from it. That player discards that card. Activate only any time you could cast a sorcery. +A:AB$ Discard | Cost$ 2 B Sac<1/Creature> | ValidTgts$ Player | Mode$ RevealYouChoose | NumCards$ 1 | SorcerySpeed$ True | SpellDescription$ Target player reveals their hand. You choose a card from it. That player discards that card. Activate only as a sorcery. AI:RemoveDeck:All Oracle:{2}{B}, Sacrifice a creature: Target opponent reveals their hand. You choose a card from it. That player discards that card. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/d/daily_bugle_building.txt b/forge-gui/res/cardsfolder/d/daily_bugle_building.txt index b4991b7747a..fe1ae148c0e 100644 --- a/forge-gui/res/cardsfolder/d/daily_bugle_building.txt +++ b/forge-gui/res/cardsfolder/d/daily_bugle_building.txt @@ -3,6 +3,6 @@ ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. A:AB$ Mana | Cost$ 1 T | Produced$ Any | Amount$ 1 | SpellDescription$ Add one mana of any color. -A:AB$ Pump | PreCostDesc$ Smear Campaign — | Cost$ 1 T | ValidTgts$ Creature.Legendary | TgtPrompt$ Select target legendary creature | KW$ Menace | SorcerySpeed$ True | SpellDescription$ Target legendary creature gains menace until end of turn. Activate only as a sorcery. +A:AB$ Pump | PrecostDesc$ Smear Campaign — | Cost$ 1 T | ValidTgts$ Creature.Legendary | TgtPrompt$ Select target legendary creature | KW$ Menace | SorcerySpeed$ True | SpellDescription$ Target legendary creature gains menace until end of turn. Activate only as a sorcery. DeckHints:Type$Legendary Oracle:{T}: Add {C}.\n{1}, {T}: Add one mana of any color.\nSmear Campaign — {1}, {T}: Target legendary creature gains menace until end of turn. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/d/draining_whelk.txt b/forge-gui/res/cardsfolder/d/draining_whelk.txt index fbf48502e5f..07d32022cb7 100644 --- a/forge-gui/res/cardsfolder/d/draining_whelk.txt +++ b/forge-gui/res/cardsfolder/d/draining_whelk.txt @@ -5,8 +5,8 @@ PT:1/1 K:Flash K:Flying T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigCounter | TriggerDescription$ When CARDNAME enters, counter target spell. Put X +1/+1 counters on CARDNAME, where X is that spell's mana value. -SVar:TrigCounter:DB$ Counter | TargetType$ Spell | ValidTgts$ Card | TgtPrompt$ Select target spell | RememberCounteredCMC$ True | SubAbility$ DBPutcounter -SVar:DBPutcounter:DB$ PutCounter | CounterType$ P1P1 | Defined$ Self | CounterNum$ X | SubAbility$ DBCleanup +SVar:TrigCounter:DB$ Counter | TargetType$ Spell | ValidTgts$ Card | TgtPrompt$ Select target spell | RememberCounteredCMC$ True | SubAbility$ DBPutCounter +SVar:DBPutCounter:DB$ PutCounter | CounterType$ P1P1 | Defined$ Self | CounterNum$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$RememberedNumber Oracle:Flash\nFlying\nWhen Draining Whelk enters, counter target spell. Put X +1/+1 counters on Draining Whelk, where X is that spell's mana value. diff --git a/forge-gui/res/cardsfolder/e/eliminate_the_impossible.txt b/forge-gui/res/cardsfolder/e/eliminate_the_impossible.txt index 56a84754772..5461b41ed77 100644 --- a/forge-gui/res/cardsfolder/e/eliminate_the_impossible.txt +++ b/forge-gui/res/cardsfolder/e/eliminate_the_impossible.txt @@ -2,7 +2,7 @@ Name:Eliminate the Impossible ManaCost:1 U Types:Instant A:SP$ Investigate | SubAbility$ DBDebuff | SpellDescription$ Investigate. Creatures your opponents control get -2/-0 until end of turn. If any of them are suspected, they're no longer suspected. (To investigate, create a Clue token. It's an artifact with "{2}, Sacrifice this artifact: Draw a card.") -SVar:DBDebuff:DB$ PumpAll | ValidCards$ Creature.OppCtrl | NumAtt$ -2 | SubAbility$ DBAlterAtribute -SVar:DBAlterAtribute:DB$ AlterAttribute | Defined$ Valid Creature.OppCtrl+IsSuspected | Attributes$ Suspected | Activate$ False +SVar:DBDebuff:DB$ PumpAll | ValidCards$ Creature.OppCtrl | NumAtt$ -2 | SubAbility$ DBAlterAttribute +SVar:DBAlterAttribute:DB$ AlterAttribute | Defined$ Valid Creature.OppCtrl+IsSuspected | Attributes$ Suspected | Activate$ False DeckHas:Ability$Token & Type$Artifact|Clue Oracle:Investigate. Creatures your opponents control get -2/-0 until end of turn. If any of them are suspected, they're no longer suspected. (To investigate, create a Clue token. It's an artifact with "{2}, Sacrifice this artifact: Draw a card.") diff --git a/forge-gui/res/cardsfolder/e/eye_for_an_eye.txt b/forge-gui/res/cardsfolder/e/eye_for_an_eye.txt index 906d10f13ea..c60cd0bbb48 100644 --- a/forge-gui/res/cardsfolder/e/eye_for_an_eye.txt +++ b/forge-gui/res/cardsfolder/e/eye_for_an_eye.txt @@ -4,8 +4,8 @@ Types:Instant A:SP$ ChooseSource | Choices$ Card,Emblem | AILogic$ NeedsPrevention | SubAbility$ DBEffect | SpellDescription$ The next time a source of your choice would deal damage to you this turn, instead that source deals that much damage to you and CARDNAME deals that much damage to that source's controller. SVar:DBEffect:DB$ Effect | ReplacementEffects$ SelflessDamage | ImprintCards$ Self | ConditionDefined$ ChosenCard | ConditionPresent$ Card,Emblem | ConditionCompare$ GE1 SVar:SelflessDamage:Event$ DamageDone | ValidTarget$ You | ValidSource$ Card.ChosenCardStrict,Emblem.ChosenCard | ReplaceWith$ SelflessDmg | Description$ The next time a source of your choice would deal damage to you this turn, instead that source deals that much damage to you and this card deals that much damage to that source's controller. -SVar:SelflessDmg:DB$ ReplaceEffect | VarName$ Affected | VarValue$ You | VarType$ Player | SubAbility$ EyeforEye -SVar:EyeforEye:DB$ DealDamage | Defined$ ReplacedSourceController | DamageSource$ EffectSource | NumDmg$ X | SubAbility$ ExileEffect +SVar:SelflessDmg:DB$ ReplaceEffect | VarName$ Affected | VarValue$ You | VarType$ Player | SubAbility$ EyeForEye +SVar:EyeForEye:DB$ DealDamage | Defined$ ReplacedSourceController | DamageSource$ EffectSource | NumDmg$ X | SubAbility$ ExileEffect SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile SVar:X:ReplaceCount$DamageAmount Oracle:The next time a source of your choice would deal damage to you this turn, instead that source deals that much damage to you and Eye for an Eye deals that much damage to that source's controller. diff --git a/forge-gui/res/cardsfolder/f/flaxen_intruder_welcome_home.txt b/forge-gui/res/cardsfolder/f/flaxen_intruder_welcome_home.txt index 24f2f60d9b1..0960e36a934 100644 --- a/forge-gui/res/cardsfolder/f/flaxen_intruder_welcome_home.txt +++ b/forge-gui/res/cardsfolder/f/flaxen_intruder_welcome_home.txt @@ -2,8 +2,8 @@ Name:Flaxen Intruder ManaCost:G Types:Creature Human Berserker PT:1/2 -T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ Trigtrig | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, you may sacrifice it. When you do, destroy target artifact or enchantment. -SVar:Trigtrig:AB$ ImmediateTrigger | Cost$ Sac<1/CARDNAME> | Execute$ TrigDestroy | TriggerDescription$ When you do, destroy target artifact or enchantment. +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigTrig | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, you may sacrifice it. When you do, destroy target artifact or enchantment. +SVar:TrigTrig:AB$ ImmediateTrigger | Cost$ Sac<1/CARDNAME> | Execute$ TrigDestroy | TriggerDescription$ When you do, destroy target artifact or enchantment. SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment AlternateMode:Adventure Oracle:Whenever Flaxen Intruder deals combat damage to a player, you may sacrifice it. When you do, destroy target artifact or enchantment. diff --git a/forge-gui/res/cardsfolder/f/frankensteins_monster.txt b/forge-gui/res/cardsfolder/f/frankensteins_monster.txt index dd2d294c307..2a666c18e96 100644 --- a/forge-gui/res/cardsfolder/f/frankensteins_monster.txt +++ b/forge-gui/res/cardsfolder/f/frankensteins_monster.txt @@ -4,8 +4,8 @@ Types:Creature Zombie PT:0/1 R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ ExileCreature | Description$ As CARDNAME enters, exile X creature cards from your graveyard. If you can't, put CARDNAME into its owner's graveyard instead of onto the battlefield. For each creature card exiled this way, CARDNAME enters with a +2/+0, +1/+1, or +0/+2 counter on it. SVar:ExileCreature:DB$ ChooseCard | ETB$ True | Choices$ Creature.YouOwn+NotDefinedReplacedSimultaneousETB | ChoiceZone$ Graveyard | Amount$ X | Mandatory$ True | ConditionCheckSVar$ CheckYard | ConditionSVarCompare$ GEX | SubAbility$ DBExile -SVar:DBExile:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Graveyard | Destination$ Exile | RememberChanged$ True | SubAbility$ Movetoyard -SVar:Movetoyard:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Graveyard | Defined$ Self | ConditionCheckSVar$ CheckExiled | ConditionSVarCompare$ LTX | Imprint$ True | ETB$ True | SubAbility$ DBChooseCounter +SVar:DBExile:DB$ ChangeZone | Defined$ ChosenCard | Origin$ Graveyard | Destination$ Exile | RememberChanged$ True | SubAbility$ MoveToYard +SVar:MoveToYard:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Graveyard | Defined$ Self | ConditionCheckSVar$ CheckExiled | ConditionSVarCompare$ LTX | Imprint$ True | ETB$ True | SubAbility$ DBChooseCounter SVar:DBChooseCounter:DB$ PutCounter | CounterTypes$ P2P0,P1P1,P0P2 | CounterNum$ X | SplitAmount$ True | ETB$ True | ConditionDefined$ Imprinted | ConditionPresent$ Card | ConditionCompare$ EQ0 | SubAbility$ DBUpdate SVar:DBUpdate:DB$ ReplaceEffect | ConditionDefined$ Imprinted | ConditionPresent$ Card | ConditionCompare$ EQ0 | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True diff --git a/forge-gui/res/cardsfolder/g/gryffs_boon.txt b/forge-gui/res/cardsfolder/g/gryffs_boon.txt index 0aadaff4fb1..323abdef6fb 100644 --- a/forge-gui/res/cardsfolder/g/gryffs_boon.txt +++ b/forge-gui/res/cardsfolder/g/gryffs_boon.txt @@ -4,6 +4,6 @@ Types:Enchantment Aura K:Enchant:Creature SVar:AttachAILogic:Pump S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 1 | AddKeyword$ Flying | Description$ Enchanted creature gets +1/+0 and has flying. -A:AB$ Pump | Cost$ 3 W | ActivationZone$ Graveyard | ValidTgts$ Creature | SorcerySpeed$ True | TgtPrompt$ Choose a creature | SubAbility$ DBChange | SpellDescription$ Return CARDNAME from your graveyard to the battlefield attached to target creature. Activate only any time you could cast a sorcery. | StackDescription$ SpellDescription -SVar:DBChange:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield | AttachedTo$ ParentTarget | SpellDescription$ | StackDescription$ SpellDescription +A:AB$ Pump | Cost$ 3 W | ActivationZone$ Graveyard | ValidTgts$ Creature | SorcerySpeed$ True | TgtPrompt$ Choose a creature | SubAbility$ DBChange | StackDescription$ REP Return_{p:You} returns & from your_from their & to target creature. Activate only as a sorcery._to {c:Targeted}. | SpellDescription$ Return CARDNAME from your graveyard to the battlefield attached to target creature. Activate only as a sorcery. +SVar:DBChange:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield | AttachedTo$ ParentTarget | StackDescription$ None Oracle:Enchant creature\nEnchanted creature gets +1/+0 and has flying.\n{3}{W}: Return Gryff's Boon from your graveyard to the battlefield attached to target creature. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/i/ignoble_soldier.txt b/forge-gui/res/cardsfolder/i/ignoble_soldier.txt index d38c4b07914..b8930c03711 100644 --- a/forge-gui/res/cardsfolder/i/ignoble_soldier.txt +++ b/forge-gui/res/cardsfolder/i/ignoble_soldier.txt @@ -2,8 +2,8 @@ Name:Ignoble Soldier ManaCost:2 W Types:Creature Human Soldier PT:3/1 -T:Mode$ AttackerBlocked | ValidCard$ Card.Self | Execute$ TrigNodamage | TriggerDescription$ Whenever CARDNAME becomes blocked, prevent all combat damage that would be dealt by it this turn. -SVar:TrigNodamage:DB$ Effect | ReplacementEffects$ RPrevent | RememberObjects$ TriggeredAttackerLKICopy | ExileOnMoved$ Battlefield +T:Mode$ AttackerBlocked | ValidCard$ Card.Self | Execute$ TrigNoDamage | TriggerDescription$ Whenever CARDNAME becomes blocked, prevent all combat damage that would be dealt by it this turn. +SVar:TrigNoDamage:DB$ Effect | ReplacementEffects$ RPrevent | RememberObjects$ TriggeredAttackerLKICopy | ExileOnMoved$ Battlefield SVar:RPrevent:Event$ DamageDone | Prevent$ True | IsCombat$ True | ValidSource$ Card.IsRemembered | Description$ Prevent all combat damage that would be dealt by it this turn. SVar:MustBeBlocked:True Oracle:Whenever Ignoble Soldier becomes blocked, prevent all combat damage that would be dealt by it this turn. diff --git a/forge-gui/res/cardsfolder/i/immersturm_skullcairn.txt b/forge-gui/res/cardsfolder/i/immersturm_skullcairn.txt index 80dfd030e84..6d2a6eb4ff7 100644 --- a/forge-gui/res/cardsfolder/i/immersturm_skullcairn.txt +++ b/forge-gui/res/cardsfolder/i/immersturm_skullcairn.txt @@ -4,7 +4,7 @@ Types:Land R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}. -A:AB$ DealDamage | Cost$ 1 B R R T Sac<1/CARDNAME> | ValidTgts$ Player | TgtPrompt$ Select target player | NumDmg$ 3 | SubAbility$ DBDiscard | SorcerySpeed$ True | SpellDescription$ It deals 3 damage to target player. That player discards a card. Activate only any time you could cast a sorcery. +A:AB$ DealDamage | Cost$ 1 B R R T Sac<1/CARDNAME> | ValidTgts$ Player | TgtPrompt$ Select target player | NumDmg$ 3 | SubAbility$ DBDiscard | SorcerySpeed$ True | SpellDescription$ It deals 3 damage to target player. That player discards a card. Activate only as a sorcery. SVar:DBDiscard:DB$ Discard | Defined$ TargetedPlayer | NumCards$ 1 | Mode$ TgtChoose DeckHas:Ability$Sacrifice Oracle:Immersturm Skullcairn enters tapped.\n{T}: Add {B}.\n{1}{B}{R}{R}, {T}, Sacrifice Immersturm Skullcairn: It deals 3 damage to target player. That player discards a card. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/i/izzet_steam_maze.txt b/forge-gui/res/cardsfolder/i/izzet_steam_maze.txt index 4ce5a51f885..4b2571dcd38 100644 --- a/forge-gui/res/cardsfolder/i/izzet_steam_maze.txt +++ b/forge-gui/res/cardsfolder/i/izzet_steam_maze.txt @@ -4,7 +4,7 @@ Types:Plane Ravnica T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ Player | Execute$ TrigCopy | TriggerZones$ Command | TriggerDescription$ Whenever a player casts an instant or sorcery spell, that player copies it. The player may choose new targets for the copy. SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Controller$ TriggeredActivator | MayChooseTarget$ True T:Mode$ ChaosEnsues | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever chaos ensues, instant and sorcery spells you cast this turn cost {3} less to cast. -SVar:RolledChaos:DB$ Effect | StaticAbilities$ ReduceSPcost -SVar:ReduceSPcost:Mode$ ReduceCost | ValidCard$ Instant,Sorcery | Type$ Spell | Activator$ You | Amount$ 3 | Description$ Instant and sorcery spells you cast this turn cost {3} less to cast. +SVar:RolledChaos:DB$ Effect | StaticAbilities$ ReduceSPCost +SVar:ReduceSPCost:Mode$ ReduceCost | ValidCard$ Instant,Sorcery | Type$ Spell | Activator$ You | Amount$ 3 | Description$ Instant and sorcery spells you cast this turn cost {3} less to cast. SVar:AIRollPlanarDieParams:Mode$ Always | RollInMain1$ True Oracle:Whenever a player casts an instant or sorcery spell, that player copies it. The player may choose new targets for the copy.\nWhenever chaos ensues, instant and sorcery spells you cast this turn cost {3} less to cast. diff --git a/forge-gui/res/cardsfolder/l/luminescent_rain.txt b/forge-gui/res/cardsfolder/l/luminescent_rain.txt index 8839c36b53d..f3180e33614 100644 --- a/forge-gui/res/cardsfolder/l/luminescent_rain.txt +++ b/forge-gui/res/cardsfolder/l/luminescent_rain.txt @@ -1,8 +1,8 @@ Name:Luminescent Rain ManaCost:2 G Types:Instant -A:SP$ ChooseType | Defined$ You | Type$ Creature | AILogic$ MostProminentComputerControls | SubAbility$ DBGainlife | SpellDescription$ Choose a creature type. You gain 2 life for each permanent you control of that type. -SVar:DBGainlife:DB$ Gainlife | LifeAmount$ X | StackDescription$ You gain 2 life for each permanent you control of that type. +A:SP$ ChooseType | Defined$ You | Type$ Creature | AILogic$ MostProminentComputerControls | SubAbility$ DBGainLife | SpellDescription$ Choose a creature type. You gain 2 life for each permanent you control of that type. +SVar:DBGainLife:DB$ GainLife | LifeAmount$ X | StackDescription$ You gain 2 life for each permanent you control of that type. SVar:X:Count$Valid Permanent.ChosenType+YouCtrl/Times.2 SVar:NeedsToPlay:Creature Oracle:Choose a creature type. You gain 2 life for each permanent you control of that type. diff --git a/forge-gui/res/cardsfolder/m/march_toward_perfection.txt b/forge-gui/res/cardsfolder/m/march_toward_perfection.txt index 8aedc9d06fd..870985aa2b7 100644 --- a/forge-gui/res/cardsfolder/m/march_toward_perfection.txt +++ b/forge-gui/res/cardsfolder/m/march_toward_perfection.txt @@ -7,6 +7,6 @@ SVar:ReplEffAddCounter:DB$ Effect | ReplacementEffects$ ETBAddCounter | Remember SVar:ETBAddCounter:Event$ Moved | Origin$ Stack | Destination$ Battlefield | ValidCard$ Card.IsRemembered | ReplaceWith$ ETBAddExtraCounter | ReplacementResult$ Updated SVar:ETBAddExtraCounter:DB$ PutCounter | ETB$ True | Defined$ ReplacedCard | CounterTypes$ P1P1,Deathtouch | CounterNum$ 1 SVar:DBDraft:DB$ Draft | Spellbook$ Entomber Exarch,Phyrexian Fleshgorger,Phyrexian Gargantua,Phyrexian Obliterator,Phyrexian Rager,Phyrexian Revoker,Toxic Abomination,Vault Skirge,Scrapwork Rager,Bilious Skulldweller,Archfiend of the Dross,Myr Convert,Zenith Chronicler,Soulless Jailer,Diminished Returner | SpellDescription$ Draft a card from CARDNAME's spellbook. -DeckHas:Ability$Counters|Lifegain|Graveyard & Type$Phyrexian|Horror|Imp|Zombie|Insect|Demon|Insect +DeckHas:Ability$Counters|LifeGain|Graveyard & Type$Phyrexian|Horror|Imp|Zombie|Insect|Demon|Insect DeckNeeds:Type$Phyrexian Oracle:You get a boon with "When you cast your next Phyrexian creature spell, that creature enters with an additional +1/+1 counter and deathtouch counter on it."\nDraft a card from March Toward Perfection's spellbook. diff --git a/forge-gui/res/cardsfolder/m/mindwhip_sliver.txt b/forge-gui/res/cardsfolder/m/mindwhip_sliver.txt index 12a690a3adb..753f09f017d 100644 --- a/forge-gui/res/cardsfolder/m/mindwhip_sliver.txt +++ b/forge-gui/res/cardsfolder/m/mindwhip_sliver.txt @@ -3,5 +3,5 @@ ManaCost:2 B Types:Creature Sliver PT:2/2 S:Mode$ Continuous | Affected$ Sliver | AddAbility$ Discard | Description$ All Slivers have "{2}, Sacrifice this permanent: Target player discards a card at random. Activate only as a sorcery." -SVar:Discard:AB$ Discard | Cost$ 2 Sac<1/CARDNAME> | ValidTgts$ Player | TgtPrompt$ Select target player | NumCards$ 1 | Mode$ Random | SpellDescription$ Target player discards a card at random. Activate only any time you could cast a sorcery. +SVar:Discard:AB$ Discard | Cost$ 2 Sac<1/CARDNAME> | ValidTgts$ Player | TgtPrompt$ Select target player | NumCards$ 1 | Mode$ Random | SpellDescription$ Target player discards a card at random. Activate only as a sorcery. Oracle:All Slivers have "{2}, Sacrifice this permanent: Target player discards a card at random. Activate only as a sorcery." diff --git a/forge-gui/res/cardsfolder/m/molten_collapse.txt b/forge-gui/res/cardsfolder/m/molten_collapse.txt index 3a94f2fd44b..ab49c22cd30 100644 --- a/forge-gui/res/cardsfolder/m/molten_collapse.txt +++ b/forge-gui/res/cardsfolder/m/molten_collapse.txt @@ -1,9 +1,9 @@ Name:Molten Collapse ManaCost:B R Types:Sorcery -A:SP$ Charm | MinCharmNum$ 1 | CharmNum$ Count$Compare Y GE1.2.1 | Choices$ DBDestroy,DBDsestroynoncreatue | AdditionalDescription$ . If you descended this turn, you may choose both instead. (You descended if a permanent card was put into your graveyard from anywhere.) +A:SP$ Charm | MinCharmNum$ 1 | CharmNum$ Count$Compare Y GE1.2.1 | Choices$ DBDestroy,DBDestroyNonCreature | AdditionalDescription$ . If you descended this turn, you may choose both instead. (You descended if a permanent card was put into your graveyard from anywhere.) SVar:DBDestroy:DB$ Destroy | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker | SpellDescription$ Destroy target creature or planeswalker. -SVar:DBDsestroynoncreatue:DB$ Destroy | ValidTgts$ Permanent.nonCreature+nonLand+cmcLE1 | TgtPrompt$ Select target noncreature, nonland permanent with mana value 1 or less | SpellDescription$ Destroy target noncreature, nonland permanent with mana value 1 or less. +SVar:DBDestroyNonCreature:DB$ Destroy | ValidTgts$ Permanent.nonCreature+nonLand+cmcLE1 | TgtPrompt$ Select target noncreature, nonland permanent with mana value 1 or less | SpellDescription$ Destroy target noncreature, nonland permanent with mana value 1 or less. SVar:Y:Count$YouDescendedThisTurn DeckHints:Ability$Mill|Graveyard|Dredge|Sacrifice Oracle:Choose one. If you descended this turn, you may choose both instead. (You descended if a permanent card was put into your graveyard from anywhere.)\n• Destroy target creature or planeswalker.\n• Destroy target noncreature, nonland permanent with mana value 1 or less. diff --git a/forge-gui/res/cardsfolder/o/ob_nixilis_of_the_black_oath.txt b/forge-gui/res/cardsfolder/o/ob_nixilis_of_the_black_oath.txt index 0bb6222a19c..28265705d08 100644 --- a/forge-gui/res/cardsfolder/o/ob_nixilis_of_the_black_oath.txt +++ b/forge-gui/res/cardsfolder/o/ob_nixilis_of_the_black_oath.txt @@ -6,8 +6,8 @@ Text:CARDNAME can be your commander. A:AB$ LoseLife | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | Defined$ Player.Opponent | LifeAmount$ 1 | SubAbility$ DBGainLife | SpellDescription$ Each opponent loses 1 life. You gain life equal to the life lost this way. | StackDescription$ SpellDescription SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ AFLifeLost | StackDescription$ None SVar:AFLifeLost:Number$0 -A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ b_5_5_demon_flying | TokenOwner$ You | SubAbility$ DBLoselife | SpellDescription$ Create a 5/5 black Demon creature token with flying. You lose 2 life. -SVar:DBLoselife:DB$ LoseLife | LifeAmount$ 2 +A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ b_5_5_demon_flying | TokenOwner$ You | SubAbility$ DBLoseLife | SpellDescription$ Create a 5/5 black Demon creature token with flying. You lose 2 life. +SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 2 A:AB$ Effect | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | Name$ Emblem — Ob Nixilis of the Black Oath | Image$ emblem_ob_nixilis_of_the_black_oath | Stackable$ False | Abilities$ ObGainLife | Duration$ Permanent | SpellDescription$ You get an emblem with "{1}{B}, Sacrifice a creature: You gain X life and draw X cards, where X is the sacrificed creature's power." SVar:ObGainLife:AB$ GainLife | Cost$ 1 B Sac<1/Creature> | ActivationZone$ Command | LifeAmount$ X | SubAbility$ DBDraw | SpellDescription$ You gain X life and draw X cards, where X is the sacrificed creature's power. SVar:DBDraw:DB$ Draw | NumCards$ X diff --git a/forge-gui/res/cardsfolder/p/perish_the_thought.txt b/forge-gui/res/cardsfolder/p/perish_the_thought.txt index 06b70ed0a78..8bc23bd9df2 100644 --- a/forge-gui/res/cardsfolder/p/perish_the_thought.txt +++ b/forge-gui/res/cardsfolder/p/perish_the_thought.txt @@ -1,6 +1,6 @@ Name:Perish the Thought ManaCost:2 B Types:Sorcery -A:SP$ RevealHand | Defined$ Targeted | ValidTgts$ Opponent | SubAbility$ ShuffleCardtoLib | StackDescription$ SpellDescription | SpellDescription$ Target opponent reveals their hand. -SVar:ShuffleCardtoLib:DB$ ChangeZone | Origin$ Hand | Destination$ Library | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | Shuffle$ True | Mandatory$ True | AlreadyRevealed$ True | StackDescription$ SpellDescription | SpellDescription$ You choose a card from it. That player shuffles that card into their library. +A:SP$ RevealHand | Defined$ Targeted | ValidTgts$ Opponent | SubAbility$ ShuffleCardToLib | StackDescription$ SpellDescription | SpellDescription$ Target opponent reveals their hand. +SVar:ShuffleCardToLib:DB$ ChangeZone | Origin$ Hand | Destination$ Library | DefinedPlayer$ Targeted | Chooser$ You | ChangeType$ Card | ChangeNum$ 1 | IsCurse$ True | Shuffle$ True | Mandatory$ True | AlreadyRevealed$ True | StackDescription$ SpellDescription | SpellDescription$ You choose a card from it. That player shuffles that card into their library. Oracle:Target opponent reveals their hand. You choose a card from it. That player shuffles that card into their library. diff --git a/forge-gui/res/cardsfolder/p/pilfering_imp.txt b/forge-gui/res/cardsfolder/p/pilfering_imp.txt index 68057c35d4c..316556ee8a5 100644 --- a/forge-gui/res/cardsfolder/p/pilfering_imp.txt +++ b/forge-gui/res/cardsfolder/p/pilfering_imp.txt @@ -3,5 +3,5 @@ ManaCost:B Types:Creature Imp PT:1/1 K:Flying -A:AB$ Discard | Cost$ 1 B T Sac<1/CARDNAME> | ValidTgts$ Player | Mode$ RevealYouChoose | DiscardValid$ Card.nonLand | NumCards$ 1 | SorcerySpeed$ True | SpellDescription$ Target player reveals their hand. You choose a nonland card from it. That player discards that card. Activate only any time you could cast a sorcery. +A:AB$ Discard | Cost$ 1 B T Sac<1/CARDNAME> | ValidTgts$ Player | Mode$ RevealYouChoose | DiscardValid$ Card.nonLand | NumCards$ 1 | SorcerySpeed$ True | SpellDescription$ Target player reveals their hand. You choose a nonland card from it. That player discards that card. Activate only as a sorcery. Oracle:Flying\n{1}{B}, {T}, Sacrifice Pilfering Imp: Target opponent reveals their hand. You choose a nonland card from it. That player discards that card. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/r/rakshasa_vizier.txt b/forge-gui/res/cardsfolder/r/rakshasa_vizier.txt index be1ac83fccb..a61dbc88dfc 100644 --- a/forge-gui/res/cardsfolder/r/rakshasa_vizier.txt +++ b/forge-gui/res/cardsfolder/r/rakshasa_vizier.txt @@ -2,7 +2,7 @@ Name:Rakshasa Vizier ManaCost:2 B G U Types:Creature Demon PT:4/4 -T:Mode$ ChangesZoneAll | ValidCards$ Card.YouOwn | Origin$ Graveyard | Destination$ Exile | TriggerZones$ Battlefield | Execute$ TrigPutcounter | TriggerDescription$ Whenever one or more cards are put into exile from your graveyard, put that many +1/+1 counters on CARDNAME. -SVar:TrigPutcounter:DB$ PutCounter | CounterType$ P1P1 | Defined$ Self | CounterNum$ X +T:Mode$ ChangesZoneAll | ValidCards$ Card.YouOwn | Origin$ Graveyard | Destination$ Exile | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever one or more cards are put into exile from your graveyard, put that many +1/+1 counters on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | Defined$ Self | CounterNum$ X SVar:X:TriggerCount$Amount Oracle:Whenever one or more cards are put into exile from your graveyard, put that many +1/+1 counters on Rakshasa Vizier. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-zar_ojanen_scion_of_efrava.txt b/forge-gui/res/cardsfolder/rebalanced/a-zar_ojanen_scion_of_efrava.txt index fe607d000bb..b3d0ceaa8b6 100644 --- a/forge-gui/res/cardsfolder/rebalanced/a-zar_ojanen_scion_of_efrava.txt +++ b/forge-gui/res/cardsfolder/rebalanced/a-zar_ojanen_scion_of_efrava.txt @@ -2,9 +2,9 @@ Name:A-Zar Ojanen, Scion of Efrava ManaCost:3 G W Types:Legendary Creature Cat Warrior PT:4/4 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutcounter | TriggerDescription$ Domain — Whenever CARDNAME enters or becomes tapped, put a +1/+1 counter on each creature you control with toughness less than the number of basic land types among lands you control. -T:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigPutcounter | TriggerZones$ Battlefield | TriggerDescription$ Domain — Whenever CARDNAME enters or becomes tapped, put a +1/+1 counter on each creature you control with toughness less than the number of basic land types among lands you control. -SVar:TrigPutcounter:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl+toughnessLTX | CounterType$ P1P1 | CounterNum$ 1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ Domain — Whenever CARDNAME enters or becomes tapped, put a +1/+1 counter on each creature you control with toughness less than the number of basic land types among lands you control. +T:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Domain — Whenever CARDNAME enters or becomes tapped, put a +1/+1 counter on each creature you control with toughness less than the number of basic land types among lands you control. +SVar:TrigPutCounter:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl+toughnessLTX | CounterType$ P1P1 | CounterNum$ 1 SVar:X:Count$Domain DeckHas:Ability$Counters Oracle:Domain — Whenever Zar Ojanen, Scion of Efrava enters or becomes tapped, put a +1/+1 counter on each creature you control with toughness less than the number of basic land types among lands you control. diff --git a/forge-gui/res/cardsfolder/s/shimatsu_the_bloodcloaked.txt b/forge-gui/res/cardsfolder/s/shimatsu_the_bloodcloaked.txt index db6d38ae3f8..5cbbdc4910e 100644 --- a/forge-gui/res/cardsfolder/s/shimatsu_the_bloodcloaked.txt +++ b/forge-gui/res/cardsfolder/s/shimatsu_the_bloodcloaked.txt @@ -3,8 +3,8 @@ ManaCost:3 R Types:Legendary Creature Demon Spirit PT:0/0 R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ TrigSac | ReplacementResult$ Updated | Description$ As CARDNAME enters, sacrifice any number of permanents. NICKNAME enters with that many +1/+1 counters on it. -SVar:TrigSac:DB$ Sacrifice | Amount$ SacX | SacValid$ Permanent | Defined$ You | RememberSacrificed$ True | Optional$ True | SubAbility$ DBPutcounter -SVar:DBPutcounter:DB$ PutCounter | ETB$ True | CounterType$ P1P1 | Defined$ Self | CounterNum$ X | SubAbility$ DBCleanup +SVar:TrigSac:DB$ Sacrifice | Amount$ SacX | SacValid$ Permanent | Defined$ You | RememberSacrificed$ True | Optional$ True | SubAbility$ DBPutCounter +SVar:DBPutCounter:DB$ PutCounter | ETB$ True | CounterType$ P1P1 | Defined$ Self | CounterNum$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:SacX:Count$Valid Permanent.YouCtrl SVar:X:Remembered$Amount diff --git a/forge-gui/res/cardsfolder/s/skygames.txt b/forge-gui/res/cardsfolder/s/skygames.txt index 790a323d3f2..af7b5503f94 100644 --- a/forge-gui/res/cardsfolder/s/skygames.txt +++ b/forge-gui/res/cardsfolder/s/skygames.txt @@ -4,6 +4,6 @@ Types:Enchantment Aura K:Enchant:Land SVar:AttachAILogic:Pump S:Mode$ Continuous | Affected$ Card.EnchantedBy | AddAbility$ DPTapCreature | Description$ Enchanted land has "{T}: Target creature gains flying until end of turn. Activate only as a sorcery." -SVar:DPTapCreature:AB$ Pump | Cost$ T | ValidTgts$ Creature | TgtPrompt$ Choose target creature. | KW$ Flying | SorcerySpeed$ True | SpellDescription$ Target creature gains flying until end of turn. Activate only any time you could cast a sorcery. +SVar:DPTapCreature:AB$ Pump | Cost$ T | ValidTgts$ Creature | TgtPrompt$ Choose target creature. | KW$ Flying | SorcerySpeed$ True | SpellDescription$ Target creature gains flying until end of turn. Activate only as a sorcery. SVar:NonStackingAttachEffect:True Oracle:Enchant land\nEnchanted land has "{T}: Target creature gains flying until end of turn. Activate only as a sorcery." diff --git a/forge-gui/res/cardsfolder/s/smoldering_tar.txt b/forge-gui/res/cardsfolder/s/smoldering_tar.txt index 9c9d6d4328a..cbf2486ad3f 100644 --- a/forge-gui/res/cardsfolder/s/smoldering_tar.txt +++ b/forge-gui/res/cardsfolder/s/smoldering_tar.txt @@ -3,6 +3,6 @@ ManaCost:2 B R Types:Enchantment T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigLoseLife | TriggerDescription$ At the beginning of your upkeep, target player loses 1 life. SVar:TrigLoseLife:DB$ LoseLife | ValidTgts$ Player | TgtPrompt$ Select a player | LifeAmount$ 1 -A:AB$ DealDamage | Cost$ Sac<1/CARDNAME> | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 4 | SorcerySpeed$ True | SpellDescription$ It deals 4 damage to target creature. Activate only any time you could cast a sorcery. +A:AB$ DealDamage | Cost$ Sac<1/CARDNAME> | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 4 | SorcerySpeed$ True | SpellDescription$ It deals 4 damage to target creature. Activate only as a sorcery. DeckHas:Ability$Sacrifice Oracle:At the beginning of your upkeep, target player loses 1 life.\nSacrifice Smoldering Tar: It deals 4 damage to target creature. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/s/squees_revenge.txt b/forge-gui/res/cardsfolder/s/squees_revenge.txt index 35f3bf4d2b1..80260f2a412 100644 --- a/forge-gui/res/cardsfolder/s/squees_revenge.txt +++ b/forge-gui/res/cardsfolder/s/squees_revenge.txt @@ -6,8 +6,8 @@ A:SP$ ChooseNumber | SubAbility$ RepeatFlip | SpellDescription$ Choose a number. SVar:RepeatFlip:DB$ Repeat | RepeatSubAbility$ FlipAgain | ConditionCheckSVar$ TimesToFlip | ConditionSVarCompare$ GT0 | RepeatCheckSVar$ FlipsDone | RepeatSVarCompare$ LTTimesToFlip | SubAbility$ DrawIfWin SVar:FlipAgain:DB$ FlipACoin | WinSubAbility$ IncrementFlips | LoseSubAbility$ IncrementLoss SVar:IncrementFlips:DB$ StoreSVar | SVar$ FlipsDone | Type$ CountSVar | Expression$ FlipsDone/Plus.1 -SVar:IncrementLoss:DB$ StoreSVar | SVar$ Loss | Type$ CountSVar | Expression$ Loss/Plus.1 | SubAbility$ SetFilpsDone -SVar:SetFilpsDone:DB$ StoreSVar | SVar$ FlipsDone | Type$ CountSVar | Expression$ TimesToFlip +SVar:IncrementLoss:DB$ StoreSVar | SVar$ Loss | Type$ CountSVar | Expression$ Loss/Plus.1 | SubAbility$ SetFlipsDone +SVar:SetFlipsDone:DB$ StoreSVar | SVar$ FlipsDone | Type$ CountSVar | Expression$ TimesToFlip # Draw cards SVar:DrawIfWin:DB$ Draw | Defined$ You | NumCards$ CardsToDraw | ConditionCheckSVar$ Loss | ConditionSVarCompare$ EQ0 SVar:TimesToFlip:Count$ChosenNumber diff --git a/forge-gui/res/cardsfolder/s/stalking_yeti.txt b/forge-gui/res/cardsfolder/s/stalking_yeti.txt index 54abd085f97..2dd00d061b5 100644 --- a/forge-gui/res/cardsfolder/s/stalking_yeti.txt +++ b/forge-gui/res/cardsfolder/s/stalking_yeti.txt @@ -5,7 +5,7 @@ PT:3/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | IsPresent$ Card.StrictlySelf | Execute$ TrigDamage | TriggerDescription$ When CARDNAME enters, if it's on the battlefield, it deals damage equal to its power to target creature an opponent controls and that creature deals damage equal to its power to CARDNAME. SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | NumDmg$ X | SubAbility$ DBDamage SVar:DBDamage:DB$ DealDamage | Defined$ Self | DamageSource$ Targeted | NumDmg$ Y -A:AB$ ChangeZone | Cost$ 2 S | Origin$ Battlefield | Destination$ Hand | SorcerySpeed$ True | SpellDescription$ Return CARDNAME to its owner's hand. Activate only any time you could cast a sorcery. +A:AB$ ChangeZone | Cost$ 2 S | Origin$ Battlefield | Destination$ Hand | SorcerySpeed$ True | SpellDescription$ Return CARDNAME to its owner's hand. Activate only as a sorcery. SVar:X:Count$CardPower SVar:Y:Targeted$CardPower AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/s/startled_awake_persistent_nightmare.txt b/forge-gui/res/cardsfolder/s/startled_awake_persistent_nightmare.txt index ac37d1ff808..4d6babaffd6 100644 --- a/forge-gui/res/cardsfolder/s/startled_awake_persistent_nightmare.txt +++ b/forge-gui/res/cardsfolder/s/startled_awake_persistent_nightmare.txt @@ -2,7 +2,7 @@ Name:Startled Awake ManaCost:2 U U Types:Sorcery A:SP$ Mill | NumCards$ 13 | ValidTgts$ Opponent | TgtPrompt$ Choose an opponent | SpellDescription$ Target opponent mills thirteen cards. -A:AB$ ChangeZone | Cost$ 3 U U | Origin$ Graveyard | Destination$ Battlefield | Transformed$ True | ActivationZone$ Graveyard | SorcerySpeed$ True | SpellDescription$ Return CARDNAME from your graveyard onto the battlefield transformed. Activate only any time you could cast a sorcery. +A:AB$ ChangeZone | Cost$ 3 U U | Origin$ Graveyard | Destination$ Battlefield | Transformed$ True | ActivationZone$ Graveyard | SorcerySpeed$ True | SpellDescription$ Return CARDNAME from your graveyard onto the battlefield transformed. Activate only as a sorcery. AlternateMode:DoubleFaced Oracle:Target opponent mills thirteen cards.\n{3}{U}{U}: Put Startled Awake from your graveyard onto the battlefield transformed. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/s/stonebinders_familiar.txt b/forge-gui/res/cardsfolder/s/stonebinders_familiar.txt index db5c746e8f2..e7da54007f4 100644 --- a/forge-gui/res/cardsfolder/s/stonebinders_familiar.txt +++ b/forge-gui/res/cardsfolder/s/stonebinders_familiar.txt @@ -2,7 +2,7 @@ Name:Stonebinder's Familiar ManaCost:W Types:Creature Spirit Dog PT:1/1 -T:Mode$ ChangesZoneAll | ValidCards$ Card.!token+!copiedSpell | Destination$ Exile | TriggerZones$ Battlefield | Execute$ TrigPutcounter | PlayerTurn$ True | ActivationLimit$ 1 | TriggerDescription$ Whenever one or more cards are put into exile during your turn, put a +1/+1 counter on CARDNAME. This ability triggers only once each turn. -SVar:TrigPutcounter:DB$ PutCounter | CounterType$ P1P1 | Defined$ Self | CounterNum$ 1 +T:Mode$ ChangesZoneAll | ValidCards$ Card.!token+!copiedSpell | Destination$ Exile | TriggerZones$ Battlefield | Execute$ TrigPutCounter | PlayerTurn$ True | ActivationLimit$ 1 | TriggerDescription$ Whenever one or more cards are put into exile during your turn, put a +1/+1 counter on CARDNAME. This ability triggers only once each turn. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | Defined$ Self | CounterNum$ 1 DeckHas:Ability$Counters Oracle:Whenever one or more cards are put into exile during your turn, put a +1/+1 counter on Stonebinder's Familiar. This ability triggers only once each turn. diff --git a/forge-gui/res/cardsfolder/s/surtland_frostpyre.txt b/forge-gui/res/cardsfolder/s/surtland_frostpyre.txt index cee470a24f3..16560ea7eb7 100644 --- a/forge-gui/res/cardsfolder/s/surtland_frostpyre.txt +++ b/forge-gui/res/cardsfolder/s/surtland_frostpyre.txt @@ -4,6 +4,6 @@ Types:Land R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}. -A:AB$ Scry | Cost$ 2 U U R T Sac<1/CARDNAME> | ScryNum$ 2 | SubAbility$ DBDamageAll | SorcerySpeed$ True | SpellDescription$ Scry 2. CARDNAME deals 2 damage to each creature. Activate only any time you could cast a sorcery. +A:AB$ Scry | Cost$ 2 U U R T Sac<1/CARDNAME> | ScryNum$ 2 | SubAbility$ DBDamageAll | SorcerySpeed$ True | SpellDescription$ Scry 2. CARDNAME deals 2 damage to each creature. Activate only as a sorcery. SVar:DBDamageAll:DB$ DamageAll | ValidCards$ Creature | NumDmg$ 2 Oracle:Surtland Frostpyre enters tapped.\n{T}: Add {R}.\n{2}{U}{U}{R}, {T}, Sacrifice Surtland Frostpyre: Scry 2. Surtland Frostpyre deals 2 damage to each creature. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/t/tahngarths_glare.txt b/forge-gui/res/cardsfolder/t/tahngarths_glare.txt index 3cd5f6a913f..597acb9369c 100644 --- a/forge-gui/res/cardsfolder/t/tahngarths_glare.txt +++ b/forge-gui/res/cardsfolder/t/tahngarths_glare.txt @@ -1,6 +1,6 @@ Name:Tahngarth's Glare ManaCost:R Types:Sorcery -A:SP$ RearrangeTopOfLibrary | ValidTgts$ Opponent | NumCards$ 3 | SubAbility$ DBRearange | SpellDescription$ Look at the top three cards of target opponent's library, then put them back in any order. That player looks at the top three cards of your library, then puts them back in any order. -SVar:DBRearange:DB$ RearrangeTopOfLibrary | Defined$ You | RearrangePlayer$ Targeted | NumCards$ 3 +A:SP$ RearrangeTopOfLibrary | ValidTgts$ Opponent | NumCards$ 3 | SubAbility$ DBRearrange | SpellDescription$ Look at the top three cards of target opponent's library, then put them back in any order. That player looks at the top three cards of your library, then puts them back in any order. +SVar:DBRearrange:DB$ RearrangeTopOfLibrary | Defined$ You | RearrangePlayer$ Targeted | NumCards$ 3 Oracle:Look at the top three cards of target opponent's library, then put them back in any order. That player looks at the top three cards of your library, then puts them back in any order. diff --git a/forge-gui/res/cardsfolder/t/the_soul_stone.txt b/forge-gui/res/cardsfolder/t/the_soul_stone.txt index 2cae4876a67..10c9e9a55ef 100644 --- a/forge-gui/res/cardsfolder/t/the_soul_stone.txt +++ b/forge-gui/res/cardsfolder/t/the_soul_stone.txt @@ -5,7 +5,7 @@ K:Indestructible A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}. A:AB$ AlterAttribute | Cost$ 6 B Exile<1/Creature> | Defined$ Self | Attributes$ Harnessed | StackDescription$ SpellDescription | SpellDescription$ Harness CARDNAME. (Once harnessed, its ∞ ability is active.) T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | IsPresent$ Card.Self+harnessed | TriggerZones$ Battlefield | Execute$ TrigReturn | TriggerDescription$ ∞ — At the beginning of your upkeep, return target creature card from your graveyard to the battlefield. -SVar:TrigReturn:DB$ Changezone | ValidTgts$ Creature.YouOwn | Origin$ Graveyard | Destination$ Battlefield +SVar:TrigReturn:DB$ ChangeZone | ValidTgts$ Creature.YouOwn | Origin$ Graveyard | Destination$ Battlefield SVar:PlayMain1:TRUE DeckHints:Ability$Graveyard Oracle:Indestructible\n{T}: Add {B}.\n{6}{B}, {T}, Exile a creature you control: Harness The Soul Stone. (Once harnessed, its ∞ ability is active.)\n∞ — At the beginning of your upkeep, return target creature card from your graveyard to the battlefield. diff --git a/forge-gui/res/cardsfolder/t/titania_voice_of_gaea_titania_gaea_incarnate.txt b/forge-gui/res/cardsfolder/t/titania_voice_of_gaea_titania_gaea_incarnate.txt index ee9a9c52e0e..bf8991d88c2 100644 --- a/forge-gui/res/cardsfolder/t/titania_voice_of_gaea_titania_gaea_incarnate.txt +++ b/forge-gui/res/cardsfolder/t/titania_voice_of_gaea_titania_gaea_incarnate.txt @@ -3,8 +3,8 @@ ManaCost:1 G G Types:Legendary Creature Elemental PT:3/4 K:Reach -T:Mode$ ChangesZoneAll | ValidCards$ Land.YouOwn+!token | Origin$ Any | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigLifegain | TriggerDescription$ Whenever one or more land cards are put into your graveyard from anywhere, you gain 2 life. -SVar:TrigLifegain:DB$ GainLife | LifeAmount$ 2 +T:Mode$ ChangesZoneAll | ValidCards$ Land.YouOwn+!token | Origin$ Any | Destination$ Graveyard | TriggerZones$ Battlefield | Execute$ TrigGainLife | TriggerDescription$ Whenever one or more land cards are put into your graveyard from anywhere, you gain 2 life. +SVar:TrigGainLife:DB$ GainLife | LifeAmount$ 2 T:Mode$ Phase | Phase$ Upkeep | CheckSVar$ X | SVarCompare$ GE4 | IsPresent$ Card.Self+YouCtrl+YouOwn | IsPresent2$ Land.YouCtrl+YouOwn+namedArgoth; Sanctum of Nature | ValidPlayer$ You | Execute$ Meld | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, if there are four or more land cards in your graveyard and you both own and control CARDNAME and a land named Argoth, Sanctum of Nature, exile them, then meld them into Titania, Gaea Incarnate. SVar:Meld:DB$ Meld | Name$ Titania, Gaea Incarnate | Primary$ Titania, Voice of Gaea | Secondary$ Argoth, Sanctum of Nature | SecondaryType$ Land SVar:X:Count$ValidGraveyard Land.YouOwn diff --git a/forge-gui/res/cardsfolder/t/transmogrifying_wand.txt b/forge-gui/res/cardsfolder/t/transmogrifying_wand.txt index 680277fe296..4dfa65e6cbb 100644 --- a/forge-gui/res/cardsfolder/t/transmogrifying_wand.txt +++ b/forge-gui/res/cardsfolder/t/transmogrifying_wand.txt @@ -3,5 +3,5 @@ ManaCost:3 Types:Artifact K:etbCounter:CHARGE:3 A:AB$ Destroy | Cost$ 1 T SubCounter<1/CHARGE> | ValidTgts$ Creature | AITgts$ BetterThanEvalRating.130 | TgtPrompt$ Select target creature | SorcerySpeed$ True | SubAbility$ OxToken | SpellDescription$ Destroy target creature. -SVar:OxToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_2_4_ox | TokenOwner$ TargetedController | SpellDescription$ Its controller creates a 2/4 white Ox creature token. Activate only any time you could cast a sorcery. +SVar:OxToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_2_4_ox | TokenOwner$ TargetedController | SpellDescription$ Its controller creates a 2/4 white Ox creature token. Activate only as a sorcery. Oracle:Transmogrifying Wand enters with three charge counters on it.\n{1}, {T}, Remove a charge counter from Transmogrifying Wand: Destroy target creature. Its controller creates a 2/4 white Ox creature token. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/t/trap_essence.txt b/forge-gui/res/cardsfolder/t/trap_essence.txt index 26ae2c1706e..d763d7b5bb5 100644 --- a/forge-gui/res/cardsfolder/t/trap_essence.txt +++ b/forge-gui/res/cardsfolder/t/trap_essence.txt @@ -1,7 +1,7 @@ Name:Trap Essence ManaCost:G U R Types:Instant -A:SP$ Counter | TargetType$ Spell | ValidTgts$ Card.Creature | TgtPrompt$ Select target creature spell | SpellDescription$ Counter target creature spell. Put two +1/+1 counters on up to one target creature. | SubAbility$ DBPutcounter -SVar:DBPutcounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 2 | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target creature | ValidTgts$ Creature +A:SP$ Counter | TargetType$ Spell | ValidTgts$ Card.Creature | TgtPrompt$ Select target creature spell | SpellDescription$ Counter target creature spell. Put two +1/+1 counters on up to one target creature. | SubAbility$ DBPutCounter +SVar:DBPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 2 | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target creature | ValidTgts$ Creature DeckHas:Ability$Counters Oracle:Counter target creature spell. Put two +1/+1 counters on up to one target creature. diff --git a/forge-gui/res/cardsfolder/t/trickery_charm.txt b/forge-gui/res/cardsfolder/t/trickery_charm.txt index aa554e7fd87..bc715abef65 100644 --- a/forge-gui/res/cardsfolder/t/trickery_charm.txt +++ b/forge-gui/res/cardsfolder/t/trickery_charm.txt @@ -1,10 +1,10 @@ Name:Trickery Charm ManaCost:U Types:Instant -A:SP$ Charm | Choices$ DBPump,DBChooseType,DBRearrage +A:SP$ Charm | Choices$ DBPump,DBChooseType,DBRearrange SVar:DBPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ Flying | SpellDescription$ Target creature gains flying until end of turn. SVar:DBChooseType:DB$ ChooseType | Type$ Creature | Defined$ You | SubAbility$ DBAnimate | SpellDescription$ Target creature becomes the creature type of your choice until end of turn. SVar:DBAnimate:DB$ Animate | ValidTgts$ Creature | TgtPrompt$ Select target creature | Types$ ChosenType | RemoveCreatureTypes$ True -SVar:DBRearrage:DB$ RearrangeTopOfLibrary | Defined$ You | NumCards$ 4 | SpellDescription$ Look at the top four cards of your library, then put them back in any order. +SVar:DBRearrange:DB$ RearrangeTopOfLibrary | Defined$ You | NumCards$ 4 | SpellDescription$ Look at the top four cards of your library, then put them back in any order. AI:RemoveDeck:All Oracle:Choose one —\n• Target creature gains flying until end of turn.\n• Target creature becomes the creature type of your choice until end of turn.\n• Look at the top four cards of your library, then put them back in any order. diff --git a/forge-gui/res/cardsfolder/u/undercity_necrolisk.txt b/forge-gui/res/cardsfolder/u/undercity_necrolisk.txt index 1dd0c6fd6d9..e24de68e287 100644 --- a/forge-gui/res/cardsfolder/u/undercity_necrolisk.txt +++ b/forge-gui/res/cardsfolder/u/undercity_necrolisk.txt @@ -2,6 +2,6 @@ Name:Undercity Necrolisk ManaCost:3 B Types:Creature Zombie Lizard PT:3/3 -A:AB$ PutCounter | Cost$ 1 Sac<1/Creature.Other/another creature> | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBPump | SorcerySpeed$ True | SpellDescription$ Put a +1/+1 counter on CARDNAME. It gains menace until end of turn. Activate only any time you could cast a sorcery. +A:AB$ PutCounter | Cost$ 1 Sac<1/Creature.Other/another creature> | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBPump | SorcerySpeed$ True | SpellDescription$ Put a +1/+1 counter on CARDNAME. It gains menace until end of turn. Activate only as a sorcery. SVar:DBPump:DB$ Pump | KW$ Menace | Defined$ Self Oracle:{1}, Sacrifice another creature: Put a +1/+1 counter on Undercity Necrolisk. It gains menace until end of turn. Activate only as a sorcery. (It can't be blocked except by two or more creatures.) diff --git a/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt b/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt index 7c3ff24de56..3c3804f783b 100644 --- a/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt +++ b/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt @@ -58,8 +58,8 @@ SVar:DBChangeLands11M:DB$ ChangeZoneAll | ChangeType$ Card.Land+IsRemembered | O SVar:DBChangeRest11M:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Graveyard | ForgetChanged$ True SVar:Tutor12M:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | SpellDescription$ Search your library for a card and put that card into your hand. Then shuffle your library. SVar:Sacrifice13M:DB$ Sacrifice | ValidTgts$ Player | Amount$ 2 | SacValid$ Creature | SpellDescription$ Target player sacrifices two creatures. -SVar:Token14M:DB$ Token | TokenScript$ b_5_5_demon_flying | SubAbility$ DBLoselife14M | SpellDescription$ Create a 5/5 black Demon creature token with flying. You lose 2 life. -SVar:DBLoselife14M:DB$ LoseLife | LifeAmount$ 2 +SVar:Token14M:DB$ Token | TokenScript$ b_5_5_demon_flying | SubAbility$ DBLoseLife14M | SpellDescription$ Create a 5/5 black Demon creature token with flying. You lose 2 life. +SVar:DBLoseLife14M:DB$ LoseLife | LifeAmount$ 2 SVar:Token15M:DB$ Token | TokenScript$ c_4_4_dragon_flying | SpellDescription$ Create a 4/4 gold Dragon creature token with flying. SVar:SetLife16M:DB$ SetLife | ValidTgts$ Player | TgtPrompt$ Select target player | LifeAmount$ 10 | SpellDescription$ Target player's life total becomes 10. SVar:Destroy17M:DB$ Destroy | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | SpellDescription$ Destroy target nonland permanent. diff --git a/forge-gui/res/cardsfolder/v/ventifact_bottle.txt b/forge-gui/res/cardsfolder/v/ventifact_bottle.txt index 123b6adc22a..3460e3acb7b 100644 --- a/forge-gui/res/cardsfolder/v/ventifact_bottle.txt +++ b/forge-gui/res/cardsfolder/v/ventifact_bottle.txt @@ -1,7 +1,7 @@ Name:Ventifact Bottle ManaCost:3 Types:Artifact -A:AB$ PutCounter | Cost$ X 1 T | CounterType$ CHARGE | CounterNum$ X | SorcerySpeed$ True | SpellDescription$ Put X charge counters on CARDNAME. Activate only any time you could cast a sorcery. +A:AB$ PutCounter | Cost$ X 1 T | CounterType$ CHARGE | CounterNum$ X | SorcerySpeed$ True | SpellDescription$ Put X charge counters on CARDNAME. Activate only as a sorcery. T:Mode$ Phase | Phase$ Main1 | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigGetMana | CheckSVar$ Y | SVarCompare$ GE1 | TriggerDescription$ At the beginning of your first main phase, if CARDNAME has a charge counter on it, tap it and remove all charge counters from it. Add {C} for each charge counter removed this way. SVar:TrigGetMana:DB$ Mana | Produced$ C | Amount$ Y | SubAbility$ TrigRemove SVar:TrigRemove:DB$ RemoveCounter | CounterType$ CHARGE | CounterNum$ Y | SubAbility$ DBTap diff --git a/forge-gui/res/cardsfolder/w/woodland_champion.txt b/forge-gui/res/cardsfolder/w/woodland_champion.txt index dea1d487785..0d393b2dc98 100644 --- a/forge-gui/res/cardsfolder/w/woodland_champion.txt +++ b/forge-gui/res/cardsfolder/w/woodland_champion.txt @@ -2,8 +2,8 @@ Name:Woodland Champion ManaCost:1 G Types:Creature Elf Scout PT:2/2 -T:Mode$ ChangesZoneAll | ValidCards$ Card.token+YouCtrl | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigPutcounter | TriggerDescription$ Whenever one or more tokens you control enter, put that many +1/+1 counters on CARDNAME. -SVar:TrigPutcounter:DB$ PutCounter | CounterType$ P1P1 | Defined$ Self | CounterNum$ X +T:Mode$ ChangesZoneAll | ValidCards$ Card.token+YouCtrl | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever one or more tokens you control enter, put that many +1/+1 counters on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | Defined$ Self | CounterNum$ X SVar:X:TriggerCount$Amount DeckHints:Ability$Token DeckHas:Ability$Counters diff --git a/forge-gui/res/cardsfolder/z/zar_ojanen_scion_of_efrava.txt b/forge-gui/res/cardsfolder/z/zar_ojanen_scion_of_efrava.txt index d9a947d6470..e37c07c77c3 100644 --- a/forge-gui/res/cardsfolder/z/zar_ojanen_scion_of_efrava.txt +++ b/forge-gui/res/cardsfolder/z/zar_ojanen_scion_of_efrava.txt @@ -2,8 +2,8 @@ Name:Zar Ojanen, Scion of Efrava ManaCost:3 G W Types:Legendary Creature Cat Warrior PT:4/4 -T:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigPutcounter | TriggerZones$ Battlefield | TriggerDescription$ Domain — Whenever CARDNAME becomes tapped, put a +1/+1 counter on each creature you control with toughness less than the number of basic land types among lands you control. -SVar:TrigPutcounter:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl+toughnessLTX | CounterType$ P1P1 | CounterNum$ 1 +T:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Domain — Whenever CARDNAME becomes tapped, put a +1/+1 counter on each creature you control with toughness less than the number of basic land types among lands you control. +SVar:TrigPutCounter:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl+toughnessLTX | CounterType$ P1P1 | CounterNum$ 1 SVar:X:Count$Domain DeckHas:Ability$Counters Oracle:Domain — Whenever Zar Ojanen, Scion of Efrava becomes tapped, put a +1/+1 counter on each creature you control with toughness less than the number of basic land types among lands you control. diff --git a/forge-gui/res/tokenscripts/g_x_x_treefolk_warrior_total_forests.txt b/forge-gui/res/tokenscripts/g_x_x_treefolk_warrior_total_forests.txt index 88c2145b493..86d29bd7081 100644 --- a/forge-gui/res/tokenscripts/g_x_x_treefolk_warrior_total_forests.txt +++ b/forge-gui/res/tokenscripts/g_x_x_treefolk_warrior_total_forests.txt @@ -3,7 +3,7 @@ ManaCost:no cost Colors:green Types:Creature Treefolk Warrior PT:*/* -S:Mode$ Continuous | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of Forests you control. +S:Mode$ Continuous | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ This creature's power and toughness are each equal to the number of Forests you control. SVar:X:Count$Valid Forest.YouCtrl SVar:BuffedBy:Forest -Oracle:CARDNAMEs power and toughness are each equal to the number of Forests you control. +Oracle:This creature's power and toughness are each equal to the number of Forests you control. From f4e90e1d389729c820e75b14aa110648eee7decd Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Wed, 24 Sep 2025 13:47:12 +0800 Subject: [PATCH 028/230] fix NoSuchMethodError on older Android - closes #8771 - closes #8770 --- .../forge/gamemodes/planarconquest/ConquestData.java | 4 ++-- .../forge/gamemodes/planarconquest/ConquestUtil.java | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestData.java b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestData.java index 0ed47c54c12..778f989cab3 100644 --- a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestData.java +++ b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestData.java @@ -294,8 +294,8 @@ public final class ConquestData { commandersUsingCard.append("\n").append(CardTranslation.getTranslatedName(commander.getName())); } } - - if (!commandersUsingCard.isEmpty()) { + // Android API StringBuilder isEmpty() is unavailable. https://developer.android.com/reference/java/lang/StringBuilder + if (commandersUsingCard.length() != 0) { SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblCommandersCardCannotBeExiledByCard", CardTranslation.getTranslatedName(card.getName()), commandersUsingCard), title, SOptionPane.INFORMATION_ICON); return false; } diff --git a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestUtil.java b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestUtil.java index 59b3221349f..19912f1e05f 100644 --- a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestUtil.java +++ b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestUtil.java @@ -147,11 +147,13 @@ public class ConquestUtil { } //Move editions of cards already in the deck to the front. Map editionStats = currentDeck.getAllCardsInASinglePool().getCardEditionStatistics(true); + // use flatMap instead of mapMulti for Android 13 and below + //https://developer.android.com/reference/java/util/stream/Stream#mapMulti List out = planes.stream() - .mapMulti((p, c) -> p.getEditions().forEach(c)) - .filter(CardEdition::hasBasicLands) - .sorted(Comparator.comparing(e -> editionStats.getOrDefault(e, 0))) - .collect(Collectors.toList()); + .flatMap(p -> p.getEditions().stream()) + .filter(CardEdition::hasBasicLands) + .sorted(Comparator.comparing(e -> editionStats.getOrDefault(e, 0))) + .collect(Collectors.toList()); return out; } From 7325443da57c8dc8eaee20f29ad9c53e29ca5c1a Mon Sep 17 00:00:00 2001 From: Paul Hammerton <18243520+paulsnoops@users.noreply.github.com> Date: Wed, 24 Sep 2025 08:29:33 +0100 Subject: [PATCH 029/230] Create Lorwyn Eclipsed.txt --- forge-gui/res/editions/Lorwyn Eclipsed.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 forge-gui/res/editions/Lorwyn Eclipsed.txt diff --git a/forge-gui/res/editions/Lorwyn Eclipsed.txt b/forge-gui/res/editions/Lorwyn Eclipsed.txt new file mode 100644 index 00000000000..f2d0f93df0b --- /dev/null +++ b/forge-gui/res/editions/Lorwyn Eclipsed.txt @@ -0,0 +1,12 @@ +[metadata] +Code=ECL +Date=2026-01-23 +Name=Lorwyn Eclipsed +Type=Expansion +ScryfallCode=ECL + +[cards] +224 R Figure of Fable @Omar Rayyan +288 R Sygg, Wanderwine Wisdom @Warren Mahy +310 M Bitterbloom Bearer @Taryn Knight +327 R Mutable Explorer @Felicita Sala From 0036c274707a1d077bbd0a0f20ca87d7e1428703 Mon Sep 17 00:00:00 2001 From: Paul Hammerton <18243520+paulsnoops@users.noreply.github.com> Date: Wed, 24 Sep 2025 08:50:46 +0100 Subject: [PATCH 030/230] Create Arena Anthology 3.txt --- forge-gui/res/editions/Arena Anthology 3.txt | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 forge-gui/res/editions/Arena Anthology 3.txt diff --git a/forge-gui/res/editions/Arena Anthology 3.txt b/forge-gui/res/editions/Arena Anthology 3.txt new file mode 100644 index 00000000000..0f43f948c99 --- /dev/null +++ b/forge-gui/res/editions/Arena Anthology 3.txt @@ -0,0 +1,33 @@ +[metadata] +Code=AA3 +Date=2025-09-23 +Name=Arena Anthology 3 +Type=Online +ScryfallCode=AA3 + +[cards] +1 U Flickerwisp @Jeremy Enecio +2 R Staff of the Storyteller @Dan Scott +3 C Force Spike @Nelson DeCastro +4 C Miscalculation @Jeff Laubenstein +5 C Peek @Adam Rex +6 R Dauthi Voidwalker @Sidharth Chaturyedi +7 U Necromancy @Pete Venters +8 C Assault Strobe @Kev Walker +9 R Broadside Bombardiers @Tomek Larek +10 C Chain Lightning @Christopher Moeller +11 U Gut, True Soul Zealot @Wayne Reynolds +12 U Oxidda Scrapmelter @Igor Kieryluk +13 C Ambush Viper @Alan Pollack +14 C Crop Rotation @DiTerlizzi +15 R Exploration @Lindsey Look +16 R Noble Hierarch @Mark Zug +17 R Fallen Shinobi @Dmitry Burmak +18 M Batterskull @Mark Zug +19 M Emrakul, the Aeons Torn @Mark Tedin +20 M Kaldra Compleat @Vincent Proce +21 R Memory Jar @Donato Giancola +22 M Ulamog, the Infinite Gyre @Aleksi Briclot +23 R Umezawa's Jitte @Christopher Moeller +24 R Shelldock Isle @Mark Tedin +25 R Yavimaya, Cradle of Growth @Sarah Finnigan From 5999909d32f13053cff3af89f80646850f5ccb6e Mon Sep 17 00:00:00 2001 From: Paul Hammerton <18243520+paulsnoops@users.noreply.github.com> Date: Wed, 24 Sep 2025 09:19:15 +0100 Subject: [PATCH 031/230] Create Arena Anthology 4.txt --- forge-gui/res/editions/Arena Anthology 4.txt | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 forge-gui/res/editions/Arena Anthology 4.txt diff --git a/forge-gui/res/editions/Arena Anthology 4.txt b/forge-gui/res/editions/Arena Anthology 4.txt new file mode 100644 index 00000000000..d2b9897d48a --- /dev/null +++ b/forge-gui/res/editions/Arena Anthology 4.txt @@ -0,0 +1,36 @@ +[metadata] +Code=AA4 +Date=2025-09-23 +Name=Arena Anthology 4 +Type=Online +ScryfallCode=AA4 + +[cards] +1 R Blazing Archon @Zoltan Boros & Gabor Szikszai +2 R Bringer of the White Dawn @Kev Walker +3 M Iona, Shield of Emeria @Jason Chan +4 C Scaled Wurm @Wayne England +5 C Stormfront Pegasus @rk post +6 R Bringer of the Blue Dawn @Greg Staples +7 R Chromescale Drake @Ben Thompson +8 R Hoverguard Sweepers @Mark A. Nelson +9 C Kraken Hatchling @Jason Felix +10 R Thing from the Deep @Parente +11 R Tidespout Tyrant @Dany Orizio +12 R Baleful Force @Eytan Zana +13 R Bringer of the Black Dawn @Carl Critchlow +14 M Demon of Death's Gate @Vance Kovacs +15 R Dread Cacodemon @Izzy +16 R Kuro, Pitlord @Jon Foster +17 R Bringer of the Red Dawn @Christopher Moeller +18 R Dragon Tyrant @Kev Walker +19 R Fire Dragon @William Simpson +20 R Furnace Dragon @Matthew D. Wilson +21 R Magmatic Force @Jung Park +22 R Bringer of the Green Dawn @Jim Murray +23 C Crash of Rhinos @Steve White +24 R Gigantomancer @Chippy +25 R Regal Force @Brandon Kitkouski +26 R Wolf Pack @Yang Jun Kwon +27 R Colossus of Sardia @Greg Staples +28 U Stratadon @Brian Snoddy From 28dcdf6ddf8c40689ce7ddcd8b3acf7ae1bfa586 Mon Sep 17 00:00:00 2001 From: Paul Hammerton <18243520+paulsnoops@users.noreply.github.com> Date: Wed, 24 Sep 2025 09:21:44 +0100 Subject: [PATCH 032/230] Update Secret Lair Drop Series.txt --- forge-gui/res/editions/Secret Lair Drop Series.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 25f454ce23f..a929bd211bf 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -2062,6 +2062,10 @@ F1540 M Rainbow Dash @John Thacker 2109 R Sylvan Safekeeper @Ian Permana 2110 M Crucible of Worlds @Ian Permana 2111 R Zuran Orb @Ian Permana +2193 M Greensleeves, Maro-Sorcerer @Jason Loik & Matthew Cohen +2194 M Polyraptor @Ben Millar +2195 R Academy Manufactor @Ben Millar +2196 M Wurmcoil Engine @Jason Loik & Matthew Cohen 7001 R Feed the Swarm @Stanislav Sherbakov 7002 R Forge Anew @Yongjae Choi 7003 R Silence @Evyn Fong From e4a83de6cf35b6abd3694a851dbee697cee4db62 Mon Sep 17 00:00:00 2001 From: Paul Hammerton Date: Wed, 24 Sep 2025 17:52:33 +0100 Subject: [PATCH 033/230] Format updates: AA3, AA4 --- forge-gui/res/formats/Archived/Alchemy/2025-07-29.txt | 1 - forge-gui/res/formats/Archived/Alchemy/2025-08-19.txt | 1 - forge-gui/res/formats/Archived/Alchemy/2025-09-23.txt | 1 - forge-gui/res/formats/Archived/Historic/2025-09-23.txt | 4 ++-- forge-gui/res/formats/Archived/Timeless/2025-09-23.txt | 4 ++-- forge-gui/res/formats/Sanctioned/Historic.txt | 4 ++-- 6 files changed, 6 insertions(+), 9 deletions(-) diff --git a/forge-gui/res/formats/Archived/Alchemy/2025-07-29.txt b/forge-gui/res/formats/Archived/Alchemy/2025-07-29.txt index f0de8ec816c..77cfe09943c 100644 --- a/forge-gui/res/formats/Archived/Alchemy/2025-07-29.txt +++ b/forge-gui/res/formats/Archived/Alchemy/2025-07-29.txt @@ -4,6 +4,5 @@ Type:Archived Subtype:Arena Effective:2025-07-29 Sets:ANA, ANB, BLB, YBLB, DSK, YDSK, FDN, DFT, YDFT, TDM, YTDM, FIN, EOE -Banned:Monstrous Rage // Due to format rotation, Alchemy loses access to several mana-fixing lands. Therefore, the following cards are legal ahead of schedule due to their inclusion in YEOE on 19 August 2025. Additional:Clifftop Retreat; Dragonskull Summit; Drowned Catacomb; Glacial Fortress; Hinterland Harbor; Isolated Chapel; Rootbound Crag; Sulfur Falls; Sunpetal Grove; Woodland Cemetery diff --git a/forge-gui/res/formats/Archived/Alchemy/2025-08-19.txt b/forge-gui/res/formats/Archived/Alchemy/2025-08-19.txt index 1dc6d843e90..758501395b3 100644 --- a/forge-gui/res/formats/Archived/Alchemy/2025-08-19.txt +++ b/forge-gui/res/formats/Archived/Alchemy/2025-08-19.txt @@ -4,4 +4,3 @@ Type:Archived Subtype:Arena Effective:2025-08-19 Sets:ANA, ANB, BLB, YBLB, DSK, YDSK, FDN, DFT, YDFT, TDM, YTDM, FIN, EOE, YEOE -Banned:Monstrous Rage diff --git a/forge-gui/res/formats/Archived/Alchemy/2025-09-23.txt b/forge-gui/res/formats/Archived/Alchemy/2025-09-23.txt index 4389cc84e95..dc1af079cdd 100644 --- a/forge-gui/res/formats/Archived/Alchemy/2025-09-23.txt +++ b/forge-gui/res/formats/Archived/Alchemy/2025-09-23.txt @@ -4,4 +4,3 @@ Type:Archived Subtype:Arena Effective:2025-09-23 Sets:ANA, ANB, BLB, YBLB, DSK, YDSK, FDN, DFT, YDFT, TDM, YTDM, FIN, EOE, YEOE, OM1 -Banned:Monstrous Rage diff --git a/forge-gui/res/formats/Archived/Historic/2025-09-23.txt b/forge-gui/res/formats/Archived/Historic/2025-09-23.txt index 05c50382658..dc175f25ab4 100644 --- a/forge-gui/res/formats/Archived/Historic/2025-09-23.txt +++ b/forge-gui/res/formats/Archived/Historic/2025-09-23.txt @@ -3,6 +3,6 @@ Name:Historic (OM1) Type:Archived Subtype:Arena Effective:2025-09-23 -Sets:KTK, XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3, WOE, WOT, YWOE, LCI, YLCI, MKM, YMKM, OTJ, OTP, BIG, YOTJ, MH3, BLB, YBLB, DSK, YDSK, FDN, J25, PIO, DFT, YDFT, TDM, YTDM, PA1, FIN, FCA, EOE, EOS, YEOE, AA1, AA2, OM1, OMB +Sets:KTK, XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3, WOE, WOT, YWOE, LCI, YLCI, MKM, YMKM, OTJ, OTP, BIG, YOTJ, MH3, BLB, YBLB, DSK, YDSK, FDN, J25, PIO, DFT, YDFT, TDM, YTDM, PA1, FIN, FCA, EOE, EOS, YEOE, AA1, AA2, OM1, OMB, AA3, AA4 Banned:Agent of Treachery; Ancient Tomb; Arid Mesa; Blood Moon; Bloodstained Mire; Brainstorm; Channel; Chrome Mox; Commandeer; Dark Ritual; Demonic Tutor; Endurance; Field of the Dead; Flare of Cultivation; Flare of Denial; Flare of Duplication; Flare of Fortitude; Flare of Malice; Flooded Strand; Force of Vigor; Fury; Grief; Harbinger of the Seas; Intruder Alarm; Land Tax; Lightning Bolt; Magus of the Moon; Mana Drain; Marsh Flats; Memory Lapse; Mishra's Bauble; Misty Rainforest; Mox Opal; Natural Order; Necropotence; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Polluted Delta; Ragavan, Nimble Pilferer; Reanimate; Scalding Tarn; Show and Tell; Sneak Attack; Solitude; Spreading Seas; Strip Mine; Subtlety; Swords to Plowshares; Temporal Manipulation; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Verdant Catacombs; Wilderness Reclamation; Windswept Heath; Winter Moon; Wooded Foothills -Additional:Admiral Brass, Unsinkable; Burden of Guilt; Clavileño, First of the Blessed; Crashing Footfalls; Desert; Desertion; Dismember; Duskmantle, House of Shadow; Enlisted Wurm; Evolutionary Leap; Fabricate; Gamble; Ghostly Prison; Gonti, Canny Acquisitor; Goro-Goro and Satoru; Ixidor, Reality Sculptor; Katilda and Lier; Kuldotha Rebirth; Leonin Relic-Warder; Magmaw; Mass Hysteria; Metalspinner's Puzzleknot; Mistveil Plains; Molten Psyche; Monologue Tax; Mystery Key; Mystic Snake; Notion Thief; Nyx Weaver; Olivia, Opulent Outlaw; Pantlaza, Sun-Favored; Persist; Port Razer; Possibility Storm; Prismatic Ending; Prismatic Vista; Putrid Warrior; Shard of Broken Glass; Slimefoot and Squee; Smuggler's Copter; Spell Snare; Stella Lee, Wild Card; Stoneforge Mystic; Timeless Dragon; Treacherous Terrain; Victimize; Xolatoyac, the Smiling Flood; Yuma, Proud Protector +Additional:Admiral Brass, Unsinkable; Burden of Guilt; Clavileño, First of the Blessed; Crashing Footfalls; Crumbling Vestige; Desert; Desertion; Dismember; Duskmantle, House of Shadow; Enlisted Wurm; Evolutionary Leap; Fabricate; Gamble; Ghostly Prison; Gonti, Canny Acquisitor; Goro-Goro and Satoru; Ixidor, Reality Sculptor; Katilda and Lier; Kuldotha Rebirth; Leonin Relic-Warder; Magmaw; Mass Hysteria; Metalspinner's Puzzleknot; Mistveil Plains; Molten Psyche; Monologue Tax; Mystery Key; Mystic Snake; Notion Thief; Nyx Weaver; Olivia, Opulent Outlaw; Pantlaza, Sun-Favored; Persist; Port Razer; Possibility Storm; Prismatic Ending; Prismatic Vista; Putrid Warrior; Shard of Broken Glass; Slimefoot and Squee; Smuggler's Copter; Spell Snare; Stella Lee, Wild Card; Stoneforge Mystic; Timeless Dragon; Treacherous Terrain; Victimize; Xolatoyac, the Smiling Flood; Yuma, Proud Protector diff --git a/forge-gui/res/formats/Archived/Timeless/2025-09-23.txt b/forge-gui/res/formats/Archived/Timeless/2025-09-23.txt index dac7c9da5ba..d0c0a1053d4 100644 --- a/forge-gui/res/formats/Archived/Timeless/2025-09-23.txt +++ b/forge-gui/res/formats/Archived/Timeless/2025-09-23.txt @@ -3,6 +3,6 @@ Name:Timeless (OM1) Type:Archived Subtype:Vintage Effective:2025-09-23 -Sets:KTK, XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3, WOE, WOT, YWOE, LCI, YLCI, MKM, YMKM, OTJ, OTP, BIG, YOTJ, MH3, BLB, YBLB, DSK, YDSK, FDN, J25, PIO, DFT, YDFT, TDM, YTDM, PA1, FIN, FCA, EOE, EOS, YEOE, AA1, AA2, OM1, OMB +Sets:KTK, XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3, WOE, WOT, YWOE, LCI, YLCI, MKM, YMKM, OTJ, OTP, BIG, YOTJ, MH3, BLB, YBLB, DSK, YDSK, FDN, J25, PIO, DFT, YDFT, TDM, YTDM, PA1, FIN, FCA, EOE, EOS, YEOE, AA1, AA2, OM1, OMB, AA3, AA4 Restricted:Channel; Demonic Tutor; Tibalt's Trickery -Additional:Admiral Brass, Unsinkable; Burden of Guilt; Clavileño, First of the Blessed; Crashing Footfalls; Desert; Desertion; Dismember; Duskmantle, House of Shadow; Endurance; Enlisted Wurm; Evolutionary Leap; Fabricate; Fury; Gamble; Ghostly Prison; Gonti, Canny Acquisitor; Goro-Goro and Satoru; Grief; Ixidor, Reality Sculptor; Katilda and Lier; Kuldotha Rebirth; Leonin Relic-Warder; Magmaw; Mass Hysteria; Metalspinner's Puzzleknot; Mistveil Plains; Molten Psyche; Monologue Tax; Mystery Key; Mystic Snake; Notion Thief; Nyx Weaver; Olivia, Opulent Outlaw; Pantlaza, Sun-Favored; Persist; Port Razer; Possibility Storm; Prismatic Ending; Prismatic Vista; Putrid Warrior; Shard of Broken Glass; Show and Tell; Slimefoot and Squee; Smuggler's Copter; Solitude; Spell Snare; Stella Lee, Wild Card; Stoneforge Mystic; Subtlety; Timeless Dragon; Treacherous Terrain; Victimize; Xolatoyac, the Smiling Flood; Yuma, Proud Protector +Additional:Admiral Brass, Unsinkable; Burden of Guilt; Clavileño, First of the Blessed; Crashing Footfalls; Crumbling Vestige; Desert; Desertion; Dismember; Duskmantle, House of Shadow; Endurance; Enlisted Wurm; Evolutionary Leap; Fabricate; Fury; Gamble; Ghostly Prison; Gonti, Canny Acquisitor; Goro-Goro and Satoru; Grief; Ixidor, Reality Sculptor; Katilda and Lier; Kuldotha Rebirth; Leonin Relic-Warder; Magmaw; Mass Hysteria; Metalspinner's Puzzleknot; Mistveil Plains; Molten Psyche; Monologue Tax; Mystery Key; Mystic Snake; Notion Thief; Nyx Weaver; Olivia, Opulent Outlaw; Pantlaza, Sun-Favored; Persist; Port Razer; Possibility Storm; Prismatic Ending; Prismatic Vista; Putrid Warrior; Shard of Broken Glass; Show and Tell; Slimefoot and Squee; Smuggler's Copter; Solitude; Spell Snare; Stella Lee, Wild Card; Stoneforge Mystic; Subtlety; Timeless Dragon; Treacherous Terrain; Victimize; Xolatoyac, the Smiling Flood; Yuma, Proud Protector diff --git a/forge-gui/res/formats/Sanctioned/Historic.txt b/forge-gui/res/formats/Sanctioned/Historic.txt index d028fab2e0c..dac663817b9 100644 --- a/forge-gui/res/formats/Sanctioned/Historic.txt +++ b/forge-gui/res/formats/Sanctioned/Historic.txt @@ -4,6 +4,6 @@ Type:Digital Subtype:Arena Effective:2019-11-21 Order:142 -Sets:KTK, XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3, WOE, WOT, YWOE, LCI, YLCI, MKM, YMKM, OTJ, OTP, BIG, YOTJ, MH3, BLB, YBLB, DSK, YDSK, FDN, J25, PIO, DFT, YDFT, TDM, YTDM, PA1, FIN, FCA, EOE, EOS, YEOE, AA1, AA2, OM1, OMB +Sets:KTK, XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3, WOE, WOT, YWOE, LCI, YLCI, MKM, YMKM, OTJ, OTP, BIG, YOTJ, MH3, BLB, YBLB, DSK, YDSK, FDN, J25, PIO, DFT, YDFT, TDM, YTDM, PA1, FIN, FCA, EOE, EOS, YEOE, AA1, AA2, OM1, OMB, AA3, AA4 Banned:Agent of Treachery; Ancient Tomb; Arid Mesa; Blood Moon; Bloodstained Mire; Brainstorm; Channel; Chrome Mox; Commandeer; Dark Ritual; Demonic Tutor; Endurance; Field of the Dead; Flare of Cultivation; Flare of Denial; Flare of Duplication; Flare of Fortitude; Flare of Malice; Flooded Strand; Force of Vigor; Fury; Grief; Harbinger of the Seas; Intruder Alarm; Land Tax; Lightning Bolt; Magus of the Moon; Mana Drain; Marsh Flats; Memory Lapse; Mishra's Bauble; Misty Rainforest; Mox Opal; Natural Order; Necropotence; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Polluted Delta; Ragavan, Nimble Pilferer; Reanimate; Scalding Tarn; Show and Tell; Sneak Attack; Solitude; Spreading Seas; Strip Mine; Subtlety; Swords to Plowshares; Temporal Manipulation; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Verdant Catacombs; Wilderness Reclamation; Windswept Heath; Winter Moon; Wooded Foothills -Additional:Admiral Brass, Unsinkable; Burden of Guilt; Clavileño, First of the Blessed; Crashing Footfalls; Desert; Desertion; Dismember; Duskmantle, House of Shadow; Enlisted Wurm; Evolutionary Leap; Fabricate; Gamble; Ghostly Prison; Gonti, Canny Acquisitor; Goro-Goro and Satoru; Ixidor, Reality Sculptor; Katilda and Lier; Kuldotha Rebirth; Leonin Relic-Warder; Magmaw; Mass Hysteria; Metalspinner's Puzzleknot; Mistveil Plains; Molten Psyche; Monologue Tax; Mystery Key; Mystic Snake; Notion Thief; Nyx Weaver; Olivia, Opulent Outlaw; Pantlaza, Sun-Favored; Persist; Port Razer; Possibility Storm; Prismatic Ending; Prismatic Vista; Putrid Warrior; Shard of Broken Glass; Slimefoot and Squee; Smuggler's Copter; Spell Snare; Stella Lee, Wild Card; Stoneforge Mystic; Timeless Dragon; Treacherous Terrain; Victimize; Xolatoyac, the Smiling Flood; Yuma, Proud Protector +Additional:Admiral Brass, Unsinkable; Burden of Guilt; Clavileño, First of the Blessed; Crashing Footfalls; Crumbling Vestige; Desert; Desertion; Dismember; Duskmantle, House of Shadow; Enlisted Wurm; Evolutionary Leap; Fabricate; Gamble; Ghostly Prison; Gonti, Canny Acquisitor; Goro-Goro and Satoru; Ixidor, Reality Sculptor; Katilda and Lier; Kuldotha Rebirth; Leonin Relic-Warder; Magmaw; Mass Hysteria; Metalspinner's Puzzleknot; Mistveil Plains; Molten Psyche; Monologue Tax; Mystery Key; Mystic Snake; Notion Thief; Nyx Weaver; Olivia, Opulent Outlaw; Pantlaza, Sun-Favored; Persist; Port Razer; Possibility Storm; Prismatic Ending; Prismatic Vista; Putrid Warrior; Shard of Broken Glass; Slimefoot and Squee; Smuggler's Copter; Spell Snare; Stella Lee, Wild Card; Stoneforge Mystic; Timeless Dragon; Treacherous Terrain; Victimize; Xolatoyac, the Smiling Flood; Yuma, Proud Protector From 2d1fe88d324e23630df164d17741427fced9634e Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Wed, 24 Sep 2025 19:52:58 +0200 Subject: [PATCH 034/230] FSkinProp: unify Enum to SkinProp places (#8764) * FSkinProp: unify Enum to SkinProp places * FSkinProp: add iconFromDeckSection --- .../toolbox/special/PlayerDetailsPanel.java | 17 +------ .../src/forge/deck/FDeckEditor.java | 14 +----- .../screens/match/views/VPlayerPanel.java | 22 ++------- .../forge/localinstance/skin/FSkinProp.java | 48 +++++++++++++++++++ 4 files changed, 56 insertions(+), 45 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java b/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java index d04d889a77d..df0d987c483 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/special/PlayerDetailsPanel.java @@ -69,22 +69,7 @@ public class PlayerDetailsPanel extends JPanel { } public static FSkinProp iconFromZone(ZoneType zoneType) { - switch (zoneType) { - case Hand: return FSkinProp.IMG_ZONE_HAND; - case Library: return FSkinProp.IMG_ZONE_LIBRARY; - case Graveyard: return FSkinProp.IMG_ZONE_GRAVEYARD; - case Exile: return FSkinProp.IMG_ZONE_EXILE; - case Sideboard: return FSkinProp.IMG_ZONE_SIDEBOARD; - case Flashback: return FSkinProp.IMG_ZONE_FLASHBACK; - case Command: return FSkinProp.IMG_ZONE_COMMAND; //IMG_PLANESWALKER - case PlanarDeck: return FSkinProp.IMG_ZONE_PLANAR; - case SchemeDeck: return FSkinProp.IMG_ZONE_SCHEME; - case AttractionDeck: return FSkinProp.IMG_ZONE_ATTRACTION; - case ContraptionDeck: return FSkinProp.IMG_ZONE_CONTRAPTION; - case Ante: return FSkinProp.IMG_ZONE_ANTE; - case Junkyard: return FSkinProp.IMG_ZONE_JUNKYARD; - default: return FSkinProp.IMG_HDZONE_LIBRARY; - } + return FSkinProp.iconFromZone(zoneType, false); } /** Adds various labels to pool area JPanel container. */ diff --git a/forge-gui-mobile/src/forge/deck/FDeckEditor.java b/forge-gui-mobile/src/forge/deck/FDeckEditor.java index eff2481bff0..0307b1a9f7d 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckEditor.java +++ b/forge-gui-mobile/src/forge/deck/FDeckEditor.java @@ -30,6 +30,7 @@ import forge.itemmanager.ItemManager.ContextMenuBuilder; import forge.itemmanager.ItemManagerConfig; import forge.itemmanager.filters.ItemFilter; import forge.localinstance.properties.ForgePreferences.FPref; +import forge.localinstance.skin.FSkinProp; import forge.menu.*; import forge.model.FModel; import forge.screens.FScreen; @@ -409,18 +410,7 @@ public class FDeckEditor extends TabPageScreen { } public static FImage iconFromDeckSection(DeckSection deckSection) { - return switch (deckSection) { - case Main -> MAIN_DECK_ICON; - case Sideboard -> SIDEBOARD_ICON; - case Commander -> FSkinImage.COMMAND; - case Avatar -> FSkinImage.AVATAR; - case Conspiracy -> FSkinImage.CONSPIRACY; - case Planes -> FSkinImage.PLANAR; - case Schemes -> FSkinImage.SCHEME; - case Attractions -> FSkinImage.ATTRACTION; - case Contraptions -> FSkinImage.CONTRAPTION; - default -> FSkinImage.HDSIDEBOARD; - }; + return FSkin.getImages().get(FSkinProp.iconFromDeckSection(deckSection, Forge.hdbuttons)); } private final DeckEditorConfig editorConfig; diff --git a/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java b/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java index 477ad165d8c..b80b3ce3930 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java @@ -9,6 +9,7 @@ import com.badlogic.gdx.utils.Align; import forge.Forge; import forge.Graphics; +import forge.assets.FSkin; import forge.assets.FSkinColor; import forge.assets.FSkinColor.Colors; import forge.assets.FSkinFont; @@ -19,6 +20,7 @@ import forge.game.card.CounterEnumType; import forge.game.player.PlayerView; import forge.game.zone.ZoneType; import forge.localinstance.properties.ForgePreferences.FPref; +import forge.localinstance.skin.FSkinProp; import forge.menu.FMenuBar; import forge.menu.FMenuItem; import forge.menu.FPopupMenu; @@ -139,23 +141,8 @@ public class VPlayerPanel extends FContainer { tabs.add(zoneTab); } - public static FSkinImage iconFromZone(ZoneType zoneType) { - return switch (zoneType) { - case Hand -> Forge.hdbuttons ? FSkinImage.HDHAND : FSkinImage.HAND; - case Library -> Forge.hdbuttons ? FSkinImage.HDLIBRARY : FSkinImage.LIBRARY; - case Graveyard -> Forge.hdbuttons ? FSkinImage.HDGRAVEYARD : FSkinImage.GRAVEYARD; - case Exile -> Forge.hdbuttons ? FSkinImage.HDEXILE : FSkinImage.EXILE; - case Sideboard -> Forge.hdbuttons ? FSkinImage.HDSIDEBOARD : FSkinImage.SIDEBOARD; - case Flashback -> Forge.hdbuttons ? FSkinImage.HDFLASHBACK : FSkinImage.FLASHBACK; - case Command -> FSkinImage.COMMAND; - case PlanarDeck -> FSkinImage.PLANAR; - case SchemeDeck -> FSkinImage.SCHEME; - case AttractionDeck -> FSkinImage.ATTRACTION; - case ContraptionDeck -> FSkinImage.CONTRAPTION; - case Ante -> FSkinImage.ANTE; - case Junkyard -> FSkinImage.JUNKYARD; - default -> FSkinImage.HDLIBRARY; - }; + public static FSkinImageInterface iconFromZone(ZoneType zoneType) { + return FSkin.getImages().get(FSkinProp.iconFromZone(zoneType, Forge.hdbuttons)); } public Iterable getTabs() { @@ -308,6 +295,7 @@ public class VPlayerPanel extends FContainer { tabManaPool.update(); } + @SuppressWarnings("incomplete-switch") public void updateZone(ZoneType zoneType) { if (zoneType == ZoneType.Battlefield) { field.update(true); diff --git a/forge-gui/src/main/java/forge/localinstance/skin/FSkinProp.java b/forge-gui/src/main/java/forge/localinstance/skin/FSkinProp.java index 99d9fb683ce..66cf630f5a6 100644 --- a/forge-gui/src/main/java/forge/localinstance/skin/FSkinProp.java +++ b/forge-gui/src/main/java/forge/localinstance/skin/FSkinProp.java @@ -22,7 +22,10 @@ import java.util.Map; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; +import forge.card.MagicColor; import forge.card.mana.ManaCostShard; +import forge.deck.DeckSection; +import forge.game.zone.ZoneType; import forge.localinstance.properties.ForgeConstants; /** @@ -705,6 +708,51 @@ public enum FSkinProp { MANA_IMG.put("AL6OFF", FSkinProp.IMG_ATTR_6_OFF); } + public static FSkinProp iconFromColor(MagicColor.Color color) { + return switch (color) { + case WHITE -> IMG_MANA_W; + case BLUE -> IMG_MANA_U; + case BLACK -> IMG_MANA_B; + case RED -> IMG_MANA_R; + case GREEN -> IMG_MANA_G; + case COLORLESS -> IMG_MANA_COLORLESS; + }; + } + + public static FSkinProp iconFromZone(ZoneType zoneType, boolean hdbuttons) { + return switch (zoneType) { + case Hand -> hdbuttons ? IMG_HDZONE_HAND : IMG_ZONE_HAND; + case Library -> hdbuttons ? IMG_HDZONE_LIBRARY : IMG_ZONE_LIBRARY; + case Graveyard -> hdbuttons ? IMG_HDZONE_GRAVEYARD : IMG_ZONE_GRAVEYARD; + case Exile -> hdbuttons ? IMG_HDZONE_EXILE : IMG_ZONE_EXILE; + case Sideboard -> hdbuttons ? IMG_HDZONE_SIDEBOARD : IMG_ZONE_SIDEBOARD; + case Flashback -> hdbuttons ? IMG_HDZONE_FLASHBACK : IMG_ZONE_FLASHBACK; + case Command -> IMG_ZONE_COMMAND; //IMG_PLANESWALKER + case PlanarDeck -> IMG_ZONE_PLANAR; + case SchemeDeck -> IMG_ZONE_SCHEME; + case AttractionDeck -> IMG_ZONE_ATTRACTION; + case ContraptionDeck -> IMG_ZONE_CONTRAPTION; + case Ante -> IMG_ZONE_ANTE; + case Junkyard ->IMG_ZONE_JUNKYARD; + default -> IMG_HDZONE_LIBRARY; + }; + } + + public static FSkinProp iconFromDeckSection(DeckSection deckSection, boolean hdbuttons) { + return switch (deckSection) { + case Main -> hdbuttons ? IMG_HDZONE_LIBRARY : ICO_DECKLIST; + case Sideboard -> hdbuttons ? IMG_HDZONE_SIDEBOARD : IMG_ZONE_SIDEBOARD; + case Commander -> IMG_ZONE_COMMAND; + case Avatar -> IMG_ZONE_AVATAR; + case Conspiracy -> IMG_ZONE_CONSPIRACY; + case Planes -> IMG_ZONE_PLANAR; + case Schemes -> IMG_ZONE_SCHEME; + case Attractions -> IMG_ZONE_ATTRACTION; + case Contraptions -> IMG_ZONE_CONTRAPTION; + default -> IMG_HDZONE_SIDEBOARD; + }; + } + public enum PropType { BACKGROUND(null), COLOR(null), From 1ded8fe5da1c48b419aa6cbc8fef56e5736cc52d Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Thu, 25 Sep 2025 06:51:02 +0800 Subject: [PATCH 035/230] fix removing item by name --- .../src/forge/adventure/player/AdventurePlayer.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java index cc3e7979482..ebc4ae17e1a 100644 --- a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java +++ b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java @@ -1194,9 +1194,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent { } public void removeItem(String name) { - ItemData item = ItemListData.getItem(name); - if (item != null) - removeItem(item); + inventoryItems.stream().filter(itemData -> name.equalsIgnoreCase(itemData.name)).findFirst().ifPresent(this::removeItem); } public void removeItem(ItemData item) { From f11c2ad8a490c8ef91a4a3fae85274f3d2c558e3 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Fri, 26 Sep 2025 08:29:07 +0800 Subject: [PATCH 036/230] fix Vertical layout for VManaPool --- forge-gui-mobile/src/forge/screens/match/views/VManaPool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui-mobile/src/forge/screens/match/views/VManaPool.java b/forge-gui-mobile/src/forge/screens/match/views/VManaPool.java index 57a13b759d0..b5e601b8dd4 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VManaPool.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VManaPool.java @@ -70,7 +70,7 @@ public class VManaPool extends VDisplayArea { float x = 0; float y = 0; - if (Forge.isLandscapeMode() && !Forge.altZoneTabs) { + if (Forge.isLandscapeMode() && (!Forge.altZoneTabs || !"Horizontal".equalsIgnoreCase(Forge.altZoneTabMode))) { float labelWidth = visibleWidth / 2; float labelHeight = visibleHeight / 3; From c10b5706f1bc79a8b9475c22cd73cacd68005e5b Mon Sep 17 00:00:00 2001 From: tool4ever Date: Fri, 26 Sep 2025 08:48:22 +0200 Subject: [PATCH 037/230] Some fixes (#8779) --- .../src/main/java/forge/ai/ComputerUtil.java | 63 ++++---- .../main/java/forge/ai/ComputerUtilMana.java | 9 +- .../java/forge/ai/ability/ChangeZoneAi.java | 3 - .../ability/effects/RestartGameEffect.java | 3 +- .../java/forge/game/card/CardFactory.java | 147 +++++++++--------- .../game/spellability/AbilityActivated.java | 2 +- .../k/kami_of_twisted_reflection.txt | 2 +- forge-gui/res/cardsfolder/p/parker_luck.txt | 1 + .../res/cardsfolder/s/spider_man_2099.txt | 2 +- 9 files changed, 113 insertions(+), 119 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index ca942690ab9..bad4b3c3bbc 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -3104,41 +3104,38 @@ public class ComputerUtil { public static CardCollection filterAITgts(SpellAbility sa, Player ai, CardCollection srcList, boolean alwaysStrict) { final Card source = sa.getHostCard(); - if (source == null) { return srcList; } - - if (sa.hasParam("AITgts")) { - CardCollection list; - String aiTgts = sa.getParam("AITgts"); - if (aiTgts.startsWith("BetterThan")) { - int value = 0; - if (aiTgts.endsWith("Source")) { - value = ComputerUtilCard.evaluateCreature(source); - if (source.isEnchanted()) { - for (Card enc : source.getEnchantedBy()) { - if (enc.getController().equals(ai)) { - value += 100; // is 100 per AI's own aura enough? - } - } - } - } else if (aiTgts.contains("EvalRating.")) { - value = AbilityUtils.calculateAmount(source, aiTgts.substring(aiTgts.indexOf(".") + 1), sa); - } else { - System.err.println("Warning: Unspecified AI target evaluation rating for SA " + sa); - value = ComputerUtilCard.evaluateCreature(source); - } - final int totalValue = value; - list = CardLists.filter(srcList, c -> ComputerUtilCard.evaluateCreature(c) > totalValue + 30); - } else { - list = CardLists.getValidCards(srcList, sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa); - } - - if (!list.isEmpty() || sa.hasParam("AITgtsStrict") || alwaysStrict) { - return list; - } else { - return srcList; - } + if (source == null || !sa.hasParam("AITgts")) { + return srcList; } + CardCollection list; + String aiTgts = sa.getParam("AITgts"); + if (aiTgts.startsWith("BetterThan")) { + int value = 0; + if (aiTgts.endsWith("Source")) { + value = ComputerUtilCard.evaluateCreature(source); + if (source.isEnchanted()) { + for (Card enc : source.getEnchantedBy()) { + if (enc.getController().equals(ai)) { + value += 100; // is 100 per AI's own aura enough? + } + } + } + } else if (aiTgts.contains("EvalRating.")) { + value = AbilityUtils.calculateAmount(source, aiTgts.substring(aiTgts.indexOf(".") + 1), sa); + } else { + System.err.println("Warning: Unspecified AI target evaluation rating for SA " + sa); + value = ComputerUtilCard.evaluateCreature(source); + } + final int totalValue = value; + list = CardLists.filter(srcList, c -> ComputerUtilCard.evaluateCreature(c) > totalValue + 30); + } else { + list = CardLists.getValidCards(srcList, sa.getParam("AITgts"), sa.getActivatingPlayer(), source, sa); + } + + if (!list.isEmpty() || sa.hasParam("AITgtsStrict") || alwaysStrict) { + return list; + } return srcList; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 52cacd0283d..9518b852d63 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -815,7 +815,7 @@ public class ComputerUtilMana { String manaProduced = predictManafromSpellAbility(saPayment, ai, toPay); payMultipleMana(cost, manaProduced, ai); - // remove from available lists + // remove to prevent re-usage since resources don't get consumed sourcesForShards.values().removeIf(CardTraitPredicates.isHostCard(saPayment.getHostCard())); } else { final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment); @@ -828,8 +828,10 @@ public class ComputerUtilMana { // subtract mana from mana pool manapool.payManaFromAbility(sa, cost, saPayment); - // no need to remove abilities from resource map, - // once their costs are paid and consume resources, they can not be used again + // need to consider if another use is now prevented + if (!cost.isPaid() && saPayment.isActivatedAbility() && !saPayment.getRestrictions().canPlay(saPayment.getHostCard(), saPayment)) { + sourcesForShards.values().removeIf(s -> s == saPayment); + } if (hasConverge) { // hack to prevent converge re-using sources @@ -1662,7 +1664,6 @@ public class ComputerUtilMana { if (replaced.contains("C")) { manaMap.put(ManaAtom.COLORLESS, m); } - } } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 6cfc0b0600c..f964381fa5f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -891,9 +891,6 @@ public class ChangeZoneAi extends SpellAbilityAi { CardCollection list = CardLists.getTargetableCards(game.getCardsIn(origin), sa); list = ComputerUtil.filterAITgts(sa, ai, list, true); - if (sa.hasParam("AITgtsOnlyBetterThanSelf")) { - list = CardLists.filter(list, card -> ComputerUtilCard.evaluateCreature(card) > ComputerUtilCard.evaluateCreature(source) + 30); - } if (source.isInZone(ZoneType.Hand)) { list = CardLists.filter(list, CardPredicates.nameNotEquals(source.getName())); // Don't get the same card back. diff --git a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java index 32bb7126c0e..92fbc180d36 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RestartGameEffect.java @@ -75,9 +75,8 @@ public class RestartGameEffect extends SpellAbilityEffect { p.clearController(); CardCollection newLibrary = new CardCollection(p.getCardsIn(restartZones, false)); - List filteredCards = null; if (leaveZone != null) { - filteredCards = CardLists.getValidCards(p.getCardsIn(leaveZone), leaveRestriction, p, sa.getHostCard(), sa); + List filteredCards = CardLists.getValidCards(p.getCardsIn(leaveZone), leaveRestriction, p, sa.getHostCard(), sa); newLibrary.addAll(filteredCards); } diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index 690b1535942..b800d8e0a25 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -466,29 +466,29 @@ public class CardFactory { return new WrappedAbility(sa.getTrigger(), sa.getWrappedAbility().copy(newHost, controller, false), sa.getDecider()); } - public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase sa) { - final Card host = sa.getHostCard(); + public static CardCloneStates getCloneStates(final Card in, final Card out, final CardTraitBase cause) { + final Card host = cause.getHostCard(); final Map origSVars = host.getSVars(); final List types = Lists.newArrayList(); final List keywords = Lists.newArrayList(); boolean KWifNew = false; final List removeKeywords = Lists.newArrayList(); List creatureTypes = null; - final CardCloneStates result = new CardCloneStates(in, sa); + final CardCloneStates result = new CardCloneStates(in, cause); - final String newName = sa.getParam("NewName"); + final String newName = cause.getParam("NewName"); ColorSet colors = null; - if (sa.hasParam("AddTypes")) { - types.addAll(Arrays.asList(sa.getParam("AddTypes").split(" & "))); + if (cause.hasParam("AddTypes")) { + types.addAll(Arrays.asList(cause.getParam("AddTypes").split(" & "))); } - if (sa.hasParam("SetCreatureTypes")) { - creatureTypes = ImmutableList.copyOf(sa.getParam("SetCreatureTypes").split(" ")); + if (cause.hasParam("SetCreatureTypes")) { + creatureTypes = ImmutableList.copyOf(cause.getParam("SetCreatureTypes").split(" ")); } - if (sa.hasParam("AddKeywords")) { - String kwString = sa.getParam("AddKeywords"); + if (cause.hasParam("AddKeywords")) { + String kwString = cause.getParam("AddKeywords"); if (kwString.startsWith("IfNew ")) { KWifNew = true; kwString = kwString.substring(6); @@ -496,21 +496,21 @@ public class CardFactory { keywords.addAll(Arrays.asList(kwString.split(" & "))); } - if (sa.hasParam("RemoveKeywords")) { - removeKeywords.addAll(Arrays.asList(sa.getParam("RemoveKeywords").split(" & "))); + if (cause.hasParam("RemoveKeywords")) { + removeKeywords.addAll(Arrays.asList(cause.getParam("RemoveKeywords").split(" & "))); } - if (sa.hasParam("AddColors")) { - colors = ColorSet.fromNames(sa.getParam("AddColors").split(",")); + if (cause.hasParam("AddColors")) { + colors = ColorSet.fromNames(cause.getParam("AddColors").split(",")); } - if (sa.hasParam("SetColor")) { - colors = ColorSet.fromNames(sa.getParam("SetColor").split(",")); + if (cause.hasParam("SetColor")) { + colors = ColorSet.fromNames(cause.getParam("SetColor").split(",")); } - if (sa.hasParam("SetColorByManaCost")) { - if (sa.hasParam("SetManaCost")) { - colors = ColorSet.fromManaCost(new ManaCost(new ManaCostParser(sa.getParam("SetManaCost")))); + if (cause.hasParam("SetColorByManaCost")) { + if (cause.hasParam("SetManaCost")) { + colors = ColorSet.fromManaCost(new ManaCost(new ManaCostParser(cause.getParam("SetManaCost")))); } else { colors = ColorSet.fromManaCost(host.getManaCost()); } @@ -522,56 +522,55 @@ public class CardFactory { // if something is cloning a facedown card, it only clones the // facedown state into original final CardState ret = new CardState(out, CardStateName.Original); - ret.copyFrom(in.getFaceDownState(), false, sa); + ret.copyFrom(in.getFaceDownState(), false, cause); result.put(CardStateName.Original, ret); } else if (in.isFlipCard()) { // if something is cloning a flip card, copy both original and // flipped state final CardState ret1 = new CardState(out, CardStateName.Original); - ret1.copyFrom(in.getState(CardStateName.Original), false, sa); + ret1.copyFrom(in.getState(CardStateName.Original), false, cause); result.put(CardStateName.Original, ret1); final CardState ret2 = new CardState(out, CardStateName.Flipped); - ret2.copyFrom(in.getState(CardStateName.Flipped), false, sa); + ret2.copyFrom(in.getState(CardStateName.Flipped), false, cause); result.put(CardStateName.Flipped, ret2); } else if (in.hasState(CardStateName.Secondary)) { final CardState ret1 = new CardState(out, CardStateName.Original); - ret1.copyFrom(in.getState(CardStateName.Original), false, sa); + ret1.copyFrom(in.getState(CardStateName.Original), false, cause); result.put(CardStateName.Original, ret1); final CardState ret2 = new CardState(out, CardStateName.Secondary); - ret2.copyFrom(in.getState(CardStateName.Secondary), false, sa); + ret2.copyFrom(in.getState(CardStateName.Secondary), false, cause); result.put(CardStateName.Secondary, ret2); - } else if (in.isTransformable() && sa instanceof SpellAbility && ( - ApiType.CopyPermanent.equals(((SpellAbility)sa).getApi()) || - ApiType.CopySpellAbility.equals(((SpellAbility)sa).getApi()) || - ApiType.ReplaceToken.equals(((SpellAbility)sa).getApi()) - )) { + } else if (in.isTransformable() && cause instanceof SpellAbility sa && ( + ApiType.CopyPermanent.equals(sa.getApi()) || + ApiType.CopySpellAbility.equals(sa.getApi()) || + ApiType.ReplaceToken.equals(sa.getApi()))) { // CopyPermanent can copy token final CardState ret1 = new CardState(out, CardStateName.Original); - ret1.copyFrom(in.getState(CardStateName.Original), false, sa); + ret1.copyFrom(in.getState(CardStateName.Original), false, cause); result.put(CardStateName.Original, ret1); final CardState ret2 = new CardState(out, CardStateName.Backside); - ret2.copyFrom(in.getState(CardStateName.Backside), false, sa); + ret2.copyFrom(in.getState(CardStateName.Backside), false, cause); result.put(CardStateName.Backside, ret2); } else if (in.isSplitCard()) { // for split cards, copy all three states final CardState ret1 = new CardState(out, CardStateName.Original); - ret1.copyFrom(in.getState(CardStateName.Original), false, sa); + ret1.copyFrom(in.getState(CardStateName.Original), false, cause); result.put(CardStateName.Original, ret1); final CardState ret2 = new CardState(out, CardStateName.LeftSplit); - ret2.copyFrom(in.getState(CardStateName.LeftSplit), false, sa); + ret2.copyFrom(in.getState(CardStateName.LeftSplit), false, cause); result.put(CardStateName.LeftSplit, ret2); final CardState ret3 = new CardState(out, CardStateName.RightSplit); - ret3.copyFrom(in.getState(CardStateName.RightSplit), false, sa); + ret3.copyFrom(in.getState(CardStateName.RightSplit), false, cause); result.put(CardStateName.RightSplit, ret3); } else { // in all other cases just copy the current state to original final CardState ret = new CardState(out, CardStateName.Original); - ret.copyFrom(in.getState(in.getCurrentStateName()), false, sa); + ret.copyFrom(in.getState(in.getCurrentStateName()), false, cause); result.put(CardStateName.Original, ret); } @@ -581,32 +580,32 @@ public class CardFactory { final CardState state = e.getValue(); // has Embalm Condition for extra changes of Vizier of Many Faces - if (sa.hasParam("Embalm") && !out.isEmbalmed()) { + if (cause.hasParam("Embalm") && !out.isEmbalmed()) { continue; } // update the names for the states - if (sa.hasParam("KeepName")) { + if (cause.hasParam("KeepName")) { state.setName(originalState.getName()); } else if (newName != null) { // convert NICKNAME descriptions? state.setName(newName); } - if (sa.hasParam("AddColors")) { + if (cause.hasParam("AddColors")) { state.addColor(colors.getColor()); } - if (sa.hasParam("SetColor") || sa.hasParam("SetColorByManaCost")) { + if (cause.hasParam("SetColor") || cause.hasParam("SetColorByManaCost")) { state.setColor(colors.getColor()); } - if (sa.hasParam("NonLegendary")) { + if (cause.hasParam("NonLegendary")) { state.removeType(CardType.Supertype.Legendary); } - if (sa.hasParam("RemoveCardTypes")) { - state.removeCardTypes(sa.hasParam("RemoveSubTypes")); + if (cause.hasParam("RemoveCardTypes")) { + state.removeCardTypes(cause.hasParam("RemoveSubTypes")); } state.addType(types); @@ -638,31 +637,31 @@ public class CardFactory { // CR 208.3 A noncreature object not on the battlefield has power or toughness only if it has a power and toughness printed on it. // currently only LKI can be trusted? - if ((sa.hasParam("SetPower") || sa.hasParam("SetToughness")) && + if ((cause.hasParam("SetPower") || cause.hasParam("SetToughness")) && (state.getType().isCreature() || (originalState != null && in.getOriginalState(originalState.getStateName()).getBasePowerString() != null))) { - if (sa.hasParam("SetPower")) { - state.setBasePower(AbilityUtils.calculateAmount(host, sa.getParam("SetPower"), sa)); + if (cause.hasParam("SetPower")) { + state.setBasePower(AbilityUtils.calculateAmount(host, cause.getParam("SetPower"), cause)); } - if (sa.hasParam("SetToughness")) { - state.setBaseToughness(AbilityUtils.calculateAmount(host, sa.getParam("SetToughness"), sa)); + if (cause.hasParam("SetToughness")) { + state.setBaseToughness(AbilityUtils.calculateAmount(host, cause.getParam("SetToughness"), cause)); } } - if (state.getType().isPlaneswalker() && sa.hasParam("SetLoyalty")) { - state.setBaseLoyalty(String.valueOf(AbilityUtils.calculateAmount(host, sa.getParam("SetLoyalty"), sa))); + if (state.getType().isPlaneswalker() && cause.hasParam("SetLoyalty")) { + state.setBaseLoyalty(String.valueOf(AbilityUtils.calculateAmount(host, cause.getParam("SetLoyalty"), cause))); } - if (sa.hasParam("RemoveCost")) { + if (cause.hasParam("RemoveCost")) { state.setManaCost(ManaCost.NO_COST); } - if (sa.hasParam("SetManaCost")) { - state.setManaCost(new ManaCost(new ManaCostParser(sa.getParam("SetManaCost")))); + if (cause.hasParam("SetManaCost")) { + state.setManaCost(new ManaCost(new ManaCostParser(cause.getParam("SetManaCost")))); } // SVars to add to clone - if (sa.hasParam("AddSVars") || sa.hasParam("GainTextSVars")) { - final String str = sa.getParamOrDefault("GainTextSVars", sa.getParam("AddSVars")); + if (cause.hasParam("AddSVars") || cause.hasParam("GainTextSVars")) { + final String str = cause.getParamOrDefault("GainTextSVars", cause.getParam("AddSVars")); for (final String s : str.split(",")) { if (origSVars.containsKey(s)) { final String actualsVar = origSVars.get(s); @@ -672,8 +671,8 @@ public class CardFactory { } // triggers to add to clone - if (sa.hasParam("AddTriggers")) { - for (final String s : sa.getParam("AddTriggers").split(",")) { + if (cause.hasParam("AddTriggers")) { + for (final String s : cause.getParam("AddTriggers").split(",")) { if (origSVars.containsKey(s)) { final String actualTrigger = origSVars.get(s); final Trigger parsedTrigger = TriggerHandler.parseTrigger(actualTrigger, out, true, state); @@ -683,8 +682,8 @@ public class CardFactory { } // abilities to add to clone - if (sa.hasParam("AddAbilities") || sa.hasParam("GainTextAbilities")) { - final String str = sa.getParamOrDefault("GainTextAbilities", sa.getParam("AddAbilities")); + if (cause.hasParam("AddAbilities") || cause.hasParam("GainTextAbilities")) { + final String str = cause.getParamOrDefault("GainTextAbilities", cause.getParam("AddAbilities")); for (final String s : str.split(",")) { if (origSVars.containsKey(s)) { final String actualAbility = origSVars.get(s); @@ -696,18 +695,18 @@ public class CardFactory { } // static abilities to add to clone - if (sa.hasParam("AddStaticAbilities")) { - final String str = sa.getParam("AddStaticAbilities"); + if (cause.hasParam("AddStaticAbilities")) { + final String str = cause.getParam("AddStaticAbilities"); for (final String s : str.split(",")) { if (origSVars.containsKey(s)) { final String actualStatic = origSVars.get(s); - state.addStaticAbility(StaticAbility.create(actualStatic, out, sa.getCardState(), true)); + state.addStaticAbility(StaticAbility.create(actualStatic, out, cause.getCardState(), true)); } } } - if (sa.hasParam("GainThisAbility") && sa instanceof SpellAbility) { - SpellAbility root = ((SpellAbility) sa).getRootAbility(); + if (cause.hasParam("GainThisAbility") && cause instanceof SpellAbility sa) { + SpellAbility root = sa.getRootAbility(); // Aurora Shifter if (root.isTrigger() && root.getTrigger().getSpawningAbility() != null) { @@ -724,35 +723,35 @@ public class CardFactory { } // Special Rules for Embalm and Eternalize - if (sa.isEmbalm() && sa.isIntrinsic()) { + if (cause.isEmbalm() && cause.isIntrinsic()) { String name = "embalm_" + TextUtil.fastReplace( TextUtil.fastReplace(host.getName(), ",", ""), " ", "_").toLowerCase(); state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode())); } - if (sa.isEternalize() && sa.isIntrinsic()) { + if (cause.isEternalize() && cause.isIntrinsic()) { String name = "eternalize_" + TextUtil.fastReplace( TextUtil.fastReplace(host.getName(), ",", ""), " ", "_").toLowerCase(); state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode())); } - if (sa.isKeyword(Keyword.OFFSPRING) && sa.isIntrinsic()) { + if (cause.isKeyword(Keyword.OFFSPRING) && cause.isIntrinsic()) { String name = "offspring_" + TextUtil.fastReplace( TextUtil.fastReplace(host.getName(), ",", ""), " ", "_").toLowerCase(); state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode())); } - if (sa.isKeyword(Keyword.SQUAD) && sa.isIntrinsic()) { + if (cause.isKeyword(Keyword.SQUAD) && cause.isIntrinsic()) { String name = "squad_" + TextUtil.fastReplace( TextUtil.fastReplace(host.getName(), ",", ""), " ", "_").toLowerCase(); state.setImageKey(StaticData.instance().getOtherImageKey(name, host.getSetCode())); } - if (sa.hasParam("GainTextOf") && originalState != null) { + if (cause.hasParam("GainTextOf") && originalState != null) { state.setSetCode(originalState.getSetCode()); state.setRarity(originalState.getRarity()); state.setImageKey(originalState.getImageKey()); @@ -764,27 +763,27 @@ public class CardFactory { continue; } - if (sa.hasParam("SetPower") && sta.hasParam("SetPower")) + if (cause.hasParam("SetPower") && sta.hasParam("SetPower")) state.removeStaticAbility(sta); - if (sa.hasParam("SetToughness") && sta.hasParam("SetToughness")) + if (cause.hasParam("SetToughness") && sta.hasParam("SetToughness")) state.removeStaticAbility(sta); // currently only Changeling and similar should be affected by that // other cards using AddType$ ChosenType should not - if (sa.hasParam("SetCreatureTypes") && sta.hasParam("AddAllCreatureTypes")) { + if (cause.hasParam("SetCreatureTypes") && sta.hasParam("AddAllCreatureTypes")) { state.removeStaticAbility(sta); } - if ((sa.hasParam("SetColor") || sa.hasParam("SetColorByManaCost")) && sta.hasParam("SetColor")) { + if ((cause.hasParam("SetColor") || cause.hasParam("SetColorByManaCost")) && sta.hasParam("SetColor")) { state.removeStaticAbility(sta); } } // remove some keywords - if (sa.hasParam("SetCreatureTypes")) { + if (cause.hasParam("SetCreatureTypes")) { state.removeIntrinsicKeyword(Keyword.CHANGELING); } - if (sa.hasParam("SetColor") || sa.hasParam("SetColorByManaCost")) { + if (cause.hasParam("SetColor") || cause.hasParam("SetColorByManaCost")) { state.removeIntrinsicKeyword(Keyword.DEVOID); } } diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java b/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java index ce10ab07de9..fcdc1b77f54 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityActivated.java @@ -92,7 +92,7 @@ public abstract class AbilityActivated extends SpellAbility implements Cloneable return false; } - if (!(this.getRestrictions().canPlay(c, this))) { + if (!getRestrictions().canPlay(c, this)) { return false; } diff --git a/forge-gui/res/cardsfolder/k/kami_of_twisted_reflection.txt b/forge-gui/res/cardsfolder/k/kami_of_twisted_reflection.txt index 5d531b281ea..a2dfd611327 100644 --- a/forge-gui/res/cardsfolder/k/kami_of_twisted_reflection.txt +++ b/forge-gui/res/cardsfolder/k/kami_of_twisted_reflection.txt @@ -2,5 +2,5 @@ Name:Kami of Twisted Reflection ManaCost:1 U U Types:Creature Spirit PT:2/2 -A:AB$ ChangeZone | Cost$ Sac<1/CARDNAME> | ValidTgts$ Creature.YouCtrl | AITgts$ Creature.Other+YouCtrl | AITgtsOnlyBetterThanSelf$ True | TgtPrompt$ Select target creature you control | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return target creature you control to its owner's hand. +A:AB$ ChangeZone | Cost$ Sac<1/CARDNAME> | ValidTgts$ Creature.YouCtrl | AITgts$ BetterThanSource | TgtPrompt$ Select target creature you control | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return target creature you control to its owner's hand. Oracle:Sacrifice Kami of Twisted Reflection: Return target creature you control to its owner's hand. diff --git a/forge-gui/res/cardsfolder/p/parker_luck.txt b/forge-gui/res/cardsfolder/p/parker_luck.txt index a7fac4e599b..802200a0944 100644 --- a/forge-gui/res/cardsfolder/p/parker_luck.txt +++ b/forge-gui/res/cardsfolder/p/parker_luck.txt @@ -8,4 +8,5 @@ SVar:DBLoseLife:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ X SVar:DBChangeZoneAll:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Hand | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$ValidLibrary Card.IsRemembered+!RememberedPlayerOwn$CardManaCost +AI:RemoveDeck:All Oracle:At the beginning of your end step, two target players each reveal the top card of their library. They each lose life equal to the mana value of the card revealed by the other player. Then they each put the card they revealed into their hand. diff --git a/forge-gui/res/cardsfolder/s/spider_man_2099.txt b/forge-gui/res/cardsfolder/s/spider_man_2099.txt index e9c82d77279..3023ae58621 100644 --- a/forge-gui/res/cardsfolder/s/spider_man_2099.txt +++ b/forge-gui/res/cardsfolder/s/spider_man_2099.txt @@ -6,7 +6,7 @@ K:Double Strike K:Vigilance S:Mode$ CantBeCast | ValidCard$ Card.Self | EffectZone$ All | Caster$ Player.Active | CheckSVar$ Z | SVarCompare$ LE3 | Description$ You can't cast CARDNAME during your first, second, or third turns of the game. SVar:Z:Count$YourTurns -T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | CheckSVar$ Y | SVarCompare$ EQ1 | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ At the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, CARDNAME deals damage equal to his power to any target. +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | CheckSVar$ Y | SVarCompare$ GE1 | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ At the beginning of your end step, if you've played a land or cast a spell this turn from anywhere other than your hand, CARDNAME deals damage equal to his power to any target. SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Any | NumDmg$ X | AILogic$ PowerDmg SVar:X:Count$CardPower T:Mode$ LandPlayed | Origin$ Exile,Library,Graveyard | ValidCard$ Land.YouCtrl | Execute$ StoreVar | Static$ True From c0f56158859d6359b0267999dda2a8a9a0902955 Mon Sep 17 00:00:00 2001 From: Chris H Date: Thu, 25 Sep 2025 23:37:42 -0400 Subject: [PATCH 038/230] Manually fixing version touches --- .../adventure/data/AdventureEventData.java | 408 +++++++++++------- .../src/forge/adventure/scene/DuelScene.java | 2 +- .../src/forge/adventure/scene/EventScene.java | 5 +- .../util/AdventureEventController.java | 29 +- 4 files changed, 269 insertions(+), 175 deletions(-) diff --git a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java index a47591b6684..b5d45a4c92a 100644 --- a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java +++ b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java @@ -32,6 +32,7 @@ import java.util.stream.Collectors; public class AdventureEventData implements Serializable { private static final long serialVersionUID = 1L; + private static final int JUMPSTART_TO_PICK_FROM = 6; public transient BoosterDraft draft; public AdventureEventParticipant[] participants; public int rounds; @@ -78,7 +79,6 @@ public class AdventureEventData implements Serializable { matchesLost = other.matchesLost; } - public Deck[] getRewardPacks(int count) { Deck[] ret = new Deck[count]; for (int i = 0; i < count; i++) { @@ -104,173 +104,17 @@ public class AdventureEventData implements Serializable { return; cardBlockName = cardBlock.getName(); - //Below all to be fully generated in later release - rewardPacks = getRewardPacks(3); - generateParticipants(7); - if (cardBlock != null) { - packConfiguration = getBoosterConfiguration(cardBlock); - - rewards = new AdventureEventData.AdventureEventReward[4]; - AdventureEventData.AdventureEventReward r0 = new AdventureEventData.AdventureEventReward(); - AdventureEventData.AdventureEventReward r1 = new AdventureEventData.AdventureEventReward(); - AdventureEventData.AdventureEventReward r2 = new AdventureEventData.AdventureEventReward(); - AdventureEventData.AdventureEventReward r3 = new AdventureEventData.AdventureEventReward(); - r0.minWins = 0; - r0.maxWins = 0; - r0.cardRewards = new Deck[]{rewardPacks[0]}; - rewards[0] = r0; - r1.minWins = 1; - r1.maxWins = 3; - r1.cardRewards = new Deck[]{rewardPacks[1], rewardPacks[2]}; - rewards[1] = r1; - r2.minWins = 2; - r2.maxWins = 3; - r2.itemRewards = new String[]{"Challenge Coin"}; - rewards[2] = r2; - } + setupDraftRewards(); } else if (format == AdventureEventController.EventFormat.Jumpstart) { - int numPacksToPickFrom = 6; - generateParticipants(7); - cardBlock = pickJumpstartCardBlock(); if (cardBlock == null) return; cardBlockName = cardBlock.getName(); - jumpstartBoosters = AdventureEventController.instance().getJumpstartBoosters(cardBlock, numPacksToPickFrom); - + jumpstartBoosters = AdventureEventController.instance().getJumpstartBoosters(cardBlock, JUMPSTART_TO_PICK_FROM); packConfiguration = new String[]{cardBlock.getLandSet().getCode(), cardBlock.getLandSet().getCode(), cardBlock.getLandSet().getCode()}; - for (AdventureEventParticipant participant : participants) { - List availableOptions = AdventureEventController.instance().getJumpstartBoosters(cardBlock, numPacksToPickFrom); - List chosenPacks = new ArrayList<>(); - - Map> themeMap = new HashMap<>(); - - //1. Search for matching themes from deck names, fill deck with them if possible - for (Deck option : availableOptions) { - // This matches up theme for all except DMU - with only 2 per color the next part will handle that - String theme = option.getName().replaceAll("\\d$", "").trim(); - if (!themeMap.containsKey(theme)) { - themeMap.put(theme, new ArrayList<>()); - } - themeMap.get(theme).add(option); - } - - String themeAdded = ""; - boolean done = false; - while (!done) { - for (int i = packConfiguration.length - chosenPacks.size(); i > 1; i--) { - if (themeAdded.isEmpty()) { - for (String theme : themeMap.keySet()) { - if (themeMap.get(theme).size() >= i) { - themeAdded = theme; - break; - } - } - } - } - if (themeAdded.isEmpty()) { - done = true; - } else { - chosenPacks.addAll(themeMap.get(themeAdded).subList(0, Math.min(themeMap.get(themeAdded).size(), packConfiguration.length - chosenPacks.size()))); - availableOptions.removeAll(themeMap.get(themeAdded)); - themeMap.remove(themeAdded); - themeAdded = ""; - } - } - - //2. Fill remaining slots with colors already picked whenever possible - Map> colorMap = new HashMap<>(); - for (Deck option : availableOptions) { - if (option.getTags().contains("black")) - colorMap.computeIfAbsent("black", (k) -> new ArrayList<>()).add(option); - if (option.getTags().contains("blue")) - colorMap.computeIfAbsent("blue", (k) -> new ArrayList<>()).add(option); - if (option.getTags().contains("green")) - colorMap.computeIfAbsent("green", (k) -> new ArrayList<>()).add(option); - if (option.getTags().contains("red")) - colorMap.computeIfAbsent("red", (k) -> new ArrayList<>()).add(option); - if (option.getTags().contains("white")) - colorMap.computeIfAbsent("white", (k) -> new ArrayList<>()).add(option); - if (option.getTags().contains("multicolor")) - colorMap.computeIfAbsent("multicolor", (k) -> new ArrayList<>()).add(option); - if (option.getTags().contains("colorless")) - colorMap.computeIfAbsent("colorless", (k) -> new ArrayList<>()).add(option); - } - - done = false; - String colorAdded = ""; - while (!done) { - List colorsAlreadyPicked = new ArrayList<>(); - for (Deck picked : chosenPacks) { - if (picked.getTags().contains("black")) colorsAlreadyPicked.add("black"); - if (picked.getTags().contains("blue")) colorsAlreadyPicked.add("blue"); - if (picked.getTags().contains("green")) colorsAlreadyPicked.add("green"); - if (picked.getTags().contains("red")) colorsAlreadyPicked.add("red"); - if (picked.getTags().contains("white")) colorsAlreadyPicked.add("white"); - if (picked.getTags().contains("multicolor")) colorsAlreadyPicked.add("multicolor"); - if (picked.getTags().contains("colorless")) colorsAlreadyPicked.add("colorless"); - } - - while (colorAdded.isEmpty() && !colorsAlreadyPicked.isEmpty()) { - String colorToTry = Aggregates.removeRandom(colorsAlreadyPicked); - for (Deck toCheck : availableOptions) { - if (toCheck.getTags().contains(colorToTry)) { - colorAdded = colorToTry; - chosenPacks.add(toCheck); - availableOptions.remove(toCheck); - break; - } - } - } - //3. If no matching color found and need more packs, add any available at random. - if (packConfiguration.length > chosenPacks.size() && colorAdded.isEmpty() && !availableOptions.isEmpty()) { - chosenPacks.add(Aggregates.removeRandom(availableOptions)); - colorAdded = ""; - } else { - done = colorAdded.isEmpty() || packConfiguration.length <= chosenPacks.size(); - colorAdded = ""; - } - - } - participant.registeredDeck = new Deck(); - for (Deck chosen : chosenPacks) { - participant.registeredDeck.getMain().addAllFlat(chosen.getMain().toFlatList()); - } - } - - rewards = new AdventureEventData.AdventureEventReward[4]; - AdventureEventData.AdventureEventReward r0 = new AdventureEventData.AdventureEventReward(); - AdventureEventData.AdventureEventReward r1 = new AdventureEventData.AdventureEventReward(); - AdventureEventData.AdventureEventReward r2 = new AdventureEventData.AdventureEventReward(); - AdventureEventData.AdventureEventReward r3 = new AdventureEventData.AdventureEventReward(); - - RewardData r0gold = new RewardData(); - r0gold.count = 100; - r0gold.type = "gold"; - r0.rewards = new RewardData[]{r0gold}; - r0.minWins = 1; - r0.maxWins = 1; - rewards[0] = r0; - RewardData r1gold = new RewardData(); - r1gold.count = 200; - r1gold.type = "gold"; - r1.rewards = new RewardData[]{r1gold}; - r1.minWins = 2; - r1.maxWins = 2; - rewards[1] = r1; - r2.minWins = 3; - r2.maxWins = 3; - RewardData r2gold = new RewardData(); - r2gold.count = 500; - r2gold.type = "gold"; - r2.rewards = new RewardData[]{r2gold}; - rewards[2] = r2; - r3.minWins = 0; - r3.maxWins = 3; - rewards[3] = r3; - //r3 will be the selected card packs + setupJumpstartRewards(); } } @@ -292,7 +136,7 @@ public class AdventureEventData implements Serializable { Random placeholder = MyRandom.getRandom(); MyRandom.setRandom(getEventRandom()); if (draft == null && (eventStatus == AdventureEventController.EventStatus.Available || eventStatus == AdventureEventController.EventStatus.Entered)) { - draft = BoosterDraft.createDraft(LimitedPoolType.Block, getCardBlock(), packConfiguration, 8); + draft = BoosterDraft.createDraft(LimitedPoolType.Block, getCardBlock(), packConfiguration, participants.length); registeredDeck = draft.getHumanPlayer().getDeck(); assignPlayerNames(draft); } @@ -309,6 +153,7 @@ public class AdventureEventData implements Serializable { private static final Predicate filterStandard = FModel.getFormats().getStandard().editionLegalPredicate; public static Predicate selectSetPool() { + // Should we negate any of these to avoid overlap? final int rollD100 = MyRandom.getRandom().nextInt(100); Predicate rolledFilter; if (rollD100 < 30) { @@ -323,7 +168,6 @@ public class AdventureEventData implements Serializable { return rolledFilter; } - private CardBlock pickWeightedCardBlock() { CardEdition.Collection editions = FModel.getMagicDb().getEditions(); ConfigData configData = Config.instance().getConfigData(); @@ -432,6 +276,66 @@ public class AdventureEventData implements Serializable { return legalBlocks.isEmpty() ? null : Aggregates.random(legalBlocks); } + private void setupDraftRewards() { + //Below all to be fully generated in later release + rewardPacks = getRewardPacks(3); + if (cardBlock != null) { + packConfiguration = getBoosterConfiguration(cardBlock); + + rewards = new AdventureEventData.AdventureEventReward[4]; + AdventureEventData.AdventureEventReward r0 = new AdventureEventData.AdventureEventReward(); + AdventureEventData.AdventureEventReward r1 = new AdventureEventData.AdventureEventReward(); + AdventureEventData.AdventureEventReward r2 = new AdventureEventData.AdventureEventReward(); + AdventureEventData.AdventureEventReward r3 = new AdventureEventData.AdventureEventReward(); + r0.minWins = 0; + r0.maxWins = 0; + r0.cardRewards = new Deck[]{rewardPacks[0]}; + rewards[0] = r0; + r1.minWins = 1; + r1.maxWins = 3; + r1.cardRewards = new Deck[]{rewardPacks[1], rewardPacks[2]}; + rewards[1] = r1; + r2.minWins = 2; + r2.maxWins = 3; + r2.itemRewards = new String[]{"Challenge Coin"}; + rewards[2] = r2; + } + } + + private void setupJumpstartRewards() { + rewards = new AdventureEventData.AdventureEventReward[4]; + AdventureEventData.AdventureEventReward r0 = new AdventureEventData.AdventureEventReward(); + AdventureEventData.AdventureEventReward r1 = new AdventureEventData.AdventureEventReward(); + AdventureEventData.AdventureEventReward r2 = new AdventureEventData.AdventureEventReward(); + AdventureEventData.AdventureEventReward r3 = new AdventureEventData.AdventureEventReward(); + + RewardData r0gold = new RewardData(); + r0gold.count = 100; + r0gold.type = "gold"; + r0.rewards = new RewardData[]{r0gold}; + r0.minWins = 1; + r0.maxWins = 1; + rewards[0] = r0; + RewardData r1gold = new RewardData(); + r1gold.count = 200; + r1gold.type = "gold"; + r1.rewards = new RewardData[]{r1gold}; + r1.minWins = 2; + r1.maxWins = 2; + rewards[1] = r1; + r2.minWins = 3; + r2.maxWins = 3; + RewardData r2gold = new RewardData(); + r2gold.count = 500; + r2gold.type = "gold"; + r2.rewards = new RewardData[]{r2gold}; + rewards[2] = r2; + r3.minWins = 0; + r3.maxWins = 3; + rewards[3] = r3; + //r3 will be the selected card packs + } + public String[] getBoosterConfiguration(CardBlock selectedBlock) { Random placeholder = MyRandom.getRandom(); @@ -465,6 +369,106 @@ public class AdventureEventData implements Serializable { } participants[numberOfOpponents] = getHumanPlayer(); + + if (format == AdventureEventController.EventFormat.Jumpstart) { + for (AdventureEventParticipant participant : participants) { + List availableOptions = AdventureEventController.instance().getJumpstartBoosters(cardBlock, JUMPSTART_TO_PICK_FROM); + List chosenPacks = new ArrayList<>(); + + Map> themeMap = new HashMap<>(); + + //1. Search for matching themes from deck names, fill deck with them if possible + for (Deck option : availableOptions) { + // This matches up theme for all except DMU - with only 2 per color the next part will handle that + String theme = option.getName().replaceAll("\\d$", "").trim(); + if (!themeMap.containsKey(theme)) { + themeMap.put(theme, new ArrayList<>()); + } + themeMap.get(theme).add(option); + } + + String themeAdded = ""; + boolean done = false; + while (!done) { + for (int i = packConfiguration.length - chosenPacks.size(); i > 1; i--) { + if (themeAdded.isEmpty()) { + for (String theme : themeMap.keySet()) { + if (themeMap.get(theme).size() >= i) { + themeAdded = theme; + break; + } + } + } + } + if (themeAdded.isEmpty()) { + done = true; + } else { + chosenPacks.addAll(themeMap.get(themeAdded).subList(0, Math.min(themeMap.get(themeAdded).size(), packConfiguration.length - chosenPacks.size()))); + availableOptions.removeAll(themeMap.get(themeAdded)); + themeMap.remove(themeAdded); + themeAdded = ""; + } + } + + //2. Fill remaining slots with colors already picked whenever possible + Map> colorMap = new HashMap<>(); + for (Deck option : availableOptions) { + if (option.getTags().contains("black")) + colorMap.computeIfAbsent("black", (k) -> new ArrayList<>()).add(option); + if (option.getTags().contains("blue")) + colorMap.computeIfAbsent("blue", (k) -> new ArrayList<>()).add(option); + if (option.getTags().contains("green")) + colorMap.computeIfAbsent("green", (k) -> new ArrayList<>()).add(option); + if (option.getTags().contains("red")) + colorMap.computeIfAbsent("red", (k) -> new ArrayList<>()).add(option); + if (option.getTags().contains("white")) + colorMap.computeIfAbsent("white", (k) -> new ArrayList<>()).add(option); + if (option.getTags().contains("multicolor")) + colorMap.computeIfAbsent("multicolor", (k) -> new ArrayList<>()).add(option); + if (option.getTags().contains("colorless")) + colorMap.computeIfAbsent("colorless", (k) -> new ArrayList<>()).add(option); + } + + done = false; + String colorAdded = ""; + while (!done) { + List colorsAlreadyPicked = new ArrayList<>(); + for (Deck picked : chosenPacks) { + if (picked.getTags().contains("black")) colorsAlreadyPicked.add("black"); + if (picked.getTags().contains("blue")) colorsAlreadyPicked.add("blue"); + if (picked.getTags().contains("green")) colorsAlreadyPicked.add("green"); + if (picked.getTags().contains("red")) colorsAlreadyPicked.add("red"); + if (picked.getTags().contains("white")) colorsAlreadyPicked.add("white"); + if (picked.getTags().contains("multicolor")) colorsAlreadyPicked.add("multicolor"); + if (picked.getTags().contains("colorless")) colorsAlreadyPicked.add("colorless"); + } + + while (colorAdded.isEmpty() && !colorsAlreadyPicked.isEmpty()) { + String colorToTry = Aggregates.removeRandom(colorsAlreadyPicked); + for (Deck toCheck : availableOptions) { + if (toCheck.getTags().contains(colorToTry)) { + colorAdded = colorToTry; + chosenPacks.add(toCheck); + availableOptions.remove(toCheck); + break; + } + } + } + //3. If no matching color found and need more packs, add any available at random. + if (packConfiguration.length > chosenPacks.size() && colorAdded.isEmpty() && !availableOptions.isEmpty()) { + chosenPacks.add(Aggregates.removeRandom(availableOptions)); + colorAdded = ""; + } else { + done = colorAdded.isEmpty() || packConfiguration.length <= chosenPacks.size(); + colorAdded = ""; + } + } + participant.registeredDeck = new Deck(); + for (Deck chosen : chosenPacks) { + participant.registeredDeck.getMain().addAllFlat(chosen.getMain().toFlatList()); + } + } + } } private void assignPlayerNames(BoosterDraft draft) { @@ -517,6 +521,31 @@ public class AdventureEventData implements Serializable { } matches.get(round).add(match); } + } else if (style == AdventureEventController.EventStyle.RoundRobin) { + // In a roundrobin everyone plays everyone else once + // We do have this logic already in ForgeTOurnament, we should see if we could reuse it + matches.put(round, new ArrayList<>()); + activePlayers = Arrays.stream(participants).collect(Collectors.toList()); + + if (round > 1) { + AdventureEventParticipant pivot = activePlayers.remove(0); + for(int i = 1; i < round; i++) { + // Rotate X amount of players, where X is the current round-1 + AdventureEventParticipant rotate = activePlayers.remove(0); + activePlayers.add(rotate); + } + activePlayers.add(0, pivot); + } + + int numPlayers = activePlayers.size(); + for (int i = 0; i < numPlayers / 2; i++) { + AdventureEventMatch match = new AdventureEventMatch(); + match.p1 = activePlayers.get(i); + match.p2 = activePlayers.get(numPlayers - i - 1); + matches.get(round).add(match); + } + } else { + System.out.println(style + " not yet implemented!!!"); } return matches.get(currentRound); } @@ -579,7 +608,58 @@ public class AdventureEventData implements Serializable { } //todo: more robust logic for event types that can be won without perfect record (Swiss w/cut, round robin) - playerWon = matchesLost == 0 || matchesWon == rounds; + if (style == AdventureEventController.EventStyle.Bracket) { + playerWon = matchesLost == 0 || matchesWon == rounds; + } else if (style == AdventureEventController.EventStyle.RoundRobin) { + if (matchesWon == rounds) { + playerWon = true; + } else { + //If multiple players are tied for first, only the one with the best tiebreaker wins + List topPlayers = new ArrayList<>(); + int bestRecord = 0; + for (AdventureEventParticipant p : participants) { + if (p.wins > bestRecord) { + bestRecord = p.wins; + topPlayers.clear(); + topPlayers.add(p); + } else if (p.wins == bestRecord) { + topPlayers.add(p); + } + } + if (topPlayers.size() == 1) { + playerWon = topPlayers.get(0).getName().equals(getHumanPlayer().getName()); + } else { + //multiple players tied for first, use tiebreaker + Map tiebreakers = new HashMap<>(); + for (AdventureEventParticipant p : topPlayers) { + int tb = 0; + for (AdventureEventMatch m : matches.values().stream().flatMap(List::stream).collect(Collectors.toList())) { + if (m.p1 == p && m.winner != null && m.winner != p) { + tb += m.p2.wins; + } else if (m.p2 == p && m.winner != null && m.winner != p) { + tb += m.p1.wins; + } + } + tiebreakers.put(p, tb); + } + int bestTiebreaker = 0; + AdventureEventParticipant winner = null; + boolean tie = false; + for (AdventureEventParticipant p : tiebreakers.keySet()) { + if (tiebreakers.get(p) > bestTiebreaker) { + bestTiebreaker = tiebreakers.get(p); + winner = p; + tie = false; + } else if (tiebreakers.get(p) == bestTiebreaker) { + tie = true; + } + } + playerWon = !tie && winner != null && winner.getName().equals(getHumanPlayer().getName()); + } + } + } else { + playerWon = false; + } eventStatus = AdventureEventController.EventStatus.Awarded; } @@ -809,7 +889,7 @@ public class AdventureEventData implements Serializable { public boolean isNoSell = false; } - enum PairingStyle { + public enum PairingStyle { SingleElimination, DoubleElimination, Swiss, diff --git a/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java b/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java index f7f1b0e77c1..c974b02e9c1 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java @@ -277,7 +277,7 @@ public class DuelScene extends ForgeScene { currentEnemy = enemy.getData(); boolean bossBattle = currentEnemy.boss; - for (int i = 0; i < 8 && currentEnemy != null; i++) { + for (int i = 0; i < playerCount && currentEnemy != null; i++) { Deck deck; if (this.chaosBattle) { //random challenge for chaos mode diff --git a/forge-gui-mobile/src/forge/adventure/scene/EventScene.java b/forge-gui-mobile/src/forge/adventure/scene/EventScene.java index efa8ea274b6..1c7c1828d6b 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/EventScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/EventScene.java @@ -51,6 +51,7 @@ public class EventScene extends MenuScene implements IAfterMatch { static PointOfInterestChanges changes; private Array entryDialog; + private AdventureEventData.AdventureEventMatch humanMatch = null; private int packsSelected = 0; //Used for meta drafts, booster drafts will use existing logic. @@ -490,7 +491,7 @@ public class EventScene extends MenuScene implements IAfterMatch { } public void startRound() { - for (AdventureEventData.AdventureEventMatch match : currentEvent.matches.get(currentEvent.currentRound)) { + for (AdventureEventData.AdventureEventMatch match : currentEvent.getMatches(currentEvent.currentRound)) { match.round = currentEvent.currentRound; if (match.winner != null) continue; @@ -539,8 +540,6 @@ public class EventScene extends MenuScene implements IAfterMatch { } } - AdventureEventData.AdventureEventMatch humanMatch = null; - public void setWinner(boolean winner, boolean isArena) { if (winner) { humanMatch.winner = humanMatch.p1; diff --git a/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java b/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java index 2e6c1c1d1d5..15a3b2100c5 100644 --- a/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java +++ b/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java @@ -5,6 +5,7 @@ import forge.StaticData; import forge.adventure.data.AdventureEventData; import forge.adventure.player.AdventurePlayer; import forge.adventure.pointofintrest.PointOfInterestChanges; +import forge.card.CardEdition; import forge.deck.Deck; import forge.item.BoosterPack; import forge.item.PaperCard; @@ -99,8 +100,9 @@ public class AdventureEventController implements Serializable { AdventureEventData e; - // TODO After a certain amount of wins, stop offering jump start events - if (random.nextInt(10) <= 2) { + // After a certain amount of wins, stop offering jump start events + if (Current.player().getStatistic().totalWins() < 10 && + random.nextInt(10) <= 2) { e = new AdventureEventData(eventSeed, EventFormat.Jumpstart); } else { e = new AdventureEventData(eventSeed, EventFormat.Draft); @@ -110,12 +112,26 @@ public class AdventureEventController implements Serializable { //covers cases where (somehow) editions that do not match the event style have been picked up return null; } + + // If chosen event seed recommends a 4 person pod, run it as a RoundRobin + CardEdition firstSet = e.cardBlock.getSets().get(0); + int podSize = firstSet.getDraftOptions().getRecommendedPodSize(); + e.sourceID = pointID; e.eventOrigin = eventOrigin; - e.eventRules = new AdventureEventData.AdventureEventRules(e.format, changes == null ? 1f : changes.getTownPriceModifier()); - e.style = style; + e.style = podSize == 4 ? EventStyle.RoundRobin : style; - switch (style) { + AdventureEventData.PairingStyle pairingStyle; + if (e.style == EventStyle.RoundRobin) { + pairingStyle = AdventureEventData.PairingStyle.RoundRobin; + } else { + pairingStyle = AdventureEventData.PairingStyle.SingleElimination; + } + + e.eventRules = new AdventureEventData.AdventureEventRules(e.format, pairingStyle, changes == null ? 1f : changes.getTownPriceModifier()); + e.generateParticipants(podSize - 1); //-1 to account for the player + + switch (e.style) { case Swiss: case Bracket: e.rounds = (e.participants.length / 2) - 1; @@ -139,9 +155,8 @@ public class AdventureEventController implements Serializable { output.setComment(setCode); return output; } - public Deck generateBoosterByColor(String color) - { + public Deck generateBoosterByColor(String color) { List cards = BoosterPack.fromColor(color).getCards(); Deck output = new Deck(); output.getMain().add(cards); From 1b419a13e8837bb48bd766095d1aa215687ff390 Mon Sep 17 00:00:00 2001 From: Agetian Date: Fri, 26 Sep 2025 16:58:37 +0300 Subject: [PATCH 039/230] - Fix isWebSligned in Card. - Add puzzleds PS_SPM1 and PS_TLA1. --- .../src/main/java/forge/game/card/Card.java | 2 +- forge-gui/res/puzzle/PS_SPM1.pzl | 16 ++++++++++++++++ forge-gui/res/puzzle/PS_TLA1.pzl | 17 +++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 forge-gui/res/puzzle/PS_SPM1.pzl create mode 100644 forge-gui/res/puzzle/PS_TLA1.pzl 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 776f9f6a0ae..37722d54e81 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -6861,7 +6861,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr } public boolean isWebSlinged() { - return getCastSA() != null & getCastSA().isAlternativeCost(AlternativeCost.WebSlinging); + return getCastSA() != null && getCastSA().isAlternativeCost(AlternativeCost.WebSlinging); } public boolean isSpecialized() { diff --git a/forge-gui/res/puzzle/PS_SPM1.pzl b/forge-gui/res/puzzle/PS_SPM1.pzl new file mode 100644 index 00000000000..84014ec7896 --- /dev/null +++ b/forge-gui/res/puzzle/PS_SPM1.pzl @@ -0,0 +1,16 @@ +[metadata] +Name:Possibility Storm - Marvel's Spider-Man #01 +URL:https://i1.wp.com/www.possibilitystorm.com/wp-content/uploads/2025/09/latest-1-scaled.jpg?ssl=1 +Goal:Win +Turns:1 +Difficulty:Uncommon +Description:Win this turn. Ensure your solution satisfies all possible blocks. Good luck! +[state] +turn=1 +activeplayer=p0 +activephase=MAIN1 +p0life=20 +p0hand=Rhino, Barreling Brute;Spider-Man India;Daily Bugle Reporters;Become Brutes;Lurking Lizards +p0battlefield=Scarlet Spider, Ben Reilly;Spider Manifestation;Gene Pollinator;Forest;Forest;Forest;Plains;Plains;Kavaron, Memorial World|Counters:CHARGE=2 +p1life=14 +p1battlefield=Carnage, Crimson Chaos diff --git a/forge-gui/res/puzzle/PS_TLA1.pzl b/forge-gui/res/puzzle/PS_TLA1.pzl new file mode 100644 index 00000000000..63ccd26d299 --- /dev/null +++ b/forge-gui/res/puzzle/PS_TLA1.pzl @@ -0,0 +1,17 @@ +[metadata] +Name:Possibility Storm - Avatar the Last Airbender #01 +URL:https://i2.wp.com/www.possibilitystorm.com/wp-content/uploads/2025/08/latest-3-scaled.jpg?ssl=1 +Goal:Win +Turns:1 +Difficulty:Rare +Description:Win this turn. Your Forest is a 0/0 creature (with one +1/+1 counter on it.) Ensure your solution satisfies all blocks. Good luck! +[state] +turn=1 +activeplayer=p0 +activephase=MAIN1 +p0life=3 +p0hand=Jeong Jeong's Deserters;Earthbending Student;Bulk Up;How to Start a Riot +p0battlefield=Haru, Hidden Talent;Garruk's Uprising;Forest|Id:1;Forgotten Monument;Forgotten Monument;Forgotten Monument;Restless Ridgeline;Cavernous Maw +p1life=11 +p1battlefield=Vindictive Warden;Vindictive Warden +p0precast=Haru, Hidden Talent:TrigEarthbend->1 From 6dd10be044733f0cc06ca1df4947b471524602f2 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Fri, 26 Sep 2025 17:00:33 +0200 Subject: [PATCH 040/230] Update wild_pack_squad.txt Closes #8786 --- forge-gui/res/cardsfolder/w/wild_pack_squad.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/w/wild_pack_squad.txt b/forge-gui/res/cardsfolder/w/wild_pack_squad.txt index 65b255a3f89..d950b866108 100644 --- a/forge-gui/res/cardsfolder/w/wild_pack_squad.txt +++ b/forge-gui/res/cardsfolder/w/wild_pack_squad.txt @@ -1,7 +1,7 @@ Name:Wild Pack Squad ManaCost:2 W Types:Creature Human Mercenary -PT:2/2 +PT:2/3 T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ At the beginning of combat on your turn, up to one target creature gains first strike and vigilance until end of turn. SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target creature | KW$ First Strike & Vigilance SVar:PlayMain1:TRUE From fd5555fdd6fc891243ed4a6096eccc0f13322781 Mon Sep 17 00:00:00 2001 From: Paul Hammerton Date: Fri, 26 Sep 2025 17:46:48 +0100 Subject: [PATCH 041/230] Edition updates: AA3, AA4, ECL, MAR, PMEI, SLD, TLE --- forge-gui/res/editions/Arena Anthology 3.txt | 44 +++++++++--------- forge-gui/res/editions/Arena Anthology 4.txt | 46 +++++++++---------- .../Avatar The Last Airbender Eternal.txt | 2 +- forge-gui/res/editions/Lorwyn Eclipsed.txt | 1 + forge-gui/res/editions/Marvel Universe.txt | 2 +- .../Media and Collaboration Promos.txt | 2 + .../res/editions/Secret Lair Drop Series.txt | 5 ++ 7 files changed, 55 insertions(+), 47 deletions(-) diff --git a/forge-gui/res/editions/Arena Anthology 3.txt b/forge-gui/res/editions/Arena Anthology 3.txt index 0f43f948c99..dc2cdd155be 100644 --- a/forge-gui/res/editions/Arena Anthology 3.txt +++ b/forge-gui/res/editions/Arena Anthology 3.txt @@ -6,28 +6,28 @@ Type=Online ScryfallCode=AA3 [cards] -1 U Flickerwisp @Jeremy Enecio -2 R Staff of the Storyteller @Dan Scott -3 C Force Spike @Nelson DeCastro -4 C Miscalculation @Jeff Laubenstein -5 C Peek @Adam Rex -6 R Dauthi Voidwalker @Sidharth Chaturyedi -7 U Necromancy @Pete Venters -8 C Assault Strobe @Kev Walker -9 R Broadside Bombardiers @Tomek Larek -10 C Chain Lightning @Christopher Moeller -11 U Gut, True Soul Zealot @Wayne Reynolds -12 U Oxidda Scrapmelter @Igor Kieryluk -13 C Ambush Viper @Alan Pollack -14 C Crop Rotation @DiTerlizzi -15 R Exploration @Lindsey Look -16 R Noble Hierarch @Mark Zug -17 R Fallen Shinobi @Dmitry Burmak -18 M Batterskull @Mark Zug -19 M Emrakul, the Aeons Torn @Mark Tedin -20 M Kaldra Compleat @Vincent Proce -21 R Memory Jar @Donato Giancola -22 M Ulamog, the Infinite Gyre @Aleksi Briclot +1 M Emrakul, the Aeons Torn @Mark Tedin +2 M Ulamog, the Infinite Gyre @Aleksi Briclot +3 U Flickerwisp @Jeremy Enecio +4 R Staff of the Storyteller @Dan Murayama Scott +5 C Force Spike @Nelson DeCastro +6 C Miscalculation @Jeff Laubenstein +7 C Peek @Adam Rex +8 R Dauthi Voidwalker @Sidharth Chaturvedi +9 U Necromancy @Pete Venters +10 C Assault Strobe @Kev Walker +11 R Broadside Bombardiers @Tomek Larek +12 C Chain Lightning @Christopher Moeller +13 U Gut, True Soul Zealot @Wayne Reynolds +14 U Oxidda Scrapmelter @Igor Kieryluk +15 C Ambush Viper @Alan Pollack +16 C Crop Rotation @DiTerlizzi +17 R Exploration @Lindsey Look +18 R Noble Hierarch @Mark Zug +19 R Fallen Shinobi @Dmitry Burmak +20 M Batterskull @Mark Zug +21 M Kaldra Compleat @Vincent Proce +22 R Memory Jar @Donato Giancola 23 R Umezawa's Jitte @Christopher Moeller 24 R Shelldock Isle @Mark Tedin 25 R Yavimaya, Cradle of Growth @Sarah Finnigan diff --git a/forge-gui/res/editions/Arena Anthology 4.txt b/forge-gui/res/editions/Arena Anthology 4.txt index d2b9897d48a..54a5e0b8bcb 100644 --- a/forge-gui/res/editions/Arena Anthology 4.txt +++ b/forge-gui/res/editions/Arena Anthology 4.txt @@ -9,28 +9,28 @@ ScryfallCode=AA4 1 R Blazing Archon @Zoltan Boros & Gabor Szikszai 2 R Bringer of the White Dawn @Kev Walker 3 M Iona, Shield of Emeria @Jason Chan -4 C Scaled Wurm @Wayne England -5 C Stormfront Pegasus @rk post -6 R Bringer of the Blue Dawn @Greg Staples -7 R Chromescale Drake @Ben Thompson -8 R Hoverguard Sweepers @Mark A. Nelson -9 C Kraken Hatchling @Jason Felix -10 R Thing from the Deep @Parente -11 R Tidespout Tyrant @Dany Orizio -12 R Baleful Force @Eytan Zana -13 R Bringer of the Black Dawn @Carl Critchlow -14 M Demon of Death's Gate @Vance Kovacs -15 R Dread Cacodemon @Izzy -16 R Kuro, Pitlord @Jon Foster -17 R Bringer of the Red Dawn @Christopher Moeller -18 R Dragon Tyrant @Kev Walker -19 R Fire Dragon @William Simpson -20 R Furnace Dragon @Matthew D. Wilson -21 R Magmatic Force @Jung Park -22 R Bringer of the Green Dawn @Jim Murray -23 C Crash of Rhinos @Steve White -24 R Gigantomancer @Chippy -25 R Regal Force @Brandon Kitkouski +4 C Stormfront Pegasus @rk post +5 R Bringer of the Blue Dawn @Greg Staples +6 R Chromescale Drake @Ben Thompson +7 R Hoverguard Sweepers @Mark A. Nelson +8 C Kraken Hatchling @Jason Felix +9 R Thing from the Deep @Paolo Parente +10 R Tidespout Tyrant @Dany Orizio +11 R Baleful Force @Eytan Zana +12 R Bringer of the Black Dawn @Carl Critchlow +13 M Demon of Death's Gate @Vance Kovacs +14 R Dread Cacodemon @Izzy +15 R Kuro, Pitlord @Jon Foster +16 R Bringer of the Red Dawn @Christopher Moeller +17 R Dragon Tyrant @Kev Walker +18 R Fire Dragon @William Simpson +19 R Furnace Dragon @Matthew D. Wilson +20 R Magmatic Force @Jung Park +21 R Bringer of the Green Dawn @Jim Murray +22 C Crash of Rhinos @Steve White +23 R Gigantomancer @Chippy +24 R Regal Force @Brandon Kitkouski +25 C Scaled Wurm @Wayne England 26 R Wolf Pack @Yang Jun Kwon 27 R Colossus of Sardia @Greg Staples -28 U Stratadon @Brian Snoddy +28 U Stratadon @Brian Snõddy diff --git a/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt b/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt index 5cab223e390..2bd102bd231 100644 --- a/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt +++ b/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt @@ -56,7 +56,7 @@ ScryfallCode=TLE 251 C Frog-Squirrels @Daniel Romanovsky 252 C Hippo-Cows @Brandon L. Hunt 253 U Match the Odds @Eilene Cherie -254 R Seismic Tutelage +254 R Seismic Tutelage @Florent Lebrun 255 U Hog-Monkey Rampage @Daniel Romanovsky 256 C Mechanical Glider @Salvatorre Zee Yazzie 257 C Feed the Swarm @Kotakan diff --git a/forge-gui/res/editions/Lorwyn Eclipsed.txt b/forge-gui/res/editions/Lorwyn Eclipsed.txt index f2d0f93df0b..af4067d14c1 100644 --- a/forge-gui/res/editions/Lorwyn Eclipsed.txt +++ b/forge-gui/res/editions/Lorwyn Eclipsed.txt @@ -6,6 +6,7 @@ Type=Expansion ScryfallCode=ECL [cards] +88 M Bitterbloom Bearer @Chris Rahn 224 R Figure of Fable @Omar Rayyan 288 R Sygg, Wanderwine Wisdom @Warren Mahy 310 M Bitterbloom Bearer @Taryn Knight diff --git a/forge-gui/res/editions/Marvel Universe.txt b/forge-gui/res/editions/Marvel Universe.txt index c413e66a41b..679e31df132 100644 --- a/forge-gui/res/editions/Marvel Universe.txt +++ b/forge-gui/res/editions/Marvel Universe.txt @@ -10,7 +10,7 @@ ScryfallCode=MAR 2 M Leyline Binding @Ron Frenz & Joe Rubinstein 3 M Nine Lives @C.F. Villa & Brian Reber 4 M Path to Exile @John Romita Sr. -5 M Reprieve @Alex Ross & Frank Giacola +5 M Reprieve @Alex Ross & Frank Giacoia 6 M Rest in Peace @Mark Buckingham & D'Israeli 7 M Wedding Ring @John Romita Sr. 8 M Clever Impersonator @Mark Bagley & Andrew Hennessy diff --git a/forge-gui/res/editions/Media and Collaboration Promos.txt b/forge-gui/res/editions/Media and Collaboration Promos.txt index 592f12e7a92..bc83dc17c83 100644 --- a/forge-gui/res/editions/Media and Collaboration Promos.txt +++ b/forge-gui/res/editions/Media and Collaboration Promos.txt @@ -83,3 +83,5 @@ ScryfallCode=PMEI 2025-16 R Spectacular Spider-Man @Alex Horley-Orlandelli 2025-17 R Huntmaster of the Fells @Mark Spears 2025-18 R Iron Spider, Stark Upgrade @Bachzim +2025-19 M Kaalia of the Vast @Justyna Dura +2025-20 R Chrome Host Seedshark @Donato Giancola diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index a929bd211bf..348a5b9bace 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -1909,6 +1909,11 @@ F1540 M Rainbow Dash @John Thacker 1942 R Mountain @Jon Vermilyea 1943 R Forest @Jon Vermilyea 1944 M Morophon, the Boundless @Natalie Andrewson +1945 R Plains @Ashley Dreyfus +1946 R Island @Ashley Dreyfus +1947 R Swamp @Ashley Dreyfus +1948 R Mountain @Ashley Dreyfus +1949 R Forest @Ashley Dreyfus 1950 R Plains @Pedro Potier 1951 R Island @Pedro Potier 1952 R Swamp @Pedro Potier From 0d020070a240f2bf03d17679a4998e3414c0fff8 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sat, 27 Sep 2025 01:20:04 +0200 Subject: [PATCH 042/230] Fix tapped cost parts blocking real payment (#8789) --- forge-ai/src/main/java/forge/ai/AiController.java | 2 +- forge-ai/src/main/java/forge/ai/AiCostDecision.java | 5 ++++- forge-ai/src/main/java/forge/ai/ComputerUtilMana.java | 11 ++++++----- forge-gui/res/cardsfolder/k/karns_sylex.txt | 3 +-- forge-gui/res/cardsfolder/s/spider_man_india.txt | 2 +- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 98f8608f8d3..00beb693357 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -925,7 +925,7 @@ public class AiController { // check if enough left (pass memory indirectly because we don't want to include those) Set tappedForMana = AiCardMemory.getMemorySet(player, MemorySet.PAYS_TAP_COST); - if (tappedForMana != null && tappedForMana.isEmpty() && + if (tappedForMana != null && !tappedForMana.isEmpty() && !ComputerUtilCost.checkTapTypeCost(player, sa.getPayCosts(), host, sa, new CardCollection(tappedForMana))) { return AiPlayDecision.CantAfford; } diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index 626e8898c67..65d59b3c534 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -29,12 +29,15 @@ public class AiCostDecision extends CostDecisionMakerBase { private final CardCollection tapped; public AiCostDecision(Player ai0, SpellAbility sa, final boolean effect) { + this(ai0, sa, effect, false); + } + public AiCostDecision(Player ai0, SpellAbility sa, final boolean effect, final boolean payMana) { super(ai0, effect, sa, sa.getHostCard()); discarded = new CardCollection(); tapped = new CardCollection(); Set tappedForMana = AiCardMemory.getMemorySet(ai0, MemorySet.PAYS_TAP_COST); - if (tappedForMana != null) { + if (!payMana && tappedForMana != null) { tapped.addAll(tappedForMana); } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 9518b852d63..dad9e7a2f4e 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -287,10 +287,6 @@ public class ComputerUtilMana { continue; } - if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST))) { - continue; - } - int amount = ma.hasParam("Amount") ? AbilityUtils.calculateAmount(ma.getHostCard(), ma.getParam("Amount"), ma) : 1; if (amount <= 0) { // wrong gamestate for variable amount @@ -357,9 +353,14 @@ public class ComputerUtilMana { continue; } + // these should come last since they reserve the paying cards + // (this means if a mana ability has both parts it doesn't currently undo reservations if the second part fails) if (!ComputerUtilCost.checkForManaSacrificeCost(ai, ma.getPayCosts(), ma, ma.isTrigger())) { continue; } + if (!ComputerUtilCost.checkTapTypeCost(ai, ma.getPayCosts(), ma.getHostCard(), sa, AiCardMemory.getMemorySet(ai, MemorySet.PAYS_TAP_COST))) { + continue; + } return paymentChoice; } @@ -819,7 +820,7 @@ public class ComputerUtilMana { sourcesForShards.values().removeIf(CardTraitPredicates.isHostCard(saPayment.getHostCard())); } else { final CostPayment pay = new CostPayment(saPayment.getPayCosts(), saPayment); - if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment, effect))) { + if (!pay.payComputerCosts(new AiCostDecision(ai, saPayment, effect, true))) { saList.remove(saPayment); continue; } diff --git a/forge-gui/res/cardsfolder/k/karns_sylex.txt b/forge-gui/res/cardsfolder/k/karns_sylex.txt index 68580d91e56..a852ea06373 100644 --- a/forge-gui/res/cardsfolder/k/karns_sylex.txt +++ b/forge-gui/res/cardsfolder/k/karns_sylex.txt @@ -3,8 +3,7 @@ ManaCost:3 Types:Legendary Artifact R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True -S:Mode$ CantPayLife | ValidPlayer$ Player | ValidCause$ Spell,Activated.nonManaAbility | ForCost$ True | Description$ Players can't pay life or sacrifice creatures to cast spells or activate abilities that aren't mana abilities. -S:Mode$ CantSacrifice | ValidCard$ Creature | ValidCause$ Spell,Activated.nonManaAbility | ForCost$ True | Secondary$ True | Description$ Players can't pay life or sacrifice creatures to cast spells or activate abilities that aren't mana abilities. +S:Mode$ CantPayLife | ValidPlayer$ Player | ValidCause$ Spell,Activated.nonManaAbility | ForCost$ True | Description$ Players can't pay life to cast spells or activate abilities that aren't mana abilities. A:AB$ DestroyAll | Cost$ X T Exile<1/CARDNAME> | ValidCards$ Permanent.nonLand+cmcLEX | SorcerySpeed$ True | SpellDescription$ Destroy each nonland permanent with mana value X or less. Activate only as a sorcery. SVar:X:Count$xPaid Oracle:Karn's Sylex enters tapped.\nPlayers can't pay life to cast spells or to activate abilities that aren't mana abilities.\n{X}, {T}, Exile Karn's Sylex: Destroy each nonland permanent with mana value X or less. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/s/spider_man_india.txt b/forge-gui/res/cardsfolder/s/spider_man_india.txt index 9066e790705..73dd5b52d25 100644 --- a/forge-gui/res/cardsfolder/s/spider_man_india.txt +++ b/forge-gui/res/cardsfolder/s/spider_man_india.txt @@ -1,7 +1,7 @@ Name:Spider-Man India ManaCost:3 G W Types:Legendary Creature Spider Human Hero -PT:4/3 +PT:4/4 K:Web-slinging:1 G W T:Mode$ SpellCast | ValidCard$ Card.Creature | ValidActivatingPlayer$ You | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Pavitr's Sevā — Whenever you cast a creature spell, put a +1/+1 counter on target creature you control. It gains flying until end of turn. SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBPump From 4ed06fae87a077d35038d11075d4e4e71f4596d7 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sat, 27 Sep 2025 08:08:53 +0800 Subject: [PATCH 043/230] add breadcrumb to GameAction moveTo --- .../src/main/java/forge/game/GameAction.java | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index b759be95564..dd35bf5ad53 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -57,6 +57,8 @@ import forge.item.PaperCard; import forge.util.*; import forge.util.collect.FCollection; import forge.util.collect.FCollectionView; +import io.sentry.Breadcrumb; +import io.sentry.Sentry; import org.apache.commons.lang3.tuple.ImmutablePair; import org.jgrapht.alg.cycle.SzwarcfiterLauerSimpleCycles; import org.jgrapht.graph.DefaultDirectedGraph; @@ -749,26 +751,29 @@ public class GameAction { public final Card moveTo(final ZoneType name, final Card c, final int libPosition, SpellAbility cause, Map params) { // Call specific functions to set PlayerZone, then move onto moveTo - switch(name) { - case Hand: return moveToHand(c, cause, params); - case Library: return moveToLibrary(c, libPosition, cause, params); - case Battlefield: return moveToPlay(c, c.getController(), cause, params); - case Graveyard: return moveToGraveyard(c, cause, params); - case Exile: - if (!c.canExiledBy(cause, true)) { - return null; - } - return exile(c, cause, params); - case Stack: return moveToStack(c, cause, params); - case PlanarDeck: - case SchemeDeck: - case AttractionDeck: - case ContraptionDeck: - return moveToVariantDeck(c, name, libPosition, cause, params); - case Junkyard: - return moveToJunkyard(c, cause, params); - default: // sideboard will also get there - return moveTo(c.getOwner().getZone(name), c, cause); + try { + return switch (name) { + case Hand -> moveToHand(c, cause, params); + case Library -> moveToLibrary(c, libPosition, cause, params); + case Battlefield -> moveToPlay(c, c.getController(), cause, params); + case Graveyard -> moveToGraveyard(c, cause, params); + case Exile -> !c.canExiledBy(cause, true) ? null : exile(c, cause, params); + case Stack -> moveToStack(c, cause, params); + case PlanarDeck, SchemeDeck, AttractionDeck, ContraptionDeck -> moveToVariantDeck(c, name, libPosition, cause, params); + case Junkyard -> moveToJunkyard(c, cause, params); + default -> moveTo(c.getOwner().getZone(name), c, cause); // sideboard will also get there + }; + } catch (Exception e) { + String msg = "GameAction:moveTo: Exception occured"; + + Breadcrumb bread = new Breadcrumb(msg); + bread.setData("Card", c.getName()); + bread.setData("SA", cause.toString()); + bread.setData("ZoneType", name.name()); + bread.setData("Player", c.getOwner()); + Sentry.addBreadcrumb(bread); + + throw new RuntimeException("Error in GameAction moveTo " + c.getName() + " to Player Zone " + name.name(), e); } } From a9c42fb340b44ef0e755ddd3b072631241ed2981 Mon Sep 17 00:00:00 2001 From: Paul Hammerton Date: Sat, 27 Sep 2025 10:40:42 +0100 Subject: [PATCH 044/230] Edition updates: ECL, OLGC, OVNT, SLD --- .../res/editions/Legacy Championship.txt | 6 +- forge-gui/res/editions/Lorwyn Eclipsed.txt | 31 +++++++++ .../res/editions/Secret Lair Drop Series.txt | 69 +++++++++++++++++++ .../res/editions/Vintage Championship.txt | 32 ++++----- 4 files changed, 119 insertions(+), 19 deletions(-) diff --git a/forge-gui/res/editions/Legacy Championship.txt b/forge-gui/res/editions/Legacy Championship.txt index 17215fc70c0..bfdafe4b4fa 100644 --- a/forge-gui/res/editions/Legacy Championship.txt +++ b/forge-gui/res/editions/Legacy Championship.txt @@ -30,6 +30,6 @@ ScryfallCode=OLGC 2022A S Gaea's Cradle @Ralph Horsley 2022B S Scrubland @Raoul Vitale 2022C S The Tabernacle at Pendrell Vale @Milivoj Ćeran -2023 M Lightning Bolt @rk post -2023A M Volcanic Island @Mark Tedin -2023EU M Force of Will @Alan Pollack +2023 S Lightning Bolt @rk post +2023A S Volcanic Island @Mark Tedin +2023EU S Force of Will @Alan Pollack diff --git a/forge-gui/res/editions/Lorwyn Eclipsed.txt b/forge-gui/res/editions/Lorwyn Eclipsed.txt index af4067d14c1..7018bb2668d 100644 --- a/forge-gui/res/editions/Lorwyn Eclipsed.txt +++ b/forge-gui/res/editions/Lorwyn Eclipsed.txt @@ -6,8 +6,39 @@ Type=Expansion ScryfallCode=ECL [cards] +13 M Eirdu, Carrier of Dawn @Lucas Graciano +27 M Morningtide's Light @Mark Poole +76 R Sygg, Wanderwine Wisdom @Justin Gerard 88 M Bitterbloom Bearer @Chris Rahn +124 R Ashling, Rekindled @Ilse Gort +186 R Mutable Explorer @Wayne Reynolds +205 R Ashling's Command @Iris Compiet +212 M Deceit @Svetlin Velinov +222 M Emptiness @Ryan Pancoast 224 R Figure of Fable @Omar Rayyan +262 R Blood Crypt @Adam Paquette +265 R Hallowed Fountain @Adam Paquette +266 R Overgrown Tomb @Adam Paquette +267 R Steam Vents @Adam Paquette +268 R Temple Garden @Adam Paquette +286 M Eirdu, Carrier of Dawn @Omar Rayyan 288 R Sygg, Wanderwine Wisdom @Warren Mahy +290 R Ashling, Rekindled @Chuck Lukacs +293 M Deceit @Kev Walker +294 M Emptiness @Jeff Miracola +301 M Morningtide's Light @adelinaillustration 310 M Bitterbloom Bearer @Taryn Knight 327 R Mutable Explorer @Felicita Sala +330 R Ashling's Command @adelinaillustration +347 R Hallowed Fountain @Justin Gerard +347☇ R Hallowed Fountain @Justin Gerard +348 R Steam Vents @Raoul Vitale +348☇ R Steam Vents @Raoul Vitale +349 R Blood Crypt @Valera Lutfullina +349☇ R Blood Crypt @Valera Lutfullina +350 R Overgrown Tomb @Matt Stewart +350☇ R Overgrown Tomb @Matt Stewart +351 R Temple Garden @Annie Stegg +351☇ R Temple Garden @Annie Stegg +352 M Bitterbloom Bearer @Rebecca Guay +372 R Figure of Fable @Omar Rayyan diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 348a5b9bace..7414e0e9e27 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -870,6 +870,7 @@ F869 R Blacker Lotus @Scott Okumura 902 R Deadeye Navigator @Jon Vermilyea 903 M The Locust God @See Machine 904 M The Scorpion God @Ricardo Cavolo +905 R Cryptic Command @Alexander Khabbazi 906 R Ignoble Hierarch @John F. Malta 907 R Seedborn Muse @Princess Hidir 908 R Arcane Signet @Alexander Forssberg @@ -1829,6 +1830,10 @@ F1540 M Rainbow Dash @John Thacker 1850 U Crib Swap @Brandon Dorman 1851 R Homeward Path @Franz Vohwinkel 1853 M Go-Shintai of Life's Origin @Yuko Shimizu +1854 M Armageddon @Kieran Yanner +1855 R Northern Paladin @Kieran Yanner +1856 M Demonic Tutor @Kieran Yanner +1857 R Lord of the Pit @Kieran Yanner 1858 R Day of Judgment @Néstor Ossandón Leal 1859 R Temporal Extortion @Aurore Folny 1860 R Toxic Deluge @Evyn Fong @@ -2067,10 +2072,73 @@ F1540 M Rainbow Dash @John Thacker 2109 R Sylvan Safekeeper @Ian Permana 2110 M Crucible of Worlds @Ian Permana 2111 R Zuran Orb @Ian Permana +2138 R Dandân @Kelogsloops +2139 R Dandân @Kelogsloops +2140 R Accumulated Knowledge @Ed He +2141 R Magical Hack @Samuele Bandini +2142 R Memory Lapse @Wylie Beckert +2143 R Mystic Sanctuary @Amélie Flechais +2165 M Heliod, Sun-Crowned @Magali Villeneuve +2166 R Steelshaper's Gift @Greg Staples +2167 R Swords to Plowshares @Lie Setiawan +2168 R Baral, Chief of Compliance @Kieran Yanner +2169 M Garruk Relentless @Justyna Dura +2170 R Reaper King @Alexander Mokhov +2171 R Glen Elendra Archmage @Jim Woodring +2172 M Guardian Project @Jim Woodring +2173 R Roon of the Hidden Realm @Jim Woodring +2174 R Soulherder @Jim Woodring +2175 M Jaws, Relentless Predator @Stephen Andrade +2176 R Descent into Avernus @Stephen Andrade +2177 R Reckless Endeavor @Stephen Andrade +2178 M Sneak Attack @Stephen Andrade +2179 R Abrade @Stephen Andrade +2181 M Bruvac the Grandiloquent @Akirant +2182 R Windfall @Dan Mumford +2183 M Captain N'ghathrod @Akirant +2184 R Nekusar, the Mindrazer @Akirant +2185 R Iron Maiden @Dan Mumford +2186 R Mindcrank @Dan Mumford +2187 R Lethal Scheme @Iron Maiden +2188 M Grave Titan @Iron Maiden +2189 R Animate Dead @Iron Maiden +2190 M Temporal Tresspass @Iron Maiden +2191 R Unearth @Iron Maiden +2192 R Lignify @Iron Maiden 2193 M Greensleeves, Maro-Sorcerer @Jason Loik & Matthew Cohen 2194 M Polyraptor @Ben Millar 2195 R Academy Manufactor @Ben Millar 2196 M Wurmcoil Engine @Jason Loik & Matthew Cohen +2197 M Ellie, Brick Master @Irvin Rodriguez +2198 M Joel, Resolute Survivor @Yongjae Choi +2202 M Abby, Merciless Soldier @Wayne Wu +2203 M Ellie, Vengeful Hunter @Irvin Rodriguez +2207 M Kratos, God of War @Magali Villeneuve +2212 M Atreus, Impulsive Son @Nathaniel Himawan +2213 M Kratos, Stoic Father @Nathaniel Himawan +2216 M Nathan Drake, Treasure Hunter @Piotr Dura +2221 M Aloy, Savior of Meridian @Crystal Fae +2226 M Jin Sakai, Ghost of Tsushima @Dominik Mayer +2282 R Vito, Thorn of the Dusk Rose @Sam Heimer +2283 R Satoru Umezawa @Sam Heimer +2284 M Voja, Jaws of the Conclave @Ryan Roadkill +2285 M Wilhelt, the Rotcleaver @Sam Heimer +2286 R Liberator, Urza's Battlethopter @Ryan Roadkill +2311 R Distant Melody @Paul Scott Canavan +2312 R Explore @Natalie Andrewson +2313 R Inspiring Call @Rian Gonzales +2314 R Chromatic Lantern @Rian Gonzales +2315 R Sol Ring @Nathan Jurevicius +2316 R Sphere of Safety @Evyn Fong +2317 R Miscast @Justyna Dura +2318 R Phyrexian Arena @Daarken +2319 R Tormenting Voice @Igor Krstic +2320 R Tamiyo's Safekeeping @Evyn Fong +2321 R Hullbreaker Horror @Narendra Bintara Adi +2322 R Maddening Cacophony @Nathaniel Himawan +2323 R Serum Visions @Justin Gerard +2324 M Umbris, Fear Manifest @Antonio José Manzanedo +2325 R Spellskite @Madeline Boni 7001 R Feed the Swarm @Stanislav Sherbakov 7002 R Forge Anew @Yongjae Choi 7003 R Silence @Evyn Fong @@ -2163,3 +2231,4 @@ VS C Viscera Seer @John Stanko 2065 c_a_treasure_sac @Josu Hernaiz 2094 c_a_treasure_sac @John Thacker 2101 c_1_1_a_myr @Caleb Meurer +2180 c_a_blood_draw @Stephen Andrade diff --git a/forge-gui/res/editions/Vintage Championship.txt b/forge-gui/res/editions/Vintage Championship.txt index 4623dadd3f5..d369647653d 100644 --- a/forge-gui/res/editions/Vintage Championship.txt +++ b/forge-gui/res/editions/Vintage Championship.txt @@ -17,27 +17,27 @@ ScryfallCode=OVNT 2011 R Time Walk @Chris Rahn 2012 R Timetwister @Matt Stewart 2013 R Ancestral Recall @Ryan Pancoast -2014 M Mox Pearl @Raoul Vitale -2015 M Mox Emerald @Raoul Vitale -2016EU M Mox Jet @Raoul Vitale -2016NA M Mox Sapphire @Raoul Vitale -2017EU M Mox Ruby @Raoul Vitale -2017NA M Black Lotus @Steven Belledin -2018 M Ancestral Recall @Raoul Vitale -2018A M Timetwister @Ralph Horsley -2018NA M Time Walk @Chris Seaman +2014 S Mox Pearl @Raoul Vitale +2015 S Mox Emerald @Raoul Vitale +2016EU S Mox Jet @Raoul Vitale +2016NA S Mox Sapphire @Raoul Vitale +2017EU S Mox Ruby @Raoul Vitale +2017NA S Black Lotus @Steven Belledin +2018 S Ancestral Recall @Raoul Vitale +2018A S Timetwister @Ralph Horsley +2018NA S Time Walk @Chris Seaman 2019 M Mox Pearl @Sidharth Chaturvedi 2019A M Mox Sapphire @Tyler Walpole -2019NA M Mox Jet @Filip Burburan +2019NA S Mox Jet @Filip Burburan 2020A M Tolarian Academy @Raoul Vitale 2020B M Library of Alexandria @Volkan Baǵa 2020C M Mishra's Workshop @Drew Baker 2021A M Mox Emerald @Eric Wilkerson 2021B M Timetwister @Chris Seaman 2021C M Mox Ruby @Jarel Threat -2022A M Bazaar of Baghdad @Chris Seaman -2022B M Time Walk @Tyler Walpole -2022C M Ancestral Recall @Volkan Baǵa -2023A M Black Lotus @Jarel Threat -2023EU M Black Lotus @Raoul Vitale -2023NA M Black Lotus @Scott M. Fischer +2022A S Bazaar of Baghdad @Chris Seaman +2022B S Time Walk @Tyler Walpole +2022C S Ancestral Recall @Volkan Baǵa +2023A S Black Lotus @Jarel Threat +2023EU S Black Lotus @Raoul Vitale +2023NA S Black Lotus @Scott M. Fischer From 4eccfa8177069bfc38da384fa7b835aaad8e0fea Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 28 Sep 2025 09:09:47 +0200 Subject: [PATCH 045/230] Restore fixes (#8800) --- .../main/java/forge/ai/ability/ConniveAi.java | 2 +- .../forge/ai/ability/ControlExchangeAi.java | 18 ++++++++--------- .../java/forge/ai/ability/MustBlockAi.java | 20 ++++--------------- .../main/java/forge/ai/ability/PhasesAi.java | 4 +--- .../res/cardsfolder/a/axelrod_gunnarson.txt | 2 +- .../b/brudiclad_telchor_engineer.txt | 4 ++-- .../cardsfolder/c/caesar_legions_emperor.txt | 2 +- .../cardsfolder/c/crisis_of_conscience.txt | 2 +- .../g/grishnakh_brash_instigator.txt | 2 +- .../res/cardsfolder/p/prosperity_tycoon.txt | 2 +- .../res/cardsfolder/s/soul_collector.txt | 2 +- 11 files changed, 22 insertions(+), 38 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/ConniveAi.java b/forge-ai/src/main/java/forge/ai/ability/ConniveAi.java index 3beb8d09463..dc7abc8cdf0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ConniveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ConniveAi.java @@ -119,7 +119,7 @@ public class ConniveAi extends SpellAbilityAi { } } return new AiAbilityDecision( - sa.isTargetNumberValid() && !sa.getTargets().isEmpty() ? 100 : 0, + sa.isTargetNumberValid() ? 100 : 0, sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.TargetingFailed ); } diff --git a/forge-ai/src/main/java/forge/ai/ability/ControlExchangeAi.java b/forge-ai/src/main/java/forge/ai/ability/ControlExchangeAi.java index 09faaaee18c..57262dd602a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ControlExchangeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ControlExchangeAi.java @@ -53,17 +53,15 @@ public class ControlExchangeAi extends SpellAbilityAi { if (mandatory) { return new AiAbilityDecision(100, AiPlayDecision.WillPlay); } - } else { - if (mandatory) { - AiAbilityDecision decision = chkDrawback(sa, aiPlayer); - if (sa.isTargetNumberValid()) { - return new AiAbilityDecision(100, AiPlayDecision.WillPlay); - } - - return decision; - } else { - return canPlay(aiPlayer, sa); + } else if (mandatory) { + AiAbilityDecision decision = chkDrawback(sa, aiPlayer); + if (sa.isTargetNumberValid()) { + return new AiAbilityDecision(100, AiPlayDecision.WillPlay); } + + return decision; + } else { + return canPlay(aiPlayer, sa); } return new AiAbilityDecision(100, AiPlayDecision.WillPlay); } diff --git a/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java b/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java index 407f3e00eb0..e98c6924426 100644 --- a/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/MustBlockAi.java @@ -6,10 +6,10 @@ import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.card.CardLists; +import forge.game.card.CardUtil; import forge.game.combat.Combat; import forge.game.combat.CombatUtil; import forge.game.keyword.Keyword; -import forge.game.phase.PhaseType; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -38,9 +38,6 @@ public class MustBlockAi extends SpellAbilityAi { if (!list.isEmpty()) { final Card blocker = ComputerUtilCard.getBestCreatureAI(list); - if (blocker == null) { - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); - } sa.getTargets().add(blocker); return new AiAbilityDecision(100, AiPlayDecision.WillPlay); } @@ -63,11 +60,6 @@ public class MustBlockAi extends SpellAbilityAi { protected AiAbilityDecision doTriggerNoCost(final Player ai, SpellAbility sa, boolean mandatory) { final Card source = sa.getHostCard(); - // only use on creatures that can attack - if (!ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.MAIN2)) { - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); - } - Card attacker = source; if (sa.hasParam("DefinedAttacker")) { final List cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa); @@ -81,13 +73,9 @@ public class MustBlockAi extends SpellAbilityAi { boolean chance = false; if (sa.usesTargeting()) { - final List list = determineGoodBlockers(attacker, ai, ai.getWeakestOpponent(), sa, true, true); - if (list.isEmpty()) { - if (sa.isTargetNumberValid()) { - return new AiAbilityDecision(100, AiPlayDecision.WillPlay); - } else { - return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed); - } + List list = determineGoodBlockers(attacker, ai, ai.getWeakestOpponent(), sa, true, true); + if (list.isEmpty() && mandatory) { + list = CardUtil.getValidCardsToTarget(sa); } final Card blocker = ComputerUtilCard.getBestCreatureAI(list); if (blocker == null) { diff --git a/forge-ai/src/main/java/forge/ai/ability/PhasesAi.java b/forge-ai/src/main/java/forge/ai/ability/PhasesAi.java index 5cdbaafdc06..02ae927180f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PhasesAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PhasesAi.java @@ -33,10 +33,8 @@ public class PhasesAi extends SpellAbilityAi { final boolean isThreatened = ComputerUtil.predictThreatenedObjects(aiPlayer, null, true).contains(source); if (isThreatened) { return new AiAbilityDecision(100, AiPlayDecision.WillPlay); - } else { - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); - } + return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); diff --git a/forge-gui/res/cardsfolder/a/axelrod_gunnarson.txt b/forge-gui/res/cardsfolder/a/axelrod_gunnarson.txt index b1e0d76711c..ce0610e463e 100644 --- a/forge-gui/res/cardsfolder/a/axelrod_gunnarson.txt +++ b/forge-gui/res/cardsfolder/a/axelrod_gunnarson.txt @@ -3,7 +3,7 @@ ManaCost:4 B B R R Types:Legendary Creature Giant PT:5/5 K:Trample -T:Mode$ ChangesZone | Origin$ Any | Destination$ Graveyard | ValidCard$ Creature.DamagedBy | Execute$ DBGainLife | TriggerDescription$ Whenever a creature dealt damage by CARDNAME this turn dies, you gain 1 life and NICKNAME deals 1 damage to target player or planeswalker. +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.DamagedBy | Execute$ DBGainLife | TriggerDescription$ Whenever a creature dealt damage by CARDNAME this turn dies, you gain 1 life and NICKNAME deals 1 damage to target player or planeswalker. SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 | SubAbility$ DBDealDamage SVar:DBDealDamage:DB$ DealDamage | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 1 DeckHas:Ability$LifeGain diff --git a/forge-gui/res/cardsfolder/b/brudiclad_telchor_engineer.txt b/forge-gui/res/cardsfolder/b/brudiclad_telchor_engineer.txt index bc3b3bbdcfd..62967b12daa 100644 --- a/forge-gui/res/cardsfolder/b/brudiclad_telchor_engineer.txt +++ b/forge-gui/res/cardsfolder/b/brudiclad_telchor_engineer.txt @@ -7,6 +7,6 @@ SVar:PlayMain1:TRUE T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of combat on your turn, create a 2/1 blue Phyrexian Myr artifact creature token. Then you may choose a token you control. If you do, each other token you control becomes a copy of that token. SVar:TrigToken:DB$ Token | TokenScript$ u_2_1_a_phyrexian_myr | SubAbility$ DBClone SVar:DBClone:DB$ Clone | Choices$ Card.token+YouCtrl | ChoiceTitle$ You may choose a token you control | ChoiceOptional$ True | ExcludeChosen$ True | CloneTarget$ Valid Card.token+YouCtrl -DeckHints:Type$Token -DeckHas:Type$Token & Type$Artifact|Myr +DeckHints:Ability$Token +DeckHas:Ability$Token & Type$Artifact|Myr Oracle:Creature tokens you control have haste.\nAt the beginning of combat on your turn, create a 2/1 blue Phyrexian Myr artifact creature token. Then you may choose a token you control. If you do, each other token you control becomes a copy of that token. diff --git a/forge-gui/res/cardsfolder/c/caesar_legions_emperor.txt b/forge-gui/res/cardsfolder/c/caesar_legions_emperor.txt index 7bffc1bd003..e42009cd035 100644 --- a/forge-gui/res/cardsfolder/c/caesar_legions_emperor.txt +++ b/forge-gui/res/cardsfolder/c/caesar_legions_emperor.txt @@ -11,5 +11,5 @@ SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1 SVar:DBDamage:DB$ DealDamage | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | NumDmg$ X | SpellDescription$ CARDNAME deals damage equal to the number of creature tokens you control to target opponent. SVar:X:Count$Valid Creature.token+YouCtrl DeckHas:Ability$Token|Sacrifice -DeckHints:Type$Token +DeckHints:Ability$Token Oracle:Whenever you attack, you may sacrifice another creature. When you do, choose two —\n• Create two 1/1 red and white Soldier creature tokens with haste that are tapped and attacking.\n• You draw a card and you lose 1 life.\n• Caesar, Legion's Emperor deals damage equal to the number of creature tokens you control to target opponent. diff --git a/forge-gui/res/cardsfolder/c/crisis_of_conscience.txt b/forge-gui/res/cardsfolder/c/crisis_of_conscience.txt index 2b901551689..4269209413e 100644 --- a/forge-gui/res/cardsfolder/c/crisis_of_conscience.txt +++ b/forge-gui/res/cardsfolder/c/crisis_of_conscience.txt @@ -4,5 +4,5 @@ Types:Sorcery A:SP$ Charm | Choices$ DBDestroyAllTokens,DBDestroyAllNonlandNontokenPermanents | CharmNum$ 1 SVar:DBDestroyAllTokens:DB$ DestroyAll | ValidCards$ Card.token | SpellDescription$ Destroy all tokens. SVar:DBDestroyAllNonlandNontokenPermanents:DB$ DestroyAll | ValidCards$ Permanent.nonLand+!token | SpellDescription$ Destroy all nonland, nontoken permanents. -DeckHints:Type$Token +DeckHints:Ability$Token Oracle:Choose one —\n• Destroy all tokens.\n• Destroy all nonland, nontoken permanents. diff --git a/forge-gui/res/cardsfolder/g/grishnakh_brash_instigator.txt b/forge-gui/res/cardsfolder/g/grishnakh_brash_instigator.txt index d3123cc9d83..16869bce724 100644 --- a/forge-gui/res/cardsfolder/g/grishnakh_brash_instigator.txt +++ b/forge-gui/res/cardsfolder/g/grishnakh_brash_instigator.txt @@ -9,5 +9,5 @@ SVar:TrigGainControl:DB$ GainControl | ValidTgts$ Creature.nonLegendary+powerLEX SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:TriggerRemembered$CardPower DeckHas:Ability$Token|Counters & Type$Orc|Army -DeckHints:Type$Token +DeckHints:Ability$Token Oracle:When Grishnákh, Brash Instigator enters, amass Orcs 2. When you do, until end of turn, gain control of target nonlegendary creature an opponent controls with power less than or equal to the amassed Army's power. Untap that creature. It gains haste until end of turn. diff --git a/forge-gui/res/cardsfolder/p/prosperity_tycoon.txt b/forge-gui/res/cardsfolder/p/prosperity_tycoon.txt index 810d7051b69..6ca2cade710 100644 --- a/forge-gui/res/cardsfolder/p/prosperity_tycoon.txt +++ b/forge-gui/res/cardsfolder/p/prosperity_tycoon.txt @@ -7,5 +7,5 @@ SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ r_1_1_mercenary_tappump A:AB$ Pump | Cost$ 2 Sac<1/Card.token/token> | Defined$ Self | KW$ Indestructible | SubAbility$ DBTap | SpellDescription$ CARDNAME gains indestructible until end of turn. Tap it. (Damage and effects that say "destroy" don't destroy it.) SVar:DBTap:DB$ Tap | Defined$ Self DeckHas:Ability$Token|Sacrifice & Type$Mercenary -DeckHints:Type$Token +DeckHints:Ability$Token Oracle:When Prosperity Tycoon enters, create a 1/1 red Mercenary creature token with "{T}: Target creature you control gets +1/+0 until end of turn. Activate only as a sorcery."\n{2}, Sacrifice a token: Prosperity Tycoon gains indestructible until end of turn. Tap it. (Damage and effects that say "destroy" don't destroy it.) diff --git a/forge-gui/res/cardsfolder/s/soul_collector.txt b/forge-gui/res/cardsfolder/s/soul_collector.txt index 61dc9eb60bf..7be64f067d7 100644 --- a/forge-gui/res/cardsfolder/s/soul_collector.txt +++ b/forge-gui/res/cardsfolder/s/soul_collector.txt @@ -4,6 +4,6 @@ Types:Creature Vampire PT:3/4 K:Flying K:Morph:B B B -T:Mode$ ChangesZone | Origin$ Any | Destination$ Graveyard | ValidCard$ Creature.DamagedBy | Execute$ TrigBounce | TriggerDescription$ Whenever a creature dealt damage by CARDNAME this turn dies, return that card to the battlefield under your control. +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.DamagedBy | Execute$ TrigBounce | TriggerDescription$ Whenever a creature dealt damage by CARDNAME this turn dies, return that card to the battlefield under your control. SVar:TrigBounce:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | Defined$ TriggeredCardLKICopy Oracle:Flying\nWhenever a creature dealt damage by Soul Collector this turn dies, return that card to the battlefield under your control.\nMorph {B}{B}{B} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.) From 4d2b634e4fbd09446dbca7d4a90bb5199edfb88e Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 28 Sep 2025 15:35:19 +0200 Subject: [PATCH 046/230] cantBeEnchantedByMsg and cantAttach StaticAbility (#8772) * cantBeEnchantedByMsg and cantAttach StaticAbility * finish cantBeAttachedMsg --- .../src/main/java/forge/game/GameEntity.java | 91 ++++++++++++------- .../src/main/java/forge/game/card/Card.java | 78 ++++++++-------- .../main/java/forge/game/card/CardState.java | 12 +-- .../main/java/forge/game/keyword/Equip.java | 2 + .../forge/game/keyword/KeywordWithType.java | 44 +++++---- .../StaticAbilityCantAttach.java | 6 +- .../match/input/InputSelectTargets.java | 9 +- 7 files changed, 139 insertions(+), 103 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-game/src/main/java/forge/game/GameEntity.java index 7e51c00234f..1447b1d78ad 100644 --- a/forge-game/src/main/java/forge/game/GameEntity.java +++ b/forge-game/src/main/java/forge/game/GameEntity.java @@ -36,12 +36,15 @@ import forge.game.card.CardPredicates; import forge.game.card.CounterType; import forge.game.keyword.Keyword; import forge.game.keyword.KeywordInterface; +import forge.game.keyword.KeywordWithType; import forge.game.player.Player; import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; +import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbilityCantAttach; import forge.game.zone.ZoneType; +import forge.util.Lang; public abstract class GameEntity extends GameObject implements IIdentifiable { protected int id; @@ -218,63 +221,83 @@ public abstract class GameEntity extends GameObject implements IIdentifiable { return canBeAttached(attach, sa, false); } public boolean canBeAttached(final Card attach, SpellAbility sa, boolean checkSBA) { - // master mode - if (!attach.isAttachment() || (attach.isCreature() && !attach.hasKeyword(Keyword.RECONFIGURE)) - || equals(attach)) { - return false; + return cantBeAttachedMsg(attach, sa, checkSBA) == null; + } + + public String cantBeAttachedMsg(final Card attach, SpellAbility sa) { + return cantBeAttachedMsg(attach, sa, false); + } + public String cantBeAttachedMsg(final Card attach, SpellAbility sa, boolean checkSBA) { + if (!attach.isAttachment()) { + return attach.getName() + " is not an attachment"; + } + if (equals(attach)) { + return attach.getName() + " can't attach to itself"; + } + + if (attach.isCreature() && !attach.hasKeyword(Keyword.RECONFIGURE)) { + return attach.getName() + " is a creature without reconfigure"; } if (attach.isPhasedOut()) { - return false; + return attach.getName() + " is phased out"; } - // check for rules - if (attach.isAura() && !canBeEnchantedBy(attach)) { - return false; + if (attach.isAura()) { + String msg = cantBeEnchantedByMsg(attach); + if (msg != null) { + return msg; + } } - if (attach.isEquipment() && !canBeEquippedBy(attach, sa)) { - return false; + if (attach.isEquipment()) { + String msg = cantBeEquippedByMsg(attach, sa); + if (msg != null) { + return msg; + } } - if (attach.isFortification() && !canBeFortifiedBy(attach)) { - return false; + if (attach.isFortification()) { + String msg = cantBeFortifiedByMsg(attach); + if (msg != null) { + return msg; + } } - // check for can't attach static - if (StaticAbilityCantAttach.cantAttach(this, attach, checkSBA)) { - return false; + StaticAbility stAb = StaticAbilityCantAttach.cantAttach(this, attach, checkSBA); + if (stAb != null) { + return stAb.toString(); } - // true for all - return true; + return null; } - protected boolean canBeEquippedBy(final Card aura, SpellAbility sa) { - /** - * Equip only to Creatures which are cards - */ - return false; - } - - protected boolean canBeFortifiedBy(final Card aura) { + protected String cantBeEquippedByMsg(final Card aura, SpellAbility sa) { /** * Equip only to Lands which are cards */ - return false; + return getName() + " is not a Creature"; } - protected boolean canBeEnchantedBy(final Card aura) { + protected String cantBeFortifiedByMsg(final Card fort) { + /** + * Equip only to Lands which are cards + */ + return getName() + " is not a Land"; + } + + protected String cantBeEnchantedByMsg(final Card aura) { if (!aura.hasKeyword(Keyword.ENCHANT)) { - return false; + return "No Enchant Keyword"; } for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) { - String k = ki.getOriginal(); - String m[] = k.split(":"); - String v = m[1]; - if (!isValid(v.split(","), aura.getController(), aura, null)) { - return false; + if (ki instanceof KeywordWithType kwt) { + String v = kwt.getValidType(); + String desc = kwt.getTypeDescription(); + if (!isValid(v.split(","), aura.getController(), aura, null)) { + return getName() + " is not " + Lang.nounWithAmount(1, desc); + } } } - return true; + return null; } public boolean hasCounters() { 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 37722d54e81..49894c670aa 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2453,17 +2453,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr } else if (keyword.startsWith("DeckLimit")) { final String[] k = keyword.split(":"); sbLong.append(k[2]).append("\r\n"); - } else if (keyword.startsWith("Enchant")) { - String m[] = keyword.split(":"); - String desc; - if (m.length > 2) { - desc = m[2]; - } else { - desc = m[1]; - if (CardType.isACardType(desc) || "Permanent".equals(desc) || "Player".equals(desc) || "Opponent".equals(desc)) { - desc = desc.toLowerCase(); - } - } + } else if (keyword.startsWith("Enchant") && inst instanceof KeywordWithType kwt) { + String desc = kwt.getTypeDescription(); sbLong.append("Enchant ").append(desc).append("\r\n"); } else if (keyword.startsWith("Morph") || keyword.startsWith("Megamorph") || keyword.startsWith("Disguise") || keyword.startsWith("Reflect") @@ -3797,7 +3788,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr public final void addLeavesPlayCommand(final GameCommand c) { leavePlayCommandList.add(c); } - + public void addStaticCommandList(Object[] objects) { staticCommandList.add(objects); } @@ -4812,7 +4803,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr public void addDraftAction(String s) { draftActions.add(s); } - + private int intensity = 0; public final void addIntensity(final int n) { intensity += n; @@ -7171,51 +7162,62 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr } @Override - protected final boolean canBeEnchantedBy(final Card aura) { + protected final String cantBeEnchantedByMsg(final Card aura) { if (!aura.hasKeyword(Keyword.ENCHANT)) { - return false; + return "No Enchant Keyword"; } for (KeywordInterface ki : aura.getKeywords(Keyword.ENCHANT)) { - String k = ki.getOriginal(); - String m[] = k.split(":"); - String v = m[1]; - if (!isValid(v.split(","), aura.getController(), aura, null)) { - return false; - } - if (!v.contains("inZone") && !isInPlay()) { - return false; + if (ki instanceof KeywordWithType kwt) { + String v = kwt.getValidType(); + String desc = kwt.getTypeDescription(); + if (!isValid(v.split(","), aura.getController(), aura, null) || (!v.contains("inZone") && !isInPlay())) { + return getName() + " is not " + Lang.nounWithAmount(1, desc); + } } } - return true; + return null; } + @Override - protected final boolean canBeEquippedBy(final Card equip, SpellAbility sa) { + protected String cantBeEquippedByMsg(final Card equip, SpellAbility sa) { if (!isInPlay()) { - return false; + return getName() + " is not in play"; } if (sa != null && sa.isEquip()) { - return isValid(sa.getTargetRestrictions().getValidTgts(), sa.getActivatingPlayer(), equip, sa); + if (!isValid(sa.getTargetRestrictions().getValidTgts(), sa.getActivatingPlayer(), equip, sa)) { + Equip eq = (Equip) sa.getKeyword(); + return getName() + " is not " + Lang.nounWithAmount(1, eq.getValidDescription()); + } + return null; } - return isCreature(); + if (!isCreature()) { + return getName() + " is not a creature"; + } + return null; } @Override - protected boolean canBeFortifiedBy(final Card fort) { - return isLand() && isInPlay() && !fort.isLand(); + protected String cantBeFortifiedByMsg(final Card fort) { + if (!isLand()) { + return getName() + " is not a Land"; + } + if (!isInPlay()) { + return getName() + " is not in play"; + } + if (fort.isLand()) { + return fort.getName() + " is a Land"; + } + + return null; } - /* (non-Javadoc) - * @see forge.game.GameEntity#canBeAttached(forge.game.card.Card, boolean) - */ @Override - public boolean canBeAttached(Card attach, SpellAbility sa, boolean checkSBA) { - // phase check there + public String cantBeAttachedMsg(final Card attach, SpellAbility sa, boolean checkSBA) { if (isPhasedOut() && !attach.isPhasedOut()) { - return false; + return getName() + " is phased out"; } - - return super.canBeAttached(attach, sa, checkSBA); + return super.cantBeAttachedMsg(attach, sa, checkSBA); } public final boolean canBeSacrificedBy(final SpellAbility source, final boolean effect) { diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index 57f9cf39896..289520ecf28 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -32,6 +32,7 @@ import forge.game.card.CardView.CardStateView; import forge.game.keyword.Keyword; import forge.game.keyword.KeywordCollection; import forge.game.keyword.KeywordInterface; +import forge.game.keyword.KeywordWithType; import forge.game.player.Player; import forge.game.replacement.ReplacementEffect; import forge.game.spellability.LandAbility; @@ -501,15 +502,8 @@ public class CardState extends GameObject implements IHasSVars, ITranslatable { String desc = ""; String extra = ""; for (KeywordInterface ki : this.getCachedKeyword(Keyword.ENCHANT)) { - String o = ki.getOriginal(); - String m[] = o.split(":"); - if (m.length > 2) { - desc = m[2]; - } else { - desc = m[1]; - if (CardType.isACardType(desc) || "Permanent".equals(desc) || "Player".equals(desc) || "Opponent".equals(desc)) { - desc = desc.toLowerCase(); - } + if (ki instanceof KeywordWithType kwt) { + desc = kwt.getTypeDescription(); } break; } diff --git a/forge-game/src/main/java/forge/game/keyword/Equip.java b/forge-game/src/main/java/forge/game/keyword/Equip.java index 66a5c2ad65d..bf7313943f1 100644 --- a/forge-game/src/main/java/forge/game/keyword/Equip.java +++ b/forge-game/src/main/java/forge/game/keyword/Equip.java @@ -7,6 +7,8 @@ public class Equip extends KeywordWithCost { public Equip() { } + public String getValidDescription() { return type; } + @Override protected void parse(String details) { String[] k = details.split(":"); diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordWithType.java b/forge-game/src/main/java/forge/game/keyword/KeywordWithType.java index a31ace72781..2bf274f1272 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordWithType.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordWithType.java @@ -3,38 +3,50 @@ package forge.game.keyword; import forge.card.CardType; public class KeywordWithType extends KeywordInstance { - protected String type; + protected String type = null; + protected String descType = null; + protected String reminderType = null; + + public String getValidType() { return type; } + public String getTypeDescription() { return descType; } @Override protected void parse(String details) { - if (CardType.isACardType(details)) { - type = details.toLowerCase(); - } else if (details.contains(":")) { + String k[]; + if (details.contains(":")) { switch (getKeyword()) { case AFFINITY: - type = details.split(":")[1]; - // type lists defined by rules should not be changed by TextChange in reminder text - if (type.equalsIgnoreCase("Outlaw")) { - type = "Assassin, Mercenary, Pirate, Rogue, and/or Warlock"; - } else if (type.equalsIgnoreCase("historic permanent")) { - type = "artifact, legendary, and/or Saga permanent"; - } - break; case BANDSWITH: + case ENCHANT: case HEXPROOF: case LANDWALK: - type = details.split(":")[1]; + k = details.split(":"); + type = k[0]; + descType = k[1]; break; default: - type = details.split(":")[0]; + k = details.split(":"); + type = k[1]; + descType = k[0]; } } else { - type = details; + descType = type = details; + } + + if (CardType.isACardType(descType) || "Permanent".equals(descType) || "Player".equals(descType) || "Opponent".equals(descType)) { + descType = descType.toLowerCase(); + } else if (descType.equalsIgnoreCase("Outlaw")) { + reminderType = "Assassin, Mercenary, Pirate, Rogue, and/or Warlock"; + } else if (type.equalsIgnoreCase("historic permanent")) { + reminderType = "artifact, legendary, and/or Saga permanent"; + } + if (reminderType == null) { + reminderType = type; } } @Override protected String formatReminderText(String reminderText) { - return String.format(reminderText, type); + return String.format(reminderText, reminderType); } } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttach.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttach.java index 8b2ba2bda27..70704f3ac95 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttach.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityCantAttach.java @@ -6,7 +6,7 @@ import forge.game.zone.ZoneType; public class StaticAbilityCantAttach { - public static boolean cantAttach(final GameEntity target, final Card card, boolean checkSBA) { + public static StaticAbility cantAttach(final GameEntity target, final Card card, boolean checkSBA) { // CantTarget static abilities for (final Card ca : target.getGame().getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) { for (final StaticAbility stAb : ca.getStaticAbilities()) { @@ -15,11 +15,11 @@ public class StaticAbilityCantAttach { } if (applyCantAttachAbility(stAb, card, target, checkSBA)) { - return true; + return stAb; } } } - return false; + return null; } public static boolean applyCantAttachAbility(final StaticAbility stAb, final Card card, final GameEntity target, boolean checkSBA) { diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectTargets.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectTargets.java index 95b09fa716e..6861b922ddd 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectTargets.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectTargets.java @@ -169,9 +169,12 @@ public final class InputSelectTargets extends InputSyncronizedBase { // TODO should use sa.canTarget(card) instead? // it doesn't have messages - if (sa.isSpell() && sa.getHostCard().isAura() && !card.canBeAttached(sa.getHostCard(), sa)) { - showMessage(sa.getHostCard() + " - Cannot enchant this card (Shroud? Protection? Restrictions?)."); - return false; + if (sa.isSpell() && sa.getHostCard().isAura()) { + String msg = card.cantBeAttachedMsg(sa.getHostCard(), sa); + if (msg != null) { + showMessage(sa.getHostCard() + " - " + msg); + return false; + } } //If the card is not a valid target if (!card.canBeTargetedBy(sa)) { From cd84663beddb9d1d2fccf41f02db029d1553420a Mon Sep 17 00:00:00 2001 From: Simisays <67333662+Simisays@users.noreply.github.com> Date: Sun, 28 Sep 2025 15:55:52 +0200 Subject: [PATCH 047/230] [ECL] 10 cards (#8794) --- .../ashling_rekindled_ashling_rimebound.txt | 26 +++++++++++++++++ .../cardsfolder/upcoming/ashlings_command.txt | 11 ++++++++ .../upcoming/bitterbloom_bearer.txt | 12 ++++++++ forge-gui/res/cardsfolder/upcoming/deceit.txt | 11 ++++++++ ...rier_of_dawn_isilu_carrier_of_twilight.txt | 26 +++++++++++++++++ .../res/cardsfolder/upcoming/emptiness.txt | 11 ++++++++ .../cardsfolder/upcoming/figure_of_fable.txt | 9 ++++++ .../upcoming/morningtides_light.txt | 12 ++++++++ .../cardsfolder/upcoming/mutable_explorer.txt | 9 ++++++ ...derwine_wisdom_sygg_wanderbrine_shield.txt | 28 +++++++++++++++++++ 10 files changed, 155 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/ashling_rekindled_ashling_rimebound.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/ashlings_command.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/deceit.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/eirdu_carrier_of_dawn_isilu_carrier_of_twilight.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/emptiness.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/figure_of_fable.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/morningtides_light.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/mutable_explorer.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sygg_wanderwine_wisdom_sygg_wanderbrine_shield.txt diff --git a/forge-gui/res/cardsfolder/upcoming/ashling_rekindled_ashling_rimebound.txt b/forge-gui/res/cardsfolder/upcoming/ashling_rekindled_ashling_rimebound.txt new file mode 100644 index 00000000000..92a3e55badf --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ashling_rekindled_ashling_rimebound.txt @@ -0,0 +1,26 @@ +Name:Ashling, Rekindled +ManaCost:1 R +Types:Legendary Creature Elemental Sorcerer +PT:1/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ Whenever this creature enters or transforms into CARDNAME, you may discard a card. If you do, draw a card. +T:Mode$ Transformed | ValidCard$ Card.Self | Execute$ TrigDraw | Secondary$ True | TriggerDescription$ Whenever this creature enters or transforms into CARDNAME, you may discard a card. If you do, draw a card. +SVar:TrigDraw:AB$ Draw | Cost$ Discard<1/Card> | NumCards$ 1 +T:Mode$ Phase | Phase$ Main1 | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigTransform | TriggerDescription$ At the beginning of your first main phase, you may pay {U}. If you do, transform NICKNAME. +SVar:TrigTransform:AB$ SetState | Cost$ U | Defined$ Self | Mode$ Transform +DeckHas:Ability$Discard +AlternateMode:DoubleFaced +Oracle:Whenever this creature enters or transforms into Ashling, Rekindled, you may discard a card. If you do, draw a card.\nAt the beginning of your first main phase, you may pay {U}. If you do, transform Ashling. + +ALTERNATE + +Name:Ashling, Rimebound +ManaCost:no cost +Colors:blue +Types:Legendary Creature Elemental Wizard +PT:1/3 +T:Mode$ Transformed | ValidCard$ Card.Self | Execute$ TrigMana | TriggerDescription$Whenever this creature transforms into CARDNAME and at the beginning of your first main phase, add two mana of any one color. Spend this mana only to cast spells with mana value 4 or greater. +T:Mode$ Phase | Phase$ Main1 | Secondary$ True | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigMana | TriggerDescription$ Whenever this creature transforms into CARDNAME and at the beginning of your first main phase, add two mana of any one color. Spend this mana only to cast spells with mana value 4 or greater. +SVar:TrigMana:DB$ Mana | Produced$ Any | Amount$ 2 | RestrictValid$ Spell.cmcGE4 +T:Mode$ Phase | Phase$ Main1 | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigTransform | TriggerDescription$ At the beginning of your first main phase, you may pay {R}. If you do, transform NICKNAME. +SVar:TrigTransform:AB$ SetState | Cost$ R | Defined$ Self | Mode$ Transform +Oracle:Whenever this creature transforms into Ashling, Rimebound and at the beginning of your first main phase, add two mana of any one color. Spend this mana only to cast spells with mana value 4 or greater.\nAt the beginning of your first main phase, you may pay {R}. If you do, transform Ashling. diff --git a/forge-gui/res/cardsfolder/upcoming/ashlings_command.txt b/forge-gui/res/cardsfolder/upcoming/ashlings_command.txt new file mode 100644 index 00000000000..835e8692ed9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ashlings_command.txt @@ -0,0 +1,11 @@ +Name:Ashling's Command +ManaCost:3 U R +Types:Kindred Instant Elemental +A:SP$ Charm | Choices$ DBToken,DBDraw,DBDamage,DBTreasure | CharmNum$ 2 +SVar:DBToken:DB$ CopyPermanent | ValidTgts$ Card.Elemental+YouCtrl | TgtPrompt$ Select target Elemental you control | SpellDescription$ Create a token that's a copy of target Elemental you control. +SVar:DBDraw:DB$ Draw | NumCards$ 2 | ValidTgts$ Player | TgtPrompt$ Choose a player | SpellDescription$ Target player draws two cards. +SVar:DBDamage:DB$ DamageAll | ValidTgts$ Player | NumDmg$ 2 | ValidCards$ Creature | ValidDescription$ each creature target player controls | SpellDescription$ CARDNAME deals 2 damage to each creature target player controls. +SVar:DBTreasure:DB$ Token | ValidTgts$ Player | TokenOwner$ ThisTargetedPlayer | TokenAmount$ 2 | TokenScript$ c_a_treasure_sac | SpellDescription$ Target player creates two Treasure tokens. +DeckHas:Ability$Token & Type$Treasure|Artifact +DeckHints:Type$Elemental +Oracle:Choose two —\n• Create a token that's a copy of target Elemental you control.\n• Target player draws two cards.\n• Ashling's Command deals 2 damage to each creature target player controls.\n• Target player creates two Treasure tokens. diff --git a/forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt b/forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt new file mode 100644 index 00000000000..7e1c1cfa47d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt @@ -0,0 +1,12 @@ +Name:Bitterbloom Bearer +ManaCost:B B +Types:Creature Faerie Rogue +PT:1/1 +K:Flash +K:Flying +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigLoseLife | TriggerDescription$ At the beginning of your upkeep, you lose 1 life and create a 1/1 black Faerie Rogue creature token with flying. +SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ 1 | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ b_1_1_faerie_rogue_flying | TokenOwner$ You +SVar:AICastPreference:NeverCastIfLifeBelow$ 4 +DeckHas:Ability$Token +Oracle:Flash\nFlying\nAt the beginning of your upkeep, you lose 1 life and create a 1/1 blue and black Faerie creature token with flying. diff --git a/forge-gui/res/cardsfolder/upcoming/deceit.txt b/forge-gui/res/cardsfolder/upcoming/deceit.txt new file mode 100644 index 00000000000..772e70b7ea3 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/deceit.txt @@ -0,0 +1,11 @@ +Name:Deceit +ManaCost:4 UB UB +Types:Creature Elemental Incarnation +PT:5/5 +K:Evoke:UB UB +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | CheckSVar$ CastSA>Count$Adamant_2.Blue.2.0 | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When this creature enters, if {U}{U} was spent to cast it, return up to one other target nonland permanent to its owner's hand. +SVar:TrigChange:DB$ ChangeZone | Origin$ Battlefield | Destination$ Hand | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Permanent.nonLand+Other | TgtPrompt$ Select up to one other target nonland permanent +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | CheckSVar$ CastSA>Count$Adamant_2.Black.2.0 | ValidCard$ Card.Self | Execute$ TrigDiscard | TriggerDescription$ When this creature enters, if {B}{B} was spent to cast it, target opponent reveals their hand. You choose a nonland card from it. That player discards that card. +SVar:TrigDiscard:DB$ Discard | ValidTgts$ Opponent | Mode$ RevealYouChoose | DiscardValid$ Card.nonLand | NumCards$ 1 +DeckHas:Ability$Discard +Oracle:When this creature enters, if {U}{U} was spent to cast it, return up to one other target nonland permanent to its owner's hand.\nWhen this creature enters, if {B}{B} was spent to cast it, target opponent reveals their hand. You choose a nonland card from it. That player discards that card.\nEvoke {U/B}{U/B} diff --git a/forge-gui/res/cardsfolder/upcoming/eirdu_carrier_of_dawn_isilu_carrier_of_twilight.txt b/forge-gui/res/cardsfolder/upcoming/eirdu_carrier_of_dawn_isilu_carrier_of_twilight.txt new file mode 100644 index 00000000000..c7bffe073be --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/eirdu_carrier_of_dawn_isilu_carrier_of_twilight.txt @@ -0,0 +1,26 @@ +Name:Eirdu, Carrier of Dawn +ManaCost:3 W W +Types:Legendary Creature Elemental God +PT:5/5 +K:Flying +K:Lifelink +S:Mode$ Continuous | Affected$ Card.Creature+YouCtrl+wasCast | AffectedZone$ Stack | AddKeyword$ Convoke | Description$ Creature spells you cast have convoke. (Your creatures can help cast those spells. Each creature you tap while casting a creature spell pays for {1} or one mana of that creature's color.) +T:Mode$ Phase | Phase$ Main1 | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigTransform | TriggerDescription$ At the beginning of your first main phase, you may pay {B}. If you do, transform NICKNAME. +SVar:TrigTransform:AB$ SetState | Cost$ B | Defined$ Self | Mode$ Transform +AlternateMode:DoubleFaced +DeckHas:Ability$LifeGain|Counters +Oracle:Flying, lifelink\nCreature spells you cast have convoke. (Your creatures can help cast those spells. Each creature you tap while casting a creature spell pays for {1} or one mana of that creature's color.)\nAt the beginning of your first main phase, you may pay {B}. If you do, transform Eirdu. + +ALTERNATE + +Name:Isilu, Carrier of Twilight +ManaCost:no cost +Colors:black +Types:Legendary Creature Elemental God +PT:5/5 +K:Flying +K:Lifelink +S:Mode$ Continuous | Affected$ Card.Creature+YouCtrl+nonToken+Other | AffectedZone$ Battlefield | AddKeyword$ Persist | Description$ Each other nontoken creature you control has persist. (When it dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner’s control with a -1/-1 counter on it.) +T:Mode$ Phase | Phase$ Main1 | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigTransform | TriggerDescription$ At the beginning of your first main phase, you may pay {W}. If you do, transform NICKNAME. +SVar:TrigTransform:AB$ SetState | Cost$ W | Defined$ Self | Mode$ Transform +Oracle:Flying, lifelink\nEach other nontoken creature you control has persist. (When it dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner's control with a -1/-1 counter on it.) At the beginning of your first main phase, you may pay {W}. If you do, transform Isilu. diff --git a/forge-gui/res/cardsfolder/upcoming/emptiness.txt b/forge-gui/res/cardsfolder/upcoming/emptiness.txt new file mode 100644 index 00000000000..d754b8efb9b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/emptiness.txt @@ -0,0 +1,11 @@ +Name:Emptiness +ManaCost:4 WB WB +Types:Creature Elemental Incarnation +PT:3/5 +K:Evoke:WB WB +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | CheckSVar$ CastSA>Count$Adamant_2.White.2.0 | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When this creature enters, if {W}{W} was spent to cast it, return target creature card with mana value 3 or less from your graveyard to the battlefield. +SVar:TrigReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouCtrl+cmcLE3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | CheckSVar$ CastSA>Count$Adamant_2.Black.2.0 | ValidCard$ Card.Self | Execute$ TrigPutCounters | TriggerDescription$ When this creature enters, if {B}{B} was spent to cast it, put three -1/-1 counters on up to one target creature. +SVar:TrigPutCounters:DB$ PutCounter | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 1 | CounterNum$ 3 | TgtPrompt$ Select up to one target creature | CounterType$ M1M1 +DeckHas:Ability$Graveyard|Counters +Oracle:When this creature enters, if {W}{W} was spent to cast it, return target creature card with mana value 3 or less from your graveyard to the battlefield.\nWhen this creature enters, if {B}{B} was spent to cast it, put three -1/-1 counters on up to one target creature.\nEvoke {W/B}{W/B} diff --git a/forge-gui/res/cardsfolder/upcoming/figure_of_fable.txt b/forge-gui/res/cardsfolder/upcoming/figure_of_fable.txt new file mode 100644 index 00000000000..a9e20245eeb --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/figure_of_fable.txt @@ -0,0 +1,9 @@ +Name:Figure of Fable +ManaCost:GW +Types:Creature Kithkin +PT:1/1 +A:AB$ Animate | Cost$ GW | Types$ Kithkin,Scout | RemoveCreatureTypes$ True | Duration$ Permanent | Power$ 2 | Toughness$ 3 | SpellDescription$ This creature becomes a Kithkin Scout with base power and toughness 2/3. +A:AB$ Animate | Cost$ 1 GW GW | ConditionPresent$ Card.Self+Scout | Types$ Kithkin,Soldier | RemoveCreatureTypes$ True | Duration$ Permanent | Power$ 4 | Toughness$ 5 | SpellDescription$ If this creature is a Scout, it becomes a Kithkin Soldier with base power and toughness 4/5. +A:AB$ Animate | Cost$ 3 GW GW GW | ConditionPresent$ Card.Self+Soldier | Types$ Kithkin,Avatar | RemoveCreatureTypes$ True | Duration$ Permanent | Power$ 7 | Toughness$ 8 | Keywords$ Protection:Player.Opponent:each of your opponents | SpellDescription$ If this creature is a Soldier, it becomes a Kithkin Avatar with base power and toughness 7/8 and protection from each of your opponents. +DeckHas:Type$Soldier|Scout|Avatar +Oracle:{G/W}: This creature becomes a Kithkin Scout with base power and toughness 2/3.\n{1}{G/W}{G/W}: If this creature is a Scout, it becomes a Kithkin Soldier with base power and toughness 4/5.\n{3}{G/W}{G/W}{G/W}: If this creature is a Soldier, it becomes a Kithkin Avatar with base power and toughness 7/8 and protection from each of your opponents. diff --git a/forge-gui/res/cardsfolder/upcoming/morningtides_light.txt b/forge-gui/res/cardsfolder/upcoming/morningtides_light.txt new file mode 100644 index 00000000000..38c22e865a5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/morningtides_light.txt @@ -0,0 +1,12 @@ +Name:Morningtide's Light +ManaCost:3 W +Types:Sorcery +A:SP$ ChangeZone | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ X | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Select any number of target creatures | SubAbility$ DelTrig | RememberChanged$ True | SpellDescription$ Exile any number of target creatures. At the beginning of the next end step, return those cards to the battlefield tapped under their owners' control. +SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigReturn | RememberObjects$ RememberedLKI | TriggerDescription$ Return exiled permanent to the battlefield under their owner's control | SubAbility$ DBCleanup +SVar:TrigReturn:DB$ ChangeZone | Origin$ Exile | Destination$ Battlefield | Tapped$ True | Defined$ DelayTriggerRememberedLKI +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBEffect +SVar:DBEffect:DB$ Effect | ReplacementEffects$ PreventDamage | AILogic$ Fog | Duration$ UntilYourNextTurn | SubAbility$ DBExileSelf | SpellDescription$ Until your next turn, prevent all damage that would be dealt to you. +SVar:PreventDamage:Event$ DamageDone | Prevent$ True | ActiveZones$ Command | ValidTarget$ You | Description$ Until your next turn, prevent all damage that would be dealt to you. +SVar:DBExileSelf:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | StackDescription$ None | SpellDescription$ Exile CARDNAME. +SVar:X:Count$Valid Creature +Oracle:Exile any number of target creatures. At the beginning of the next end step, return those cards to the battlefield tapped under their owners' control.\nUntil your next turn, prevent all damage that would be dealt to you.\nExile Morningtide's Light. diff --git a/forge-gui/res/cardsfolder/upcoming/mutable_explorer.txt b/forge-gui/res/cardsfolder/upcoming/mutable_explorer.txt new file mode 100644 index 00000000000..db04d9a1fd7 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/mutable_explorer.txt @@ -0,0 +1,9 @@ +Name:Mutable Explorer +ManaCost:2 G +Types:Creature Shapeshifter +PT:1/1 +K:Changeling +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When this creature enters, create a tapped Mutavault token. (It's a land with "{T}: Add {C}" and "{1}: This token becomes a 2/2 creature with all creature types until end of turn. It's still a land.") +SVar:TrigToken:DB$ CopyPermanent | DefinedName$ Mutavault | TokenTapped$ True +DeckHas:Ability$Token & Type$Land +Oracle:Changeling (This card is every creature type.)\nWhen this creature enters, create a tapped Mutavault token. (It's a land with "{T}: Add {C}" and "{1}: This token becomes a 2/2 creature with all creature types until end of turn. It's still a land.") diff --git a/forge-gui/res/cardsfolder/upcoming/sygg_wanderwine_wisdom_sygg_wanderbrine_shield.txt b/forge-gui/res/cardsfolder/upcoming/sygg_wanderwine_wisdom_sygg_wanderbrine_shield.txt new file mode 100644 index 00000000000..11d6dcce0b5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sygg_wanderwine_wisdom_sygg_wanderbrine_shield.txt @@ -0,0 +1,28 @@ +Name:Sygg, Wanderwine Wisdom +ManaCost:1 U +Types:Legendary Creature Merfolk Wizard +PT:2/2 +S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | Description$ CARDNAME can't be blocked. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigAnimate | TriggerDescription$ Whenever this creature enters or transforms into CARDNAME, target creature gains "Whenever this creature deals combat damage to a player or planeswalker, draw a card" until end of turn. +T:Mode$ Transformed | ValidCard$ Card.Self | Execute$ TrigAnimate | Secondary$ True | TriggerDescription$ Whenever this creature enters or transforms into CARDNAME, target creature gains "Whenever this creature deals combat damage to a player or planeswalker, draw a card" until end of turn. +SVar:TrigAnimate:DB$ Animate | ValidTgts$ Creature | Triggers$ DamageTrig +SVar:DamageTrig:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player,Planeswalker | Execute$ TrigDraw | CombatDamage$ True | TriggerDescription$ Whenever this creature deals combat damage to a player or planeswalker, draw a card +SVar:TrigDraw:DB$ Draw | NumCards$ 1 +T:Mode$ Phase | Phase$ Main1 | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigTransform | TriggerDescription$ At the beginning of your first main phase, you may pay {W}. If you do, transform NICKNAME. +SVar:TrigTransform:AB$ SetState | Cost$ W | Defined$ Self | Mode$ Transform +AlternateMode:DoubleFaced +Oracle:Sygg can't be blocked.\nWhenever this creature enters or transforms into Sygg, Wanderwine Wisdom, target creature gains "Whenever this creature deals combat damage to a player or planeswalker, draw a card" until end of turn.\nAt the beginning of your first main phase, you may pay {W}. If you do, transform Sygg. + +ALTERNATE + +Name:Sygg, Wanderbrine Shield +ManaCost:no cost +Colors:white +Types:Legendary Creature Merfolk Rogue +PT:2/2 +S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | Description$ CARDNAME can't be blocked. +T:Mode$ Transformed | ValidCard$ Card.Self | Execute$ TrigProt | TriggerDescription$ Whenever this creature transforms into CARDNAME, target creature you control gains protection from each color until your next turn. +SVar:TrigProt:DB$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | KW$ Protection from each color | Duration$ UntilYourNextTurn +T:Mode$ Phase | Phase$ Main1 | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigTransform | TriggerDescription$ At the beginning of your first main phase, you may pay {U}. If you do, transform NICKNAME. +SVar:TrigTransform:AB$ SetState | Cost$ U | Defined$ Self | Mode$ Transform +Oracle:Sygg can't be blocked.\nWhenever this creature transforms into Sygg, Wanderbrine Shield, target creature you control gains protection from each color until your next turn.\nAt the beginning of your first main phase, you may pay {U}. If you do, transform Sygg. From a21a61cb051dfb12500762f596ba1deec50df675 Mon Sep 17 00:00:00 2001 From: Simisays <67333662+Simisays@users.noreply.github.com> Date: Sun, 28 Sep 2025 21:07:54 +0200 Subject: [PATCH 048/230] SLD 11 cards + support for new partner variants (#8797) --- .../src/main/java/forge/card/CardRules.java | 7 +++++++ .../src/main/java/forge/game/card/Card.java | 1 + .../src/main/java/forge/game/keyword/Keyword.java | 2 ++ .../upcoming/abby_merciless_soldier.txt | 13 +++++++++++++ .../upcoming/aloy_savior_of_meridian.txt | 11 +++++++++++ .../cardsfolder/upcoming/atreus_impulsive_son.txt | 10 ++++++++++ .../cardsfolder/upcoming/ellie_brick_master.txt | 9 +++++++++ .../upcoming/ellie_vengeful_hunter.txt | 9 +++++++++ .../upcoming/jaws_relentless_predator.txt | 15 +++++++++++++++ .../upcoming/jin_sakai_ghost_of_tsushima.txt | 12 ++++++++++++ .../upcoming/joel_resolute_survivor.txt | 12 ++++++++++++ .../cardsfolder/upcoming/kratos_god_of_war.txt | 10 ++++++++++ .../cardsfolder/upcoming/kratos_stoic_father.txt | 14 ++++++++++++++ .../upcoming/nathan_drake_treasure_hunter.txt | 11 +++++++++++ forge-gui/res/tokenscripts/cordyceps_infected.txt | 6 ++++++ 15 files changed, 142 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/abby_merciless_soldier.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/aloy_savior_of_meridian.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/atreus_impulsive_son.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/ellie_brick_master.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/ellie_vengeful_hunter.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/jaws_relentless_predator.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/jin_sakai_ghost_of_tsushima.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/kratos_god_of_war.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/kratos_stoic_father.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/nathan_drake_treasure_hunter.txt create mode 100644 forge-gui/res/tokenscripts/cordyceps_infected.txt diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index 2a9bb30ba4b..450f727c3e4 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -324,6 +324,12 @@ public final class CardRules implements ICardCharacteristics { } if (hasKeyword("Friends forever") && b.hasKeyword("Friends forever")) { legal = true; // Stranger Things Secret Lair gimmick partner commander + } + if (hasKeyword("Partner - Survivors") && b.hasKeyword("Partner - Survivors")) { + legal = true; // The Last of Us Secret Lair gimmick partner commander + } + if (hasKeyword("Partner - Father & Son") && b.hasKeyword("Partner - Father & Son")) { + legal = true; // God of War Secret Lair gimmick partner commander } if (hasKeyword("Choose a Background") && b.canBeBackground() || b.hasKeyword("Choose a Background") && canBeBackground()) { @@ -342,6 +348,7 @@ public final class CardRules implements ICardCharacteristics { } return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() || hasKeyword("Friends forever") || hasKeyword("Choose a Background") || + hasKeyword("Partner - Father & Son") || hasKeyword("Partner - Survivors") || hasKeyword("Doctor's companion") || isDoctor()); } 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 49894c670aa..35acfe1de7a 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2598,6 +2598,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr || keyword.equals("Battle cry") || keyword.equals("Devoid") || keyword.equals("Riot") || keyword.equals("Daybound") || keyword.equals("Nightbound") || keyword.equals("Friends forever") || keyword.equals("Choose a Background") + || keyword.equals("Partner - Father & Son") || keyword.equals("Partner - Survivors") || keyword.equals("Space sculptor") || keyword.equals("Doctor's companion") || keyword.equals("Start your engines")) { sbLong.append(keyword).append(" (").append(inst.getReminderText()).append(")"); diff --git a/forge-game/src/main/java/forge/game/keyword/Keyword.java b/forge-game/src/main/java/forge/game/keyword/Keyword.java index 8a28b77cf5c..809b824c07f 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -141,6 +141,8 @@ public enum Keyword { OFFSPRING("Offspring", KeywordWithCost.class, false, "You may pay an additional %s as you cast this spell. If you do, when this creature enters, create a 1/1 token copy of it."), OVERLOAD("Overload", KeywordWithCost.class, false, "You may cast this spell for its overload cost. If you do, change its text by replacing all instances of \"target\" with \"each.\""), PARTNER("Partner", Partner.class, true, "You can have two commanders if both have partner."), + PARTNER_SURVIVOR("Partner - Survivors", Partner.class, true, "You can have two commanders if both have this ability."), + PARTNER_FATHER_AND_SON("Partner - Father & Son", Partner.class, true, "You can have two commanders if both have this ability."), PERSIST("Persist", SimpleKeyword.class, false, "When this creature dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner's control with a -1/-1 counter on it."), PHASING("Phasing", SimpleKeyword.class, true, "This phases in or out before you untap during each of your untap steps. While it's phased out, it's treated as though it doesn't exist."), PLOT("Plot", KeywordWithCost.class, false, "You may pay %s and exile this card from your hand. Cast it as a sorcery on a later turn without paying its mana cost. Plot only as a sorcery."), diff --git a/forge-gui/res/cardsfolder/upcoming/abby_merciless_soldier.txt b/forge-gui/res/cardsfolder/upcoming/abby_merciless_soldier.txt new file mode 100644 index 00000000000..d83e8604560 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/abby_merciless_soldier.txt @@ -0,0 +1,13 @@ +Name:Abby, Merciless Soldier +ManaCost:1 R G +Types:Legendary Creature Human Survivor +PT:4/4 +K:Partner - Survivors +T:Mode$ SpellCast | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When you cast this spell, create a number of 1/1 black Fungus Zombie creature tokens named Cordyceps Infected equal to the amount of mana spent to cast it. +SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ cordyceps_infected | TokenOwner$ You +SVar:X:Count$CastTotalManaSpent +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ DBChooseOpp | Layer$ Control | Description$ NICKNAME enters under the control of an opponent of your choice. +SVar:DBChooseOpp:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | ChoiceTitle$ Choose an opponent to give control to | AILogic$ Curse | SubAbility$ MoveToPlay +SVar:MoveToPlay:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Battlefield | Defined$ ReplacedCard | GainControl$ ChosenPlayer +DeckHas:Ability$Token & Type$Zombie|Fungus +Oracle:When you cast this spell, create a number of 1/1 black Fungus Zombie creature tokens named Cordyceps Infected equal to the amount of mana spent to cast it.\nAbby enters under the control of an opponent of your choice.\nPartner—Survivors (You can have two commanders if both have this ability.) diff --git a/forge-gui/res/cardsfolder/upcoming/aloy_savior_of_meridian.txt b/forge-gui/res/cardsfolder/upcoming/aloy_savior_of_meridian.txt new file mode 100644 index 00000000000..ae6ec383090 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/aloy_savior_of_meridian.txt @@ -0,0 +1,11 @@ +Name:Aloy, Savior of Meridian +ManaCost:3 G U +Types:Legendary Creature Human Warrior +PT:3/5 +K:Vigilance +K:Reach +T:Mode$ AttackersDeclared | AttackingPlayer$ You | ValidAttackers$ Creature.Artifact+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigDiscover | TriggerDescription$ In You, All Things Are Possible — Whenever one or more artifact creatures you control attack, discover X, where X is the greatest power among them. (Exile cards from the top of your library until you exile a nonland card with that mana value or less. Cast it without paying its mana cost or put it into your hand. Put the rest on the bottom in a random order.) +SVar:TrigDiscover:DB$ Discover | Num$ X +SVar:X:TriggerObjectsAttackers$GreatestPower +DeckNeeds:Type$Artifact & Type$Creature +Oracle:Vigilance, reach\nIn You, All Things Are Possible — Whenever one or more artifact creatures you control attack, discover X, where X is the greatest power among them. (Exile cards from the top of your library until you exile a nonland card with that mana value or less. Cast it without paying its mana cost or put it into your hand. Put the rest on the bottom in a random order.) diff --git a/forge-gui/res/cardsfolder/upcoming/atreus_impulsive_son.txt b/forge-gui/res/cardsfolder/upcoming/atreus_impulsive_son.txt new file mode 100644 index 00000000000..b6fc41bbc8d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/atreus_impulsive_son.txt @@ -0,0 +1,10 @@ +Name:Atreus, Impulsive Son +ManaCost:1 U R +Types:Legendary Creature God Archer +PT:2/4 +K:Reach +K:Partner - Father & Son +A:AB$ Draw | Cost$ 3 T | NumCards$ X | SubAbility$ DBDamage | SpellDescription$ Draw a card for each experience counter you have, then discard a card. +SVar:DBDamage:DB$ DealDamage | NumDmg$ 2 | Defined$ Opponent | SpellDescription$ CARDNAME deals 2 damage to each opponent. +SVar:X:Count$YourCountersExperience +Oracle:Reach\n{3}, {T}: Draw a card for each experience counter you have, then discard a card. Atreus, Impulsive Son deals 2 damage to each opponent.\nPartner-Father & son diff --git a/forge-gui/res/cardsfolder/upcoming/ellie_brick_master.txt b/forge-gui/res/cardsfolder/upcoming/ellie_brick_master.txt new file mode 100644 index 00000000000..2823c85f96f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ellie_brick_master.txt @@ -0,0 +1,9 @@ +Name:Ellie, Brick Master +ManaCost:1 R +Types:Legendary Creature Human Survivor +PT:2/1 +K:Partner - Survivors +T:Mode$ AttackersDeclaredOneTarget | AttackedTarget$ Opponent | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Distract the Horde - Whenever a player attacks one of your opponents, that attacking player creates a tapped 1/1 black Fungus Zombie creature token named Cordyceps Infected that's attacking that opponent. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ cordyceps_infected | TokenOwner$ TriggeredAttackingPlayer | TokenTapped$ True | TokenAttacking$ TriggeredAttackedTarget +DeckHas:Ability$Token & Type$Zombie|Fungus +Oracle:Distract the Horde - Whenever a player attacks one of your opponents, that attacking player creates a tapped 1/1 black Fungus Zombie creature token named Cordyceps Infected that's attacking that opponent.\nPartner — Survivors (You can have two commanders if both have this ability.) diff --git a/forge-gui/res/cardsfolder/upcoming/ellie_vengeful_hunter.txt b/forge-gui/res/cardsfolder/upcoming/ellie_vengeful_hunter.txt new file mode 100644 index 00000000000..30b5001b11c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ellie_vengeful_hunter.txt @@ -0,0 +1,9 @@ +Name:Ellie, Vengeful Hunter +ManaCost:1 B R +Types:Legendary Creature Human Survivor +PT:3/1 +K:Partner - Survivors +A:AB$ DealDamage | Cost$ PayLife<2> Sac<1/Creature.Other/another creature> | NumDmg$ 2 | ValidTgts$ Player | SubAbility$ DBPump | SpellDescription$ NICKNAME deals 2 damage to target player and gains indestructible until end of turn. +SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Indestructible +DeckHas:Ability$Sacrifice +Oracle:Pay 2 life, Sacrifice another creature: Ellie deals 2 damage to target player and gains indestructible until end of turn.\nPartner - Survivors (You can have two commanders if both have this ability.) diff --git a/forge-gui/res/cardsfolder/upcoming/jaws_relentless_predator.txt b/forge-gui/res/cardsfolder/upcoming/jaws_relentless_predator.txt new file mode 100644 index 00000000000..f54416fbd56 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/jaws_relentless_predator.txt @@ -0,0 +1,15 @@ +Name:Jaws, Relentless Predator +ManaCost:3 R R +Types:Legendary Creature Shark +PT:5/5 +K:Trample +K:Haste +T:Mode$ DamageDone | ValidSource$ Creature.YouCtrl | ValidTarget$ Player | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever NICKNAME deals combat damage to a player, create that many Blood tokens. +SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ c_a_blood_draw | TokenOwner$ You +SVar:X:TriggerCount$DamageAmount +T:Mode$ Sacrificed | ValidCard$ Artifact.nonCreature | Execute$ TrigDamage | TriggerZones$ Battlefield | TriggerDescription$ Whenever a noncreature artifact is sacrificed or destroyed, NICKNAME deals 1 damage to each opponent. +T:Mode$ Destroyed | ValidCard$ Artifact.nonCreature | Execute$ TrigDamage | TriggerZones$ Battlefield | Secondary$ True | TriggerDescription$ Whenever a noncreature artifact is sacrificed or destroyed, NICKNAME deals 1 damage to each opponent. +SVar:TrigDamage:DB$ DealDamage | NumDmg$ 1 | Defined$ Opponent +DeckHints:Ability$Sacrifice|Token & Type$Blood|Treasure|Map|Clue +DeckHas:Ability$Token|Sacrifice & Type$Artifact|Blood +Oracle:Trample, haste\nWhenever Jaws deals combat damage to a player, create that many Blood tokens.\nWhenever a noncreature artifact is sacrificed or destroyed, Jaws deals 1 damage to each opponent. diff --git a/forge-gui/res/cardsfolder/upcoming/jin_sakai_ghost_of_tsushima.txt b/forge-gui/res/cardsfolder/upcoming/jin_sakai_ghost_of_tsushima.txt new file mode 100644 index 00000000000..28c9904ee1b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/jin_sakai_ghost_of_tsushima.txt @@ -0,0 +1,12 @@ +Name:Jin Sakai, Ghost of Tsushima +ManaCost:1 W U B +Types:Legendary Creature Human Samurai +PT:2/4 +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDraw | TriggerDescription$ Whenever NICKNAME deals combat damage to a player, draw a card. +SVar:TrigDraw:DB$ Draw | NumCards$ 1 +T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | Alone$ True | TriggerZones$ Battlefield | Execute$ TrigCharm | IsPresent$ Creature.attacking+Other | NoResolvingCheck$ True | PresentCompare$ EQ0 | TriggerDescription$ Whenever a creature you control attacks a player, if no other creatures are attacking that player, ABILITY +SVar:TrigCharm:DB$ Charm | Choices$ Standoff,Ghost +SVar:Standoff:DB$ Pump | Defined$ TriggeredAttackerLKICopy | KW$ Double Strike | SpellDescription$ Standoff — It gains double strike until end of turn. +SVar:Ghost:DB$ Effect | RememberObjects$ Self | ExileOnMoved$ Battlefield | StaticAbilities$ Unblockable | SpellDescription$ Ghost — It can't be blocked this turn. +SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ EFFECTSOURCE can't be blocked this turn. +Oracle:Whenever Jin Sakai deals combat damage to a player, draw a card.\nWhenever a creature you control attacks a player, if no other creatures are attacking that player, choose one —-\n• Standoff — It gains double strike until end of turn.\n• Ghost — It can't be blocked this turn. diff --git a/forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt b/forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt new file mode 100644 index 00000000000..d95aa4ce05d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt @@ -0,0 +1,12 @@ +Name:Joel, Resolute Survivor +ManaCost:3 B G +Types:Legendary Creature Human Survivor +PT:4/4 +K:Menace +K:Partner - Survivors +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.token | Execute$ TrigPutCounter | ActivationLimit$ 1 | TriggerDescription$ Whenever a creature token dies, put a +1/+1 counter on NICKNAME and draw a card. This ability triggers only once each turn. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterNum$ 1 | CounterType$ P1P1 | SubAbility$ DBDraw +SVar:TrigDraw:DB$ Draw +DeckHas:Ability$Counters +DeckHints:Ability$Sacrifice|Token +Oracle:Menace\nWhenever a creature token dies, put a +1/+1 counter on Joel and draw a card. This ability triggers only once each turn.\nPartner — Survivors (You can have two commanders if both have this ability.) diff --git a/forge-gui/res/cardsfolder/upcoming/kratos_god_of_war.txt b/forge-gui/res/cardsfolder/upcoming/kratos_god_of_war.txt new file mode 100644 index 00000000000..ebdc6fd9082 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kratos_god_of_war.txt @@ -0,0 +1,10 @@ +Name:Kratos, God of War +ManaCost:R R R +Types:Legendary Creature God Warrior +PT:2/3 +K:Double Strike +S:Mode$ Continuous | Affected$ Creature | AddKeyword$ Haste | Description$ All creatures have haste. +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ At the beginning of each player's end step, NICKNAME deals damage to that player equal to the number of creatures that player controls that didn't attack this turn. +SVar:TrigDamage:DB$ DealDamage | Defined$ TriggeredPlayer | NumDmg$ X +SVar:X:Count$Valid Creature.!attackedThisTurn+ActivePlayerCtrl +Oracle:Double strike\nAll creatures have haste.\nAt the beginning of each player's end step, Kratos deals damage to that player equal to the number of creatures that player controls that didn't attack this turn. diff --git a/forge-gui/res/cardsfolder/upcoming/kratos_stoic_father.txt b/forge-gui/res/cardsfolder/upcoming/kratos_stoic_father.txt new file mode 100644 index 00000000000..c1cba612160 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kratos_stoic_father.txt @@ -0,0 +1,14 @@ +Name:Kratos, Stoic Father +ManaCost:2 R W +Types:Legendary Creature God Warrior +PT:4/4 +K:Partner - Father & Son +T:Mode$ AttackersDeclared | ValidAttackers$ God.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigExperience | TriggerDescription$ Whenever you attack with one or more Gods and whenever a God dies, you get an experience counter. +T:Mode$ ChangesZone | ValidCard$ God | TriggerZones$ Battlefield | Secondary$ True | Destination$ Graveyard | Execute$ TrigExperience | TriggerDescription$ Whenever you attack with one or more Gods and whenever a God dies, you get an experience counter. +SVar:TrigExperience:DB$ PutCounter | Defined$ You | CounterType$ Experience | CounterNum$ 1 +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ At the beginning of your end step, put a number of +1/+1 counters on target creature equal to the number of experience counters you have. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | CounterType$ P1P1 | CounterNum$ X +SVar:X:Count$YourCountersExperience +DeckHas:Ability$Counters +DeckHints:Type$God +Oracle:Whenever you attack with one or more Gods and whenever a God dies, you get an experience counter.\nAt the beginning of your end step, put a number of +1/+1 counters on target creature equal to the number of experience counters you have.\nPartner-Father & son (You can have two commanders if both have this ability.) diff --git a/forge-gui/res/cardsfolder/upcoming/nathan_drake_treasure_hunter.txt b/forge-gui/res/cardsfolder/upcoming/nathan_drake_treasure_hunter.txt new file mode 100644 index 00000000000..2a035bb2972 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/nathan_drake_treasure_hunter.txt @@ -0,0 +1,11 @@ +Name:Nathan Drake, Treasure Hunter +ManaCost:U B R +Types:Legendary Creature Human Rogue +PT:3/2 +K:First Strike +S:Mode$ ManaConvert | ValidPlayer$ You | ValidCard$ Card.YouDontOwn+YouCtrl | ValidSA$ Spell,Activated | ManaConversion$ AnyType->AnyColor | Description$ You may spend mana as though it were mana of any color to cast spells you don't own or to activate abilities of permanents you control but don't own. +T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigExile | TriggerDescription$ Whenever NICKNAME attacks, exile the top card of each player's library. You may cast a spell from among those cards. +SVar:TrigExile:DB$ Dig | Defined$ Player | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBPlay +SVar:DBPlay:DB$ Play | Valid$ Card.IsRemembered | ValidSA$ Spell | ValidZone$ Exile | Controller$ You | Optional$ True | Amount$ 1 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:First strike\nYou may spend mana as though it were mana of any color to cast spells you don't own or to activate abilities of permanents you control but don't own.\nWhenever Nathan Drake attacks, exile the top card of each player's library. You may cast a spell from among those cards. diff --git a/forge-gui/res/tokenscripts/cordyceps_infected.txt b/forge-gui/res/tokenscripts/cordyceps_infected.txt new file mode 100644 index 00000000000..52dad650b09 --- /dev/null +++ b/forge-gui/res/tokenscripts/cordyceps_infected.txt @@ -0,0 +1,6 @@ +Name:Cordyceps Infected +ManaCost:no cost +Colors:black +Types:Creature Fungus Zombie +PT:1/1 +Oracle: From 1d97a4ec7e2e4731ef1f57bc69f742e99e190dc3 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 28 Sep 2025 21:20:22 +0200 Subject: [PATCH 049/230] Update eirdu_carrier_of_dawn_isilu_carrier_of_twilight.txt --- .../eirdu_carrier_of_dawn_isilu_carrier_of_twilight.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/eirdu_carrier_of_dawn_isilu_carrier_of_twilight.txt b/forge-gui/res/cardsfolder/upcoming/eirdu_carrier_of_dawn_isilu_carrier_of_twilight.txt index c7bffe073be..a74137a872a 100644 --- a/forge-gui/res/cardsfolder/upcoming/eirdu_carrier_of_dawn_isilu_carrier_of_twilight.txt +++ b/forge-gui/res/cardsfolder/upcoming/eirdu_carrier_of_dawn_isilu_carrier_of_twilight.txt @@ -20,7 +20,7 @@ Types:Legendary Creature Elemental God PT:5/5 K:Flying K:Lifelink -S:Mode$ Continuous | Affected$ Card.Creature+YouCtrl+nonToken+Other | AffectedZone$ Battlefield | AddKeyword$ Persist | Description$ Each other nontoken creature you control has persist. (When it dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner’s control with a -1/-1 counter on it.) +S:Mode$ Continuous | Affected$ Card.Creature+YouCtrl+!token+Other | AffectedZone$ Battlefield | AddKeyword$ Persist | Description$ Each other nontoken creature you control has persist. (When it dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner’s control with a -1/-1 counter on it.) T:Mode$ Phase | Phase$ Main1 | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigTransform | TriggerDescription$ At the beginning of your first main phase, you may pay {W}. If you do, transform NICKNAME. SVar:TrigTransform:AB$ SetState | Cost$ W | Defined$ Self | Mode$ Transform Oracle:Flying, lifelink\nEach other nontoken creature you control has persist. (When it dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner's control with a -1/-1 counter on it.) At the beginning of your first main phase, you may pay {W}. If you do, transform Isilu. From a390284b8e965deaf72a9860a88458fb222315af Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 28 Sep 2025 21:55:25 +0200 Subject: [PATCH 050/230] Property cleanup (#8804) * remove NotTriggeredCard * remove nonManaAbility * remove notattacking and notblocking * remove notnamed --- .../main/java/forge/ai/ability/CloneAi.java | 2 +- .../src/main/java/forge/game/ForgeScript.java | 2 -- .../java/forge/game/card/CardProperty.java | 20 ------------------- forge-gui/res/cardsfolder/a/abeyance.txt | 2 +- .../res/cardsfolder/a/akron_legionnaire.txt | 2 +- forge-gui/res/cardsfolder/a/alarum.txt | 2 +- forge-gui/res/cardsfolder/a/animate_dead.txt | 2 +- .../cardsfolder/a/anointed_peacekeeper.txt | 2 +- .../res/cardsfolder/a/arcades_sabboth.txt | 2 +- .../res/cardsfolder/a/ashnod_the_uncaring.txt | 2 +- forge-gui/res/cardsfolder/a/atog.txt | 2 +- .../res/cardsfolder/b/battlemages_bracers.txt | 2 +- .../cardsfolder/b/blade_of_shared_souls.txt | 2 +- forge-gui/res/cardsfolder/b/bound_in_gold.txt | 2 +- .../res/cardsfolder/b/burning_rune_demon.txt | 2 +- .../res/cardsfolder/b/burning_tree_shaman.txt | 2 +- forge-gui/res/cardsfolder/c/caged_sun.txt | 2 +- .../res/cardsfolder/c/clever_conjurer.txt | 2 +- .../cardsfolder/c/colfenor_the_last_yew.txt | 2 +- .../res/cardsfolder/c/crackdown_construct.txt | 2 +- .../res/cardsfolder/c/cutthroat_centurion.txt | 2 +- .../res/cardsfolder/d/damping_matrix.txt | 2 +- .../res/cardsfolder/d/dance_of_the_dead.txt | 2 +- .../res/cardsfolder/d/detention_sphere.txt | 2 +- .../d/dhalsim_pliable_pacifist.txt | 2 +- .../res/cardsfolder/d/disruptor_flute.txt | 2 +- .../res/cardsfolder/d/domineering_will.txt | 2 +- .../res/cardsfolder/d/dutiful_replicator.txt | 2 +- .../cardsfolder/d/dynaheir_invoker_adept.txt | 2 +- .../res/cardsfolder/e/ebondeath_dracolich.txt | 2 +- .../res/cardsfolder/e/eye_of_singularity.txt | 2 +- .../res/cardsfolder/f/faiths_fetters.txt | 2 +- .../res/cardsfolder/f/farrels_mantle.txt | 2 +- ...flamescroll_celebrant_revel_in_silence.txt | 2 +- forge-gui/res/cardsfolder/g/gaeas_liege.txt | 2 +- .../cardsfolder/g/geralf_the_fleshwright.txt | 2 +- forge-gui/res/cardsfolder/g/grip_of_chaos.txt | 2 +- .../res/cardsfolder/g/guildsworn_prowler.txt | 2 +- forge-gui/res/cardsfolder/h/hand_to_hand.txt | 2 +- forge-gui/res/cardsfolder/h/harsh_mentor.txt | 2 +- .../res/cardsfolder/h/hollow_warrior.txt | 4 ++-- forge-gui/res/cardsfolder/h/horned_kavu.txt | 2 +- .../res/cardsfolder/i/illicit_masquerade.txt | 2 +- .../cardsfolder/i/illusionists_bracers.txt | 2 +- .../res/cardsfolder/i/immolation_shaman.txt | 2 +- forge-gui/res/cardsfolder/i/imprison.txt | 2 +- .../res/cardsfolder/i/intercessors_arrest.txt | 2 +- forge-gui/res/cardsfolder/k/karns_sylex.txt | 2 +- .../cardsfolder/k/kurkesh_onakke_ancient.txt | 2 +- forge-gui/res/cardsfolder/k/kusari_gama.txt | 2 +- .../res/cardsfolder/m/martial_impetus.txt | 2 +- .../cardsfolder/m/mondassian_colony_ship.txt | 2 +- .../res/cardsfolder/m/mothrider_cavalry.txt | 2 +- .../cardsfolder/o/overwhelming_splendor.txt | 2 +- .../cardsfolder/p/phyrexian_dreadnought.txt | 2 +- forge-gui/res/cardsfolder/p/pit_automaton.txt | 2 +- .../res/cardsfolder/p/pithing_needle.txt | 2 +- .../res/cardsfolder/p/psychic_battle.txt | 2 +- .../res/cardsfolder/r/realmbreakers_grasp.txt | 2 +- .../cardsfolder/r/rings_of_brighthearth.txt | 2 +- forge-gui/res/cardsfolder/r/rowan_kenrith.txt | 2 +- .../res/cardsfolder/r/runic_armasaur.txt | 2 +- .../res/cardsfolder/r/ruthless_instincts.txt | 2 +- .../rebalanced/a-earthquake_dragon.txt | 2 +- .../rebalanced/a-guildsworn_prowler.txt | 2 +- .../res/cardsfolder/s/sala_deck_boss.txt | 2 +- ...asaya_orochi_ascendant_sasayas_essence.txt | 2 +- .../res/cardsfolder/s/sculpting_steel.txt | 2 +- .../res/cardsfolder/s/shared_animosity.txt | 2 +- .../s/sharkey_tyrant_of_the_shire.txt | 4 ++-- .../res/cardsfolder/s/sigil_of_valor.txt | 2 +- .../res/cardsfolder/s/sorcerous_spyglass.txt | 2 +- .../s/staff_of_eden_vaults_key.txt | 2 +- .../res/cardsfolder/s/suppression_field.txt | 2 +- .../res/cardsfolder/t/tahngarths_rage.txt | 2 +- ...he_enigma_jewel_locus_of_enlightenment.txt | 2 +- .../res/cardsfolder/t/the_flood_of_mars.txt | 2 +- forge-gui/res/cardsfolder/t/tiamat.txt | 2 +- forge-gui/res/cardsfolder/t/tinker.txt | 2 +- forge-gui/res/cardsfolder/t/tithe_taker.txt | 2 +- .../t/toralf_god_of_fury_toralfs_hammer.txt | 2 +- forge-gui/res/cardsfolder/t/total_war.txt | 2 +- forge-gui/res/cardsfolder/t/tromokratis.txt | 2 +- .../res/cardsfolder/u/unlikely_alliance.txt | 2 +- .../v/valakut_the_molten_pinnacle.txt | 2 +- .../cardsfolder/v/verrak_warped_sengir.txt | 2 +- forge-gui/res/cardsfolder/v/vesuva.txt | 2 +- .../res/cardsfolder/w/wheel_of_fortune.txt | 2 +- .../res/cardsfolder/w/wizened_mentor.txt | 2 +- .../res/cardsfolder/z/zirda_the_dawnwaker.txt | 2 +- .../java/forge/gui/card/CardScriptParser.java | 8 ++++---- 91 files changed, 94 insertions(+), 116 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java index 519f26bdd3b..942bde1491f 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java @@ -188,7 +188,7 @@ public class CloneAi extends SpellAbilityAi { final boolean canCloneLegendary = "True".equalsIgnoreCase(sa.getParam("NonLegendary")); String filter = !isVesuva ? "Permanent.YouDontCtrl,Permanent.nonLegendary" - : "Permanent.YouDontCtrl+notnamed" + name + ",Permanent.nonLegendary+notnamed" + name; + : "Permanent.YouDontCtrl+!named" + name + ",Permanent.nonLegendary+!named" + name; // TODO: rewrite this block so that this is done somehow more elegantly if (canCloneLegendary) { diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index 5ce1b116732..9c462d9e22c 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -166,8 +166,6 @@ public class ForgeScript { Card source, CardTraitBase spellAbility) { if (property.equals("ManaAbility")) { return sa.isManaAbility(); - } else if (property.equals("nonManaAbility")) { - return !sa.isManaAbility(); } else if (property.equals("withoutXCost")) { return !sa.costHasManaX(); } else if (property.startsWith("XCost")) { 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 db8c2e94a33..e27a4f8fed7 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -66,12 +66,6 @@ public class CardProperty { if (!card.sharesNameWith(name)) { return false; } - } else if (property.startsWith("notnamed")) { - String name = TextUtil.fastReplace(property.substring(8), ";", ","); // workaround for card name with "," - name = TextUtil.fastReplace(name, "_", " "); - if (card.sharesNameWith(name)) { - return false; - } } else if (property.equals("NamedCard")) { boolean found = false; for (String name : source.getNamedCards()) { @@ -1564,8 +1558,6 @@ public class CardProperty { return false; } } - } else if (property.startsWith("notattacking")) { - return null == combat || !combat.isAttacking(card); } else if (property.startsWith("enlistedThisCombat")) { if (card.getEnlistedThisCombat() == false) return false; } else if (property.startsWith("attackedThisCombat")) { @@ -1619,8 +1611,6 @@ public class CardProperty { if (Collections.disjoint(combat.getAttackersBlockedBy(source), combat.getAttackersBlockedBy(card))) { return false; } - } else if (property.startsWith("notblocking")) { - return null == combat || !combat.isBlocking(card); } // Nex predicates refer to past combat and don't need a reference to actual combat else if (property.equals("blocked")) { @@ -2073,16 +2063,6 @@ public class CardProperty { } else { return false; } - } else if (property.startsWith("NotTriggered")) { - final String key = property.substring("NotTriggered".length()); - if (spellAbility instanceof SpellAbility) { - SpellAbility sa = (SpellAbility) spellAbility; - if (card.equals(sa.getRootAbility().getTriggeringObject(AbilityKey.fromString(key)))) { - return false; - } - } else { - return false; - } } else if (property.startsWith("NotDefined")) { final String key = property.substring("NotDefined".length()); if (AbilityUtils.getDefinedCards(source, key, spellAbility).contains(card)) { diff --git a/forge-gui/res/cardsfolder/a/abeyance.txt b/forge-gui/res/cardsfolder/a/abeyance.txt index 4a02b1bb11a..7d482d121a6 100644 --- a/forge-gui/res/cardsfolder/a/abeyance.txt +++ b/forge-gui/res/cardsfolder/a/abeyance.txt @@ -3,6 +3,6 @@ ManaCost:1 W Types:Instant A:SP$ Effect | ValidTgts$ Player | StaticAbilities$ STCantBeCast,STCantBeActivated | RememberObjects$ Targeted | AILogic$ BeginningOfOppTurn | SubAbility$ DBDraw | SpellDescription$ Until end of turn, target player can't cast instant or sorcery spells, and that player can't activate abilities that aren't mana abilities. SVar:STCantBeCast:Mode$ CantBeCast | ValidCard$ Instant,Sorcery | Caster$ Player.IsRemembered | Description$ Target player can't cast instant or sorcery spells, and that player can't activate abilities that aren't mana abilities. -SVar:STCantBeActivated:Mode$ CantBeActivated | ValidCard$ Card | ValidSA$ Activated.nonManaAbility | Activator$ Player.IsRemembered +SVar:STCantBeActivated:Mode$ CantBeActivated | ValidCard$ Card | ValidSA$ Activated.!ManaAbility | Activator$ Player.IsRemembered SVar:DBDraw:DB$ Draw | SpellDescription$ Draw a card. Oracle:Until end of turn, target player can't cast instant or sorcery spells, and that player can't activate abilities that aren't mana abilities.\nDraw a card. diff --git a/forge-gui/res/cardsfolder/a/akron_legionnaire.txt b/forge-gui/res/cardsfolder/a/akron_legionnaire.txt index 6a7a67319e4..e5c8b392416 100644 --- a/forge-gui/res/cardsfolder/a/akron_legionnaire.txt +++ b/forge-gui/res/cardsfolder/a/akron_legionnaire.txt @@ -2,7 +2,7 @@ Name:Akron Legionnaire ManaCost:6 W W Types:Creature Giant Soldier PT:8/4 -S:Mode$ CantAttack | ValidCard$ Creature.YouCtrl+nonArtifact+notnamedAkron Legionnaire | Description$ Except for creatures named Akron Legionnaire and artifact creatures, creatures you control can't attack. +S:Mode$ CantAttack | ValidCard$ Creature.YouCtrl+nonArtifact+!namedAkron Legionnaire | Description$ Except for creatures named Akron Legionnaire and artifact creatures, creatures you control can't attack. DeckHints:Type$Artifact DeckNeeds:Name$Akron Legionnaire Oracle:Except for creatures named Akron Legionnaire and artifact creatures, creatures you control can't attack. diff --git a/forge-gui/res/cardsfolder/a/alarum.txt b/forge-gui/res/cardsfolder/a/alarum.txt index 4e903cf80bb..8576991c091 100644 --- a/forge-gui/res/cardsfolder/a/alarum.txt +++ b/forge-gui/res/cardsfolder/a/alarum.txt @@ -1,6 +1,6 @@ Name:Alarum ManaCost:1 W Types:Instant -A:SP$ Pump | ValidTgts$ Creature.notattacking | TgtPrompt$ Select target nonattacking creature | NumAtt$ +1 | NumDef$ +3 | SubAbility$ DBUntap | SpellDescription$ Untap target nonattacking creature. It gets +1/+3 until end of turn. +A:SP$ Pump | ValidTgts$ Creature.!attacking | TgtPrompt$ Select target nonattacking creature | NumAtt$ +1 | NumDef$ +3 | SubAbility$ DBUntap | SpellDescription$ Untap target nonattacking creature. It gets +1/+3 until end of turn. SVar:DBUntap:DB$ Untap | Defined$ Targeted Oracle:Untap target nonattacking creature. It gets +1/+3 until end of turn. diff --git a/forge-gui/res/cardsfolder/a/animate_dead.txt b/forge-gui/res/cardsfolder/a/animate_dead.txt index f505055471f..884f653dc7b 100644 --- a/forge-gui/res/cardsfolder/a/animate_dead.txt +++ b/forge-gui/res/cardsfolder/a/animate_dead.txt @@ -3,7 +3,7 @@ ManaCost:1 B Types:Enchantment Aura K:Enchant:Creature.inZoneGraveyard:creature card in a graveyard SVar:AttachAILogic:Reanimate -SVar:AttachAITgts:Creature.notnamedWorldgorger Dragon +SVar:AttachAITgts:Creature.!namedWorldgorger Dragon T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | IsPresent$ Card.StrictlySelf | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters, 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 | Keywords$ Enchant:Creature.IsRemembered:creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant:Creature.inZoneGraveyard:creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach diff --git a/forge-gui/res/cardsfolder/a/anointed_peacekeeper.txt b/forge-gui/res/cardsfolder/a/anointed_peacekeeper.txt index e63e2720ae6..3e2abd91e46 100644 --- a/forge-gui/res/cardsfolder/a/anointed_peacekeeper.txt +++ b/forge-gui/res/cardsfolder/a/anointed_peacekeeper.txt @@ -9,6 +9,6 @@ SVar:DBLook:DB$ RevealHand | Defined$ ChosenPlayer | Look$ True | SubAbility$ DB SVar:DBNameCard:DB$ NameCard | Defined$ You | SubAbility$ DBClear SVar:DBClear:DB$ Cleanup | ClearChosenPlayer$ True S:Mode$ RaiseCost | EffectZone$ Battlefield | ValidCard$ Card.NamedCard | Type$ Spell | Amount$ 2 | Activator$ Opponent | Description$ Spells your opponents cast with the chosen name cost {2} more to cast. -S:Mode$ RaiseCost | EffectZone$ Battlefield | ValidCard$ Card.NamedCard | ValidSpell$ Activated.nonManaAbility | Amount$ 2 | Description$ Activated abilities of sources with the chosen name cost {2} more to activate unless they're mana abilities. +S:Mode$ RaiseCost | EffectZone$ Battlefield | ValidCard$ Card.NamedCard | ValidSpell$ Activated.!ManaAbility | Amount$ 2 | Description$ Activated abilities of sources with the chosen name cost {2} more to activate unless they're mana abilities. AI:RemoveDeck:Random Oracle:Vigilance\nAs Anointed Peacekeeper enters, look at an opponent's hand, then choose any card name.\nSpells your opponents cast with the chosen name cost {2} more to cast.\nActivated abilities of sources with the chosen name cost {2} more to activate unless they're mana abilities. diff --git a/forge-gui/res/cardsfolder/a/arcades_sabboth.txt b/forge-gui/res/cardsfolder/a/arcades_sabboth.txt index 593cecef1fc..bf316d4dccf 100644 --- a/forge-gui/res/cardsfolder/a/arcades_sabboth.txt +++ b/forge-gui/res/cardsfolder/a/arcades_sabboth.txt @@ -5,7 +5,7 @@ PT:7/7 K:Flying T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigUpkeep | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you pay {G}{W}{U}. SVar:TrigUpkeep:DB$ Sacrifice | UnlessPayer$ You | UnlessCost$ G W U -S:Mode$ Continuous | Affected$ Creature.notattacking+untapped+YouCtrl | AddToughness$ 2 | Description$ Each untapped creature you control gets +0/+2 as long as it's not attacking. +S:Mode$ Continuous | Affected$ Creature.!attacking+untapped+YouCtrl | AddToughness$ 2 | Description$ Each untapped creature you control gets +0/+2 as long as it's not attacking. A:AB$ Pump | Cost$ W | Defined$ Self | NumDef$ +1 | SpellDescription$ CARDNAME gets +0/+1 until end of turn. DeckHints:Type$Wall & Keyword$Defender Oracle:Flying\nAt the beginning of your upkeep, sacrifice Arcades Sabboth unless you pay {G}{W}{U}.\nEach untapped creature you control gets +0/+2 as long as it's not attacking.\n{W}: Arcades Sabboth gets +0/+1 until end of turn. diff --git a/forge-gui/res/cardsfolder/a/ashnod_the_uncaring.txt b/forge-gui/res/cardsfolder/a/ashnod_the_uncaring.txt index 7a60eef2554..94634e668d9 100644 --- a/forge-gui/res/cardsfolder/a/ashnod_the_uncaring.txt +++ b/forge-gui/res/cardsfolder/a/ashnod_the_uncaring.txt @@ -3,7 +3,7 @@ ManaCost:2 U B R Types:Legendary Creature Human Artificer PT:1/4 K:Deathtouch -T:Mode$ AbilityCast | ValidCard$ Artifact.inZoneBattlefield,Creature.inZoneBattlefield | ValidSA$ SpellAbility.nonManaAbility | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Condition$ Sacrificed | Execute$ TrigCopy | OptionalDecider$ You | TriggerDescription$ Whenever you activate an ability of an artifact or creature that isn't a mana ability, if one or more permanents were sacrificed to activate it, you may copy that ability. You may choose new targets for the copy. (Sacrificing an artifact for mana to activate an ability doesn't count.) +T:Mode$ AbilityCast | ValidCard$ Artifact.inZoneBattlefield,Creature.inZoneBattlefield | ValidSA$ SpellAbility.!ManaAbility | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Condition$ Sacrificed | Execute$ TrigCopy | OptionalDecider$ You | TriggerDescription$ Whenever you activate an ability of an artifact or creature that isn't a mana ability, if one or more permanents were sacrificed to activate it, you may copy that ability. You may choose new targets for the copy. (Sacrificing an artifact for mana to activate an ability doesn't count.) SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True DeckNeeds:Ability$Sacrifice DeckHints:Type$Artifact diff --git a/forge-gui/res/cardsfolder/a/atog.txt b/forge-gui/res/cardsfolder/a/atog.txt index f0415a1ac9e..0a5913d0972 100644 --- a/forge-gui/res/cardsfolder/a/atog.txt +++ b/forge-gui/res/cardsfolder/a/atog.txt @@ -3,7 +3,7 @@ ManaCost:1 R Types:Creature Atog PT:1/2 A:AB$ Pump | Cost$ Sac<1/Artifact> | Defined$ Self | NumAtt$ +2 | NumDef$ +2 | SpellDescription$ CARDNAME gets +2/+2 until end of turn. -SVar:AIPreference:SacCost$Artifact.token,Artifact.cmcEQ0+nonLegendary+notnamedBlack Lotus,Artifact.cmcEQ1,Artifact.cmcEQ2,Artifact.cmcEQ3 +SVar:AIPreference:SacCost$Artifact.token,Artifact.cmcEQ0+nonLegendary+!namedBlack Lotus,Artifact.cmcEQ1,Artifact.cmcEQ2,Artifact.cmcEQ3 DeckHas:Ability$Sacrifice DeckNeeds:Type$Artifact Oracle:Sacrifice an artifact: Atog gets +2/+2 until end of turn. diff --git a/forge-gui/res/cardsfolder/b/battlemages_bracers.txt b/forge-gui/res/cardsfolder/b/battlemages_bracers.txt index 548265d91b5..56455252b62 100644 --- a/forge-gui/res/cardsfolder/b/battlemages_bracers.txt +++ b/forge-gui/res/cardsfolder/b/battlemages_bracers.txt @@ -3,6 +3,6 @@ ManaCost:2 R Types:Artifact Equipment K:Equip:2 S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddKeyword$ Haste | Description$ Equipped creature has haste. -T:Mode$ AbilityCast | ValidCard$ Creature.EquippedBy+inRealZoneBattlefield | ValidSA$ SpellAbility.nonManaAbility | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigCopyAbility | TriggerDescription$ Whenever an ability of equipped creature is activated, if it isn't a mana ability, you may pay {1}. If you do, copy that ability. You may choose new targets for the copy. +T:Mode$ AbilityCast | ValidCard$ Creature.EquippedBy+inRealZoneBattlefield | ValidSA$ SpellAbility.!ManaAbility | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigCopyAbility | TriggerDescription$ Whenever an ability of equipped creature is activated, if it isn't a mana ability, you may pay {1}. If you do, copy that ability. You may choose new targets for the copy. SVar:TrigCopyAbility:AB$ CopySpellAbility | Cost$ 1 | Defined$ TriggeredSpellAbility | MayChooseTarget$ True Oracle:Equipped creature has haste.\nWhenever an ability of equipped creature is activated, if it isn't a mana ability, you may pay {1}. If you do, copy that ability. You may choose new targets for the copy.\nEquip {2} diff --git a/forge-gui/res/cardsfolder/b/blade_of_shared_souls.txt b/forge-gui/res/cardsfolder/b/blade_of_shared_souls.txt index 83370e5ef21..7c380c8dd34 100644 --- a/forge-gui/res/cardsfolder/b/blade_of_shared_souls.txt +++ b/forge-gui/res/cardsfolder/b/blade_of_shared_souls.txt @@ -3,7 +3,7 @@ ManaCost:2 U Types:Artifact Equipment K:For Mirrodin T:Mode$ Attached | ValidSource$ Card.Self | TriggerZones$ Battlefield | ValidTarget$ Creature | Execute$ TrigCopy | TriggerDescription$ Whenever CARDNAME becomes attached to a creature, for as long as CARDNAME remains attached to it, you may have that creature become a copy of another target creature you control. -SVar:TrigCopy:DB$ Clone | ValidTgts$ Creature.YouCtrl+NotTriggeredTarget | Optional$ True | TgtPrompt$ Choose another target creature you control | CloneTarget$ TriggeredTargetLKICopy | Duration$ UntilUnattached +SVar:TrigCopy:DB$ Clone | ValidTgts$ Creature.YouCtrl+!TriggeredTarget | Optional$ True | TgtPrompt$ Choose another target creature you control | CloneTarget$ TriggeredTargetLKICopy | Duration$ UntilUnattached K:Equip:2 DeckHas:Ability$Token & Type$Rebel & Color$Red Oracle:For Mirrodin! (When this Equipment enters, create a 2/2 red Rebel creature token, then attach this to it.)\nWhenever Blade of Shared Souls becomes attached to a creature, for as long as Blade of Shared Souls remains attached to it, you may have that creature become a copy of another target creature you control.\nEquip {2} diff --git a/forge-gui/res/cardsfolder/b/bound_in_gold.txt b/forge-gui/res/cardsfolder/b/bound_in_gold.txt index 468a68290ef..7f6a7eb0dd9 100644 --- a/forge-gui/res/cardsfolder/b/bound_in_gold.txt +++ b/forge-gui/res/cardsfolder/b/bound_in_gold.txt @@ -3,5 +3,5 @@ ManaCost:2 W Types:Enchantment Aura K:Enchant:Permanent SVar:AttachAILogic:Curse -S:Mode$ CantAttack,CantBlock,CantCrew,CantBeActivated | ValidCard$ Card.EnchantedBy | ValidSA$ Activated.nonManaAbility | Description$ Enchanted permanent can't attack, block, or crew Vehicles, and its activated abilities can't be activated unless they're mana abilities. +S:Mode$ CantAttack,CantBlock,CantCrew,CantBeActivated | ValidCard$ Card.EnchantedBy | ValidSA$ Activated.!ManaAbility | Description$ Enchanted permanent can't attack, block, or crew Vehicles, and its activated abilities can't be activated unless they're mana abilities. Oracle:Enchant permanent\nEnchanted permanent can't attack, block, or crew Vehicles, and its activated abilities can't be activated unless they're mana abilities. diff --git a/forge-gui/res/cardsfolder/b/burning_rune_demon.txt b/forge-gui/res/cardsfolder/b/burning_rune_demon.txt index 0cef5dcc70a..8520c561a04 100644 --- a/forge-gui/res/cardsfolder/b/burning_rune_demon.txt +++ b/forge-gui/res/cardsfolder/b/burning_rune_demon.txt @@ -4,7 +4,7 @@ Types:Creature Demon Berserker PT:6/6 K:Flying T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for exactly two cards not named Burning-Rune Demon that have different names. If you do, reveal those cards. An opponent chooses one of them. Put the chosen card into your hand and the other into your graveyard, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Card.notnamedBurning-Rune Demon | ChangeNum$ 2 | DifferentNames$ True | RememberChanged$ True | Reveal$ True | Shuffle$ False | AILogic$ Intuition | SubAbility$ DBChoosePlayer +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Card.!namedBurning-Rune Demon | ChangeNum$ 2 | DifferentNames$ True | RememberChanged$ True | Reveal$ True | Shuffle$ False | AILogic$ Intuition | SubAbility$ DBChoosePlayer SVar:DBChoosePlayer:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | ChoiceTitle$ Choose an opponent | SubAbility$ DBChangeZone1 SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.IsRemembered | Chooser$ ChosenPlayer | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to be put into the hand of CARDNAME's controller | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None SVar:DBChangeZone2:DB$ ChangeZoneAll | Origin$ Library | Destination$ Graveyard | ChangeType$ Card.IsRemembered | Shuffle$ True | StackDescription$ None | SubAbility$ DBCleanup diff --git a/forge-gui/res/cardsfolder/b/burning_tree_shaman.txt b/forge-gui/res/cardsfolder/b/burning_tree_shaman.txt index 3643336923d..df3170e5738 100644 --- a/forge-gui/res/cardsfolder/b/burning_tree_shaman.txt +++ b/forge-gui/res/cardsfolder/b/burning_tree_shaman.txt @@ -2,6 +2,6 @@ Name:Burning-Tree Shaman ManaCost:1 R G Types:Creature Centaur Shaman PT:3/4 -T:Mode$ AbilityCast | ValidActivatingPlayer$ Player | ValidSA$ SpellAbility.nonManaAbility | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever a player activates an ability that isn't a mana ability, CARDNAME deals 1 damage to that player. +T:Mode$ AbilityCast | ValidActivatingPlayer$ Player | ValidSA$ SpellAbility.!ManaAbility | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever a player activates an ability that isn't a mana ability, CARDNAME deals 1 damage to that player. SVar:TrigDmg:DB$ DealDamage | NumDmg$ 1 | Defined$ TriggeredActivator Oracle:Whenever a player activates an ability that isn't a mana ability, Burning-Tree Shaman deals 1 damage to that player. diff --git a/forge-gui/res/cardsfolder/c/caged_sun.txt b/forge-gui/res/cardsfolder/c/caged_sun.txt index 49199b27327..40c41f94581 100644 --- a/forge-gui/res/cardsfolder/c/caged_sun.txt +++ b/forge-gui/res/cardsfolder/c/caged_sun.txt @@ -5,6 +5,6 @@ K:ETBReplacement:Other:ChooseColor SVar:ChooseColor:DB$ ChooseColor | Defined$ You | AILogic$ MostProminentInComputerDeck | SpellDescription$ As CARDNAME enters, choose a color. S:Mode$ Continuous | Affected$ Creature.ChosenColor+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Creatures you control of the chosen color get +1/+1. T:Mode$ ManaAdded | ValidSource$ Land | ValidSA$ SpellAbility.ManaAbility | Produced$ ChosenColor | Player$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever a land's ability causes you to add one or more mana of the chosen color, add an additional one mana of that color. -T:Mode$ ManaAdded | ValidSource$ Land | ValidSA$ SpellAbility.nonManaAbility | Produced$ ChosenColor | Player$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Secondary$ True | TriggerDescription$ Whenever a land's ability causes you to add one or more mana of the chosen color, add an additional one mana of that color. +T:Mode$ ManaAdded | ValidSource$ Land | ValidSA$ SpellAbility.!ManaAbility | Produced$ ChosenColor | Player$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Secondary$ True | TriggerDescription$ Whenever a land's ability causes you to add one or more mana of the chosen color, add an additional one mana of that color. SVar:TrigMana:DB$ Mana | Produced$ Chosen | Amount$ 1 Oracle:As Caged Sun enters, choose a color.\nCreatures you control of the chosen color get +1/+1.\nWhenever a land's ability causes you to add one or more mana of the chosen color, add an additional one mana of that color. diff --git a/forge-gui/res/cardsfolder/c/clever_conjurer.txt b/forge-gui/res/cardsfolder/c/clever_conjurer.txt index 6b1d34d72c8..72bb957603d 100644 --- a/forge-gui/res/cardsfolder/c/clever_conjurer.txt +++ b/forge-gui/res/cardsfolder/c/clever_conjurer.txt @@ -2,5 +2,5 @@ Name:Clever Conjurer ManaCost:2 U Types:Creature Gnome Wizard PT:2/3 -A:AB$ Untap | Cost$ T | ValidTgts$ Permanent.Other+notnamedClever Conjurer | TgtPrompt$ Select another target permanent not named Clever Conjurer | SorcerySpeed$ True | PrecostDesc$ Mage Hand — | SpellDescription$ Untap target permanent not named Clever Conjurer. Activate only as a sorcery. +A:AB$ Untap | Cost$ T | ValidTgts$ Permanent.Other+!namedClever Conjurer | TgtPrompt$ Select another target permanent not named Clever Conjurer | SorcerySpeed$ True | PrecostDesc$ Mage Hand — | SpellDescription$ Untap target permanent not named Clever Conjurer. Activate only as a sorcery. Oracle:Mage Hand — {T}: Untap target permanent not named Clever Conjurer. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/c/colfenor_the_last_yew.txt b/forge-gui/res/cardsfolder/c/colfenor_the_last_yew.txt index f3ffdab8922..ed65290a80e 100644 --- a/forge-gui/res/cardsfolder/c/colfenor_the_last_yew.txt +++ b/forge-gui/res/cardsfolder/c/colfenor_the_last_yew.txt @@ -5,7 +5,7 @@ PT:3/7 K:Vigilance K:Reach T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self,Creature.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigChange | TriggerDescription$ Whenever CARDNAME or another creature you control dies, return up to one other target creature card with lesser toughness from your graveyard to your hand. -SVar:TrigChange:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 1 | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Creature.Other+NotTriggeredNewCard+toughnessLTX+YouOwn | TgtPrompt$ Select up to one other target creature card with lesser toughness from your graveyard to return to your hand +SVar:TrigChange:DB$ ChangeZone | TargetMin$ 0 | TargetMax$ 1 | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Creature.Other+!TriggeredNewCard+toughnessLTX+YouOwn | TgtPrompt$ Select up to one other target creature card with lesser toughness from your graveyard to return to your hand SVar:X:TriggeredCard$CardToughness DeckHas:Ability$Graveyard Oracle:Vigilance, reach\nWhenever Colfenor, the Last Yew or another creature you control dies, return up to one other target creature card with lesser toughness from your graveyard to your hand. diff --git a/forge-gui/res/cardsfolder/c/crackdown_construct.txt b/forge-gui/res/cardsfolder/c/crackdown_construct.txt index bc4abab45dd..e57f5824825 100644 --- a/forge-gui/res/cardsfolder/c/crackdown_construct.txt +++ b/forge-gui/res/cardsfolder/c/crackdown_construct.txt @@ -2,6 +2,6 @@ Name:Crackdown Construct ManaCost:4 Types:Artifact Creature Construct PT:2/2 -T:Mode$ AbilityCast | ValidCard$ Artifact.inZoneBattlefield,Creature.inZoneBattlefield | ValidSA$ SpellAbility.nonManaAbility | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever you activate an ability of an artifact or creature that isn't a mana ability, CARDNAME gets +1/+1 until end of turn. +T:Mode$ AbilityCast | ValidCard$ Artifact.inZoneBattlefield,Creature.inZoneBattlefield | ValidSA$ SpellAbility.!ManaAbility | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever you activate an ability of an artifact or creature that isn't a mana ability, CARDNAME gets +1/+1 until end of turn. SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ +1 | NumDef$ +1 Oracle:Whenever you activate an ability of an artifact or creature that isn't a mana ability, Crackdown Construct gets +1/+1 until end of turn. diff --git a/forge-gui/res/cardsfolder/c/cutthroat_centurion.txt b/forge-gui/res/cardsfolder/c/cutthroat_centurion.txt index ab918836983..1f8ef783805 100644 --- a/forge-gui/res/cardsfolder/c/cutthroat_centurion.txt +++ b/forge-gui/res/cardsfolder/c/cutthroat_centurion.txt @@ -3,6 +3,6 @@ ManaCost:2 B Types:Artifact Creature Phyrexian Warrior PT:2/2 A:AB$ Pump | Cost$ Sac<1/Creature.Other;Artifact.Other/another creature or artifact> | Defined$ Self | NumDef$ +2 | NumAtt$ +2 | AILogic$ Aristocrat | ActivationLimit$ 1 | SpellDescription$ CARDNAME gets +2/+2 until end of turn. Activate only once each turn. -SVar:AIPreference:SacCost$Artifact.token,Creature.token,Artifact.cmcEQ0+nonLegendary+notnamedBlack Lotus,Artifact.cmcEQ1,Artifact.cmcEQ2,Artifact.cmcEQ3,Creature.cmcLE3 +SVar:AIPreference:SacCost$Artifact.token,Creature.token,Artifact.cmcEQ0+nonLegendary+!namedBlack Lotus,Artifact.cmcEQ1,Artifact.cmcEQ2,Artifact.cmcEQ3,Creature.cmcLE3 DeckHas:Ability$Sacrifice Oracle:Sacrifice another artifact or creature: Cutthroat Centurion gets +2/+2 until end of turn. Activate only once each turn. diff --git a/forge-gui/res/cardsfolder/d/damping_matrix.txt b/forge-gui/res/cardsfolder/d/damping_matrix.txt index 6ecddcea24f..a45be1a203a 100644 --- a/forge-gui/res/cardsfolder/d/damping_matrix.txt +++ b/forge-gui/res/cardsfolder/d/damping_matrix.txt @@ -1,7 +1,7 @@ Name:Damping Matrix ManaCost:3 Types:Artifact -S:Mode$ CantBeActivated | ValidCard$ Artifact,Creature | ValidSA$ Activated.nonManaAbility | AffectedZone$ Battlefield | Description$ Activated abilities of artifacts and creatures can't be activated unless they're mana abilities. +S:Mode$ CantBeActivated | ValidCard$ Artifact,Creature | ValidSA$ Activated.!ManaAbility | AffectedZone$ Battlefield | Description$ Activated abilities of artifacts and creatures can't be activated unless they're mana abilities. SVar:NonStackingEffect:True AI:RemoveDeck:Random Oracle:Activated abilities of artifacts and creatures can't be activated unless they're mana abilities. 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 1477bb7f2b7..4fa2167fea8 100644 --- a/forge-gui/res/cardsfolder/d/dance_of_the_dead.txt +++ b/forge-gui/res/cardsfolder/d/dance_of_the_dead.txt @@ -3,7 +3,7 @@ ManaCost:1 B Types:Enchantment Aura K:Enchant:Creature.inZoneGraveyard:creature card in a graveyard SVar:AttachAILogic:Reanimate -SVar:AttachAITgts:Creature.notnamedWorldgorger Dragon +SVar:AttachAITgts:Creature.!namedWorldgorger Dragon T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | IsPresent$ Card.StrictlySelf | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME enters, 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 | Keywords$ Enchant:Creature.IsRemembered:creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant:Creature.inZoneGraveyard:creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach diff --git a/forge-gui/res/cardsfolder/d/detention_sphere.txt b/forge-gui/res/cardsfolder/d/detention_sphere.txt index b50d472dcc0..eb0667d23b6 100644 --- a/forge-gui/res/cardsfolder/d/detention_sphere.txt +++ b/forge-gui/res/cardsfolder/d/detention_sphere.txt @@ -2,7 +2,7 @@ Name:Detention Sphere ManaCost:1 W U Types:Enchantment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | OptionalDecider$ You | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters, you may exile target nonland permanent not named Detention Sphere and all other permanents with the same name as that permanent. -SVar:TrigExile:DB$ Pump | ValidTgts$ Permanent.nonLand+notnamedDetention Sphere | IsCurse$ True | TgtPrompt$ Choose target nonland permanent not named Detention Sphere | SubAbility$ DBChangeZoneAll +SVar:TrigExile:DB$ Pump | ValidTgts$ Permanent.nonLand+!namedDetention Sphere | IsCurse$ True | TgtPrompt$ Choose target nonland permanent not named Detention Sphere | SubAbility$ DBChangeZoneAll SVar:DBChangeZoneAll:DB$ ChangeZoneAll | ChangeType$ Targeted.Self,Targeted.sameName | Origin$ Battlefield | Destination$ Exile T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME leaves the battlefield, return the exiled cards to the battlefield under their owner's control. SVar:TrigReturn:DB$ ChangeZoneAll | ChangeType$ Card.ExiledWithSource | Origin$ Exile | Destination$ Battlefield diff --git a/forge-gui/res/cardsfolder/d/dhalsim_pliable_pacifist.txt b/forge-gui/res/cardsfolder/d/dhalsim_pliable_pacifist.txt index 8799de32013..98403677176 100644 --- a/forge-gui/res/cardsfolder/d/dhalsim_pliable_pacifist.txt +++ b/forge-gui/res/cardsfolder/d/dhalsim_pliable_pacifist.txt @@ -4,7 +4,7 @@ ManaCost:2 G W Types:Legendary Creature Human Monk PT:1/3 K:Reach -S:Mode$ Continuous | Affected$ Card.Self+notattacking | AddKeyword$ Hexproof | Description$ Teleport — CARDNAME has hexproof unless he's attacking. +S:Mode$ Continuous | Affected$ Card.Self+!attacking | AddKeyword$ Hexproof | 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 diff --git a/forge-gui/res/cardsfolder/d/disruptor_flute.txt b/forge-gui/res/cardsfolder/d/disruptor_flute.txt index 27c819e214b..9891e5e8d69 100644 --- a/forge-gui/res/cardsfolder/d/disruptor_flute.txt +++ b/forge-gui/res/cardsfolder/d/disruptor_flute.txt @@ -5,6 +5,6 @@ K:Flash K:ETBReplacement:Other:DBNameCard SVar:DBNameCard:DB$ NameCard | Defined$ You | SpellDescription$ As CARDNAME enters, choose a card name. S:Mode$ RaiseCost | EffectZone$ Battlefield | ValidCard$ Card.NamedCard | Type$ Spell | Activator$ Player | Amount$ 3 | Description$ Spells with the chosen name cost 3 more to cast. -S:Mode$ CantBeActivated | ValidCard$ Card.NamedCard | ValidSA$ Activated.nonManaAbility | Description$ Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. +S:Mode$ CantBeActivated | ValidCard$ Card.NamedCard | ValidSA$ Activated.!ManaAbility | Description$ Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. AI:RemoveDeck:Random Oracle:Flash\nAs Disruptor Flute enters, choose a card name.\nSpells with the chosen name cost 3 more to cast.\nActivated abilities of sources with the chosen name can't be activated unless they're mana abilities. diff --git a/forge-gui/res/cardsfolder/d/domineering_will.txt b/forge-gui/res/cardsfolder/d/domineering_will.txt index 1a5649f406c..d35ee6879a0 100644 --- a/forge-gui/res/cardsfolder/d/domineering_will.txt +++ b/forge-gui/res/cardsfolder/d/domineering_will.txt @@ -2,7 +2,7 @@ Name:Domineering Will ManaCost:3 U Types:Instant A:SP$ Pump | ValidTgts$ Player | StackDescription$ None | SubAbility$ DBGainControl | SpellDescription$ Target player gains control of up to three target nonattacking creatures until end of turn. Untap those creatures. They block this turn if able. -SVar:DBGainControl:DB$ GainControl | ValidTgts$ Creature.notattacking | TgtPrompt$ Select up to three target nonattacking creatures | LoseControl$ EOT | TargetMin$ 0 | TargetMax$ 3 | NewController$ ParentTarget | Untap$ True | SubAbility$ DBEffect +SVar:DBGainControl:DB$ GainControl | ValidTgts$ Creature.!attacking | TgtPrompt$ Select up to three target nonattacking creatures | LoseControl$ EOT | TargetMin$ 0 | TargetMax$ 3 | NewController$ ParentTarget | Untap$ True | SubAbility$ DBEffect SVar:DBEffect:DB$ Effect | RememberObjects$ Targeted | ForgetOnMoved$ Battlefield | StaticAbilities$ MustBlock | SpellDescription$ They block this turn if able. SVar:MustBlock:Mode$ MustBlock | ValidCreature$ Card.IsRemembered | Description$ Those creatures block this turn if able. AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/d/dutiful_replicator.txt b/forge-gui/res/cardsfolder/d/dutiful_replicator.txt index def1c64cbae..c99c1f66045 100644 --- a/forge-gui/res/cardsfolder/d/dutiful_replicator.txt +++ b/forge-gui/res/cardsfolder/d/dutiful_replicator.txt @@ -4,7 +4,7 @@ Types:Artifact Creature Assembly-Worker PT:3/2 T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigImmediate | TriggerDescription$ When CARDNAME enters, you may pay {1}. When you do, create a token that's a copy of target token you control not named Dutiful Replicator. SVar:TrigImmediate:AB$ ImmediateTrigger | Cost$ 1 | Execute$ TrigClone | TriggerDescription$ When you do, create a token that's a copy of target token you control not named Dutiful Replicator. -SVar:TrigClone:DB$ CopyPermanent | ValidTgts$ Permanent.token+YouCtrl+notnamedDutiful Replicator | TgtPrompt$ Select target token you control not named Dutiful Replicator +SVar:TrigClone:DB$ CopyPermanent | ValidTgts$ Permanent.token+YouCtrl+!namedDutiful Replicator | TgtPrompt$ Select target token you control not named Dutiful Replicator DeckHas:Ability$Token DeckNeeds:Ability$Token Oracle:When Dutiful Replicator enters, you may pay {1}. When you do, create a token that's a copy of target token you control not named Dutiful Replicator. diff --git a/forge-gui/res/cardsfolder/d/dynaheir_invoker_adept.txt b/forge-gui/res/cardsfolder/d/dynaheir_invoker_adept.txt index 6f25676575d..bbda52be4d7 100644 --- a/forge-gui/res/cardsfolder/d/dynaheir_invoker_adept.txt +++ b/forge-gui/res/cardsfolder/d/dynaheir_invoker_adept.txt @@ -5,7 +5,7 @@ PT:4/4 K:Haste S:Mode$ ActivateAbilityAsIfHaste | ValidCard$ Creature.Other+YouCtrl+inZoneBattlefield | Description$ You may activate abilities of other creatures you control as though those creatures had haste. A:AB$ Effect | Cost$ T | Triggers$ ActivateTrig | SpellDescription$ When you next activate an ability that isn't a mana ability this turn by spending four or more mana to activate it, copy that ability. You may choose new targets for the copy. -SVar:ActivateTrig:Mode$ AbilityCast | ValidSA$ SpellAbility.nonManaAbility+ManaSpent GE4 | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCopy | OneOff$ True | TriggerDescription$ When you next activate an ability that isn't a mana ability this turn by spending four or more mana to activate it, copy that ability. You may choose new targets for the copy. +SVar:ActivateTrig:Mode$ AbilityCast | ValidSA$ SpellAbility.!ManaAbility+ManaSpent GE4 | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCopy | OneOff$ True | TriggerDescription$ When you next activate an ability that isn't a mana ability this turn by spending four or more mana to activate it, copy that ability. You may choose new targets for the copy. SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True SVar:BuffedBy:Creature.hasAbility Activated Oracle:Haste\nYou may activate abilities of other creatures you control as though those creatures had haste.\n{T}: When you next activate an ability that isn't a mana ability this turn by spending four or more mana to activate it, copy that ability. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/e/ebondeath_dracolich.txt b/forge-gui/res/cardsfolder/e/ebondeath_dracolich.txt index c69ad0b624e..9b5da7c7a22 100644 --- a/forge-gui/res/cardsfolder/e/ebondeath_dracolich.txt +++ b/forge-gui/res/cardsfolder/e/ebondeath_dracolich.txt @@ -7,7 +7,7 @@ K:Flying R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Graveyard | AffectedZone$ Graveyard | MayPlay$ True | CheckSVar$ X | SVarCompare$ GE1 | Description$ You may cast CARDNAME from your graveyard if a creature not named Ebondeath, Dracolich died this turn. -SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Creature.notnamedEbondeath; Dracolich +SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Creature.!namedEbondeath; Dracolich SVar:SacMe:3 SVar:DiscardMe:3 Oracle:Flash\nFlying\nEbondeath, Dracolich enters tapped.\nYou may cast Ebondeath, Dracolich from your graveyard if a creature not named Ebondeath, Dracolich died this turn. diff --git a/forge-gui/res/cardsfolder/e/eye_of_singularity.txt b/forge-gui/res/cardsfolder/e/eye_of_singularity.txt index 212994fcf74..d568f846d66 100644 --- a/forge-gui/res/cardsfolder/e/eye_of_singularity.txt +++ b/forge-gui/res/cardsfolder/e/eye_of_singularity.txt @@ -7,6 +7,6 @@ SVar:DBRem:DB$ Pump | ImprintCards$ Valid Permanent.sharesNameWith Remembered+Is 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, destroy all other permanents with that name. They can't be regenerated. -SVar:TrigDestroy:DB$ DestroyAll | ValidCards$ Permanent.NotTriggeredCard+sharesNameWith TriggeredCard | NoRegen$ True +SVar:TrigDestroy:DB$ DestroyAll | ValidCards$ Permanent.!TriggeredCard+sharesNameWith TriggeredCard | NoRegen$ True AI:RemoveDeck:All Oracle:When Eye of Singularity enters, 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, destroy all other permanents with that name. They can't be regenerated. diff --git a/forge-gui/res/cardsfolder/f/faiths_fetters.txt b/forge-gui/res/cardsfolder/f/faiths_fetters.txt index 0c286bdecda..9cf396d4677 100644 --- a/forge-gui/res/cardsfolder/f/faiths_fetters.txt +++ b/forge-gui/res/cardsfolder/f/faiths_fetters.txt @@ -6,6 +6,6 @@ SVar:AttachAITgts:Creature SVar:AttachAILogic:Curse T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigGainLife | TriggerDescription$ When CARDNAME enters, you gain 4 life. SVar:TrigGainLife:DB$ GainLife | LifeAmount$ 4 -S:Mode$ CantAttack,CantBlock,CantBeActivated | ValidCard$ Card.EnchantedBy | ValidSA$ Activated.nonManaAbility | Description$ Enchanted permanent can't attack or block, and its activated abilities can't be activated unless they're mana abilities. +S:Mode$ CantAttack,CantBlock,CantBeActivated | ValidCard$ Card.EnchantedBy | ValidSA$ Activated.!ManaAbility | Description$ Enchanted permanent can't attack or block, and its activated abilities can't be activated unless they're mana abilities. DeckHas:Ability$LifeGain Oracle:Enchant permanent\nWhen Faith's Fetters enters, you gain 4 life.\nEnchanted permanent can't attack or block, and its activated abilities can't be activated unless they're mana abilities. diff --git a/forge-gui/res/cardsfolder/f/farrels_mantle.txt b/forge-gui/res/cardsfolder/f/farrels_mantle.txt index ceb3ee09044..3b5bb7e3d17 100644 --- a/forge-gui/res/cardsfolder/f/farrels_mantle.txt +++ b/forge-gui/res/cardsfolder/f/farrels_mantle.txt @@ -4,7 +4,7 @@ Types:Enchantment Aura K:Enchant:Creature SVar:AttachAILogic:Pump T:Mode$ AttackerUnblocked | ValidCard$ Creature.EnchantedBy | Execute$ FarrelDmg | OptionalDecider$ EnchantedController | TriggerDescription$ Whenever enchanted creature attacks and isn't blocked, its controller may have it deal damage equal to its power plus 2 to another target creature. If that player does, the attacking creature assigns no combat damage this turn. -SVar:FarrelDmg:DB$ DealDamage | ValidTgts$ Creature.NotTriggeredAttacker | TgtPrompt$ Select another target creature | NumDmg$ FarrelX | DamageSource$ TriggeredAttackerLKICopy | SubAbility$ DBNoCombatDamage +SVar:FarrelDmg:DB$ DealDamage | ValidTgts$ Creature.!TriggeredAttacker | TgtPrompt$ Select another target creature | NumDmg$ FarrelX | DamageSource$ TriggeredAttackerLKICopy | SubAbility$ DBNoCombatDamage SVar:DBNoCombatDamage:DB$ Effect | RememberObjects$ TriggeredAttackerLKICopy | StaticAbilities$ SNoCombatDamage | ForgetOnMoved$ Battlefield SVar:SNoCombatDamage:Mode$ AssignNoCombatDamage | ValidCard$ Card.IsRemembered | Description$ Remembered creature assigns no combat damage this turn. SVar:FarrelX:TriggeredAttacker$CardPower/Plus.2 diff --git a/forge-gui/res/cardsfolder/f/flamescroll_celebrant_revel_in_silence.txt b/forge-gui/res/cardsfolder/f/flamescroll_celebrant_revel_in_silence.txt index 1ff11b2e485..bd2c8391d85 100644 --- a/forge-gui/res/cardsfolder/f/flamescroll_celebrant_revel_in_silence.txt +++ b/forge-gui/res/cardsfolder/f/flamescroll_celebrant_revel_in_silence.txt @@ -2,7 +2,7 @@ Name:Flamescroll Celebrant ManaCost:1 R Types:Creature Human Shaman PT:2/1 -T:Mode$ AbilityCast | ValidActivatingPlayer$ Opponent | ValidSA$ SpellAbility.nonManaAbility | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever an opponent activates an ability that isn't a mana ability, CARDNAME deals 1 damage to that player. +T:Mode$ AbilityCast | ValidActivatingPlayer$ Opponent | ValidSA$ SpellAbility.!ManaAbility | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever an opponent activates an ability that isn't a mana ability, CARDNAME deals 1 damage to that player. SVar:TrigDmg:DB$ DealDamage | NumDmg$ 1 | Defined$ TriggeredActivator A:AB$ Pump | Cost$ 1 R | Defined$ Self | NumAtt$ +2 | SpellDescription$ CARDNAME gets +2/+0 until end of turn. AlternateMode:Modal diff --git a/forge-gui/res/cardsfolder/g/gaeas_liege.txt b/forge-gui/res/cardsfolder/g/gaeas_liege.txt index a39be798793..74135634aba 100644 --- a/forge-gui/res/cardsfolder/g/gaeas_liege.txt +++ b/forge-gui/res/cardsfolder/g/gaeas_liege.txt @@ -2,7 +2,7 @@ Name:Gaea's Liege ManaCost:3 G G G Types:Creature Avatar PT:*/* -S:Mode$ Continuous | CharacteristicDefining$ True | IsPresent$ Card.Self+notattacking | SetPower$ X | SetToughness$ X | Description$ As long as CARDNAME isn't attacking, its power and toughness are each equal to the number of Forests you control. As long as CARDNAME is attacking, its power and toughness are each equal to the number of Forests defending player controls. +S:Mode$ Continuous | CharacteristicDefining$ True | IsPresent$ Card.Self+!attacking | SetPower$ X | SetToughness$ X | Description$ As long as CARDNAME isn't attacking, its power and toughness are each equal to the number of Forests you control. As long as CARDNAME is attacking, its power and toughness are each equal to the number of Forests defending player controls. SVar:X:Count$Valid Forest.YouCtrl S:Mode$ Continuous | CharacteristicDefining$ True | IsPresent$ Card.Self+attacking | SetPower$ Y | SetToughness$ Y SVar:Y:Count$Valid Forest.DefenderCtrl diff --git a/forge-gui/res/cardsfolder/g/geralf_the_fleshwright.txt b/forge-gui/res/cardsfolder/g/geralf_the_fleshwright.txt index 6537785e945..8776f598cf7 100644 --- a/forge-gui/res/cardsfolder/g/geralf_the_fleshwright.txt +++ b/forge-gui/res/cardsfolder/g/geralf_the_fleshwright.txt @@ -6,7 +6,7 @@ T:Mode$ SpellCast | ValidCard$ Card.YouCtrl | ValidActivatingPlayer$ You | Trigg SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ ub_2_2_zombie_rogue | TokenOwner$ You T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | TriggerZones$ Battlefield | ValidCard$ Zombie.YouCtrl | Execute$ DBPutCounter | TriggerDescription$ Whenever a Zombie you control enters, put a +1/+1 counter on it for each other Zombie that entered the battlefield under your control this turn. SVar:DBPutCounter:DB$ PutCounter | Defined$ TriggeredCard | CounterNum$ X | CounterType$ P1P1 -SVar:X:Count$ThisTurnEntered_Battlefield_Zombie.YouCtrl+NotTriggeredCard +SVar:X:Count$ThisTurnEntered_Battlefield_Zombie.YouCtrl+!TriggeredCard DeckHas:Ability$Token|Counters & Type$Zombie|Rogue & Color$Black DeckHints:Type$Zombie Oracle:Whenever you cast a spell during your turn other than your first spell that turn, create a 2/2 blue and black Zombie Rogue creature token.\nWhenever a Zombie you control enters, put a +1/+1 counter on it for each other Zombie that entered the battlefield under your control this turn. diff --git a/forge-gui/res/cardsfolder/g/grip_of_chaos.txt b/forge-gui/res/cardsfolder/g/grip_of_chaos.txt index ed2aa2199f1..a0ad6cc5d69 100644 --- a/forge-gui/res/cardsfolder/g/grip_of_chaos.txt +++ b/forge-gui/res/cardsfolder/g/grip_of_chaos.txt @@ -1,7 +1,7 @@ Name:Grip of Chaos ManaCost:4 R R Types:Enchantment -T:Mode$ SpellAbilityCast | ValidSA$ SpellAbility.nonManaAbility | IsSingleTarget$ True | TriggerZones$ Battlefield | Execute$ TrigChangeTarget | TriggerDescription$ Whenever a spell or ability is put onto the stack, if it has a single target, reselect its target at random. (Select from among all legal targets.) +T:Mode$ SpellAbilityCast | ValidSA$ SpellAbility.!ManaAbility | IsSingleTarget$ True | TriggerZones$ Battlefield | Execute$ TrigChangeTarget | TriggerDescription$ Whenever a spell or ability is put onto the stack, if it has a single target, reselect its target at random. (Select from among all legal targets.) T:Mode$ SpellAbilityCopy | IsSingleTarget$ True | TriggerZones$ Battlefield | Execute$ TrigChangeTarget | Secondary$ True | TriggerDescription$ Whenever a spell or ability is put onto the stack, if it has a single target, reselect its target at random. (Select from among all legal targets.) SVar:TrigChangeTarget:DB$ ChangeTargets | Defined$ TriggeredSpellAbility | RandomTarget$ True AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/g/guildsworn_prowler.txt b/forge-gui/res/cardsfolder/g/guildsworn_prowler.txt index 70060ab91d8..64884b3d96f 100644 --- a/forge-gui/res/cardsfolder/g/guildsworn_prowler.txt +++ b/forge-gui/res/cardsfolder/g/guildsworn_prowler.txt @@ -3,6 +3,6 @@ ManaCost:1 B Types:Creature Tiefling Rogue Assassin PT:2/1 K:Deathtouch -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+notblocking | Execute$ TrigDraw | TriggerDescription$ When CARDNAME dies, if it wasn't blocking, draw a card. +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+!blocking | Execute$ TrigDraw | TriggerDescription$ When CARDNAME dies, if it wasn't blocking, draw a card. SVar:TrigDraw:DB$ Draw Oracle:Deathtouch\nWhen Guildsworn Prowler dies, if it wasn't blocking, draw a card. diff --git a/forge-gui/res/cardsfolder/h/hand_to_hand.txt b/forge-gui/res/cardsfolder/h/hand_to_hand.txt index a72f994596c..9fca1411c01 100644 --- a/forge-gui/res/cardsfolder/h/hand_to_hand.txt +++ b/forge-gui/res/cardsfolder/h/hand_to_hand.txt @@ -2,7 +2,7 @@ Name:Hand to Hand ManaCost:2 R Types:Enchantment S:Mode$ CantBeCast | ValidCard$ Instant | Phases$ BeginCombat->EndCombat | Description$ During combat, players can't cast instant spells or activate abilities that aren't mana abilities. -S:Mode$ CantBeActivated | ValidCard$ Card | Phases$ BeginCombat->EndCombat | ValidSA$ Activated.nonManaAbility +S:Mode$ CantBeActivated | ValidCard$ Card | Phases$ BeginCombat->EndCombat | ValidSA$ Activated.!ManaAbility SVar:NonStackingEffect:True AI:RemoveDeck:Random Oracle:During combat, players can't cast instant spells or activate abilities that aren't mana abilities. diff --git a/forge-gui/res/cardsfolder/h/harsh_mentor.txt b/forge-gui/res/cardsfolder/h/harsh_mentor.txt index e0dbfa32f87..643cb4079df 100644 --- a/forge-gui/res/cardsfolder/h/harsh_mentor.txt +++ b/forge-gui/res/cardsfolder/h/harsh_mentor.txt @@ -2,6 +2,6 @@ Name:Harsh Mentor ManaCost:1 R Types:Creature Human Cleric PT:2/2 -T:Mode$ AbilityCast | ValidActivatingPlayer$ Opponent | ValidCard$ Artifact.inZoneBattlefield,Creature.inZoneBattlefield,Land.inZoneBattlefield | ValidSA$ SpellAbility.nonManaAbility | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever an opponent activates an ability of an artifact, creature, or land on the battlefield, if it isn't a mana ability, CARDNAME deals 2 damage to that player. +T:Mode$ AbilityCast | ValidActivatingPlayer$ Opponent | ValidCard$ Artifact.inZoneBattlefield,Creature.inZoneBattlefield,Land.inZoneBattlefield | ValidSA$ SpellAbility.!ManaAbility | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever an opponent activates an ability of an artifact, creature, or land on the battlefield, if it isn't a mana ability, CARDNAME deals 2 damage to that player. SVar:TrigDmg:DB$ DealDamage | NumDmg$ 2 | Defined$ TriggeredActivator Oracle:Whenever an opponent activates an ability of an artifact, creature, or land on the battlefield, if it isn't a mana ability, Harsh Mentor deals 2 damage to that player. diff --git a/forge-gui/res/cardsfolder/h/hollow_warrior.txt b/forge-gui/res/cardsfolder/h/hollow_warrior.txt index a199895c1f1..9328cf70a8b 100644 --- a/forge-gui/res/cardsfolder/h/hollow_warrior.txt +++ b/forge-gui/res/cardsfolder/h/hollow_warrior.txt @@ -2,7 +2,7 @@ Name:Hollow Warrior ManaCost:4 Types:Artifact Creature Golem Warrior PT:4/4 -S:Mode$ CantAttackUnless | ValidCard$ Card.Self | Cost$ tapXType<1/Creature.notattacking> | Description$ CARDNAME can't attack or block unless you tap an untapped creature you control not declared as an attacking or blocking creature this combat. (This cost is paid as attackers or blockers are declared.) -S:Mode$ CantBlockUnless | ValidCard$ Card.Self | Cost$ tapXType<1/Creature.notblocking> +S:Mode$ CantAttackUnless | ValidCard$ Card.Self | Cost$ tapXType<1/Creature.!attacking> | Description$ CARDNAME can't attack or block unless you tap an untapped creature you control not declared as an attacking or blocking creature this combat. (This cost is paid as attackers or blockers are declared.) +S:Mode$ CantBlockUnless | ValidCard$ Card.Self | Cost$ tapXType<1/Creature.!blocking> AI:RemoveDeck:Random Oracle:Hollow Warrior can't attack or block unless you tap an untapped creature you control not declared as an attacking or blocking creature this combat. (This cost is paid as attackers or blockers are declared.) diff --git a/forge-gui/res/cardsfolder/h/horned_kavu.txt b/forge-gui/res/cardsfolder/h/horned_kavu.txt index a806934093b..ca755dd82fb 100644 --- a/forge-gui/res/cardsfolder/h/horned_kavu.txt +++ b/forge-gui/res/cardsfolder/h/horned_kavu.txt @@ -4,5 +4,5 @@ Types:Creature Kavu PT:3/4 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters, return a red or green creature you control to its owner's hand. SVar:TrigChange:DB$ ChangeZone | Origin$ Battlefield | Destination$ Hand | Hidden$ True | Mandatory$ True | ChangeType$ Creature.Red+YouCtrl,Creature.Green+YouCtrl | AILogic$ NeverBounceItself -SVar:NeedsToPlay:Creature.Red+YouCtrl+cmcLE3+notnamedHorned Kavu,Creature.Green+YouCtrl+cmcLE3+notnamedHorned Kavu +SVar:NeedsToPlay:Creature.Red+YouCtrl+cmcLE3+!namedHorned Kavu,Creature.Green+YouCtrl+cmcLE3+!namedHorned Kavu Oracle:When Horned Kavu enters, return a red or green creature you control to its owner's hand. diff --git a/forge-gui/res/cardsfolder/i/illicit_masquerade.txt b/forge-gui/res/cardsfolder/i/illicit_masquerade.txt index af756efa2da..095b6666c95 100644 --- a/forge-gui/res/cardsfolder/i/illicit_masquerade.txt +++ b/forge-gui/res/cardsfolder/i/illicit_masquerade.txt @@ -6,7 +6,7 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S SVar:TrigPutCountersAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl | CounterType$ IMPOSTOR | CounterNum$ 1 T:Mode$ ChangesZone | ValidCard$ Creature.YouCtrl+counters_GE1_IMPOSTOR | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigExile | TriggerZones$ Battlefield | TriggerDescription$ Whenever a creature you control with an impostor counter on it dies, exile it. Return up to one other target creature card from your graveyard to the battlefield. SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | Defined$ TriggeredCard | SubAbility$ DBReturn -SVar:DBReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose up to one other target creature card in your graveyard | ValidTgts$ Creature.YouOwn+NotTriggeredCard | TargetMin$ 0 | TargetMax$ 1 +SVar:DBReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Choose up to one other target creature card in your graveyard | ValidTgts$ Creature.YouOwn+!TriggeredCard | TargetMin$ 0 | TargetMax$ 1 DeckHas:Ability$Counters|Graveyard DeckHints:Ability$Graveyard|Discard|Mill Oracle:Flash\nWhen Illicit Masquerade enters, put an impostor counter on each creature you control.\nWhenever a creature you control with an impostor counter on it dies, exile it. Return up to one other target creature card from your graveyard to the battlefield. diff --git a/forge-gui/res/cardsfolder/i/illusionists_bracers.txt b/forge-gui/res/cardsfolder/i/illusionists_bracers.txt index 8e57678e6fb..24eefef8a9e 100644 --- a/forge-gui/res/cardsfolder/i/illusionists_bracers.txt +++ b/forge-gui/res/cardsfolder/i/illusionists_bracers.txt @@ -2,6 +2,6 @@ Name:Illusionist's Bracers ManaCost:2 Types:Artifact Equipment K:Equip:3 -T:Mode$ AbilityCast | ValidCard$ Creature.EquippedBy+inRealZoneBattlefield | ValidSA$ SpellAbility.nonManaAbility | TriggerZones$ Battlefield | Execute$ TrigCopyAbility | OptionalDecider$ You | TriggerDescription$ Whenever an ability of equipped creature is activated, if it isn't a mana ability, copy that ability. You may choose new targets for the copy. +T:Mode$ AbilityCast | ValidCard$ Creature.EquippedBy+inRealZoneBattlefield | ValidSA$ SpellAbility.!ManaAbility | TriggerZones$ Battlefield | Execute$ TrigCopyAbility | OptionalDecider$ You | TriggerDescription$ Whenever an ability of equipped creature is activated, if it isn't a mana ability, copy that ability. You may choose new targets for the copy. SVar:TrigCopyAbility:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True Oracle:Whenever an ability of equipped creature is activated, if it isn't a mana ability, copy that ability. You may choose new targets for the copy.\nEquip {3} diff --git a/forge-gui/res/cardsfolder/i/immolation_shaman.txt b/forge-gui/res/cardsfolder/i/immolation_shaman.txt index b4e991f382f..16574414388 100644 --- a/forge-gui/res/cardsfolder/i/immolation_shaman.txt +++ b/forge-gui/res/cardsfolder/i/immolation_shaman.txt @@ -2,7 +2,7 @@ Name:Immolation Shaman ManaCost:1 R Types:Creature Lizard Shaman PT:1/3 -T:Mode$ AbilityCast | ValidActivatingPlayer$ Opponent | ValidCard$ Artifact.inZoneBattlefield,Creature.inZoneBattlefield,Land.inZoneBattlefield | ValidSA$ SpellAbility.nonManaAbility | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever an opponent activates an ability of an artifact, creature, or land that isn't a mana ability, CARDNAME deals 1 damage to that player. +T:Mode$ AbilityCast | ValidActivatingPlayer$ Opponent | ValidCard$ Artifact.inZoneBattlefield,Creature.inZoneBattlefield,Land.inZoneBattlefield | ValidSA$ SpellAbility.!ManaAbility | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever an opponent activates an ability of an artifact, creature, or land that isn't a mana ability, CARDNAME deals 1 damage to that player. SVar:TrigDmg:DB$ DealDamage | NumDmg$ 1 | Defined$ TriggeredActivator A:AB$ Pump | Cost$ 3 R R | Defined$ Self | NumAtt$ +3 | NumDef$ +3 | KW$ Menace | SpellDescription$ CARDNAME gets +3/+3 and gains menace until end of turn. Oracle:Whenever an opponent activates an ability of an artifact, creature, or land that isn't a mana ability, Immolation Shaman deals 1 damage to that player.\n{3}{R}{R}: Immolation Shaman gets +3/+3 and gains menace until end of turn. diff --git a/forge-gui/res/cardsfolder/i/imprison.txt b/forge-gui/res/cardsfolder/i/imprison.txt index e1fecafc969..4796490c95e 100644 --- a/forge-gui/res/cardsfolder/i/imprison.txt +++ b/forge-gui/res/cardsfolder/i/imprison.txt @@ -3,7 +3,7 @@ ManaCost:B Types:Enchantment Aura K:Enchant:Creature SVar:AttachAILogic:Curse -T:Mode$ AbilityCast | ValidCard$ Creature.EnchantedBy | ValidSA$ SpellAbility.nonManaAbility+hasTapCost | TriggerZones$ Battlefield | Execute$ TrigDestroy1 | TriggerDescription$ Whenever a player activates an ability of enchanted creature with {T} in its activation cost that isn't a mana ability, you may pay {1}. If you do, counter that ability. If you don't, destroy CARDNAME. +T:Mode$ AbilityCast | ValidCard$ Creature.EnchantedBy | ValidSA$ SpellAbility.!ManaAbility+hasTapCost | TriggerZones$ Battlefield | Execute$ TrigDestroy1 | TriggerDescription$ Whenever a player activates an ability of enchanted creature with {T} in its activation cost that isn't a mana ability, you may pay {1}. If you do, counter that ability. If you don't, destroy CARDNAME. SVar:TrigDestroy1:DB$ Destroy | Defined$ Self | UnlessCost$ 1 | UnlessPayer$ You | UnlessResolveSubs$ WhenPaid | SubAbility$ DBCounter SVar:DBCounter:DB$ Counter | Defined$ TriggeredSpellAbility T:Mode$ Attacks | ValidCard$ Card.EnchantedBy | TriggerZones$ Battlefield | Execute$ TrigDestroy2 | TriggerDescription$ Whenever enchanted creature attacks or blocks, you may pay {1}. If you do, tap the creature, remove it from combat, and creatures it was blocking that had become blocked by only that creature this combat become unblocked. If you don't, destroy CARDNAME. diff --git a/forge-gui/res/cardsfolder/i/intercessors_arrest.txt b/forge-gui/res/cardsfolder/i/intercessors_arrest.txt index a4c9f0207c7..68a9c929216 100644 --- a/forge-gui/res/cardsfolder/i/intercessors_arrest.txt +++ b/forge-gui/res/cardsfolder/i/intercessors_arrest.txt @@ -3,5 +3,5 @@ ManaCost:2 W Types:Enchantment Aura K:Enchant:Permanent SVar:AttachAILogic:Curse -S:Mode$ CantAttack,CantBlock,CantCrew,CantBeActivated | ValidCard$ Card.EnchantedBy | ValidSA$ Activated.nonManaAbility | Description$ Enchanted permanent can't attack, block, or crew Vehicles. Its activated abilities can't be activated unless they're mana abilities. +S:Mode$ CantAttack,CantBlock,CantCrew,CantBeActivated | ValidCard$ Card.EnchantedBy | ValidSA$ Activated.!ManaAbility | Description$ Enchanted permanent can't attack, block, or crew Vehicles. Its activated abilities can't be activated unless they're mana abilities. Oracle:Enchant permanent\nEnchanted permanent can't attack, block, or crew Vehicles. Its activated abilities can't be activated unless they're mana abilities. diff --git a/forge-gui/res/cardsfolder/k/karns_sylex.txt b/forge-gui/res/cardsfolder/k/karns_sylex.txt index a852ea06373..3d75b39feef 100644 --- a/forge-gui/res/cardsfolder/k/karns_sylex.txt +++ b/forge-gui/res/cardsfolder/k/karns_sylex.txt @@ -3,7 +3,7 @@ ManaCost:3 Types:Legendary Artifact R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True -S:Mode$ CantPayLife | ValidPlayer$ Player | ValidCause$ Spell,Activated.nonManaAbility | ForCost$ True | Description$ Players can't pay life to cast spells or activate abilities that aren't mana abilities. +S:Mode$ CantPayLife | ValidPlayer$ Player | ValidCause$ Spell,Activated.!ManaAbility | ForCost$ True | Description$ Players can't pay life to cast spells or activate abilities that aren't mana abilities. A:AB$ DestroyAll | Cost$ X T Exile<1/CARDNAME> | ValidCards$ Permanent.nonLand+cmcLEX | SorcerySpeed$ True | SpellDescription$ Destroy each nonland permanent with mana value X or less. Activate only as a sorcery. SVar:X:Count$xPaid Oracle:Karn's Sylex enters tapped.\nPlayers can't pay life to cast spells or to activate abilities that aren't mana abilities.\n{X}, {T}, Exile Karn's Sylex: Destroy each nonland permanent with mana value X or less. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/k/kurkesh_onakke_ancient.txt b/forge-gui/res/cardsfolder/k/kurkesh_onakke_ancient.txt index 355d05f4206..9a50c74e3c3 100644 --- a/forge-gui/res/cardsfolder/k/kurkesh_onakke_ancient.txt +++ b/forge-gui/res/cardsfolder/k/kurkesh_onakke_ancient.txt @@ -2,6 +2,6 @@ Name:Kurkesh, Onakke Ancient ManaCost:2 R R Types:Legendary Creature Ogre Spirit PT:4/3 -T:Mode$ AbilityCast | ValidCard$ Artifact | ValidSA$ SpellAbility.nonManaAbility | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCopyAbility | TriggerDescription$ Whenever you activate an ability of an artifact, if it isn't a mana ability, you may pay {R}. If you do, copy that ability. You may choose new targets for the copy. +T:Mode$ AbilityCast | ValidCard$ Artifact | ValidSA$ SpellAbility.!ManaAbility | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCopyAbility | TriggerDescription$ Whenever you activate an ability of an artifact, if it isn't a mana ability, you may pay {R}. If you do, copy that ability. You may choose new targets for the copy. SVar:TrigCopyAbility:AB$ CopySpellAbility | Cost$ R | Defined$ TriggeredSpellAbility | MayChooseTarget$ True Oracle:Whenever you activate an ability of an artifact, if it isn't a mana ability, you may pay {R}. If you do, copy that ability. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/k/kusari_gama.txt b/forge-gui/res/cardsfolder/k/kusari_gama.txt index 1234de9ebe1..f7c4d914371 100644 --- a/forge-gui/res/cardsfolder/k/kusari_gama.txt +++ b/forge-gui/res/cardsfolder/k/kusari_gama.txt @@ -5,6 +5,6 @@ K:Equip:3 S:Mode$ Continuous | Affected$ Card.EquippedBy | AddAbility$ GamaPump | Description$ Equipped creature has "{2}: This creature gets +1/+0 until end of turn." SVar:GamaPump:AB$ Pump | Cost$ 2 | Defined$ Self | NumAtt$ +1 | SpellDescription$ CARDNAME gets +1/+0 until end of turn. T:Mode$ DamageDone | ValidSource$ Card.EquippedBy | ValidTarget$ Creature.blocking | Execute$ GamaDamage | TriggerZones$ Battlefield | TriggerDescription$ Whenever equipped creature deals damage to a blocking creature, CARDNAME deals that much damage to each other creature defending player controls. -SVar:GamaDamage:DB$ DamageAll | NumDmg$ X | ValidCards$ Creature.NotTriggeredTarget+ControlledBy TriggeredTargetController +SVar:GamaDamage:DB$ DamageAll | NumDmg$ X | ValidCards$ Creature.!TriggeredTarget+ControlledBy TriggeredTargetController SVar:X:TriggerCount$DamageAmount Oracle:Equipped creature has "{2}: This creature gets +1/+0 until end of turn."\nWhenever equipped creature deals damage to a blocking creature, Kusari-Gama deals that much damage to each other creature defending player controls.\nEquip {3} ({3}: Attach to target creature you control. Equip only as a sorcery.) diff --git a/forge-gui/res/cardsfolder/m/martial_impetus.txt b/forge-gui/res/cardsfolder/m/martial_impetus.txt index f4838e951c7..351d24c6d1a 100644 --- a/forge-gui/res/cardsfolder/m/martial_impetus.txt +++ b/forge-gui/res/cardsfolder/m/martial_impetus.txt @@ -5,5 +5,5 @@ K:Enchant:Creature SVar:AttachAILogic:Pump S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 1 | AddToughness$ 1 | Goad$ True | Description$ Enchanted creature gets +1/+1 and is goaded. (It attacks each combat if able and attacks a player other than you if able.) T:Mode$ Attacks | ValidCard$ Card.AttachedBy | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever enchanted creature attacks, each other creature that's attacking one of your opponents gets +1/+1 until end of turn. -SVar:TrigPump:DB$ PumpAll | ValidCards$ Creature.NotTriggeredAttacker+attacking Opponent | NumAtt$ +1 | NumDef$ +1 +SVar:TrigPump:DB$ PumpAll | ValidCards$ Creature.!TriggeredAttacker+attacking Opponent | NumAtt$ +1 | NumDef$ +1 Oracle:Enchant creature\nEnchanted creature gets +1/+1 and is goaded. (It attacks each combat if able and attacks a player other than you if able.)\nWhenever enchanted creature attacks, each other creature that's attacking one of your opponents gets +1/+1 until end of turn. diff --git a/forge-gui/res/cardsfolder/m/mondassian_colony_ship.txt b/forge-gui/res/cardsfolder/m/mondassian_colony_ship.txt index b296cec7daa..93ab698e38a 100644 --- a/forge-gui/res/cardsfolder/m/mondassian_colony_ship.txt +++ b/forge-gui/res/cardsfolder/m/mondassian_colony_ship.txt @@ -3,7 +3,7 @@ ManaCost:no cost Types:Plane Spacecraft T:Mode$ Attacks | ValidCard$ Creature | TriggerZones$ Command | Execute$ AnimosityPump | TriggerDescription$ Whenever a creature attacks, it gets +1/+1 until end of turn for each other creature its controller controls that shares a creature type with it. SVar:AnimosityPump:DB$ Pump | Defined$ TriggeredAttackerLKICopy | NumAtt$ +X | NumDef$ +X -SVar:X:Count$Valid Creature.NotTriggeredAttacker+ControlledBy AttackingPlayer+sharesCreatureTypeWith TriggeredAttacker +SVar:X:Count$Valid Creature.!TriggeredAttacker+ControlledBy AttackingPlayer+sharesCreatureTypeWith TriggeredAttacker T:Mode$ ChaosEnsues | TriggerZones$ Command | Execute$ TrigSetState | TriggerDescription$ Whenever chaos ensues, turn target creature face down. It becomes a 2/2 Cyberman artifact creature. SVar:TrigSetState:DB$ SetState | ValidTgts$ Creature | Mode$ TurnFaceDown | FaceDownSetType$ Artifact & Creature & Cyberman | FaceDownPower$ 2 | FaceDownToughness$ 2 DeckHas:Type$Cyberman diff --git a/forge-gui/res/cardsfolder/m/mothrider_cavalry.txt b/forge-gui/res/cardsfolder/m/mothrider_cavalry.txt index d82b74142a4..1b77fe6e639 100644 --- a/forge-gui/res/cardsfolder/m/mothrider_cavalry.txt +++ b/forge-gui/res/cardsfolder/m/mothrider_cavalry.txt @@ -3,7 +3,7 @@ ManaCost:2 W W Types:Creature Human Samurai PT:2/2 K:Flying -S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ 2 | EffectZone$ All | IsPresent$ Creature.YouOwn+notnamedMothrider Cavalry | PresentZone$ Hand | PresentCompare$ EQ0 | Description$ This spell costs {2} less to cast if you have no other creature cards in hand or if the only other creature cards in your hand are named Mothrider Cavalry. +S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ 2 | EffectZone$ All | IsPresent$ Creature.YouOwn+!namedMothrider Cavalry | PresentZone$ Hand | PresentCompare$ EQ0 | Description$ This spell costs {2} less to cast if you have no other creature cards in hand or if the only other creature cards in your hand are named Mothrider Cavalry. S:Mode$ Continuous | Affected$ Creature.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other creatures you control get +1/+1. DeckNeeds:Name$Mothrider Cavalry Oracle:This spell costs {2} less to cast if you have no other creature cards in hand or if the only other creature cards in your hand are named Mothrider Cavalry.\nFlying\nOther creatures you control get +1/+1. diff --git a/forge-gui/res/cardsfolder/o/overwhelming_splendor.txt b/forge-gui/res/cardsfolder/o/overwhelming_splendor.txt index 55017f773f2..1520f75730a 100644 --- a/forge-gui/res/cardsfolder/o/overwhelming_splendor.txt +++ b/forge-gui/res/cardsfolder/o/overwhelming_splendor.txt @@ -4,5 +4,5 @@ Types:Enchantment Aura Curse K:Enchant:Player SVar:AttachAILogic:Curse S:Mode$ Continuous | Affected$ Creature.EnchantedPlayerCtrl | SetPower$ 1 | SetToughness$ 1 | RemoveAllAbilities$ True | Description$ Creatures enchanted player controls lose all abilities and have base power and toughness 1/1. -S:Mode$ CantBeActivated | Activator$ Player.EnchantedBy | ValidSA$ Activated.nonManaAbility+!Loyalty | Description$ Enchanted player can't activate abilities that aren't mana abilities or loyalty abilities. +S:Mode$ CantBeActivated | Activator$ Player.EnchantedBy | ValidSA$ Activated.!ManaAbility+!Loyalty | Description$ Enchanted player can't activate abilities that aren't mana abilities or loyalty abilities. Oracle:Enchant player\nCreatures enchanted player controls lose all abilities and have base power and toughness 1/1.\nEnchanted player can't activate abilities that aren't mana abilities or loyalty abilities. diff --git a/forge-gui/res/cardsfolder/p/phyrexian_dreadnought.txt b/forge-gui/res/cardsfolder/p/phyrexian_dreadnought.txt index 24550a572d9..a3058a7ba2f 100644 --- a/forge-gui/res/cardsfolder/p/phyrexian_dreadnought.txt +++ b/forge-gui/res/cardsfolder/p/phyrexian_dreadnought.txt @@ -11,7 +11,7 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:TotalPower:Remembered$CardPower SVar:X:Count$Valid Creature.YouCtrl SVar:NeedsToPlayVar:Y GE12 -SVar:Y:Count$SumPower_Creature.YouCtrl+notnamedPhyrexian Dreadnought +SVar:Y:Count$SumPower_Creature.YouCtrl+!namedPhyrexian Dreadnought AI:RemoveDeck:Random DeckHas:Ability$Sacrifice Oracle:Trample\nWhen Phyrexian Dreadnought enters, sacrifice it unless you sacrifice any number of creatures with total power 12 or greater. diff --git a/forge-gui/res/cardsfolder/p/pit_automaton.txt b/forge-gui/res/cardsfolder/p/pit_automaton.txt index be8a8afa095..5e1393f4066 100644 --- a/forge-gui/res/cardsfolder/p/pit_automaton.txt +++ b/forge-gui/res/cardsfolder/p/pit_automaton.txt @@ -4,6 +4,6 @@ Types:Artifact Creature Construct PT:0/4 K:Defender A:AB$ Mana | Cost$ T | Produced$ C | Amount$ 2 | RestrictValid$ Activated | SpellDescription$ Add {C}{C}. Spend this mana only to activate abilities. -A:AB$ DelayedTrigger | Cost$ 2 T | AILogic$ SpellCopy | Mode$ AbilityCast | ValidSA$ Activated.Exhaust+nonManaAbility | ValidActivatingPlayer$ You | ThisTurn$ True | Execute$ EffTrigCopy | SpellDescription$ When you next activate an exhaust ability that isn't a mana ability this turn, copy it. You may choose new targets for the copy. +A:AB$ DelayedTrigger | Cost$ 2 T | AILogic$ SpellCopy | Mode$ AbilityCast | ValidSA$ Activated.Exhaust+!ManaAbility | ValidActivatingPlayer$ You | ThisTurn$ True | Execute$ EffTrigCopy | SpellDescription$ When you next activate an exhaust ability that isn't a mana ability this turn, copy it. You may choose new targets for the copy. SVar:EffTrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True Oracle:Defender\n{T}: Add {C}{C}. Spend this mana only to activate abilities.\n{2}, {T}: When you next activate an exhaust ability that isn't a mana ability this turn, copy it. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/p/pithing_needle.txt b/forge-gui/res/cardsfolder/p/pithing_needle.txt index 1ac4895d24f..a75c2631bc1 100644 --- a/forge-gui/res/cardsfolder/p/pithing_needle.txt +++ b/forge-gui/res/cardsfolder/p/pithing_needle.txt @@ -3,6 +3,6 @@ ManaCost:1 Types:Artifact K:ETBReplacement:Other:DBNameCard SVar:DBNameCard:DB$ NameCard | Defined$ You | AILogic$ PithingNeedle | SpellDescription$ As CARDNAME enters, choose a card name. -S:Mode$ CantBeActivated | ValidCard$ Card.NamedCard | ValidSA$ Activated.nonManaAbility | Description$ Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. +S:Mode$ CantBeActivated | ValidCard$ Card.NamedCard | ValidSA$ Activated.!ManaAbility | Description$ Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. AI:RemoveDeck:Random Oracle:As Pithing Needle enters, choose a card name.\nActivated abilities of sources with the chosen name can't be activated unless they're mana abilities. diff --git a/forge-gui/res/cardsfolder/p/psychic_battle.txt b/forge-gui/res/cardsfolder/p/psychic_battle.txt index 7c96b64007c..71775f6f770 100644 --- a/forge-gui/res/cardsfolder/p/psychic_battle.txt +++ b/forge-gui/res/cardsfolder/p/psychic_battle.txt @@ -1,7 +1,7 @@ Name:Psychic Battle ManaCost:3 U U Types:Enchantment -T:Mode$ BecomesTargetOnce | ValidCause$ Card.notnamedPsychic Battle | TriggerZones$ Battlefield | Execute$ TrigReveal | TriggerDescription$ Whenever a player chooses one or more targets, each player reveals the top card of their library. The player who reveals the card with the highest mana value may change the target or targets. If two or more cards are tied for highest cost, the target or targets remain unchanged. Changing targets this way doesn't trigger abilities of permanents named Psychic Battle. +T:Mode$ BecomesTargetOnce | ValidCause$ Card.!namedPsychic Battle | TriggerZones$ Battlefield | Execute$ TrigReveal | TriggerDescription$ Whenever a player chooses one or more targets, each player reveals the top card of their library. The player who reveals the card with the highest mana value may change the target or targets. If two or more cards are tied for highest cost, the target or targets remain unchanged. Changing targets this way doesn't trigger abilities of permanents named Psychic Battle. SVar:TrigReveal:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBReveal | SubAbility$ DBChangeTargets SVar:DBReveal:DB$ PeekAndReveal | Defined$ Remembered | RememberRevealed$ True | SubAbility$ DBCheckLibrary SVar:DBCheckLibrary:DB$ Branch | BranchConditionSVar$ NumRememberedCard | TrueSubAbility$ DBCheckImprinted diff --git a/forge-gui/res/cardsfolder/r/realmbreakers_grasp.txt b/forge-gui/res/cardsfolder/r/realmbreakers_grasp.txt index 2a9fa93ea6d..60a48443c17 100644 --- a/forge-gui/res/cardsfolder/r/realmbreakers_grasp.txt +++ b/forge-gui/res/cardsfolder/r/realmbreakers_grasp.txt @@ -3,5 +3,5 @@ ManaCost:1 W Types:Enchantment Aura K:Enchant:Artifact,Creature:artifact or creature SVar:AttachAILogic:Curse -S:Mode$ CantAttack,CantBlock,CantBeActivated | ValidCard$ Card.EnchantedBy | ValidSA$ Activated.nonManaAbility | Description$ Enchanted permanent can't attack or block, and its activated abilities can't be activated unless they're mana abilities. +S:Mode$ CantAttack,CantBlock,CantBeActivated | ValidCard$ Card.EnchantedBy | ValidSA$ Activated.!ManaAbility | Description$ Enchanted permanent can't attack or block, and its activated abilities can't be activated unless they're mana abilities. Oracle:Enchant artifact or creature\nEnchanted permanent can't attack or block, and its activated abilities can't be activated unless they're mana abilities. diff --git a/forge-gui/res/cardsfolder/r/rings_of_brighthearth.txt b/forge-gui/res/cardsfolder/r/rings_of_brighthearth.txt index 5e1edfdf780..fbc5c863705 100644 --- a/forge-gui/res/cardsfolder/r/rings_of_brighthearth.txt +++ b/forge-gui/res/cardsfolder/r/rings_of_brighthearth.txt @@ -1,6 +1,6 @@ Name:Rings of Brighthearth ManaCost:3 Types:Artifact -T:Mode$ AbilityCast | ValidActivatingPlayer$ You | ValidSA$ SpellAbility.nonManaAbility | TriggerZones$ Battlefield | Execute$ TrigCopySpell | OptionalDecider$ You | TriggerDescription$ Whenever you activate an ability, if it isn't a mana ability, you may pay {2}. If you do, copy that ability. You may choose new targets for the copy. +T:Mode$ AbilityCast | ValidActivatingPlayer$ You | ValidSA$ SpellAbility.!ManaAbility | TriggerZones$ Battlefield | Execute$ TrigCopySpell | OptionalDecider$ You | TriggerDescription$ Whenever you activate an ability, if it isn't a mana ability, you may pay {2}. If you do, copy that ability. You may choose new targets for the copy. SVar:TrigCopySpell:AB$ CopySpellAbility | Cost$ 2 | Defined$ TriggeredSpellAbility | MayChooseTarget$ True Oracle:Whenever you activate an ability, if it isn't a mana ability, you may pay {2}. If you do, copy that ability. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/r/rowan_kenrith.txt b/forge-gui/res/cardsfolder/r/rowan_kenrith.txt index bc53b45708e..9efa223c332 100644 --- a/forge-gui/res/cardsfolder/r/rowan_kenrith.txt +++ b/forge-gui/res/cardsfolder/r/rowan_kenrith.txt @@ -11,7 +11,7 @@ SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True A:AB$ DamageAll | Cost$ SubCounter<2/LOYALTY> | ValidTgts$ Player | TgtPrompt$ Select target player | NumDmg$ 3 | ValidCards$ Creature.tapped | Planeswalker$ True | ValidDescription$ each tapped creature target player controls. | SpellDescription$ CARDNAME deals 3 damage to each tapped creature target player controls. A:AB$ Effect | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidTgts$ Player | EffectOwner$ Targeted | Name$ Emblem — Rowan Kenrith | Image$ emblem_rowan_kenrith | Triggers$ CopyAbility | Duration$ Permanent | AILogic$ Always | SpellDescription$ Target player gets an emblem with "Whenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy." -SVar:CopyAbility:Mode$ AbilityCast | ValidActivatingPlayer$ You | ValidSA$ SpellAbility.nonManaAbility | TriggerZones$ Command | Execute$ TrigCopy | TriggerDescription$ Whenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy. +SVar:CopyAbility:Mode$ AbilityCast | ValidActivatingPlayer$ You | ValidSA$ SpellAbility.!ManaAbility | TriggerZones$ Command | Execute$ TrigCopy | TriggerDescription$ Whenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy. SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True DeckHints:Name$Will Kenrith Oracle:[+2]: During target player's next turn, each creature that player controls attacks if able.\n[-2]: Rowan Kenrith deals 3 damage to each tapped creature target player controls.\n[-8]: Target player gets an emblem with "Whenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy."\nPartner with Will Kenrith\nRowan Kenrith can be your commander. diff --git a/forge-gui/res/cardsfolder/r/runic_armasaur.txt b/forge-gui/res/cardsfolder/r/runic_armasaur.txt index d685168bdc0..4f86002588f 100644 --- a/forge-gui/res/cardsfolder/r/runic_armasaur.txt +++ b/forge-gui/res/cardsfolder/r/runic_armasaur.txt @@ -2,6 +2,6 @@ Name:Runic Armasaur ManaCost:1 G G Types:Creature Dinosaur PT:2/5 -T:Mode$ AbilityCast | ValidActivatingPlayer$ Opponent | ValidCard$ Creature.inZoneBattlefield,Land.inZoneBattlefield | ValidSA$ SpellAbility.nonManaAbility | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigDraw | TriggerDescription$ Whenever an opponent activates an ability of a creature or land that isn't a mana ability, you may draw a card. +T:Mode$ AbilityCast | ValidActivatingPlayer$ Opponent | ValidCard$ Creature.inZoneBattlefield,Land.inZoneBattlefield | ValidSA$ SpellAbility.!ManaAbility | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigDraw | TriggerDescription$ Whenever an opponent activates an ability of a creature or land that isn't a mana ability, you may draw a card. SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 Oracle:Whenever an opponent activates an ability of a creature or land that isn't a mana ability, you may draw a card. diff --git a/forge-gui/res/cardsfolder/r/ruthless_instincts.txt b/forge-gui/res/cardsfolder/r/ruthless_instincts.txt index 95ca1c3d546..6d16f3f02fd 100644 --- a/forge-gui/res/cardsfolder/r/ruthless_instincts.txt +++ b/forge-gui/res/cardsfolder/r/ruthless_instincts.txt @@ -2,7 +2,7 @@ Name:Ruthless Instincts ManaCost:2 G Types:Instant A:SP$ Charm | Choices$ DBPump1,DBPump2 -SVar:DBPump1:DB$ Pump | ValidTgts$ Creature.notattacking | TgtPrompt$ Select target nonattacking creature | KW$ Reach & Deathtouch | SubAbility$ DBUntap | SpellDescription$ Target nonattacking creature gains reach and deathtouch until end of turn. Untap it. +SVar:DBPump1:DB$ Pump | ValidTgts$ Creature.!attacking | TgtPrompt$ Select target nonattacking creature | KW$ Reach & Deathtouch | SubAbility$ DBUntap | SpellDescription$ Target nonattacking creature gains reach and deathtouch until end of turn. Untap it. SVar:DBUntap:DB$ Untap | Defined$ Targeted SVar:DBPump2:DB$ Pump | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature | NumAtt$ +2 | NumDef$ +2 | KW$ Trample | SpellDescription$ Target attacking creature gets +2/+2 and gains trample until end of turn. Oracle:Choose one —\n• Target nonattacking creature gains reach and deathtouch until end of turn. Untap it.\n• Target attacking creature gets +2/+2 and gains trample until end of turn. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-earthquake_dragon.txt b/forge-gui/res/cardsfolder/rebalanced/a-earthquake_dragon.txt index bdf88028444..dbc247c5834 100644 --- a/forge-gui/res/cardsfolder/rebalanced/a-earthquake_dragon.txt +++ b/forge-gui/res/cardsfolder/rebalanced/a-earthquake_dragon.txt @@ -8,6 +8,6 @@ S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone A:AB$ ChangeZone | Cost$ 2 G Sac<1/Land> | Origin$ Graveyard | Destination$ Hand | ActivationZone$ Graveyard | SpellDescription$ Return CARDNAME from your graveyard to your hand. SVar:DiscardMe:1 SVar:PlayMain1:TRUE -SVar:X:Count$Valid Creature.Dragon+notnamedEarthquake Dragon+notnamedA-Earthquake Dragon+YouCtrl$SumCMC +SVar:X:Count$Valid Creature.Dragon+!namedEarthquake Dragon+!namedA-Earthquake Dragon+YouCtrl$SumCMC DeckHints:Type$Dragon Oracle:This spell costs {X} less to cast, where X is the total mana value of Dragons you control not named Earthquake Dragon.\nFlying, trample\n{2}{G}, Sacrifice a land: Return Earthquake Dragon from your graveyard to your hand. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-guildsworn_prowler.txt b/forge-gui/res/cardsfolder/rebalanced/a-guildsworn_prowler.txt index d2e075214a3..5927d7dd35b 100644 --- a/forge-gui/res/cardsfolder/rebalanced/a-guildsworn_prowler.txt +++ b/forge-gui/res/cardsfolder/rebalanced/a-guildsworn_prowler.txt @@ -3,6 +3,6 @@ ManaCost:1 B Types:Creature Tiefling Rogue Assassin PT:1/1 K:Deathtouch -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+notblocking | Execute$ TrigDraw | TriggerDescription$ When CARDNAME dies, if it wasn't blocking, draw a card. +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+!blocking | Execute$ TrigDraw | TriggerDescription$ When CARDNAME dies, if it wasn't blocking, draw a card. SVar:TrigDraw:DB$ Draw Oracle:Deathtouch\nWhen Guildsworn Prowler dies, if it wasn't blocking, draw a card. diff --git a/forge-gui/res/cardsfolder/s/sala_deck_boss.txt b/forge-gui/res/cardsfolder/s/sala_deck_boss.txt index dca5a955b01..d0becccfce9 100644 --- a/forge-gui/res/cardsfolder/s/sala_deck_boss.txt +++ b/forge-gui/res/cardsfolder/s/sala_deck_boss.txt @@ -3,6 +3,6 @@ ManaCost:1 U R Types:Legendary Creature Squid Pirate PT:3/3 S:Mode$ Continuous | Affected$ Creature.YouCtrl+hasAbility Activated.Exhaust | AddKeyword$ Haste | Description$ Each creature you control with an exhaust ability has haste. -T:Mode$ AbilityCast | ValidActivatingPlayer$ You | ValidSA$ Activated.Exhaust+nonManaAbility | TriggerZones$ Battlefield | Execute$ TrigCopy | TriggerDescription$ Whenever you activate an exhaust ability that isn't a mana ability, copy it. You may choose new targets for the copy. +T:Mode$ AbilityCast | ValidActivatingPlayer$ You | ValidSA$ Activated.Exhaust+!ManaAbility | TriggerZones$ Battlefield | Execute$ TrigCopy | TriggerDescription$ Whenever you activate an exhaust ability that isn't a mana ability, copy it. You may choose new targets for the copy. SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True Oracle:Each creature you control with an exhaust ability has haste.\nWhenever you activate an exhaust ability that isn't a mana ability, copy it. You may choose new targets for the copy. 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 88e48cb6f6f..57f31c7f729 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 @@ -14,6 +14,6 @@ ManaCost:1 G G Colors:green Types:Legendary Enchantment 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:DBRepeat:DB$ RepeatEach | RepeatCards$ Land.YouCtrl+!TriggeredCard+sharesNameWith TriggeredCard | RepeatSubAbility$ DBManaReflect SVar:DBManaReflect:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You 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/sculpting_steel.txt b/forge-gui/res/cardsfolder/s/sculpting_steel.txt index 1863839700b..50adb17d6e3 100644 --- a/forge-gui/res/cardsfolder/s/sculpting_steel.txt +++ b/forge-gui/res/cardsfolder/s/sculpting_steel.txt @@ -3,5 +3,5 @@ ManaCost:3 Types:Artifact K:ETBReplacement:Copy:DBCopy:Optional SVar:DBCopy:DB$ Clone | Choices$ Artifact.Other | SpellDescription$ You may have CARDNAME enter as a copy of any artifact on the battlefield. -SVar:NeedsToPlay:Artifact.YouDontCtrl+notnamedSculpting Steel,Artifact.YouCtrl+nonLegendary+notnamedSculpting Steel +SVar:NeedsToPlay:Artifact.YouDontCtrl+!namedSculpting Steel,Artifact.YouCtrl+nonLegendary+!namedSculpting Steel Oracle:You may have Sculpting Steel enter as a copy of any artifact on the battlefield. diff --git a/forge-gui/res/cardsfolder/s/shared_animosity.txt b/forge-gui/res/cardsfolder/s/shared_animosity.txt index 73b8536a799..8c313794f73 100644 --- a/forge-gui/res/cardsfolder/s/shared_animosity.txt +++ b/forge-gui/res/cardsfolder/s/shared_animosity.txt @@ -3,6 +3,6 @@ ManaCost:2 R Types:Enchantment T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ AnimosityPump | TriggerDescription$ Whenever a creature you control attacks, it gets +1/+0 until end of turn for each other attacking creature that shares a creature type with it. SVar:AnimosityPump:DB$ Pump | Defined$ TriggeredAttackerLKICopy | NumAtt$ +X -SVar:X:Count$Valid Creature.attacking+NotTriggeredAttacker+sharesCreatureTypeWith TriggeredAttacker +SVar:X:Count$Valid Creature.attacking+!TriggeredAttacker+sharesCreatureTypeWith TriggeredAttacker AI:RemoveDeck:Random Oracle:Whenever a creature you control attacks, it gets +1/+0 until end of turn for each other attacking creature that shares a creature type with it. diff --git a/forge-gui/res/cardsfolder/s/sharkey_tyrant_of_the_shire.txt b/forge-gui/res/cardsfolder/s/sharkey_tyrant_of_the_shire.txt index 922e79e84c1..289f5293502 100644 --- a/forge-gui/res/cardsfolder/s/sharkey_tyrant_of_the_shire.txt +++ b/forge-gui/res/cardsfolder/s/sharkey_tyrant_of_the_shire.txt @@ -2,8 +2,8 @@ Name:Sharkey, Tyrant of the Shire ManaCost:2 U B Types:Legendary Creature Avatar Rogue PT:2/4 -S:Mode$ CantBeActivated | AffectedZone$ Battlefield | ValidCard$ Land.OppCtrl | ValidSA$ Activated.nonManaAbility | Description$ Activated abilities of lands your opponents control can't be activated unless they're mana abilities. -S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Battlefield | GainsAbilitiesOf$ Land.OppCtrl | GainsValidAbilities$ Activated.nonManaAbility | Description$ CARDNAME has all activated abilities of lands your opponents control except mana abilities. +S:Mode$ CantBeActivated | AffectedZone$ Battlefield | ValidCard$ Land.OppCtrl | ValidSA$ Activated.!ManaAbility | Description$ Activated abilities of lands your opponents control can't be activated unless they're mana abilities. +S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Battlefield | GainsAbilitiesOf$ Land.OppCtrl | GainsValidAbilities$ Activated.!ManaAbility | Description$ CARDNAME has all activated abilities of lands your opponents control except mana abilities. S:Mode$ ManaConvert | ValidPlayer$ You | ValidCard$ Card.Self | ValidSA$ Activated | ManaConversion$ AnyType->AnyType | Description$ Mana of any type can be spent to activate NICKNAME's abilities. SVar:PlayMain1:TRUE Oracle:Activated abilities of lands your opponents control can't be activated unless they're mana abilities.\nSharkey, Tyrant of the Shire has all activated abilities of lands your opponents control except mana abilities.\nMana of any type can be spent to activate Sharkey's abilities. diff --git a/forge-gui/res/cardsfolder/s/sigil_of_valor.txt b/forge-gui/res/cardsfolder/s/sigil_of_valor.txt index ff317a2a6c3..0c8383322cd 100644 --- a/forge-gui/res/cardsfolder/s/sigil_of_valor.txt +++ b/forge-gui/res/cardsfolder/s/sigil_of_valor.txt @@ -4,5 +4,5 @@ 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 | Defined$ TriggeredAttackerLKICopy | NumAtt$ +X | NumDef$ +X -SVar:X:Count$Valid Creature.YouCtrl+NotTriggeredAttacker +SVar:X:Count$Valid Creature.YouCtrl+!TriggeredAttacker 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/sorcerous_spyglass.txt b/forge-gui/res/cardsfolder/s/sorcerous_spyglass.txt index 2280d79249b..528e5fd49b7 100644 --- a/forge-gui/res/cardsfolder/s/sorcerous_spyglass.txt +++ b/forge-gui/res/cardsfolder/s/sorcerous_spyglass.txt @@ -6,7 +6,7 @@ SVar:ChoosePlayer:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | C SVar:DBLook:DB$ RevealHand | Defined$ ChosenPlayer | Look$ True | SubAbility$ DBNameCard SVar:DBNameCard:DB$ NameCard | Defined$ You | SubAbility$ DBClear SVar:DBClear:DB$ Cleanup | ClearChosenPlayer$ True -S:Mode$ CantBeActivated | ValidCard$ Card.NamedCard | ValidSA$ Activated.nonManaAbility | Description$ Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. +S:Mode$ CantBeActivated | ValidCard$ Card.NamedCard | ValidSA$ Activated.!ManaAbility | Description$ Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. AI:RemoveDeck:Random # TODO: Might improve AI logic to support it (the AI needs to pick cards that actually have activated nonmana abilities on them) AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/s/staff_of_eden_vaults_key.txt b/forge-gui/res/cardsfolder/s/staff_of_eden_vaults_key.txt index 0e12ca3a513..d4befdfb6c3 100644 --- a/forge-gui/res/cardsfolder/s/staff_of_eden_vaults_key.txt +++ b/forge-gui/res/cardsfolder/s/staff_of_eden_vaults_key.txt @@ -2,7 +2,7 @@ Name:Staff of Eden, Vault's Key ManaCost:6 Types:Legendary Artifact T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters, put target legendary permanent card not named Staff of Eden, Vault's Key from a graveyard onto the battlefield under your control. -SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Permanent.Legendary+notnamedStaff of Eden; Vault's Key | GainControl$ True | ChangeNum$ 1 | Mandatory$ True | TgtPrompt$ Choose target legendary permanent card not named Staff of Eden, Vault's Key in a graveyard +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Permanent.Legendary+!namedStaff of Eden; Vault's Key | GainControl$ True | ChangeNum$ 1 | Mandatory$ True | TgtPrompt$ Choose target legendary permanent card not named Staff of Eden, Vault's Key in a graveyard A:AB$ Draw | Cost$ T | NumCards$ Count$Valid Permanent.YouCtrl+YouDontOwn | SpellDescription$ Draw a card for each permanent you control but don't own. SVar:X:Count$Valid Permanent.YouCtrl$Colors Oracle:When Staff of Eden, Vault's Key enters, put target legendary permanent card not named Staff of Eden, Vault's Key from a graveyard onto the battlefield under your control.\n{T}: Draw a card for each permanent you control but don't own. diff --git a/forge-gui/res/cardsfolder/s/suppression_field.txt b/forge-gui/res/cardsfolder/s/suppression_field.txt index 7ae4ff55f04..7b9a84967f3 100644 --- a/forge-gui/res/cardsfolder/s/suppression_field.txt +++ b/forge-gui/res/cardsfolder/s/suppression_field.txt @@ -1,6 +1,6 @@ Name:Suppression Field ManaCost:1 W Types:Enchantment -S:Mode$ RaiseCost | ValidCard$ Card | ValidSpell$ Activated.nonManaAbility | Amount$ 2 | Description$ Activated abilities cost {2} more to activate unless they're mana abilities. +S:Mode$ RaiseCost | ValidCard$ Card | ValidSpell$ Activated.!ManaAbility | Amount$ 2 | Description$ Activated abilities cost {2} more to activate unless they're mana abilities. AI:RemoveDeck:Random Oracle:Activated abilities cost {2} more to activate unless they're mana abilities. diff --git a/forge-gui/res/cardsfolder/t/tahngarths_rage.txt b/forge-gui/res/cardsfolder/t/tahngarths_rage.txt index d891554a897..8935b0e0d13 100644 --- a/forge-gui/res/cardsfolder/t/tahngarths_rage.txt +++ b/forge-gui/res/cardsfolder/t/tahngarths_rage.txt @@ -4,6 +4,6 @@ Types:Enchantment Aura K:Enchant:Creature SVar:AttachAILogic:Pump S:Mode$ Continuous | Affected$ Creature.EnchantedBy+attacking | AddPower$ 3 | Description$ Enchanted creature gets +3/+0 as long as it's attacking. Otherwise, it gets -2/-1. -S:Mode$ Continuous | Affected$ Creature.EnchantedBy+notattacking | AddPower$ -2 | AddToughness$ -1 +S:Mode$ Continuous | Affected$ Creature.EnchantedBy+!attacking | AddPower$ -2 | AddToughness$ -1 AI:RemoveDeck:All Oracle:Enchant creature\nEnchanted creature gets +3/+0 as long as it's attacking. Otherwise, it gets -2/-1. diff --git a/forge-gui/res/cardsfolder/t/the_enigma_jewel_locus_of_enlightenment.txt b/forge-gui/res/cardsfolder/t/the_enigma_jewel_locus_of_enlightenment.txt index 52214ec4c21..52eb62a0420 100644 --- a/forge-gui/res/cardsfolder/t/the_enigma_jewel_locus_of_enlightenment.txt +++ b/forge-gui/res/cardsfolder/t/the_enigma_jewel_locus_of_enlightenment.txt @@ -19,6 +19,6 @@ ManaCost:no cost Colors:blue Types:Legendary Artifact S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Battlefield | GainsAbilitiesOfDefined$ ExiledWith | GainsAbilitiesLimitPerTurn$ 1 | Description$ CARDNAME has each activated ability of the exiled cards used to craft it. You may activate each of those abilities only once each turn. -T:Mode$ AbilityCast | ValidActivatingPlayer$ You | ValidSA$ SpellAbility.nonManaAbility | TriggerZones$ Battlefield | Execute$ TrigCopy | TriggerDescription$ Whenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy. +T:Mode$ AbilityCast | ValidActivatingPlayer$ You | ValidSA$ SpellAbility.!ManaAbility | TriggerZones$ Battlefield | Execute$ TrigCopy | TriggerDescription$ Whenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy. SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True Oracle:Locus of Enlightenment has each activated ability of the exiled cards used to craft it. You may activate each of those abilities only once each turn.\nWhenever you activate an ability that isn't a mana ability, copy it. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/t/the_flood_of_mars.txt b/forge-gui/res/cardsfolder/t/the_flood_of_mars.txt index 74d9d85f5d5..def601ca0d7 100644 --- a/forge-gui/res/cardsfolder/t/the_flood_of_mars.txt +++ b/forge-gui/res/cardsfolder/t/the_flood_of_mars.txt @@ -4,7 +4,7 @@ Types:Creature Alien Zombie Horror PT:3/3 K:Landwalk:Island T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ DBCounter | TriggerDescription$ Water Always Wins — Whenever CARDNAME attacks, put a flood counter on another target creature or land. If it's a creature, it becomes a copy of CARDNAME. If it's a land, it becomes an Island in addition to its other types. -SVar:DBCounter:DB$ PutCounter | ValidTgts$ Creature.Other,Land.Other | AITgts$ Creature.OppCtrl+notnamedThe Flood of Mars,Land.OppCtrl+nonIsland | TgtPrompt$ Select target creature or land | CounterType$ FLOOD | SubAbility$ DBCopy +SVar:DBCounter:DB$ PutCounter | ValidTgts$ Creature.Other,Land.Other | AITgts$ Creature.OppCtrl+!namedThe Flood of Mars,Land.OppCtrl+nonIsland | TgtPrompt$ Select target creature or land | CounterType$ FLOOD | SubAbility$ DBCopy SVar:DBCopy:DB$ Clone | ConditionDefined$ ParentTarget | ConditionPresent$ Creature | Defined$ Self | CloneZone$ Battlefield | CloneTarget$ ParentTarget | SubAbility$ DBAnimate SVar:DBAnimate:DB$ Animate | ConditionDefined$ ParentTarget | ConditionPresent$ Land | Defined$ ParentTarget | Types$ Island | Duration$ Permanent | IsCurse$ True SVar:HasAttackEffect:TRUE diff --git a/forge-gui/res/cardsfolder/t/tiamat.txt b/forge-gui/res/cardsfolder/t/tiamat.txt index 70850d10e36..500ae7cf09e 100644 --- a/forge-gui/res/cardsfolder/t/tiamat.txt +++ b/forge-gui/res/cardsfolder/t/tiamat.txt @@ -4,6 +4,6 @@ Types:Legendary Creature Dragon God PT:7/7 K:Flying T:Mode$ ChangesZone | ValidCard$ Card.Self+wasCastByYou | Destination$ Battlefield | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters, if you cast it, search your library for up to five Dragon cards not named Tiamat that each have different names, reveal them, put them into your hand, then shuffle. -SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Dragon.notnamedTiamat | ChangeNum$ 5 | DifferentNames$ True +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Dragon.!namedTiamat | ChangeNum$ 5 | DifferentNames$ True DeckNeeds:Type$Dragon Oracle:Flying\nWhen Tiamat enters, if you cast it, search your library for up to five Dragon cards not named Tiamat that each have different names, reveal them, put them into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/t/tinker.txt b/forge-gui/res/cardsfolder/t/tinker.txt index 6f09b31821b..c521cec5abc 100644 --- a/forge-gui/res/cardsfolder/t/tinker.txt +++ b/forge-gui/res/cardsfolder/t/tinker.txt @@ -3,5 +3,5 @@ ManaCost:2 U Types:Sorcery A:SP$ ChangeZone | Cost$ 2 U Sac<1/Artifact> | Origin$ Library | Destination$ Battlefield | ChangeType$ Artifact | ChangeNum$ 1 | SpellDescription$ Search your library for an artifact card, put that card onto the battlefield, then shuffle. AI:RemoveDeck:Random -SVar:AIPreference:SacCost$Artifact.token,Artifact.cmcEQ0+nonLegendary+notnamedMox Pearl+notnamedMox Sapphire+notnamedMox Ruby+notnamedMox Emerald+notnamedMox Jet+notnamedBlack Lotus+notnamedLotus Petal,Artifact.cmcEQ1,Artifact.cmcEQ2,Artifact.cmcEQ3 +SVar:AIPreference:SacCost$Artifact.token,Artifact.cmcEQ0+nonLegendary+!namedMox Pearl+!namedMox Sapphire+!namedMox Ruby+!namedMox Emerald+!namedMox Jet+!namedBlack Lotus+!namedLotus Petal,Artifact.cmcEQ1,Artifact.cmcEQ2,Artifact.cmcEQ3 Oracle:As an additional cost to cast this spell, sacrifice an artifact.\nSearch your library for an artifact card, put that card onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/t/tithe_taker.txt b/forge-gui/res/cardsfolder/t/tithe_taker.txt index 1f4f33dae51..f5bccde22da 100644 --- a/forge-gui/res/cardsfolder/t/tithe_taker.txt +++ b/forge-gui/res/cardsfolder/t/tithe_taker.txt @@ -3,6 +3,6 @@ ManaCost:1 W Types:Creature Human Soldier PT:2/1 K:Afterlife:1 -S:Mode$ RaiseCost | ValidCard$ Card | Activator$ Opponent | ValidSpell$ Spell,Activated.nonManaAbility | Amount$ 1 | Condition$ PlayerTurn | Description$ During your turn, spells your opponents cast cost {1} more to cast and abilities your opponents activate cost {1} more to activate unless they're mana abilities. +S:Mode$ RaiseCost | ValidCard$ Card | Activator$ Opponent | ValidSpell$ Spell,Activated.!ManaAbility | Amount$ 1 | Condition$ PlayerTurn | Description$ During your turn, spells your opponents cast cost {1} more to cast and abilities your opponents activate cost {1} more to activate unless they're mana abilities. DeckHas:Ability$Token Oracle:During your turn, spells your opponents cast cost {1} more to cast and abilities your opponents activate cost {1} more to activate unless they're mana abilities.\nAfterlife 1 (When this creature dies, create a 1/1 white and black Spirit creature token with flying.) diff --git a/forge-gui/res/cardsfolder/t/toralf_god_of_fury_toralfs_hammer.txt b/forge-gui/res/cardsfolder/t/toralf_god_of_fury_toralfs_hammer.txt index e4b00532551..37b2bd7c1cf 100644 --- a/forge-gui/res/cardsfolder/t/toralf_god_of_fury_toralfs_hammer.txt +++ b/forge-gui/res/cardsfolder/t/toralf_god_of_fury_toralfs_hammer.txt @@ -4,7 +4,7 @@ Types:Legendary Creature God PT:5/4 K:Trample T:Mode$ ExcessDamage | ValidTarget$ Creature.OppCtrl,Planeswalker.OppCtrl | CombatDamage$ False | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ Whenever a creature or planeswalker an opponent controls is dealt excess noncombat damage, NICKNAME deals damage equal to the excess to any target other than that permanent. -SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature.NotTriggeredTarget,Player,Planeswalker.NotTriggeredTarget | TgtPrompt$ Select any target | NumDmg$ X +SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature.!TriggeredTarget,Player,Planeswalker.!TriggeredTarget | TgtPrompt$ Select any target | NumDmg$ X SVar:X:TriggerCount$DamageAmount AlternateMode:Modal Oracle:Trample\nWhenever a creature or planeswalker an opponent controls is dealt excess noncombat damage, Toralf deals damage equal to the excess to any target other than that permanent. diff --git a/forge-gui/res/cardsfolder/t/total_war.txt b/forge-gui/res/cardsfolder/t/total_war.txt index 8f02f7cd756..1e3f9e89bb0 100644 --- a/forge-gui/res/cardsfolder/t/total_war.txt +++ b/forge-gui/res/cardsfolder/t/total_war.txt @@ -2,7 +2,7 @@ Name:Total War ManaCost:3 R Types:Enchantment T:Mode$ AttackersDeclared | Execute$ TrigDestroy | TriggerZones$ Battlefield | AttackingPlayer$ Player | TriggerDescription$ Whenever a player attacks with one or more creatures, destroy all untapped non-Wall creatures that player controls that didn't attack, except for creatures the player hasn't controlled continuously since the beginning of the turn. -SVar:TrigDestroy:DB$ DestroyAll | ValidCards$ Creature.nonWall+!firstTurnControlled+untapped+ActivePlayerCtrl+notattacking +SVar:TrigDestroy:DB$ DestroyAll | ValidCards$ Creature.nonWall+!firstTurnControlled+untapped+ActivePlayerCtrl+!attacking AI:RemoveDeck:All AI:RemoveDeck:Random Oracle:Whenever a player attacks with one or more creatures, destroy all untapped non-Wall creatures that player controls that didn't attack, except for creatures the player hasn't controlled continuously since the beginning of the turn. diff --git a/forge-gui/res/cardsfolder/t/tromokratis.txt b/forge-gui/res/cardsfolder/t/tromokratis.txt index 50838ce2c12..a264303dfbc 100644 --- a/forge-gui/res/cardsfolder/t/tromokratis.txt +++ b/forge-gui/res/cardsfolder/t/tromokratis.txt @@ -2,6 +2,6 @@ Name:Tromokratis ManaCost:5 U U Types:Legendary Creature Kraken PT:8/8 -S:Mode$ Continuous | Affected$ Card.Self+notattacking+notblocking | AddKeyword$ Hexproof | Description$ CARDNAME has hexproof unless it's attacking or blocking. +S:Mode$ Continuous | Affected$ Card.Self+!attacking+!blocking | AddKeyword$ Hexproof | Description$ CARDNAME has hexproof unless it's attacking or blocking. S:Mode$ MinMaxBlocker | ValidCard$ Card.Self | Min$ All | Description$ CARDNAME can't be blocked unless all creatures defending player controls block it. (If any creature that player controls doesn't block this creature, it can't be blocked.) Oracle:Tromokratis has hexproof unless it's attacking or blocking.\nTromokratis can't be blocked unless all creatures defending player controls block it. (If any creature that player controls doesn't block this creature, it can't be blocked.) diff --git a/forge-gui/res/cardsfolder/u/unlikely_alliance.txt b/forge-gui/res/cardsfolder/u/unlikely_alliance.txt index c89f5ae5336..b8f376742f7 100644 --- a/forge-gui/res/cardsfolder/u/unlikely_alliance.txt +++ b/forge-gui/res/cardsfolder/u/unlikely_alliance.txt @@ -1,6 +1,6 @@ Name:Unlikely Alliance ManaCost:1 W Types:Enchantment -A:AB$ Pump | Cost$ 1 W | ValidTgts$ Creature.notattacking+notblocking | TgtPrompt$ Select target nonattacking, nonblocking creature | NumDef$ +2 | SpellDescription$ Target nonattacking, nonblocking creature gets +0/+2 until end of turn. +A:AB$ Pump | Cost$ 1 W | ValidTgts$ Creature.!attacking+!blocking | TgtPrompt$ Select target nonattacking, nonblocking creature | NumDef$ +2 | SpellDescription$ Target nonattacking, nonblocking creature gets +0/+2 until end of turn. AI:RemoveDeck:All Oracle:{1}{W}: Target nonattacking, nonblocking creature gets +0/+2 until end of turn. diff --git a/forge-gui/res/cardsfolder/v/valakut_the_molten_pinnacle.txt b/forge-gui/res/cardsfolder/v/valakut_the_molten_pinnacle.txt index 3fae1dd8263..e97d901375a 100644 --- a/forge-gui/res/cardsfolder/v/valakut_the_molten_pinnacle.txt +++ b/forge-gui/res/cardsfolder/v/valakut_the_molten_pinnacle.txt @@ -4,6 +4,6 @@ Types:Land R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True T:Mode$ ChangesZone | ValidCard$ Mountain.YouCtrl | Origin$ Any | Destination$ Battlefield | Execute$ TrigDamage | IsPresent$ Mountain.YouCtrl | PresentCompare$ GE6 | NoResolvingCheck$ True | TriggerZones$ Battlefield | TriggerDescription$ Whenever a Mountain you control enters, if you control at least five other Mountains, you may have CARDNAME deal 3 damage to any target. -SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Any | NumDmg$ 3 | ConditionPresent$ Mountain.YouCtrl+NotTriggeredCard | ConditionCompare$ GE5 | OptionalDecider$ You +SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Any | NumDmg$ 3 | ConditionPresent$ Mountain.YouCtrl+!TriggeredCard | ConditionCompare$ GE5 | OptionalDecider$ You A:AB$ Mana | Cost$ T | Produced$ R | SpellDescription$ Add {R}. Oracle:Valakut, the Molten Pinnacle enters tapped.\nWhenever a Mountain you control enters, if you control at least five other Mountains, you may have Valakut, the Molten Pinnacle deal 3 damage to any target.\n{T}: Add {R}. diff --git a/forge-gui/res/cardsfolder/v/verrak_warped_sengir.txt b/forge-gui/res/cardsfolder/v/verrak_warped_sengir.txt index 89c4ee547fb..84c4a28a1fc 100644 --- a/forge-gui/res/cardsfolder/v/verrak_warped_sengir.txt +++ b/forge-gui/res/cardsfolder/v/verrak_warped_sengir.txt @@ -5,7 +5,7 @@ PT:2/2 K:Flying K:Deathtouch K:Lifelink -T:Mode$ AbilityCast | ValidActivatingPlayer$ You | ValidSA$ SpellAbility.nonManaAbility | Condition$ LifePaid | TriggerZones$ Battlefield | Execute$ TrigCopySpell | TriggerDescription$ Whenever you activate an ability that isn't a mana ability, if life was paid to activate it, you may pay that much life again. If you do, copy that ability. You may choose new targets for the copy. +T:Mode$ AbilityCast | ValidActivatingPlayer$ You | ValidSA$ SpellAbility.!ManaAbility | Condition$ LifePaid | TriggerZones$ Battlefield | Execute$ TrigCopySpell | TriggerDescription$ Whenever you activate an ability that isn't a mana ability, if life was paid to activate it, you may pay that much life again. If you do, copy that ability. You may choose new targets for the copy. SVar:TrigCopySpell:AB$ CopySpellAbility | Cost$ PayLife | Defined$ TriggeredSpellAbility | MayChooseTarget$ True SVar:X:TriggerCount$LifeAmount Oracle:Flying, deathtouch, lifelink\nWhenever you activate an ability that isn't a mana ability, if life was paid to activate it, you may pay that much life again. If you do, copy that ability. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/v/vesuva.txt b/forge-gui/res/cardsfolder/v/vesuva.txt index 95dfe9dbf7f..8a73c32230c 100644 --- a/forge-gui/res/cardsfolder/v/vesuva.txt +++ b/forge-gui/res/cardsfolder/v/vesuva.txt @@ -3,5 +3,5 @@ ManaCost:no cost Types:Land K:ETBReplacement:Copy:DBCopy:Optional SVar:DBCopy:DB$ Clone | Choices$ Land.Other | IntoPlayTapped$ True | SpellDescription$ You may have CARDNAME enter tapped as a copy of any land on the battlefield. -SVar:NeedsToPlay:Land.YouDontCtrl+notnamedVesuva,Land.YouCtrl+nonLegendary+notnamedVesuva +SVar:NeedsToPlay:Land.YouDontCtrl+!namedVesuva,Land.YouCtrl+nonLegendary+!namedVesuva Oracle:You may have Vesuva enter tapped as a copy of any land on the battlefield. diff --git a/forge-gui/res/cardsfolder/w/wheel_of_fortune.txt b/forge-gui/res/cardsfolder/w/wheel_of_fortune.txt index 592a3c65c59..dda2fa73ceb 100644 --- a/forge-gui/res/cardsfolder/w/wheel_of_fortune.txt +++ b/forge-gui/res/cardsfolder/w/wheel_of_fortune.txt @@ -4,5 +4,5 @@ Types:Sorcery A:SP$ Discard | Mode$ Hand | Defined$ Player | SubAbility$ DBEachDraw | SpellDescription$ Each player discards their hand, then draws seven cards. SVar:DBEachDraw:DB$ Draw | Defined$ Player | NumCards$ 7 SVar:NeedsToPlayVar:Y LE2 -SVar:Y:Count$ValidHand Card.YouOwn+notnamedWheel of Fortune +SVar:Y:Count$ValidHand Card.YouOwn+!namedWheel of Fortune Oracle:Each player discards their hand, then draws seven cards. diff --git a/forge-gui/res/cardsfolder/w/wizened_mentor.txt b/forge-gui/res/cardsfolder/w/wizened_mentor.txt index c159c94a484..2ed3cf369e0 100644 --- a/forge-gui/res/cardsfolder/w/wizened_mentor.txt +++ b/forge-gui/res/cardsfolder/w/wizened_mentor.txt @@ -2,6 +2,6 @@ Name:Wizened Mentor ManaCost:1 W Types:Creature Zombie Cleric PT:2/2 -T:Mode$ AbilityCast | ValidActivatingPlayer$ Opponent | ValidSA$ Activated.nonManaAbility | ActivationLimit$ 1 | TriggerZones$ Battlefield | ValidCard$ Permanent.inZoneBattlefield | Execute$ TrigToken | TriggerDescription$ Whenever an opponent activates an ability of a permanent that isn't a mana ability, you create a 1/1 white Zombie creature token. This ability triggers only once each turn. +T:Mode$ AbilityCast | ValidActivatingPlayer$ Opponent | ValidSA$ Activated.!ManaAbility | ActivationLimit$ 1 | TriggerZones$ Battlefield | ValidCard$ Permanent.inZoneBattlefield | Execute$ TrigToken | TriggerDescription$ Whenever an opponent activates an ability of a permanent that isn't a mana ability, you create a 1/1 white Zombie creature token. This ability triggers only once each turn. SVar:TrigToken:DB$ Token | TokenScript$ w_1_1_zombie Oracle:Whenever an opponent activates an ability of a permanent that isn't a mana ability, you create a 1/1 white Zombie creature token. This ability triggers only once each turn. diff --git a/forge-gui/res/cardsfolder/z/zirda_the_dawnwaker.txt b/forge-gui/res/cardsfolder/z/zirda_the_dawnwaker.txt index 3919feeb886..71af28fe384 100644 --- a/forge-gui/res/cardsfolder/z/zirda_the_dawnwaker.txt +++ b/forge-gui/res/cardsfolder/z/zirda_the_dawnwaker.txt @@ -3,6 +3,6 @@ ManaCost:1 RW RW Types:Legendary Creature Elemental Fox PT:3/3 K:Companion:Permanent.hasAbility Activated,Instant,Sorcery:Each permanent card in your starting deck has an activated ability. -S:Mode$ ReduceCost | ValidCard$ Card | Activator$ You | ValidSpell$ Activated.nonManaAbility | Amount$ 2 | MinMana$ 1 | Description$ Abilities you activate that aren't mana abilities cost {2} less to activate. This effect can't reduce the mana in that cost to less than one mana. +S:Mode$ ReduceCost | ValidCard$ Card | Activator$ You | ValidSpell$ Activated.!ManaAbility | Amount$ 2 | MinMana$ 1 | Description$ Abilities you activate that aren't mana abilities cost {2} less to activate. This effect can't reduce the mana in that cost to less than one mana. A:AB$ Pump | Cost$ 1 T | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn. Oracle:Companion — Each permanent card in your starting deck has an activated ability. (If this card is your chosen companion, you may put it into your hand from outside the game for {3} any time you could cast a sorcery.)\nAbilities you activate that aren't mana abilities cost {2} less to activate. This effect can't reduce the mana in that cost to less than one mana.\n{1}, {T}: Target creature can't block this turn. diff --git a/forge-gui/src/main/java/forge/gui/card/CardScriptParser.java b/forge-gui/src/main/java/forge/gui/card/CardScriptParser.java index 6f80e3165b9..9a4eadd99df 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardScriptParser.java +++ b/forge-gui/src/main/java/forge/gui/card/CardScriptParser.java @@ -469,11 +469,11 @@ public final class CardScriptParser { "leastPower", "leastToughness", "greatestCMC", "greatestRememberedCMC", "lowestRememberedCMC", "lowestCMC", "enchanted", "enchanting", "equipped", - "equipping", "modified", "token", "nonToken", "hasXCost", "suspended", - "delved", "attacking", "attackingYou", "notattacking", + "equipping", "modified", "token", "hasXCost", "suspended", + "delved", "attacking", "attackingYou", "attackedBySourceThisCombat", "blocking", "blockingSource", "blockingCreatureYouCtrl", "blockingRemembered", - "sharesBlockingAssignmentWith", "notblocking", "blocked", + "sharesBlockingAssignmentWith", "blocked", "blockedBySource", "blockedThisTurn", "blockedByThisTurn", "blockedBySourceThisTurn", "isBlockedByRemembered", "blockedRemembered", "blockedByRemembered", "unblocked", "attackersBandedWith", @@ -485,7 +485,7 @@ public final class CardScriptParser { "wasNotCast", "ChosenType", "IsNotChosenType", "IsCommander", "IsRenowned"); private static final Set VALID_EXCLUSIVE_STARTSWITH = ImmutableSortedSet - .of("named", "notnamed", "OwnedBy", "ControlledBy", + .of("named", "OwnedBy", "ControlledBy", "ControllerControls", "AttachedTo", "EnchantedBy", "TopGraveyard", "SharesColorWith", "MostProminentColor", "notSharesColorWith", From 8dc9a18c4ffe45316fb122a6967b7142b52b3f22 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Mon, 29 Sep 2025 16:31:21 +0200 Subject: [PATCH 051/230] Card: addColor use internal Record (#8793) --- .../ability/effects/AnimateEffectBase.java | 2 +- .../src/main/java/forge/game/card/Card.java | 38 ++++++++-------- .../main/java/forge/game/card/CardColor.java | 45 ------------------- .../main/java/forge/game/card/CardView.java | 2 +- .../game/card/perpetual/PerpetualColors.java | 2 +- .../card/perpetual/PerpetualIncorporate.java | 2 +- .../StaticAbilityContinuous.java | 4 +- 7 files changed, 24 insertions(+), 71 deletions(-) delete mode 100644 forge-game/src/main/java/forge/game/card/CardColor.java diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java index 561d2978d5b..efdfb6b197e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffectBase.java @@ -138,7 +138,7 @@ public abstract class AnimateEffectBase extends SpellAbilityEffect { if (perpetual) { c.addPerpetual(new PerpetualColors(timestamp, colors, overwrite)); } - c.addColor(colors, !overwrite, timestamp, 0, false); + c.addColor(colors, !overwrite, timestamp, null); } if (sa.hasParam("LeaveBattlefield")) { 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 35acfe1de7a..5033e19a81b 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -449,7 +449,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr public final void updateColorForView() { currentState.getView().updateColors(this); - currentState.getView().updateHasChangeColors(!Iterables.isEmpty(getChangedCardColors())); + currentState.getView().updateHasChangeColors(hasChangedCardColors()); } public void updateAttackingForView() { @@ -4291,18 +4291,10 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr } public boolean clearChangedCardColors() { - boolean changed = false; + boolean changed = hasChangedCardColors(); - if (!changedCardColorsByText.isEmpty()) - changed = true; changedCardColorsByText.clear(); - - if (!changedCardTypesCharacterDefining.isEmpty()) - changed = true; changedCardTypesCharacterDefining.clear(); - - if (!changedCardColors.isEmpty()) - changed = true; changedCardColors.clear(); return changed; @@ -4388,17 +4380,19 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr } } - public Iterable getChangedCardColors() { - return Iterables.concat(changedCardColorsByText.values(), changedCardColorsCharacterDefining.values(), changedCardColors.values()); + public boolean hasChangedCardColors() { + return !changedCardColorsByText.isEmpty() || !changedCardColorsCharacterDefining.isEmpty() || !changedCardColors.isEmpty(); } - public void addColorByText(final ColorSet color, final long timestamp, final long staticId) { - changedCardColorsByText.put(timestamp, staticId, new CardColor(color, false)); + public void addColorByText(final ColorSet color, final long timestamp, final StaticAbility stAb) { + changedCardColorsByText.put(timestamp, (long)stAb.getId(), new CardColor(color, false)); updateColorForView(); } - public final void addColor(final ColorSet color, final boolean addToColors, final long timestamp, final long staticId, final boolean cda) { - (cda ? changedCardColorsCharacterDefining : changedCardColors).put(timestamp, staticId, new CardColor(color, addToColors)); + public final void addColor(final ColorSet color, final boolean addToColors, final long timestamp, final StaticAbility stAb) { + (stAb != null && stAb.isCharacteristicDefining() ? changedCardColorsCharacterDefining : changedCardColors).put( + timestamp, stAb != null ? stAb.getId() : (long)0, new CardColor(color, addToColors) + ); updateColorForView(); } @@ -4425,16 +4419,20 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr } public final ColorSet getColor(CardState state) { byte colors = state.getColor(); - for (final CardColor cc : getChangedCardColors()) { - if (cc.isAdditional()) { - colors |= cc.getColorMask(); + for (final CardColor cc : Iterables.concat(changedCardColorsByText.values(), changedCardColorsCharacterDefining.values(), changedCardColors.values())) { + if (cc.additional()) { + colors |= cc.color().getColor(); } else { - colors = cc.getColorMask(); + colors = cc.color().getColor(); } } return ColorSet.fromMask(colors); } + private record CardColor(ColorSet color, boolean additional) { + + } + public final int getCurrentLoyalty() { return getCounters(CounterEnumType.LOYALTY); } diff --git a/forge-game/src/main/java/forge/game/card/CardColor.java b/forge-game/src/main/java/forge/game/card/CardColor.java deleted file mode 100644 index c37460a4128..00000000000 --- a/forge-game/src/main/java/forge/game/card/CardColor.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Forge: Play Magic: the Gathering. - * Copyright (C) 2011 Forge Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package forge.game.card; - -import forge.card.ColorSet; - -/** - *

- * Card_Color class. - *

- * - * @author Forge - * @version $Id$ - */ -public class CardColor { - private final byte colorMask; - public final byte getColorMask() { - return colorMask; - } - - private final boolean additional; - public final boolean isAdditional() { - return this.additional; - } - - CardColor(final ColorSet colors, final boolean addToColors) { - this.colorMask = colors.getColor(); - this.additional = addToColors; - } -} diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index 52fbf98b263..773b82204aa 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -1095,7 +1095,7 @@ public class CardView extends GameEntityView { if (c.getGame() != null) { if (c.hasPerpetual()) currentStateView.updateColors(c); else currentStateView.updateColors(currentState); - currentStateView.updateHasChangeColors(!Iterables.isEmpty(c.getChangedCardColors())); + currentStateView.updateHasChangeColors(c.hasChangedCardColors()); } } else { currentStateView.updateLoyalty(currentState); diff --git a/forge-game/src/main/java/forge/game/card/perpetual/PerpetualColors.java b/forge-game/src/main/java/forge/game/card/perpetual/PerpetualColors.java index ecdf4f287cb..20c999c65c5 100644 --- a/forge-game/src/main/java/forge/game/card/perpetual/PerpetualColors.java +++ b/forge-game/src/main/java/forge/game/card/perpetual/PerpetualColors.java @@ -12,7 +12,7 @@ public record PerpetualColors(long timestamp, ColorSet colors, boolean overwrite @Override public void applyEffect(Card c) { - c.addColor(colors, !overwrite, timestamp, (long) 0, false); + c.addColor(colors, !overwrite, timestamp, null); } } diff --git a/forge-game/src/main/java/forge/game/card/perpetual/PerpetualIncorporate.java b/forge-game/src/main/java/forge/game/card/perpetual/PerpetualIncorporate.java index 0b647fe1f5d..386b7c2c588 100644 --- a/forge-game/src/main/java/forge/game/card/perpetual/PerpetualIncorporate.java +++ b/forge-game/src/main/java/forge/game/card/perpetual/PerpetualIncorporate.java @@ -17,7 +17,7 @@ public record PerpetualIncorporate(long timestamp, ManaCost incorporate) impleme ColorSet colors = ColorSet.fromMask(incorporate.getColorProfile()); final ManaCost newCost = ManaCost.combine(c.getManaCost(), incorporate); c.addChangedManaCost(newCost, timestamp, (long) 0); - c.addColor(colors, true, timestamp, (long) 0, false); + c.addColor(colors, true, timestamp, null); c.updateManaCostForView(); if (c.getFirstSpellAbility() != null) { 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 64c755f6810..e53361d4ce8 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -623,7 +623,7 @@ public final class StaticAbilityContinuous { // Mana cost affectedCard.addChangedManaCost(state.getManaCost(), se.getTimestamp(), stAb.getId()); // color - affectedCard.addColorByText(ColorSet.fromMask(state.getColor()), se.getTimestamp(), stAb.getId()); + affectedCard.addColorByText(ColorSet.fromMask(state.getColor()), se.getTimestamp(), stAb); // type affectedCard.addChangedCardTypesByText(new CardType(state.getType()), se.getTimestamp(), stAb.getId()); // abilities @@ -856,7 +856,7 @@ public final class StaticAbilityContinuous { // add colors if (addColors != null) { - affectedCard.addColor(addColors, !overwriteColors, se.getTimestamp(), stAb.getId(), stAb.isCharacteristicDefining()); + affectedCard.addColor(addColors, !overwriteColors, se.getTimestamp(), stAb); } if (layer == StaticAbilityLayer.RULES) { From 74dc09b2e521681c5bfe65ad491ca1625d1ca8c5 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Mon, 29 Sep 2025 20:07:43 +0200 Subject: [PATCH 052/230] 12 FIC Scene cards + Formidable Speaker (#8808) --- .../game/trigger/TriggerAttackerBlocked.java | 13 +++++++++++-- forge-gui/res/cardsfolder/s/soul_shredder.txt | 2 +- .../cardsfolder/upcoming/formidable_speaker.txt | 8 ++++++++ .../res/cardsfolder/upcoming/mega_flare.txt | 10 ++++++++++ .../upcoming/noctis_heir_apparent.txt | 14 ++++++++++++++ .../cardsfolder/upcoming/rinoa_angel_wing.txt | 9 +++++++++ .../cardsfolder/upcoming/search_for_dagger.txt | 7 +++++++ .../upcoming/seifer_balamb_rival.txt | 11 +++++++++++ .../upcoming/squall_gunblade_duelist.txt | 12 ++++++++++++ .../upcoming/the_destined_black_mage.txt | 14 ++++++++++++++ .../cardsfolder/upcoming/the_destined_thief.txt | 17 +++++++++++++++++ .../upcoming/the_destined_warrior.txt | 16 ++++++++++++++++ .../upcoming/the_destined_white_mage.txt | 14 ++++++++++++++ .../cardsfolder/upcoming/vivis_persistence.txt | 9 +++++++++ .../cardsfolder/upcoming/warriors_resolve.txt | 8 ++++++++ 15 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/formidable_speaker.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/mega_flare.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/noctis_heir_apparent.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/rinoa_angel_wing.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/search_for_dagger.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/seifer_balamb_rival.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/squall_gunblade_duelist.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/the_destined_black_mage.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/the_destined_thief.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/the_destined_warrior.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/the_destined_white_mage.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/vivis_persistence.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/warriors_resolve.txt diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java b/forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java index 23fa0b0b5b3..676283cfffd 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java @@ -21,8 +21,12 @@ import java.util.Map; import com.google.common.collect.Iterables; import forge.game.ability.AbilityKey; +import forge.game.ability.AbilityUtils; import forge.game.card.Card; +import forge.game.card.CardCollection; +import forge.game.card.CardLists; import forge.game.spellability.SpellAbility; +import forge.util.Expressions; import forge.util.Localizer; /** @@ -59,8 +63,13 @@ public class TriggerAttackerBlocked extends Trigger { return false; } - if (!matchesValidParam("ValidBlocker", runParams.get(AbilityKey.Blockers))) { - return false; + if (hasParam("ValidBlocker")) { + String param = getParamOrDefault("ValidBlockerAmount", "GE1"); + int attackers = CardLists.getValidCardCount((CardCollection) runParams.get(AbilityKey.Blockers), getParam("ValidBlocker"), getHostCard().getController(), getHostCard(), this); + int amount = AbilityUtils.calculateAmount(getHostCard(), param.substring(2), this); + if (!Expressions.compare(attackers, param, amount)) { + return false; + } } return true; diff --git a/forge-gui/res/cardsfolder/s/soul_shredder.txt b/forge-gui/res/cardsfolder/s/soul_shredder.txt index 2f4d2c10369..ecdf073e8f8 100644 --- a/forge-gui/res/cardsfolder/s/soul_shredder.txt +++ b/forge-gui/res/cardsfolder/s/soul_shredder.txt @@ -7,4 +7,4 @@ K:Crew:1 T:Mode$ ChangesZoneAll | TriggerZones$ Battlefield,Graveyard | ValidCards$ Creature.Other | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigPump | TriggerDescription$ Whenever one or more other creatures die, CARDNAME perpetually gets +1/+1. This ability also triggers if CARDNAME is in your graveyard. SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ +1 | NumDef$ +1 | Duration$ Perpetual A:AB$ ChangeZone | Cost$ 1 Sac<2/Creature> | Origin$ Graveyard | Destination$ Battlefield | ActivationZone$ Graveyard | SorcerySpeed$ True | SpellDescription$ Return CARDNAME from your graveyard to the battlefield. -Oracle:Haste\nWhenever one or more other creatures die, Soul Shredder perpetually gets +1/+1. This ability also triggers if Sould Shredder is in your graveyard.\nCrew 1\n{1}, Sacrifice two creatures: Return Soul Shredder from your graveyard to the battlefield. Activate only as a sorcery. +Oracle:Haste\nWhenever one or more other creatures die, Soul Shredder perpetually gets +1/+1. This ability also triggers if Soul Shredder is in your graveyard.\nCrew 1\n{1}, Sacrifice two creatures: Return Soul Shredder from your graveyard to the battlefield. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/upcoming/formidable_speaker.txt b/forge-gui/res/cardsfolder/upcoming/formidable_speaker.txt new file mode 100644 index 00000000000..c929e42c203 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/formidable_speaker.txt @@ -0,0 +1,8 @@ +Name:Formidable Speaker +ManaCost:2 G +Types:Creature Elf Druid +PT:2/4 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearch | TriggerDescription$ When this creature enters, you may discard a card. If you do, search your library for a creature card, reveal it, put it into your hand, then shuffle. +SVar:TrigSearch:AB$ ChangeZone | Cost$ Discard<1/Card> | Origin$ Library | Destination$ Hand | ChangeType$ Creature | ChangeNum$ 1 +A:AB$ Untap | Cost$ 1 T | ValidTgts$ Permanent.Other | TgtPrompt$ Select another target permanent. | SpellDescription$ Untap another target permanent. +Oracle:When this creature enters, you may discard a card. If you do, search your library for a creature card, reveal it, put it into your hand, then shuffle.\n{1}, {T}: Untap another target permanent. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/mega_flare.txt b/forge-gui/res/cardsfolder/upcoming/mega_flare.txt new file mode 100644 index 00000000000..01f128f6e89 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/mega_flare.txt @@ -0,0 +1,10 @@ +Name:Mega Flare +ManaCost:2 R +Types:Sorcery +K:Kicker:3 R R +A:SP$ Token | Condition$ Kicked | TokenScript$ r_6_6_dragon_flying | SubAbility$ DBDamage | SpellDescription$ If this spell was kicked, create a 6/6 red Dragon creature token with flying. For each opponent, choose up to one target creature that player controls. CARDNAME deals damage equal to the greatest power among creatures you control to each of the chosen creatures. +SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl | TgtPrompt$ For each opponent, choose up to one target creature that player controls | TargetMin$ 0 | TargetMax$ OneEach | TargetsForEachPlayer$ True | NumDmg$ X +SVar:X:Count$Valid Creature.YouCtrl$GreatestPower +SVar:OneEach:PlayerCountOpponents$Amount +DeckHas:Ability$Token +Oracle:Kicker {3}{R}{R}\nIf this spell was kicked, create a 6/6 red Dragon creature token with flying.\nFor each opponent, choose up to one target creature that player controls. Mega Flare deals damage equal to the greatest power among creatures you control to each of the chosen creatures. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/noctis_heir_apparent.txt b/forge-gui/res/cardsfolder/upcoming/noctis_heir_apparent.txt new file mode 100644 index 00000000000..d3605fb39c6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/noctis_heir_apparent.txt @@ -0,0 +1,14 @@ +Name:Noctis, Heir Apparent +ManaCost:W U B +Types:Legendary Creature Human Noble +PT:2/3 +T:Mode$ ChangesZone | Phase$ BeginCombat->EndCombat | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | Execute$ EquipmentSelection | OptionalDecider$ You | TriggerDescription$ Whenever a creature you control enters during combat, you may attach target Equipment you control to target creature you control. +SVar:EquipmentSelection:DB$ Pump | ValidTgts$ Equipment.YouCtrl | TgtPrompt$ Select target equipment you control | SubAbility$ DBAttach | StackDescription$ None +SVar:DBAttach:DB$ Attach | Object$ ParentTarget | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control. +A:AB$ ChangeZone | PrecostDesc$ Warp-Strike — | Cost$ 3 | Defined$ Self | Origin$ Battlefield | Destination$ Exile | SubAbility$ DelTrig | RememberChanged$ True | SpellDescription$ Exile NICKNAME. Return it to the battlefield under its owner's control tapped and attacking at the beginning of that player's next declare attackers step. It can't be blocked that combat. +SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | ValidPlayer$ Player.CardOwner | Phase$ Declare Attackers | Execute$ TrigReturn | ConditionDefined$ Remembered | ConditionPresent$ Card | RememberObjects$ Remembered | SubAbility$ DBCleanup | TriggerDescription$ Return NICKNAME to the battlefield under its owner's control tapped and attacking at the beginning of that player's next declare attackers step. It can't be blocked that combat. +SVar:TrigReturn:DB$ ChangeZone | Defined$ DelayTriggerRememberedLKI | Origin$ Exile | Destination$ Battlefield | Tapped$ True | Attacking$ True | RememberChanged$ True | SubAbility$ DBEffect +SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedLKI | StaticAbilities$ STUnblockable | Duration$ UntilEndOfCombat | ExileOnMoved$ Battlefield | SubAbility$ DBCleanup +SVar:STUnblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ EFFECTSOURCE can't be blocked this combat. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:Whenever a creature you control enters during combat, you may attach target Equipment you control to target creature you control.\nWarp-Strike — {3}: Exile Noctis. Return it to the battlefield under its owner's control tapped and attacking at the beginning of that player's next declare attackers step. It can't be blocked that combat. diff --git a/forge-gui/res/cardsfolder/upcoming/rinoa_angel_wing.txt b/forge-gui/res/cardsfolder/upcoming/rinoa_angel_wing.txt new file mode 100644 index 00000000000..c36355c5c03 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/rinoa_angel_wing.txt @@ -0,0 +1,9 @@ +Name:Rinoa, Angel Wing +ManaCost:2 W +Types:Legendary Creature Human Rebel Warlock +PT:2/4 +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPumpAll | TriggerDescription$ At the beginning of combat on your turn, creatures you control with flying get +1/+1 and gain vigilance until end of turn. +SVar:TrigPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl+withFlying | NumAtt$ +1 | NumDef$ +1 | KW$ Vigilance +T:Mode$ ChangesZoneAll | ValidCards$ Creature.attacking+YouCtrl | Origin$ Battlefield | Destination$ Graveyard | TriggerZones$ Battlefield | OptionalDecider$ You | ResolvedLimit$ 1 | Execute$ TrigChangeZone | TriggerDescription$ Whenever one or more attacking creatures you control die, you may return one of them to the battlefield tapped under its owner's control with a flying counter on it. Do this only once each turn. +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | ChangeNum$ 1 | Destination$ Battlefield | Hidden$ True | Tapped$ True | ChooseFromDefined$ TriggeredCards | Mandatory$ True | WithCountersType$ Flying +Oracle:At the beginning of combat on your turn, creatures you control with flying get +1/+1 and gain vigilance until end of turn.\nWhenever one or more attacking creatures you control die, you may return one of them to the battlefield tapped under its owner's control with a flying counter on it. Do this only once each turn. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/search_for_dagger.txt b/forge-gui/res/cardsfolder/upcoming/search_for_dagger.txt new file mode 100644 index 00000000000..63722e79fc8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/search_for_dagger.txt @@ -0,0 +1,7 @@ +Name:Search for Dagger +ManaCost:1 W +Types:Enchantment +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.IsCommander+YouOwn | Execute$ TrigDig | TriggerZones$ Battlefield | TriggerDescription$ Whenever your commander enters or attacks, look at the top six cards of your library. You may reveal a legendary creature card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. +T:Mode$ Attacks | ValidCard$ Card.IsCommander+YouOwn | Execute$ TrigDig | TriggerZones$ Battlefield | Secondary$ True | TriggerDescription$ Whenever your commander enters or attacks, look at the top six cards of your library. You may reveal a legendary creature card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. +SVar:TrigDig:DB$ Dig | DigNum$ 6 | ChangeNum$ 1 | Optional$ True | ForceRevealToController$ True | ChangeValid$ Creature.Legendary | RestRandomOrder$ True +Oracle:Whenever your commander enters or attacks, look at the top six cards of your library. You may reveal a legendary creature card from among them and put it into your hand. 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/seifer_balamb_rival.txt b/forge-gui/res/cardsfolder/upcoming/seifer_balamb_rival.txt new file mode 100644 index 00000000000..782ee963b4a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/seifer_balamb_rival.txt @@ -0,0 +1,11 @@ +Name:Seifer, Balamb Rival +ManaCost:2 B R +Types:Legendary Creature Human Mercenary +PT:4/3 +K:First Strike +T:Mode$ AttackersDeclaredOneTarget | AttackedTarget$ Player | ValidAttackers$ Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigGoad | TriggerDescription$ Whenever you attack a player, goad target creature that player controls. +SVar:TrigGoad:DB$ Goad | ValidTgts$ Creature | TargetsWithDefinedController$ TriggeredAttackedTarget | TgtPrompt$ Select target creature that player controls +T:Mode$ AttackerBlocked | ValidCard$ Creature.attacking Opponent | ValidBlocker$ Creature | Execute$ TrigPump | ValidBlockerAmount$ GE2 | TriggerDescription$ Whenever a creature attacking one of your opponents becomes blocked by two or more creatures, that attacking creature gains deathtouch until end of turn. +SVar:TrigPump:DB$ Pump | Defined$ TriggeredAttacker | KW$ Deathtouch +SVar:PlayMain1:TRUE +Oracle:First strike\nWhenever you attack a player, goad target creature that player controls.\nWhenever a creature attacking one of your opponents becomes blocked by two or more creatures, that attacking creature gains deathtouch until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/squall_gunblade_duelist.txt b/forge-gui/res/cardsfolder/upcoming/squall_gunblade_duelist.txt new file mode 100644 index 00000000000..2a310097f3a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/squall_gunblade_duelist.txt @@ -0,0 +1,12 @@ +Name:Squall, Gunblade Duelist +ManaCost:R W B +Types:Legendary Creature Human Mercenary +PT:3/2 +K:First Strike +K:ETBReplacement:Other:ChooseNumber +SVar:ChooseNumber:DB$ ChooseNumber | Defined$ You | SpellDescription$ As NICKNAME enters, choose a number. +T:Mode$ AttackersDeclaredOneTarget | AttackedTarget$ Opponent | ValidAttackers$ Creature.powerEQChosen,Creature.toughnessEQChosen | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ Whenever one or more creatures attack one of your opponents, if any of those creatures have power or toughness equal to the chosen number, NICKNAME deals damage equal to its power to defending player. +SVar:TrigDealDamage:DB$ DealDamage | Defined$ TriggeredAttackedTarget | NumDmg$ X | ConditionCheckSVar$ Y | ConditionSVarCompare$ GE1 +SVar:X:Count$CardPower +SVar:Y:Count$Valid Creature.TriggeredAttackers+powerEQChosen,Creature.TriggeredAttackers+toughnessEQChosen +Oracle:First strike\nAs Squall enters, choose a number.\nWhenever one or more creatures attack one of your opponents, if any of those creatures have power or toughness equal to the chosen number, Squall deals damage equal to its power to defending player. diff --git a/forge-gui/res/cardsfolder/upcoming/the_destined_black_mage.txt b/forge-gui/res/cardsfolder/upcoming/the_destined_black_mage.txt new file mode 100644 index 00000000000..ac77619e6f1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_destined_black_mage.txt @@ -0,0 +1,14 @@ +Name:The Destined Black Mage +ManaCost:2 B +Types:Legendary Creature Human Wizard +PT:3/2 +K:Deathtouch +A:AB$ Pump | Cost$ B T | ValidTgts$ Creature.Other+YouCtrl | TgtPrompt$ Select another target creature you control | KW$ Deathtouch | SpellDescription$ Another target creature you control gains deathtouch until end of turn. +T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ Whenever you cast a noncreature spell, CARDNAME deals 1 damage to each opponent. If you have a full party, it deals 3 damage to each opponent instead. +SVar:TrigDamage:DB$ DealDamage | Defined$ Player.Opponent | NumDmg$ X +SVar:X:Count$Compare Y EQ4.3.1 +SVar:Y:Count$Party +SVar:BuffedBy:Cleric,Rogue,Warrior +DeckHas:Ability$Party +DeckHints:Type$Cleric|Rogue|Warrior +Oracle:Deathtouch\n{B}, {T}: Another target creature you control gains deathtouch until end of turn.\nWhenever you cast a noncreature spell, The Destined Black Mage deals 1 damage to each opponent. If you have a full party, it deals 3 damage to each opponent instead. diff --git a/forge-gui/res/cardsfolder/upcoming/the_destined_thief.txt b/forge-gui/res/cardsfolder/upcoming/the_destined_thief.txt new file mode 100644 index 00000000000..0aafcd628ac --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_destined_thief.txt @@ -0,0 +1,17 @@ +Name:The Destined Thief +ManaCost:2 U +Types:Legendary Creature Human Rogue +PT:2/2 +S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | Description$ CARDNAME can't be blocked. +A:AB$ Effect | Cost$ U T | ValidTgts$ Creature.Other+YouCtrl | TgtPrompt$ Select another target creature you control | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | StaticAbilities$ Unblockable | AILogic$ Pump | StackDescription$ {c:Targeted} can't be blocked this turn. | SpellDescription$ Another target creature you control can't be blocked this turn. +SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. +T:Mode$ DamageAll | CombatDamage$ True | ValidSource$ Creature.YouCtrl | ValidTarget$ Player | TriggerZones$ Battlefield | Execute$ TrigBranch | TriggerDescription$ Whenever one or more creatures you control deal combat damage to one or more players, draw a card, then discard a card. If you have a full party, instead draw three cards. +SVar:TrigBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$EQ4 | TrueSubAbility$ DBDraw1 | FalseSubAbility$ DBDraw2 +SVar:TrigDraw1:DB$ Draw | NumCards$ 3 +SVar:TrigDraw2:DB$ Draw | SubAbility$ DBDiscard +SVar:DBDiscard:DB$ Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 1 +SVar:X:Count$Party +SVar:BuffedBy:Cleric,Warrior,Wizard +DeckHas:Ability$Party +DeckHints:Type$Cleric|Warrior|Wizard +Oracle:The Destined Thief can't be blocked.\n{U}, {T}: Another target creature you control can't be blocked this turn.\nWhenever one or more creatures you control deal combat damage to one or more players, draw a card, then discard a card. If you have a full party, instead draw three cards. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/the_destined_warrior.txt b/forge-gui/res/cardsfolder/upcoming/the_destined_warrior.txt new file mode 100644 index 00000000000..8c9cf62f330 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_destined_warrior.txt @@ -0,0 +1,16 @@ +Name:The Destined Warrior +ManaCost:1 W U B +Types:Legendary Creature Human Warrior +PT:3/3 +K:First Strike +K:Vigilance +K:Menace +S:Mode$ ReduceCost | ValidCard$ Cleric,Rogue,Warrior,Wizard | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Cleric, Rogue, Warrior, and Wizard spells you cast cost {1} less to cast. +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPumpAll | TriggerDescription$ At the beginning of combat on your turn, creatures you control get +1/+0 until end of turn. If you have a full party, creatures you control get +3/+0 until end of turn instead. +SVar:TrigPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl | NumAtt$ +X +SVar:X:Count$Compare Y EQ4.3.1 +SVar:Y:Count$Party +SVar:BuffedBy:Cleric,Rogue,Wizard +DeckHas:Ability$Party +DeckHints:Type$Cleric|Rogue|Warrior|Wizard +Oracle:First strike, vigilance, menace\nCleric, Rogue, Warrior, and Wizard spells you cast cost {1} less to cast.\nAt the beginning of combat on your turn, creatures you control get +1/+0 until end of turn. If you have a full party, creatures you control get +3/+0 until end of turn instead. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/the_destined_white_mage.txt b/forge-gui/res/cardsfolder/upcoming/the_destined_white_mage.txt new file mode 100644 index 00000000000..5621c7503dc --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_destined_white_mage.txt @@ -0,0 +1,14 @@ +Name:The Destined White Mage +ManaCost:2 W +Types:Legendary Creature Human Cleric +PT:2/3 +K:Lifelink +A:AB$ Pump | Cost$ W T | ValidTgts$ Creature.Other+YouCtrl | TgtPrompt$ Select another target creature you control | KW$ Lifelink | SpellDescription$ Another target creature you control gains lifelink until end of turn. +T:Mode$ LifeGained | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you gain life, put a +1/+1 counter on target creature you control. If you have a full party, put three +1/+1 counters on that creature instead. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ P1P1 | CounterNum$ X +SVar:X:Count$Compare Y EQ4.3.1 +SVar:Y:Count$Party +SVar:BuffedBy:Rogue,Warrior,Wizard +DeckHas:Ability$Party +DeckHints:Type$Rogue|Warrior|Wizard +Oracle:Lifelink\n{W}, {T}: Another target creature you control gains lifelink until end of turn.\nWhenever you gain life, put a +1/+1 counter on target creature you control. If you have a full party, put three +1/+1 counters on that creature instead. diff --git a/forge-gui/res/cardsfolder/upcoming/vivis_persistence.txt b/forge-gui/res/cardsfolder/upcoming/vivis_persistence.txt new file mode 100644 index 00000000000..f0ba1f67b23 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/vivis_persistence.txt @@ -0,0 +1,9 @@ +Name:Vivi's Persistence +ManaCost:1 R +Types:Instant +A:SP$ Token | TokenScript$ b_0_1_wizard_snipe | TokenAmount$ 1 | TokenOwner$ You | SpellDescription$ Create a 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent." +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.IsCommander+YouOwn | Execute$ TrigChangeZone | TriggerZones$ Graveyard | TriggerDescription$ Whenever your commander enters or attacks, you may pay {2}. If you do, return this card from your graveyard to your hand. +T:Mode$ Attacks | ValidCard$ Card.IsCommander+YouOwn | Execute$ TrigChangeZone | TriggerZones$ Graveyard | Secondary$ True | TriggerDescription$ Whenever your commander enters or attacks, you may pay {2}. If you do, return this card from your graveyard to your hand. +SVar:TrigChangeZone:AB$ ChangeZone | Cost$ 2 | Defined$ Self | Origin$ Graveyard | Destination$ Hand +DeckHas:Ability$Token +Oracle:Create a 0/1 black Wizard creature token with "Whenever you cast a noncreature spell, this token deals 1 damage to each opponent."\nWhenever your commander enters or attacks, you may pay {2}. If you do, return this card from your graveyard to your hand. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/warriors_resolve.txt b/forge-gui/res/cardsfolder/upcoming/warriors_resolve.txt new file mode 100644 index 00000000000..17b6338b4e6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/warriors_resolve.txt @@ -0,0 +1,8 @@ +Name:Warrior's Resolve +ManaCost:2 W +Types:Enchantment +S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddKeyword$ Training | Description$ Creatures you control have training. (Whenever a creature you control attacks with another creature with greater power, put a +1/+1 counter on the creature with lesser power.) +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ TrigDraw | TriggerZones$ Battlefield | IsPresent$ Creature.YouCtrl+counters_GE1_P1P1+attackedThisTurn | PresentCompare$ GE1 | TriggerDescription$ At the beginning of your end step, if you control a creature with a +1/+1 counter on it that attacked this turn, draw a card. +SVar:TrigDraw:DB$ Draw +SVar:PlayMain1:TRUE +Oracle:Creatures you control have training. (Whenever a creature you control attacks with another creature with greater power, put a +1/+1 counter on the creature with lesser power.)\nAt the beginning of your end step, if you control a creature with a +1/+1 counter on it that attacked this turn, draw a card. \ No newline at end of file From dab01c9b86a0c357280b254b77750f9fbb4947b4 Mon Sep 17 00:00:00 2001 From: Renato Filipe Vidal Santos <45150760+dracontes@users.noreply.github.com> Date: Tue, 30 Sep 2025 06:48:49 +0100 Subject: [PATCH 053/230] YEOE: 4 cards, batch 2 (#8451) --- .../cardsfolder/upcoming/hydroponics_architect.txt | 12 ++++++++++++ .../res/cardsfolder/upcoming/kavaron_consumed.txt | 7 +++++++ .../cardsfolder/upcoming/machinists_dismissal.txt | 7 +++++++ forge-gui/res/cardsfolder/upcoming/mine_security.txt | 11 +++++++++++ 4 files changed, 37 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/hydroponics_architect.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/kavaron_consumed.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/machinists_dismissal.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/mine_security.txt diff --git a/forge-gui/res/cardsfolder/upcoming/hydroponics_architect.txt b/forge-gui/res/cardsfolder/upcoming/hydroponics_architect.txt new file mode 100644 index 00000000000..c84afd5041e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/hydroponics_architect.txt @@ -0,0 +1,12 @@ +Name:Hydroponics Architect +ManaCost:U +Types:Creature Insect +PT:1/2 +K:Flying +T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigRandom | TriggerDescription$ Whenever this creature attacks, a random land card in your library perpetually becomes a basic Island in addition to its other types and gains "When this land enters, draw a card." +SVar:TrigRandom:DB$ ChooseCard | Choices$ Land.YouOwn | ChoiceZone$ Library | AtRandom$ True | Amount$ 1 | SubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | Defined$ ChosenCard | Types$ Basic,Island | Triggers$ LandETBDraw | Duration$ Perpetual | SubAbility$ DBCleanup +SVar:LandETBDraw:Mode$ ChangesZone | ValidCard$ Card.Self | Execute$ TrigDraw | Origin$ Any | Destination$ Battlefield | TriggerDescription$ When this land enters, draw a card. +SVar:TrigDraw:DB$ Draw +SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True +Oracle:Flying\nWhenever this creature attacks, a random land card in your library perpetually becomes a basic Island in addition to its other types and gains "When this land enters, draw a card." diff --git a/forge-gui/res/cardsfolder/upcoming/kavaron_consumed.txt b/forge-gui/res/cardsfolder/upcoming/kavaron_consumed.txt new file mode 100644 index 00000000000..f7d3e3faa2c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kavaron_consumed.txt @@ -0,0 +1,7 @@ +Name:Kavaron Consumed +ManaCost:3 R R +Types:Sorcery +A:SP$ ChangeZone | Hidden$ True | Origin$ Hand | Destination$ Battlefield | ChangeType$ Artifact.YouOwn,Creature.YouOwn | SelectPrompt$ Select an artifact or creature card in your hand | RememberChanged$ True | AtEOT$ Sacrifice | Optional$ True | SubAbility$ DBAnimatePerpetual | StackDescription$ REP You_{p:You} & your hand_their hand & Sacrifice_{p:You} sacrifices | SpellDescription$ You may put an artifact or creature card from your hand onto the battlefield. It perpetually becomes an artifact creature with base power and toughness 4/4 and gains haste. Sacrifice it at the beginning of the next end step. +SVar:DBAnimatePerpetual:DB$ Animate | Defined$ Remembered | Types$ Creature,Artifact | Keywords$ Haste | Power$ 4 | Toughness$ 4 | Duration$ Perpetual | SubAbility$ DBCleanup | StackDescription$ None +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:You may put an artifact or creature card from your hand onto the battlefield. It perpetually becomes an artifact creature with base power and toughness 4/4 and gains haste. Sacrifice it at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/upcoming/machinists_dismissal.txt b/forge-gui/res/cardsfolder/upcoming/machinists_dismissal.txt new file mode 100644 index 00000000000..3df073978a1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/machinists_dismissal.txt @@ -0,0 +1,7 @@ +Name:Machinist's Dismissal +ManaCost:4 U +Types:Instant +S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {1} less to cast for each artifact you control and each artifact card in your hand. +SVar:X:Count$ValidBattlefield,Hand Artifact.YouCtrl +A:SP$ Counter | TargetType$ Spell | TgtPrompt$ Select target noncreature spell | ValidTgts$ Card.nonCreature | SpellDescription$ Counter target noncreature spell. +Oracle:This spell costs {1} less to cast for each artifact you control and each artifact card in your hand.\nCounter target noncreature spell. diff --git a/forge-gui/res/cardsfolder/upcoming/mine_security.txt b/forge-gui/res/cardsfolder/upcoming/mine_security.txt new file mode 100644 index 00000000000..4d3038f8db9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/mine_security.txt @@ -0,0 +1,11 @@ +Name:Mine Security +ManaCost:1 R +Types:Creature Kavu Soldier +PT:3/1 +K:Trample +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigConjure | TriggerDescription$ When this creature enters, conjure a card named Flametongue Kavu into the top eight cards of your library at random. It perpetually gains "You may pay {0} rather than pay this spell's mana cost." +SVar:TrigConjure:DB$ MakeCard | Conjure$ True | Name$ Flametongue Kavu | Zone$ Library | LibraryPosition$ Count$Random.0.8 | RememberMade$ True | SubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | Defined$ Remembered | staticAbilities$ FreeCast | Duration$ Perpetual | SubAbility$ DBCleanup +SVar:FreeCast:Mode$ Continuous | MayPlay$ True | MayPlayAltManaCost$ 0 | MayPlayDontGrantZonePermissions$ True | Affected$ Card.Self | AffectedZone$ Hand,Graveyard,Exile,Library,Command | EffectZone$ All | Description$ You may pay {0} rather than pay this spell's mana cost. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:Trample\nWhen this creature enters, conjure a card named Flametongue Kavu into the top eight cards of your library at random. It perpetually gains "You may pay {0} rather than pay this spell's mana cost." From dcdf2b6e7aae931e2b1a2e04778354f375d6d5ed Mon Sep 17 00:00:00 2001 From: Renato Filipe Vidal Santos <45150760+dracontes@users.noreply.github.com> Date: Tue, 30 Sep 2025 07:35:08 +0100 Subject: [PATCH 054/230] YEOE: 4 cards, batch 3 (#8499) --- .../main/java/forge/game/trigger/TriggerType.java | 1 + .../src/main/java/forge/game/zone/MagicStack.java | 10 ++++++++++ .../res/cardsfolder/upcoming/monoist_gravliner.txt | 9 +++++++++ .../res/cardsfolder/upcoming/spirited_simulacrum.txt | 12 ++++++++++++ .../res/cardsfolder/upcoming/squadron_carrier.txt | 10 ++++++++++ .../res/cardsfolder/upcoming/thought_partition.txt | 8 ++++++++ 6 files changed, 50 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/monoist_gravliner.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/spirited_simulacrum.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/squadron_carrier.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/thought_partition.txt diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerType.java b/forge-game/src/main/java/forge/game/trigger/TriggerType.java index 4059b28d8d4..241712e5dc4 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerType.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerType.java @@ -139,6 +139,7 @@ public enum TriggerType { SpellCast(TriggerSpellAbilityCastOrCopy.class), SpellCastOrCopy(TriggerSpellAbilityCastOrCopy.class), SpellCopy(TriggerSpellAbilityCastOrCopy.class), + Stationed(TriggerCrewedSaddled.class), Surveil(TriggerSurveil.class), TakesInitiative(TriggerTakesInitiative.class), TapAll(TriggerTapAll.class), 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 2ef9d3cddb8..37f0eec264b 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -453,6 +453,16 @@ public class MagicStack /* extends MyObservable */ implements Iterable crews = sp.getPaidList("Tapped", true); + if (crews != null) { + for (Card c : crews) { + Map stationParams = AbilityKey.mapFromCard(sp.getHostCard()); + stationParams.put(AbilityKey.Crew, c); + game.getTriggerHandler().runTrigger(TriggerType.Stationed, stationParams, false); + } + } + } } else { // Run Copy triggers if (sp.isSpell()) { diff --git a/forge-gui/res/cardsfolder/upcoming/monoist_gravliner.txt b/forge-gui/res/cardsfolder/upcoming/monoist_gravliner.txt new file mode 100644 index 00000000000..d56da20c2db --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/monoist_gravliner.txt @@ -0,0 +1,9 @@ +Name:Monoist Gravliner +ManaCost:1 B +Types:Artifact Spacecraft +PT:2/3 +K:Station:6 +T:Mode$ Stationed | ValidCard$ Card.Self | ValidCrew$ Creature | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Whenever a creature stations this Spacecraft, that creature perpetually gains deathtouch and lifelink. +SVar:TrigPump:DB$ Pump | Defined$ Valid Creature.TriggeredCrew | KW$ Deathtouch & Lifelink | Duration$ Perpetual +S:Mode$ Continuous | Affected$ Card.Self+counters_GE6_CHARGE | AddType$ Creature | AddKeyword$ Flying & Deathtouch & Lifelink | Description$ STATION 6+ Flying, deathtouch, lifelink +Oracle:Whenever a creature stations this Spacecraft, that creature perpetually gains deathtouch and lifelink.\nStation\nSTATION 6+\nFlying, deathtouch, lifelink diff --git a/forge-gui/res/cardsfolder/upcoming/spirited_simulacrum.txt b/forge-gui/res/cardsfolder/upcoming/spirited_simulacrum.txt new file mode 100644 index 00000000000..aae28d246c9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/spirited_simulacrum.txt @@ -0,0 +1,12 @@ +Name:Spirited Simulacrum +ManaCost:4 +Types:Artifact Creature Robot +PT:2/1 +K:Warp:4 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSeekLand | TriggerDescription$ When this creature enters, seek a land card and put it onto the battlefield tapped. +SVar:TrigSeekLand:DB$ Seek | Type$ Card.Land | RememberFound$ True | SubAbility$ DBPut +SVar:DBPut:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | Defined$ Remembered | Tapped$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigSeekNonLand | TriggerDescription$ When this creature leaves the battlefield, seek a nonland card. +SVar:TrigSeekNonLand:DB$ Seek | Type$ Card.nonLand +Oracle:When this creature enters, seek a land card and put it onto the battlefield tapped.\nWhen this creature leaves the battlefield, seek a nonland card.\nWarp {4} diff --git a/forge-gui/res/cardsfolder/upcoming/squadron_carrier.txt b/forge-gui/res/cardsfolder/upcoming/squadron_carrier.txt new file mode 100644 index 00000000000..18ffa7c260c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/squadron_carrier.txt @@ -0,0 +1,10 @@ +Name:Squadron Carrier +ManaCost:2 W +Types:Artifact Spacecraft +PT:4/4 +K:Station:10 +S:Mode$ Continuous | Affected$ Spacecraft.YouCtrl | AddAbility$ ABConjureExh | Description$ Spacecraft you control have "Exhaust — {W}: Conjure a card named Starfighter Pilot onto the battlefield." +SVar:ABConjureExh:AB$ MakeCard | Cost$ W | Conjure$ True | Name$ Starfighter Pilot | Zone$ Battlefield | Exhaust$ True | SpellDescription$ Conjure a card named Starfighter Pilot onto the battlefield. +S:Mode$ Continuous | Affected$ Card.Self+counters_GE10_CHARGE | AddType$ Creature | AddStaticAbility$ CarrierStatic | Description$ STATION 10+ Creatures you control have flying. +SVar:CarrierStatic:Mode$ Continuous | Affected$ Creature.YouCtrl | AddKeyword$ Flying | Description$ Creatures you control have flying. +Oracle:Spacecraft you control have "Exhaust — {W}: Conjure a card named Starfighter Pilot onto the battlefield."\nStation\nSTATION 10+\nCreatures you control have flying. diff --git a/forge-gui/res/cardsfolder/upcoming/thought_partition.txt b/forge-gui/res/cardsfolder/upcoming/thought_partition.txt new file mode 100644 index 00000000000..3344644497f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/thought_partition.txt @@ -0,0 +1,8 @@ +Name:Thought Partition +ManaCost:W +Types:Sorcery +A:SP$ Reveal | ValidTgts$ Opponent | RevealAllValid$ Card.nonLand+TargetedPlayerCtrl | RememberRevealed$ True | SubAbility$ DBChooseCard | StackDescription$ REP Target opponent_{p:Targeted} & You may_{p:You} may & If you_If they | SpellDescription$ Target opponent reveals all nonland cards in their hand. You may choose one of those cards. If you do, it perpetually becomes white and its mana cost perpetually becomes {5}. +SVar:DBChooseCard:DB$ ChooseCard | Defined$ You | ChoiceZone$ Hand | Choices$ Card.IsRemembered | ChoiceTitle$ Choose a card that was revealed in targeted player's hand | SubAbility$ DBAnimate | StackDescription$ None +SVar:DBAnimate:DB$ Animate | Defined$ ChosenCard | Colors$ White | OverwriteColors$ True | ManaCost$ 5 | Duration$ Perpetual | SubAbility$ DBCleanup | StackDescription$ None +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:Target opponent reveals all nonland cards in their hand. You may choose one of those cards. If you do, it perpetually becomes white and its mana cost perpetually becomes {5}. From eeac0272ab4e520a692a98798411248817ce4ef8 Mon Sep 17 00:00:00 2001 From: Renato Filipe Vidal Santos <45150760+dracontes@users.noreply.github.com> Date: Tue, 30 Sep 2025 07:42:50 +0100 Subject: [PATCH 055/230] YEOE: 7 cards (#8476) --- .../java/forge/game/ability/effects/DraftEffect.java | 5 +++++ .../res/cardsfolder/upcoming/brood_astronomer.txt | 10 ++++++++++ .../res/cardsfolder/upcoming/eumidian_lifeseed.txt | 7 +++++++ forge-gui/res/cardsfolder/upcoming/mutable_pupa.txt | 8 ++++++++ .../res/cardsfolder/upcoming/network_marauder.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/prototype_x_8.txt | 12 ++++++++++++ .../res/cardsfolder/upcoming/sliver_weftwinder.txt | 11 +++++++++++ .../res/cardsfolder/upcoming/volatile_orbit.txt | 7 +++++++ 8 files changed, 68 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/brood_astronomer.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/eumidian_lifeseed.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/mutable_pupa.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/network_marauder.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/prototype_x_8.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sliver_weftwinder.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/volatile_orbit.txt diff --git a/forge-game/src/main/java/forge/game/ability/effects/DraftEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DraftEffect.java index 8c2ff61c0e8..e1a707bd1d8 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DraftEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DraftEffect.java @@ -73,6 +73,11 @@ import java.util.*; } Card made = game.getAction().moveTo(zone, c, sa, moveParams); + if (zone.equals(ZoneType.Battlefield)) { + if (sa.hasParam("Tapped")) { + made.setTapped(true); + } + } if (zone.equals(ZoneType.Exile)) { handleExiledWith(made, sa); if (sa.hasParam("ExileFaceDown")) { diff --git a/forge-gui/res/cardsfolder/upcoming/brood_astronomer.txt b/forge-gui/res/cardsfolder/upcoming/brood_astronomer.txt new file mode 100644 index 00000000000..2fb9cab7566 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/brood_astronomer.txt @@ -0,0 +1,10 @@ +Name:Brood Astronomer +ManaCost:1 G +Types:Creature Insect Scientist +PT:2/2 +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigDraft | TriggerDescription$ When this creature enters, you may sacrifice a land. If you do, draft a card from the Planets spellbook and put it onto the battlefield tapped. +SVar:TrigDraft:AB$ Draft | Cost$ Sac<1/Land> | SpellbookName$ Planets | Spellbook$ Adagia; Windswept Bastion,Evendo; Waking Haven,Kavaron; Memorial World,Susur Secundi; Void Altar,Uthros; Titanic Godcore | Zone$ Battlefield | Tapped$ True +A:AB$ Mana | Cost$ T | Produced$ Any | Amount$ X | SpellDescription$ Add one mana of any color. If you control a Planet with twelve or more charge counters on it, add three mana of any one color instead. +SVar:X:Count$Compare Y GE1.3.1 +SVar:Y:Count$Valid Planet.YouCtrl+counters_GE12_CHARGE +Oracle:When this creature enters, you may sacrifice a land. If you do, draft a card from the Planets spellbook and put it onto the battlefield tapped.\n{T}: Add one mana of any color. If you control a Planet with twelve or more charge counters on it, add three mana of any one color instead. diff --git a/forge-gui/res/cardsfolder/upcoming/eumidian_lifeseed.txt b/forge-gui/res/cardsfolder/upcoming/eumidian_lifeseed.txt new file mode 100644 index 00000000000..d1131611ef8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/eumidian_lifeseed.txt @@ -0,0 +1,7 @@ +Name:Eumidian Lifeseed +ManaCost:G +Types:Artifact +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigDraft | TriggerDescription$ When this artifact enters, draft a card from CARDNAME's spellbook. +SVar:TrigDraft:DB$ Draft | Spellbook$ Adagia; Windswept Bastion,Blast Zone,Cascading Cataracts,Contested War Zone,Deserted Temple,Dust Bowl,Kavaron; Memorial World,Mutavault,Nesting Grounds,Plaza of Heroes,Sunken Citadel,Susur Secundi; Void Altar,Terrain Generator,Uthros; Titanic Godcore +A:AB$ Mana | Cost$ T | Produced$ Any | Amount$ 1 | RestrictValid$ Activated.Land | SpellDescription$ Add one mana of any color. Spend this mana only to activate abilities of land sources. +Oracle:When this artifact enters, draft a card from Eumidian Lifeseed's spellbook.\n{T}: Add one mana of any color. Spend this mana only to activate abilities of land sources. diff --git a/forge-gui/res/cardsfolder/upcoming/mutable_pupa.txt b/forge-gui/res/cardsfolder/upcoming/mutable_pupa.txt new file mode 100644 index 00000000000..a0710448992 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/mutable_pupa.txt @@ -0,0 +1,8 @@ +Name:Mutable Pupa +ManaCost:G +Types:Creature Insect +PT:1/1 +K:Evolve +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever another creature you control enters, this creature perpetually gains flying if that creature has flying. The same is true for first strike, double strike, deathtouch, haste, hexproof, indestructible, lifelink, menace, reach, trample, and vigilance. +SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ First Strike & Flying & Deathtouch & Double Strike & Haste & Hexproof & Indestructible & Lifelink & Menace & Reach & Trample & Vigilance | SharedKeywordsZone$ Battlefield,Hand,Graveyard,Exile,Library,Command | SharedRestrictions$ Card.TriggeredCard | Duration$ Perpetual +Oracle:Evolve\nWhenever another creature you control enters, this creature perpetually gains flying if that creature has flying. The same is true for first strike, double strike, deathtouch, haste, hexproof, indestructible, lifelink, menace, reach, trample, and vigilance. diff --git a/forge-gui/res/cardsfolder/upcoming/network_marauder.txt b/forge-gui/res/cardsfolder/upcoming/network_marauder.txt new file mode 100644 index 00000000000..f9fd4c5afe9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/network_marauder.txt @@ -0,0 +1,8 @@ +Name:Network Marauder +ManaCost:2 U +Types:Artifact Creature Robot Pirate +PT:1/2 +K:Warp:1 U +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self,Artifact.YouCtrl+cmcGE3 | Execute$ TrigPump | TriggerDescription$ When this creature enters and whenever another artifact you control with mana value 3 or greater enters, all artifact creature cards and Spacecraft cards you own perpetually get +1/+0. +SVar:TrigPump:DB$ PumpAll | ValidCards$ Artifact.Creature+YouOwn+!token,Spacecraft.YouOwn+!token | PumpZone$ All | NumAtt$ +1 | Duration$ Perpetual +Oracle:When this creature enters and whenever another artifact you control with mana value 3 or greater enters, all artifact creature cards and Spacecraft cards you own perpetually get +1/+0.\nWarp {1}{U} diff --git a/forge-gui/res/cardsfolder/upcoming/prototype_x_8.txt b/forge-gui/res/cardsfolder/upcoming/prototype_x_8.txt new file mode 100644 index 00000000000..c543c1e3498 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/prototype_x_8.txt @@ -0,0 +1,12 @@ +Name:Prototype X-8 +ManaCost:6 U B +Types:Legendary Artifact Creature Robot Octopus +PT:8/8 +S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {1} less to cast for each creature card in your graveyard. +SVar:X:Count$ValidGraveyard Creature.YouOwn +T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Creature.wasCastByYou+YouCtrl+Other | Execute$ TrigSac | TriggerZones$ Battlefield | TriggerDescription$ Whenever another creature you control enters, if you cast it, sacrifice it. Conjure a duplicate of it onto the battlefield. The duplicate perpetually becomes a Robot artifact in addition to its other types. +SVar:TrigSac:DB$ SacrificeAll | Defined$ TriggeredCardLKICopy | SubAbility$ DBConjure +SVar:DBConjure:DB$ MakeCard | Conjure$ True | DefinedName$ TriggeredCardLKICopy | Zone$ Battlefield | RememberMade$ True | SubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Types$ Artifact,Robot | Duration$ Perpetual | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:This spell costs {1} less to cast for each creature card in your graveyard.\nWhenever another creature you control enters, if you cast it, sacrifice it. Conjure a duplicate of it onto the battlefield. The duplicate perpetually becomes a Robot artifact in addition to its other types. diff --git a/forge-gui/res/cardsfolder/upcoming/sliver_weftwinder.txt b/forge-gui/res/cardsfolder/upcoming/sliver_weftwinder.txt new file mode 100644 index 00000000000..ca92378651b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sliver_weftwinder.txt @@ -0,0 +1,11 @@ +Name:Sliver Weftwinder +ManaCost:W U B R G +Types:Legendary Creature Sliver +PT:6/6 +K:Warp:3 +S:Mode$ Continuous | Affected$ Sliver.YouOwn | AffectedZone$ Hand | AddKeyword$ Warp:3 | Description$ Sliver cards in your hand have warp {3}. +S:Mode$ Continuous | Affected$ Creature.Sliver+YouCtrl | AddTrigger$ WeftwinderTrig | Description$ Sliver creatures you control have "When this creature enters, conjure a random card from the Slivers spellbook into the top ten cards of your library at random, then draw a card." +SVar:WeftwinderTrig:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ WeftwinderConjure | TriggerDescription$ When this creature enters, conjure a random card from the Slivers spellbook into the top ten cards of your library at random, then draw a card. +SVar:WeftwinderConjure:DB$ MakeCard | Conjure$ True | AtRandom$ True | Zone$ Library | LibraryPosition$ Count$Random.0.10 | SpellbookName$ Slivers | Spellbook$ Belligerent Sliver,Bladeback Sliver,Blur Sliver,Bonescythe Sliver,Cleaving Sliver,Cloudshredder Sliver,Diffusion Sliver,Dregscape Sliver,Enduring Sliver,First Sliver's Chosen,Hollowhead Sliver,Lancer Sliver,Lavabelly Sliver,Leeching Sliver,Manaweft Sliver,Predatory Sliver,Scuttling Sliver,Sentinel Sliver,Sliver Hivelord,Spiteful Sliver,Steelform Sliver,Striking Sliver,Tempered Sliver,The First Sliver | SubAbility$ WeftwinderDraw +SVar:WeftwinderDraw:DB$ Draw +Oracle:Sliver cards in your hand have warp {3}.\nSliver creatures you control have "When this creature enters, conjure a random card from the Slivers spellbook into the top ten cards of your library at random, then draw a card."\nWarp {3} diff --git a/forge-gui/res/cardsfolder/upcoming/volatile_orbit.txt b/forge-gui/res/cardsfolder/upcoming/volatile_orbit.txt new file mode 100644 index 00000000000..093afb64140 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/volatile_orbit.txt @@ -0,0 +1,7 @@ +Name:Volatile Orbit +ManaCost:R G +Types:Enchantment +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDamage | IsPresent$ Planet.YouCtrl | TriggerDescription$ When this enchantment enters, if you control a Planet, this enchantment deals 4 damage to any target. +SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Any | NumDmg$ 4 +A:AB$ MakeCard | Cost$ 1 R G Sac<1/CARDNAME/this enchantment> | Conjure$ True | SorcerySpeed$ True | SpellbookName$ Planets | Spellbook$ Adagia; Windswept Bastion,Evendo; Waking Haven,Kavaron; Memorial World,Susur Secundi; Void Altar,Uthros; Titanic Godcore | Zone$ Battlefield | Tapped$ True | WithCounter$ CHARGE | WithCounterNum$ 8 | SpellDescription$ Conjure a card of your choice from the Planets spellbook onto the battlefield tapped with eight charge counters on it. Activate only as a sorcery. +Oracle:When this enchantment enters, if you control a Planet, this enchantment deals 4 damage to any target.\n{1}{R}{G}, Sacrifice this enchantment: Conjure a card of your choice from the Planets spellbook onto the battlefield tapped with eight charge counters on it. Activate only as a sorcery. From b90c14660cc28d66670f0f0dd4d621e3cc956469 Mon Sep 17 00:00:00 2001 From: Simisays <67333662+Simisays@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:22:24 +0200 Subject: [PATCH 056/230] FIC 12 Panorama/Holiday box cards (#8803) --- .../res/cardsfolder/upcoming/amarant_coral.txt | 10 ++++++++++ .../res/cardsfolder/upcoming/brilliant_wings.txt | 9 +++++++++ .../cardsfolder/upcoming/campsite_cuisine.txt | 12 ++++++++++++ .../upcoming/chaos_shrines_black_crystal.txt | 8 ++++++++ .../res/cardsfolder/upcoming/chocobo_camp.txt | 16 ++++++++++++++++ .../res/cardsfolder/upcoming/duelists_flame.txt | 12 ++++++++++++ .../upcoming/edea_possessed_sorceress.txt | 11 +++++++++++ .../res/cardsfolder/upcoming/fated_clash.txt | 8 ++++++++ .../res/cardsfolder/upcoming/fishing_gear.txt | 11 +++++++++++ .../cardsfolder/upcoming/flash_photography.txt | 8 ++++++++ .../upcoming/garland_royal_kidnapper.txt | 15 +++++++++++++++ .../upcoming/judgment_of_alexander.txt | 11 +++++++++++ forge-gui/res/lists/TypeLists.txt | 1 + 13 files changed, 132 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/amarant_coral.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/brilliant_wings.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/campsite_cuisine.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/chaos_shrines_black_crystal.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/duelists_flame.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/edea_possessed_sorceress.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/fated_clash.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/fishing_gear.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/flash_photography.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/garland_royal_kidnapper.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/judgment_of_alexander.txt diff --git a/forge-gui/res/cardsfolder/upcoming/amarant_coral.txt b/forge-gui/res/cardsfolder/upcoming/amarant_coral.txt new file mode 100644 index 00000000000..0f31bca6ea6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/amarant_coral.txt @@ -0,0 +1,10 @@ +Name:Amarant Coral +ManaCost:2 R G +Types:Legendary Creature Human Monk +PT:5/4 +K:Trample +S:Mode$ MustAttack | ValidCreature$ Card.Self | Description$ CARDNAME attacks each combat if able. +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Opponent | CombatDamage$ True | Execute$ TrigDmg | TriggerDescription$ No Mercy - Whenever CARDNAME deals combat damage to an opponent, it deals that much damage to each other opponent +SVar:TrigDmg:DB$ DamageAll | ValidPlayers$ OppNonTriggeredTarget | NumDmg$ X +SVar:X:TriggerCount$DamageAmount +Oracle:Trample\nAmarant Coral attacks each combat if able.\nNo Mercy - Whenever Amarant Coral deals combat damage to an opponent, it deals that much damage to each other opponent. diff --git a/forge-gui/res/cardsfolder/upcoming/brilliant_wings.txt b/forge-gui/res/cardsfolder/upcoming/brilliant_wings.txt new file mode 100644 index 00000000000..f61f68cb633 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/brilliant_wings.txt @@ -0,0 +1,9 @@ +Name:Brilliant Wings +ManaCost:1 W +Types:Enchantment Aura +K:Flash +K:Enchant:Creature.YouCtrl:creature you control +S:Mode$ Continuous | Affected$ Permanent.EnchantedBy | AddKeyword$ Flying & Hexproof | Description$ Enchanted permanent has flying and hexproof. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigAttach | TriggerDescription$ Whenever a creature you control enters, you may pay {1}. If you do, attach this Aura to that creature. +SVar:TrigAttach:AB$ Attach | Cost$ 1 | Defined$ TriggeredCardLKICopy +Oracle:Flash\nEnchant creature you control\nEnchanted permanent has flying and hexproof.\nWhenever a creature you control enters, you may pay {1}. If you do, attach this Aura to that creature. diff --git a/forge-gui/res/cardsfolder/upcoming/campsite_cuisine.txt b/forge-gui/res/cardsfolder/upcoming/campsite_cuisine.txt new file mode 100644 index 00000000000..7df4eacd01b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/campsite_cuisine.txt @@ -0,0 +1,12 @@ +Name:Campsite Cuisine +ManaCost:1 G +Types:Enchantment +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self,Creature.Legendary+YouCtrl | Execute$ TrigFood | TriggerDescription$ Whenever this enchantment or a legendary creature you control enters, create a Food token. +SVar:TrigFood:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_food_sac | TokenOwner$ You +T:Mode$ AttackersDeclared | Execute$ TrigImmediateTrig | TriggerZones$ Battlefield | AttackingPlayer$ You | TriggerDescription$ Whenever you attack, you may sacrifice X Foods. When you do, up to X target attacking creatures each get +3/+3 and gain trample and indestructible until end of turn. +SVar:TrigImmediateTrig:AB$ ImmediateTrigger | Cost$ Sac | Execute$ TrigPump | TriggerDescription$ When you do, up to X target attacking creatures each get +3/+3 and gain trample and indestructible until end of turn. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.attacking | TargetMin$ 0 | TargetMax$ X | TgtPrompt$ Select up to X target attacking creatures | NumAtt$ +3 | NumDef$ +3 | KW$ Trample & Indestructible +SVar:X:Count$xPaid +DeckHas:Ability$Token|Sacrifice & Type$Artifact|Food +DeckHints:Type$Legendary & Type$Creature +Oracle:Whenever this enchantment or a legendary creature you control enters, create a Food token.\nWhenever you attack, you may sacrifice X Foods. When you do, up to X target attacking creatures each get +3/+3 and gain trample and indestructible until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/chaos_shrines_black_crystal.txt b/forge-gui/res/cardsfolder/upcoming/chaos_shrines_black_crystal.txt new file mode 100644 index 00000000000..0b4bf7a07d0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/chaos_shrines_black_crystal.txt @@ -0,0 +1,8 @@ +Name:Chaos Shrine's Black Crystal +ManaCost:3 B +Types:Legendary Artifact +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouCtrl+!token | TriggerZones$ Battlefield | Execute$ TrigChange | TriggerDescription$ Whenever a nontoken creature you control dies, exile it. +SVar:TrigChange:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Exile +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ TrigReturn | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your upkeep, you may put a creature card exiled with CARDNAME onto the battlefield under your control with a finality counter on it. +SVar:TrigReturn:DB$ ChangeZone | Origin$ Exile | Destination$ Battlefield | Hidden$ True | ChangeType$ Creature.ExiledWithSource | GainControl$ True | WithCountersType$ FINALITY | WithCountersAmount$ 1 | ChangeNum$ 1 +Oracle:Whenever a nontoken creature you control dies, exile it.\nAt the beginning of your upkeep, you may put a creature card exiled with Chaos Shrine's Black Crystal onto the battlefield under your control with a finality counter on it. diff --git a/forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt b/forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt new file mode 100644 index 00000000000..80a452c8ac7 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt @@ -0,0 +1,16 @@ +Name:Chocobo Camp +ManaCost:no cost +Types:Land +Types:Legendary Land +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ LandTapped | ReplacementResult$ Updated | Description$ CARDNAME enters tapped unless you control a legendary creature. +SVar:LandTapped:DB$ Tap | Defined$ Self | ETB$ True | ConditionPresent$ Creature.Legendary+YouCtrl | ConditionCompare$ EQ0 +A:AB$ Mana | Cost$ T | Produced$ G | SubAbility$ DBEffect | SpellDescription$ Add {G}. +SVar:DBEffect:DB$ Effect | Triggers$ SpellCastTrig | SpellDescription$ When you next cast a Bird creature spell this turn, it enters with an additional +1/+1 counter on it. +SVar:SpellCastTrig:Mode$ SpellCast | ValidCard$ Bird | ValidActivatingPlayer$ You | Execute$ TrigCounter | OneOff$ True | TriggerDescription$ When you next cast a Bird creature spell this turn, it enters with an additional +1/+1 counter on it. +SVar:TrigCounter:DB$ Effect | ReplacementEffects$ ETBCounters | RememberObjects$ TriggeredCard | ForgetOnMoved$ Stack +SVar:ETBCounters:Event$ Moved | Origin$ Stack | Destination$ Battlefield | ValidCard$ Card.IsRemembered | ReplaceWith$ ETBAddExtraCounter | ReplacementResult$ Updated | Description$ That creature enters with an additional +1/+1 counters on it. +SVar:ETBAddExtraCounter:DB$ PutCounter | ETB$ True | Defined$ ReplacedCard | CounterType$ P1P1 | CounterNum$ 1 +A:AB$ Token | Cost$ 4 G G T | TokenScript$ g_2_2_bird_landfall | SpellDescription$ Creature a 2/2 green Bird creature token with "Whenever a land you control enters, this token gets +1/+0 until end of turn". +DeckHas:Ability$Token & Type$Artifact|Bird +DeckHints:Type$Legendary +Oracle:This Land enters tapped unless you control a legendary creature.\n{T}: Add {G}. When you next cast a Bird creature spell this turn, it enters with an additional +1/+1 counter on it.\n{4}{G}{G}, {T}, Creature a 2/2 green Bird creature token with "Whenever a land you control enters, this token gets +1/+0 until end of turn". diff --git a/forge-gui/res/cardsfolder/upcoming/duelists_flame.txt b/forge-gui/res/cardsfolder/upcoming/duelists_flame.txt new file mode 100644 index 00000000000..50fc69fe260 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/duelists_flame.txt @@ -0,0 +1,12 @@ +Name:Duelist's Flame +ManaCost:X R R +Types:Instant +A:SP$ Pump | ValidTgts$ Creature.blocked+YouCtrl | TgtPrompt$ Select target blocked creature you control | NumAtt$ +X | KW$ Trample | SubAbility$ DBAnimate | SpellDescription$ Until end of turn, target blocked creature you control gets +X/+0 and gains trample and "Whenever this creature deals combat damage to a player, look at that many cards from the top of your library. Exile up to one nonland card from among them and put the rest on the bottom of your library in a random order. You may cast the exiled card without paying its mana cost." +SVar:DBAnimate:DB$ Animate | Defined$ Targeted | Triggers$ DamageTrig +SVar:DamageTrig:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ TrigDig | CombatDamage$ True | TriggerDescription$ Whenever this creature deals combat damage to a player, look at that many cards from the top of your library. Exile up to one nonland card from among them and put the rest on the bottom of your library in a random order. You may cast the exiled card without paying its mana cost. +SVar:TrigDig:DB$ Dig | DigNum$ Y | ChangeNum$ 1 | ChangeValid$ Card.nonLand | Optional$ True | RememberChanged$ True | DestinationZone$ Exile | RestRandomOrder$ True | SubAbility$ DBCast +SVar:DBCast:DB$ Play | Valid$ Card.IsRemembered | ValidZone$ Exile | ValidSA$ Spell | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:Y:TriggerCount$DamageAmount +SVar:X:Count$xPaid +Oracle:Until end of turn, target blocked creature you control gets +X/+0 and gains trample and "Whenever this creature deals combat damage to a player, look at that many cards from the top of your library. Exile up to one nonland card from among them and put the rest on the bottom of your library in a random order. You may cast the exiled card without paying its mana cost." diff --git a/forge-gui/res/cardsfolder/upcoming/edea_possessed_sorceress.txt b/forge-gui/res/cardsfolder/upcoming/edea_possessed_sorceress.txt new file mode 100644 index 00000000000..3956b438c52 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/edea_possessed_sorceress.txt @@ -0,0 +1,11 @@ +Name:Edea, Possessed Sorceress +ManaCost:2 U B R +Types:Legendary Creature Human Warlock +PT:2/5 +K:Ward:2 +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | Execute$ TrigGainControl | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of combat on your turn, gain control of target creature an opponent controls until end of turn. Untap that creature. It gains haste until end of turn. +SVar:TrigGainControl:DB$ GainControl | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | LoseControl$ EOT | Untap$ True | AddKWs$ Haste +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | TriggerZones$ Battlefield | ValidCard$ Creature.YouDontOwn+YouCtrl | Execute$ TrigReturn | TriggerDescription$ Whenever a creature you control but don't own dies, return it to the battlefield under it's owner's control and you draw a card. +SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ All | Destination$ Battlefield | SubAbility$ TrigDraw +SVar:TrigDraw:DB$ Draw | NumCards$ 1 +Oracle:Ward {2}\nAt the beginning of combat on your turn, gain control of target creature an opponent controls until end of turn. Untap that creature. It gains haste until end of turn.\nWhenever a creature you control but don't own dies, return it to the battlefield under it's owner's control and you draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/fated_clash.txt b/forge-gui/res/cardsfolder/upcoming/fated_clash.txt new file mode 100644 index 00000000000..0f7d9d0da34 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fated_clash.txt @@ -0,0 +1,8 @@ +Name:Fated Clash +ManaCost:3 W W +Types:Sorcery +S:Mode$ CastWithFlash | ValidCard$ Card.Self | ValidSA$ Spell | IsPresent$ Creature.attacking | IsPresent2$ Creature.blocking | EffectZone$ All | Caster$ You | Description$ You may cast this spell as though it had flash if a creature is attacking and a creature is blocking. +A:SP$ Pump | KW$ Indestructible | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SubAbility$ DBPumpOpp | SpellDescription$ Target creature you control and target creature an opponent controls each gain indestructible until end of turn. +SVar:DBPumpOpp:DB$ Pump | KW$ Indestructible | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | SubAbility$ DBDestroyAll +SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Creature | SpellDescription$ Then destroy all creatures. +Oracle:You may cast this spell as though it had flash if a creature is attacking and a creature is blocking.\nTarget creature you control and target creature an opponent controls each gain indestructible until end of turn. Then destoy all creatures. diff --git a/forge-gui/res/cardsfolder/upcoming/fishing_gear.txt b/forge-gui/res/cardsfolder/upcoming/fishing_gear.txt new file mode 100644 index 00000000000..34453ba2dcc --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fishing_gear.txt @@ -0,0 +1,11 @@ +Name:Fishing Gear +ManaCost:3 +Types:Artifact Equipment +K:Equip:2 +T:Mode$ DamageDone | ValidSource$ Creature.EquippedBy | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigExile | TriggerZones$ Battlefield | TriggerDescription$ Whenever equipped creature deals combat damage to a player, exile the top card of that player's library. If it's a permanent card, you may put it onto the battlefield under your control. If you don't, create a 1/1 blue Fish creature token. +SVar:TrigExile:DB$ Dig | Defined$ TriggeredTarget | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBChange +SVar:DBChange:DB$ ChangeZone | ConditionDefined$ Remembered | ConditionPresent$ Permanent | GainControl$ True | Defined$ Remembered | Origin$ Exile | Optional$ True | Imprint$ True | Destination$ Battlefield | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ u_1_1_fish | TokenOwner$ You | ConditionDefined$ Imprinted | ConditionPresent$ Card | ConditionCompare$ EQ0 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True | ClearRemembered$ True +DeckHas:Ability$Token & Type$Fish +Oracle:Whenever equipped creature deals combat damage to a player, exile the top card of that player's library. If it's a permanent card, you may put it onto the battlefield under your control. If you don't, create a 1/1 blue Fish creature token.\nEquip {2} diff --git a/forge-gui/res/cardsfolder/upcoming/flash_photography.txt b/forge-gui/res/cardsfolder/upcoming/flash_photography.txt new file mode 100644 index 00000000000..9a46cfeed1c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/flash_photography.txt @@ -0,0 +1,8 @@ +Name:Flash Photography +ManaCost:2 U U +Types:Sorcery +K:Flashback:4 U U +S:Mode$ CastWithFlash | ValidCard$ Card.Self | ValidSA$ Spell.IsTargeting Valid Permanent.YouCtrl | EffectZone$ All | Caster$ You | Description$ You may cast this spell as though it had flash if it targets a permanent you control. +A:SP$ CopyPermanent | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | StackDescription$ SpellDescription | SpellDescription$ Create a token that's a copy of target permanent. +DeckHas:Ability$Token +Oracle:You may cast this spell as though it had flash if it targets a permanent you control.\nCreate a token that's a copy of target permanent.\nFlashback {4}{U)}U) diff --git a/forge-gui/res/cardsfolder/upcoming/garland_royal_kidnapper.txt b/forge-gui/res/cardsfolder/upcoming/garland_royal_kidnapper.txt new file mode 100644 index 00000000000..8f7fb85ac3e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/garland_royal_kidnapper.txt @@ -0,0 +1,15 @@ +Name:Garland, Royal Kidnapper +ManaCost:2 U B +Types:Legendary Creature Human Knight +PT:3/4 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMonarch | TriggerDescription$ When NICKNAME enters, you become the monarch. +SVar:TrigMonarch:DB$ BecomeMonarch | Defined$ You +T:Mode$ BecomeMonarch | ValidPlayer$ Opponent | TriggerZones$ Battlefield | Execute$ TrigEffect | TriggerDescription$ Whenever an opponent becomes the monarch, gain control of target creature that player controls for as long as they're the monarch. +SVar:TrigEffect:DB$ Effect | ImprintCards$ Targeted | ValidTgts$ Creature.ControlledBy TriggeredPlayer | TgtPrompt$ Choose target creature that player controls | RememberObjects$ TriggeredPlayer | Triggers$ ExileMe | StaticAbilities$ GainControl | Duration$ Permanent +SVar:GainControl:Mode$ Continuous | Affected$ Card.IsImprinted | CheckSVar$ X | SVarCompare$ EQ1 | GainControl$ You | Description$ You gain control of that creature for as long as that player is the monarch. +SVar:ExileMe:Mode$ Always | Static$ True | Execute$ ExileEffect | CheckSVar$ X | SVarCompare$ EQ0 +SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile +SVar:X:PlayerCountRememberedPlayer$HasPropertyisMonarch +S:Mode$ Continuous | Affected$ Creature.YouCtrl+YouDontOwn| AddPower$ 2 | AddToughness$ 2 | Description$ Creatures you control but don't own get +2/+2 and can't be sacrificed. +S:Mode$ CantSacrifice | ValidCard$ Creature.YouCtrl+YouDontOwn | Secondary$ True | SpellDescription$ Creatures you control but don't own get +2/+2 and can't be sacrificed. +Oracle:When Garland enters, you become the monarch.\nWhenever an opponent becomes the monarch, gain control of target creature that player controls for as long as they're the monarch.\nCreatures you control but don't own get +2/+2 and can't be sacrificed. diff --git a/forge-gui/res/cardsfolder/upcoming/judgment_of_alexander.txt b/forge-gui/res/cardsfolder/upcoming/judgment_of_alexander.txt new file mode 100644 index 00000000000..8674cdf8c81 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/judgment_of_alexander.txt @@ -0,0 +1,11 @@ +Name:Judgment of Alexander +ManaCost:2 W +Types:Instant +A:SP$ Effect | ReplacementEffects$ PreventDamage | AILogic$ Fog | SpellDescription$ Prevent all damage that would be dealt to you this turn by sources your opponents control. Whenever damage from a creature is prevented this way, each commander creature you control deals damage equal to its power to that creature. +SVar:PreventDamage:Event$ DamageDone | ActiveZones$ Command | ValidTarget$ You | ValidSource$ Card.OppCtrl,Emblem.OppCtrl | ReplaceWith$ DBTrigger | PreventionEffect$ True | ExecuteMode$ PerSource | Description$ Prevent all damage that would be dealt to you this turn by sources your opponents control. Whenever damage from a creature is prevented this way, each commander creature you control deals damage equal to its power to that creature. +SVar:DBTrigger:DB$ ImmediateTrigger | Execute$ TrigDamageCreature | RememberObjects$ ReplacedSource | ConditionCheckSVar$ Y | TriggerDescription$ Whenever damage from a creature is prevented this way, each commander creature you control deals damage equal to its power to that creature. +SVar:TrigDamageCreature:DB$ DealDamage | Defined$ DelayTriggerRemembered | DamageSource$ Valid Card.IsCommander+YouCtrl | NumDmg$ X +SVar:X:Count$Valid Card.IsCommander+YouCtrl$CardPower +SVar:Y:ReplacedSource$Valid Card.Creature +AI:RemoveDeck:NonCommander +Oracle:Prevent all damage that would be dealt to you this turn by sources your opponents control.\nWhenever damage from a creature is prevented this way, each commander creature you control deals damage equal to its power to that creature. diff --git a/forge-gui/res/lists/TypeLists.txt b/forge-gui/res/lists/TypeLists.txt index 89fabc6c904..02d36c77629 100644 --- a/forge-gui/res/lists/TypeLists.txt +++ b/forge-gui/res/lists/TypeLists.txt @@ -283,6 +283,7 @@ Snail:Snails Snake:Snakes Soldier:Soldiers Soltari:Soltaris +Sorcerer:Sorcerers Spawn:Spawns Specter:Specters Spellshaper:Spellshapers From bc6eed5c7d3d2e2570bf68c0f72006ad226d3abe Mon Sep 17 00:00:00 2001 From: Simisays <67333662+Simisays@users.noreply.github.com> Date: Tue, 30 Sep 2025 19:32:21 +0200 Subject: [PATCH 057/230] Update joel_resolute_survivor.txt (#8814) --- forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt b/forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt index d95aa4ce05d..698539d3352 100644 --- a/forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt +++ b/forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt @@ -6,7 +6,7 @@ K:Menace K:Partner - Survivors T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.token | Execute$ TrigPutCounter | ActivationLimit$ 1 | TriggerDescription$ Whenever a creature token dies, put a +1/+1 counter on NICKNAME and draw a card. This ability triggers only once each turn. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterNum$ 1 | CounterType$ P1P1 | SubAbility$ DBDraw -SVar:TrigDraw:DB$ Draw +SVar:DBDraw:DB$ Draw DeckHas:Ability$Counters DeckHints:Ability$Sacrifice|Token Oracle:Menace\nWhenever a creature token dies, put a +1/+1 counter on Joel and draw a card. This ability triggers only once each turn.\nPartner — Survivors (You can have two commanders if both have this ability.) From cdbf10f1f7acf8c7855573161994279ab0ac181a Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Tue, 30 Sep 2025 20:43:42 +0200 Subject: [PATCH 058/230] Change type desc (#8809) * add ChangeTypeDesc for basic land * Search N is up to * add some more ChangeTypeDesc * add some more ChangeTypeDesc * add some more ChangeTypeDesc * Update panorama * update landscape * Update Clan Monuments * Small Monument to Perfection change * update capenna fetchers * remove ChangeNum from basic Land searchers * better desc for fetch lands * remove uneeded ChangeTypeDesc --- forge-game/src/main/java/forge/game/CardTraitBase.java | 4 +++- .../forge/game/ability/effects/ChangeZoneEffect.java | 10 +++++++--- forge-gui/res/cardsfolder/a/aangs_journey.txt | 4 ++-- forge-gui/res/cardsfolder/a/abzan_monument.txt | 2 +- forge-gui/res/cardsfolder/a/ainok_guide.txt | 2 +- forge-gui/res/cardsfolder/a/analyze_the_pollen.txt | 2 +- forge-gui/res/cardsfolder/a/anchor_to_reality.txt | 2 +- forge-gui/res/cardsfolder/a/armillary_sphere.txt | 2 +- forge-gui/res/cardsfolder/a/assassins_trophy.txt | 2 +- forge-gui/res/cardsfolder/a/atalan_jackal.txt | 2 +- forge-gui/res/cardsfolder/a/attune_with_aether.txt | 2 +- forge-gui/res/cardsfolder/a/avatar_of_growth.txt | 2 +- forge-gui/res/cardsfolder/b/bad_river.txt | 2 +- forge-gui/res/cardsfolder/b/bant_panorama.txt | 2 +- .../b/beanstalk_giant_fertile_footsteps.txt | 2 +- forge-gui/res/cardsfolder/b/beneath_the_sands.txt | 2 +- .../res/cardsfolder/b/bitterthorn_nissas_animus.txt | 2 +- forge-gui/res/cardsfolder/b/blighted_woodland.txt | 2 +- forge-gui/res/cardsfolder/b/bloodstained_mire.txt | 2 +- .../cardsfolder/b/bloomvine_regent_claim_territory.txt | 2 +- forge-gui/res/cardsfolder/b/borderland_explorer.txt | 2 +- forge-gui/res/cardsfolder/b/borderland_ranger.txt | 2 +- forge-gui/res/cardsfolder/b/boundless_realms.txt | 2 +- forge-gui/res/cardsfolder/b/bountiful_landscape.txt | 2 +- forge-gui/res/cardsfolder/b/braidwood_sextant.txt | 2 +- forge-gui/res/cardsfolder/b/brave_the_wilds.txt | 2 +- forge-gui/res/cardsfolder/b/brokers_hideout.txt | 2 +- forge-gui/res/cardsfolder/b/burnished_hart.txt | 2 +- forge-gui/res/cardsfolder/b/bushwhack.txt | 2 +- forge-gui/res/cardsfolder/c/cabaretti_courtyard.txt | 2 +- forge-gui/res/cardsfolder/c/campus_guide.txt | 2 +- forge-gui/res/cardsfolder/c/caravan_vigil.txt | 2 +- forge-gui/res/cardsfolder/c/celebrate_the_harvest.txt | 2 +- forge-gui/res/cardsfolder/c/centaur_rootcaster.txt | 2 +- forge-gui/res/cardsfolder/c/civic_wayfinder.txt | 2 +- forge-gui/res/cardsfolder/c/cleansing_wildfire.txt | 2 +- forge-gui/res/cardsfolder/c/collective_voyage.txt | 2 +- forge-gui/res/cardsfolder/c/coming_attraction.txt | 2 +- forge-gui/res/cardsfolder/c/contaminated_landscape.txt | 2 +- forge-gui/res/cardsfolder/c/cultivate.txt | 2 +- forge-gui/res/cardsfolder/d/dawntreader_elk.txt | 2 +- forge-gui/res/cardsfolder/d/deathbellow_war_cry.txt | 2 +- forge-gui/res/cardsfolder/d/deathsprout.txt | 2 +- forge-gui/res/cardsfolder/d/deceptive_landscape.txt | 2 +- forge-gui/res/cardsfolder/d/deep_reconnaissance.txt | 2 +- forge-gui/res/cardsfolder/d/demolition_field.txt | 4 ++-- forge-gui/res/cardsfolder/d/dig_up.txt | 2 +- forge-gui/res/cardsfolder/d/diligent_farmhand.txt | 2 +- forge-gui/res/cardsfolder/d/dire_strain_rampage.txt | 2 +- forge-gui/res/cardsfolder/d/dragonstorm.txt | 2 +- forge-gui/res/cardsfolder/d/dreamscape_artist.txt | 2 +- .../res/cardsfolder/d/druid_of_the_emerald_grove.txt | 2 +- forge-gui/res/cardsfolder/e/ecologists_terrarium.txt | 2 +- forge-gui/res/cardsfolder/e/edge_of_autumn.txt | 2 +- forge-gui/res/cardsfolder/e/elanor_gardner.txt | 2 +- forge-gui/res/cardsfolder/e/elfhame_sanctuary.txt | 2 +- forge-gui/res/cardsfolder/e/elvish_pioneer.txt | 2 +- forge-gui/res/cardsfolder/e/embermouth_sentinel.txt | 4 ++-- forge-gui/res/cardsfolder/e/embodiment_of_spring.txt | 2 +- forge-gui/res/cardsfolder/e/emergent_sequence.txt | 2 +- .../res/cardsfolder/e/encroaching_dragonstorm.txt | 2 +- forge-gui/res/cardsfolder/e/enigma_ridges.txt | 2 +- forge-gui/res/cardsfolder/e/enlightened_tutor.txt | 2 +- forge-gui/res/cardsfolder/e/entish_restoration.txt | 2 +- forge-gui/res/cardsfolder/e/environmental_sciences.txt | 2 +- forge-gui/res/cardsfolder/e/escape_tunnel.txt | 2 +- forge-gui/res/cardsfolder/e/esper_panorama.txt | 2 +- forge-gui/res/cardsfolder/e/evolution_charm.txt | 2 +- forge-gui/res/cardsfolder/e/evolving_wilds.txt | 2 +- forge-gui/res/cardsfolder/e/exploding_borders.txt | 2 +- forge-gui/res/cardsfolder/e/explosive_vegetation.txt | 2 +- forge-gui/res/cardsfolder/f/fabled_passage.txt | 2 +- forge-gui/res/cardsfolder/f/far_wanderings.txt | 2 +- forge-gui/res/cardsfolder/f/farfinder.txt | 2 +- forge-gui/res/cardsfolder/f/farhaven_elf.txt | 2 +- forge-gui/res/cardsfolder/f/fertilid.txt | 2 +- forge-gui/res/cardsfolder/f/fertilids_favor.txt | 2 +- forge-gui/res/cardsfolder/f/field_of_ruin.txt | 2 +- forge-gui/res/cardsfolder/f/firebrand_ranger.txt | 2 +- forge-gui/res/cardsfolder/f/flare_of_cultivation.txt | 2 +- forge-gui/res/cardsfolder/f/flood_plain.txt | 2 +- forge-gui/res/cardsfolder/f/flooded_strand.txt | 2 +- forge-gui/res/cardsfolder/f/flower_flourish.txt | 2 +- forge-gui/res/cardsfolder/f/font_of_fertility.txt | 2 +- forge-gui/res/cardsfolder/f/forceful_cultivator.txt | 2 +- forge-gui/res/cardsfolder/f/foreboding_landscape.txt | 2 +- forge-gui/res/cardsfolder/f/forestfolk.txt | 2 +- forge-gui/res/cardsfolder/f/fork_in_the_road.txt | 2 +- forge-gui/res/cardsfolder/f/fountainport_bell.txt | 2 +- forge-gui/res/cardsfolder/f/frenzied_tilling.txt | 2 +- forge-gui/res/cardsfolder/f/from_the_ashes.txt | 2 +- forge-gui/res/cardsfolder/f/frontier_guide.txt | 2 +- forge-gui/res/cardsfolder/g/geomancers_gambit.txt | 2 +- forge-gui/res/cardsfolder/g/ghost_quarter.txt | 2 +- forge-gui/res/cardsfolder/g/giant_ladybug.txt | 2 +- forge-gui/res/cardsfolder/g/go_forth.txt | 2 +- forge-gui/res/cardsfolder/g/grasslands.txt | 2 +- forge-gui/res/cardsfolder/g/greater_tanuki.txt | 2 +- forge-gui/res/cardsfolder/g/greenseeker.txt | 2 +- forge-gui/res/cardsfolder/g/grixis_panorama.txt | 2 +- forge-gui/res/cardsfolder/g/grow_from_the_ashes.txt | 2 +- forge-gui/res/cardsfolder/g/growth_charm.txt | 2 +- forge-gui/res/cardsfolder/g/growth_spasm.txt | 2 +- forge-gui/res/cardsfolder/h/harrow.txt | 2 +- forge-gui/res/cardsfolder/h/harvest_season.txt | 2 +- forge-gui/res/cardsfolder/h/heaped_harvest.txt | 2 +- forge-gui/res/cardsfolder/h/herd_migration.txt | 2 +- forge-gui/res/cardsfolder/h/hithlain_rope.txt | 2 +- forge-gui/res/cardsfolder/h/horizon_boughs.txt | 2 +- forge-gui/res/cardsfolder/h/horizon_seeker.txt | 2 +- forge-gui/res/cardsfolder/h/horizon_spellbomb.txt | 2 +- .../huatli_poet_of_unity_roar_of_the_fifth_people.txt | 2 +- .../i/invasion_of_zendikar_awakened_skyclave.txt | 2 +- forge-gui/res/cardsfolder/j/jaheiras_respite.txt | 2 +- forge-gui/res/cardsfolder/j/jeskai_monument.txt | 2 +- forge-gui/res/cardsfolder/j/journey_for_the_elixir.txt | 2 +- forge-gui/res/cardsfolder/j/journey_of_discovery.txt | 2 +- forge-gui/res/cardsfolder/j/journeyers_kite.txt | 2 +- forge-gui/res/cardsfolder/j/jund_panorama.txt | 2 +- forge-gui/res/cardsfolder/j/jungle_wayfinder.txt | 2 +- forge-gui/res/cardsfolder/k/kamigawa_charm.txt | 2 +- .../k/kellan_the_fae_blooded_birthright_boon.txt | 2 +- forge-gui/res/cardsfolder/k/keys_to_the_house.txt | 2 +- .../res/cardsfolder/k/khalni_heart_expedition.txt | 2 +- forge-gui/res/cardsfolder/k/knowledge_exploitation.txt | 2 +- .../res/cardsfolder/k/kodama_of_the_west_tree.txt | 2 +- forge-gui/res/cardsfolder/k/kodamas_reach.txt | 2 +- forge-gui/res/cardsfolder/k/krenkos_buzzcrusher.txt | 2 +- forge-gui/res/cardsfolder/k/krosan_tusker.txt | 2 +- forge-gui/res/cardsfolder/l/land_tax.txt | 2 +- forge-gui/res/cardsfolder/l/larval_scoutlander.txt | 2 +- forge-gui/res/cardsfolder/l/lay_of_the_land.txt | 2 +- forge-gui/res/cardsfolder/l/loam_larva.txt | 2 +- forge-gui/res/cardsfolder/l/lumbering_worldwagon.txt | 2 +- forge-gui/res/cardsfolder/m/maestros_theater.txt | 2 +- forge-gui/res/cardsfolder/m/magmatic_hellkite.txt | 2 +- forge-gui/res/cardsfolder/m/many_partings.txt | 2 +- forge-gui/res/cardsfolder/m/map_the_wastes.txt | 2 +- forge-gui/res/cardsfolder/m/mardu_monument.txt | 2 +- forge-gui/res/cardsfolder/m/marsh_flats.txt | 2 +- forge-gui/res/cardsfolder/m/merchant_scroll.txt | 2 +- forge-gui/res/cardsfolder/m/migration_path.txt | 2 +- forge-gui/res/cardsfolder/m/migratory_greathorn.txt | 2 +- forge-gui/res/cardsfolder/m/misty_rainforest.txt | 2 +- forge-gui/res/cardsfolder/m/moggcatcher.txt | 2 +- .../res/cardsfolder/m/moldering_gym_weight_room.txt | 2 +- forge-gui/res/cardsfolder/m/monument_to_perfection.txt | 2 +- forge-gui/res/cardsfolder/m/mountain_valley.txt | 2 +- forge-gui/res/cardsfolder/m/murasa.txt | 2 +- forge-gui/res/cardsfolder/m/murasa_rootgrazer.txt | 2 +- forge-gui/res/cardsfolder/m/mycosynth_wellspring.txt | 2 +- forge-gui/res/cardsfolder/m/myr_turbine.txt | 2 +- forge-gui/res/cardsfolder/m/myriad_landscape.txt | 2 +- forge-gui/res/cardsfolder/m/mystical_tutor.txt | 2 +- forge-gui/res/cardsfolder/n/nahiri_the_harbinger.txt | 2 +- forge-gui/res/cardsfolder/n/natural_balance.txt | 2 +- forge-gui/res/cardsfolder/n/natural_connection.txt | 2 +- forge-gui/res/cardsfolder/n/naya_panorama.txt | 2 +- forge-gui/res/cardsfolder/n/new_frontiers.txt | 2 +- forge-gui/res/cardsfolder/n/nissa_worldwaker.txt | 2 +- forge-gui/res/cardsfolder/n/nissas_expedition.txt | 2 +- forge-gui/res/cardsfolder/n/nissas_renewal.txt | 2 +- forge-gui/res/cardsfolder/o/oashra_cultivator.txt | 2 +- forge-gui/res/cardsfolder/o/oath_of_lieges.txt | 2 +- forge-gui/res/cardsfolder/o/obscura_storefront.txt | 2 +- forge-gui/res/cardsfolder/o/old_growth_dryads.txt | 2 +- forge-gui/res/cardsfolder/o/omen_of_the_hunt.txt | 2 +- forge-gui/res/cardsfolder/o/ominous_parcel.txt | 2 +- forge-gui/res/cardsfolder/o/ondu_giant.txt | 2 +- forge-gui/res/cardsfolder/o/one_with_nature.txt | 2 +- forge-gui/res/cardsfolder/o/open_the_armory.txt | 2 +- forge-gui/res/cardsfolder/o/ordeal_of_nylea.txt | 2 +- forge-gui/res/cardsfolder/o/orochi_colony.txt | 2 +- forge-gui/res/cardsfolder/p/path_of_the_animist.txt | 2 +- forge-gui/res/cardsfolder/p/path_to_exile.txt | 2 +- forge-gui/res/cardsfolder/p/path_to_the_festival.txt | 2 +- forge-gui/res/cardsfolder/p/path_to_the_world_tree.txt | 2 +- forge-gui/res/cardsfolder/p/peregrination.txt | 2 +- forge-gui/res/cardsfolder/p/perilous_landscape.txt | 2 +- forge-gui/res/cardsfolder/p/pilgrims_eye.txt | 2 +- forge-gui/res/cardsfolder/p/planar_birth.txt | 2 +- forge-gui/res/cardsfolder/p/point_the_way.txt | 2 +- forge-gui/res/cardsfolder/p/polluted_delta.txt | 2 +- .../res/cardsfolder/p/priest_of_the_wakening_sun.txt | 2 +- forge-gui/res/cardsfolder/p/primal_druid.txt | 2 +- forge-gui/res/cardsfolder/p/primal_growth.txt | 2 +- forge-gui/res/cardsfolder/p/primeval_herald.txt | 2 +- forge-gui/res/cardsfolder/p/prismatic_vista.txt | 2 +- forge-gui/res/cardsfolder/p/promising_vein.txt | 2 +- forge-gui/res/cardsfolder/p/purestrain_genestealer.txt | 2 +- forge-gui/res/cardsfolder/q/quiet_speculation.txt | 2 +- forge-gui/res/cardsfolder/q/quirion_trailblazer.txt | 2 +- forge-gui/res/cardsfolder/r/radioactive_spider.txt | 3 +-- forge-gui/res/cardsfolder/r/rampaging_growth.txt | 2 +- forge-gui/res/cardsfolder/r/rampant_growth.txt | 2 +- forge-gui/res/cardsfolder/r/rampant_rejuvenator.txt | 2 +- forge-gui/res/cardsfolder/r/rampart_architect.txt | 2 +- forge-gui/res/cardsfolder/r/ranging_raptors.txt | 2 +- .../res/cardsfolder/r/realms_befitting_my_majesty.txt | 2 +- forge-gui/res/cardsfolder/r/reckless_handling.txt | 2 +- forge-gui/res/cardsfolder/r/renegade_map.txt | 2 +- forge-gui/res/cardsfolder/r/renewal.txt | 2 +- forge-gui/res/cardsfolder/r/return_from_the_wilds.txt | 2 +- forge-gui/res/cardsfolder/r/rites_of_spring.txt | 2 +- forge-gui/res/cardsfolder/r/riveteers_overlook.txt | 2 +- forge-gui/res/cardsfolder/r/road_ruin.txt | 2 +- forge-gui/res/cardsfolder/r/roamers_routine.txt | 2 +- forge-gui/res/cardsfolder/r/rocky_tar_pit.txt | 2 +- forge-gui/res/cardsfolder/r/roiling_regrowth.txt | 2 +- forge-gui/res/cardsfolder/r/ruin_in_their_wake.txt | 2 +- .../res/cardsfolder/rebalanced/a-ominous_parcel.txt | 2 +- .../cardsfolder/rebalanced/a-scout_the_wilderness.txt | 2 +- forge-gui/res/cardsfolder/s/safewright_quest.txt | 2 +- .../res/cardsfolder/s/sagu_wildling_roost_seek.txt | 2 +- forge-gui/res/cardsfolder/s/sakura_tribe_elder.txt | 2 +- forge-gui/res/cardsfolder/s/samut_the_tested.txt | 6 +++--- forge-gui/res/cardsfolder/s/sandworm.txt | 2 +- forge-gui/res/cardsfolder/s/sarkhans_triumph.txt | 2 +- forge-gui/res/cardsfolder/s/scalding_tarn.txt | 2 +- forge-gui/res/cardsfolder/s/scholarship_sponsor.txt | 2 +- forge-gui/res/cardsfolder/s/scout_the_wilderness.txt | 2 +- forge-gui/res/cardsfolder/s/scouting_trek.txt | 2 +- forge-gui/res/cardsfolder/s/scrapyard_recombiner.txt | 2 +- forge-gui/res/cardsfolder/s/seahunter.txt | 2 +- forge-gui/res/cardsfolder/s/search_for_tomorrow.txt | 2 +- forge-gui/res/cardsfolder/s/seek_the_horizon.txt | 2 +- forge-gui/res/cardsfolder/s/seething_landscape.txt | 2 +- forge-gui/res/cardsfolder/s/settle_the_wreckage.txt | 2 +- forge-gui/res/cardsfolder/s/shadow_rite_priest.txt | 2 +- forge-gui/res/cardsfolder/s/shattered_landscape.txt | 2 +- forge-gui/res/cardsfolder/s/sheltering_landscape.txt | 2 +- forge-gui/res/cardsfolder/s/shire_terrace.txt | 2 +- forge-gui/res/cardsfolder/s/silkwing_scout.txt | 2 +- forge-gui/res/cardsfolder/s/silverglade_pathfinder.txt | 2 +- forge-gui/res/cardsfolder/s/skittering_surveyor.txt | 2 +- forge-gui/res/cardsfolder/s/skyshroud_poacher.txt | 2 +- forge-gui/res/cardsfolder/s/solemn_simulacrum.txt | 2 +- forge-gui/res/cardsfolder/s/solve_the_equation.txt | 2 +- forge-gui/res/cardsfolder/s/spider_bot.txt | 2 +- .../cardsfolder/s/spider_man_brooklyn_visionary.txt | 2 +- forge-gui/res/cardsfolder/s/spineseeker_centipede.txt | 2 +- forge-gui/res/cardsfolder/s/sporocyst.txt | 2 +- forge-gui/res/cardsfolder/s/spring_mind.txt | 2 +- forge-gui/res/cardsfolder/s/springbloom_druid.txt | 2 +- forge-gui/res/cardsfolder/s/sprouting_vines.txt | 2 +- forge-gui/res/cardsfolder/s/subway_train.txt | 2 +- forge-gui/res/cardsfolder/s/sultai_monument.txt | 2 +- forge-gui/res/cardsfolder/s/summon_fenrir.txt | 2 +- .../s/sundering_eruption_volcanic_fissure.txt | 2 +- forge-gui/res/cardsfolder/s/supply_demand.txt | 2 +- forge-gui/res/cardsfolder/s/surveyors_scope.txt | 2 +- .../res/cardsfolder/s/sutina_speaker_of_the_tajuru.txt | 2 +- .../res/cardsfolder/s/sword_of_hearth_and_home.txt | 2 +- forge-gui/res/cardsfolder/s/sword_of_the_animist.txt | 2 +- forge-gui/res/cardsfolder/s/sylvan_ranger.txt | 2 +- forge-gui/res/cardsfolder/t/temur_monument.txt | 2 +- forge-gui/res/cardsfolder/t/terminal_moraine.txt | 2 +- forge-gui/res/cardsfolder/t/terramorph.txt | 2 +- forge-gui/res/cardsfolder/t/terramorphic_expanse.txt | 2 +- .../t/thaumatic_compass_spires_of_orazca.txt | 2 +- forge-gui/res/cardsfolder/t/thawing_glaciers.txt | 2 +- forge-gui/res/cardsfolder/t/the_weatherseed_treaty.txt | 2 +- forge-gui/res/cardsfolder/t/they_went_this_way.txt | 2 +- forge-gui/res/cardsfolder/t/thirsting_roots.txt | 2 +- .../res/cardsfolder/t/threats_around_every_corner.txt | 2 +- forge-gui/res/cardsfolder/t/thunderherd_migration.txt | 2 +- forge-gui/res/cardsfolder/t/time_of_need.txt | 2 +- forge-gui/res/cardsfolder/t/topiary_stomper.txt | 2 +- forge-gui/res/cardsfolder/t/trail_of_mystery.txt | 2 +- forge-gui/res/cardsfolder/t/tranquil_landscape.txt | 2 +- .../res/cardsfolder/t/travel_through_caradhras.txt | 2 +- forge-gui/res/cardsfolder/t/travelers_amulet.txt | 2 +- forge-gui/res/cardsfolder/t/traverse_the_outlands.txt | 2 +- forge-gui/res/cardsfolder/t/traverse_the_ulvenwald.txt | 2 +- forge-gui/res/cardsfolder/t/twisted_landscape.txt | 2 +- forge-gui/res/cardsfolder/u/undercity.txt | 2 +- forge-gui/res/cardsfolder/u/unmarked_grave.txt | 2 +- forge-gui/res/cardsfolder/u/untamed_wilds.txt | 2 +- forge-gui/res/cardsfolder/v/vastwood_surge.txt | 2 +- forge-gui/res/cardsfolder/v/verdant_catacombs.txt | 2 +- forge-gui/res/cardsfolder/v/verdant_confluence.txt | 2 +- forge-gui/res/cardsfolder/v/verdant_crescendo.txt | 2 +- forge-gui/res/cardsfolder/v/verdant_mastery.txt | 2 +- forge-gui/res/cardsfolder/v/veteran_explorer.txt | 2 +- forge-gui/res/cardsfolder/v/vibrant_cityscape.txt | 2 +- .../res/cardsfolder/v/viewpoint_synchronization.txt | 2 +- forge-gui/res/cardsfolder/v/viridian_emissary.txt | 2 +- forge-gui/res/cardsfolder/v/volatile_fault.txt | 2 +- forge-gui/res/cardsfolder/w/wanderers_twig.txt | 2 +- forge-gui/res/cardsfolder/w/warped_landscape.txt | 2 +- forge-gui/res/cardsfolder/w/wave_of_vitriol.txt | 2 +- forge-gui/res/cardsfolder/w/wayfarers_bauble.txt | 2 +- forge-gui/res/cardsfolder/w/weird_harvest.txt | 2 +- forge-gui/res/cardsfolder/w/white_orchid_phantom.txt | 2 +- forge-gui/res/cardsfolder/w/wild_crocodile.txt | 2 +- forge-gui/res/cardsfolder/w/wild_endeavor.txt | 2 +- forge-gui/res/cardsfolder/w/wild_field_scarecrow.txt | 2 +- forge-gui/res/cardsfolder/w/wild_wanderer.txt | 2 +- forge-gui/res/cardsfolder/w/winds_of_abandon.txt | 2 +- forge-gui/res/cardsfolder/w/windswept_heath.txt | 2 +- forge-gui/res/cardsfolder/w/wooded_foothills.txt | 2 +- forge-gui/res/cardsfolder/w/woodland_investigation.txt | 2 +- forge-gui/res/cardsfolder/w/world_map.txt | 2 +- forge-gui/res/cardsfolder/y/yavimaya_elder.txt | 2 +- forge-gui/res/cardsfolder/y/yavimaya_granger.txt | 2 +- forge-gui/res/cardsfolder/y/you_happen_on_a_glade.txt | 2 +- forge-gui/res/tokenscripts/c_a_lander_sac_search.txt | 2 +- 307 files changed, 320 insertions(+), 315 deletions(-) diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index f707f2b0aea..4d84640e782 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -62,7 +62,9 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView, /** Keys of descriptive (text) parameters. */ private static final ImmutableList descriptiveKeys = ImmutableList.builder() - .add("Description", "SpellDescription", "StackDescription", "TriggerDescription").build(); + .add("Description", "SpellDescription", "StackDescription", "TriggerDescription") + .add("ChangeTypeDesc") + .build(); /** * Keys that should not changed 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 948d3a23bda..d2ef0a58164 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 @@ -31,6 +31,7 @@ import org.apache.commons.lang3.tuple.Pair; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public class ChangeZoneEffect extends SpellAbilityEffect { @@ -103,6 +104,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } final String destination = sa.getParam("Destination"); + final int num = sa.hasParam("ChangeNum") ? AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa) : 1; String type = "card"; boolean defined = false; if (sa.hasParam("ChangeTypeDesc")) { @@ -117,12 +119,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect { type = Lang.joinHomogenous(tgts); defined = true; } else if (sa.hasParam("ChangeType") && !sa.getParam("ChangeType").equals("Card")) { - final String ct = sa.getParam("ChangeType"); - type = CardType.CoreType.isValidEnum(ct) ? ct.toLowerCase() : ct; + List typeList = Arrays.stream(sa.getParam("ChangeType").split(",")).map(ct -> CardType.isACardType(ct) ? ct.toLowerCase() : ct).collect(Collectors.toList()); + type = Lang.joinHomogenous(typeList, null, num == 1 ? "or" : "and/or"); } final String cardTag = type.contains("card") ? "" : " card"; - final int num = sa.hasParam("ChangeNum") ? AbilityUtils.calculateAmount(host, sa.getParam("ChangeNum"), sa) : 1; boolean tapped = sa.hasParam("Tapped"); boolean attacking = sa.hasParam("Attacking"); if (sa.isNinjutsu()) { @@ -152,6 +153,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } else { sb.append(" for "); } + if (num != 1) { + sb.append(" up to "); + } sb.append(Lang.nounWithNumeralExceptOne(num, type + cardTag)).append(", "); if (!sa.hasParam("NoReveal") && ZoneType.smartValueOf(destination) != null && ZoneType.smartValueOf(destination).isHidden()) { if (choosers.size() == 1) { diff --git a/forge-gui/res/cardsfolder/a/aangs_journey.txt b/forge-gui/res/cardsfolder/a/aangs_journey.txt index 160b3dc006a..2766c7431d6 100644 --- a/forge-gui/res/cardsfolder/a/aangs_journey.txt +++ b/forge-gui/res/cardsfolder/a/aangs_journey.txt @@ -2,8 +2,8 @@ Name:Aang's Journey ManaCost:2 Types:Sorcery Lesson K:Kicker:2 -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ0 | SubAbility$ DBChangeZone | SpellDescription$ Search your library for a basic land card. If this spell was kicked, instead search your library for a basic land card and a Shrine card. Reveal those cards, put them into your hand, then shuffle. You gain 2 life. -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ EACH Land.Basic & Shrine | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ1 | SubAbility$ DBGainLife +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ0 | SubAbility$ DBChangeZone | SpellDescription$ Search your library for a basic land card. If this spell was kicked, instead search your library for a basic land card and a Shrine card. Reveal those cards, put them into your hand, then shuffle. You gain 2 life. +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ EACH Land.Basic & Shrine | ChangeTypeDesc$ basic land card and a Shrine card | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ1 | SubAbility$ DBGainLife SVar:DBGainLife:DB$ GainLife | LifeAmount$ 2 SVar:X:Count$TimesKicked Oracle:Kicker {2} (You may pay an additional {2} as you cast this spell.)\nSearch your library for a basic land card. If this spell was kicked, instead search your library for a basic land card and a Shrine card. Reveal those cards, put them into your hand, then shuffle.\nYou gain 2 life. diff --git a/forge-gui/res/cardsfolder/a/abzan_monument.txt b/forge-gui/res/cardsfolder/a/abzan_monument.txt index 9a098358311..d328c9cc84e 100644 --- a/forge-gui/res/cardsfolder/a/abzan_monument.txt +++ b/forge-gui/res/cardsfolder/a/abzan_monument.txt @@ -2,7 +2,7 @@ Name:Abzan Monument ManaCost:2 Types:Artifact T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When this artifact enters, search your library for a basic Plains, Swamp, or Forest card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Plains+Basic,Land.Swamp+Basic,Land.Forest+Basic +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Plains.Basic,Swamp.Basic,Forest.Basic | ChangeTypeDesc$ basic Plains, Swamp, or Forest A:AB$ Token | Cost$ 1 W B G T Sac<1/CARDNAME> | TokenAmount$ 1 | TokenPower$ X | TokenToughness$ X | TokenScript$ w_x_x_spirit | TokenOwner$ You | SorcerySpeed$ True | SpellDescription$ Create an X/X white Spirit creature token, where X is the greatest toughness among creatures you control. Activate only as a sorcery. SVar:X:Count$Valid Creature.YouCtrl$GreatestToughness DeckHas:Ability$Token diff --git a/forge-gui/res/cardsfolder/a/ainok_guide.txt b/forge-gui/res/cardsfolder/a/ainok_guide.txt index 5584544da28..8733314caf3 100644 --- a/forge-gui/res/cardsfolder/a/ainok_guide.txt +++ b/forge-gui/res/cardsfolder/a/ainok_guide.txt @@ -5,6 +5,6 @@ PT:1/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When CARDNAME enters, ABILITY SVar:TrigCharm:DB$ Charm | Choices$ DBCounter,DBSearch SVar:DBCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on CARDNAME. -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | SpellDescription$ Search your library for a basic land card, reveal it, then shuffle and put that card on top. +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, then shuffle and put that card on top. DeckHas:Ability$Counters Oracle:When Ainok Guide enters, choose one —\n• Put a +1/+1 counter on Ainok Guide.\n• Search your library for a basic land card, reveal it, then shuffle and put that card on top. diff --git a/forge-gui/res/cardsfolder/a/analyze_the_pollen.txt b/forge-gui/res/cardsfolder/a/analyze_the_pollen.txt index a8daf21e612..55d24cbae5b 100644 --- a/forge-gui/res/cardsfolder/a/analyze_the_pollen.txt +++ b/forge-gui/res/cardsfolder/a/analyze_the_pollen.txt @@ -2,7 +2,7 @@ Name:Analyze the Pollen ManaCost:G Types:Sorcery S:Mode$ OptionalCost | EffectZone$ All | ValidCard$ Card.Self | ValidSA$ Spell | Cost$ CollectEvidence<8> | Description$ As an additional cost to cast this spell, you may collect evidence 8. (Exile cards with total mana value 8 or greater from your graveyard.) -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ConditionDefined$ Collected | ConditionPresent$ Card | ConditionCompare$ EQ0 | ChangeType$ Land.Basic | ChangeNum$ 1 | SubAbility$ DBChangeZone | SpellDescription$ Search your library for a basic land card. If evidence was collected, instead search your library for a creature or land card. Reveal that card, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ConditionDefined$ Collected | ConditionPresent$ Card | ConditionCompare$ EQ0 | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SubAbility$ DBChangeZone | SpellDescription$ Search your library for a basic land card. If evidence was collected, instead search your library for a creature or land card. Reveal that card, put it into your hand, then shuffle. SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land,Creature | ChangeNum$ 1 | ConditionDefined$ Collected | ConditionPresent$ Card DeckHints:Ability$Graveyard|Discard|Dredge|Mill Oracle:As an additional cost to cast this spell, you may collect evidence 8. (Exile cards with total mana value 8 or greater from your graveyard.)\nSearch your library for a basic land card. If evidence was collected, instead search your library for a creature or land card. Reveal that card, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/a/anchor_to_reality.txt b/forge-gui/res/cardsfolder/a/anchor_to_reality.txt index 0c7da6ef690..82f76244f1d 100644 --- a/forge-gui/res/cardsfolder/a/anchor_to_reality.txt +++ b/forge-gui/res/cardsfolder/a/anchor_to_reality.txt @@ -1,7 +1,7 @@ Name:Anchor to Reality ManaCost:2 U U Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 U U Sac<1/Artifact;Creature/artifact or creature> | Origin$ Library | Destination$ Battlefield | ChangeType$ Equipment,Vehicle | ChangeTypeDesc$ Equipment or Vehicle card | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBScry | SpellDescription$ Search your library for an Equipment or Vehicle card, put that card onto the battlefield, then shuffle. +A:SP$ ChangeZone | Cost$ 2 U U Sac<1/Artifact;Creature/artifact or creature> | Origin$ Library | Destination$ Battlefield | ChangeType$ Equipment,Vehicle | RememberChanged$ True | SubAbility$ DBScry | SpellDescription$ Search your library for an Equipment or Vehicle card, put that card onto the battlefield, then shuffle. SVar:DBScry:DB$ Scry | ConditionDefined$ Remembered | ConditionPresent$ Card | ScryNum$ X | SubAbility$ DBCleanup | SpellDescription$ If it has mana value less than the sacrificed permanent's mana value, scry 2. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$Compare Y LTZ.2.0 diff --git a/forge-gui/res/cardsfolder/a/armillary_sphere.txt b/forge-gui/res/cardsfolder/a/armillary_sphere.txt index 492127ba6f4..4d173118d17 100644 --- a/forge-gui/res/cardsfolder/a/armillary_sphere.txt +++ b/forge-gui/res/cardsfolder/a/armillary_sphere.txt @@ -1,5 +1,5 @@ Name:Armillary Sphere ManaCost:2 Types:Artifact -A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle. Oracle:{2}, {T}, Sacrifice Armillary Sphere: Search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/a/assassins_trophy.txt b/forge-gui/res/cardsfolder/a/assassins_trophy.txt index 3c0683f5a46..cca766e449d 100644 --- a/forge-gui/res/cardsfolder/a/assassins_trophy.txt +++ b/forge-gui/res/cardsfolder/a/assassins_trophy.txt @@ -2,5 +2,5 @@ Name:Assassin's Trophy ManaCost:B G Types:Instant A:SP$ Destroy | ValidTgts$ Permanent.OppCtrl | AITgts$ Permanent.nonLand,Land.nonBasic | TgtPrompt$ Select target permanent an opponent controls | SubAbility$ DBChange | SpellDescription$ Destroy target permanent an opponent controls. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle. -SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True +SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True Oracle:Destroy target permanent an opponent controls. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/a/atalan_jackal.txt b/forge-gui/res/cardsfolder/a/atalan_jackal.txt index f83d9b42e5d..4a18df48eba 100644 --- a/forge-gui/res/cardsfolder/a/atalan_jackal.txt +++ b/forge-gui/res/cardsfolder/a/atalan_jackal.txt @@ -5,5 +5,5 @@ PT:2/2 K:Trample K:Haste T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | TriggerZones$ Battlefield | Execute$ TrigSearch | CombatDamage$ True | OptionalDecider$ You | TriggerDescription$ Skilled Outrider — Whenever CARDNAME deals combat damage to a player, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land Oracle:Trample, haste\nSkilled Outrider — Whenever Atalan Jackal deals combat damage to a player, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/a/attune_with_aether.txt b/forge-gui/res/cardsfolder/a/attune_with_aether.txt index 9ffce915781..e5f92d54ef6 100644 --- a/forge-gui/res/cardsfolder/a/attune_with_aether.txt +++ b/forge-gui/res/cardsfolder/a/attune_with_aether.txt @@ -1,6 +1,6 @@ Name:Attune with Aether ManaCost:G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | SubAbility$ DBEnergy | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. You get {E}{E} (two energy counters). +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SubAbility$ DBEnergy | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. You get {E}{E} (two energy counters). SVar:DBEnergy:DB$ PutCounter | Defined$ You | CounterType$ ENERGY | CounterNum$ 2 Oracle:Search your library for a basic land card, reveal it, put it into your hand, then shuffle. You get {E}{E} (two energy counters). diff --git a/forge-gui/res/cardsfolder/a/avatar_of_growth.txt b/forge-gui/res/cardsfolder/a/avatar_of_growth.txt index 66acf4c56be..cc9adb68f04 100644 --- a/forge-gui/res/cardsfolder/a/avatar_of_growth.txt +++ b/forge-gui/res/cardsfolder/a/avatar_of_growth.txt @@ -5,6 +5,6 @@ PT:4/4 K:Trample S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ Trample T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearch | TriggerDescription$ When CARDNAME enters, each player searches their library for up to two basic land cards, puts them onto the battlefield, then shuffles. -SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | DefinedPlayer$ Player | ChangeNum$ 2 | Shuffle$ True +SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ Player | ChangeNum$ 2 | Shuffle$ True SVar:X:PlayerCountOpponents$Amount Oracle:This spell costs {1} less to cast for each opponent you have.\nTrample\nWhen Avatar of Growth enters, each player searches their library for up to two basic land cards, puts them onto the battlefield, then shuffles. diff --git a/forge-gui/res/cardsfolder/b/bad_river.txt b/forge-gui/res/cardsfolder/b/bad_river.txt index 2a72590f0b2..fb59485d270 100644 --- a/forge-gui/res/cardsfolder/b/bad_river.txt +++ b/forge-gui/res/cardsfolder/b/bad_river.txt @@ -3,5 +3,5 @@ ManaCost:no cost Types:Land R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Island,Swamp | ChangeNum$ 1 | SpellDescription$ Search your library for an Island or Swamp card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Island,Swamp | SpellDescription$ Search your library for an Island or Swamp card, put it onto the battlefield, then shuffle. Oracle:Bad River enters tapped.\n{T}, Sacrifice Bad River: Search your library for an Island or Swamp card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/b/bant_panorama.txt b/forge-gui/res/cardsfolder/b/bant_panorama.txt index 5daf89d4ec4..a09855b7170 100644 --- a/forge-gui/res/cardsfolder/b/bant_panorama.txt +++ b/forge-gui/res/cardsfolder/b/bant_panorama.txt @@ -2,5 +2,5 @@ Name:Bant Panorama ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Forest+Basic,Land.Plains+Basic,Land.Island+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Forest, Plains, or Island card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Forest.Basic,Plains.Basic,Island.Basic | ChangeTypeDesc$ basic Forest, Plains, or Island | SpellDescription$ Search your library for a basic Forest, Plains, or Island card, put it onto the battlefield tapped, then shuffle. Oracle:{T}: Add {C}.\n{1}, {T}, Sacrifice Bant Panorama: Search your library for a basic Forest, Plains, or Island card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/b/beanstalk_giant_fertile_footsteps.txt b/forge-gui/res/cardsfolder/b/beanstalk_giant_fertile_footsteps.txt index 2595f2063b8..21a1ca529e0 100644 --- a/forge-gui/res/cardsfolder/b/beanstalk_giant_fertile_footsteps.txt +++ b/forge-gui/res/cardsfolder/b/beanstalk_giant_fertile_footsteps.txt @@ -12,5 +12,5 @@ ALTERNATE Name:Fertile Footsteps ManaCost:2 G Types:Sorcery Adventure -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield, then shuffle. (Then exile this card. You may cast the creature later from exile.) +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield, then shuffle. (Then exile this card. You may cast the creature later from exile.) Oracle:Search your library for a basic land card, put it onto the battlefield, then shuffle. (Then exile this card. You may cast the creature later from exile.) diff --git a/forge-gui/res/cardsfolder/b/beneath_the_sands.txt b/forge-gui/res/cardsfolder/b/beneath_the_sands.txt index c9b45dc14be..acccfbfb97b 100644 --- a/forge-gui/res/cardsfolder/b/beneath_the_sands.txt +++ b/forge-gui/res/cardsfolder/b/beneath_the_sands.txt @@ -1,6 +1,6 @@ Name:Beneath the Sands ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. K:Cycling:2 Oracle:Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/b/bitterthorn_nissas_animus.txt b/forge-gui/res/cardsfolder/b/bitterthorn_nissas_animus.txt index 1ff84d6e33d..f46c48d1a78 100644 --- a/forge-gui/res/cardsfolder/b/bitterthorn_nissas_animus.txt +++ b/forge-gui/res/cardsfolder/b/bitterthorn_nissas_animus.txt @@ -5,7 +5,7 @@ K:Living Weapon K:Equip:3 S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creature gets +1/+1. T:Mode$ Attacks | ValidCard$ Creature.EquippedBy | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ Whenever equipped creature attacks, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ShuffleNonMandatory$ True S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddSVar$ AE SVar:AE:SVar:HasAttackEffect:TRUE DeckHas:Ability$Token & Type$Germ|Phyrexian diff --git a/forge-gui/res/cardsfolder/b/blighted_woodland.txt b/forge-gui/res/cardsfolder/b/blighted_woodland.txt index e26fc88f742..679f0fc48e8 100644 --- a/forge-gui/res/cardsfolder/b/blighted_woodland.txt +++ b/forge-gui/res/cardsfolder/b/blighted_woodland.txt @@ -2,7 +2,7 @@ Name:Blighted Woodland ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ 3 G T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 3 G T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. DeckHas:Ability$Mana.Colorless DeckNeeds:Color$Green Oracle:{T}: Add {C}.\n{3}{G}, {T}, Sacrifice Blighted Woodland: Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/b/bloodstained_mire.txt b/forge-gui/res/cardsfolder/b/bloodstained_mire.txt index e0a517c1d9f..990998d5125 100644 --- a/forge-gui/res/cardsfolder/b/bloodstained_mire.txt +++ b/forge-gui/res/cardsfolder/b/bloodstained_mire.txt @@ -1,5 +1,5 @@ Name:Bloodstained Mire ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Swamp,Mountain | ChangeNum$ 1 | SpellDescription$ Search your library for a Swamp or Mountain card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Swamp,Mountain | SpellDescription$ Search your library for a Swamp or Mountain card, put it onto the battlefield, then shuffle. Oracle:{T}, Pay 1 life, Sacrifice Bloodstained Mire: Search your library for a Swamp or Mountain card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/b/bloomvine_regent_claim_territory.txt b/forge-gui/res/cardsfolder/b/bloomvine_regent_claim_territory.txt index 8cb3e6667fe..bc1d52ee98c 100644 --- a/forge-gui/res/cardsfolder/b/bloomvine_regent_claim_territory.txt +++ b/forge-gui/res/cardsfolder/b/bloomvine_regent_claim_territory.txt @@ -13,7 +13,7 @@ ALTERNATE Name:Claim Territory ManaCost:2 G Types:Sorcery Omen -A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Forest.Basic | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic Forest cards, reveal them, put one onto the battlefield tapped and the other into your hand, then shuffle. (Also shuffle this card.) +A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Forest.Basic | ChangeTypeDesc$ basic Forest | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic Forest cards, reveal them, put one onto the battlefield tapped and the other into your hand, then shuffle. (Also shuffle this card.) SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.IsRemembered | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put onto the battlefield | Tapped$ True | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.IsRemembered | Mandatory$ True | NoLooking$ True | StackDescription$ None | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/b/borderland_explorer.txt b/forge-gui/res/cardsfolder/b/borderland_explorer.txt index 3317a6cdd73..b8634b348b5 100644 --- a/forge-gui/res/cardsfolder/b/borderland_explorer.txt +++ b/forge-gui/res/cardsfolder/b/borderland_explorer.txt @@ -7,7 +7,7 @@ SVar:TrigChoose:DB$ GenericChoice | TempRemember$ Chooser | ShowChoice$ ExceptSe SVar:Discard:DB$ Pump | Defined$ Remembered | NoteCards$ Self | NoteCardsFor$ Discard | SpellDescription$ Discard a card. SVar:No:DB$ Pump | SpellDescription$ Do not discard a card. SVar:DBDiscard:DB$ Discard | Defined$ Player.NotedForDiscard | Mode$ TgtChoose | RememberDiscarded$ True | SubAbility$ DBSearch -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | DefinedPlayer$ RememberedOwner | SubAbility$ DBCleanup +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ RememberedOwner | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBClearNotes SVar:DBClearNotes:DB$ Pump | Defined$ Player | ClearNotedCardsFor$ Discard DeckHas:Ability$Discard diff --git a/forge-gui/res/cardsfolder/b/borderland_ranger.txt b/forge-gui/res/cardsfolder/b/borderland_ranger.txt index 53dded29539..e1424daa819 100644 --- a/forge-gui/res/cardsfolder/b/borderland_ranger.txt +++ b/forge-gui/res/cardsfolder/b/borderland_ranger.txt @@ -3,5 +3,5 @@ ManaCost:2 G Types:Creature Human Scout Ranger PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:When Borderland Ranger enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/b/boundless_realms.txt b/forge-gui/res/cardsfolder/b/boundless_realms.txt index c0214c85fcb..f2ddd045565 100644 --- a/forge-gui/res/cardsfolder/b/boundless_realms.txt +++ b/forge-gui/res/cardsfolder/b/boundless_realms.txt @@ -1,6 +1,6 @@ Name:Boundless Realms ManaCost:6 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ X | Tapped$ True | SpellDescription$ Search your library for X basic land cards, where X is the number of lands you control, put them onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | Tapped$ True | SpellDescription$ Search your library for X basic land cards, where X is the number of lands you control, put them onto the battlefield tapped, then shuffle. SVar:X:Count$Valid Land.YouCtrl Oracle:Search your library for up to X basic land cards, where X is the number of lands you control, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/b/bountiful_landscape.txt b/forge-gui/res/cardsfolder/b/bountiful_landscape.txt index 0f37325a33f..0ab9fbe599c 100644 --- a/forge-gui/res/cardsfolder/b/bountiful_landscape.txt +++ b/forge-gui/res/cardsfolder/b/bountiful_landscape.txt @@ -2,6 +2,6 @@ Name:Bountiful Landscape ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Forest+Basic,Land.Island+Basic,Land.Mountain+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Forest, Island, or Mountain card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Forest.Basic,Island.Basic,Mountain.Basic | ChangeTypeDesc$ basic Forest, Island, or Mountain | SpellDescription$ Search your library for a basic Forest, Island, or Mountain card, put it onto the battlefield tapped, then shuffle. K:Cycling:G U R Oracle:{T}: Add {C}.\n{T}, Sacrifice Bountiful Landscape: Search your library for a basic Forest, Island, or Mountain card, put it onto the battlefield tapped, then shuffle.\nCycling {G}{U}{R} ({G}{U}{R}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/b/braidwood_sextant.txt b/forge-gui/res/cardsfolder/b/braidwood_sextant.txt index fdd8bb376e8..5ce8628811d 100644 --- a/forge-gui/res/cardsfolder/b/braidwood_sextant.txt +++ b/forge-gui/res/cardsfolder/b/braidwood_sextant.txt @@ -1,5 +1,5 @@ Name:Braidwood Sextant ManaCost:1 Types:Artifact -A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal that card, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal that card, put it into your hand, then shuffle. Oracle:{2}, {T}, Sacrifice Braidwood Sextant: Search your library for a basic land card, reveal that card, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/b/brave_the_wilds.txt b/forge-gui/res/cardsfolder/b/brave_the_wilds.txt index aaf01d66563..9ba9962a721 100644 --- a/forge-gui/res/cardsfolder/b/brave_the_wilds.txt +++ b/forge-gui/res/cardsfolder/b/brave_the_wilds.txt @@ -3,7 +3,7 @@ ManaCost:G Types:Sorcery K:Bargain A:SP$ Animate | ValidTgts$ Land.YouCtrl | TargetMin$ X | TargetMax$ X | TgtPrompt$ Select target land you control | Power$ 3 | Toughness$ 3 | Types$ Creature,Elemental | Duration$ Permanent | SubAbility$ DBChangeZone | Condition$ Bargain | Keywords$ Haste | SpellDescription$ If this spell was bargained, target land you control becomes a 3/3 Elemental creature with haste that's still a land. -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. SVar:X:Count$Bargained.1.0 DeckHints:Type$Artifact|Enchantment & Ability$Token DeckHas:Ability$Sacrifice & Type$Elemental diff --git a/forge-gui/res/cardsfolder/b/brokers_hideout.txt b/forge-gui/res/cardsfolder/b/brokers_hideout.txt index a0d7090667a..63020480f2c 100644 --- a/forge-gui/res/cardsfolder/b/brokers_hideout.txt +++ b/forge-gui/res/cardsfolder/b/brokers_hideout.txt @@ -4,7 +4,7 @@ Types:Land T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBSacrifice | TriggerDescription$ When CARDNAME enters, sacrifice it. When you do, search your library for a basic Forest, Plains, or Island card, put it onto the battlefield tapped, then shuffle and you gain 1 life. SVar:DBSacrifice:DB$ Sacrifice | Defined$ Self | RememberSacrificed$ True | SubAbility$ DBImmediateTrigger SVar:DBImmediateTrigger:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | Execute$ DBChangeZone | SubAbility$ DBCleanup | TriggerDescription$ Search your library for a basic Forest, Plains, or Island card, put it onto the battlefield tapped, then shuffle and you gain 1 life. -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Forest+Basic,Land.Plains+Basic,Land.Island+Basic | ChangeNum$ 1 | SubAbility$ DBGainLife +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Forest.Basic,Plains.Basic,Island.Basic | ChangeTypeDesc$ basic Forest, Plains, or Island | SubAbility$ DBGainLife SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Ability$Sacrifice|LifeGain diff --git a/forge-gui/res/cardsfolder/b/burnished_hart.txt b/forge-gui/res/cardsfolder/b/burnished_hart.txt index 7abe85f1b23..986c995bb44 100644 --- a/forge-gui/res/cardsfolder/b/burnished_hart.txt +++ b/forge-gui/res/cardsfolder/b/burnished_hart.txt @@ -2,5 +2,5 @@ Name:Burnished Hart ManaCost:3 Types:Artifact Creature Elk PT:2/2 -A:AB$ ChangeZone | Cost$ 3 Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 2 | Tapped$ True | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 3 Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | Tapped$ True | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. Oracle:{3}, Sacrifice Burnished Hart: Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/b/bushwhack.txt b/forge-gui/res/cardsfolder/b/bushwhack.txt index d1659b41e0c..8e9b441bc71 100644 --- a/forge-gui/res/cardsfolder/b/bushwhack.txt +++ b/forge-gui/res/cardsfolder/b/bushwhack.txt @@ -2,7 +2,7 @@ Name:Bushwhack ManaCost:G Types:Sorcery A:SP$ Charm | Choices$ FetchBasic,Fight -SVar:FetchBasic:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +SVar:FetchBasic:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. SVar:Fight:DB$ Pump | AILogic$ Fight | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Choose target creature you control | SubAbility$ DBFight | SpellDescription$ Target creature you control fights target creature you don't control. SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Choose target creature you don't control Oracle:Choose one —\n• Search your library for a basic land card, reveal it, put it into your hand, then shuffle.\n• Target creature you control fights target creature you don't control. (Each deals damage equal to its power to the other.) diff --git a/forge-gui/res/cardsfolder/c/cabaretti_courtyard.txt b/forge-gui/res/cardsfolder/c/cabaretti_courtyard.txt index 151bbed9c44..65eb5b0ca06 100644 --- a/forge-gui/res/cardsfolder/c/cabaretti_courtyard.txt +++ b/forge-gui/res/cardsfolder/c/cabaretti_courtyard.txt @@ -4,7 +4,7 @@ Types:Land T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBSacrifice | TriggerDescription$ When CARDNAME enters, sacrifice it. When you do, search your library for a basic Mountain, Forest, or Plains card, put it onto the battlefield tapped, then shuffle and you gain 1 life. SVar:DBSacrifice:DB$ Sacrifice | RememberSacrificed$ True | SubAbility$ DBImmediateTrigger SVar:DBImmediateTrigger:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | Execute$ DBChangeZone | SubAbility$ DBCleanup | TriggerDescription$ Search your library for a basic Mountain, Forest, or Plains card, put it onto the battlefield tapped, then shuffle and you gain 1 life. -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Mountain+Basic,Land.Forest+Basic,Land.Plains+Basic | ChangeNum$ 1 | SubAbility$ DBGainLife +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Mountain.Basic,Forest.Basic,Plains.Basic | ChangeTypeDesc$ basic Mountain, Forest, or Plains | SubAbility$ DBGainLife SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Ability$Sacrifice|LifeGain diff --git a/forge-gui/res/cardsfolder/c/campus_guide.txt b/forge-gui/res/cardsfolder/c/campus_guide.txt index 40079635200..ae6fd598c03 100644 --- a/forge-gui/res/cardsfolder/c/campus_guide.txt +++ b/forge-gui/res/cardsfolder/c/campus_guide.txt @@ -3,5 +3,5 @@ ManaCost:2 Types:Artifact Creature Golem PT:2/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:When Campus Guide enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top. diff --git a/forge-gui/res/cardsfolder/c/caravan_vigil.txt b/forge-gui/res/cardsfolder/c/caravan_vigil.txt index a223a83018a..ee5396a550c 100644 --- a/forge-gui/res/cardsfolder/c/caravan_vigil.txt +++ b/forge-gui/res/cardsfolder/c/caravan_vigil.txt @@ -1,5 +1,5 @@ Name:Caravan Vigil ManaCost:G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | DestinationAlternative$ Battlefield | DestAltSVar$ Count$ThisTurnEntered_Graveyard_from_Battlefield_Creature | ChangeType$ Land.Basic | StackDescription$ SpellDescription | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle.,,,,,,Morbid — You may put that card onto the battlefield instead of putting it into your hand if a creature died this turn. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | DestinationAlternative$ Battlefield | DestAltSVar$ Count$ThisTurnEntered_Graveyard_from_Battlefield_Creature | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | StackDescription$ SpellDescription | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle.,,,,,,Morbid — You may put that card onto the battlefield instead of putting it into your hand if a creature died this turn. Oracle:Search your library for a basic land card, reveal it, put it into your hand, then shuffle.\nMorbid — You may put that card onto the battlefield instead of putting it into your hand if a creature died this turn. diff --git a/forge-gui/res/cardsfolder/c/celebrate_the_harvest.txt b/forge-gui/res/cardsfolder/c/celebrate_the_harvest.txt index 597c7c979e0..51703eea0b1 100644 --- a/forge-gui/res/cardsfolder/c/celebrate_the_harvest.txt +++ b/forge-gui/res/cardsfolder/c/celebrate_the_harvest.txt @@ -1,6 +1,6 @@ Name:Celebrate the Harvest ManaCost:3 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ X | Tapped$ True | SelectPrompt$ Select up to X basic land cards | StackDescription$ SpellDescription | SpellDescription$ Search your library for up to X basic land cards, where X is the number of different powers among creatures you control. Put those cards onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | Tapped$ True | SelectPrompt$ Select up to X basic land cards | StackDescription$ SpellDescription | SpellDescription$ Search your library for up to X basic land cards, where X is the number of different powers among creatures you control. Put those cards onto the battlefield tapped, then shuffle. SVar:X:Count$DifferentPower_Creature.YouCtrl Oracle:Search your library for up to X basic land cards, where X is the number of different powers among creatures you control. Put those cards onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/c/centaur_rootcaster.txt b/forge-gui/res/cardsfolder/c/centaur_rootcaster.txt index 3369e75cd15..7abdbb30b30 100644 --- a/forge-gui/res/cardsfolder/c/centaur_rootcaster.txt +++ b/forge-gui/res/cardsfolder/c/centaur_rootcaster.txt @@ -3,5 +3,5 @@ ManaCost:3 G Types:Creature Centaur Druid PT:2/2 T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ TrigChange | CombatDamage$ True | OptionalDecider$ You | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:Whenever Centaur Rootcaster deals combat damage to a player, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/c/civic_wayfinder.txt b/forge-gui/res/cardsfolder/c/civic_wayfinder.txt index 7a2c0a2e0d6..55960c76f66 100644 --- a/forge-gui/res/cardsfolder/c/civic_wayfinder.txt +++ b/forge-gui/res/cardsfolder/c/civic_wayfinder.txt @@ -3,5 +3,5 @@ ManaCost:2 G Types:Creature Elf Druid Warrior PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:When Civic Wayfinder enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/c/cleansing_wildfire.txt b/forge-gui/res/cardsfolder/c/cleansing_wildfire.txt index 30200a18783..b9adf47bce0 100644 --- a/forge-gui/res/cardsfolder/c/cleansing_wildfire.txt +++ b/forge-gui/res/cardsfolder/c/cleansing_wildfire.txt @@ -2,6 +2,6 @@ Name:Cleansing Wildfire ManaCost:1 R Types:Sorcery A:SP$ Destroy | ValidTgts$ Land | TgtPrompt$ Select target land | SubAbility$ DBChange | SpellDescription$ Destroy target land. Its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True | SubAbility$ DBDraw | StackDescription$ Its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle their library. +SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True | SubAbility$ DBDraw | StackDescription$ Its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle their library. SVar:DBDraw:DB$ Draw | NumCards$ 1 | SpellDescription$ Draw a card. Oracle:Destroy target land. Its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle.\nDraw a card. diff --git a/forge-gui/res/cardsfolder/c/collective_voyage.txt b/forge-gui/res/cardsfolder/c/collective_voyage.txt index 76c99008032..ed6990b00e6 100644 --- a/forge-gui/res/cardsfolder/c/collective_voyage.txt +++ b/forge-gui/res/cardsfolder/c/collective_voyage.txt @@ -4,7 +4,7 @@ Types:Sorcery A:SP$ RepeatEach | RepeatPlayers$ Player | StartingWith$ You | RepeatSubAbility$ DBPay | SubAbility$ DBSearch | StackDescription$ SpellDescription | SpellDescription$ Join forces — Starting with you, each player may pay any amount of mana. Each player searches their library for up to X basic land cards, where X is the total amount of mana paid this way, puts them onto the battlefield tapped, then shuffles. SVar:DBPay:DB$ ChooseNumber | Defined$ Player.IsRemembered | ChooseAnyNumber$ True | ListTitle$ amount of mana to pay | SubAbility$ DBStore SVar:DBStore:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ CountSVar | Expression$ JoinForcesAmount/Plus.Y | UnlessCost$ Y | UnlessPayer$ Player.IsRemembered | UnlessSwitched$ True -SVar:DBSearch:DB$ ChangeZone | DefinedPlayer$ Player | ChangeType$ Land.Basic | ChangeNum$ JoinForcesAmount | Origin$ Library | Destination$ Battlefield | Tapped$ True | SubAbility$ DBReset | StackDescription$ None +SVar:DBSearch:DB$ ChangeZone | DefinedPlayer$ Player | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ JoinForcesAmount | Origin$ Library | Destination$ Battlefield | Tapped$ True | SubAbility$ DBReset | StackDescription$ None SVar:DBReset:DB$ StoreSVar | SVar$ JoinForcesAmount | Type$ Number | Expression$ 0 SVar:Y:Count$ChosenNumber SVar:JoinForcesAmount:Number$0 diff --git a/forge-gui/res/cardsfolder/c/coming_attraction.txt b/forge-gui/res/cardsfolder/c/coming_attraction.txt index de226a42e04..30d23397f88 100644 --- a/forge-gui/res/cardsfolder/c/coming_attraction.txt +++ b/forge-gui/res/cardsfolder/c/coming_attraction.txt @@ -1,6 +1,6 @@ Name:Coming Attraction ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | SubAbility$ DBOpenAttraction | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | SubAbility$ DBOpenAttraction | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. SVar:DBOpenAttraction:DB$ OpenAttraction | SpellDescription$ Open an Attraction. (Put the top card of your Attraction deck onto the battlefield.) Oracle:Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Open an Attraction. (Put the top card of your Attraction deck onto the battlefield.) diff --git a/forge-gui/res/cardsfolder/c/contaminated_landscape.txt b/forge-gui/res/cardsfolder/c/contaminated_landscape.txt index 0cabd4643c0..f5766fee496 100644 --- a/forge-gui/res/cardsfolder/c/contaminated_landscape.txt +++ b/forge-gui/res/cardsfolder/c/contaminated_landscape.txt @@ -2,6 +2,6 @@ Name:Contaminated Landscape ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Plains+Basic,Land.Island+Basic,Land.Swamp+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Plains, Island, or Swamp card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Plains.Basic,Island.Basic,Swamp.Basic | ChangeTypeDesc$ basic Plains, Island, or Swamp | SpellDescription$ Search your library for a basic Plains, Island, or Swamp card, put it onto the battlefield tapped, then shuffle. K:Cycling:W U B Oracle:{T}: Add {C}.\n{T}, Sacrifice Contaminated Landscape: Search your library for a basic Plains, Island, or Swamp card, put it onto the battlefield tapped, then shuffle.\nCycling {W}{U}{B} ({W}{U}{B}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/c/cultivate.txt b/forge-gui/res/cardsfolder/c/cultivate.txt index dc93f59004d..59f81d0da0b 100644 --- a/forge-gui/res/cardsfolder/c/cultivate.txt +++ b/forge-gui/res/cardsfolder/c/cultivate.txt @@ -1,7 +1,7 @@ Name:Cultivate ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle. SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.IsRemembered | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put onto the battlefield | Tapped$ True | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.IsRemembered | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put into your hand | StackDescription$ None | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/d/dawntreader_elk.txt b/forge-gui/res/cardsfolder/d/dawntreader_elk.txt index 46e562a72d5..fd5b73dfae4 100644 --- a/forge-gui/res/cardsfolder/d/dawntreader_elk.txt +++ b/forge-gui/res/cardsfolder/d/dawntreader_elk.txt @@ -2,5 +2,5 @@ Name:Dawntreader Elk ManaCost:1 G Types:Creature Elk PT:2/2 -A:AB$ ChangeZone | Cost$ G Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card and put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ G Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card and put it onto the battlefield tapped, then shuffle. Oracle:{G}, Sacrifice Dawntreader Elk: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/d/deathbellow_war_cry.txt b/forge-gui/res/cardsfolder/d/deathbellow_war_cry.txt index c2efa4c6a94..3fc257fdc23 100644 --- a/forge-gui/res/cardsfolder/d/deathbellow_war_cry.txt +++ b/forge-gui/res/cardsfolder/d/deathbellow_war_cry.txt @@ -1,6 +1,6 @@ Name:Deathbellow War Cry ManaCost:5 R R R Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature.Minotaur | ChangeNum$ 4 | DifferentNames$ True | SpellDescription$ Search your library for up to four Minotaur creature cards with different names, put them onto the battlefield, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature.Minotaur | ChangeTypeDesc$ Minotaur creature | ChangeNum$ 4 | DifferentNames$ True | SpellDescription$ Search your library for up to four Minotaur creature cards with different names, put them onto the battlefield, then shuffle. DeckNeeds:Type$Minotaur Oracle:Search your library for up to four Minotaur creature cards with different names, put them onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/d/deathsprout.txt b/forge-gui/res/cardsfolder/d/deathsprout.txt index e92c0bae6b1..a75d44f89e9 100644 --- a/forge-gui/res/cardsfolder/d/deathsprout.txt +++ b/forge-gui/res/cardsfolder/d/deathsprout.txt @@ -2,5 +2,5 @@ Name:Deathsprout ManaCost:1 B B G Types:Instant A:SP$ Destroy | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBSearch | SpellDescription$ Destroy target creature. Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True Oracle:Destroy target creature. Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/d/deceptive_landscape.txt b/forge-gui/res/cardsfolder/d/deceptive_landscape.txt index 09a7742f151..6691ccfd006 100644 --- a/forge-gui/res/cardsfolder/d/deceptive_landscape.txt +++ b/forge-gui/res/cardsfolder/d/deceptive_landscape.txt @@ -2,6 +2,6 @@ Name:Deceptive Landscape ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Plains+Basic,Land.Swamp+Basic,Land.Forest+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Plains, Swamp, or Forest card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Plains.Basic,Swamp.Basic,Forest.Basic | ChangeTypeDesc$ basic Plains, Swamp, or Forest | SpellDescription$ Search your library for a basic Plains, Swamp, or Forest card, put it onto the battlefield tapped, then shuffle. K:Cycling:W B G Oracle:{T}: Add {C}.\n{T}, Sacrifice Deceptive Landscape: Search your library for a basic Plains, Swamp, or Forest card, put it onto the battlefield tapped, then shuffle.\nCycling {W}{B}{G} ({W}{B}{G}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/d/deep_reconnaissance.txt b/forge-gui/res/cardsfolder/d/deep_reconnaissance.txt index f594afa0312..46a5fdd5ee0 100644 --- a/forge-gui/res/cardsfolder/d/deep_reconnaissance.txt +++ b/forge-gui/res/cardsfolder/d/deep_reconnaissance.txt @@ -2,5 +2,5 @@ Name:Deep Reconnaissance ManaCost:2 G Types:Sorcery K:Flashback:4 G -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Oracle:Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.\nFlashback {4}{G} (You may cast this card from your graveyard for its flashback cost. Then exile it.) diff --git a/forge-gui/res/cardsfolder/d/demolition_field.txt b/forge-gui/res/cardsfolder/d/demolition_field.txt index b562926b74f..b8669e4b60d 100644 --- a/forge-gui/res/cardsfolder/d/demolition_field.txt +++ b/forge-gui/res/cardsfolder/d/demolition_field.txt @@ -3,8 +3,8 @@ ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. A:AB$ Destroy | Cost$ 2 T Sac<1/CARDNAME> | ValidTgts$ Land.nonBasic+OppCtrl | TgtPrompt$ Select target nonbasic land an opponent controls | SubAbility$ DBSearch | AILogic$ GhostQuarter | SpellDescription$ Destroy target nonbasic land an opponent controls. That land's controller may search their library for a basic land card, put it onto the battlefield, then shuffle. You may search your library for a basic land card, put it onto the battlefield, then shuffle. -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ TargetedController | ChangeType$ Land.Basic | ChangeNum$ 1 | SubAbility$ DBSearchBis | StackDescription$ That land's controller may search their library for a basic land card, put it onto the battlefield, then shuffle. You may search your library for a basic land card, put it onto the battlefield, then shuffle. -SVar:DBSearchBis:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ You | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ TargetedController | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SubAbility$ DBSearchBis | StackDescription$ That land's controller may search their library for a basic land card, put it onto the battlefield, then shuffle. You may search your library for a basic land card, put it onto the battlefield, then shuffle. +SVar:DBSearchBis:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ You | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land AI:RemoveDeck:Random DeckHas:Ability$Sacrifice Oracle:{T}: Add {C}.\n{2}, {T}, Sacrifice Demolition Field: Destroy target nonbasic land an opponent controls. That land's controller may search their library for a basic land card, put it onto the battlefield, then shuffle. You may search your library for a basic land card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/d/dig_up.txt b/forge-gui/res/cardsfolder/d/dig_up.txt index a761e641864..cb39edcb029 100644 --- a/forge-gui/res/cardsfolder/d/dig_up.txt +++ b/forge-gui/res/cardsfolder/d/dig_up.txt @@ -1,6 +1,6 @@ Name:Dig Up ManaCost:G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | StackDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. | SpellDescription$ Search your library for a [basic land] card, [reveal it,] put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | StackDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. | SpellDescription$ Search your library for a [basic land] card, [reveal it,] put it into your hand, then shuffle. A:SP$ ChangeZone | Cost$ 1 B B G | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | NoReveal$ True | PrecostDesc$ Cleave | CostDesc$ {1}{B}{B}{G} | NonBasicSpell$ True | SpellDescription$ (You may cast this spell for its cleave cost. If you do, remove the words in square brackets.) | StackDescription$ Search your library for a card, put it into your hand, then shuffle. Oracle:Cleave {1}{B}{B}{G} (You may cast this spell for its cleave cost. If you do, remove the words in square brackets.)\nSearch your library for a [basic land] card, [reveal it,] put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/d/diligent_farmhand.txt b/forge-gui/res/cardsfolder/d/diligent_farmhand.txt index 8caa48a1342..8411519dbf9 100644 --- a/forge-gui/res/cardsfolder/d/diligent_farmhand.txt +++ b/forge-gui/res/cardsfolder/d/diligent_farmhand.txt @@ -2,7 +2,7 @@ Name:Diligent Farmhand ManaCost:G Types:Creature Human Druid PT:1/1 -A:AB$ ChangeZone | Cost$ 1 G Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 1 G Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. S:Mode$ Continuous | Affected$ Card.Self | EffectZone$ Graveyard | AffectedZone$ Graveyard | AddHiddenKeyword$ CARDNAME count as Muscle Burst. | Description$ If CARDNAME is in a graveyard, effects from spells named Muscle Burst count it as a card named Muscle Burst. DeckHints:Name$Diligent Farmhand|Muscle Burst Oracle:{1}{G}, Sacrifice Diligent Farmhand: Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.\nIf Diligent Farmhand is in a graveyard, effects from spells named Muscle Burst count it as a card named Muscle Burst. diff --git a/forge-gui/res/cardsfolder/d/dire_strain_rampage.txt b/forge-gui/res/cardsfolder/d/dire_strain_rampage.txt index ab685face2e..6219e99691c 100644 --- a/forge-gui/res/cardsfolder/d/dire_strain_rampage.txt +++ b/forge-gui/res/cardsfolder/d/dire_strain_rampage.txt @@ -2,7 +2,7 @@ Name:Dire-Strain Rampage ManaCost:1 R G Types:Sorcery A:SP$ Destroy | ValidTgts$ Artifact,Enchantment,Land | TgtPrompt$ Select target artifact, enchantment, or land | RememberDestroyed$ True | SubAbility$ DBChangeZone | SpellDescription$ Destroy target artifact, enchantment, or land. If a land was destroyed this way, its controller may search their library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. Otherwise, its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:DBChangeZone:DB$ ChangeZone | DefinedPlayer$ TargetedController | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeNum$ X | ChangeType$ Land.Basic | Optional$ True | ShuffleNonMandatory$ True | SubAbility$ DBCleanup | StackDescription$ If a land was destroyed this way, its controller may search their library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. Otherwise, its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. +SVar:DBChangeZone:DB$ ChangeZone | DefinedPlayer$ TargetedController | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeNum$ X | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Optional$ True | ShuffleNonMandatory$ True | SubAbility$ DBCleanup | StackDescription$ If a land was destroyed this way, its controller may search their library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. Otherwise, its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Remembered$Valid Land/Plus.1 K:Flashback:3 R G diff --git a/forge-gui/res/cardsfolder/d/dragonstorm.txt b/forge-gui/res/cardsfolder/d/dragonstorm.txt index 478234ef013..97474badc4f 100644 --- a/forge-gui/res/cardsfolder/d/dragonstorm.txt +++ b/forge-gui/res/cardsfolder/d/dragonstorm.txt @@ -1,7 +1,7 @@ Name:Dragonstorm ManaCost:8 R Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Permanent.Dragon | ChangeNum$ 1 | SpellDescription$ Search your library for a Dragon permanent card, put it onto the battlefield, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Permanent.Dragon | ChangeTypeDesc$ Dragon permanent | SpellDescription$ Search your library for a Dragon permanent card, put it onto the battlefield, then shuffle. K:Storm AI:RemoveDeck:Random Oracle:Search your library for a Dragon permanent card, put it onto the battlefield, then shuffle.\nStorm (When you cast this spell, copy it for each spell cast before it this turn.) diff --git a/forge-gui/res/cardsfolder/d/dreamscape_artist.txt b/forge-gui/res/cardsfolder/d/dreamscape_artist.txt index 4f45f4ec745..a6816674ee4 100644 --- a/forge-gui/res/cardsfolder/d/dreamscape_artist.txt +++ b/forge-gui/res/cardsfolder/d/dreamscape_artist.txt @@ -2,5 +2,5 @@ Name:Dreamscape Artist ManaCost:1 U Types:Creature Human Spellshaper PT:1/1 -A:AB$ ChangeZone | Cost$ 2 U T Discard<1/Card> Sac<1/Land> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ 2 U T Discard<1/Card> Sac<1/Land> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield, then shuffle. Oracle:{2}{U}, {T}, Discard a card, Sacrifice a land: Search your library for up to two basic land cards, put them onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/d/druid_of_the_emerald_grove.txt b/forge-gui/res/cardsfolder/d/druid_of_the_emerald_grove.txt index b33c765faaf..b3c9038475a 100644 --- a/forge-gui/res/cardsfolder/d/druid_of_the_emerald_grove.txt +++ b/forge-gui/res/cardsfolder/d/druid_of_the_emerald_grove.txt @@ -3,7 +3,7 @@ ManaCost:3 G Types:Creature Dwarf Druid PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBChangeZone | TriggerDescription$ When CARDNAME enters, search your library for up to two basic land cards and reveal them, then ABILITY -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Hidden$ True | ChangeNum$ 2 | ChangeType$ Land.Basic | Destination$ Library | RememberChanged$ True | Reveal$ True | Shuffle$ False | SubAbility$ DBRollDice +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Hidden$ True | ChangeNum$ 2 | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Destination$ Library | RememberChanged$ True | Reveal$ True | Shuffle$ False | SubAbility$ DBRollDice SVar:DBRollDice:DB$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-9:TutuDaDome,10-19:WonTwoField,Else:ToFourField | SpellDescription$ then roll a d20. SVar:TutuDaDome:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.IsRemembered | ChangeNum$ 2 | SubAbility$ DBCleanup | SpellDescription$ 1-9 VERT Put those cards into your hand, then shuffle. SVar:WonTwoField:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Card.IsRemembered | ChangeNum$ 1 | ForgetChanged$ True | NoShuffle$ True | SubAbility$ WonTwoDome | SpellDescription$ 10-19 VERT Put one of those cards onto the battlefield tapped and the other into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/e/ecologists_terrarium.txt b/forge-gui/res/cardsfolder/e/ecologists_terrarium.txt index 8052be300f3..d566d13be3b 100644 --- a/forge-gui/res/cardsfolder/e/ecologists_terrarium.txt +++ b/forge-gui/res/cardsfolder/e/ecologists_terrarium.txt @@ -2,7 +2,7 @@ Name:Ecologist's Terrarium ManaCost:2 Types:Artifact T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ChangeTypeDesc$ basic land card | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True A:AB$ PutCounter | Cost$ 2 T Sac<1/CARDNAME> | ValidTgts$ Creature | SorcerySpeed$ True | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on target creature. Activate only as a sorcery. DeckHas:Ability$Counters Oracle:When Ecologist's Terrarium enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle.\n{2}, {T}, Sacrifice Ecologist's Terrarium: Put a +1/+1 counter on target creature. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/e/edge_of_autumn.txt b/forge-gui/res/cardsfolder/e/edge_of_autumn.txt index fe4ec6832de..d948e7d15ad 100644 --- a/forge-gui/res/cardsfolder/e/edge_of_autumn.txt +++ b/forge-gui/res/cardsfolder/e/edge_of_autumn.txt @@ -1,7 +1,7 @@ Name:Edge of Autumn ManaCost:1 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | ConditionPresent$ Land.YouCtrl | ConditionCompare$ LE4 | ConditionDescription$ If you control four or fewer lands, | SpellDescription$ If you control four or fewer lands, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | ConditionPresent$ Land.YouCtrl | ConditionCompare$ LE4 | ConditionDescription$ If you control four or fewer lands, | SpellDescription$ If you control four or fewer lands, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. K:Cycling:Sac<1/Land> AI:RemoveDeck:All Oracle:If you control four or fewer lands, search your library for a basic land card, put it onto the battlefield tapped, then shuffle.\nCycling—Sacrifice a land. (Sacrifice a land, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/e/elanor_gardner.txt b/forge-gui/res/cardsfolder/e/elanor_gardner.txt index 1c00c4cbd4a..85c55a7d1cf 100644 --- a/forge-gui/res/cardsfolder/e/elanor_gardner.txt +++ b/forge-gui/res/cardsfolder/e/elanor_gardner.txt @@ -5,7 +5,7 @@ PT:2/4 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigFood | TriggerDescription$ When CARDNAME enters, create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this artifact: You gain 3 life.") SVar:TrigFood:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_food_sac | TokenOwner$ You T:Mode$ Phase | Phase$ End of Turn | TriggerZones$ Battlefield | ValidPlayer$ You | CheckSVar$ FoodCheck | SVarCompare$ GE1 | OptionalDecider$ You | Execute$ TrigSearch | TriggerDescription$ At the beginning of your end step, if you sacrificed a Food this turn, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. -SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 +SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 SVar:FoodCheck:PlayerCountPropertyYou$SacrificedThisTurn Food DeckHas:Ability$LifeGain|Token|Food DeckHints:Ability$Food diff --git a/forge-gui/res/cardsfolder/e/elfhame_sanctuary.txt b/forge-gui/res/cardsfolder/e/elfhame_sanctuary.txt index 2ec4413a10a..b15e1809cab 100644 --- a/forge-gui/res/cardsfolder/e/elfhame_sanctuary.txt +++ b/forge-gui/res/cardsfolder/e/elfhame_sanctuary.txt @@ -2,7 +2,7 @@ Name:Elfhame Sanctuary ManaCost:1 G Types:Enchantment T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigChange | TriggerDescription$ At the beginning of your upkeep, you may search your library for a basic land card, reveal that card, put it into your hand, then shuffle. If you do, you skip your draw step this turn. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SubAbility$ DBSkipDraw | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SubAbility$ DBSkipDraw | ShuffleNonMandatory$ True SVar:DBSkipDraw:DB$ SkipPhase | Defined$ You | Step$ Draw | Duration$ EndOfTurn AI:RemoveDeck:All Oracle:At the beginning of your upkeep, you may search your library for a basic land card, reveal that card, put it into your hand, then shuffle. If you do, you skip your draw step this turn. diff --git a/forge-gui/res/cardsfolder/e/elvish_pioneer.txt b/forge-gui/res/cardsfolder/e/elvish_pioneer.txt index 8dbbe0d047a..319a84d7141 100644 --- a/forge-gui/res/cardsfolder/e/elvish_pioneer.txt +++ b/forge-gui/res/cardsfolder/e/elvish_pioneer.txt @@ -3,5 +3,5 @@ ManaCost:G Types:Creature Elf Druid PT:1/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may put a basic land card from your hand onto the battlefield tapped. -SVar:TrigChange:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChange:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land Oracle:When Elvish Pioneer enters, you may put a basic land card from your hand onto the battlefield tapped. diff --git a/forge-gui/res/cardsfolder/e/embermouth_sentinel.txt b/forge-gui/res/cardsfolder/e/embermouth_sentinel.txt index bd6c8475e2f..84c2a0dcc9b 100644 --- a/forge-gui/res/cardsfolder/e/embermouth_sentinel.txt +++ b/forge-gui/res/cardsfolder/e/embermouth_sentinel.txt @@ -4,7 +4,7 @@ Types:Artifact Creature Chimera PT:2/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigBranch | OptionalDecider$ You | TriggerDescription$ When this creature enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top. If you control a Dragon, put that card onto the battlefield tapped instead. SVar:TrigBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$ GE1 | TrueSubAbility$ RampantLand | FalseSubAbility$ GuidedLand -SVar:GuidedLand:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ShuffleNonMandatory$ True -SVar:RampantLand:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ShuffleNonMandatory$ True +SVar:GuidedLand:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True +SVar:RampantLand:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ShuffleNonMandatory$ True SVar:X:Count$Valid Dragon.YouCtrl Oracle:When this creature enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top. If you control a Dragon, put that card onto the battlefield tapped instead. diff --git a/forge-gui/res/cardsfolder/e/embodiment_of_spring.txt b/forge-gui/res/cardsfolder/e/embodiment_of_spring.txt index 7eada395913..5a41521d3ac 100644 --- a/forge-gui/res/cardsfolder/e/embodiment_of_spring.txt +++ b/forge-gui/res/cardsfolder/e/embodiment_of_spring.txt @@ -2,7 +2,7 @@ Name:Embodiment of Spring ManaCost:U Types:Creature Elemental PT:0/3 -A:AB$ ChangeZone | Cost$ 1 G T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card and put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 1 G T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card and put it onto the battlefield tapped, then shuffle. DeckHints:Color$Green # TODO: Just like with Sakura-Tribe Elder, the AI will sac it at its earliest convenience to search for a land. Might improve conditions for when the AI might not want to do that. Oracle:{1}{G}, {T}, Sacrifice Embodiment of Spring: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/e/emergent_sequence.txt b/forge-gui/res/cardsfolder/e/emergent_sequence.txt index 3c7d5465fc7..10f21f5001f 100644 --- a/forge-gui/res/cardsfolder/e/emergent_sequence.txt +++ b/forge-gui/res/cardsfolder/e/emergent_sequence.txt @@ -1,7 +1,7 @@ Name:Emergent Sequence ManaCost:1 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | RememberChanged$ True | ChangeNum$ 1 | SubAbility$ DBAnimate | StackDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. That land becomes a 0/0 green and blue Fractal creature that's still a land. Put a +1/+1 counter on it for each land you had enter the battlefield under your control this turn. | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. That land becomes a 0/0 green and blue Fractal creature that's still a land. Put a +1/+1 counter on it for each land you had enter the battlefield under your control this turn. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | RememberChanged$ True | ChangeNum$ 1 | SubAbility$ DBAnimate | StackDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. That land becomes a 0/0 green and blue Fractal creature that's still a land. Put a +1/+1 counter on it for each land you had enter the battlefield under your control this turn. | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. That land becomes a 0/0 green and blue Fractal creature that's still a land. Put a +1/+1 counter on it for each land you had enter the battlefield under your control this turn. SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Power$ 0 | Toughness$ 0 | Colors$ Green,Blue | OverwriteColors$ True | Types$ Creature,Fractal | Duration$ Permanent | SubAbility$ DBPutCounter SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/e/encroaching_dragonstorm.txt b/forge-gui/res/cardsfolder/e/encroaching_dragonstorm.txt index 5373a953ae6..dd24492e9fd 100644 --- a/forge-gui/res/cardsfolder/e/encroaching_dragonstorm.txt +++ b/forge-gui/res/cardsfolder/e/encroaching_dragonstorm.txt @@ -2,7 +2,7 @@ Name:Encroaching Dragonstorm ManaCost:3 G Types:Enchantment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearch | TriggerDescription$ When this enchantment enters, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. -SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 2 +SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | TriggerZones$ Battlefield | ValidCard$ Dragon.YouCtrl | Execute$ TrigReturn | TriggerDescription$ When a Dragon you control enters, return this enchantment to its owner's hand. SVar:TrigReturn:DB$ ChangeZone | Origin$ Battlefield | Destination$ Hand | Defined$ Self DeckHints:Type$Dragon diff --git a/forge-gui/res/cardsfolder/e/enigma_ridges.txt b/forge-gui/res/cardsfolder/e/enigma_ridges.txt index ab6a4851cd5..f024a503d35 100644 --- a/forge-gui/res/cardsfolder/e/enigma_ridges.txt +++ b/forge-gui/res/cardsfolder/e/enigma_ridges.txt @@ -3,7 +3,7 @@ ManaCost:no cost Types:Plane Echoir T:Mode$ PlaneswalkedTo | ValidCard$ Card.Self | Execute$ TrigRepeatEach | TriggerDescription$ When you planeswalk to CARDNAME, each player who controls fewer lands than the player who controls the most lands searches their library for a number of basic land cards less than or equal to the difference, reveals them, puts them into their hand, then shuffles. SVar:TrigRepeatEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBChangeZone -SVar:DBChangeZone:DB$ ChangeZone | ConditionCheckSVar$ X | ConditionSVarCompare$ LTY | DefinedPlayer$ Remembered | Chooser$ Remembered | ChangeType$ Land.Basic | ChangeNum$ Z | Origin$ Library | Destination$ Hand +SVar:DBChangeZone:DB$ ChangeZone | ConditionCheckSVar$ X | ConditionSVarCompare$ LTY | DefinedPlayer$ Remembered | Chooser$ Remembered | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ Z | Origin$ Library | Destination$ Hand SVar:X:Count$Valid Land.RememberedPlayerCtrl SVar:Y:PlayerCountPlayers$HighestValid Land.YouCtrl SVar:Z:SVar$Y/Minus.X diff --git a/forge-gui/res/cardsfolder/e/enlightened_tutor.txt b/forge-gui/res/cardsfolder/e/enlightened_tutor.txt index f6e98e8bd09..bd185b8bfa0 100644 --- a/forge-gui/res/cardsfolder/e/enlightened_tutor.txt +++ b/forge-gui/res/cardsfolder/e/enlightened_tutor.txt @@ -1,6 +1,6 @@ Name:Enlightened Tutor ManaCost:W Types:Instant -A:SP$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Artifact,Enchantment | ChangeNum$ 1 | SpellDescription$ Search your library for an artifact or enchantment card, reveal it, then shuffle and put that card on top. +A:SP$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Artifact,Enchantment | SpellDescription$ Search your library for an artifact or enchantment card, reveal it, then shuffle and put that card on top. AI:RemoveDeck:Random Oracle:Search your library for an artifact or enchantment card, reveal it, then shuffle and put that card on top. diff --git a/forge-gui/res/cardsfolder/e/entish_restoration.txt b/forge-gui/res/cardsfolder/e/entish_restoration.txt index 00db588e562..e7ce321079e 100644 --- a/forge-gui/res/cardsfolder/e/entish_restoration.txt +++ b/forge-gui/res/cardsfolder/e/entish_restoration.txt @@ -2,7 +2,7 @@ Name:Entish Restoration ManaCost:2 G Types:Instant A:SP$ Sacrifice | Defined$ You | SacValid$ Land | SubAbility$ DBChangeZone | SpellDescription$ Sacrifice a land. Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. If you control a creature with power 4 or greater, instead search your library for up to three basic land cards, put them onto the battlefield tapped, then shuffle. -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ X | Tapped$ True +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | Tapped$ True SVar:AIPreference:SacCost$Land.Basic+tapped SVar:X:Count$Compare Y GE1.3.2 SVar:Y:Count$Valid Creature.YouCtrl+powerGE4 diff --git a/forge-gui/res/cardsfolder/e/environmental_sciences.txt b/forge-gui/res/cardsfolder/e/environmental_sciences.txt index ec41b822cb2..69fabb0e0bc 100644 --- a/forge-gui/res/cardsfolder/e/environmental_sciences.txt +++ b/forge-gui/res/cardsfolder/e/environmental_sciences.txt @@ -1,7 +1,7 @@ Name:Environmental Sciences ManaCost:2 Types:Sorcery Lesson -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SubAbility$ DBGainLife | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. You gain 2 life. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SubAbility$ DBGainLife | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. You gain 2 life. SVar:DBGainLife:DB$ GainLife | LifeAmount$ 2 DeckHas:Ability$LifeGain Oracle:Search your library for a basic land card, reveal it, put it into your hand, then shuffle. You gain 2 life. diff --git a/forge-gui/res/cardsfolder/e/escape_tunnel.txt b/forge-gui/res/cardsfolder/e/escape_tunnel.txt index 6ff53b6e1ff..9cd28e00f2f 100644 --- a/forge-gui/res/cardsfolder/e/escape_tunnel.txt +++ b/forge-gui/res/cardsfolder/e/escape_tunnel.txt @@ -1,7 +1,7 @@ Name:Escape Tunnel ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. A:AB$ Effect | Cost$ T Sac<1/CARDNAME> | ValidTgts$ Creature.powerLE2 | TgtPrompt$ Select target creature with power 2 or less | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | StaticAbilities$ Unblockable | AILogic$ Pump | StackDescription$ {c:Targeted} can't be blocked this turn. | SpellDescription$ Target creature with power 2 or less can't be blocked this turn. SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. DeckHas:Ability$Sacrifice diff --git a/forge-gui/res/cardsfolder/e/esper_panorama.txt b/forge-gui/res/cardsfolder/e/esper_panorama.txt index fc623cba812..087e7d0c5c9 100644 --- a/forge-gui/res/cardsfolder/e/esper_panorama.txt +++ b/forge-gui/res/cardsfolder/e/esper_panorama.txt @@ -2,5 +2,5 @@ Name:Esper Panorama ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Plains+Basic,Land.Island+Basic,Land.Swamp+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Plains, Island, or Swamp card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Plains.Basic,Island.Basic,Swamp.Basic | ChangeTypeDesc$ basic Plains, Island, or Swamp | SpellDescription$ Search your library for a basic Plains, Island, or Swamp card, put it onto the battlefield tapped, then shuffle. Oracle:{T}: Add {C}.\n{1}, {T}, Sacrifice Esper Panorama: Search your library for a basic Plains, Island, or Swamp card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/e/evolution_charm.txt b/forge-gui/res/cardsfolder/e/evolution_charm.txt index 8946fd1b636..28b8fd342fb 100644 --- a/forge-gui/res/cardsfolder/e/evolution_charm.txt +++ b/forge-gui/res/cardsfolder/e/evolution_charm.txt @@ -2,7 +2,7 @@ Name:Evolution Charm ManaCost:1 G Types:Instant A:SP$ Charm | Choices$ SearchLand,Return,DoPump | CharmNum$ 1 -SVar:SearchLand:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +SVar:SearchLand:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. SVar:Return:DB$ ChangeZone | TgtPrompt$ Choose target creature card in your graveyard | ValidTgts$ Creature.YouCtrl | Origin$ Graveyard | Destination$ Hand | SpellDescription$ Return target creature card from your graveyard to your hand. SVar:DoPump:DB$ Pump | ValidTgts$ Creature | KW$ Flying | TgtPrompt$ Select target creature | SpellDescription$ Target creature gains flying until end of turn. AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/e/evolving_wilds.txt b/forge-gui/res/cardsfolder/e/evolving_wilds.txt index 15e0f5fe8af..0ec71745caf 100644 --- a/forge-gui/res/cardsfolder/e/evolving_wilds.txt +++ b/forge-gui/res/cardsfolder/e/evolving_wilds.txt @@ -1,5 +1,5 @@ Name:Evolving Wilds ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Oracle:{T}, Sacrifice Evolving Wilds: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/e/exploding_borders.txt b/forge-gui/res/cardsfolder/e/exploding_borders.txt index 06f98408353..47dd36b8c58 100644 --- a/forge-gui/res/cardsfolder/e/exploding_borders.txt +++ b/forge-gui/res/cardsfolder/e/exploding_borders.txt @@ -1,7 +1,7 @@ Name:Exploding Borders ManaCost:2 R G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | SubAbility$ DBDealDamage | SpellDescription$ Domain — Search your library for a basic land card and put that card onto the battlefield tapped. Then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | SubAbility$ DBDealDamage | SpellDescription$ Domain — Search your library for a basic land card and put that card onto the battlefield tapped. Then shuffle. SVar:DBDealDamage:DB$ DealDamage | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ X | SpellDescription$ CARDNAME deals X damage to target player or planeswalker, where X is the number of basic land types among lands you control. SVar:X:Count$Domain Oracle:Domain — Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Exploding Borders deals X damage to target player or planeswalker, where X is the number of basic land types among lands you control. diff --git a/forge-gui/res/cardsfolder/e/explosive_vegetation.txt b/forge-gui/res/cardsfolder/e/explosive_vegetation.txt index 58ff4299097..627f2f6acf1 100644 --- a/forge-gui/res/cardsfolder/e/explosive_vegetation.txt +++ b/forge-gui/res/cardsfolder/e/explosive_vegetation.txt @@ -1,5 +1,5 @@ Name:Explosive Vegetation ManaCost:3 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. Oracle:Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/f/fabled_passage.txt b/forge-gui/res/cardsfolder/f/fabled_passage.txt index e51b36ab511..82434537b09 100644 --- a/forge-gui/res/cardsfolder/f/fabled_passage.txt +++ b/forge-gui/res/cardsfolder/f/fabled_passage.txt @@ -1,7 +1,7 @@ Name:Fabled Passage ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBUntap | StackDescription$ SpellDescription | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Then if you control four or more lands, untap that land. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | RememberChanged$ True | SubAbility$ DBUntap | StackDescription$ SpellDescription | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Then if you control four or more lands, untap that land. SVar:DBUntap:DB$ Untap | Defined$ Remembered | ConditionPresent$ Land.YouCtrl | ConditionCompare$ GE4 | ConditionDescription$ If you control four or more lands, untap that land. | SubAbility$ DBCleanup | StackDescription$ None SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:{T}, Sacrifice Fabled Passage: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Then if you control four or more lands, untap that land. diff --git a/forge-gui/res/cardsfolder/f/far_wanderings.txt b/forge-gui/res/cardsfolder/f/far_wanderings.txt index d32ecf108c6..96c324381fc 100644 --- a/forge-gui/res/cardsfolder/f/far_wanderings.txt +++ b/forge-gui/res/cardsfolder/f/far_wanderings.txt @@ -1,6 +1,6 @@ Name:Far Wanderings ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ X | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Threshold — If there are seven or more cards in your graveyard, instead search your library for up to three basic land cards, put them onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ X | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Threshold — If there are seven or more cards in your graveyard, instead search your library for up to three basic land cards, put them onto the battlefield tapped, then shuffle. SVar:X:Count$Threshold.3.1 Oracle:Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.\nThreshold — If there are seven or more cards in your graveyard, instead search your library for up to three basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/f/farfinder.txt b/forge-gui/res/cardsfolder/f/farfinder.txt index 02484e75c9b..4b262a19a68 100644 --- a/forge-gui/res/cardsfolder/f/farfinder.txt +++ b/forge-gui/res/cardsfolder/f/farfinder.txt @@ -4,5 +4,5 @@ Types:Creature Fox PT:1/1 K:Vigilance T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:Vigilance\nWhen Farfinder enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/f/farhaven_elf.txt b/forge-gui/res/cardsfolder/f/farhaven_elf.txt index 2e531f3c1aa..32691b84bd2 100644 --- a/forge-gui/res/cardsfolder/f/farhaven_elf.txt +++ b/forge-gui/res/cardsfolder/f/farhaven_elf.txt @@ -3,5 +3,5 @@ ManaCost:2 G Types:Creature Elf Druid PT:1/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:When Farhaven Elf enters, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/f/fertilid.txt b/forge-gui/res/cardsfolder/f/fertilid.txt index 8c8f6218f6c..f2cf6ef916f 100644 --- a/forge-gui/res/cardsfolder/f/fertilid.txt +++ b/forge-gui/res/cardsfolder/f/fertilid.txt @@ -3,6 +3,6 @@ ManaCost:2 G Types:Creature Elemental PT:0/0 K:etbCounter:P1P1:2 -A:AB$ ChangeZone | Cost$ 1 G SubCounter<1/P1P1> | ValidTgts$ Player | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | Chooser$ Targeted | SpellDescription$ Target player searches their library for a basic land card, puts it onto the battlefield tapped, then shuffles. +A:AB$ ChangeZone | Cost$ 1 G SubCounter<1/P1P1> | ValidTgts$ Player | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Chooser$ Targeted | SpellDescription$ Target player searches their library for a basic land card, puts it onto the battlefield tapped, then shuffles. DeckHas:Ability$Counters Oracle:Fertilid enters with two +1/+1 counters on it.\n{1}{G}, Remove a +1/+1 counter from Fertilid: Target player searches their library for a basic land card, puts it onto the battlefield tapped, then shuffles. diff --git a/forge-gui/res/cardsfolder/f/fertilids_favor.txt b/forge-gui/res/cardsfolder/f/fertilids_favor.txt index 454c67933b3..375bc0a836f 100644 --- a/forge-gui/res/cardsfolder/f/fertilids_favor.txt +++ b/forge-gui/res/cardsfolder/f/fertilids_favor.txt @@ -1,7 +1,7 @@ Name:Fertilid's Favor ManaCost:3 G Types:Instant -A:SP$ ChangeZone | ValidTgts$ Player | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SubAbility$ DBCounter | Chooser$ Targeted | SpellDescription$ Target player searches their library for a basic land card, puts it onto the battlefield tapped, then shuffles. Put two +1/+1 counters on up to one target artifact or creature. +A:SP$ ChangeZone | ValidTgts$ Player | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SubAbility$ DBCounter | Chooser$ Targeted | SpellDescription$ Target player searches their library for a basic land card, puts it onto the battlefield tapped, then shuffles. Put two +1/+1 counters on up to one target artifact or creature. SVar:DBCounter:DB$ PutCounter | ValidTgts$ Creature,Artifact | TgtPrompt$ Select up to one target creature or artifact | TargetMin$ 0 | TargetMax$ 1 | CounterType$ P1P1 | CounterNum$ 2 DeckHas:Ability$Counters Oracle:Target player searches their library for a basic land card, puts it onto the battlefield tapped, then shuffles. Put two +1/+1 counters on up to one target artifact or creature. diff --git a/forge-gui/res/cardsfolder/f/field_of_ruin.txt b/forge-gui/res/cardsfolder/f/field_of_ruin.txt index c7bf2aa8719..7c5b47b1a27 100644 --- a/forge-gui/res/cardsfolder/f/field_of_ruin.txt +++ b/forge-gui/res/cardsfolder/f/field_of_ruin.txt @@ -3,6 +3,6 @@ ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. A:AB$ Destroy | Cost$ 2 T Sac<1/CARDNAME> | ValidTgts$ Land.nonBasic+OppCtrl | TgtPrompt$ Select target nonbasic land an opponent controls. | SubAbility$ DBSearch | AILogic$ GhostQuarter | SpellDescription$ Destroy target nonbasic land an opponent controls. Each player searches their library for a basic land card, puts it onto the battlefield, then shuffles. -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ Player | ChangeType$ Land.Basic | ChangeNum$ 1 | StackDescription$ Each player searches their library for a basic land card, puts it onto the battlefield, then shuffles their library. +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ Player | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | StackDescription$ Each player searches their library for a basic land card, puts it onto the battlefield, then shuffles their library. AI:RemoveDeck:Random Oracle:{T}: Add {C}.\n{2}, {T}, Sacrifice Field of Ruin: Destroy target nonbasic land an opponent controls. Each player searches their library for a basic land card, puts it onto the battlefield, then shuffles. diff --git a/forge-gui/res/cardsfolder/f/firebrand_ranger.txt b/forge-gui/res/cardsfolder/f/firebrand_ranger.txt index 7777bcbea11..2ff329505f3 100644 --- a/forge-gui/res/cardsfolder/f/firebrand_ranger.txt +++ b/forge-gui/res/cardsfolder/f/firebrand_ranger.txt @@ -2,5 +2,5 @@ Name:Firebrand Ranger ManaCost:1 R Types:Creature Human Soldier Ranger PT:2/1 -A:AB$ ChangeZone | Cost$ G T | Origin$ Hand | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Optional$ You | AILogic$ AtOppEOT | SpellDescription$ You may put a basic land card from your hand onto the battlefield. +A:AB$ ChangeZone | Cost$ G T | Origin$ Hand | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Optional$ You | AILogic$ AtOppEOT | SpellDescription$ You may put a basic land card from your hand onto the battlefield. Oracle:{G}, {T}: You may put a basic land card from your hand onto the battlefield. diff --git a/forge-gui/res/cardsfolder/f/flare_of_cultivation.txt b/forge-gui/res/cardsfolder/f/flare_of_cultivation.txt index 5a8b310ac30..f6a12c40603 100644 --- a/forge-gui/res/cardsfolder/f/flare_of_cultivation.txt +++ b/forge-gui/res/cardsfolder/f/flare_of_cultivation.txt @@ -2,7 +2,7 @@ Name:Flare of Cultivation ManaCost:1 G G Types:Sorcery S:Mode$ AlternativeCost | ValidSA$ Spell.Self | EffectZone$ All | Cost$ Sac<1/Creature.Green+!token/a nontoken green creature> | Description$ You may sacrifice a nontoken green creature rather than pay this spell's mana cost. -A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle. SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.IsRemembered | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put onto the battlefield | Tapped$ True | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.IsRemembered | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put into your hand | StackDescription$ None | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/f/flood_plain.txt b/forge-gui/res/cardsfolder/f/flood_plain.txt index af5e7da885d..848180bfe24 100644 --- a/forge-gui/res/cardsfolder/f/flood_plain.txt +++ b/forge-gui/res/cardsfolder/f/flood_plain.txt @@ -3,5 +3,5 @@ ManaCost:no cost Types:Land R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Plains,Island | ChangeNum$ 1 | SpellDescription$ Search your library for a Plains or Island card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Plains,Island | SpellDescription$ Search your library for a Plains or Island card, put it onto the battlefield, then shuffle. Oracle:Flood Plain enters tapped.\n{T}, Sacrifice Flood Plain: Search your library for a Plains or Island card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/f/flooded_strand.txt b/forge-gui/res/cardsfolder/f/flooded_strand.txt index f27bf84964c..7861fce53b1 100644 --- a/forge-gui/res/cardsfolder/f/flooded_strand.txt +++ b/forge-gui/res/cardsfolder/f/flooded_strand.txt @@ -1,5 +1,5 @@ Name:Flooded Strand ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Plains,Island | ChangeNum$ 1 | SpellDescription$ Search your library for a Plains or Island card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Plains,Island | SpellDescription$ Search your library for a Plains or Island card, put it onto the battlefield, then shuffle. Oracle:{T}, Pay 1 life, Sacrifice Flooded Strand: Search your library for a Plains or Island card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/f/flower_flourish.txt b/forge-gui/res/cardsfolder/f/flower_flourish.txt index 0c541acd6c9..6fde17b50d0 100644 --- a/forge-gui/res/cardsfolder/f/flower_flourish.txt +++ b/forge-gui/res/cardsfolder/f/flower_flourish.txt @@ -1,7 +1,7 @@ Name:Flower ManaCost:GW Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Forest+Basic,Land.Plains+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Forest or Plains card, reveal it, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Forest.Basic,Plains.Basic | ChangeTypeDesc$ basic Forest or Plains | SpellDescription$ Search your library for a basic Forest or Plains card, reveal it, put it into your hand, then shuffle. AlternateMode:Split Oracle:Search your library for a basic Forest or Plains card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/f/font_of_fertility.txt b/forge-gui/res/cardsfolder/f/font_of_fertility.txt index 0a5b76a6873..fe2736142ee 100644 --- a/forge-gui/res/cardsfolder/f/font_of_fertility.txt +++ b/forge-gui/res/cardsfolder/f/font_of_fertility.txt @@ -1,5 +1,5 @@ Name:Font of Fertility ManaCost:G Types:Enchantment -A:AB$ ChangeZone | Cost$ 1 G Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card and put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 1 G Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card and put it onto the battlefield tapped, then shuffle. Oracle:{1}{G}, Sacrifice Font of Fertility: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/f/forceful_cultivator.txt b/forge-gui/res/cardsfolder/f/forceful_cultivator.txt index 61d74845f10..42b203bdf6e 100644 --- a/forge-gui/res/cardsfolder/f/forceful_cultivator.txt +++ b/forge-gui/res/cardsfolder/f/forceful_cultivator.txt @@ -4,5 +4,5 @@ Types:Creature Snake Shaman PT:2/3 S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ 2 | EffectZone$ All | IsPresent$ Land.YouOwn | PresentZone$ Hand | PresentCompare$ EQ0 | Description$ This spell costs {2} less to cast if there are no land cards in your hand. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters, search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land Oracle:This spell costs {2} less to cast if there are no land cards in your hand.\nWhen Forceful Cultivator enters, search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/f/foreboding_landscape.txt b/forge-gui/res/cardsfolder/f/foreboding_landscape.txt index 34b38bcd9f5..fac61eea1f9 100644 --- a/forge-gui/res/cardsfolder/f/foreboding_landscape.txt +++ b/forge-gui/res/cardsfolder/f/foreboding_landscape.txt @@ -2,6 +2,6 @@ Name:Foreboding Landscape ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Swamp+Basic,Land.Forest+Basic,Land.Island+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Swamp, Forest, or Island card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Swamp.Basic,Forest.Basic,Island.Basic | ChangeTypeDesc$ basic Swamp, Forest, or Island | SpellDescription$ Search your library for a basic Swamp, Forest, or Island card, put it onto the battlefield tapped, then shuffle. K:Cycling:B G U Oracle:{T}: Add {C}.\n{T}, Sacrifice Foreboding Landscape: Search your library for a basic Swamp, Forest, or Island card, put it onto the battlefield tapped, then shuffle.\nCycling {B}{G}{U} ({B}{G}{U}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/f/forestfolk.txt b/forge-gui/res/cardsfolder/f/forestfolk.txt index 8b6286097c4..c5daf467c49 100644 --- a/forge-gui/res/cardsfolder/f/forestfolk.txt +++ b/forge-gui/res/cardsfolder/f/forestfolk.txt @@ -3,7 +3,7 @@ ManaCost:2 G U Types:Creature Elf Wizard PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME leaves the battlefield, draw a card. SVar:TrigDraw:DB$ Draw | Defined$ TriggeredCardController | NumCards$ 1 SVar:SacMe:1 diff --git a/forge-gui/res/cardsfolder/f/fork_in_the_road.txt b/forge-gui/res/cardsfolder/f/fork_in_the_road.txt index 4c28cc25c8a..5d093a7b355 100644 --- a/forge-gui/res/cardsfolder/f/fork_in_the_road.txt +++ b/forge-gui/res/cardsfolder/f/fork_in_the_road.txt @@ -1,7 +1,7 @@ Name:Fork in the Road ManaCost:1 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards and reveal them. Put one into your hand and the other into your graveyard. Then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards and reveal them. Put one into your hand and the other into your graveyard. Then shuffle. SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic+IsRemembered | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put into your hand | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Graveyard | ChangeType$ Land.Basic+IsRemembered | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put into your graveyard | StackDescription$ None | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/f/fountainport_bell.txt b/forge-gui/res/cardsfolder/f/fountainport_bell.txt index d8cf86af6ce..d7bfe43acd8 100644 --- a/forge-gui/res/cardsfolder/f/fountainport_bell.txt +++ b/forge-gui/res/cardsfolder/f/fountainport_bell.txt @@ -2,6 +2,6 @@ Name:Fountainport Bell ManaCost:1 Types:Artifact T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True A:AB$ Draw | Cost$ 1 Sac<1/CARDNAME> | NumCards$ 1 | SpellDescription$ Draw a card. Oracle:When Fountainport Bell enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top.\n{1}, Sacrifice Fountainport Bell: Draw a card. diff --git a/forge-gui/res/cardsfolder/f/frenzied_tilling.txt b/forge-gui/res/cardsfolder/f/frenzied_tilling.txt index 20952793f12..d7597aec863 100644 --- a/forge-gui/res/cardsfolder/f/frenzied_tilling.txt +++ b/forge-gui/res/cardsfolder/f/frenzied_tilling.txt @@ -2,5 +2,5 @@ Name:Frenzied Tilling ManaCost:3 R G Types:Sorcery A:SP$ Destroy | ValidTgts$ Land | TgtPrompt$ Select target land | SubAbility$ DBSearch | SpellDescription$ Destroy target land. Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True Oracle:Destroy target land. Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/f/from_the_ashes.txt b/forge-gui/res/cardsfolder/f/from_the_ashes.txt index 90107398379..06a2707881b 100644 --- a/forge-gui/res/cardsfolder/f/from_the_ashes.txt +++ b/forge-gui/res/cardsfolder/f/from_the_ashes.txt @@ -3,7 +3,7 @@ ManaCost:3 R Types:Sorcery A:SP$ DestroyAll | ValidCards$ Land.nonBasic | RememberDestroyed$ True | SubAbility$ DBRepeat | SpellDescription$ Destroy all nonbasic lands. For each land destroyed this way, its controller may search their library for a basic land card and put it onto the battlefield. Then each player who searched their library this way shuffles. SVar:DBRepeat:DB$ RepeatEach | UseImprinted$ True | DefinedCards$ DirectRemembered | RepeatSubAbility$ DBSearch | SubAbility$ DBShuffle -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | RememberChanged$ True | DefinedPlayer$ ImprintedController | Chooser$ ImprintedController | NoShuffle$ True | Optional$ True +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | RememberChanged$ True | DefinedPlayer$ ImprintedController | Chooser$ ImprintedController | NoShuffle$ True | Optional$ True SVar:DBShuffle:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ ShuffleSearched | SubAbility$ DBCleanup SVar:ShuffleSearched:DB$ Shuffle | Defined$ Player.IsRemembered | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 SVar:X:Count$Valid Card.IsRemembered+RememberedPlayerCtrl diff --git a/forge-gui/res/cardsfolder/f/frontier_guide.txt b/forge-gui/res/cardsfolder/f/frontier_guide.txt index d127259f69f..f2f9b6cd5e8 100644 --- a/forge-gui/res/cardsfolder/f/frontier_guide.txt +++ b/forge-gui/res/cardsfolder/f/frontier_guide.txt @@ -2,5 +2,5 @@ Name:Frontier Guide ManaCost:1 G Types:Creature Elf Scout PT:1/1 -A:AB$ ChangeZone | Cost$ 3 G T | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 3 G T | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Oracle:{3}{G}, {T}: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/g/geomancers_gambit.txt b/forge-gui/res/cardsfolder/g/geomancers_gambit.txt index 69bf863a9f1..8a6d5783574 100644 --- a/forge-gui/res/cardsfolder/g/geomancers_gambit.txt +++ b/forge-gui/res/cardsfolder/g/geomancers_gambit.txt @@ -2,6 +2,6 @@ Name:Geomancer's Gambit ManaCost:2 R Types:Sorcery A:SP$ Destroy | ValidTgts$ Land | TgtPrompt$ Select target land | SubAbility$ DBChange | SpellDescription$ Destroy target land. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle. -SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True | SubAbility$ DBDraw +SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True | SubAbility$ DBDraw SVar:DBDraw:DB$ Draw | NumCards$ 1 | SpellDescription$ Draw a card. Oracle:Destroy target land. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle.\nDraw a card. diff --git a/forge-gui/res/cardsfolder/g/ghost_quarter.txt b/forge-gui/res/cardsfolder/g/ghost_quarter.txt index f239fef961c..a785783c29c 100644 --- a/forge-gui/res/cardsfolder/g/ghost_quarter.txt +++ b/forge-gui/res/cardsfolder/g/ghost_quarter.txt @@ -3,6 +3,6 @@ ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. A:AB$ Destroy | Cost$ T Sac<1/CARDNAME> | ValidTgts$ Land | TgtPrompt$ Select target land. | SubAbility$ DBChange | AILogic$ GhostQuarter | AITgts$ Land.nonBasic | SpellDescription$ Destroy target land. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle. -SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True +SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True AI:RemoveDeck:Random Oracle:{T}: Add {C}.\n{T}, Sacrifice Ghost Quarter: Destroy target land. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/g/giant_ladybug.txt b/forge-gui/res/cardsfolder/g/giant_ladybug.txt index bab6095c03c..5c430428883 100644 --- a/forge-gui/res/cardsfolder/g/giant_ladybug.txt +++ b/forge-gui/res/cardsfolder/g/giant_ladybug.txt @@ -4,5 +4,5 @@ Types:Creature Insect PT:4/1 K:Reach T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:Reach\nWhen Giant Ladybug enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top. diff --git a/forge-gui/res/cardsfolder/g/go_forth.txt b/forge-gui/res/cardsfolder/g/go_forth.txt index 230607bacf7..4fa42016702 100644 --- a/forge-gui/res/cardsfolder/g/go_forth.txt +++ b/forge-gui/res/cardsfolder/g/go_forth.txt @@ -2,6 +2,6 @@ Name:Go Forth ManaCost:G Types:Instant A:SP$ Charm | Choices$ DBSearch,DBPump | CharmNum$ 1 -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. SVar:DBPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +2 | NumDef$ +2 | SpellDescription$ Target creature gets +2/+2 until end of turn. Oracle:Choose one —\n• Search your library for a basic land card, reveal it, put it into your hand, then shuffle.\n• Target creature gets +2/+2 until end of turn. diff --git a/forge-gui/res/cardsfolder/g/grasslands.txt b/forge-gui/res/cardsfolder/g/grasslands.txt index 74bd3afc184..de704bdb6f0 100644 --- a/forge-gui/res/cardsfolder/g/grasslands.txt +++ b/forge-gui/res/cardsfolder/g/grasslands.txt @@ -3,5 +3,5 @@ ManaCost:no cost Types:Land R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Forest,Plains | ChangeNum$ 1 | SpellDescription$ Search your library for a Forest or Plains card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Forest,Plains | SpellDescription$ Search your library for a Forest or Plains card, put it onto the battlefield, then shuffle. Oracle:Grasslands enters tapped.\n{T}, Sacrifice Grasslands: Search your library for a Forest or Plains card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/g/greater_tanuki.txt b/forge-gui/res/cardsfolder/g/greater_tanuki.txt index d86c1786efa..948fb996178 100644 --- a/forge-gui/res/cardsfolder/g/greater_tanuki.txt +++ b/forge-gui/res/cardsfolder/g/greater_tanuki.txt @@ -3,6 +3,6 @@ ManaCost:4 G G Types:Enchantment Creature Dog PT:6/5 K:Trample -A:AB$ ChangeZone | PrecostDesc$ Channel — | Cost$ 2 G Discard<1/CARDNAME> | ActivationZone$ Hand | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | StackDescription$ SpellDescription | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | PrecostDesc$ Channel — | Cost$ 2 G Discard<1/CARDNAME> | ActivationZone$ Hand | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | StackDescription$ SpellDescription | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. DeckHas:Ability$Discard Oracle:Trample\nChannel — {2}{G}, Discard Greater Tanuki: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/g/greenseeker.txt b/forge-gui/res/cardsfolder/g/greenseeker.txt index 8f0bfc2533e..f717290a627 100644 --- a/forge-gui/res/cardsfolder/g/greenseeker.txt +++ b/forge-gui/res/cardsfolder/g/greenseeker.txt @@ -2,6 +2,6 @@ Name:Greenseeker ManaCost:G Types:Creature Elf Spellshaper PT:1/1 -A:AB$ ChangeZone | Cost$ G T Discard<1/Card> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ G T Discard<1/Card> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. AI:RemoveDeck:All Oracle:{G}, {T}, Discard a card: Search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/g/grixis_panorama.txt b/forge-gui/res/cardsfolder/g/grixis_panorama.txt index 9128052e71e..1e46036959f 100644 --- a/forge-gui/res/cardsfolder/g/grixis_panorama.txt +++ b/forge-gui/res/cardsfolder/g/grixis_panorama.txt @@ -2,5 +2,5 @@ Name:Grixis Panorama ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Island+Basic,Land.Swamp+Basic,Land.Mountain+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Island.Basic,Swamp.Basic,Mountain.Basic | ChangeTypeDesc$ basic Island, Swamp, or Mountain | SpellDescription$ Search your library for a basic Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle. Oracle:{T}: Add {C}.\n{1}, {T}, Sacrifice Grixis Panorama: Search your library for a basic Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/g/grow_from_the_ashes.txt b/forge-gui/res/cardsfolder/g/grow_from_the_ashes.txt index 5d65cc68226..eb7d1eb63ae 100644 --- a/forge-gui/res/cardsfolder/g/grow_from_the_ashes.txt +++ b/forge-gui/res/cardsfolder/g/grow_from_the_ashes.txt @@ -2,6 +2,6 @@ Name:Grow from the Ashes ManaCost:2 G Types:Sorcery K:Kicker:2 -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ X | SpellDescription$ Search your library for a basic land card, put it onto the battlefield, then shuffle. If this spell was kicked, instead search your library for two basic land cards, put them onto the battlefield, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | SpellDescription$ Search your library for a basic land card, put it onto the battlefield, then shuffle. If this spell was kicked, instead search your library for two basic land cards, put them onto the battlefield, then shuffle. SVar:X:Count$Kicked.2.1 Oracle:Kicker {2} (You may pay an additional {2} as you cast this spell.)\nSearch your library for a basic land card, put it onto the battlefield, then shuffle. If this spell was kicked, instead search your library for two basic land cards, put them onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/g/growth_charm.txt b/forge-gui/res/cardsfolder/g/growth_charm.txt index 9336bc70fdb..db5fd0d4bb7 100644 --- a/forge-gui/res/cardsfolder/g/growth_charm.txt +++ b/forge-gui/res/cardsfolder/g/growth_charm.txt @@ -2,7 +2,7 @@ Name:Growth Charm ManaCost:1 G G Types:Instant A:SP$ Charm | Choices$ DBRamp,DBGiant,DBRegrow | Defined$ You -SVar:DBRamp:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card and put that card onto the battlefield tapped. Then shuffle. +SVar:DBRamp:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card and put that card onto the battlefield tapped. Then shuffle. SVar:DBGiant:DB$ Pump | Cost$ G | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +3 | NumDef$ +3 | SpellDescription$ Target creature gets +3/+3 until end of turn. SVar:DBRegrow:DB$ ChangeZone | Cost$ 1 G | Origin$ Graveyard | Destination$ Hand | TgtPrompt$ Select target card in your graveyard | ValidTgts$ Card.YouCtrl | SpellDescription$ Return target card from your graveyard to your hand. Oracle:Choose one —\n• Rampant Growth\n• Giant Growth\n• Regrowth diff --git a/forge-gui/res/cardsfolder/g/growth_spasm.txt b/forge-gui/res/cardsfolder/g/growth_spasm.txt index 242a42e4c01..e0aeec9d04e 100644 --- a/forge-gui/res/cardsfolder/g/growth_spasm.txt +++ b/forge-gui/res/cardsfolder/g/growth_spasm.txt @@ -1,7 +1,7 @@ Name:Growth Spasm ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | SubAbility$ DBToken | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | SubAbility$ DBToken | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_0_1_eldrazi_spawn_sac | TokenOwner$ You | SpellDescription$ Create a 0/1 colorless Eldrazi Spawn creature token. It has "Sacrifice this creature: Add {C}." DeckHints:Type$Eldrazi DeckHas:Ability$Mana.Colorless|Token diff --git a/forge-gui/res/cardsfolder/h/harrow.txt b/forge-gui/res/cardsfolder/h/harrow.txt index 33569b4e841..3b2e4d12777 100644 --- a/forge-gui/res/cardsfolder/h/harrow.txt +++ b/forge-gui/res/cardsfolder/h/harrow.txt @@ -1,6 +1,6 @@ Name:Harrow ManaCost:2 G Types:Instant -A:SP$ ChangeZone | Cost$ 2 G Sac<1/Land> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield, then shuffle. +A:SP$ ChangeZone | Cost$ 2 G Sac<1/Land> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield, then shuffle. SVar:AIPreference:SacCost$Land.Basic Oracle:As an additional cost to cast this spell, sacrifice a land.\nSearch your library for up to two basic land cards, put them onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/h/harvest_season.txt b/forge-gui/res/cardsfolder/h/harvest_season.txt index f3a644cf5d6..c488d62ea4b 100644 --- a/forge-gui/res/cardsfolder/h/harvest_season.txt +++ b/forge-gui/res/cardsfolder/h/harvest_season.txt @@ -1,6 +1,6 @@ Name:Harvest Season ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ X | Tapped$ True | SpellDescription$ Search your library for up to X basic land cards, where X is the number of tapped creatures you control, put those cards onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | Tapped$ True | SpellDescription$ Search your library for up to X basic land cards, where X is the number of tapped creatures you control, put those cards onto the battlefield tapped, then shuffle. SVar:X:Count$Valid Creature.tapped+YouCtrl Oracle:Search your library for up to X basic land cards, where X is the number of tapped creatures you control, put those cards onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/h/heaped_harvest.txt b/forge-gui/res/cardsfolder/h/heaped_harvest.txt index 696a3e01720..2b86377d262 100644 --- a/forge-gui/res/cardsfolder/h/heaped_harvest.txt +++ b/forge-gui/res/cardsfolder/h/heaped_harvest.txt @@ -3,7 +3,7 @@ ManaCost:2 G Types:Artifact Food T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters and when you sacrifice it, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. T:Mode$ Sacrificed | ValidPlayer$ You | ValidCard$ Card.Self | Execute$ TrigChange | Secondary$ True | TriggerZones$ Battlefield | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters and when you sacrifice it, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True A:AB$ GainLife | Cost$ 2 T Sac<1/CARDNAME> | Defined$ You | LifeAmount$ 3 | SpellDescription$ You gain 3 life. SVar:SacMe:5 DeckHas:Ability$LifeGain|Food diff --git a/forge-gui/res/cardsfolder/h/herd_migration.txt b/forge-gui/res/cardsfolder/h/herd_migration.txt index df23cd8cc77..0abc160a05f 100644 --- a/forge-gui/res/cardsfolder/h/herd_migration.txt +++ b/forge-gui/res/cardsfolder/h/herd_migration.txt @@ -2,7 +2,7 @@ Name:Herd Migration ManaCost:6 G Types:Sorcery A:SP$ Token | TokenAmount$ X | TokenScript$ g_3_3_beast | TokenOwner$ You | SpellDescription$ Domain — Create a 3/3 green Beast creature token for each basic land type among lands you control. -A:AB$ ChangeZone | Cost$ 1 G Discard<1/CARDNAME> | ActivationZone$ Hand | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ChangeTypeDesc$ basic land card | SubAbility$ DBGainLife | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. You gain 3 life. +A:AB$ ChangeZone | Cost$ 1 G Discard<1/CARDNAME> | ActivationZone$ Hand | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SubAbility$ DBGainLife | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. You gain 3 life. SVar:DBGainLife:DB$ GainLife | LifeAmount$ 3 DeckHas:Ability$Discard|LifeGain|Token & Type$Beast SVar:X:Count$Domain diff --git a/forge-gui/res/cardsfolder/h/hithlain_rope.txt b/forge-gui/res/cardsfolder/h/hithlain_rope.txt index 9af07ee80d1..90167d48ef1 100644 --- a/forge-gui/res/cardsfolder/h/hithlain_rope.txt +++ b/forge-gui/res/cardsfolder/h/hithlain_rope.txt @@ -2,7 +2,7 @@ Name:Hithlain Rope ManaCost:2 Types:Artifact S:Mode$ CantSacrifice | ValidCard$ Card.Self | Description$ CARDNAME can't be sacrificed. -A:AB$ ChangeZone | Cost$ 1 T | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SubAbility$ DBPass | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. The player to your right gains control of CARDNAME. +A:AB$ ChangeZone | Cost$ 1 T | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SubAbility$ DBPass | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. The player to your right gains control of CARDNAME. A:AB$ Draw | Cost$ 2 T | SubAbility$ DBPass | SpellDescription$ Draw a card. The player to your right gains control of CARDNAME. SVar:DBPass:DB$ GainControl | Defined$ Self | NewController$ NextPlayerToYourRight Oracle:Hithlain Rope can't be sacrificed.\n{1}, {T}: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. The player to your right gains control of Hithlain Rope.\n{2}, {T}: Draw a card. The player to your right gains control of Hithlain Rope. diff --git a/forge-gui/res/cardsfolder/h/horizon_boughs.txt b/forge-gui/res/cardsfolder/h/horizon_boughs.txt index 06fad108964..b23646bd126 100644 --- a/forge-gui/res/cardsfolder/h/horizon_boughs.txt +++ b/forge-gui/res/cardsfolder/h/horizon_boughs.txt @@ -3,6 +3,6 @@ ManaCost:no cost Types:Plane Pyrulea S:Mode$ Continuous | EffectZone$ Command | Affected$ Permanent | AddHiddenKeyword$ CARDNAME untaps during each other player's untap step. | Description$ All permanents untap during each player's untap step. T:Mode$ ChaosEnsues | TriggerZones$ Command | Execute$ DBFetch | TriggerDescription$ Whenever chaos ensues, you may search your library for up to three basic land cards, put them onto the battlefield tapped, then shuffle. -SVar:DBFetch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 3 | ShuffleNonMandatory$ True +SVar:DBFetch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 3 | ShuffleNonMandatory$ True SVar:AIRollPlanarDieParams:Mode$ Always | LowPriority$ True | MaxRollsPerTurn$ 9 Oracle:All permanents untap during each player's untap step.\nWhenever chaos ensues, you may search your library for up to three basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/h/horizon_seeker.txt b/forge-gui/res/cardsfolder/h/horizon_seeker.txt index 89aa71d133e..b3766d6fe1d 100644 --- a/forge-gui/res/cardsfolder/h/horizon_seeker.txt +++ b/forge-gui/res/cardsfolder/h/horizon_seeker.txt @@ -2,5 +2,5 @@ Name:Horizon Seeker ManaCost:2 G Types:Creature Human Warrior PT:3/2 -A:AB$ ChangeZone | Cost$ 1 G | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | Boast$ True | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 1 G | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Boast$ True | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. Oracle:Boast — {1}{G}: Search your library for a basic land card, reveal it, put it into your hand, then shuffle. (Activate only if this creature attacked this turn and only once each turn.) diff --git a/forge-gui/res/cardsfolder/h/horizon_spellbomb.txt b/forge-gui/res/cardsfolder/h/horizon_spellbomb.txt index 12a27333533..8fb54f10863 100644 --- a/forge-gui/res/cardsfolder/h/horizon_spellbomb.txt +++ b/forge-gui/res/cardsfolder/h/horizon_spellbomb.txt @@ -1,7 +1,7 @@ Name:Horizon Spellbomb ManaCost:1 Types:Artifact -A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | ChangeType$ Land.Basic | Origin$ Library | Destination$ Hand | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Origin$ Library | Destination$ Hand | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | OptionalDecider$ TriggeredCardController | Execute$ TrigDraw | TriggerDescription$ When CARDNAME is put into a graveyard from the battlefield, you may pay {G}. If you do, draw a card. SVar:TrigDraw:AB$ Draw | Cost$ G | NumCards$ 1 Oracle:{2}, {T}, Sacrifice Horizon Spellbomb: Search your library for a basic land card, reveal it, put it into your hand, then shuffle.\nWhen Horizon Spellbomb is put into a graveyard from the battlefield, you may pay {G}. If you do, draw a card. diff --git a/forge-gui/res/cardsfolder/h/huatli_poet_of_unity_roar_of_the_fifth_people.txt b/forge-gui/res/cardsfolder/h/huatli_poet_of_unity_roar_of_the_fifth_people.txt index c9729cef42d..8b4fc6eed77 100644 --- a/forge-gui/res/cardsfolder/h/huatli_poet_of_unity_roar_of_the_fifth_people.txt +++ b/forge-gui/res/cardsfolder/h/huatli_poet_of_unity_roar_of_the_fifth_people.txt @@ -3,7 +3,7 @@ ManaCost:2 G Types:Legendary Creature Human Warrior Bard PT:2/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters, search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land A:AB$ ChangeZone | Cost$ 3 RW RW | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBReturn | SorcerySpeed$ True | StackDescription$ SpellDescription | SpellDescription$ Exile NICKNAME, then return it to the battlefield transformed under its owner's control. Activate only as a sorcery. SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | Transformed$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/i/invasion_of_zendikar_awakened_skyclave.txt b/forge-gui/res/cardsfolder/i/invasion_of_zendikar_awakened_skyclave.txt index ba44ec75b89..6348ab29f33 100644 --- a/forge-gui/res/cardsfolder/i/invasion_of_zendikar_awakened_skyclave.txt +++ b/forge-gui/res/cardsfolder/i/invasion_of_zendikar_awakened_skyclave.txt @@ -3,7 +3,7 @@ ManaCost:3 G Types:Battle Siege Defense:3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearch | TriggerDescription$ When CARDNAME enters, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. -SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 2 +SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 AlternateMode:DoubleFaced Oracle:(As a Siege enters, choose an opponent to protect it. You and others can attack it. When it's defeated, exile it, then cast it transformed.)\nWhen Invasion of Zendikar enters, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/j/jaheiras_respite.txt b/forge-gui/res/cardsfolder/j/jaheiras_respite.txt index 929ef0dc8a7..277f4266867 100644 --- a/forge-gui/res/cardsfolder/j/jaheiras_respite.txt +++ b/forge-gui/res/cardsfolder/j/jaheiras_respite.txt @@ -1,7 +1,7 @@ Name:Jaheira's Respite ManaCost:4 G Types:Instant -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ X | Tapped$ True | SubAbility$ DBFog | SpellDescription$ Search your library for up to X basic land cards, where X is the number of creatures attacking you, put those cards onto the battlefield tapped, then shuffle. Prevent all combat damage that would be dealt this turn. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | Tapped$ True | SubAbility$ DBFog | SpellDescription$ Search your library for up to X basic land cards, where X is the number of creatures attacking you, put those cards onto the battlefield tapped, then shuffle. Prevent all combat damage that would be dealt this turn. SVar:X:Count$Valid Creature.attackingYou SVar:DBFog:DB$ Fog Oracle:Search your library for up to X basic land cards, where X is the number of creatures attacking you, put those cards onto the battlefield tapped, then shuffle.\nPrevent all combat damage that would be dealt this turn. diff --git a/forge-gui/res/cardsfolder/j/jeskai_monument.txt b/forge-gui/res/cardsfolder/j/jeskai_monument.txt index 3a8d4b26062..ef93bb8e4af 100644 --- a/forge-gui/res/cardsfolder/j/jeskai_monument.txt +++ b/forge-gui/res/cardsfolder/j/jeskai_monument.txt @@ -2,7 +2,7 @@ Name:Jeskai Monument ManaCost:2 Types:Artifact T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When this artifact enters, search your library for a basic Island, Mountain, or Plains card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Island+Basic,Land.Mountain+Basic,Land.Plains+Basic +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Island.Basic,Mountain.Basic,Plains.Basic | ChangeTypeDesc$ basic Island, Mountain, or Plains A:AB$ Token | Cost$ 1 U R W T Sac<1/CARDNAME> | TokenAmount$ 2 | TokenScript$ w_1_1_bird_flying | TokenOwner$ You | SorcerySpeed$ True | SpellDescription$ Create two 1/1 white Bird creature tokens with flying. Activate only as a sorcery. DeckHas:Ability$Token Oracle:When this artifact enters, search your library for a basic Island, Mountain, or Plains card, reveal it, put it into your hand, then shuffle.\n{1}{U}{R}{W}, {T}, Sacrifice this artifact: Create two 1/1 white Bird creature tokens with flying. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/j/journey_for_the_elixir.txt b/forge-gui/res/cardsfolder/j/journey_for_the_elixir.txt index b46c05ca583..5a97df45598 100644 --- a/forge-gui/res/cardsfolder/j/journey_for_the_elixir.txt +++ b/forge-gui/res/cardsfolder/j/journey_for_the_elixir.txt @@ -1,7 +1,7 @@ Name:Journey for the Elixir ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library,Graveyard | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | Shuffle$ False | SubAbility$ DBSearch1 | SpellDescription$ Search your library and graveyard for a basic land card and a card named Jiang Yanggu, reveal them, put them into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library,Graveyard | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Shuffle$ False | SubAbility$ DBSearch1 | SpellDescription$ Search your library and graveyard for a basic land card and a card named Jiang Yanggu, reveal them, put them into your hand, then shuffle. SVar:DBSearch1:DB$ ChangeZone | Origin$ Library,Graveyard | Destination$ Hand | ChangeType$ Card.namedJiang Yanggu | ChangeNum$ 1 DeckNeeds:Name$Jiang Yanggu Oracle:Search your library and graveyard for a basic land card and a card named Jiang Yanggu, reveal them, put them into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/j/journey_of_discovery.txt b/forge-gui/res/cardsfolder/j/journey_of_discovery.txt index 60aea28cebb..fb098ac74b9 100644 --- a/forge-gui/res/cardsfolder/j/journey_of_discovery.txt +++ b/forge-gui/res/cardsfolder/j/journey_of_discovery.txt @@ -3,7 +3,7 @@ ManaCost:2 G Types:Sorcery K:Entwine:2 G A:SP$ Charm | Choices$ DBChangeZone,DBEffect | CharmNum$ 1 -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle. +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle. SVar:DBEffect:DB$ Effect | StaticAbilities$ JourneyOfDis | AILogic$ Always | SpellDescription$ You may play up to two additional lands this turn. SVar:JourneyOfDis:Mode$ Continuous | Affected$ You | AdjustLandPlays$ 2 | Description$ You may play two additional lands this turn. AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/j/journeyers_kite.txt b/forge-gui/res/cardsfolder/j/journeyers_kite.txt index 5c238d3d792..467fd70d729 100644 --- a/forge-gui/res/cardsfolder/j/journeyers_kite.txt +++ b/forge-gui/res/cardsfolder/j/journeyers_kite.txt @@ -1,5 +1,5 @@ Name:Journeyer's Kite ManaCost:2 Types:Artifact -A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. Oracle:{3}, {T}: Search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/j/jund_panorama.txt b/forge-gui/res/cardsfolder/j/jund_panorama.txt index cf66eee31c9..a114281d878 100644 --- a/forge-gui/res/cardsfolder/j/jund_panorama.txt +++ b/forge-gui/res/cardsfolder/j/jund_panorama.txt @@ -2,5 +2,5 @@ Name:Jund Panorama ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Swamp+Basic,Land.Mountain+Basic,Land.Forest+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Swamp, Mountain, or Forest card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Swamp.Basic,Mountain.Basic,Forest.Basic | ChangeTypeDesc$ basic Swamp, Mountain, or Forest | SpellDescription$ Search your library for a basic Swamp, Mountain, or Forest card, put it onto the battlefield tapped, then shuffle. Oracle:{T}: Add {C}.\n{1}, {T}, Sacrifice Jund Panorama: Search your library for a basic Swamp, Mountain, or Forest card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/j/jungle_wayfinder.txt b/forge-gui/res/cardsfolder/j/jungle_wayfinder.txt index a42476effec..695b1f4b121 100644 --- a/forge-gui/res/cardsfolder/j/jungle_wayfinder.txt +++ b/forge-gui/res/cardsfolder/j/jungle_wayfinder.txt @@ -3,5 +3,5 @@ ManaCost:2 G Types:Creature Elf Warrior PT:3/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters, each player may search their library for a basic land card, reveal it, put it into their hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | DefinedPlayer$ Player | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ Player | ChangeNum$ 1 | ShuffleNonMandatory$ True Oracle:When Jungle Wayfinder enters, each player may search their library for a basic land card, reveal it, put it into their hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/k/kamigawa_charm.txt b/forge-gui/res/cardsfolder/k/kamigawa_charm.txt index 385cd100663..23bcaad437e 100644 --- a/forge-gui/res/cardsfolder/k/kamigawa_charm.txt +++ b/forge-gui/res/cardsfolder/k/kamigawa_charm.txt @@ -4,7 +4,7 @@ Types:Sorcery A:SP$ Charm | Choices$ DosansOldestChant,KodamasReach,TimeOfNeed | Defined$ You SVar:DosansOldestChant:DB$ GainLife | LifeAmount$ 6 | SubAbility$ DBDraw | SpellDescription$ You gain 6 life. Draw a card. SVar:DBDraw:DB$ Draw | NumCards$ 1 -SVar:KodamasReach:DB$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle. +SVar:KodamasReach:DB$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle. SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.IsRemembered | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put onto the battlefield | Tapped$ True | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.IsRemembered | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put into your hand | StackDescription$ None | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/k/kellan_the_fae_blooded_birthright_boon.txt b/forge-gui/res/cardsfolder/k/kellan_the_fae_blooded_birthright_boon.txt index ba9f7bd4989..62bb5b3a2e7 100644 --- a/forge-gui/res/cardsfolder/k/kellan_the_fae_blooded_birthright_boon.txt +++ b/forge-gui/res/cardsfolder/k/kellan_the_fae_blooded_birthright_boon.txt @@ -16,5 +16,5 @@ ALTERNATE Name:Birthright Boon ManaCost:1 W Types:Sorcery Adventure -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Aura,Equipment | ChangeNum$ 1 | SpellDescription$ Search your library for an Aura or Equipment card, reveal it, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Aura,Equipment | SpellDescription$ Search your library for an Aura or Equipment card, reveal it, put it into your hand, then shuffle. Oracle:Search your library for an Aura or Equipment card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/k/keys_to_the_house.txt b/forge-gui/res/cardsfolder/k/keys_to_the_house.txt index e11d451871e..e831128c6ac 100644 --- a/forge-gui/res/cardsfolder/k/keys_to_the_house.txt +++ b/forge-gui/res/cardsfolder/k/keys_to_the_house.txt @@ -1,6 +1,6 @@ Name:Keys to the House ManaCost:1 Types:Artifact -A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. A:AB$ UnlockDoor | Cost$ 3 T Sac<1/CARDNAME> | Mode$ LockOrUnlock | ValidTgts$ Room.YouCtrl | TgtPrompt$ Choose target Room you control | SorcerySpeed$ True | SpellDescription$ Lock or unlock a door of target Room you control. Activate only as a sorcery. Oracle:{1}, {T}, Sacrifice Keys to the House: Search your library for a basic land card, reveal it, put it into your hand, then shuffle.\n{3}, {T}, Sacrifice Keys to the House: Lock or unlock a door of target Room you control. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/k/khalni_heart_expedition.txt b/forge-gui/res/cardsfolder/k/khalni_heart_expedition.txt index 93fd55fd29c..6c7502ed0b9 100644 --- a/forge-gui/res/cardsfolder/k/khalni_heart_expedition.txt +++ b/forge-gui/res/cardsfolder/k/khalni_heart_expedition.txt @@ -3,7 +3,7 @@ ManaCost:1 G Types:Enchantment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Landfall — Whenever a land you control enters, you may put a quest counter on CARDNAME. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ QUEST | CounterNum$ 1 -A:AB$ ChangeZone | Cost$ SubCounter<3/QUEST> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 2 | Tapped$ True | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ SubCounter<3/QUEST> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | Tapped$ True | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. SVar:MaxQuestEffect:3 DeckHas:Ability$Counters Oracle:Landfall — Whenever a land you control enters, you may put a quest counter on Khalni Heart Expedition.\nRemove three quest counters from Khalni Heart Expedition and sacrifice it: Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/k/knowledge_exploitation.txt b/forge-gui/res/cardsfolder/k/knowledge_exploitation.txt index 21bfa1d8ddc..295efb2fe38 100644 --- a/forge-gui/res/cardsfolder/k/knowledge_exploitation.txt +++ b/forge-gui/res/cardsfolder/k/knowledge_exploitation.txt @@ -2,7 +2,7 @@ Name:Knowledge Exploitation ManaCost:5 U U Types:Kindred Sorcery Rogue K:Prowl:3 U -A:SP$ ChangeZone | ValidTgts$ Opponent | Origin$ Library | Destination$ Library | ChangeType$ Instant,Sorcery | ChangeNum$ 1 | RememberChanged$ True | Reveal$ True | Shuffle$ False | DefinedPlayer$ Targeted | Chooser$ You | SubAbility$ DBPlay | StackDescription$ Search {p:Targeted}'s library for an instant or sorcery card | SpellDescription$ Search target opponent's library for an instant or sorcery card. You may cast that card without paying its mana cost. Then that player shuffles. +A:SP$ ChangeZone | ValidTgts$ Opponent | Origin$ Library | Destination$ Library | ChangeType$ Instant,Sorcery | RememberChanged$ True | Reveal$ True | Shuffle$ False | DefinedPlayer$ Targeted | Chooser$ You | SubAbility$ DBPlay | StackDescription$ Search {p:Targeted}'s library for an instant or sorcery card | SpellDescription$ Search target opponent's library for an instant or sorcery card. You may cast that card without paying its mana cost. Then that player shuffles. SVar:DBPlay:DB$ Play | Defined$ Remembered | Controller$ You | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | SubAbility$ DBShuffle SVar:DBShuffle:DB$ Shuffle | Defined$ RememberedController | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/k/kodama_of_the_west_tree.txt b/forge-gui/res/cardsfolder/k/kodama_of_the_west_tree.txt index 967aaaf12e3..54382f36e51 100644 --- a/forge-gui/res/cardsfolder/k/kodama_of_the_west_tree.txt +++ b/forge-gui/res/cardsfolder/k/kodama_of_the_west_tree.txt @@ -5,6 +5,6 @@ PT:3/3 K:Reach S:Mode$ Continuous | Affected$ Creature.modified+YouCtrl | AddKeyword$ Trample | Description$ Modified creatures you control have trample. T:Mode$ DamageDone | ValidSource$ Creature.modified+YouCtrl | ValidTarget$ Opponent | TriggerZones$ Battlefield | CombatDamage$ True | Execute$ TrigSearch | TriggerDescription$ Whenever a modified creature you control deals combat damage to an opponent, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 +SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 DeckHints:Type$Aura|Equipment & Ability$Counters Oracle:Reach\nModified creatures you control have trample. (Equipment, Auras you control, and counters are modifications.)\nWhenever a modified creature you control deals combat damage to an opponent, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/k/kodamas_reach.txt b/forge-gui/res/cardsfolder/k/kodamas_reach.txt index dc9f6bd9453..0b550bd16cf 100644 --- a/forge-gui/res/cardsfolder/k/kodamas_reach.txt +++ b/forge-gui/res/cardsfolder/k/kodamas_reach.txt @@ -1,7 +1,7 @@ Name:Kodama's Reach ManaCost:2 G Types:Sorcery Arcane -A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle. SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.IsRemembered | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put onto the battlefield | Tapped$ True | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.IsRemembered | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put into your hand | StackDescription$ None | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/k/krenkos_buzzcrusher.txt b/forge-gui/res/cardsfolder/k/krenkos_buzzcrusher.txt index 3ac3d15e3fa..fef54fe87ea 100644 --- a/forge-gui/res/cardsfolder/k/krenkos_buzzcrusher.txt +++ b/forge-gui/res/cardsfolder/k/krenkos_buzzcrusher.txt @@ -8,6 +8,6 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S SVar:DBForEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBChoose | SubAbility$ DBDestroy SVar:DBChoose:DB$ ChooseCard | ChoiceZone$ Battlefield | Amount$ 1 | Choices$ Land.nonBasic+RememberedPlayerCtrl | Optional$ True | ImprintChosen$ True | ChoiceTitle$ Choose up to one nonbasic land this player controls SVar:DBDestroy:DB$ Destroy | Defined$ Imprinted | RememberDestroyed$ True | SubAbility$ DBSearch -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | DefinedPlayer$ RememberedController | Optional$ True | SubAbility$ DBCleanup +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ RememberedController | Optional$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True Oracle:Flying, trample\nWhen Krenko's Buzzcrusher enters, for each player, destroy up to one nonbasic land that player controls. For each land destroyed this way, its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/k/krosan_tusker.txt b/forge-gui/res/cardsfolder/k/krosan_tusker.txt index dd7945c5401..f4b6207f5d0 100644 --- a/forge-gui/res/cardsfolder/k/krosan_tusker.txt +++ b/forge-gui/res/cardsfolder/k/krosan_tusker.txt @@ -4,5 +4,5 @@ Types:Creature Boar Beast PT:6/5 K:Cycling:2 G T:Mode$ Cycled | ValidCard$ Card.Self | Execute$ TrigChangeZone | OptionalDecider$ You | TriggerDescription$ When you cycle CARDNAME, you may search your library for a basic land card, reveal that card, put it into your hand, then shuffle. (Do this before you draw.) -SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:Cycling {2}{G} ({2}{G}, Discard this card: Draw a card.)\nWhen you cycle Krosan Tusker, you may search your library for a basic land card, reveal that card, put it into your hand, then shuffle. (Do this before you draw.) diff --git a/forge-gui/res/cardsfolder/l/land_tax.txt b/forge-gui/res/cardsfolder/l/land_tax.txt index b0ae1f05747..6cdc628e643 100644 --- a/forge-gui/res/cardsfolder/l/land_tax.txt +++ b/forge-gui/res/cardsfolder/l/land_tax.txt @@ -2,7 +2,7 @@ Name:Land Tax ManaCost:W Types:Enchantment T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigChange | CheckSVar$ Y | SVarCompare$ GTX | TriggerDescription$ At the beginning of your upkeep, if an opponent controls more lands than you, you may search your library for up to three basic land cards, reveal them, put them into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 3 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 3 | ShuffleNonMandatory$ True SVar:X:Count$Valid Land.YouCtrl SVar:Y:PlayerCountOpponents$HighestValid Land.YouCtrl Oracle:At the beginning of your upkeep, if an opponent controls more lands than you, you may search your library for up to three basic land cards, reveal them, put them into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/l/larval_scoutlander.txt b/forge-gui/res/cardsfolder/l/larval_scoutlander.txt index 17c76aa646b..4b65c5a4255 100644 --- a/forge-gui/res/cardsfolder/l/larval_scoutlander.txt +++ b/forge-gui/res/cardsfolder/l/larval_scoutlander.txt @@ -3,7 +3,7 @@ ManaCost:2 G Types:Artifact Spacecraft PT:3/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When this Spacecraft enters, you may sacrifice a land or Lander. If you do, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. -SVar:TrigChangeZone:AB$ ChangeZone | Cost$ Sac<1/Land;Lander/land or Lander> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 2 | Tapped$ True +SVar:TrigChangeZone:AB$ ChangeZone | Cost$ Sac<1/Land;Lander/land or Lander> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | Tapped$ True K:Station:7 S:Mode$ Continuous | Affected$ Card.Self+counters_GE7_CHARGE | AddType$ Creature | AddKeyword$ Flying | Description$ STATION 7+ Flying Oracle:When this Spacecraft enters, you may sacrifice a land or Lander. If you do, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle.\nStation (Tap another creature you control: Put charge counters equal to its power on this Spacecraft. Station only as a sorcery. It's an artifact creature at 7+.)\nSTATION 7+\nFlying diff --git a/forge-gui/res/cardsfolder/l/lay_of_the_land.txt b/forge-gui/res/cardsfolder/l/lay_of_the_land.txt index 1ea27c89f9e..b5310fb8b4b 100644 --- a/forge-gui/res/cardsfolder/l/lay_of_the_land.txt +++ b/forge-gui/res/cardsfolder/l/lay_of_the_land.txt @@ -1,5 +1,5 @@ Name:Lay of the Land ManaCost:G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. Oracle:Search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/l/loam_larva.txt b/forge-gui/res/cardsfolder/l/loam_larva.txt index 8fcad11cae0..d1394e63b79 100644 --- a/forge-gui/res/cardsfolder/l/loam_larva.txt +++ b/forge-gui/res/cardsfolder/l/loam_larva.txt @@ -3,5 +3,5 @@ ManaCost:1 G Types:Creature Insect PT:1/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:When Loam Larva enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top. diff --git a/forge-gui/res/cardsfolder/l/lumbering_worldwagon.txt b/forge-gui/res/cardsfolder/l/lumbering_worldwagon.txt index 71014d3d590..ea2cef8462e 100644 --- a/forge-gui/res/cardsfolder/l/lumbering_worldwagon.txt +++ b/forge-gui/res/cardsfolder/l/lumbering_worldwagon.txt @@ -5,7 +5,7 @@ PT:*/4 S:Mode$ Continuous | CharacteristicDefining$ True | SetPower$ X | Description$ This Vehicle's power is equal to the number of lands you control. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | OptionalDecider$ You | TriggerDescription$ Whenever this Vehicle enters or attacks, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerZones$ Battlefield | OptionalDecider$ You | Secondary$ True | TriggerDescription$ Whenever this Vehicle enters or attacks, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land K:Crew:4 SVar:X:Count$Valid Land.YouCtrl SVar:BuffedBy:Land diff --git a/forge-gui/res/cardsfolder/m/maestros_theater.txt b/forge-gui/res/cardsfolder/m/maestros_theater.txt index 738c72e7533..f3d1652be63 100644 --- a/forge-gui/res/cardsfolder/m/maestros_theater.txt +++ b/forge-gui/res/cardsfolder/m/maestros_theater.txt @@ -4,7 +4,7 @@ Types:Land T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBSacrifice | TriggerDescription$ When CARDNAME enters, sacrifice it. When you do, search your library for a basic Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle and you gain 1 life. SVar:DBSacrifice:DB$ Sacrifice | RememberSacrificed$ True | SubAbility$ DBImmediateTrigger SVar:DBImmediateTrigger:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | Execute$ DBChangeZone | SubAbility$ DBCleanup | TriggerDescription$ Search your library for a basic Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle and you gain 1 life. -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Island+Basic,Land.Swamp+Basic,Land.Mountain+Basic | ChangeNum$ 1 | SubAbility$ DBGainLife +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Island.Basic,Swamp.Basic,Mountain.Basic | ChangeTypeDesc$ basic Island, Swamp, or Mountain | SubAbility$ DBGainLife SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Ability$Sacrifice|LifeGain diff --git a/forge-gui/res/cardsfolder/m/magmatic_hellkite.txt b/forge-gui/res/cardsfolder/m/magmatic_hellkite.txt index 99ceec4e7a4..0d954cbeefd 100644 --- a/forge-gui/res/cardsfolder/m/magmatic_hellkite.txt +++ b/forge-gui/res/cardsfolder/m/magmatic_hellkite.txt @@ -5,5 +5,5 @@ PT:4/5 K:Flying T:Mode$ ChangesZone | ValidCard$ Card.Self | Destination$ Battlefield | Execute$ TrigDestroy | TriggerDescription$ When this creature enters, destroy target nonbasic land an opponent controls. Its controller searches their library for a basic land card, puts it on the battlefield tapped with a stun counter on it, then shuffles. (If a permanent with a stun counter would become untapped, remove one from it instead.) SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Land.nonBasic+OppCtrl | TgtPrompt$ Select target nonbasic land an opponent controls | SubAbility$ DBChange -SVar:DBChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | DefinedPlayer$ TargetedController | Tapped$ True | WithCountersType$ STUN +SVar:DBChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ TargetedController | Tapped$ True | WithCountersType$ STUN Oracle:Flying\nWhen this creature enters, destroy target nonbasic land an opponent controls. Its controller searches their library for a basic land card, puts it on the battlefield tapped with a stun counter on it, then shuffles. (If a permanent with a stun counter would become untapped, remove one from it instead.) diff --git a/forge-gui/res/cardsfolder/m/many_partings.txt b/forge-gui/res/cardsfolder/m/many_partings.txt index f51fdc39d29..b59bc04dcac 100644 --- a/forge-gui/res/cardsfolder/m/many_partings.txt +++ b/forge-gui/res/cardsfolder/m/many_partings.txt @@ -1,7 +1,7 @@ Name:Many Partings ManaCost:G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SubAbility$ DBToken | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. Create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this artifact: You gain 3 life.") +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SubAbility$ DBToken | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. Create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this artifact: You gain 3 life.") SVar:DBToken:DB$ Token | TokenScript$ c_a_food_sac DeckHas:Ability$Token|LifeGain|Sacrifice & Type$Artifact|Food Oracle:Search your library for a basic land card, reveal it, put it into your hand, then shuffle. Create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this artifact: You gain 3 life.") diff --git a/forge-gui/res/cardsfolder/m/map_the_wastes.txt b/forge-gui/res/cardsfolder/m/map_the_wastes.txt index f6ca95af749..a059b1f63e8 100644 --- a/forge-gui/res/cardsfolder/m/map_the_wastes.txt +++ b/forge-gui/res/cardsfolder/m/map_the_wastes.txt @@ -1,7 +1,7 @@ Name:Map the Wastes ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | SubAbility$ DBBolster | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Bolster 1. (Choose a creature with the least toughness among creatures you control and put a +1/+1 counter on it.) +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | SubAbility$ DBBolster | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Bolster 1. (Choose a creature with the least toughness among creatures you control and put a +1/+1 counter on it.) SVar:DBBolster:DB$ PutCounter | Bolster$ True | CounterNum$ 1 | CounterType$ P1P1 DeckHas:Ability$Counters Oracle:Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Bolster 1. (Choose a creature with the least toughness among creatures you control and put a +1/+1 counter on it.) diff --git a/forge-gui/res/cardsfolder/m/mardu_monument.txt b/forge-gui/res/cardsfolder/m/mardu_monument.txt index 8b569e78f20..fd56ce66f3e 100644 --- a/forge-gui/res/cardsfolder/m/mardu_monument.txt +++ b/forge-gui/res/cardsfolder/m/mardu_monument.txt @@ -2,7 +2,7 @@ Name:Mardu Monument ManaCost:2 Types:Artifact T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When this artifact enters, search your library for a basic Mountain, Plains, or Swamp card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Mountain+Basic,Land.Plains+Basic,Land.Swamp+Basic +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Mountain.Basic,Plains.Basic,Swamp.Basic | ChangeTypeDesc$ basic Mountain, Plains, or Swamp A:AB$ Token | Cost$ 2 R W B T Sac<1/CARDNAME> | TokenAmount$ 3 | TokenScript$ r_1_1_warrior | TokenOwner$ You | PumpKeywords$ Menace & Haste | PumpDuration$ EOT | SorcerySpeed$ True | SpellDescription$ Create three 1/1 red Warrior creature tokens. They gain menace and haste until end of turn. Activate only as a sorcery. (A creature with menace can't be blocked except by two or more creatures.) DeckHas:Ability$Token Oracle:When this artifact enters, search your library for a basic Mountain, Plains, or Swamp card, reveal it, put it into your hand, then shuffle.\n{2}{R}{W}{B}, {T}, Sacrifice this artifact: Create three 1/1 red Warrior creature tokens. They gain menace and haste until end of turn. Activate only as a sorcery. (A creature with menace can't be blocked except by two or more creatures.) diff --git a/forge-gui/res/cardsfolder/m/marsh_flats.txt b/forge-gui/res/cardsfolder/m/marsh_flats.txt index ff67cc21ae2..2a455802e98 100644 --- a/forge-gui/res/cardsfolder/m/marsh_flats.txt +++ b/forge-gui/res/cardsfolder/m/marsh_flats.txt @@ -1,5 +1,5 @@ Name:Marsh Flats ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Plains,Swamp | ChangeNum$ 1 | SpellDescription$ Search your library for a Plains or Swamp card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Plains,Swamp | SpellDescription$ Search your library for a Plains or Swamp card, put it onto the battlefield, then shuffle. Oracle:{T}, Pay 1 life, Sacrifice Marsh Flats: Search your library for a Plains or Swamp card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/m/merchant_scroll.txt b/forge-gui/res/cardsfolder/m/merchant_scroll.txt index 2c630b6def4..ba1690e0c71 100644 --- a/forge-gui/res/cardsfolder/m/merchant_scroll.txt +++ b/forge-gui/res/cardsfolder/m/merchant_scroll.txt @@ -1,6 +1,6 @@ Name:Merchant Scroll ManaCost:1 U Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Instant.Blue | ChangeNum$ 1 | SpellDescription$ Search your library for a blue instant card, reveal that card, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Instant.Blue | ChangeTypeDesc$ blue instant | SpellDescription$ Search your library for a blue instant card, reveal that card, put it into your hand, then shuffle. AI:RemoveDeck:All Oracle:Search your library for a blue instant card, reveal that card, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/m/migration_path.txt b/forge-gui/res/cardsfolder/m/migration_path.txt index 08becc0292d..64abc1e8909 100644 --- a/forge-gui/res/cardsfolder/m/migration_path.txt +++ b/forge-gui/res/cardsfolder/m/migration_path.txt @@ -1,6 +1,6 @@ Name:Migration Path ManaCost:3 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 2 | StackDescription$ SpellDescription | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | StackDescription$ SpellDescription | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. K:Cycling:2 Oracle:Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/m/migratory_greathorn.txt b/forge-gui/res/cardsfolder/m/migratory_greathorn.txt index 5ab769dd426..18002e002c4 100644 --- a/forge-gui/res/cardsfolder/m/migratory_greathorn.txt +++ b/forge-gui/res/cardsfolder/m/migratory_greathorn.txt @@ -4,5 +4,5 @@ Types:Creature Beast PT:3/4 K:Mutate:2 G T:Mode$ Mutates | ValidCard$ Card.Self | Execute$ TrigChange | TriggerZones$ Battlefield | TriggerDescription$ Whenever this creature mutates, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land Oracle:Mutate {2}{G} (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.)\nWhenever this creature mutates, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/m/misty_rainforest.txt b/forge-gui/res/cardsfolder/m/misty_rainforest.txt index 367dd6e6aee..0207fab6398 100644 --- a/forge-gui/res/cardsfolder/m/misty_rainforest.txt +++ b/forge-gui/res/cardsfolder/m/misty_rainforest.txt @@ -1,5 +1,5 @@ Name:Misty Rainforest ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Forest,Island | ChangeNum$ 1 | SpellDescription$ Search your library for a Forest or Island card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Forest,Island | SpellDescription$ Search your library for a Forest or Island card, put it onto the battlefield, then shuffle. Oracle:{T}, Pay 1 life, Sacrifice Misty Rainforest: Search your library for a Forest or Island card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/m/moggcatcher.txt b/forge-gui/res/cardsfolder/m/moggcatcher.txt index d4a59f15253..b694bac10fe 100644 --- a/forge-gui/res/cardsfolder/m/moggcatcher.txt +++ b/forge-gui/res/cardsfolder/m/moggcatcher.txt @@ -2,7 +2,7 @@ Name:Moggcatcher ManaCost:2 R R Types:Creature Human Mercenary PT:2/2 -A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Battlefield | ChangeType$ Permanent.Goblin | ChangeNum$ 1 | SpellDescription$ Search your library for a Goblin permanent card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Battlefield | ChangeType$ Permanent.Goblin | ChangeTypeDesc$ Goblin permanent | SpellDescription$ Search your library for a Goblin permanent card, put it onto the battlefield, then shuffle. AI:RemoveDeck:Random SVar:NonCombatPriority:3 Oracle:{3}, {T}: Search your library for a Goblin permanent card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/m/moldering_gym_weight_room.txt b/forge-gui/res/cardsfolder/m/moldering_gym_weight_room.txt index 194b112c79a..d5cef8a1722 100644 --- a/forge-gui/res/cardsfolder/m/moldering_gym_weight_room.txt +++ b/forge-gui/res/cardsfolder/m/moldering_gym_weight_room.txt @@ -2,7 +2,7 @@ Name:Moldering Gym ManaCost:2 G Types:Enchantment Room T:Mode$ UnlockDoor | ValidPlayer$ You | ValidCard$ Card.Self | ThisDoor$ True | Execute$ TrigSearch | TriggerDescription$ When you unlock this door, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land AlternateMode:Split Oracle:(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)\nWhen you unlock this door, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/m/monument_to_perfection.txt b/forge-gui/res/cardsfolder/m/monument_to_perfection.txt index a4df800d0e8..8de4eb6e0c1 100644 --- a/forge-gui/res/cardsfolder/m/monument_to_perfection.txt +++ b/forge-gui/res/cardsfolder/m/monument_to_perfection.txt @@ -3,7 +3,7 @@ ManaCost:2 Types:Artifact A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic,Land.Locus,Land.Sphere | ChangeTypeDesc$ basic, Sphere, or Locus land card | SpellDescription$ Search your library for a basic, Sphere, or Locus land card, reveal it, put it into your hand, then shuffle. A:AB$ Animate | Cost$ 3 | CheckSVar$ CountAll | SVarCompare$ GE9 | Power$ 9 | Toughness$ 9 | Types$ Artifact,Creature,Phyrexian,Construct | RemoveCreatureTypes$ True | RemoveAllAbilities$ True | Defined$ Self | Keywords$ Indestructible & Toxic:9 | Duration$ Permanent | SpellDescription$ CARDNAME becomes a 9/9 Phyrexian Construct artifact creature, loses all abilities, and gains indestructible and toxic 9. Activate only if there are nine or more lands with different names among the basic, Sphere, and Locus lands you control. -SVar:CountAll:Count$DifferentCardNames_Land.YouCtrl+inZoneBattlefield+Basic,Land.YouCtrl+inZoneBattlefield+Sphere,Land.YouCtrl+inZoneBattlefield+Locus +SVar:CountAll:Count$DifferentCardNames_Land.YouCtrl+inZoneBattlefield+Basic,Sphere.YouCtrl+inZoneBattlefield,Locus.YouCtrl+inZoneBattlefield DeckHas:Type$Phyrexian|Construct|Artifact DeckNeeds:Type$Locus|Sphere Oracle:{3}, {T}: Search your library for a basic, Sphere, or Locus land card, reveal it, put it into your hand, then shuffle.\n{3}: Monument to Perfection becomes a 9/9 Phyrexian Construct artifact creature, loses all abilities, and gains indestructible and toxic 9. Activate only if there are nine or more lands with different names among the basic, Sphere, and Locus lands you control. diff --git a/forge-gui/res/cardsfolder/m/mountain_valley.txt b/forge-gui/res/cardsfolder/m/mountain_valley.txt index 9bb7cea6777..ec612427231 100644 --- a/forge-gui/res/cardsfolder/m/mountain_valley.txt +++ b/forge-gui/res/cardsfolder/m/mountain_valley.txt @@ -3,5 +3,5 @@ ManaCost:no cost Types:Land R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Mountain,Forest | ChangeNum$ 1 | SpellDescription$ Search your library for a Mountain or Forest card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Mountain,Forest | SpellDescription$ Search your library for a Mountain or Forest card, put it onto the battlefield, then shuffle. Oracle:Mountain Valley enters tapped.\n{T}, Sacrifice Mountain Valley: Search your library for a Mountain or Forest card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/m/murasa.txt b/forge-gui/res/cardsfolder/m/murasa.txt index aae59b2230b..85eb4f93a64 100644 --- a/forge-gui/res/cardsfolder/m/murasa.txt +++ b/forge-gui/res/cardsfolder/m/murasa.txt @@ -2,7 +2,7 @@ Name:Murasa ManaCost:no cost Types:Plane Zendikar T:Mode$ ChangesZone | ValidCard$ Creature.!token | Origin$ Any | Destination$ Battlefield | TriggerZones$ Command | Execute$ TrigRamp | OptionalDecider$ TriggeredCardController | TriggerDescription$ Whenever a nontoken creature enters, its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigRamp:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | DefinedPlayer$ TriggeredCardController | ShuffleNonMandatory$ True +SVar:TrigRamp:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ TriggeredCardController | ShuffleNonMandatory$ True T:Mode$ ChaosEnsues | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever chaos ensues, target land becomes a 4/4 creature that's still a land. SVar:RolledChaos:DB$ Animate | ValidTgts$ Land | Power$ 4 | Toughness$ 4 | Types$ Creature | Duration$ Permanent SVar:AIRollPlanarDieParams:Mode$ Always diff --git a/forge-gui/res/cardsfolder/m/murasa_rootgrazer.txt b/forge-gui/res/cardsfolder/m/murasa_rootgrazer.txt index a983ffdd9ff..d1c5c2bb2d9 100644 --- a/forge-gui/res/cardsfolder/m/murasa_rootgrazer.txt +++ b/forge-gui/res/cardsfolder/m/murasa_rootgrazer.txt @@ -3,6 +3,6 @@ ManaCost:G W Types:Creature Beast PT:2/3 K:Vigilance -A:AB$ ChangeZone | Cost$ T | Origin$ Hand | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Optional$ You | AILogic$ AtOppEOT | SpellDescription$ You may put a basic land card from your hand onto the battlefield. +A:AB$ ChangeZone | Cost$ T | Origin$ Hand | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Optional$ You | AILogic$ AtOppEOT | SpellDescription$ You may put a basic land card from your hand onto the battlefield. A:AB$ ChangeZone | Cost$ T | ValidTgts$ Land.Basic+YouCtrl | TgtPrompt$ Select target basic land you control | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return target basic land you control to its owner's hand. Oracle:Vigilance\n{T}: You may put a basic land card from your hand onto the battlefield.\n{T}: Return target basic land you control to its owner's hand. diff --git a/forge-gui/res/cardsfolder/m/mycosynth_wellspring.txt b/forge-gui/res/cardsfolder/m/mycosynth_wellspring.txt index 4545ca508f3..78bdb3d269b 100644 --- a/forge-gui/res/cardsfolder/m/mycosynth_wellspring.txt +++ b/forge-gui/res/cardsfolder/m/mycosynth_wellspring.txt @@ -3,6 +3,6 @@ ManaCost:2 Types:Artifact T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters or is put into a graveyard from the battlefield, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | Secondary$ True | TriggerDescription$ When CARDNAME enters or is put into a graveyard from the battlefield, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True SVar:SacMe:5 Oracle:When Mycosynth Wellspring enters or is put into a graveyard from the battlefield, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/m/myr_turbine.txt b/forge-gui/res/cardsfolder/m/myr_turbine.txt index a49c136919a..9b534156113 100644 --- a/forge-gui/res/cardsfolder/m/myr_turbine.txt +++ b/forge-gui/res/cardsfolder/m/myr_turbine.txt @@ -2,7 +2,7 @@ Name:Myr Turbine ManaCost:5 Types:Artifact A:AB$ Token | Cost$ T | TokenAmount$ 1 | TokenScript$ c_1_1_a_myr | TokenOwner$ You | SpellDescription$ Create a 1/1 colorless Myr artifact creature token. -A:AB$ ChangeZone | Cost$ T tapXType<5/Myr> | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature.Myr | ChangeNum$ 1 | SpellDescription$ Search your library for a Myr creature card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T tapXType<5/Myr> | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature.Myr | ChangeTypeDesc$ Myr creature | SpellDescription$ Search your library for a Myr creature card, put it onto the battlefield, then shuffle. DeckHints:Type$Myr DeckHas:Ability$Token Oracle:{T}: Create a 1/1 colorless Myr artifact creature token.\n{T}, Tap five untapped Myr you control: Search your library for a Myr creature card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/m/myriad_landscape.txt b/forge-gui/res/cardsfolder/m/myriad_landscape.txt index b45e8fab1e5..6b46cf526ac 100644 --- a/forge-gui/res/cardsfolder/m/myriad_landscape.txt +++ b/forge-gui/res/cardsfolder/m/myriad_landscape.txt @@ -4,5 +4,5 @@ Types:Land R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 2 | ShareLandType$ True | SpellDescription$ Search your library for up to two basic land cards that share a land type, put them onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 2 | ShareLandType$ True | SpellDescription$ Search your library for up to two basic land cards that share a land type, put them onto the battlefield tapped, then shuffle. Oracle:Myriad Landscape enters tapped.\n{T}: Add {C}.\n{2}, {T}, Sacrifice Myriad Landscape: Search your library for up to two basic land cards that share a land type, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/m/mystical_tutor.txt b/forge-gui/res/cardsfolder/m/mystical_tutor.txt index 3983fe82e67..d2a344e9bb4 100644 --- a/forge-gui/res/cardsfolder/m/mystical_tutor.txt +++ b/forge-gui/res/cardsfolder/m/mystical_tutor.txt @@ -1,7 +1,7 @@ Name:Mystical Tutor ManaCost:U Types:Instant -A:SP$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Instant,Sorcery | ChangeNum$ 1 | SpellDescription$ Search your library for an instant or sorcery card, reveal it, then shuffle and put that card on top. +A:SP$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Instant,Sorcery | SpellDescription$ Search your library for an instant or sorcery card, reveal it, then shuffle and put that card on top. # TODO: The AI will currently search for the most expensive valid card in the library. This can be used to the AI's advantage with careful deck design (which is why this card only works in properly designed AI decks). Consider improving tutoring logic with priorities. AI:RemoveDeck:Random Oracle:Search your library for an instant or sorcery card, reveal it, then shuffle and put that card on top. diff --git a/forge-gui/res/cardsfolder/n/nahiri_the_harbinger.txt b/forge-gui/res/cardsfolder/n/nahiri_the_harbinger.txt index a21ab759b1d..5e3f685b58d 100644 --- a/forge-gui/res/cardsfolder/n/nahiri_the_harbinger.txt +++ b/forge-gui/res/cardsfolder/n/nahiri_the_harbinger.txt @@ -4,7 +4,7 @@ Types:Legendary Planeswalker Nahiri Loyalty:4 A:AB$ Draw | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | Defined$ You | NumCards$ 1 | UnlessCost$ Discard<1/Card> | UnlessSwitched$ True | UnlessPayer$ You | SpellDescription$ You may discard a card. If you do, draw a card. A:AB$ ChangeZone | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | ValidTgts$ Enchantment,Artifact.tapped,Creature.tapped | TgtPrompt$ Select target enchantment, tapped artifact, or tapped creature | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target enchantment, tapped artifact, or tapped creature. -A:AB$ ChangeZone | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Artifact,Creature | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBPump | SpellDescription$ Search your library for an artifact or creature card, put it onto the battlefield, then shuffle. It gains haste. Return it to your hand at the beginning of the next end step. +A:AB$ ChangeZone | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Artifact,Creature | RememberChanged$ True | SubAbility$ DBPump | SpellDescription$ Search your library for an artifact or creature card, put it onto the battlefield, then shuffle. It gains haste. Return it to your hand at the beginning of the next end step. SVar:DBPump:DB$ Animate | Keywords$ Haste | Duration$ Permanent | AtEOT$ Hand | Defined$ Remembered | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Ability$Discard diff --git a/forge-gui/res/cardsfolder/n/natural_balance.txt b/forge-gui/res/cardsfolder/n/natural_balance.txt index c84abcd92f3..cc0c1aec172 100644 --- a/forge-gui/res/cardsfolder/n/natural_balance.txt +++ b/forge-gui/res/cardsfolder/n/natural_balance.txt @@ -3,7 +3,7 @@ ManaCost:2 G G Types:Sorcery A:SP$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ BalanceLands | AILogic$ BalanceLands | SpellDescription$ Each player who controls six or more lands chooses five lands they control and sacrifices the rest. Each player who controls four or fewer lands may search their library for up to X basic land cards and put them onto the battlefield, where X is five minus the number of lands they control. Then each player who searched their library this way shuffles. SVar:BalanceLands:DB$ Sacrifice | SacValid$ Land | Amount$ SacX | Defined$ Remembered | ConditionCheckSVar$ SacX | ConditionSVarCompare$ GT0 | SubAbility$ FetchLands -SVar:FetchLands:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ FetchX | DefinedPlayer$ Remembered | ConditionCheckSVar$ FetchX | ConditionSVarCompare$ GT0 | ShuffleNonMandatory$ True +SVar:FetchLands:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ FetchX | DefinedPlayer$ Remembered | ConditionCheckSVar$ FetchX | ConditionSVarCompare$ GT0 | ShuffleNonMandatory$ True SVar:LandsControlled:Count$Valid Land.RememberedPlayerCtrl SVar:SacX:SVar$LandsControlled/Minus.5 SVar:FetchX:Number$5/Minus.LandsControlled diff --git a/forge-gui/res/cardsfolder/n/natural_connection.txt b/forge-gui/res/cardsfolder/n/natural_connection.txt index 7ba9871a690..c92bff4a33c 100644 --- a/forge-gui/res/cardsfolder/n/natural_connection.txt +++ b/forge-gui/res/cardsfolder/n/natural_connection.txt @@ -1,5 +1,5 @@ Name:Natural Connection ManaCost:2 G Types:Instant -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Oracle:Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/n/naya_panorama.txt b/forge-gui/res/cardsfolder/n/naya_panorama.txt index 4f5a74afc75..be8209b8fb9 100644 --- a/forge-gui/res/cardsfolder/n/naya_panorama.txt +++ b/forge-gui/res/cardsfolder/n/naya_panorama.txt @@ -2,5 +2,5 @@ Name:Naya Panorama ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Mountain+Basic,Land.Forest+Basic,Land.Plains+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Mountain, Forest, or Plains card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Mountain.Basic,Forest.Basic,Plains.Basic | ChangeTypeDesc$ basic Mountain, Forest, or Plains | SpellDescription$ Search your library for a basic Mountain, Forest, or Plains card, put it onto the battlefield tapped, then shuffle. Oracle:{T}: Add {C}.\n{1}, {T}, Sacrifice Naya Panorama: Search your library for a basic Mountain, Forest, or Plains card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/n/new_frontiers.txt b/forge-gui/res/cardsfolder/n/new_frontiers.txt index 90ea5f9a449..fd8881bcb69 100644 --- a/forge-gui/res/cardsfolder/n/new_frontiers.txt +++ b/forge-gui/res/cardsfolder/n/new_frontiers.txt @@ -1,7 +1,7 @@ Name:New Frontiers ManaCost:X G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | DefinedPlayer$ Player | ChangeNum$ X | Optional$ True | Tapped$ True | ShuffleNonMandatory$ True | SpellDescription$ Each player may search their library for up to X basic land cards and put them onto the battlefield tapped. Then each player who searched their library this way shuffles. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ Player | ChangeNum$ X | Optional$ True | Tapped$ True | ShuffleNonMandatory$ True | SpellDescription$ Each player may search their library for up to X basic land cards and put them onto the battlefield tapped. Then each player who searched their library this way shuffles. SVar:X:Count$xPaid AI:RemoveDeck:All Oracle:Each player may search their library for up to X basic land cards and put them onto the battlefield tapped. Then each player who searched their library this way shuffles. diff --git a/forge-gui/res/cardsfolder/n/nissa_worldwaker.txt b/forge-gui/res/cardsfolder/n/nissa_worldwaker.txt index a4d3a4f5036..ba0aa0bcabc 100644 --- a/forge-gui/res/cardsfolder/n/nissa_worldwaker.txt +++ b/forge-gui/res/cardsfolder/n/nissa_worldwaker.txt @@ -4,7 +4,7 @@ Types:Legendary Planeswalker Nissa Loyalty:3 A:AB$ Animate | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select target land you control | Power$ 4 | Toughness$ 4 | Types$ Creature,Elemental | Duration$ Permanent | Keywords$ Trample | SpellDescription$ Target land you control becomes a 4/4 Elemental creature with trample. It's still a land. A:AB$ Untap | Cost$ AddCounter<1/LOYALTY> | ValidTgts$ Forest | TgtPrompt$ Choose target forest | TargetMin$ 0 | TargetMax$ 4 | Planeswalker$ True | SpellDescription$ Untap up to four target Forests. -A:AB$ ChangeZone | Cost$ SubCounter<7/LOYALTY> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ XFetch | Planeswalker$ True | Ultimate$ True | StackDescription$ SpellDescription | RememberChanged$ True | SubAbility$ DBAnimate | SpellDescription$ Search your library for any number of basic land cards, put them onto the battlefield, then shuffle. Those lands become 4/4 Elemental creatures with trample. They're still lands. +A:AB$ ChangeZone | Cost$ SubCounter<7/LOYALTY> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ XFetch | Planeswalker$ True | Ultimate$ True | StackDescription$ SpellDescription | RememberChanged$ True | SubAbility$ DBAnimate | SpellDescription$ Search your library for any number of basic land cards, put them onto the battlefield, then shuffle. Those lands become 4/4 Elemental creatures with trample. They're still lands. SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Power$ 4 | Toughness$ 4 | Types$ Creature,Elemental | Duration$ Permanent | Keywords$ Trample | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:XFetch:Count$ValidLibrary Land.Basic+YouCtrl diff --git a/forge-gui/res/cardsfolder/n/nissas_expedition.txt b/forge-gui/res/cardsfolder/n/nissas_expedition.txt index 28b476ed43b..137c3086ed9 100644 --- a/forge-gui/res/cardsfolder/n/nissas_expedition.txt +++ b/forge-gui/res/cardsfolder/n/nissas_expedition.txt @@ -2,5 +2,5 @@ Name:Nissa's Expedition ManaCost:4 G Types:Sorcery K:Convoke -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 2 | Tapped$ True | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | Tapped$ True | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. Oracle:Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature's color.)\nSearch your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/n/nissas_renewal.txt b/forge-gui/res/cardsfolder/n/nissas_renewal.txt index f032639f788..a56ad80cdce 100644 --- a/forge-gui/res/cardsfolder/n/nissas_renewal.txt +++ b/forge-gui/res/cardsfolder/n/nissas_renewal.txt @@ -1,6 +1,6 @@ Name:Nissa's Renewal ManaCost:5 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 3 | SubAbility$ DBGainLife | SpellDescription$ Search your library for up to three basic land cards, put them onto the battlefield tapped, then shuffle. You gain 7 life. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 3 | SubAbility$ DBGainLife | SpellDescription$ Search your library for up to three basic land cards, put them onto the battlefield tapped, then shuffle. You gain 7 life. SVar:DBGainLife:DB$ GainLife | LifeAmount$ 7 Oracle:Search your library for up to three basic land cards, put them onto the battlefield tapped, then shuffle. You gain 7 life. diff --git a/forge-gui/res/cardsfolder/o/oashra_cultivator.txt b/forge-gui/res/cardsfolder/o/oashra_cultivator.txt index fc120716bea..fb0c00ac813 100644 --- a/forge-gui/res/cardsfolder/o/oashra_cultivator.txt +++ b/forge-gui/res/cardsfolder/o/oashra_cultivator.txt @@ -2,5 +2,5 @@ Name:Oashra Cultivator ManaCost:G Types:Creature Human Druid PT:0/3 -A:AB$ ChangeZone | Cost$ 2 G T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 2 G T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Oracle:{2}{G}, {T}, Sacrifice Oashra Cultivator: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/o/oath_of_lieges.txt b/forge-gui/res/cardsfolder/o/oath_of_lieges.txt index e174fdcd218..2b20bfc53a9 100644 --- a/forge-gui/res/cardsfolder/o/oath_of_lieges.txt +++ b/forge-gui/res/cardsfolder/o/oath_of_lieges.txt @@ -2,7 +2,7 @@ Name:Oath of Lieges ManaCost:1 W Types:Enchantment T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | Execute$ LiegesSearch | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of each player's upkeep, that player chooses target player who controls more lands than they do and is their opponent. The first player may search their library for a basic land card, put that card onto the battlefield, then shuffle. -SVar:LiegesSearch:DB$ ChangeZone | TargetingPlayer$ TriggeredPlayer | ValidTgts$ Player.OpponentToActive+withMoreLandsThanActive | TgtPrompt$ Choose target opponent who controls more land than you | DefinedPlayer$ TriggeredPlayer | Chooser$ TriggeredPlayer | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Optional$ True | ShuffleNonMandatory$ True +SVar:LiegesSearch:DB$ ChangeZone | TargetingPlayer$ TriggeredPlayer | ValidTgts$ Player.OpponentToActive+withMoreLandsThanActive | TgtPrompt$ Choose target opponent who controls more land than you | DefinedPlayer$ TriggeredPlayer | Chooser$ TriggeredPlayer | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Optional$ True | ShuffleNonMandatory$ True SVar:NeedsToPlayVar:Z GEY SVar:Z:Count$Valid Land.YouDontCtrl+inZoneBattlefield SVar:Y:Count$Valid Land.YouCtrl+inZoneBattlefield diff --git a/forge-gui/res/cardsfolder/o/obscura_storefront.txt b/forge-gui/res/cardsfolder/o/obscura_storefront.txt index 03373ed06b7..591f4f8ee84 100644 --- a/forge-gui/res/cardsfolder/o/obscura_storefront.txt +++ b/forge-gui/res/cardsfolder/o/obscura_storefront.txt @@ -4,7 +4,7 @@ Types:Land T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBSacrifice | TriggerDescription$ When CARDNAME enters, sacrifice it. When you do, search your library for a basic Plains, Island, or Swamp card, put it onto the battlefield tapped, then shuffle and you gain 1 life. SVar:DBSacrifice:DB$ Sacrifice | RememberSacrificed$ True | SubAbility$ DBImmediateTrigger SVar:DBImmediateTrigger:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | Execute$ DBChangeZone | SubAbility$ DBCleanup | TriggerDescription$ Search your library for a basic Plains, Island, or Swamp card, put it onto the battlefield tapped, then shuffle and you gain 1 life. -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Plains+Basic,Land.Island+Basic,Land.Swamp+Basic | ChangeNum$ 1 | SubAbility$ DBGainLife +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Plains.Basic,Island.Basic,Swamp.Basic | ChangeTypeDesc$ basic Plains, Island, or Swamp | SubAbility$ DBGainLife SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Ability$Sacrifice|LifeGain diff --git a/forge-gui/res/cardsfolder/o/old_growth_dryads.txt b/forge-gui/res/cardsfolder/o/old_growth_dryads.txt index b11bf4391b0..980fe24cdb9 100644 --- a/forge-gui/res/cardsfolder/o/old_growth_dryads.txt +++ b/forge-gui/res/cardsfolder/o/old_growth_dryads.txt @@ -3,5 +3,5 @@ ManaCost:G Types:Creature Dryad PT:3/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters, each opponent may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | DefinedPlayer$ Player.Opponent | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | DefinedPlayer$ Player.Opponent | ChangeNum$ 1 | ShuffleNonMandatory$ True Oracle:When Old-Growth Dryads enters, each opponent may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/o/omen_of_the_hunt.txt b/forge-gui/res/cardsfolder/o/omen_of_the_hunt.txt index df869ad556c..239b6021929 100644 --- a/forge-gui/res/cardsfolder/o/omen_of_the_hunt.txt +++ b/forge-gui/res/cardsfolder/o/omen_of_the_hunt.txt @@ -3,6 +3,6 @@ ManaCost:2 G Types:Enchantment K:Flash T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearch | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | ShuffleNonMandatory$ True A:AB$ Scry | Cost$ 2 G Sac<1/CARDNAME> | ScryNum$ 2 | AILogic$ BestOpportunity | SpellDescription$ Scry 2. Oracle:Flash\nWhen Omen of the Hunt enters, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle.\n{2}{G}, Sacrifice Omen of the Hunt: Scry 2. diff --git a/forge-gui/res/cardsfolder/o/ominous_parcel.txt b/forge-gui/res/cardsfolder/o/ominous_parcel.txt index feea1c008e8..1309ee5e763 100644 --- a/forge-gui/res/cardsfolder/o/ominous_parcel.txt +++ b/forge-gui/res/cardsfolder/o/ominous_parcel.txt @@ -1,6 +1,6 @@ Name:Ominous Parcel ManaCost:1 Types:Artifact -A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. A:AB$ DealDamage | Cost$ 5 T Sac<1/CARDNAME> | ValidTgts$ Creature | NumDmg$ 4 | SpellDescription$ It deals 4 damage to target creature. Oracle:{2}, {T}, Sacrifice Ominous Parcel: Search your library for a basic land card, reveal it, put it into your hand, then shuffle.\n{5}, {T}, Sacrifice Ominous Parcel: It deals 4 damage to target creature. diff --git a/forge-gui/res/cardsfolder/o/ondu_giant.txt b/forge-gui/res/cardsfolder/o/ondu_giant.txt index 5889a7f30bd..7ef64a2b588 100644 --- a/forge-gui/res/cardsfolder/o/ondu_giant.txt +++ b/forge-gui/res/cardsfolder/o/ondu_giant.txt @@ -3,5 +3,5 @@ ManaCost:3 G Types:Creature Giant Druid PT:2/4 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ShuffleNonMandatory$ True Oracle:When Ondu Giant enters, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/o/one_with_nature.txt b/forge-gui/res/cardsfolder/o/one_with_nature.txt index 76a2a6aca03..7baa3e00694 100644 --- a/forge-gui/res/cardsfolder/o/one_with_nature.txt +++ b/forge-gui/res/cardsfolder/o/one_with_nature.txt @@ -5,5 +5,5 @@ K:Enchant:Creature SVar:AttachAITgts:Card.powerGE1 SVar:AttachAILogic:Pump T:Mode$ DamageDone | ValidSource$ Card.AttachedBy | ValidTarget$ Player | TriggerZones$ Battlefield | Execute$ TrigChange | CombatDamage$ True | TriggerDescription$ Whenever enchanted creature deals combat damage to a player, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:Enchant creature\nWhenever enchanted creature deals combat damage to a player, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/o/open_the_armory.txt b/forge-gui/res/cardsfolder/o/open_the_armory.txt index 085ca9dd571..73a5e1ddaf6 100644 --- a/forge-gui/res/cardsfolder/o/open_the_armory.txt +++ b/forge-gui/res/cardsfolder/o/open_the_armory.txt @@ -1,6 +1,6 @@ Name:Open the Armory ManaCost:1 W Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Aura,Equipment | ChangeNum$ 1 | SpellDescription$ Search your library for an Aura or Equipment card, reveal it, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Aura,Equipment | SpellDescription$ Search your library for an Aura or Equipment card, reveal it, put it into your hand, then shuffle. AI:RemoveDeck:Random Oracle:Search your library for an Aura or Equipment card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/o/ordeal_of_nylea.txt b/forge-gui/res/cardsfolder/o/ordeal_of_nylea.txt index 4698a3e9830..b89fe747183 100644 --- a/forge-gui/res/cardsfolder/o/ordeal_of_nylea.txt +++ b/forge-gui/res/cardsfolder/o/ordeal_of_nylea.txt @@ -7,7 +7,7 @@ T:Mode$ Attacks | ValidCard$ Card.AttachedBy | Execute$ TrigPutCounter | Trigger SVar:TrigPutCounter:DB$ PutCounter | Defined$ TriggeredAttackerLKICopy | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBSac SVar:DBSac:DB$ Sacrifice | ConditionDefined$ TriggeredAttackerLKICopy | ConditionPresent$ Card.counters_GE3_P1P1 T:Mode$ Sacrificed | ValidPlayer$ You | ValidCard$ Card.Self | Execute$ TrigDiscard | TriggerZones$ Battlefield | TriggerDescription$ When you sacrifice CARDNAME, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. -SVar:TrigDiscard:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 2 +SVar:TrigDiscard:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 S:Mode$ Continuous | Affected$ Creature.AttachedBy | AddSVar$ AE SVar:AE:SVar:HasAttackEffect:TRUE SVar:SacMe:4 diff --git a/forge-gui/res/cardsfolder/o/orochi_colony.txt b/forge-gui/res/cardsfolder/o/orochi_colony.txt index 65da5a78198..ad0da2087ef 100644 --- a/forge-gui/res/cardsfolder/o/orochi_colony.txt +++ b/forge-gui/res/cardsfolder/o/orochi_colony.txt @@ -2,7 +2,7 @@ Name:Orochi Colony ManaCost:no cost Types:Plane Kamigawa T:Mode$ DamageDone | ValidSource$ Creature.YouCtrl | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigRamp | TriggerZones$ Command | TriggerDescription$ Whenever a creature you control deals combat damage to a player, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigRamp:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigRamp:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True T:Mode$ ChaosEnsues | TriggerZones$ Command | Execute$ RolledChaos | TriggerDescription$ Whenever chaos ensues, target creature can't be blocked this turn. SVar:RolledChaos:DB$ Effect | ValidTgts$ Creature | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | StaticAbilities$ Unblockable | AILogic$ Pump SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. diff --git a/forge-gui/res/cardsfolder/p/path_of_the_animist.txt b/forge-gui/res/cardsfolder/p/path_of_the_animist.txt index c5bbead63b2..de0fa4933b4 100644 --- a/forge-gui/res/cardsfolder/p/path_of_the_animist.txt +++ b/forge-gui/res/cardsfolder/p/path_of_the_animist.txt @@ -1,7 +1,7 @@ Name:Path of the Animist ManaCost:3 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 2 | Tapped$ True | SubAbility$ DBSpace | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | Tapped$ True | SubAbility$ DBSpace | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. SVar:DBSpace:DB$ BlankLine | SubAbility$ DBVote | SpellDescription$ ,,,,,, SVar:DBVote:DB$ Vote | Defined$ Player | Choices$ DBPlaneswalk,DBChaos | VoteTiedAbility$ DBChaos | StackDescription$ SpellDescription | SpellDescription$ Will of the Planeswalkers — Starting with you, each player votes for planeswalk or chaos. If planeswalk gets more votes, planeswalk. If chaos gets more votes or the vote is tied, chaos ensues. SVar:DBPlaneswalk:DB$ Planeswalk | SpellDescription$ Planeswalk diff --git a/forge-gui/res/cardsfolder/p/path_to_exile.txt b/forge-gui/res/cardsfolder/p/path_to_exile.txt index 0f64e9a4ef6..d688dfa4553 100644 --- a/forge-gui/res/cardsfolder/p/path_to_exile.txt +++ b/forge-gui/res/cardsfolder/p/path_to_exile.txt @@ -2,5 +2,5 @@ Name:Path to Exile ManaCost:W Types:Instant A:SP$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Creature | SubAbility$ DBChange | StackDescription$ Exile {c:Targeted}. {p:TargetedController} may search their library for a basic land card, put that card onto the battlefield tapped, then shuffle their library. | SpellDescription$ Exile target creature. Its controller may search their library for a basic land card, put that card onto the battlefield tapped, then shuffle. -SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True | StackDescription$ None +SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True | StackDescription$ None Oracle:Exile target creature. Its controller may search their library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/p/path_to_the_festival.txt b/forge-gui/res/cardsfolder/p/path_to_the_festival.txt index 8cbb222daf7..de048866373 100644 --- a/forge-gui/res/cardsfolder/p/path_to_the_festival.txt +++ b/forge-gui/res/cardsfolder/p/path_to_the_festival.txt @@ -1,7 +1,7 @@ Name:Path to the Festival ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBScry | StackDescription$ SpellDescription | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Then if there are three or more basic land types among lands you control, scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.) +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | RememberChanged$ True | SubAbility$ DBScry | StackDescription$ SpellDescription | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Then if there are three or more basic land types among lands you control, scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.) SVar:DBScry:DB$ Scry | ConditionCheckSVar$ X | ConditionSVarCompare$ GE3 | ScryNum$ 1 SVar:X:PlayerCountPropertyYou$DomainPlayer K:Flashback:4 G diff --git a/forge-gui/res/cardsfolder/p/path_to_the_world_tree.txt b/forge-gui/res/cardsfolder/p/path_to_the_world_tree.txt index 6efc1b9aea4..279e9043ff5 100644 --- a/forge-gui/res/cardsfolder/p/path_to_the_world_tree.txt +++ b/forge-gui/res/cardsfolder/p/path_to_the_world_tree.txt @@ -2,7 +2,7 @@ Name:Path to the World Tree ManaCost:1 G Types:Enchantment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters, search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land A:AB$ GainLife | Cost$ 2 W U B R G Sac<1/CARDNAME> | Defined$ You | LifeAmount$ 2 | SubAbility$ DBDraw | SpellDescription$ You gain 2 life and draw two cards. Target opponent loses 2 life. CARDNAME deals 2 damage to up to one target creature. You create a 2/2 green Bear creature token. SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 2 | SubAbility$ DBLoseLife SVar:DBLoseLife:DB$ LoseLife | ValidTgts$ Player.Opponent | TgtPrompt$ Choose an opponent | LifeAmount$ 2 | SubAbility$ DBDamage diff --git a/forge-gui/res/cardsfolder/p/peregrination.txt b/forge-gui/res/cardsfolder/p/peregrination.txt index 7c2a934fa4b..fc3fb85d0b3 100644 --- a/forge-gui/res/cardsfolder/p/peregrination.txt +++ b/forge-gui/res/cardsfolder/p/peregrination.txt @@ -1,7 +1,7 @@ Name:Peregrination ManaCost:3 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards, reveal those cards, and put one onto the battlefield tapped and the other into your hand. Shuffle, then scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.) +A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to two basic land cards, reveal those cards, and put one onto the battlefield tapped and the other into your hand. Shuffle, then scry 1. (Look at the top card of your library. You may put that card on the bottom of your library.) SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.IsRemembered | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put onto the battlefield | Tapped$ True | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.IsRemembered | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put into your hand | StackDescription$ None | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBScry diff --git a/forge-gui/res/cardsfolder/p/perilous_landscape.txt b/forge-gui/res/cardsfolder/p/perilous_landscape.txt index cd2635a4da7..cfed140ef59 100644 --- a/forge-gui/res/cardsfolder/p/perilous_landscape.txt +++ b/forge-gui/res/cardsfolder/p/perilous_landscape.txt @@ -2,6 +2,6 @@ Name:Perilous Landscape ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Island+Basic,Land.Mountain+Basic,Land.Plains+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Island, Mountain, or Plains card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Island.Basic,Mountain.Basic,Plains.Basic | ChangeTypeDesc$ basic Island, Mountain, or Plains | SpellDescription$ Search your library for a basic Island, Mountain, or Plains card, put it onto the battlefield tapped, then shuffle. K:Cycling:U R W Oracle:{T}: Add {C}.\n{T}, Sacrifice Perilous Landscape: Search your library for a basic Island, Mountain, or Plains card, put it onto the battlefield tapped, then shuffle.\nCycling {U}{R}{W} ({U}{R}{W}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/p/pilgrims_eye.txt b/forge-gui/res/cardsfolder/p/pilgrims_eye.txt index 1c7d14ba92a..b2e290bf9b3 100644 --- a/forge-gui/res/cardsfolder/p/pilgrims_eye.txt +++ b/forge-gui/res/cardsfolder/p/pilgrims_eye.txt @@ -4,5 +4,5 @@ Types:Artifact Creature Thopter PT:1/1 K:Flying T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:Flying\nWhen Pilgrim's Eye enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/p/planar_birth.txt b/forge-gui/res/cardsfolder/p/planar_birth.txt index f84f4434ebc..04019bc427f 100644 --- a/forge-gui/res/cardsfolder/p/planar_birth.txt +++ b/forge-gui/res/cardsfolder/p/planar_birth.txt @@ -1,6 +1,6 @@ Name:Planar Birth ManaCost:1 W Types:Sorcery -A:SP$ ChangeZoneAll | ChangeType$ Land.Basic | Origin$ Graveyard | Destination$ Battlefield | Tapped$ True | SpellDescription$ Return all basic land cards from all graveyards to the battlefield tapped under their owners' control. +A:SP$ ChangeZoneAll | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Origin$ Graveyard | Destination$ Battlefield | Tapped$ True | SpellDescription$ Return all basic land cards from all graveyards to the battlefield tapped under their owners' control. AI:RemoveDeck:Random Oracle:Return all basic land cards from all graveyards to the battlefield tapped under their owners' control. diff --git a/forge-gui/res/cardsfolder/p/point_the_way.txt b/forge-gui/res/cardsfolder/p/point_the_way.txt index 0ca1ed000f4..11dee17d0eb 100644 --- a/forge-gui/res/cardsfolder/p/point_the_way.txt +++ b/forge-gui/res/cardsfolder/p/point_the_way.txt @@ -2,6 +2,6 @@ Name:Point the Way ManaCost:G Types:Enchantment K:Start your engines -A:AB$ ChangeZone | Cost$ 3 G Sac<1/CARDNAME/this enchantment> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ Count$YourSpeed | Tapped$ True | SpellDescription$ Search your library for up to X basic land cards, where X is your speed. Put those cards onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 3 G Sac<1/CARDNAME/this enchantment> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ Count$YourSpeed | Tapped$ True | SpellDescription$ Search your library for up to X basic land cards, where X is your speed. Put those cards onto the battlefield tapped, then shuffle. DeckHas:Ability$Sacrifice Oracle:Start your engines! (If you have no speed, it starts at 1. It increases once on each of your turns when an opponent loses life. Max speed is 4.)\n{3}{G}, Sacrifice this enchantment: Search your library for up to X basic land cards, where X is your speed. Put those cards onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/p/polluted_delta.txt b/forge-gui/res/cardsfolder/p/polluted_delta.txt index b2aa5893acf..7717ce7c8b4 100644 --- a/forge-gui/res/cardsfolder/p/polluted_delta.txt +++ b/forge-gui/res/cardsfolder/p/polluted_delta.txt @@ -1,5 +1,5 @@ Name:Polluted Delta ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Island,Swamp | ChangeNum$ 1 | SpellDescription$ Search your library for a Island or Swamp card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Island,Swamp | SpellDescription$ Search your library for a Island or Swamp card, put it onto the battlefield, then shuffle. Oracle:{T}, Pay 1 life, Sacrifice Polluted Delta: Search your library for an Island or Swamp card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/p/priest_of_the_wakening_sun.txt b/forge-gui/res/cardsfolder/p/priest_of_the_wakening_sun.txt index 2d65952968d..9781f933a1e 100644 --- a/forge-gui/res/cardsfolder/p/priest_of_the_wakening_sun.txt +++ b/forge-gui/res/cardsfolder/p/priest_of_the_wakening_sun.txt @@ -4,6 +4,6 @@ Types:Creature Human Cleric PT:1/1 T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ ABGainLife | OptionalDecider$ You | TriggerDescription$ At the beginning of your upkeep, you may reveal a Dinosaur card from your hand. If you do, you gain 2 life. SVar:ABGainLife:DB$ GainLife | UnlessCost$ Reveal<1/Creature.Dinosaur> | UnlessPayer$ You | UnlessSwitched$ True | LifeAmount$ 2 | StackDescription$ you gain 2 life. | SpellDescription$ You gain 2 life. -A:AB$ ChangeZone | Cost$ 3 W W Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Creature.Dinosaur | ChangeNum$ 1 | SpellDescription$ Search your library for a Dinosaur card, reveal it, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 3 W W Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Dinosaur | SpellDescription$ Search your library for a Dinosaur card, reveal it, put it into your hand, then shuffle. DeckHints:Type$Dinosaur Oracle:At the beginning of your upkeep, you may reveal a Dinosaur card from your hand. If you do, you gain 2 life.\n{3}{W}{W}, Sacrifice Priest of the Wakening Sun: Search your library for a Dinosaur card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/p/primal_druid.txt b/forge-gui/res/cardsfolder/p/primal_druid.txt index 3cbdc004467..5d3a80146fe 100644 --- a/forge-gui/res/cardsfolder/p/primal_druid.txt +++ b/forge-gui/res/cardsfolder/p/primal_druid.txt @@ -3,5 +3,5 @@ ManaCost:1 G Types:Creature Human Druid PT:0/3 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | ShuffleNonMandatory$ True Oracle:When Primal Druid dies, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/p/primal_growth.txt b/forge-gui/res/cardsfolder/p/primal_growth.txt index 6a29c53f1db..24866d2cdf5 100644 --- a/forge-gui/res/cardsfolder/p/primal_growth.txt +++ b/forge-gui/res/cardsfolder/p/primal_growth.txt @@ -2,6 +2,6 @@ Name:Primal Growth ManaCost:2 G Types:Sorcery K:Kicker:Sac<1/Creature> -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ X | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield, then shuffle. If this spell was kicked, instead search your library for up to two basic land cards, put them onto the battlefield, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield, then shuffle. If this spell was kicked, instead search your library for up to two basic land cards, put them onto the battlefield, then shuffle. SVar:X:Count$Kicked.2.1 Oracle:Kicker—Sacrifice a creature. (You may sacrifice a creature in addition to any other costs as you cast this spell.)\nSearch your library for a basic land card, put that card onto the battlefield, then shuffle. If this spell was kicked, instead search your library for up to two basic land cards, put them onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/p/primeval_herald.txt b/forge-gui/res/cardsfolder/p/primeval_herald.txt index 3af4b74e16c..af2ad5d9bea 100644 --- a/forge-gui/res/cardsfolder/p/primeval_herald.txt +++ b/forge-gui/res/cardsfolder/p/primeval_herald.txt @@ -5,6 +5,6 @@ PT:3/1 K:Trample T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ Whenever CARDNAME enters or attacks, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigChange | TriggerZones$ Battlefield | OptionalDecider$ You | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters or attacks, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True SVar:HasAttackEffect:TRUE Oracle:Trample\nWhenever Primeval Herald enters or attacks, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/p/prismatic_vista.txt b/forge-gui/res/cardsfolder/p/prismatic_vista.txt index 8782d31f4a2..d78221a64b1 100644 --- a/forge-gui/res/cardsfolder/p/prismatic_vista.txt +++ b/forge-gui/res/cardsfolder/p/prismatic_vista.txt @@ -1,6 +1,6 @@ Name:Prismatic Vista ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield, then shuffle. DeckHas:Ability$Sacrifice Oracle:{T}, Pay 1 life, Sacrifice Prismatic Vista: Search your library for a basic land card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/p/promising_vein.txt b/forge-gui/res/cardsfolder/p/promising_vein.txt index e1a0d4be938..4b9316f7bcb 100644 --- a/forge-gui/res/cardsfolder/p/promising_vein.txt +++ b/forge-gui/res/cardsfolder/p/promising_vein.txt @@ -2,6 +2,6 @@ Name:Promising Vein ManaCost:no cost Types:Land Cave A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. DeckHas:Ability$Sacrifice Oracle:{T}: Add {C}.\n{1}, {T}, Sacrifice Promising Vein: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/p/purestrain_genestealer.txt b/forge-gui/res/cardsfolder/p/purestrain_genestealer.txt index 5504a3c55c1..5ce9d72497b 100644 --- a/forge-gui/res/cardsfolder/p/purestrain_genestealer.txt +++ b/forge-gui/res/cardsfolder/p/purestrain_genestealer.txt @@ -4,7 +4,7 @@ Types:Creature Tyranid PT:1/1 K:etbCounter:P1P1:2 T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigRamp | TriggerDescription$ Vanguard Species — Whenever CARDNAME attacks, you may remove a +1/+1 counter from it. If you do, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigRamp:AB$ ChangeZone | Cost$ SubCounter<1/P1P1> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigRamp:AB$ ChangeZone | Cost$ SubCounter<1/P1P1> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land SVar:HasAttackEffect:TRUE DeckHas:Ability$Counters Oracle:Purestrain Genestealer enters with two +1/+1 counters on it.\nVanguard Species — Whenever Purestrain Genestealer attacks, you may remove a +1/+1 counter from it. If you do, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/q/quiet_speculation.txt b/forge-gui/res/cardsfolder/q/quiet_speculation.txt index 84cf709db8a..56f9380da1b 100644 --- a/forge-gui/res/cardsfolder/q/quiet_speculation.txt +++ b/forge-gui/res/cardsfolder/q/quiet_speculation.txt @@ -1,6 +1,6 @@ Name:Quiet Speculation ManaCost:1 U Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Graveyard | ValidTgts$ Player | ChangeType$ Card.withFlashback | ChangeNum$ 3 | SpellDescription$ Search target player's library for up to three cards with flashback and put them into that player's graveyard. Then the player shuffles. +A:SP$ ChangeZone | Origin$ Library | Destination$ Graveyard | ValidTgts$ Player | ChangeType$ Card.withFlashback | ChangeTypeDesc$ cards with flashback | ChangeNum$ 3 | SpellDescription$ Search target player's library for up to three cards with flashback and put them into that player's graveyard. Then the player shuffles. AI:RemoveDeck:All Oracle:Search target player's library for up to three cards with flashback and put them into that player's graveyard. Then the player shuffles. diff --git a/forge-gui/res/cardsfolder/q/quirion_trailblazer.txt b/forge-gui/res/cardsfolder/q/quirion_trailblazer.txt index b9252fdc99d..ac82e18531e 100644 --- a/forge-gui/res/cardsfolder/q/quirion_trailblazer.txt +++ b/forge-gui/res/cardsfolder/q/quirion_trailblazer.txt @@ -3,5 +3,5 @@ ManaCost:3 G Types:Creature Elf Scout PT:1/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ShuffleNonMandatory$ True Oracle:When Quirion Trailblazer enters, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/r/radioactive_spider.txt b/forge-gui/res/cardsfolder/r/radioactive_spider.txt index aa26f8325ef..9d180549e85 100644 --- a/forge-gui/res/cardsfolder/r/radioactive_spider.txt +++ b/forge-gui/res/cardsfolder/r/radioactive_spider.txt @@ -4,6 +4,5 @@ Types:Creature Spider PT:1/1 K:Reach K:Deathtouch -A:AB$ ChangeZone | Cost$ 2 Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Spider.Hero | ChangeNum$ 1 | SorcerySpeed$ True | SpellDescription$ Search your libary for a Spider Hero card, reveal it, put it into your hand, then shuffle. Activate only as a sorcery. -DeckHints:Type$Spider|Hero +A:AB$ ChangeZone | Cost$ 2 Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Spider.Hero | ChangeTypeDesc$ Spider Hero | SorcerySpeed$ True | SpellDescription$ Search your libary for a Spider Hero card, reveal it, put it into your hand, then shuffle. Activate only as a sorcery. Oracle:Reach, deathtouch\nFateful Bite — {2}, Sacrifice this creature: Search your libary for a Spider Hero card, reveal it, put it into your hand, then shuffle. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/r/rampaging_growth.txt b/forge-gui/res/cardsfolder/r/rampaging_growth.txt index 7ea2b4cd5a8..238f23875f0 100644 --- a/forge-gui/res/cardsfolder/r/rampaging_growth.txt +++ b/forge-gui/res/cardsfolder/r/rampaging_growth.txt @@ -1,7 +1,7 @@ Name:Rampaging Growth ManaCost:3 G Types:Instant -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | RememberChanged$ True | ChangeNum$ 1 | SubAbility$ DBAnimate | SpellDescription$ Search your library for a basic land card and put that card onto the battlefield, then shuffle. Until end of turn, that land becomes a 4/3 Insect creature with reach and haste. It's still a land. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | RememberChanged$ True | ChangeNum$ 1 | SubAbility$ DBAnimate | SpellDescription$ Search your library for a basic land card and put that card onto the battlefield, then shuffle. Until end of turn, that land becomes a 4/3 Insect creature with reach and haste. It's still a land. SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Power$ 4 | Toughness$ 3 | Types$ Creature,Insect | Keywords$ Haste & Reach | StackDescription$ None | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Type$Insect diff --git a/forge-gui/res/cardsfolder/r/rampant_growth.txt b/forge-gui/res/cardsfolder/r/rampant_growth.txt index 0d87977e68b..ebf1e1e8372 100644 --- a/forge-gui/res/cardsfolder/r/rampant_growth.txt +++ b/forge-gui/res/cardsfolder/r/rampant_growth.txt @@ -1,5 +1,5 @@ Name:Rampant Growth ManaCost:1 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | StackDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | StackDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Oracle:Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/r/rampant_rejuvenator.txt b/forge-gui/res/cardsfolder/r/rampant_rejuvenator.txt index 94940aec976..978a5c61b43 100644 --- a/forge-gui/res/cardsfolder/r/rampant_rejuvenator.txt +++ b/forge-gui/res/cardsfolder/r/rampant_rejuvenator.txt @@ -4,7 +4,7 @@ Types:Creature Plant Hydra PT:0/0 K:etbCounter:P1P1:2 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME dies, search your library for up to X basic land cards, where X is CARDNAME's power, put them onto the battlefield, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ X +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X SVar:X:TriggeredCard$CardPower DeckHas:Ability$Counters Oracle:Rampant Rejuvenator enters with two +1/+1 counters on it.\nWhen Rampant Rejuvenator dies, search your library for up to X basic land cards, where X is Rampant Rejuvenator's power, put them onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/r/rampart_architect.txt b/forge-gui/res/cardsfolder/r/rampart_architect.txt index 9bd128ad0a5..97bfc7df8f5 100644 --- a/forge-gui/res/cardsfolder/r/rampart_architect.txt +++ b/forge-gui/res/cardsfolder/r/rampart_architect.txt @@ -6,6 +6,6 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigToken | Secondary$ True | TriggerDescription$ Whenever this creature enters or attacks, create a 1/3 white Wall creature token with defender. SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_3_wall_defender | TokenOwner$ You T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouCtrl+withDefender | TriggerZones$ Battlefield | Execute$ TrigChangeZone | OptionalDecider$ You | TriggerDescription$ Whenever a creature you control with defender dies, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. -SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True DeckHas:Ability$Token Oracle:Whenever this creature enters or attacks, create a 1/3 white Wall creature token with defender.\nWhenever a creature you control with defender dies, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/r/ranging_raptors.txt b/forge-gui/res/cardsfolder/r/ranging_raptors.txt index fd4379096ab..eeb4e1bf388 100644 --- a/forge-gui/res/cardsfolder/r/ranging_raptors.txt +++ b/forge-gui/res/cardsfolder/r/ranging_raptors.txt @@ -3,6 +3,6 @@ ManaCost:2 G Types:Creature Dinosaur PT:2/3 T:Mode$ DamageDoneOnce | Execute$ TrigSearchLand | ValidTarget$ Card.Self | TriggerZones$ Battlefield | OptionalDecider$ You | TriggerDescription$ Enrage — Whenever CARDNAME is dealt damage, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigSearchLand:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | ShuffleNonMandatory$ True +SVar:TrigSearchLand:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ShuffleNonMandatory$ True SVar:HasCombatEffect:TRUE Oracle:Enrage — Whenever Ranging Raptors is dealt damage, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/r/realms_befitting_my_majesty.txt b/forge-gui/res/cardsfolder/r/realms_befitting_my_majesty.txt index f719f55fa24..5653729c842 100644 --- a/forge-gui/res/cardsfolder/r/realms_befitting_my_majesty.txt +++ b/forge-gui/res/cardsfolder/r/realms_befitting_my_majesty.txt @@ -2,5 +2,5 @@ Name:Realms Befitting My Majesty ManaCost:no cost Types:Scheme T:Mode$ SetInMotion | ValidCard$ Card.Self | Execute$ GetRealms | TriggerZones$ Command | TriggerDescription$ When you set this scheme in motion, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. -SVar:GetRealms:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 2 +SVar:GetRealms:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 Oracle:When you set this scheme in motion, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/r/reckless_handling.txt b/forge-gui/res/cardsfolder/r/reckless_handling.txt index ed24c2f4aba..77d1b262ce5 100644 --- a/forge-gui/res/cardsfolder/r/reckless_handling.txt +++ b/forge-gui/res/cardsfolder/r/reckless_handling.txt @@ -1,7 +1,7 @@ Name:Reckless Handling ManaCost:1 R Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.Artifact | ChangeNum$ 1 | SubAbility$ DBDiscard | SpellDescription$ Search your library for an artifact card, reveal it, put it into your hand, shuffle, then discard a card at random. If an artifact card was discarded this way, CARDNAME deals 2 damage to each opponent. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Artifact | SubAbility$ DBDiscard | SpellDescription$ Search your library for an artifact card, reveal it, put it into your hand, shuffle, then discard a card at random. If an artifact card was discarded this way, CARDNAME deals 2 damage to each opponent. SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ Random | SubAbility$ DBDealDamage | RememberDiscarded$ True SVar:DBDealDamage:DB$ DamageAll | ConditionDefined$ Remembered | ConditionPresent$ Card.Artifact | ConditionCompare$ EQ1 | StackDescription$ If an artifact card was discarded this way, CARDNAME deals 2 damage to each opponent | ValidPlayers$ Opponent | NumDmg$ 2 | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/r/renegade_map.txt b/forge-gui/res/cardsfolder/r/renegade_map.txt index 5ef3471d431..5c8590cee59 100644 --- a/forge-gui/res/cardsfolder/r/renegade_map.txt +++ b/forge-gui/res/cardsfolder/r/renegade_map.txt @@ -3,5 +3,5 @@ ManaCost:1 Types:Artifact R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. Oracle:Renegade Map enters tapped.\n{T}, Sacrifice Renegade Map: Search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/r/renewal.txt b/forge-gui/res/cardsfolder/r/renewal.txt index 9bfd94d3911..c5a978bba17 100644 --- a/forge-gui/res/cardsfolder/r/renewal.txt +++ b/forge-gui/res/cardsfolder/r/renewal.txt @@ -1,7 +1,7 @@ Name:Renewal ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Cost$ 2 G Sac<1/Land> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield, then shuffle. Draw a card at the beginning of the next turn's upkeep. | SubAbility$ DelTrigSlowtrip +A:SP$ ChangeZone | Cost$ 2 G Sac<1/Land> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield, then shuffle. Draw a card at the beginning of the next turn's upkeep. | SubAbility$ DelTrigSlowtrip SVar:DelTrigSlowtrip:DB$ DelayedTrigger | NextTurn$ True | Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | Execute$ DrawSlowtrip | TriggerDescription$ Draw a card. SVar:DrawSlowtrip:DB$ Draw | NumCards$ 1 | Defined$ You DeckHas:Ability$Sacrifice diff --git a/forge-gui/res/cardsfolder/r/return_from_the_wilds.txt b/forge-gui/res/cardsfolder/r/return_from_the_wilds.txt index fe35b971cc4..00e20d14600 100644 --- a/forge-gui/res/cardsfolder/r/return_from_the_wilds.txt +++ b/forge-gui/res/cardsfolder/r/return_from_the_wilds.txt @@ -2,7 +2,7 @@ Name:Return from the Wilds ManaCost:2 G Types:Sorcery A:SP$ Charm | Choices$ DBSearch,DBHuman,DBFood | CharmNum$ 2 -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. SVar:DBHuman:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_human | TokenOwner$ You | SpellDescription$ Create a 1/1 white Human creature token. SVar:DBFood:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_food_sac | TokenOwner$ You | SpellDescription$ Create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this artifact: You gain 3 life.") DeckHas:Ability$Token & Type$Artifact|Food|Human diff --git a/forge-gui/res/cardsfolder/r/rites_of_spring.txt b/forge-gui/res/cardsfolder/r/rites_of_spring.txt index 90f1604e7dd..947c5867285 100644 --- a/forge-gui/res/cardsfolder/r/rites_of_spring.txt +++ b/forge-gui/res/cardsfolder/r/rites_of_spring.txt @@ -2,7 +2,7 @@ Name:Rites of Spring ManaCost:1 G Types:Sorcery A:SP$ Discard | Defined$ You | AnyNumber$ True | Mode$ TgtChoose | Optional$ True | RememberDiscarded$ True | SubAbility$ DBChangeZone | SpellDescription$ Discard any number of cards. Search your library for up to that many basic land cards, reveal them, put them into your hand, then shuffle. -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ X | SubAbility$ DBCleanup +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Remembered$Amount AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/r/riveteers_overlook.txt b/forge-gui/res/cardsfolder/r/riveteers_overlook.txt index 0e0e398b032..daf01410757 100644 --- a/forge-gui/res/cardsfolder/r/riveteers_overlook.txt +++ b/forge-gui/res/cardsfolder/r/riveteers_overlook.txt @@ -4,7 +4,7 @@ Types:Land T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBSacrifice | TriggerDescription$ When CARDNAME enters, sacrifice it. When you do, search your library for a basic Swamp, Mountain, or Forest card, put it onto the battlefield tapped, then shuffle and you gain 1 life. SVar:DBSacrifice:DB$ Sacrifice | RememberSacrificed$ True | SubAbility$ DBImmediateTrigger SVar:DBImmediateTrigger:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | Execute$ DBChangeZone | SubAbility$ DBCleanup | TriggerDescription$ Search your library for a basic Swamp, Mountain, or Forest card, put it onto the battlefield tapped, then shuffle and you gain 1 life. -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Swamp+Basic,Land.Mountain+Basic,Land.Forest+Basic | ChangeNum$ 1 | SubAbility$ DBGainLife +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Swamp.Basic,Mountain.Basic,Forest.Basic | ChangeTypeDesc$ basic Swamp, Mountain, or Forest | SubAbility$ DBGainLife SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Ability$Sacrifice|LifeGain diff --git a/forge-gui/res/cardsfolder/r/road_ruin.txt b/forge-gui/res/cardsfolder/r/road_ruin.txt index c0b68918718..1d2977c474a 100644 --- a/forge-gui/res/cardsfolder/r/road_ruin.txt +++ b/forge-gui/res/cardsfolder/r/road_ruin.txt @@ -1,7 +1,7 @@ Name:Road ManaCost:2 G Types:Instant -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. AlternateMode:Split Oracle:Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/r/roamers_routine.txt b/forge-gui/res/cardsfolder/r/roamers_routine.txt index e484ca42d42..76631ebee96 100644 --- a/forge-gui/res/cardsfolder/r/roamers_routine.txt +++ b/forge-gui/res/cardsfolder/r/roamers_routine.txt @@ -1,6 +1,6 @@ Name:Roamer's Routine ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. K:Harmonize:4 G Oracle:Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.\nHarmonize {4}{G} (You may cast this card from your graveyard for its harmonize cost. You may tap a creature you control to reduce that cost by {X}, where X is its power. Then exile this spell.) diff --git a/forge-gui/res/cardsfolder/r/rocky_tar_pit.txt b/forge-gui/res/cardsfolder/r/rocky_tar_pit.txt index c30766dd2d9..0d8eb9e7e9d 100644 --- a/forge-gui/res/cardsfolder/r/rocky_tar_pit.txt +++ b/forge-gui/res/cardsfolder/r/rocky_tar_pit.txt @@ -3,5 +3,5 @@ ManaCost:no cost Types:Land R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Swamp,Mountain | ChangeNum$ 1 | SpellDescription$ Search your library for a Swamp or Mountain card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Swamp,Mountain | SpellDescription$ Search your library for a Swamp or Mountain card, put it onto the battlefield, then shuffle. Oracle:Rocky Tar Pit enters tapped.\n{T}, Sacrifice Rocky Tar Pit: Search your library for a Swamp or Mountain card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/r/roiling_regrowth.txt b/forge-gui/res/cardsfolder/r/roiling_regrowth.txt index 15c8b8be7a0..428bbf00f43 100644 --- a/forge-gui/res/cardsfolder/r/roiling_regrowth.txt +++ b/forge-gui/res/cardsfolder/r/roiling_regrowth.txt @@ -2,6 +2,6 @@ Name:Roiling Regrowth ManaCost:2 G Types:Instant A:SP$ Sacrifice | Defined$ You | SacValid$ Land | SubAbility$ DBChangeZone | StackDescription$ SpellDescription | SpellDescription$ Sacrifice a land. Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 2 | Tapped$ True | StackDescription$ None +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | Tapped$ True | StackDescription$ None SVar:AIPreference:SacCost$Land.Basic+tapped Oracle:Sacrifice a land. Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/r/ruin_in_their_wake.txt b/forge-gui/res/cardsfolder/r/ruin_in_their_wake.txt index 9037fb7a22d..60701cbf1d8 100644 --- a/forge-gui/res/cardsfolder/r/ruin_in_their_wake.txt +++ b/forge-gui/res/cardsfolder/r/ruin_in_their_wake.txt @@ -2,7 +2,7 @@ Name:Ruin in Their Wake ManaCost:1 G Types:Sorcery K:Devoid -A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeNum$ 1 | Reveal$ True | Shuffle$ False | RememberChanged$ True | SubAbility$ DBChangeZone | SpellDescription$ Search your library for a basic land card and reveal it. You may put that card onto the battlefield tapped if you control a land named Wastes. Otherwise, put that card into your hand. Then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Reveal$ True | Shuffle$ False | RememberChanged$ True | SubAbility$ DBChangeZone | SpellDescription$ Search your library for a basic land card and reveal it. You may put that card onto the battlefield tapped if you control a land named Wastes. Otherwise, put that card into your hand. Then shuffle. SVar:DBChangeZone:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Battlefield | Tapped$ True | ConditionPresent$ Land.namedWastes+YouCtrl | ForgetChanged$ True | Optional$ True | SubAbility$ DBChangeZone2 SVar:DBChangeZone2:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Hand | SubAbility$ DBShuffle SVar:DBShuffle:DB$ Shuffle | Defined$ You | SubAbility$ DBCleanup diff --git a/forge-gui/res/cardsfolder/rebalanced/a-ominous_parcel.txt b/forge-gui/res/cardsfolder/rebalanced/a-ominous_parcel.txt index 828df989baf..e85023326dc 100644 --- a/forge-gui/res/cardsfolder/rebalanced/a-ominous_parcel.txt +++ b/forge-gui/res/cardsfolder/rebalanced/a-ominous_parcel.txt @@ -1,6 +1,6 @@ Name:A-Ominous Parcel ManaCost:1 Types:Artifact -A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. A:AB$ DealDamage | Cost$ 5 T Sac<1/CARDNAME> | ValidTgts$ Creature | NumDmg$ 4 | SpellDescription$ It deals 4 damage to target creature. Oracle:{1}, {T}, Sacrifice Ominous Parcel: Search your library for a basic land card, reveal it, put it into your hand, then shuffle.\n{5}, {T}, Sacrifice Ominous Parcel: It deals 4 damage to target creature. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-scout_the_wilderness.txt b/forge-gui/res/cardsfolder/rebalanced/a-scout_the_wilderness.txt index f7d05ab4cdd..ba0e5db4b5d 100644 --- a/forge-gui/res/cardsfolder/rebalanced/a-scout_the_wilderness.txt +++ b/forge-gui/res/cardsfolder/rebalanced/a-scout_the_wilderness.txt @@ -2,7 +2,7 @@ Name:A-Scout the Wilderness ManaCost:2 G Types:Sorcery K:Kicker:W -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SubAbility$ DBToken | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. If this spell was kicked, create two 1/1 white Soldier creature tokens. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SubAbility$ DBToken | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. If this spell was kicked, create two 1/1 white Soldier creature tokens. SVar:DBToken:DB$ Token | TokenAmount$ 2 | TokenScript$ w_1_1_soldier | Condition$ Kicked DeckHints:Color$White DeckHas:Ability$Token & Type$Soldier diff --git a/forge-gui/res/cardsfolder/s/safewright_quest.txt b/forge-gui/res/cardsfolder/s/safewright_quest.txt index 70d86dabc66..b9a9c289d78 100644 --- a/forge-gui/res/cardsfolder/s/safewright_quest.txt +++ b/forge-gui/res/cardsfolder/s/safewright_quest.txt @@ -1,5 +1,5 @@ Name:Safewright Quest ManaCost:GW Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Forest,Plains | ChangeNum$ 1 | SpellDescription$ Search your library for a Forest or Plains card, reveal it, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Forest,Plains | SpellDescription$ Search your library for a Forest or Plains card, reveal it, put it into your hand, then shuffle. Oracle:Search your library for a Forest or Plains card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/sagu_wildling_roost_seek.txt b/forge-gui/res/cardsfolder/s/sagu_wildling_roost_seek.txt index fb30d49e8b7..810bcaf8db6 100644 --- a/forge-gui/res/cardsfolder/s/sagu_wildling_roost_seek.txt +++ b/forge-gui/res/cardsfolder/s/sagu_wildling_roost_seek.txt @@ -13,5 +13,5 @@ ALTERNATE Name:Roost Seek ManaCost:G Types:Sorcery Omen -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. (Also shuffle this card.) +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. (Also shuffle this card.) Oracle:Search your library for a basic land card, reveal it, put it into your hand, then shuffle. (Also shuffle this card.) diff --git a/forge-gui/res/cardsfolder/s/sakura_tribe_elder.txt b/forge-gui/res/cardsfolder/s/sakura_tribe_elder.txt index ea4f8a93127..d91ab8389ef 100644 --- a/forge-gui/res/cardsfolder/s/sakura_tribe_elder.txt +++ b/forge-gui/res/cardsfolder/s/sakura_tribe_elder.txt @@ -2,6 +2,6 @@ Name:Sakura-Tribe Elder ManaCost:1 G Types:Creature Snake Shaman PT:1/1 -A:AB$ ChangeZone | Cost$ Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. SVar:SacMeAfterBlock:TRUE Oracle:Sacrifice Sakura-Tribe Elder: Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/samut_the_tested.txt b/forge-gui/res/cardsfolder/s/samut_the_tested.txt index aa66779e6aa..c4cef24be38 100644 --- a/forge-gui/res/cardsfolder/s/samut_the_tested.txt +++ b/forge-gui/res/cardsfolder/s/samut_the_tested.txt @@ -2,7 +2,7 @@ Name:Samut, the Tested ManaCost:2 R G Types:Legendary Planeswalker Samut Loyalty:4 -A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | Planeswalker$ True | KW$ Double Strike | SpellDescription$ Up to one target creature gains double strike until end of turn. -A:AB$ DealDamage | Cost$ SubCounter<2/LOYALTY> | ValidTgts$ Any | TgtPrompt$ Select any target to distribute damage to | NumDmg$ 2 | TargetMin$ 1 | TargetMax$ 2 | DividedAsYouChoose$ 2 | Planeswalker$ True | SpellDescription$ CARDNAME deals 2 damage divided as you choose among one or two targets. -A:AB$ ChangeZone | Cost$ SubCounter<7/LOYALTY> | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature,Planeswalker | ChangeNum$ 2 | Planeswalker$ True | Ultimate$ True | SpellDescription$ Search your library for up to two creature and/or planeswalker cards, put them onto the battlefield, then shuffle. +A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | KW$ Double Strike | SpellDescription$ Up to one target creature gains double strike until end of turn. +A:AB$ DealDamage | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | ValidTgts$ Any | TgtPrompt$ Select any target to distribute damage to | NumDmg$ 2 | TargetMin$ 1 | TargetMax$ 2 | DividedAsYouChoose$ 2 | SpellDescription$ CARDNAME deals 2 damage divided as you choose among one or two targets. +A:AB$ ChangeZone | Cost$ SubCounter<7/LOYALTY> | Planeswalker$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature,Planeswalker | ChangeNum$ 2 | Ultimate$ True | SpellDescription$ Search your library for up to two creature and/or planeswalker cards, put them onto the battlefield, then shuffle. Oracle:[+1]: Up to one target creature gains double strike until end of turn.\n[-2]: Samut, the Tested deals 2 damage divided as you choose among one or two targets.\n[-7]: Search your library for up to two creature and/or planeswalker cards, put them onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/sandworm.txt b/forge-gui/res/cardsfolder/s/sandworm.txt index dcb4837ae2c..cd6303d589c 100644 --- a/forge-gui/res/cardsfolder/s/sandworm.txt +++ b/forge-gui/res/cardsfolder/s/sandworm.txt @@ -5,5 +5,5 @@ PT:5/4 K:Haste T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDestroy | TriggerDescription$ When this creature enters, destroy target land. Its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Land | SubAbility$ DBChange | AITgts$ Land.nonBasic | SpellDescription$ Destroy target land. Its controller may search their library for a basic land card, put it onto the battlefield, then shuffle. -SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True +SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True Oracle:Haste\nWhen this creature enters, destroy target land. Its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/sarkhans_triumph.txt b/forge-gui/res/cardsfolder/s/sarkhans_triumph.txt index fa9abec458c..2d152a6fd25 100644 --- a/forge-gui/res/cardsfolder/s/sarkhans_triumph.txt +++ b/forge-gui/res/cardsfolder/s/sarkhans_triumph.txt @@ -1,7 +1,7 @@ Name:Sarkhan's Triumph ManaCost:2 R Types:Instant -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Creature.Dragon | ChangeNum$ 1 | SpellDescription$ Search your library for a Dragon creature card, reveal it, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Creature.Dragon | ChangeTypeDesc$ Dragon creature | SpellDescription$ Search your library for a Dragon creature card, reveal it, put it into your hand, then shuffle. AI:RemoveDeck:Random DeckNeeds:Type$Dragon Oracle:Search your library for a Dragon creature card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/scalding_tarn.txt b/forge-gui/res/cardsfolder/s/scalding_tarn.txt index a61dd110d4a..abbba1590b9 100644 --- a/forge-gui/res/cardsfolder/s/scalding_tarn.txt +++ b/forge-gui/res/cardsfolder/s/scalding_tarn.txt @@ -1,5 +1,5 @@ Name:Scalding Tarn ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Island,Mountain | ChangeNum$ 1 | SpellDescription$ Search your library for a Island or Mountain card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Island,Mountain | SpellDescription$ Search your library for a Island or Mountain card, put it onto the battlefield, then shuffle. Oracle:{T}, Pay 1 life, Sacrifice Scalding Tarn: Search your library for an Island or Mountain card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/scholarship_sponsor.txt b/forge-gui/res/cardsfolder/s/scholarship_sponsor.txt index bf9ad1d46a5..5869b37f1ac 100644 --- a/forge-gui/res/cardsfolder/s/scholarship_sponsor.txt +++ b/forge-gui/res/cardsfolder/s/scholarship_sponsor.txt @@ -4,7 +4,7 @@ Types:Creature Human Advisor PT:3/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigRepeatEach | TriggerDescription$ When CARDNAME enters, each player who controls fewer lands than the player who controls the most lands searches their library for a number of basic land cards less than or equal to the difference, puts those cards onto the battlefield tapped, then shuffles. SVar:TrigRepeatEach:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBChangeZone -SVar:DBChangeZone:DB$ ChangeZone | ConditionCheckSVar$ X | ConditionSVarCompare$ LTY | DefinedPlayer$ Remembered | Chooser$ Remembered | ChangeType$ Land.Basic | ChangeNum$ Z | Origin$ Library | Destination$ Battlefield | Tapped$ True +SVar:DBChangeZone:DB$ ChangeZone | ConditionCheckSVar$ X | ConditionSVarCompare$ LTY | DefinedPlayer$ Remembered | Chooser$ Remembered | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ Z | Origin$ Library | Destination$ Battlefield | Tapped$ True SVar:X:Count$Valid Land.RememberedPlayerCtrl SVar:Y:PlayerCountPlayers$HighestValid Land.YouCtrl SVar:Z:SVar$Y/Minus.X diff --git a/forge-gui/res/cardsfolder/s/scout_the_wilderness.txt b/forge-gui/res/cardsfolder/s/scout_the_wilderness.txt index 6df041b89f6..5c126ccddcb 100644 --- a/forge-gui/res/cardsfolder/s/scout_the_wilderness.txt +++ b/forge-gui/res/cardsfolder/s/scout_the_wilderness.txt @@ -2,7 +2,7 @@ Name:Scout the Wilderness ManaCost:2 G Types:Sorcery K:Kicker:1 W -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SubAbility$ DBToken | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. If this spell was kicked, create two 1/1 white Soldier creature tokens. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SubAbility$ DBToken | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. If this spell was kicked, create two 1/1 white Soldier creature tokens. SVar:DBToken:DB$ Token | TokenAmount$ 2 | TokenScript$ w_1_1_soldier | Condition$ Kicked DeckHints:Color$White DeckHas:Ability$Token & Type$Soldier diff --git a/forge-gui/res/cardsfolder/s/scouting_trek.txt b/forge-gui/res/cardsfolder/s/scouting_trek.txt index 9b5a435812f..138c5e18dbf 100644 --- a/forge-gui/res/cardsfolder/s/scouting_trek.txt +++ b/forge-gui/res/cardsfolder/s/scouting_trek.txt @@ -1,7 +1,7 @@ Name:Scouting Trek ManaCost:1 G Types:Sorcery -A:SP$ ChangeZone | ChangeNum$ X | ChangeType$ Land.Basic | Origin$ Library | Destination$ Library | Reorder$ True | LibraryPosition$ 0 | SpellDescription$ Search your library for any number of basic land cards, reveal those cards, then shuffle and put them on top. +A:SP$ ChangeZone | ChangeNum$ X | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Origin$ Library | Destination$ Library | Reorder$ True | LibraryPosition$ 0 | SpellDescription$ Search your library for any number of basic land cards, reveal those cards, then shuffle and put them on top. SVar:X:Count$ValidLibrary Land.Basic+YouOwn AI:RemoveDeck:All Oracle:Search your library for any number of basic land cards, reveal those cards, then shuffle and put them on top. diff --git a/forge-gui/res/cardsfolder/s/scrapyard_recombiner.txt b/forge-gui/res/cardsfolder/s/scrapyard_recombiner.txt index 6889d8d2ecd..893699ffd64 100644 --- a/forge-gui/res/cardsfolder/s/scrapyard_recombiner.txt +++ b/forge-gui/res/cardsfolder/s/scrapyard_recombiner.txt @@ -3,7 +3,7 @@ ManaCost:3 Types:Artifact Creature Construct PT:0/0 K:Modular:2 -A:AB$ ChangeZone | Cost$ T Sac<1/Artifact> | Origin$ Library | Destination$ Hand | ChangeType$ Card.Construct | ChangeNum$ 1 | SpellDescription$ Search your library for a Construct card, reveal it, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/Artifact> | Origin$ Library | Destination$ Hand | ChangeType$ Construct | SpellDescription$ Search your library for a Construct card, reveal it, put it into your hand, then shuffle. SVar:AIPreference:SacCost$Artifact.token,Artifact.cmcEQ1 AI:RemoveDeck:Random DeckHas:Ability$Counters diff --git a/forge-gui/res/cardsfolder/s/seahunter.txt b/forge-gui/res/cardsfolder/s/seahunter.txt index e71ad422ed8..7690383325a 100644 --- a/forge-gui/res/cardsfolder/s/seahunter.txt +++ b/forge-gui/res/cardsfolder/s/seahunter.txt @@ -2,7 +2,7 @@ Name:Seahunter ManaCost:2 U U Types:Creature Human Mercenary PT:2/2 -A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Battlefield | ChangeType$ Permanent.Merfolk | ChangeNum$ 1 | SpellDescription$ Search your library for a Merfolk permanent card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Battlefield | ChangeType$ Permanent.Merfolk | ChangeTypeDesc$ Merfolk permanent | SpellDescription$ Search your library for a Merfolk permanent card, put it onto the battlefield, then shuffle. AI:RemoveDeck:Random SVar:NonCombatPriority:3 Oracle:{3}, {T}: Search your library for a Merfolk permanent card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/search_for_tomorrow.txt b/forge-gui/res/cardsfolder/s/search_for_tomorrow.txt index 4e919bb158a..ffdae0d4465 100644 --- a/forge-gui/res/cardsfolder/s/search_for_tomorrow.txt +++ b/forge-gui/res/cardsfolder/s/search_for_tomorrow.txt @@ -2,5 +2,5 @@ Name:Search for Tomorrow ManaCost:2 G Types:Sorcery K:Suspend:2:G -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield, then shuffle. Oracle:Search your library for a basic land card, put it onto the battlefield, then shuffle.\nSuspend 2—{G} (Rather than cast this card from your hand, you may pay {G} and exile it with two time counters on it. At the beginning of your upkeep, remove a time counter. When the last is removed, you may cast it without paying its mana cost.) diff --git a/forge-gui/res/cardsfolder/s/seek_the_horizon.txt b/forge-gui/res/cardsfolder/s/seek_the_horizon.txt index 861e1826ab5..b6136d48f27 100644 --- a/forge-gui/res/cardsfolder/s/seek_the_horizon.txt +++ b/forge-gui/res/cardsfolder/s/seek_the_horizon.txt @@ -1,5 +1,5 @@ Name:Seek the Horizon ManaCost:3 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 3 | SpellDescription$ Search your library for up to three basic land cards, reveal them, put them into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 3 | SpellDescription$ Search your library for up to three basic land cards, reveal them, put them into your hand, then shuffle. Oracle:Search your library for up to three basic land cards, reveal them, put them into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/seething_landscape.txt b/forge-gui/res/cardsfolder/s/seething_landscape.txt index ba1d733d929..de8939f0a2f 100644 --- a/forge-gui/res/cardsfolder/s/seething_landscape.txt +++ b/forge-gui/res/cardsfolder/s/seething_landscape.txt @@ -2,6 +2,6 @@ Name:Seething Landscape ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Island+Basic,Land.Swamp+Basic,Land.Mountain+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Island.Basic,Swamp.Basic,Mountain.Basic | ChangeTypeDesc$ basic Island, Swamp, or Mountain | SpellDescription$ Search your library for a basic Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle. K:Cycling:U B R Oracle:{T}: Add {C}.\n{T}, Sacrifice Seething Landscape: Search your library for a basic Island, Swamp, or Mountain card, put it onto the battlefield tapped, then shuffle.\nCycling {U}{B}{R} ({U}{B}{R}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/s/settle_the_wreckage.txt b/forge-gui/res/cardsfolder/s/settle_the_wreckage.txt index b7c1ccbe580..db0a8ffd91b 100644 --- a/forge-gui/res/cardsfolder/s/settle_the_wreckage.txt +++ b/forge-gui/res/cardsfolder/s/settle_the_wreckage.txt @@ -2,7 +2,7 @@ Name:Settle the Wreckage ManaCost:2 W W Types:Instant A:SP$ ChangeZoneAll | ValidTgts$ Player | ChangeType$ Creature.attacking | TgtPrompt$ Select target player | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBGetLands | SpellDescription$ Exile all attacking creatures target player controls. That player may search their library for that many basic land cards, put those cards onto the battlefield tapped, then shuffle. -SVar:DBGetLands:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ X | DefinedPlayer$ TargetedPlayer | ShuffleNonMandatory$ True | SubAbility$ DBCleanup +SVar:DBGetLands:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | DefinedPlayer$ TargetedPlayer | ShuffleNonMandatory$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$RememberedSize Oracle:Exile all attacking creatures target player controls. That player may search their library for that many basic land cards, put those cards onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/shadow_rite_priest.txt b/forge-gui/res/cardsfolder/s/shadow_rite_priest.txt index 9f10a079aec..cecc223384d 100644 --- a/forge-gui/res/cardsfolder/s/shadow_rite_priest.txt +++ b/forge-gui/res/cardsfolder/s/shadow_rite_priest.txt @@ -3,7 +3,7 @@ ManaCost:1 B Types:Creature Human Cleric PT:2/2 S:Mode$ Continuous | Affected$ Cleric.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other Clerics you control get +1/+1. -A:AB$ ChangeZone | Cost$ T 3 B B Sac<1/Cleric.Other> | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature.Black | SpellDescription$ Search your library for a black creature card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T 3 B B Sac<1/Cleric.Other> | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature.Black | ChangeTypeDesc$ black creature | SpellDescription$ Search your library for a black creature card, put it onto the battlefield, then shuffle. DeckHas:Ability$Sacrifice DeckNeeds:Type$Cleric Oracle:Other Clerics you control get +1/+1.\n{3}{B}{B}, {T}, Sacrifice another Cleric: Search your library for a black creature card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/shattered_landscape.txt b/forge-gui/res/cardsfolder/s/shattered_landscape.txt index d699fa72878..7f49d7df92a 100644 --- a/forge-gui/res/cardsfolder/s/shattered_landscape.txt +++ b/forge-gui/res/cardsfolder/s/shattered_landscape.txt @@ -2,6 +2,6 @@ Name:Shattered Landscape ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Mountain+Basic,Land.Plains+Basic,Land.Swamp+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Mountain, Plains, or Swamp card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Mountain.Basic,Plains.Basic,Swamp.Basic | ChangeTypeDesc$ basic Mountain, Plains, or Swamp | SpellDescription$ Search your library for a basic Mountain, Plains, or Swamp card, put it onto the battlefield tapped, then shuffle. K:Cycling:R W B Oracle:{T}: Add {C}.\n{T}, Sacrifice Shattered Landscape: Search your library for a basic Mountain, Plains, or Swamp card, put it onto the battlefield tapped, then shuffle.\nCycling {R}{W}{B} ({R}{W}{B}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/s/sheltering_landscape.txt b/forge-gui/res/cardsfolder/s/sheltering_landscape.txt index 72c47ebc740..15bfa78f4e0 100644 --- a/forge-gui/res/cardsfolder/s/sheltering_landscape.txt +++ b/forge-gui/res/cardsfolder/s/sheltering_landscape.txt @@ -2,6 +2,6 @@ Name:Sheltering Landscape ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Mountain+Basic,Land.Forest+Basic,Land.Plains+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Mountain, Forest, or Plains card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Mountain.Basic,Forest.Basic,Plains.Basic | ChangeTypeDesc$ basic Mountain, Forest, or Plains | SpellDescription$ Search your library for a basic Mountain, Forest, or Plains card, put it onto the battlefield tapped, then shuffle. K:Cycling:R G W Oracle:{T}: Add {C}.\n{T}, Sacrifice Sheltering Landscape: Search your library for a basic Mountain, Forest, or Plains card, put it onto the battlefield tapped, then shuffle.\nCycling {R}{G}{W} ({R}{G}{W}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/s/shire_terrace.txt b/forge-gui/res/cardsfolder/s/shire_terrace.txt index 8acd308d15a..0b2f3d81e57 100644 --- a/forge-gui/res/cardsfolder/s/shire_terrace.txt +++ b/forge-gui/res/cardsfolder/s/shire_terrace.txt @@ -2,6 +2,6 @@ Name:Shire Terrace ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. DeckHas:Ability$Sacrifice Oracle:{T}: Add {C}.\n{1}, {T}, Sacrifice Shire Terrace: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/silkwing_scout.txt b/forge-gui/res/cardsfolder/s/silkwing_scout.txt index 0a33db69925..e43a52167ba 100644 --- a/forge-gui/res/cardsfolder/s/silkwing_scout.txt +++ b/forge-gui/res/cardsfolder/s/silkwing_scout.txt @@ -3,5 +3,5 @@ ManaCost:2 U Types:Creature Faerie Scout PT:2/1 K:Flying -A:AB$ ChangeZone | Cost$ G Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ G Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Oracle:Flying\n{G}, Sacrifice Silkwing Scout: Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/silverglade_pathfinder.txt b/forge-gui/res/cardsfolder/s/silverglade_pathfinder.txt index 2f8b41bea4b..7dda6656dba 100644 --- a/forge-gui/res/cardsfolder/s/silverglade_pathfinder.txt +++ b/forge-gui/res/cardsfolder/s/silverglade_pathfinder.txt @@ -2,6 +2,6 @@ Name:Silverglade Pathfinder ManaCost:1 G Types:Creature Dryad Spellshaper PT:1/1 -A:AB$ ChangeZone | Cost$ 1 G T Discard<1/Card> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 1 G T Discard<1/Card> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. AI:RemoveDeck:All Oracle:{1}{G}, {T}, Discard a card: Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/skittering_surveyor.txt b/forge-gui/res/cardsfolder/s/skittering_surveyor.txt index d0477f7a883..5821745a2a8 100644 --- a/forge-gui/res/cardsfolder/s/skittering_surveyor.txt +++ b/forge-gui/res/cardsfolder/s/skittering_surveyor.txt @@ -3,5 +3,5 @@ ManaCost:3 Types:Artifact Creature Construct PT:1/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:When Skittering Surveyor enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/skyshroud_poacher.txt b/forge-gui/res/cardsfolder/s/skyshroud_poacher.txt index 61645cbe9c7..21ade090a31 100644 --- a/forge-gui/res/cardsfolder/s/skyshroud_poacher.txt +++ b/forge-gui/res/cardsfolder/s/skyshroud_poacher.txt @@ -2,7 +2,7 @@ Name:Skyshroud Poacher ManaCost:2 G G Types:Creature Human Rebel PT:2/2 -A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Battlefield | ChangeType$ Permanent.Elf | ChangeNum$ 1 | SpellDescription$ Search your library for an Elf permanent card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Battlefield | ChangeType$ Permanent.Elf | ChangeTypeDesc$ Elf permanent | SpellDescription$ Search your library for an Elf permanent card, put it onto the battlefield, then shuffle. AI:RemoveDeck:Random SVar:NonCombatPriority:3 Oracle:{3}, {T}: Search your library for an Elf permanent card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/solemn_simulacrum.txt b/forge-gui/res/cardsfolder/s/solemn_simulacrum.txt index 53fa9ded52d..7546753c02d 100644 --- a/forge-gui/res/cardsfolder/s/solemn_simulacrum.txt +++ b/forge-gui/res/cardsfolder/s/solemn_simulacrum.txt @@ -3,7 +3,7 @@ ManaCost:4 Types:Artifact Creature Golem PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigDraw | OptionalDecider$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, you may draw a card. SVar:TrigDraw:DB$ Draw | Defined$ TriggeredCardController | NumCards$ 1 SVar:SacMe:1 diff --git a/forge-gui/res/cardsfolder/s/solve_the_equation.txt b/forge-gui/res/cardsfolder/s/solve_the_equation.txt index 23f2c6d3ce2..b693c30609b 100644 --- a/forge-gui/res/cardsfolder/s/solve_the_equation.txt +++ b/forge-gui/res/cardsfolder/s/solve_the_equation.txt @@ -1,5 +1,5 @@ Name:Solve the Equation ManaCost:2 U Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Instant,Sorcery | ChangeNum$ 1 | SpellDescription$ Search your library for an instant or sorcery card, reveal it, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Instant,Sorcery | SpellDescription$ Search your library for an instant or sorcery card, reveal it, put it into your hand, then shuffle. Oracle:Search your library for an instant or sorcery card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/spider_bot.txt b/forge-gui/res/cardsfolder/s/spider_bot.txt index 610fcc1f4c9..16b7c1fcb4d 100644 --- a/forge-gui/res/cardsfolder/s/spider_bot.txt +++ b/forge-gui/res/cardsfolder/s/spider_bot.txt @@ -4,5 +4,5 @@ Types:Artifact Creature Spider Robot Scout PT:2/1 K:Reach T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When this creature enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:Reach\nWhen this creature enters, you may search your library for a basic land card, reveal it, then shuffle and put that card on top. diff --git a/forge-gui/res/cardsfolder/s/spider_man_brooklyn_visionary.txt b/forge-gui/res/cardsfolder/s/spider_man_brooklyn_visionary.txt index e8e3c9ed39f..f0931bbc73e 100644 --- a/forge-gui/res/cardsfolder/s/spider_man_brooklyn_visionary.txt +++ b/forge-gui/res/cardsfolder/s/spider_man_brooklyn_visionary.txt @@ -4,6 +4,6 @@ Types:Legendary Creature Spider Human Hero PT:4/3 K:Web-slinging:2 G T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When NICKNAME enters, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land Oracle:Web-slinging {2}{G} (You may cast this spell for {2}{G} if you also return a tapped creature you control to its owner's hand.)\nWhen Spider-Man enters, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/spineseeker_centipede.txt b/forge-gui/res/cardsfolder/s/spineseeker_centipede.txt index b68d24a3faa..3e8a0972211 100644 --- a/forge-gui/res/cardsfolder/s/spineseeker_centipede.txt +++ b/forge-gui/res/cardsfolder/s/spineseeker_centipede.txt @@ -3,7 +3,7 @@ ManaCost:2 G Types:Creature Insect PT:2/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters, search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 1 | AddToughness$ 2 | AddKeyword$ Vigilance | Condition$ Delirium | Description$ Delirium — CARDNAME gets +1/+2 and has vigilance as long as there are four or more card types among cards in your graveyard. DeckHints:Ability$Graveyard|Discard DeckHas:Ability$Delirium diff --git a/forge-gui/res/cardsfolder/s/sporocyst.txt b/forge-gui/res/cardsfolder/s/sporocyst.txt index c6daf2606fa..2b6a57eb3e0 100644 --- a/forge-gui/res/cardsfolder/s/sporocyst.txt +++ b/forge-gui/res/cardsfolder/s/sporocyst.txt @@ -5,7 +5,7 @@ PT:0/0 K:Ravenous K:Defender T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ Spore Chimney — When CARDNAME enters, search your library for up to X basic land cards, put them onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ X | Tapped$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | Tapped$ True SVar:X:Count$xPaid DeckHas:Ability$Counters Oracle:Ravenous (This creature enters with X +1/+1 counters on it. If X is 5 or more, draw a card when it enters.)\nDefender\nSpore Chimney — When Sporocyst enters, search your library for up to X basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/spring_mind.txt b/forge-gui/res/cardsfolder/s/spring_mind.txt index 5c2880400da..d8cc44f1a25 100644 --- a/forge-gui/res/cardsfolder/s/spring_mind.txt +++ b/forge-gui/res/cardsfolder/s/spring_mind.txt @@ -1,7 +1,7 @@ Name:Spring ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. AlternateMode:Split Oracle:Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/springbloom_druid.txt b/forge-gui/res/cardsfolder/s/springbloom_druid.txt index 23b30810f80..e24814b102e 100644 --- a/forge-gui/res/cardsfolder/s/springbloom_druid.txt +++ b/forge-gui/res/cardsfolder/s/springbloom_druid.txt @@ -3,5 +3,5 @@ ManaCost:2 G Types:Creature Elf Druid PT:1/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigRamp | TriggerDescription$ When CARDNAME enters, you may sacrifice a land. If you do, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. -SVar:TrigRamp:AB$ ChangeZone | Cost$ Sac<1/Land> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 2 | ShuffleNonMandatory$ True +SVar:TrigRamp:AB$ ChangeZone | Cost$ Sac<1/Land> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | ShuffleNonMandatory$ True Oracle:When Springbloom Druid enters, you may sacrifice a land. If you do, search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/sprouting_vines.txt b/forge-gui/res/cardsfolder/s/sprouting_vines.txt index 63cafa6128c..7c014082652 100644 --- a/forge-gui/res/cardsfolder/s/sprouting_vines.txt +++ b/forge-gui/res/cardsfolder/s/sprouting_vines.txt @@ -1,6 +1,6 @@ Name:Sprouting Vines ManaCost:2 G Types:Instant -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal that card, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal that card, put it into your hand, then shuffle. K:Storm Oracle:Search your library for a basic land card, reveal that card, put it into your hand, then shuffle.\nStorm (When you cast this spell, copy it for each spell cast before it this turn.) diff --git a/forge-gui/res/cardsfolder/s/subway_train.txt b/forge-gui/res/cardsfolder/s/subway_train.txt index 93e2eacaec0..18947379674 100644 --- a/forge-gui/res/cardsfolder/s/subway_train.txt +++ b/forge-gui/res/cardsfolder/s/subway_train.txt @@ -3,6 +3,6 @@ ManaCost:2 Types:Artifact Vehicle PT:3/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When this Vehicle enters, you may pay {G}. If you do, search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChangeZone:AB$ ChangeZone | Cost$ G | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChangeZone:AB$ ChangeZone | Cost$ G | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land K:Crew:2 Oracle:When this Vehicle enters, you may pay {G}. If you do, search your library for a basic land card, reveal it, put it into your hand, then shuffle.\nCrew 2 (Tap any number of creatures you control with total power 2 or more: This Vehicle becomes an artifact creature until end of turn.) diff --git a/forge-gui/res/cardsfolder/s/sultai_monument.txt b/forge-gui/res/cardsfolder/s/sultai_monument.txt index 23515beb47f..1b6035a57e5 100644 --- a/forge-gui/res/cardsfolder/s/sultai_monument.txt +++ b/forge-gui/res/cardsfolder/s/sultai_monument.txt @@ -2,7 +2,7 @@ Name:Sultai Monument ManaCost:2 Types:Artifact T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When this artifact enters, search your library for a basic Swamp, Forest, or Island card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Swamp+Basic,Land.Forest+Basic,Land.Island+Basic +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Swamp.Basic,Forest.Basic,Island.Basic | ChangeTypeDesc$ basic Swamp, Forest, or Island A:AB$ Token | Cost$ 2 B G U T Sac<1/CARDNAME> | TokenAmount$ 2 | TokenScript$ b_2_2_zombie_druid | TokenOwner$ You | SorcerySpeed$ True | SpellDescription$ Create two 2/2 black Zombie Druid creature tokens. Activate only as a sorcery. DeckHas:Ability$Token Oracle:When this artifact enters, search your library for a basic Swamp, Forest, or Island card, reveal it, put it into your hand, then shuffle.\n{2}{B}{G}{U}, {T}, Sacrifice this artifact: Create two 2/2 black Zombie Druid creature tokens. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/s/summon_fenrir.txt b/forge-gui/res/cardsfolder/s/summon_fenrir.txt index 68ece48b7c4..af42a1bb574 100644 --- a/forge-gui/res/cardsfolder/s/summon_fenrir.txt +++ b/forge-gui/res/cardsfolder/s/summon_fenrir.txt @@ -3,7 +3,7 @@ ManaCost:2 G Types:Enchantment Creature Saga Wolf PT:3/2 K:Chapter:3:DBFetch,DBCast,DBDraw -SVar:DBFetch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Crescent Fang — Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +SVar:DBFetch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Crescent Fang — Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. SVar:DBCast:DB$ Effect | Triggers$ SpellCast | SpellDescription$ Heavenward Howl — When you next cast a creature spell this turn, that creature enters with an additional +1/+1 counter on it. SVar:SpellCast:Mode$ SpellCast | ValidCard$ Creature | ValidActivatingPlayer$ You | OneOff$ True | Execute$ TrigEffect | TriggerDescription$ When you next cast a creature spell this turn, that creature enters with an additional +1/+1 counter on it. SVar:TrigEffect:DB$ Effect | RememberObjects$ TriggeredCard | ReplacementEffects$ ETBCreat | ExileOnMoved$ Stack diff --git a/forge-gui/res/cardsfolder/s/sundering_eruption_volcanic_fissure.txt b/forge-gui/res/cardsfolder/s/sundering_eruption_volcanic_fissure.txt index 340283ac564..75ff7b5a9f8 100644 --- a/forge-gui/res/cardsfolder/s/sundering_eruption_volcanic_fissure.txt +++ b/forge-gui/res/cardsfolder/s/sundering_eruption_volcanic_fissure.txt @@ -2,7 +2,7 @@ Name:Sundering Eruption ManaCost:2 R Types:Sorcery A:SP$ Destroy | ValidTgts$ Land | TgtPrompt$ Select target land | SubAbility$ DBChange | SpellDescription$ Destroy target land. Its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. Creatures without flying can't block this turn. -SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True | SubAbility$ CantBlockEffect +SVar:DBChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True | SubAbility$ CantBlockEffect SVar:CantBlockEffect:DB$ Effect | StaticAbilities$ KWPump | AILogic$ Evasion | SpellDescription$ Creatures without flying can't block this turn. SVar:KWPump:Mode$ Continuous | AffectedZone$ Battlefield | Affected$ Creature.withoutFlying | AddHiddenKeyword$ CARDNAME can't block. | Description$ Creatures without flying can't block this turn. SVar:PlayMain1:TRUE diff --git a/forge-gui/res/cardsfolder/s/supply_demand.txt b/forge-gui/res/cardsfolder/s/supply_demand.txt index 292da85b4a7..02bec1a233c 100644 --- a/forge-gui/res/cardsfolder/s/supply_demand.txt +++ b/forge-gui/res/cardsfolder/s/supply_demand.txt @@ -12,5 +12,5 @@ ALTERNATE Name:Demand ManaCost:1 W U Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.MultiColor | ChangeNum$ 1 | SpellDescription$ Search your library for a multicolored card, reveal it, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.MultiColor | ChangeTypeDesc$ multicolored | SpellDescription$ Search your library for a multicolored card, reveal it, put it into your hand, then shuffle. Oracle:Search your library for a multicolored card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/s/surveyors_scope.txt b/forge-gui/res/cardsfolder/s/surveyors_scope.txt index 57f3a33c646..34fe105b7a3 100644 --- a/forge-gui/res/cardsfolder/s/surveyors_scope.txt +++ b/forge-gui/res/cardsfolder/s/surveyors_scope.txt @@ -1,7 +1,7 @@ Name:Surveyor's Scope ManaCost:2 Types:Artifact -A:AB$ ChangeZone | Cost$ T Exile<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ X | SpellDescription$ Search your library for up to X basic land cards, where X is the number of players who control at least two more lands than you. Put those lands onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T Exile<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | SpellDescription$ Search your library for up to X basic land cards, where X is the number of players who control at least two more lands than you. Put those lands onto the battlefield, then shuffle. SVar:X:PlayerCountPropertywithAtLeast2MoreLandsThanYou$Amount AI:RemoveDeck:All AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/s/sutina_speaker_of_the_tajuru.txt b/forge-gui/res/cardsfolder/s/sutina_speaker_of_the_tajuru.txt index cf081b4e1a2..1441c8e6ed0 100644 --- a/forge-gui/res/cardsfolder/s/sutina_speaker_of_the_tajuru.txt +++ b/forge-gui/res/cardsfolder/s/sutina_speaker_of_the_tajuru.txt @@ -3,7 +3,7 @@ ManaCost:2 G Types:Legendary Creature Elf Scout PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigImmediateTrig | TriggerDescription$ Whenever NICKNAME attacks, you may return a land you control to its owner's hand. When you do, put a +1/+1 counter on target creature. SVar:TrigImmediateTrig:AB$ ImmediateTrigger | Cost$ Return<1/Land> | Execute$ TrigPutCounter | TriggerDescription$ When you do, put a +1/+1 counter on target creature. SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 diff --git a/forge-gui/res/cardsfolder/s/sword_of_hearth_and_home.txt b/forge-gui/res/cardsfolder/s/sword_of_hearth_and_home.txt index 0afa6aef4ab..4c1e92eefae 100644 --- a/forge-gui/res/cardsfolder/s/sword_of_hearth_and_home.txt +++ b/forge-gui/res/cardsfolder/s/sword_of_hearth_and_home.txt @@ -5,7 +5,7 @@ K:Equip:2 S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 2 | AddToughness$ 2 | AddSVar$ SwordOfHearthAndHomeCE | AddKeyword$ Protection from green & Protection from white | Description$ Equipped creature gets +2/+2 and has protection from green and from white. T:Mode$ DamageDone | ValidSource$ Creature.EquippedBy | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigBlink | TriggerZones$ Battlefield | TriggerDescription$ Whenever equipped creature deals combat damage to a player, exile up to one target creature you own, then search your library for a basic land card. Put both cards onto the battlefield under your control, then shuffle. SVar:TrigBlink:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | ValidTgts$ Creature.YouOwn | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select target creature you own | SubAbility$ DBLand -SVar:DBLand:DB$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBReturn +SVar:DBLand:DB$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | RememberChanged$ True | SubAbility$ DBReturn SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ All | Destination$ Battlefield | GainControl$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:SwordOfHearthAndHomeCE:SVar:MustBeBlocked:AttackingPlayerConservative diff --git a/forge-gui/res/cardsfolder/s/sword_of_the_animist.txt b/forge-gui/res/cardsfolder/s/sword_of_the_animist.txt index 55f07dcf52e..b7ec40ac387 100644 --- a/forge-gui/res/cardsfolder/s/sword_of_the_animist.txt +++ b/forge-gui/res/cardsfolder/s/sword_of_the_animist.txt @@ -4,5 +4,5 @@ Types:Legendary Artifact Equipment K:Equip:2 S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | Description$ Equipped creature gets +1/+1. T:Mode$ Attacks | ValidCard$ Card.EquippedBy | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ Whenever equipped creature attacks, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ShuffleNonMandatory$ True Oracle:Equipped creature gets +1/+1.\nWhenever equipped creature attacks, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle.\nEquip {2} diff --git a/forge-gui/res/cardsfolder/s/sylvan_ranger.txt b/forge-gui/res/cardsfolder/s/sylvan_ranger.txt index b57972684ad..5d995bd8711 100644 --- a/forge-gui/res/cardsfolder/s/sylvan_ranger.txt +++ b/forge-gui/res/cardsfolder/s/sylvan_ranger.txt @@ -3,5 +3,5 @@ ManaCost:1 G Types:Creature Elf Scout Ranger PT:1/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:When Sylvan Ranger enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/t/temur_monument.txt b/forge-gui/res/cardsfolder/t/temur_monument.txt index 1b8e2059014..745ea9e6ee8 100644 --- a/forge-gui/res/cardsfolder/t/temur_monument.txt +++ b/forge-gui/res/cardsfolder/t/temur_monument.txt @@ -2,7 +2,7 @@ Name:Temur Monument ManaCost:2 Types:Artifact T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When this artifact enters, search your library for a basic Forest, Island, or Mountain card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Forest+Basic,Land.Island+Basic,Land.Mountain+Basic +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Forest.Basic,Island.Basic,Mountain.Basic | ChangeTypeDesc$ basic Forest, Island, or Mountain A:AB$ Token | Cost$ 3 G U R T Sac<1/CARDNAME> | TokenAmount$ 1 | TokenScript$ g_5_5_elephant | TokenOwner$ You | SorcerySpeed$ True | SpellDescription$ Create a 5/5 green Elephant creature token. Activate only as a sorcery. DeckHas:Ability$Token Oracle:When this artifact enters, search your library for a basic Forest, Island, or Mountain card, reveal it, put it into your hand, then shuffle.\n{3}{G}{U}{R}, {T}, Sacrifice this artifact: Create a 5/5 green Elephant creature token. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/t/terminal_moraine.txt b/forge-gui/res/cardsfolder/t/terminal_moraine.txt index 16327fc7f84..334f41ad784 100644 --- a/forge-gui/res/cardsfolder/t/terminal_moraine.txt +++ b/forge-gui/res/cardsfolder/t/terminal_moraine.txt @@ -2,5 +2,5 @@ Name:Terminal Moraine ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Oracle:{T}: Add {C}.\n{2}, {T}, Sacrifice Terminal Moraine: Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/t/terramorph.txt b/forge-gui/res/cardsfolder/t/terramorph.txt index 676ed36f43e..9c0e4ed9502 100644 --- a/forge-gui/res/cardsfolder/t/terramorph.txt +++ b/forge-gui/res/cardsfolder/t/terramorph.txt @@ -2,5 +2,5 @@ Name:Terramorph ManaCost:3 G Types:Sorcery K:Rebound -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield, then shuffle. Oracle:Search your library for a basic land card, put it onto the battlefield, then shuffle.\nRebound (If you cast this spell from your hand, exile it as it resolves. At the beginning of your next upkeep, you may cast this card from exile without paying its mana cost.) diff --git a/forge-gui/res/cardsfolder/t/terramorphic_expanse.txt b/forge-gui/res/cardsfolder/t/terramorphic_expanse.txt index ff6396b3f52..19e121a2391 100644 --- a/forge-gui/res/cardsfolder/t/terramorphic_expanse.txt +++ b/forge-gui/res/cardsfolder/t/terramorphic_expanse.txt @@ -1,5 +1,5 @@ Name:Terramorphic Expanse ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Oracle:{T}, Sacrifice Terramorphic Expanse: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/t/thaumatic_compass_spires_of_orazca.txt b/forge-gui/res/cardsfolder/t/thaumatic_compass_spires_of_orazca.txt index a06ff1473ba..cc416023b52 100644 --- a/forge-gui/res/cardsfolder/t/thaumatic_compass_spires_of_orazca.txt +++ b/forge-gui/res/cardsfolder/t/thaumatic_compass_spires_of_orazca.txt @@ -1,7 +1,7 @@ Name:Thaumatic Compass ManaCost:2 Types:Artifact -A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card and put that card into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card and put that card into your hand, then shuffle. T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | IsPresent$ Land.YouCtrl | PresentCompare$ GE7 | Execute$ DBTransform | TriggerDescription$ At the beginning of your end step, if you control seven or more lands, transform CARDNAME. SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform AlternateMode:DoubleFaced diff --git a/forge-gui/res/cardsfolder/t/thawing_glaciers.txt b/forge-gui/res/cardsfolder/t/thawing_glaciers.txt index 3c4d2ea40f1..df175601ca1 100644 --- a/forge-gui/res/cardsfolder/t/thawing_glaciers.txt +++ b/forge-gui/res/cardsfolder/t/thawing_glaciers.txt @@ -3,7 +3,7 @@ ManaCost:no cost Types:Land R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ CARDNAME enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True -A:AB$ ChangeZone | Cost$ 1 T | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | Shuffle$ True | SubAbility$ DBDelTrig | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Return CARDNAME to its owner's hand at the beginning of the next cleanup step. +A:AB$ ChangeZone | Cost$ 1 T | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | Shuffle$ True | SubAbility$ DBDelTrig | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Return CARDNAME to its owner's hand at the beginning of the next cleanup step. SVar:DBDelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ Cleanup | ValidPlayer$ Player | Execute$ TrigBounce | TriggerDescription$ Return CARDNAME to its owner's hand at the beginning of the next cleanup step. SVar:TrigBounce:DB$ ChangeZone | Origin$ Battlefield | Destination$ Hand | Defined$ Self Oracle:Thawing Glaciers enters tapped.\n{1}, {T}: Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Return Thawing Glaciers to its owner's hand at the beginning of the next cleanup step. diff --git a/forge-gui/res/cardsfolder/t/the_weatherseed_treaty.txt b/forge-gui/res/cardsfolder/t/the_weatherseed_treaty.txt index 2a591a67fa3..d05ba70ac3e 100644 --- a/forge-gui/res/cardsfolder/t/the_weatherseed_treaty.txt +++ b/forge-gui/res/cardsfolder/t/the_weatherseed_treaty.txt @@ -3,7 +3,7 @@ ManaCost:2 G Types:Enchantment Saga K:Read ahead K:Chapter:3:DBChangeZone,DBToken,DBDomain -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card and put that card onto the battlefield tapped. Then shuffle. +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card and put that card onto the battlefield tapped. Then shuffle. SVar:DBToken:DB$ Token | TokenScript$ g_1_1_saproling | SpellDescription$ Create a 1/1 green Saproling creature token. SVar:DBDomain:DB$ Pump | ValidTgts$ Creature.YouCtrl | NumAtt$ +X | NumDef$ +X | KW$ Trample | TgtPrompt$ Select target creature you control | SpellDescription$ Domain — Target creature you control gets +X/+X and gains trample until end of turn, where X is the number of basic land types among lands you control. SVar:X:Count$Domain diff --git a/forge-gui/res/cardsfolder/t/they_went_this_way.txt b/forge-gui/res/cardsfolder/t/they_went_this_way.txt index 04937564b6a..0a881c5d54e 100644 --- a/forge-gui/res/cardsfolder/t/they_went_this_way.txt +++ b/forge-gui/res/cardsfolder/t/they_went_this_way.txt @@ -1,7 +1,7 @@ Name:They Went This Way ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | SubAbility$ DBInvestigate | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | SubAbility$ DBInvestigate | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. SVar:DBInvestigate:DB$ Investigate | SpellDescription$ Investigate. (Create a Clue token. It's an artifact with "{2}, Sacrifice this artifact: Draw a card.") DeckHas:Ability$Investigate|Token|Sacrifice & Type$Artifact|Clue Oracle:Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Investigate. (Create a Clue token. It's an artifact with "{2}, Sacrifice this artifact: Draw a card.") diff --git a/forge-gui/res/cardsfolder/t/thirsting_roots.txt b/forge-gui/res/cardsfolder/t/thirsting_roots.txt index fa43cdfcec3..32e27b9afe4 100644 --- a/forge-gui/res/cardsfolder/t/thirsting_roots.txt +++ b/forge-gui/res/cardsfolder/t/thirsting_roots.txt @@ -2,7 +2,7 @@ Name:Thirsting Roots ManaCost:G Types:Sorcery A:SP$ Charm | Choices$ DBSearch,DBProliferate | CharmNum$ 1 -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. SVar:DBProliferate:DB$ Proliferate | SpellDescription$ Proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) DeckHas:Ability$Proliferate Oracle:Choose one —\n• Search your library for a basic land card, reveal it, put it into your hand, then shuffle.\n• Proliferate. (Choose any number of permanents and/or players, then give each another counter of each kind already there.) diff --git a/forge-gui/res/cardsfolder/t/threats_around_every_corner.txt b/forge-gui/res/cardsfolder/t/threats_around_every_corner.txt index 7225071fc54..139fa1931f6 100644 --- a/forge-gui/res/cardsfolder/t/threats_around_every_corner.txt +++ b/forge-gui/res/cardsfolder/t/threats_around_every_corner.txt @@ -4,5 +4,5 @@ Types:Enchantment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDread | TriggerDescription$ When CARDNAME enters, manifest dread. SVar:TrigDread:DB$ ManifestDread T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Permanent.faceDown+YouCtrl | Execute$ TrigChange | TriggerZones$ Battlefield | TriggerDescription$ Whenever a face-down permanent you control enters, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land Oracle:When Threats Around Every Corner enters, manifest dread.\nWhenever a face-down permanent you control enters, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/t/thunderherd_migration.txt b/forge-gui/res/cardsfolder/t/thunderherd_migration.txt index ff46f48b8c1..6bce7b88b9d 100644 --- a/forge-gui/res/cardsfolder/t/thunderherd_migration.txt +++ b/forge-gui/res/cardsfolder/t/thunderherd_migration.txt @@ -2,5 +2,5 @@ Name:Thunderherd Migration ManaCost:1 G Types:Sorcery K:AlternateAdditionalCost:Reveal<1/Dinosaur>:1 -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | StackDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | StackDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Oracle:As an additional cost to cast this spell, reveal a Dinosaur card from your hand or pay {1}.\nSearch your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/t/time_of_need.txt b/forge-gui/res/cardsfolder/t/time_of_need.txt index b7bb4760c26..5b90a7b7381 100644 --- a/forge-gui/res/cardsfolder/t/time_of_need.txt +++ b/forge-gui/res/cardsfolder/t/time_of_need.txt @@ -1,7 +1,7 @@ Name:Time of Need ManaCost:1 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Creature.Legendary | ChangeNum$ 1 | SpellDescription$ Search your library for a legendary creature card, reveal it, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Creature.Legendary | ChangeTypeDesc$ legendary creature | SpellDescription$ Search your library for a legendary creature card, reveal it, put it into your hand, then shuffle. AI:RemoveDeck:Random DeckHints:Type$Legendary Oracle:Search your library for a legendary creature card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/t/topiary_stomper.txt b/forge-gui/res/cardsfolder/t/topiary_stomper.txt index efea016190b..427c1b6c000 100644 --- a/forge-gui/res/cardsfolder/t/topiary_stomper.txt +++ b/forge-gui/res/cardsfolder/t/topiary_stomper.txt @@ -4,7 +4,7 @@ Types:Creature Plant Dinosaur PT:4/4 K:Vigilance T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigSearch | TriggerDescription$ When CARDNAME enters, search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True +SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True S:Mode$ CantAttack,CantBlock | ValidCard$ Card.Self | IsPresent$ Land.YouCtrl | PresentCompare$ LT7 | Description$ CARDNAME can't attack or block unless you control seven or more lands. SVar:BuffedBy:Land Oracle:Vigilance\nWhen Topiary Stomper enters, search your library for a basic land card, put it onto the battlefield tapped, then shuffle.\nTopiary Stomper can't attack or block unless you control seven or more lands. diff --git a/forge-gui/res/cardsfolder/t/trail_of_mystery.txt b/forge-gui/res/cardsfolder/t/trail_of_mystery.txt index 5bfa3a806a0..8130d559d29 100644 --- a/forge-gui/res/cardsfolder/t/trail_of_mystery.txt +++ b/forge-gui/res/cardsfolder/t/trail_of_mystery.txt @@ -2,7 +2,7 @@ Name:Trail of Mystery ManaCost:1 G Types:Enchantment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.faceDown+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ Whenever a face-down creature you control enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True T:Mode$ TurnFaceUp | ValidCard$ Permanent.Creature+YouCtrl | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Whenever a permanent you control is turned face up, if it's a creature, it gets +2/+2 until end of turn. SVar:TrigPump:DB$ Pump | Defined$ TriggeredCard | NumAtt$ +2 | NumDef$ +2 | ConditionDefined$ TriggeredCard | ConditionPresent$ Creature | ConditionCompare$ GE1 AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/t/tranquil_landscape.txt b/forge-gui/res/cardsfolder/t/tranquil_landscape.txt index c54d74eb18b..cad4c454115 100644 --- a/forge-gui/res/cardsfolder/t/tranquil_landscape.txt +++ b/forge-gui/res/cardsfolder/t/tranquil_landscape.txt @@ -2,6 +2,6 @@ Name:Tranquil Landscape ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Forest+Basic,Land.Plains+Basic,Land.Island+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Forest, Plains, or Island card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Forest.Basic,Plains.Basic,Island.Basic | ChangeTypeDesc$ basic Forest, Plains, or Island | SpellDescription$ Search your library for a basic Forest, Plains, or Island card, put it onto the battlefield tapped, then shuffle. K:Cycling:G W U Oracle:{T}: Add {C}.\n{T}, Sacrifice Tranquil Landscape: Search your library for a basic Forest, Plains, or Island card, put it onto the battlefield tapped, then shuffle.\nCycling {G}{W}{U} ({G}{W}{U}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/t/travel_through_caradhras.txt b/forge-gui/res/cardsfolder/t/travel_through_caradhras.txt index 4756dd5e1f5..3db5f1f5d95 100644 --- a/forge-gui/res/cardsfolder/t/travel_through_caradhras.txt +++ b/forge-gui/res/cardsfolder/t/travel_through_caradhras.txt @@ -2,7 +2,7 @@ Name:Travel Through Caradhras ManaCost:5 G Types:Sorcery A:SP$ Vote | Defined$ Player | Choices$ VoteRedhornPass,VoteMinesofMoria | StoreVoteNum$ True | SubAbility$ DBExile | SpellDescription$ Council's dilemma — Starting with you, each player votes for Redhorn Pass or Mines of Moria. For each Redhorn Pass vote, search your library for a basic land card and put it onto the battlefield tapped. If you search your library this way, shuffle. For each Mines of Moria vote, return a card from your graveyard to your hand. -SVar:VoteRedhornPass:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ VoteNum | Tapped$ True | SpellDescription$ Redhorn Pass +SVar:VoteRedhornPass:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ VoteNum | Tapped$ True | SpellDescription$ Redhorn Pass SVar:VoteMinesofMoria:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ChangeType$ Card.YouOwn | Mandatory$ True | SelectPrompt$ Return a card from your graveyard to your hand for each Mines of Moria vote | Hidden$ True | ChangeNum$ VoteNum | SpellDescription$ Mines of Moria SVar:DBExile:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | SpellDescription$ Exile CARDNAME. DeckHas:Ability$Graveyard diff --git a/forge-gui/res/cardsfolder/t/travelers_amulet.txt b/forge-gui/res/cardsfolder/t/travelers_amulet.txt index 6fee0e30ee6..8d32a22dd34 100644 --- a/forge-gui/res/cardsfolder/t/travelers_amulet.txt +++ b/forge-gui/res/cardsfolder/t/travelers_amulet.txt @@ -1,5 +1,5 @@ Name:Traveler's Amulet ManaCost:1 Types:Artifact -A:AB$ ChangeZone | Cost$ 1 Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 1 Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. Oracle:{1}, Sacrifice Traveler's Amulet: Search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/t/traverse_the_outlands.txt b/forge-gui/res/cardsfolder/t/traverse_the_outlands.txt index cca0f270473..66a59a9940f 100644 --- a/forge-gui/res/cardsfolder/t/traverse_the_outlands.txt +++ b/forge-gui/res/cardsfolder/t/traverse_the_outlands.txt @@ -1,7 +1,7 @@ Name:Traverse the Outlands ManaCost:4 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ X | Tapped$ True | SpellDescription$ Search your library for up to X basic land cards, where X is the greatest power among creatures you control. Put those cards onto the battlefield tapped, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | Tapped$ True | SpellDescription$ Search your library for up to X basic land cards, where X is the greatest power among creatures you control. Put those cards onto the battlefield tapped, then shuffle. SVar:X:Count$Valid Creature.YouCtrl$GreatestPower SVar:NeedsToPlay:Creature.YouCtrl+powerGE2 Oracle:Search your library for up to X basic land cards, where X is the greatest power among creatures you control. Put those cards onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/t/traverse_the_ulvenwald.txt b/forge-gui/res/cardsfolder/t/traverse_the_ulvenwald.txt index fdaa29bf962..87ea20b7db4 100644 --- a/forge-gui/res/cardsfolder/t/traverse_the_ulvenwald.txt +++ b/forge-gui/res/cardsfolder/t/traverse_the_ulvenwald.txt @@ -1,7 +1,7 @@ Name:Traverse the Ulvenwald ManaCost:G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ0 | SubAbility$ DBChangeZone | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. Delirium — If there are four or more card types among cards in your graveyard, instead search your library for a creature or land card, reveal it, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ0 | SubAbility$ DBChangeZone | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. Delirium — If there are four or more card types among cards in your graveyard, instead search your library for a creature or land card, reveal it, put it into your hand, then shuffle. SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land,Creature | ChangeNum$ 1 | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ1 SVar:X:Count$Delirium.1.0 DeckHints:Ability$Graveyard|Discard diff --git a/forge-gui/res/cardsfolder/t/twisted_landscape.txt b/forge-gui/res/cardsfolder/t/twisted_landscape.txt index 7a0e2ec5a06..ab99137d4b3 100644 --- a/forge-gui/res/cardsfolder/t/twisted_landscape.txt +++ b/forge-gui/res/cardsfolder/t/twisted_landscape.txt @@ -2,6 +2,6 @@ Name:Twisted Landscape ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Swamp+Basic,Land.Mountain+Basic,Land.Forest+Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic Swamp, Mountain, or Forest card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Swamp.Basic,Mountain.Basic,Forest.Basic | ChangeTypeDesc$ basic Swamp, Mountain, or Forest | SpellDescription$ Search your library for a basic Swamp, Mountain, or Forest card, put it onto the battlefield tapped, then shuffle. K:Cycling:B R G Oracle:{T}: Add {C}.\n{T}, Sacrifice Twisted Landscape: Search your library for a basic Swamp, Mountain, or Forest card, put it onto the battlefield tapped, then shuffle.\nCycling {B}{R}{G} ({B}{R}{G}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/u/undercity.txt b/forge-gui/res/cardsfolder/u/undercity.txt index 2127bb7c615..686173ae02b 100644 --- a/forge-gui/res/cardsfolder/u/undercity.txt +++ b/forge-gui/res/cardsfolder/u/undercity.txt @@ -3,7 +3,7 @@ ManaCost:no cost Types:Dungeon Undercity K:You can't enter this dungeon unless you "venture into Undercity". K:Dungeon:Entrance,Forge,Well,Trap,Arena,Stash,Archives,Catacombs,Throne -SVar:Entrance:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | RoomName$ Secret Entrance | NextRoom$ Forge,Well | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +SVar:Entrance:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | RoomName$ Secret Entrance | NextRoom$ Forge,Well | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. SVar:Forge:DB$ PutCounter | ValidTgts$ Creature | CounterType$ P1P1 | CounterNum$ 2 | RoomName$ Forge | NextRoom$ Trap,Arena | SpellDescription$ Put two +1/+1 counters on target creature. SVar:Well:DB$ Scry | ScryNum$ 2 | RoomName$ Lost Well | NextRoom$ Arena,Stash | SpellDescription$ Scry 2. SVar:Trap:DB$ LoseLife | ValidTgts$ Player | LifeAmount$ 5 | RoomName$ Trap! | NextRoom$ Archives | SpellDescription$ Target player loses 5 life. diff --git a/forge-gui/res/cardsfolder/u/unmarked_grave.txt b/forge-gui/res/cardsfolder/u/unmarked_grave.txt index dab2d334e6b..2c2f0ce8908 100644 --- a/forge-gui/res/cardsfolder/u/unmarked_grave.txt +++ b/forge-gui/res/cardsfolder/u/unmarked_grave.txt @@ -1,7 +1,7 @@ Name:Unmarked Grave ManaCost:1 B Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Graveyard | ChangeType$ Card.nonLegendary | ChangeNum$ 1 | SpellDescription$ Search your library for a nonlegendary card, put that card into your graveyard, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Graveyard | ChangeType$ Card.!Legendary | ChangeTypeDesc$ nonlegendary | SpellDescription$ Search your library for a nonlegendary card, put that card into your graveyard, then shuffle. AI:RemoveDeck:Random DeckHas:Ability$Graveyard Oracle:Search your library for a nonlegendary card, put that card into your graveyard, then shuffle. diff --git a/forge-gui/res/cardsfolder/u/untamed_wilds.txt b/forge-gui/res/cardsfolder/u/untamed_wilds.txt index 81d2b9c56f4..a0d142adc7c 100644 --- a/forge-gui/res/cardsfolder/u/untamed_wilds.txt +++ b/forge-gui/res/cardsfolder/u/untamed_wilds.txt @@ -1,5 +1,5 @@ Name:Untamed Wilds ManaCost:2 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield, then shuffle. Oracle:Search your library for a basic land card, put that card onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/v/vastwood_surge.txt b/forge-gui/res/cardsfolder/v/vastwood_surge.txt index 3386505c769..4bcb308f651 100644 --- a/forge-gui/res/cardsfolder/v/vastwood_surge.txt +++ b/forge-gui/res/cardsfolder/v/vastwood_surge.txt @@ -2,7 +2,7 @@ Name:Vastwood Surge ManaCost:3 G Types:Sorcery K:Kicker:4 -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 2 | Tapped$ True | SubAbility$ DBPutCounterAll | StackDescription$ SpellDescription | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. If this spell was kicked, put two +1/+1 counters on each creature you control. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | Tapped$ True | SubAbility$ DBPutCounterAll | StackDescription$ SpellDescription | SpellDescription$ Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. If this spell was kicked, put two +1/+1 counters on each creature you control. SVar:DBPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 2 | Condition$ Kicked | StackDescription$ None DeckHas:Ability$Counters Oracle:Kicker {4} (You may pay an additional {4} as you cast this spell.)\nSearch your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. If this spell was kicked, put two +1/+1 counters on each creature you control. diff --git a/forge-gui/res/cardsfolder/v/verdant_catacombs.txt b/forge-gui/res/cardsfolder/v/verdant_catacombs.txt index 89420bcd044..7b82954e7a8 100644 --- a/forge-gui/res/cardsfolder/v/verdant_catacombs.txt +++ b/forge-gui/res/cardsfolder/v/verdant_catacombs.txt @@ -1,5 +1,5 @@ Name:Verdant Catacombs ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Swamp,Forest | ChangeNum$ 1 | SpellDescription$ Search your library for a Swamp or Forest card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Swamp,Forest | SpellDescription$ Search your library for a Swamp or Forest card, put it onto the battlefield, then shuffle. Oracle:{T}, Pay 1 life, Sacrifice Verdant Catacombs: Search your library for a Swamp or Forest card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/v/verdant_confluence.txt b/forge-gui/res/cardsfolder/v/verdant_confluence.txt index 1571878ba23..edc8d290804 100644 --- a/forge-gui/res/cardsfolder/v/verdant_confluence.txt +++ b/forge-gui/res/cardsfolder/v/verdant_confluence.txt @@ -4,5 +4,5 @@ Types:Sorcery A:SP$ Charm | Choices$ DBPutCounter,DBChangeZone1,DBChangeZone2 | CharmNum$ 3 | CanRepeatModes$ True SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 2 | SpellDescription$ Put two +1/+1 counters on target creature. SVar:DBChangeZone1:DB$ ChangeZone | TgtPrompt$ Choose target permanent card in your graveyard | ValidTgts$ Permanent.YouOwn | Origin$ Graveyard | Destination$ Hand | SpellDescription$ Return target permanent card from your graveyard to your hand. -SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Oracle:Choose three. You may choose the same mode more than once.\n• Put two +1/+1 counters on target creature.\n• Return target permanent card from your graveyard to your hand.\n• Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/v/verdant_crescendo.txt b/forge-gui/res/cardsfolder/v/verdant_crescendo.txt index f4de7e8a0b4..1db3073a687 100644 --- a/forge-gui/res/cardsfolder/v/verdant_crescendo.txt +++ b/forge-gui/res/cardsfolder/v/verdant_crescendo.txt @@ -1,7 +1,7 @@ Name:Verdant Crescendo ManaCost:3 G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | Shuffle$ False | SubAbility$ DBSearch | SpellDescription$ Search your library for a basic land card and put it onto the battlefield tapped. Search your library and graveyard for a card named Nissa, Nature's Artisan, reveal it, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Shuffle$ False | SubAbility$ DBSearch | SpellDescription$ Search your library for a basic land card and put it onto the battlefield tapped. Search your library and graveyard for a card named Nissa, Nature's Artisan, reveal it, put it into your hand, then shuffle. SVar:DBSearch:DB$ ChangeZone | Origin$ Library,Graveyard | Destination$ Hand | ChangeType$ Card.namedNissa; Nature's Artisan | ChangeNum$ 1 DeckHints:Name$Nissa, Nature's Artisan Oracle:Search your library for a basic land card and put it onto the battlefield tapped. Search your library and graveyard for a card named Nissa, Nature's Artisan, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/v/verdant_mastery.txt b/forge-gui/res/cardsfolder/v/verdant_mastery.txt index 66bbc7cb3a6..230ea585e83 100644 --- a/forge-gui/res/cardsfolder/v/verdant_mastery.txt +++ b/forge-gui/res/cardsfolder/v/verdant_mastery.txt @@ -2,7 +2,7 @@ Name:Verdant Mastery ManaCost:5 G Types:Sorcery S:Mode$ AlternativeCost | ValidSA$ Spell.Self | EffectZone$ All | Cost$ 3 G | Description$ You may pay {3}{G} rather than pay this spell's mana cost. | StackDescription$ Search your library for up to four basic land cards and reveal them. Put one of them onto the battlefield tapped under an opponent's control if the {3}{G} cost was paid. Put two of them onto the battlefield tapped under your control and the rest into your hand. Then shuffle. -A:SP$ ChangeZone | Origin$ Library | Hidden$ True | ChangeNum$ 4 | ChangeType$ Land.Basic | Destination$ Library | RememberChanged$ True | Reveal$ True | Shuffle$ False | SubAbility$ DBBranch | StackDescription$ Search your library for up to four basic land cards and reveal them. Put two of them onto the battlefield tapped under your control and the rest into your hand. Then shuffle. | SpellDescription$ Search your library for up to four basic land cards and reveal them. Put one of them onto the battlefield tapped under an opponent's control if the {3}{G} cost was paid. Put two of them onto the battlefield tapped under your control and the rest into your hand. Then shuffle. +A:SP$ ChangeZone | Origin$ Library | Hidden$ True | ChangeNum$ 4 | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Destination$ Library | RememberChanged$ True | Reveal$ True | Shuffle$ False | SubAbility$ DBBranch | StackDescription$ Search your library for up to four basic land cards and reveal them. Put two of them onto the battlefield tapped under your control and the rest into your hand. Then shuffle. | SpellDescription$ Search your library for up to four basic land cards and reveal them. Put one of them onto the battlefield tapped under an opponent's control if the {3}{G} cost was paid. Put two of them onto the battlefield tapped under your control and the rest into your hand. Then shuffle. SVar:DBBranch:DB$ Branch | BranchConditionSVar$ AltCostPaid | BranchConditionSVarCompare$ GE1 | TrueSubAbility$ OppShare | FalseSubAbility$ DBChangeZone2 SVar:OppShare:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | ChoiceTitle$ Choose an opponent | SubAbility$ DBChangeZone SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Card.IsRemembered | ChangeNum$ 1 | Mandatory$ True | SelectPrompt$ Select a land to put onto the battlefield tapped under an opponent's control | GainControl$ ChosenPlayer | ForgetChanged$ True | NoShuffle$ True | SubAbility$ DBChangeZone2 diff --git a/forge-gui/res/cardsfolder/v/veteran_explorer.txt b/forge-gui/res/cardsfolder/v/veteran_explorer.txt index 76fee79cbd2..b72b63d14e6 100644 --- a/forge-gui/res/cardsfolder/v/veteran_explorer.txt +++ b/forge-gui/res/cardsfolder/v/veteran_explorer.txt @@ -3,5 +3,5 @@ ManaCost:G Types:Creature Human Soldier Scout PT:1/1 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME dies, each player may search their library for up to two basic land cards, put them onto the battlefield, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | DefinedPlayer$ Player | ChangeNum$ 2 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ Player | ChangeNum$ 2 | ShuffleNonMandatory$ True Oracle:When Veteran Explorer dies, each player may search their library for up to two basic land cards, put them onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/v/vibrant_cityscape.txt b/forge-gui/res/cardsfolder/v/vibrant_cityscape.txt index c0fcf81fb41..118b71918cc 100644 --- a/forge-gui/res/cardsfolder/v/vibrant_cityscape.txt +++ b/forge-gui/res/cardsfolder/v/vibrant_cityscape.txt @@ -1,5 +1,5 @@ Name:Vibrant Cityscape ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Oracle:{T}, Sacrifice this land: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/v/viewpoint_synchronization.txt b/forge-gui/res/cardsfolder/v/viewpoint_synchronization.txt index fe202451d2b..c6a663a3ff2 100644 --- a/forge-gui/res/cardsfolder/v/viewpoint_synchronization.txt +++ b/forge-gui/res/cardsfolder/v/viewpoint_synchronization.txt @@ -2,7 +2,7 @@ Name:Viewpoint Synchronization ManaCost:4 G Types:Sorcery K:Freerunning:2 G -A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeNum$ 3 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to three basic land cards and reveal them. Put two of them onto the battlefield tapped and the other into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 3 | RememberChanged$ True | Reveal$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to three basic land cards and reveal them. Put two of them onto the battlefield tapped and the other into your hand, then shuffle. SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.IsRemembered | ChangeNum$ 2 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put onto the battlefield | Tapped$ True | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.IsRemembered | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to put into your hand | StackDescription$ None | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/v/viridian_emissary.txt b/forge-gui/res/cardsfolder/v/viridian_emissary.txt index e73166fe97b..efc60a40b3d 100644 --- a/forge-gui/res/cardsfolder/v/viridian_emissary.txt +++ b/forge-gui/res/cardsfolder/v/viridian_emissary.txt @@ -3,5 +3,5 @@ ManaCost:1 G Types:Creature Phyrexian Elf Scout PT:2/1 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:When Viridian Emissary dies, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/v/volatile_fault.txt b/forge-gui/res/cardsfolder/v/volatile_fault.txt index 2f335b71539..f6b76ac11a1 100644 --- a/forge-gui/res/cardsfolder/v/volatile_fault.txt +++ b/forge-gui/res/cardsfolder/v/volatile_fault.txt @@ -3,7 +3,7 @@ ManaCost:no cost Types:Land Cave A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. A:AB$ Destroy | Cost$ 1 T Sac<1/CARDNAME> | ValidTgts$ Land.nonBasic+OppCtrl | TgtPrompt$ Select target nonbasic land an opponent controls | SubAbility$ DBSearch | AILogic$ GhostQuarter | SpellDescription$ Destroy target nonbasic land an opponent controls. That player may search their library for a basic land card, put it onto the battlefield, then shuffle. You create a Treasure token. -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ TargetedController | ChangeType$ Land.Basic | ChangeNum$ 1 | SubAbility$ DBTreasure | StackDescription$ That land's controller may search their library for a basic land card, put it onto the battlefield, then shuffle. You create a Treasure token. +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ TargetedController | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SubAbility$ DBTreasure | StackDescription$ That land's controller may search their library for a basic land card, put it onto the battlefield, then shuffle. You create a Treasure token. SVar:DBTreasure:DB$ Token | TokenScript$ c_a_treasure_sac DeckHas:Ability$Sacrifice|Token & Type$Treasure|Artifact Oracle:{T}: Add {C}.\n{1}, {T}, Sacrifice Volatile Fault: Destroy target nonbasic land an opponent controls. That player may search their library for a basic land card, put it onto the battlefield, then shuffle. You create a Treasure token. diff --git a/forge-gui/res/cardsfolder/w/wanderers_twig.txt b/forge-gui/res/cardsfolder/w/wanderers_twig.txt index fd2b863e2d3..f3b910ce4ce 100644 --- a/forge-gui/res/cardsfolder/w/wanderers_twig.txt +++ b/forge-gui/res/cardsfolder/w/wanderers_twig.txt @@ -1,5 +1,5 @@ Name:Wanderer's Twig ManaCost:1 Types:Artifact -A:AB$ ChangeZone | Cost$ 1 Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal that card, and put it into your hand. Then shuffle. +A:AB$ ChangeZone | Cost$ 1 Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal that card, and put it into your hand. Then shuffle. Oracle:{1}, Sacrifice Wanderer's Twig: Search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/w/warped_landscape.txt b/forge-gui/res/cardsfolder/w/warped_landscape.txt index 790a70854f1..ef0f4fb95b1 100644 --- a/forge-gui/res/cardsfolder/w/warped_landscape.txt +++ b/forge-gui/res/cardsfolder/w/warped_landscape.txt @@ -2,5 +2,5 @@ Name:Warped Landscape ManaCost:no cost Types:Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Oracle:{T}: Add {C}.\n{2}, {T}, Sacrifice Warped Landscape: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/w/wave_of_vitriol.txt b/forge-gui/res/cardsfolder/w/wave_of_vitriol.txt index 5556d6f296f..f4864796cfd 100644 --- a/forge-gui/res/cardsfolder/w/wave_of_vitriol.txt +++ b/forge-gui/res/cardsfolder/w/wave_of_vitriol.txt @@ -3,7 +3,7 @@ ManaCost:5 G G Types:Sorcery A:SP$ SacrificeAll | ValidCards$ Artifact,Enchantment,Land.nonBasic | RememberSacrificed$ True | SubAbility$ DBRepeat | SpellDescription$ Each player sacrifices all artifacts, enchantments, and nonbasic lands they control. For each land sacrificed this way, its controller may search their library for a basic land card and put it onto the battlefield tapped. Then each player who searched their library this way shuffles. SVar:DBRepeat:DB$ RepeatEach | DefinedCards$ DirectRemembered.Land | UseImprinted$ True | RepeatSubAbility$ DBSearch | ClearRemembered$ True | SubAbility$ DBShuffle -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True | RememberSearched$ True | DefinedPlayer$ ImprintedController | Chooser$ ImprintedController | NoShuffle$ True | Optional$ True +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | RememberSearched$ True | DefinedPlayer$ ImprintedController | Chooser$ ImprintedController | NoShuffle$ True | Optional$ True SVar:DBShuffle:DB$ Shuffle | Defined$ Player.IsRemembered | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/w/wayfarers_bauble.txt b/forge-gui/res/cardsfolder/w/wayfarers_bauble.txt index 333b41df7c6..7d4e5d3673a 100644 --- a/forge-gui/res/cardsfolder/w/wayfarers_bauble.txt +++ b/forge-gui/res/cardsfolder/w/wayfarers_bauble.txt @@ -1,5 +1,5 @@ Name:Wayfarer's Bauble ManaCost:1 Types:Artifact -A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. Oracle:{2}, {T}, Sacrifice Wayfarer's Bauble: Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/w/weird_harvest.txt b/forge-gui/res/cardsfolder/w/weird_harvest.txt index fb8db36e313..b5966efd3e5 100644 --- a/forge-gui/res/cardsfolder/w/weird_harvest.txt +++ b/forge-gui/res/cardsfolder/w/weird_harvest.txt @@ -1,7 +1,7 @@ Name:Weird Harvest ManaCost:X G G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | DefinedPlayer$ Player | ChangeType$ Card.Creature | Reveal$ True | Optional$ True | ShuffleNonMandatory$ True | ChangeNum$ X | SpellDescription$ Each player may search their library for up to X creature cards, reveal those cards, put them into their hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | DefinedPlayer$ Player | ChangeType$ Creature | Reveal$ True | Optional$ True | ShuffleNonMandatory$ True | ChangeNum$ X | SpellDescription$ Each player may search their library for up to X creature cards, reveal those cards, put them into their hand, then shuffle. SVar:X:Count$xPaid AI:RemoveDeck:All Oracle:Each player may search their library for up to X creature cards, reveal those cards, put them into their hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/w/white_orchid_phantom.txt b/forge-gui/res/cardsfolder/w/white_orchid_phantom.txt index 1e2d4e58252..8940a099e7e 100644 --- a/forge-gui/res/cardsfolder/w/white_orchid_phantom.txt +++ b/forge-gui/res/cardsfolder/w/white_orchid_phantom.txt @@ -6,5 +6,5 @@ K:Flying K:First Strike T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDestroy | TriggerDescription$ When CARDNAME enters, destroy up to one target nonbasic land. Its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Land.nonBasic | TgtPrompt$ Select up to one target nonbasic land | TargetMin$ 0 | TargetMax$ 1 | SubAbility$ DBSearch | AILogic$ GhostQuarter -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ TargetedController | ChangeType$ Land.Basic | ChangeNum$ 1 | Tapped$ True +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | DefinedPlayer$ TargetedController | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True Oracle:Flying, first strike\nWhen White Orchid Phantom enters, destroy up to one target nonbasic land. Its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/w/wild_crocodile.txt b/forge-gui/res/cardsfolder/w/wild_crocodile.txt index a31753e29e7..49d36be5464 100644 --- a/forge-gui/res/cardsfolder/w/wild_crocodile.txt +++ b/forge-gui/res/cardsfolder/w/wild_crocodile.txt @@ -3,5 +3,5 @@ ManaCost:1 G Types:Host Creature Crocodile PT:1/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | Host$ True | TriggerDescription$ When this creature enters, search your library for a basic land card, reveal it, put it into your hand, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land Oracle:When this creature enters, search your library for a basic land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/w/wild_endeavor.txt b/forge-gui/res/cardsfolder/w/wild_endeavor.txt index c777509bae9..65cd84be586 100644 --- a/forge-gui/res/cardsfolder/w/wild_endeavor.txt +++ b/forge-gui/res/cardsfolder/w/wild_endeavor.txt @@ -3,6 +3,6 @@ ManaCost:4 G G Types:Sorcery A:SP$ RollDice | Amount$ 2 | Sides$ 4 | ChosenSVar$ X | OtherSVar$ Y | SubAbility$ DBToken | StackDescription$ SpellDescription | SpellDescription$ Roll two d4 and choose one result. Create a number of 3/3 green Beast creature tokens equal to that result. Then search your library for a number of basic land cards equal to the other result, put them onto the battlefield tapped, then shuffle. SVar:DBToken:DB$ Token | TokenAmount$ X | TokenScript$ g_3_3_beast | SubAbility$ DBChangeZone -SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | ChangeNum$ Y | ChangeType$ Land.Basic | Destination$ Battlefield | Tapped$ True | StackDescription$ None +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | ChangeNum$ Y | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Destination$ Battlefield | Tapped$ True | StackDescription$ None DeckHas:Ability$Token Oracle:Roll two d4 and choose one result. Create a number of 3/3 green Beast creature tokens equal to that result. Then search your library for a number of basic land cards equal to the other result, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/w/wild_field_scarecrow.txt b/forge-gui/res/cardsfolder/w/wild_field_scarecrow.txt index 65d534edb1c..2166d0c21fc 100644 --- a/forge-gui/res/cardsfolder/w/wild_field_scarecrow.txt +++ b/forge-gui/res/cardsfolder/w/wild_field_scarecrow.txt @@ -3,5 +3,5 @@ ManaCost:3 Types:Artifact Creature Scarecrow PT:1/4 K:Defender -A:AB$ ChangeZone | Cost$ 2 Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 2 Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | SpellDescription$ Search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle. Oracle:Defender\n{2}, Sacrifice Wild-Field Scarecrow: Search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/w/wild_wanderer.txt b/forge-gui/res/cardsfolder/w/wild_wanderer.txt index d2fdfbfc414..0ea0c69ec85 100644 --- a/forge-gui/res/cardsfolder/w/wild_wanderer.txt +++ b/forge-gui/res/cardsfolder/w/wild_wanderer.txt @@ -3,5 +3,5 @@ ManaCost:3 G Types:Creature Elf Druid PT:3/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | OptionalDecider$ You | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land Oracle:When Wild Wanderer enters, you may search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/w/winds_of_abandon.txt b/forge-gui/res/cardsfolder/w/winds_of_abandon.txt index 202b5843423..5ce0a168546 100644 --- a/forge-gui/res/cardsfolder/w/winds_of_abandon.txt +++ b/forge-gui/res/cardsfolder/w/winds_of_abandon.txt @@ -4,7 +4,7 @@ Types:Sorcery A:SP$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control | SubAbility$ DBGetLandsAll | RememberLKI$ True | SpellDescription$ Exile target creature you don't control. For each creature exiled this way, its controller searches their library for a basic land card. Those players put those cards onto the battlefield tapped, then shuffle. SVar:DBGetLandsAll:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBGetLandsOne | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:DBGetLandsOne:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ X | DefinedPlayer$ Player.IsRemembered | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 +SVar:DBGetLandsOne:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ X | DefinedPlayer$ Player.IsRemembered | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 SVar:X:RememberedLKI$FilterControlledByRemembered_Number$1 K:Overload:4 W W Oracle:Exile target creature you don't control. For each creature exiled this way, its controller searches their library for a basic land card. Those players put those cards onto the battlefield tapped, then shuffle.\nOverload {4}{W}{W} (You may cast this spell for its overload cost. If you do, change its text by replacing all instances of "target" with "each.") diff --git a/forge-gui/res/cardsfolder/w/windswept_heath.txt b/forge-gui/res/cardsfolder/w/windswept_heath.txt index 3f654c895c6..b9b85aabc94 100644 --- a/forge-gui/res/cardsfolder/w/windswept_heath.txt +++ b/forge-gui/res/cardsfolder/w/windswept_heath.txt @@ -1,5 +1,5 @@ Name:Windswept Heath ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Forest,Plains | ChangeNum$ 1 | SpellDescription$ Search your library for a Forest or Plains card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Forest,Plains | SpellDescription$ Search your library for a Forest or Plains card, put it onto the battlefield, then shuffle. Oracle:{T}, Pay 1 life, Sacrifice Windswept Heath: Search your library for a Forest or Plains card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/w/wooded_foothills.txt b/forge-gui/res/cardsfolder/w/wooded_foothills.txt index 48ba0218285..fb49cea9c94 100644 --- a/forge-gui/res/cardsfolder/w/wooded_foothills.txt +++ b/forge-gui/res/cardsfolder/w/wooded_foothills.txt @@ -1,5 +1,5 @@ Name:Wooded Foothills ManaCost:no cost Types:Land -A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Mountain,Forest | ChangeNum$ 1 | SpellDescription$ Search your library for a Mountain or Forest card, put it onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Mountain,Forest | SpellDescription$ Search your library for a Mountain or Forest card, put it onto the battlefield, then shuffle. Oracle:{T}, Pay 1 life, Sacrifice Wooded Foothills: Search your library for a Mountain or Forest card, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/w/woodland_investigation.txt b/forge-gui/res/cardsfolder/w/woodland_investigation.txt index 4c3098a4a69..11139a5c75c 100644 --- a/forge-gui/res/cardsfolder/w/woodland_investigation.txt +++ b/forge-gui/res/cardsfolder/w/woodland_investigation.txt @@ -1,7 +1,7 @@ Name:Woodland Investigation ManaCost:G Types:Sorcery -A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | RememberChanged$ True | SubAbility$ DBAnimate | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +A:SP$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | RememberChanged$ True | SubAbility$ DBAnimate | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. SVar:DBAnimate:DB$ Animate | Types$ Clue,Artifact | Duration$ Perpetual | Abilities$ ClueSac | Defined$ Remembered | SubAbility$ DBCleanup | SpellDescription$ It perpetually becomes a Clue artifact in addition to its other types and gains "{2}, Sacrifice this permanent: Draw a card." SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:ClueSac:AB$ Draw | Cost$ 2 Sac<1/CARDNAME/this permanent> | NumCards$ 1 | SpellDescription$ Draw a card. diff --git a/forge-gui/res/cardsfolder/w/world_map.txt b/forge-gui/res/cardsfolder/w/world_map.txt index cc25adc9bb3..a3cb6c9d1ac 100644 --- a/forge-gui/res/cardsfolder/w/world_map.txt +++ b/forge-gui/res/cardsfolder/w/world_map.txt @@ -1,6 +1,6 @@ Name:World Map ManaCost:1 Types:Artifact -A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. +A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, reveal it, put it into your hand, then shuffle. A:AB$ ChangeZone | Cost$ 3 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Hand | ChangeType$ Land | ChangeNum$ 1 | SpellDescription$ Search your library for a land card, reveal it, put it into your hand, then shuffle. Oracle:{1}, {T}, Sacrifice this artifact: Search your library for a basic land card, reveal it, put it into your hand, then shuffle.\n{3}, {T}, Sacrifice this artifact: Search your library for a land card, reveal it, put it into your hand, then shuffle. diff --git a/forge-gui/res/cardsfolder/y/yavimaya_elder.txt b/forge-gui/res/cardsfolder/y/yavimaya_elder.txt index cf09af3c89e..077f3e093ce 100644 --- a/forge-gui/res/cardsfolder/y/yavimaya_elder.txt +++ b/forge-gui/res/cardsfolder/y/yavimaya_elder.txt @@ -3,6 +3,6 @@ ManaCost:1 G G Types:Creature Human Druid PT:2/1 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigFetch | OptionalDecider$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, you may search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle. -SVar:TrigFetch:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 2 | ShuffleNonMandatory$ True +SVar:TrigFetch:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | ShuffleNonMandatory$ True A:AB$ Draw | Cost$ 2 Sac<1/CARDNAME> | NumCards$ 1 | SpellDescription$ Draw a card. Oracle:When Yavimaya Elder dies, you may search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle.\n{2}, Sacrifice Yavimaya Elder: Draw a card. diff --git a/forge-gui/res/cardsfolder/y/yavimaya_granger.txt b/forge-gui/res/cardsfolder/y/yavimaya_granger.txt index ae41326e259..00126f4288c 100644 --- a/forge-gui/res/cardsfolder/y/yavimaya_granger.txt +++ b/forge-gui/res/cardsfolder/y/yavimaya_granger.txt @@ -4,5 +4,5 @@ Types:Creature Elf PT:2/2 K:Echo:2 G T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | ShuffleNonMandatory$ True +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ShuffleNonMandatory$ True Oracle:Echo {2}{G} (At the beginning of your upkeep, if this came under your control since the beginning of your last upkeep, sacrifice it unless you pay its echo cost.)\nWhen Yavimaya Granger enters, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/y/you_happen_on_a_glade.txt b/forge-gui/res/cardsfolder/y/you_happen_on_a_glade.txt index 5cce37a5ed4..8d7f5974f85 100644 --- a/forge-gui/res/cardsfolder/y/you_happen_on_a_glade.txt +++ b/forge-gui/res/cardsfolder/y/you_happen_on_a_glade.txt @@ -2,6 +2,6 @@ Name:You Happen On a Glade ManaCost:2 G Types:Instant A:SP$ Charm | Choices$ JourneyOn,MakeCamp -SVar:JourneyOn:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeNum$ 2 | Reveal$ True | StackDescription$ SpellDescription | SpellDescription$ Journey On — Search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle. +SVar:JourneyOn:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | Reveal$ True | StackDescription$ SpellDescription | SpellDescription$ Journey On — Search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle. SVar:MakeCamp:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | TgtPrompt$ Choose target permanent card in your graveyard | ValidTgts$ Permanent.YouOwn | SpellDescription$ Make Camp — Return target permanent card from your graveyard to your hand. Oracle:Choose one —\n• Journey On — Search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle.\n• Make Camp — Return target permanent card from your graveyard to your hand. diff --git a/forge-gui/res/tokenscripts/c_a_lander_sac_search.txt b/forge-gui/res/tokenscripts/c_a_lander_sac_search.txt index a653f6ef453..45d3f1ff151 100644 --- a/forge-gui/res/tokenscripts/c_a_lander_sac_search.txt +++ b/forge-gui/res/tokenscripts/c_a_lander_sac_search.txt @@ -1,5 +1,5 @@ Name:Lander Token ManaCost:no cost Types:Artifact Lander -A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +A:AB$ ChangeZone | Cost$ 2 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. Oracle:{2}, {T}, Sacrifice this token: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. From a1042e0a1dfc3b4319a4940f922d36f95562f1cc Mon Sep 17 00:00:00 2001 From: Simisays <67333662+Simisays@users.noreply.github.com> Date: Tue, 30 Sep 2025 21:24:51 +0200 Subject: [PATCH 059/230] Update jin_sakai_ghost_of_tsushima.txt (#8815) --- .../cardsfolder/upcoming/jin_sakai_ghost_of_tsushima.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/jin_sakai_ghost_of_tsushima.txt b/forge-gui/res/cardsfolder/upcoming/jin_sakai_ghost_of_tsushima.txt index 28c9904ee1b..de33dbf3392 100644 --- a/forge-gui/res/cardsfolder/upcoming/jin_sakai_ghost_of_tsushima.txt +++ b/forge-gui/res/cardsfolder/upcoming/jin_sakai_ghost_of_tsushima.txt @@ -4,9 +4,9 @@ Types:Legendary Creature Human Samurai PT:2/4 T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDraw | TriggerDescription$ Whenever NICKNAME deals combat damage to a player, draw a card. SVar:TrigDraw:DB$ Draw | NumCards$ 1 -T:Mode$ Attacks | ValidCard$ Creature.YouCtrl | Alone$ True | TriggerZones$ Battlefield | Execute$ TrigCharm | IsPresent$ Creature.attacking+Other | NoResolvingCheck$ True | PresentCompare$ EQ0 | TriggerDescription$ Whenever a creature you control attacks a player, if no other creatures are attacking that player, ABILITY +T:Mode$ AttackersDeclaredOneTarget | ValidAttackers$ Creature.YouCtrl | AttackedTarget$ Player | TriggerZones$ Battlefield | Execute$ TrigCharm | ValidAttackersAmount$ EQ1 | TriggerDescription$ Whenever a creature you control attacks a player, if no other creatures are attacking that player, ABILITY SVar:TrigCharm:DB$ Charm | Choices$ Standoff,Ghost -SVar:Standoff:DB$ Pump | Defined$ TriggeredAttackerLKICopy | KW$ Double Strike | SpellDescription$ Standoff — It gains double strike until end of turn. -SVar:Ghost:DB$ Effect | RememberObjects$ Self | ExileOnMoved$ Battlefield | StaticAbilities$ Unblockable | SpellDescription$ Ghost — It can't be blocked this turn. +SVar:Standoff:DB$ Pump | Defined$ TriggeredAttackers | KW$ Double Strike | SpellDescription$ Standoff — It gains double strike until end of turn. +SVar:Ghost:DB$ Effect | RememberObjects$ TriggeredAttackers | ExileOnMoved$ Battlefield | StaticAbilities$ Unblockable | SpellDescription$ Ghost — It can't be blocked this turn. SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ EFFECTSOURCE can't be blocked this turn. Oracle:Whenever Jin Sakai deals combat damage to a player, draw a card.\nWhenever a creature you control attacks a player, if no other creatures are attacking that player, choose one —-\n• Standoff — It gains double strike until end of turn.\n• Ghost — It can't be blocked this turn. From c0fe93ee30f280b0043b4a6921e61be804d5f575 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Wed, 1 Oct 2025 11:39:31 +0200 Subject: [PATCH 060/230] Update the_destined_thief.txt fix DBDraw SVars --- forge-gui/res/cardsfolder/upcoming/the_destined_thief.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/the_destined_thief.txt b/forge-gui/res/cardsfolder/upcoming/the_destined_thief.txt index 0aafcd628ac..6c3dfac6341 100644 --- a/forge-gui/res/cardsfolder/upcoming/the_destined_thief.txt +++ b/forge-gui/res/cardsfolder/upcoming/the_destined_thief.txt @@ -7,11 +7,11 @@ A:AB$ Effect | Cost$ U T | ValidTgts$ Creature.Other+YouCtrl | TgtPrompt$ Select SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. T:Mode$ DamageAll | CombatDamage$ True | ValidSource$ Creature.YouCtrl | ValidTarget$ Player | TriggerZones$ Battlefield | Execute$ TrigBranch | TriggerDescription$ Whenever one or more creatures you control deal combat damage to one or more players, draw a card, then discard a card. If you have a full party, instead draw three cards. SVar:TrigBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$EQ4 | TrueSubAbility$ DBDraw1 | FalseSubAbility$ DBDraw2 -SVar:TrigDraw1:DB$ Draw | NumCards$ 3 -SVar:TrigDraw2:DB$ Draw | SubAbility$ DBDiscard +SVar:DBDraw1:DB$ Draw | NumCards$ 3 +SVar:DBDraw2:DB$ Draw | SubAbility$ DBDiscard SVar:DBDiscard:DB$ Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 1 SVar:X:Count$Party SVar:BuffedBy:Cleric,Warrior,Wizard DeckHas:Ability$Party DeckHints:Type$Cleric|Warrior|Wizard -Oracle:The Destined Thief can't be blocked.\n{U}, {T}: Another target creature you control can't be blocked this turn.\nWhenever one or more creatures you control deal combat damage to one or more players, draw a card, then discard a card. If you have a full party, instead draw three cards. \ No newline at end of file +Oracle:The Destined Thief can't be blocked.\n{U}, {T}: Another target creature you control can't be blocked this turn.\nWhenever one or more creatures you control deal combat damage to one or more players, draw a card, then discard a card. If you have a full party, instead draw three cards. From 31f8da56877d87d63c42b8dd69439709e72921d7 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Wed, 1 Oct 2025 16:54:46 +0200 Subject: [PATCH 061/230] Some fixes (#8816) --- .../src/main/java/forge/ai/AiController.java | 44 ++++--------------- .../java/forge/ai/PlayerControllerAi.java | 6 ++- .../main/java/forge/ai/ability/CloneAi.java | 4 ++ .../java/forge/ai/ability/CountersPutAi.java | 37 +++++----------- .../main/java/forge/ai/ability/EffectAi.java | 2 +- .../main/java/forge/ai/ability/FightAi.java | 2 +- .../main/java/forge/ai/ability/PumpAi.java | 2 +- .../game/ability/effects/EffectEffect.java | 10 ++--- .../java/forge/game/card/CardFactoryUtil.java | 2 +- .../res/cardsfolder/s/samite_ministration.txt | 8 ++-- .../upcoming/atreus_impulsive_son.txt | 3 +- 11 files changed, 43 insertions(+), 77 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 00beb693357..c9d698c7582 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -887,27 +887,8 @@ public class AiController { private AiPlayDecision canPlayAndPayForFace(final SpellAbility sa) { final Card host = sa.getHostCard(); - // Check a predefined condition - if (sa.hasParam("AICheckSVar")) { - final String svarToCheck = sa.getParam("AICheckSVar"); - String comparator = "GE"; - int compareTo = 1; - - if (sa.hasParam("AISVarCompare")) { - final String fullCmp = sa.getParam("AISVarCompare"); - comparator = fullCmp.substring(0, 2); - final String strCmpTo = fullCmp.substring(2); - try { - compareTo = Integer.parseInt(strCmpTo); - } catch (final Exception ignored) { - compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), sa); - } - } - - int left = AbilityUtils.calculateAmount(host, svarToCheck, sa); - if (!Expressions.compare(left, comparator, compareTo)) { - return AiPlayDecision.AnotherTime; - } + if (sa.hasParam("AICheckSVar") && !aiShouldRun(sa, sa, host, null)) { + return AiPlayDecision.AnotherTime; } // this is the "heaviest" check, which also sets up targets, defines X, etc. @@ -1817,14 +1798,9 @@ public class AiController { * @param sa the sa * @return true, if successful */ - public final boolean aiShouldRun(final ReplacementEffect effect, final SpellAbility sa, GameEntity affected) { - Card hostCard = effect.getHostCard(); - if (hostCard.hasAlternateState()) { - hostCard = game.getCardState(hostCard); - } - + public final boolean aiShouldRun(final CardTraitBase effect, final SpellAbility sa, final Card host, final GameEntity affected) { if (effect.hasParam("AILogic") && effect.getParam("AILogic").equalsIgnoreCase("ProtectFriendly")) { - final Player controller = hostCard.getController(); + final Player controller = host.getController(); if (affected instanceof Player) { return !((Player) affected).isOpponentOf(controller); } @@ -1833,7 +1809,6 @@ public class AiController { } } if (effect.hasParam("AICheckSVar")) { - System.out.println("aiShouldRun?" + sa); final String svarToCheck = effect.getParam("AICheckSVar"); String comparator = "GE"; int compareTo = 1; @@ -1846,9 +1821,9 @@ public class AiController { compareTo = Integer.parseInt(strCmpTo); } catch (final Exception ignored) { if (sa == null) { - compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), effect); + compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), effect); } else { - compareTo = AbilityUtils.calculateAmount(hostCard, hostCard.getSVar(strCmpTo), sa); + compareTo = AbilityUtils.calculateAmount(host, host.getSVar(strCmpTo), sa); } } } @@ -1856,13 +1831,12 @@ public class AiController { int left = 0; if (sa == null) { - left = AbilityUtils.calculateAmount(hostCard, svarToCheck, effect); + left = AbilityUtils.calculateAmount(host, svarToCheck, effect); } else { - left = AbilityUtils.calculateAmount(hostCard, svarToCheck, sa); + left = AbilityUtils.calculateAmount(host, svarToCheck, sa); } - System.out.println("aiShouldRun?" + left + comparator + compareTo); return Expressions.compare(left, comparator, compareTo); - } else if (effect.hasParam("AICheckDredge")) { + } else if (effect.isKeyword(Keyword.DREDGE)) { return player.getCardsIn(ZoneType.Library).size() > 8 || player.isCardInPlay("Laboratory Maniac"); } else return sa != null && doTrigger(sa, false); } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 1b94140f717..81bc75db880 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -460,7 +460,11 @@ public class PlayerControllerAi extends PlayerController { @Override public boolean confirmReplacementEffect(ReplacementEffect replacementEffect, SpellAbility effectSA, GameEntity affected, String question) { - return brains.aiShouldRun(replacementEffect, effectSA, affected); + Card host = replacementEffect.getHostCard(); + if (host.hasAlternateState()) { + host = host.getGame().getCardState(host); + } + return brains.aiShouldRun(replacementEffect, effectSA, host, affected); } @Override diff --git a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java index 942bde1491f..b7199681f47 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CloneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CloneAi.java @@ -96,6 +96,10 @@ public class CloneAi extends SpellAbilityAi { if (sa.usesTargeting()) { chance = cloneTgtAI(sa); } else { + if (sa.isReplacementAbility() && host.isCloned()) { + // prevent StackOverflow from infinite loop copying another ETB RE + return new AiAbilityDecision(0, AiPlayDecision.StopRunawayActivations); + } if (sa.hasParam("Choices")) { CardCollectionView choices = CardLists.getValidCards(host.getGame().getCardsIn(ZoneType.Battlefield), sa.getParam("Choices"), host.getController(), host, sa); diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index e8c18354697..77dfc9c9443 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -92,9 +92,8 @@ public class CountersPutAi extends CountersAi { return false; } return chance > MyRandom.getRandom().nextFloat(); - } else { - return false; } + return false; } if (sa.isKeyword(Keyword.LEVEL_UP)) { @@ -124,7 +123,6 @@ public class CountersPutAi extends CountersAi { final Cost abCost = sa.getPayCosts(); final Card source = sa.getHostCard(); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); - CardCollection list; Card choice = null; final String amountStr = sa.getParamOrDefault("CounterNum", "1"); final boolean divided = sa.isDividedAsYouChoose(); @@ -292,10 +290,8 @@ public class CountersPutAi extends CountersAi { if (willActivate) { return new AiAbilityDecision(100, AiPlayDecision.WillPlay); - } else { - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } - + return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } else if (logic.equals("ChargeToBestCMC")) { return doChargeToCMCLogic(ai, sa); } else if (logic.equals("ChargeToBestOppControlledCMC")) { @@ -348,7 +344,7 @@ public class CountersPutAi extends CountersAi { if (type.equals("P1P1")) { nPump = amount; } - return FightAi.canFightAi(ai, sa, nPump, nPump); + return FightAi.canFight(ai, sa, nPump, nPump); } if (amountStr.equals("X")) { @@ -451,6 +447,7 @@ public class CountersPutAi extends CountersAi { sa.resetTargets(); + CardCollection list; if (sa.isCurse()) { list = ai.getOpponents().getCardsIn(ZoneType.Battlefield); } else { @@ -746,7 +743,7 @@ public class CountersPutAi extends CountersAi { protected AiAbilityDecision doTriggerNoCost(Player ai, SpellAbility sa, boolean mandatory) { final SpellAbility root = sa.getRootAbility(); final Card source = sa.getHostCard(); - final String aiLogic = sa.getParamOrDefault("AILogic", ""); + final String aiLogic = sa.getParam("AILogic"); final String amountStr = sa.getParamOrDefault("CounterNum", "1"); final boolean divided = sa.isDividedAsYouChoose(); final int amount = AbilityUtils.calculateAmount(source, amountStr, sa); @@ -765,14 +762,10 @@ public class CountersPutAi extends CountersAi { } if ("ChargeToBestCMC".equals(aiLogic)) { - AiAbilityDecision decision = doChargeToCMCLogic(ai, sa); - if (decision.willingToPlay()) { - return decision; - } if (mandatory) { return new AiAbilityDecision(50, AiPlayDecision.MandatoryPlay); } - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); + return doChargeToCMCLogic(ai, sa); } if (!sa.usesTargeting()) { @@ -796,7 +789,6 @@ public class CountersPutAi extends CountersAi { // things like Powder Keg, which are way too complex for the AI } } else if (sa.getTargetRestrictions().canOnlyTgtOpponent() && !sa.getTargetRestrictions().canTgtCreature()) { - // can only target opponent PlayerCollection playerList = new PlayerCollection(IterableUtil.filter( sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class)); @@ -811,13 +803,12 @@ public class CountersPutAi extends CountersAi { sa.getTargets().add(choice); } } else { - String logic = sa.getParam("AILogic"); - if ("Fight".equals(logic) || "PowerDmg".equals(logic)) { + if ("Fight".equals(aiLogic) || "PowerDmg".equals(aiLogic)) { int nPump = 0; if (type.equals("P1P1")) { nPump = amount; } - AiAbilityDecision decision = FightAi.canFightAi(ai, sa, nPump, nPump); + AiAbilityDecision decision = FightAi.canFight(ai, sa, nPump, nPump); if (decision.willingToPlay()) { return decision; } @@ -838,7 +829,6 @@ public class CountersPutAi extends CountersAi { while (sa.canAddMoreTarget()) { if (mandatory) { - // When things are mandatory, gotta handle a little differently if ((list.isEmpty() || !preferred) && sa.isTargetNumberValid()) { return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed); } @@ -863,7 +853,7 @@ public class CountersPutAi extends CountersAi { return new AiAbilityDecision(sa.isTargetNumberValid() ? 100 : 0, sa.isTargetNumberValid() ? AiPlayDecision.WillPlay : AiPlayDecision.CantPlayAi); } - Card choice = null; + Card choice; // Choose targets here: if (sa.isCurse()) { @@ -889,10 +879,10 @@ public class CountersPutAi extends CountersAi { choice = Aggregates.random(list); } if (choice != null && divided) { - int alloc = Math.max(amount / totalTargets, 1); if (sa.getTargets().size() == Math.min(totalTargets, sa.getMaxTargets()) - 1) { sa.addDividedAllocation(choice, left); } else { + int alloc = Math.max(amount / totalTargets, 1); sa.addDividedAllocation(choice, alloc); left -= alloc; } @@ -982,9 +972,7 @@ public class CountersPutAi extends CountersAi { final String amountStr = sa.getParamOrDefault("CounterNum", "1"); final int amount = AbilityUtils.calculateAmount(sa.getHostCard(), amountStr, sa); - final boolean isCurse = sa.isCurse(); - - if (isCurse) { + if (sa.isCurse()) { final CardCollection opponents = CardLists.filterControlledBy(options, ai.getOpponents()); if (!opponents.isEmpty()) { @@ -1210,9 +1198,8 @@ public class CountersPutAi extends CountersAi { } if (numCtrs < optimalCMC) { return new AiAbilityDecision(100, AiPlayDecision.WillPlay); - } else { - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } + return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } private AiAbilityDecision doChargeToOppCtrlCMCLogic(Player ai, SpellAbility sa) { 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 746a5b7623f..8e7b0d073bc 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java @@ -270,7 +270,7 @@ public class EffectAi extends SpellAbilityAi { } return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } else if (logic.equals("Fight")) { - return FightAi.canFightAi(ai, sa, 0,0); + return FightAi.canFight(ai, sa, 0,0); } else if (logic.equals("Pump")) { sa.resetTargets(); List options = CardUtil.getValidCardsToTarget(sa); diff --git a/forge-ai/src/main/java/forge/ai/ability/FightAi.java b/forge-ai/src/main/java/forge/ai/ability/FightAi.java index 083aa8c155b..ff15bb89907 100644 --- a/forge-ai/src/main/java/forge/ai/ability/FightAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/FightAi.java @@ -177,7 +177,7 @@ public class FightAi extends SpellAbilityAi { * @param power bonus to power * @return true if fight effect should be played, false otherwise */ - public static AiAbilityDecision canFightAi(final Player ai, final SpellAbility sa, int power, int toughness) { + public static AiAbilityDecision canFight(final Player ai, final SpellAbility sa, int power, int toughness) { final Card source = sa.getHostCard(); final String sourceName = ComputerUtilAbility.getAbilitySourceName(sa); AbilitySub tgtFight = sa.getSubAbility(); 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 3c0cec2634d..bc7c4f8431e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -453,7 +453,7 @@ public class PumpAi extends PumpAiBase { } if (isFight) { - return FightAi.canFightAi(ai, sa, attack, defense).willingToPlay(); + return FightAi.canFight(ai, sa, attack, defense).willingToPlay(); } } 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 a7ca54bd0e2..954c833b7ab 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 @@ -269,22 +269,22 @@ public class EffectEffect extends SpellAbilityEffect { } } - // Set Chosen Color(s) if (hostCard.hasChosenColor()) { eff.setChosenColors(Lists.newArrayList(hostCard.getChosenColors())); } - // Set Chosen Cards if (hostCard.hasChosenCard()) { eff.setChosenCards(hostCard.getChosenCards()); } - // Set Chosen Player if (hostCard.hasChosenPlayer()) { eff.setChosenPlayer(hostCard.getChosenPlayer()); } - // Set Chosen Type + if (hostCard.getChosenDirection() != null) { + eff.setChosenDirection(hostCard.getChosenDirection()); + } + if (hostCard.hasChosenType()) { eff.setChosenType(hostCard.getChosenType()); } @@ -292,12 +292,10 @@ public class EffectEffect extends SpellAbilityEffect { eff.setChosenType2(hostCard.getChosenType2()); } - // Set Chosen name if (hostCard.hasNamedCard()) { eff.setNamedCards(Lists.newArrayList(hostCard.getNamedCards())); } - // chosen number if (sa.hasParam("SetChosenNumber")) { eff.setChosenNumber(AbilityUtils.calculateAmount(hostCard, sa.getParam("SetChosenNumber"), sa)); } else if (hostCard.hasChosenNumber()) { 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 98a32ff3b00..f5a3c133177 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -2238,7 +2238,7 @@ public class CardFactoryUtil { final String actualRep = "Event$ Draw | ActiveZones$ Graveyard | ValidPlayer$ You | " + "Secondary$ True | Optional$ True | CheckSVar$ " + "DredgeCheckLib | SVarCompare$ GE" + dredgeAmount - + " | AICheckDredge$ True | Description$ CARDNAME - Dredge " + dredgeAmount; + + " | Description$ CARDNAME - Dredge " + dredgeAmount; final String abString = "DB$ Mill | Defined$ You | NumCards$ " + dredgeAmount; diff --git a/forge-gui/res/cardsfolder/s/samite_ministration.txt b/forge-gui/res/cardsfolder/s/samite_ministration.txt index dbd342c8b59..367118617af 100644 --- a/forge-gui/res/cardsfolder/s/samite_ministration.txt +++ b/forge-gui/res/cardsfolder/s/samite_ministration.txt @@ -3,11 +3,9 @@ ManaCost:1 W Types:Instant A:SP$ ChooseSource | Choices$ Card,Emblem | AILogic$ NeedsPrevention | SubAbility$ DBEffect | StackDescription$ SpellDescription | SpellDescription$ Prevent all damage that would be dealt to you this turn by a source of your choice. Whenever damage from a black or red source is prevented this way this turn, you gain that much life. SVar:DBEffect:DB$ Effect | ReplacementEffects$ RepDmg | ConditionDefined$ ChosenCard | ConditionPresent$ Card,Emblem | ConditionCompare$ GE1 -SVar:RepDmg:Event$ DamageDone | ValidTarget$ You | ValidSource$ Card.ChosenCardStrict,Emblem.ChosenCard | ReplaceWith$ DBStoreSVar | PreventionEffect$ True | Description$ Prevent all damage that would be dealt to you this turn by a source of your choice. Whenever damage from a black or red source is prevented this way this turn, you gain that much life. -SVar:DBStoreSVar:DB$ StoreSVar | SVar$ Z | Type$ Calculate | Expression$ X | SubAbility$ DBTrigger +SVar:RepDmg:Event$ DamageDone | ValidTarget$ You | ValidSource$ Card.ChosenCardStrict,Emblem.ChosenCard | ReplaceWith$ DBTrigger | PreventionEffect$ True | Description$ Prevent all damage that would be dealt to you this turn by a source of your choice. Whenever damage from a black or red source is prevented this way this turn, you gain that much life. SVar:DBTrigger:DB$ ImmediateTrigger | Execute$ GainLifeYou | ConditionCheckSVar$ Y | ConditionSVarCompare$ GE1 | TriggerDescription$ Whenever damage from a black or red source is prevented this way this turn, you gain that much life. -SVar:GainLifeYou:DB$ GainLife | Defined$ You | LifeAmount$ Z -SVar:X:ReplaceCount$DamageAmount +SVar:GainLifeYou:DB$ GainLife | Defined$ You | LifeAmount$ X +SVar:X:Spawner>ReplaceCount$DamageAmount SVar:Y:ReplacedSource$Valid Card.BlackSource,Card.RedSource -SVar:Z:Number$0 Oracle:Prevent all damage that would be dealt to you this turn by a source of your choice. Whenever damage from a black or red source is prevented this way this turn, you gain that much life. diff --git a/forge-gui/res/cardsfolder/upcoming/atreus_impulsive_son.txt b/forge-gui/res/cardsfolder/upcoming/atreus_impulsive_son.txt index b6fc41bbc8d..c6a78a975db 100644 --- a/forge-gui/res/cardsfolder/upcoming/atreus_impulsive_son.txt +++ b/forge-gui/res/cardsfolder/upcoming/atreus_impulsive_son.txt @@ -4,7 +4,8 @@ Types:Legendary Creature God Archer PT:2/4 K:Reach K:Partner - Father & Son -A:AB$ Draw | Cost$ 3 T | NumCards$ X | SubAbility$ DBDamage | SpellDescription$ Draw a card for each experience counter you have, then discard a card. +A:AB$ Draw | Cost$ 3 T | NumCards$ X | SubAbility$ DBDiscard | SpellDescription$ Draw a card for each experience counter you have, then discard a card. +SVar:DBDiscard:DB$ Discard | Mode$ YouChoose | SubAbility$ DBDamage SVar:DBDamage:DB$ DealDamage | NumDmg$ 2 | Defined$ Opponent | SpellDescription$ CARDNAME deals 2 damage to each opponent. SVar:X:Count$YourCountersExperience Oracle:Reach\n{3}, {T}: Draw a card for each experience counter you have, then discard a card. Atreus, Impulsive Son deals 2 damage to each opponent.\nPartner-Father & son From 04fbe9fac3062b8f626cc01b35345e036aac1674 Mon Sep 17 00:00:00 2001 From: Paul Hammerton Date: Wed, 1 Oct 2025 18:12:16 +0100 Subject: [PATCH 062/230] Edition updates: ECL, FIC, FIN, SLD, SLP --- .../res/editions/Final Fantasy Commander.txt | 48 +++++++++++++- forge-gui/res/editions/Final Fantasy.txt | 8 +++ forge-gui/res/editions/Lorwyn Eclipsed.txt | 2 + .../res/editions/Secret Lair Drop Series.txt | 64 ++++++++++++++++--- .../res/editions/Secret Lair Showdown.txt | 3 + 5 files changed, 113 insertions(+), 12 deletions(-) diff --git a/forge-gui/res/editions/Final Fantasy Commander.txt b/forge-gui/res/editions/Final Fantasy Commander.txt index 27d4733e4ee..38511d0b529 100644 --- a/forge-gui/res/editions/Final Fantasy Commander.txt +++ b/forge-gui/res/editions/Final Fantasy Commander.txt @@ -202,7 +202,7 @@ ScryfallCode=FIC 194 R Summon: Good King Mog XII @Minoru 195 R Summon: Ixion @Yoshiya 196 R Summon: Yojimbo @Hisashi Momose -197 R Summon: Valefor @SENNSU +197 R Summon: Valefor @Maiko Aoji 198 R Summon: Esper Valigarmanda @TAKUMI 199 R Summon: Kujata @Yoshiya 200 R Summon: Magus Sisters @Karo ARAI @@ -254,7 +254,7 @@ ScryfallCode=FIC 246 M Luminous Broodmoth @Kazto Furuya 247 U Palace Jailer @Touge369 248 U Path to Exile @Yumi Yaoshida -249 R Promise of Loyalty @Lack +249 R Promise of Loyalty @KOHEI YAMADA 250 R Puresteel Paladin @Ignatius Budi 251 R Resourceful Defense @Shiyu 252 R Scholar of New Horizons @Allen Morris @@ -340,7 +340,7 @@ ScryfallCode=FIC 332 C Arcane Signet @Grace Zhu 333 C Arcane Signet @Randy Gallegos 334 C Arcane Signet @Russell Lu -335 C Arcane Signet @Russell Lu +335 C Arcane Signet @Madeline Boni 336 R Armory Automaton @Gas1 337 R Champion's Helm @Russell Lu 338 U Colossus Hammer @Kevin Sidharta @@ -447,6 +447,48 @@ ScryfallCode=FIC 439 R Underground River @Shahab Alizadeh 440 R Vineglimmer Snarl @Mauricio Calle 441 C Wooded Ridgeline @Hristo D. Chukov +442 R Garland, Royal Kidnapper @David Rapoza +443 R The Destined Warrior @David Rapoza +444 R The Destined White Mage @David Rapoza +445 R Chaos Shrine's Black Crystal @David Rapoza +446 R The Destined Thief @David Rapoza +447 R The Destined Black Mage @David Rapoza +448 R Edea, Possessed Sorceress @Lius Lasahido +449 R Fated Clash @Lius Lasahido +450 R Rinoa, Angel Wing @Lius Lasahido +451 R Seifer, Balamb Rival @Lius Lasahido +452 R Duelist's Flame @Lius Lasahido +453 R Squall, Gunblade Duelist @Lius Lasahido +454 R Brilliant Wings @Erion Makuo +455 R Judgment of Alexander @Erion Makuo +456 R Mega Flare @Erion Makuo +457 R Amarant Coral @Erion Makuo +458 R Vivi's Persistence @Erion Makuo +459 R Search for Dagger @Erion Makuo +460 R Noctis, Heir Apparent @Winona Nelson +461 R Fishing Gear @Winona Nelson +462 R Chocobo Camp @Winona Nelson +463 R Flash Photography @Winona Nelson +464 R Campsite Cuisine @Winona Nelson +465 R Warrior's Resolve @Winona Nelson +466 R Tataru Taru @Domco. +467 R Lulu, Stern Guardian @Hiromu +468 R Rikku, Resourceful Guardian @Ayuko +469 M Snapcaster Mage @Canata Katana +470 R Yuffie, Materia Hunter @Nijihayashi +471 R Aerith, Last Ancient @MiDQN +472 R Barret, Avalanche Leader @Dai-XT +473 R Emet-Selch of the Third Seat @Kuregure +474 R Estinien Varlineau @Yakotakos +475 R Locke, Treasure Hunter @Kato Ayaka +476 R Mog, Moogle Warrior @Rorubei +477 R Wakka, Devoted Guardian @Dai-XT +478 L Plains @Fuzichoco +479 L Island @Ai Nanahira +480 L Swamp @Ryuichi Sakuma +481 L Mountain @Nao Miyoshi +482 L Forest @ZOUNOSE +483 R Birds of Paradise @Takotto 484 C Command Tower @Anthony Devine 485 C Command Tower @Eduardo Francisco 486 C Command Tower @Jonas De Ro diff --git a/forge-gui/res/editions/Final Fantasy.txt b/forge-gui/res/editions/Final Fantasy.txt index c9d4e536a78..b43e27275d0 100644 --- a/forge-gui/res/editions/Final Fantasy.txt +++ b/forge-gui/res/editions/Final Fantasy.txt @@ -631,6 +631,14 @@ Replace=.18F BasicLand:fromSheet("FIN cards"):!fromSheet("FIN dual lands")+ 551c M Traveling Chocobo @Toni Infante 551d M Traveling Chocobo @Toni Infante 551f M Traveling Chocobo @Toni Infante +564 M Cloud, Midgar Mercenary @Inuchiyo Meimaru +565 R Stiltzkin, Moogle Merchant @Yukihiro Maruo +566 R A Realm Reborn @Susumu Kuroi +567 R Tifa Lockhart @Yoshiro Ambe +568 M Traveling Chocobo @Ishikawa Kenta +569 R Choco, Seeker of Paradise @Kemonomichi +570 M Vivi Ornitier @Ryanroro +571 M Yuna, Hope of Spira @Yuichi Murakami 572 L Plains @Jonas De Ro 573 L Island @Eddie Mendoza 574 L Swamp @Domenico Cava diff --git a/forge-gui/res/editions/Lorwyn Eclipsed.txt b/forge-gui/res/editions/Lorwyn Eclipsed.txt index 7018bb2668d..f33b3f8df99 100644 --- a/forge-gui/res/editions/Lorwyn Eclipsed.txt +++ b/forge-gui/res/editions/Lorwyn Eclipsed.txt @@ -11,6 +11,7 @@ ScryfallCode=ECL 76 R Sygg, Wanderwine Wisdom @Justin Gerard 88 M Bitterbloom Bearer @Chris Rahn 124 R Ashling, Rekindled @Ilse Gort +176 R Formidable Speaker @Aurore Folny 186 R Mutable Explorer @Wayne Reynolds 205 R Ashling's Command @Iris Compiet 212 M Deceit @Svetlin Velinov @@ -41,4 +42,5 @@ ScryfallCode=ECL 351 R Temple Garden @Annie Stegg 351☇ R Temple Garden @Annie Stegg 352 M Bitterbloom Bearer @Rebecca Guay +366 R Formidable Speaker @Aurore Folny 372 R Figure of Fable @Omar Rayyan diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 7414e0e9e27..e54fd203057 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -1180,7 +1180,7 @@ F869 R Blacker Lotus @Scott Okumura 1305 M Worldspine Wurm @Ryan Alexander Lee 1307 R Pack Rat @Leonardo Santanna 1308 R Shared Summons @Leonardo Santanna -1309 R Sylvan Offering @Igor Krstic +1309 R Sylvan Offering @Jodie Muir 1310 M Sliver Legion @Igor Krstic 1311 R Goblin Lackey @Wizard of Barge 1311★ R Goblin Lackey @Wizard of Barge @@ -1497,7 +1497,7 @@ F1540 M Rainbow Dash @John Thacker 1548★ R Simian Spirit Guide @Graham Yarrington 1549 M Prince of Thralls @Graham Yarrington 1549★ M Prince of Thralls @Graham Yarrington -1550 R Sun Titan @Jesper Ejsing +1550 M Sun Titan @Jesper Ejsing 1551 R Breeches, Eager Pillager @Jesper Ejsing 1552 R Deflecting Swat @Jesper Ejsing 1553 R Llanowar Elves @Jesper Ejsing @@ -1959,7 +1959,7 @@ F1540 M Rainbow Dash @John Thacker 1983 R Aggravated Assault @Frank Frazetta 1984 R Desperate Ritual @Frank Frazetta 1985 R Deadly Dispute @Terry Dodson -1986 M Go for the Throat @Kael Ngu +1986 R Go for the Throat @Kael Ngu 1987 R Lightning Greaves @Todd Nauck 1988 R Sol Ring @Roberta Ingranata 1989 R Command Tower @Roberta Ingranata @@ -1988,11 +1988,11 @@ F1540 M Rainbow Dash @John Thacker 2016 R Harrow @Jordan Crane 2017 R World Shaper @Jordan Crane 2018 R Horn of Greed @Jordan Crane -2019 R Damnation @Mark Bagley & Jay David Ramos -2020 R Dark Ritual @Mark Bagley & Jay David Ramos -2021 R Peer into the Abyss @Mark Bagley & Jay David Ramos -2022 R Surgical Extraction @Mark Bagley & Jay David Ramos -2023 R Tendrils of Agony @Mark Bagley & Jay David Ramos +2019 R Damnation @Mark Bagley +2020 R Dark Ritual @Mark Bagley +2021 R Peer into the Abyss @Mark Bagley +2022 R Surgical Extraction @Mark Bagley +2023 R Tendrils of Agony @Mark Bagley 2024 R Goblin Bombardment @Phil Foglio 2025 R Orcish Lumberjack @Phil Foglio 2026 R Constant Mists @Phil Foglio @@ -2078,6 +2078,27 @@ F1540 M Rainbow Dash @John Thacker 2141 R Magical Hack @Samuele Bandini 2142 R Memory Lapse @Wylie Beckert 2143 R Mystic Sanctuary @Amélie Flechais +2144 R Island @Kelogsloops +2145 R Island @Kelogsloops +2146 R Island @Kelogsloops +2147 R Island @Kelogsloops +2148 C Brainstorm @DiTerlizzi +2149 M Capture of Jingzhou @Jack Wei +2150 U Chart a Course @Olena Richards +2151 U Control Magic @Clint Cearley +2152 R Crystal Spray @Jeff Miracola +2153 R Day's Undoing @Jonas De Ro +2154 C Mental Note @Bradley Williams +2155 U Metamorphose @Ron Spencer +2156 U Predict @Rebecca Guay +2157 U Telling Time @Scott M. Fischer +2158 U Unsubstantiate @Victor Adame Minguez +2159 C Halimar Depths @Volkan Baǵa +2160 C Haunted Fengraf @Adam Paquette +2161 C Lonely Sandbar @Heather Hudson +2162 C Remote Isle @Ciruelo +2163 C The Surgical Bay @Sarah Finnigan +2164 U Svyelunite Temple @Liz Danforth 2165 M Heliod, Sun-Crowned @Magali Villeneuve 2166 R Steelshaper's Gift @Greg Staples 2167 R Swords to Plowshares @Lie Setiawan @@ -2102,7 +2123,7 @@ F1540 M Rainbow Dash @John Thacker 2187 R Lethal Scheme @Iron Maiden 2188 M Grave Titan @Iron Maiden 2189 R Animate Dead @Iron Maiden -2190 M Temporal Tresspass @Iron Maiden +2190 M Temporal Trespass @Iron Maiden 2191 R Unearth @Iron Maiden 2192 R Lignify @Iron Maiden 2193 M Greensleeves, Maro-Sorcerer @Jason Loik & Matthew Cohen @@ -2111,14 +2132,36 @@ F1540 M Rainbow Dash @John Thacker 2196 M Wurmcoil Engine @Jason Loik & Matthew Cohen 2197 M Ellie, Brick Master @Irvin Rodriguez 2198 M Joel, Resolute Survivor @Yongjae Choi +2199 R Cabal Ritual @Bastien L. Deharme +2200 M Haunted One @Bastien L. Deharme 2202 M Abby, Merciless Soldier @Wayne Wu 2203 M Ellie, Vengeful Hunter @Irvin Rodriguez +2204 R Dictate of Erebos @Bastien L. Deharme +2205 R Mycoloth @Yongjae Choi 2207 M Kratos, God of War @Magali Villeneuve +2208 R World at War @Lie Setiawan +2209 R Rite of Flame @Johan Grenier +2210 R Sulfuric Vortex @Aleksi Briclot +2211 R Pyrohemia @Chris Rahn 2212 M Atreus, Impulsive Son @Nathaniel Himawan 2213 M Kratos, Stoic Father @Nathaniel Himawan +2214 R Teferi's Ageless Insight @Aleksi Briclot & Alix Branwyn +2215 M Iroas, God of Victory @Joshua Raphael 2216 M Nathan Drake, Treasure Hunter @Piotr Dura +2217 R Midnight Clock @Nereida +2218 R Whip of Erebos @Justyna Dura +2219 R Chain Reaction @Lie Setiawan +2220 M Passionate Archaeologist @Justyna Dura 2221 M Aloy, Savior of Meridian @Crystal Fae +2222 R Farseek @Toni Infante +2223 M Blightsteel Colossus @Narendra Bintara Adi +2224 R Tarrian's Soulcleaver @Gaboleps +2225 R Meteor Golem @Narendra Bintara Adi 2226 M Jin Sakai, Ghost of Tsushima @Dominik Mayer +2227 R Path to Exile @Dominik Mayer +2228 R Borne Upon a Wind @Matteo Bassini +2229 R Ghostly Flicker @Dominik Mayer +2230 R Eiganjo Castle @Matteo Bassini 2282 R Vito, Thorn of the Dusk Rose @Sam Heimer 2283 R Satoru Umezawa @Sam Heimer 2284 M Voja, Jaws of the Conclave @Ryan Roadkill @@ -2167,6 +2210,7 @@ F1540 M Rainbow Dash @John Thacker 7035 M Lotus Petal @Mike Burns 7036 M Lotus Petal @Mike Burns 7037 M Lotus Petal @Mike Burns +7040 R Vision Charm @ 8001 M Jace, the Mind Sculptor @Wizard of Barge 9990 R Doom Blade @Cynthia Sheppard 9991 R Massacre @Andrey Kuzinskiy @@ -2232,3 +2276,5 @@ VS C Viscera Seer @John Stanko 2094 c_a_treasure_sac @John Thacker 2101 c_1_1_a_myr @Caleb Meurer 2180 c_a_blood_draw @Stephen Andrade +2201 cordyceps_infected @Wayne Wu +2206 cordyceps_infected @Irvin Rodriguez diff --git a/forge-gui/res/editions/Secret Lair Showdown.txt b/forge-gui/res/editions/Secret Lair Showdown.txt index 585caaef160..dda2aafd701 100644 --- a/forge-gui/res/editions/Secret Lair Showdown.txt +++ b/forge-gui/res/editions/Secret Lair Showdown.txt @@ -49,3 +49,6 @@ ScryfallCode=SLP 42 M Garruk Wildspeaker @Merwan Chabane 43 R Shoot the Sheriff @Dan Black 44 R Laughing Jasper Flint @Ed Repka +45 R Innkeeper's Talent @Ben Newman +46 R Flowerfoot Swordmaster @Ampreh +47 R Manifold Mouse @Neo.G From 27a0a8f791f303f18eaa3f4b657add09d54d96eb Mon Sep 17 00:00:00 2001 From: tool4ever Date: Thu, 2 Oct 2025 19:48:54 +0000 Subject: [PATCH 063/230] Update the_spot_living_portal.txt Closes #8819 --- forge-gui/res/cardsfolder/t/the_spot_living_portal.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/t/the_spot_living_portal.txt b/forge-gui/res/cardsfolder/t/the_spot_living_portal.txt index aaf1d828e5c..307691abbcf 100644 --- a/forge-gui/res/cardsfolder/t/the_spot_living_portal.txt +++ b/forge-gui/res/cardsfolder/t/the_spot_living_portal.txt @@ -8,6 +8,6 @@ SVar:DBMarkGraveyard:DB$ Pump | ValidTgts$ Permanent.nonLand | TgtZone$ Graveyar SVar:DBExile:DB$ ChangeZone | Origin$ Battlefield,Graveyard | Destination$ Exile | Defined$ Targeted T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When NICKNAME dies, put him on the bottom of his owner's library. If you do, return the exiled cards to their owners' hands. SVar:TrigChangeZone:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Library | LibraryPosition$ -1 | RememberChanged$ True | SubAbility$ DBChangeZone -SVar:DBChangeZone:DB$ ChangeZone | Defined$ Card.ExiledWithSource | Origin$ Exile | Destination$ Hand | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup +SVar:DBChangeZone:DB$ ChangeZone | Defined$ ExiledWith | Origin$ Exile | Destination$ Hand | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:When The Spot enters, exile up to one target nonland permanent and up to one target nonland permanent card from a graveyard.\nWhen The Spot dies, put him on the bottom of his owner's library. If you do, return the exiled cards to their owners' hands. From d46d7300b4fc722392994ecc7f8fe779440cad09 Mon Sep 17 00:00:00 2001 From: Chris H Date: Thu, 2 Oct 2025 23:37:06 -0400 Subject: [PATCH 064/230] Reduce cost of Bird creature token spell --- forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt b/forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt index 80a452c8ac7..f92f1baff6d 100644 --- a/forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt +++ b/forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt @@ -10,7 +10,7 @@ SVar:SpellCastTrig:Mode$ SpellCast | ValidCard$ Bird | ValidActivatingPlayer$ Yo SVar:TrigCounter:DB$ Effect | ReplacementEffects$ ETBCounters | RememberObjects$ TriggeredCard | ForgetOnMoved$ Stack SVar:ETBCounters:Event$ Moved | Origin$ Stack | Destination$ Battlefield | ValidCard$ Card.IsRemembered | ReplaceWith$ ETBAddExtraCounter | ReplacementResult$ Updated | Description$ That creature enters with an additional +1/+1 counters on it. SVar:ETBAddExtraCounter:DB$ PutCounter | ETB$ True | Defined$ ReplacedCard | CounterType$ P1P1 | CounterNum$ 1 -A:AB$ Token | Cost$ 4 G G T | TokenScript$ g_2_2_bird_landfall | SpellDescription$ Creature a 2/2 green Bird creature token with "Whenever a land you control enters, this token gets +1/+0 until end of turn". +A:AB$ Token | Cost$ 2 G G T | TokenScript$ g_2_2_bird_landfall | SpellDescription$ Creature a 2/2 green Bird creature token with "Whenever a land you control enters, this token gets +1/+0 until end of turn". DeckHas:Ability$Token & Type$Artifact|Bird DeckHints:Type$Legendary -Oracle:This Land enters tapped unless you control a legendary creature.\n{T}: Add {G}. When you next cast a Bird creature spell this turn, it enters with an additional +1/+1 counter on it.\n{4}{G}{G}, {T}, Creature a 2/2 green Bird creature token with "Whenever a land you control enters, this token gets +1/+0 until end of turn". +Oracle:This Land enters tapped unless you control a legendary creature.\n{T}: Add {G}. When you next cast a Bird creature spell this turn, it enters with an additional +1/+1 counter on it.\n{2}{G}{G}, {T}, Creature a 2/2 green Bird creature token with "Whenever a land you control enters, this token gets +1/+0 until end of turn". From 50c7188e0410240444408e46a195a4ff2b430aaa Mon Sep 17 00:00:00 2001 From: squee1968 Date: Fri, 3 Oct 2025 13:54:02 -0500 Subject: [PATCH 065/230] Update chocobo_camp.txt --- forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt b/forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt index f92f1baff6d..ffa79a2783f 100644 --- a/forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt +++ b/forge-gui/res/cardsfolder/upcoming/chocobo_camp.txt @@ -1,7 +1,6 @@ Name:Chocobo Camp ManaCost:no cost Types:Land -Types:Legendary Land R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ LandTapped | ReplacementResult$ Updated | Description$ CARDNAME enters tapped unless you control a legendary creature. SVar:LandTapped:DB$ Tap | Defined$ Self | ETB$ True | ConditionPresent$ Creature.Legendary+YouCtrl | ConditionCompare$ EQ0 A:AB$ Mana | Cost$ T | Produced$ G | SubAbility$ DBEffect | SpellDescription$ Add {G}. From 183100fbdcc353fff687e0c20e1c9bd5824cfa9d Mon Sep 17 00:00:00 2001 From: tool4ever Date: Fri, 3 Oct 2025 20:16:57 +0000 Subject: [PATCH 066/230] Update soul_collector.txt --- forge-gui/res/cardsfolder/s/soul_collector.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/s/soul_collector.txt b/forge-gui/res/cardsfolder/s/soul_collector.txt index 7be64f067d7..d076bac7562 100644 --- a/forge-gui/res/cardsfolder/s/soul_collector.txt +++ b/forge-gui/res/cardsfolder/s/soul_collector.txt @@ -5,5 +5,5 @@ PT:3/4 K:Flying K:Morph:B B B T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.DamagedBy | Execute$ TrigBounce | TriggerDescription$ Whenever a creature dealt damage by CARDNAME this turn dies, return that card to the battlefield under your control. -SVar:TrigBounce:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | Defined$ TriggeredCardLKICopy +SVar:TrigBounce:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | Defined$ TriggeredNewCardLKICopy Oracle:Flying\nWhenever a creature dealt damage by Soul Collector this turn dies, return that card to the battlefield under your control.\nMorph {B}{B}{B} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its morph cost.) From 46df4cdb0d282918dee53335e4b68052b544f69b Mon Sep 17 00:00:00 2001 From: Paul Hammerton Date: Fri, 3 Oct 2025 21:28:30 +0100 Subject: [PATCH 067/230] Edition updates: SLD --- forge-gui/res/editions/Secret Lair Drop Series.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index e54fd203057..00a50154195 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -2146,6 +2146,7 @@ F1540 M Rainbow Dash @John Thacker 2212 M Atreus, Impulsive Son @Nathaniel Himawan 2213 M Kratos, Stoic Father @Nathaniel Himawan 2214 R Teferi's Ageless Insight @Aleksi Briclot & Alix Branwyn +2214☇ R Teferi's Ageless Insight @Aleksi Briclot & Alix Branwyn 2215 M Iroas, God of Victory @Joshua Raphael 2216 M Nathan Drake, Treasure Hunter @Piotr Dura 2217 R Midnight Clock @Nereida From a2b324c9ce12bc3a3ca54fbaf11c42439a3f94eb Mon Sep 17 00:00:00 2001 From: kevlahnota Date: Sat, 4 Oct 2025 07:50:11 +0800 Subject: [PATCH 068/230] prevent NPE VAvatar.java --- forge-gui-mobile/src/forge/screens/match/views/VAvatar.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui-mobile/src/forge/screens/match/views/VAvatar.java b/forge-gui-mobile/src/forge/screens/match/views/VAvatar.java index 424f0ccfddd..0cd8d1da1d6 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VAvatar.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VAvatar.java @@ -124,7 +124,7 @@ public class VAvatar extends FDisplayObject { float w = isHovered() ? getWidth()/16f+getWidth() : getWidth(); float h = isHovered() ? getWidth()/16f+getHeight() : getHeight(); - if (avatarAnimation != null && !MatchController.instance.getGameView().isMatchOver()) { + if (avatarAnimation != null && MatchController.instance.getGameView() != null && !MatchController.instance.getGameView().isMatchOver()) { if (player.wasAvatarLifeChanged()) { avatarAnimation.start(); avatarAnimation.drawAvatar(g, image, 0, 0, w, h); From 091c61829bc9feb9f465e736c61630dcb012abcc Mon Sep 17 00:00:00 2001 From: Eradev Date: Sat, 4 Oct 2025 03:56:40 -0400 Subject: [PATCH 069/230] Fix Div by zero error --- .../src/main/java/forge/gamemodes/quest/QuestUtilCards.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestUtilCards.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestUtilCards.java index 853751014e3..1d4e8efbc8c 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestUtilCards.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestUtilCards.java @@ -792,6 +792,11 @@ public final class QuestUtilCards { Predicate filter = PaperCardPredicates.printedInSet(edition); Iterable editionCards = IterableUtil.filter(FModel.getMagicDb().getCommonCards().getAllCards(), filter); + // For editions such as MB1 which only contains PLST cards. + if (!editionCards.iterator().hasNext()) { + return 0; + } + ItemPool ownedCards = questAssets.getCardPool(); // 100% means at least one of every basic land and at least 4 of every other card in the set int completeCards = 0; From 66a8e1f04f14736ce7f94b7c3df21fd0d2654f68 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sat, 4 Oct 2025 13:30:21 +0200 Subject: [PATCH 070/230] Fix Henzie + Gorex (#8829) --- forge-game/src/main/java/forge/game/GameAction.java | 8 +++++--- .../java/forge/game/ability/SpellAbilityEffect.java | 2 +- forge-game/src/main/java/forge/game/cost/Cost.java | 10 +++++----- forge-gui/res/cardsfolder/a/aladdins_lamp.txt | 6 ++---- forge-gui/res/cardsfolder/d/droning_bureaucrats.txt | 6 ++---- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index dd35bf5ad53..0c985d805ea 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -1602,9 +1602,7 @@ public class GameAction { } // recheck the game over condition at this point to make sure no other win conditions apply now. - if (!game.isGameOver()) { - checkGameOverCondition(); - } + checkGameOverCondition(); if (game.getAge() != GameStage.Play) { return false; @@ -1885,6 +1883,10 @@ public class GameAction { } public void checkGameOverCondition() { + if (game.isGameOver()) { + return; + } + // award loses as SBE GameEndReason reason = null; List losers = null; 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 96bc1c9c6e1..f9e8358fa23 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -1069,7 +1069,7 @@ public abstract class SpellAbilityEffect { // if ability was granted use that source so they can be kept apart later if (cause.isCopiedTrait()) { exilingSource = cause.getOriginalHost(); - } else if (cause.getKeyword() != null && cause.getKeyword().getStatic() != null) { + } else if (!cause.isSpell() && cause.getKeyword() != null && cause.getKeyword().getStatic() != null) { exilingSource = cause.getKeyword().getStatic().getOriginalHost(); } movedCard.setExiledWith(exilingSource); diff --git a/forge-game/src/main/java/forge/game/cost/Cost.java b/forge-game/src/main/java/forge/game/cost/Cost.java index dec565a9282..8cf784c6e7e 100644 --- a/forge-game/src/main/java/forge/game/cost/Cost.java +++ b/forge-game/src/main/java/forge/game/cost/Cost.java @@ -237,17 +237,17 @@ public class Cost implements Serializable { CostPartMana parsedMana = null; for (String part : parts) { if (part.startsWith("XMin")) { - xMin = (part); + xMin = part; } else if ("Mandatory".equals(part)) { this.isMandatory = true; } else { CostPart cp = parseCostPart(part, tapCost, untapCost); if (null != cp) - if (cp instanceof CostPartMana) { - parsedMana = (CostPartMana) cp; + if (cp instanceof CostPartMana p) { + parsedMana = p; } else { - if (cp instanceof CostPartWithList) { - ((CostPartWithList)cp).setIntrinsic(intrinsic); + if (cp instanceof CostPartWithList p) { + p.setIntrinsic(intrinsic); } this.costParts.add(cp); } diff --git a/forge-gui/res/cardsfolder/a/aladdins_lamp.txt b/forge-gui/res/cardsfolder/a/aladdins_lamp.txt index 98413ef0f7d..fbc2487a83d 100644 --- a/forge-gui/res/cardsfolder/a/aladdins_lamp.txt +++ b/forge-gui/res/cardsfolder/a/aladdins_lamp.txt @@ -1,14 +1,12 @@ Name:Aladdin's Lamp ManaCost:10 Types:Artifact -A:AB$ StoreSVar | Cost$ XMin1 X T | SVar$ DigNum | Type$ Count | Expression$ xPaid | SubAbility$ TheMagic | SpellDescription$ The next time you would draw a card this turn, instead look at the top X cards of your library, put all but one of them on the bottom of your library in a random order, then draw a card. X can't be 0. -SVar:TheMagic:DB$ Effect | Name$ Aladdin's Wish | ReplacementEffects$ DrawReplace +A:AB$ Effect | Cost$ XMin1 X T | Name$ Aladdin's Wish | ReplacementEffects$ DrawReplace | SetChosenNumber$ X | SpellDescription$ The next time you would draw a card this turn, instead look at the top X cards of your library, put all but one of them on the bottom of your library in a random order, then draw a card. X can't be 0. SVar:DrawReplace:Event$ Draw | ValidPlayer$ You | ReplaceWith$ AladdinDraw | Description$ The next time you would draw a card this turn, instead look at the top X cards of your library, put all but one of them on the bottom of your library in a random order, then draw a card. -SVar:AladdinDraw:DB$ Dig | DigNum$ DigNum | ChangeNum$ 1 | RestRandomOrder$ True | DestinationZone$ Library | LibraryPosition$ 0 | SubAbility$ DBDraw +SVar:AladdinDraw:DB$ Dig | DigNum$ Count$ChosenNumber | ChangeNum$ 1 | RestRandomOrder$ True | DestinationZone$ Library | LibraryPosition$ 0 | SubAbility$ DBDraw SVar:DBDraw:DB$ Draw | SubAbility$ ExileEffect SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile SVar:X:Count$xPaid -SVar:DigNum:Number$0 AI:RemoveDeck:Random AI:RemoveDeck:All Oracle:{X}, {T}: The next time you would draw a card this turn, instead look at the top X cards of your library, put all but one of them on the bottom of your library in a random order, then draw a card. X can't be 0. diff --git a/forge-gui/res/cardsfolder/d/droning_bureaucrats.txt b/forge-gui/res/cardsfolder/d/droning_bureaucrats.txt index 57d193f3a96..31de2a29884 100644 --- a/forge-gui/res/cardsfolder/d/droning_bureaucrats.txt +++ b/forge-gui/res/cardsfolder/d/droning_bureaucrats.txt @@ -2,10 +2,8 @@ Name:Droning Bureaucrats ManaCost:3 W Types:Creature Human Advisor PT:1/4 -A:AB$ StoreSVar | Cost$ X T | SVar$ DroningX | Type$ Count | Expression$ xPaid | SubAbility$ CreateDroningEffect | SpellDescription$ Each creature with mana value X can't attack or block this turn. -SVar:CreateDroningEffect:DB$ Effect | StaticAbilities$ NoCombat -SVar:NoCombat:Mode$ Continuous | Affected$ Creature.cmcEQDroningX | AddHiddenKeyword$ CARDNAME can't attack or block. | Description$ Each creature with mana value X can't attack or block this turn. +A:AB$ Effect | Cost$ X T | StaticAbilities$ NoCombat | SetChosenNumber$ X | SpellDescription$ Each creature with mana value X can't attack or block this turn. +SVar:NoCombat:Mode$ Continuous | Affected$ Creature.cmcChosen | AddHiddenKeyword$ CARDNAME can't attack or block. | Description$ Each creature with mana value X can't attack or block this turn. SVar:X:Count$xPaid -SVar:DroningX:Number$0 AI:RemoveDeck:All Oracle:{X}, {T}: Each creature with mana value X can't attack or block this turn. From 7acfe6c2406fb32bf00c1e2a53ec0f105121ab51 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 4 Oct 2025 15:36:17 +0200 Subject: [PATCH 071/230] Different names rework (#8823) * turn CardChangedName into record * Add CardLists.getDifferentNamesCount * CostDiscard: rename to +WithDifferentNames * CostSacrifice: use getDifferentNamesCount * Refactor DifferentCardNames_ --- .../main/java/forge/ai/AiCostDecision.java | 2 +- .../java/forge/game/ability/AbilityUtils.java | 20 +++--------- .../src/main/java/forge/game/card/Card.java | 10 ++++-- .../java/forge/game/card/CardChangedName.java | 24 -------------- .../main/java/forge/game/card/CardLists.java | 27 ++++++++++++++++ .../java/forge/game/cost/CostDiscard.java | 31 ++++++++++++------- .../java/forge/game/cost/CostSacrifice.java | 13 +------- .../res/cardsfolder/a/all_fates_scroll.txt | 2 +- .../cardsfolder/a/audience_with_trostani.txt | 2 +- .../res/cardsfolder/a/awakened_amalgam.txt | 2 +- .../res/cardsfolder/e/eerie_ultimatum.txt | 2 +- forge-gui/res/cardsfolder/e/euroakus.txt | 2 +- .../res/cardsfolder/f/field_of_the_dead.txt | 2 +- .../res/cardsfolder/f/fungal_colossus.txt | 2 +- .../cardsfolder/g/gimbal_gremlin_prodigy.txt | 2 +- .../res/cardsfolder/l/lilianas_contract.txt | 2 +- forge-gui/res/cardsfolder/m/mazes_end.txt | 2 +- .../cardsfolder/m/monument_to_perfection.txt | 2 +- .../n/neriv_crackling_vanguard.txt | 2 +- .../cardsfolder/o/ormos_archive_keeper.txt | 2 +- .../cardsfolder/s/sandsteppe_war_riders.txt | 2 +- forge-gui/res/cardsfolder/s/scalpelexis.txt | 2 +- .../res/cardsfolder/s/signal_the_clans.txt | 2 +- forge-gui/res/cardsfolder/s/survey_mechan.txt | 2 +- .../res/cardsfolder/s/swine_rebellion.txt | 2 +- .../res/cardsfolder/t/the_necrobloom.txt | 2 +- .../java/forge/player/HumanCostDecision.java | 2 +- 27 files changed, 80 insertions(+), 87 deletions(-) delete mode 100644 forge-game/src/main/java/forge/game/card/CardChangedName.java diff --git a/forge-ai/src/main/java/forge/ai/AiCostDecision.java b/forge-ai/src/main/java/forge/ai/AiCostDecision.java index 65d59b3c534..88de7ba4eb4 100644 --- a/forge-ai/src/main/java/forge/ai/AiCostDecision.java +++ b/forge-ai/src/main/java/forge/ai/AiCostDecision.java @@ -113,7 +113,7 @@ public class AiCostDecision extends CostDecisionMakerBase { randomSubset = ability.getActivatingPlayer().getController().orderMoveToZoneList(randomSubset, ZoneType.Graveyard, ability); } return PaymentDecision.card(randomSubset); - } else if (type.equals("DifferentNames")) { + } else if (type.contains("+WithDifferentNames")) { CardCollection differentNames = new CardCollection(); CardCollection discardMe = CardLists.filter(hand, CardPredicates.hasSVar("DiscardMe")); while (c > 0) { 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 78d13487b62..a7a4437854f 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -43,7 +43,6 @@ import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; - public class AbilityUtils { private final static ImmutableList cmpList = ImmutableList.of("LT", "LE", "EQ", "GE", "GT", "NE"); @@ -2889,21 +2888,6 @@ public class AbilityUtils { return max; } - if (sq[0].startsWith("DifferentCardNames_")) { - final List crdname = Lists.newArrayList(); - final String restriction = l[0].substring(19); - CardCollection list = CardLists.getValidCards(game.getCardsInGame(), restriction, player, c, ctb); - // TODO rewrite with sharesName to respect Spy Kit - for (final Card card : list) { - String name = card.getName(); - // CR 201.2b Those objects have different names only if each of them has at least one name and no two objects in that group have a name in common - if (!crdname.contains(name) && !name.isEmpty()) { - crdname.add(name); - } - } - return doXMath(crdname.size(), expr, c, ctb); - } - if (sq[0].startsWith("MostProminentCreatureType")) { String restriction = l[0].split(" ")[1]; CardCollection list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), restriction, player, c, ctb); @@ -3760,6 +3744,10 @@ public class AbilityUtils { return CardUtil.getColorsFromCards(paidList).countColors(); } + if (string.equals("DifferentCardNames")) { + return CardLists.getDifferentNamesCount(paidList); + } + if (string.equals("DifferentColorPair")) { final Set diffPair = new HashSet<>(); for (final Card card : paidList) { 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 5033e19a81b..d5ea3aaf3e5 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -965,7 +965,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr String name = state.getName(); for (CardChangedName change : this.changedCardNames.values()) { if (change.isOverwrite()) { - name = change.getNewName(); + name = change.newName(); } } return alt ? StaticData.instance().getCommonCards().getName(name, true) : name; @@ -980,7 +980,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr for (CardChangedName change : this.changedCardNames.values()) { if (change.isOverwrite()) { result = false; - } else if (change.isAddNonLegendaryCreatureNames()) { + } else if (change.addNonLegendaryCreatureNames()) { result = true; } } @@ -1013,6 +1013,12 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr currentState.getView().updateName(currentState); } + private record CardChangedName(String newName, boolean addNonLegendaryCreatureNames) { + public boolean isOverwrite() { + return newName != null; + } + } + public void setGamePieceType(GamePieceType gamePieceType) { this.gamePieceType = gamePieceType; this.view.updateGamePieceType(this); diff --git a/forge-game/src/main/java/forge/game/card/CardChangedName.java b/forge-game/src/main/java/forge/game/card/CardChangedName.java deleted file mode 100644 index 63dcd82ac8f..00000000000 --- a/forge-game/src/main/java/forge/game/card/CardChangedName.java +++ /dev/null @@ -1,24 +0,0 @@ -package forge.game.card; - -public class CardChangedName { - - protected String newName; - protected boolean addNonLegendaryCreatureNames = false; - - public CardChangedName(String newName, boolean addNonLegendaryCreatureNames) { - this.newName = newName; - this.addNonLegendaryCreatureNames = addNonLegendaryCreatureNames; - } - - public String getNewName() { - return newName; - } - - public boolean isOverwrite() { - return newName != null; - } - - public boolean isAddNonLegendaryCreatureNames() { - return addNonLegendaryCreatureNames; - } -} diff --git a/forge-game/src/main/java/forge/game/card/CardLists.java b/forge-game/src/main/java/forge/game/card/CardLists.java index 279499db015..8f91260b10d 100644 --- a/forge-game/src/main/java/forge/game/card/CardLists.java +++ b/forge-game/src/main/java/forge/game/card/CardLists.java @@ -26,12 +26,17 @@ import forge.game.spellability.TargetRestrictions; import forge.game.staticability.StaticAbilityTapPowerValue; import forge.util.IterableUtil; import forge.util.MyRandom; +import forge.util.StreamUtil; import forge.util.collect.FCollectionView; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.function.Predicate; +import java.util.stream.Collector; +import java.util.stream.Collectors; /** *

@@ -480,4 +485,26 @@ public class CardLists { // (b) including the last element return isSubsetSum(numList, sum) || isSubsetSum(numList, sum - last); } + + public static int getDifferentNamesCount(Iterable cardList) { + // first part the ones with SpyKit, and already collect them via + Map> parted = StreamUtil.stream(cardList).collect(Collectors + .partitioningBy(Card::hasNonLegendaryCreatureNames, Collector.of(ArrayList::new, (list, c) -> { + if (!c.hasNoName() && list.stream().noneMatch(c2 -> c.sharesNameWith(c2))) { + list.add(c); + } + }, (l1, l2) -> { + l1.addAll(l2); + return l1; + }))); + List preList = parted.get(Boolean.FALSE); + + // then try to apply the SpyKit ones + for (Card c : parted.get(Boolean.TRUE)) { + if (preList.stream().noneMatch(c2 -> c.sharesNameWith(c2))) { + preList.add(c); + } + } + return preList.size(); + } } diff --git a/forge-game/src/main/java/forge/game/cost/CostDiscard.java b/forge-game/src/main/java/forge/game/cost/CostDiscard.java index e89a8213419..755c341ed14 100644 --- a/forge-game/src/main/java/forge/game/cost/CostDiscard.java +++ b/forge-game/src/main/java/forge/game/cost/CostDiscard.java @@ -18,7 +18,6 @@ package forge.game.cost; import com.google.common.collect.Lists; -import com.google.common.collect.Sets; import forge.game.ability.AbilityKey; import forge.game.card.*; import forge.game.player.Player; @@ -29,7 +28,6 @@ import forge.util.TextUtil; import java.util.List; import java.util.Map; -import java.util.Set; /** * The Class CostDiscard. @@ -63,11 +61,20 @@ public class CostDiscard extends CostPartWithList { public Integer getMaxAmountX(SpellAbility ability, Player payer, final boolean effect) { final Card source = ability.getHostCard(); String type = this.getType(); + + boolean differentNames = false; + if (type.contains("+WithDifferentNames")) { + type = type.replace("+WithDifferentNames", ""); + differentNames = true; + } CardCollectionView handList = payer.canDiscardBy(ability, effect) ? payer.getCardsIn(ZoneType.Hand) : CardCollection.EMPTY; if (!type.equals("Random")) { handList = CardLists.getValidCards(handList, type.split(";"), payer, source, ability); } + if (differentNames) { + return CardLists.getDifferentNamesCount(handList); + } return handList.size(); } @@ -92,7 +99,7 @@ public class CostDiscard extends CostPartWithList { else if (this.getType().equals("LastDrawn")) { sb.append("the last card you drew this turn"); } - else if (this.getType().equals("DifferentNames")) { + else if (this.getType().contains("+WithDifferentNames")) { sb.append(Cost.convertAmountTypeToWords(i, this.getAmount(), "Card")).append(" with different names"); } else { @@ -145,21 +152,17 @@ public class CostDiscard extends CostPartWithList { final Card c = payer.getLastDrawnCard(); return handList.contains(c); } - else if (type.equals("DifferentNames")) { - Set cardNames = Sets.newHashSet(); - for (Card c : handList) { - if (!c.hasNoName()) { - cardNames.add(c.getName()); - } - } - return cardNames.size() >= amount; - } else { boolean sameName = false; + boolean differentNames = false; if (type.contains("+WithSameName")) { sameName = true; type = TextUtil.fastReplace(type, "+WithSameName", ""); } + if (type.contains("+WithDifferentNames")) { + type = type.replace("+WithDifferentNames", ""); + differentNames = true; + } if (type.contains("ChosenColor") && !source.hasChosenColor()) { //color hasn't been chosen yet, so skip getValidCards } else if (!type.equals("Random") && !type.contains("X")) { @@ -173,6 +176,10 @@ public class CostDiscard extends CostPartWithList { } } return false; + } else if (differentNames) { + if (CardLists.getDifferentNamesCount(handList) < amount) { + return false; + } } int adjustment = 0; if (source.isInZone(ZoneType.Hand) && payer.equals(source.getOwner())) { diff --git a/forge-game/src/main/java/forge/game/cost/CostSacrifice.java b/forge-game/src/main/java/forge/game/cost/CostSacrifice.java index a99c3da8b2d..357235f88bc 100644 --- a/forge-game/src/main/java/forge/game/cost/CostSacrifice.java +++ b/forge-game/src/main/java/forge/game/cost/CostSacrifice.java @@ -17,7 +17,6 @@ */ package forge.game.cost; -import com.google.common.collect.Sets; import forge.card.CardType; import forge.game.Game; import forge.game.ability.AbilityKey; @@ -31,7 +30,6 @@ import forge.game.zone.ZoneType; import forge.util.Lang; import java.util.Map; -import java.util.Set; /** * The Class CostSacrifice. @@ -74,16 +72,7 @@ public class CostSacrifice extends CostPartWithList { } typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability, effect)); if (differentNames) { - // TODO rewrite with sharesName to respect Spy Kit - final Set crdname = Sets.newHashSet(); - for (final Card card : typeList) { - String name = card.getName(); - // CR 201.2b Those objects have different names only if each of them has at least one name and no two objects in that group have a name in common - if (!card.hasNoName()) { - crdname.add(name); - } - } - return crdname.size(); + return CardLists.getDifferentNamesCount(typeList); } return typeList.size(); } diff --git a/forge-gui/res/cardsfolder/a/all_fates_scroll.txt b/forge-gui/res/cardsfolder/a/all_fates_scroll.txt index 1499d57f567..5b7a41b234f 100644 --- a/forge-gui/res/cardsfolder/a/all_fates_scroll.txt +++ b/forge-gui/res/cardsfolder/a/all_fates_scroll.txt @@ -3,5 +3,5 @@ ManaCost:3 Types:Artifact A:AB$ Mana | Cost$ T | Produced$ Any | SpellDescription$ Add one mana of any color. A:AB$ Draw | Cost$ 7 T Sac<1/CARDNAME> | NumCards$ X | SpellDescription$ Draw X cards, where X is the number of differently named lands you control. -SVar:X:Count$DifferentCardNames_Land.YouCtrl+inZoneBattlefield +SVar:X:Count$Valid Land.YouCtrl$DifferentCardNames Oracle:{T}: Add one mana of any color.\n{7}, {T}, Sacrifice this artifact: Draw X cards, where X is the number of differently named lands you control. diff --git a/forge-gui/res/cardsfolder/a/audience_with_trostani.txt b/forge-gui/res/cardsfolder/a/audience_with_trostani.txt index dd2ba9d7f0a..372fd29d0ff 100644 --- a/forge-gui/res/cardsfolder/a/audience_with_trostani.txt +++ b/forge-gui/res/cardsfolder/a/audience_with_trostani.txt @@ -3,7 +3,7 @@ ManaCost:2 G Types:Sorcery A:SP$ Token | TokenScript$ g_0_1_plant | SubAbility$ DBDraw | SpellDescription$ Create a 0/1 green Plant creature token, then draw cards equal to the number of differently named creature tokens you control. SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ X | StackDescription$ None -SVar:X:Count$DifferentCardNames_Creature.YouCtrl+token+inZoneBattlefield +SVar:X:Count$Valid Creature.YouCtrl+token$DifferentCardNames DeckHas:Ability$Token & Type$Plant DeckHints:Ability$Token Oracle:Create a 0/1 green Plant creature token, then draw cards equal to the number of differently named creature tokens you control. diff --git a/forge-gui/res/cardsfolder/a/awakened_amalgam.txt b/forge-gui/res/cardsfolder/a/awakened_amalgam.txt index c98e4b02b80..74720c705b9 100644 --- a/forge-gui/res/cardsfolder/a/awakened_amalgam.txt +++ b/forge-gui/res/cardsfolder/a/awakened_amalgam.txt @@ -3,5 +3,5 @@ ManaCost:4 Types:Artifact Creature Golem PT:*/* S:Mode$ Continuous | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of differently named lands you control. -SVar:X:Count$DifferentCardNames_Land.YouCtrl+inZoneBattlefield +SVar:X:Count$Valid Land.YouCtrl$DifferentCardNames Oracle:Awakened Amalgam's power and toughness are each equal to the number of differently named lands you control. diff --git a/forge-gui/res/cardsfolder/e/eerie_ultimatum.txt b/forge-gui/res/cardsfolder/e/eerie_ultimatum.txt index 355ff616eff..cba20ce6570 100644 --- a/forge-gui/res/cardsfolder/e/eerie_ultimatum.txt +++ b/forge-gui/res/cardsfolder/e/eerie_ultimatum.txt @@ -2,7 +2,7 @@ Name:Eerie Ultimatum ManaCost:W W B B B G G Types:Sorcery A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ChangeType$ Permanent.YouOwn | ChangeNumDesc$ any number of | ChangeTypeDesc$ permanent cards with different names | DifferentNames$ True | ChangeNum$ X | Hidden$ True | SpellDescription$ Return any number of permanent cards with different names from your graveyard to the battlefield. -SVar:X:Count$DifferentCardNames_Permanent.YouOwn+inZoneGraveyard +SVar:X:Count$ValidGraveyard Permanent.YouOwn$DifferentCardNames SVar:IsReanimatorCard:TRUE DeckHas:Ability$Graveyard Oracle:Return any number of permanent cards with different names from your graveyard to the battlefield. diff --git a/forge-gui/res/cardsfolder/e/euroakus.txt b/forge-gui/res/cardsfolder/e/euroakus.txt index d1f03154637..262a250357b 100644 --- a/forge-gui/res/cardsfolder/e/euroakus.txt +++ b/forge-gui/res/cardsfolder/e/euroakus.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Treefolk Wizard PT:6/6 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters, create a number of 1/1 blue Human Wizard creature tokens equal to the number of differently named lands you control. SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ u_1_1_human_wizard -SVar:X:Count$DifferentCardNames_Land.YouCtrl+inZoneBattlefield +SVar:X:Count$Valid Land.YouCtrl$DifferentCardNames A:AB$ Draw | Cost$ 4 G U | NumCards$ Y | SubAbility$ PumpAll | SpellDescription$ Draw a card for each Wizard you control. They each get +1/+1 until end of turn for each card in your hand. SVar:Y:Count$Valid Wizard.YouCtrl SVar:PumpAll:DB$ PumpAll | ValidCards$ Wizard.YouCtrl | NumAtt$ +Z | NumDef$ +Z diff --git a/forge-gui/res/cardsfolder/f/field_of_the_dead.txt b/forge-gui/res/cardsfolder/f/field_of_the_dead.txt index 39bd2e04423..d941c9e4fd6 100644 --- a/forge-gui/res/cardsfolder/f/field_of_the_dead.txt +++ b/forge-gui/res/cardsfolder/f/field_of_the_dead.txt @@ -6,6 +6,6 @@ SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self,Land.Other+YouCtrl | CheckSVar$ X | SVarCompare$ GE7 | Execute$ TrigToken | TriggerDescription$ Whenever CARDNAME or another land you control enters, if you control seven or more lands with different names, create a 2/2 black Zombie creature token. SVar:TrigToken:DB$ Token | TokenScript$ b_2_2_zombie | TokenOwner$ You | TokenAmount$ 1 -SVar:X:Count$DifferentCardNames_Land.YouCtrl+inZoneBattlefield +SVar:X:Count$Valid Land.YouCtrl$DifferentCardNames DeckHas:Ability$Token Oracle:Field of the Dead enters tapped.\n{T}: Add {C}.\nWhenever Field of the Dead or another land you control enters, if you control seven or more lands with different names, create a 2/2 black Zombie creature token. diff --git a/forge-gui/res/cardsfolder/f/fungal_colossus.txt b/forge-gui/res/cardsfolder/f/fungal_colossus.txt index 54d0be698a6..0409a9e823e 100644 --- a/forge-gui/res/cardsfolder/f/fungal_colossus.txt +++ b/forge-gui/res/cardsfolder/f/fungal_colossus.txt @@ -3,5 +3,5 @@ ManaCost:6 G Types:Creature Fungus Beast PT:5/5 S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {X} less to cast, where X is the number of differently named lands you control. -SVar:X:Count$DifferentCardNames_Land.YouCtrl+inZoneBattlefield +SVar:X:Count$Valid Land.YouCtrl$DifferentCardNames Oracle:This spell costs {X} less to cast, where X is the number of differently named lands you control. diff --git a/forge-gui/res/cardsfolder/g/gimbal_gremlin_prodigy.txt b/forge-gui/res/cardsfolder/g/gimbal_gremlin_prodigy.txt index 9aa3b8aa800..125325c25f2 100644 --- a/forge-gui/res/cardsfolder/g/gimbal_gremlin_prodigy.txt +++ b/forge-gui/res/cardsfolder/g/gimbal_gremlin_prodigy.txt @@ -7,7 +7,7 @@ T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefiel SVar:TrigToken:DB$ Token | TokenScript$ r_0_0_a_gremlin | RememberTokens$ True | SubAbility$ DBCounters SVar:DBCounters:DB$ PutCounter | Defined$ Remembered | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$DifferentCardNames_Artifact.YouCtrl+inZoneBattlefield+token +SVar:X:Count$Valid Artifact.YouCtrl+token$DifferentCardNames DeckHas:Ability$Token|Counters DeckHints:Type$Artifact & Ability$Token Oracle:Artifact creatures you control have trample.\nAt the beginning of your end step, create a 0/0 red Gremlin artifact creature token. Put X +1/+1 counters on it, where X is the number of differently named artifact tokens you control. diff --git a/forge-gui/res/cardsfolder/l/lilianas_contract.txt b/forge-gui/res/cardsfolder/l/lilianas_contract.txt index b6946fef4da..51abebdd572 100644 --- a/forge-gui/res/cardsfolder/l/lilianas_contract.txt +++ b/forge-gui/res/cardsfolder/l/lilianas_contract.txt @@ -3,7 +3,7 @@ ManaCost:3 B B Types:Enchantment T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | Execute$ DBWin | TriggerZones$ Battlefield | CheckSVar$ Contractors | SVarCompare$ GE4 | TriggerDescription$ At the beginning of your upkeep, if you control four or more Demons with different names, you win the game. SVar:DBWin:DB$ WinsGame | Defined$ You | ConditionCheckSVar$ Contractors | ConditionSVarCompare$ GE4 -SVar:Contractors:Count$DifferentCardNames_Demon.YouCtrl+inRealZoneBattlefield +SVar:Contractors:Count$Valid Demon.YouCtrl$DifferentCardNames T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters, you draw four cards and you lose 4 life. SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 4 | SubAbility$ DBLoseLife SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 4 diff --git a/forge-gui/res/cardsfolder/m/mazes_end.txt b/forge-gui/res/cardsfolder/m/mazes_end.txt index 5b0c03d6450..016ff08dcbd 100644 --- a/forge-gui/res/cardsfolder/m/mazes_end.txt +++ b/forge-gui/res/cardsfolder/m/mazes_end.txt @@ -6,6 +6,6 @@ SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. A:AB$ ChangeZone | Cost$ 3 T Return<1/CARDNAME> | ChangeType$ Gate | ChangeNum$ 1 | Origin$ Library | Destination$ Battlefield | AILogic$ MazesEnd | SubAbility$ DBWin | SpellDescription$ Search your library for a Gate card, put it onto the battlefield, then shuffle. If you control ten or more Gates with different names, you win the game. SVar:DBWin:DB$ WinsGame | Defined$ You | ConditionCheckSVar$ MazeGate | ConditionSVarCompare$ GE10 -SVar:MazeGate:Count$DifferentCardNames_Gate.YouCtrl+inZoneBattlefield +SVar:X:Count$Valid Gate.YouCtrl$DifferentCardNames AI:RemoveDeck:Random Oracle:Maze's End enters tapped.\n{T}: Add {C}.\n{3}, {T}, Return Maze's End to its owner's hand: Search your library for a Gate card, put it onto the battlefield, then shuffle. If you control ten or more Gates with different names, you win the game. diff --git a/forge-gui/res/cardsfolder/m/monument_to_perfection.txt b/forge-gui/res/cardsfolder/m/monument_to_perfection.txt index 8de4eb6e0c1..a39c9e3356b 100644 --- a/forge-gui/res/cardsfolder/m/monument_to_perfection.txt +++ b/forge-gui/res/cardsfolder/m/monument_to_perfection.txt @@ -3,7 +3,7 @@ ManaCost:2 Types:Artifact A:AB$ ChangeZone | Cost$ 3 T | Origin$ Library | Destination$ Hand | ChangeType$ Land.Basic,Land.Locus,Land.Sphere | ChangeTypeDesc$ basic, Sphere, or Locus land card | SpellDescription$ Search your library for a basic, Sphere, or Locus land card, reveal it, put it into your hand, then shuffle. A:AB$ Animate | Cost$ 3 | CheckSVar$ CountAll | SVarCompare$ GE9 | Power$ 9 | Toughness$ 9 | Types$ Artifact,Creature,Phyrexian,Construct | RemoveCreatureTypes$ True | RemoveAllAbilities$ True | Defined$ Self | Keywords$ Indestructible & Toxic:9 | Duration$ Permanent | SpellDescription$ CARDNAME becomes a 9/9 Phyrexian Construct artifact creature, loses all abilities, and gains indestructible and toxic 9. Activate only if there are nine or more lands with different names among the basic, Sphere, and Locus lands you control. -SVar:CountAll:Count$DifferentCardNames_Land.YouCtrl+inZoneBattlefield+Basic,Sphere.YouCtrl+inZoneBattlefield,Locus.YouCtrl+inZoneBattlefield +SVar:CountAll:Count$Valid Land.YouCtrl+Basic,Sphere.YouCtrl,Locus.YouCtrl$DifferentCardNames DeckHas:Type$Phyrexian|Construct|Artifact DeckNeeds:Type$Locus|Sphere Oracle:{3}, {T}: Search your library for a basic, Sphere, or Locus land card, reveal it, put it into your hand, then shuffle.\n{3}: Monument to Perfection becomes a 9/9 Phyrexian Construct artifact creature, loses all abilities, and gains indestructible and toxic 9. Activate only if there are nine or more lands with different names among the basic, Sphere, and Locus lands you control. diff --git a/forge-gui/res/cardsfolder/n/neriv_crackling_vanguard.txt b/forge-gui/res/cardsfolder/n/neriv_crackling_vanguard.txt index 943d36038bf..b5c3405fbe8 100644 --- a/forge-gui/res/cardsfolder/n/neriv_crackling_vanguard.txt +++ b/forge-gui/res/cardsfolder/n/neriv_crackling_vanguard.txt @@ -11,7 +11,7 @@ SVar:TrigExile:DB$ Dig | Defined$ You | DigNum$ X | ChangeNum$ All | Destination SVar:DBEffectYou:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STPlay | SubAbility$ DBCleanup | ForgetOnMoved$ Exile | Duration$ Permanent SVar:STPlay:Mode$ Continuous | MayPlay$ True | Affected$ Card.IsRemembered | AffectedZone$ Exile | CheckSVar$ Y | Description$ During any turn you attacked with a commander, you may play those cards. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$DifferentCardNames_Permanent.token+YouCtrl+inZoneBattlefield +SVar:X:Count$Valid Permanent.YouCtrl+token$DifferentCardNames SVar:Y:Count$CreaturesAttackedThisTurn Creature.IsCommander+YouCtrl SVar:HasAttackEffect:TRUE Oracle:Flying,deathtouch\nWhen Neriv enters, create two 1/1 red Goblin creature tokens.\nWhenever Neriv attacks, exile a number of cards from the top of your library equal to the number of differently named tokens you control. During any turn you attacked with a commander, you may play those cards. diff --git a/forge-gui/res/cardsfolder/o/ormos_archive_keeper.txt b/forge-gui/res/cardsfolder/o/ormos_archive_keeper.txt index a8162c859d5..417b4d2919c 100644 --- a/forge-gui/res/cardsfolder/o/ormos_archive_keeper.txt +++ b/forge-gui/res/cardsfolder/o/ormos_archive_keeper.txt @@ -5,6 +5,6 @@ PT:5/5 K:Flying R:Event$ Draw | ActiveZones$ Battlefield | ValidPlayer$ You | IsPresent$ Card.YouOwn | PresentZone$ Library | PresentCompare$ EQ0 | ReplaceWith$ AddCounters | Description$ If you would draw a card while your library has no cards in it, instead put five +1/+1 counters on CARDNAME. SVar:AddCounters:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 5 | Defined$ Self -A:AB$ Draw | Cost$ 1 U U Discard<3/DifferentNames> | NumCards$ 5 | SpellDescription$ Draw five cards. +A:AB$ Draw | Cost$ 1 U U Discard<3/Card+WithDifferentNames> | NumCards$ 5 | SpellDescription$ Draw five cards. AI:RemoveDeck:All Oracle:Flying\nIf you would draw a card while your library has no cards in it, instead put five +1/+1 counters on Ormos, Archive Keeper.\n{1}{U}{U}, Discard three cards with different names: Draw five cards. diff --git a/forge-gui/res/cardsfolder/s/sandsteppe_war_riders.txt b/forge-gui/res/cardsfolder/s/sandsteppe_war_riders.txt index 29f0206aa2c..05084018acc 100644 --- a/forge-gui/res/cardsfolder/s/sandsteppe_war_riders.txt +++ b/forge-gui/res/cardsfolder/s/sandsteppe_war_riders.txt @@ -5,6 +5,6 @@ PT:4/4 K:Trample T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ At the beginning of combat on your turn, bolster X, where X is the number of differently named artifact tokens you control. (Choose a creature with the least toughness among creatures you control and put X +1/+1 counters on it.) SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ X | Bolster$ True -SVar:X:Count$DifferentCardNames_Artifact.YouCtrl+token+inZoneBattlefield +SVar:X:Count$Valid Artifact.YouCtrl+token$DifferentCardNames DeckHas:Ability$Counters Oracle:Trample\nAt the beginning of combat on your turn, bolster X, where X is the number of differently named artifact tokens you control. (Choose a creature with the least toughness among creatures you control and put X +1/+1 counters on it.) diff --git a/forge-gui/res/cardsfolder/s/scalpelexis.txt b/forge-gui/res/cardsfolder/s/scalpelexis.txt index 050f476d302..e6820f26a0e 100644 --- a/forge-gui/res/cardsfolder/s/scalpelexis.txt +++ b/forge-gui/res/cardsfolder/s/scalpelexis.txt @@ -8,6 +8,6 @@ SVar:TrigExile:DB$ Repeat | RepeatSubAbility$ DBExileCleanup | RepeatCheckSVar$ SVar:DBExileCleanup:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBExile SVar:DBExile:DB$ Dig | Defined$ TriggeredTarget | DigNum$ 4 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$DifferentCardNames_Card.IsRemembered +SVar:X:Remembered$DifferentCardNames SVar:Y:Count$RememberedSize Oracle:Flying (This creature can't be blocked except by creatures with flying or reach.)\nWhenever Scalpelexis deals combat damage to a player, that player exiles the top four cards of their library. If two or more of those cards have the same name, repeat this process. diff --git a/forge-gui/res/cardsfolder/s/signal_the_clans.txt b/forge-gui/res/cardsfolder/s/signal_the_clans.txt index d449618dc05..a0a4574f962 100644 --- a/forge-gui/res/cardsfolder/s/signal_the_clans.txt +++ b/forge-gui/res/cardsfolder/s/signal_the_clans.txt @@ -6,6 +6,6 @@ SVar:DBChoose:DB$ ChooseCard | Defined$ You | Amount$ 1 | AtRandom$ True | Choic SVar:DBChange:DB$ ChangeZone | Origin$ Library | Destination$ Hand | Defined$ ChosenCard | StackDescription$ None | SubAbility$ DBShuffle | ConditionCheckSVar$ X | ConditionSVarCompare$ EQ3 SVar:DBShuffle:DB$ Shuffle | Defined$ You | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$DifferentCardNames_Creature.IsRemembered +SVar:X:Remembered$DifferentCardNames AI:RemoveDeck:All Oracle:Search your library for three creature cards and reveal them. If you reveal three cards with different names, choose one of them at random and put that card into your hand. Shuffle the rest into your library. diff --git a/forge-gui/res/cardsfolder/s/survey_mechan.txt b/forge-gui/res/cardsfolder/s/survey_mechan.txt index aba0c2c2635..b87e31264cf 100644 --- a/forge-gui/res/cardsfolder/s/survey_mechan.txt +++ b/forge-gui/res/cardsfolder/s/survey_mechan.txt @@ -7,5 +7,5 @@ K:Hexproof A:AB$ DealDamage | Cost$ 10 Sac<1/CARDNAME> | ValidTgts$ Any | NumDmg$ 3 | ReduceCost$ X | SubAbility$ DBDraw | SpellDescription$ It deals 3 damage to any target. Target player draws three cards and gains 3 life. This ability costs {X} less to activate, where X is the number of differently named lands you control. SVar:DBDraw:DB$ Draw | NumCards$ 3 | ValidTgts$ Player | SubAbility$ DBGainLife SVar:DBGainLife:DB$ GainLife | Defined$ ParentTarget | LifeAmount$ 3 -SVar:X:Count$DifferentCardNames_Land.YouCtrl+inZoneBattlefield +SVar:X:Count$Valid Land.YouCtrl$DifferentCardNames Oracle:Flying\nHexproof (This creature can't be the target of spells or abilities your opponents control.)\n{10}, Sacrifice this creature: It deals 3 damage to any target. Target player draws three cards and gains 3 life. This ability costs {X} less to activate, where X is the number of differently named lands you control. diff --git a/forge-gui/res/cardsfolder/s/swine_rebellion.txt b/forge-gui/res/cardsfolder/s/swine_rebellion.txt index c740ec7f736..abb939ffff9 100644 --- a/forge-gui/res/cardsfolder/s/swine_rebellion.txt +++ b/forge-gui/res/cardsfolder/s/swine_rebellion.txt @@ -6,7 +6,7 @@ SVar:DBConjureAll:DB$ MakeCard | Conjure$ True | Names$ First Little Pig,Second SVar:DBConjureTwo:DB$ MakeCard | Conjure$ True | SpellbookName$ the Three Pigs | Spellbook$ First Little Pig,Second Little Pig,Third Little Pig | SpellbookAmount$ 2 | RememberMade$ True | Zone$ Hand | SubAbility$ DBPutBattlefield SVar:DBPutBattlefield:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Card.IsRemembered | ChangeNum$ 1 | Mandatory$ True | SelectPrompt$ Select a card to put onto the battlefield | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$DifferentCardNames_Boar.YouCtrl+inRealZoneBattlefield +SVar:X:Count$Valid Boar.YouCtrl$DifferentCardNames DeckHints:Type$Boar DeckHas:Type$Boar Oracle:If you control three or more Boars with different names, conjure each card from the Three Pigs spellbook onto the battlefield.\nIf you control two or fewer Boars with different names, conjure two cards of your choice from the Three Pigs spellbook into your hand, then put one of them onto the battlefield. diff --git a/forge-gui/res/cardsfolder/t/the_necrobloom.txt b/forge-gui/res/cardsfolder/t/the_necrobloom.txt index b615362e816..bc8fc3cf677 100644 --- a/forge-gui/res/cardsfolder/t/the_necrobloom.txt +++ b/forge-gui/res/cardsfolder/t/the_necrobloom.txt @@ -6,7 +6,7 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Land.Y SVar:TrigBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$ GE7 | TrueSubAbility$ DBZombie | FalseSubAbility$ DBPlant SVar:DBPlant:DB$ Token | TokenAmount$ 1 | TokenScript$ g_0_1_plant | TokenOwner$ You SVar:DBZombie:DB$ Token | TokenAmount$ 1 | TokenScript$ b_2_2_zombie | TokenOwner$ You -SVar:X:Count$DifferentCardNames_Land.YouCtrl+inZoneBattlefield +SVar:X:Count$Valid Land.YouCtrl$DifferentCardNames S:Mode$ Continuous | AffectedZone$ Graveyard | Affected$ Land.YouOwn | AddKeyword$ Dredge:2 | Description$ Land cards in your graveyard have dredge 2. (You may return a land card from your graveyard to your hand and mill two cards instead of drawing a card.) DeckHas:Ability$Token & Type$Zombie DeckHints:Ability$Mill diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index cab21c22947..b8824798392 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -113,7 +113,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { } return PaymentDecision.card(randomSubset); } - if (discardType.equals("DifferentNames")) { + if (discardType.contains("+WithDifferentNames")) { final CardCollection discarded = new CardCollection(); while (c > 0) { final InputSelectCardsFromList inp = new InputSelectCardsFromList(controller, 1, 1, hand, ability); From c1e87334cd55132be94a48c58023f1a47665d398 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sat, 4 Oct 2025 22:31:47 +0200 Subject: [PATCH 072/230] Some cleanup (#8833) --- .../src/main/java/forge/ai/ability/ChangeZoneAi.java | 8 +++----- .../main/java/forge/ai/ability/CopyPermanentAi.java | 4 ++-- forge-ai/src/main/java/forge/ai/ability/MillAi.java | 12 ++++-------- .../a/avatar_aang_aang_master_of_elements.txt | 1 + forge-gui/res/cardsfolder/c/cogwork_assembler.txt | 2 +- .../res/cardsfolder/c/colossal_badger_dig_deep.txt | 3 ++- forge-gui/res/cardsfolder/d/domri_city_smasher.txt | 2 +- forge-gui/res/cardsfolder/g/grave_strength.txt | 3 ++- forge-gui/res/cardsfolder/h/hate_mirage.txt | 2 +- .../res/cardsfolder/k/kiki_jiki_mirror_breaker.txt | 2 +- forge-gui/res/cardsfolder/r/red_suns_twilight.txt | 2 +- forge-gui/res/cardsfolder/t/the_fire_crystal.txt | 2 +- .../res/cardsfolder/t/the_jolly_balloon_man.txt | 2 +- .../upcoming/jaws_relentless_predator.txt | 2 +- 14 files changed, 22 insertions(+), 25 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index f964381fa5f..4c922f348e0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -101,11 +101,7 @@ public class ChangeZoneAi extends SpellAbilityAi { sa.getHostCard().removeSVar("AIPreferenceOverride"); } - if (aiLogic.equals("BeforeCombat")) { - if (ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN)) { - return false; - } - } else if (aiLogic.equals("SurpriseBlock")) { + if (aiLogic.equals("SurpriseBlock")) { if (ai.getGame().getPhaseHandler().getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) { return false; } @@ -765,6 +761,8 @@ public class ChangeZoneAi extends SpellAbilityAi { return ph.getNextTurn().equals(ai) && ph.is(PhaseType.END_OF_TURN); } else if (aiLogic.equals("Main1") && ph.is(PhaseType.MAIN1, ai)) { return true; + } else if (aiLogic.equals("BeforeCombat")) { + return ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN); } if (sa.isHidden()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java index 779d6a02e15..7fb91ce52d8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopyPermanentAi.java @@ -44,7 +44,7 @@ public class CopyPermanentAi extends SpellAbilityAi { // Not at EOT phase return new AiAbilityDecision(0, AiPlayDecision.WaitForEndOfTurn); } - } if ("DuplicatePerms".equals(aiLogic)) { + } else if ("DuplicatePerms".equals(aiLogic)) { final List valid = AbilityUtils.getDefinedCards(source, sa.getParam("Defined"), sa); if (valid.size() < 2) { return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards); @@ -212,7 +212,7 @@ public class CopyPermanentAi extends SpellAbilityAi { if (mandatory) { return new AiAbilityDecision(100, AiPlayDecision.WillPlay); } else { - return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed); + return new AiAbilityDecision(0, AiPlayDecision.MissingNeededCards); } } } diff --git a/forge-ai/src/main/java/forge/ai/ability/MillAi.java b/forge-ai/src/main/java/forge/ai/ability/MillAi.java index 2f6ae8947a0..4fe634834e0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/MillAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/MillAi.java @@ -24,12 +24,7 @@ public class MillAi extends SpellAbilityAi { @Override protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) { - PhaseHandler ph = ai.getGame().getPhaseHandler(); - - if (aiLogic.equals("Main1")) { - return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases") - || ComputerUtil.castSpellInMain1(ai, sa); - } else if (aiLogic.equals("LilianaMill")) { + if (aiLogic.equals("LilianaMill")) { // TODO convert to AICheckSVar // Only mill if a "Raise Dead" target is available, in case of control decks with few creatures return CardLists.filter(ai.getCardsIn(ZoneType.Graveyard), CardPredicates.CREATURES).size() >= 1; @@ -55,9 +50,10 @@ public class MillAi extends SpellAbilityAi { // because they are also potentially useful for combat return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn().equals(ai); } - return true; + return !ph.getPhase().isBefore(PhaseType.MAIN2) || sa.hasParam("ActivationPhases") + || ComputerUtil.castSpellInMain1(ai, sa); } - + @Override protected AiAbilityDecision checkApiLogic(final Player ai, final SpellAbility sa) { /* diff --git a/forge-gui/res/cardsfolder/a/avatar_aang_aang_master_of_elements.txt b/forge-gui/res/cardsfolder/a/avatar_aang_aang_master_of_elements.txt index 8e4e9a77b46..6fda3cadd20 100644 --- a/forge-gui/res/cardsfolder/a/avatar_aang_aang_master_of_elements.txt +++ b/forge-gui/res/cardsfolder/a/avatar_aang_aang_master_of_elements.txt @@ -8,6 +8,7 @@ T:Mode$ ElementalBend | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerD SVar:TrigDraw:DB$ Draw | SubAbility$ DBTransform SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform | ConditionCheckSVar$ X SVar:X:Count$AllFourBend.1.0 +AlternateMode:DoubleFaced Oracle:Flying, firebending 2\nWhenever you waterbend, earthbend, firebend, or airbend, draw a card. Then if you've done all four this turn, transform Avatar Aang. ALTERNATE diff --git a/forge-gui/res/cardsfolder/c/cogwork_assembler.txt b/forge-gui/res/cardsfolder/c/cogwork_assembler.txt index 5d60e998b0c..4904c95647a 100644 --- a/forge-gui/res/cardsfolder/c/cogwork_assembler.txt +++ b/forge-gui/res/cardsfolder/c/cogwork_assembler.txt @@ -2,5 +2,5 @@ Name:Cogwork Assembler ManaCost:3 Types:Artifact Creature Assembly-Worker PT:2/3 -A:AB$ CopyPermanent | Cost$ 7 | ValidTgts$ Artifact | TgtPrompt$ Select target artifact. | PumpKeywords$ Haste | AtEOT$ Exile | AILogic$ BeforeCombat | SpellDescription$ Create a token that's a copy of target artifact. That token gains haste. Exile it at the beginning of the next end step. +A:AB$ CopyPermanent | Cost$ 7 | ValidTgts$ Artifact | TgtPrompt$ Select target artifact. | PumpKeywords$ Haste | AtEOT$ Exile | SpellDescription$ Create a token that's a copy of target artifact. That token gains haste. Exile it at the beginning of the next end step. Oracle:{7}: Create a token that's a copy of target artifact. That token gains haste. Exile it at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/c/colossal_badger_dig_deep.txt b/forge-gui/res/cardsfolder/c/colossal_badger_dig_deep.txt index 9e645ab3542..73e7e83cb0c 100644 --- a/forge-gui/res/cardsfolder/c/colossal_badger_dig_deep.txt +++ b/forge-gui/res/cardsfolder/c/colossal_badger_dig_deep.txt @@ -13,9 +13,10 @@ ALTERNATE Name:Dig Deep ManaCost:1 G Types:Sorcery Adventure -A:SP$ Mill | NumCards$ 4 | RememberMilled$ True | SubAbility$ DBPutCounter | AILogic$ Main1 | SpellDescription$ Choose target creature. Mill four cards, +A:SP$ Mill | NumCards$ 4 | RememberMilled$ True | SubAbility$ DBPutCounter | SpellDescription$ Choose target creature. Mill four cards, SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBCleanup | SpellDescription$ then put a +1/+1 counter on that creature for each creature card milled this way. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Remembered$Valid Creature +SVar:PlayMain1:TRUE DeckHas:Ability$Counters Oracle:Choose target creature. Mill four cards, then put a +1/+1 counter on that creature for each creature card milled this way. diff --git a/forge-gui/res/cardsfolder/d/domri_city_smasher.txt b/forge-gui/res/cardsfolder/d/domri_city_smasher.txt index 6248c2c3d9b..f1d605bbcae 100644 --- a/forge-gui/res/cardsfolder/d/domri_city_smasher.txt +++ b/forge-gui/res/cardsfolder/d/domri_city_smasher.txt @@ -2,7 +2,7 @@ Name:Domri, City Smasher ManaCost:4 R G Types:Legendary Planeswalker Domri Loyalty:4 -A:AB$ PumpAll | Cost$ AddCounter<2/LOYALTY> | ValidCards$ Creature.YouCtrl | KW$ Haste | NumAtt$ +1 | NumDef$ +1 | Planeswalker$ True | AILogic$ Main1 | SpellDescription$ Creatures you control get +1/+1 and gain haste until end of turn. +A:AB$ PumpAll | Cost$ AddCounter<2/LOYALTY> | ValidCards$ Creature.YouCtrl | KW$ Haste | NumAtt$ +1 | NumDef$ +1 | Planeswalker$ True | SpellDescription$ Creatures you control get +1/+1 and gain haste until end of turn. A:AB$ DealDamage | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | ValidTgts$ Any | NumDmg$ 3 | SpellDescription$ CARDNAME deals 3 damage to any target. A:AB$ PutCounterAll | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidCards$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 3 | SubAbility$ DBPumpAll | SpellDescription$ Put three +1/+1 counters on each creature you control. Those creatures gain trample until end of turn. SVar:DBPumpAll:DB$ PumpAll | KW$ Trample | ValidCards$ Creature.YouCtrl diff --git a/forge-gui/res/cardsfolder/g/grave_strength.txt b/forge-gui/res/cardsfolder/g/grave_strength.txt index c21d32cd380..b8dc01cedef 100644 --- a/forge-gui/res/cardsfolder/g/grave_strength.txt +++ b/forge-gui/res/cardsfolder/g/grave_strength.txt @@ -1,8 +1,9 @@ Name:Grave Strength ManaCost:1 B Types:Sorcery -A:SP$ Mill | NumCards$ 3 | Defined$ You | SubAbility$ DBPutCounter | AILogic$ Main1 | SpellDescription$ Choose target creature. Mill three cards, then put a +1/+1 counter on that creature for each creature card in your graveyard. +A:SP$ Mill | NumCards$ 3 | Defined$ You | SubAbility$ DBPutCounter | SpellDescription$ Choose target creature. Mill three cards, then put a +1/+1 counter on that creature for each creature card in your graveyard. SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ X SVar:X:Count$ValidGraveyard Creature.YouOwn DeckHas:Ability$Graveyard +SVar:PlayMain1:TRUE Oracle:Choose target creature. Mill three cards, then put a +1/+1 counter on that creature for each creature card in your graveyard. diff --git a/forge-gui/res/cardsfolder/h/hate_mirage.txt b/forge-gui/res/cardsfolder/h/hate_mirage.txt index c5566f0e471..6d53d5b4eac 100644 --- a/forge-gui/res/cardsfolder/h/hate_mirage.txt +++ b/forge-gui/res/cardsfolder/h/hate_mirage.txt @@ -1,5 +1,5 @@ Name:Hate Mirage ManaCost:3 R Types:Sorcery -A:SP$ CopyPermanent | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select up to two target creatures you don't control | TargetMin$ 0 | TargetMax$ 2 | PumpKeywords$ Haste | AtEOT$ Exile | AILogic$ BeforeCombat | SpellDescription$ Choose up to two target creatures you don't control. For each of those creatures, create a token that's a copy of that creature. Those tokens gain haste. Exile them at the beginning of the next end step. +A:SP$ CopyPermanent | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select up to two target creatures you don't control | TargetMin$ 0 | TargetMax$ 2 | PumpKeywords$ Haste | AtEOT$ Exile | SpellDescription$ Choose up to two target creatures you don't control. For each of those creatures, create a token that's a copy of that creature. Those tokens gain haste. Exile them at the beginning of the next end step. Oracle:Choose up to two target creatures you don't control. For each of those creatures, create a token that's a copy of that creature. Those tokens gain haste. Exile them at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/k/kiki_jiki_mirror_breaker.txt b/forge-gui/res/cardsfolder/k/kiki_jiki_mirror_breaker.txt index 13fd2d5e3d4..0836fe77007 100644 --- a/forge-gui/res/cardsfolder/k/kiki_jiki_mirror_breaker.txt +++ b/forge-gui/res/cardsfolder/k/kiki_jiki_mirror_breaker.txt @@ -3,6 +3,6 @@ ManaCost:2 R R R Types:Legendary Creature Goblin Shaman PT:2/2 K:Haste -A:AB$ CopyPermanent | Cost$ T | ValidTgts$ Creature.nonLegendary+YouCtrl | TgtPrompt$ Select target nonlegendary creature you control | AddKeywords$ Haste | AtEOT$ Sacrifice | AILogic$ BeforeCombat | SpellDescription$ Create a token that's a copy of target nonlegendary creature you control, except it has haste. Sacrifice it at the beginning of the next end step. +A:AB$ CopyPermanent | Cost$ T | ValidTgts$ Creature.nonLegendary+YouCtrl | TgtPrompt$ Select target nonlegendary creature you control | AddKeywords$ Haste | AtEOT$ Sacrifice | SpellDescription$ Create a token that's a copy of target nonlegendary creature you control, except it has haste. Sacrifice it at the beginning of the next end step. SVar:UntapMe:True Oracle:Haste\n{T}: Create a token that's a copy of target nonlegendary creature you control, except it has haste. Sacrifice it at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/r/red_suns_twilight.txt b/forge-gui/res/cardsfolder/r/red_suns_twilight.txt index 56c289a9810..464b7c64538 100644 --- a/forge-gui/res/cardsfolder/r/red_suns_twilight.txt +++ b/forge-gui/res/cardsfolder/r/red_suns_twilight.txt @@ -2,7 +2,7 @@ Name:Red Sun's Twilight ManaCost:X R R Types:Sorcery A:SP$ Destroy | TargetMin$ 0 | TargetMax$ X | ValidTgts$ Artifact | TgtPrompt$ Select X target artifacts | RememberDestroyed$ True | SubAbility$ DBCopy | SpellDescription$ Destroy up to X target artifacts. -SVar:DBCopy:DB$ CopyPermanent | Defined$ Remembered | ConditionCheckSVar$ X | ConditionSVarCompare$ GE5 | PumpKeywords$ Haste | SubAbility$ DBCleanup | AtEOT$ Exile | AILogic$ BeforeCombat | SpellDescription$ If X is 5 or more, for each artifact destroyed this way, create a token that's a copy of it. Those tokens gain haste. Exile them at the beginning of the next end step. +SVar:DBCopy:DB$ CopyPermanent | Defined$ Remembered | ConditionCheckSVar$ X | ConditionSVarCompare$ GE5 | PumpKeywords$ Haste | SubAbility$ DBCleanup | AtEOT$ Exile | SpellDescription$ If X is 5 or more, for each artifact destroyed this way, create a token that's a copy of it. Those tokens gain haste. Exile them at the beginning of the next end step. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$xPaid DeckHas:Ability$Token diff --git a/forge-gui/res/cardsfolder/t/the_fire_crystal.txt b/forge-gui/res/cardsfolder/t/the_fire_crystal.txt index 07edfa20504..45dac1ddaaf 100644 --- a/forge-gui/res/cardsfolder/t/the_fire_crystal.txt +++ b/forge-gui/res/cardsfolder/t/the_fire_crystal.txt @@ -3,6 +3,6 @@ ManaCost:2 R R Types:Legendary Artifact S:Mode$ ReduceCost | ValidCard$ Card.Red | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Red spells you cast cost {1} less to cast. S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddKeyword$ Haste | Description$ Creatures you control have haste. -A:AB$ CopyPermanent | Cost$ 4 R R T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | AtEOT$ Sacrifice | AILogic$ BeforeCombat | SpellDescription$ Create a token that's a copy of target creature you control. Sacrifice it at the beginning of the next end step. +A:AB$ CopyPermanent | Cost$ 4 R R T | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | AtEOT$ Sacrifice | SpellDescription$ Create a token that's a copy of target creature you control. Sacrifice it at the beginning of the next end step. SVar:PlayMain1:TRUE Oracle:Red spells you cast cost {1} less to cast.\nCreatures you control have haste.\n{4}{R}{R}, {T}: Create a token that's a copy of target creature you control. Sacrifice it at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/t/the_jolly_balloon_man.txt b/forge-gui/res/cardsfolder/t/the_jolly_balloon_man.txt index f5e3c3dbd97..3be7a9e9855 100644 --- a/forge-gui/res/cardsfolder/t/the_jolly_balloon_man.txt +++ b/forge-gui/res/cardsfolder/t/the_jolly_balloon_man.txt @@ -3,6 +3,6 @@ ManaCost:1 R W Types:Legendary Creature Human Clown PT:1/4 K:Haste -A:AB$ CopyPermanent | Cost$ 1 T | ValidTgts$ Creature.Other+YouCtrl | TgtPrompt$ Select another target creature you control | SetPower$ 1 | SetToughness$ 1 | AddTypes$ Creature & Balloon | AddColors$ Red | AddKeywords$ Flying & Haste | SorcerySpeed$ True | AtEOT$ Sacrifice | AILogic$ BeforeCombat | SpellDescription$ Create a token that's a copy of another target creature you control, except it's a 1/1 red Balloon creature in addition to its other colors and types and it has flying and haste. Sacrifice it at the beginning of the next end step. Activate only as a sorcery. +A:AB$ CopyPermanent | Cost$ 1 T | ValidTgts$ Creature.Other+YouCtrl | TgtPrompt$ Select another target creature you control | SetPower$ 1 | SetToughness$ 1 | AddTypes$ Creature & Balloon | AddColors$ Red | AddKeywords$ Flying & Haste | SorcerySpeed$ True | AtEOT$ Sacrifice | SpellDescription$ Create a token that's a copy of another target creature you control, except it's a 1/1 red Balloon creature in addition to its other colors and types and it has flying and haste. Sacrifice it at the beginning of the next end step. Activate only as a sorcery. SVar:UntapMe:True Oracle:Haste\n{1}, {T}: Create a token that's a copy of another target creature you control, except it's a 1/1 red Balloon creature in addition to its other colors and types and it has flying and haste. Sacrifice it at the beginning of the next end step. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/upcoming/jaws_relentless_predator.txt b/forge-gui/res/cardsfolder/upcoming/jaws_relentless_predator.txt index f54416fbd56..e0343194db1 100644 --- a/forge-gui/res/cardsfolder/upcoming/jaws_relentless_predator.txt +++ b/forge-gui/res/cardsfolder/upcoming/jaws_relentless_predator.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Shark PT:5/5 K:Trample K:Haste -T:Mode$ DamageDone | ValidSource$ Creature.YouCtrl | ValidTarget$ Player | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever NICKNAME deals combat damage to a player, create that many Blood tokens. +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever NICKNAME deals combat damage to a player, create that many Blood tokens. SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ c_a_blood_draw | TokenOwner$ You SVar:X:TriggerCount$DamageAmount T:Mode$ Sacrificed | ValidCard$ Artifact.nonCreature | Execute$ TrigDamage | TriggerZones$ Battlefield | TriggerDescription$ Whenever a noncreature artifact is sacrificed or destroyed, NICKNAME deals 1 damage to each opponent. From 8cd386b654f4ef07c2268c2c199a1522f0b2a1d3 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 5 Oct 2025 08:39:51 +0000 Subject: [PATCH 073/230] Update ChangeZoneAi.java --- forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 4c922f348e0..590c4285a96 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -762,7 +762,7 @@ public class ChangeZoneAi extends SpellAbilityAi { } else if (aiLogic.equals("Main1") && ph.is(PhaseType.MAIN1, ai)) { return true; } else if (aiLogic.equals("BeforeCombat")) { - return ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN); + return !ai.getGame().getPhaseHandler().getPhase().isAfter(PhaseType.COMBAT_BEGIN); } if (sa.isHidden()) { From 4cef049a1d93adb0aea59bc61c2c4f7f2297edf2 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 5 Oct 2025 11:27:21 +0200 Subject: [PATCH 074/230] AbilityUtils: move SumPower to handlePaid (#8838) * AbilityUtils: move SumPower to handlePaid * Replace SumPower with CardPower * Replace SumToughness with CardToughness --- .../main/java/forge/game/ability/AbilityUtils.java | 11 ----------- forge-gui/res/cardsfolder/a/adipose_offspring.txt | 2 +- forge-gui/res/cardsfolder/a/atarka_beastbreaker.txt | 2 +- forge-gui/res/cardsfolder/a/atarka_pummeler.txt | 2 +- forge-gui/res/cardsfolder/b/battle_cry_goblin.txt | 2 +- forge-gui/res/cardsfolder/b/betor_kin_to_all.txt | 2 +- forge-gui/res/cardsfolder/b/bow_to_my_command.txt | 2 +- .../res/cardsfolder/c/case_of_the_trampled_garden.txt | 2 +- forge-gui/res/cardsfolder/c/circle_of_elders.txt | 2 +- forge-gui/res/cardsfolder/c/crater_elemental.txt | 2 +- forge-gui/res/cardsfolder/d/dragon_scarred_bear.txt | 2 +- forge-gui/res/cardsfolder/d/dragon_whisperer.txt | 2 +- forge-gui/res/cardsfolder/f/finneas_ace_archer.txt | 2 +- forge-gui/res/cardsfolder/g/ghalta_primal_hunger.txt | 2 +- forge-gui/res/cardsfolder/g/gimlis_reckless_might.txt | 2 +- forge-gui/res/cardsfolder/g/glade_watcher.txt | 2 +- forge-gui/res/cardsfolder/g/gnoll_hunter.txt | 2 +- forge-gui/res/cardsfolder/h/hobgoblin_captain.txt | 2 +- forge-gui/res/cardsfolder/i/intrepid_outlander.txt | 2 +- .../res/cardsfolder/k/klauth_unrivaled_ancient.txt | 2 +- forge-gui/res/cardsfolder/l/loxodon_lifechanter.txt | 2 +- forge-gui/res/cardsfolder/l/lurking_arynx.txt | 2 +- forge-gui/res/cardsfolder/m/martial_coup.txt | 4 ++-- forge-gui/res/cardsfolder/m/minion_of_the_mighty.txt | 2 +- forge-gui/res/cardsfolder/m/mosswort_bridge.txt | 2 +- forge-gui/res/cardsfolder/o/owlbear_shepherd.txt | 2 +- forge-gui/res/cardsfolder/p/phyrexian_dreadnought.txt | 2 +- forge-gui/res/cardsfolder/s/sabertooth_outrider.txt | 2 +- .../res/cardsfolder/s/shaman_of_forgotten_ways.txt | 2 +- forge-gui/res/cardsfolder/s/stampeding_elk_herd.txt | 2 +- .../res/cardsfolder/s/surrak_the_hunt_caller.txt | 2 +- .../res/cardsfolder/t/targ_nar_demon_fang_gnoll.txt | 2 +- .../res/cardsfolder/t/the_pride_of_hull_clade.txt | 2 +- forge-gui/res/cardsfolder/t/tiger_tribe_hunter.txt | 2 +- forge-gui/res/cardsfolder/t/towering_titan.txt | 2 +- forge-gui/res/cardsfolder/u/ugins_mastery.txt | 2 +- ..._magnus_tactician_ultra_magnus_armored_carrier.txt | 2 +- forge-gui/res/cardsfolder/v/volcanic_salvo.txt | 2 +- forge-gui/res/cardsfolder/v/vulpine_harvester.txt | 2 +- forge-gui/res/cardsfolder/w/werewolf_pack_leader.txt | 2 +- 40 files changed, 40 insertions(+), 51 deletions(-) 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 a7a4437854f..6c856609ec9 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -2902,13 +2902,6 @@ public class AbilityUtils { } // TODO move below to handlePaid - if (sq[0].startsWith("SumPower")) { - final String[] restrictions = l[0].split("_"); - int sumPower = game.getCardsIn(ZoneType.Battlefield).stream() - .filter(CardPredicates.restriction(restrictions[1], player, c, ctb)) - .mapToInt(Card::getNetPower).sum(); - return doXMath(sumPower, expr, c, ctb); - } if (sq[0].startsWith("DifferentPower_")) { final String restriction = l[0].substring(15); final int uniquePowers = (int) game.getCardsIn(ZoneType.Battlefield).stream() @@ -3732,10 +3725,6 @@ public class AbilityUtils { return CardLists.getTotalPower(paidList, ctb); } - if (string.startsWith("SumToughness")) { - return Aggregates.sum(paidList, Card::getNetToughness); - } - if (string.startsWith("GreatestCMC")) { return Aggregates.max(paidList, Card::getCMC); } diff --git a/forge-gui/res/cardsfolder/a/adipose_offspring.txt b/forge-gui/res/cardsfolder/a/adipose_offspring.txt index 700226388f9..bc141a219db 100644 --- a/forge-gui/res/cardsfolder/a/adipose_offspring.txt +++ b/forge-gui/res/cardsfolder/a/adipose_offspring.txt @@ -6,6 +6,6 @@ K:Emerge:5 W T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters, create a 2/2 white Alien creature token. If CARDNAME's emerge cost was paid, instead create X of those tokens, where X is the sacrificed creature's toughness. SVar:TrigToken:DB$ Token | TokenAmount$ N | TokenScript$ w_2_2_alien | TokenOwner$ You SVar:N:Count$Emerged.T.1 -SVar:T:Emerged$SumToughness +SVar:T:Emerged$CardToughness DeckHas:Ability$Token Oracle:Emerge {5}{W} (You may cast this spell by sacrificing a creature and paying the emerge cost reduced by that creature's mana value.)\nWhen Adipose Offspring enters, create a 2/2 white Alien creature token. If Adipose Offspring's emerge cost was paid, instead create X of those tokens, where X is the sacrificed creature's toughness. diff --git a/forge-gui/res/cardsfolder/a/atarka_beastbreaker.txt b/forge-gui/res/cardsfolder/a/atarka_beastbreaker.txt index 64d658a096c..9208d1f2716 100644 --- a/forge-gui/res/cardsfolder/a/atarka_beastbreaker.txt +++ b/forge-gui/res/cardsfolder/a/atarka_beastbreaker.txt @@ -3,5 +3,5 @@ ManaCost:1 G Types:Creature Human Warrior PT:2/2 A:AB$ Pump | Cost$ 4 G | Defined$ Self | NumAtt$ +4 | NumDef$ +4 | CheckSVar$ FormidableTest | SVarCompare$ GE8 | PrecostDesc$ Formidable — | SpellDescription$ CARDNAME gets +4/+4 until end of turn. Activate only if creatures you control have total power 8 or greater. -SVar:FormidableTest:Count$SumPower_Creature.YouCtrl +SVar:FormidableTest:Count$Valid Creature.YouCtrl$CardPower Oracle:Formidable — {4}{G}: Atarka Beastbreaker gets +4/+4 until end of turn. Activate only if creatures you control have total power 8 or greater. diff --git a/forge-gui/res/cardsfolder/a/atarka_pummeler.txt b/forge-gui/res/cardsfolder/a/atarka_pummeler.txt index a8101f9279a..f2af1db0584 100644 --- a/forge-gui/res/cardsfolder/a/atarka_pummeler.txt +++ b/forge-gui/res/cardsfolder/a/atarka_pummeler.txt @@ -3,5 +3,5 @@ ManaCost:4 R Types:Creature Ogre Warrior PT:4/5 A:AB$ PumpAll | Cost$ 3 R R | ValidCards$ Creature.YouCtrl | KW$ Menace | CheckSVar$ FormidableTest | SVarCompare$ GE8 | PrecostDesc$ Formidable — | SpellDescription$ Creatures you control gain menace until end of turn. Activate only if creatures you control have total power 8 or greater. -SVar:FormidableTest:Count$SumPower_Creature.YouCtrl +SVar:FormidableTest:Count$Valid Creature.YouCtrl$CardPower Oracle:Formidable — {3}{R}{R}: Creatures you control gain menace until end of turn. Activate only if creatures you control have total power 8 or greater. (They can't be blocked except by two or more creatures.) diff --git a/forge-gui/res/cardsfolder/b/battle_cry_goblin.txt b/forge-gui/res/cardsfolder/b/battle_cry_goblin.txt index 787b97c7d6a..2ad13695736 100644 --- a/forge-gui/res/cardsfolder/b/battle_cry_goblin.txt +++ b/forge-gui/res/cardsfolder/b/battle_cry_goblin.txt @@ -5,6 +5,6 @@ PT:2/2 A:AB$ PumpAll | Cost$ 1 R | ValidCards$ Creature.Goblin+YouCtrl | NumAtt$ +1 | KW$ Haste | SpellDescription$ Goblins you control get +1/+0 and gain haste until end of turn. T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigToken | TriggerZones$ Battlefield | CheckSVar$ PackTactics | SVarCompare$ GE6 | NoResolvingCheck$ True | TriggerDescription$ Pack tactics — Whenever CARDNAME attacks, if you attacked with creatures with total power 6 or greater this combat, create a 1/1 red Goblin creature token that's tapped and attacking. SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ r_1_1_goblin | TokenOwner$ You | TokenTapped$ True | TokenAttacking$ True -SVar:PackTactics:Count$SumPower_Creature.attacking +SVar:PackTactics:Count$Valid Creature.attacking$CardPower DeckHas:Ability$Token Oracle:{1}{R}: Goblins you control get +1/+0 and gain haste until end of turn.\nPack tactics — Whenever Battle Cry Goblin attacks, if you attacked with creatures with total power 6 or greater this combat, create a 1/1 red Goblin creature token that's tapped and attacking. diff --git a/forge-gui/res/cardsfolder/b/betor_kin_to_all.txt b/forge-gui/res/cardsfolder/b/betor_kin_to_all.txt index 9eba4f8c722..aac139ffbf1 100644 --- a/forge-gui/res/cardsfolder/b/betor_kin_to_all.txt +++ b/forge-gui/res/cardsfolder/b/betor_kin_to_all.txt @@ -9,5 +9,5 @@ SVar:DBUntapAll:DB$ UntapAll | ValidCards$ Creature.YouCtrl | ConditionCheckSVar SVar:RepeatLoseLife:DB$ RepeatEach | RepeatPlayers$ Opponent | RepeatSubAbility$ DBLoseLife | ConditionCheckSVar$ X | ConditionSVarCompare$ GE40 SVar:DBLoseLife:DB$ LoseLife | Defined$ Player.IsRemembered | LifeAmount$ HalfUp SVar:HalfUp:PlayerCountRemembered$LifeTotal/HalfUp -SVar:X:Count$Valid Creature.YouCtrl$SumToughness +SVar:X:Count$Valid Creature.YouCtrl$CardToughness Oracle:Flying\nAt the beginning of your end step, if creatures you control have total toughness 10 or greater, draw a card. Then if creatures you control have total toughness 20 or greater, untap each creature you control. Then if creatures you control have total toughness 40 or greater, each opponent loses half their life, rounded up. 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 9df772014fc..ed1a944a9a9 100644 --- a/forge-gui/res/cardsfolder/b/bow_to_my_command.txt +++ b/forge-gui/res/cardsfolder/b/bow_to_my_command.txt @@ -16,5 +16,5 @@ SVar:AbandonSelf:DB$ Abandon | SubAbility$ DBCleanup | ConditionCheckSVar$ Tappe 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 +SVar:TappedCreaturePower:Remembered$CardPower Oracle:(An ongoing scheme remains face up until it's abandoned.)\nAs you set this scheme in motion, choose an opponent.\nCreatures the chosen player controls can't attack you or planeswalkers you control.\nAt the beginning of your opponents' end step, they may tap any number of untapped creatures they control with total power 8 or greater. If they do, abandon this scheme. diff --git a/forge-gui/res/cardsfolder/c/case_of_the_trampled_garden.txt b/forge-gui/res/cardsfolder/c/case_of_the_trampled_garden.txt index 0d984f81a28..eeebb69b0ca 100644 --- a/forge-gui/res/cardsfolder/c/case_of_the_trampled_garden.txt +++ b/forge-gui/res/cardsfolder/c/case_of_the_trampled_garden.txt @@ -8,6 +8,6 @@ SVar:TrigSolve:DB$ AlterAttribute | Defined$ Self | Attributes$ Solved T:Mode$ AttackersDeclared | IsPresent$ Card.Self+IsSolved | AttackingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCounterAttacker | TriggerDescription$ Solved — Whenever you attack, put a +1/+1 counter on target attacking creature. It gains trample until end of turn. SVar:TrigCounterAttacker:DB$ PutCounter | ValidTgts$ Creature.attacking | TgtPrompt$ Select target attacking creature | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBPump SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Trample -SVar:X:Count$SumPower_Creature.YouCtrl +SVar:X:Count$Valid Creature.YouCtrl$CardPower DeckHas:Ability$Counters Oracle:When this Case enters, distribute two +1/+1 counters among one or two target creatures you control.\nTo solve — Creatures you control have total power 8 or greater. (If unsolved, solve at the beginning of your end step.)\nSolved — Whenever you attack, put a +1/+1 counter on target attacking creature. It gains trample until end of turn. diff --git a/forge-gui/res/cardsfolder/c/circle_of_elders.txt b/forge-gui/res/cardsfolder/c/circle_of_elders.txt index 9ba44f4ae35..9b82eb7f732 100644 --- a/forge-gui/res/cardsfolder/c/circle_of_elders.txt +++ b/forge-gui/res/cardsfolder/c/circle_of_elders.txt @@ -4,5 +4,5 @@ Types:Creature Human Shaman PT:2/4 K:Vigilance A:AB$ Mana | Cost$ T | Produced$ C | Amount$ 3 | CheckSVar$ FormidableTest | SVarCompare$ GE8 | PrecostDesc$ Formidable — | SpellDescription$ Add {C}{C}{C}. Activate only if creatures you control have total power 8 or greater. -SVar:FormidableTest:Count$SumPower_Creature.YouCtrl +SVar:FormidableTest:Count$Valid Creature.YouCtrl$CardPower Oracle:Vigilance\nFormidable — {T}: Add {C}{C}{C}. Activate only if creatures you control have total power 8 or greater. diff --git a/forge-gui/res/cardsfolder/c/crater_elemental.txt b/forge-gui/res/cardsfolder/c/crater_elemental.txt index 9a505f7f568..23b37c1d934 100644 --- a/forge-gui/res/cardsfolder/c/crater_elemental.txt +++ b/forge-gui/res/cardsfolder/c/crater_elemental.txt @@ -4,7 +4,7 @@ Types:Creature Elemental PT:0/6 A:AB$ DealDamage | Cost$ R T Sac<1/CARDNAME> | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 4 | SpellDescription$ It deals 4 damage to target creature. A:AB$ Animate | Cost$ 2 R | Defined$ Self | Power$ 8 | CheckSVar$ FormidableTest | SVarCompare$ GE8 | PrecostDesc$ Formidable — | SpellDescription$ CARDNAME has base power 8 until end of turn. Activate only if creatures you control have total power 8 or greater. -SVar:FormidableTest:Count$SumPower_Creature.YouCtrl +SVar:FormidableTest:Count$Valid Creature.YouCtrl$CardPower AI:RemoveDeck:All DeckHas:Ability$Sacrifice Oracle:{R}, {T}, Sacrifice Crater Elemental: It deals 4 damage to target creature.\nFormidable — {2}{R}: Crater Elemental has base power 8 until end of turn. Activate only if creatures you control have total power 8 or greater. diff --git a/forge-gui/res/cardsfolder/d/dragon_scarred_bear.txt b/forge-gui/res/cardsfolder/d/dragon_scarred_bear.txt index 93285bc960f..f955ecd30c1 100644 --- a/forge-gui/res/cardsfolder/d/dragon_scarred_bear.txt +++ b/forge-gui/res/cardsfolder/d/dragon_scarred_bear.txt @@ -3,5 +3,5 @@ ManaCost:2 G Types:Creature Bear PT:3/2 A:AB$ Regenerate | Cost$ 1 G | PrecostDesc$ Formidable — | CheckSVar$ FormidableTest | SVarCompare$ GE8 | SpellDescription$ Regenerate CARDNAME. Activate only if creatures you control have total power 8 or greater. -SVar:FormidableTest:Count$SumPower_Creature.YouCtrl +SVar:FormidableTest:Count$Valid Creature.YouCtrl$CardPower Oracle:Formidable — {1}{G}: Regenerate Dragon-Scarred Bear. Activate only if creatures you control have total power 8 or greater. diff --git a/forge-gui/res/cardsfolder/d/dragon_whisperer.txt b/forge-gui/res/cardsfolder/d/dragon_whisperer.txt index a1417ac2adb..cf987d3a42d 100644 --- a/forge-gui/res/cardsfolder/d/dragon_whisperer.txt +++ b/forge-gui/res/cardsfolder/d/dragon_whisperer.txt @@ -5,7 +5,7 @@ PT:2/2 A:AB$ Pump | Cost$ R | KW$ Flying | Defined$ Self | SpellDescription$ CARDNAME gains flying until end of turn. A:AB$ Pump | Cost$ 1 R | Defined$ Self | NumAtt$ +1 | SpellDescription$ CARDNAME gets +1/+0 until end of turn. A:AB$ Token | Cost$ 4 R R | TokenOwner$ You | TokenAmount$ 1 | TokenScript$ r_4_4_dragon_flying | CheckSVar$ FormidableTest | SVarCompare$ GE8 | PrecostDesc$ Formidable — | SpellDescription$ Create a 4/4 red Dragon creature token with flying. Activate only if creatures you control have total power 8 or greater. -SVar:FormidableTest:Count$SumPower_Creature.YouCtrl +SVar:FormidableTest:Count$Valid Creature.YouCtrl$CardPower DeckHas:Ability$Token DeckHints:Type$Dragon Oracle:{R}: Dragon Whisperer gains flying until end of turn.\n{1}{R}: Dragon Whisperer gets +1/+0 until end of turn.\nFormidable — {4}{R}{R}: Create a 4/4 red Dragon creature token with flying. Activate only if creatures you control have total power 8 or greater. diff --git a/forge-gui/res/cardsfolder/f/finneas_ace_archer.txt b/forge-gui/res/cardsfolder/f/finneas_ace_archer.txt index 89a2a7a1788..483b5239bc4 100644 --- a/forge-gui/res/cardsfolder/f/finneas_ace_archer.txt +++ b/forge-gui/res/cardsfolder/f/finneas_ace_archer.txt @@ -7,5 +7,5 @@ K:Reach T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPutCounterAll | TriggerDescription$ Whenever CARDNAME attacks, put a +1/+1 counter on each other creature you control that's a token or a Rabbit. Then if creatures you control have total power 10 or greater, draw a card. SVar:TrigPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl+StrictlyOther+token,Creature.YouCtrl+StrictlyOther+Rabbit | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw SVar:DBDraw:DB$ Draw | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10 -SVar:X:Count$SumPower_Creature.YouCtrl +SVar:X:Count$Valid Creature.YouCtrl$CardPower Oracle:Vigilance, reach\nWhenever Finneas, Ace Archer attacks, put a +1/+1 counter on each other creature you control that's a token or a Rabbit. Then if creatures you control have total power 10 or greater, draw a card. diff --git a/forge-gui/res/cardsfolder/g/ghalta_primal_hunger.txt b/forge-gui/res/cardsfolder/g/ghalta_primal_hunger.txt index 87a39b29bcd..1f60b409986 100644 --- a/forge-gui/res/cardsfolder/g/ghalta_primal_hunger.txt +++ b/forge-gui/res/cardsfolder/g/ghalta_primal_hunger.txt @@ -4,5 +4,5 @@ Types:Legendary Creature Elder Dinosaur PT:12/12 K:Trample S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {X} less to cast, where X is the total power of creatures you control. -SVar:X:Count$SumPower_Creature.YouCtrl +SVar:X:Count$Valid Creature.YouCtrl$CardPower Oracle:This spell costs {X} less to cast, where X is the total power of creatures you control.\nTrample diff --git a/forge-gui/res/cardsfolder/g/gimlis_reckless_might.txt b/forge-gui/res/cardsfolder/g/gimlis_reckless_might.txt index 3d0b1c5fbc5..371f1e87d96 100644 --- a/forge-gui/res/cardsfolder/g/gimlis_reckless_might.txt +++ b/forge-gui/res/cardsfolder/g/gimlis_reckless_might.txt @@ -5,7 +5,7 @@ S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddKeyword$ Haste | Descriptio T:Mode$ AttackersDeclared | AttackingPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE8 | Execute$ TrigFight | TriggerDescription$ Formidable — Whenever you attack, if creatures you control have total power 8 or greater, target attacking creature you control fights up to one target creature you don't control. SVar:TrigFight:DB$ Pump | ValidTgts$ Creature.YouCtrl+attacking | AILogic$ Fight | TgtPrompt$ Select target attacking creature you control | SubAbility$ DBFight SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target creature you don't control -SVar:X:Count$SumPower_Creature.YouCtrl +SVar:X:Count$Valid Creature.YouCtrl$CardPower SVar:BuffedBy:Creature SVar:NonStackingEffect:True SVar:PlayMain1:TRUE diff --git a/forge-gui/res/cardsfolder/g/glade_watcher.txt b/forge-gui/res/cardsfolder/g/glade_watcher.txt index a657890ed4d..322c8000ff4 100644 --- a/forge-gui/res/cardsfolder/g/glade_watcher.txt +++ b/forge-gui/res/cardsfolder/g/glade_watcher.txt @@ -5,5 +5,5 @@ PT:3/3 K:Defender A:AB$ Effect | Cost$ G | StaticAbilities$ CanAttack | CheckSVar$ FormidableTest | SVarCompare$ GE8 | PrecostDesc$ Formidable — | SpellDescription$ CARDNAME can attack this turn as though it didn't have defender. Activate only if creatures you control have total power 8 or greater. SVar:CanAttack:Mode$ CanAttackDefender | ValidCard$ Card.EffectSource | Description$ EFFECTSOURCE can attack this turn as though it didn't have defender. -SVar:FormidableTest:Count$SumPower_Creature.YouCtrl +SVar:FormidableTest:Count$Valid Creature.YouCtrl$CardPower Oracle:Defender\nFormidable — {G}: Glade Watcher can attack this turn as though it didn't have defender. Activate only if creatures you control have total power 8 or greater. diff --git a/forge-gui/res/cardsfolder/g/gnoll_hunter.txt b/forge-gui/res/cardsfolder/g/gnoll_hunter.txt index a452f8ba51d..a8ffd3ef220 100644 --- a/forge-gui/res/cardsfolder/g/gnoll_hunter.txt +++ b/forge-gui/res/cardsfolder/g/gnoll_hunter.txt @@ -4,6 +4,6 @@ Types:Creature Gnoll PT:2/2 T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerZones$ Battlefield | CheckSVar$ PackTactics | SVarCompare$ GE6 | NoResolvingCheck$ True | TriggerDescription$ Pack tactics — Whenever CARDNAME attacks, if you attacked with creatures with total power 6 or greater this combat, put a +1/+1 counter on CARDNAME. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 -SVar:PackTactics:Count$SumPower_Creature.attacking +SVar:PackTactics:Count$Valid Creature.attacking$CardPower DeckHas:Ability$Counters Oracle:Pack tactics — Whenever Gnoll Hunter attacks, if you attacked with creatures with total power 6 or greater this combat, put a +1/+1 counter on Gnoll Hunter. diff --git a/forge-gui/res/cardsfolder/h/hobgoblin_captain.txt b/forge-gui/res/cardsfolder/h/hobgoblin_captain.txt index cf75d4df78f..ad4e5764345 100644 --- a/forge-gui/res/cardsfolder/h/hobgoblin_captain.txt +++ b/forge-gui/res/cardsfolder/h/hobgoblin_captain.txt @@ -4,5 +4,5 @@ Types:Creature Goblin Barbarian PT:3/1 T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPump | TriggerZones$ Battlefield | CheckSVar$ PackTactics | SVarCompare$ GE6 | NoResolvingCheck$ True | TriggerDescription$ Pack tactics — Whenever CARDNAME attacks, if you attacked with creatures with total power 6 or greater this combat, CARDNAME gains first strike until end of turn. SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ First Strike -SVar:PackTactics:Count$SumPower_Creature.attacking +SVar:PackTactics:Count$Valid Creature.attacking$CardPower Oracle:Pack tactics — Whenever Hobgoblin Captain attacks, if you attacked with creatures with total power 6 or greater this combat, Hobgoblin Captain gains first strike until end of turn. diff --git a/forge-gui/res/cardsfolder/i/intrepid_outlander.txt b/forge-gui/res/cardsfolder/i/intrepid_outlander.txt index c475addc35d..83c180e1c96 100644 --- a/forge-gui/res/cardsfolder/i/intrepid_outlander.txt +++ b/forge-gui/res/cardsfolder/i/intrepid_outlander.txt @@ -5,5 +5,5 @@ PT:2/3 K:Reach T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigVenture | TriggerZones$ Battlefield | CheckSVar$ PackTactics | SVarCompare$ GE6 | NoResolvingCheck$ True | TriggerDescription$ Pack tactics — Whenever CARDNAME attacks, if you attacked with creatures with total power 6 or greater this combat, venture into the dungeon. (Enter the first room or advance to the next room.) SVar:TrigVenture:DB$ Venture -SVar:PackTactics:Count$SumPower_Creature.attacking +SVar:PackTactics:Count$Valid Creature.attacking$CardPower Oracle:Reach\nPack tactics — Whenever Intrepid Outlander attacks, if you attacked with creatures with total power 6 or greater this combat, venture into the dungeon. (Enter the first room or advance to the next room.) diff --git a/forge-gui/res/cardsfolder/k/klauth_unrivaled_ancient.txt b/forge-gui/res/cardsfolder/k/klauth_unrivaled_ancient.txt index 4de844dbb42..dc5306de016 100644 --- a/forge-gui/res/cardsfolder/k/klauth_unrivaled_ancient.txt +++ b/forge-gui/res/cardsfolder/k/klauth_unrivaled_ancient.txt @@ -6,6 +6,6 @@ K:Flying K:Haste T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigMana | TriggerDescription$ Whenever CARDNAME attacks, add X mana in any combination of colors, where X is the total power of attacking creatures. Spend this mana only to cast spells. Until end of turn, you don't lose this mana as steps and phases end. SVar:TrigMana:DB$ Mana | Produced$ Combo Any | Amount$ X | PersistentMana$ True | RestrictValid$ Spell -SVar:X:Count$SumPower_Creature.attacking +SVar:X:Count$Valid Creature.attacking$CardPower SVar:HasAttackEffect:TRUE Oracle:Flying, haste\nWhenever Klauth, Unrivaled Ancient attacks, add X mana in any combination of colors, where X is the total power of attacking creatures. Spend this mana only to cast spells. Until end of turn, you don't lose this mana as steps and phases end. diff --git a/forge-gui/res/cardsfolder/l/loxodon_lifechanter.txt b/forge-gui/res/cardsfolder/l/loxodon_lifechanter.txt index 18f139b27ee..fc4e3999064 100644 --- a/forge-gui/res/cardsfolder/l/loxodon_lifechanter.txt +++ b/forge-gui/res/cardsfolder/l/loxodon_lifechanter.txt @@ -4,7 +4,7 @@ Types:Creature Elephant Cleric PT:4/6 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigLife | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may have your life total become the total toughness of creatures you control. SVar:TrigLife:DB$ SetLife | Defined$ You | LifeAmount$ Y -SVar:Y:Count$Valid Creature.YouCtrl$SumToughness +SVar:Y:Count$Valid Creature.YouCtrl$CardToughness A:AB$ Pump | Cost$ 5 W | NumAtt$ +X | NumDef$ +X | SpellDescription$ CARDNAME gets +X/+X until end of turn, where X is your life total. SVar:X:Count$YourLifeTotal AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/l/lurking_arynx.txt b/forge-gui/res/cardsfolder/l/lurking_arynx.txt index 939d91a881a..66e0d11267e 100644 --- a/forge-gui/res/cardsfolder/l/lurking_arynx.txt +++ b/forge-gui/res/cardsfolder/l/lurking_arynx.txt @@ -3,5 +3,5 @@ ManaCost:4 G Types:Creature Cat Beast PT:3/5 A:AB$ MustBlock | Cost$ 2 G | ValidTgts$ Creature | CheckSVar$ FormidableTest | SVarCompare$ GE8 | PrecostDesc$ Formidable — | TgtPrompt$ Select target creature that must block this creature this turn | SpellDescription$ Target creature blocks CARDNAME this turn if able. Activate only if creatures you control have total power 8 or greater. -SVar:FormidableTest:Count$SumPower_Creature.YouCtrl +SVar:FormidableTest:Count$Valid Creature.YouCtrl$CardPower Oracle:Formidable — {2}{G}: Target creature blocks Lurking Arynx this turn if able. Activate only if creatures you control have total power 8 or greater. diff --git a/forge-gui/res/cardsfolder/m/martial_coup.txt b/forge-gui/res/cardsfolder/m/martial_coup.txt index 06e54384827..4f1c0612ba1 100644 --- a/forge-gui/res/cardsfolder/m/martial_coup.txt +++ b/forge-gui/res/cardsfolder/m/martial_coup.txt @@ -6,6 +6,6 @@ SVar:CoupDestroy:DB$ DestroyAll | ValidCards$ Creature.IsNotRemembered | Conditi SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Count$xPaid SVar:NeedsToPlayVar:OwnCreats LEOppCreats -SVar:OwnCreats:Count$SumPower_Creature.YouCtrl -SVar:OppCreats:Count$SumPower_Creature.OppCtrl +SVar:OwnCreats:Count$Valid Creature.YouCtrl$CardPower +SVar:OppCreats:Count$Valid Creature.OppCtrl$CardPower Oracle:Create X 1/1 white Soldier creature tokens. If X is 5 or more, destroy all other creatures. diff --git a/forge-gui/res/cardsfolder/m/minion_of_the_mighty.txt b/forge-gui/res/cardsfolder/m/minion_of_the_mighty.txt index 4e49a9eeec5..a30909b9999 100644 --- a/forge-gui/res/cardsfolder/m/minion_of_the_mighty.txt +++ b/forge-gui/res/cardsfolder/m/minion_of_the_mighty.txt @@ -5,6 +5,6 @@ PT:0/1 K:Menace T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigChange | TriggerZones$ Battlefield | CheckSVar$ PackTactics | SVarCompare$ GE6 | NoResolvingCheck$ True | TriggerDescription$ Pack tactics — Whenever CARDNAME attacks, if you attacked with creatures with total power 6 or greater this combat, you may put a Dragon creature card from your hand onto the battlefield tapped and attacking. SVar:TrigChange:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature.Dragon+YouCtrl | Tapped$ True | Attacking$ True -SVar:PackTactics:Count$SumPower_Creature.attacking +SVar:PackTactics:Count$Valid Creature.attacking$CardPower DeckHints:Type$Dragon Oracle:Menace\nPack tactics — Whenever Minion of the Mighty attacks, if you attacked with creatures with total power 6 or greater this combat, you may put a Dragon creature card from your hand onto the battlefield tapped and attacking. diff --git a/forge-gui/res/cardsfolder/m/mosswort_bridge.txt b/forge-gui/res/cardsfolder/m/mosswort_bridge.txt index 5507fbb4640..a84a1b62de8 100644 --- a/forge-gui/res/cardsfolder/m/mosswort_bridge.txt +++ b/forge-gui/res/cardsfolder/m/mosswort_bridge.txt @@ -6,5 +6,5 @@ R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementRe SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}. A:AB$ Play | Cost$ G T | Defined$ ExiledWith | Amount$ All | Controller$ You | WithoutManaCost$ True | Optional$ True | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10 | SpellDescription$ You may play the exiled card without paying its mana cost if creatures you control have total power 10 or greater. -SVar:X:Count$SumPower_Creature.YouCtrl +SVar:X:Count$Valid Creature.YouCtrl$CardPower Oracle:Hideaway 4 (When this permanent enters, look at the top four cards of your library, exile one face down, then put the rest on the bottom of your library.)\nMosswort Bridge enters tapped.\n{T}: Add {G}.\n{G}, {T}: You may play the exiled card without paying its mana cost if creatures you control have total power 10 or greater. diff --git a/forge-gui/res/cardsfolder/o/owlbear_shepherd.txt b/forge-gui/res/cardsfolder/o/owlbear_shepherd.txt index 263da52d739..0a7c297df69 100644 --- a/forge-gui/res/cardsfolder/o/owlbear_shepherd.txt +++ b/forge-gui/res/cardsfolder/o/owlbear_shepherd.txt @@ -3,6 +3,6 @@ ManaCost:2 G Types:Creature Goblin Druid PT:1/4 T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ TrigDraw | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE8 | TriggerDescription$ At the beginning of your end step, if creatures you control have total power 8 or greater, draw a card. -SVar:X:Count$SumPower_Creature.YouCtrl +SVar:X:Count$Valid Creature.YouCtrl$CardPower SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 Oracle:At the beginning of your end step, if creatures you control have total power 8 or greater, draw a card. diff --git a/forge-gui/res/cardsfolder/p/phyrexian_dreadnought.txt b/forge-gui/res/cardsfolder/p/phyrexian_dreadnought.txt index a3058a7ba2f..ef898fe7ea4 100644 --- a/forge-gui/res/cardsfolder/p/phyrexian_dreadnought.txt +++ b/forge-gui/res/cardsfolder/p/phyrexian_dreadnought.txt @@ -11,7 +11,7 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:TotalPower:Remembered$CardPower SVar:X:Count$Valid Creature.YouCtrl SVar:NeedsToPlayVar:Y GE12 -SVar:Y:Count$SumPower_Creature.YouCtrl+!namedPhyrexian Dreadnought +SVar:Y:Count$Valid Creature.YouCtrl+!namedPhyrexian Dreadnought$CardPower AI:RemoveDeck:Random DeckHas:Ability$Sacrifice Oracle:Trample\nWhen Phyrexian Dreadnought enters, sacrifice it unless you sacrifice any number of creatures with total power 12 or greater. diff --git a/forge-gui/res/cardsfolder/s/sabertooth_outrider.txt b/forge-gui/res/cardsfolder/s/sabertooth_outrider.txt index 2ab6765dafb..903e7de20da 100644 --- a/forge-gui/res/cardsfolder/s/sabertooth_outrider.txt +++ b/forge-gui/res/cardsfolder/s/sabertooth_outrider.txt @@ -5,5 +5,5 @@ PT:4/2 K:Trample T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE8 | Execute$ TrigFormidable | TriggerDescription$ Formidable — Whenever CARDNAME attacks, if creatures you control have total power 8 or greater, CARDNAME gains first strike until end of turn. SVar:TrigFormidable:DB$ Pump | Defined$ Self | KW$ First Strike -SVar:X:Count$SumPower_Creature.YouCtrl +SVar:X:Count$Valid Creature.YouCtrl$CardPower Oracle:Trample\nFormidable — Whenever Sabertooth Outrider attacks, if creatures you control have total power 8 or greater, Sabertooth Outrider gains first strike until end of turn. diff --git a/forge-gui/res/cardsfolder/s/shaman_of_forgotten_ways.txt b/forge-gui/res/cardsfolder/s/shaman_of_forgotten_ways.txt index 9d683f7ef4f..3616893e4ad 100644 --- a/forge-gui/res/cardsfolder/s/shaman_of_forgotten_ways.txt +++ b/forge-gui/res/cardsfolder/s/shaman_of_forgotten_ways.txt @@ -4,7 +4,7 @@ Types:Creature Human Shaman PT:2/3 A:AB$ Mana | Cost$ T | Produced$ Combo Any | Amount$ 2 | RestrictValid$ Spell.Creature | SpellDescription$ Add two mana in any combination of colors. Spend this mana only to cast creature spells. A:AB$ RepeatEach | Cost$ 9 G G T | PrecostDesc$ Formidable — | CheckSVar$ FormidableTest | SVarCompare$ GE8 | RepeatPlayers$ Player | RepeatSubAbility$ DBSetLife | SpellDescription$ Each player's life total becomes the number of creatures they control. Activate only if creatures you control have total power 8 or greater. -SVar:FormidableTest:Count$SumPower_Creature.YouCtrl +SVar:FormidableTest:Count$Valid Creature.YouCtrl$CardPower SVar:DBSetLife:DB$ SetLife | Defined$ Player.IsRemembered | LifeAmount$ X SVar:X:Count$Valid Creature.RememberedPlayerCtrl Oracle:{T}: Add two mana in any combination of colors. Spend this mana only to cast creature spells.\nFormidable — {9}{G}{G}, {T}: Each player's life total becomes the number of creatures they control. Activate only if creatures you control have total power 8 or greater. diff --git a/forge-gui/res/cardsfolder/s/stampeding_elk_herd.txt b/forge-gui/res/cardsfolder/s/stampeding_elk_herd.txt index f6464acbb80..9e46bb5fb2e 100644 --- a/forge-gui/res/cardsfolder/s/stampeding_elk_herd.txt +++ b/forge-gui/res/cardsfolder/s/stampeding_elk_herd.txt @@ -4,5 +4,5 @@ Types:Creature Elk PT:5/5 T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE8 | Execute$ TrigFormidable | TriggerDescription$ Formidable — Whenever CARDNAME attacks, if creatures you control have total power 8 or greater, creatures you control gain trample until end of turn. SVar:TrigFormidable:DB$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Trample -SVar:X:Count$SumPower_Creature.YouCtrl +SVar:X:Count$Valid Creature.YouCtrl$CardPower Oracle:Formidable — Whenever Stampeding Elk Herd attacks, if creatures you control have total power 8 or greater, creatures you control gain trample until end of turn. diff --git a/forge-gui/res/cardsfolder/s/surrak_the_hunt_caller.txt b/forge-gui/res/cardsfolder/s/surrak_the_hunt_caller.txt index 917c363bed9..81a79b1b7c9 100644 --- a/forge-gui/res/cardsfolder/s/surrak_the_hunt_caller.txt +++ b/forge-gui/res/cardsfolder/s/surrak_the_hunt_caller.txt @@ -3,6 +3,6 @@ ManaCost:2 G G Types:Legendary Creature Human Warrior PT:5/4 T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ GE8 | Execute$ TrigPump | TriggerDescription$ Formidable — At the beginning of combat on your turn, if creatures you control have total power 8 or greater, target creature you control gains haste until end of turn. -SVar:X:Count$SumPower_Creature.YouCtrl +SVar:X:Count$Valid Creature.YouCtrl$CardPower SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | KW$ Haste Oracle:Formidable — At the beginning of combat on your turn, if creatures you control have total power 8 or greater, target creature you control gains haste until end of turn. diff --git a/forge-gui/res/cardsfolder/t/targ_nar_demon_fang_gnoll.txt b/forge-gui/res/cardsfolder/t/targ_nar_demon_fang_gnoll.txt index ca4bef9d990..724a26f227c 100644 --- a/forge-gui/res/cardsfolder/t/targ_nar_demon_fang_gnoll.txt +++ b/forge-gui/res/cardsfolder/t/targ_nar_demon_fang_gnoll.txt @@ -4,6 +4,6 @@ Types:Legendary Creature Gnoll PT:2/2 T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPumpAll | TriggerZones$ Battlefield | CheckSVar$ PackTactics | SVarCompare$ GE6 | NoResolvingCheck$ True | TriggerDescription$ Pack tactics — Whenever CARDNAME attacks, if you attacked with creatures with total power 6 or greater this combat, attacking creatures get +1/+0 until end of turn. SVar:TrigPumpAll:DB$ PumpAll | ValidCards$ Creature.attacking | NumAtt$ +1 -SVar:PackTactics:Count$SumPower_Creature.attacking +SVar:PackTactics:Count$Valid Creature.attacking$CardPower A:AB$ Pump | Cost$ 2 R G | NumAtt$ Double | NumDef$ Double | StackDescription$ SpellDescription | SpellDescription$ Double NICKNAME's power and toughness until end of turn. Oracle:Pack tactics — Whenever Targ Nar, Demon-Fang Gnoll attacks, if you attacked with creatures with total power 6 or greater this combat, attacking creatures get +1/+0 until end of turn.\n{2}{R}{G}: Double Targ Nar's power and toughness until end of turn. diff --git a/forge-gui/res/cardsfolder/t/the_pride_of_hull_clade.txt b/forge-gui/res/cardsfolder/t/the_pride_of_hull_clade.txt index 799cff950ce..1306042cd6a 100644 --- a/forge-gui/res/cardsfolder/t/the_pride_of_hull_clade.txt +++ b/forge-gui/res/cardsfolder/t/the_pride_of_hull_clade.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Crocodile Elk Turtle PT:2/15 K:Defender S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {X} less to cast, where X is the total toughness of creatures you control. -SVar:X:Count$Valid Creature.YouCtrl$SumToughness +SVar:X:Count$Valid Creature.YouCtrl$CardToughness A:AB$ Pump | Cost$ 2 U U | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | NumAtt$ +1 | SubAbility$ DBEffect | SpellDescription$ Until end of turn, target creature you control gets +1/+0, gains "Whenever this creature deals combat damage to a player, draw cards equal to its toughness," and can attack as though it didn't have defender. SVar:DBEffect:DB$ Effect | RememberObjects$ Targeted | Triggers$ TrigDamage | StaticAbilities$ CanAttack | ForgetOnMoved$ Battlefield | SubAbility$ DBAnimate SVar:CanAttack:Mode$ CanAttackDefender | ValidCard$ Card.IsRemembered | Description$ CARDNAME can attack as though it didn't have defender. diff --git a/forge-gui/res/cardsfolder/t/tiger_tribe_hunter.txt b/forge-gui/res/cardsfolder/t/tiger_tribe_hunter.txt index 3c41872f15a..165f60f22ca 100644 --- a/forge-gui/res/cardsfolder/t/tiger_tribe_hunter.txt +++ b/forge-gui/res/cardsfolder/t/tiger_tribe_hunter.txt @@ -7,6 +7,6 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigImmediate | TriggerZones$ SVar:TrigImmediate:AB$ ImmediateTrigger | Cost$ Sac<1/Creature.Other/another creature> | RememberObjects$ Sacrificed | Execute$ TrigDamage | TriggerDescription$ If you do, CARDNAME deals damage equal to the sacrificed creature's power to target creature. SVar:TrigDamage:DB$ DealDamage | ConditionDefined$ DelayTriggerRememberedLKI | ValidTgts$ Creature | NumDmg$ X SVar:X:TriggerRemembered$CardPower -SVar:PackTactics:Count$SumPower_Creature.attacking +SVar:PackTactics:Count$Valid Creature.attacking$CardPower DeckHas:Ability$Sacrifice Oracle:Trample\nPack tactics — Whenever Tiger-Tribe Hunter attacks, if you attacked with creatures with total power 6 or greater this combat, you may sacrifice another creature. When you do, Tiger-Tribe Hunter deals damage equal to the sacrificed creature's power to target creature. diff --git a/forge-gui/res/cardsfolder/t/towering_titan.txt b/forge-gui/res/cardsfolder/t/towering_titan.txt index c7e8a0ea5b6..a6e0610edf4 100644 --- a/forge-gui/res/cardsfolder/t/towering_titan.txt +++ b/forge-gui/res/cardsfolder/t/towering_titan.txt @@ -3,7 +3,7 @@ ManaCost:4 G G Types:Creature Giant PT:0/0 K:etbCounter:P1P1:X:no Condition:CARDNAME enters with X +1/+1 counters on it, where X is the total toughness of other creatures you control. -SVar:X:Count$Valid Creature.Other+YouCtrl$SumToughness +SVar:X:Count$Valid Creature.Other+YouCtrl$CardToughness A:AB$ PumpAll | Cost$ Sac<1/Creature.withDefender/creature with defender> | ValidCards$ Creature | KW$ Trample | SpellDescription$ All creatures gain trample until end of turn. SVar:NeedsToPlayVar:Z GE3 SVar:Z:Count$Valid Creature.YouCtrl+toughnessGE2 diff --git a/forge-gui/res/cardsfolder/u/ugins_mastery.txt b/forge-gui/res/cardsfolder/u/ugins_mastery.txt index fea95ec0a39..45be8ee3bdc 100644 --- a/forge-gui/res/cardsfolder/u/ugins_mastery.txt +++ b/forge-gui/res/cardsfolder/u/ugins_mastery.txt @@ -5,5 +5,5 @@ T:Mode$ SpellCast | ValidCard$ Creature.Colorless | ValidActivatingPlayer$ You | SVar:TrigManifest:DB$ Manifest T:Mode$ AttackersDeclared | ValidAttackers$ Creature.YouCtrl | Execute$ TrigState | TriggerZones$ Battlefield | CheckSVar$ PackTactics | SVarCompare$ GE6 | NoResolvingCheck$ True | TriggerDescription$ Whenever you attack with creatures with total power 6 or greater, you may turn a face-down creature you control face up. SVar:TrigState:DB$ SetState | Choices$ Creature.faceDown+YouCtrl | ChoiceTitle$ Select a face-down creature you control | Mode$ TurnFaceUp -SVar:PackTactics:Count$SumPower_Creature.attacking +SVar:PackTactics:Count$Valid Creature.attacking$CardPower Oracle:Whenever you cast a colorless creature spell, manifest the top card of your library. (Put it onto the battlefield face down as a 2/2 creature. Turn it face up any time for its mana cost if it's a creature card.)\nWhenever you attack with creatures with total power 6 or greater, you may turn a face-down creature you control face up. diff --git a/forge-gui/res/cardsfolder/u/ultra_magnus_tactician_ultra_magnus_armored_carrier.txt b/forge-gui/res/cardsfolder/u/ultra_magnus_tactician_ultra_magnus_armored_carrier.txt index 796f3d422fb..0133ddbdda7 100644 --- a/forge-gui/res/cardsfolder/u/ultra_magnus_tactician_ultra_magnus_armored_carrier.txt +++ b/forge-gui/res/cardsfolder/u/ultra_magnus_tactician_ultra_magnus_armored_carrier.txt @@ -25,6 +25,6 @@ K:Haste T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Formidable — Whenever NICKNAME attacks, attacking creatures you control gain indestructible until end of turn. If those creatures have total power 8 or greater, convert NICKNAME. SVar:TrigPump:DB$ PumpAll | ValidCards$ Creature.attacking+YouCtrl | KW$ Indestructible | SubAbility$ DBConvert SVar:DBConvert:DB$ SetState | Mode$ Transform | ConditionCheckSVar$ FormidableTest | ConditionSVarCompare$ GE8 -SVar:FormidableTest:Count$SumPower_Creature.attacking+YouCtrl +SVar:FormidableTest:Count$Valid Creature.attacking+YouCtrl$CardPower SVar:HasAttackEffect:TRUE Oracle:Living metal (During your turn, this Vehicle is also a creature.)\nHaste\nFormidable — Whenever Ultra Magnus attacks, attacking creatures you control gain indestructible until end of turn. If those creatures have total power 8 or greater, convert Ultra Magnus. diff --git a/forge-gui/res/cardsfolder/v/volcanic_salvo.txt b/forge-gui/res/cardsfolder/v/volcanic_salvo.txt index d4c7da8aca3..6d47bbd8c25 100644 --- a/forge-gui/res/cardsfolder/v/volcanic_salvo.txt +++ b/forge-gui/res/cardsfolder/v/volcanic_salvo.txt @@ -2,6 +2,6 @@ Name:Volcanic Salvo ManaCost:10 R R Types:Sorcery S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ CARDNAME costs {X} less to cast, where X is the total power of creatures you control. -SVar:X:Count$SumPower_Creature.YouCtrl +SVar:X:Count$Valid Creature.YouCtrl$CardPower A:SP$ DealDamage | ValidTgts$ Creature,Planeswalker | TargetMin$ 0 | TargetMax$ 2 | TgtPrompt$ Select target creature and/or planeswalkers | NumDmg$ 6 | SpellDescription$ CARDNAME deals 6 damage to each of up to two target creatures and/or planeswalkers. Oracle:This spell costs {X} less to cast, where X is the total power of creatures you control.\nVolcanic Salvo deals 6 damage to each of up to two target creatures and/or planeswalkers. diff --git a/forge-gui/res/cardsfolder/v/vulpine_harvester.txt b/forge-gui/res/cardsfolder/v/vulpine_harvester.txt index 59b77e89ad2..83ecf16216d 100644 --- a/forge-gui/res/cardsfolder/v/vulpine_harvester.txt +++ b/forge-gui/res/cardsfolder/v/vulpine_harvester.txt @@ -4,7 +4,7 @@ Types:Creature Phyrexian Fox PT:3/3 T:Mode$ AttackersDeclared | ValidAttackers$ Phyrexian.YouCtrl | Execute$ TrigReturn | TriggerZones$ Battlefield | TriggerDescription$ Whenever one or more Phyrexians you control attack, return target artifact card from your graveyard to the battlefield if its mana value is less than or equal to their total power. SVar:TrigReturn:DB$ ChangeZone | ValidTgts$ Artifact.YouCtrl | ConditionDefined$ Targeted | ConditionPresent$ Card.cmcLEX | TgtPrompt$ Select target artifact in your graveyard | Origin$ Graveyard | Destination$ Battlefield -SVar:X:Count$SumPower_Phyrexian.YouCtrl+attacking +SVar:X:Count$Valid Phyrexian.YouCtrl+attacking$CardPower DeckHas:Ability$Graveyard DeckHints:Type$Artifact DeckNeeds:Type$Phyrexian diff --git a/forge-gui/res/cardsfolder/w/werewolf_pack_leader.txt b/forge-gui/res/cardsfolder/w/werewolf_pack_leader.txt index 75f0c997f1e..2a1d2a003c6 100644 --- a/forge-gui/res/cardsfolder/w/werewolf_pack_leader.txt +++ b/forge-gui/res/cardsfolder/w/werewolf_pack_leader.txt @@ -5,5 +5,5 @@ PT:3/3 T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerZones$ Battlefield | CheckSVar$ PackTactics | SVarCompare$ GE6 | NoResolvingCheck$ True | TriggerDescription$ Pack tactics — Whenever CARDNAME attacks, if you attacked with creatures with total power 6 or greater this combat, draw a card. A:AB$ Animate | Cost$ 3 G | Defined$ Self | Power$ 5 | Toughness$ 3 | Keywords$ Trample | RemoveTypes$ Human | SpellDescription$ Until end of turn, CARDNAME has base power and toughness 5/3, gains trample, and isn't a Human. SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 -SVar:PackTactics:Count$SumPower_Creature.attacking +SVar:PackTactics:Count$Valid Creature.attacking$CardPower Oracle:Pack tactics — Whenever Werewolf Pack Leader attacks, if you attacked with creatures with total power 6 or greater this combat, draw a card.\n{3}{G}: Until end of turn, Werewolf Pack Leader has base power and toughness 5/3, gains trample, and isn't a Human. From da8b77acf777bd1e4d4272a73005d64ce5aa6f31 Mon Sep 17 00:00:00 2001 From: Agetian Date: Sun, 5 Oct 2025 19:21:15 +0300 Subject: [PATCH 075/230] Add AtOppEOT logic to a couple lands that the AI misuses (#8839) * - AtOppEOT is automatic now (also tweak the FlipACoinAi template to make use of it) * - AILogic$ Never is now generic. --- forge-ai/src/main/java/forge/ai/ability/FlipACoinAi.java | 8 +++----- forge-gui/res/cardsfolder/b/bucolic_ranch.txt | 2 +- forge-gui/res/cardsfolder/t/the_gold_saucer.txt | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/FlipACoinAi.java b/forge-ai/src/main/java/forge/ai/ability/FlipACoinAi.java index 885c09975a8..031f4077b9c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/FlipACoinAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/FlipACoinAi.java @@ -12,15 +12,13 @@ import forge.game.spellability.SpellAbility; public class FlipACoinAi extends SpellAbilityAi { /* (non-Javadoc) - * @see forge.card.abilityfactory.SpellAiLogic#canPlayAI(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) + * @see forge.card.abilityfactory.SpellAiLogic#checkApiLogic(forge.game.player.Player, java.util.Map, forge.card.spellability.SpellAbility) */ @Override - protected AiAbilityDecision canPlay(Player ai, SpellAbility sa) { + protected AiAbilityDecision checkApiLogic(Player ai, SpellAbility sa) { if (sa.hasParam("AILogic")) { String ailogic = sa.getParam("AILogic"); - if (ailogic.equals("Never")) { - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); - } else if (ailogic.equals("PhaseOut")) { + if (ailogic.equals("PhaseOut")) { if (!ComputerUtil.predictThreatenedObjects(sa.getActivatingPlayer(), sa).contains(sa.getHostCard())) { return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } diff --git a/forge-gui/res/cardsfolder/b/bucolic_ranch.txt b/forge-gui/res/cardsfolder/b/bucolic_ranch.txt index cc0b5e81e0a..5dcad48b6b9 100644 --- a/forge-gui/res/cardsfolder/b/bucolic_ranch.txt +++ b/forge-gui/res/cardsfolder/b/bucolic_ranch.txt @@ -3,7 +3,7 @@ ManaCost:no cost Types:Land Desert A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. A:AB$ Mana | Cost$ T | Produced$ Any | Amount$ 1 | RestrictValid$ Spell.Mount | SpellDescription$ Add one mana of any color. Spend this mana only to cast a Mount spell. -A:AB$ PeekAndReveal | Cost$ 3 T | PeekAmount$ 1 | RevealValid$ Card.Mount | RevealOptional$ True | RememberRevealed$ True | SubAbility$ DBChangeZone | SpellDescription$ Look at the top card of your library. If it's a Mount card, you may reveal it and put it into your hand. If you don't put it into your hand, you may put it on the bottom of your library. +A:AB$ PeekAndReveal | Cost$ 3 T | PeekAmount$ 1 | RevealValid$ Card.Mount | RevealOptional$ True | RememberRevealed$ True | SubAbility$ DBChangeZone | AILogic$ AtOppEOT | SpellDescription$ Look at the top card of your library. If it's a Mount card, you may reveal it and put it into your hand. If you don't put it into your hand, you may put it on the bottom of your library. SVar:DBChangeZone:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Hand | SubAbility$ DBChangeZone2 SVar:DBChangeZone2:DB$ ChangeZone | Optional$ True | Defined$ TopOfLibrary | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ EQ0 | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/t/the_gold_saucer.txt b/forge-gui/res/cardsfolder/t/the_gold_saucer.txt index cb385f67d36..498e3de4eea 100644 --- a/forge-gui/res/cardsfolder/t/the_gold_saucer.txt +++ b/forge-gui/res/cardsfolder/t/the_gold_saucer.txt @@ -2,7 +2,7 @@ Name:The Gold Saucer ManaCost:no cost Types:Land Town A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. -A:AB$ FlipACoin | Cost$ 2 T | WinSubAbility$ DBToken | SpellDescription$ Flip a coin. If you win the flip, create a Treasure token. +A:AB$ FlipACoin | Cost$ 2 T | WinSubAbility$ DBToken | AILogic$ AtOppEOT | SpellDescription$ Flip a coin. If you win the flip, create a Treasure token. SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | TokenOwner$ You A:AB$ Draw | Cost$ 3 T Sac<2/Artifact> | SpellDescription$ Draw a card. DeckHas:Ability$Token From 0af4be42c4f36abb16a74899c5e354d8ad5f61fa Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 5 Oct 2025 18:31:12 +0200 Subject: [PATCH 076/230] DungeonsCompleted with handlePaid (#8842) --- .../java/forge/game/ability/AbilityUtils.java | 44 +++---------------- .../cardsfolder/a/acererak_the_archlich.txt | 2 +- .../cardsfolder/b/barrowin_of_clan_undurr.txt | 2 +- .../c/caves_of_chaos_adventurer.txt | 2 +- .../res/cardsfolder/c/cloister_gargoyle.txt | 2 +- .../cardsfolder/e/eccentric_apprentice.txt | 2 +- .../cardsfolder/e/ellywick_tumblestrum.txt | 2 +- forge-gui/res/cardsfolder/g/gloom_stalker.txt | 2 +- .../cardsfolder/i/imoen_mystic_trickster.txt | 2 +- .../cardsfolder/n/nadaar_selfless_paladin.txt | 2 +- .../res/cardsfolder/p/precipitous_drop.txt | 2 +- .../cardsfolder/r/ravenloft_adventurer.txt | 2 +- .../res/cardsfolder/r/rilsa_rael_kingpin.txt | 2 +- .../rebalanced/a-acererak_the_archlich.txt | 2 +- .../rebalanced/a-cloister_gargoyle.txt | 2 +- .../rebalanced/a-ellywick_tumblestrum.txt | 2 +- .../rebalanced/a-precipitous_drop.txt | 2 +- .../s/safana_calimport_cutthroat.txt | 2 +- forge-gui/res/cardsfolder/s/sarevoks_tome.txt | 2 +- .../t/tomb_of_horrors_adventurer.txt | 2 +- .../u/undermountain_adventurer.txt | 2 +- .../cardsfolder/w/white_plume_adventurer.txt | 2 +- 22 files changed, 27 insertions(+), 59 deletions(-) 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 6c856609ec9..601f6df2d2a 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -522,6 +522,8 @@ public class AbilityUtils { } } else if (calcX[0].equals("OriginalHost")) { val = xCount(ability.getOriginalHost(), calcX[1], ability); + } else if (calcX[0].equals("DungeonsCompleted")) { + val = handlePaid(player.getCompletedDungeons(), calcX[1], card, ability); } else if (calcX[0].startsWith("ExiledWith")) { val = handlePaid(card.getExiledCards(), calcX[1], card, ability); } else if (calcX[0].startsWith("Convoked")) { @@ -3421,6 +3423,7 @@ public class AbilityUtils { } public static int playerXProperty(final Player player, final String s, final Card source, CardTraitBase ctb) { + final String[] l = s.split("/"); final String m = CardFactoryUtil.extractOperators(s); @@ -3472,6 +3475,7 @@ public class AbilityUtils { final String[] sq = l[0].split("\\."); final String value = sq[0]; + final String[] calcX = value.split("\\$", 2); if (value.contains("NumPowerSurgeLands")) { return doXMath(player.getNumPowerSurgeLands(), m, source, ctb); @@ -3607,46 +3611,10 @@ public class AbilityUtils { return doXMath(player.hasBeenDealtCombatDamageSinceLastTurn() ? 1 : 0, m, source, ctb); } - if (value.equals("DungeonsCompleted")) { - return doXMath(player.getCompletedDungeons().size(), m, source, ctb); - } - if (value.equals("RingTemptedYou")) { return doXMath(player.getNumRingTemptedYou(), m, source, ctb); } - if (value.startsWith("DungeonCompletedNamed")) { - String [] full = value.split("_"); - String name = full[1]; - int completed = 0; - List dungeons = player.getCompletedDungeons(); - for (Card c : dungeons) { - if (c.getName().equals(name)) { - ++completed; - } - } - return doXMath(completed, m, source, ctb); - } - if (value.equals("DifferentlyNamedDungeonsCompleted")) { - int amount = 0; - List dungeons = player.getCompletedDungeons(); - for (int i = 0; i < dungeons.size(); ++i) { - Card d1 = dungeons.get(i); - boolean hasSameName = false; - for (int j = i - 1; j >= 0; --j) { - Card d2 = dungeons.get(j); - if (d1.getName().equals(d2.getName())) { - hasSameName = true; - break; - } - } - if (!hasSameName) { - ++amount; - } - } - return doXMath(amount, m, source, ctb); - } - if (value.equals("AttractionsVisitedThisTurn")) { return doXMath(player.getAttractionsVisitedThisTurn(), m, source, ctb); } @@ -3733,8 +3701,8 @@ public class AbilityUtils { return CardUtil.getColorsFromCards(paidList).countColors(); } - if (string.equals("DifferentCardNames")) { - return CardLists.getDifferentNamesCount(paidList); + if (string.startsWith("DifferentCardNames")) { + return doXMath(CardLists.getDifferentNamesCount(paidList), CardFactoryUtil.extractOperators(string), source, ctb); } if (string.equals("DifferentColorPair")) { diff --git a/forge-gui/res/cardsfolder/a/acererak_the_archlich.txt b/forge-gui/res/cardsfolder/a/acererak_the_archlich.txt index fc1ef36c3ae..ff3b874fa66 100644 --- a/forge-gui/res/cardsfolder/a/acererak_the_archlich.txt +++ b/forge-gui/res/cardsfolder/a/acererak_the_archlich.txt @@ -5,7 +5,7 @@ PT:5/5 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | CheckSVar$ X | SVarCompare$ LT1 | Execute$ TrigBounce | TriggerDescription$ When CARDNAME enters, if you haven't completed Tomb of Annihilation, return CARDNAME to its owner's hand and venture into the dungeon. SVar:TrigBounce:DB$ ChangeZone | Origin$ Battlefield | Destination$ Hand | SubAbility$ DBVenture SVar:DBVenture:DB$ Venture -SVar:X:PlayerCountPropertyYou$DungeonCompletedNamed_Tomb of Annihilation +SVar:X:DungeonsCompleted$Valid Card.namedTomb of Annihilation T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigRepeat | TriggerDescription$ Whenever CARDNAME attacks, for each opponent, you create a 2/2 black Zombie creature token unless that player sacrifices a creature. SVar:TrigRepeat:DB$ RepeatEach | RepeatPlayers$ Opponent | RepeatSubAbility$ DBToken SVar:DBToken:DB$ Token | TokenScript$ b_2_2_zombie | UnlessCost$ Sac<1/Creature> | UnlessPayer$ Player.IsRemembered diff --git a/forge-gui/res/cardsfolder/b/barrowin_of_clan_undurr.txt b/forge-gui/res/cardsfolder/b/barrowin_of_clan_undurr.txt index f6a684d1191..2895b5144bd 100644 --- a/forge-gui/res/cardsfolder/b/barrowin_of_clan_undurr.txt +++ b/forge-gui/res/cardsfolder/b/barrowin_of_clan_undurr.txt @@ -6,7 +6,7 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S T:Mode$ Attacks | ValidCard$ Card.Self | CheckSVar$ X | SVarCompare$ GE1 | Execute$ DBChangeZone | TriggerDescription$ Whenever CARDNAME attacks, return up to one creature card with mana value 3 or less from your graveyard to the battlefield if you've completed a dungeon. SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Hidden$ True | ChangeType$ Creature.YouOwn+cmcLE3 SVar:DBVenture:DB$ Venture | Defined$ You -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount DeckHints:Ability$Mill|Discard DeckHas:Ability$Graveyard Oracle:When Barrowin of Clan Undurr enters, venture into the dungeon. (Enter the first room or advance to the next room.)\nWhenever Barrowin of Clan Undurr attacks, return up to one creature card with mana value 3 or less from your graveyard to the battlefield if you've completed a dungeon. diff --git a/forge-gui/res/cardsfolder/c/caves_of_chaos_adventurer.txt b/forge-gui/res/cardsfolder/c/caves_of_chaos_adventurer.txt index 87633e9a623..c7a84a87788 100644 --- a/forge-gui/res/cardsfolder/c/caves_of_chaos_adventurer.txt +++ b/forge-gui/res/cardsfolder/c/caves_of_chaos_adventurer.txt @@ -13,6 +13,6 @@ SVar:STPlay2:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | A SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | RememberObjects$ Remembered | ForgetOnMoved$ Exile SVar:STPlay:Mode$ Continuous | MayPlay$ True | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play that card this turn. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount SVar:HasAttackEffect:TRUE Oracle:Trample\nWhen Caves of Chaos Adventurer enters, you take the initiative.\nWhenever Caves of Chaos Adventurer attacks, exile the top card of your library. If you've completed a dungeon, you may play that card this turn without paying its mana cost. Otherwise, you may play that card this turn. diff --git a/forge-gui/res/cardsfolder/c/cloister_gargoyle.txt b/forge-gui/res/cardsfolder/c/cloister_gargoyle.txt index e2ff4417f81..07ea1913c59 100644 --- a/forge-gui/res/cardsfolder/c/cloister_gargoyle.txt +++ b/forge-gui/res/cardsfolder/c/cloister_gargoyle.txt @@ -5,5 +5,5 @@ PT:0/4 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBVenture | TriggerDescription$ When CARDNAME enters, venture into the dungeon. (Enter the first room or advance to the next room.) SVar:DBVenture:DB$ Venture | Defined$ You S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 3 | AddKeyword$ Flying | CheckSVar$ X | SVarCompare$ GE1 | Description$ As long as you've completed a dungeon, CARDNAME gets +3/+0 and has flying. -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount Oracle:When Cloister Gargoyle enters, venture into the dungeon. (Enter the first room or advance to the next room.)\nAs long as you've completed a dungeon, Cloister Gargoyle gets +3/+0 and has flying. diff --git a/forge-gui/res/cardsfolder/e/eccentric_apprentice.txt b/forge-gui/res/cardsfolder/e/eccentric_apprentice.txt index 4c24fa6ba13..3693371bab8 100644 --- a/forge-gui/res/cardsfolder/e/eccentric_apprentice.txt +++ b/forge-gui/res/cardsfolder/e/eccentric_apprentice.txt @@ -7,5 +7,5 @@ T:Mode$ ChangesZone | ValidCard$ Card.Self | Destination$ Battlefield | Execute$ SVar:TrigVenture:DB$ Venture T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | CheckSVar$ X | Execute$ TrigAnimate | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of combat on your turn, if you've completed a dungeon, up to one target creature becomes a Bird with base power and toughness 1/1 and flying until end of turn. SVar:TrigAnimate:DB$ Animate | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target creature | Power$ 1 | Toughness$ 1 | Types$ Bird | RemoveCreatureTypes$ True | Keywords$ Flying -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount Oracle:Flying\nWhen Eccentric Apprentice enters, venture into the dungeon. (Enter the first room or advance to the next room.)\nAt the beginning of combat on your turn, if you've completed a dungeon, up to one target creature becomes a Bird with base power and toughness 1/1 and flying until end of turn. diff --git a/forge-gui/res/cardsfolder/e/ellywick_tumblestrum.txt b/forge-gui/res/cardsfolder/e/ellywick_tumblestrum.txt index 7dde30bfbd5..4241f683d8f 100644 --- a/forge-gui/res/cardsfolder/e/ellywick_tumblestrum.txt +++ b/forge-gui/res/cardsfolder/e/ellywick_tumblestrum.txt @@ -9,5 +9,5 @@ SVar:IsLegendary:Count$ValidHand Creature.Legendary+IsRemembered SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True A:AB$ Effect | Cost$ SubCounter<7/LOYALTY> | Planeswalker$ True | Ultimate$ True | Name$ Emblem — Ellywick Tumblestrum | Image$ emblem_ellywick_tumblestrum | StaticAbilities$ STOverrun | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "Creatures you control have trample and haste and get +2/+2 for each differently named dungeon you've completed." SVar:STOverrun:Mode$ Continuous | Affected$ Creature.YouCtrl | AffectedZone$ Battlefield | AddPower$ X | AddToughness$ X | AddKeyword$ Trample & Haste | Description$ Creatures you control have trample and haste and get +2/+2 for each differently named dungeon you've completed. -SVar:X:PlayerCountPropertyYou$DifferentlyNamedDungeonsCompleted/Twice +SVar:X:DungeonsCompleted$DifferentCardNames/Twice Oracle:[+1]: Venture into the dungeon. (Enter the first room or advance to the next room.)\n[-2]: Look at the top six cards of your library. You may reveal a creature card from among them and put it into your hand. If it's legendary, you gain 3 life. Put the rest on the bottom of your library in a random order.\n[-7]: You get an emblem with "Creatures you control have trample and haste and get +2/+2 for each differently named dungeon you've completed." diff --git a/forge-gui/res/cardsfolder/g/gloom_stalker.txt b/forge-gui/res/cardsfolder/g/gloom_stalker.txt index c033e830b2a..455e3bcfcd1 100644 --- a/forge-gui/res/cardsfolder/g/gloom_stalker.txt +++ b/forge-gui/res/cardsfolder/g/gloom_stalker.txt @@ -3,5 +3,5 @@ ManaCost:2 W Types:Creature Dwarf Ranger PT:2/3 S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Double Strike | CheckSVar$ X | SVarCompare$ GE1 | Description$ As long as you've completed a dungeon, CARDNAME has double strike. -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount Oracle:As long as you've completed a dungeon, Gloom Stalker has double strike. diff --git a/forge-gui/res/cardsfolder/i/imoen_mystic_trickster.txt b/forge-gui/res/cardsfolder/i/imoen_mystic_trickster.txt index 39346837310..3ea3c9d999d 100644 --- a/forge-gui/res/cardsfolder/i/imoen_mystic_trickster.txt +++ b/forge-gui/res/cardsfolder/i/imoen_mystic_trickster.txt @@ -7,5 +7,5 @@ T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefiel SVar:TrigDraw:DB$ Draw | SubAbility$ DBDraw SVar:DBDraw:DB$ Draw | ConditionCheckSVar$ X K:Choose a Background -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount Oracle:Ward {2} (Whenever this creature becomes the target of a spell or ability an opponent controls, counter it unless that player pays {2}.)\nAt the beginning of your end step, if you have the initiative, draw a card. Draw another card if you've completed a dungeon.\nChoose a Background (You can have a Background as a second commander.) diff --git a/forge-gui/res/cardsfolder/n/nadaar_selfless_paladin.txt b/forge-gui/res/cardsfolder/n/nadaar_selfless_paladin.txt index 75ab85f438e..41ae0d4a943 100644 --- a/forge-gui/res/cardsfolder/n/nadaar_selfless_paladin.txt +++ b/forge-gui/res/cardsfolder/n/nadaar_selfless_paladin.txt @@ -7,5 +7,5 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ DBVenture | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters or attacks, venture into the dungeon. (Enter the first room or advance to the next room.) SVar:DBVenture:DB$ Venture | Defined$ You S:Mode$ Continuous | Affected$ Creature.YouCtrl+Other | AddPower$ 1 | AddToughness$ 1 | CheckSVar$ X | SVarCompare$ GE1 | Description$ Other creatures you control get +1/+1 as long as you've completed a dungeon. -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount Oracle:Vigilance\nWhenever Nadaar, Selfless Paladin enters or attacks, venture into the dungeon. (Enter the first room or advance to the next room.)\nOther creatures you control get +1/+1 as long as you've completed a dungeon. diff --git a/forge-gui/res/cardsfolder/p/precipitous_drop.txt b/forge-gui/res/cardsfolder/p/precipitous_drop.txt index 6291bd0ecab..c763fc18a6e 100644 --- a/forge-gui/res/cardsfolder/p/precipitous_drop.txt +++ b/forge-gui/res/cardsfolder/p/precipitous_drop.txt @@ -7,5 +7,5 @@ T:Mode$ ChangesZone | ValidCard$ Card.Self | Destination$ Battlefield | Execute$ SVar:TrigVenture:DB$ Venture S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ -X | AddToughness$ -X | Description$ Enchanted creature gets -2/-2. It gets -5/-5 instead as long as you've completed a dungeon. SVar:X:Count$Compare Y GE1.5.2 -SVar:Y:PlayerCountPropertyYou$DungeonsCompleted +SVar:Y:DungeonsCompleted$Amount Oracle:Enchant creature\nWhen Precipitous Drop enters, venture into the dungeon. (Enter the first room or advance to the next room.)\nEnchanted creature gets -2/-2. It gets -5/-5 instead as long as you've completed a dungeon. diff --git a/forge-gui/res/cardsfolder/r/ravenloft_adventurer.txt b/forge-gui/res/cardsfolder/r/ravenloft_adventurer.txt index d6eff347fad..e2634b39e1d 100644 --- a/forge-gui/res/cardsfolder/r/ravenloft_adventurer.txt +++ b/forge-gui/res/cardsfolder/r/ravenloft_adventurer.txt @@ -10,7 +10,7 @@ SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterType$ HIT | SubA SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigLoseLife | CheckSVar$ X | TriggerDescription$ Whenever CARDNAME attacks, if you've completed a dungeon, defending player loses 1 life for each card they own in exile with a hit counter on it. SVar:TrigLoseLife:DB$ LoseLife | Defined$ TriggeredDefendingPlayer | LifeAmount$ Y -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount SVar:Y:TriggeredDefendingPlayer$ValidExile Card.YouOwn+counters_GE1_HIT SVar:HasAttackEffect:TRUE DeckHints:Name$Etrata, the Silencer|Mari, the Killing Quill diff --git a/forge-gui/res/cardsfolder/r/rilsa_rael_kingpin.txt b/forge-gui/res/cardsfolder/r/rilsa_rael_kingpin.txt index 19118fa2a06..6f5614bccc6 100644 --- a/forge-gui/res/cardsfolder/r/rilsa_rael_kingpin.txt +++ b/forge-gui/res/cardsfolder/r/rilsa_rael_kingpin.txt @@ -8,5 +8,5 @@ SVar:TrigInitiative:DB$ TakeInitiative T:Mode$ AttackersDeclared | AttackingPlayer$ You | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Whenever you attack, target attacking creature gains deathtouch until end of turn. If you've completed a dungeon, that creature also gets +5/+0 and gains first strike and menace until end of turn. SVar:TrigPump:DB$ Pump | KW$ Deathtouch | TgtPrompt$ Select target attacking creature | ValidTgts$ Creature.attacking | SubAbility$ DBPump SVar:DBPump:DB$ Pump | ConditionCheckSVar$ X | NumAtt$ +5 | KW$ First Strike & Menace | Defined$ Targeted -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount Oracle:Deathtouch\nWhen Rilsa Rael, Kingpin enters, you take the initiative.\nWhenever you attack, target attacking creature gains deathtouch until end of turn. If you've completed a dungeon, that creature also gets +5/+0 and gains first strike and menace until end of turn. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-acererak_the_archlich.txt b/forge-gui/res/cardsfolder/rebalanced/a-acererak_the_archlich.txt index 6099cb71f11..7729f3e0ad5 100644 --- a/forge-gui/res/cardsfolder/rebalanced/a-acererak_the_archlich.txt +++ b/forge-gui/res/cardsfolder/rebalanced/a-acererak_the_archlich.txt @@ -5,7 +5,7 @@ PT:5/5 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | CheckSVar$ X | SVarCompare$ LT1 | Execute$ TrigBounce | TriggerDescription$ When CARDNAME enters, if you haven't completed Tomb of Annihilation, return CARDNAME to its owner's hand and venture into the dungeon. SVar:TrigBounce:DB$ ChangeZone | Origin$ Battlefield | Destination$ Hand | SubAbility$ DBVenture SVar:DBVenture:DB$ Venture -SVar:X:PlayerCountPropertyYou$DungeonCompletedNamed_Tomb of Annihilation +SVar:X:DungeonsCompleted$Valid Card.namedTomb of Annihilation T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ Whenever CARDNAME attacks, create a number of 2/2 black Zombie creature tokens equal to the number of opponents you have. SVar:TrigToken:DB$ Token | TokenAmount$ Y | TokenScript$ b_2_2_zombie | TokenOwner$ You SVar:Y:PlayerCountOpponents$Amount diff --git a/forge-gui/res/cardsfolder/rebalanced/a-cloister_gargoyle.txt b/forge-gui/res/cardsfolder/rebalanced/a-cloister_gargoyle.txt index 5518835c584..d662a29c699 100644 --- a/forge-gui/res/cardsfolder/rebalanced/a-cloister_gargoyle.txt +++ b/forge-gui/res/cardsfolder/rebalanced/a-cloister_gargoyle.txt @@ -5,5 +5,5 @@ PT:0/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ DBVenture | TriggerDescription$ When CARDNAME enters, venture into the dungeon. SVar:DBVenture:DB$ Venture | Defined$ You S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 3 | AddKeyword$ Flying | CheckSVar$ X | SVarCompare$ GE1 | Description$ As long as you've completed a dungeon, CARDNAME gets +3/+0 and has flying. -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount Oracle:When Cloister Gargoyle enters, venture into the dungeon.\nAs long as you've completed a dungeon, Cloister Gargoyle gets +3/+0 and has flying. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-ellywick_tumblestrum.txt b/forge-gui/res/cardsfolder/rebalanced/a-ellywick_tumblestrum.txt index 6326cd1adfa..3a40b2cf6fc 100644 --- a/forge-gui/res/cardsfolder/rebalanced/a-ellywick_tumblestrum.txt +++ b/forge-gui/res/cardsfolder/rebalanced/a-ellywick_tumblestrum.txt @@ -9,5 +9,5 @@ SVar:IsLegendary:Count$ValidHand Creature.Legendary+IsRemembered SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True A:AB$ Effect | Cost$ SubCounter<6/LOYALTY> | Planeswalker$ True | Ultimate$ True | Name$ Emblem — Ellywick Tumblestrum | Image$ emblem_ellywick_tumblestrum | StaticAbilities$ STOverrun | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "Creatures you control have trample and haste and get +2/+2 for each differently named dungeon you've completed." SVar:STOverrun:Mode$ Continuous | Affected$ Creature.YouCtrl | AffectedZone$ Battlefield | AddPower$ X | AddToughness$ X | AddKeyword$ Trample & Haste | Description$ Creatures you control have trample and haste and get +2/+2 for each differently named dungeon you've completed. -SVar:X:PlayerCountPropertyYou$DifferentlyNamedDungeonsCompleted/Twice +SVar:X:DungeonsCompleted$DifferentCardNames/Twice Oracle:[+1]: Venture into the dungeon.\n[-2]: Look at the top six cards of your library. You may reveal a creature card from among them and put it into your hand. If it's legendary, you gain 3 life. Put the rest on the bottom of your library in a random order.\n[-6]: You get an emblem with "Creatures you control have trample and haste and get +2/+2 for each differently named dungeon you've completed." diff --git a/forge-gui/res/cardsfolder/rebalanced/a-precipitous_drop.txt b/forge-gui/res/cardsfolder/rebalanced/a-precipitous_drop.txt index 964588d0ffc..d756a4a6086 100644 --- a/forge-gui/res/cardsfolder/rebalanced/a-precipitous_drop.txt +++ b/forge-gui/res/cardsfolder/rebalanced/a-precipitous_drop.txt @@ -7,5 +7,5 @@ T:Mode$ ChangesZone | ValidCard$ Card.Self | Destination$ Battlefield | Execute$ SVar:TrigVenture:DB$ Venture S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ -X | AddToughness$ -X | Description$ Enchanted creature gets -2/-2. It gets -5/-5 instead as long as you've completed a dungeon. SVar:X:Count$Compare Y GE1.5.2 -SVar:Y:PlayerCountPropertyYou$DungeonsCompleted +SVar:Y:DungeonsCompleted$Amount Oracle:Enchant creature\nWhen Precipitous Drop enters, venture into the dungeon.\nEnchanted creature gets -2/-2. It gets -5/-5 instead as long as you've completed a dungeon. diff --git a/forge-gui/res/cardsfolder/s/safana_calimport_cutthroat.txt b/forge-gui/res/cardsfolder/s/safana_calimport_cutthroat.txt index 854c04f066e..067da524e15 100644 --- a/forge-gui/res/cardsfolder/s/safana_calimport_cutthroat.txt +++ b/forge-gui/res/cardsfolder/s/safana_calimport_cutthroat.txt @@ -6,7 +6,7 @@ K:Menace T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | CheckDefinedPlayer$ You.hasInitiative | Execute$ TrigTreasure | TriggerDescription$ At the beginning of your end step, if you have the initiative, create a Treasure token. If you've completed a dungeon, create three of those tokens instead. SVar:TrigTreasure:DB$ Token | TokenAmount$ X | TokenScript$ c_a_treasure_sac SVar:X:Count$Compare Y GE1.3.1 -SVar:Y:PlayerCountPropertyYou$DungeonsCompleted +SVar:Y:DungeonsCompleted$Amount K:Choose a Background AI:RemoveDeck:Random DeckHas:Ability$Token|Sacrifice & Type$Artifact|Treasure diff --git a/forge-gui/res/cardsfolder/s/sarevoks_tome.txt b/forge-gui/res/cardsfolder/s/sarevoks_tome.txt index 7fc15162f11..13451e69f62 100644 --- a/forge-gui/res/cardsfolder/s/sarevoks_tome.txt +++ b/forge-gui/res/cardsfolder/s/sarevoks_tome.txt @@ -8,5 +8,5 @@ SVar:X:Count$Initiative.2.1 A:AB$ DigUntil | Cost$ 3 T | Valid$ Card.nonLand | ValidDescription$ nonland | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | SubAbility$ DBPlay | CheckSVar$ Y | NoPutDesc$ True | SpellDescription$ Exile cards from the top of your library until you exile a nonland card. You may cast that card without paying its mana cost. Activate only if you've completed a dungeon. SVar:DBPlay:DB$ Play | Defined$ Remembered | DefinedDesc$ that card | ValidSA$ Spell | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:Y:PlayerCountPropertyYou$DungeonsCompleted +SVar:Y:DungeonsCompleted$Amount Oracle:When Sarevok's Tome enters, you take the initiative.\n{T}: Add {C}. If you have the initiative, add {C}{C} instead.\n{3}, {T}: Exile cards from the top of your library until you exile a nonland card. You may cast that card without paying its mana cost. Activate only if you've completed a dungeon. diff --git a/forge-gui/res/cardsfolder/t/tomb_of_horrors_adventurer.txt b/forge-gui/res/cardsfolder/t/tomb_of_horrors_adventurer.txt index af5eafc2d13..96b5c6982b1 100644 --- a/forge-gui/res/cardsfolder/t/tomb_of_horrors_adventurer.txt +++ b/forge-gui/res/cardsfolder/t/tomb_of_horrors_adventurer.txt @@ -6,7 +6,7 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S SVar:TrigInitiative:DB$ TakeInitiative T:Mode$ SpellCast | ValidCard$ Card.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigCopy | ActivatorThisTurnCast$ EQ2 | ValidActivatingPlayer$ You | TriggerDescription$ Whenever you cast your second spell each turn, copy it. If you've completed a dungeon, copy that spell twice instead. You may choose new targets for the copies. (A copy of a permanent spell becomes a token.) SVar:TrigCopy:DB$ CopySpellAbility | Amount$ Y | Defined$ TriggeredSpellAbility | MayChooseTarget$ True -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount SVar:Y:Count$Compare X GE1.2.1 DeckHas:Ability$Token Oracle:When Tomb of Horrors Adventurer enters, you take the initiative.\nWhenever you cast your second spell each turn, copy it. If you've completed a dungeon, copy that spell twice instead. You may choose new targets for the copies. (A copy of a permanent spell becomes a token.) diff --git a/forge-gui/res/cardsfolder/u/undermountain_adventurer.txt b/forge-gui/res/cardsfolder/u/undermountain_adventurer.txt index 2af00567659..363455136e2 100644 --- a/forge-gui/res/cardsfolder/u/undermountain_adventurer.txt +++ b/forge-gui/res/cardsfolder/u/undermountain_adventurer.txt @@ -6,6 +6,6 @@ K:Vigilance T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigInitiative | TriggerDescription$ When CARDNAME enters, you take the initiative. SVar:TrigInitiative:DB$ TakeInitiative A:AB$ Mana | Cost$ T | Produced$ G | Amount$ Y | SpellDescription$ Add {G}{G}. If you've completed a dungeon, add six {G} instead. -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount SVar:Y:Count$Compare X GE1.6.2 Oracle:Vigilance\nWhen Undermountain Adventurer enters, you take the initiative.\n{T}: Add {G}{G}. If you've completed a dungeon, add six {G} instead. diff --git a/forge-gui/res/cardsfolder/w/white_plume_adventurer.txt b/forge-gui/res/cardsfolder/w/white_plume_adventurer.txt index 0441ab0e7eb..4bd103e86a9 100644 --- a/forge-gui/res/cardsfolder/w/white_plume_adventurer.txt +++ b/forge-gui/res/cardsfolder/w/white_plume_adventurer.txt @@ -8,5 +8,5 @@ T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Opponent | TriggerZones$ Battlefiel SVar:TrigBranch:DB$ Branch | BranchConditionSVar$ X | TrueSubAbility$ DBUntapAll | FalseSubAbility$ DBUntap SVar:DBUntapAll:DB$ UntapAll | ValidCards$ Creature.YouCtrl SVar:DBUntap:DB$ Untap | UntapExactly$ True | UntapType$ Creature.YouCtrl+tapped | Amount$ 1 -SVar:X:PlayerCountPropertyYou$DungeonsCompleted +SVar:X:DungeonsCompleted$Amount Oracle:When White Plume Adventurer enters battlefield, you take the initiative.\nAt the beginning of each opponent's upkeep, untap a creature you control. If you've completed a dungeon, untap all creatures you control instead. From 39f6377f33da3d20336c460d9b394c704f1263cb Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 5 Oct 2025 19:12:38 +0200 Subject: [PATCH 077/230] remove leftover variable --- forge-game/src/main/java/forge/game/ability/AbilityUtils.java | 1 - 1 file changed, 1 deletion(-) 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 601f6df2d2a..5738433e1c2 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -3475,7 +3475,6 @@ public class AbilityUtils { final String[] sq = l[0].split("\\."); final String value = sq[0]; - final String[] calcX = value.split("\\$", 2); if (value.contains("NumPowerSurgeLands")) { return doXMath(player.getNumPowerSurgeLands(), m, source, ctb); From 81e1306cd7228185e0ebb56cab9857594b3f05af Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 5 Oct 2025 18:31:42 +0000 Subject: [PATCH 078/230] Update garland_royal_kidnapper.txt --- .../res/cardsfolder/upcoming/garland_royal_kidnapper.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/garland_royal_kidnapper.txt b/forge-gui/res/cardsfolder/upcoming/garland_royal_kidnapper.txt index 8f7fb85ac3e..ea349288514 100644 --- a/forge-gui/res/cardsfolder/upcoming/garland_royal_kidnapper.txt +++ b/forge-gui/res/cardsfolder/upcoming/garland_royal_kidnapper.txt @@ -2,8 +2,8 @@ Name:Garland, Royal Kidnapper ManaCost:2 U B Types:Legendary Creature Human Knight PT:3/4 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMonarch | TriggerDescription$ When NICKNAME enters, you become the monarch. -SVar:TrigMonarch:DB$ BecomeMonarch | Defined$ You +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMonarch | TriggerDescription$ When NICKNAME enters, target opponent becomes the monarch. +SVar:TrigMonarch:DB$ BecomeMonarch | ValidTgts$ Opponent T:Mode$ BecomeMonarch | ValidPlayer$ Opponent | TriggerZones$ Battlefield | Execute$ TrigEffect | TriggerDescription$ Whenever an opponent becomes the monarch, gain control of target creature that player controls for as long as they're the monarch. SVar:TrigEffect:DB$ Effect | ImprintCards$ Targeted | ValidTgts$ Creature.ControlledBy TriggeredPlayer | TgtPrompt$ Choose target creature that player controls | RememberObjects$ TriggeredPlayer | Triggers$ ExileMe | StaticAbilities$ GainControl | Duration$ Permanent SVar:GainControl:Mode$ Continuous | Affected$ Card.IsImprinted | CheckSVar$ X | SVarCompare$ EQ1 | GainControl$ You | Description$ You gain control of that creature for as long as that player is the monarch. @@ -12,4 +12,4 @@ SVar:ExileEffect:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ SVar:X:PlayerCountRememberedPlayer$HasPropertyisMonarch S:Mode$ Continuous | Affected$ Creature.YouCtrl+YouDontOwn| AddPower$ 2 | AddToughness$ 2 | Description$ Creatures you control but don't own get +2/+2 and can't be sacrificed. S:Mode$ CantSacrifice | ValidCard$ Creature.YouCtrl+YouDontOwn | Secondary$ True | SpellDescription$ Creatures you control but don't own get +2/+2 and can't be sacrificed. -Oracle:When Garland enters, you become the monarch.\nWhenever an opponent becomes the monarch, gain control of target creature that player controls for as long as they're the monarch.\nCreatures you control but don't own get +2/+2 and can't be sacrificed. +Oracle:When Garland enters, target opponent becomes the monarch.\nWhenever an opponent becomes the monarch, gain control of target creature that player controls for as long as they're the monarch.\nCreatures you control but don't own get +2/+2 and can't be sacrificed. From c4a3b5c5453f6a2753cc769a7407ab41297a6782 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Mon, 6 Oct 2025 13:01:37 +0200 Subject: [PATCH 079/230] Update avatar_aang_aang_master_of_elements.txt --- .../cardsfolder/a/avatar_aang_aang_master_of_elements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/cardsfolder/a/avatar_aang_aang_master_of_elements.txt b/forge-gui/res/cardsfolder/a/avatar_aang_aang_master_of_elements.txt index 6fda3cadd20..52806599b90 100644 --- a/forge-gui/res/cardsfolder/a/avatar_aang_aang_master_of_elements.txt +++ b/forge-gui/res/cardsfolder/a/avatar_aang_aang_master_of_elements.txt @@ -19,9 +19,9 @@ Types:Legendary Creature Avatar Ally PT:6/6 K:Flying S:Mode$ ReduceCost | ValidCard$ Card | Type$ Spell | Activator$ You | Amount$ 1 | Color$ W U B R G | Description$ Spells you cast cost {W}{U}{B}{R}{G} less to cast. (This can reduce generic costs.) -T:Mode$ Phase | Phase$ Upkeep | TriggerZones$ Battlefield | Execute$ TrigToken | OptionalDecider$ You | TriggerDescription$ At the beginning of each upkeep, you may transform CARDNAME. If you do, you gain 4 life, draw four cards, put four +1/+1 counters on him, and he deals 4 damage to each opponent. -SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform | RememberChanged$ True | SubAbility$ DBGainLife -SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 4 | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ DBDraw +T:Mode$ Phase | Phase$ Upkeep | TriggerZones$ Battlefield | Execute$ TrigTransform | OptionalDecider$ You | TriggerDescription$ At the beginning of each upkeep, you may transform CARDNAME. If you do, you gain 4 life, draw four cards, put four +1/+1 counters on him, and he deals 4 damage to each opponent. +SVar:TrigTransform:DB$ SetState | Defined$ Self | Mode$ Transform | RememberChanged$ True | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 4 | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ DBDraw SVar:DBDraw:DB$ Draw | NumCards$ 4 | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ DBPutCounter SVar:DBPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 4 | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ DBDamage SVar:DBDamage:DB$ DealDamage | Defined$ Player.Opponent | NumDmg$ 4 | CounterNum$ 4 | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ DBCleanup From a5765905d81ac023871dfee16973a2e4ae85fbe5 Mon Sep 17 00:00:00 2001 From: Eradev Date: Sun, 5 Oct 2025 12:23:58 -0400 Subject: [PATCH 080/230] Fix Quest start with boosters and unsactionned pool --- .../src/main/java/forge/gamemodes/quest/BoosterUtils.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java b/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java index ceea7e594e9..5615effad3a 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java @@ -99,7 +99,10 @@ public final class BoosterUtils { if (userPrefs != null && userPrefs.getPoolType() == StartingPoolPreferences.PoolType.BOOSTERS) { - for (InventoryItem inventoryItem : generateRandomBoosterPacks(userPrefs.getNumberOfBoosters(), formatStartingPool.editionLegalPredicate)) { + Predicate editionLegalPredicate = formatStartingPool == null + ? cardEdition -> true + : formatStartingPool.editionLegalPredicate; + for (InventoryItem inventoryItem : generateRandomBoosterPacks(userPrefs.getNumberOfBoosters(), editionLegalPredicate)) { cards.addAll(((BoosterPack) inventoryItem).getCards()); } From 5bbc86c1a1449c3726fea643a5b2df5db6b78aa1 Mon Sep 17 00:00:00 2001 From: Eradev Date: Sun, 5 Oct 2025 12:30:03 -0400 Subject: [PATCH 081/230] Fix crash in Quest rewards screen --- .../gamemodes/quest/QuestEventDraft.java | 44 +++---------------- 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java index 0dc634a9d86..03d03970ffd 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java @@ -62,7 +62,6 @@ import forge.util.storage.IStorage; public class QuestEventDraft implements IQuestEvent { public static class QuestDraftPrizes { - public int credits; public List boosterPacks; public List individualCards; @@ -85,7 +84,6 @@ public class QuestEventDraft implements IQuestEvent { public void addSelectedCard(final PaperCard card) { FModel.getQuest().getCards().addSingleCard(card, 1); } - } public static final String UNDETERMINED = "quest_draft_undetermined_place"; @@ -233,7 +231,6 @@ public class QuestEventDraft implements IQuestEvent { } public void setWinner(final String playerName) { - if (QuestDraftUtils.TOURNAMENT_TOGGLE) { TournamentPairing pairing = bracket.getNextPairing(); for(TournamentPlayer player : pairing.getPairedPlayers()) { @@ -290,7 +287,6 @@ public class QuestEventDraft implements IQuestEvent { * Generates the prizes for the player and saves them to the current quest. */ public QuestDraftPrizes collectPrizes() { - final int place = getPlayerPlacement(); int prizePool = entryFee * 9; @@ -345,11 +341,9 @@ public class QuestEventDraft implements IQuestEvent { } return null; - } private QuestDraftPrizes generateFirstPlacePrizes(final int prizePool) { - int credits = 2 * (prizePool / 3); //First place gets 2/3 the total prize pool final List cards = new ArrayList<>(); final List boosters = new ArrayList<>(); @@ -366,11 +360,9 @@ public class QuestEventDraft implements IQuestEvent { awardSelectedRare(prizes); return prizes; - } private QuestDraftPrizes generateSecondPlacePrizes(final int prizePool) { - int credits = prizePool / 3; //Second place gets 1/3 the total prize pool final List cards = new ArrayList<>(); final List boosters = new ArrayList<>(); @@ -388,11 +380,9 @@ public class QuestEventDraft implements IQuestEvent { awardSelectedRare(prizes); return prizes; - } private QuestDraftPrizes generateThirdPlacePrizes() { - final int credits = 0; final List cards = new ArrayList<>(); @@ -407,11 +397,9 @@ public class QuestEventDraft implements IQuestEvent { prizes.individualCards = cards; return prizes; - } private QuestDraftPrizes generateFourthPlacePrizes() { - final int credits = 0; final List cards = new ArrayList<>(); @@ -440,7 +428,6 @@ public class QuestEventDraft implements IQuestEvent { } private void awardSelectedRare(final QuestDraftPrizes prizes) { - final List possibleCards = new ArrayList<>(); final List cardNames = new ArrayList<>(); @@ -466,7 +453,6 @@ public class QuestEventDraft implements IQuestEvent { } private PaperCard getPromoCard() { - final CardEdition randomEdition = getRandomEdition(); final List cardsInEdition = new ArrayList<>(); final List cardNames = new ArrayList<>(); @@ -480,6 +466,11 @@ public class QuestEventDraft implements IQuestEvent { } } + // For sets such as MB1 that only have cards from PLST. + if (cardsInEdition.isEmpty()) { + return FModel.getQuest().getCards().addRandomRare(); + } + EditionEntry randomCard; PaperCard promo = null; @@ -495,7 +486,6 @@ public class QuestEventDraft implements IQuestEvent { } return promo; - } private CardEdition getRandomEdition() { @@ -506,7 +496,6 @@ public class QuestEventDraft implements IQuestEvent { } return editions.get((int) (MyRandom.getRandom().nextDouble() * editions.size())); - } private Set getAllEditions() { @@ -517,7 +506,6 @@ public class QuestEventDraft implements IQuestEvent { } return editions; - } private static int getBoosterPrice(final BoosterPack booster) { @@ -528,11 +516,9 @@ public class QuestEventDraft implements IQuestEvent { value = MAP_PRICES.getOrDefault(boosterName, 395); return value; - } public boolean playerHasMatchesLeft() { - if (QuestDraftUtils.TOURNAMENT_TOGGLE) { return !bracket.isTournamentOver() && bracket.isPlayerRemaining(-1); } @@ -585,7 +571,6 @@ public class QuestEventDraft implements IQuestEvent { } return nextMatchIndex != -1 && standings[nextMatchIndex].equals(UNDETERMINED); - } public int getPlayerPlacement() { @@ -622,11 +607,9 @@ public class QuestEventDraft implements IQuestEvent { } return -1; - } public String getPlacementString() { - final int place = getPlayerPlacement(); String output; @@ -650,7 +633,6 @@ public class QuestEventDraft implements IQuestEvent { } return output; - } public boolean canEnter() { @@ -683,7 +665,6 @@ public class QuestEventDraft implements IQuestEvent { } public static class QuestDraftFormat implements Comparable { - private CardEdition edition; private CardBlock block; @@ -746,11 +727,9 @@ public class QuestEventDraft implements IQuestEvent { public int compareTo(final QuestDraftFormat other) { return toString().compareToIgnoreCase(other.toString()); } - } private static List getAllowedSets(final QuestController quest) { - final List allowedQuestSets = new ArrayList<>(); if (quest.getFormat() != null) { @@ -771,11 +750,9 @@ public class QuestEventDraft implements IQuestEvent { } return allowedQuestSets; - } private static List getBlocks() { - final List blocks = new ArrayList<>(); final IStorage storage = FModel.getBlocks(); @@ -786,11 +763,9 @@ public class QuestEventDraft implements IQuestEvent { } return blocks; - } public static List getAvailableFormats(final QuestController quest) { - final List allowedQuestSets = getAllowedSets(quest); final List possibleFormats = new ArrayList<>(); final List blocks = getBlocks(); @@ -812,7 +787,6 @@ public class QuestEventDraft implements IQuestEvent { if (blockAllowed) { possibleFormats.add(new QuestDraftFormat(block)); } - } for (CardEdition allowedQuestSet : allowedQuestSets) { @@ -840,7 +814,6 @@ public class QuestEventDraft implements IQuestEvent { Collections.sort(possibleFormats); return possibleFormats; - } /** @@ -849,7 +822,6 @@ public class QuestEventDraft implements IQuestEvent { * @return The created draft or null in the event no draft could be created. */ public static QuestEventDraft getRandomDraftOrNull(final QuestController quest) { - final List possibleFormats = getAvailableFormats(quest); if (possibleFormats.isEmpty()) { @@ -858,7 +830,6 @@ public class QuestEventDraft implements IQuestEvent { Collections.shuffle(possibleFormats); return getDraftOrNull(quest, possibleFormats.get(0)); - } /** @@ -866,7 +837,6 @@ public class QuestEventDraft implements IQuestEvent { * @return The created draft or null in the event no draft could be created. */ public static QuestEventDraft getDraftOrNull(final QuestController quest, final QuestDraftFormat format) { - final QuestEventDraft event = new QuestEventDraft(format.getName()); if (format.isSet()) { @@ -936,13 +906,11 @@ public class QuestEventDraft implements IQuestEvent { event.aiIcons[i] = icon; usedNames.add(event.aiNames[i]); usedIcons.add(icon); - } event.bracket = createBracketFromStandings(event.standings, event.aiNames, event.aiIcons); return event; - } private static int calculateEntryFee(final String[] boosters) { @@ -960,7 +928,6 @@ public class QuestEventDraft implements IQuestEvent { } return (int) (entryFee * 1.5); - } private static Set getSetCombos(final QuestController quest, final CardBlock block) { @@ -1041,7 +1008,6 @@ public class QuestEventDraft implements IQuestEvent { } return possibleCombinations; - } public static TournamentBracket createBracketFromStandings(String[] standings, String[] aiNames, int[] aiIcons) { From 5b2a56bd4d3f5bbdb400ac369b4a68220e7722a4 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Mon, 6 Oct 2025 17:52:18 +0000 Subject: [PATCH 082/230] Update the_rise_of_sozin_fire_lord_sozin.txt --- .../res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt b/forge-gui/res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt index dbf399f6d6b..5c28f921a82 100644 --- a/forge-gui/res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt +++ b/forge-gui/res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt @@ -8,6 +8,7 @@ SVar:DBExile:DB$ ChangeZone | ValidTgts$ Opponent | ChangeType$ Card.NamedCard | SVar:DBTransform:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBReturn | SpellDescription$ Exile this Saga, then return it to the battlefield transformed under your control. SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | Transformed$ True | GainControl$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +AlternateMode:DoubleFaced Oracle:(As this Saga enters and after your draw step, add a lore counter.)\nI — Destroy all creatures.\nII — Choose a card name. Search target opponent's graveyard, hand, and library for up to four cards with that name and exile them. Then that player shuffles.\nIII — Exile this Saga, then return it to the battlefield transformed under your control. ALTERNATE From 04f454ae8394d2ea8455770c48f1321ba81278e5 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Tue, 7 Oct 2025 05:32:41 +0800 Subject: [PATCH 083/230] prevent NPE --- forge-gui-mobile/src/forge/screens/match/views/VStack.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui-mobile/src/forge/screens/match/views/VStack.java b/forge-gui-mobile/src/forge/screens/match/views/VStack.java index 6a88375b573..22fa22d1771 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VStack.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VStack.java @@ -224,17 +224,17 @@ public class VStack extends FDropDown { activeItem.getLeft() + VStack.CARD_WIDTH * FCardPanel.TARGET_ORIGIN_FACTOR_X + VStack.PADDING + VStack.BORDER_THICKNESS, activeItem.getTop() + VStack.CARD_HEIGHT * FCardPanel.TARGET_ORIGIN_FACTOR_Y + VStack.PADDING + VStack.BORDER_THICKNESS); - PlayerView activator = activeStackInstance.getActivatingPlayer(); + PlayerView activator = activeStackInstance == null ? null : activeStackInstance.getActivatingPlayer(); arrowOrigin = arrowOrigin.add(screenPos.x, screenPos.y); StackItemView instance = activeStackInstance; while (instance != null) { for (CardView c : instance.getTargetCards()) { - TargetingOverlay.ArcConnection conn = activator.isOpponentOf(c.getController()) ? TargetingOverlay.ArcConnection.FoesStackTargeting : TargetingOverlay.ArcConnection.FriendsStackTargeting; + TargetingOverlay.ArcConnection conn = activator != null && activator.isOpponentOf(c.getController()) ? TargetingOverlay.ArcConnection.FoesStackTargeting : TargetingOverlay.ArcConnection.FriendsStackTargeting; TargetingOverlay.drawArrow(g, arrowOrigin, VCardDisplayArea.CardAreaPanel.get(c).getTargetingArrowOrigin(), conn); } for (PlayerView p : instance.getTargetPlayers()) { - TargetingOverlay.ArcConnection conn = activator.isOpponentOf(p) ? TargetingOverlay.ArcConnection.FoesStackTargeting : TargetingOverlay.ArcConnection.FriendsStackTargeting; + TargetingOverlay.ArcConnection conn = activator != null && activator.isOpponentOf(p) ? TargetingOverlay.ArcConnection.FoesStackTargeting : TargetingOverlay.ArcConnection.FriendsStackTargeting; TargetingOverlay.drawArrow(g, arrowOrigin, MatchScreen.getPlayerPanel(p).getAvatar().getTargetingArrowOrigin(), conn); } instance = instance.getSubInstance(); From bbf7a2597986a8276af68defaa1caa60280f9376 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Mon, 6 Oct 2025 20:50:28 +0200 Subject: [PATCH 084/230] Refactor UnlockedDoors using PlayerProperty --- .../java/forge/game/ability/AbilityUtils.java | 29 +++---------------- .../c/central_elevator_promising_stairs.txt | 2 +- .../res/cardsfolder/r/rampaging_soulrager.txt | 2 +- .../s/smoky_lounge_misty_salon.txt | 2 +- 4 files changed, 7 insertions(+), 28 deletions(-) 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 5738433e1c2..2f8d310fcc7 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -2543,34 +2543,13 @@ public class AbilityUtils { return doXMath(CardLists.getValidCardCount(game.getLeftGraveyardThisTurn(), validFilter, player, c, ctb), expr, c, ctb); } - // Count$UnlockedDoors - if (sq[0].startsWith("UnlockedDoors")) { - final String[] workingCopy = l[0].split(" ", 2); - final String validFilter = workingCopy[1]; - - int unlocked = 0; - for (Card doorCard : CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validFilter, player, c, ctb)) { - unlocked += doorCard.getUnlockedRooms().size(); - } - - return doXMath(unlocked, expr, c, ctb); + if (sq[0].equals("UnlockedDoors")) { + return doXMath(player.getUnlockedDoors().size(), expr, c, ctb); } - // Count$DistinctUnlockedDoors // Counts the distinct names of unlocked doors. Used for the "Promising Stairs" - if (sq[0].startsWith("DistinctUnlockedDoors")) { - final String[] workingCopy = l[0].split(" ", 2); - final String validFilter = workingCopy[1]; - - Set viewedNames = new HashSet<>(); - for (Card doorCard : CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), validFilter, player, c, ctb)) { - for(CardStateName stateName : doorCard.getUnlockedRooms()) { - viewedNames.add(doorCard.getState(stateName).getName()); - } - } - int distinctUnlocked = viewedNames.size(); - - return doXMath(distinctUnlocked, expr, c, ctb); + if (sq[0].equals("DistinctUnlockedDoors")) { + return doXMath(Sets.newHashSet(player.getUnlockedDoors()).size(), expr, c, ctb); } // Manapool diff --git a/forge-gui/res/cardsfolder/c/central_elevator_promising_stairs.txt b/forge-gui/res/cardsfolder/c/central_elevator_promising_stairs.txt index 26ca18797da..751dcb0cc7a 100644 --- a/forge-gui/res/cardsfolder/c/central_elevator_promising_stairs.txt +++ b/forge-gui/res/cardsfolder/c/central_elevator_promising_stairs.txt @@ -14,5 +14,5 @@ Types:Enchantment Room T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSurveil | TriggerDescription$ At the beginning of your upkeep, surveil 1. You win the game if there are eight or more different names among unlocked doors of Rooms you control. SVar:TrigSurveil:DB$ Surveil | SubAbility$ DBWin SVar:DBWin:DB$ WinsGame | Defined$ You | ConditionCheckSVar$ RoomsUnlocked | ConditionSVarCompare$ GE8 -SVar:RoomsUnlocked:Count$DistinctUnlockedDoors Card.Room+YouCtrl +SVar:RoomsUnlocked:Count$DistinctUnlockedDoors Oracle:At the beginning of your upkeep, surveil 1. You win the game if there are eight or more different names among unlocked doors of Rooms you control. diff --git a/forge-gui/res/cardsfolder/r/rampaging_soulrager.txt b/forge-gui/res/cardsfolder/r/rampaging_soulrager.txt index af9fc947061..86f35bb255f 100644 --- a/forge-gui/res/cardsfolder/r/rampaging_soulrager.txt +++ b/forge-gui/res/cardsfolder/r/rampaging_soulrager.txt @@ -3,5 +3,5 @@ ManaCost:2 R Types:Creature Spirit PT:1/4 S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 3 | CheckSVar$ X | SVarCompare$ GE2 | Description$ CARDNAME gets +3/+0 as long as there are two or more unlocked doors among Rooms you control. -SVar:X:Count$UnlockedDoors Card.Room+YouCtrl +SVar:X:Count$UnlockedDoors Oracle:Rampaging Soulrager gets +3/+0 as long as there are two or more unlocked doors among Rooms you control. diff --git a/forge-gui/res/cardsfolder/s/smoky_lounge_misty_salon.txt b/forge-gui/res/cardsfolder/s/smoky_lounge_misty_salon.txt index 831b569d199..0f2b8e92648 100644 --- a/forge-gui/res/cardsfolder/s/smoky_lounge_misty_salon.txt +++ b/forge-gui/res/cardsfolder/s/smoky_lounge_misty_salon.txt @@ -13,5 +13,5 @@ ManaCost:3 U Types:Enchantment Room T:Mode$ UnlockDoor | ValidPlayer$ You | ValidCard$ Card.Self | ThisDoor$ True | Execute$ TrigToken | TriggerDescription$ When you unlock this door, create an X/X blue Spirit creature token with flying, where X is the number of unlocked doors among Rooms you control. SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ u_x_x_spirit_flying | TokenPower$ X | TokenToughness$ X | TokenOwner$ You -SVar:X:Count$UnlockedDoors Card.Room+YouCtrl +SVar:X:Count$UnlockedDoors Oracle:(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)\nWhen you unlock this door, create an X/X blue Spirit creature token with flying, where X is the number of unlocked doors among Rooms you control. From 1b8c87e578171f7893bf6ca139c6730f25adea9c Mon Sep 17 00:00:00 2001 From: Eradev Date: Mon, 6 Oct 2025 23:52:17 -0400 Subject: [PATCH 085/230] Fix event init --- .../src/forge/adventure/util/AdventureEventController.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java b/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java index 15a3b2100c5..128ee1a92df 100644 --- a/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java +++ b/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java @@ -113,9 +113,10 @@ public class AdventureEventController implements Serializable { return null; } - // If chosen event seed recommends a 4 person pod, run it as a RoundRobin - CardEdition firstSet = e.cardBlock.getSets().get(0); - int podSize = firstSet.getDraftOptions().getRecommendedPodSize(); + // If the chosen event seed recommends a four-person pod, run it as a RoundRobin + // Set can be null when it is only a meta set such as some Jumpstart events. + CardEdition firstSet = e.cardBlock.getSets().isEmpty() ? null : e.cardBlock.getSets().get(0); + int podSize = firstSet == null ? 8 : firstSet.getDraftOptions().getRecommendedPodSize(); e.sourceID = pointID; e.eventOrigin = eventOrigin; From ab1d423e2801bbffb769a064d51511eddb2524a7 Mon Sep 17 00:00:00 2001 From: Eradev Date: Tue, 7 Oct 2025 00:31:55 -0400 Subject: [PATCH 086/230] Update the_soul_stone.txt (#8854) --- forge-gui/res/cardsfolder/t/the_soul_stone.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/t/the_soul_stone.txt b/forge-gui/res/cardsfolder/t/the_soul_stone.txt index 10c9e9a55ef..bda18d419c7 100644 --- a/forge-gui/res/cardsfolder/t/the_soul_stone.txt +++ b/forge-gui/res/cardsfolder/t/the_soul_stone.txt @@ -3,7 +3,7 @@ ManaCost:1 B Types:Legendary Artifact Infinity Stone K:Indestructible A:AB$ Mana | Cost$ T | Produced$ B | SpellDescription$ Add {B}. -A:AB$ AlterAttribute | Cost$ 6 B Exile<1/Creature> | Defined$ Self | Attributes$ Harnessed | StackDescription$ SpellDescription | SpellDescription$ Harness CARDNAME. (Once harnessed, its ∞ ability is active.) +A:AB$ AlterAttribute | Cost$ 6 B T Exile<1/Creature> | Defined$ Self | Attributes$ Harnessed | StackDescription$ SpellDescription | SpellDescription$ Harness CARDNAME. (Once harnessed, its ∞ ability is active.) T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | IsPresent$ Card.Self+harnessed | TriggerZones$ Battlefield | Execute$ TrigReturn | TriggerDescription$ ∞ — At the beginning of your upkeep, return target creature card from your graveyard to the battlefield. SVar:TrigReturn:DB$ ChangeZone | ValidTgts$ Creature.YouOwn | Origin$ Graveyard | Destination$ Battlefield SVar:PlayMain1:TRUE From 5fae32e048f789af6df02e7193bb81c673b80220 Mon Sep 17 00:00:00 2001 From: Renato Filipe Vidal Santos <45150760+dracontes@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:58:21 +0000 Subject: [PATCH 087/230] Add files via upload (#8846) Co-authored-by: Agetian --- .../res/adventure/Amonkhet/world/shops.json | 6 +- .../Crystal_Kingdoms/world/quests.json | 2 +- .../Crystal_Kingdoms/world/shops.json | 2 +- .../world/town_names_blue.txt | 2 +- .../decks/shops/inn_Missionaries.dck | 4 +- .../Innistrad/decks/shops/inn_Peasants.dck | 2 +- .../decks/standard/INN/inn_peasant_easy.dck | 2 +- .../decks/standard/INN/inn_the_whisperers.dck | 2 +- .../starter/inn/Adventure - INN Low Red.dck | 2 +- .../cave/inn_cave_river_entrance.tmx | 2 +- .../map/Innistrad/library/inn_library.tmx | 2 +- .../res/adventure/Innistrad/world/items.json | 2 +- .../res/adventure/Innistrad/world/quests.json | 146 +++++++++--------- .../res/adventure/Innistrad/world/shops.json | 22 +-- .../Shandalar Old Border/world/enemies.json | 12 +- .../Shandalar Old Border/world/quests.json | 86 +++++------ .../Shandalar Old Border/world/shops.json | 10 +- .../Shandalar Old Border/world/shops.json.bak | 10 +- .../world/town_names_black.txt | 2 +- .../world/town_names_blue.txt | 2 +- .../world/town_names_green.txt | 2 +- .../world/town_names_waste.txt | 10 +- .../res/adventure/Shandalar/world/quests.json | 2 +- .../res/adventure/Shandalar/world/shops.json | 2 +- .../Shandalar/world/town_names_blue.txt | 2 +- .../common/maps/map/grove/grove_1_bears.tmx | 2 +- .../library_of_varsil_3.tmx | 2 +- .../adventure/common/maps/map/maze/maze_2.tmx | 2 +- 28 files changed, 172 insertions(+), 172 deletions(-) diff --git a/forge-gui/res/adventure/Amonkhet/world/shops.json b/forge-gui/res/adventure/Amonkhet/world/shops.json index 575c86f6a9e..cbbeff52fb3 100644 --- a/forge-gui/res/adventure/Amonkhet/world/shops.json +++ b/forge-gui/res/adventure/Amonkhet/world/shops.json @@ -170,13 +170,13 @@ "rewards": [ { "count":6, - "cardText": "draw(s)?|(exile|reveal|look|search).*library|scry|seek|conjure ", + "cardText": "draw(s)?|(exile|reveal|look|search).*library|scry|seek|conjure", "colors": ["blue"], "editions":["AKH","HOU","MP2","AKR"] }, { "count":2, - "cardText": "draw(s)?|(exile|reveal|look|search).*library|scry|seek|conjure ", + "cardText": "draw(s)?|(exile|reveal|look|search).*library|scry|seek|conjure", "editions":["AKH","HOU","MP2","AKR"] }] }, @@ -1391,7 +1391,7 @@ }] },{ "name":"Azorius", -"description":"Azorious Shop, LLC", +"description":"Azorius Shop, LLC", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"AzoriusShop", "rewards": [ diff --git a/forge-gui/res/adventure/Crystal_Kingdoms/world/quests.json b/forge-gui/res/adventure/Crystal_Kingdoms/world/quests.json index fd558d2620a..69c57ac4ce4 100644 --- a/forge-gui/res/adventure/Crystal_Kingdoms/world/quests.json +++ b/forge-gui/res/adventure/Crystal_Kingdoms/world/quests.json @@ -6347,7 +6347,7 @@ "POIReference": "" } ], - "name": "I'll take care of it, note the location of the factory on my map.(Accept Quest) (WARNING HARD QUEST)", + "name": "I'll take care of it. If you'd note the location of the factory on my map... (Accept Quest) (WARNING HARD QUEST)", "text": "Once you have vanquished the mechanical threat and quelled the chaos within the factory, return to me, Maven the Alchemist, and you shall be rewarded handsomely for your bravery and service to our community. Be warned, however, for the path ahead will test your mettle, cunning, and combat prowess. May fortune favor you on this perilous undertaking!" }, { diff --git a/forge-gui/res/adventure/Crystal_Kingdoms/world/shops.json b/forge-gui/res/adventure/Crystal_Kingdoms/world/shops.json index c4daabc2a53..fda46190466 100644 --- a/forge-gui/res/adventure/Crystal_Kingdoms/world/shops.json +++ b/forge-gui/res/adventure/Crystal_Kingdoms/world/shops.json @@ -1439,7 +1439,7 @@ }] },{ "name":"Azorius", -"description":"Azorious Shop, LLC", +"description":"Azorius Shop, LLC", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"AzoriusShop", "rewards": [ diff --git a/forge-gui/res/adventure/Crystal_Kingdoms/world/town_names_blue.txt b/forge-gui/res/adventure/Crystal_Kingdoms/world/town_names_blue.txt index c7bf62dbd28..565a46be16b 100644 --- a/forge-gui/res/adventure/Crystal_Kingdoms/world/town_names_blue.txt +++ b/forge-gui/res/adventure/Crystal_Kingdoms/world/town_names_blue.txt @@ -335,7 +335,7 @@ Fourmill Run Port Rachkham Cloudy Shallows Slumnis -Silver Pointe +Silver Point Abjuration Point Crow's Nest The Rookery diff --git a/forge-gui/res/adventure/Innistrad/decks/shops/inn_Missionaries.dck b/forge-gui/res/adventure/Innistrad/decks/shops/inn_Missionaries.dck index 6c0b5337885..1f58bf6f208 100644 --- a/forge-gui/res/adventure/Innistrad/decks/shops/inn_Missionaries.dck +++ b/forge-gui/res/adventure/Innistrad/decks/shops/inn_Missionaries.dck @@ -13,7 +13,7 @@ Name=INN_Missionaries 2 Blazing Torch|ISD|1 1 Burn at the Stake|AVR|1 1 Chapel Geist|ISD|1 -2 Chaplain of ALms|MID|1 +2 Chaplain of Alms|MID|1 1 Chaplain's Blessing|SOI|1 1 Cloistered Youth|ISD|1 2 Crossroads Consecrator|EMN|1 @@ -32,7 +32,7 @@ Name=INN_Missionaries 1 Forsaken Sanctuary|SOI|1 1 Geist of the Lonely Vigil|EMN|1 1 Isolated Chapel|ISD|1 -1 Jerren. Corrupted Bishop|MID|1 +1 Jerren, Corrupted Bishop|MID|1 1 Kindly Ancestor|VOW|1 1 Mad Prophet|SOI|1 1 Make a Wish|ISD|1 diff --git a/forge-gui/res/adventure/Innistrad/decks/shops/inn_Peasants.dck b/forge-gui/res/adventure/Innistrad/decks/shops/inn_Peasants.dck index 489270b1e9a..65e9743589d 100644 --- a/forge-gui/res/adventure/Innistrad/decks/shops/inn_Peasants.dck +++ b/forge-gui/res/adventure/Innistrad/decks/shops/inn_Peasants.dck @@ -1,7 +1,7 @@ [metadata] Name=INN_Peasants [Main] -1 Alchemists's Apprentice|AVR|1 +1 Alchemist's Apprentice|AVR|1 3 Ambitious Farmhand|MID|1 1 Apothecary Geist|SOI|1 3 Baithook Angler|MID|1 diff --git a/forge-gui/res/adventure/Innistrad/decks/standard/INN/inn_peasant_easy.dck b/forge-gui/res/adventure/Innistrad/decks/standard/INN/inn_peasant_easy.dck index f1d9c987a53..11bc4451108 100644 --- a/forge-gui/res/adventure/Innistrad/decks/standard/INN/inn_peasant_easy.dck +++ b/forge-gui/res/adventure/Innistrad/decks/standard/INN/inn_peasant_easy.dck @@ -6,7 +6,7 @@ Name=INN_peasant_easy 2 Angel's Mercy|AVR|1 3 Ambitious Farmhand|MID|1 2 Beloved Beggar|MID|1 -2 Berieved Survivor|MID|1 +2 Bereaved Survivor|MID|1 2 Doomed Traveler|ISD|1 2 Bar the Door|DKA|1 2 Gather the Townsfolk|DKA|1 diff --git a/forge-gui/res/adventure/Innistrad/decks/standard/INN/inn_the_whisperers.dck b/forge-gui/res/adventure/Innistrad/decks/standard/INN/inn_the_whisperers.dck index bd63094a76b..df72a6bc53c 100644 --- a/forge-gui/res/adventure/Innistrad/decks/standard/INN/inn_the_whisperers.dck +++ b/forge-gui/res/adventure/Innistrad/decks/standard/INN/inn_the_whisperers.dck @@ -11,7 +11,7 @@ Name=INN_The_Whisperers 2 Battleground Geist|ISD|1 2 Drogskol Captain|DKA|1 2 Gallows Warden|ISD|1 -2 Midknight Haunting|ISD|1 +2 Midnight Haunting|ISD|1 2 Malevolent Hermit|MID|1 2 Thing in the Ice|SOI|1 2 Ambitious Farmhand|MID|1 diff --git a/forge-gui/res/adventure/Innistrad/decks/starter/inn/Adventure - INN Low Red.dck b/forge-gui/res/adventure/Innistrad/decks/starter/inn/Adventure - INN Low Red.dck index 8ab672e7ea3..abcb735cc91 100644 --- a/forge-gui/res/adventure/Innistrad/decks/starter/inn/Adventure - INN Low Red.dck +++ b/forge-gui/res/adventure/Innistrad/decks/starter/inn/Adventure - INN Low Red.dck @@ -16,7 +16,7 @@ Name=Adventure - INN Low Red 1 Brimstone Vandal|MID|1 2 Festival Crasher|MID|1 2 Pyre Hound|SOI|1 -1 Curse of BLoodletting|DKA|1 +1 Curse of Bloodletting|DKA|1 1 Incendiary Flow|EMN|1 1 Rage Thrower|ISD|1 1 Spellrune Painter|MID|1 diff --git a/forge-gui/res/adventure/Innistrad/maps/map/Innistrad/cave/inn_cave_river_entrance.tmx b/forge-gui/res/adventure/Innistrad/maps/map/Innistrad/cave/inn_cave_river_entrance.tmx index a5e8167f30b..567fcfa8363 100644 --- a/forge-gui/res/adventure/Innistrad/maps/map/Innistrad/cave/inn_cave_river_entrance.tmx +++ b/forge-gui/res/adventure/Innistrad/maps/map/Innistrad/cave/inn_cave_river_entrance.tmx @@ -268,7 +268,7 @@ 0,4901,4902,4748,782,3146,1259,621,782,782,3146,1259,621,3146,1259,1259,1259,621,782,462,4743,4744,0,0,0,0,0,0,0,0,0,0 - + 0,0,22705,0,0,0,0,0,0,0,0,0,0,0,0,0,7082,0,0,22547,0,24596,0,0,0,0,0,0,0,0,0,0, 22550,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, diff --git a/forge-gui/res/adventure/Innistrad/maps/map/Innistrad/library/inn_library.tmx b/forge-gui/res/adventure/Innistrad/maps/map/Innistrad/library/inn_library.tmx index 0b9363732ff..45a693d0027 100644 --- a/forge-gui/res/adventure/Innistrad/maps/map/Innistrad/library/inn_library.tmx +++ b/forge-gui/res/adventure/Innistrad/maps/map/Innistrad/library/inn_library.tmx @@ -199,7 +199,7 @@ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 - + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, diff --git a/forge-gui/res/adventure/Innistrad/world/items.json b/forge-gui/res/adventure/Innistrad/world/items.json index 3df7d3e1343..0b2cd30ee90 100644 --- a/forge-gui/res/adventure/Innistrad/world/items.json +++ b/forge-gui/res/adventure/Innistrad/world/items.json @@ -343,7 +343,7 @@ "questItem": true }, { - "name": "Grolnoks Key", + "name": "Grolnok's Key", "iconName": "StrangeKey", "questItem": true }, diff --git a/forge-gui/res/adventure/Innistrad/world/quests.json b/forge-gui/res/adventure/Innistrad/world/quests.json index d5f1d68ccc0..b84ce987936 100644 --- a/forge-gui/res/adventure/Innistrad/world/quests.json +++ b/forge-gui/res/adventure/Innistrad/world/quests.json @@ -41,7 +41,7 @@ }, { "name": "\"And what if I find the right people myself?\"", - "text": "He shrugs as though that wouldn't bother him. \"Then I'll have to find someone bigger, badder, and most importantly faster than you to work with.\"", + "text": "He shrugs as though that wouldn't bother him. \"Then I'll have to find someone bigger, badder, and, most importantly, faster than you to work with.\"", "options": [ { "name": "\"Good luck with that.\" (Decline Quest)" @@ -435,7 +435,7 @@ ], "objective": "Travel", "prologue": { - "text": "Nothing like a really long walk to strech the legs, right? You could likely save yourself some time with the right spells, but... is that going to be safe?", + "text": "Nothing like a really long walk to stretch the legs, right? You could likely save yourself some time with the right spells, but... is that going to be safe?", "options": [ { "name": "(Begin your quest)" @@ -592,7 +592,7 @@ ] } ], - "name": "Quickly and discretely help yourself to a spell before continuing", + "name": "Quickly and discreetly help yourself to a spell before continuing", "text": "You receive a spell of dubious quality.", "options": [ { @@ -849,7 +849,7 @@ "POIReference": "" } ], - "text": "Consciously or unconsciously, you brush your shoulders off as you walk back in to town. The locals appear delighted that you have taken care of their problem. (+3 local reputation)", + "text": "Consciously or unconsciously, you brush your shoulders off as you walk back into town. The locals appear delighted that you have taken care of their problem. (+3 local reputation)", "options": [ { "action": [ @@ -1047,7 +1047,7 @@ }, { "name": "\"Urgency is expensive.\"", - "text": "\"So is not being the next scheduled burial.\" As you're still processesing that statement, the figure continues. \"Ten mana shards. And you can keep the bones.\"", + "text": "\"So is not being the next scheduled burial.\" As you're still processing that statement, the figure continues. \"Ten mana shards. And you can keep the bones.\"", "options": [ { "action": [ @@ -1284,7 +1284,7 @@ } ], "name": "You shrug your shoulders. It's not your problem. (Decline Quest)", - "text": "The criminal glances at you and hurredly scampers off. (-1 Reputation)", + "text": "The criminal glances at you and hurriedly scampers off. (-1 Reputation)", "options": [ { "name": "(Continue)" @@ -1343,7 +1343,7 @@ ] }, { - "name": "You clear your throat in an exagerated manner.", + "name": "You clear your throat in an exaggerated manner.", "text": "The $(enemy_2) drops a small satchel as they begin to run away.", "options": [ { @@ -2225,7 +2225,7 @@ }, { "name": "Curious as to why this would be on the board, your gaze lingers for a moment.", - "text": "As you look at the wordless paper, words find their way in to your mind by unknown other means. 'FIND.' '{COLOR=red}KILL!{ENDCOLOR}' 'REWARD.'", + "text": "As you look at the wordless paper, words find their way into your mind by unknown means. 'FIND.' '{COLOR=red}KILL!{ENDCOLOR}' 'REWARD.'", "options": [ { "action": [ @@ -2275,7 +2275,7 @@ ] }, { - "name": "You decide that the invasive thoughts, if you can call them that, are unwelcomed, and you take a step back.", + "name": "You decide that the invasive thoughts, if you can call them that, are unwelcome, and you take a step back.", "text": "The thoughts urgently follow you for a moment. '{COLOR=red}KKKKiiiiill...{ENDCOLOR}' But as you take another step back, the words vanish from your mind.", "options": [ { @@ -2310,7 +2310,7 @@ "options": [ { "name": "You continue to read.", - "text": "Secondly, another handwriting has scrawled over what might have actually been a romantic bit with the following. \"Don't bother. I killed him yesterday\"", + "text": "Secondly, another's handwriting was scrawled over what might have actually been a romantic bit with the following. \"Don't bother. I killed him yesterday\"", "options": [ { "name": "You shake your head and walk away. (Decline Quest)" @@ -2727,7 +2727,7 @@ ] } ], - "name": "\"Almost. I believe there's a reward due to level the scales.\"", + "name": "\"Almost. I believe there's a reward due, to level the scales.\"", "text": "(-1 Local Reputation) The druid frowns slightly, but hands you a bundle wrapped in small vines.", "options": [ { @@ -2902,7 +2902,7 @@ "options": [ { "name": "\"Capable just so happens to be my middle name.\"", - "text": "He looks perplexed for a moment, but glances back at the wagon as though distracted by it. \"I was hoping you could handle some business for me\"", + "text": "He looks perplexed for a moment, but glances back at the wagon as though distracted by it. \"I was hoping you could handle some business for me.\"", "options": [ { "name": "Business? What sort of business?", @@ -3683,7 +3683,7 @@ "POIReference": "$(poi_2)" } ], - "text": "Despite the insistance of the needle you decide that you will not finish clearing the $(poi_2). As if it could sense this somehow, the onyx compass disappears. (-2 Local Reputation)", + "text": "Despite the insistence of the needle you decide that you will not finish clearing the $(poi_2). As if it could sense this somehow, the onyx compass disappears. (-2 Local Reputation)", "options": [ { "name": "(Quest Failed)" @@ -3790,7 +3790,7 @@ "name": "A Vision of Destruction", "description": "Clear out all enemies in the $(poi_1) and report back", "offerDialog": { - "text": "Walking in to the village, an old man looks up as if expecting you and rushes over (to the extent that he is able) \"$(playername). I need you to turn around and leave. NOW.\"", + "text": "Walking into the village, an old man looks up as if expecting you and rushes over (to the extent that he is able) \"$(playername). I need you to turn around and leave. NOW.\"", "options": [ { "name": "You take a good look at the old man, but do not recognize his features. \"Should I know you?\"", @@ -4003,7 +4003,7 @@ "text": "\"My vision was less than specific about whether or not it would be changed by your actions. So... yes.\"", "options": [ { - "name": "You glance around at a clear sky warily before going on in to town. (Complete Quest)" + "name": "You glance around at a clear sky warily before going on into town. (Complete Quest)" } ] } @@ -4139,7 +4139,7 @@ }, { "name": "\"I must decline. I respect the local inhabitants far more than faceless nobility.\" (Decline Quest)", - "text": "He gives you the smallest bow imaginable, just enough to say that one was given without indicating respect.", + "text": "He gives you the smallest bow imaginable, just enough to say that one was given, without indicating respect.", "options": [ { "name": "(Continue)" @@ -4174,7 +4174,7 @@ }, { "name": "You can't put your finger on it, but something seems off about the man. \"This isn't a good time.\" (Decline Quest)", - "text": "He gives you the smallest bow imaginable, just enough to say that one was given without indicating respect. (-1 Local Reputation)", + "text": "He gives you the smallest bow imaginable, just enough to say that one was given, without indicating respect. (-1 Local Reputation)", "options": [ { "name": "(Continue)" @@ -4345,7 +4345,7 @@ "options": [ { "name": "You wait for him to continue.", - "text": "\"I've come in to an inheritance of a small estate that I've been expecting for years. Recently, I've had some hard times, and I've convinced some individuals to let me borrow against the land.\" ", + "text": "\"I've come into an inheritance of a small estate that I've been expecting for years. Recently, I've had some hard times, and I've convinced some individuals to let me borrow against the land.\" ", "options": [ { "name": "\"I see.\" You think you know where this is headed.", @@ -4588,7 +4588,7 @@ "text": "Most of the ads are nondescript, weather worn, or written in an unfamiliar language. A few catch your eye, however.", "options": [ { - "name": "You look at what seems to be an advertisment of some sort off to one side.", + "name": "You look at what seems to be an advertisement of some sort off to one side.", "text": "It reads: \"Gimgee's self-replicating paper. When you need unlimited paper or to clear a forest from afar, it's got to be Gimgee's\".", "options": [ { @@ -4657,7 +4657,7 @@ }, "prologue": {}, "epilogue": { - "text": "You feel awkward pulling your $(enemy_1)s in to town, but it doesn't actually seem that out of place here with other macabre scenes around. (This quest will only given in black biome in the future).", + "text": "You feel awkward pulling your $(enemy_1)s into town, but it doesn't actually seem that out of place here with other macabre scenes around. (This quest will only given in black biome in the future).", "options": [ { "name": "You look around for someone that seems to be expecting bodies.", @@ -4708,7 +4708,7 @@ }, { "name": "You take a closer look at the carts.", - "text": "$(enemy_1)s and a few random creatures are filling most of one cart., while the other holds a few identical satchels of goods.", + "text": "$(enemy_1)s and a few random creatures are filling most of one cart, while the other holds a few identical satchels of goods.", "options": [ { "name": "Turn your attention to the carts' attendant.", @@ -4862,7 +4862,7 @@ "text": "Most of the ads are nondescript, weather worn, or written in an unfamiliar language. A few catch your eye, however.", "options": [ { - "name": "You look at what seems to be an advertisment of some sort off to one side.", + "name": "You look at what seems to be an advertisement of some sort off to one side.", "text": "\"A focused mind receives great rewards. Focus on defeating 3 $(enemy_2)s, and be rewarded.\"", "options": [ { @@ -4952,7 +4952,7 @@ } ], "name": "Warily take the items.", - "text": "No sooner than you do, the Djinn dissapears in a puff of smoke. When you turn back, the $(enemy_2) you just defeated has vanished as well.", + "text": "No sooner than you do, the Djinn disappears in a puff of smoke. When you turn back, the $(enemy_2) you just defeated has vanished as well.", "options": [ { "action": [ @@ -5150,7 +5150,7 @@ } ], "name": "\"And if I bring in six?\" (Accept Quest)", - "text": "\"Then you will have brought down too many.We're thinning them, not removing them.\"", + "text": "\"Then you will have brought down too many. We're thinning them, not removing them.\"", "options": [ { "name": "You nod and prepare to leave. (Continue)" @@ -5597,7 +5597,7 @@ "text": "With little hope of catching the damsel, he turns his attention to you. \"Can I interest you in assisting me with some scientific experiments?\"", "options": [ { - "name": "\"It really depends on what they are.\" You look at him suspsiciously.", + "name": "\"It really depends on what they are.\" You look at him suspiciously.", "text": "\"You're not a farmhand, so it will have to be.\" He thinks for a moment, pulling out a well worn notebook and flipping through the pages.", "options": [ { @@ -5633,7 +5633,7 @@ "name": "\"I can do that.\" (Accept Quest)" }, { - "name": "\"Sorry, I just decided that I'm more in to magic than science.\" (Decline Quest)" + "name": "\"Sorry, I just decided that I'm more into magic than science.\" (Decline Quest)" } ] } @@ -5749,7 +5749,7 @@ "text": "Most of the ads are nondescript, weather worn, or written in an unfamiliar language. A few catch your eye, however.", "options": [ { - "name": "You look at what seems to be an advertisment of some sort off to one side.", + "name": "You look at what seems to be an advertisement of some sort off to one side.", "text": "It reads: \"Gimgee's rocks. When you need a good rock, think Gimgee's\".", "options": [ { @@ -5905,7 +5905,7 @@ "name": "Heart of a Champion", "description": "Enter and win an upcoming arena event", "offerDialog": { - "text": "\"DO YOU HAVE WHAT IT TAKES? ARE YOU THE BEST IN SHANDALAR???\" A young girl yells at the top of her lungs at each passer by in the town. Most people come in to view already covering their ears, having heard this plenty of times before.", + "text": "\"DO YOU HAVE WHAT IT TAKES? ARE YOU THE BEST IN SHANDALAR???\" A young girl yells at the top of her lungs at each passer by in the town. Most people come into view already covering their ears, having heard this plenty of times before.", "options": [ { "name": "You walk over to her. \"Okay, kid, settle down, I heard you. What's this about?\"", @@ -6065,7 +6065,7 @@ "objective": "Travel", "prologue": {}, "epilogue": { - "text": "As you walk through the $(poi_1) gates, you can feel the excitement building, eminating, radiating from the city's arena. Most of the populace is already there or on their way. ", + "text": "As you walk through the $(poi_1) gates, you can feel the excitement building, emanating, radiating from the city's arena. Most of the populace is already there or on their way. ", "options": [ { "name": "(continue)", @@ -6127,7 +6127,7 @@ "POIReference": "" } ], - "name": "Giant bugs holding balls eh ? Count me out. (Decline quest)", + "name": "Giant bugs holding balls eh? Count me out. (Decline quest)", "text": "Figured you weren't up to the challenge, come back to me when you are.", "options": [ { @@ -6136,7 +6136,7 @@ ] }, { - "name": "Let me guess, you want to me to deal with this situation ?", + "name": "Let me guess, you want to me to deal with this situation?", "text": "Well Yes, Let me explain the situation; Xira's balls have become a bit of a problem. She has been hosting them every night, and they're becoming increasingly extravagant and disruptive. The townspeople are getting tired of the constant noise and commotion, and it's affecting their daily lives.", "options": [ { @@ -6228,7 +6228,7 @@ ] } ], - "name": "It's nothing I coudn't handle (Complete Quest)" + "name": "It's nothing I couldn't handle (Complete Quest)" } ] }, @@ -6331,7 +6331,7 @@ "POIReference": "" } ], - "name": "Sorry, I don't have to the time for this. (Decline quest)", + "name": "Sorry, I don't have the time for this. (Decline quest)", "text": "Figured you weren't up to the challenge, come back to me when you are.", "options": [ { @@ -6340,11 +6340,11 @@ ] }, { - "name": "Let me guess, you want to me to deal with this situation ?", + "name": "Let me guess, you want to me to deal with this situation?", "text": "The factory, now infested with an array of peculiar mechs and skilled artificers, poses a grave threat to our community and the delicate balance of technology. Slobad, once hailed as a genius, was known for his unorthodox creations and their volatile nature. However, a catastrophic incident occurred years ago, leading to the factory's abandonment. Now, the mechanical monstrosities have been reactivated, wreaking havoc on unsuspecting wanderers and nearby settlements.", "options": [ { - "name": "I see. So you want me to go to his factory and defeat him ?", + "name": "I see. So you want me to go to his factory and defeat him?", "text": "Your task, should you accept it, is to venture into Slobad's factory and cleanse it of its mechanical menaces. You will face a myriad of strange mechs, each with its unique capabilities and behaviors. Additionally, the factory's artificers, skilled engineers corrupted by their own creations, will fiercely defend their inventions, making your mission all the more challenging.", "options": [ { @@ -6364,7 +6364,7 @@ "POIReference": "" } ], - "name": "I'll take care of it, note the location of the factory on my map.(Accept Quest) (WARNING HARD QUEST)", + "name": "I'll take care of it. If you'd note the location of the factory on my map... (Accept Quest) (WARNING HARD QUEST)", "text": "Once you have vanquished the mechanical threat and quelled the chaos within the factory, return to me, Maven the Alchemist, and you shall be rewarded handsomely for your bravery and service to our community. Be warned, however, for the path ahead will test your mettle, cunning, and combat prowess. May fortune favor you on this perilous undertaking!" }, { @@ -6384,7 +6384,7 @@ "POIReference": "" } ], - "name": "\"Do you really think I have nothing better to do ? Find someone else to take care of it\" (Decline Quest)", + "name": "\"Do you really think I have nothing better to do? Find someone else to take care of it\" (Decline Quest)", "text": "Maven the Alchemist keeps a passive look on his face. \"Soon those things will be balanced as well.\"", "options": [ { @@ -6434,7 +6434,7 @@ ] } ], - "name": "It's nothing I coudn't handle (Complete Quest)" + "name": "It's nothing I couldn't handle (Complete Quest)" } ] }, @@ -6534,7 +6534,7 @@ "POIReference": "" } ], - "name": "Sorry, I don't have to the time for this. (Decline quest)", + "name": "Sorry, I don't have the time for this. (Decline quest)", "text": "Figured you weren't up to the challenge, come back to me when you are. (-1 local reputation)", "options": [ { @@ -6547,7 +6547,7 @@ "text": "Thank you, noble adventurer. Slimefoot is a creature of pure malevolence, a monstrous being that has taken root in the heart of the treacherous swamp. Its corrosive touch and toxic aura have brought devastation to our lands. To defeat it, you must journey through the perilous swamp, filled with treacherous terrain and deadly creatures lurking within.", "options": [ { - "name": "I see. So you want me to travel to Slimefoots swamp and defeat him ?", + "name": "I see. So you want me to travel to Slimefoot's swamp and defeat him?", "text": "Slimefoot is a formidable foe, adept at both offense and defense. Its body secretes a corrosive slime, and its tentacles strike with lightning speed. Prepare yourself for a challenging battle, my friend. Draw upon your combat skills, use potions and magical abilities wisely, and exploit any weaknesses you can find. Only then can you hope to overcome this vile creature.", "options": [ { @@ -6632,7 +6632,7 @@ ] } ], - "name": "It's nothing I coudn't handle (Complete Quest)" + "name": "It's nothing I couldn't handle (Complete Quest)" } ] }, @@ -6688,7 +6688,7 @@ { "id": 2, "name": "Travel", - "description": "Return to town and report your success in clearing Slimefoots Lair.", + "description": "Return to town and report your success in clearing Slimefoot's Lair.", "mapFlag": "", "mapFlagValue": 1, "here": true, @@ -6733,7 +6733,7 @@ "POIReference": "" } ], - "name": "Sorry, I don't have to the time for this. (Decline Quest)", + "name": "Sorry, I don't have the time for this. (Decline Quest)", "text": "Figured you weren't up to the challenge, come back to me when you are. ", "options": [ { @@ -6742,7 +6742,7 @@ ] }, { - "name": "Let me guess, you want to me to deal with this situation ?", + "name": "Let me guess, you want to me to deal with this situation?", "text": "The town council seeks the aid of a skilled and courageous individual like yourself to venture into the depths of the old sewer near this town and eradicate this slimy menace once and for all.", "options": [ { @@ -6766,7 +6766,7 @@ "POIReference": "" } ], - "name": "Consider it done, note the location of the old sewers on my map. (Accept Quest) (WARNING HARD QUEST)" + "name": "Consider it done. If you'd note the location of the old sewers on my map... (Accept Quest) (WARNING HARD QUEST)" }, { "action": [ @@ -6837,7 +6837,7 @@ ] } ], - "name": "It's nothing I coudn't handle (Complete Quest)" + "name": "It's nothing I couldn't handle (Complete Quest)" } ] }, @@ -6927,7 +6927,7 @@ "options": [ { "name": "I need answers..", - "text": "The only thing that stands clearly in your mind, is an unexplicable desire to march towards a strange structure in the nearby moonlight. You cannot explain the feeling, but it as if it's expecting your arrival.", + "text": "The only thing that stands clearly in your mind, is an inexplicable desire to march towards a strange structure in the nearby moonlight. You cannot explain the feeling, but it as if it's expecting your arrival.", "options": [ { "name": "(Approach)" @@ -7199,7 +7199,7 @@ }, { "name": "\"Cut to the chase already.\"", - "text": "\"Right. He and his hole dissapeared, then another appeared and beasties came out, and I ran. Simple enough?\"", + "text": "\"Right. He and his hole disappeared, then another appeared and beasties came out, and I ran. Simple enough?\"", "options": [ { "action": [ @@ -7402,7 +7402,7 @@ ], "objective": "Travel", "prologue": { - "text": "(Old Man's Voice) All major locations in Innistrad can be divided up in to one of two categories: towns and dungeons.", + "text": "(Old Man's Voice) All major locations in Innistrad can be divided up into one of two categories: towns and dungeons.", "options": [ { "name": "How are you speaking to me?", @@ -7410,7 +7410,7 @@ "options": [ { "name": "Thanks for the advice...", - "text": "As i'm so kind, have another hint: If you see one, follow a road. All roads lead to a town. You also move faster on roads and fewer enemies will appear.", + "text": "As I'm so kind, have another hint: If you see one, follow a road. All roads lead to a town. You also move faster on roads and fewer enemies will appear.", "options": [ { "name": "(Say nothing and set off)" @@ -7434,7 +7434,7 @@ "objective": "Leave", "anyPOI": true, "prologue": { - "text": "(Old Man's Voice) You will find the towns in a region of Innistrad will look similar to eachother, and offer many of the same services. Though larger towns will offer more...", + "text": "(Old Man's Voice) You will find the towns in a region of Innistrad will look similar to each other, and offer many of the same services. Though larger towns will offer more...", "options": [ { "name": "Will I ever be free of your voice?", @@ -7508,7 +7508,7 @@ "text": "*Chuckles again* You'll see... Unlike on the world map, an enemy that defeats you in a dungeon will remain on the map, to torment you; you can try to duel them again, or run away and seek out another opponent. If you need to heal yourself, go back to a town.", "options": [ { - "name": "*Reamain silent*", + "name": "*Remain silent*", "text": "Oh! Also, some quests like this one, have multiple objectives that can be achieved simultaneously. Your other objective is to find and enter a cave on the world map. An enemy defeated in a cave or on the way there I will count as the enemy to defeat. Just for you. So feel free to do these things in any order.", "options": [ { @@ -7600,7 +7600,7 @@ { "id": 31, "isTemplate": true, - "name": "Buillding A Collection", + "name": "Building A Collection", "offerDialog": {}, "prologue": {}, "epilogue": {}, @@ -7780,7 +7780,7 @@ "POIReference": "" } ], - "name": "Sorry, I don't have to the time for this. (Decline Quest)", + "name": "Sorry, I don't have the time for this. (Decline Quest)", "text": "Figured you weren't up to the challenge, come back to me when you are. ", "options": [ { @@ -7885,7 +7885,7 @@ ] } ], - "name": "It's nothing I coudn't handle (Complete Quest)" + "name": "It's nothing I couldn't handle (Complete Quest)" } ] }, @@ -7986,7 +7986,7 @@ "POIReference": "" } ], - "name": "Sorry, I don't have to the time for this. (Decline Quest)", + "name": "Sorry, I don't have the time for this. (Decline Quest)", "text": "Figured you weren't up to the challenge, come back to me when you are. ", "options": [ { @@ -8091,7 +8091,7 @@ ] } ], - "name": "It's nothing I coudn't handle (Complete Quest)" + "name": "It's nothing I couldn't handle (Complete Quest)" } ] }, @@ -8192,7 +8192,7 @@ "POIReference": "" } ], - "name": "Sorry, I don't have to the time for this. (Decline Quest)", + "name": "Sorry, I don't have the time for this. (Decline Quest)", "text": "Figured you weren't up to the challenge, come back to me when you are. ", "options": [ { @@ -8296,7 +8296,7 @@ ] } ], - "name": "It's nothing I coudn't handle (Complete Quest)" + "name": "It's nothing I couldn't handle (Complete Quest)" } ] }, @@ -8429,7 +8429,7 @@ "POIReference": "$(poi_3)" } ], - "name": "(Quest complete)" + "name": "(Quest Complete)" } ] }, @@ -8617,7 +8617,7 @@ "POIReference": "$(poi_3)" } ], - "name": "(Quest complete)" + "name": "(Quest Complete)" } ] }, @@ -8805,7 +8805,7 @@ "POIReference": "$(poi_3)" } ], - "name": "(Quest complete)" + "name": "(Quest Complete)" } ] }, @@ -8958,7 +8958,7 @@ ] }, { - "name": "\"It's just a tree, I can handle some elves, and I support your bansai hobby.\" (Accept Quest)", + "name": "\"It's just a tree, I can handle some elves, and I support your bonsai hobby.\" (Accept Quest)", "action": [ { "removeItem": "", @@ -8992,7 +8992,7 @@ "POIReference": "$(poi_3)" } ], - "name": "(Quest complete)" + "name": "(Quest Complete)" } ] }, @@ -9118,7 +9118,7 @@ "plains_town_tribal", "swamp_town_generic", "swamp_town_identity", - "swamo_town_tribal", + "swamp_town_tribal", "waste_town_generic", "waste_town_identity", "waste_town_tribal" @@ -9180,7 +9180,7 @@ "POIReference": "$(poi_3)" } ], - "name": "(Quest complete)" + "name": "(Quest Complete)" } ] }, @@ -9343,7 +9343,7 @@ ], "anyPOI": true, "prologue": { - "text": "You should note: Since this quest objective is to complete other quests, your navigation arrow will lead you to quest sources while you are tracking it. Once you accept a side quest, go in to your quest log and track that quest in order to update navigation hints.", + "text": "You should note: Since this quest objective is to complete other quests, your navigation arrow will lead you to quest sources while you are tracking it. Once you accept a side quest, go into your quest log and track that quest in order to update navigation hints.", "options": [ { "name": "Understood" @@ -9814,7 +9814,7 @@ ], "prologue": {}, "epilogue": { - "text": "Within moments of walking in to town, a wiry young elf approaches you. Or at least he looks young, it's so hard to tell with elves. He briefly holds out an amulet identical to the one Donovan gave you, and beckons you to follow him to the inn.", + "text": "Within moments of walking into town, a wiry young elf approaches you. Or at least he looks young, it's so hard to tell with elves. He briefly holds out an amulet identical to the one Donovan gave you, and beckons you to follow him to the inn.", "options": [ { "name": "(Continue)", @@ -9881,7 +9881,7 @@ "offerDialog": {}, "prologue": {}, "epilogue": { - "text": "A slight whistle alerts you to Acirxes' presence. You're not entirely sure if he has impeccible timing or if he watched you complete your most recent job, but it appears that Sir Donovan has more work for you.", + "text": "A slight whistle alerts you to Acirxes' presence. You're not entirely sure if he has impeccable timing or if he watched you complete your most recent job, but it appears that Sir Donovan has more work for you.", "options": [ { "name": "(Continue)", @@ -9968,7 +9968,7 @@ "options": [ { "name": "\"Consider it done.\"", - "text": "\"If you can handle that, it should be a short trip from there to $(poi_4) after. I've got some... 'buisiness' to take care of there. I'll meet you at the inn on the north end of the central peninsula.\"", + "text": "\"If you can handle that, it should be a short trip from there to $(poi_4) after. I've got some... 'business' to take care of there. I'll meet you at the inn on the north end of the central peninsula.\"", "action": [ { "setQuestFlag": { @@ -9984,7 +9984,7 @@ "options": [ { "name": "\"No, apparently not.\"", - "text": "\"So that's why we're sending you. Meet me in $(poi_4) after. I've got some... 'buisiness' to take care of there and I'll add a personal reward if you bring me back the head of whoever's running the show at the library.\" He looks away before walking off. \"I owe that much to Gwen...\"", + "text": "\"So that's why we're sending you. Meet me in $(poi_4) after. I've got some... 'business' to take care of there and I'll add a personal reward if you bring me back the head of whoever's running the show at the library.\" He looks away before walking off. \"I owe that much to Gwen...\"", "options": [ { "name": "(Continue)", @@ -10026,7 +10026,7 @@ "options": [ { "name": "(Continue)", - "text": "A small group of scholars carrying books around the entrance seems to confirm the building's purpose, but something odd about their manerisms has you on edge as you approach.", + "text": "A small group of scholars carrying books around the entrance seems to confirm the building's purpose, but something odd about their mannerisms has you on edge as you approach.", "options": [ { "name": "(Continue)" @@ -10590,7 +10590,7 @@ ] }, "epilogue": { - "text": "As you walk in to town, a familiar cloaked figure is waiting and watching for you.", + "text": "As you walk into town, a familiar cloaked figure is waiting and watching for you.", "options": [ { "name": "(Continue)" diff --git a/forge-gui/res/adventure/Innistrad/world/shops.json b/forge-gui/res/adventure/Innistrad/world/shops.json index 8012b9f33c7..0d2f1e9b8d1 100644 --- a/forge-gui/res/adventure/Innistrad/world/shops.json +++ b/forge-gui/res/adventure/Innistrad/world/shops.json @@ -171,12 +171,12 @@ "rewards": [ { "count":6, - "cardText": "draw(s)?|(exile|reveal|look|search).*library|scry|seek|conjure ", + "cardText": "draw(s)?|(exile|reveal|look|search).*library|scry|seek|conjure", "colors": ["blue"] }, { "count":2, - "cardText": "draw(s)?|(exile|reveal|look|search).*library|scry|seek|conjure " + "cardText": "draw(s)?|(exile|reveal|look|search).*library|scry|seek|conjure" }] }, { @@ -467,7 +467,7 @@ }] },{ "name":"White4", -"description":"Only mostly dead", +"description":"Only Mostly Dead", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"RotatingShop", "overlaySprite":"Overlay6White", @@ -500,7 +500,7 @@ }] },{ "name":"White6", -"description":"Strict dogma", +"description":"Strict Dogma", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"RotatingShop", "overlaySprite":"Overlay6White", @@ -982,7 +982,7 @@ }] },{ "name":"Creature2Eldrazi", -"description": "Eldritch Emissaries", +"description":"Eldritch Emissaries", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"CreatureShop", "overlaySprite":"Overlay2Colorless", @@ -1429,7 +1429,7 @@ }] },{ "name":"Blue", -"description":"Hermitic Study", +"description":"Hermetic Study", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"BlueShop", "rewards": [ @@ -1439,7 +1439,7 @@ }] },{ "name":"Azorius", -"description":"Azorious Shop, LLC", +"description":"Azorius Shop, LLC", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"AzoriusShop", "rewards": [ @@ -1623,7 +1623,7 @@ }] },{ "name":"RWG", - "description":"Caberetti Curios", + "description":"Cabaretti Curios", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"RWGShop", "rewards": [ @@ -1912,7 +1912,7 @@ }] },{ "name":"Angel", - "description":"Halos R' Us", + "description":"Halos 'R' Us", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"AngelShop", "rewards": [ @@ -2231,7 +2231,7 @@ }] },{ "name":"Sliver4Green", - "description":"Venemous Hive", + "description":"Venomous Hive", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"SliverShop", "overlaySprite":"Overlay4Green", @@ -4974,7 +4974,7 @@ ] },{ "name":"Shaman", - "description":"Shaman for ya man", + "description":"Shaman for Ya Man", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"ShamanShop", "rewards": [ diff --git a/forge-gui/res/adventure/Shandalar Old Border/world/enemies.json b/forge-gui/res/adventure/Shandalar Old Border/world/enemies.json index 45317d55b0c..c2a664a0e40 100644 --- a/forge-gui/res/adventure/Shandalar Old Border/world/enemies.json +++ b/forge-gui/res/adventure/Shandalar Old Border/world/enemies.json @@ -5154,7 +5154,7 @@ "Aberration", "Animal", "Chandra", - "FIre", + "Fire", "IdentityRed" ] }, @@ -5259,7 +5259,7 @@ "Human", "Pyromancer", "Wizard", - "FIre", + "Fire", "IdentityRed" ] }, @@ -5364,7 +5364,7 @@ "Pyromancer", "Human", "Wizard", - "FIre", + "Fire", "IdentityRed" ] }, @@ -5469,7 +5469,7 @@ "Wizard", "Chandra", "Human", - "FIre", + "Fire", "IdentityRed" ] }, @@ -10837,7 +10837,7 @@ ], "colors": "R", "questTags": [ - "FIre", + "Fire", "Elemental", "Humanoid", "Flying", @@ -16103,7 +16103,7 @@ }, { "name": "Human elite", - "nameOverride": "Legionaire", + "nameOverride": "Legionnaire", "sprite": "sprites/enemy/humanoid/human/soldier/legionite.atlas", "deck": [ "decks/standard/human_good.json" diff --git a/forge-gui/res/adventure/Shandalar Old Border/world/quests.json b/forge-gui/res/adventure/Shandalar Old Border/world/quests.json index c63ff7fd6e0..b46fc46d9e4 100644 --- a/forge-gui/res/adventure/Shandalar Old Border/world/quests.json +++ b/forge-gui/res/adventure/Shandalar Old Border/world/quests.json @@ -41,7 +41,7 @@ }, { "name": "\"And what if I find the right people myself?\"", - "text": "He shrugs as though that wouldn't bother him. \"Then I'll have to find someone bigger, badder, and most importantly, faster than you to work with.\"", + "text": "He shrugs as though that wouldn't bother him. \"Then I'll have to find someone bigger, badder, and, most importantly, faster than you to work with.\"", "options": [ { "name": "\"Good luck with that.\" (Decline Quest)" @@ -591,7 +591,7 @@ ] } ], - "name": "Quickly and discretely help yourself to a spell before continuing.", + "name": "Quickly and discreetly help yourself to a spell before continuing.", "text": "You receive a spell of dubious quality.", "options": [ { @@ -1268,7 +1268,7 @@ } ], "name": "You shrug your shoulders. It's not your problem. (Decline Quest)", - "text": "The criminal glances at you and hurredly scampers off. (-1 Reputation)", + "text": "The criminal glances at you and hurriedly scampers off. (-1 Reputation)", "options": [ { "name": "(Continue)" @@ -1327,7 +1327,7 @@ ] }, { - "name": "You clear your throat in an exagerated manner.", + "name": "You clear your throat in an exaggerated manner.", "text": "The $(enemy_2) drops a small satchel as they begin to run away.", "options": [ { @@ -2200,7 +2200,7 @@ }, { "name": "Curious as to why this would be on the board, your gaze lingers for a moment.", - "text": "As you look at the wordless paper, words find their way into your mind by unknown, other, means. 'FIND.' '{COLOR=red}KILL!{ENDCOLOR}' 'REWARD.'", + "text": "As you look at the wordless paper, words find their way into your mind by unknown means. 'FIND.' '{COLOR=red}KILL!{ENDCOLOR}' 'REWARD.'", "options": [ { "action": [ @@ -2285,7 +2285,7 @@ "options": [ { "name": "You continue to read.", - "text": "Secondly, another's handwriting has scrawled over what might have actually been a romantic bit, with the following. \"Don't bother. I killed him yesterday\"", + "text": "Secondly, another's handwriting was scrawled over what might have actually been a romantic bit with the following. \"Don't bother. I killed him yesterday\"", "options": [ { "name": "You shake your head and walk away. (Decline Quest)" @@ -2870,7 +2870,7 @@ "options": [ { "name": "\"Capable just so happens to be my middle name.\"", - "text": "He looks perplexed for a moment, but glances back at the wagon as though distracted by it. \"I was hoping you could handle some business for me\"", + "text": "He looks perplexed for a moment, but glances back at the wagon as though distracted by it. \"I was hoping you could handle some business for me.\"", "options": [ { "name": "Business? What sort of business?", @@ -3646,7 +3646,7 @@ "POIReference": "$(poi_2)" } ], - "text": "Despite the insistance of the compass needle, you decide that you will not finish clearing the $(poi_2). As if it could sense this somehow, the onyx compass disappears. (-2 Local Reputation)", + "text": "Despite the insistence of the compass needle, you decide that you will not finish clearing the $(poi_2). As if it could sense this somehow, the onyx compass disappears. (-2 Local Reputation)", "options": [ { "name": "(Quest Failed)" @@ -3749,7 +3749,7 @@ "name": "A Vision of Destruction", "description": "Clear out all enemies in the $(poi_1) and report back.", "offerDialog": { - "text": "Walking in to the village, an old man looks up as if expecting you and rushes over (to the extent that he is able) \"$(playername). I need you to turn around and leave. NOW.\"", + "text": "Walking into the village, an old man looks up as if expecting you and rushes over (to the extent that he is able) \"$(playername). I need you to turn around and leave. NOW.\"", "options": [ { "name": "You take a good look at the old man, but do not recognize his features. \"Should I know you?\"", @@ -4339,7 +4339,7 @@ "POIReference": "" } ], - "name": "\"So long as I get to keep whatever I find along the way, too.\" (Accept Quest)." + "name": "\"So long as I get to keep whatever I find along the way too.\" (Accept Quest)." }, { "name": "\"I don't think I'm interested. Sorry.\" (Decline Quest)" @@ -4558,7 +4558,7 @@ "text": "Most of the ads are nondescript, weather worn, or written in an unfamiliar language. A few catch your eye, however.", "options": [ { - "name": "You look at what seems to be an advertisement of some sort, off to one side.", + "name": "You look at what seems to be an advertisement of some sort off to one side.", "text": "It reads: \"Gimgee's self-replicating paper. When you need unlimited paper or to clear a forest from afar, it's got to be Gimgee's.\"", "options": [ { @@ -4627,7 +4627,7 @@ }, "prologue": {}, "epilogue": { - "text": "You feel awkward pulling your $(enemy_1)s in to town, but it doesn't actually seem that out of place here with other macabre scenes around. (This quest will only given in black biome in the future).", + "text": "You feel awkward pulling your $(enemy_1)s into town, but it doesn't actually seem that out of place here with other macabre scenes around. (This quest will only given in black biome in the future).", "options": [ { "name": "You look around for someone that seems to be expecting bodies.", @@ -4678,7 +4678,7 @@ }, { "name": "You take a closer look at the carts.", - "text": "$(enemy_1)s and a few random creatures are filling most of one cart., while the other holds a few identical satchels of goods.", + "text": "$(enemy_1)s and a few random creatures are filling most of one cart, while the other holds a few identical satchels of goods.", "options": [ { "name": "Turn your attention to the carts' attendant.", @@ -5116,7 +5116,7 @@ } ], "name": "\"And if I bring in six?\" (Accept Quest)", - "text": "\"Then you will have brought down too many.We're thinning them, not removing them.\"", + "text": "\"Then you will have brought down too many. We're thinning them, not removing them.\"", "options": [ { "name": "You nod and prepare to leave. (Continue)" @@ -5600,7 +5600,7 @@ "name": "\"I can do that.\" (Accept Quest)" }, { - "name": "\"Sorry, I just decided that I'm more in to magic than science.\" (Decline Quest)" + "name": "\"Sorry, I just decided that I'm more into magic than science.\" (Decline Quest)" } ] } @@ -5892,7 +5892,7 @@ "name": "Heart of a Champion", "description": "Enter and win an upcoming arena event.", "offerDialog": { - "text": "\"DO YOU HAVE WHAT IT TAKES? ARE YOU THE BEST IN SHANDALAR???\" A young girl yells at the top of her lungs at each passer by in the town. Most people come in to view already covering their ears, having heard this plenty of times before.", + "text": "\"DO YOU HAVE WHAT IT TAKES? ARE YOU THE BEST IN SHANDALAR???\" A young girl yells at the top of her lungs at each passer by in the town. Most people come into view already covering their ears, having heard this plenty of times before.", "options": [ { "name": "You walk over to her. \"Okay kid, settle down, I heard you. What's this about?\"", @@ -6119,7 +6119,7 @@ ] }, { - "name": "Let me guess, you want to me to deal with this situation ?", + "name": "Let me guess, you want to me to deal with this situation?", "text": "Well yes. Let me explain the situation; Xira's balls have become a bit of a problem. She has been hosting them every night, and they're becoming increasingly extravagant and disruptive. The townspeople are getting tired of the constant noise and commotion, and it's affecting their daily lives.", "options": [ { @@ -6314,7 +6314,7 @@ "POIReference": "" } ], - "name": "Sorry, I don't have to the time for this. (Decline Quest)", + "name": "Sorry, I don't have the time for this. (Decline Quest)", "text": "Figured you weren't up to the challenge, come back to me when you are.", "options": [ { @@ -6323,11 +6323,11 @@ ] }, { - "name": "Let me guess, you want to me to deal with this situation ?", + "name": "Let me guess, you want to me to deal with this situation?", "text": "The factory, now infested with an array of peculiar mechs and skilled artificers, poses a grave threat to our community and the delicate balance of technology. Slobad, once hailed as a genius, was known for his unorthodox creations and their volatile nature. However, a catastrophic incident occurred years ago, leading to the factory's abandonment. Now, the mechanical monstrosities have been reactivated, wreaking havoc on unsuspecting wanderers and nearby settlements.", "options": [ { - "name": "I see. So you want me to go to his factory and defeat him ?", + "name": "I see. So you want me to go to his factory and defeat him?", "text": "Your task, should you accept it, is to venture into Slobad's factory and cleanse it of its mechanical menaces. You will face a myriad of strange mechs, each with its unique capabilities and behaviors. Additionally, the factory's artificers, skilled engineers corrupted by their own creations, will fiercely defend their inventions. Making your mission all the more challenging.", "options": [ { @@ -6347,7 +6347,7 @@ "POIReference": "" } ], - "name": "I'll take care of it, note the location of the factory on my map.(Accept Quest) (WARNING HARD QUEST)", + "name": "I'll take care of it. If you'd note the location of the factory on my map... (Accept Quest) (WARNING HARD QUEST)", "text": "Once you have vanquished the mechanical threat and quelled the chaos within the factory, return to me, Maven the Alchemist, and you shall be rewarded handsomely for your bravery and service to our community. Be warned, however, for the path ahead will test your mettle, cunning, and combat prowess. May fortune favor you on this perilous undertaking!" }, { @@ -6367,7 +6367,7 @@ "POIReference": "" } ], - "name": "\"Do you really think I have nothing better to do ? Find someone else to take care of it\" (Decline Quest)", + "name": "\"Do you really think I have nothing better to do? Find someone else to take care of it\" (Decline Quest)", "text": "Maven the Alchemist keeps a passive look on his face. \"Soon those things will be balanced as well.\"", "options": [ { @@ -6518,7 +6518,7 @@ "POIReference": "" } ], - "name": "Sorry, I don't have to the time for this. (Decline Quest)", + "name": "Sorry, I don't have the time for this. (Decline Quest)", "text": "Figured you weren't up to the challenge, come back to me when you are. (-1 Local Reputation)", "options": [ { @@ -6531,7 +6531,7 @@ "text": "Thank you, noble adventurer. Slimefoot is a creature of pure malevolence. A monstrous being that has taken root in the heart of the treacherous swamp. Its corrosive touch and toxic aura have brought devastation to our lands. To defeat it, you must journey through the perilous swamp, filled with treacherous terrain and deadly creatures lurking within.", "options": [ { - "name": "I see. So you want me to travel to Slimefoots swamp and defeat him ?", + "name": "I see. So you want me to travel to Slimefoot's swamp and defeat him?", "text": "Slimefoot is a formidable foe, adept at both offense and defense. Its body secretes a corrosive slime, and its tentacles strike with lightning speed. Prepare yourself for a challenging battle, my friend. Draw upon your combat skills, use potions and magical abilities wisely, and exploit any weaknesses you can find. Only then can you hope to overcome this vile creature.", "options": [ { @@ -6674,7 +6674,7 @@ { "id": 2, "name": "Travel", - "description": "Return to town and report your success in clearing Slimefoots Lair.", + "description": "Return to town and report your success in clearing Slimefoot's Lair.", "mapFlag": "", "mapFlagValue": 1, "here": true, @@ -6717,7 +6717,7 @@ "POIReference": "" } ], - "name": "Sorry, I don't have to the time for this. (Decline Quest)", + "name": "Sorry, I don't have the time for this. (Decline Quest)", "text": "Figured you weren't up to the challenge, come back to me when you are.", "options": [ { @@ -6726,7 +6726,7 @@ ] }, { - "name": "Let me guess, you want to me to deal with this situation ?", + "name": "Let me guess, you want to me to deal with this situation?", "text": "The town council seeks the aid of a skilled and courageous individual like yourself to venture into the depths of the old sewer near this town and eradicate this slimy menace once and for all.", "options": [ { @@ -6750,7 +6750,7 @@ "POIReference": "" } ], - "name": "Consider it done, note the location of the old sewers on my map. (Accept Quest) (WARNING HARD QUEST)" + "name": "Consider it done. If you'd note the location of the old sewers on my map... (Accept Quest) (WARNING HARD QUEST)" }, { "action": [ @@ -7344,7 +7344,7 @@ ], "objective": "Travel", "prologue": { - "text": "All major locations in Shandalar can be divided up in to one of two categories: towns and dungeons.", + "text": "All major locations in Shandalar can be divided up into one of two categories: towns and dungeons.", "options": [ { "name": "(Continue)", @@ -7381,7 +7381,7 @@ "options": [ { "name": "(Continue)", - "text": "The inn contains some special events. You can also sell extra cards there, or buy temporary extra health.\n\nThe '?' sign denotes a town square / job board where you can obtain side quests.\n\nAll of the other buildings with signs out front are shops, most of them sell cards.\n\nTo leave town, walk back toward the edge of the screen just below your current location.", + "text": "The inn contains some special events. You can also sell extra cards there, or buy temporary extra health.\n\nThe '?' sign denotes a town square or a job board where you can obtain side quests.\n\nAll of the other buildings with signs out front are shops, most of them sell cards.\n\nTo leave town, walk back toward the edge of the screen just below your current location.", "options": [ { "name": "(Continue)", @@ -7441,7 +7441,7 @@ "options": [ { "name": "(Continue)", - "text": "Your next objective is to defeat any single enemy in a duel. You can find them in a dungeon like this one, or wandering around outside on the world map. To begin a duel, simply walk in to the enemy.", + "text": "Your next objective is to defeat any single enemy in a duel. You can find them in a dungeon like this one, or wandering around outside on the world map. To begin a duel, simply walk into the enemy.", "options": [ { "name": "(Continue)", @@ -7713,7 +7713,7 @@ "POIReference": "" } ], - "name": "Sorry, I don't have to the time for this. (Decline Quest)", + "name": "Sorry, I don't have the time for this. (Decline Quest)", "text": "Figured you weren't up to the challenge, come back to me when you are.", "options": [ { @@ -7921,7 +7921,7 @@ "POIReference": "" } ], - "name": "Sorry, I don't have to the time for this. (Decline Quest)", + "name": "Sorry, I don't have the time for this. (Decline Quest)", "text": "Figured you weren't up to the challenge, come back to me when you are.", "options": [ { @@ -8127,7 +8127,7 @@ "POIReference": "" } ], - "name": "Sorry, I don't have to the time for this. (Decline Quest)", + "name": "Sorry, I don't have the time for this. (Decline Quest)", "text": "Figured you weren't up to the challenge, come back to me when you are.", "options": [ { @@ -8367,7 +8367,7 @@ "POIReference": "$(poi_3)" } ], - "name": "(Quest complete)" + "name": "(Quest Complete)" } ] }, @@ -8551,7 +8551,7 @@ "POIReference": "$(poi_3)" } ], - "name": "(Quest complete)" + "name": "(Quest Complete)" } ] }, @@ -8735,7 +8735,7 @@ "POIReference": "$(poi_3)" } ], - "name": "(Quest complete)" + "name": "(Quest Complete)" } ] }, @@ -8918,7 +8918,7 @@ "POIReference": "$(poi_3)" } ], - "name": "(Quest complete)" + "name": "(Quest Complete)" } ] }, @@ -9102,7 +9102,7 @@ "POIReference": "$(poi_3)" } ], - "name": "(Quest complete)" + "name": "(Quest Complete)" } ] }, @@ -9260,7 +9260,7 @@ ], "objective": "CompleteQuest", "prologue": { - "text": "Note: Since this quest objective is to complete other quests, your navigation arrow will lead you to quest sources while you are tracking it. Once you accept a side quest, go in to your quest log and track that quest in order to update navigation hints.", + "text": "Note: Since this quest objective is to complete other quests, your navigation arrow will lead you to quest sources while you are tracking it. Once you accept a side quest, go into your quest log and track that quest in order to update navigation hints.", "options": [ { "name": "(Continue)" @@ -9715,7 +9715,7 @@ "prerequisiteIDs": [ 4 ], "prologue": {}, "epilogue": { - "text": "Within moments of walking in to town, a wiry young elf approaches you. Or at least he looks young, it's so hard to tell with elves. He briefly holds out an amulet identical to the one Donovan gave you, and beckons you to follow him to the inn.", + "text": "Within moments of walking into town, a wiry young elf approaches you. Or at least he looks young, it's so hard to tell with elves. He briefly holds out an amulet identical to the one Donovan gave you, and beckons you to follow him to the inn.", "options": [ { "action": [ @@ -9925,7 +9925,7 @@ "options": [ { "name": "(Continue)", - "text": "A small group of scholars carrying books around the entrance seems to confirm the building's purpose, but something odd about their manerisms has you on edge as you approach.", + "text": "A small group of scholars carrying books around the entrance seems to confirm the building's purpose, but something odd about their mannerisms has you on edge as you approach.", "options": [ { "name": "(Continue)" @@ -10492,7 +10492,7 @@ "POIReference": "" } ], - "text": "As you walk in to town, a familiar cloaked figure is waiting and watching for you.", + "text": "As you walk into town, a familiar cloaked figure is waiting and watching for you.", "options": [ { "name": "(Continue)" diff --git a/forge-gui/res/adventure/Shandalar Old Border/world/shops.json b/forge-gui/res/adventure/Shandalar Old Border/world/shops.json index cd02a244a45..c702be7e517 100644 --- a/forge-gui/res/adventure/Shandalar Old Border/world/shops.json +++ b/forge-gui/res/adventure/Shandalar Old Border/world/shops.json @@ -467,7 +467,7 @@ }] },{ "name":"White4", - "description":"Only mostly dead", + "description":"Only Mostly Dead", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"RotatingShop", "overlaySprite":"Overlay6White", @@ -1415,7 +1415,7 @@ }] },{ "name":"Blue", - "description":"Hermitic Study", + "description":"Hermetic Study", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"BlueShop", "rewards": [ @@ -1830,7 +1830,7 @@ }] },{ "name":"Angel", - "description":"Halos R' Us", + "description":"Halos 'R' Us", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"AngelShop", "rewards": [ { "count":8, "subTypes": ["Angel"] } ] @@ -2577,7 +2577,7 @@ "rewards": [ { "count":4, "type":"Union", "cardUnion": [ { "subTypes": ["Rogue"] }, { "cardText": "Rogue" } ] }, { "count":4, "type":"Union", "cardUnion": [ { "subTypes": ["Rogue"], "colors": ["blue"] }, { "cardText": "Rogue", "colors": ["blue"] } ] } ] },{ "name":"Shaman", - "description":"Shaman for ya man", + "description":"Shaman for Ya Man", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"ShamanShop", "rewards": [ { "count":8, "type":"Union", "cardUnion": [ { "subTypes": ["Shaman"] }, { "cardText": "Shaman" } ] } ] @@ -2671,7 +2671,7 @@ }, { "name": "GreenBoosterPackShop", - "description":"Nature’s Nurture Packs", + "description":"Nature's Nurture Packs", "spriteAtlas":"maps/tileset/buildings.atlas", "overlaySprite": "Overlay4Green", "sprite": "BoosterShop", diff --git a/forge-gui/res/adventure/Shandalar Old Border/world/shops.json.bak b/forge-gui/res/adventure/Shandalar Old Border/world/shops.json.bak index 1eb639d23ca..1cfdd35ec43 100644 --- a/forge-gui/res/adventure/Shandalar Old Border/world/shops.json.bak +++ b/forge-gui/res/adventure/Shandalar Old Border/world/shops.json.bak @@ -467,7 +467,7 @@ }] },{ "name":"White4", - "description":"Only mostly dead", + "description":"Only Mostly Dead", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"RotatingShop", "overlaySprite":"Overlay6White", @@ -1341,7 +1341,7 @@ }] },{ "name":"Blue", - "description":"Hermitic Study", + "description":"Hermetic Study", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"BlueShop", "rewards": [ @@ -1756,7 +1756,7 @@ }] },{ "name":"Angel", - "description":"Halos R' Us", + "description":"Halos 'R' Us", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"AngelShop", "rewards": [ { "count":8, "subTypes": ["Angel"] } ] @@ -2431,7 +2431,7 @@ "rewards": [ { "count":4, "type":"Union", "cardUnion": [ { "subTypes": ["Rogue"] }, { "cardText": "Rogue" } ] }, { "count":4, "type":"Union", "cardUnion": [ { "subTypes": ["Rogue"], "colors": ["blue"] }, { "cardText": "Rogue", "colors": ["blue"] } ] } ] },{ "name":"Shaman", - "description":"Shaman for ya man", + "description":"Shaman for Ya Man", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"ShamanShop", "rewards": [ { "count":8, "type":"Union", "cardUnion": [ { "subTypes": ["Shaman"] }, { "cardText": "Shaman" } ] } ] @@ -2525,7 +2525,7 @@ }, { "name": "GreenBoosterPackShop", - "description":"Nature’s Nurture Packs", + "description":"Nature's Nurture Packs", "spriteAtlas":"maps/tileset/buildings.atlas", "overlaySprite": "Overlay4Green", "sprite": "BoosterShop", diff --git a/forge-gui/res/adventure/Shandalar Old Border/world/town_names_black.txt b/forge-gui/res/adventure/Shandalar Old Border/world/town_names_black.txt index 6389c96e9aa..0bcfe96935b 100644 --- a/forge-gui/res/adventure/Shandalar Old Border/world/town_names_black.txt +++ b/forge-gui/res/adventure/Shandalar Old Border/world/town_names_black.txt @@ -369,7 +369,7 @@ Mirelight Ravencrest Mill Korven's Tomb Gorgon's Gallery -Moldermnouth +Moudhrelmont The Three Sisters Moldermouth Reaver's Point diff --git a/forge-gui/res/adventure/Shandalar Old Border/world/town_names_blue.txt b/forge-gui/res/adventure/Shandalar Old Border/world/town_names_blue.txt index c7bf62dbd28..565a46be16b 100644 --- a/forge-gui/res/adventure/Shandalar Old Border/world/town_names_blue.txt +++ b/forge-gui/res/adventure/Shandalar Old Border/world/town_names_blue.txt @@ -335,7 +335,7 @@ Fourmill Run Port Rachkham Cloudy Shallows Slumnis -Silver Pointe +Silver Point Abjuration Point Crow's Nest The Rookery diff --git a/forge-gui/res/adventure/Shandalar Old Border/world/town_names_green.txt b/forge-gui/res/adventure/Shandalar Old Border/world/town_names_green.txt index 575e2e7d3e2..a339330f88c 100644 --- a/forge-gui/res/adventure/Shandalar Old Border/world/town_names_green.txt +++ b/forge-gui/res/adventure/Shandalar Old Border/world/town_names_green.txt @@ -333,7 +333,7 @@ Three Oaks Bowerglen Redwood Ford Ogre's Gate -Mandrake Hollow +Mandrake Hollow Irongate Mott Felden Pond Foxglove Point diff --git a/forge-gui/res/adventure/Shandalar Old Border/world/town_names_waste.txt b/forge-gui/res/adventure/Shandalar Old Border/world/town_names_waste.txt index c7b778d7b38..5e49b77af8c 100644 --- a/forge-gui/res/adventure/Shandalar Old Border/world/town_names_waste.txt +++ b/forge-gui/res/adventure/Shandalar Old Border/world/town_names_waste.txt @@ -45,7 +45,7 @@ Celestine Crossing Celestine Town Celestine Village Celestine Market -Camp Celestine +Camp Celestine Celestine Tower Celestine Bazaar Celestine Forge @@ -73,7 +73,7 @@ Eloren Crossing Eloren Town Eloren Village Eloren Market -Camp Eloren +Camp Eloren Eloren Tower Eloren Bazaar Eloren Forge @@ -87,7 +87,7 @@ Hornwall Crossing Hornwall Town Hornwall Village Hornwall Market -Camp Hornwall +Camp Hornwall Hornwall Tower Hornwall Bazaar Hornwall Forge @@ -101,7 +101,7 @@ Kraag Crossing Kraagtown Kraag Village Kraag Market -Camp Kraag +Camp Kraag Kraag Tower Kraag Bazaar Kraag Forge @@ -385,7 +385,7 @@ Merrowcreek Crookedbrook Warrior's Rest Golemstooth -Gobspike +Gobspike Rabbitsden Greyrock Rabbitpath diff --git a/forge-gui/res/adventure/Shandalar/world/quests.json b/forge-gui/res/adventure/Shandalar/world/quests.json index cc8eb1902ca..0571793f72f 100644 --- a/forge-gui/res/adventure/Shandalar/world/quests.json +++ b/forge-gui/res/adventure/Shandalar/world/quests.json @@ -6347,7 +6347,7 @@ "POIReference": "" } ], - "name": "I'll take care of it, note the location of the factory on my map.(Accept Quest) (WARNING HARD QUEST)", + "name": "I'll take care of it. If you'd note the location of the factory on my map... (Accept Quest) (WARNING HARD QUEST)", "text": "Once you have vanquished the mechanical threat and quelled the chaos within the factory, return to me, Maven the Alchemist, and you shall be rewarded handsomely for your bravery and service to our community. Be warned, however, for the path ahead will test your mettle, cunning, and combat prowess. May fortune favor you on this perilous undertaking!" }, { diff --git a/forge-gui/res/adventure/Shandalar/world/shops.json b/forge-gui/res/adventure/Shandalar/world/shops.json index c4daabc2a53..fda46190466 100644 --- a/forge-gui/res/adventure/Shandalar/world/shops.json +++ b/forge-gui/res/adventure/Shandalar/world/shops.json @@ -1439,7 +1439,7 @@ }] },{ "name":"Azorius", -"description":"Azorious Shop, LLC", +"description":"Azorius Shop, LLC", "spriteAtlas":"maps/tileset/buildings.atlas", "sprite":"AzoriusShop", "rewards": [ diff --git a/forge-gui/res/adventure/Shandalar/world/town_names_blue.txt b/forge-gui/res/adventure/Shandalar/world/town_names_blue.txt index c7bf62dbd28..565a46be16b 100644 --- a/forge-gui/res/adventure/Shandalar/world/town_names_blue.txt +++ b/forge-gui/res/adventure/Shandalar/world/town_names_blue.txt @@ -335,7 +335,7 @@ Fourmill Run Port Rachkham Cloudy Shallows Slumnis -Silver Pointe +Silver Point Abjuration Point Crow's Nest The Rookery diff --git a/forge-gui/res/adventure/common/maps/map/grove/grove_1_bears.tmx b/forge-gui/res/adventure/common/maps/map/grove/grove_1_bears.tmx index 8bf9638f732..73e8f3c27fd 100644 --- a/forge-gui/res/adventure/common/maps/map/grove/grove_1_bears.tmx +++ b/forge-gui/res/adventure/common/maps/map/grove/grove_1_bears.tmx @@ -142,7 +142,7 @@ [ { - "text": "Suddenly you hear strange sounds coming behind you. A coarse voice thunders behind you: HONEY MINE, YOU DIE !", + "text": "Suddenly you hear strange sounds coming behind you. A coarse voice thunders behind you: HONEY MINE, YOU DIE!", "options": [ { "name": "You have a bad feeling about this.", diff --git a/forge-gui/res/adventure/common/maps/map/main_story_explore/library_of_varsil_3.tmx b/forge-gui/res/adventure/common/maps/map/main_story_explore/library_of_varsil_3.tmx index 27a3d85c4db..39c0710a9d6 100644 --- a/forge-gui/res/adventure/common/maps/map/main_story_explore/library_of_varsil_3.tmx +++ b/forge-gui/res/adventure/common/maps/map/main_story_explore/library_of_varsil_3.tmx @@ -94,7 +94,7 @@ [ { - "text": "'Shandalaar’s Most Burnable Cities'. Well, this one sounds like light reading...", + "text": "'Shandalaar's Most Burnable Cities'. Well, this one sounds like light reading...", "options": [ { "name": "(Continue)", diff --git a/forge-gui/res/adventure/common/maps/map/maze/maze_2.tmx b/forge-gui/res/adventure/common/maps/map/maze/maze_2.tmx index be9fad594ed..583faa56ca2 100644 --- a/forge-gui/res/adventure/common/maps/map/maze/maze_2.tmx +++ b/forge-gui/res/adventure/common/maps/map/maze/maze_2.tmx @@ -63,7 +63,7 @@ [ { - "text": "Suddenly you hear strange sounds coming behind you. A strange voice calls out hoarsely: INTRUDER ! This is your final mistake ! ", + "text": "Suddenly you hear strange sounds coming behind you. A strange voice calls out hoarsely: INTRUDER! This is your final mistake!", "options": [ { "name": "You have a bad feeling about this.", From f6d33ed483d7444f4736409f90a9fdd7ce97d529 Mon Sep 17 00:00:00 2001 From: Jetz72 Date: Tue, 7 Oct 2025 14:51:51 -0400 Subject: [PATCH 088/230] Update bug_report.md Add issue type to template. --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dd84ea7824f..6a8ca9e1859 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,6 +4,7 @@ about: Create a report to help us improve title: '' labels: '' assignees: '' +type: 'Bug' --- @@ -31,7 +32,6 @@ If applicable, add screenshots to help explain your problem. **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** From a22f28ef50299f90f788fa5775a42fbd778e60cd Mon Sep 17 00:00:00 2001 From: Jetz72 Date: Tue, 7 Oct 2025 14:53:15 -0400 Subject: [PATCH 089/230] Update feature_request.md --- .github/ISSUE_TEMPLATE/feature_request.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7d615..8520249f9cb 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,6 +4,7 @@ about: Suggest an idea for this project title: '' labels: '' assignees: '' +type: 'Feature' --- From 6935f962d389dac845d9cef741ee89932eefbbe0 Mon Sep 17 00:00:00 2001 From: Eradev Date: Wed, 8 Oct 2025 05:39:43 -0400 Subject: [PATCH 090/230] Update AKH (#8863) --- forge-gui/res/editions/Amonkhet.txt | 32 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/forge-gui/res/editions/Amonkhet.txt b/forge-gui/res/editions/Amonkhet.txt index fb59b29580f..2859e50699a 100644 --- a/forge-gui/res/editions/Amonkhet.txt +++ b/forge-gui/res/editions/Amonkhet.txt @@ -2,7 +2,6 @@ Code=AKH Date=2017-04-28 Name=Amonkhet -Code2=AKH Type=Expansion BoosterCovers=5 Booster=10 Common:fromSheet("AKH cards"), 3 Uncommon:fromSheet("AKH cards"), 1 RareMythic:fromSheet("AKH cards"), 1 BasicLand AKH @@ -11,7 +10,6 @@ FatPack=10 FatPackExtraSlots=80 BasicLands AdditionalSetUnlockedInQuest=MPS_AKH ChaosDraftThemes=GRAVEYARD_MATTERS -ScryfallCode=AKH [cards] 1 M Angel of Sanctions @Min Yum @@ -316,18 +314,18 @@ ScryfallCode=AKH 24 g_5_5_wurm @Slawomir Maniak [other] -embalm_angel_of_sanctions -embalm_anointer_priest -embalm_aven_initiate -embalm_aven_wind_guide -embalm_glyph_keeper -embalm_heart-piercer_manticore -embalm_honored_hydra -embalm_labyrinth_guardian -embalm_oketra's_attendant -embalm_sacred_cat -embalm_tah-crop_skirmisher -embalm_temmet_vizier_of_naktamun -embalm_trueheart_duelist -embalm_unwavering_initiate -embalm_vizier_of_many_faces \ No newline at end of file +1 embalm_angel_of_sanctions @Min Yum +2 embalm_anointer_priest @Lake Hurwitz +3 embalm_aven_initiate @Jakub Kasper +4 embalm_aven_wind_guide @Sidharth Chaturvedi +5 embalm_glyph_keeper @Chris Rahn +6 embalm_heart-piercer_manticore @Scott Murphy +7 embalm_honored_hydra @Todd Lockwood +8 embalm_labyrinth_guardian @Yeong-Hao Han +9 embalm_oketra's_attendant @Lake Hurwitz +10 embalm_sacred_cat @Zezhou Chen +11 embalm_tah-crop_skirmisher @Victor Adame Minguez +12 embalm_temmet_vizier_of_naktamun @Anna Steinbauer +13 embalm_trueheart_duelist @Izzy +14 embalm_unwavering_initiate @Greg Opalinski +15 embalm_vizier_of_many_faces @Ryan Yee \ No newline at end of file From 5a59113d78d26a596a7d46efd07e11d2b611ab2d Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 14:08:24 +0300 Subject: [PATCH 091/230] - Fix MoveCounters with beneficial counters. (#8865) --- .../java/forge/ai/ability/CountersMoveAi.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java index 6d6cc83b351..b6d88c8941b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java @@ -424,16 +424,22 @@ public class CountersMoveAi extends SpellAbilityAi { // move counter to opponents creature but only if you can not steal them // try to move to something useless or something that would leave play - List oppList = CardLists.filterControlledBy(tgtCards, ai.getOpponents()); - if (!oppList.isEmpty()) { - List best = CardLists.filter(oppList, card -> { + boolean isNegative = ComputerUtil.isNegativeCounter(cType, src); + List filteredTgtList; + if (isNegative) { + filteredTgtList = CardLists.filterControlledBy(tgtCards, ai.getOpponents()); + } else { + filteredTgtList = CardLists.filter(tgtCards, CardPredicates.isControlledByAnyOf(ai.getAllies()).or(CardPredicates.isController(ai))); + } + if (!filteredTgtList.isEmpty()) { + List best = CardLists.filter(filteredTgtList, card -> { // gain from useless - if (!ComputerUtilCard.isUselessCreature(ai, card)) { + if (isNegative && !ComputerUtilCard.isUselessCreature(ai, card)) { return true; } // source would leave the game - if (!card.hasSVar("EndOfTurnLeavePlay")) { + if (isNegative && !card.hasSVar("EndOfTurnLeavePlay")) { return true; } @@ -441,7 +447,7 @@ public class CountersMoveAi extends SpellAbilityAi { }); if (best.isEmpty()) { - best = oppList; + best = filteredTgtList; } Card card = ComputerUtilCard.getBestCreatureAI(best); From f0f1f5a1d4c4fa0c81c6392a6ea8949b62bfdbb7 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 14:23:11 +0300 Subject: [PATCH 092/230] Slight cleanup amendment to the MoveCounters AI (#8866) * - Fix MoveCounters with beneficial counters. * - Slight cleanup. --- .../src/main/java/forge/ai/ability/CountersMoveAi.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java index b6d88c8941b..5a8b2caf7d8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java @@ -426,11 +426,9 @@ public class CountersMoveAi extends SpellAbilityAi { // try to move to something useless or something that would leave play boolean isNegative = ComputerUtil.isNegativeCounter(cType, src); List filteredTgtList; - if (isNegative) { - filteredTgtList = CardLists.filterControlledBy(tgtCards, ai.getOpponents()); - } else { - filteredTgtList = CardLists.filter(tgtCards, CardPredicates.isControlledByAnyOf(ai.getAllies()).or(CardPredicates.isController(ai))); - } + filteredTgtList = isNegative ? CardLists.filterControlledBy(tgtCards, ai.getOpponents()) : + CardLists.filter(tgtCards, CardPredicates.isControlledByAnyOf(ai.getAllies()).or(CardPredicates.isController(ai))); + if (!filteredTgtList.isEmpty()) { List best = CardLists.filter(filteredTgtList, card -> { // gain from useless From 7f02c7309046b62951a12b71ad2a80ee1c4ce980 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 20:55:55 +0300 Subject: [PATCH 093/230] Further amendment to AI MoveCounters (#8869) --- forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java index 5a8b2caf7d8..d102ea2065d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java @@ -427,7 +427,7 @@ public class CountersMoveAi extends SpellAbilityAi { boolean isNegative = ComputerUtil.isNegativeCounter(cType, src); List filteredTgtList; filteredTgtList = isNegative ? CardLists.filterControlledBy(tgtCards, ai.getOpponents()) : - CardLists.filter(tgtCards, CardPredicates.isControlledByAnyOf(ai.getAllies()).or(CardPredicates.isController(ai))); + CardLists.filterControlledBy(tgtCards, ai.getYourTeam()); if (!filteredTgtList.isEmpty()) { List best = CardLists.filter(filteredTgtList, card -> { From b844181c0b5b1b351633b2c18ef655fff7d802b6 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Wed, 8 Oct 2025 20:58:12 +0200 Subject: [PATCH 094/230] Benevolent Blessing: fix exception ignoring opponent auras (#8870) --- .../src/main/java/forge/ai/ComputerUtilCombat.java | 12 ++++++------ .../src/main/java/forge/ai/ability/CounterAi.java | 4 +--- .../main/java/forge/ai/ability/DigMultipleAi.java | 4 ---- .../game/staticability/StaticAbilityContinuous.java | 7 +++++-- forge-gui/res/cardsfolder/b/benevolent_blessing.txt | 2 +- .../res/cardsfolder/m/maralen_of_the_mornsong.txt | 2 +- forge-gui/res/cardsfolder/s/spinal_embrace.txt | 2 +- forge-gui/res/cardsfolder/s/steel_dromedary.txt | 2 +- 8 files changed, 16 insertions(+), 19 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 4e54a2b731a..5126f906227 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -177,16 +177,16 @@ public class ComputerUtilCombat { public static int damageIfUnblocked(final Card attacker, final GameEntity attacked, final Combat combat, boolean withoutAbilities) { int damage = attacker.getNetCombatDamage(); int sum = 0; - if (attacked instanceof Player player && !player.canLoseLife()) { - return 0; - } - - // ask ReplacementDamage directly - if (isCombatDamagePrevented(attacker, attacked, damage)) { + if (attacked instanceof Player p && !p.canLoseLife()) { return 0; } if (!attacker.hasKeyword(Keyword.INFECT)) { + // ask ReplacementDamage directly + if (isCombatDamagePrevented(attacker, attacked, damage)) { + return 0; + } + damage += predictPowerBonusOfAttacker(attacker, null, combat, withoutAbilities); sum = predictDamageTo(attacked, damage, attacker, true); if (attacker.hasDoubleStrike()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java index dc9f5b16e02..f8c6060117a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java @@ -135,9 +135,7 @@ public class CounterAi extends SpellAbilityAi { if (sa.hasParam("AILogic")) { String logic = sa.getParam("AILogic"); - if ("Never".equals(logic)) { - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); - } else if (logic.startsWith("MinCMC.")) { // TODO fix Daze and fold into AITgts + if (logic.startsWith("MinCMC.")) { // TODO fix Daze and fold into AITgts int minCMC = Integer.parseInt(logic.substring(7)); if (tgtCMC < minCMC) { return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); diff --git a/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java b/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java index a6ff092ffbc..9a646b9e0d2 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigMultipleAi.java @@ -37,10 +37,6 @@ public class DigMultipleAi extends SpellAbilityAi { return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } - if ("Never".equals(sa.getParam("AILogic"))) { - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); - } - // don't deck yourself if (sa.hasParam("DestinationZone2") && !"Library".equals(sa.getParam("DestinationZone2"))) { int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa); 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 e53361d4ce8..407e794ebd3 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -170,9 +170,11 @@ public final class StaticAbilityContinuous { addKeywords = Lists.newArrayList(Arrays.asList(params.get("AddKeyword").split(" & "))); final List newKeywords = Lists.newArrayList(); - // update keywords with Chosen parts - final String hostCardUID = Integer.toString(hostCard.getId()); // Protection with "doesn't remove" effect + // Protection with "doesn't remove" effect + final String hostCardUID = Integer.toString(hostCard.getId()); + final String hostCardControllerUID = Integer.toString(hostCard.getController().getId()); + // update keywords with Chosen parts addKeywords.removeIf(input -> { if (!hostCard.hasChosenColor() && input.contains("ChosenColor")) { return true; @@ -285,6 +287,7 @@ public final class StaticAbilityContinuous { input = input.replaceAll("chosenEvenOdd", hostCard.getChosenEvenOdd().toString().toLowerCase()); } input = input.replace("HostCardUID", hostCardUID); + input = input.replace("HostCardControllerUID", hostCardControllerUID); if (params.containsKey("CalcKeywordN")) { input = input.replace("N", String.valueOf(AbilityUtils.calculateAmount(hostCard, params.get("CalcKeywordN"), stAb))); } diff --git a/forge-gui/res/cardsfolder/b/benevolent_blessing.txt b/forge-gui/res/cardsfolder/b/benevolent_blessing.txt index c109b73970d..bea6937fef1 100644 --- a/forge-gui/res/cardsfolder/b/benevolent_blessing.txt +++ b/forge-gui/res/cardsfolder/b/benevolent_blessing.txt @@ -6,5 +6,5 @@ K:Enchant:Creature K:ETBReplacement:Other:ChooseColor SVar:ChooseColor:DB$ ChooseColor | Defined$ You | AILogic$ MostProminentInHumanDeck | SpellDescription$ As CARDNAME enters, choose a color. SVar:AttachAILogic:Pump -S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Protection:Card.ChosenColor:chosenColor:Aura.YouCtrl,Equipment.YouCtrl:SBA | Description$ Enchanted creature has protection from the chosen color. This effect doesn't remove Auras and Equipment you control that are already attached to it. +S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddKeyword$ Protection:Card.ChosenColor:chosenColor:Aura.ControlledBy Player.PlayerUID_HostCardControllerUID,Equipment.ControlledBy Player.PlayerUID_HostCardControllerUID:SBA | Description$ Enchanted creature has protection from the chosen color. This effect doesn't remove Auras and Equipment you control that are already attached to it. Oracle:Flash\nEnchant creature\nAs Benevolent Blessing enters, choose a color.\nEnchanted creature has protection from the chosen color. This effect doesn't remove Auras and Equipment you control that are already attached to it. diff --git a/forge-gui/res/cardsfolder/m/maralen_of_the_mornsong.txt b/forge-gui/res/cardsfolder/m/maralen_of_the_mornsong.txt index 31c3678c2c0..0b86615bc6d 100644 --- a/forge-gui/res/cardsfolder/m/maralen_of_the_mornsong.txt +++ b/forge-gui/res/cardsfolder/m/maralen_of_the_mornsong.txt @@ -5,6 +5,6 @@ PT:2/3 S:Mode$ CantDraw | ValidPlayer$ Player | Description$ Players can't draw cards. T:Mode$ Phase | Phase$ Draw | ValidPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigDrain | TriggerDescription$ At the beginning of each player's draw step, that player loses 3 life, searches their library for a card, puts it into their hand, then shuffles. SVar:TrigDrain:DB$ LoseLife | Defined$ TriggeredPlayer | LifeAmount$ 3 | SubAbility$ DBTutor -SVar:DBTutor:DB$ ChangeZone | DefinedPlayer$ TriggeredPlayer | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 +SVar:DBTutor:DB$ ChangeZone | DefinedPlayer$ TriggeredPlayer | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True AI:RemoveDeck:Random Oracle:Players can't draw cards.\nAt the beginning of each player's draw step, that player loses 3 life, searches their library for a card, puts it into their hand, then shuffles. diff --git a/forge-gui/res/cardsfolder/s/spinal_embrace.txt b/forge-gui/res/cardsfolder/s/spinal_embrace.txt index b3444c068c6..4d3b6fae407 100644 --- a/forge-gui/res/cardsfolder/s/spinal_embrace.txt +++ b/forge-gui/res/cardsfolder/s/spinal_embrace.txt @@ -2,7 +2,7 @@ Name:Spinal Embrace ManaCost:3 U U B Types:Instant A:SP$ Untap | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control | ActivationPhases$ BeginCombat->EndCombat | SubAbility$ DBChange | SpellDescription$ Cast this spell only during combat. Untap target creature you don't control and gain control of it. It gains haste until end of turn. At the beginning of the next end step, sacrifice it. If you do, you gain life equal to its toughness. -SVar:DBChange:DB$ GainControl | Defined$ Targeted | SubAbility$ DBAnimate +SVar:DBChange:DB$ GainControl | Defined$ Targeted | AddKWs$ Haste | SubAbility$ DBAnimate SVar:DBAnimate:DB$ Animate | Defined$ Targeted | sVars$ SneakAttackEOT | SubAbility$ DelTrig SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End Of Turn | Execute$ TrigSac | RememberObjects$ Targeted | TriggerDescription$ At the beginning of the next end step, sacrifice it. If you do, you gain life equal to its toughness. | AILogic$ Always | ConditionDefined$ Targeted | ConditionPresent$ Card | ConditionCompare$ GE1 SVar:TrigSac:DB$ SacrificeAll | Defined$ DelayTriggerRememberedLKI | Controller$ You | RememberSacrificed$ True | SubAbility$ DBGainLife diff --git a/forge-gui/res/cardsfolder/s/steel_dromedary.txt b/forge-gui/res/cardsfolder/s/steel_dromedary.txt index 79ac5a7a8d8..f64b86b2a1b 100644 --- a/forge-gui/res/cardsfolder/s/steel_dromedary.txt +++ b/forge-gui/res/cardsfolder/s/steel_dromedary.txt @@ -7,6 +7,6 @@ SVar:CamelTapped:DB$ Tap | Defined$ Self | ETB$ True | SubAbility$ DBAddCounter SVar:DBAddCounter:DB$ PutCounter | ETB$ True | Defined$ Self | CounterType$ P1P1 | CounterNum$ 2 R:Event$ Untap | ActiveZones$ Battlefield | ValidCard$ Card.Self+counters_GE1_P1P1 | ValidStepTurnToController$ You | Layer$ CantHappen | Description$ CARDNAME doesn't untap during your untap step if it has a +1/+1 counter on it. T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigMoveCounter | TriggerDescription$ At the beginning of combat on your turn, you may move a +1/+1 counter from CARDNAME onto target creature. -SVar:TrigMoveCounter:DB$ MoveCounter | ValidTgts$ Creature.Other | TgtPrompt$ Select another target creature | Source$ Self | CounterType$ P1P1 | CounterNum$ 1 +SVar:TrigMoveCounter:DB$ MoveCounter | ValidTgts$ Creature | Source$ Self | CounterType$ P1P1 | CounterNum$ 1 DeckHas:Ability$Counters Oracle:Steel Dromedary enters tapped with two +1/+1 counters on it.\nSteel Dromedary doesn't untap during your untap step if it has a +1/+1 counter on it.\nAt the beginning of combat on your turn, you may move a +1/+1 counter from Steel Dromedary onto target creature. From f7c965de02a5e3c175776a3112f1deb220297e98 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Thu, 9 Oct 2025 06:06:59 +0800 Subject: [PATCH 095/230] prevent NPE --- .../adventure/scene/AdventureDeckEditor.java | 306 ++++++++++++------ 1 file changed, 201 insertions(+), 105 deletions(-) diff --git a/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java b/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java index 808b366c3ac..d3425a17e33 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java +++ b/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java @@ -43,10 +43,25 @@ import java.util.function.Predicate; public class AdventureDeckEditor extends FDeckEditor { protected static class AdventureEditorConfig extends DeckEditorConfig { - @Override public GameType getGameType() { return GameType.Adventure; } - @Override public DeckFormat getDeckFormat() { return DeckFormat.Adventure; } - @Override protected IDeckController getController() { return ADVENTURE_DECK_CONTROLLER; } - @Override public boolean usePlayerInventory() { return true; } + @Override + public GameType getGameType() { + return GameType.Adventure; + } + + @Override + public DeckFormat getDeckFormat() { + return DeckFormat.Adventure; + } + + @Override + protected IDeckController getController() { + return ADVENTURE_DECK_CONTROLLER; + } + + @Override + public boolean usePlayerInventory() { + return true; + } @Override protected DeckEditorPage[] getInitialPages() { @@ -110,18 +125,34 @@ public class AdventureDeckEditor extends FDeckEditor { protected static class DeckPreviewConfig extends AdventureEditorConfig { private final Deck deckToPreview; + public DeckPreviewConfig(Deck deckToPreview) { this.deckToPreview = deckToPreview; } - @Override public boolean usePlayerInventory() { return false; } - @Override public boolean isLimited() { return true; } - @Override public ItemPool getCardPool(boolean wantUnique) { return deckToPreview.getAllCardsInASinglePool(true, true); } - @Override public boolean allowsCardReplacement() { return false; } + @Override + public boolean usePlayerInventory() { + return false; + } + + @Override + public boolean isLimited() { + return true; + } + + @Override + public ItemPool getCardPool(boolean wantUnique) { + return deckToPreview.getAllCardsInASinglePool(true, true); + } + + @Override + public boolean allowsCardReplacement() { + return false; + } @Override protected DeckEditorPage[] getInitialPages() { - return new DeckEditorPage[] {new ContentPreviewPage(deckToPreview)}; + return new DeckEditorPage[]{new ContentPreviewPage(deckToPreview)}; } } @@ -134,20 +165,39 @@ public class AdventureDeckEditor extends FDeckEditor { this.controller = new AdventureEventDeckController(event); } - @Override public GameType getGameType() { return GameType.AdventureEvent; } - @Override public DeckFormat getDeckFormat() { return DeckFormat.Limited; } - @Override public boolean isLimited() { return true; } - @Override public boolean isDraft() { return event.getDraft() != null; } - @Override protected IDeckController getController() { return this.controller; } + @Override + public GameType getGameType() { + return GameType.AdventureEvent; + } + + @Override + public DeckFormat getDeckFormat() { + return DeckFormat.Limited; + } + + @Override + public boolean isLimited() { + return true; + } + + @Override + public boolean isDraft() { + return event.getDraft() != null; + } + + @Override + protected IDeckController getController() { + return this.controller; + } @Override public List getBasicLandSets(Deck currentDeck) { - if(event.cardBlock != null) { - if(event.cardBlock.getLandSet() != null) + if (event.cardBlock != null) { + if (event.cardBlock.getLandSet() != null) return List.of(event.cardBlock.getLandSet()); List eventSets = new ArrayList<>(event.cardBlock.getSets()); eventSets.removeIf(Predicate.not(CardEdition::hasBasicLands)); - if(!eventSets.isEmpty()) + if (!eventSets.isEmpty()) return eventSets; } return List.of(DeckProxy.getDefaultLandSet(event.registeredDeck)); @@ -170,9 +220,9 @@ public class AdventureDeckEditor extends FDeckEditor { case Entered: if (event.getDraft() != null) return new DeckEditorPage[]{ - new DraftPackPage(new AdventureCardManager()), - new AdventureDeckSectionPage(DeckSection.Main, ItemManagerConfig.DRAFT_POOL), - new AdventureDeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.SIDEBOARD) + new DraftPackPage(new AdventureCardManager()), + new AdventureDeckSectionPage(DeckSection.Main, ItemManagerConfig.DRAFT_POOL), + new AdventureDeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.SIDEBOARD) }; default: return new DeckEditorPage[]{ @@ -242,7 +292,7 @@ public class AdventureDeckEditor extends FDeckEditor { Localizer localizer = Forge.getLocalizer(); String label = localizer.getMessage("lblSellFor") + " " + Current.player().cardSellPrice(card); int sellable = cardManager.getItemCount(card); - if(sellable <= 0) + if (sellable <= 0) return; String prompt = card + " - " + label + " " + localizer.getMessage("lblHowMany"); @@ -250,7 +300,7 @@ public class AdventureDeckEditor extends FDeckEditor { int sold = Current.player().sellCard(card, result); removeCard(card, sold); })); - if(cardIsFavorite(card)) + if (cardIsFavorite(card)) sellItem.setTextColor(255, 0, 0); menu.addItem(sellItem); } @@ -292,7 +342,7 @@ public class AdventureDeckEditor extends FDeckEditor { value += Current.player().cardSellPrice(entry.getKey()) * entry.getValue(); } - if(toSell.isEmpty()) + if (toSell.isEmpty()) return; FOptionPane.showConfirmDialog(Forge.getLocalizer().getMessage("lblSellAllConfirm", toSell.countAll(), value), Forge.getLocalizer().getMessage("lblSellCurrentFilters"), Forge.getLocalizer().getMessage("lblSell"), Forge.getLocalizer().getMessage("lblCancel"), false, result -> { @@ -306,13 +356,21 @@ public class AdventureDeckEditor extends FDeckEditor { @Override public void setCardFavorited(PaperCard card, boolean isFavorite) { AdventurePlayer player = Current.player(); - if(isFavorite) + if (isFavorite) player.favoriteCards.add(card); else player.favoriteCards.remove(card); } - @Override protected boolean cardIsFavorite(PaperCard card) { return Current.player().favoriteCards.contains(card); } - @Override protected boolean allowFavoriteCards() { return true; } + + @Override + protected boolean cardIsFavorite(PaperCard card) { + return Current.player().favoriteCards.contains(card); + } + + @Override + protected boolean allowFavoriteCards() { + return true; + } } private static class CollectionCatalogPage extends CatalogPage { @@ -350,18 +408,19 @@ public class AdventureDeckEditor extends FDeckEditor { int safeToSellCount = amountInCollection - copiesUsedInDecks; //Number we can sell without losing cards from a deck. int autoSellCount = Current.player().autoSellCards.count(card); //Number currently in auto-sell. int canMoveToAutoSell = safeToSellCount - autoSellCount; //Number that can be moved to auto-sell from here. - + if (card.getRules().isUnsupported()) { menu.clearItems(); FMenuItem removeItem = new FMenuItem(localizer.getMessage("lblRemoveUnsupportedCard"), FSkinImage.HDDELETE, e -> - removeCard(card, safeToSellCount)); + removeCard(card, safeToSellCount)); menu.addItem(removeItem); return; } if (copiesUsedInDecks > 0) { String text = localizer.getMessage("lblCopiesInUse", copiesUsedInDecks); - FMenuItem usedHint = new FMenuItem(text, FSkinImage.HDLIBRARY, n -> {}); + FMenuItem usedHint = new FMenuItem(text, FSkinImage.HDLIBRARY, n -> { + }); usedHint.setEnabled(false); menu.addItem(usedHint); } @@ -398,13 +457,21 @@ public class AdventureDeckEditor extends FDeckEditor { @Override public void setCardFavorited(PaperCard card, boolean isFavorite) { AdventurePlayer player = Current.player(); - if(isFavorite) + if (isFavorite) player.favoriteCards.add(card); else player.favoriteCards.remove(card); } - @Override protected boolean cardIsFavorite(PaperCard card) { return Current.player().favoriteCards.contains(card); } - @Override protected boolean allowFavoriteCards() { return true; } + + @Override + protected boolean cardIsFavorite(PaperCard card) { + return Current.player().favoriteCards.contains(card); + } + + @Override + protected boolean allowFavoriteCards() { + return true; + } @Override public void buildDeckMenu(FPopupMenu menu) { @@ -425,7 +492,7 @@ public class AdventureDeckEditor extends FDeckEditor { continue; toMove.add(entry.getKey(), entry.getValue()); } - if(toMove.isEmpty()) + if (toMove.isEmpty()) return; FOptionPane.showConfirmDialog(Forge.getLocalizer().getMessage("lblAutoSellCurrentFiltersConfirm", toMove.countAll()), Forge.getLocalizer().getMessage("lblAutoSellCurrentFilters"), Forge.getLocalizer().getMessage("lblAutoSell"), Forge.getLocalizer().getMessage("lblCancel"), false, result -> { @@ -480,20 +547,20 @@ public class AdventureDeckEditor extends FDeckEditor { super.buildMenu(menu, card); Localizer localizer = Forge.getLocalizer(); AdventurePlayer player = Current.player(); - if(isShop()) { + if (isShop()) { String label = localizer.getMessage("lblSellFor") + " " + player.cardSellPrice(card); int sellable = cardManager.getItemCount(card); - if(sellable <= 0) + if (sellable <= 0) return; String prompt = card + " - " + label + " " + localizer.getMessage("lblHowMany"); menu.addItem(new FMenuItem(label, SIDEBOARD_ICON, new MoveQuantityPrompt(prompt, sellable, result -> { - int sold = player.sellCard(card, result); - removeCard(card, sold); - }) + int sold = player.sellCard(card, result); + removeCard(card, sold); + }) )); } - if(parentScreen instanceof AdventureDeckEditor adventureEditor && adventureEditor.getCatalogPage() != null) { + if (parentScreen instanceof AdventureDeckEditor adventureEditor && adventureEditor.getCatalogPage() != null) { CatalogPage catalogPage = adventureEditor.getCatalogPage(); int autoSellCount = cardManager.getItemCount(card); int amountInCollection = player.getCards().count(card); @@ -509,7 +576,7 @@ public class AdventureDeckEditor extends FDeckEditor { @Override protected void onCardActivated(PaperCard card) { - if(isShop()) { + if (isShop()) { Current.player().sellCard(card, 1); removeCard(card, 1); } @@ -519,7 +586,7 @@ public class AdventureDeckEditor extends FDeckEditor { public AdventureEventData getCurrentEvent() { IDeckController controller = getDeckController(); - if(!(controller instanceof AdventureEventDeckController eventController)) + if (!(controller instanceof AdventureEventDeckController eventController)) return null; return eventController.currentEvent; } @@ -527,7 +594,7 @@ public class AdventureDeckEditor extends FDeckEditor { @Override public BoosterDraft getDraft() { AdventureEventData currentEvent = getCurrentEvent(); - if(currentEvent == null) + if (currentEvent == null) return null; return currentEvent.getDraft(); } @@ -535,7 +602,7 @@ public class AdventureDeckEditor extends FDeckEditor { @Override public boolean isDrafting() { AdventureEventData currentEvent = getCurrentEvent(); - if(currentEvent == null) + if (currentEvent == null) return false; return currentEvent.draft != null && !currentEvent.isDraftComplete; } @@ -550,7 +617,7 @@ public class AdventureDeckEditor extends FDeckEditor { public void completeDraft() { super.completeDraft(); AdventureEventData currentEvent = getCurrentEvent(); - if(currentEvent == null) + if (currentEvent == null) return; currentEvent.isDraftComplete = true; Deck[] opponentDecks = currentEvent.getDraft().getComputerDecks(); @@ -624,9 +691,9 @@ public class AdventureDeckEditor extends FDeckEditor { private static final FImage AUTO_SELL_ICON = FSkinImage.HDEXILE; //to-maybe-do: Custom adventure icon for this? Adventure should really just have its own skin. public static FImage iconFromDeckSection(DeckSection deckSection) { - if(deckSection == DeckSection.Main) + if (deckSection == DeckSection.Main) return MAIN_DECK_ICON; - if(deckSection == DeckSection.Sideboard) + if (deckSection == DeckSection.Sideboard) return FDeckEditor.SIDEBOARD_ICON; return FDeckEditor.iconFromDeckSection(deckSection); } @@ -661,7 +728,7 @@ public class AdventureDeckEditor extends FDeckEditor { if (page instanceof CollectionCatalogPage) { if (!Current.player().getUnsupportedCards().isEmpty()) GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblRemoveAllUnsupportedCards"), - -1, -1, Current.player().getUnsupportedCards(), result -> Current.player().getUnsupportedCards().clear()); + -1, -1, Current.player().getUnsupportedCards(), result -> Current.player().getUnsupportedCards().clear()); break; } } @@ -688,7 +755,7 @@ public class AdventureDeckEditor extends FDeckEditor { public AdventureDeckEditor(boolean createAsShop) { super(createAsShop ? new ShopConfig() : new AdventureEditorConfig(), createAsShop ? null : Current.player().getSelectedDeck()); - if(createAsShop) + if (createAsShop) setHeaderText(Forge.getLocalizer().getMessage("lblSell")); } @@ -696,7 +763,7 @@ public class AdventureDeckEditor extends FDeckEditor { super(new AdventureEventEditorConfig(event), event.registeredDeck); currentEvent = event; - if(event.getDraft() != null && event.getDraft().shouldShowDraftLog()) { + if (event.getDraft() != null && event.getDraft().shouldShowDraftLog()) { this.draftLog = new FDraftLog(); event.getDraft().setLogEntry(this.draftLog); deckHeader.initDraftLog(this.draftLog, this); @@ -715,33 +782,36 @@ public class AdventureDeckEditor extends FDeckEditor { @Override protected void addChosenBasicLands(CardPool landsToAdd) { - if(isLimitedEditor()) + if (isLimitedEditor()) super.addChosenBasicLands(landsToAdd); + //Take the basic lands from the player's collection if they have them. If they need more, create unsellable copies. - CardPool requiredNewLands = new CardPool(); - CardPool landsToMove = new CardPool(); CatalogPage catalog = getCatalogPage(); - ItemPool availablePool = catalog.getCardPool(); - for(Map.Entry entry : landsToAdd) { - int needed = entry.getValue(); - PaperCard card = entry.getKey(); - int moveableSellable = Math.min(availablePool.count(card), needed); - landsToMove.add(card, moveableSellable); - needed -= moveableSellable; - if(needed <= 0) - continue; - PaperCard unsellable = card.getNoSellVersion(); - //It'd probably be better to do some kind of fuzzy search that matches prints but ignores flags. - //But for now, unsellable is the only one that should matter here. - int moveableUnsellable = Math.min(availablePool.count(unsellable), needed); - landsToMove.add(unsellable, needed); //We'll acquire the rest later. - if(needed > moveableUnsellable) - requiredNewLands.add(unsellable, needed - moveableUnsellable); + if (catalog != null) { // TODO find out why this is null on some devices since it shouldn't be null + CardPool requiredNewLands = new CardPool(); + CardPool landsToMove = new CardPool(); + ItemPool availablePool = catalog.getCardPool(); + for (Map.Entry entry : landsToAdd) { + int needed = entry.getValue(); + PaperCard card = entry.getKey(); + int moveableSellable = Math.min(availablePool.count(card), needed); + landsToMove.add(card, moveableSellable); + needed -= moveableSellable; + if (needed <= 0) + continue; + PaperCard unsellable = card.getNoSellVersion(); + //It'd probably be better to do some kind of fuzzy search that matches prints but ignores flags. + //But for now, unsellable is the only one that should matter here. + int moveableUnsellable = Math.min(availablePool.count(unsellable), needed); + landsToMove.add(unsellable, needed); //We'll acquire the rest later. + if (needed > moveableUnsellable) + requiredNewLands.add(unsellable, needed - moveableUnsellable); + } + if (!requiredNewLands.isEmpty()) + Current.player().addCards(requiredNewLands); + catalog.refresh(); + catalog.moveCards(landsToMove, getMainDeckPage()); } - if(!requiredNewLands.isEmpty()) - Current.player().addCards(requiredNewLands); - catalog.refresh(); - catalog.moveCards(landsToMove, getMainDeckPage()); } @Override @@ -753,15 +823,15 @@ public class AdventureDeckEditor extends FDeckEditor { @Override protected void cacheTabPages() { super.cacheTabPages(); - for(TabPage page : tabPages) { - if(page instanceof CollectionAutoSellPage) + for (TabPage page : tabPages) { + if (page instanceof CollectionAutoSellPage) this.autoSellPage = (CollectionAutoSellPage) page; } } @Override protected boolean allowAddBasic() { - if(getEditorConfig() instanceof DeckPreviewConfig) + if (getEditorConfig() instanceof DeckPreviewConfig) return false; AdventureEventData currentEvent = getCurrentEvent(); if (currentEvent == null) @@ -812,7 +882,7 @@ public class AdventureDeckEditor extends FDeckEditor { @Override public void onClose(final Consumer canCloseCallback) { - if(canCloseCallback == null) { + if (canCloseCallback == null) { resolveClose(null, true); return; } @@ -821,8 +891,7 @@ public class AdventureDeckEditor extends FDeckEditor { if (isDrafting()) { FOptionPane.showConfirmDialog(localizer.getMessage("lblEndAdventureEventConfirm"), localizer.getMessage("lblLeaveDraft"), localizer.getMessage("lblLeave"), localizer.getMessage("lblCancel"), false, result -> resolveClose(canCloseCallback, result == true)); return; - } - else if(getEditorConfig().isLimited() || getDeck().isEmpty()) { + } else if (getEditorConfig().isLimited() || getDeck().isEmpty()) { resolveClose(canCloseCallback, true); return; } @@ -839,18 +908,18 @@ public class AdventureDeckEditor extends FDeckEditor { } private void resolveClose(final Consumer canCloseCallback, boolean result) { - if(result) { + if (result) { Current.player().newCards.clear(); - if(isDrafting()) + if (isDrafting()) getCurrentEvent().eventStatus = AdventureEventController.EventStatus.Abandoned; } - if(canCloseCallback != null) + if (canCloseCallback != null) canCloseCallback.accept(result); } @Override protected void devAddCards(CardPool cards) { - if(!getEditorConfig().usePlayerInventory()) { + if (!getEditorConfig().usePlayerInventory()) { //Drafting. super.devAddCards(cards); return; @@ -875,9 +944,9 @@ public class AdventureDeckEditor extends FDeckEditor { protected String getItemSuffix(Map.Entry item) { PaperCard card = item.getKey(); String parentSuffix = super.getItemSuffix(item); - if(card.hasNoSellValue()) { + if (card.hasNoSellValue()) { String valueText = " [NO VALUE]"; - if(parentSuffix == null) + if (parentSuffix == null) return valueText; return String.join(" ", valueText, parentSuffix); } @@ -891,7 +960,7 @@ public class AdventureDeckEditor extends FDeckEditor { public void drawValue(Graphics g, Map.Entry value, FSkinFont font, FSkinColor foreColor, FSkinColor backColor, boolean pressed, float x, float y, float w, float h) { super.drawValue(g, value, font, foreColor, backColor, pressed, x, y, w, h); - if(showPriceInfo()) { + if (showPriceInfo()) { float totalHeight = h + 2 * FList.PADDING; float cardArtWidth = totalHeight * CardRenderer.CARD_ART_RATIO; @@ -958,6 +1027,7 @@ public class AdventureDeckEditor extends FDeckEditor { } private static final AdventureDeckController ADVENTURE_DECK_CONTROLLER = new AdventureDeckController(); + /** * Barebones deck controller. Doesn't really need to do anything since Adventure Decks are updated in real time * while they're edited, and they're only saved when the adventure is saved. @@ -969,29 +1039,41 @@ public class AdventureDeckEditor extends FDeckEditor { @Override public void setEditor(FDeckEditor editor) { this.editor = editor; - if(editor != null) - editor.notifyNewControllerModel(); - } - - @Override public void setDeck(Deck deck) { - this.currentDeck = deck; - if(editor != null) + if (editor != null) editor.notifyNewControllerModel(); } - @Override public Deck getDeck() { return currentDeck; } - @Override public void newDeck() { + + @Override + public void setDeck(Deck deck) { + this.currentDeck = deck; + if (editor != null) + editor.notifyNewControllerModel(); + } + + @Override + public Deck getDeck() { + return currentDeck; + } + + @Override + public void newDeck() { setDeck(new Deck("Adventure Deck")); } @Override public String getDeckDisplayName() { - if(currentDeck == null) + if (currentDeck == null) return "New Deck"; return currentDeck.getName(); } - @Override public void notifyModelChanged() {} // - @Override public void exitWithoutSaving() {} //Too many external variables to just revert the deck. Not supported for now. + @Override + public void notifyModelChanged() { + } // + + @Override + public void exitWithoutSaving() { + } //Too many external variables to just revert the deck. Not supported for now. } private static class AdventureEventDeckController implements IDeckController { @@ -1005,26 +1087,40 @@ public class AdventureDeckEditor extends FDeckEditor { @Override public void setEditor(FDeckEditor editor) { this.editor = editor; - if(editor != null) + if (editor != null) editor.notifyNewControllerModel(); } - @Override public void setDeck(Deck deck) {this.newDeck();} //Deck is supplied by the event. - @Override public void newDeck() { - if(editor != null) + @Override + public void setDeck(Deck deck) { + this.newDeck(); + } //Deck is supplied by the event. + + @Override + public void newDeck() { + if (editor != null) editor.notifyNewControllerModel(); } - @Override public Deck getDeck() { return currentEvent.registeredDeck; } + + @Override + public Deck getDeck() { + return currentEvent.registeredDeck; + } @Override public String getDeckDisplayName() { - if(getDeck() == null) + if (getDeck() == null) return "Uninitialized Deck"; return getDeck().getName(); } - @Override public void notifyModelChanged() {} // - @Override public void exitWithoutSaving() {} //Too many external variables to just revert the deck. Not supported for now. + @Override + public void notifyModelChanged() { + } // + + @Override + public void exitWithoutSaving() { + } //Too many external variables to just revert the deck. Not supported for now. } } From 0ee3dc4e6727cee9f52e3826f4dd5c8549477540 Mon Sep 17 00:00:00 2001 From: Eradev Date: Wed, 8 Oct 2025 12:05:11 -0400 Subject: [PATCH 096/230] Check for Snow lands and Wastes when generating random decks --- .../java/forge/card/mana/ManaCostShard.java | 1 - .../src/forge/adventure/util/CardUtil.java | 121 ++++++++++-------- .../adventure/common/decks/standard/crab.json | 4 +- 3 files changed, 71 insertions(+), 55 deletions(-) diff --git a/forge-core/src/main/java/forge/card/mana/ManaCostShard.java b/forge-core/src/main/java/forge/card/mana/ManaCostShard.java index 1d558520a25..667ed3ba19d 100644 --- a/forge-core/src/main/java/forge/card/mana/ManaCostShard.java +++ b/forge-core/src/main/java/forge/card/mana/ManaCostShard.java @@ -63,7 +63,6 @@ public enum ManaCostShard { S(ManaAtom.IS_SNOW, "S"), GENERIC(ManaAtom.GENERIC, "1"), - /* Phyrexian */ WP(ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "W/P", "WP"), UP(ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "U/P", "UP"), diff --git a/forge-gui-mobile/src/forge/adventure/util/CardUtil.java b/forge-gui-mobile/src/forge/adventure/util/CardUtil.java index f25b3ba451b..6c38a8ee648 100644 --- a/forge-gui-mobile/src/forge/adventure/util/CardUtil.java +++ b/forge-gui-mobile/src/forge/adventure/util/CardUtil.java @@ -9,6 +9,7 @@ import forge.adventure.data.GeneratedDeckData; import forge.adventure.data.GeneratedDeckTemplateData; import forge.adventure.data.RewardData; import forge.card.*; +import forge.card.mana.ManaCost; import forge.card.mana.ManaCostShard; import forge.deck.Deck; import forge.deck.DeckSection; @@ -499,67 +500,83 @@ public class CardUtil { } private static List fillWithLands(List nonLands, GeneratedDeckTemplateData template) { - int red=0; - int blue=0; - int green=0; - int white=0; - int black=0; - int colorLess=0; - int cardCount=nonLands.size(); - List cards=new ArrayList<>(); - boolean allCardVariants=Config.instance().getSettingData().useAllCardVariants; + int red = 0, blue = 0, green = 0, white = 0, black = 0, colorless = 0; + int cardCount = nonLands.size(); + List cards = new ArrayList<>(); + boolean allCardVariants = Config.instance().getSettingData().useAllCardVariants; + boolean useSnowLands = false; - for(PaperCard nonLand:nonLands) - { - red+=nonLand.getRules().getManaCost().getShardCount(ManaCostShard.RED); - green+=nonLand.getRules().getManaCost().getShardCount(ManaCostShard.GREEN); - white+=nonLand.getRules().getManaCost().getShardCount(ManaCostShard.WHITE); - blue+=nonLand.getRules().getManaCost().getShardCount(ManaCostShard.BLUE); - black+=nonLand.getRules().getManaCost().getShardCount(ManaCostShard.BLACK); - colorLess+=nonLand.getRules().getManaCost().getShardCount(ManaCostShard.GENERIC); + for (PaperCard nonLand : nonLands) { + CardRules rules = nonLand.getRules(); + ManaCost manaCost = rules.getManaCost(); + + red += manaCost.getShardCount(ManaCostShard.RED); + green += manaCost.getShardCount(ManaCostShard.GREEN); + white += manaCost.getShardCount(ManaCostShard.WHITE); + blue += manaCost.getShardCount(ManaCostShard.BLUE); + black += manaCost.getShardCount(ManaCostShard.BLACK); + colorless += manaCost.getShardCount(ManaCostShard.COLORLESS); + + // Check for Snow lands requirement + if (!useSnowLands) { + if (manaCost.getShardCount(ManaCostShard.S) > 0) { + useSnowLands = true; + continue; + } + + if (rules.getAiHints() != null && rules.getAiHints().getDeckHints() != null) { + useSnowLands = rules.getAiHints().getDeckHints().contains(Type.TYPE, "Snow"); + } + } } - float sum= red+ blue+ green+ white+ black; - int neededLands=template.count-cardCount; - int neededDualLands= Math.round (neededLands*template.rares); - int neededBase=neededLands-neededDualLands; + + float sumColoredCost = red + blue + green + white + black; + int neededLands = template.count - cardCount; + int neededDualLands = Math.round(neededLands * template.rares); + int neededBase = neededLands - neededDualLands; String edition = ""; + if (allCardVariants) { PaperCard templateLand = CardUtil.getCardByName("Plains"); edition = templateLand.getEdition(); } - if(sum==0.) - { - cards.addAll(generateLands("Wastes",neededLands)); - } - else - { - int mount=Math.round(neededBase*(red/sum)); - int island=Math.round(neededBase*(blue/sum)); - int forest=Math.round(neededBase*(green/sum)); - int plains=Math.round(neededBase*(white/sum)); - int swamp=Math.round(neededBase*(black/sum)); - cards.addAll(generateLands("Plains",plains,edition)); - cards.addAll(generateLands("Island",island,edition)); - cards.addAll(generateLands("Forest",forest,edition)); - cards.addAll(generateLands("Mountain",mount,edition)); - cards.addAll(generateLands("Swamp",swamp,edition)); - List landTypes=new ArrayList<>(); - if(mount>0) - landTypes.add("Mountain"); - if(island>0) - landTypes.add("Island"); - if(plains>0) - landTypes.add("Plains"); - if(swamp>0) - landTypes.add("Swamp"); - if(forest>0) - landTypes.add("Forest"); - cards.addAll(generateDualLands(landTypes,neededDualLands)); + if (sumColoredCost == 0) { + cards.addAll(generateLands("Wastes", neededLands)); + } else { + float sumTotalCost = sumColoredCost + colorless; + + int mountain = Math.round(neededBase * (red / sumTotalCost)); + int island = Math.round(neededBase * (blue / sumTotalCost)); + int forest = Math.round(neededBase * (green / sumTotalCost)); + int plains = Math.round(neededBase * (white / sumTotalCost)); + int swamp = Math.round(neededBase * (black / sumTotalCost)); + int wastes = Math.round(neededBase * (colorless / sumTotalCost)); + + cards.addAll(generateLands(useSnowLands ? "Snow-Covered Plains" : "Plains", plains, edition)); + cards.addAll(generateLands(useSnowLands ? "Snow-Covered Island" : "Island", island, edition)); + cards.addAll(generateLands(useSnowLands ? "Snow-Covered Forest" : "Forest", forest, edition)); + cards.addAll(generateLands(useSnowLands ? "Snow-Covered Mountain" : "Mountain", mountain, edition)); + cards.addAll(generateLands(useSnowLands ? "Snow-Covered Swamp" : "Swamp", swamp, edition)); + cards.addAll(generateLands(useSnowLands ? "Snow-Covered Wastes" : "Wastes", wastes, edition)); + + List landTypes = new ArrayList<>(); + if (mountain > 0) + landTypes.add("Mountain"); + if (island > 0) + landTypes.add("Island"); + if (plains > 0) + landTypes.add("Plains"); + if (swamp > 0) + landTypes.add("Swamp"); + if (forest > 0) + landTypes.add("Forest"); + + cards.addAll(generateDualLands(landTypes, neededDualLands)); } + return cards; } - private static Collection generateDualLands(List landName, int count) { ArrayList rewards=new ArrayList<>(); RewardData base= new RewardData(); @@ -737,14 +754,14 @@ public class CardUtil { if (deck != null) return deck; } + Json json = new Json(); FileHandle handle = Config.instance().getFile(path); if (handle.exists()) return generateDeck(json.fromJson(GeneratedDeckData.class, handle), starterEdition, discourageDuplicates); Deck deck = DeckgenUtil.getRandomOrPreconOrThemeDeck(colors, true, false, true); - System.err.println("Error loading JSON: " + handle.path() + "\nGenerating random deck: "+deck.getName()); + System.err.println("Error loading JSON: " + handle.path() + "\nGenerating random deck: " + deck.getName()); return deck; - } private static final GameFormat.Collection formats = FModel.getFormats(); diff --git a/forge-gui/res/adventure/common/decks/standard/crab.json b/forge-gui/res/adventure/common/decks/standard/crab.json index 56a7d1a5522..8fcf24f389d 100644 --- a/forge-gui/res/adventure/common/decks/standard/crab.json +++ b/forge-gui/res/adventure/common/decks/standard/crab.json @@ -1,10 +1,10 @@ { -"name":"Crab", + "name":"Crab", "template": { "count":60, "colors":["Blue"], - "tribe":"Crab", + "tribe":"Crab", "tribeCards":1.0, "tribeSynergyCards":0.2, "rares":0.4 From caf8b0535fd4f24303c16d6aa3d0bee1b4d415ea Mon Sep 17 00:00:00 2001 From: Eradev Date: Wed, 8 Oct 2025 12:14:59 -0400 Subject: [PATCH 097/230] CardUtil File format --- .../src/forge/adventure/util/CardUtil.java | 684 ++++++++---------- 1 file changed, 312 insertions(+), 372 deletions(-) diff --git a/forge-gui-mobile/src/forge/adventure/util/CardUtil.java b/forge-gui-mobile/src/forge/adventure/util/CardUtil.java index 6c38a8ee648..ce55a815131 100644 --- a/forge-gui-mobile/src/forge/adventure/util/CardUtil.java +++ b/forge-gui-mobile/src/forge/adventure/util/CardUtil.java @@ -37,32 +37,32 @@ import static forge.adventure.data.RewardData.generateAllCards; */ public class CardUtil { public static final class CardPredicate implements Predicate { - enum ColorType - { + enum ColorType { Any, Colorless, MultiColor, MonoColor } - private final List rarities=new ArrayList<>(); - private final List editions=new ArrayList<>(); - private final List subType=new ArrayList<>(); - private final List keyWords=new ArrayList<>(); - private final List type=new ArrayList<>(); - private final List superType=new ArrayList<>(); - private final List manaCosts =new ArrayList<>(); + + private final List rarities = new ArrayList<>(); + private final List editions = new ArrayList<>(); + private final List subType = new ArrayList<>(); + private final List keyWords = new ArrayList<>(); + private final List type = new ArrayList<>(); + private final List superType = new ArrayList<>(); + private final List manaCosts = new ArrayList<>(); private final Pattern text; private final boolean matchAllSubTypes; private final boolean matchAllColors; - private int colors; + private int colors; private final ColorType colorType; private final boolean shouldBeEqual; - private final List deckNeeds=new ArrayList<>(); + private final List deckNeeds = new ArrayList<>(); private final String minDate; private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); private static Date parseDate(String date) { - if( date.length() <= 7 ) + if (date.length() <= 7) date = date + "-01"; try { return formatter.parse(date); @@ -73,12 +73,12 @@ public class CardUtil { @Override public boolean test(final PaperCard card) { - if(!this.rarities.isEmpty()&&!this.rarities.contains(card.getRarity())) + if (!this.rarities.isEmpty() && !this.rarities.contains(card.getRarity())) return !this.shouldBeEqual; - if(!this.editions.isEmpty()&&!this.editions.contains(card.getEdition())) { + if (!this.editions.isEmpty() && !this.editions.contains(card.getEdition())) { boolean found = false; List allPrintings = FModel.getMagicDb().getCommonCards().getAllCards(card.getCardName()); - for (PaperCard c : allPrintings){ + for (PaperCard c : allPrintings) { if (this.editions.contains(c.getEdition())) { found = true; break; @@ -87,7 +87,7 @@ public class CardUtil { if (!found) return !this.shouldBeEqual; } - if(!this.minDate.isEmpty()) { + if (!this.minDate.isEmpty()) { boolean found = false; List allPrintings = FModel.getMagicDb().getCommonCards().getAllCards(card.getCardName()); List cardEditionList = new ArrayList<>(); @@ -100,7 +100,7 @@ public class CardUtil { cardEditionList.add(e); } - for (PaperCard c : allPrintings){ + for (PaperCard c : allPrintings) { for (CardEdition e : cardEditionList) { if (e.getCode().equals(c.getEdition())) { found = true; @@ -111,144 +111,120 @@ public class CardUtil { if (!found) return !this.shouldBeEqual; } - if(!this.manaCosts.isEmpty()&&!this.manaCosts.contains(card.getRules().getManaCost().getCMC())) + if (!this.manaCosts.isEmpty() && !this.manaCosts.contains(card.getRules().getManaCost().getCMC())) return !this.shouldBeEqual; - if(this.text!=null&& !this.text.matcher(card.getRules().getOracleText()).find()) + if (this.text != null && !this.text.matcher(card.getRules().getOracleText()).find()) return !this.shouldBeEqual; - if(this.matchAllColors) - { - if(!card.getRules().getColor().hasAllColors(this.colors)) - { + if (this.matchAllColors) { + if (!card.getRules().getColor().hasAllColors(this.colors)) { return !this.shouldBeEqual; } } - if(this.colors!= MagicColor.ALL_COLORS) - { - if(!card.getRules().getColor().hasNoColorsExcept(this.colors)||(this.colors != MagicColor.COLORLESS && card.getRules().getColor().isColorless())) + if (this.colors != MagicColor.ALL_COLORS) { + if (!card.getRules().getColor().hasNoColorsExcept(this.colors) + || (this.colors != MagicColor.COLORLESS && card.getRules().getColor().isColorless())) return !this.shouldBeEqual; } - if(colorType!=ColorType.Any) - { - switch (colorType) - { + if (colorType != ColorType.Any) { + switch (colorType) { case Colorless: - if(!card.getRules().getColor().isColorless()) + if (!card.getRules().getColor().isColorless()) return !this.shouldBeEqual; break; case MonoColor: - if(!card.getRules().getColor().isMonoColor()) + if (!card.getRules().getColor().isMonoColor()) return !this.shouldBeEqual; break; case MultiColor: - if(!card.getRules().getColor().isMulticolor()) + if (!card.getRules().getColor().isMulticolor()) return !this.shouldBeEqual; break; } } - if(!this.type.isEmpty()) - { - boolean found=false; - for(CardType.CoreType type:card.getRules().getType().getCoreTypes()) - { - if(this.type.contains(type)) - { - found=true; + if (!this.type.isEmpty()) { + boolean found = false; + for (CardType.CoreType type : card.getRules().getType().getCoreTypes()) { + if (this.type.contains(type)) { + found = true; break; } } - if(!found) + if (!found) return !this.shouldBeEqual; } - if(!this.superType.isEmpty()) - { - boolean found=false; - for(CardType.Supertype type:card.getRules().getType().getSupertypes()) - { - if(this.superType.contains(type)) - { - found=true; + if (!this.superType.isEmpty()) { + boolean found = false; + for (CardType.Supertype type : card.getRules().getType().getSupertypes()) { + if (this.superType.contains(type)) { + found = true; break; } } - if(!found) + if (!found) return !this.shouldBeEqual; } - if(this.matchAllSubTypes) - { - if(!this.subType.isEmpty()) - { - if(this.subType.size()!= Iterables.size(card.getRules().getType().getSubtypes())) + if (this.matchAllSubTypes) { + if (!this.subType.isEmpty()) { + if (this.subType.size() != Iterables.size(card.getRules().getType().getSubtypes())) return !this.shouldBeEqual; - for(String subtype:card.getRules().getType().getSubtypes()) - { - if(!this.subType.contains(subtype)) - { + for (String subtype : card.getRules().getType().getSubtypes()) { + if (!this.subType.contains(subtype)) { return !this.shouldBeEqual; } } } - } - else - { - if(!this.subType.isEmpty()) - { - boolean found=false; - for(String subtype:card.getRules().getType().getSubtypes()) - { - if(this.subType.contains(subtype)) - { - found=true; + } else { + if (!this.subType.isEmpty()) { + boolean found = false; + for (String subtype : card.getRules().getType().getSubtypes()) { + if (this.subType.contains(subtype)) { + found = true; break; } } - if(!found) + if (!found) return !this.shouldBeEqual; } } - if(!this.keyWords.isEmpty()) - { - boolean found=false; - for(String keyWord:this.keyWords) - { - if(card.getRules().hasKeyword(keyWord)) - { - found=true; + if (!this.keyWords.isEmpty()) { + boolean found = false; + for (String keyWord : this.keyWords) { + if (card.getRules().hasKeyword(keyWord)) { + found = true; break; } } - if(!found) + if (!found) return !this.shouldBeEqual; } - if(!this.deckNeeds.isEmpty()) - { + if (!this.deckNeeds.isEmpty()) { boolean found = false; - for(String need:this.deckNeeds) - { - //FormatExpected: X$Y, where X is DeckHints.Type and Y is a string descriptor + for (String need : this.deckNeeds) { + // FormatExpected: X$Y, where X is DeckHints.Type and Y is a string descriptor String[] parts = need.split("\\$"); - if (parts.length != 2){ + if (parts.length != 2) { continue; } DeckHints.Type t = DeckHints.Type.valueOf(parts[0].toUpperCase()); DeckHints hints = card.getRules().getAiHints().getDeckHints(); - if (hints != null && hints.contains(t, parts[1])){ - found=true; + if (hints != null && hints.contains(t, parts[1])) { + found = true; break; } } - if(!found) + if (!found) return !this.shouldBeEqual; } - return this.shouldBeEqual; } + private Pattern getPattern(RewardData type) { if (type.cardText == null || type.cardText.isEmpty()) return null; @@ -259,106 +235,89 @@ public class CardUtil { return null; } } + public CardPredicate(final RewardData type, final boolean wantEqual) { - this.matchAllSubTypes=type.matchAllSubTypes; - this.matchAllColors=type.matchAllColors; + this.matchAllSubTypes = type.matchAllSubTypes; + this.matchAllColors = type.matchAllColors; this.shouldBeEqual = wantEqual; - for(int i=0;type.manaCosts!=null&&i getPredicateResult(Iterable cards,final RewardData data) - { + public static List getPredicateResult(Iterable cards, final RewardData data) { List result = new ArrayList<>(); CardPredicate pre = new CardPredicate(data, true); - for (final PaperCard item : cards) - { - if(pre.test(item)) + for (final PaperCard item : cards) { + if (pre.test(item)) result.add(item); } return result; } - public static List generateCards(Iterable cards,final RewardData data, final int count, Random r) - { + public static List generateCards(Iterable cards, final RewardData data, final int count, + Random r) { boolean allCardVariants = Config.instance().getSettingData().useAllCardVariants; final List result = new ArrayList<>(); List pool = getPredicateResult(cards, data); - if (pool.size() > 0) { + if (!pool.isEmpty()) { for (int i = 0; i < count; i++) { PaperCard candidate = pool.get(r.nextInt(pool.size())); if (candidate != null) { if (allCardVariants) { - PaperCard finalCandidate = CardUtil.getCardByName(candidate.getCardName()); // get a random set variant + // Get a random set variant + PaperCard finalCandidate = CardUtil.getCardByName(candidate.getCardName()); result.add(finalCandidate); } else { result.add(candidate); @@ -368,113 +327,102 @@ public class CardUtil { } return result; } - public static int getCardPrice(PaperCard card) - { - if(card==null) - return 0; - switch (card.getRarity()) - { - case BasicLand: - return 5; - case Common: - return 50; - case Uncommon: - return 150; - case Rare: - return 300; - case MythicRare: - return 500; - default: - return 600; - } - } - public static int getRewardPrice(Reward reward) - { - PaperCard card=reward.getCard(); - if(card!=null) - return getCardPrice(card); - if(reward.getItem()!=null) - return reward.getItem().cost; - if(reward.getType()== Reward.Type.Life) - return reward.getCount()*500; - if(reward.getType()== Reward.Type.Shards) - return reward.getCount()*500; - if(reward.getType()== Reward.Type.Gold) - return reward.getCount(); - /*if(reward.getType() == Reward.Type.CardPack) - return reward.getDeck().get(DeckSection.Main).countAll()*70;*/ - //TODO: Heitor - Price by card count and type of boosterPack. + public static int getCardPrice(PaperCard card) { + if (card == null) + return 0; + + return switch (card.getRarity()) { + case BasicLand -> 5; + case Common -> 50; + case Uncommon -> 150; + case Rare -> 300; + case MythicRare -> 500; + default -> 600; + }; + } + + public static int getRewardPrice(Reward reward) { + PaperCard card = reward.getCard(); + if (card != null) + return getCardPrice(card); + if (reward.getItem() != null) + return reward.getItem().cost; + if (reward.getType() == Reward.Type.Life) + return reward.getCount() * 500; + if (reward.getType() == Reward.Type.Shards) + return reward.getCount() * 500; + if (reward.getType() == Reward.Type.Gold) + return reward.getCount(); + /* + * if(reward.getType() == Reward.Type.CardPack) + * return reward.getDeck().get(DeckSection.Main).countAll()*70; + */ + // TODO: Heitor - Price by card count and type of boosterPack. return 1000; } - public static Deck generateDeck(GeneratedDeckData data, CardEdition starterEdition, boolean discourageDuplicates) - { - List editionCodes = (starterEdition != null)?Arrays.asList(starterEdition.getCode(), starterEdition.getCode2()):Arrays.asList("JMP", "J22", "DMU", "BRO", "ONE", "MOM"); - Deck deck= new Deck(data.name); - if(data.mainDeck!=null) - { + public static Deck generateDeck(GeneratedDeckData data, CardEdition starterEdition, boolean discourageDuplicates) { + List editionCodes = (starterEdition != null) + ? Arrays.asList(starterEdition.getCode(), starterEdition.getCode2()) + : Arrays.asList("JMP", "J22", "DMU", "BRO", "ONE", "MOM"); + Deck deck = new Deck(data.name); + if (data.mainDeck != null) { deck.getOrCreate(DeckSection.Main).addAllFlat(generateAllCards(Arrays.asList(data.mainDeck), true)); - if(data.sideBoard!=null) - deck.getOrCreate(DeckSection.Sideboard).addAllFlat(generateAllCards(Arrays.asList(data.sideBoard), true)); + if (data.sideBoard != null) + deck.getOrCreate(DeckSection.Sideboard) + .addAllFlat(generateAllCards(Arrays.asList(data.sideBoard), true)); return deck; } - if(data.jumpstartPacks!=null) - { + if (data.jumpstartPacks != null) { deck.getOrCreate(DeckSection.Main); - Map > packCandidates=null; - List usedPackNames=new ArrayList(); - - for(int i=0;i> packCandidates = null; + List usedPackNames = new ArrayList(); + for (int i = 0; i < data.jumpstartPacks.length; i++) { final byte targetColor = MagicColor.fromName(data.jumpstartPacks[i]); - String targetName; - switch (targetColor) - { - default: - case MagicColor.WHITE: targetName = "Plains"; break; - case MagicColor.BLUE: targetName = "Island"; break; - case MagicColor.BLACK: targetName = "Swamp"; break; - case MagicColor.RED: targetName = "Mountain";break; - case MagicColor.GREEN: targetName = "Forest"; break; - } + String targetName = switch (targetColor) { + case MagicColor.BLUE -> "Island"; + case MagicColor.BLACK -> "Swamp"; + case MagicColor.RED -> "Mountain"; + case MagicColor.GREEN -> "Forest"; + default -> "Plains"; + }; - packCandidates=new HashMap<>(); - for(SealedTemplate template : StaticData.instance().getSpecialBoosters()) - { - if (!editionCodes.contains(template.getEdition().split("\\s",2)[0])) + packCandidates = new HashMap<>(); + for (SealedTemplate template : StaticData.instance().getSpecialBoosters()) { + if (!editionCodes.contains(template.getEdition().split("\\s", 2)[0])) continue; List packContents = new UnOpenedProduct(template).get(); if (packContents.size() < 18 | packContents.size() > 25) continue; - if (packContents.stream().filter(x -> x.getName().equals(targetName)).count() >=3) + if (packContents.stream().filter(x -> x.getName().equals(targetName)).count() >= 3) packCandidates.putIfAbsent(template.getEdition(), packContents); } List selectedPack; if (discourageDuplicates) { - Map > filteredPackCandidates= new HashMap<>(); - for (java.util.Map.Entry> entry: packCandidates.entrySet()){ - if (!usedPackNames.contains(entry.getKey())){ - filteredPackCandidates.put(entry.getKey(), entry.getValue()); //deep copy so that packCandidates can be used if filtered ends up being empty + Map> filteredPackCandidates = new HashMap<>(); + for (java.util.Map.Entry> entry : packCandidates.entrySet()) { + if (!usedPackNames.contains(entry.getKey())) { + // Deep copy so that packCandidates can be used if filtered ends up being empty + filteredPackCandidates.put(entry.getKey(), entry.getValue()); } } - //Only re-use a pack if all possibilities have already been chosen - if (filteredPackCandidates.size() == 0) + // Only re-use a pack if all possibilities have already been chosen + if (filteredPackCandidates.isEmpty()) filteredPackCandidates = packCandidates; Object[] keys = filteredPackCandidates.keySet().toArray(); - String keyName = (String)keys[Current.world().getRandom().nextInt(keys.length)]; + String keyName = (String) keys[Current.world().getRandom().nextInt(keys.length)]; usedPackNames.add(keyName); selectedPack = filteredPackCandidates.remove(keyName); - } - else{ + } else { Object[] keys = packCandidates.keySet().toArray(); - selectedPack = packCandidates.get((String)keys[Current.world().getRandom().nextInt(keys.length)]); + selectedPack = packCandidates.get((String) keys[Current.world().getRandom().nextInt(keys.length)]); } - //if the packContents size above is below 20, just get random card + // If the packContents size above is below 20, add random cards int size = 20 - selectedPack.size(); for (int c = 0; c < size; c++) { selectedPack.add(Aggregates.random(selectedPack)); @@ -483,19 +431,18 @@ public class CardUtil { } return deck; } - if(data.template!=null) - { - float count=data.template.count; - float lands=count*0.4f; - float spells=count-lands; - List dataArray= generateRewards(data.template,spells*0.5f,new int[]{1,2}); - dataArray.addAll(generateRewards(data.template,spells*0.3f,new int[]{3,4,5})); - dataArray.addAll(generateRewards(data.template,spells*0.2f,new int[]{6,7,8})); - List nonLand= generateAllCards(dataArray, true); + if (data.template != null) { + float count = data.template.count; + float lands = count * 0.4f; + float spells = count - lands; + List dataArray = generateRewards(data.template, spells * 0.5f, new int[] { 1, 2 }); + dataArray.addAll(generateRewards(data.template, spells * 0.3f, new int[] { 3, 4, 5 })); + dataArray.addAll(generateRewards(data.template, spells * 0.2f, new int[] { 6, 7, 8 })); + List nonLand = generateAllCards(dataArray, true); - nonLand.addAll(fillWithLands(nonLand,data.template)); - deck.getOrCreate(DeckSection.Main).addAllFlat(nonLand); - } + nonLand.addAll(fillWithLands(nonLand, data.template)); + deck.getOrCreate(DeckSection.Main).addAllFlat(nonLand); + } return deck; } @@ -577,102 +524,93 @@ public class CardUtil { return cards; } + private static Collection generateDualLands(List landName, int count) { - ArrayList rewards=new ArrayList<>(); - RewardData base= new RewardData(); + ArrayList rewards = new ArrayList<>(); + RewardData base = new RewardData(); rewards.add(base); - base.cardTypes=new String[]{"Land"}; - base.count=count; - base.matchAllSubTypes=true; - if(landName.size()==1) - { - base.subTypes=new String[]{landName.get(0)}; - } - else if(landName.size()==2) - { - base.subTypes=new String[]{landName.get(0),landName.get(1)}; - } - else if(landName.size()==3) - { - RewardData sub1= new RewardData(base); - RewardData sub2= new RewardData(base); - sub1.count/=3; - sub2.count/=3; - base.count-=sub1.count; - base.count-=sub2.count; + base.cardTypes = new String[] { "Land" }; + base.count = count; + base.matchAllSubTypes = true; + if (landName.size() == 1) { + base.subTypes = new String[] { landName.get(0) }; + } else if (landName.size() == 2) { + base.subTypes = new String[] { landName.get(0), landName.get(1) }; + } else if (landName.size() == 3) { + RewardData sub1 = new RewardData(base); + RewardData sub2 = new RewardData(base); + sub1.count /= 3; + sub2.count /= 3; + base.count -= sub1.count; + base.count -= sub2.count; - base.subTypes=new String[]{landName.get(0),landName.get(1)}; - sub1.subTypes=new String[]{landName.get(1),landName.get(2)}; - sub2.subTypes=new String[]{landName.get(0),landName.get(2)}; - rewards.addAll(Arrays.asList(sub1,sub2)); - } - else if(landName.size()==4) - { - RewardData sub1= new RewardData(base); - RewardData sub2= new RewardData(base); - RewardData sub3= new RewardData(base); - RewardData sub4= new RewardData(base); - sub1.count/=5; - sub2.count/=5; - sub3.count/=5; - sub4.count/=5; - base.count-=sub1.count; - base.count-=sub2.count; - base.count-=sub3.count; - base.count-=sub4.count; + base.subTypes = new String[] { landName.get(0), landName.get(1) }; + sub1.subTypes = new String[] { landName.get(1), landName.get(2) }; + sub2.subTypes = new String[] { landName.get(0), landName.get(2) }; + rewards.addAll(Arrays.asList(sub1, sub2)); + } else if (landName.size() == 4) { + RewardData sub1 = new RewardData(base); + RewardData sub2 = new RewardData(base); + RewardData sub3 = new RewardData(base); + RewardData sub4 = new RewardData(base); + sub1.count /= 5; + sub2.count /= 5; + sub3.count /= 5; + sub4.count /= 5; + base.count -= sub1.count; + base.count -= sub2.count; + base.count -= sub3.count; + base.count -= sub4.count; - base.subTypes = new String[]{landName.get(0),landName.get(1)}; - sub1.subTypes = new String[]{landName.get(0),landName.get(2)}; - sub2.subTypes = new String[]{landName.get(0),landName.get(3)}; - sub3.subTypes = new String[]{landName.get(1),landName.get(2)}; - sub4.subTypes = new String[]{landName.get(1),landName.get(3)}; - rewards.addAll(Arrays.asList(sub1,sub2,sub3,sub4)); - } - else if(landName.size()==5) - { - RewardData sub1= new RewardData(base); - RewardData sub2= new RewardData(base); - RewardData sub3= new RewardData(base); - RewardData sub4= new RewardData(base); - RewardData sub5= new RewardData(base); - RewardData sub6= new RewardData(base); - RewardData sub7= new RewardData(base); - RewardData sub8= new RewardData(base); - RewardData sub9= new RewardData(base); - sub1.count/=10; - sub2.count/=10; - sub3.count/=10; - sub4.count/=10; - sub5.count/=10; - sub6.count/=10; - sub7.count/=10; - sub8.count/=10; - sub9.count/=10; - base.count-=sub1.count; - base.count-=sub2.count; - base.count-=sub3.count; - base.count-=sub4.count; - base.count-=sub5.count; - base.count-=sub6.count; - base.count-=sub7.count; - base.count-=sub8.count; - base.count-=sub9.count; + base.subTypes = new String[] { landName.get(0), landName.get(1) }; + sub1.subTypes = new String[] { landName.get(0), landName.get(2) }; + sub2.subTypes = new String[] { landName.get(0), landName.get(3) }; + sub3.subTypes = new String[] { landName.get(1), landName.get(2) }; + sub4.subTypes = new String[] { landName.get(1), landName.get(3) }; + rewards.addAll(Arrays.asList(sub1, sub2, sub3, sub4)); + } else if (landName.size() == 5) { + RewardData sub1 = new RewardData(base); + RewardData sub2 = new RewardData(base); + RewardData sub3 = new RewardData(base); + RewardData sub4 = new RewardData(base); + RewardData sub5 = new RewardData(base); + RewardData sub6 = new RewardData(base); + RewardData sub7 = new RewardData(base); + RewardData sub8 = new RewardData(base); + RewardData sub9 = new RewardData(base); + sub1.count /= 10; + sub2.count /= 10; + sub3.count /= 10; + sub4.count /= 10; + sub5.count /= 10; + sub6.count /= 10; + sub7.count /= 10; + sub8.count /= 10; + sub9.count /= 10; + base.count -= sub1.count; + base.count -= sub2.count; + base.count -= sub3.count; + base.count -= sub4.count; + base.count -= sub5.count; + base.count -= sub6.count; + base.count -= sub7.count; + base.count -= sub8.count; + base.count -= sub9.count; - base.subTypes=new String[]{landName.get(0),landName.get(1)}; - sub1.subTypes=new String[]{landName.get(0),landName.get(2)}; - sub2.subTypes=new String[]{landName.get(0),landName.get(3)}; - sub3.subTypes=new String[]{landName.get(0),landName.get(4)}; - sub4.subTypes=new String[]{landName.get(1),landName.get(2)}; - sub5.subTypes=new String[]{landName.get(1),landName.get(3)}; - sub6.subTypes=new String[]{landName.get(1),landName.get(4)}; - sub7.subTypes=new String[]{landName.get(2),landName.get(3)}; - sub8.subTypes=new String[]{landName.get(2),landName.get(4)}; - sub9.subTypes=new String[]{landName.get(3),landName.get(4)}; - rewards.addAll(Arrays.asList(sub1,sub2,sub3,sub4,sub5,sub6,sub7,sub8,sub9)); + base.subTypes = new String[] { landName.get(0), landName.get(1) }; + sub1.subTypes = new String[] { landName.get(0), landName.get(2) }; + sub2.subTypes = new String[] { landName.get(0), landName.get(3) }; + sub3.subTypes = new String[] { landName.get(0), landName.get(4) }; + sub4.subTypes = new String[] { landName.get(1), landName.get(2) }; + sub5.subTypes = new String[] { landName.get(1), landName.get(3) }; + sub6.subTypes = new String[] { landName.get(1), landName.get(4) }; + sub7.subTypes = new String[] { landName.get(2), landName.get(3) }; + sub8.subTypes = new String[] { landName.get(2), landName.get(4) }; + sub9.subTypes = new String[] { landName.get(3), landName.get(4) }; + rewards.addAll(Arrays.asList(sub1, sub2, sub3, sub4, sub5, sub6, sub7, sub8, sub9)); } - Collection ret = new ArrayList<>(generateAllCards(rewards, true)); - return ret; + return new ArrayList<>(generateAllCards(rewards, true)); } private static Collection generateLands(String landName, int count) { @@ -700,43 +638,44 @@ public class CardUtil { } private static List generateRewards(GeneratedDeckTemplateData template, float count, int[] manaCosts) { - ArrayList ret=new ArrayList<>(); - ret.addAll(templateGenerate(template,count-(count*template.rares),manaCosts,new String[]{"Uncommon","Common"})); - ret.addAll(templateGenerate(template,count*template.rares,manaCosts,new String[]{"Rare","Mythic Rare"})); + ArrayList ret = new ArrayList<>(); + ret.addAll(templateGenerate(template, count - (count * template.rares), manaCosts, + new String[] { "Uncommon", "Common" })); + ret.addAll( + templateGenerate(template, count * template.rares, manaCosts, new String[] { "Rare", "Mythic Rare" })); return ret; } - private static ArrayList templateGenerate(GeneratedDeckTemplateData template, float count, int[] manaCosts, String[] strings) { - ArrayList ret=new ArrayList<>(); - RewardData base= new RewardData(); - base.manaCosts=manaCosts; - base.rarity=strings; - base.colors=template.colors; - if(template.tribe!=null&&!template.tribe.isEmpty()) - { - RewardData caresAbout= new RewardData(base); - caresAbout.cardText="\\b"+template.tribe+"\\b"; - caresAbout.count= Math.round(count*template.tribeSynergyCards); + private static ArrayList templateGenerate(GeneratedDeckTemplateData template, float count, + int[] manaCosts, String[] strings) { + ArrayList ret = new ArrayList<>(); + RewardData base = new RewardData(); + base.manaCosts = manaCosts; + base.rarity = strings; + base.colors = template.colors; + if (template.tribe != null && !template.tribe.isEmpty()) { + RewardData caresAbout = new RewardData(base); + caresAbout.cardText = "\\b" + template.tribe + "\\b"; + caresAbout.count = Math.round(count * template.tribeSynergyCards); ret.add(caresAbout); - base.subTypes=new String[]{template.tribe}; - base.count= Math.round(count*(1-template.tribeSynergyCards)); - } - else - { - base.count= Math.round(count); + base.subTypes = new String[] { template.tribe }; + base.count = Math.round(count * (1 - template.tribeSynergyCards)); + } else { + base.count = Math.round(count); } ret.add(base); - return ret; + return ret; } - public static Deck getDeck(String path, boolean forAI, boolean isFantasyMode, String colors, boolean isTheme, boolean useGeneticAI) { - return getDeck(path, forAI, isFantasyMode, colors, isTheme, useGeneticAI, null,true); + public static Deck getDeck(String path, boolean forAI, boolean isFantasyMode, String colors, boolean isTheme, + boolean useGeneticAI) { + return getDeck(path, forAI, isFantasyMode, colors, isTheme, useGeneticAI, null, true); } - public static Deck getDeck(String path, boolean forAI, boolean isFantasyMode, String colors, boolean isTheme, boolean useGeneticAI, CardEdition starterEdition, boolean discourageDuplicates) - { - if(path.endsWith(".dck")) { + public static Deck getDeck(String path, boolean forAI, boolean isFantasyMode, String colors, boolean isTheme, + boolean useGeneticAI, CardEdition starterEdition, boolean discourageDuplicates) { + if (path.endsWith(".dck")) { FileHandle fileHandle = Config.instance().getFile(path); Deck deck = null; if (fileHandle != null) { @@ -749,12 +688,10 @@ public class CardUtil { return deck; } - if(forAI && (isFantasyMode||useGeneticAI)) { - Deck deck = DeckgenUtil.getRandomOrPreconOrThemeDeck(colors, forAI, isTheme, useGeneticAI); - if (deck != null) - return deck; + if (forAI && (isFantasyMode || useGeneticAI)) { + return DeckgenUtil.getRandomOrPreconOrThemeDeck(colors, forAI, isTheme, useGeneticAI); } - + Json json = new Json(); FileHandle handle = Config.instance().getFile(path); if (handle.exists()) @@ -764,41 +701,41 @@ public class CardUtil { return deck; } - private static final GameFormat.Collection formats = FModel.getFormats(); - - - - + private static final GameFormat.Collection formats = FModel.getFormats(); private static final Predicate filterPioneer = formats.getPioneer().editionLegalPredicate; - private static final Predicate filterModern= formats.getModern().editionLegalPredicate; + private static final Predicate filterModern = formats.getModern().editionLegalPredicate; private static final Predicate filterVintage = formats.getVintage().editionLegalPredicate; private static final Predicate filterStandard = formats.getStandard().editionLegalPredicate; - public static Deck generateStandardBoosterAsDeck(){ + + public static Deck generateStandardBoosterAsDeck() { return generateRandomBoosterPackAsDeck(filterStandard); } - public static Deck generatePioneerBoosterAsDeck(){ + + public static Deck generatePioneerBoosterAsDeck() { return generateRandomBoosterPackAsDeck(filterPioneer); } - public static Deck generateModernBoosterAsDeck(){ + + public static Deck generateModernBoosterAsDeck() { return generateRandomBoosterPackAsDeck(filterModern); } - public static Deck generateVintageBoosterAsDeck(){ + + public static Deck generateVintageBoosterAsDeck() { return generateRandomBoosterPackAsDeck(filterVintage); } - public static Deck generateBoosterPackAsDeck(String code){ + public static Deck generateBoosterPackAsDeck(String code) { ConfigData configData = Config.instance().getConfigData(); if (configData.allowedEditions != null) { - if (!Arrays.asList(configData.allowedEditions).contains(code)){ + if (!Arrays.asList(configData.allowedEditions).contains(code)) { System.err.println("Cannot generate booster pack, '" + code + "' is not an allowed edition"); } - } else if (Arrays.asList(configData.restrictedEditions).contains(code)){ + } else if (Arrays.asList(configData.restrictedEditions).contains(code)) { System.err.println("Cannot generate booster pack, '" + code + "' is a restricted edition"); } CardEdition edition = StaticData.instance().getEditions().get(code); - if (edition == null){ + if (edition == null) { System.err.println("Set code '" + code + "' not found."); return new Deck(); } @@ -806,7 +743,7 @@ public class CardUtil { return generateBoosterPackAsDeck(edition); } - public static Deck generateBoosterPackAsDeck(CardEdition edition){ + public static Deck generateBoosterPackAsDeck(CardEdition edition) { Deck d = new Deck("Booster pack"); d.setComment(edition.getCode()); d.getMain().add(BoosterPack.fromSet(edition).getCards()); @@ -833,7 +770,7 @@ public class CardUtil { public static PaperCard getCardByName(String cardName) { List validCards; - //Faster to ask the CardDB for a card name than it is to search the pool. + // Faster to ask the CardDB for a card name than it is to search the pool. if (Config.instance().getSettingData().useAllCardVariants) validCards = FModel.getMagicDb().getCommonCards().getAllCards(cardName); else @@ -854,7 +791,9 @@ public class CardUtil { .filter(input -> input.getEdition().equals(edition)).collect(Collectors.toList()); if (validCards.isEmpty()) { - System.err.println("Unexpected behavior: tried to call getCardByNameAndEdition for card " + cardName + " from the edition " + edition + ", but didn't find it in the DB. A random existing instance will be returned if found."); + System.err.println("Unexpected behavior: tried to call getCardByNameAndEdition for card " + cardName + + " from the edition " + edition + + ", but didn't find it in the DB. A random existing instance will be returned if found."); return getCardByName(cardName); } @@ -862,7 +801,8 @@ public class CardUtil { } public static Collection getFullCardPool(boolean allCardVariants) { - return allCardVariants ? FModel.getMagicDb().getCommonCards().getAllCards() : FModel.getMagicDb().getCommonCards().getUniqueCardsNoAlt(); + return allCardVariants + ? FModel.getMagicDb().getCommonCards().getAllCards() + : FModel.getMagicDb().getCommonCards().getUniqueCardsNoAlt(); } } - From 4e17b4c9d2ae06e2f69269b39976f2102e459c01 Mon Sep 17 00:00:00 2001 From: Eradev Date: Wed, 8 Oct 2025 12:19:31 -0400 Subject: [PATCH 098/230] Missing import --- forge-gui-mobile/src/forge/adventure/util/CardUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui-mobile/src/forge/adventure/util/CardUtil.java b/forge-gui-mobile/src/forge/adventure/util/CardUtil.java index ce55a815131..3dbc5403951 100644 --- a/forge-gui-mobile/src/forge/adventure/util/CardUtil.java +++ b/forge-gui-mobile/src/forge/adventure/util/CardUtil.java @@ -9,6 +9,7 @@ import forge.adventure.data.GeneratedDeckData; import forge.adventure.data.GeneratedDeckTemplateData; import forge.adventure.data.RewardData; import forge.card.*; +import forge.card.DeckHints.Type; import forge.card.mana.ManaCost; import forge.card.mana.ManaCostShard; import forge.deck.Deck; From bbd9fb7c37ca09294fa312320dfdb564dc40946c Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 08:47:19 +0300 Subject: [PATCH 099/230] - Fix AI not ordering combatants for damage in non-legacy mode. --- forge-game/src/main/java/forge/game/combat/Combat.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java index 4f81d53ef7e..4797c94c038 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-game/src/main/java/forge/game/combat/Combat.java @@ -492,7 +492,7 @@ public class Combat { /** If there are multiple blockers, the Attacker declares the Assignment Order */ public void orderBlockersForDamageAssignment(Card attacker, CardCollection blockers) { // this method performs controller's role - if (blockers.size() <= 1 || !this.legacyOrderCombatants) { + if (blockers.size() <= 1 || (!this.legacyOrderCombatants && !playerWhoAttacks.isAI())) { blockersOrderedForDamageAssignment.get().put(attacker, new CardCollection(blockers)); return; } @@ -529,7 +529,7 @@ public class Combat { final CardCollection oldBlockers = blockersOrderedForDamageAssignment.get().get(attacker); if (oldBlockers == null || oldBlockers.isEmpty()) { blockersOrderedForDamageAssignment.get().put(attacker, new CardCollection(blocker)); - } else if (this.legacyOrderCombatants) { + } else if (this.legacyOrderCombatants || playerWhoAttacks.isAI()) { CardCollection orderedBlockers = playerWhoAttacks.getController().orderBlocker(attacker, blocker, oldBlockers); blockersOrderedForDamageAssignment.get().put(attacker, orderedBlockers); } else { @@ -550,7 +550,7 @@ public class Combat { // They need a reverse map here: Blocker => List Player blockerCtrl = blocker.getController(); - CardCollection orderedAttacker = attackers.size() <= 1 || !this.legacyOrderCombatants ? attackers : blockerCtrl.getController().orderAttackers(blocker, attackers); + CardCollection orderedAttacker = attackers.size() <= 1 || (!this.legacyOrderCombatants && !blockerCtrl.getController().isAI()) ? attackers : blockerCtrl.getController().orderAttackers(blocker, attackers); // Damage Ordering needs to take cards like Melee into account, is that happening? attackersOrderedForDamageAssignment.get().put(blocker, orderedAttacker); From 5a19c9ed7e31c658cec4c52392a73422c5a6e24d Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 10:25:45 +0300 Subject: [PATCH 100/230] Revert "- Fix AI not ordering combatants for damage in non-legacy mode." This reverts commit ff784183dc1ca8c0e75d8231efb388360909a45a. --- forge-game/src/main/java/forge/game/combat/Combat.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java index 4797c94c038..4f81d53ef7e 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-game/src/main/java/forge/game/combat/Combat.java @@ -492,7 +492,7 @@ public class Combat { /** If there are multiple blockers, the Attacker declares the Assignment Order */ public void orderBlockersForDamageAssignment(Card attacker, CardCollection blockers) { // this method performs controller's role - if (blockers.size() <= 1 || (!this.legacyOrderCombatants && !playerWhoAttacks.isAI())) { + if (blockers.size() <= 1 || !this.legacyOrderCombatants) { blockersOrderedForDamageAssignment.get().put(attacker, new CardCollection(blockers)); return; } @@ -529,7 +529,7 @@ public class Combat { final CardCollection oldBlockers = blockersOrderedForDamageAssignment.get().get(attacker); if (oldBlockers == null || oldBlockers.isEmpty()) { blockersOrderedForDamageAssignment.get().put(attacker, new CardCollection(blocker)); - } else if (this.legacyOrderCombatants || playerWhoAttacks.isAI()) { + } else if (this.legacyOrderCombatants) { CardCollection orderedBlockers = playerWhoAttacks.getController().orderBlocker(attacker, blocker, oldBlockers); blockersOrderedForDamageAssignment.get().put(attacker, orderedBlockers); } else { @@ -550,7 +550,7 @@ public class Combat { // They need a reverse map here: Blocker => List Player blockerCtrl = blocker.getController(); - CardCollection orderedAttacker = attackers.size() <= 1 || (!this.legacyOrderCombatants && !blockerCtrl.getController().isAI()) ? attackers : blockerCtrl.getController().orderAttackers(blocker, attackers); + CardCollection orderedAttacker = attackers.size() <= 1 || !this.legacyOrderCombatants ? attackers : blockerCtrl.getController().orderAttackers(blocker, attackers); // Damage Ordering needs to take cards like Melee into account, is that happening? attackersOrderedForDamageAssignment.get().put(blocker, orderedAttacker); From 7deb7b1ecc0eb2858baa897a45d979fefc9b5fea Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 10:32:04 +0300 Subject: [PATCH 101/230] - Fix AI not ordering combatants for damage in non-legacy mode - take two (inside distributeAIDamage) --- .../src/main/java/forge/ai/ComputerUtilCombat.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 5126f906227..87a45df1aaa 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2020,6 +2020,7 @@ public class ComputerUtilCombat { Map damageMap = Maps.newHashMap(); Combat combat = attacker.getGame().getCombat(); + // Order the combatants in preferred order boolean isAttacking = defender != null; // Check for Banding, Defensive Formation @@ -2046,8 +2047,11 @@ public class ComputerUtilCombat { // TODO sort remaining tramplers for DamageDone triggers } - if (block.size() == 1) { - final Card blocker = block.getFirst(); + final CardCollection orderedBlockers = new CardCollection(block); + ComputerUtilCard.sortByEvaluateCreature(orderedBlockers); + + if (orderedBlockers.size() == 1) { + final Card blocker = orderedBlockers.getFirst(); int dmgToBlocker = dmgCanDeal; if (hasTrample && isAttacking && !aiDistributesBandingDmg) { // otherwise no entity to deliver damage via trample @@ -2070,7 +2074,7 @@ public class ComputerUtilCombat { // Does the attacker deal lethal damage to all blockers //Blocking Order now determined after declare blockers Card lastBlocker = null; - for (final Card b : block) { + for (final Card b : orderedBlockers) { lastBlocker = b; final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, attacker, true); if (dmgToKill <= dmgCanDeal) { @@ -2097,7 +2101,7 @@ public class ComputerUtilCombat { } else { // In the event of Banding or Defensive Formation, assign max damage to the blocker who // can tank all the damage or to the worst blocker to lose as little as possible - for (final Card b : block) { + for (final Card b : orderedBlockers) { final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, attacker, true); if (dmgToKill > dmgCanDeal) { damageMap.put(b, dmgCanDeal); @@ -2105,7 +2109,7 @@ public class ComputerUtilCombat { } } if (damageMap.isEmpty()) { - damageMap.put(ComputerUtilCard.getWorstCreatureAI(block), dmgCanDeal); + damageMap.put(ComputerUtilCard.getWorstCreatureAI(orderedBlockers), dmgCanDeal); } } return damageMap; From af947c9af507b3f203dcd43ca32155a07b1f4d19 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 10:33:22 +0300 Subject: [PATCH 102/230] - Fix comment placement --- forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 87a45df1aaa..bef268d2092 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2020,7 +2020,6 @@ public class ComputerUtilCombat { Map damageMap = Maps.newHashMap(); Combat combat = attacker.getGame().getCombat(); - // Order the combatants in preferred order boolean isAttacking = defender != null; // Check for Banding, Defensive Formation @@ -2047,6 +2046,7 @@ public class ComputerUtilCombat { // TODO sort remaining tramplers for DamageDone triggers } + // Order the combatants in preferred order final CardCollection orderedBlockers = new CardCollection(block); ComputerUtilCard.sortByEvaluateCreature(orderedBlockers); From 6152933766d5fc00c1e3e9ee334f6f3a03d5f901 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 10:38:19 +0300 Subject: [PATCH 103/230] - Only call the sort command in case legacy ordering is disabled, assume sorted elsewhere otherwise. --- forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index bef268d2092..e041d0ef8d2 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2046,9 +2046,12 @@ public class ComputerUtilCombat { // TODO sort remaining tramplers for DamageDone triggers } - // Order the combatants in preferred order + // Order the combatants in preferred order in case legacy ordering is disabled + final boolean legacyOrderCombatants = self.getGame().getRules().hasOrderCombatants(); final CardCollection orderedBlockers = new CardCollection(block); - ComputerUtilCard.sortByEvaluateCreature(orderedBlockers); + if (!legacyOrderCombatants) { + ComputerUtilCard.sortByEvaluateCreature(orderedBlockers); // assume sorted in case the legacy option is enabled + } if (orderedBlockers.size() == 1) { final Card blocker = orderedBlockers.getFirst(); From 29fe1266d7b6116de591d656c1b76021cd926de1 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 10:47:57 +0300 Subject: [PATCH 104/230] - Reuse AiBlockController.orderBlockers --- forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index e041d0ef8d2..cb0933aea97 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2050,7 +2050,7 @@ public class ComputerUtilCombat { final boolean legacyOrderCombatants = self.getGame().getRules().hasOrderCombatants(); final CardCollection orderedBlockers = new CardCollection(block); if (!legacyOrderCombatants) { - ComputerUtilCard.sortByEvaluateCreature(orderedBlockers); // assume sorted in case the legacy option is enabled + AiBlockController.orderBlockers(attacker, orderedBlockers); // assume sorted in case the legacy option is enabled } if (orderedBlockers.size() == 1) { From 590abbe8e67c0cda3f965536af819058cf262d68 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 12:11:03 +0300 Subject: [PATCH 105/230] - Assign the result of the orderBlockers call. --- forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index cb0933aea97..42a6d821390 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2048,9 +2048,9 @@ public class ComputerUtilCombat { // Order the combatants in preferred order in case legacy ordering is disabled final boolean legacyOrderCombatants = self.getGame().getRules().hasOrderCombatants(); - final CardCollection orderedBlockers = new CardCollection(block); + CardCollection orderedBlockers = new CardCollection(block); if (!legacyOrderCombatants) { - AiBlockController.orderBlockers(attacker, orderedBlockers); // assume sorted in case the legacy option is enabled + orderedBlockers = AiBlockController.orderBlockers(attacker, orderedBlockers); // assume sorted in case the legacy option is enabled } if (orderedBlockers.size() == 1) { From 3a956c4e20f5ce64fb1db23d12b18a6333e17b98 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 12:29:12 +0300 Subject: [PATCH 106/230] - Take 4. Avoid creating an extra CardCollection unless needed. --- .../main/java/forge/ai/ComputerUtilCombat.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 42a6d821390..d0868516d0a 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2016,7 +2016,7 @@ public class ComputerUtilCombat { * @param defender * @param overrideOrder overriding combatant order */ - public static Map distributeAIDamage(final Player self, final Card attacker, final CardCollectionView block, final CardCollectionView remaining, int dmgCanDeal, GameEntity defender, boolean overrideOrder) { + public static Map distributeAIDamage(final Player self, final Card attacker, CardCollectionView block, final CardCollectionView remaining, int dmgCanDeal, GameEntity defender, boolean overrideOrder) { Map damageMap = Maps.newHashMap(); Combat combat = attacker.getGame().getCombat(); @@ -2047,14 +2047,12 @@ public class ComputerUtilCombat { } // Order the combatants in preferred order in case legacy ordering is disabled - final boolean legacyOrderCombatants = self.getGame().getRules().hasOrderCombatants(); - CardCollection orderedBlockers = new CardCollection(block); - if (!legacyOrderCombatants) { - orderedBlockers = AiBlockController.orderBlockers(attacker, orderedBlockers); // assume sorted in case the legacy option is enabled + if (!self.getGame().getRules().hasOrderCombatants()) { + block = AiBlockController.orderBlockers(attacker, new CardCollection(block)); // assume sorted in case the legacy option is enabled } - if (orderedBlockers.size() == 1) { - final Card blocker = orderedBlockers.getFirst(); + if (block.size() == 1) { + final Card blocker = block.getFirst(); int dmgToBlocker = dmgCanDeal; if (hasTrample && isAttacking && !aiDistributesBandingDmg) { // otherwise no entity to deliver damage via trample @@ -2077,7 +2075,7 @@ public class ComputerUtilCombat { // Does the attacker deal lethal damage to all blockers //Blocking Order now determined after declare blockers Card lastBlocker = null; - for (final Card b : orderedBlockers) { + for (final Card b : block) { lastBlocker = b; final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, attacker, true); if (dmgToKill <= dmgCanDeal) { @@ -2104,7 +2102,7 @@ public class ComputerUtilCombat { } else { // In the event of Banding or Defensive Formation, assign max damage to the blocker who // can tank all the damage or to the worst blocker to lose as little as possible - for (final Card b : orderedBlockers) { + for (final Card b : block) { final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, attacker, true); if (dmgToKill > dmgCanDeal) { damageMap.put(b, dmgCanDeal); @@ -2112,7 +2110,7 @@ public class ComputerUtilCombat { } } if (damageMap.isEmpty()) { - damageMap.put(ComputerUtilCard.getWorstCreatureAI(orderedBlockers), dmgCanDeal); + damageMap.put(ComputerUtilCard.getWorstCreatureAI(block), dmgCanDeal); } } return damageMap; From 4256d9ae1f4de2dfd39cfde7153dfaae3495f628 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 12:32:09 +0300 Subject: [PATCH 107/230] - Add a TODO. --- forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index d0868516d0a..c63b2b06d4e 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2048,6 +2048,7 @@ public class ComputerUtilCombat { // Order the combatants in preferred order in case legacy ordering is disabled if (!self.getGame().getRules().hasOrderCombatants()) { + // TODO: sometimes, orderAttackers needs to be called instead - differentiate this here somehow block = AiBlockController.orderBlockers(attacker, new CardCollection(block)); // assume sorted in case the legacy option is enabled } From 845969bc34d4c099b80cdf370ac34cf10df23c84 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 20:41:02 +0300 Subject: [PATCH 108/230] Update forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java Co-authored-by: Chris H --- forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index c63b2b06d4e..7af0f41188c 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2048,7 +2048,11 @@ public class ComputerUtilCombat { // Order the combatants in preferred order in case legacy ordering is disabled if (!self.getGame().getRules().hasOrderCombatants()) { - // TODO: sometimes, orderAttackers needs to be called instead - differentiate this here somehow + if (combatant.isAttacking()) { + opposedCombatants = AiBlockController.orderBlockers(combatant, new CardCollection(opposedCombatants)); + } else { + opposedCombatants = AiBlockController.orderAttackers(combatant, opposedCombatants); + } block = AiBlockController.orderBlockers(attacker, new CardCollection(block)); // assume sorted in case the legacy option is enabled } From a0442e7c3e2657dfb4d0e4847ced8708aa9b3ff6 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 8 Oct 2025 20:45:04 +0300 Subject: [PATCH 109/230] - Tweak the parameter names. --- .../java/forge/ai/ComputerUtilCombat.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 7af0f41188c..8d0e3a950b0 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2008,35 +2008,35 @@ public class ComputerUtilCombat { * * @param self * a {@link forge.game.player.Player} object. - * @param attacker + * @param combatant * a {@link forge.game.card.Card} object. - * @param block + * @param opposedCombatants * @param dmgCanDeal * a int. * @param defender * @param overrideOrder overriding combatant order */ - public static Map distributeAIDamage(final Player self, final Card attacker, CardCollectionView block, final CardCollectionView remaining, int dmgCanDeal, GameEntity defender, boolean overrideOrder) { + public static Map distributeAIDamage(final Player self, final Card combatant, CardCollectionView opposedCombatants, final CardCollectionView remaining, int dmgCanDeal, GameEntity defender, boolean overrideOrder) { Map damageMap = Maps.newHashMap(); - Combat combat = attacker.getGame().getCombat(); + Combat combat = combatant.getGame().getCombat(); boolean isAttacking = defender != null; // Check for Banding, Defensive Formation - boolean isAttackingMe = isAttacking && combat.getDefenderPlayerByAttacker(attacker).equals(self); - boolean isBlockingMyBand = attacker.getController().isOpponentOf(self) && AttackingBand.isValidBand(block, true); + boolean isAttackingMe = isAttacking && combat.getDefenderPlayerByAttacker(combatant).equals(self); + boolean isBlockingMyBand = combatant.getController().isOpponentOf(self) && AttackingBand.isValidBand(opposedCombatants, true); final boolean aiDistributesBandingDmg = isAttackingMe || isBlockingMyBand; - final boolean hasTrample = attacker.hasKeyword(Keyword.TRAMPLE); + final boolean hasTrample = combatant.hasKeyword(Keyword.TRAMPLE); - if (combat != null && remaining != null && hasTrample && attacker.isAttacking() && !aiDistributesBandingDmg) { + if (combat != null && remaining != null && hasTrample && combatant.isAttacking() && !aiDistributesBandingDmg) { // if attacker has trample and some of its blockers are also blocking others it's generally a good idea // to assign those without trample first so we can maximize the damage to the defender for (final Card c : remaining) { - if (c == attacker || c.hasKeyword(Keyword.TRAMPLE)) { + if (c == combatant || c.hasKeyword(Keyword.TRAMPLE)) { continue; } - final CardCollection sharedBlockers = new CardCollection(block); + final CardCollection sharedBlockers = new CardCollection(opposedCombatants); sharedBlockers.retainAll(combat.getBlockers(c)); if (!sharedBlockers.isEmpty()) { // signal skip for now @@ -2051,17 +2051,17 @@ public class ComputerUtilCombat { if (combatant.isAttacking()) { opposedCombatants = AiBlockController.orderBlockers(combatant, new CardCollection(opposedCombatants)); } else { - opposedCombatants = AiBlockController.orderAttackers(combatant, opposedCombatants); + opposedCombatants = AiBlockController.orderAttackers(combatant, new CardCollection(opposedCombatants)); } - block = AiBlockController.orderBlockers(attacker, new CardCollection(block)); // assume sorted in case the legacy option is enabled + opposedCombatants = AiBlockController.orderBlockers(combatant, new CardCollection(opposedCombatants)); // assume sorted in case the legacy option is enabled } - if (block.size() == 1) { - final Card blocker = block.getFirst(); + if (opposedCombatants.size() == 1) { + final Card blocker = opposedCombatants.getFirst(); int dmgToBlocker = dmgCanDeal; if (hasTrample && isAttacking && !aiDistributesBandingDmg) { // otherwise no entity to deliver damage via trample - dmgToBlocker = getEnoughDamageToKill(blocker, dmgCanDeal, attacker, true); + dmgToBlocker = getEnoughDamageToKill(blocker, dmgCanDeal, combatant, true); if (dmgCanDeal < dmgToBlocker) { // can't kill so just put the lowest legal amount @@ -2080,9 +2080,9 @@ public class ComputerUtilCombat { // Does the attacker deal lethal damage to all blockers //Blocking Order now determined after declare blockers Card lastBlocker = null; - for (final Card b : block) { + for (final Card b : opposedCombatants) { lastBlocker = b; - final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, attacker, true); + final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, combatant, true); if (dmgToKill <= dmgCanDeal) { damageMap.put(b, dmgToKill); dmgCanDeal -= dmgToKill; @@ -2107,15 +2107,15 @@ public class ComputerUtilCombat { } else { // In the event of Banding or Defensive Formation, assign max damage to the blocker who // can tank all the damage or to the worst blocker to lose as little as possible - for (final Card b : block) { - final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, attacker, true); + for (final Card b : opposedCombatants) { + final int dmgToKill = getEnoughDamageToKill(b, dmgCanDeal, combatant, true); if (dmgToKill > dmgCanDeal) { damageMap.put(b, dmgCanDeal); break; } } if (damageMap.isEmpty()) { - damageMap.put(ComputerUtilCard.getWorstCreatureAI(block), dmgCanDeal); + damageMap.put(ComputerUtilCard.getWorstCreatureAI(opposedCombatants), dmgCanDeal); } } return damageMap; From 7b8f69a022a2a617cf64755b15ca42e76c740d09 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Wed, 8 Oct 2025 17:58:08 +0000 Subject: [PATCH 110/230] Update ComputerUtilCombat.java --- forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 8d0e3a950b0..602d8a23f07 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2051,9 +2051,8 @@ public class ComputerUtilCombat { if (combatant.isAttacking()) { opposedCombatants = AiBlockController.orderBlockers(combatant, new CardCollection(opposedCombatants)); } else { - opposedCombatants = AiBlockController.orderAttackers(combatant, new CardCollection(opposedCombatants)); + opposedCombatants = AiBlockController.orderAttackers(combatant, new CardCollection(opposedCombatants)); } - opposedCombatants = AiBlockController.orderBlockers(combatant, new CardCollection(opposedCombatants)); // assume sorted in case the legacy option is enabled } if (opposedCombatants.size() == 1) { From 87977e836dda9019473c5b55bb5edf95dcf8afb2 Mon Sep 17 00:00:00 2001 From: Eradev Date: Wed, 8 Oct 2025 14:42:42 -0400 Subject: [PATCH 111/230] Update PLS --- forge-gui/res/editions/Planeshift.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/forge-gui/res/editions/Planeshift.txt b/forge-gui/res/editions/Planeshift.txt index 2ab5d8d4ecc..c176514da90 100644 --- a/forge-gui/res/editions/Planeshift.txt +++ b/forge-gui/res/editions/Planeshift.txt @@ -5,11 +5,10 @@ Name=Planeshift Code2=PS Type=Expansion BoosterCovers=1 -Booster=11 Common, 3 Uncommon, 1 Rare, 0 Special +Booster=11 Common, 3 Uncommon, 1 Rare FatPack=6 Foil=OldStyle FoilAlwaysInCommonSlot=False -ScryfallCode=PLS [cards] 1 C Aura Blast @Ron Walotsky From bceb3c450314e7ca45035a483fabdf824427064c Mon Sep 17 00:00:00 2001 From: Eradev Date: Wed, 8 Oct 2025 14:44:47 -0400 Subject: [PATCH 112/230] Check every edition of a card instead of just the first one --- .../java/forge/item/PaperCardPredicates.java | 36 +++++++++++++++++++ .../src/forge/adventure/data/RewardData.java | 35 +++++++++--------- 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/forge-core/src/main/java/forge/item/PaperCardPredicates.java b/forge-core/src/main/java/forge/item/PaperCardPredicates.java index b685885baa1..ec7f67d600c 100644 --- a/forge-core/src/main/java/forge/item/PaperCardPredicates.java +++ b/forge-core/src/main/java/forge/item/PaperCardPredicates.java @@ -57,6 +57,42 @@ public abstract class PaperCardPredicates { return new PredicateFoil(isFoil); } + /** + * Filters cards that were printed in any of the specified editions. + */ + public static Predicate printedInAnyEditions(final String[] editionCodes) { + Set editions = new HashSet<>(Arrays.asList(editionCodes)); + + return card -> StaticData.instance().getCommonCards().getAllCards(card.getName()).stream() + .map(PaperCard::getEdition).anyMatch(editionCode -> + editions.contains(editionCode) && + StaticData.instance().getCardEdition(editionCode).isCardObtainable(card.getName()) + ); + } + + /** + * Filters cards that only printed in any of the specified editions. + */ + public static Predicate onlyPrintedInEditions(final String[] editionCodes) { + Set editions = new HashSet<>(Arrays.asList(editionCodes)); + + return card -> StaticData.instance().getCommonCards().getAllCards(card.getName()).stream() + .map(PaperCard::getEdition).allMatch(editionCode -> + editions.contains(editionCode) && + StaticData.instance().getCardEdition(editionCode).isCardObtainable(card.getName()) + ); + } + + /** + * Filters cards that are obtainable in any edition. + */ + public static Predicate isObtainableAnyEdition() { + return card -> StaticData.instance().getCommonCards().getAllCards(card.getName()).stream() + .map(PaperCard::getEdition).anyMatch(editionCode -> + StaticData.instance().getCardEdition(editionCode).isCardObtainable(card.getName()) + ); + } + private static final class PredicatePrintedWithRarity implements Predicate { private final CardRarity matchingRarity; diff --git a/forge-gui-mobile/src/forge/adventure/data/RewardData.java b/forge-gui-mobile/src/forge/adventure/data/RewardData.java index 28447db939f..6ae9bbe6bda 100644 --- a/forge-gui-mobile/src/forge/adventure/data/RewardData.java +++ b/forge-gui-mobile/src/forge/adventure/data/RewardData.java @@ -9,6 +9,7 @@ import forge.adventure.world.WorldSave; import forge.card.CardEdition; import forge.deck.Deck; import forge.item.PaperCard; +import forge.item.PaperCardPredicates; import forge.model.FModel; import forge.util.IterableUtil; import forge.util.StreamUtil; @@ -17,7 +18,6 @@ import java.io.Serializable; import java.util.*; import java.util.function.Predicate; - /** * Data class that will be used to read Json configuration files * BiomeData @@ -97,11 +97,23 @@ public class RewardData implements Serializable { ConfigData configData = Config.instance().getConfigData(); RewardData legals = configData.legalCards; - if(legals==null) - allCards = CardUtil.getFullCardPool(false); // we need unique cards only here, so that a unique card can be chosen before a set variant is determined - else - allCards = IterableUtil.filter(CardUtil.getFullCardPool(false), new CardUtil.CardPredicate(legals, true)); - //Filter out specific cards. + allCards = CardUtil.getFullCardPool(false); + + if(legals != null) + allCards = IterableUtil.filter(allCards, new CardUtil.CardPredicate(legals, true)); + + // Filter out by editions and obtainability + if (configData.allowedEditions != null && configData.allowedEditions.length > 0) { + allCards = IterableUtil.filter(allCards, PaperCardPredicates.printedInAnyEditions(configData.allowedEditions)); + } else if (configData.restrictedEditions != null && configData.restrictedEditions.length > 0) { + allCards = IterableUtil.filter(allCards, PaperCardPredicates.onlyPrintedInEditions(configData.restrictedEditions).negate()); + } else { + allCards = IterableUtil.filter(allCards, PaperCardPredicates.isObtainableAnyEdition()); + } + + Set restrictedCards = new HashSet<>(Arrays.asList(configData.restrictedCards)); + + // Filter out specific cards. allCards = IterableUtil.filter(allCards, input -> { if (input == null) return false; @@ -109,23 +121,14 @@ public class RewardData implements Serializable { return false; if (input.getRules().getAiHints().getRemNonCommanderDecks()) return false; - if (configData.allowedEditions != null) { - if (!Arrays.asList(configData.allowedEditions).contains(input.getEdition())) - return false; - } else if (Arrays.asList(configData.restrictedEditions).contains(input.getEdition())) - return false; - if (input.getRules().isCustom() && input.getImageKey(false).startsWith(ImageKeys.ADVENTURECARD_PREFIX)) { return false; } - return !Arrays.asList(configData.restrictedCards).contains(input.getName()); + return !restrictedCards.contains(input.getName()); }); - // Only allow obtainable cards - allCards = IterableUtil.filter(allCards, input -> StaticData.instance().getCardEdition(input.getEdition()).isCardObtainable(input.getCardName())); - //Filter AI cards for enemies. allEnemyCards = IterableUtil.filter(allCards, input -> { if (input == null) return false; From f3ecd512535bc93f75b1b5f21ed21794748b4446 Mon Sep 17 00:00:00 2001 From: Eradev Date: Wed, 8 Oct 2025 14:46:05 -0400 Subject: [PATCH 113/230] Minor cleanup --- .../main/java/forge/card/ICardDatabase.java | 2 -- .../util/AdventureEventController.java | 36 +++++++------------ .../Shandalar Old Border/config.json | 4 +-- 3 files changed, 13 insertions(+), 29 deletions(-) diff --git a/forge-core/src/main/java/forge/card/ICardDatabase.java b/forge-core/src/main/java/forge/card/ICardDatabase.java index 1f516664621..537d3499648 100644 --- a/forge-core/src/main/java/forge/card/ICardDatabase.java +++ b/forge-core/src/main/java/forge/card/ICardDatabase.java @@ -75,8 +75,6 @@ public interface ICardDatabase extends Iterable { PaperCard getCardFromEditionsReleasedAfter(String cardName, CardArtPreference artPreference, int artIndex, Date releaseDate); PaperCard getCardFromEditionsReleasedAfter(String cardName, CardArtPreference artPreference, int artIndex, Date releaseDate, Predicate filter); - - /* CARDS COLLECTION RETRIEVAL METHODS * ================================== */ Collection getAllCards(); diff --git a/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java b/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java index 128ee1a92df..ed1fce30831 100644 --- a/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java +++ b/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java @@ -1,6 +1,5 @@ package forge.adventure.util; -import com.badlogic.gdx.utils.Array; import forge.StaticData; import forge.adventure.data.AdventureEventData; import forge.adventure.player.AdventurePlayer; @@ -21,11 +20,9 @@ import java.time.LocalDate; import java.util.*; public class AdventureEventController implements Serializable { - public void finalizeEvent(AdventureEventData completedEvent) { Current.player().getStatistic().setResult(completedEvent); Current.player().removeEvent(completedEvent); - } public enum EventFormat { @@ -42,13 +39,13 @@ public class AdventureEventController implements Serializable { } public enum EventStatus { - Available, //New event - Entered, //Entry fee paid, deck not locked in - Ready, //Deck is registered but can still be edited - Started, //Matches available - Completed, //All matches complete, rewards pending - Awarded, //Rewards distributed - Abandoned //Ended without completing all matches + Available, // New event + Entered, // Entry fee paid, deck not locked in + Ready, // Deck is registered but can still be edited + Started, // Matches available + Completed, // All matches complete, rewards pending + Awarded, // Rewards distributed + Abandoned // Ended without completing all matches } private static AdventureEventController object; @@ -64,16 +61,7 @@ public class AdventureEventController implements Serializable { } - private transient Array allEvents = new Array<>(); - private Map nextEventDate = new HashMap<>(); - - public AdventureEventController(AdventureEventController other) { - if (object == null) { - object = this; - } else { - System.out.println("Could not initialize AdventureEventController. An instance already exists and cannot be merged."); - } - } + private final Map nextEventDate = new HashMap<>(); public static void clear() { object = null; @@ -81,7 +69,7 @@ public class AdventureEventController implements Serializable { public AdventureEventData createEvent(EventStyle style, String pointID, int eventOrigin, PointOfInterestChanges changes) { if (nextEventDate.containsKey(pointID) && nextEventDate.get(pointID) >= LocalDate.now().toEpochDay()) { - //No event currently available here + // No event currently available here return null; } @@ -100,7 +88,7 @@ public class AdventureEventController implements Serializable { AdventureEventData e; - // After a certain amount of wins, stop offering jump start events + // After a certain number of wins, stop offering Jumpstart events if (Current.player().getStatistic().totalWins() < 10 && random.nextInt(10) <= 2) { e = new AdventureEventData(eventSeed, EventFormat.Jumpstart); @@ -168,8 +156,8 @@ public class AdventureEventController implements Serializable { } public List getJumpstartBoosters(CardBlock block, int count) { - //Get all candidates then remove at random until no more than count are included - //This will prevent duplicate choices within a round of a Jumpstart draft + // Get all candidates, then remove at random until no more than count are included + // This will prevent duplicate choices within a round of a Jumpstart draft List packsAsDecks = new ArrayList<>(); for (SealedTemplate template : StaticData.instance().getSpecialBoosters()) { if (!template.getEdition().contains(block.getLandSet().getCode())) diff --git a/forge-gui/res/adventure/Shandalar Old Border/config.json b/forge-gui/res/adventure/Shandalar Old Border/config.json index 5821f361163..541274739f7 100644 --- a/forge-gui/res/adventure/Shandalar Old Border/config.json +++ b/forge-gui/res/adventure/Shandalar Old Border/config.json @@ -70,9 +70,7 @@ "Underground Sea", "Volcanic Island" ], - "restrictedEditions": [ - - ], + "restrictedEditions": [], "allowedEditions": [ "LEA", "LEB", From 1962a8d28b1c6c805f45896a4638876fd829237e Mon Sep 17 00:00:00 2001 From: Eradev Date: Wed, 8 Oct 2025 14:52:46 -0400 Subject: [PATCH 114/230] Fix typo --- forge-gui-mobile/src/forge/adventure/scene/SpellSmithScene.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui-mobile/src/forge/adventure/scene/SpellSmithScene.java b/forge-gui-mobile/src/forge/adventure/scene/SpellSmithScene.java index 31d77f9e2dd..690d7cbc271 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/SpellSmithScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/SpellSmithScene.java @@ -401,7 +401,7 @@ public class SpellSmithScene extends UIScene { if (cost_low > -1) totalCost *= 2.5f; //And CMC cost multiplier. cardPool = StreamUtil.stream(P).collect(Collectors.toList()); - poolSize.setText(((cardPool.size() > 0 ? "[/][FOREST]" : "[/][RED]")) + cardPool.size() + " possible card" + (cardPool.size() != 1 ? "s" : "")); + poolSize.setText(((cardPool.size() > 0 ? "[/][FOREST]" : "[/][RED]")) + cardPool.size() + " possible card" + (cardPool.size() > 1 ? "s" : "")); currentPrice = (int) totalCost; currentShardPrice = (int) (totalCost * 0.2f); //Intentionally rounding up via the cast to int pullUsingGold.setText("[+Pull][+goldcoin] "+ currentPrice); From f8d883d91ff6fe770b97d4b7477eafd6aa1a9121 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Thu, 9 Oct 2025 15:03:45 +0200 Subject: [PATCH 115/230] ColorSet: turn Set into an Enum (#8757) --- .../main/java/forge/ai/ComputerUtilMana.java | 11 +- .../java/forge/ai/PlayerControllerAi.java | 6 +- .../src/main/java/forge/card/ColorSet.java | 209 ++++++------------ .../src/main/java/forge/item/PaperCard.java | 2 +- .../java/forge/item/PaperCardPredicates.java | 28 +-- .../src/main/java/forge/game/ForgeScript.java | 2 +- .../ability/effects/ChangeTextEffect.java | 4 +- .../game/ability/effects/ManaEffect.java | 2 +- .../game/ability/effects/ProtectEffect.java | 4 +- .../ability/effects/ReplaceManaEffect.java | 4 +- .../src/main/java/forge/game/card/Card.java | 2 +- .../forge/game/player/AchievementTracker.java | 2 +- .../game/spellability/AbilityManaPart.java | 7 +- .../StaticAbilityContinuous.java | 23 +- .../src/main/java/forge/game/zone/Zone.java | 2 +- .../java/forge/trackable/TrackableTypes.java | 2 +- .../itemmanager/views/ColorSetRenderer.java | 2 +- .../screens/match/VAssignGenericAmount.java | 18 +- .../util/PlayerControllerForTests.java | 4 +- .../adventure/player/AdventurePlayer.java | 6 +- .../src/forge/adventure/util/CardUtil.java | 3 +- .../match/views/VAssignGenericAmount.java | 22 +- .../gamemodes/match/input/InputPayMana.java | 2 +- .../planarconquest/ConquestRegion.java | 2 +- .../forge/gamemodes/quest/BoosterUtils.java | 2 +- .../java/forge/itemmanager/ColumnDef.java | 6 +- .../java/forge/itemmanager/SFilterUtil.java | 2 +- .../forge/player/PlayerControllerHuman.java | 13 +- 28 files changed, 138 insertions(+), 254 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index dad9e7a2f4e..60af06cc6ba 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -710,9 +710,9 @@ public class ComputerUtilMana { if (hasConverge && (toPay == ManaCostShard.GENERIC || toPay == ManaCostShard.X)) { final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION; - for (final byte b : ColorSet.fromMask(unpaidColors)) { + for (final MagicColor.Color b : ColorSet.fromMask(unpaidColors)) { // try and pay other colors for converge - final ManaCostShard shard = ManaCostShard.valueOf(b); + final ManaCostShard shard = ManaCostShard.valueOf(b.getColorMask()); saList = sourcesForShards.get(shard); if (saList != null && !saList.isEmpty()) { toPay = shard; @@ -896,7 +896,8 @@ public class ComputerUtilMana { if (hasConverge) { // add extra colors for paying converge final int unpaidColors = cost.getUnpaidColors() + cost.getColorsPaid() ^ ManaCostShard.COLORS_SUPERPOSITION; - for (final byte b : ColorSet.fromMask(unpaidColors)) { + for (final MagicColor.Color color : ColorSet.fromMask(unpaidColors)) { + final byte b = color.getColorMask(); final ManaCostShard shard = ManaCostShard.valueOf(b); if (!sourcesForShards.containsKey(shard)) { if (ai.getManaPool().canPayForShardWithColor(shard, b)) { @@ -923,7 +924,7 @@ public class ComputerUtilMana { ColorSet shared = ColorSet.fromMask(toPay.getColorMask()).getSharedColors(ColorSet.fromNames(m.getComboColors(saPayment).split(" "))); // but other effects might still lead to a more permissive payment if (!shared.isColorless()) { - m.setExpressChoice(ColorSet.fromMask(shared.iterator().next())); + m.setExpressChoice(shared.iterator().next().getShortName()); } getComboManaChoice(ai, saPayment, sa, cost); } @@ -1098,7 +1099,7 @@ public class ComputerUtilMana { // * pay hybrids // * pay phyrexian, keep mana for colorless // * pay generic - return cost.getShardToPayByPriority(shardsToPay, ColorSet.ALL_COLORS.getColor()); + return cost.getShardToPayByPriority(shardsToPay, ColorSet.WUBRG.getColor()); } private static void adjustManaCostToAvoidNegEffects(ManaCostBeingPaid cost, final Card card, Player ai) { diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 81bc75db880..05f92ee72f2 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -1028,13 +1028,13 @@ public class PlayerControllerAi extends PlayerController { if ((colors.getColor() & chosenColorMask) != 0) { return chosenColorMask; } - return Iterables.getFirst(colors, (byte)0); + return Iterables.getFirst(colors, MagicColor.Color.COLORLESS).getColorMask(); } @Override public byte chooseColor(String message, SpellAbility sa, ColorSet colors) { if (colors.countColors() < 2) { - return Iterables.getFirst(colors, MagicColor.WHITE); + return Iterables.getFirst(colors, MagicColor.Color.WHITE).getColorMask(); } // You may switch on sa.getApi() here and use sa.getParam("AILogic") CardCollectionView hand = player.getCardsIn(ZoneType.Hand); @@ -1047,7 +1047,7 @@ public class PlayerControllerAi extends PlayerController { if ((colors.getColor() & chosenColorMask) != 0) { return chosenColorMask; } - return Iterables.getFirst(colors, MagicColor.WHITE); + return Iterables.getFirst(colors, MagicColor.Color.WHITE).getColorMask(); } @Override diff --git a/forge-core/src/main/java/forge/card/ColorSet.java b/forge-core/src/main/java/forge/card/ColorSet.java index 74c67321d24..e4b3fe19a5f 100644 --- a/forge-core/src/main/java/forge/card/ColorSet.java +++ b/forge-core/src/main/java/forge/card/ColorSet.java @@ -17,14 +17,12 @@ */ package forge.card; -import com.google.common.collect.UnmodifiableIterator; import forge.card.MagicColor.Color; import forge.card.mana.ManaCost; import forge.util.BinaryUtil; import java.io.Serializable; import java.util.*; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -37,90 +35,56 @@ import java.util.stream.Stream; * * */ -public final class ColorSet implements Comparable, Iterable, Serializable { +public enum ColorSet implements Iterable, Serializable { + + C(Color.COLORLESS), + W(Color.WHITE), + U(Color.BLUE), + WU(Color.WHITE, Color.BLUE), + B(Color.BLACK), + WB(Color.WHITE, Color.BLACK), + UB(Color.BLUE, Color.BLACK), + WUB(Color.WHITE, Color.BLUE, Color.BLACK), + R(Color.RED), + RW(Color.RED, Color.WHITE), + UR(Color.BLUE, Color.RED), + URW(Color.BLUE, Color.RED, Color.WHITE), + BR(Color.BLACK, Color.RED), + RWB(Color.RED, Color.WHITE, Color.BLACK), + UBR(Color.BLUE, Color.BLACK, Color.RED), + WUBR(Color.WHITE, Color.BLUE, Color.BLACK, Color.RED), + G(Color.GREEN), + GW(Color.GREEN, Color.WHITE), + GU(Color.GREEN, Color.BLUE), + GWU(Color.GREEN, Color.WHITE, Color.BLUE), + BG(Color.BLACK, Color.GREEN), + WBG(Color.WHITE, Color.BLACK, Color.GREEN), + BGU(Color.BLACK, Color.GREEN, Color.BLUE), + GWUB(Color.GREEN, Color.WHITE, Color.BLUE, Color.BLACK), + RG(Color.RED, Color.GREEN), + RGW(Color.RED, Color.GREEN, Color.WHITE), + GUR(Color.GREEN, Color.BLUE, Color.RED), + RGWU(Color.RED, Color.GREEN, Color.WHITE, Color.BLUE), + BRG(Color.BLACK, Color.RED, Color.GREEN), + BRGW(Color.BLACK, Color.RED, Color.GREEN, Color.WHITE), + UBRG(Color.BLUE, Color.BLACK, Color.RED, Color.GREEN), + WUBRG(Color.WHITE, Color.BLUE, Color.BLACK, Color.RED, Color.GREEN) + ; + private static final long serialVersionUID = 794691267379929080L; - // needs to be before other static - private static final ColorSet[] cache = new ColorSet[MagicColor.ALL_COLORS + 1]; - static { - byte COLORLESS = MagicColor.COLORLESS; - byte WHITE = MagicColor.WHITE; - byte BLUE = MagicColor.BLUE; - byte BLACK = MagicColor.BLACK; - byte RED = MagicColor.RED; - byte GREEN = MagicColor.GREEN; - Color C = Color.COLORLESS; - Color W = Color.WHITE; - Color U = Color.BLUE; - Color B = Color.BLACK; - Color R = Color.RED; - Color G = Color.GREEN; - - //colorless - cache[COLORLESS] = new ColorSet(C); - - //mono-color - cache[WHITE] = new ColorSet(W); - cache[BLUE] = new ColorSet(U); - cache[BLACK] = new ColorSet(B); - cache[RED] = new ColorSet(R); - cache[GREEN] = new ColorSet(G); - - //two-color - cache[WHITE | BLUE] = new ColorSet(W, U); - cache[WHITE | BLACK] = new ColorSet(W, B); - cache[BLUE | BLACK] = new ColorSet(U, B); - cache[BLUE | RED] = new ColorSet(U, R); - cache[BLACK | RED] = new ColorSet(B, R); - cache[BLACK | GREEN] = new ColorSet(B, G); - cache[RED | GREEN] = new ColorSet(R, G); - cache[RED | WHITE] = new ColorSet(R, W); - cache[GREEN | WHITE] = new ColorSet(G, W); - cache[GREEN | BLUE] = new ColorSet(G, U); - - //three-color - cache[WHITE | BLUE | BLACK] = new ColorSet(W, U, B); - cache[WHITE | BLACK | GREEN] = new ColorSet(W, B, G); - cache[BLUE | BLACK | RED] = new ColorSet(U, B, R); - cache[BLUE | RED | WHITE] = new ColorSet(U, R, W); - cache[BLACK | RED | GREEN] = new ColorSet(B, R, G); - cache[BLACK | GREEN | BLUE] = new ColorSet(B, G, U); - cache[RED | GREEN | WHITE] = new ColorSet(R, G, W); - cache[RED | WHITE | BLACK] = new ColorSet(R, W, B); - cache[GREEN | WHITE | BLUE] = new ColorSet(G, W, U); - cache[GREEN | BLUE | RED] = new ColorSet(G, U, R); - - //four-color - cache[WHITE | BLUE | BLACK | RED] = new ColorSet(W, U, B, R); - cache[BLUE | BLACK | RED | GREEN] = new ColorSet(U, B, R, G); - cache[BLACK | RED | GREEN | WHITE] = new ColorSet(B, R, G, W); - cache[RED | GREEN | WHITE | BLUE] = new ColorSet(R, G, W, U); - cache[GREEN | WHITE | BLUE | BLACK] = new ColorSet(G, W, U, B); - - //five-color - cache[WHITE | BLUE | BLACK | RED | GREEN] = new ColorSet(W, U, B, R, G); - } private final Collection orderedShards; - private final byte myColor; private final float orderWeight; - private final Set enumSet; - private final String desc; - - public static final ColorSet ALL_COLORS = fromMask(MagicColor.ALL_COLORS); - public static final ColorSet NO_COLORS = fromMask(MagicColor.COLORLESS); private ColorSet(final Color... ordered) { this.orderedShards = Arrays.asList(ordered); - this.myColor = orderedShards.stream().map(Color::getColorMask).reduce((byte)0, (a, b) -> (byte)(a | b)); - this.orderWeight = this.getOrderWeight(); - this.enumSet = EnumSet.copyOf(orderedShards); - this.desc = orderedShards.stream().map(Color::getShortName).collect(Collectors.joining()); + this.orderWeight = this.calcOrderWeight(); } public static ColorSet fromMask(final int mask) { final int mask32 = mask & MagicColor.ALL_COLORS; - return cache[mask32]; + return values()[mask32]; } public static ColorSet fromEnums(final Color... colors) { @@ -167,7 +131,10 @@ public final class ColorSet implements Comparable, Iterable, Ser * @return true, if successful */ public boolean hasAnyColor(final int colormask) { - return (this.myColor & colormask) != 0; + return (this.ordinal() & colormask) != 0; + } + public boolean hasAnyColor(final Color c) { + return this.orderedShards.contains(c); } /** @@ -178,12 +145,12 @@ public final class ColorSet implements Comparable, Iterable, Ser * @return true, if successful */ public boolean hasAllColors(final int colormask) { - return (this.myColor & colormask) == colormask; + return (this.ordinal() & colormask) == colormask; } /** this has exactly the colors defined by operand. */ public boolean hasExactlyColor(final int colormask) { - return this.myColor == colormask; + return this.ordinal() == colormask; } /** this has no other colors except defined by operand. */ @@ -193,17 +160,17 @@ public final class ColorSet implements Comparable, Iterable, Ser /** this has no other colors except defined by operand. */ public boolean hasNoColorsExcept(final int colormask) { - return (this.myColor & ~colormask) == 0; + return (this.ordinal() & ~colormask) == 0; } /** This returns the colors that colormask contains that are not in color */ public ColorSet getMissingColors(final byte colormask) { - return fromMask(this.myColor & ~colormask); + return fromMask(this.ordinal() & ~colormask); } /** Operand has no other colors except defined by this. */ public boolean containsAllColorsFrom(final int colorProfile) { - return (~this.myColor & colorProfile) == 0; + return (~this.ordinal() & colorProfile) == 0; } /** @@ -212,7 +179,7 @@ public final class ColorSet implements Comparable, Iterable, Ser * @return the int */ public int countColors() { - return BinaryUtil.bitCount(this.myColor); + return BinaryUtil.bitCount(this.ordinal()); } // bit count // order has to be: W U B R G multi colorless - same as cards numbering @@ -222,7 +189,7 @@ public final class ColorSet implements Comparable, Iterable, Ser * * @return the order weight */ - private float getOrderWeight() { + private float calcOrderWeight() { float res = this.countColors(); if (hasWhite()) { res += 0.0005f; @@ -241,6 +208,10 @@ public final class ColorSet implements Comparable, Iterable, Ser } return res; } + public float getOrderWeight() + { + return orderWeight; + } /** * Checks if is colorless. @@ -248,7 +219,7 @@ public final class ColorSet implements Comparable, Iterable, Ser * @return true, if is colorless */ public boolean isColorless() { - return this.myColor == 0; + return this == C; } /** @@ -266,7 +237,7 @@ public final class ColorSet implements Comparable, Iterable, Ser * @return true, if is all colors */ public boolean isAllColors() { - return this == ALL_COLORS; + return this == WUBRG; } /** @@ -286,17 +257,7 @@ public final class ColorSet implements Comparable, Iterable, Ser * @return true, if is equal */ public boolean isEqual(final byte color) { - return color == this.myColor; - } - - /* - * (non-Javadoc) - * - * @see java.lang.Comparable#compareTo(java.lang.Object) - */ - @Override - public int compareTo(final ColorSet other) { - return Float.compare(this.orderWeight, other.orderWeight); + return color == this.ordinal(); } // Presets @@ -346,23 +307,13 @@ public final class ColorSet implements Comparable, Iterable, Ser } public ColorSet inverse() { - byte mask = this.myColor; + byte mask = (byte)this.ordinal(); mask ^= MagicColor.ALL_COLORS; return fromMask(mask); } public byte getColor() { - return myColor; - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return desc; + return (byte)ordinal(); } /** @@ -372,7 +323,7 @@ public final class ColorSet implements Comparable, Iterable, Ser * @return true, if successful */ public boolean sharesColorWith(final ColorSet ccOther) { - return (this.myColor & ccOther.myColor) != 0; + return (this.ordinal() & ccOther.ordinal()) != 0; } public ColorSet getSharedColors(final ColorSet ccOther) { @@ -380,50 +331,20 @@ public final class ColorSet implements Comparable, Iterable, Ser } public ColorSet getOffColors(final ColorSet ccOther) { - return fromMask(~this.myColor & ccOther.myColor); + return fromMask(~this.ordinal() & ccOther.ordinal()); } public Set toEnumSet() { - return EnumSet.copyOf(enumSet); + return EnumSet.copyOf(orderedShards); } - @Override - public Iterator iterator() { - return new ColorIterator(); - } - - private class ColorIterator extends UnmodifiableIterator { - int currentBit = -1; - - private int getIndexOfNextColor(){ - int nextBit = currentBit + 1; - while (nextBit < MagicColor.NUMBER_OR_COLORS) { - if ((myColor & MagicColor.WUBRG[nextBit]) != 0) { - break; - } - nextBit++; - } - return nextBit; - } - - @Override - public boolean hasNext() { - return getIndexOfNextColor() < MagicColor.NUMBER_OR_COLORS; - } - - @Override - public Byte next() { - currentBit = getIndexOfNextColor(); - if (currentBit >= MagicColor.NUMBER_OR_COLORS) { - throw new NoSuchElementException(); - } - - return MagicColor.WUBRG[currentBit]; - } + //@Override + public Iterator iterator() { + return this.orderedShards.iterator(); } public Stream stream() { - return this.toEnumSet().stream(); + return this.orderedShards.stream(); } //Get array of mana cost shards for color set in the proper order diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index 6d693af47b3..63e5642ba3e 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -593,7 +593,7 @@ public class PaperCard implements Comparable, InventoryItemFromSet, public PaperCardFlags withMarkedColors(ColorSet markedColors) { if(markedColors == null) - markedColors = ColorSet.NO_COLORS; + markedColors = ColorSet.C; return new PaperCardFlags(this, markedColors, null); } diff --git a/forge-core/src/main/java/forge/item/PaperCardPredicates.java b/forge-core/src/main/java/forge/item/PaperCardPredicates.java index ec7f67d600c..449d191b9c6 100644 --- a/forge-core/src/main/java/forge/item/PaperCardPredicates.java +++ b/forge-core/src/main/java/forge/item/PaperCardPredicates.java @@ -112,25 +112,19 @@ public abstract class PaperCardPredicates { } private static final class PredicateColor implements Predicate { - private final byte operand; + private final MagicColor.Color operand; - private PredicateColor(final byte color) { + private PredicateColor(final MagicColor.Color color) { this.operand = color; } @Override public boolean test(final PaperCard card) { - for (final byte color : card.getRules().getColor()) { - if (color == operand) { - return true; - } + if (card.getRules().getColor().hasAnyColor(operand)) { + return true; } - if (card.getRules().getType().hasType(CardType.CoreType.Land)) { - for (final byte color : card.getRules().getColorIdentity()) { - if (color == operand) { - return true; - } - } + if (card.getRules().getType().hasType(CardType.CoreType.Land) && card.getRules().getColorIdentity().hasAnyColor(operand)) { + return true; } return false; } @@ -235,11 +229,11 @@ public abstract class PaperCardPredicates { public static final Predicate IS_RARE_OR_MYTHIC = PaperCardPredicates.IS_RARE.or(PaperCardPredicates.IS_MYTHIC_RARE); public static final Predicate IS_SPECIAL = new PredicateRarity(CardRarity.Special); public static final Predicate IS_BASIC_LAND_RARITY = new PredicateRarity(CardRarity.BasicLand); - public static final Predicate IS_BLACK = new PredicateColor(MagicColor.BLACK); - public static final Predicate IS_BLUE = new PredicateColor(MagicColor.BLUE); - public static final Predicate IS_GREEN = new PredicateColor(MagicColor.GREEN); - public static final Predicate IS_RED = new PredicateColor(MagicColor.RED); - public static final Predicate IS_WHITE = new PredicateColor(MagicColor.WHITE); + public static final Predicate IS_BLACK = new PredicateColor(MagicColor.Color.BLACK); + public static final Predicate IS_BLUE = new PredicateColor(MagicColor.Color.BLUE); + public static final Predicate IS_GREEN = new PredicateColor(MagicColor.Color.GREEN); + public static final Predicate IS_RED = new PredicateColor(MagicColor.Color.RED); + public static final Predicate IS_WHITE = new PredicateColor(MagicColor.Color.WHITE); public static final Predicate IS_COLORLESS = paperCard -> paperCard.getRules().getColor().isColorless(); public static final Predicate IS_UNREBALANCED = PaperCard::isUnRebalanced; public static final Predicate IS_REBALANCED = PaperCard::isRebalanced; diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index 9c462d9e22c..cf7a461f92b 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -35,7 +35,7 @@ public class ForgeScript { boolean withSource = property.endsWith("Source"); final ColorSet colors; if (withSource && StaticAbilityColorlessDamageSource.colorlessDamageSource(cardState)) { - colors = ColorSet.NO_COLORS; + colors = ColorSet.C; } else { colors = cardState.getCard().getColor(cardState); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeTextEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeTextEffect.java index b425d41a6f8..74308bc66c7 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeTextEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeTextEffect.java @@ -34,7 +34,7 @@ public class ChangeTextEffect extends SpellAbilityEffect { final String[] changedColorWordsArray = sa.getParam("ChangeColorWord").split(" "); if (changedColorWordsArray[0].equals("Choose")) { originalColor = sa.getActivatingPlayer().getController().chooseColor( - Localizer.getInstance().getMessage("lblChooseColorReplace"), sa, ColorSet.ALL_COLORS); + Localizer.getInstance().getMessage("lblChooseColorReplace"), sa, ColorSet.WUBRG); changedColorWordOriginal = TextUtil.capitalize(MagicColor.toLongString(originalColor)); } else { changedColorWordOriginal = changedColorWordsArray[0]; @@ -44,7 +44,7 @@ public class ChangeTextEffect extends SpellAbilityEffect { if (changedColorWordsArray[1].equals("Choose")) { final ColorSet possibleNewColors; if (originalColor == 0) { // no original color (ie. any or absent) - possibleNewColors = ColorSet.ALL_COLORS; + possibleNewColors = ColorSet.WUBRG; } else { // may choose any except original color possibleNewColors = ColorSet.fromMask(originalColor).inverse(); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java index 00092734f13..b21b252e4cb 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManaEffect.java @@ -156,7 +156,7 @@ public class ManaEffect extends SpellAbilityEffect { for (int nChar = 0; nChar < colorsNeeded.length(); nChar++) { mask |= MagicColor.fromName(colorsNeeded.charAt(nChar)); } - colorMenu = mask == 0 ? ColorSet.ALL_COLORS : ColorSet.fromMask(mask); + colorMenu = mask == 0 ? ColorSet.WUBRG : ColorSet.fromMask(mask); byte val = chooser.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, colorMenu); if (0 == val) { throw new RuntimeException("ManaEffect::resolve() /*any mana*/ - " + p + " color mana choice is empty for " + card.getName()); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ProtectEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ProtectEffect.java index 278080894a4..bbcab86846e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ProtectEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ProtectEffect.java @@ -124,8 +124,8 @@ public class ProtectEffect extends SpellAbilityEffect { } } else if (sa.getParam("Gains").startsWith("Defined")) { CardCollection def = AbilityUtils.getDefinedCards(host, sa.getParam("Gains").substring(8), sa); - for (final Byte color : def.get(0).getColor()) { - gains.add(MagicColor.toLongString(color)); + for (final MagicColor.Color color : def.get(0).getColor()) { + gains.add(color.getName()); } } else { gains.addAll(choices); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java index 70ff51e0829..f754eedb5ad 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ReplaceManaEffect.java @@ -34,14 +34,14 @@ public class ReplaceManaEffect extends SpellAbilityEffect { // replace type and amount replaced = sa.getParam("ReplaceMana"); if ("Any".equals(replaced)) { - byte rs = player.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS); + byte rs = player.getController().chooseColor("Choose a color", sa, ColorSet.WUBRG); replaced = MagicColor.toShortString(rs); } } else if (sa.hasParam("ReplaceType")) { // replace color and colorless String color = sa.getParam("ReplaceType"); if ("Any".equals(color)) { - byte rs = player.getController().chooseColor("Choose a color", sa, ColorSet.ALL_COLORS); + byte rs = player.getController().chooseColor("Choose a color", sa, ColorSet.WUBRG); color = MagicColor.toShortString(rs); } else { // convert in case Color Word used 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 d5ea3aaf3e5..b9cc50ede98 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2271,7 +2271,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr public final ColorSet getMarkedColors() { if (markedColor == null) { - return ColorSet.NO_COLORS; + return ColorSet.C; } return markedColor; } diff --git a/forge-game/src/main/java/forge/game/player/AchievementTracker.java b/forge-game/src/main/java/forge/game/player/AchievementTracker.java index a597642ff08..457c75451a0 100644 --- a/forge-game/src/main/java/forge/game/player/AchievementTracker.java +++ b/forge-game/src/main/java/forge/game/player/AchievementTracker.java @@ -27,7 +27,7 @@ public class AchievementTracker { activatedNonPWUltimates.add(card.getName()); } } - if (card.getColor().equals(ColorSet.ALL_COLORS)) { + if (card.getColor().equals(ColorSet.WUBRG)) { challengesCompleted.add("Chromatic"); } } 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 005fed45f76..fd1b57c2969 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -17,7 +17,6 @@ */ package forge.game.spellability; -import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import forge.card.ColorSet; import forge.card.GamePieceType; @@ -49,9 +48,9 @@ import forge.game.zone.ZoneType; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; -import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** *

@@ -658,8 +657,8 @@ public class AbilityManaPart implements java.io.Serializable { } // replace Chosen for Spire colors if (origProduced.contains("ColorID")) { - Iterator colors = Iterators.transform(sa.getHostCard().getMarkedColors().iterator(), MagicColor::toLongString); - origProduced = origProduced.replace("ColorID", getChosenColor(sa, () -> colors)); + String str = sa.getHostCard().getMarkedColors().stream().map(c -> c.getShortName()).collect(Collectors.joining(" ")); + origProduced = origProduced.replace("ColorID", str); } if (origProduced.contains("NotedColors")) { // Should only be used for Paliano, the High City 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 407e794ebd3..bd394802db2 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -207,12 +207,12 @@ public final class StaticAbilityContinuous { if (input.contains("CommanderColorID")) { if (!hostCard.getController().getCommanders().isEmpty()) { if (input.contains("NotCommanderColorID")) { - for (Byte color : hostCard.getController().getNotCommanderColorID()) { - newKeywords.add(input.replace("NotCommanderColorID", MagicColor.toLongString(color))); + for (MagicColor.Color color : hostCard.getController().getNotCommanderColorID()) { + newKeywords.add(input.replace("NotCommanderColorID", color.getName())); } return true; - } else for (Byte color : hostCard.getController().getCommanderColorID()) { - newKeywords.add(input.replace("CommanderColorID", MagicColor.toLongString(color))); + } else for (MagicColor.Color color : hostCard.getController().getCommanderColorID()) { + newKeywords.add(input.replace("CommanderColorID", color.getName())); } return true; } @@ -220,12 +220,9 @@ public final class StaticAbilityContinuous { } // two variants for Red vs. red in keyword if (input.contains("ColorsYouCtrl") || input.contains("colorsYouCtrl")) { - final ColorSet colorsYouCtrl = CardUtil.getColorsFromCards(controller.getCardsIn(ZoneType.Battlefield)); - - for (byte color : colorsYouCtrl) { - final String colorWord = MagicColor.toLongString(color); - String y = input.replaceAll("ColorsYouCtrl", StringUtils.capitalize(colorWord)); - y = y.replaceAll("colorsYouCtrl", colorWord); + for (MagicColor.Color color : CardUtil.getColorsFromCards(controller.getCardsIn(ZoneType.Battlefield))) { + String y = input.replaceAll("ColorsYouCtrl", StringUtils.capitalize(color.getName())); + y = y.replaceAll("colorsYouCtrl", color.getName()); newKeywords.add(y); } return true; @@ -709,8 +706,8 @@ public final class StaticAbilityContinuous { newKeywords.removeIf(input -> { // replace one Keyword with list of keywords if (input.startsWith("Protection") && input.contains("CardColors")) { - for (Byte color : affectedCard.getColor()) { - extraKeywords.add(input.replace("CardColors", MagicColor.toLongString(color))); + for (MagicColor.Color color : affectedCard.getColor()) { + extraKeywords.add(input.replace("CardColors", color.getName())); } return true; } @@ -924,7 +921,7 @@ public final class StaticAbilityContinuous { addColors = ColorSet.fromNames(hostCard.getChosenColors()); } } else if (colors.equals("All")) { - addColors = ColorSet.ALL_COLORS; + addColors = ColorSet.WUBRG; } else { addColors = ColorSet.fromNames(colors.split(" & ")); } diff --git a/forge-game/src/main/java/forge/game/zone/Zone.java b/forge-game/src/main/java/forge/game/zone/Zone.java index 6f2d4a53d03..5d57174a4db 100644 --- a/forge-game/src/main/java/forge/game/zone/Zone.java +++ b/forge-game/src/main/java/forge/game/zone/Zone.java @@ -54,7 +54,7 @@ public class Zone implements java.io.Serializable, Iterable { // might support different order via preference later private static final Comparator COMPARATOR = Comparator.comparingInt((Card c) -> c.getCMC()) - .thenComparing(c -> c.getColor()) + .thenComparing(c -> c.getColor().getOrderWeight()) .thenComparing(Comparator.comparing(Card::getName)) .thenComparing(Card::hasPerpetual); diff --git a/forge-game/src/main/java/forge/trackable/TrackableTypes.java b/forge-game/src/main/java/forge/trackable/TrackableTypes.java index 5c81f403365..fe2464d90bf 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableTypes.java +++ b/forge-game/src/main/java/forge/trackable/TrackableTypes.java @@ -464,7 +464,7 @@ public class TrackableTypes { public static final TrackableType ColorSetType = new TrackableType() { @Override public ColorSet getDefaultValue() { - return ColorSet.NO_COLORS; + return ColorSet.C; } @Override diff --git a/forge-gui-desktop/src/main/java/forge/itemmanager/views/ColorSetRenderer.java b/forge-gui-desktop/src/main/java/forge/itemmanager/views/ColorSetRenderer.java index 68903a583d6..f449f6e8418 100644 --- a/forge-gui-desktop/src/main/java/forge/itemmanager/views/ColorSetRenderer.java +++ b/forge-gui-desktop/src/main/java/forge/itemmanager/views/ColorSetRenderer.java @@ -33,7 +33,7 @@ public class ColorSetRenderer extends ItemCellRenderer { this.cs = (ColorSet) value; } else { - this.cs = ColorSet.NO_COLORS; + this.cs = ColorSet.C; } this.setToolTipText(cs.toString()); return super.getTableCellRendererComponent(table, "", isSelected, hasFocus, row, column); diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/VAssignGenericAmount.java b/forge-gui-desktop/src/main/java/forge/screens/match/VAssignGenericAmount.java index 07f46812b09..379a6604456 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/VAssignGenericAmount.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/VAssignGenericAmount.java @@ -214,22 +214,8 @@ public class VAssignGenericAmount { pnlTargets.add(mp, "w 145px!, h 170px!, gap 5px 5px 3px 3px, ax center"); mp.addMouseListener(mad); targetsMap.put(mp, at); - } else if (at.entity instanceof Byte) { - SkinImage manaSymbol; - byte color = (Byte) at.entity; - if (color == MagicColor.WHITE) { - manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_W); - } else if (color == MagicColor.BLUE) { - manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_U); - } else if (color == MagicColor.BLACK) { - manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_B); - } else if (color == MagicColor.RED) { - manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_R); - } else if (color == MagicColor.GREEN) { - manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_G); - } else { // Should never come here, but add this to avoid compile error - manaSymbol = FSkin.getImage(FSkinProp.IMG_MANA_COLORLESS); - } + } else if (at.entity instanceof MagicColor.Color color) { + SkinImage manaSymbol = FSkin.getImage(FSkinProp.MANA_IMG.get(color.getShortName())); final MiscCardPanel mp = new MiscCardPanel(matchUI, "", manaSymbol); mp.setCardBounds(0, 0, 70, 70); pnlTargets.add(mp, "w 100px!, h 150px!, gap 5px 5px 3px 3px, ax center"); diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java index 958d18eb986..4d84a599a72 100644 --- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java +++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java @@ -471,12 +471,12 @@ public class PlayerControllerForTests extends PlayerController { @Override public byte chooseColor(String message, SpellAbility sa, ColorSet colors) { - return Iterables.getFirst(colors, MagicColor.WHITE); + return Iterables.getFirst(colors, MagicColor.Color.WHITE).getColorMask(); } @Override public byte chooseColorAllowColorless(String message, Card card, ColorSet colors) { - return Iterables.getFirst(colors, (byte)0); + return Iterables.getFirst(colors, MagicColor.Color.COLORLESS).getColorMask(); } private CardCollection chooseItems(CardCollectionView items, int amount) { diff --git a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java index ebc4ae17e1a..450d98d328e 100644 --- a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java +++ b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java @@ -45,7 +45,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent { private int heroRace; private int avatarIndex; private boolean isFemale; - private ColorSet colorIdentity = ColorSet.ALL_COLORS; + private ColorSet colorIdentity = ColorSet.WUBRG; // Deck data private Deck deck; @@ -396,9 +396,9 @@ public class AdventurePlayer implements Serializable, SaveFileContent { if (temp != null) setColorIdentity(temp); else - colorIdentity = ColorSet.ALL_COLORS; + colorIdentity = ColorSet.WUBRG; } else - colorIdentity = ColorSet.ALL_COLORS; + colorIdentity = ColorSet.WUBRG; gold = data.readInt("gold"); maxLife = data.readInt("maxLife"); diff --git a/forge-gui-mobile/src/forge/adventure/util/CardUtil.java b/forge-gui-mobile/src/forge/adventure/util/CardUtil.java index 3dbc5403951..7e551f2f407 100644 --- a/forge-gui-mobile/src/forge/adventure/util/CardUtil.java +++ b/forge-gui-mobile/src/forge/adventure/util/CardUtil.java @@ -250,8 +250,7 @@ public class CardUtil { this.colors = 0; for (String color : type.colors) { if ("colorID".equals(color)) - for (byte c : Current.player().getColorIdentity()) - colors |= c; + colors |= Current.player().getColorIdentity().getColor(); else colors |= MagicColor.fromName(color.toLowerCase()); } diff --git a/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java b/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java index bd0d7946e1b..1b92d31e27f 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VAssignGenericAmount.java @@ -37,7 +37,6 @@ import forge.card.CardZoom; import forge.card.MagicColor; import forge.game.card.CardView; import forge.game.player.PlayerView; -import forge.localinstance.skin.FSkinProp; import forge.screens.match.MatchController; import forge.toolbox.FCardPanel; import forge.toolbox.FContainer; @@ -165,25 +164,10 @@ public class VAssignGenericAmount extends FDialog { max = max0; if (entity instanceof CardView) { obj = add(new EffectSourcePanel((CardView)entity)); - } else if (entity instanceof PlayerView) { - PlayerView player = (PlayerView)entity; + } else if (entity instanceof PlayerView player) { obj = add(new MiscTargetPanel(player.getName(), MatchController.getPlayerAvatar(player), null)); - } else if (entity instanceof Byte) { - FSkinImageInterface manaSymbol; - byte color = (Byte) entity; - if (color == MagicColor.WHITE) { - manaSymbol = Forge.getAssets().images().get(FSkinProp.IMG_MANA_W); - } else if (color == MagicColor.BLUE) { - manaSymbol = Forge.getAssets().images().get(FSkinProp.IMG_MANA_U); - } else if (color == MagicColor.BLACK) { - manaSymbol = Forge.getAssets().images().get(FSkinProp.IMG_MANA_B); - } else if (color == MagicColor.RED) { - manaSymbol = Forge.getAssets().images().get(FSkinProp.IMG_MANA_R); - } else if (color == MagicColor.GREEN) { - manaSymbol = Forge.getAssets().images().get(FSkinProp.IMG_MANA_G); - } else { // Should never come here, but add this to avoid compile error - manaSymbol = Forge.getAssets().images().get(FSkinProp.IMG_MANA_COLORLESS); - } + } else if (entity instanceof MagicColor.Color color) { + FSkinImageInterface manaSymbol = Forge.getAssets().manaImages().get(color.getShortName()); obj = add(new MiscTargetPanel("", manaSymbol, entity)); } else { obj = add(new MiscTargetPanel(entity.toString(), FSkinImage.UNKNOWN, null)); diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java index 6cb1739caa7..ece5b77ad90 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPayMana.java @@ -276,7 +276,7 @@ public abstract class InputPayMana extends InputSyncronizedBase { // If the card has any ability that tracks mana spent, skip express Mana choice if (saPaidFor.tracksManaSpent()) { - colorCanUse = ColorSet.ALL_COLORS.getColor(); + colorCanUse = ColorSet.WUBRG.getColor(); guessAbilityWithRequiredColors = false; } diff --git a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestRegion.java b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestRegion.java index 36481c91475..bcab7af219c 100644 --- a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestRegion.java +++ b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestRegion.java @@ -105,7 +105,7 @@ public class ConquestRegion { protected ConquestRegion read(String line) { String name = null; String artCardName = null; - ColorSet colorSet = ColorSet.ALL_COLORS; + ColorSet colorSet = ColorSet.WUBRG; Predicate pred = x -> true; String key, value; diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java b/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java index 5615effad3a..8ffe5316f36 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/BoosterUtils.java @@ -560,7 +560,7 @@ public final class BoosterUtils { public static void sort(List cards) { //sort cards alphabetically so colors appear together and rares appear on top cards.sort(Comparator.comparing(PaperCard::getName)); - cards.sort(Comparator.comparing(c -> c.getRules().getColor())); + cards.sort(Comparator.comparing(c -> c.getRules().getColor().getOrderWeight())); cards.sort(Comparator.comparing(PaperCard::getRarity).reversed()); } } diff --git a/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java b/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java index 97af782408d..f5c2f080810 100644 --- a/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java +++ b/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java @@ -91,7 +91,7 @@ public enum ColumnDef { * The color column. */ COLOR("lblColor", "ttColor", 46, true, SortState.ASC, - from -> toColor(from.getKey()), + from -> toColor(from.getKey()).getOrderWeight(), from -> toColor(from.getKey())), /** * The power column. @@ -271,7 +271,7 @@ public enum ColumnDef { * The deck color column. */ DECK_COLOR("lblColor", "ttColor", 70, true, SortState.ASC, - from -> toDeckColor(from.getKey()), + from -> toDeckColor(from.getKey()).getOrderWeight(), from -> toDeckColor(from.getKey())), /** * The deck format column. @@ -377,7 +377,7 @@ public enum ColumnDef { } private static ColorSet toColor(final InventoryItem i) { - return i instanceof IPaperCard ? ((IPaperCard) i).getRules().getColor() : ColorSet.NO_COLORS; + return i instanceof IPaperCard ? ((IPaperCard) i).getRules().getColor() : ColorSet.C; } private static Integer toPower(final InventoryItem i) { diff --git a/forge-gui/src/main/java/forge/itemmanager/SFilterUtil.java b/forge-gui/src/main/java/forge/itemmanager/SFilterUtil.java index ad37ae9afc9..6e97592adf8 100644 --- a/forge-gui/src/main/java/forge/itemmanager/SFilterUtil.java +++ b/forge-gui/src/main/java/forge/itemmanager/SFilterUtil.java @@ -441,7 +441,7 @@ public class SFilterUtil { final byte colors = colors0; final boolean wantColorless = buttonMap.get(StatTypes.COLORLESS).isSelected(); final boolean wantMulticolor = buttonMap.get(StatTypes.MULTICOLOR).isSelected(); - final boolean wantAllColors = colors == ColorSet.ALL_COLORS.getColor(); + final boolean wantAllColors = colors == ColorSet.WUBRG.getColor(); //Use color identity instead of color for lands, unless all colors are filtered out anyway. final boolean filterLandsByCI = colors != 0 && FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_FILTER_LANDS_BY_COLOR_IDENTITY); diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 224927bd9ba..9c4ff34ed04 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -353,16 +353,19 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont final CardView vSource = CardView.get(sa.getHostCard()); final Map vAffected = new LinkedHashMap<>(manaAmount); Integer maxAmount = different ? 1 : manaAmount; - for (Byte color : colorSet) { + for (MagicColor.Color color : colorSet) { + if (color == MagicColor.Color.COLORLESS) { + continue; + } vAffected.put(color, maxAmount); } final Map vResult = getGui().assignGenericAmount(vSource, vAffected, manaAmount, false, localizer.getMessage("lblMana").toLowerCase()); Map result = new HashMap<>(); if (vResult != null) { //fix for netplay - for (Byte color : colorSet) { + for (MagicColor.Color color : colorSet) { if (vResult.containsKey(color)) { - result.put(color, vResult.get(color)); + result.put(color.getColorMask(), vResult.get(color)); } } } @@ -1868,8 +1871,8 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont if (withColorless) { colorNamesBuilder.add(MagicColor.toLongString(MagicColor.COLORLESS)); } - for (final Byte b : colors) { - colorNamesBuilder.add(MagicColor.toLongString(b)); + for (final MagicColor.Color color : colors) { + colorNamesBuilder.add(color.getName()); } final ImmutableList colorNames = colorNamesBuilder.build(); if (colorNames.size() > 2) { From 327d60ed84605637455c377fcdf2bb72bcd3395e Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Thu, 9 Oct 2025 17:39:26 +0200 Subject: [PATCH 116/230] Update doctor_octopus_master_planner.txt Closes #8879 --- forge-gui/res/cardsfolder/d/doctor_octopus_master_planner.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/d/doctor_octopus_master_planner.txt b/forge-gui/res/cardsfolder/d/doctor_octopus_master_planner.txt index f0d00a5ba67..a69eb46bb0e 100644 --- a/forge-gui/res/cardsfolder/d/doctor_octopus_master_planner.txt +++ b/forge-gui/res/cardsfolder/d/doctor_octopus_master_planner.txt @@ -2,7 +2,7 @@ Name:Doctor Octopus, Master Planner ManaCost:5 U B Types:Legendary Creature Human Scientist Villain PT:4/8 -S:Mode$ Continuous | Affected$ Villain.Other | AddPower$ 2 | AddToughness$ 2 | Description$ Other Villains you control get +2/+2. +S:Mode$ Continuous | Affected$ Villain.Other+YouCtrl | AddPower$ 2 | AddToughness$ 2 | Description$ Other Villains you control get +2/+2. S:Mode$ Continuous | Affected$ You | SetMaxHandSize$ 8 | Description$ Your maximum hand size is eight. T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | CheckSVar$ X | SVarCompare$ LT8 | Execute$ TrigDraw | TriggerDescription$ At the beginning of your end step, if you have fewer than eight cards in hand, draw cards equal to the difference. SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ Difference From 02c865765bbae766fafbca2abba4d7f6a1527b67 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Fri, 10 Oct 2025 11:26:40 +0200 Subject: [PATCH 117/230] Update DeckRecognizer.java remove call to getLocalisedMagicColorName --- forge-core/src/main/java/forge/deck/DeckRecognizer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-core/src/main/java/forge/deck/DeckRecognizer.java b/forge-core/src/main/java/forge/deck/DeckRecognizer.java index 1723a1942e4..f62da14d13d 100644 --- a/forge-core/src/main/java/forge/deck/DeckRecognizer.java +++ b/forge-core/src/main/java/forge/deck/DeckRecognizer.java @@ -993,7 +993,7 @@ public class DeckRecognizer { private static String getMagicColourLabel(MagicColor.Color magicColor) { if (magicColor == null) // Multicolour - return String.format("%s {W}{U}{B}{R}{G}", getLocalisedMagicColorName("Multicolour")); + return String.format("%s {W}{U}{B}{R}{G}", Localizer.getInstance().getMessage("lblMulticolor")); return String.format("%s %s", magicColor.getTranslatedName(), magicColor.getSymbol()); } From 8478d3ce47da8f4e0a1d904b359417afac4d90d6 Mon Sep 17 00:00:00 2001 From: Renato Filipe Vidal Santos <45150760+dracontes@users.noreply.github.com> Date: Sat, 11 Oct 2025 06:03:43 +0000 Subject: [PATCH 118/230] Adventure cleanup: 2025-10-10 (#8896) * Update skep_outer.tmx * Update skep_outer.tmx * Update skep_outer.tmx * Update quests.json * Update quests.json * Update quests.json * Update quests.json --- forge-gui/res/adventure/Crystal_Kingdoms/world/quests.json | 2 +- forge-gui/res/adventure/Innistrad/world/quests.json | 4 ++-- .../res/adventure/Shandalar Old Border/world/quests.json | 2 +- forge-gui/res/adventure/Shandalar/world/quests.json | 2 +- forge-gui/res/adventure/common/maps/map/skep/skep_outer.tmx | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/forge-gui/res/adventure/Crystal_Kingdoms/world/quests.json b/forge-gui/res/adventure/Crystal_Kingdoms/world/quests.json index 69c57ac4ce4..c0c6ab66522 100644 --- a/forge-gui/res/adventure/Crystal_Kingdoms/world/quests.json +++ b/forge-gui/res/adventure/Crystal_Kingdoms/world/quests.json @@ -7095,7 +7095,7 @@ "options": [ { "name": "\"What was the spell?\"", - "text": "\"I'm not an expert in the island magics, so I didn't recognize it until a hole opened in front of 'em, and through they swam.... Flopped...? Went. Anyhow, needless to say I was stunned.\"", + "text": "\"I'm not an expert in the island magics, so I didn't recognize it until a hole opened in front of 'em, and through they swam... Flopped...? Went. Anyhow, needless to say I was stunned.\"", "options": [ { "name": "\"So, a portal?\"", diff --git a/forge-gui/res/adventure/Innistrad/world/quests.json b/forge-gui/res/adventure/Innistrad/world/quests.json index b84ce987936..518cba15f4f 100644 --- a/forge-gui/res/adventure/Innistrad/world/quests.json +++ b/forge-gui/res/adventure/Innistrad/world/quests.json @@ -7150,7 +7150,7 @@ "options": [ { "name": "\"What was the spell?\"", - "text": "\"I'm not an expert in the island magics, so I didn't recognize it until a hole opened in front of 'em, and through they swam.... Flopped...? Went. Anyhow, needless to say I was stunned.\"", + "text": "\"I'm not an expert in the island magics, so I didn't recognize it until a hole opened in front of 'em, and through they swam... Flopped...? Went. Anyhow, needless to say I was stunned.\"", "options": [ { "name": "\"So, a portal?\"", @@ -11370,4 +11370,4 @@ } ] } -] \ No newline at end of file +] diff --git a/forge-gui/res/adventure/Shandalar Old Border/world/quests.json b/forge-gui/res/adventure/Shandalar Old Border/world/quests.json index b46fc46d9e4..ad349c6f70d 100644 --- a/forge-gui/res/adventure/Shandalar Old Border/world/quests.json +++ b/forge-gui/res/adventure/Shandalar Old Border/world/quests.json @@ -7095,7 +7095,7 @@ "options": [ { "name": "\"What was the spell?\"", - "text": "\"I'm not an expert in the island magics, so I didn't recognize it until a hole opened in front of 'em, and through they swam.... Flopped...? Went. Anyhow, needless to say I was stunned.\"", + "text": "\"I'm not an expert in the island magics, so I didn't recognize it until a hole opened in front of 'em, and through they swam... Flopped...? Went. Anyhow, needless to say I was stunned.\"", "options": [ { "name": "\"So, a portal?\"", diff --git a/forge-gui/res/adventure/Shandalar/world/quests.json b/forge-gui/res/adventure/Shandalar/world/quests.json index 0571793f72f..d5c74fef141 100644 --- a/forge-gui/res/adventure/Shandalar/world/quests.json +++ b/forge-gui/res/adventure/Shandalar/world/quests.json @@ -7096,7 +7096,7 @@ "options": [ { "name": "\"What was the spell?\"", - "text": "\"I'm not an expert in the island magics, so I didn't recognize it until a hole opened in front of 'em, and through they swam.... Flopped...? Went. Anyhow, needless to say I was stunned.\"", + "text": "\"I'm not an expert in the island magics, so I didn't recognize it until a hole opened in front of 'em, and through they swam... Flopped...? Went. Anyhow, needless to say I was stunned.\"", "options": [ { "name": "\"So, a portal?\"", diff --git a/forge-gui/res/adventure/common/maps/map/skep/skep_outer.tmx b/forge-gui/res/adventure/common/maps/map/skep/skep_outer.tmx index 5d24e16071f..c80736363fd 100644 --- a/forge-gui/res/adventure/common/maps/map/skep/skep_outer.tmx +++ b/forge-gui/res/adventure/common/maps/map/skep/skep_outer.tmx @@ -54,7 +54,7 @@ "options": [ { "name": "Usurped? What are you talking about?", - "text": "For millenia I've been keeping the Sliver of the Skep healthy and peaceful,keeping them under control to prevent them attacking outsiders. Everything changed when the Sliver Queen attacked the Skep. She attacked me and took control over this hive. As you can see, I was unable to defeat her.", + "text": "For millenia I've been keeping the Slivers of the Skep healthy and peaceful, keeping them under control to prevent them attacking outsiders. Everything changed when the Sliver Queen attacked the Skep. She attacked me and took control over this hive. As you can see, I was unable to defeat her.", "options": [ { "name": "So I guess the Sliver Queen is causing the Slivers of the Skep to be this aggressive? ", @@ -95,7 +95,7 @@ "not": true } ], - "text": "Weak, ... I am, defeat the Sliver Queen .... Before I lose the last of my strength.", + "text": "Weak, ... I am, defeat the Sliver Queen ... Before I lose the last of my strength.", "options": [ { "name": "(Continue)" @@ -108,7 +108,7 @@ { "checkQuestFlag": "SliverQueenDefeated" } ], "name": "The Sliver Queen has been defeated!", - "text": "My .... my gratitude to you stranger. I feel my strength slowly returning. As promised I'll take control of the Slivers again as her queen, the people of Shandalar shouldn't fear the Slivers of the Skep anymore. Though be warned adventurer, my children will still defend against any intruder why goes into the skep. Goodbye Adventurer", + "text": "My ... my gratitude to you stranger. I feel my strength slowly returning. As promised, I'll take control of the Slivers again as their queen. The people of Shandalar shouldn't fear the Slivers of the Skep anymore. Though be warned, adventurer, my children will still defend against any intruder who goes into the Skep. Goodbye, adventurer!", "options": [ { "name": "(Continue)", From 52adb2dc2be388133cb386615aa19d632fd6299d Mon Sep 17 00:00:00 2001 From: Eradev Date: Sat, 11 Oct 2025 02:05:08 -0400 Subject: [PATCH 119/230] Ensure you get the valid card (#8878) --- .../gamemodes/quest/QuestEventDraft.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java index 03d03970ffd..9681fa17fc8 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestEventDraft.java @@ -429,15 +429,19 @@ public class QuestEventDraft implements IQuestEvent { private void awardSelectedRare(final QuestDraftPrizes prizes) { final List possibleCards = new ArrayList<>(); - final List cardNames = new ArrayList<>(); + final HashSet cardNames = new HashSet<>(); for (final CardEdition edition : getAllEditions()) { for (final EditionEntry card : edition.getObtainableCards()) { + if (cardNames.contains(card.name())) { + continue; + } + if (card.rarity() == CardRarity.Rare || card.rarity() == CardRarity.MythicRare) { - final PaperCard cardToAdd = FModel.getMagicDb().getCommonCards().getCard(card.name(), edition.getCode()); - if (cardToAdd != null && !cardNames.contains(cardToAdd.getName())) { + final PaperCard cardToAdd = FModel.getMagicDb().getCommonCards().getCard(card.name(), edition.getCode(), card.collectorNumber()); + if (cardToAdd != null) { possibleCards.add(cardToAdd); - cardNames.add(cardToAdd.getName()); + cardNames.add(card.name()); } } } @@ -455,18 +459,20 @@ public class QuestEventDraft implements IQuestEvent { private PaperCard getPromoCard() { final CardEdition randomEdition = getRandomEdition(); final List cardsInEdition = new ArrayList<>(); - final List cardNames = new ArrayList<>(); + final HashSet cardNames = new HashSet<>(); for (final EditionEntry card : randomEdition.getObtainableCards()) { + if (cardNames.contains(card.name())) { + continue; + } + if (card.rarity() == CardRarity.Rare || card.rarity() == CardRarity.MythicRare) { - if (!cardNames.contains(card.name())) { - cardsInEdition.add(card); - cardNames.add(card.name()); - } + cardsInEdition.add(card); + cardNames.add(card.name()); } } - // For sets such as MB1 that only have cards from PLST. + // For sets such as MB1 that only have cards from PLST, or without any rare+ at all if (cardsInEdition.isEmpty()) { return FModel.getQuest().getCards().addRandomRare(); } @@ -478,7 +484,7 @@ public class QuestEventDraft implements IQuestEvent { while (promo == null && attempts-- > 0) { randomCard = cardsInEdition.get((int) (MyRandom.getRandom().nextDouble() * cardsInEdition.size())); - promo = FModel.getMagicDb().getCommonCards().getCard(randomCard.name(), randomEdition.getCode()); + promo = FModel.getMagicDb().getCommonCards().getCard(randomCard.name(), randomEdition.getCode(), randomCard.collectorNumber()); } if (promo == null) { From 63002aba970b9bc93e1416716b2b2fc63e0c246d Mon Sep 17 00:00:00 2001 From: Eradev Date: Sat, 11 Oct 2025 02:05:20 -0400 Subject: [PATCH 120/230] Fix Ambitious Dragonborn (#8898) * Update IKO * Update Ambitious Dragonborn --- forge-gui/res/cardsfolder/a/ambitious_dragonborn.txt | 3 ++- forge-gui/res/editions/Ikoria Lair of Behemoths.txt | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/cardsfolder/a/ambitious_dragonborn.txt b/forge-gui/res/cardsfolder/a/ambitious_dragonborn.txt index c641465c8a0..e4534b17834 100644 --- a/forge-gui/res/cardsfolder/a/ambitious_dragonborn.txt +++ b/forge-gui/res/cardsfolder/a/ambitious_dragonborn.txt @@ -5,4 +5,5 @@ PT:0/0 K:etbCounter:P1P1:X:no Condition:CARDNAME enters with X +1/+1 counters on it, where X is the greatest power among creatures you control and creature cards in your graveyard. SVar:X:Count$ValidGraveyard,Battlefield Creature.YouCtrl$GreatestPower DeckHas:Ability$Graveyard|Counters -Oracle:Ambitious Dragonborn enters with X +1/+1 counters on it, where X is the greatest power among creatures you control and creature cards in your graveyard. +SVar:NeedsToPlay:X GE1 +Oracle:Ambitious Dragonborn enters with X +1/+1 counters on it, where X is the greatest power among creatures you control and creature cards in your graveyard. \ No newline at end of file diff --git a/forge-gui/res/editions/Ikoria Lair of Behemoths.txt b/forge-gui/res/editions/Ikoria Lair of Behemoths.txt index d1970ad414b..dd3fbf2859f 100644 --- a/forge-gui/res/editions/Ikoria Lair of Behemoths.txt +++ b/forge-gui/res/editions/Ikoria Lair of Behemoths.txt @@ -7,8 +7,6 @@ BoosterCovers=3 Booster=10 Common:fromSheet("IKO cards"):!fromSheet("IKO Lands"), 3 Uncommon:fromSheet("IKO cards"), 1 RareMythic:fromSheet("IKO cards"), 1 fromSheet("IKO Lands") Prerelease=6 Boosters, 1 RareMythic+ FatPackExtraSlots=20 BasicLands, 20 BasicLands+ -#, 1 Colossification+|IKO|3 -ScryfallCode=IKO [cards] 1 C Adaptive Shimmerer @Jason Felix @@ -288,7 +286,7 @@ ScryfallCode=IKO [buy a box] 275 M Zilortha, Strength Incarnate @Antonio José Manzanedo -275a M Zilortha, Strength Incarnate @Chase Stone +275y M Zilortha, Strength Incarnate @Chase Stone [borderless] 276 M Lukka, Coppercoat Outcast @Kieran Yanner From 8bb15f6bbcb0b99d0fc6586f9c456ed8955d52e6 Mon Sep 17 00:00:00 2001 From: Paul Hammerton Date: Sat, 11 Oct 2025 10:25:52 +0100 Subject: [PATCH 121/230] Edition updates: PMEI, PZA, SLD, TMC, TMT --- .../Media and Collaboration Promos.txt | 1 + .../res/editions/Secret Lair Drop Series.txt | 9 +++++++ ...ge Mutant Hero Turtles Source Material.txt | 8 ++++++ .../Teenage Mutant Ninja Turtles Eternal.txt | 19 +++++++++++++ .../editions/Teenage Mutant Ninja Turtles.txt | 27 +++++++++++++++++++ 5 files changed, 64 insertions(+) create mode 100644 forge-gui/res/editions/Teenage Mutant Hero Turtles Source Material.txt create mode 100644 forge-gui/res/editions/Teenage Mutant Ninja Turtles Eternal.txt create mode 100644 forge-gui/res/editions/Teenage Mutant Ninja Turtles.txt diff --git a/forge-gui/res/editions/Media and Collaboration Promos.txt b/forge-gui/res/editions/Media and Collaboration Promos.txt index bc83dc17c83..13cf87faf04 100644 --- a/forge-gui/res/editions/Media and Collaboration Promos.txt +++ b/forge-gui/res/editions/Media and Collaboration Promos.txt @@ -85,3 +85,4 @@ ScryfallCode=PMEI 2025-18 R Iron Spider, Stark Upgrade @Bachzim 2025-19 M Kaalia of the Vast @Justyna Dura 2025-20 R Chrome Host Seedshark @Donato Giancola +2025-21 M Cloud, Midgar Mercenary @Square Enix diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 00a50154195..d321805ff3d 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -2195,6 +2195,15 @@ F1540 M Rainbow Dash @John Thacker 7010 R Counterspell @Tyler Walpole 7011 R Dismember @Gregg Schigiel 7012 R Command Tower @Jon Vermilyea +7013 R Brainstorm @Roberta Ingranata +7014 R Fatal Push @Veronica Fish +7015 R Harmonize @Terry Dodson +7016 R Brainstorm @Roberta Ingranata +7017 R Fatal Push @Veronica Fish +7018 R Harmonize @Terry Dodson +7019 R Brainstorm @Roberta Ingranata +7020 R Fatal Push @Veronica Fish +7021 R Harmonize @Terry Dodson 7022 R Goblin Bombardment @Phil Foglio 7023 M Consecrated Sphinx @Imiri Sakabashira 7024 R Resculpt @Imiri Sakabashira diff --git a/forge-gui/res/editions/Teenage Mutant Hero Turtles Source Material.txt b/forge-gui/res/editions/Teenage Mutant Hero Turtles Source Material.txt new file mode 100644 index 00000000000..30cb0c7f5ca --- /dev/null +++ b/forge-gui/res/editions/Teenage Mutant Hero Turtles Source Material.txt @@ -0,0 +1,8 @@ +[metadata] +Code=PZA +Date=2026-03-06 +Name=Teenage Mutant Hero Turtles Source Material +Type=Collector_Edition + +[cards] +11 M Doubling Season @Kevin Eastman diff --git a/forge-gui/res/editions/Teenage Mutant Ninja Turtles Eternal.txt b/forge-gui/res/editions/Teenage Mutant Ninja Turtles Eternal.txt new file mode 100644 index 00000000000..c2a47d1028c --- /dev/null +++ b/forge-gui/res/editions/Teenage Mutant Ninja Turtles Eternal.txt @@ -0,0 +1,19 @@ +[metadata] +Code=TMC +Date=2026-03-06 +Name=Teenage Mutant Ninja Turtles Eternal +Type=Expansion + +[cards] +1 M Leonardo, the Balance @Inkognit +2 M Donatello, the Brains @Jason Rainville +3 M Splinter, the Mentor @Andrea Tentori Montalto +4 M Raphael, the Muscle @Ryan Pancoast +5 M Michelangelo, the Heart @Néstor Ossandón Leal +6 M Heroes in a Half Shell @Victor Maury +101 M Leonardo, Worldly Warrior @Nathaniel Himawan +109 M Donatello, Rad Scientist @Thomas Chamberlain-Keen +111 R Donnie & April, Adorkable Duo @Le Vuong +118 M Raphael, Tag Team Tough @Randy Vargas +124 M Michelangelo, On the Scene @Lie Setiawan +131 M Dark Ritual @Filipe Pagliuso diff --git a/forge-gui/res/editions/Teenage Mutant Ninja Turtles.txt b/forge-gui/res/editions/Teenage Mutant Ninja Turtles.txt new file mode 100644 index 00000000000..c3dc850faab --- /dev/null +++ b/forge-gui/res/editions/Teenage Mutant Ninja Turtles.txt @@ -0,0 +1,27 @@ +[metadata] +Code=TMT +Date=2026-03-06 +Name=Teenage Mutant Ninja Turtles +Type=Expansion + +[cards] +17 M Leonardo, Sewer Samurai @Ryan Pancoast +27 R Turtles Forever @Devin Elle Kurtz +29 R April O'Neil, Hacktivist @Xabi Gaztelua +43 R Krang, Master Mind @Narendra Bintara Adi +83 M Super Shredder @Néstor Ossandón Leal +87 U Casey Jones, Jury-Rig Justiciar @Lordigan +105 R Raphael's Technique @Andreas Zafiratos +140 R Bebop & Rocksteady @Néstor Ossandón Leal +253 L Plains @Gaboleps +254 L Island @Gaboleps +255 L Swamp @Gaboleps +256 L Mountain @Gaboleps +257 L Forest @Gaboleps +261 R Turtles Forever @Devin Elle Kurtz +301 M Leonardo, Sewer Samurai @Kevin Eastman +310 L Plains @Gaboleps +311 L Island @Gaboleps +312 L Swamp @Gaboleps +313 L Mountain @Gaboleps +314 L Forest @Gaboleps From 9147836f1a17726a443f939f3722f911b8e417b6 Mon Sep 17 00:00:00 2001 From: Eradev Date: Sat, 11 Oct 2025 18:49:48 -0400 Subject: [PATCH 122/230] Update MPS_RNA --- forge-gui/res/editions/Mythic Edition - Ravnica Allegiance.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/editions/Mythic Edition - Ravnica Allegiance.txt b/forge-gui/res/editions/Mythic Edition - Ravnica Allegiance.txt index 6f542e2f761..65147adc252 100644 --- a/forge-gui/res/editions/Mythic Edition - Ravnica Allegiance.txt +++ b/forge-gui/res/editions/Mythic Edition - Ravnica Allegiance.txt @@ -22,3 +22,4 @@ R1 c_0_0_a_construct_total_artifacts @Victor Adame Minguez R2 emblem_dack_fayden @Jason Rainville R3 emblem_domri_chaos_bringer @Jason Rainville R4 emblem_jaya_ballard @Kieran Yanner +R5 emblem_tamiyo_the_moon_sage @Lius Lasahido \ No newline at end of file From 3d15f743c36d750d7f26232e54e451e37fb4b430 Mon Sep 17 00:00:00 2001 From: Renato Filipe Vidal Santos <45150760+dracontes@users.noreply.github.com> Date: Sun, 12 Oct 2025 12:48:02 +0000 Subject: [PATCH 123/230] Update the_darkness_crystal.txt --- forge-gui/res/cardsfolder/t/the_darkness_crystal.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/t/the_darkness_crystal.txt b/forge-gui/res/cardsfolder/t/the_darkness_crystal.txt index 037d83d1d04..69492e03e51 100644 --- a/forge-gui/res/cardsfolder/t/the_darkness_crystal.txt +++ b/forge-gui/res/cardsfolder/t/the_darkness_crystal.txt @@ -2,7 +2,7 @@ Name:The Darkness Crystal ManaCost:2 B B Types:Legendary Artifact S:Mode$ ReduceCost | ValidCard$ Card.Black | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Black spells you cast cost {1} less to cast. -R:Event$ Moved | ActiveZones$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Creature+!token+OppCtrl | ReplaceWith$ Exile | Description$ If a nontoken creature an opponent controls would die, instead exile it and you gain 2 life. +R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Creature+!token+OppCtrl | ReplaceWith$ Exile | Description$ If a nontoken creature an opponent controls would die, instead exile it and you gain 2 life. SVar:Exile:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Exile | Defined$ ReplacedCard | SubAbility$ DBGainLife SVar:DBGainLife:DB$ GainLife | LifeAmount$ 2 A:AB$ ChangeZone | Cost$ 4 B B T | ValidTgts$ Creature.ExiledWithSource | TgtZone$ Exile | TgtPrompt$ Select target creature card exiled with CARDNAME | Tapped$ True | WithCountersType$ P1P1 | WithCountersAmount$ 2 | GainControl$ True | Origin$ Exile | Destination$ Battlefield | SpellDescription$ Put target creature card exiled with CARDNAME onto the battlefield tapped under your control with two additional +1/+1 counters on it. From 43cefed04802d965490592f876805a40abf252ad Mon Sep 17 00:00:00 2001 From: Renato Filipe Vidal Santos <45150760+dracontes@users.noreply.github.com> Date: Sun, 12 Oct 2025 13:04:38 +0000 Subject: [PATCH 124/230] Update lumbering_laundry.txt --- forge-gui/res/cardsfolder/l/lumbering_laundry.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/l/lumbering_laundry.txt b/forge-gui/res/cardsfolder/l/lumbering_laundry.txt index 2779a9255ba..ba58088ffeb 100644 --- a/forge-gui/res/cardsfolder/l/lumbering_laundry.txt +++ b/forge-gui/res/cardsfolder/l/lumbering_laundry.txt @@ -5,4 +5,4 @@ PT:4/5 A:AB$ Effect | Cost$ 2 | StaticAbilities$ MayLookFaceDown | SpellDescription$ Until end of turn, you may look at face-down creatures you don't control any time. SVar:MayLookFaceDown:Mode$ Continuous | Affected$ Creature.faceDown+YouDontCtrl | AffectedZone$ Battlefield | MayLookAt$ You | Description$ You may look at face-down creatures you don't control any time. K:Disguise:5 -Oracle:{2}: Until end of turn, you may look at face-down creatures you don't control any time. +Oracle:{2}: Until end of turn, you may look at face-down creatures you don't control any time.\nDisguise {5} (You may cast this card face down for {3} as a 2/2 creature with ward {2}. Turn it face up any time for its disguise cost.) From 010f9175506f6a71c331844f1ed48a51494932e3 Mon Sep 17 00:00:00 2001 From: Renato Filipe Vidal Santos <45150760+dracontes@users.noreply.github.com> Date: Sun, 12 Oct 2025 13:35:07 +0000 Subject: [PATCH 125/230] Update consuming_ferocity.txt --- forge-gui/res/cardsfolder/c/consuming_ferocity.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/c/consuming_ferocity.txt b/forge-gui/res/cardsfolder/c/consuming_ferocity.txt index f12ec9c6b67..435bb7b385e 100644 --- a/forge-gui/res/cardsfolder/c/consuming_ferocity.txt +++ b/forge-gui/res/cardsfolder/c/consuming_ferocity.txt @@ -6,7 +6,7 @@ SVar:AttachAILogic:Curse S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 1 | Description$ Enchanted creature gets +1/+0. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ At the beginning of your upkeep, put a +1/+0 counter on enchanted creature. If that creature has three or more +1/+0 counters on it, it deals damage equal to its power to its controller, then destroy that creature and it can't be regenerated. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Enchanted | CounterType$ P1P0 | CounterNum$ 1 | SubAbility$ DBDmg -SVar:DBDmg:DB$ DealDamage | Defined$ TriggeredPlayer | DamageSource$ Enchanted | NumDmg$ X | ConditionCheckSVar$ Y | ConditionSVarCompare$ GE3 | SubAbility$ DBDes +SVar:DBDmg:DB$ DealDamage | Defined$ EnchantedController | DamageSource$ Enchanted | NumDmg$ X | ConditionCheckSVar$ Y | ConditionSVarCompare$ GE3 | SubAbility$ DBDes SVar:DBDes:DB$ Destroy | Defined$ Enchanted | NoRegen$ True | ConditionCheckSVar$ Y | ConditionSVarCompare$ GE3 SVar:Y:Count$Valid Creature.EnchantedBy$CardCounters.P1P0 SVar:X:Enchanted$CardPower From fdbdffd874a414dfb1899d14fe41301c288ca21e Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Fri, 10 Oct 2025 12:25:30 +0200 Subject: [PATCH 126/230] Update DeckRecognizer.java Remove extra manaSymbolsMap --- .../main/java/forge/deck/DeckRecognizer.java | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/forge-core/src/main/java/forge/deck/DeckRecognizer.java b/forge-core/src/main/java/forge/deck/DeckRecognizer.java index f62da14d13d..8ea96e9847a 100644 --- a/forge-core/src/main/java/forge/deck/DeckRecognizer.java +++ b/forge-core/src/main/java/forge/deck/DeckRecognizer.java @@ -22,6 +22,7 @@ import forge.StaticData; import forge.card.CardDb; import forge.card.CardEdition; import forge.card.CardType; +import forge.card.ColorSet; import forge.card.MagicColor; import forge.item.IPaperCard; import forge.item.PaperCard; @@ -997,26 +998,13 @@ public class DeckRecognizer { return String.format("%s %s", magicColor.getTranslatedName(), magicColor.getSymbol()); } - private static final HashMap manaSymbolsMap = new HashMap() {{ - put(MagicColor.WHITE | MagicColor.BLUE, "WU"); - put(MagicColor.BLUE | MagicColor.BLACK, "UB"); - put(MagicColor.BLACK | MagicColor.RED, "BR"); - put(MagicColor.RED | MagicColor.GREEN, "RG"); - put(MagicColor.GREEN | MagicColor.WHITE, "GW"); - put(MagicColor.WHITE | MagicColor.BLACK, "WB"); - put(MagicColor.BLUE | MagicColor.RED, "UR"); - put(MagicColor.BLACK | MagicColor.GREEN, "BG"); - put(MagicColor.RED | MagicColor.WHITE, "RW"); - put(MagicColor.GREEN | MagicColor.BLUE, "GU"); - }}; - private static String getMagicColourLabel(MagicColor.Color magicColor1, MagicColor.Color magicColor2){ + private static String getMagicColourLabel(MagicColor.Color magicColor1, MagicColor.Color magicColor2) { if (magicColor2 == null || magicColor2 == MagicColor.Color.COLORLESS || magicColor1 == MagicColor.Color.COLORLESS) return String.format("%s // %s", getMagicColourLabel(magicColor1), getMagicColourLabel(magicColor2)); String localisedName1 = magicColor1.getTranslatedName(); String localisedName2 = magicColor2.getTranslatedName(); - String comboManaSymbol = manaSymbolsMap.get(magicColor1.getColorMask() | magicColor2.getColorMask()); - return String.format("%s/%s {%s}", localisedName1, localisedName2, comboManaSymbol); + return String.format("%s/%s {%s}", localisedName1, localisedName2, ColorSet.fromEnums(magicColor1, magicColor2)); } private static MagicColor.Color getMagicColor(String colorName){ From c454253d48ffc1e0dc9fef2fb739119060d3171c Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Fri, 10 Oct 2025 12:45:40 +0200 Subject: [PATCH 127/230] ManaCostShard: build imageKey Remove "/" dynamic for `imageKey` --- .../java/forge/card/mana/ManaCostShard.java | 91 +++++++++---------- 1 file changed, 41 insertions(+), 50 deletions(-) diff --git a/forge-core/src/main/java/forge/card/mana/ManaCostShard.java b/forge-core/src/main/java/forge/card/mana/ManaCostShard.java index 667ed3ba19d..41b9b667256 100644 --- a/forge-core/src/main/java/forge/card/mana/ManaCostShard.java +++ b/forge-core/src/main/java/forge/card/mana/ManaCostShard.java @@ -17,6 +17,7 @@ */ package forge.card.mana; +import forge.card.ColorSet; import forge.util.BinaryUtil; /** @@ -34,51 +35,51 @@ public enum ManaCostShard { COLORLESS(ManaAtom.COLORLESS, "C"), /* Hybrid */ - WU(ManaAtom.WHITE | ManaAtom.BLUE, "W/U", "WU"), - WB(ManaAtom.WHITE | ManaAtom.BLACK, "W/B", "WB"), - UB(ManaAtom.BLUE | ManaAtom.BLACK, "U/B", "UB"), - UR(ManaAtom.BLUE | ManaAtom.RED, "U/R", "UR"), - BR(ManaAtom.BLACK | ManaAtom.RED, "B/R", "BR"), - BG(ManaAtom.BLACK | ManaAtom.GREEN, "B/G", "BG"), - RW(ManaAtom.RED | ManaAtom.WHITE, "R/W", "RW"), - RG(ManaAtom.RED | ManaAtom.GREEN, "R/G", "RG"), - GW(ManaAtom.GREEN | ManaAtom.WHITE, "G/W", "GW"), - GU(ManaAtom.GREEN | ManaAtom.BLUE, "G/U", "GU"), + WU(ManaAtom.WHITE | ManaAtom.BLUE, "W/U"), + WB(ManaAtom.WHITE | ManaAtom.BLACK, "W/B"), + UB(ManaAtom.BLUE | ManaAtom.BLACK, "U/B"), + UR(ManaAtom.BLUE | ManaAtom.RED, "U/R"), + BR(ManaAtom.BLACK | ManaAtom.RED, "B/R"), + BG(ManaAtom.BLACK | ManaAtom.GREEN, "B/G"), + RW(ManaAtom.RED | ManaAtom.WHITE, "R/W"), + RG(ManaAtom.RED | ManaAtom.GREEN, "R/G"), + GW(ManaAtom.GREEN | ManaAtom.WHITE, "G/W"), + GU(ManaAtom.GREEN | ManaAtom.BLUE, "G/U"), /* Or 2 generic */ - W2(ManaAtom.WHITE | ManaAtom.OR_2_GENERIC, "2/W", "2W"), - U2(ManaAtom.BLUE | ManaAtom.OR_2_GENERIC, "2/U", "2U"), - B2(ManaAtom.BLACK | ManaAtom.OR_2_GENERIC, "2/B", "2B"), - R2(ManaAtom.RED | ManaAtom.OR_2_GENERIC, "2/R", "2R"), - G2(ManaAtom.GREEN | ManaAtom.OR_2_GENERIC, "2/G", "2G"), + W2(ManaAtom.WHITE | ManaAtom.OR_2_GENERIC, "2/W"), + U2(ManaAtom.BLUE | ManaAtom.OR_2_GENERIC, "2/U"), + B2(ManaAtom.BLACK | ManaAtom.OR_2_GENERIC, "2/B"), + R2(ManaAtom.RED | ManaAtom.OR_2_GENERIC, "2/R"), + G2(ManaAtom.GREEN | ManaAtom.OR_2_GENERIC, "2/G"), /* Or Colorless */ - CW(ManaAtom.WHITE | ManaAtom.COLORLESS, "C/W", "CW"), - CU(ManaAtom.BLUE | ManaAtom.COLORLESS, "C/U", "CU"), - CB(ManaAtom.BLACK | ManaAtom.COLORLESS, "C/B", "CB"), - CR(ManaAtom.RED | ManaAtom.COLORLESS, "C/R", "CR"), - CG(ManaAtom.GREEN | ManaAtom.COLORLESS, "C/G", "CG"), + CW(ManaAtom.WHITE | ManaAtom.COLORLESS, "C/W"), + CU(ManaAtom.BLUE | ManaAtom.COLORLESS, "C/U"), + CB(ManaAtom.BLACK | ManaAtom.COLORLESS, "C/B"), + CR(ManaAtom.RED | ManaAtom.COLORLESS, "C/R"), + CG(ManaAtom.GREEN | ManaAtom.COLORLESS, "C/G"), // Snow and colorless S(ManaAtom.IS_SNOW, "S"), GENERIC(ManaAtom.GENERIC, "1"), /* Phyrexian */ - WP(ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "W/P", "WP"), - UP(ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "U/P", "UP"), - BP(ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "B/P", "BP"), - RP(ManaAtom.RED | ManaAtom.OR_2_LIFE, "R/P", "RP"), - GP(ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "G/P", "GP"), - BGP(ManaAtom.BLACK | ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "B/G/P", "BGP"), - BRP(ManaAtom.BLACK | ManaAtom.RED | ManaAtom.OR_2_LIFE, "B/R/P", "BRP"), - GUP(ManaAtom.GREEN | ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "G/U/P", "GUP"), - GWP(ManaAtom.GREEN | ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "G/W/P", "GWP"), - RGP(ManaAtom.RED | ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "R/G/P", "RGP"), - RWP(ManaAtom.RED | ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "R/W/P", "RWP"), - UBP(ManaAtom.BLUE | ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "U/B/P", "UBP"), - URP(ManaAtom.BLUE | ManaAtom.RED | ManaAtom.OR_2_LIFE, "U/R/P", "URP"), - WBP(ManaAtom.WHITE | ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "W/B/P", "WBP"), - WUP(ManaAtom.WHITE | ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "W/U/P", "WUP"), + WP(ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "W/P"), + UP(ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "U/P"), + BP(ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "B/P"), + RP(ManaAtom.RED | ManaAtom.OR_2_LIFE, "R/P"), + GP(ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "G/P"), + BGP(ManaAtom.BLACK | ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "B/G/P"), + BRP(ManaAtom.BLACK | ManaAtom.RED | ManaAtom.OR_2_LIFE, "B/R/P"), + GUP(ManaAtom.GREEN | ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "G/U/P"), + GWP(ManaAtom.GREEN | ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "G/W/P"), + RGP(ManaAtom.RED | ManaAtom.GREEN | ManaAtom.OR_2_LIFE, "R/G/P"), + RWP(ManaAtom.RED | ManaAtom.WHITE | ManaAtom.OR_2_LIFE, "R/W/P"), + UBP(ManaAtom.BLUE | ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "U/B/P"), + URP(ManaAtom.BLUE | ManaAtom.RED | ManaAtom.OR_2_LIFE, "U/R/P"), + WBP(ManaAtom.WHITE | ManaAtom.BLACK | ManaAtom.OR_2_LIFE, "W/B/P"), + WUP(ManaAtom.WHITE | ManaAtom.BLUE | ManaAtom.OR_2_LIFE, "W/U/P"), X(ManaAtom.IS_X, "X"), @@ -107,26 +108,12 @@ public enum ManaCostShard { * the s value */ ManaCostShard(final int value, final String sValue) { - this(value, sValue, sValue); - } - - /** - * Instantiates a new card mana cost shard. - * - * @param value - * the value - * @param sValue - * the s value - * @param imgKey - * the img key - */ - ManaCostShard(final int value, final String sValue, final String imgKey) { this.shard = value; this.cmc = this.getCMC(); this.cmpc = this.getCmpCost(); this.stringValue = "{" + sValue + "}"; this.shortStringValue = sValue; - this.imageKey = imgKey; + this.imageKey = sValue.replace("/", ""); } public static final int COLORS_SUPERPOSITION = ManaAtom.WHITE | ManaAtom.BLUE | ManaAtom.BLACK | ManaAtom.RED | ManaAtom.GREEN; @@ -185,6 +172,10 @@ public enum ManaCostShard { return (byte)(this.shard & COLORS_SUPERPOSITION); } + public final ColorSet getColor() { + return ColorSet.fromMask(getColorMask()); + } + /** * Value of. * From 6ddb3124ac1f003d779be5ea5dede3bdb3b089a6 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Mon, 13 Oct 2025 19:49:05 +0200 Subject: [PATCH 128/230] Fix Pump / Draw AI half broken (#8918) --- forge-ai/src/main/java/forge/ai/ability/DrawAi.java | 11 +++++------ forge-ai/src/main/java/forge/ai/ability/PumpAi.java | 6 +++--- .../src/main/java/forge/ai/ability/PumpAllAi.java | 2 +- forge-gui/res/cardsfolder/n/not_dead_after_all.txt | 2 +- .../res/cardsfolder/s/sin_unending_cataclysm.txt | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index df4be1cd9a4..0a9cc6285b7 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -54,7 +54,7 @@ public class DrawAi extends SpellAbilityAi { } if (ComputerUtil.playImmediately(ai, sa)) { - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); + return new AiAbilityDecision(100, AiPlayDecision.WillPlay); } // Don't tap creatures that may be able to block @@ -73,9 +73,10 @@ public class DrawAi extends SpellAbilityAi { // TODO: make this configurable in the AI profile return new AiAbilityDecision(100, AiPlayDecision.WillPlay); } + return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable); } - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); + return new AiAbilityDecision(100, AiPlayDecision.WillPlay); } /* @@ -173,9 +174,8 @@ public class DrawAi extends SpellAbilityAi { public AiAbilityDecision chkDrawback(SpellAbility sa, Player ai) { if (targetAI(ai, sa, sa.isTrigger() && sa.getHostCard().isInPlay())) { return new AiAbilityDecision(100, AiPlayDecision.WillPlay); - } else { - return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed); } + return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed); } /** @@ -540,9 +540,8 @@ public class DrawAi extends SpellAbilityAi { if (targetAI(ai, sa, mandatory)) { return new AiAbilityDecision(100, AiPlayDecision.WillPlay); - } else { - return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed); } + return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed); } /* (non-Javadoc) 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 bc7c4f8431e..948337ed207 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -339,11 +339,11 @@ public class PumpAi extends PumpAiBase { return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } - if (!pumpTgtAI(ai, sa, defense, attack, false, false)) { - return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed); + if (pumpTgtAI(ai, sa, defense, attack, false, false)) { + return new AiAbilityDecision(100, AiPlayDecision.WillPlay); } - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); + return new AiAbilityDecision(0, AiPlayDecision.TargetingFailed); } private boolean pumpTgtAI(final Player ai, final SpellAbility sa, final int defense, final int attack, final boolean mandatory, diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java index 83f74410723..c082ea153d6 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAllAi.java @@ -131,7 +131,7 @@ public class PumpAllAi extends PumpAiBase { boolean result = ai.getCreaturesInPlay().anyMatch(c -> c.isValid(valid, source.getController(), source, sa) && ComputerUtilCard.shouldPumpCard(ai, sa, c, defense, power, keywords)); return result ? new AiAbilityDecision(100, AiPlayDecision.WillPlay) : new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); - } // pumpAllCanPlayAI() + } @Override public AiAbilityDecision chkDrawback(SpellAbility sa, Player aiPlayer) { diff --git a/forge-gui/res/cardsfolder/n/not_dead_after_all.txt b/forge-gui/res/cardsfolder/n/not_dead_after_all.txt index b7e77609139..5107a31ce99 100644 --- a/forge-gui/res/cardsfolder/n/not_dead_after_all.txt +++ b/forge-gui/res/cardsfolder/n/not_dead_after_all.txt @@ -3,7 +3,7 @@ ManaCost:B Types:Instant A:SP$ Animate | Triggers$ TrigChangeZone | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | SpellDescription$ Until end of turn, target creature you control gains "When this creature dies, return it to the battlefield tapped under its owner's control, then create a Wicked Role token attached to it." (Enchanted creature gets +1/+1. When this Aura is put into a graveyard, each opponent loses 1 life.) SVar:TrigChangeZone:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone2 | TriggerController$ TriggeredCardController | TriggerDescription$ When this creature dies, return it to the battlefield tapped under its owner's control, then create a Wicked Role token attached to it. (Enchanted creature gets +1/+1. When this Aura is put into a graveyard, each opponent loses 1 life.) -SVar:TrigChangeZone2:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ TriggeredNewCardLKICopy | SubAbility$ DBToken +SVar:TrigChangeZone2:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ TriggeredNewCardLKICopy | Tapped$ True | SubAbility$ DBToken SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ role_wicked | TokenOwner$ You | AttachedTo$ TriggeredNewCardLKICopy DeckHas:Ability$Token & Type$Role|Aura Oracle:Until end of turn, target creature you control gains "When this creature dies, return it to the battlefield tapped under its owner's control, then create a Wicked Role token attached to it." (Enchanted creature gets +1/+1. When this Aura is put into a graveyard, each opponent loses 1 life.) diff --git a/forge-gui/res/cardsfolder/s/sin_unending_cataclysm.txt b/forge-gui/res/cardsfolder/s/sin_unending_cataclysm.txt index 346ebd68e10..5d6db9b352a 100644 --- a/forge-gui/res/cardsfolder/s/sin_unending_cataclysm.txt +++ b/forge-gui/res/cardsfolder/s/sin_unending_cataclysm.txt @@ -10,7 +10,7 @@ SVar:DBPutCounters:DB$ PutCounter | ETB$ True | Defined$ Self | CounterType$ P1P SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ When NICKNAME dies, put its counters on target creature you control, then shuffle this card into its owner's library. SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ EachFromSource | EachFromSource$ TriggeredCardLKICopy | SubAbility$ DBChangeZone -SVar:DBChangeZone:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Library | LibraryPosition$ 0 | Shuffle$ True +SVar:DBChangeZone:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Library | LibraryPosition$ 0 | Shuffle$ True SVar:X:Count$RememberedNumber/Times.2 DeckHas:Ability$Counters Oracle:Flying, trample\nAs Sin enters, remove all counters from any number of artifacts, creatures, and enchantments. Sin enters with X +1/+1 counters on it, where X is twice the number of counters removed this way.\nWhen Sin dies, put its counters on target creature you control, then shuffle this card into its owner's library. From 7fb7c62fbe0823988004e2c5187cea04f28a5350 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Mon, 13 Oct 2025 19:50:20 +0200 Subject: [PATCH 129/230] 15 TMT/TMC cards (#8895) --- forge-core/src/main/java/forge/card/CardRules.java | 5 ++++- .../src/main/java/forge/game/keyword/Keyword.java | 3 ++- .../cardsfolder/upcoming/april_oneil_hacktivist.txt | 8 ++++++++ .../cardsfolder/upcoming/atreus_impulsive_son.txt | 2 +- .../res/cardsfolder/upcoming/bebop_rocksteady.txt | 8 ++++++++ .../upcoming/casey_jones_jury_rig_justiciar.txt | 8 ++++++++ .../cardsfolder/upcoming/donatello_rad_scientist.txt | 9 +++++++++ .../cardsfolder/upcoming/donatello_the_brains.txt | 9 +++++++++ .../upcoming/donnie_april_adorkable_duo.txt | 9 +++++++++ .../res/cardsfolder/upcoming/ellie_brick_master.txt | 2 +- .../cardsfolder/upcoming/ellie_vengeful_hunter.txt | 2 +- .../cardsfolder/upcoming/heroes_in_a_half_shell.txt | 12 ++++++++++++ .../cardsfolder/upcoming/joel_resolute_survivor.txt | 2 +- .../res/cardsfolder/upcoming/krang_master_mind.txt | 12 ++++++++++++ .../res/cardsfolder/upcoming/kratos_stoic_father.txt | 2 +- .../cardsfolder/upcoming/leonardo_the_balance.txt | 10 ++++++++++ .../upcoming/leonardo_worldly_warrior.txt | 8 ++++++++ .../upcoming/michelangelo_on_the_scene.txt | 11 +++++++++++ .../cardsfolder/upcoming/michelangelo_the_heart.txt | 11 +++++++++++ .../res/cardsfolder/upcoming/raphael_the_muscle.txt | 11 +++++++++++ .../res/cardsfolder/upcoming/splinter_the_mentor.txt | 10 ++++++++++ .../res/cardsfolder/upcoming/super_shredder.txt | 9 +++++++++ forge-gui/res/lists/TypeLists.txt | 2 ++ forge-gui/res/tokenscripts/c_a_mutagen_sac.txt | 5 +++++ 24 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/april_oneil_hacktivist.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/bebop_rocksteady.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/casey_jones_jury_rig_justiciar.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/donatello_rad_scientist.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/donatello_the_brains.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/donnie_april_adorkable_duo.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/krang_master_mind.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/leonardo_the_balance.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/leonardo_worldly_warrior.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/michelangelo_on_the_scene.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/michelangelo_the_heart.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/raphael_the_muscle.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/splinter_the_mentor.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/super_shredder.txt create mode 100644 forge-gui/res/tokenscripts/c_a_mutagen_sac.txt diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index 450f727c3e4..3d3ffe0648c 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -331,6 +331,9 @@ public final class CardRules implements ICardCharacteristics { if (hasKeyword("Partner - Father & Son") && b.hasKeyword("Partner - Father & Son")) { legal = true; // God of War Secret Lair gimmick partner commander } + if (hasKeyword("Partner - Character select") && b.hasKeyword("Partner - Character select")) { + legal = true; // TMNT Commander deck gimmick partner commander + } if (hasKeyword("Choose a Background") && b.canBeBackground() || b.hasKeyword("Choose a Background") && canBeBackground()) { legal = true; // commander with background @@ -348,7 +351,7 @@ public final class CardRules implements ICardCharacteristics { } return canBeCommander() && (hasKeyword("Partner") || !this.partnerWith.isEmpty() || hasKeyword("Friends forever") || hasKeyword("Choose a Background") || - hasKeyword("Partner - Father & Son") || hasKeyword("Partner - Survivors") || + hasKeyword("Partner - Father & Son") || hasKeyword("Partner - Survivors") || hasKeyword("Partner - Character select") || hasKeyword("Doctor's companion") || isDoctor()); } diff --git a/forge-game/src/main/java/forge/game/keyword/Keyword.java b/forge-game/src/main/java/forge/game/keyword/Keyword.java index 809b824c07f..65b74ab7276 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -141,8 +141,9 @@ public enum Keyword { OFFSPRING("Offspring", KeywordWithCost.class, false, "You may pay an additional %s as you cast this spell. If you do, when this creature enters, create a 1/1 token copy of it."), OVERLOAD("Overload", KeywordWithCost.class, false, "You may cast this spell for its overload cost. If you do, change its text by replacing all instances of \"target\" with \"each.\""), PARTNER("Partner", Partner.class, true, "You can have two commanders if both have partner."), - PARTNER_SURVIVOR("Partner - Survivors", Partner.class, true, "You can have two commanders if both have this ability."), + PARTNER_SURVIVORS("Partner - Survivors", Partner.class, true, "You can have two commanders if both have this ability."), PARTNER_FATHER_AND_SON("Partner - Father & Son", Partner.class, true, "You can have two commanders if both have this ability."), + PARTNER_CHARACTER_SELECT("Partner - Character select", Partner.class, true, "You can have two commanders if both have this ability."), PERSIST("Persist", SimpleKeyword.class, false, "When this creature dies, if it had no -1/-1 counters on it, return it to the battlefield under its owner's control with a -1/-1 counter on it."), PHASING("Phasing", SimpleKeyword.class, true, "This phases in or out before you untap during each of your untap steps. While it's phased out, it's treated as though it doesn't exist."), PLOT("Plot", KeywordWithCost.class, false, "You may pay %s and exile this card from your hand. Cast it as a sorcery on a later turn without paying its mana cost. Plot only as a sorcery."), diff --git a/forge-gui/res/cardsfolder/upcoming/april_oneil_hacktivist.txt b/forge-gui/res/cardsfolder/upcoming/april_oneil_hacktivist.txt new file mode 100644 index 00000000000..35c0bcdb435 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/april_oneil_hacktivist.txt @@ -0,0 +1,8 @@ +Name:April O'Neil, Hacktivist +ManaCost:3 U +Types:Legendary Creature Human Scientist +PT:1/5 +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ At the beginning of your end step, draw a card for each card type among spells you've cast this turn. +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ X +SVar:X:Count$ThisTurnCast_Card.YouCtrl$CardTypes +Oracle:At the beginning of your end step, draw a card for each card type among spells you've cast this turn. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/atreus_impulsive_son.txt b/forge-gui/res/cardsfolder/upcoming/atreus_impulsive_son.txt index c6a78a975db..dd52b26a846 100644 --- a/forge-gui/res/cardsfolder/upcoming/atreus_impulsive_son.txt +++ b/forge-gui/res/cardsfolder/upcoming/atreus_impulsive_son.txt @@ -8,4 +8,4 @@ A:AB$ Draw | Cost$ 3 T | NumCards$ X | SubAbility$ DBDiscard | SpellDescription$ SVar:DBDiscard:DB$ Discard | Mode$ YouChoose | SubAbility$ DBDamage SVar:DBDamage:DB$ DealDamage | NumDmg$ 2 | Defined$ Opponent | SpellDescription$ CARDNAME deals 2 damage to each opponent. SVar:X:Count$YourCountersExperience -Oracle:Reach\n{3}, {T}: Draw a card for each experience counter you have, then discard a card. Atreus, Impulsive Son deals 2 damage to each opponent.\nPartner-Father & son +Oracle:Reach\n{3}, {T}: Draw a card for each experience counter you have, then discard a card. Atreus, Impulsive Son deals 2 damage to each opponent.\nPartner—Father & son diff --git a/forge-gui/res/cardsfolder/upcoming/bebop_rocksteady.txt b/forge-gui/res/cardsfolder/upcoming/bebop_rocksteady.txt new file mode 100644 index 00000000000..6ccc2948003 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bebop_rocksteady.txt @@ -0,0 +1,8 @@ +Name:Bebop & Rocksteady +ManaCost:1 BG BG +Types:Legendary Creature Boar Rhino Mutant +PT:7/5 +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigSac | TriggerDescription$ Whenever CARDNAME attack or block, sacrifice a permanent unless you discard a card. +T:Mode$ Blocks | ValidCard$ Card.Self | Execute$ TrigSac | Secondary$ True | TriggerDescription$ Whenever CARDNAME attack or block, sacrifice a permanent unless you discard a card. +SVar:TrigSac:DB$ Sacrifice | SacValid$ Permanent | UnlessCost$ Discard<1/Card> | UnlessPayer$ You +Oracle:Whenever Bebop & Rocksteady attack or block, sacrifice a permanent unless you discard a card. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/casey_jones_jury_rig_justiciar.txt b/forge-gui/res/cardsfolder/upcoming/casey_jones_jury_rig_justiciar.txt new file mode 100644 index 00000000000..32ce76b9a98 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/casey_jones_jury_rig_justiciar.txt @@ -0,0 +1,8 @@ +Name:Casey Jones, Jury-Rig Justiciar +ManaCost:1 R +Types:Legendary Creature Human Berserker +PT:2/1 +K:Haste +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDig | TriggerDescription$ When NICKNAME enters, look at the top four cards of your library. You may reveal an artifact card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. +SVar:TrigDig:DB$ Dig | DigNum$ 4 | ChangeNum$ 1 | Optional$ True | ForceRevealToController$ True | ChangeValid$ Artifact | RestRandomOrder$ True +Oracle:Haste\nWhen Casey Jones enters, look at the top four cards of your library. You may reveal an artifact card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/cardsfolder/upcoming/donatello_rad_scientist.txt b/forge-gui/res/cardsfolder/upcoming/donatello_rad_scientist.txt new file mode 100644 index 00000000000..844b82add2f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/donatello_rad_scientist.txt @@ -0,0 +1,9 @@ +Name:Donatello, Rad Scientist +ManaCost:5 U +Types:Legendary Creature Mutant Ninja Turtle +PT:5/6 +K:Vigilance +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTap | TriggerDescription$ When NICKNAME enters, tap up to three target creatures your opponents control. Put a stun counter on each of them. (If a permanent with a stun counter would become untapped, remove one from it instead.) +SVar:TrigTap:DB$ Tap | ValidTgts$ Creature.OppCtrl | TargetMin$ 0 | TargetMax$ 3 | TgtPrompt$ Select up to three target creatures your opponents control | SubAbility$ DBCounter +SVar:DBCounter:DB$ PutCounter | Defined$ Targeted | CounterType$ Stun | CounterNum$ 1 +Oracle:Vigilance (Attacking doesn't cause this creature to tap.)\nWhen Donatello enters, tap up to three target creatures your opponents control. Put a stun counter on each of them. (If a permanent with a stun counter would become untapped, remove one from it instead.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/donatello_the_brains.txt b/forge-gui/res/cardsfolder/upcoming/donatello_the_brains.txt new file mode 100644 index 00000000000..28a6515d74e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/donatello_the_brains.txt @@ -0,0 +1,9 @@ +Name:Donatello, the Brains +ManaCost:2 U +Types:Legendary Creature Mutant Ninja Turtle +PT:2/4 +R:Event$ CreateToken | ActiveZones$ Battlefield | ValidToken$ Card.YouCtrl | ReplaceWith$ DBReplace | Description$ If one or more tokens would be created under your control, those tokens plus an additional Mutagen token are created instead. +SVar:DBReplace:DB$ ReplaceToken | Type$ AddToken | Amount$ 1 | TokenScript$ c_a_mutagen_sac +K:Partner - Character select +DeckHas:Ability$Sacrifice|Token & Type$Mutagen +Oracle:If one or more tokens would be created under your control, those tokens plus an additional Mutagen token are created instead.\nPartner—Character select (You can have two commanders if both have this ability.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/donnie_april_adorkable_duo.txt b/forge-gui/res/cardsfolder/upcoming/donnie_april_adorkable_duo.txt new file mode 100644 index 00000000000..8ecd5c994fe --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/donnie_april_adorkable_duo.txt @@ -0,0 +1,9 @@ +Name:Donnie & April, Adorkable Duo +ManaCost:4 U +Types:Legendary Creature Mutant Ninja Human Turtle +PT:3/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When NICKNAME enters, ABILITY +SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBDraw,DBChangeZone | AdditionalDescription$ or both. Each mode must target a different player. +SVar:DBDraw:DB$ Draw | NumCards$ 2 | ValidTgts$ Player | TargetUnique$ True | SpellDescription$ Target player draws two cards. +SVar:DBChangeZone:DB$ ChangeZone | ValidTgts$ Player | TargetUnique$ True | Hidden$ True | Mandatory$ True | ChangeType$ Artifact.TargetedPlayerOwn,Instant.TargetedPlayerOwn,Sorcery.TargetedPlayerOwn | ChangeTypeDesc$ artifact, instant or sorcery | ChangeNum$ 1 | Origin$ Graveyard | Destination$ Hand | SpellDescription$ Target player returns an artifact, instant, or sorcery card from their graveyard to their hand. +Oracle:When Donnie & April enter, choose one or both. Each mode must target a different player.\n• Target player draws two cards.\n• Target player returns an artifact, instant, or sorcery card from their graveyard to their hand. diff --git a/forge-gui/res/cardsfolder/upcoming/ellie_brick_master.txt b/forge-gui/res/cardsfolder/upcoming/ellie_brick_master.txt index 2823c85f96f..dbf96050654 100644 --- a/forge-gui/res/cardsfolder/upcoming/ellie_brick_master.txt +++ b/forge-gui/res/cardsfolder/upcoming/ellie_brick_master.txt @@ -6,4 +6,4 @@ K:Partner - Survivors T:Mode$ AttackersDeclaredOneTarget | AttackedTarget$ Opponent | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Distract the Horde - Whenever a player attacks one of your opponents, that attacking player creates a tapped 1/1 black Fungus Zombie creature token named Cordyceps Infected that's attacking that opponent. SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ cordyceps_infected | TokenOwner$ TriggeredAttackingPlayer | TokenTapped$ True | TokenAttacking$ TriggeredAttackedTarget DeckHas:Ability$Token & Type$Zombie|Fungus -Oracle:Distract the Horde - Whenever a player attacks one of your opponents, that attacking player creates a tapped 1/1 black Fungus Zombie creature token named Cordyceps Infected that's attacking that opponent.\nPartner — Survivors (You can have two commanders if both have this ability.) +Oracle:Distract the Horde - Whenever a player attacks one of your opponents, that attacking player creates a tapped 1/1 black Fungus Zombie creature token named Cordyceps Infected that's attacking that opponent.\nPartner—Survivors (You can have two commanders if both have this ability.) diff --git a/forge-gui/res/cardsfolder/upcoming/ellie_vengeful_hunter.txt b/forge-gui/res/cardsfolder/upcoming/ellie_vengeful_hunter.txt index 30b5001b11c..b3a9548c401 100644 --- a/forge-gui/res/cardsfolder/upcoming/ellie_vengeful_hunter.txt +++ b/forge-gui/res/cardsfolder/upcoming/ellie_vengeful_hunter.txt @@ -6,4 +6,4 @@ K:Partner - Survivors A:AB$ DealDamage | Cost$ PayLife<2> Sac<1/Creature.Other/another creature> | NumDmg$ 2 | ValidTgts$ Player | SubAbility$ DBPump | SpellDescription$ NICKNAME deals 2 damage to target player and gains indestructible until end of turn. SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Indestructible DeckHas:Ability$Sacrifice -Oracle:Pay 2 life, Sacrifice another creature: Ellie deals 2 damage to target player and gains indestructible until end of turn.\nPartner - Survivors (You can have two commanders if both have this ability.) +Oracle:Pay 2 life, Sacrifice another creature: Ellie deals 2 damage to target player and gains indestructible until end of turn.\nPartner—Survivors (You can have two commanders if both have this ability.) diff --git a/forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt b/forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt new file mode 100644 index 00000000000..dd8b61bddb8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt @@ -0,0 +1,12 @@ +Name:Heroes in a Half Shell +ManaCost:W U B R G +Types:Legendary Creature Mutant Ninja Turtle +PT:5/5 +K:Vigilance +K:Menace +K:Trample +K:Haste +T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Mutant.YouCtrl,Ninja.YouCtrl,Turtle.YouCtrl | ValidTarget$ Player | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever one or more Mutants, Ninjas, and/or Turtles you control deal combat damage to a player, put a +1/+1 counter on each of those creatures and draw a card. +SVar:DBPutCounter:DB$ PutCounter | Defined$ TriggeredSources | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw +Oracle:Vigilance, menace, trample, haste\nWhenever one or more Mutants, Ninjas, and/or Turtles you control deal combat damage to a player, put a +1/+1 counter on each of those creatures and draw a card. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt b/forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt index 698539d3352..ae7278635c5 100644 --- a/forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt +++ b/forge-gui/res/cardsfolder/upcoming/joel_resolute_survivor.txt @@ -9,4 +9,4 @@ SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterNum$ 1 | CounterType SVar:DBDraw:DB$ Draw DeckHas:Ability$Counters DeckHints:Ability$Sacrifice|Token -Oracle:Menace\nWhenever a creature token dies, put a +1/+1 counter on Joel and draw a card. This ability triggers only once each turn.\nPartner — Survivors (You can have two commanders if both have this ability.) +Oracle:Menace\nWhenever a creature token dies, put a +1/+1 counter on Joel and draw a card. This ability triggers only once each turn.\nPartner—Survivors (You can have two commanders if both have this ability.) diff --git a/forge-gui/res/cardsfolder/upcoming/krang_master_mind.txt b/forge-gui/res/cardsfolder/upcoming/krang_master_mind.txt new file mode 100644 index 00000000000..61ec7ef3607 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/krang_master_mind.txt @@ -0,0 +1,12 @@ +Name:Krang, Master Mind +ManaCost:6 U U +Types:Legendary Artifact Creature Utrom Warrior +PT:1/4 +K:Affinity:Artifact +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | CheckSVar$ X | SVarCompare$ LT4 | Execute$ TrigDraw | TriggerDescription$ When NICKNAME enters, if you have fewer than four cards in hand, draw cards equal to the difference. +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ Y +S:Mode$ Continuous | Affected$ Card.Self | AddPower$ Z | Description$ NICKNAME gets +1/+0 for each other artifact you control. +SVar:X:Count$ValidHand Card.YouOwn +SVar:Y:Number$4/Minus.X +SVar:Z:Count$Valid Artifact.Other+YouCtrl +Oracle:Affinity for artifacts (This spell costs {1} less to cast for each artifact you control.)\nWhen Krang enters, if you have fewer than four cards in hand, draw cards equal to the difference.\nKrang gets +1/+0 for each other artifact you control. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/kratos_stoic_father.txt b/forge-gui/res/cardsfolder/upcoming/kratos_stoic_father.txt index c1cba612160..1bbaca1f92e 100644 --- a/forge-gui/res/cardsfolder/upcoming/kratos_stoic_father.txt +++ b/forge-gui/res/cardsfolder/upcoming/kratos_stoic_father.txt @@ -11,4 +11,4 @@ SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | CounterType$ P1P1 | C SVar:X:Count$YourCountersExperience DeckHas:Ability$Counters DeckHints:Type$God -Oracle:Whenever you attack with one or more Gods and whenever a God dies, you get an experience counter.\nAt the beginning of your end step, put a number of +1/+1 counters on target creature equal to the number of experience counters you have.\nPartner-Father & son (You can have two commanders if both have this ability.) +Oracle:Whenever you attack with one or more Gods and whenever a God dies, you get an experience counter.\nAt the beginning of your end step, put a number of +1/+1 counters on target creature equal to the number of experience counters you have.\nPartner—Father & son (You can have two commanders if both have this ability.) diff --git a/forge-gui/res/cardsfolder/upcoming/leonardo_the_balance.txt b/forge-gui/res/cardsfolder/upcoming/leonardo_the_balance.txt new file mode 100644 index 00000000000..0b73afc0598 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/leonardo_the_balance.txt @@ -0,0 +1,10 @@ +Name:Leonardo, the Balance +ManaCost:3 W +Types:Legendary Creature Mutant Ninja Turtle +PT:3/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.token+YouCtrl | TriggerZones$ Battlefield | Execute$ DBPutCounterAll | OptionalDecider$ You | ResolvedLimit$ 1 | TriggerDescription$ Whenever a token you control enters, you may put a +1/+1 counter on each creature you control. Do this only once each turn. +SVar:DBPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 +A:AB$ PumpAll | Cost$ W U B R G | ValidCards$ Creature.YouCtrl | KW$ Menace & Trample & Lifelink | SpellDescription$ Creatures you control gain menace, trample, and lifelink until end of turn. +K:Partner - Character select +DeckHints:Ability$Token +Oracle:Whenever a token you control enters, you may put a +1/+1 counter on each creature you control. Do this only once each turn.\n{W}{U}{B}{R}{G}: Creatures you control gain menace, trample, and lifelink until end of turn.\nPartner—Character select (You can have two commanders if both have this ability.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/leonardo_worldly_warrior.txt b/forge-gui/res/cardsfolder/upcoming/leonardo_worldly_warrior.txt new file mode 100644 index 00000000000..371df3dc65a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/leonardo_worldly_warrior.txt @@ -0,0 +1,8 @@ +Name:Leonardo, Worldly Warrior +ManaCost:7 W +Types:Legendary Creature Mutant Ninja Turtle +PT:5/5 +S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {1} less to cast for each creature you control. +K:Double Strike +SVar:X:Count$Valid Creature.YouCtrl +Oracle:This spell costs {1} less to cast for each creature you control.\nDouble strike \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/michelangelo_on_the_scene.txt b/forge-gui/res/cardsfolder/upcoming/michelangelo_on_the_scene.txt new file mode 100644 index 00000000000..b8086d362ff --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/michelangelo_on_the_scene.txt @@ -0,0 +1,11 @@ +Name:Michelangelo, On the Scene +ManaCost:4 G G +Types:Legendary Creature Mutant Ninja Turtle +PT:2/2 +K:Trample +K:etbCounter:P1P1:X:no Condition:NICKNAME enters with a +1/+1 counter on him for each land you control. +SVar:X:Count$Valid Land.YouCtrl +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When NICKNAME dies, return this card to your hand. +SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Hand +DeckHas:Ability$Counters +Oracle:Trample (This creature can deal excess combat damage to the player it's attacking.)\nMichelangelo enters with a +1/+1 counter on him for each land you control.\nWhen Michelangelo dies, return this card to your hand. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/michelangelo_the_heart.txt b/forge-gui/res/cardsfolder/upcoming/michelangelo_the_heart.txt new file mode 100644 index 00000000000..d293cad68f2 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/michelangelo_the_heart.txt @@ -0,0 +1,11 @@ +Name:Michelangelo, the Heart +ManaCost:1 G +Types:Legendary Creature Mutant Ninja Turtle +PT:2/1 +K:Trample +T:Mode$ Phase | Phase$ Main | PhaseCount$ 2 | ValidPlayer$ You | CheckSVar$ RaidTest | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Raid (the Fridge) — At the beginning of your second main phase, if you attacked this turn, put a +1/+1 counter on target creature and create a Food token. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenScript$ c_a_food_sac +K:Partner - Character select +SVar:RaidTest:Count$AttackersDeclared +Oracle:Trample\nRaid (the Fridge) — At the beginning of your second main phase, if you attacked this turn, put a +1/+1 counter on target creature and create a Food token.\nPartner—Character select (You can have two commanders if both have this ability.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/raphael_the_muscle.txt b/forge-gui/res/cardsfolder/upcoming/raphael_the_muscle.txt new file mode 100644 index 00000000000..bb3d8971ac8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/raphael_the_muscle.txt @@ -0,0 +1,11 @@ +Name:Raphael, the Muscle +ManaCost:4 R +Types:Legendary Creature Mutant Ninja Turtle +PT:4/4 +R:Event$ DamageDone | ActiveZones$ Battlefield | ValidSource$ Creature.YouCtrl+HasCounters | ValidTarget$ Permanent,Player | ReplaceWith$ DmgTwice | Description$ Double all damage that creatures you control with counters on them would deal. +SVar:DmgTwice:DB$ ReplaceEffect | VarName$ DamageAmount | VarValue$ X +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When NICKNAME enters, create a Mutagen token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_mutagen_sac | TokenOwner$ You +K:Partner - Character select +SVar:X:ReplaceCount$DamageAmount/Twice +Oracle:Double all damage that creatures you control with counters on them would deal.\nWhen Raphael enters, create a Mutagen token.\nPartner—Character select (You can have two commanders if both have this ability.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/splinter_the_mentor.txt b/forge-gui/res/cardsfolder/upcoming/splinter_the_mentor.txt new file mode 100644 index 00000000000..7bba6154b97 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/splinter_the_mentor.txt @@ -0,0 +1,10 @@ +Name:Splinter, the Mentor +ManaCost:1 B +Types:Legendary Creature Mutant Ninja Rat +PT:2/2 +K:Menace +T:Mode$ ChangesZone | ValidCard$ Card.Self,Creature.Other+!token+YouCtrl | Origin$ Battlefield | Destination$ Any | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever NICKNAME or another nontoken creature you control leaves the battlefield, create a Mutagen token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_mutagen_sac | TokenOwner$ You +K:Partner - Character select +DeckHas:Ability$Token & Type$Artifact|Mutagen +Oracle:Menace\nWhenever Splinter or another nontoken creature you control leaves the battlefield, create a Mutagen token.\nPartner—Character select (You can have two commanders if both have this ability.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/super_shredder.txt b/forge-gui/res/cardsfolder/upcoming/super_shredder.txt new file mode 100644 index 00000000000..e13fd94ded6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/super_shredder.txt @@ -0,0 +1,9 @@ +Name:Super Shredder +ManaCost:1 B +Types:Legendary Creature Mutant Ninja Human +PT:1/1 +K:Menace +T:Mode$ ChangesZone | ValidCard$ Permanent.Other | Origin$ Battlefield | Destination$ Any | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever another permanent leaves the battlefield, put a +1/+1 counter on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +DeckHas:Ability$Counters +Oracle:Menace\nWhenever another permanent leaves the battlefield, put a +1/+1 counter on Super Shredder. \ No newline at end of file diff --git a/forge-gui/res/lists/TypeLists.txt b/forge-gui/res/lists/TypeLists.txt index 02d36c77629..d3a368b2758 100644 --- a/forge-gui/res/lists/TypeLists.txt +++ b/forge-gui/res/lists/TypeLists.txt @@ -316,6 +316,7 @@ Troll:Trolls Turtle:Turtles Tyranid:Tyranids Unicorn:Unicorns +Utrom:Utroms Vampire:Vampires Varmint:Varmints Vedalken:Vedalkens @@ -374,6 +375,7 @@ Junk Key:Keys Lander:Landers Map +Mutagen Powerstone:Powerstones Spacecraft:Spacecraft Stone:Stones diff --git a/forge-gui/res/tokenscripts/c_a_mutagen_sac.txt b/forge-gui/res/tokenscripts/c_a_mutagen_sac.txt new file mode 100644 index 00000000000..51db6b5b78c --- /dev/null +++ b/forge-gui/res/tokenscripts/c_a_mutagen_sac.txt @@ -0,0 +1,5 @@ +Name:Mutagen Token +ManaCost:no cost +Types:Artifact Mutagen +A:AB$ PutCounter | Cost$ 1 T Sac<1/CARDNAME/this token> | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SorcerySpeed$ True | SpellDescription$ Put a +1/+1 counter on target creature. Activate only as a sorcery. +Oracle:{1}, {T}, Sacrifice this token: Put a +1/+1 counter on target creature. Activate only as a sorcery. \ No newline at end of file From 808ecde0c045045676178c928f843b510c832db1 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Tue, 14 Oct 2025 08:02:17 +0200 Subject: [PATCH 130/230] Update the_death_of_gwen_stacy.txt --- forge-gui/res/cardsfolder/t/the_death_of_gwen_stacy.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/t/the_death_of_gwen_stacy.txt b/forge-gui/res/cardsfolder/t/the_death_of_gwen_stacy.txt index 1a0f5e8bd23..cb60a96de5c 100644 --- a/forge-gui/res/cardsfolder/t/the_death_of_gwen_stacy.txt +++ b/forge-gui/res/cardsfolder/t/the_death_of_gwen_stacy.txt @@ -10,7 +10,7 @@ SVar:DBDiscard:DB$ Discard | Defined$ Player.NotedForDiscard | Mode$ TgtChoose | SVar:DBLoseLife:DB$ LoseLife | Defined$ NonRememberedOwner | LifeAmount$ 3 | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBClearNotes SVar:DBClearNotes:DB$ Pump | Defined$ Player | ClearNotedCardsFor$ Discard -SVar:DBExile:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Player | TgtPrompt$ Select any number of target players | TargetMin$ 0 | TargetMax$ MaxTgt | ChangeType$ Card | SubAbility$ DBDraw | StackDescription$ Exile graveyards ({p:Targeted}). | SpellDescription$ Exile any number of target players' graveyards. +SVar:DBExile:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Exile | ValidTgts$ Player | TgtPrompt$ Select any number of target players | TargetMin$ 0 | TargetMax$ MaxTgt | ChangeType$ Card | StackDescription$ Exile graveyards ({p:Targeted}). | SpellDescription$ Exile any number of target players' graveyards. DeckHas:Ability$Discard SVar:MaxTgt:PlayerCountPlayers$Amount Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI — Destroy target creature.\nII — Each player may discard a card. Each player who doesn't loses 3 life.\nIII — Exile any number of target players' graveyards. From 05a81d00fde84b7c7eb769c8ec2fca7d2f681d31 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Tue, 14 Oct 2025 06:58:59 +0000 Subject: [PATCH 131/230] Update rally_maneuver.txt Closes #8924 --- forge-gui/res/cardsfolder/r/rally_maneuver.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/r/rally_maneuver.txt b/forge-gui/res/cardsfolder/r/rally_maneuver.txt index 236d5786b88..b045f9139d6 100644 --- a/forge-gui/res/cardsfolder/r/rally_maneuver.txt +++ b/forge-gui/res/cardsfolder/r/rally_maneuver.txt @@ -2,6 +2,6 @@ Name:Rally Maneuver ManaCost:2 W Types:Instant A:SP$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature to get +2/+0 and first strike | TargetUnique$ True | NumAtt$ +2 | KW$ First Strike | SubAbility$ DBPumpOther | SpellDescription$ Target creature gets +2/+0 and gains first strike until end of turn. Up to one other target creature gets +0/+2 and gains lifelink until end of turn. -SVar:DBPumpOther:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select another target creature to get +0/+2 and lifelink | TargetUnique$ True | NumDef$ +2 | KW$ Lifelink +SVar:DBPumpOther:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select another target creature to get +0/+2 and lifelink | TargetUnique$ True | TargetMin$ 0 | NumDef$ +2 | KW$ Lifelink DeckHas:Ability$LifeGain Oracle:Target creature gets +2/+0 and gains first strike until end of turn. Up to one other target creature gets +0/+2 and gains lifelink until end of turn. From 17d361d62d0bab3c75ab0c5c524dc9ec9eac06f5 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Tue, 14 Oct 2025 08:53:07 +0000 Subject: [PATCH 132/230] ~ lf --- .../upcoming/april_oneil_hacktivist.txt | 14 ++++++------ .../cardsfolder/upcoming/bebop_rocksteady.txt | 14 ++++++------ .../casey_jones_jury_rig_justiciar.txt | 16 +++++++------- .../upcoming/donatello_rad_scientist.txt | 16 +++++++------- .../upcoming/donatello_the_brains.txt | 16 +++++++------- .../upcoming/donnie_april_adorkable_duo.txt | 18 +++++++-------- .../upcoming/heroes_in_a_half_shell.txt | 22 +++++++++---------- .../upcoming/krang_master_mind.txt | 22 +++++++++---------- .../upcoming/leonardo_the_balance.txt | 18 +++++++-------- .../upcoming/leonardo_worldly_warrior.txt | 14 ++++++------ .../upcoming/michelangelo_on_the_scene.txt | 20 ++++++++--------- .../upcoming/michelangelo_the_heart.txt | 20 ++++++++--------- .../upcoming/raphael_the_muscle.txt | 20 ++++++++--------- .../upcoming/splinter_the_mentor.txt | 18 +++++++-------- .../cardsfolder/upcoming/super_shredder.txt | 16 +++++++------- 15 files changed, 132 insertions(+), 132 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/april_oneil_hacktivist.txt b/forge-gui/res/cardsfolder/upcoming/april_oneil_hacktivist.txt index 35c0bcdb435..9d1b0387845 100644 --- a/forge-gui/res/cardsfolder/upcoming/april_oneil_hacktivist.txt +++ b/forge-gui/res/cardsfolder/upcoming/april_oneil_hacktivist.txt @@ -1,8 +1,8 @@ -Name:April O'Neil, Hacktivist -ManaCost:3 U -Types:Legendary Creature Human Scientist -PT:1/5 -T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ At the beginning of your end step, draw a card for each card type among spells you've cast this turn. -SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ X -SVar:X:Count$ThisTurnCast_Card.YouCtrl$CardTypes +Name:April O'Neil, Hacktivist +ManaCost:3 U +Types:Legendary Creature Human Scientist +PT:1/5 +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ At the beginning of your end step, draw a card for each card type among spells you've cast this turn. +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ X +SVar:X:Count$ThisTurnCast_Card.YouCtrl$CardTypes Oracle:At the beginning of your end step, draw a card for each card type among spells you've cast this turn. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/bebop_rocksteady.txt b/forge-gui/res/cardsfolder/upcoming/bebop_rocksteady.txt index 6ccc2948003..4dea5aa9e8b 100644 --- a/forge-gui/res/cardsfolder/upcoming/bebop_rocksteady.txt +++ b/forge-gui/res/cardsfolder/upcoming/bebop_rocksteady.txt @@ -1,8 +1,8 @@ -Name:Bebop & Rocksteady -ManaCost:1 BG BG -Types:Legendary Creature Boar Rhino Mutant -PT:7/5 -T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigSac | TriggerDescription$ Whenever CARDNAME attack or block, sacrifice a permanent unless you discard a card. -T:Mode$ Blocks | ValidCard$ Card.Self | Execute$ TrigSac | Secondary$ True | TriggerDescription$ Whenever CARDNAME attack or block, sacrifice a permanent unless you discard a card. -SVar:TrigSac:DB$ Sacrifice | SacValid$ Permanent | UnlessCost$ Discard<1/Card> | UnlessPayer$ You +Name:Bebop & Rocksteady +ManaCost:1 BG BG +Types:Legendary Creature Boar Rhino Mutant +PT:7/5 +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigSac | TriggerDescription$ Whenever CARDNAME attack or block, sacrifice a permanent unless you discard a card. +T:Mode$ Blocks | ValidCard$ Card.Self | Execute$ TrigSac | Secondary$ True | TriggerDescription$ Whenever CARDNAME attack or block, sacrifice a permanent unless you discard a card. +SVar:TrigSac:DB$ Sacrifice | SacValid$ Permanent | UnlessCost$ Discard<1/Card> | UnlessPayer$ You Oracle:Whenever Bebop & Rocksteady attack or block, sacrifice a permanent unless you discard a card. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/casey_jones_jury_rig_justiciar.txt b/forge-gui/res/cardsfolder/upcoming/casey_jones_jury_rig_justiciar.txt index 32ce76b9a98..97946251d1b 100644 --- a/forge-gui/res/cardsfolder/upcoming/casey_jones_jury_rig_justiciar.txt +++ b/forge-gui/res/cardsfolder/upcoming/casey_jones_jury_rig_justiciar.txt @@ -1,8 +1,8 @@ -Name:Casey Jones, Jury-Rig Justiciar -ManaCost:1 R -Types:Legendary Creature Human Berserker -PT:2/1 -K:Haste -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDig | TriggerDescription$ When NICKNAME enters, look at the top four cards of your library. You may reveal an artifact card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. -SVar:TrigDig:DB$ Dig | DigNum$ 4 | ChangeNum$ 1 | Optional$ True | ForceRevealToController$ True | ChangeValid$ Artifact | RestRandomOrder$ True -Oracle:Haste\nWhen Casey Jones enters, look at the top four cards of your library. You may reveal an artifact card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. +Name:Casey Jones, Jury-Rig Justiciar +ManaCost:1 R +Types:Legendary Creature Human Berserker +PT:2/1 +K:Haste +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDig | TriggerDescription$ When NICKNAME enters, look at the top four cards of your library. You may reveal an artifact card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. +SVar:TrigDig:DB$ Dig | DigNum$ 4 | ChangeNum$ 1 | Optional$ True | ForceRevealToController$ True | ChangeValid$ Artifact | RestRandomOrder$ True +Oracle:Haste\nWhen Casey Jones enters, look at the top four cards of your library. You may reveal an artifact card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/cardsfolder/upcoming/donatello_rad_scientist.txt b/forge-gui/res/cardsfolder/upcoming/donatello_rad_scientist.txt index 844b82add2f..fe2d6cdc19a 100644 --- a/forge-gui/res/cardsfolder/upcoming/donatello_rad_scientist.txt +++ b/forge-gui/res/cardsfolder/upcoming/donatello_rad_scientist.txt @@ -1,9 +1,9 @@ -Name:Donatello, Rad Scientist -ManaCost:5 U -Types:Legendary Creature Mutant Ninja Turtle -PT:5/6 -K:Vigilance -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTap | TriggerDescription$ When NICKNAME enters, tap up to three target creatures your opponents control. Put a stun counter on each of them. (If a permanent with a stun counter would become untapped, remove one from it instead.) -SVar:TrigTap:DB$ Tap | ValidTgts$ Creature.OppCtrl | TargetMin$ 0 | TargetMax$ 3 | TgtPrompt$ Select up to three target creatures your opponents control | SubAbility$ DBCounter -SVar:DBCounter:DB$ PutCounter | Defined$ Targeted | CounterType$ Stun | CounterNum$ 1 +Name:Donatello, Rad Scientist +ManaCost:5 U +Types:Legendary Creature Mutant Ninja Turtle +PT:5/6 +K:Vigilance +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTap | TriggerDescription$ When NICKNAME enters, tap up to three target creatures your opponents control. Put a stun counter on each of them. (If a permanent with a stun counter would become untapped, remove one from it instead.) +SVar:TrigTap:DB$ Tap | ValidTgts$ Creature.OppCtrl | TargetMin$ 0 | TargetMax$ 3 | TgtPrompt$ Select up to three target creatures your opponents control | SubAbility$ DBCounter +SVar:DBCounter:DB$ PutCounter | Defined$ Targeted | CounterType$ Stun | CounterNum$ 1 Oracle:Vigilance (Attacking doesn't cause this creature to tap.)\nWhen Donatello enters, tap up to three target creatures your opponents control. Put a stun counter on each of them. (If a permanent with a stun counter would become untapped, remove one from it instead.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/donatello_the_brains.txt b/forge-gui/res/cardsfolder/upcoming/donatello_the_brains.txt index 28a6515d74e..f963d47fd9a 100644 --- a/forge-gui/res/cardsfolder/upcoming/donatello_the_brains.txt +++ b/forge-gui/res/cardsfolder/upcoming/donatello_the_brains.txt @@ -1,9 +1,9 @@ -Name:Donatello, the Brains -ManaCost:2 U -Types:Legendary Creature Mutant Ninja Turtle -PT:2/4 -R:Event$ CreateToken | ActiveZones$ Battlefield | ValidToken$ Card.YouCtrl | ReplaceWith$ DBReplace | Description$ If one or more tokens would be created under your control, those tokens plus an additional Mutagen token are created instead. -SVar:DBReplace:DB$ ReplaceToken | Type$ AddToken | Amount$ 1 | TokenScript$ c_a_mutagen_sac -K:Partner - Character select -DeckHas:Ability$Sacrifice|Token & Type$Mutagen +Name:Donatello, the Brains +ManaCost:2 U +Types:Legendary Creature Mutant Ninja Turtle +PT:2/4 +R:Event$ CreateToken | ActiveZones$ Battlefield | ValidToken$ Card.YouCtrl | ReplaceWith$ DBReplace | Description$ If one or more tokens would be created under your control, those tokens plus an additional Mutagen token are created instead. +SVar:DBReplace:DB$ ReplaceToken | Type$ AddToken | Amount$ 1 | TokenScript$ c_a_mutagen_sac +K:Partner - Character select +DeckHas:Ability$Sacrifice|Token & Type$Mutagen Oracle:If one or more tokens would be created under your control, those tokens plus an additional Mutagen token are created instead.\nPartner—Character select (You can have two commanders if both have this ability.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/donnie_april_adorkable_duo.txt b/forge-gui/res/cardsfolder/upcoming/donnie_april_adorkable_duo.txt index 8ecd5c994fe..ba44bb71c86 100644 --- a/forge-gui/res/cardsfolder/upcoming/donnie_april_adorkable_duo.txt +++ b/forge-gui/res/cardsfolder/upcoming/donnie_april_adorkable_duo.txt @@ -1,9 +1,9 @@ -Name:Donnie & April, Adorkable Duo -ManaCost:4 U -Types:Legendary Creature Mutant Ninja Human Turtle -PT:3/3 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When NICKNAME enters, ABILITY -SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBDraw,DBChangeZone | AdditionalDescription$ or both. Each mode must target a different player. -SVar:DBDraw:DB$ Draw | NumCards$ 2 | ValidTgts$ Player | TargetUnique$ True | SpellDescription$ Target player draws two cards. -SVar:DBChangeZone:DB$ ChangeZone | ValidTgts$ Player | TargetUnique$ True | Hidden$ True | Mandatory$ True | ChangeType$ Artifact.TargetedPlayerOwn,Instant.TargetedPlayerOwn,Sorcery.TargetedPlayerOwn | ChangeTypeDesc$ artifact, instant or sorcery | ChangeNum$ 1 | Origin$ Graveyard | Destination$ Hand | SpellDescription$ Target player returns an artifact, instant, or sorcery card from their graveyard to their hand. -Oracle:When Donnie & April enter, choose one or both. Each mode must target a different player.\n• Target player draws two cards.\n• Target player returns an artifact, instant, or sorcery card from their graveyard to their hand. +Name:Donnie & April, Adorkable Duo +ManaCost:4 U +Types:Legendary Creature Mutant Ninja Human Turtle +PT:3/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When NICKNAME enters, ABILITY +SVar:TrigCharm:DB$ Charm | MinCharmNum$ 1 | CharmNum$ 2 | Choices$ DBDraw,DBChangeZone | AdditionalDescription$ or both. Each mode must target a different player. +SVar:DBDraw:DB$ Draw | NumCards$ 2 | ValidTgts$ Player | TargetUnique$ True | SpellDescription$ Target player draws two cards. +SVar:DBChangeZone:DB$ ChangeZone | ValidTgts$ Player | TargetUnique$ True | Hidden$ True | Mandatory$ True | ChangeType$ Artifact.TargetedPlayerOwn,Instant.TargetedPlayerOwn,Sorcery.TargetedPlayerOwn | ChangeTypeDesc$ artifact, instant or sorcery | ChangeNum$ 1 | Origin$ Graveyard | Destination$ Hand | SpellDescription$ Target player returns an artifact, instant, or sorcery card from their graveyard to their hand. +Oracle:When Donnie & April enter, choose one or both. Each mode must target a different player.\n• Target player draws two cards.\n• Target player returns an artifact, instant, or sorcery card from their graveyard to their hand. diff --git a/forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt b/forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt index dd8b61bddb8..61d5aed1558 100644 --- a/forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt +++ b/forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt @@ -1,12 +1,12 @@ -Name:Heroes in a Half Shell -ManaCost:W U B R G -Types:Legendary Creature Mutant Ninja Turtle -PT:5/5 -K:Vigilance -K:Menace -K:Trample -K:Haste -T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Mutant.YouCtrl,Ninja.YouCtrl,Turtle.YouCtrl | ValidTarget$ Player | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever one or more Mutants, Ninjas, and/or Turtles you control deal combat damage to a player, put a +1/+1 counter on each of those creatures and draw a card. -SVar:DBPutCounter:DB$ PutCounter | Defined$ TriggeredSources | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw -SVar:DBDraw:DB$ Draw +Name:Heroes in a Half Shell +ManaCost:W U B R G +Types:Legendary Creature Mutant Ninja Turtle +PT:5/5 +K:Vigilance +K:Menace +K:Trample +K:Haste +T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Mutant.YouCtrl,Ninja.YouCtrl,Turtle.YouCtrl | ValidTarget$ Player | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever one or more Mutants, Ninjas, and/or Turtles you control deal combat damage to a player, put a +1/+1 counter on each of those creatures and draw a card. +SVar:DBPutCounter:DB$ PutCounter | Defined$ TriggeredSources | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw Oracle:Vigilance, menace, trample, haste\nWhenever one or more Mutants, Ninjas, and/or Turtles you control deal combat damage to a player, put a +1/+1 counter on each of those creatures and draw a card. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/krang_master_mind.txt b/forge-gui/res/cardsfolder/upcoming/krang_master_mind.txt index 61ec7ef3607..4396b490a88 100644 --- a/forge-gui/res/cardsfolder/upcoming/krang_master_mind.txt +++ b/forge-gui/res/cardsfolder/upcoming/krang_master_mind.txt @@ -1,12 +1,12 @@ -Name:Krang, Master Mind -ManaCost:6 U U -Types:Legendary Artifact Creature Utrom Warrior -PT:1/4 -K:Affinity:Artifact -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | CheckSVar$ X | SVarCompare$ LT4 | Execute$ TrigDraw | TriggerDescription$ When NICKNAME enters, if you have fewer than four cards in hand, draw cards equal to the difference. -SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ Y -S:Mode$ Continuous | Affected$ Card.Self | AddPower$ Z | Description$ NICKNAME gets +1/+0 for each other artifact you control. -SVar:X:Count$ValidHand Card.YouOwn -SVar:Y:Number$4/Minus.X -SVar:Z:Count$Valid Artifact.Other+YouCtrl +Name:Krang, Master Mind +ManaCost:6 U U +Types:Legendary Artifact Creature Utrom Warrior +PT:1/4 +K:Affinity:Artifact +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | CheckSVar$ X | SVarCompare$ LT4 | Execute$ TrigDraw | TriggerDescription$ When NICKNAME enters, if you have fewer than four cards in hand, draw cards equal to the difference. +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ Y +S:Mode$ Continuous | Affected$ Card.Self | AddPower$ Z | Description$ NICKNAME gets +1/+0 for each other artifact you control. +SVar:X:Count$ValidHand Card.YouOwn +SVar:Y:Number$4/Minus.X +SVar:Z:Count$Valid Artifact.Other+YouCtrl Oracle:Affinity for artifacts (This spell costs {1} less to cast for each artifact you control.)\nWhen Krang enters, if you have fewer than four cards in hand, draw cards equal to the difference.\nKrang gets +1/+0 for each other artifact you control. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/leonardo_the_balance.txt b/forge-gui/res/cardsfolder/upcoming/leonardo_the_balance.txt index 0b73afc0598..1c7b2d49070 100644 --- a/forge-gui/res/cardsfolder/upcoming/leonardo_the_balance.txt +++ b/forge-gui/res/cardsfolder/upcoming/leonardo_the_balance.txt @@ -1,10 +1,10 @@ -Name:Leonardo, the Balance -ManaCost:3 W -Types:Legendary Creature Mutant Ninja Turtle -PT:3/3 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.token+YouCtrl | TriggerZones$ Battlefield | Execute$ DBPutCounterAll | OptionalDecider$ You | ResolvedLimit$ 1 | TriggerDescription$ Whenever a token you control enters, you may put a +1/+1 counter on each creature you control. Do this only once each turn. -SVar:DBPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 -A:AB$ PumpAll | Cost$ W U B R G | ValidCards$ Creature.YouCtrl | KW$ Menace & Trample & Lifelink | SpellDescription$ Creatures you control gain menace, trample, and lifelink until end of turn. -K:Partner - Character select -DeckHints:Ability$Token +Name:Leonardo, the Balance +ManaCost:3 W +Types:Legendary Creature Mutant Ninja Turtle +PT:3/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.token+YouCtrl | TriggerZones$ Battlefield | Execute$ DBPutCounterAll | OptionalDecider$ You | ResolvedLimit$ 1 | TriggerDescription$ Whenever a token you control enters, you may put a +1/+1 counter on each creature you control. Do this only once each turn. +SVar:DBPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 +A:AB$ PumpAll | Cost$ W U B R G | ValidCards$ Creature.YouCtrl | KW$ Menace & Trample & Lifelink | SpellDescription$ Creatures you control gain menace, trample, and lifelink until end of turn. +K:Partner - Character select +DeckHints:Ability$Token Oracle:Whenever a token you control enters, you may put a +1/+1 counter on each creature you control. Do this only once each turn.\n{W}{U}{B}{R}{G}: Creatures you control gain menace, trample, and lifelink until end of turn.\nPartner—Character select (You can have two commanders if both have this ability.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/leonardo_worldly_warrior.txt b/forge-gui/res/cardsfolder/upcoming/leonardo_worldly_warrior.txt index 371df3dc65a..bf3b0ad8eb6 100644 --- a/forge-gui/res/cardsfolder/upcoming/leonardo_worldly_warrior.txt +++ b/forge-gui/res/cardsfolder/upcoming/leonardo_worldly_warrior.txt @@ -1,8 +1,8 @@ -Name:Leonardo, Worldly Warrior -ManaCost:7 W -Types:Legendary Creature Mutant Ninja Turtle -PT:5/5 -S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {1} less to cast for each creature you control. -K:Double Strike -SVar:X:Count$Valid Creature.YouCtrl +Name:Leonardo, Worldly Warrior +ManaCost:7 W +Types:Legendary Creature Mutant Ninja Turtle +PT:5/5 +S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {1} less to cast for each creature you control. +K:Double Strike +SVar:X:Count$Valid Creature.YouCtrl Oracle:This spell costs {1} less to cast for each creature you control.\nDouble strike \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/michelangelo_on_the_scene.txt b/forge-gui/res/cardsfolder/upcoming/michelangelo_on_the_scene.txt index b8086d362ff..2f02e6215a7 100644 --- a/forge-gui/res/cardsfolder/upcoming/michelangelo_on_the_scene.txt +++ b/forge-gui/res/cardsfolder/upcoming/michelangelo_on_the_scene.txt @@ -1,11 +1,11 @@ -Name:Michelangelo, On the Scene -ManaCost:4 G G -Types:Legendary Creature Mutant Ninja Turtle -PT:2/2 -K:Trample -K:etbCounter:P1P1:X:no Condition:NICKNAME enters with a +1/+1 counter on him for each land you control. -SVar:X:Count$Valid Land.YouCtrl -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When NICKNAME dies, return this card to your hand. -SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Hand -DeckHas:Ability$Counters +Name:Michelangelo, On the Scene +ManaCost:4 G G +Types:Legendary Creature Mutant Ninja Turtle +PT:2/2 +K:Trample +K:etbCounter:P1P1:X:no Condition:NICKNAME enters with a +1/+1 counter on him for each land you control. +SVar:X:Count$Valid Land.YouCtrl +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When NICKNAME dies, return this card to your hand. +SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Hand +DeckHas:Ability$Counters Oracle:Trample (This creature can deal excess combat damage to the player it's attacking.)\nMichelangelo enters with a +1/+1 counter on him for each land you control.\nWhen Michelangelo dies, return this card to your hand. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/michelangelo_the_heart.txt b/forge-gui/res/cardsfolder/upcoming/michelangelo_the_heart.txt index d293cad68f2..f501f177008 100644 --- a/forge-gui/res/cardsfolder/upcoming/michelangelo_the_heart.txt +++ b/forge-gui/res/cardsfolder/upcoming/michelangelo_the_heart.txt @@ -1,11 +1,11 @@ -Name:Michelangelo, the Heart -ManaCost:1 G -Types:Legendary Creature Mutant Ninja Turtle -PT:2/1 -K:Trample -T:Mode$ Phase | Phase$ Main | PhaseCount$ 2 | ValidPlayer$ You | CheckSVar$ RaidTest | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Raid (the Fridge) — At the beginning of your second main phase, if you attacked this turn, put a +1/+1 counter on target creature and create a Food token. -SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBToken -SVar:DBToken:DB$ Token | TokenScript$ c_a_food_sac -K:Partner - Character select -SVar:RaidTest:Count$AttackersDeclared +Name:Michelangelo, the Heart +ManaCost:1 G +Types:Legendary Creature Mutant Ninja Turtle +PT:2/1 +K:Trample +T:Mode$ Phase | Phase$ Main | PhaseCount$ 2 | ValidPlayer$ You | CheckSVar$ RaidTest | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Raid (the Fridge) — At the beginning of your second main phase, if you attacked this turn, put a +1/+1 counter on target creature and create a Food token. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenScript$ c_a_food_sac +K:Partner - Character select +SVar:RaidTest:Count$AttackersDeclared Oracle:Trample\nRaid (the Fridge) — At the beginning of your second main phase, if you attacked this turn, put a +1/+1 counter on target creature and create a Food token.\nPartner—Character select (You can have two commanders if both have this ability.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/raphael_the_muscle.txt b/forge-gui/res/cardsfolder/upcoming/raphael_the_muscle.txt index bb3d8971ac8..7c8b4212bec 100644 --- a/forge-gui/res/cardsfolder/upcoming/raphael_the_muscle.txt +++ b/forge-gui/res/cardsfolder/upcoming/raphael_the_muscle.txt @@ -1,11 +1,11 @@ -Name:Raphael, the Muscle -ManaCost:4 R -Types:Legendary Creature Mutant Ninja Turtle -PT:4/4 -R:Event$ DamageDone | ActiveZones$ Battlefield | ValidSource$ Creature.YouCtrl+HasCounters | ValidTarget$ Permanent,Player | ReplaceWith$ DmgTwice | Description$ Double all damage that creatures you control with counters on them would deal. -SVar:DmgTwice:DB$ ReplaceEffect | VarName$ DamageAmount | VarValue$ X -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When NICKNAME enters, create a Mutagen token. -SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_mutagen_sac | TokenOwner$ You -K:Partner - Character select -SVar:X:ReplaceCount$DamageAmount/Twice +Name:Raphael, the Muscle +ManaCost:4 R +Types:Legendary Creature Mutant Ninja Turtle +PT:4/4 +R:Event$ DamageDone | ActiveZones$ Battlefield | ValidSource$ Creature.YouCtrl+HasCounters | ValidTarget$ Permanent,Player | ReplaceWith$ DmgTwice | Description$ Double all damage that creatures you control with counters on them would deal. +SVar:DmgTwice:DB$ ReplaceEffect | VarName$ DamageAmount | VarValue$ X +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When NICKNAME enters, create a Mutagen token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_mutagen_sac | TokenOwner$ You +K:Partner - Character select +SVar:X:ReplaceCount$DamageAmount/Twice Oracle:Double all damage that creatures you control with counters on them would deal.\nWhen Raphael enters, create a Mutagen token.\nPartner—Character select (You can have two commanders if both have this ability.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/splinter_the_mentor.txt b/forge-gui/res/cardsfolder/upcoming/splinter_the_mentor.txt index 7bba6154b97..714f02dcdfd 100644 --- a/forge-gui/res/cardsfolder/upcoming/splinter_the_mentor.txt +++ b/forge-gui/res/cardsfolder/upcoming/splinter_the_mentor.txt @@ -1,10 +1,10 @@ -Name:Splinter, the Mentor -ManaCost:1 B -Types:Legendary Creature Mutant Ninja Rat -PT:2/2 -K:Menace -T:Mode$ ChangesZone | ValidCard$ Card.Self,Creature.Other+!token+YouCtrl | Origin$ Battlefield | Destination$ Any | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever NICKNAME or another nontoken creature you control leaves the battlefield, create a Mutagen token. -SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_mutagen_sac | TokenOwner$ You -K:Partner - Character select -DeckHas:Ability$Token & Type$Artifact|Mutagen +Name:Splinter, the Mentor +ManaCost:1 B +Types:Legendary Creature Mutant Ninja Rat +PT:2/2 +K:Menace +T:Mode$ ChangesZone | ValidCard$ Card.Self,Creature.Other+!token+YouCtrl | Origin$ Battlefield | Destination$ Any | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever NICKNAME or another nontoken creature you control leaves the battlefield, create a Mutagen token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_mutagen_sac | TokenOwner$ You +K:Partner - Character select +DeckHas:Ability$Token & Type$Artifact|Mutagen Oracle:Menace\nWhenever Splinter or another nontoken creature you control leaves the battlefield, create a Mutagen token.\nPartner—Character select (You can have two commanders if both have this ability.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/super_shredder.txt b/forge-gui/res/cardsfolder/upcoming/super_shredder.txt index e13fd94ded6..1b3164b9ea4 100644 --- a/forge-gui/res/cardsfolder/upcoming/super_shredder.txt +++ b/forge-gui/res/cardsfolder/upcoming/super_shredder.txt @@ -1,9 +1,9 @@ -Name:Super Shredder -ManaCost:1 B -Types:Legendary Creature Mutant Ninja Human -PT:1/1 -K:Menace -T:Mode$ ChangesZone | ValidCard$ Permanent.Other | Origin$ Battlefield | Destination$ Any | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever another permanent leaves the battlefield, put a +1/+1 counter on CARDNAME. -SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 -DeckHas:Ability$Counters +Name:Super Shredder +ManaCost:1 B +Types:Legendary Creature Mutant Ninja Human +PT:1/1 +K:Menace +T:Mode$ ChangesZone | ValidCard$ Permanent.Other | Origin$ Battlefield | Destination$ Any | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever another permanent leaves the battlefield, put a +1/+1 counter on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +DeckHas:Ability$Counters Oracle:Menace\nWhenever another permanent leaves the battlefield, put a +1/+1 counter on Super Shredder. \ No newline at end of file From 6617e2627c942427ccef592277694d8d3e6d1c3a Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Thu, 18 Sep 2025 07:25:15 +0200 Subject: [PATCH 133/230] Conquest: make ColorButton instead of ColorFilter --- .../src/forge/card/ColorSetImage.java | 38 --- .../planarconquest/ConquestAEtherScreen.java | 126 +++++--- .../planarconquest/ConquestUtil.java | 282 +++++------------- 3 files changed, 160 insertions(+), 286 deletions(-) delete mode 100644 forge-gui-mobile/src/forge/card/ColorSetImage.java diff --git a/forge-gui-mobile/src/forge/card/ColorSetImage.java b/forge-gui-mobile/src/forge/card/ColorSetImage.java deleted file mode 100644 index 8e874be9778..00000000000 --- a/forge-gui-mobile/src/forge/card/ColorSetImage.java +++ /dev/null @@ -1,38 +0,0 @@ -package forge.card; - -import forge.Forge; -import forge.Graphics; -import forge.assets.FImage; -import forge.localinstance.skin.FSkinProp; - -public class ColorSetImage implements FImage { - private final ColorSet colorSet; - private final int shardCount; - - public ColorSetImage(ColorSet colorSet0) { - colorSet = colorSet0; - shardCount = colorSet.getOrderedColors().size(); - } - - @Override - public float getWidth() { - return Forge.getAssets().images().get(FSkinProp.IMG_MANA_W).getWidth() * shardCount; - } - - @Override - public float getHeight() { - return Forge.getAssets().images().get(FSkinProp.IMG_MANA_W).getHeight(); - } - - @Override - public void draw(Graphics g, float x, float y, float w, float h) { - float imageSize = w / shardCount; - if (imageSize > h) { - imageSize = h; - float w0 = imageSize * shardCount; - x += (w - w0) / 2; - w = w0; - } - CardFaceSymbols.drawColorSet(g, colorSet, x, y, imageSize); - } -} diff --git a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestAEtherScreen.java b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestAEtherScreen.java index f13d3ceb3a1..797dc82bd62 100644 --- a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestAEtherScreen.java +++ b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestAEtherScreen.java @@ -1,9 +1,13 @@ package forge.screens.planarconquest; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.Rectangle; @@ -14,25 +18,27 @@ import com.google.common.collect.Iterables; import forge.Forge; import forge.Graphics; import forge.animation.ForgeAnimation; +import forge.assets.FImage; import forge.assets.FSkin; import forge.assets.FSkinColor; import forge.assets.FSkinFont; import forge.assets.FSkinTexture; import forge.assets.TextRenderer; +import forge.card.CardFaceSymbols; import forge.card.CardRarity; import forge.card.CardRenderer; import forge.card.CardRenderer.CardStackPosition; import forge.card.CardZoom; import forge.card.ColorSet; -import forge.card.ColorSetImage; +import forge.card.MagicColor; import forge.gamemodes.planarconquest.ConquestCommander; import forge.gamemodes.planarconquest.ConquestData; import forge.gamemodes.planarconquest.ConquestPlane; import forge.gamemodes.planarconquest.ConquestPreferences.CQPref; import forge.gamemodes.planarconquest.ConquestUtil; -import forge.gamemodes.planarconquest.ConquestUtil.AEtherFilter; import forge.item.PaperCard; import forge.localinstance.skin.FSkinProp; +import forge.localinstance.skin.IHasSkinProp; import forge.model.FModel; import forge.screens.FScreen; import forge.toolbox.FCardPanel; @@ -54,10 +60,10 @@ public class ConquestAEtherScreen extends FScreen { private final Set filteredPool = new HashSet<>(); private final Set strictPool = new HashSet<>(); - private final FilterButton btnColorFilter = add(new FilterButton(Forge.getLocalizer().getMessage("lblColor"), ConquestUtil.COLOR_FILTERS)); - private final FilterButton btnTypeFilter = add(new FilterButton(Forge.getLocalizer().getMessage("lblType"), ConquestUtil.TYPE_FILTERS)); - private final FilterButton btnRarityFilter = add(new FilterButton(Forge.getLocalizer().getMessage("lblRarity"), ConquestUtil.RARITY_FILTERS)); - private final FilterButton btnCMCFilter = add(new FilterButton(Forge.getLocalizer().getMessage("lblCMC"), ConquestUtil.CMC_FILTERS)); + private final ColorButton btnColorFilter = add(new ColorButton(Forge.getLocalizer().getMessage("lblColor"))); + private final FilterButton btnTypeFilter = add(new FilterButton(Forge.getLocalizer().getMessage("lblType"), ConquestUtil.TypeFilter.values())); + private final FilterButton btnRarityFilter = add(new FilterButton(Forge.getLocalizer().getMessage("lblRarity"), ConquestUtil.RarityFilter.values())); + private final FilterButton btnCMCFilter = add(new FilterButton(Forge.getLocalizer().getMessage("lblCMC"), ConquestUtil.CMCFilter.values())); private final FLabel lblShards = add(new FLabel.Builder().font(LABEL_FONT).align(Align.center).parseSymbols().build()); @@ -93,10 +99,10 @@ public class ConquestAEtherScreen extends FScreen { } private void resetFilters() { - btnColorFilter.setSelectedOption(ConquestUtil.getColorFilter(commander.getCard().getRules().getColorIdentity())); - btnTypeFilter.setSelectedOption(AEtherFilter.CREATURE); - btnRarityFilter.setSelectedOption(AEtherFilter.COMMON); - btnCMCFilter.setSelectedOption(AEtherFilter.CMC_LOW_MID); + btnColorFilter.setSelectedOption(commander.getCard().getRules().getColorIdentity()); + btnTypeFilter.setSelectedOption(ConquestUtil.TypeFilter.CREATURE); + btnRarityFilter.setSelectedOption(ConquestUtil.RarityFilter.COMMON); + btnCMCFilter.setSelectedOption(ConquestUtil.CMCFilter.CMC_LOW_MID); } private void updateFilteredPool() { @@ -344,18 +350,18 @@ public class ConquestAEtherScreen extends FScreen { } } - private class FilterButton extends FLabel implements Predicate { + private abstract class AbstractFilterButton extends FLabel implements Predicate { private final String caption; - private final List options; - private AEtherFilter selectedOption; + private final List options; + protected T selectedOption; - private FilterButton(String caption0, AEtherFilter[] options0) { + private AbstractFilterButton(String caption0, T[] options0, final Function display) { super(new FLabel.Builder().iconInBackground().pressedColor(FILTER_BUTTON_PRESSED_COLOR) .textColor(FILTER_BUTTON_TEXT_COLOR).alphaComposite(1f).align(Align.center)); caption = caption0; options = ImmutableList.copyOf(options0); setSelectedOption(options.get(0)); - setCommand(e -> GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblSelectCaptionFilter", caption), 0, 1, options, Set.of(selectedOption), null, result -> { + setCommand(e -> GuiChoose.getChoices(Forge.getLocalizer().getMessage("lblSelectCaptionFilter", caption), 0, 1, options, Set.of(selectedOption), display, result -> { if (!result.isEmpty()) { setSelectedOption(result.get(0)); updateFilteredPool(); @@ -363,30 +369,13 @@ public class ConquestAEtherScreen extends FScreen { })); } - private void setSelectedOption(AEtherFilter selectedOption0) { - if (selectedOption == selectedOption0) { return; } + public void setSelectedOption(T selectedOption0) { + if (selectedOption == selectedOption0) { + return; + } selectedOption = selectedOption0; - - FSkinProp skinProp = selectedOption.getSkinProp(); - if (skinProp != null) { - setIcon(FSkin.getImages().get(skinProp)); - } - else { - ColorSet color = selectedOption.getColor(); - if (color != null) { - setIcon(new ColorSetImage(color)); - } - else { - System.out.println("No icon for filter " + selectedOption.name()); - setIcon(null); - } - } } - @Override - public boolean test(PaperCard card) { - return selectedOption.test(card); - } @Override protected void drawContent(Graphics g, float w, float h, final boolean pressed) { @@ -396,4 +385,69 @@ public class ConquestAEtherScreen extends FScreen { super.drawContent(g, w, h, pressed); } } + + private class ColorButton extends AbstractFilterButton { + private ColorButton(String caption0) { + super(caption0, Arrays.stream(ColorSet.values()).sorted(Comparator.comparing(ColorSet::getOrderWeight)).toArray(ColorSet[]::new), + c -> "Playable in " + c.stream().map(MagicColor.Color::getSymbol).collect(Collectors.joining())); + } + + @Override + public void setSelectedOption(ColorSet selectedOption0) { + super.setSelectedOption(selectedOption0); + + setIcon(new ColorSetImage(selectedOption)); + } + + @Override + public boolean test(PaperCard card) { + return card.getRules().getColorIdentity().hasNoColorsExcept(selectedOption); + } + + private record ColorSetImage(ColorSet colorSet, int shardCount) implements FImage { + public ColorSetImage(ColorSet colorSet0) { + this(colorSet0, colorSet0.getOrderedColors().size()); + } + + @Override + public float getWidth() { + return Forge.getAssets().images().get(FSkinProp.IMG_MANA_W).getWidth() * shardCount; + } + + @Override + public float getHeight() { + return Forge.getAssets().images().get(FSkinProp.IMG_MANA_W).getHeight(); + } + + @Override + public void draw(Graphics g, float x, float y, float w, float h) { + float imageSize = w / shardCount; + if (imageSize > h) { + imageSize = h; + float w0 = imageSize * shardCount; + x += (w - w0) / 2; + w = w0; + } + CardFaceSymbols.drawColorSet(g, colorSet, x, y, imageSize); + } + } + } + + private class FilterButton & IHasSkinProp & Predicate> extends AbstractFilterButton { + private FilterButton(String caption0, T[] options0) { + super(caption0, options0, null); + } + + @Override + public void setSelectedOption(T selectedOption0) { + super.setSelectedOption(selectedOption0); + + setIcon(FSkin.getImages().get(selectedOption.getSkinProp())); + } + + @Override + public boolean test(PaperCard card) { + return selectedOption.test(card); + } + } } diff --git a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestUtil.java b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestUtil.java index 19912f1e05f..46a81c04541 100644 --- a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestUtil.java +++ b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestUtil.java @@ -215,220 +215,23 @@ public class ConquestUtil { }; } - public enum AEtherFilter implements IHasSkinProp, Predicate { - C (null, new ColorFilter(MagicColor.COLORLESS)), - W (null, new ColorFilter(MagicColor.WHITE)), - U (null, new ColorFilter(MagicColor.BLUE)), - B (null, new ColorFilter(MagicColor.BLACK)), - R (null, new ColorFilter(MagicColor.RED)), - G (null, new ColorFilter(MagicColor.GREEN)), - - WU (null, new ColorFilter(MagicColor.WHITE | MagicColor.BLUE)), - WB (null, new ColorFilter(MagicColor.WHITE | MagicColor.BLACK)), - UB (null, new ColorFilter(MagicColor.BLUE | MagicColor.BLACK)), - UR (null, new ColorFilter(MagicColor.BLUE | MagicColor.RED)), - BR (null, new ColorFilter(MagicColor.BLACK | MagicColor.RED)), - BG (null, new ColorFilter(MagicColor.BLACK | MagicColor.GREEN)), - RG (null, new ColorFilter(MagicColor.RED | MagicColor.GREEN)), - RW (null, new ColorFilter(MagicColor.RED | MagicColor.WHITE)), - GW (null, new ColorFilter(MagicColor.GREEN | MagicColor.WHITE)), - GU (null, new ColorFilter(MagicColor.GREEN | MagicColor.BLUE)), - - WUB (null, new ColorFilter(MagicColor.WHITE | MagicColor.BLUE | MagicColor.BLACK)), - WBG (null, new ColorFilter(MagicColor.WHITE | MagicColor.BLACK | MagicColor.GREEN)), - UBR (null, new ColorFilter(MagicColor.BLUE | MagicColor.BLACK | MagicColor.RED)), - URW (null, new ColorFilter(MagicColor.BLUE | MagicColor.RED | MagicColor.WHITE)), - BRG (null, new ColorFilter(MagicColor.BLACK | MagicColor.RED | MagicColor.GREEN)), - BGU (null, new ColorFilter(MagicColor.BLACK | MagicColor.GREEN | MagicColor.BLUE)), - RGW (null, new ColorFilter(MagicColor.RED | MagicColor.GREEN | MagicColor.WHITE)), - RWB (null, new ColorFilter(MagicColor.RED | MagicColor.WHITE | MagicColor.BLACK)), - GWU (null, new ColorFilter(MagicColor.GREEN | MagicColor.WHITE | MagicColor.BLUE)), - GUR (null, new ColorFilter(MagicColor.GREEN | MagicColor.BLUE | MagicColor.RED)), - - WUBR (null, new ColorFilter(MagicColor.WHITE | MagicColor.BLUE | MagicColor.BLACK | MagicColor.RED)), - WUBG (null, new ColorFilter(MagicColor.WHITE | MagicColor.BLUE | MagicColor.BLACK | MagicColor.GREEN)), - WURG (null, new ColorFilter(MagicColor.WHITE | MagicColor.BLUE | MagicColor.RED | MagicColor.GREEN)), - WBRG (null, new ColorFilter(MagicColor.WHITE | MagicColor.BLACK | MagicColor.RED | MagicColor.GREEN)), - UBRG (null, new ColorFilter(MagicColor.BLUE | MagicColor.BLACK | MagicColor.RED | MagicColor.GREEN)), - - WUBRG (null, new ColorFilter(MagicColor.ALL_COLORS)), - - CREATURE (FSkinProp.IMG_CREATURE, new TypeFilter(EnumSet.of(CoreType.Creature), "Creature")), - NONCREATURE_PERMANENT (FSkinProp.IMG_ENCHANTMENT, new TypeFilter(EnumSet.of(CoreType.Artifact, CoreType.Enchantment, CoreType.Planeswalker, CoreType.Land), EnumSet.of(CoreType.Creature), "Noncreature Permanent")), - INSTANT_SORCERY (FSkinProp.IMG_SORCERY, new TypeFilter(EnumSet.of(CoreType.Instant, CoreType.Sorcery), "Instant or Sorcery")), - - COMMON (FSkinProp.IMG_PW_BADGE_COMMON, new RarityFilter(EnumSet.of(CardRarity.Common, CardRarity.Uncommon, CardRarity.Rare, CardRarity.Special, CardRarity.MythicRare))), - UNCOMMON (FSkinProp.IMG_PW_BADGE_UNCOMMON, new RarityFilter(EnumSet.of(CardRarity.Uncommon, CardRarity.Rare, CardRarity.Special, CardRarity.MythicRare))), - RARE (FSkinProp.IMG_PW_BADGE_RARE, new RarityFilter(EnumSet.of(CardRarity.Rare, CardRarity.Special, CardRarity.MythicRare))), - MYTHIC (FSkinProp.IMG_PW_BADGE_MYTHIC, new RarityFilter(EnumSet.of(CardRarity.MythicRare))), - - CMC_LOW (FSkinProp.IMG_CMC_LOW, new CMCFilter(0, 3)), - CMC_LOW_MID (FSkinProp.IMG_CMC_LOW_MID, new CMCFilter(2, 5)), - CMC_MID_HIGH (FSkinProp.IMG_CMC_MID_HIGH, new CMCFilter(4, 7)), - CMC_HIGH (FSkinProp.IMG_CMC_HIGH, new CMCFilter(6, -1)); - + public enum TypeFilter implements IHasSkinProp, Predicate + { + CREATURE (FSkinProp.IMG_CREATURE, EnumSet.of(CoreType.Creature), "Creature"), + NONCREATURE_PERMANENT (FSkinProp.IMG_ENCHANTMENT, EnumSet.of(CoreType.Artifact, CoreType.Enchantment, CoreType.Planeswalker, CoreType.Land), EnumSet.of(CoreType.Creature), "Noncreature Permanent"), + INSTANT_SORCERY (FSkinProp.IMG_SORCERY, EnumSet.of(CoreType.Instant, CoreType.Sorcery), "Instant or Sorcery") + ; private final FSkinProp skinProp; - private final Predicate predicate; - - AEtherFilter(final FSkinProp skinProp0, final Predicate predicate0) { - skinProp = skinProp0; - predicate = predicate0; - } - - @Override - public FSkinProp getSkinProp() { - return skinProp; - } - - @Override - public boolean test(PaperCard card) { - return predicate.test(card); - } - - public ColorSet getColor() { - if (predicate instanceof ColorFilter cf) { - return cf.color; - } - return null; - } - - public CardRarity getRarity() { - return getRarity(0d); - } - public CardRarity getRarity(double random) { - if (predicate instanceof RarityFilter rf) { - return rf.getRarity(random); - } - return null; - } - - @Override - public String toString() { - return predicate.toString(); - } - } - - public static AEtherFilter getColorFilter(ColorSet color) { - StringBuilder name = new StringBuilder(); - for (MagicColor.Color s : color.getOrderedColors()) { - name.append(s.getShortName()); - } - try { - return AEtherFilter.valueOf(name.toString()); - } - catch (Exception e) { - System.err.println("No color filter with name " + name); - return AEtherFilter.WUBRG; //return 5-color filter as fallback - } - } - - public static void updateRarityFilterOdds(ConquestPreferences prefs) { - - Map odds = Maps.newEnumMap(CardRarity.class); - if (prefs.getPrefBoolean(CQPref.AETHER_USE_DEFAULT_RARITY_ODDS)) { - odds.put(CardRarity.Common, 1d); - odds.put(CardRarity.Uncommon, 0.17); - odds.put(CardRarity.Rare, 0.03); - odds.put(CardRarity.MythicRare, 0.005); - } else { - double commonsPerBooster = prefs.getPrefInt(CQPref.BOOSTER_COMMONS); - double uncommonPerBooster = prefs.getPrefInt(CQPref.BOOSTER_UNCOMMONS); - double raresPerBooster = prefs.getPrefInt(CQPref.BOOSTER_RARES); - double mythicsPerBooster = raresPerBooster / (double)prefs.getPrefInt(CQPref.BOOSTERS_PER_MYTHIC); - - odds.put(CardRarity.Common, 1d); - odds.put(CardRarity.Uncommon, uncommonPerBooster / commonsPerBooster); - odds.put(CardRarity.Rare, raresPerBooster / commonsPerBooster); - odds.put(CardRarity.MythicRare, mythicsPerBooster / commonsPerBooster); - } - - for (AEtherFilter filter : RARITY_FILTERS) { - ((RarityFilter)filter.predicate).updateOdds(odds); - } - } - - public static final AEtherFilter[] COLOR_FILTERS = new AEtherFilter[] { - AEtherFilter.C, - AEtherFilter.W, - AEtherFilter.U, - AEtherFilter.B, - AEtherFilter.R, - AEtherFilter.G, - AEtherFilter.WU, - AEtherFilter.WB, - AEtherFilter.UB, - AEtherFilter.UR, - AEtherFilter.BR, - AEtherFilter.BG, - AEtherFilter.RG, - AEtherFilter.RW, - AEtherFilter.GW, - AEtherFilter.GU, - AEtherFilter.WUB, - AEtherFilter.WBG, - AEtherFilter.UBR, - AEtherFilter.URW, - AEtherFilter.BRG, - AEtherFilter.BGU, - AEtherFilter.RGW, - AEtherFilter.RWB, - AEtherFilter.GWU, - AEtherFilter.GUR, - AEtherFilter.WUBR, - AEtherFilter.WUBG, - AEtherFilter.WURG, - AEtherFilter.WBRG, - AEtherFilter.UBRG, - AEtherFilter.WUBRG}; - - public static final AEtherFilter[] TYPE_FILTERS = new AEtherFilter[] { - AEtherFilter.CREATURE, - AEtherFilter.NONCREATURE_PERMANENT, - AEtherFilter.INSTANT_SORCERY }; - - public static final AEtherFilter[] RARITY_FILTERS = new AEtherFilter[] { - AEtherFilter.COMMON, - AEtherFilter.UNCOMMON, - AEtherFilter.RARE, - AEtherFilter.MYTHIC }; - - public static final AEtherFilter[] CMC_FILTERS = new AEtherFilter[] { - AEtherFilter.CMC_LOW, - AEtherFilter.CMC_LOW_MID, - AEtherFilter.CMC_MID_HIGH, - AEtherFilter.CMC_HIGH }; - - private static class ColorFilter implements Predicate { - private final ColorSet color; - - private ColorFilter(int colorMask0) { - color = ColorSet.fromMask(colorMask0); - } - - @Override - public boolean test(PaperCard card) { - return card.getRules().getColorIdentity().hasNoColorsExcept(color); - } - @Override - public String toString() { - StringBuilder sb = new StringBuilder("Playable in "); - for (MagicColor.Color c : color.getOrderedColors()) { - sb.append(c.getSymbol()); - } - return sb.toString(); - } - } - - private static class TypeFilter implements Predicate { private final Iterable types; private final Iterable nonTypes; private final String caption; - private TypeFilter(Iterable types0, final String caption0) { - this(types0, null, caption0); + private TypeFilter(FSkinProp skinProp, Iterable types0, final String caption0) { + this(skinProp, types0, null, caption0); } - private TypeFilter(Iterable types0, Iterable nonTypes0, final String caption0) { + private TypeFilter(FSkinProp skinProp0, Iterable types0, Iterable nonTypes0, final String caption0) { + skinProp = skinProp0; types = types0; nonTypes = nonTypes0; caption = caption0; @@ -452,22 +255,38 @@ public class ConquestUtil { return false; } @Override + public FSkinProp getSkinProp() { + return skinProp; + } + @Override public String toString() { return caption; } } - private static class RarityFilter implements Predicate { + + public enum RarityFilter implements IHasSkinProp, Predicate + { + COMMON (FSkinProp.IMG_PW_BADGE_COMMON, EnumSet.of(CardRarity.Common, CardRarity.Uncommon, CardRarity.Rare, CardRarity.Special, CardRarity.MythicRare)), + UNCOMMON (FSkinProp.IMG_PW_BADGE_UNCOMMON, EnumSet.of(CardRarity.Uncommon, CardRarity.Rare, CardRarity.Special, CardRarity.MythicRare)), + RARE (FSkinProp.IMG_PW_BADGE_RARE, EnumSet.of(CardRarity.Rare, CardRarity.Special, CardRarity.MythicRare)), + MYTHIC (FSkinProp.IMG_PW_BADGE_MYTHIC, EnumSet.of(CardRarity.MythicRare)), + ; + private final FSkinProp skinProp; private final Map rarityOdds; private String caption = ""; - private RarityFilter(Iterable rarities0) { + private RarityFilter(FSkinProp skinProp0, Iterable rarities0) { + skinProp = skinProp0; rarityOdds = Maps.newEnumMap(CardRarity.class); for (CardRarity rarity : rarities0) { rarityOdds.put(rarity, 0d); //values will be set later } } + public CardRarity getRarity() { + return getRarity(0d); + } public CardRarity getRarity(double random) { double total = 0d; CardRarity rarity = null; @@ -518,17 +337,52 @@ public class ConquestUtil { public boolean test(PaperCard card) { return rarityOdds.containsKey(card.getRarity()); } - + @Override + public FSkinProp getSkinProp() { + return skinProp; + } @Override public String toString() { return caption; } } - private static class CMCFilter implements Predicate { + public static void updateRarityFilterOdds(ConquestPreferences prefs) { + Map odds = Maps.newEnumMap(CardRarity.class); + if (prefs.getPrefBoolean(CQPref.AETHER_USE_DEFAULT_RARITY_ODDS)) { + odds.put(CardRarity.Common, 1d); + odds.put(CardRarity.Uncommon, 0.17); + odds.put(CardRarity.Rare, 0.03); + odds.put(CardRarity.MythicRare, 0.005); + } else { + double commonsPerBooster = prefs.getPrefInt(CQPref.BOOSTER_COMMONS); + double uncommonPerBooster = prefs.getPrefInt(CQPref.BOOSTER_UNCOMMONS); + double raresPerBooster = prefs.getPrefInt(CQPref.BOOSTER_RARES); + double mythicsPerBooster = raresPerBooster / (double)prefs.getPrefInt(CQPref.BOOSTERS_PER_MYTHIC); + + odds.put(CardRarity.Common, 1d); + odds.put(CardRarity.Uncommon, uncommonPerBooster / commonsPerBooster); + odds.put(CardRarity.Rare, raresPerBooster / commonsPerBooster); + odds.put(CardRarity.MythicRare, mythicsPerBooster / commonsPerBooster); + } + + for (RarityFilter filter : RarityFilter.values()) { + filter.updateOdds(odds); + } + } + + public enum CMCFilter implements IHasSkinProp, Predicate + { + CMC_LOW (FSkinProp.IMG_CMC_LOW, 0, 3), + CMC_LOW_MID (FSkinProp.IMG_CMC_LOW_MID, 2, 5), + CMC_MID_HIGH (FSkinProp.IMG_CMC_MID_HIGH, 4, 7), + CMC_HIGH (FSkinProp.IMG_CMC_HIGH, 6, -1); + + private final FSkinProp skinProp; private final int cmcMin, cmcMax; - private CMCFilter(int cmcMin0, int cmcMax0) { + private CMCFilter(FSkinProp skinProp0, int cmcMin0, int cmcMax0) { + skinProp = skinProp0; cmcMin = cmcMin0; cmcMax = cmcMax0; } @@ -539,6 +393,10 @@ public class ConquestUtil { if (cardCmc < cmcMin) { return false; } return cmcMax == -1 || cardCmc <= cmcMax; } + @Override + public FSkinProp getSkinProp() { + return skinProp; + } @Override public String toString() { From fac5b6c253a1eca505b5058f1571e5e7e9272112 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Tue, 14 Oct 2025 19:16:21 +0200 Subject: [PATCH 134/230] Support Turtles Forever (#8926) --- .../game/ability/effects/ChangeZoneEffect.java | 13 ++++++++++--- forge-gui/res/cardsfolder/b/burning_rune_demon.txt | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) 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 d2ef0a58164..885b6ab442d 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 @@ -1103,7 +1103,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (changeType.startsWith("EACH")) { String[] eachTypes = changeType.substring(5).split(" & "); for (String thisType : eachTypes) { - for (int i = 0; i < changeNum && destination != null; i++) { + for (int i = 0; i < changeNum; i++) { CardCollection thisList = (CardCollection) AbilityUtils.filterListByType(fetchList, thisType, sa); if (!chosenCards.isEmpty()) { thisList.removeAll(chosenCards); @@ -1138,7 +1138,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { // maybe prompt the user if they selected fewer than the maximum possible? } else { // one at a time - for (int i = 0; i < changeNum && destination != null; i++) { + for (int i = 0; i < changeNum; i++) { if (sa.hasParam("DifferentNames")) { for (Card c : chosenCards) { fetchList = CardLists.filter(fetchList, CardPredicates.sharesNameWith(c).negate()); @@ -1252,6 +1252,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { player.removeController(controlTimestamp); } + if (sa.hasParam("Exactly") && chosenCards.size() < changeNum) { + continue; + } + HiddenOriginChoices choices = new HiddenOriginChoices(); choices.searchedLibrary = searchedLibrary; choices.shuffleMandatory = shuffleMandatory; @@ -1281,7 +1285,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { Player decider = ObjectUtils.firstNonNull(chooser, player); for (final Card c : chosenCards) { - Card movedCard = null; + Card movedCard; final Zone originZone = game.getZoneOf(c); Map moveParams = AbilityKey.newMap(); moveParams.put(AbilityKey.FoundSearchingLibrary, searchedLibrary); @@ -1413,6 +1417,9 @@ public class ChangeZoneEffect extends SpellAbilityEffect { movedCard.addMayLookFaceDownExile(sa.getActivatingPlayer()); } } + else if (destination == null) { + movedCard = c; + } else { movedCard = game.getAction().moveTo(destination, c, 0, sa, moveParams); } diff --git a/forge-gui/res/cardsfolder/b/burning_rune_demon.txt b/forge-gui/res/cardsfolder/b/burning_rune_demon.txt index 8520c561a04..dc3310814a6 100644 --- a/forge-gui/res/cardsfolder/b/burning_rune_demon.txt +++ b/forge-gui/res/cardsfolder/b/burning_rune_demon.txt @@ -4,7 +4,7 @@ Types:Creature Demon Berserker PT:6/6 K:Flying T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters, you may search your library for exactly two cards not named Burning-Rune Demon that have different names. If you do, reveal those cards. An opponent chooses one of them. Put the chosen card into your hand and the other into your graveyard, then shuffle. -SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Card.!namedBurning-Rune Demon | ChangeNum$ 2 | DifferentNames$ True | RememberChanged$ True | Reveal$ True | Shuffle$ False | AILogic$ Intuition | SubAbility$ DBChoosePlayer +SVar:TrigChange:DB$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Card.!namedBurning-Rune Demon | ChangeNum$ 2 | DifferentNames$ True | Exactly$ True | RememberChanged$ True | Reveal$ True | Shuffle$ False | AILogic$ Intuition | SubAbility$ DBChoosePlayer SVar:DBChoosePlayer:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | ChoiceTitle$ Choose an opponent | SubAbility$ DBChangeZone1 SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.IsRemembered | Chooser$ ChosenPlayer | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to be put into the hand of CARDNAME's controller | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None SVar:DBChangeZone2:DB$ ChangeZoneAll | Origin$ Library | Destination$ Graveyard | ChangeType$ Card.IsRemembered | Shuffle$ True | StackDescription$ None | SubAbility$ DBCleanup From 6b17b650844411a1dd2d4e2282293510ad158f4c Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Wed, 15 Oct 2025 04:55:59 +0200 Subject: [PATCH 135/230] Update shriek_treblemaker.txt Closes #8929 --- forge-gui/res/cardsfolder/s/shriek_treblemaker.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/s/shriek_treblemaker.txt b/forge-gui/res/cardsfolder/s/shriek_treblemaker.txt index cfc11c43140..8e41bff7069 100644 --- a/forge-gui/res/cardsfolder/s/shriek_treblemaker.txt +++ b/forge-gui/res/cardsfolder/s/shriek_treblemaker.txt @@ -2,7 +2,6 @@ Name:Shriek, Treblemaker ManaCost:2 BR Types:Legendary Creature Mutant Villain PT:2/3 -K:Menace T:Mode$ Phase | Phase$ Main1 | ValidPlayer$ You | Execute$ DBImmediateTrig | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your first main phase, you may discard a card. When you do, target creature can't block this turn. SVar:DBImmediateTrig:AB$ ImmediateTrigger | Cost$ Discard<1/Card> | Execute$ TrigPump | TriggerDescription$ You may discard a card. When you do, target creature can't block this turn. SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | KW$ HIDDEN CARDNAME can't block. | TgtPrompt$ Select target creature. | IsCurse$ True From da92a93d1d660c2d22291dc0e90c8e248ca5d3bd Mon Sep 17 00:00:00 2001 From: tool4ever Date: Wed, 15 Oct 2025 07:12:50 +0200 Subject: [PATCH 136/230] Fix The Lost and the Damned not triggering for tokens (#8928) Co-authored-by: tool4EvEr --- .../main/java/forge/game/trigger/TriggerChangesZone.java | 8 ++++++++ forge-gui/res/cardsfolder/n/name_sticker_goblin.txt | 2 +- forge-gui/res/cardsfolder/t/the_lost_and_the_damned.txt | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java b/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java index 59a55fe5fc2..5bf497ec3c7 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerChangesZone.java @@ -92,6 +92,14 @@ public class TriggerChangesZone extends Trigger { } } + if (hasParam("ExcludedOrigins")) { + if (ArrayUtils.contains( + getParam("ExcludedOrigins").split(","), runParams.get(AbilityKey.Origin) + )) { + return false; + } + } + if (hasParam("ExcludedDestinations")) { if (ArrayUtils.contains( getParam("ExcludedDestinations").split(","), runParams.get(AbilityKey.Destination) diff --git a/forge-gui/res/cardsfolder/n/name_sticker_goblin.txt b/forge-gui/res/cardsfolder/n/name_sticker_goblin.txt index cf0d2543e4d..4ebdf69da62 100644 --- a/forge-gui/res/cardsfolder/n/name_sticker_goblin.txt +++ b/forge-gui/res/cardsfolder/n/name_sticker_goblin.txt @@ -2,7 +2,7 @@ Name:"Name Sticker" Goblin ManaCost:2 R Types:Creature Goblin Guest PT:2/2 -T:Mode$ ChangesZone | Origin$ Hand,Stack,Library | Destination$ Battlefield | ValidCard$ Card.Self | IsPresent$ Creature.YouCtrl+named"Name Sticker" Goblin | PresentCompare$ LE9 | Execute$ TrigRollDice | TriggerDescription$ When CARDNAME enters from anywhere other than a graveyard or exile, if it's on the battlefield and you control 9 or fewer creatures named "Name Sticker" Goblin, roll a 20-sided die. +T:Mode$ ChangesZone | ExcludedOrigins$ Graveyard,Exile | Destination$ Battlefield | ValidCard$ Card.Self | IsPresent$ Creature.YouCtrl+named"Name Sticker" Goblin | IsPresent2$ Card.Self | PresentCompare$ LE9 | Execute$ TrigRollDice | TriggerDescription$ When CARDNAME enters from anywhere other than a graveyard or exile, if it's on the battlefield and you control 9 or fewer creatures named "Name Sticker" Goblin, roll a 20-sided die. SVar:TrigRollDice:DB$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-6:DBMana1,7-14:DBMana2,15-20:DBMana3 SVar:DBMana1:DB$ Mana | Produced$ R | Amount$ 4 | SpellDescription$ 1-6 VERT Add {R}{R}{R}{R}. SVar:DBMana2:DB$ Mana | Produced$ R | Amount$ 5 | SpellDescription$ 7-14 VERT Add {R}{R}{R}{R}{R}. diff --git a/forge-gui/res/cardsfolder/t/the_lost_and_the_damned.txt b/forge-gui/res/cardsfolder/t/the_lost_and_the_damned.txt index 23f7d3498e7..9c7452b80fd 100644 --- a/forge-gui/res/cardsfolder/t/the_lost_and_the_damned.txt +++ b/forge-gui/res/cardsfolder/t/the_lost_and_the_damned.txt @@ -1,7 +1,7 @@ Name:The Lost and the Damned ManaCost:1 U R Types:Enchantment -T:Mode$ ChangesZone | Origin$ Graveyard,Exile,Command,Library | Destination$ Battlefield | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever a land you control enters from anywhere other than your hand or you cast a spell from anywhere other than your hand, create 3/3 red Spawn creature token. +T:Mode$ ChangesZone | ExcludedOrigins$ Hand | Destination$ Battlefield | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever a land you control enters from anywhere other than your hand or you cast a spell from anywhere other than your hand, create 3/3 red Spawn creature token. T:Mode$ SpellCast | ValidCard$ Card.!wasCastFromYourHand | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | Secondary$ True | TriggerDescription$ Whenever a land you control enters from anywhere other than your hand or you cast a spell from anywhere other than your hand, create 3/3 red Spawn creature token. SVar:TrigToken:DB$ Token | TokenScript$ r_3_3_spawn DeckHas:Ability$Token & Type$Spawn From 4827e10bd45e88d341fa0d5a01bffa2c4d6dbeaa Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Wed, 15 Oct 2025 08:10:15 +0200 Subject: [PATCH 137/230] Create turtles_forever.txt (#8927) --- forge-gui/res/cardsfolder/upcoming/turtles_forever.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/turtles_forever.txt diff --git a/forge-gui/res/cardsfolder/upcoming/turtles_forever.txt b/forge-gui/res/cardsfolder/upcoming/turtles_forever.txt new file mode 100644 index 00000000000..3feabc1db18 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/turtles_forever.txt @@ -0,0 +1,8 @@ +Name:Turtles Forever +ManaCost:3 W +Types:Instant +A:SP$ ChangeZone | Origin$ Library,Sideboard | ChangeType$ Creature.Legendary | ChangeNum$ 4 | RememberChanged$ True | Exactly$ True | Reveal$ True | Shuffle$ False | DifferentNames$ True | SubAbility$ DBChangeZone | SpellDescription$ Search your library and/or outside the game for exactly four legendary creature cards you own with different names, then reveal those cards. An opponent chooses two of them. Put the chosen cards into your hand and shuffle the rest into your library. +SVar:DBChangeZone:DB$ ChangeZone | Chooser$ Opponent | SelectPrompt$ Choose two cards for the hand | NoLooking$ True | Hidden$ True | Origin$ Library,Sideboard | Destination$ Hand | ChangeType$ Card.IsRemembered | ChangeNum$ 2 | Mandatory$ True | SelectPrompt$ Select two cards for the hand | Shuffle$ False | ForgetChanged$ True | SubAbility$ DBChangeZoneAll +SVar:DBChangeZoneAll:DB$ ChangeZoneAll | Origin$ Library,Sideboard | Destination$ Library | ChangeType$ Card.IsRemembered | Shuffle$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:Search your library and/or outside the game for exactly four legendary creature cards you own with different names, then reveal those cards. An opponent chooses two of them. Put the chosen cards into your hand and shuffle the rest into your library. From 04186de39718727253c034b71c22c3263c43a139 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Wed, 15 Oct 2025 11:19:33 +0200 Subject: [PATCH 138/230] Update bitterbloom_bearer.txt --- forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt b/forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt index 7e1c1cfa47d..890ac68bffd 100644 --- a/forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt +++ b/forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt @@ -6,7 +6,7 @@ K:Flash K:Flying T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigLoseLife | TriggerDescription$ At the beginning of your upkeep, you lose 1 life and create a 1/1 black Faerie Rogue creature token with flying. SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ 1 | SubAbility$ DBToken -SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ b_1_1_faerie_rogue_flying | TokenOwner$ You +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ ub_1_1_faerie_rogue_flying | TokenOwner$ You SVar:AICastPreference:NeverCastIfLifeBelow$ 4 DeckHas:Ability$Token Oracle:Flash\nFlying\nAt the beginning of your upkeep, you lose 1 life and create a 1/1 blue and black Faerie creature token with flying. From f9150bea34f9564284ef076c3e4748b31846dcaa Mon Sep 17 00:00:00 2001 From: tool4ever Date: Wed, 15 Oct 2025 11:30:41 +0200 Subject: [PATCH 139/230] Fix Bitterbloom Bearer (#8932) --- .../main/java/forge/game/ability/effects/MutateEffect.java | 2 +- forge-game/src/main/java/forge/game/zone/Zone.java | 3 +-- forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt | 4 ++-- forge-gui/res/tokenscripts/ub_1_1_faerie_flying.txt | 7 +++++++ 4 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 forge-gui/res/tokenscripts/ub_1_1_faerie_flying.txt diff --git a/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java index 4cf4203da68..3500663e6cf 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/MutateEffect.java @@ -80,7 +80,7 @@ public class MutateEffect extends SpellAbilityEffect { game.getTriggerHandler().clearActiveTriggers(target, null); game.getTriggerHandler().registerActiveTrigger(target, false); - game.getAction().moveTo(p.getZone(ZoneType.Merged), host, sa, AbilityKey.newMap()); + game.getAction().moveTo(p.getZone(ZoneType.Merged), host, sa); host.setTapped(target.isTapped()); host.setFlipped(target.isFlipped()); diff --git a/forge-game/src/main/java/forge/game/zone/Zone.java b/forge-game/src/main/java/forge/game/zone/Zone.java index 5d57174a4db..04d17364f3c 100644 --- a/forge-game/src/main/java/forge/game/zone/Zone.java +++ b/forge-game/src/main/java/forge/game/zone/Zone.java @@ -111,8 +111,7 @@ public class Zone implements java.io.Serializable, Iterable { final Zone oldZone = game.getZoneOf(c); final ZoneType zt = oldZone == null ? ZoneType.Stack : oldZone.getZoneType(); - // only if the zoneType differs from this - // don't go in there is its a control change + // don't go in there if its a control change if (zt != zoneType) { c.setTurnInController(getPlayer()); c.setTurnInZone(game.getPhaseHandler().getTurn()); diff --git a/forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt b/forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt index 890ac68bffd..a0a74d7de4f 100644 --- a/forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt +++ b/forge-gui/res/cardsfolder/upcoming/bitterbloom_bearer.txt @@ -4,9 +4,9 @@ Types:Creature Faerie Rogue PT:1/1 K:Flash K:Flying -T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigLoseLife | TriggerDescription$ At the beginning of your upkeep, you lose 1 life and create a 1/1 black Faerie Rogue creature token with flying. +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigLoseLife | TriggerDescription$ At the beginning of your upkeep, you lose 1 life and create a 1/1 black Faerie creature token with flying. SVar:TrigLoseLife:DB$ LoseLife | LifeAmount$ 1 | SubAbility$ DBToken -SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ ub_1_1_faerie_rogue_flying | TokenOwner$ You +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ ub_1_1_faerie_flying | TokenOwner$ You SVar:AICastPreference:NeverCastIfLifeBelow$ 4 DeckHas:Ability$Token Oracle:Flash\nFlying\nAt the beginning of your upkeep, you lose 1 life and create a 1/1 blue and black Faerie creature token with flying. diff --git a/forge-gui/res/tokenscripts/ub_1_1_faerie_flying.txt b/forge-gui/res/tokenscripts/ub_1_1_faerie_flying.txt new file mode 100644 index 00000000000..cc6178832c9 --- /dev/null +++ b/forge-gui/res/tokenscripts/ub_1_1_faerie_flying.txt @@ -0,0 +1,7 @@ +Name:Faerie Token +ManaCost:no cost +Colors:blue,black +Types:Creature Faerie +PT:1/1 +K:Flying +Oracle:Flying From 8ae1bcd2c53031fa24b9e000f2d20562398e0d7a Mon Sep 17 00:00:00 2001 From: tool4ever Date: Wed, 15 Oct 2025 13:22:00 +0200 Subject: [PATCH 140/230] Update mazes_end.txt Closes #8933 --- forge-gui/res/cardsfolder/m/mazes_end.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/m/mazes_end.txt b/forge-gui/res/cardsfolder/m/mazes_end.txt index 016ff08dcbd..ecee1c92509 100644 --- a/forge-gui/res/cardsfolder/m/mazes_end.txt +++ b/forge-gui/res/cardsfolder/m/mazes_end.txt @@ -6,6 +6,6 @@ SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. A:AB$ ChangeZone | Cost$ 3 T Return<1/CARDNAME> | ChangeType$ Gate | ChangeNum$ 1 | Origin$ Library | Destination$ Battlefield | AILogic$ MazesEnd | SubAbility$ DBWin | SpellDescription$ Search your library for a Gate card, put it onto the battlefield, then shuffle. If you control ten or more Gates with different names, you win the game. SVar:DBWin:DB$ WinsGame | Defined$ You | ConditionCheckSVar$ MazeGate | ConditionSVarCompare$ GE10 -SVar:X:Count$Valid Gate.YouCtrl$DifferentCardNames +SVar:MazeGate:Count$Valid Gate.YouCtrl$DifferentCardNames AI:RemoveDeck:Random Oracle:Maze's End enters tapped.\n{T}: Add {C}.\n{3}, {T}, Return Maze's End to its owner's hand: Search your library for a Gate card, put it onto the battlefield, then shuffle. If you control ten or more Gates with different names, you win the game. From 7e47a6ee2cb7550ba0b5bf234bcdb18ea2639216 Mon Sep 17 00:00:00 2001 From: Jetz Date: Wed, 15 Oct 2025 09:53:36 -0400 Subject: [PATCH 141/230] Move EventStyle specification closer to similar parameters. --- .../adventure/data/AdventureEventData.java | 25 ++++++++++++++++--- .../src/forge/adventure/scene/InnScene.java | 4 +-- .../util/AdventureEventController.java | 21 +++++----------- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java index 9faae9e41d4..bebf229ec65 100644 --- a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java +++ b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java @@ -96,11 +96,11 @@ public class AdventureEventData implements Serializable { } public AdventureEventData(Long seed, AdventureEventController.EventFormat selectedFormat) { - this(seed, selectedFormat, AdventureEventController.EventStyle.Bracket, pickCardBlockByFormat(selectedFormat)); + this(seed, selectedFormat, null, pickCardBlockByFormat(selectedFormat)); } - public AdventureEventData(Long seed, AdventureEventController.EventFormat selectedFormat, AdventureEventController.EventStyle style) { - this(seed, selectedFormat, style, pickCardBlockByFormat(selectedFormat)); + public AdventureEventData(Long seed, AdventureEventController.EventFormat selectedFormat, CardBlock cardBlock) { + this(seed, selectedFormat, null, cardBlock); } public AdventureEventData(Long seed, AdventureEventController.EventFormat selectedFormat, AdventureEventController.EventStyle style, CardBlock cardBlock) { @@ -121,6 +121,15 @@ public class AdventureEventData implements Serializable { setupJumpstartRewards(); } + if(style == null) { + // If the chosen event seed recommends a four-person pod, run it as a RoundRobin + if (getRecommendedPodSize(cardBlock) == 4) + style = AdventureEventController.EventStyle.RoundRobin; + else + style = AdventureEventController.EventStyle.Bracket; + } + this.style = style; + switch (style) { case Swiss: case Bracket: @@ -372,6 +381,10 @@ public class AdventureEventData implements Serializable { } } + public void generateParticipants() { + this.generateParticipants(getRecommendedPodSize(this.cardBlock) - 1); //-1 to account for the player + } + public void generateParticipants(int numberOfOpponents) { participants = new AdventureEventParticipant[numberOfOpponents + 1]; @@ -483,6 +496,12 @@ public class AdventureEventData implements Serializable { } } + public static int getRecommendedPodSize(CardBlock cardBlock) { + // Set can be null when it is only a meta set such as some Jumpstart events. + CardEdition firstSet = cardBlock.getSets().isEmpty() ? null : cardBlock.getSets().get(0); + return firstSet == null ? 8 : firstSet.getDraftOptions().getRecommendedPodSize(); + } + private void assignPlayerNames(BoosterDraft draft) { if(participants == null) return; diff --git a/forge-gui-mobile/src/forge/adventure/scene/InnScene.java b/forge-gui-mobile/src/forge/adventure/scene/InnScene.java index 4aa6346212a..0a42184c015 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/InnScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/InnScene.java @@ -158,7 +158,7 @@ public class InnScene extends UIScene { } } AdventureEventController controller = AdventureEventController.instance(); - localEvent = controller.createEvent(AdventureEventController.EventStyle.Bracket, localPointOfInterestId); + localEvent = controller.createEvent(localPointOfInterestId); if(localEvent != null) controller.initializeEvent(localEvent, localPointOfInterestId, localObjectId, changes); } @@ -166,7 +166,7 @@ public class InnScene extends UIScene { public static void replaceLocalEvent(AdventureEventController.EventFormat format, CardBlock cardBlock) { AdventurePlayer.current().getEvents().removeIf((data) -> data.sourceID.equals(localPointOfInterestId) && data.eventOrigin == localObjectId); AdventureEventController controller = AdventureEventController.instance(); - localEvent = controller.createEvent(AdventureEventController.EventStyle.Bracket, format, cardBlock, localPointOfInterestId); + localEvent = controller.createEvent(format, cardBlock, localPointOfInterestId); if(localEvent != null) controller.initializeEvent(localEvent, localPointOfInterestId, localObjectId, changes); } diff --git a/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java b/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java index 7a9cd093f88..99a373ca78c 100644 --- a/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java +++ b/forge-gui-mobile/src/forge/adventure/util/AdventureEventController.java @@ -4,7 +4,6 @@ import forge.StaticData; import forge.adventure.data.AdventureEventData; import forge.adventure.player.AdventurePlayer; import forge.adventure.pointofintrest.PointOfInterestChanges; -import forge.card.CardEdition; import forge.deck.Deck; import forge.item.BoosterPack; import forge.item.PaperCard; @@ -73,7 +72,7 @@ public class AdventureEventController implements Serializable { object = null; } - public AdventureEventData createEvent(EventStyle style, String pointID) { + public AdventureEventData createEvent(String pointID) { if (nextEventDate.containsKey(pointID) && nextEventDate.get(pointID) >= LocalDate.now().toEpochDay()) { // No event currently available here return null; @@ -86,9 +85,9 @@ public class AdventureEventController implements Serializable { // After a certain number of wins, stop offering Jumpstart events if (Current.player().getStatistic().totalWins() < 10 && random.nextInt(10) <= 2) { - e = new AdventureEventData(eventSeed, EventFormat.Jumpstart, style); + e = new AdventureEventData(eventSeed, EventFormat.Jumpstart); } else { - e = new AdventureEventData(eventSeed, EventFormat.Draft, style); + e = new AdventureEventData(eventSeed, EventFormat.Draft); } if (e.cardBlock == null) { @@ -98,9 +97,9 @@ public class AdventureEventController implements Serializable { return e; } - public AdventureEventData createEvent(EventStyle style, EventFormat format, CardBlock cardBlock, String pointID) { + public AdventureEventData createEvent(EventFormat format, CardBlock cardBlock, String pointID) { long eventSeed = getEventSeed(pointID); - AdventureEventData e = new AdventureEventData(eventSeed, format, style, cardBlock); + AdventureEventData e = new AdventureEventData(eventSeed, format, cardBlock); if(e.cardBlock == null) return null; return e; @@ -121,16 +120,8 @@ public class AdventureEventController implements Serializable { } public void initializeEvent(AdventureEventData e, String pointID, int eventOrigin, PointOfInterestChanges changes) { - // If the chosen event seed recommends a four-person pod, run it as a RoundRobin - // Set can be null when it is only a meta set such as some Jumpstart events. - ///TODO: Move some of this into the event - CardEdition firstSet = e.cardBlock.getSets().isEmpty() ? null : e.cardBlock.getSets().get(0); - int podSize = firstSet == null ? 8 : firstSet.getDraftOptions().getRecommendedPodSize(); - e.sourceID = pointID; e.eventOrigin = eventOrigin; - if(podSize == 4) - e.style = EventStyle.RoundRobin; AdventureEventData.PairingStyle pairingStyle; if (e.style == EventStyle.RoundRobin) { @@ -141,7 +132,7 @@ public class AdventureEventController implements Serializable { e.eventRules = new AdventureEventData.AdventureEventRules(e.format, pairingStyle, changes == null ? 1f : changes.getTownPriceModifier()); - e.generateParticipants(podSize - 1); //-1 to account for the player + e.generateParticipants(); AdventurePlayer.current().addEvent(e); nextEventDate.put(pointID, LocalDate.now().toEpochDay() + new Random().nextInt(2)); //next local event availability date From 77dae2418fae91c1c274772124e6d39d4a343349 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Thu, 16 Oct 2025 10:05:41 +0200 Subject: [PATCH 142/230] Clean up Ruin Grinder (#8930) --- .../game/ability/SpellAbilityEffect.java | 4 -- .../game/ability/effects/DiscardEffect.java | 63 ++++++++++++------- forge-gui/res/cardsfolder/r/ruin_grinder.txt | 11 ++-- .../res/cardsfolder/s/sail_into_the_west.txt | 9 +-- forge-gui/res/cardsfolder/s/snort.txt | 11 ++-- .../res/cardsfolder/w/will_of_the_jeskai.txt | 9 +-- 6 files changed, 54 insertions(+), 53 deletions(-) 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 f9e8358fa23..b63c7e759e7 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -891,10 +891,6 @@ public abstract class SpellAbilityEffect { runParams.put(AbilityKey.Cause, sa); runParams.put(AbilityKey.DiscardedBefore, discardedBefore.get(p)); p.getGame().getTriggerHandler().runTrigger(TriggerType.DiscardedAll, runParams, false); - - if (sa.hasParam("RememberDiscardingPlayers")) { - sa.getHostCard().addRemembered(p); - } } } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java index b0cbd11fc25..e9049e4e859 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DiscardEffect.java @@ -143,12 +143,12 @@ public class DiscardEffect extends SpellAbilityEffect { continue; } - boolean runDiscard = !sa.hasParam("Optional") - || p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, sa.getParam("DiscardMessage"), null); - if (runDiscard) { - toBeDiscarded = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedCards"), sa); - toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa); + if (sa.hasParam("Optional") && !p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, sa.getParam("DiscardMessage"), null)) { + continue; } + + toBeDiscarded = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedCards"), sa); + toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa); } if (mode.equals("Hand")) { @@ -159,6 +159,17 @@ public class DiscardEffect extends SpellAbilityEffect { continue; } + String message = Localizer.getInstance().getMessage("lblDoYouWantDiscardYourHand"); + if (sa.hasParam("Optional")) { + if (!p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message, null)) { + continue; + } else if (discarders.size() > 1) { + // later players need to know the decision + message = Localizer.getInstance().getMessage("lblPlayerKeepNCardsHand", p.getName(), String.valueOf(p.getZone(ZoneType.Hand).size())); + game.getAction().notifyOfValue(sa, p, message, p); + } + } + toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa); } @@ -172,16 +183,17 @@ public class DiscardEffect extends SpellAbilityEffect { if (!p.canDiscardBy(sa, true)) { continue; } + String message = Localizer.getInstance().getMessage("lblWouldYouLikeRandomDiscardTargetCard", String.valueOf(numCards)); - boolean runDiscard = !sa.hasParam("Optional") || p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message, null); - - if (runDiscard) { - final String valid = sa.getParamOrDefault("DiscardValid", "Card"); - List list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), valid, source.getController(), source, sa); - - toBeDiscarded = new CardCollection(Aggregates.random(list, numCards)); - toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa); + if (sa.hasParam("Optional") && !p.getController().confirmAction(sa, PlayerActionConfirmMode.Random, message, null)) { + continue; } + + final String valid = sa.getParamOrDefault("DiscardValid", "Card"); + List list = CardLists.getValidCards(p.getCardsIn(ZoneType.Hand), valid, source.getController(), source, sa); + + toBeDiscarded = new CardCollection(Aggregates.random(list, numCards)); + toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa); } else if (mode.equals("TgtChoose") && sa.hasParam("UnlessType")) { if (!p.canDiscardBy(sa, true)) { @@ -194,30 +206,27 @@ public class DiscardEffect extends SpellAbilityEffect { } } else if (mode.equals("RevealDiscardAll")) { - // Reveal final CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand); - for (final Player opp : p.getAllOtherPlayers()) { - opp.getController().reveal(dPHand, ZoneType.Hand, p, Localizer.getInstance().getMessage("lblReveal") + " "); + if (dPHand.isEmpty()) { + continue; } + game.getAction().reveal(dPHand, ZoneType.Hand, p, true, Localizer.getInstance().getMessage("lblReveal") + " "); + if (!p.canDiscardBy(sa, true)) { continue; } String valid = sa.getParamOrDefault("DiscardValid", "Card"); - if (valid.contains("X")) { - valid = TextUtil.fastReplace(valid, - "X", Integer.toString(AbilityUtils.calculateAmount(source, "X", sa))); - } - toBeDiscarded = CardLists.getValidCards(dPHand, valid, source.getController(), source, sa); toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa); } else if (mode.endsWith("YouChoose") || mode.endsWith("TgtChoose")) { CardCollectionView dPHand = p.getCardsIn(ZoneType.Hand); - if (dPHand.isEmpty()) - continue; // for loop over players + if (dPHand.isEmpty()) { + continue; + } if (sa.hasParam("RevealNumber")) { int amount = AbilityUtils.calculateAmount(source, sa.getParam("RevealNumber"), sa); @@ -250,6 +259,10 @@ public class DiscardEffect extends SpellAbilityEffect { toBeDiscarded = max == 0 ? CardCollection.EMPTY : chooser.getController().chooseCardsToDiscardFrom(p, sa, validCards, min, max); + if (toBeDiscarded.isEmpty()) { + continue; + } + toBeDiscarded = GameActionUtil.orderCardsByTheirOwners(game, toBeDiscarded, ZoneType.Graveyard, sa); if (mode.startsWith("Reveal") && p != chooser) { @@ -259,6 +272,10 @@ public class DiscardEffect extends SpellAbilityEffect { discardedMap.put(p, toBeDiscarded); } + if (sa.hasParam("RememberDiscardingPlayers")) { + source.addRemembered(discardedMap.keySet()); + } + Map params = AbilityKey.newMap(); CardZoneTable table = AbilityKey.addCardZoneTableParams(params, sa); diff --git a/forge-gui/res/cardsfolder/r/ruin_grinder.txt b/forge-gui/res/cardsfolder/r/ruin_grinder.txt index df579106569..2b8ec34b3db 100644 --- a/forge-gui/res/cardsfolder/r/ruin_grinder.txt +++ b/forge-gui/res/cardsfolder/r/ruin_grinder.txt @@ -3,13 +3,10 @@ ManaCost:5 R Types:Artifact Creature Construct PT:7/4 K:Menace -T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigChoose | TriggerDescription$ When CARDNAME dies, each player may discard their hand and draw seven cards. -SVar:TrigChoose:DB$ GenericChoice | TempRemember$ Chooser | ShowChoice$ ExceptSelf | Defined$ Player | Choices$ Discard,No | SubAbility$ DBDiscard -SVar:Discard:DB$ Pump | Defined$ Remembered | NoteCards$ Self | NoteCardsFor$ Discard | SpellDescription$ I will discard my hand. -SVar:No:DB$ Pump | SpellDescription$ I will NOT discard my hand. -SVar:DBDiscard:DB$ Discard | Mode$ Hand | Defined$ Player.NotedForDiscard | SubAbility$ DBDraw -SVar:DBDraw:DB$ Draw | Defined$ Player.NotedForDiscard | NumCards$ 7 | SubAbility$ DBClearNotes -SVar:DBClearNotes:DB$ Pump | Defined$ Player | ClearNotedCardsFor$ Discard +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigDiscard | TriggerDescription$ When CARDNAME dies, each player may discard their hand and draw seven cards. +SVar:TrigDiscard:DB$ Discard | Mode$ Hand | Defined$ Player | Optional$ True | RememberDiscardingPlayers$ True | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw | Defined$ Remembered | NumCards$ 7 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True K:TypeCycling:Mountain:2 DeckHas:Ability$Discard Oracle:Menace\nWhen Ruin Grinder dies, each player may discard their hand and draw seven cards.\nMountaincycling {2} ({2}, Discard this card: Search your library for a Mountain card, reveal it, put it into your hand, then shuffle.) diff --git a/forge-gui/res/cardsfolder/s/sail_into_the_west.txt b/forge-gui/res/cardsfolder/s/sail_into_the_west.txt index df646180798..73229a8e0f6 100644 --- a/forge-gui/res/cardsfolder/s/sail_into_the_west.txt +++ b/forge-gui/res/cardsfolder/s/sail_into_the_west.txt @@ -4,11 +4,8 @@ Types:Instant A:SP$ Vote | Defined$ Player | Choices$ DBChangeZone,DBWheel | VoteTiedAbility$ DBWheel | StackDescription$ REP you, each player votes_{p:You}, {p:Player} each vote & each player returns_{p:Player} each return & each player may_{p:Player} may each | SpellDescription$ Will of the council — Starting with you, each player votes for return or embark. If return gets more votes, each player returns up to two cards from their graveyard to their hand, then you exile CARDNAME. If embark gets more votes or the vote is tied, each player may discard their hand and draw seven cards. SVar:DBChangeZone:DB$ ChangeZone | Defined$ Player | Origin$ Graveyard | Destination$ Hand | ChangeNum$ 2 | Hidden$ True | SubAbility$ ExileSelf | SpellDescription$ If return gets more votes, each player returns up to two cards from their graveyard to their hand, then you exile CARDNAME. SVar:ExileSelf:DB$ ChangeZone | Origin$ Stack | Destination$ Exile -SVar:DBWheel:DB$ GenericChoice | TempRemember$ Chooser | ShowChoice$ ExceptSelf | Defined$ Player | Choices$ Discard,No | SubAbility$ DBDiscard | SpellDescription$ If embark gets more votes or the vote is tied, each player may discard their hand and draw seven cards. -SVar:Discard:DB$ Pump | Defined$ Remembered | NoteCards$ Self | NoteCardsFor$ Discard | SpellDescription$ I will discard my hand. -SVar:No:DB$ Pump | SpellDescription$ I will NOT discard my hand. -SVar:DBDiscard:DB$ Discard | Mode$ Hand | Defined$ Player.NotedForDiscard | SubAbility$ DBDraw -SVar:DBDraw:DB$ Draw | Defined$ Player.NotedForDiscard | NumCards$ 7 | SubAbility$ DBClearNotes -SVar:DBClearNotes:DB$ Pump | Defined$ Player | ClearNotedCardsFor$ Discard +SVar:DBWheel:DB$ Discard | Mode$ Hand | Defined$ Player | Optional$ True | RememberDiscardingPlayers$ True | SubAbility$ DBDraw | SpellDescription$ If embark gets more votes or the vote is tied, each player may discard their hand and draw seven cards. +SVar:DBDraw:DB$ Draw | Defined$ Remembered | NumCards$ 7 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Ability$Discard|Graveyard Oracle:Will of the council — Starting with you, each player votes for return or embark. If return gets more votes, each player returns up to two cards from their graveyard to their hand, then you exile Sail into the West. If embark gets more votes or the vote is tied, each player may discard their hand and draw seven cards. diff --git a/forge-gui/res/cardsfolder/s/snort.txt b/forge-gui/res/cardsfolder/s/snort.txt index f7c0a69e02a..2127fc2df2e 100644 --- a/forge-gui/res/cardsfolder/s/snort.txt +++ b/forge-gui/res/cardsfolder/s/snort.txt @@ -1,12 +1,9 @@ Name:Snort ManaCost:3 R Types:Sorcery -A:SP$ GenericChoice | TempRemember$ Chooser | ShowChoice$ ExceptSelf | Defined$ Player | Choices$ Discard,No | SubAbility$ DBDiscard | StackDescription$ SpellDescription | SpellDescription$ Each player may discard their hand and draw five cards. Then CARDNAME deals 5 damage to each opponent who discarded their hand this way. -SVar:Discard:DB$ Pump | Defined$ Remembered | NoteCards$ Self | NoteCardsFor$ Discard | SpellDescription$ I will discard my hand. -SVar:No:DB$ Pump | SpellDescription$ I will NOT discard my hand. -SVar:DBDiscard:DB$ Discard | Mode$ Hand | Defined$ Player.NotedForDiscard | SubAbility$ DBDraw -SVar:DBDraw:DB$ Draw | Defined$ Player.NotedForDiscard | NumCards$ 5 | SubAbility$ DBDmg -SVar:DBDmg:DB$ DamageAll | ValidPlayers$ Player.Opponent+NotedForDiscard | NumDmg$ 5 | SubAbility$ DBClearNotes | StackDescription$ None -SVar:DBClearNotes:DB$ Pump | Defined$ Player | ClearNotedCardsFor$ Discard | StackDescription$ None +A:SP$ Discard | Mode$ Hand | Defined$ Player | Optional$ True | RememberDiscardingPlayers$ True | SubAbility$ DBDraw | StackDescription$ SpellDescription | SpellDescription$ Each player may discard their hand and draw five cards. Then CARDNAME deals 5 damage to each opponent who discarded their hand this way. +SVar:DBDraw:DB$ Draw | Defined$ Remembered | NumCards$ 5 | SubAbility$ DBDmg +SVar:DBDmg:DB$ DamageAll | ValidPlayers$ Opponent.IsRemembered | NumDmg$ 5 | SubAbility$ DBCleanup | StackDescription$ None +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True K:Flashback:5 R Oracle:Each player may discard their hand and draw five cards. Then Snort deals 5 damage to each opponent who discarded their hand this way.\nFlashback {5}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.) diff --git a/forge-gui/res/cardsfolder/w/will_of_the_jeskai.txt b/forge-gui/res/cardsfolder/w/will_of_the_jeskai.txt index 2eb6ff8fef8..86c231d0e28 100644 --- a/forge-gui/res/cardsfolder/w/will_of_the_jeskai.txt +++ b/forge-gui/res/cardsfolder/w/will_of_the_jeskai.txt @@ -2,12 +2,9 @@ Name:Will of the Jeskai ManaCost:3 R Types:Sorcery A:SP$ Charm | MinCharmNum$ 1 | CharmNum$ Count$Compare Y GE1.2.1 | Choices$ DBWheel,DBFlames | AdditionalDescription$ . If you control a commander as you cast this spell, you may choose both instead. -SVar:DBWheel:DB$ GenericChoice | TempRemember$ Chooser | ShowChoice$ ExceptSelf | Defined$ Player | Choices$ Discard,No | SubAbility$ DBDiscard | SpellDescription$ Each player may discard their hand and draw five cards. -SVar:Discard:DB$ Pump | Defined$ Remembered | NoteCards$ Self | NoteCardsFor$ Discard | SpellDescription$ I will discard my hand. -SVar:No:DB$ Pump | SpellDescription$ I will NOT discard my hand. -SVar:DBDiscard:DB$ Discard | Mode$ Hand | Defined$ Player.NotedForDiscard | SubAbility$ DBDraw -SVar:DBDraw:DB$ Draw | Defined$ Player.NotedForDiscard | NumCards$ 5 | SubAbility$ DBClearNotes -SVar:DBClearNotes:DB$ Pump | Defined$ Player | ClearNotedCardsFor$ Discard | StackDescription$ None +SVar:DBWheel:DB$ Discard | Mode$ Hand | Defined$ Player | Optional$ True | RememberDiscardingPlayers$ True | SubAbility$ DBDraw | SpellDescription$ If embark gets more votes or the vote is tied, each player may discard their hand and draw seven cards. +SVar:DBDraw:DB$ Draw | Defined$ Remembered | NumCards$ 7 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:DBFlames:DB$ PumpAll | ValidCards$ Instant.YouCtrl,Sorcery.YouCtrl | KW$ Flashback | PumpZone$ Graveyard | SpellDescription$ Each instant and sorcery card in your graveyard gains flashback until end of turn. The flashback cost is equal to its mana cost. SVar:Y:Count$Valid Card.IsCommander+YouCtrl DeckHas:Ability$Discard From f64701d039b2c75457b71c0e7fea0d76c7f70781 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Thu, 16 Oct 2025 11:04:40 +0200 Subject: [PATCH 143/230] Raphael, Tag Team Tough and support (#8936) --- forge-game/src/main/java/forge/game/Game.java | 5 +++ .../java/forge/game/ability/AbilityUtils.java | 5 +++ .../forge/game/card/CardDamageHistory.java | 10 ++++- .../forge/game/player/PlayerProperty.java | 38 ++++++++++--------- .../forge/game/trigger/TriggerDamageDone.java | 18 --------- ...nsumes_all_vessel_of_the_all_consuming.txt | 2 +- .../res/cardsfolder/i/impact_resonance.txt | 2 +- .../upcoming/raphael_tag_team_tough.txt | 9 +++++ 8 files changed, 49 insertions(+), 40 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/raphael_tag_team_tough.txt diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index c9f8171f7c2..b3335d35ea9 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -1292,6 +1292,11 @@ public class Game { return dmgList; } + public int getSingleMaxDamageDoneThisTurn() { + return globalDamageHistory.stream().flatMap(cdh -> cdh.getAllDmgInstances().stream()). + mapToInt(dmg -> dmg.getLeft()).max().orElse(0); + } + public void addGlobalDamageHistory(CardDamageHistory cdh, Pair dmg, Card source, GameEntity target) { globalDamageHistory.add(cdh); damageThisTurnLKI.put(dmg, Pair.of(source, target)); 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 2f8d310fcc7..58ed6ff4e81 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -2426,6 +2426,11 @@ public class AbilityUtils { return doXMath(sum, expr, c, ctb); } + if (sq[0].equals("SingleMaxDamageThisTurn")) { + int sum = game.getSingleMaxDamageDoneThisTurn(); + return doXMath(sum, expr, c, ctb); + } + if (sq[0].contains("DamageThisTurn")) { String[] props = l[0].split(" "); Boolean isCombat = null; diff --git a/forge-game/src/main/java/forge/game/card/CardDamageHistory.java b/forge-game/src/main/java/forge/game/card/CardDamageHistory.java index 03dddee587f..8e204669d23 100644 --- a/forge-game/src/main/java/forge/game/card/CardDamageHistory.java +++ b/forge-game/src/main/java/forge/game/card/CardDamageHistory.java @@ -1,6 +1,5 @@ package forge.game.card; - import com.google.common.collect.Lists; import forge.game.CardTraitBase; import forge.game.GameEntity; @@ -250,6 +249,9 @@ public class CardDamageHistory { } public int getDamageDoneThisTurn(Boolean isCombat, boolean anyIsEnough, String validSourceCard, String validTargetEntity, Card source, Player sourceController, CardTraitBase ctb) { + return getDamageDoneThisTurn(isCombat, anyIsEnough, false, validSourceCard, validTargetEntity, source, sourceController, ctb); + } + public int getDamageDoneThisTurn(Boolean isCombat, boolean anyIsEnough, boolean times, String validSourceCard, String validTargetEntity, Card source, Player sourceController, CardTraitBase ctb) { int sum = 0; for (Pair damage : damageDoneThisTurn) { Pair sourceToTarget = sourceController.getGame().getDamageLKI(damage); @@ -265,7 +267,7 @@ public class CardDamageHistory { continue; } } - sum += damage.getLeft(); + sum += times ? 1 : damage.getLeft(); if (anyIsEnough) { break; } @@ -273,6 +275,10 @@ public class CardDamageHistory { return sum; } + public List> getAllDmgInstances() { + return damageDoneThisTurn; + } + public void newTurn() { attackedThisTurn.clear(); attackedBattleThisTurn = false; diff --git a/forge-game/src/main/java/forge/game/player/PlayerProperty.java b/forge-game/src/main/java/forge/game/player/PlayerProperty.java index 8407d9ee065..bafe3ebdd2b 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerProperty.java +++ b/forge-game/src/main/java/forge/game/player/PlayerProperty.java @@ -132,32 +132,34 @@ public class PlayerProperty { return false; } } else if (property.startsWith("wasDealt")) { - boolean found = false; - String validCard = null; Boolean combat = null; if (property.contains("CombatDamage")) { combat = true; } - if (property.contains("ThisTurnBySource")) { - found = source.getDamageHistory().getDamageDoneThisTurn(combat, validCard == null, validCard, "You", source, player, spellAbility) > 0; - } else { - String comp = "GE"; - int right = 1; - int numValid = 0; + String validCard = null; + String comp = "GE"; + int right = 1; - if (property.contains("ThisTurnBy")) { - String[] props = property.split(" "); + if (property.contains("ThisTurnBy")) { + int idx = 2; + String[] props = property.split(" "); + if (property.contains("BySource")) { + idx--; + } else { validCard = props[1]; - if (props.length > 2) { - comp = props[2].substring(0, 2); - right = AbilityUtils.calculateAmount(source, props[2].substring(2), spellAbility); - } } - - numValid = game.getDamageDoneThisTurn(combat, validCard == null, validCard, "You", source, player, spellAbility).size(); - found = Expressions.compare(numValid, comp, right); + if (props.length > idx) { + comp = props[idx].substring(0, 2); + right = AbilityUtils.calculateAmount(source, props[idx].substring(2), spellAbility); + } } - if (!found) { + int result; + if (property.contains("BySource")) { + result = source.getDamageHistory().getDamageDoneThisTurn(combat, false, property.contains("SourceTimes"), null, "You", source, player, spellAbility); + } else { + result = game.getDamageDoneThisTurn(combat, validCard == null, validCard, "You", source, player, spellAbility).size(); + } + if (!Expressions.compare(result, comp, right)) { return false; } } else if (property.equals("attackedBySourceThisCombat")) { diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDamageDone.java b/forge-game/src/main/java/forge/game/trigger/TriggerDamageDone.java index 371e764ec79..91fc5d8fc36 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerDamageDone.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerDamageDone.java @@ -22,7 +22,6 @@ import java.util.Map; import forge.game.ability.AbilityKey; import forge.game.card.Card; import forge.game.card.CardCopyService; -import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.util.Expressions; import forge.util.Localizer; @@ -105,23 +104,6 @@ public class TriggerDamageDone extends Trigger { } } - if (hasParam("DamageToTargetThisTurnCondition")) { - final String fullParam = getParam("DamageToTargetThisTurnCondition"); - - final String operator = fullParam.substring(0, 2); - final int operand = Integer.parseInt(fullParam.substring(2)); - final Object target = runParams.get(AbilityKey.DamageTarget); - final Card source = (Card) runParams.get(AbilityKey.DamageSource); - - if (target instanceof Player trigTgt) { - if (!Expressions.compare(trigTgt.getAssignedDamage(null, source), operator, operand)) { - return false; - } - } else { - return false; //for now this is only used to check damage assigned to a player - } - } - return true; } diff --git a/forge-gui/res/cardsfolder/h/hidetsugu_consumes_all_vessel_of_the_all_consuming.txt b/forge-gui/res/cardsfolder/h/hidetsugu_consumes_all_vessel_of_the_all_consuming.txt index 1abc8404ce5..4820ae4311c 100644 --- a/forge-gui/res/cardsfolder/h/hidetsugu_consumes_all_vessel_of_the_all_consuming.txt +++ b/forge-gui/res/cardsfolder/h/hidetsugu_consumes_all_vessel_of_the_all_consuming.txt @@ -20,7 +20,7 @@ PT:3/3 K:Trample T:Mode$ DamageDealtOnce | ValidSource$ Card.Self | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals damage, put a +1/+1 counter on it. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 -T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ TrigLose | DamageToTargetThisTurnCondition$ GE10 | TriggerDescription$ Whenever CARDNAME deals damage to a player, if it has dealt 10 or more damage to that player this turn, they lose the game. +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player.wasDealtDamageThisTurnBySource GE10 | Execute$ TrigLose | TriggerDescription$ Whenever CARDNAME deals damage to a player, if it has dealt 10 or more damage to that player this turn, they lose the game. SVar:TrigLose:DB$ LosesGame | Defined$ TriggeredTarget DeckHas:Ability$Counters Oracle:Trample\nWhenever Vessel of the All-Consuming deals damage, put a +1/+1 counter on it.\nWhenever Vessel of the All-Consuming deals damage to a player, if it has dealt 10 or more damage to that player this turn, they lose the game. diff --git a/forge-gui/res/cardsfolder/i/impact_resonance.txt b/forge-gui/res/cardsfolder/i/impact_resonance.txt index c3c78e332f2..c29cec314f9 100644 --- a/forge-gui/res/cardsfolder/i/impact_resonance.txt +++ b/forge-gui/res/cardsfolder/i/impact_resonance.txt @@ -2,6 +2,6 @@ Name:Impact Resonance ManaCost:1 R Types:Instant A:SP$ DealDamage | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ MaxTgts | NumDmg$ X | DividedAsYouChoose$ X | SpellDescription$ CARDNAME deals X damage divided as you choose among any number of target creatures, where X is the greatest amount of damage dealt by a source to a permanent or player this turn. -SVar:X:Count$MaxDamageThisTurn Card Permanent,Player +SVar:X:Count$SingleMaxDamageThisTurn SVar:MaxTgts:Count$Valid Creature Oracle:Impact Resonance deals X damage divided as you choose among any number of target creatures, where X is the greatest amount of damage dealt by a source to a permanent or player this turn. diff --git a/forge-gui/res/cardsfolder/upcoming/raphael_tag_team_tough.txt b/forge-gui/res/cardsfolder/upcoming/raphael_tag_team_tough.txt new file mode 100644 index 00000000000..cd2d5515265 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/raphael_tag_team_tough.txt @@ -0,0 +1,9 @@ +Name:Raphael, Tag Team Tough +ManaCost:4 R R +Types:Legendary Creature Mutant Ninja Turtle +PT:5/6 +K:Menace +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player.wasDealtCombatDamageThisTurnBySourceTimes EQ1 | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ Whenever NICKNAME deals combat damage to a player for the first time each turn, untap all attacking creatures. After this combat phase, there is an additional combat phase. +SVar:TrigUntap:DB$ UntapAll | ValidCards$ Creature.attacking | SubAbility$ DBAddCombat +SVar:DBAddCombat:DB$ AddPhase | ExtraPhase$ Combat | AfterPhase$ EndCombat +Oracle:Menace (This creature can’t be blocked except by two or more creatures.)\nWhenever Raphael deals combat damage to a player for the first time each turn, untap all attacking creatures. After this combat phase, there is an additional combat phase. From 4a616c8ac6a2dc7a2e635afb47046a5698f99c72 Mon Sep 17 00:00:00 2001 From: Jetz Date: Thu, 16 Oct 2025 09:09:08 -0400 Subject: [PATCH 144/230] Move round calculation to after participant amount is set. --- .../adventure/data/AdventureEventData.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java index bebf229ec65..d6246d1381a 100644 --- a/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java +++ b/forge-gui-mobile/src/forge/adventure/data/AdventureEventData.java @@ -129,16 +129,6 @@ public class AdventureEventData implements Serializable { style = AdventureEventController.EventStyle.Bracket; } this.style = style; - - switch (style) { - case Swiss: - case Bracket: - this.rounds = (participants.length / 2) - 1; - break; - case RoundRobin: - this.rounds = participants.length - 1; - break; - } } public void setEventSeed(long seed) { @@ -494,6 +484,16 @@ public class AdventureEventData implements Serializable { } } } + + switch (this.style) { + case Swiss: + case Bracket: + this.rounds = (participants.length / 2) - 1; + break; + case RoundRobin: + this.rounds = participants.length - 1; + break; + } } public static int getRecommendedPodSize(CardBlock cardBlock) { From 32fb982d0ddf8f092306055666a7942a84e78d6e Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:29:08 +0200 Subject: [PATCH 145/230] Update the_lost_and_the_damned.txt (#8931) --- forge-gui/res/cardsfolder/t/the_lost_and_the_damned.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/cardsfolder/t/the_lost_and_the_damned.txt b/forge-gui/res/cardsfolder/t/the_lost_and_the_damned.txt index 9c7452b80fd..9701a2bca88 100644 --- a/forge-gui/res/cardsfolder/t/the_lost_and_the_damned.txt +++ b/forge-gui/res/cardsfolder/t/the_lost_and_the_damned.txt @@ -2,6 +2,7 @@ Name:The Lost and the Damned ManaCost:1 U R Types:Enchantment T:Mode$ ChangesZone | ExcludedOrigins$ Hand | Destination$ Battlefield | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever a land you control enters from anywhere other than your hand or you cast a spell from anywhere other than your hand, create 3/3 red Spawn creature token. +T:Mode$ ChangesZone | Origin$ Hand | Destination$ Battlefield | ValidCard$ Land.YouCtrl+YouDontOwn | TriggerZones$ Battlefield | Execute$ TrigToken | Secondary$ True | TriggerDescription$ Whenever a land you control enters from anywhere other than your hand or you cast a spell from anywhere other than your hand, create 3/3 red Spawn creature token. T:Mode$ SpellCast | ValidCard$ Card.!wasCastFromYourHand | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | Secondary$ True | TriggerDescription$ Whenever a land you control enters from anywhere other than your hand or you cast a spell from anywhere other than your hand, create 3/3 red Spawn creature token. SVar:TrigToken:DB$ Token | TokenScript$ r_3_3_spawn DeckHas:Ability$Token & Type$Spawn From b9008b7f1211812aab23aa81c296ef46b4990e86 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Fri, 17 Oct 2025 10:50:01 +0200 Subject: [PATCH 146/230] Update heroes_in_a_half_shell.txt --- forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt b/forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt index 61d5aed1558..9b4d04f04d7 100644 --- a/forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt +++ b/forge-gui/res/cardsfolder/upcoming/heroes_in_a_half_shell.txt @@ -7,6 +7,6 @@ K:Menace K:Trample K:Haste T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Mutant.YouCtrl,Ninja.YouCtrl,Turtle.YouCtrl | ValidTarget$ Player | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever one or more Mutants, Ninjas, and/or Turtles you control deal combat damage to a player, put a +1/+1 counter on each of those creatures and draw a card. -SVar:DBPutCounter:DB$ PutCounter | Defined$ TriggeredSources | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw +SVar:TrigPutCounter:DB$ PutCounter | Defined$ TriggeredSources | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw SVar:DBDraw:DB$ Draw -Oracle:Vigilance, menace, trample, haste\nWhenever one or more Mutants, Ninjas, and/or Turtles you control deal combat damage to a player, put a +1/+1 counter on each of those creatures and draw a card. \ No newline at end of file +Oracle:Vigilance, menace, trample, haste\nWhenever one or more Mutants, Ninjas, and/or Turtles you control deal combat damage to a player, put a +1/+1 counter on each of those creatures and draw a card. From 5588268c743a1b8f2cd878014011c20c65359842 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Fri, 17 Oct 2025 13:59:55 +0200 Subject: [PATCH 147/230] Fix complex targeting restriction in mysterious_stranger (#8940) --- forge-gui/res/cardsfolder/m/mysterious_stranger.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/m/mysterious_stranger.txt b/forge-gui/res/cardsfolder/m/mysterious_stranger.txt index c4c5f93cf62..894314e415c 100644 --- a/forge-gui/res/cardsfolder/m/mysterious_stranger.txt +++ b/forge-gui/res/cardsfolder/m/mysterious_stranger.txt @@ -8,6 +8,6 @@ SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | Va SVar:ChooseRandom:DB$ ChooseCard | Choices$ Card.IsRemembered | ChoiceZone$ Exile | Defined$ You | Amount$ 1 | AtRandom$ True | ConditionCheckSVar$ CountExiled | ConditionSVarCompare$ GE2 | SubAbility$ DBPlay SVar:DBPlay:DB$ Play | Valid$ Card.ChosenCard | ValidSA$ Spell | ValidZone$ Exile | WithoutManaCost$ True | Optional$ True | CopyCard$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenCard$ True -SVar:OneEach:PlayerCountPlayers$Amount +SVar:OneEach:PlayerCountPlayers$HasPropertyHasCardsInGraveyard_Instant,Sorcery_GE1 SVar:CountExiled:Count$ValidExile Card.IsRemembered Oracle:Flash\nWhen Mysterious Stranger enters, for each graveyard with an instant or sorcery card in it, exile target instant or sorcery card from that graveyard. If two or more cards are exiled this way, choose one of them at random and copy it. You may cast the copy without paying its mana cost. From bbf5319815d706851df98fa02e79866921ed224b Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Fri, 17 Oct 2025 17:55:46 +0200 Subject: [PATCH 148/230] KeywordCollection: Use linkedHashSetValues to prevent duplicates (#8941) --- .../src/main/java/forge/game/keyword/KeywordCollection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java index 4a5e7e37e6a..63202efbdca 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java @@ -15,7 +15,7 @@ public class KeywordCollection implements Iterable { private transient KeywordCollectionView view; // don't use enumKeys it causes a slow down private final Multimap map = MultimapBuilder.hashKeys() - .arrayListValues().build(); + .linkedHashSetValues().build(); public KeywordCollection() { super(); From a0a3546691711aaa1f338d2d65298975fa2f4237 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Fri, 17 Oct 2025 20:32:01 +0200 Subject: [PATCH 149/230] GameObject: turn into an interface --- forge-game/src/main/java/forge/game/CardTraitBase.java | 2 +- forge-game/src/main/java/forge/game/GameEntity.java | 2 +- forge-game/src/main/java/forge/game/GameObject.java | 10 +++++----- .../src/main/java/forge/game/card/CardState.java | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index 4d84640e782..109d007a1ae 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -36,7 +36,7 @@ import forge.util.ITranslatable; * Base class for Triggers,ReplacementEffects and StaticAbilities. * */ -public abstract class CardTraitBase extends GameObject implements IHasCardView, IHasSVars { +public abstract class CardTraitBase implements GameObject, IHasCardView, IHasSVars { /** The host card. */ protected Card hostCard; diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-game/src/main/java/forge/game/GameEntity.java index 1447b1d78ad..f52612e1e42 100644 --- a/forge-game/src/main/java/forge/game/GameEntity.java +++ b/forge-game/src/main/java/forge/game/GameEntity.java @@ -46,7 +46,7 @@ import forge.game.staticability.StaticAbilityCantAttach; import forge.game.zone.ZoneType; import forge.util.Lang; -public abstract class GameEntity extends GameObject implements IIdentifiable { +public abstract class GameEntity implements GameObject, IIdentifiable { protected int id; private String name = ""; protected CardCollection attachedCards = new CardCollection(); diff --git a/forge-game/src/main/java/forge/game/GameObject.java b/forge-game/src/main/java/forge/game/GameObject.java index bf5141cd913..976bbc6b736 100644 --- a/forge-game/src/main/java/forge/game/GameObject.java +++ b/forge-game/src/main/java/forge/game/GameObject.java @@ -4,9 +4,9 @@ import forge.game.card.Card; import forge.game.player.Player; import forge.game.spellability.SpellAbility; -public abstract class GameObject { +public interface GameObject { - public boolean canBeTargetedBy(final SpellAbility sa) { + public default boolean canBeTargetedBy(final SpellAbility sa) { return false; } @@ -22,7 +22,7 @@ public abstract class GameObject { * @param spellAbility * @return true, if is valid */ - public boolean isValid(final String[] restrictions, final Player sourceController, final Card source, CardTraitBase spellAbility) { + public default boolean isValid(final String[] restrictions, final Player sourceController, final Card source, CardTraitBase spellAbility) { for (final String restriction : restrictions) { if (this.isValid(restriction, sourceController, source, spellAbility)) { return true; @@ -43,7 +43,7 @@ public abstract class GameObject { * @param spellAbility * @return true, if is valid */ - public boolean isValid(final String restriction, final Player sourceController, final Card source, CardTraitBase spellAbility) { + public default boolean isValid(final String restriction, final Player sourceController, final Card source, CardTraitBase spellAbility) { return false; } @@ -59,7 +59,7 @@ public abstract class GameObject { * @param spellAbility * @return true, if successful */ - public boolean hasProperty(final String property, final Player sourceController, final Card source, CardTraitBase spellAbility) { + public default boolean hasProperty(final String property, final Player sourceController, final Card source, CardTraitBase spellAbility) { return false; } } \ No newline at end of file diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index 289520ecf28..0ddad73e783 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -56,7 +56,7 @@ import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; -public class CardState extends GameObject implements IHasSVars, ITranslatable { +public class CardState implements GameObject, IHasSVars, ITranslatable { private String name = ""; private CardType type = new CardType(false); private ManaCost manaCost = ManaCost.NO_COST; From 27a638a32b0b7b945d213fc12d75370129a766de Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 18 Oct 2025 09:27:38 +0200 Subject: [PATCH 150/230] TMT: Add Sneak (#8915) * TMT: Add Sneak * Sneak on Creature --- .../game/ability/effects/PermanentEffect.java | 23 ++++++-- .../src/main/java/forge/game/card/Card.java | 53 +++++++++---------- .../java/forge/game/card/CardFactoryUtil.java | 25 +++++++++ .../main/java/forge/game/keyword/Keyword.java | 3 +- .../game/spellability/AlternativeCost.java | 1 + .../forge/game/spellability/SpellAbility.java | 4 ++ .../spellability/SpellAbilityRestriction.java | 5 ++ .../upcoming/raphaels_technique.txt | 8 +++ 8 files changed, 88 insertions(+), 34 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/raphaels_technique.txt diff --git a/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java index 14ce438c8ea..ebdb3e96d0a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PermanentEffect.java @@ -6,10 +6,12 @@ import java.util.Map; import com.google.common.collect.Lists; import forge.game.Game; +import forge.game.GameEntity; import forge.game.ability.AbilityKey; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardZoneTable; +import forge.game.event.GameEventCombatChanged; import forge.game.spellability.SpellAbility; public class PermanentEffect extends SpellAbilityEffect { @@ -28,24 +30,37 @@ public class PermanentEffect extends SpellAbilityEffect { final Map moveParams = AbilityKey.newMap(); final CardZoneTable table = AbilityKey.addCardZoneTableParams(moveParams, sa); + if ((sa.isIntrinsic() || host.wasCast()) && sa.isSneak()) { + host.setTapped(true); + } + final Card c = game.getAction().moveToPlay(host, sa, moveParams); sa.setHostCard(c); // CR 608.3g - if (sa.isIntrinsic() || c.wasCast()) { - if (sa.isDash() && c.isInPlay()) { + if ((sa.isIntrinsic() || c.wasCast()) && c.isInPlay()) { + if (sa.isDash()) { registerDelayedTrigger(sa, "Hand", Lists.newArrayList(c)); // add AI hint c.addChangedSVars(Collections.singletonMap("EndOfTurnLeavePlay", "Dash"), c.getGame().getNextTimestamp(), 0); } - if (sa.isBlitz() && c.isInPlay()) { + if (sa.isBlitz()) { registerDelayedTrigger(sa, "Sacrifice", Lists.newArrayList(c)); c.addChangedSVars(Collections.singletonMap("EndOfTurnLeavePlay", "Blitz"), c.getGame().getNextTimestamp(), 0); } - if (sa.isWarp() && c.isInPlay()) { + if (sa.isWarp()) { registerDelayedTrigger(sa, "Exile", Lists.newArrayList(c)); c.addChangedSVars(Collections.singletonMap("EndOfTurnLeavePlay", "Warp"), c.getGame().getNextTimestamp(), 0); } + if (sa.isSneak() && c.isCreature()) { + final Card returned = sa.getPaidList("Returned", true).getFirst(); + final GameEntity defender = game.getCombat().getDefenderByAttacker(returned); + game.getCombat().addAttacker(c, defender); + game.getCombat().getBandOfAttacker(c).setBlocked(false); + + game.updateCombatForView(); + game.fireEvent(new GameEventCombatChanged()); + } } table.triggerChangesZoneAll(game, sa); 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 b9cc50ede98..7d67205718f 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3288,19 +3288,9 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr } else if (keyword.startsWith("Starting intensity")) { sbAfter.append(TextUtil.fastReplace(keyword, ":", " ")).append("\r\n"); } else if (keyword.startsWith("Escalate") || keyword.startsWith("Buyback") - || keyword.startsWith("Freerunning") || keyword.startsWith("Prowl")) { - final String[] k = keyword.split(":"); - final String manacost = k[1]; - final Cost cost = new Cost(manacost, false); - - StringBuilder sbCost = new StringBuilder(k[0]); - if (!cost.isOnlyManaCost()) { - sbCost.append("—"); - } else { - sbCost.append(" "); - } - sbCost.append(cost.toSimpleString()); - sbBefore.append(sbCost).append(" (").append(inst.getReminderText()).append(")"); + || keyword.startsWith("Freerunning") || keyword.startsWith("Prowl") + || keyword.startsWith("Sneak") || keyword.startsWith("Cleave")) { + sbBefore.append(formatKeywordWithCost(inst)); sbBefore.append("\r\n"); } else if (keyword.startsWith("Multikicker")) { final String[] n = keyword.split(":"); @@ -3329,22 +3319,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr || keyword.startsWith("Escape") || keyword.startsWith("Foretell:") || keyword.startsWith("Disturb") || keyword.startsWith("Overload") || keyword.startsWith("Plot") || keyword.startsWith("Mayhem")) { - final String[] k = keyword.split(":"); - final Cost mCost; - if (k.length < 2 || "ManaCost".equals(k[1])) { - mCost = new Cost(getManaCost(), false); - } else { - mCost = new Cost(k[1], false); - } - - StringBuilder sbCost = new StringBuilder(k[0]); - if (!mCost.isOnlyManaCost()) { - sbCost.append("—"); - } else { - sbCost.append(" "); - } - sbCost.append(mCost.toSimpleString()); - sbAfter.append(sbCost).append(" (").append(inst.getReminderText()).append(")"); + sbAfter.append(formatKeywordWithCost(inst)); sbAfter.append("\r\n"); } else if (keyword.equals("Gift")) { sbBefore.append(keyword); @@ -3456,6 +3431,26 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr return sb; } + private String formatKeywordWithCost(final KeywordInterface inst) { + final String keyword = inst.getOriginal(); + final String[] k = keyword.split(":"); + final Cost mCost; + if (k.length < 2 || "ManaCost".equals(k[1])) { + mCost = new Cost(getManaCost(), false); + } else { + mCost = new Cost(k[1], false); + } + + StringBuilder sbCost = new StringBuilder(k[0]); + if (!mCost.isOnlyManaCost()) { + sbCost.append("—"); + } else { + sbCost.append(" "); + } + sbCost.append(mCost.toSimpleString()); + return sbCost + " (" + inst.getReminderText() + ")"; + } + private String formatSpellAbility(final SpellAbility sa) { final StringBuilder sb = new StringBuilder(); sb.append(sa.toString()).append("\r\n\r\n"); 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 f5a3c133177..d4faab44a01 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3621,6 +3621,31 @@ public class CardFactoryUtil { sa.setSVar("ScavengeX", "Exiled$CardPower"); sa.setIntrinsic(intrinsic); inst.addSpellAbility(sa); + } else if (keyword.startsWith("Sneak")) { + final String[] k = keyword.split(":"); + final String manaCost = k[1]; + final Cost webCost = new Cost(manaCost + " Return<1/Creature.attacking+unblocked/unblocked attacker>", false); + + final SpellAbility newSA = card.getFirstSpellAbilityWithFallback().copyWithManaCostReplaced(host.getController(), webCost); + + if (k.length > 2) { + newSA.getMapParams().put("ValidAfterStack", k[2]); + } + + final StringBuilder desc = new StringBuilder(); + desc.append("Sneak ").append(ManaCostParser.parse(manaCost)).append(" ("); + desc.append(inst.getReminderText()); + desc.append(")"); + + newSA.setDescription(desc.toString()); + + final StringBuilder sb = new StringBuilder(); + sb.append(card.getName()).append(" (Sneak)"); + newSA.setStackDescription(sb.toString()); + newSA.putParam("Secondary", "True"); + newSA.setAlternativeCost(AlternativeCost.Sneak); + newSA.setIntrinsic(intrinsic); + inst.addSpellAbility(newSA); } else if (keyword.startsWith("Station")) { String effect = "AB$ PutCounter | Cost$ tapXType<1/Creature.Other> | Defined$ Self " + "| CounterType$ CHARGE | CounterNum$ StationX | SorcerySpeed$ True " + diff --git a/forge-game/src/main/java/forge/game/keyword/Keyword.java b/forge-game/src/main/java/forge/game/keyword/Keyword.java index 65b74ab7276..de9091e62bd 100644 --- a/forge-game/src/main/java/forge/game/keyword/Keyword.java +++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java @@ -168,10 +168,11 @@ public enum Keyword { RIOT("Riot", SimpleKeyword.class, false, "This creature enters with your choice of a +1/+1 counter or haste."), RIPPLE("Ripple", KeywordWithAmount.class, false, "When you cast this spell, you may reveal the top {%d:card} of your library. You may cast any of those cards with the same name as this spell without paying their mana costs. Put the rest on the bottom of your library in any order."), SADDLE("Saddle", KeywordWithAmount.class, false, "Tap any number of other creatures you control with total power %1$d or more: This Mount becomes saddled until end of turn. Saddle only as a sorcery."), + SCAVENGE("Scavenge", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: Put a number of +1/+1 counters equal to this card's power on target creature. Scavenge only as a sorcery."), SHADOW("Shadow", SimpleKeyword.class, true, "This creature can block or be blocked by only creatures with shadow."), SHROUD("Shroud", SimpleKeyword.class, true, "This can't be the target of spells or abilities."), SKULK("Skulk", SimpleKeyword.class, true, "This creature can't be blocked by creatures with greater power."), - SCAVENGE("Scavenge", KeywordWithCost.class, false, "%s, Exile this card from your graveyard: Put a number of +1/+1 counters equal to this card's power on target creature. Scavenge only as a sorcery."), + SNEAK("Sneak", KeywordWithCost.class, false, "You may cast this spell for %s if you also return an unblocked attacker you control to hand during the declare blockers step."), SOULBOND("Soulbond", SimpleKeyword.class, true, "You may pair this creature with another unpaired creature when either enters. They remain paired for as long as you control both of them."), SOULSHIFT("Soulshift", KeywordWithAmount.class, false, "When this creature dies, you may return target Spirit card with mana value %d or less from your graveyard to your hand."), SPACE_SCULPTOR("Space sculptor", SimpleKeyword.class, true, "CARDNAME divides the battlefield into alpha, beta, and gamma sectors. If a creature isn't assigned to a sector, its controller assigns it to one. Opponents assign first."), diff --git a/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java index c8b6b0b9893..e27de46839b 100644 --- a/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java +++ b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java @@ -21,6 +21,7 @@ public enum AlternativeCost { Overload, Prowl, Plotted, + Sneak, Spectacle, Surge, Warp, 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 f404a3bb148..3ef4ba06acf 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -686,6 +686,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return isAlternativeCost(AlternativeCost.Prowl); } + public final boolean isSneak() { + return isAlternativeCost(AlternativeCost.Sneak); + } + public final boolean isSurged() { return isAlternativeCost(AlternativeCost.Surge); } 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 615d27406c6..cb0d14ac216 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java @@ -337,6 +337,11 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { return false; } } + if (sa.isSneak()) { + if (!game.getPhaseHandler().is(PhaseType.COMBAT_DECLARE_BLOCKERS)) { + return false; + } + } return true; } diff --git a/forge-gui/res/cardsfolder/upcoming/raphaels_technique.txt b/forge-gui/res/cardsfolder/upcoming/raphaels_technique.txt new file mode 100644 index 00000000000..7766cde845c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/raphaels_technique.txt @@ -0,0 +1,8 @@ +Name:Raphael's Technique +ManaCost:4 R R +Types:Instant +K:Sneak:2 R +A:SP$ Discard | Mode$ Hand | Defined$ Player | Optional$ True | RememberDiscardingPlayers$ True | SubAbility$ DBDraw | SpellDescription$ Each player may discard their hand and draw seven cards. +SVar:DBDraw:DB$ Draw | Defined$ Remembered | NumCards$ 7 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:Sneak {2}{R} (You may cast this spell for {2}{R} if you also return an unblocked attacker you control to hand during the declare blockers step.)\nEach player may discard their hand and draw seven cards. From d94fe1e9587f8d1f3701e9e2f7a6b64c35fc2529 Mon Sep 17 00:00:00 2001 From: Cees Timmerman Date: Sat, 18 Oct 2025 09:29:39 +0200 Subject: [PATCH 151/230] Fix Elturel Survivors not played (#8920) (#8921) --- forge-ai/src/main/java/forge/ai/AiAttackController.java | 3 ++- forge-gui/res/cardsfolder/e/elturel_survivors.txt | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index c90bdc81df8..501fab87101 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -1102,7 +1102,8 @@ public class AiAttackController { for (final Card pCard : myList) { // if the creature can attack then it's a potential attacker this // turn, assume summoning sickness creatures will be able to - if (ComputerUtilCombat.canAttackNextTurn(pCard) && pCard.getNetCombatDamage() > 0) { + // TODO: Account for triggered power boosts. + if (ComputerUtilCombat.canAttackNextTurn(pCard) && (pCard.getNetCombatDamage() > 0 || "TRUE".equals(pCard.getSVar("HasAttackEffect")))) { candidateAttackers.add(pCard); candidateUnblockedDamage += ComputerUtilCombat.damageIfUnblocked(pCard, defendingOpponent, null, false); computerForces++; diff --git a/forge-gui/res/cardsfolder/e/elturel_survivors.txt b/forge-gui/res/cardsfolder/e/elturel_survivors.txt index da3d8d71731..9390f821863 100644 --- a/forge-gui/res/cardsfolder/e/elturel_survivors.txt +++ b/forge-gui/res/cardsfolder/e/elturel_survivors.txt @@ -6,7 +6,6 @@ K:Trample K:Myriad S:Mode$ Continuous | Affected$ Creature.Self+attacking | AddPower$ X | Description$ As long as CARDNAME is attacking, it gets +X/+0, where X is the number of lands defending player controls. SVar:X:Count$Valid Land.DefenderCtrl -AI:RemoveDeck:All SVar:BuffedBy:Land.OppCtrl SVar:HasAttackEffect:TRUE Oracle:Trample, myriad\nAs long as Elturel Survivors is attacking, it gets +X/+0, where X is the number of lands defending player controls. From a99c899eec378aec5e0f662b096697a765c4d28c Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 18 Oct 2025 14:24:29 +0200 Subject: [PATCH 152/230] GameAction: cleanup Static giving Abilities to the Stack (#8587) * Add test that fails on master engine (before KeywordCollection sanity fix) --- .../forge/ai/simulation/GameSimulator.java | 14 ++++++ .../src/main/java/forge/game/GameAction.java | 43 ++++++------------- .../src/main/java/forge/game/card/Card.java | 1 - .../src/test/java/forge/ai/AITest.java | 6 +-- .../ai/simulation/GameSimulationTest.java | 29 +++++++++++++ 5 files changed, 58 insertions(+), 35 deletions(-) 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 d4d58e22b00..c05199f0d15 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java @@ -5,6 +5,7 @@ import forge.ai.ComputerUtil; import forge.ai.PlayerControllerAi; import forge.ai.simulation.GameStateEvaluator.Score; import forge.game.Game; +import forge.game.GameActionUtil; import forge.game.GameObject; import forge.game.card.Card; import forge.game.phase.PhaseType; @@ -131,6 +132,19 @@ public class GameSimulator { Card hostCard = (Card) copier.find(origHostCard); String desc = sa.getDescription(); FCollectionView candidates = hostCard.getSpellAbilities(); + + SpellAbility result = saMatcher(candidates, desc); + for (SpellAbility cSa : candidates) { + if (result != null) { + break; + } + result = saMatcher(GameActionUtil.getAlternativeCosts(cSa, aiPlayer, true), desc); + } + + return result; + } + + private SpellAbility saMatcher(Iterable candidates, String desc) { // first pass for accuracy (spells with alternative costs) for (SpellAbility cSa : candidates) { if (desc.equals(cSa.getDescription())) { diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 0c985d805ea..a27c5ebc422 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -223,6 +223,7 @@ public class GameAction { } if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) { + copied.setCastFrom(zoneFrom); copied.setCastSA(cause); copied.setSplitStateToPlayAbility(cause); @@ -232,6 +233,13 @@ public class GameAction { KeywordInterface kw = cause.getKeyword(); if (kw != null) { copied.addKeywordForStaticAbility(kw); + + // CR 400.7g If an effect grants a nonland card an ability that allows it to be cast, + // that ability will continue to apply to the new object that card became after it moved to the stack as a result of being cast this way. + if (!cause.isIntrinsic()) { + kw.setHostCard(copied); + copied.addChangedCardKeywordsInternal(ImmutableList.of(kw), null, false, copied.getGameTimestamp(), kw.getStatic(), false); + } } } } else { @@ -300,7 +308,6 @@ public class GameAction { // Temporary disable commander replacement effect // 903.9a if (fromBattlefield && !toBattlefield && c.isCommander() && c.hasMergedCard()) { - // Disable the commander replacement effect c.getOwner().setCommanderReplacementSuppressed(true); } @@ -445,14 +452,8 @@ public class GameAction { } if (zoneFrom.is(ZoneType.Stack) && toBattlefield) { - // 400.7a Effects from static abilities that give a permanent spell on the stack an ability - // that allows it to be cast for an alternative cost continue to apply to the permanent that spell becomes. - if (c.getCastSA() != null && !c.getCastSA().isIntrinsic() && c.getKeywords().contains(c.getCastSA().getKeyword())) { - KeywordInterface ki = c.getCastSA().getKeyword(); - ki.setHostCard(copied); - copied.addChangedCardKeywordsInternal(ImmutableList.of(ki), null, false, copied.getGameTimestamp(), null, true); - } - // TODO hot fix for non-intrinsic offspring + // CR 400.7b Effects from static abilities that grant an ability to a permanent spell that functions on the battlefield + // continue to apply to the permanent that spell becomes Multimap addKw = MultimapBuilder.hashKeys().arrayListValues().build(); for (KeywordInterface kw : c.getKeywords(Keyword.OFFSPRING)) { if (!kw.isIntrinsic()) { @@ -465,7 +466,7 @@ public class GameAction { } } - // 607.2q linked ability can find cards exiled as cost while it was a spell + // CR 607.2q linked ability can find cards exiled as cost while it was a spell copied.addExiledCards(c.getExiledCards()); } @@ -486,7 +487,7 @@ public class GameAction { if (card.isRealCommander()) { card.setMoveToCommandZone(true); } - // 727.3e & 903.9a + // CR 727.3e & 903.9a if (wasToken && !card.isRealToken() || card.isRealCommander()) { Map repParams = AbilityKey.mapFromAffected(card); repParams.put(AbilityKey.CardLKI, card); @@ -561,13 +562,6 @@ public class GameAction { // update static abilities after etb counters have been placed checkStaticAbilities(); - // 400.7g try adding keyword back into card if it doesn't already have it - if (zoneTo.is(ZoneType.Stack) && cause != null && cause.isSpell() && !cause.isIntrinsic() && c.equals(cause.getHostCard())) { - if (cause.getKeyword() != null && !copied.getKeywords().contains(cause.getKeyword())) { - copied.addChangedCardKeywordsInternal(ImmutableList.of(cause.getKeyword()), null, false, game.getNextTimestamp(), null, true); - } - } - // CR 603.6b if (toBattlefield) { zoneTo.saveLKI(copied, lastKnownInfo); @@ -827,19 +821,6 @@ public class GameAction { } } - if (zoneTo.is(ZoneType.Stack)) { - c.setCastFrom(zoneFrom); - if (cause != null && cause.isSpell() && c.equals(cause.getHostCard())) { - c.setCastSA(cause); - } else { - c.setCastSA(null); - } - } else if (zoneFrom == null || !(zoneFrom.is(ZoneType.Stack) && - (zoneTo.is(ZoneType.Battlefield) || zoneTo.is(ZoneType.Merged)))) { - c.setCastFrom(null); - c.setCastSA(null); - } - if (c.isRealCommander()) { c.setMoveToCommandZone(true); } 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 7d67205718f..ce71d039611 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2582,7 +2582,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr sbLong.append(" ").append(ManaCostParser.parse(k[1])); sbLong.append(" (").append(inst.getReminderText()).append(")"); sbLong.append("\r\n"); - } else if (inst.getKeyword().equals(Keyword.COMPANION)) { sbLong.append("Companion — "); sbLong.append(((Companion)inst).getDescription()); diff --git a/forge-gui-desktop/src/test/java/forge/ai/AITest.java b/forge-gui-desktop/src/test/java/forge/ai/AITest.java index 23588815a25..2dbfb8a19cc 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/AITest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/AITest.java @@ -151,19 +151,19 @@ public class AITest { return c; } - void playUntilStackClear(Game game) { + protected void playUntilStackClear(Game game) { do { game.getPhaseHandler().mainLoopStep(); } while (!game.isGameOver() && !game.getStack().isEmpty()); } - void playUntilPhase(Game game, PhaseType phase) { + protected void playUntilPhase(Game game, PhaseType phase) { do { game.getPhaseHandler().mainLoopStep(); } while (!game.isGameOver() && !game.getPhaseHandler().is(phase)); } - void playUntilNextTurn(Game game) { + protected void playUntilNextTurn(Game game) { Player current = game.getPhaseHandler().getPlayerTurn(); do { game.getPhaseHandler().mainLoopStep(); diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulationTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulationTest.java index 76fd308cb7a..a45957966c7 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulationTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/GameSimulationTest.java @@ -2610,6 +2610,35 @@ public class GameSimulationTest extends SimulationTest { AssertJUnit.assertTrue(nonBasicForest.getType().hasSubtype("Mountain")); } + @Test + public void testHenzie() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(0); + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + + addCard("Henzie \"Toolbox\" Torre", p); + addCardToZone("Wastes", p, ZoneType.Library); + addCards("Plains", 5, p); + Card spell = addCardToZone("Serra Angel", p, ZoneType.Hand); + + game.getAction().checkStaticAbilities(); + List sas = spell.getAllPossibleAbilities(p, true); + SpellAbility blitz = sas.get(1); + + GameSimulator sim = createSimulator(game, p); + game = sim.getSimulatedGameState(); + sim.simulateSpellAbility(blitz); + spell = findCardWithName(game, "Serra Angel"); + + AssertJUnit.assertEquals(1, spell.getAmountOfKeyword(Keyword.BLITZ)); + AssertJUnit.assertTrue(spell.hasKeyword(Keyword.HASTE)); + + playUntilNextTurn(game); + + AssertJUnit.assertEquals(1, game.getPlayers().get(0).getCardsIn(ZoneType.Hand).size()); + AssertJUnit.assertTrue(spell.isInZone(ZoneType.Graveyard)); + } + /** * Test for "Volo's Journal" usage by the AI. This test checks if the AI correctly * adds the correct types to the "Volo's Journal" when casting the spells in order From 472f94af66e76470f870e2b8c19a1093cd2de131 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Sat, 18 Oct 2025 21:13:59 +0200 Subject: [PATCH 153/230] Leonardo, Sewer Samurai (TMT) (#8948) --- .../cardsfolder/upcoming/leonardo_sewer_samurai.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/leonardo_sewer_samurai.txt diff --git a/forge-gui/res/cardsfolder/upcoming/leonardo_sewer_samurai.txt b/forge-gui/res/cardsfolder/upcoming/leonardo_sewer_samurai.txt new file mode 100644 index 00000000000..bf5688ef474 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/leonardo_sewer_samurai.txt @@ -0,0 +1,13 @@ +Name:Leonardo, Sewer Samurai +ManaCost:3 W +Types:Legendary Creature Mutant Ninja Turtle Samurai +PT:3/3 +K:Sneak:2 W W +K:Double Strike +S:Mode$ Continuous | Affected$ Creature.nonLand+YouOwn+powerLE1,Creature.nonLand+YouOwn+toughnessLE1 | Condition$ PlayerTurn | MayPlay$ True | EffectZone$ Battlefield | AffectedZone$ Graveyard | Description$ During your turn, you may cast creature spells with power or toughness 1 or less from your graveyard. If you cast a spell this way, that creature enters with a finality counter on it. (If a creature with a finality counter on it would die, exile it instead.) +T:Mode$ SpellCast | ValidCard$ Card.CastSa Spell.MayPlaySource | Execute$ TrigEffect | Static$ True | TriggerZones$ Battlefield +SVar:TrigEffect:DB$ Effect | ReplacementEffects$ ReMoved | RememberObjects$ TriggeredCard +SVar:ReMoved:Event$ Moved | Origin$ Stack | ValidCard$ Card.IsRemembered | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBCounter +SVar:ETBCounter:DB$ PutCounter | ETB$ True | Defined$ ReplacedCard | CounterType$ FINALITY | CounterNum$ 1 | SubAbility$ RemoveSelf +SVar:RemoveSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile +Oracle:Sneak {2}{W}{W}\nDouble strike\nDuring your turn, you may cast creature spells with power or toughness 1 or less from your graveyard. If you cast a spell this way, that creature enters with a finality counter on it. (If a creature with a finality counter on it would die, exile it instead.) From 24ef895cb7ae615cdac397d6c8c5f40629338691 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 19 Oct 2025 10:15:36 +0200 Subject: [PATCH 154/230] Fix Leonardo (#8949) --- .../java/forge/ai/AiAttackController.java | 23 +------------------ .../java/forge/ai/ComputerUtilCombat.java | 6 ++++- .../src/main/java/forge/game/card/Card.java | 3 ++- .../java/forge/game/card/CardFactoryUtil.java | 1 + 4 files changed, 9 insertions(+), 24 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 501fab87101..6f94142741b 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -275,27 +275,6 @@ public class AiAttackController { return false; } - // the attacker will die to a triggered ability (e.g. Sarkhan the Masterless) - for (Card c : ai.getOpponents().getCardsIn(ZoneType.Battlefield)) { - for (Trigger t : c.getTriggers()) { - if (t.getMode() == TriggerType.Attacks) { - SpellAbility sa = t.ensureAbility(); - if (sa == null) { - continue; - } - - if (sa.getApi() == ApiType.EachDamage && "TriggeredAttacker".equals(sa.getParam("Defined"))) { - List valid = CardLists.getValidCards(c.getController().getCreaturesInPlay(), sa.getParam("ValidCards"), c.getController(), c, sa); - // TODO: this assumes that 1 damage is dealt per creature. Improve this to check the parameter/X to determine - // how much damage is dealt by each of the creatures in the valid list. - if (attacker.getNetToughness() <= valid.size()) { - return false; - } - } - } - } - } - if ("TRUE".equals(attacker.getSVar("HasAttackEffect"))) { return true; } @@ -1005,7 +984,7 @@ public class AiAttackController { if (attackMax != -1 && combat.getAttackers().size() >= attackMax) return aiAggression; - // TODO if lifeInDanger use chance to hold back some + // TODO if lifeInDanger use chance to hold back some (especially in multiplayer) if (canAttackWrapper(attacker, defender) && isEffectiveAttacker(ai, attacker, combat, defender)) { combat.addAttacker(attacker, defender); } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 602d8a23f07..b70f478fcc3 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -1433,7 +1433,11 @@ public class ComputerUtilCombat { int damage = AbilityUtils.calculateAmount(source, sa.getParam("NumDmg"), sa); toughness -= predictDamageTo(attacker, damage, source, false); - continue; + } else if (sa.getApi() == ApiType.EachDamage && "TriggeredAttackerLKICopy".equals(sa.getParam("Defined"))) { + List valid = CardLists.getValidCards(source.getController().getCreaturesInPlay(), sa.getParam("ValidCards"), source.getController(), source, sa); + // TODO: this assumes that 1 damage is dealt per creature. Improve this to check the parameter/X to determine + // how much damage is dealt by each of the creatures in the valid list. + toughness -= valid.size(); } else if (ApiType.Pump.equals(sa.getApi())) { if (!sa.hasParam("NumDef")) { continue; 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 ce71d039611..0dce93c1862 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -2469,7 +2469,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr || keyword.startsWith("Reconfigure") || keyword.startsWith("Squad") || keyword.startsWith("Miracle") || keyword.startsWith("More Than Meets the Eye") || keyword.startsWith("Level up") || keyword.startsWith("Plot") - || keyword.startsWith("Offspring") || keyword.startsWith("Mayhem")) { + || keyword.startsWith("Offspring") || keyword.startsWith("Mayhem") + || keyword.startsWith("Sneak")) { String[] k = keyword.split(":"); sbLong.append(k[0]); if (k.length > 1) { diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index d4faab44a01..534deb267fb 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3645,6 +3645,7 @@ public class CardFactoryUtil { newSA.putParam("Secondary", "True"); newSA.setAlternativeCost(AlternativeCost.Sneak); newSA.setIntrinsic(intrinsic); + newSA.getRestrictions().setInstantSpeed(true); inst.addSpellAbility(newSA); } else if (keyword.startsWith("Station")) { String effect = "AB$ PutCounter | Cost$ tapXType<1/Creature.Other> | Defined$ Self " + From 80a4a425755606de3c821777cf9084d50e6cda2c Mon Sep 17 00:00:00 2001 From: Eradev Date: Sun, 19 Oct 2025 04:17:21 -0400 Subject: [PATCH 155/230] Card typo and rewards fixes (#8953) --- .../Crystal_Kingdoms/world/enemies.json | 24 +++++++++---------- .../Shandalar Old Border/world/enemies.json | 24 +++++++++---------- .../common/custom_cards/jace_boss_effect.txt | 2 +- .../res/adventure/common/world/enemies.json | 24 +++++++++---------- .../res/cardsfolder/w/wilson_bear_comrade.txt | 2 +- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/forge-gui/res/adventure/Crystal_Kingdoms/world/enemies.json b/forge-gui/res/adventure/Crystal_Kingdoms/world/enemies.json index 588ee73db57..5bc92163a30 100644 --- a/forge-gui/res/adventure/Crystal_Kingdoms/world/enemies.json +++ b/forge-gui/res/adventure/Crystal_Kingdoms/world/enemies.json @@ -468,7 +468,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" }, { "type": "shards", @@ -555,7 +555,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" }, { "type": "shards", @@ -1047,7 +1047,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{W}" + "cardText": "\\{W\\}" } ], "colors": "W", @@ -2174,7 +2174,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" } ], "colors": "U", @@ -3461,7 +3461,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -3536,7 +3536,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -3878,7 +3878,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" }, { "type": "shards", @@ -10809,7 +10809,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" } ], "colors": "R", @@ -13216,7 +13216,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -22387,7 +22387,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -22462,7 +22462,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -27516,7 +27516,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" } ], "colors": "R", diff --git a/forge-gui/res/adventure/Shandalar Old Border/world/enemies.json b/forge-gui/res/adventure/Shandalar Old Border/world/enemies.json index c2a664a0e40..1c60e61ca3f 100644 --- a/forge-gui/res/adventure/Shandalar Old Border/world/enemies.json +++ b/forge-gui/res/adventure/Shandalar Old Border/world/enemies.json @@ -459,7 +459,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" }, { "type": "shards", @@ -544,7 +544,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" }, { "type": "shards", @@ -1032,7 +1032,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{W}" + "cardText": "\\{W\\}" } ], "colors": "W", @@ -2149,7 +2149,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" } ], "colors": "U", @@ -3433,7 +3433,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -3508,7 +3508,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -3845,7 +3845,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" }, { "type": "shards", @@ -10749,7 +10749,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" } ], "colors": "R", @@ -13150,7 +13150,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -22301,7 +22301,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -22376,7 +22376,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -27417,7 +27417,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" } ], "colors": "R", diff --git a/forge-gui/res/adventure/common/custom_cards/jace_boss_effect.txt b/forge-gui/res/adventure/common/custom_cards/jace_boss_effect.txt index d55dac8a008..d5f32a1a50f 100644 --- a/forge-gui/res/adventure/common/custom_cards/jace_boss_effect.txt +++ b/forge-gui/res/adventure/common/custom_cards/jace_boss_effect.txt @@ -6,7 +6,7 @@ T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Command | SVar:TrigBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$GE10 | TrueSubAbility$ DBChoose | FalseSubAbility$ DBConjureSpellBook SVar:DBChoose:DB$ ChooseCard | Choices$ Creature | ChoiceZone$ Library | AtRandom$ True | SubAbility$ DBConjure SVar:DBConjure:DB$ MakeCard | Conjure$ True | DefinedName$ ChosenCard | Zone$ Battlefield | RememberMade$ True | SubAbility$ DBEffect -SVar:DBEffect:DB$ Animate | Defined$ Remembered | Power$ 2 | Toughness$ 2 | Triggers$ DismissTarget | Duration$ Perpetual | SubAbility$ DBCleanup +SVar:DBEffect:DB$ Animate | Defined$ Remembered | Power$ 2 | Toughness$ 2 | Colors$ Blue | OverwriteColors$ True | Types$ Creature,Illusion | RemoveCardTypes$ True |Triggers$ DismissTarget | Duration$ Perpetual | SubAbility$ DBCleanup SVar:DismissTarget:Mode$ BecomesTarget | ValidTarget$ Card.Self | TriggerZones$ Battlefield | Execute$ DismissSac | TriggerDescription$ When this creature becomes the target of a spell or ability, sacrifice it. SVar:DismissSac:DB$ Sacrifice SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True | ClearRemembered$ True diff --git a/forge-gui/res/adventure/common/world/enemies.json b/forge-gui/res/adventure/common/world/enemies.json index 3d8cb56ca22..bdf17049051 100644 --- a/forge-gui/res/adventure/common/world/enemies.json +++ b/forge-gui/res/adventure/common/world/enemies.json @@ -468,7 +468,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" }, { "type": "shards", @@ -555,7 +555,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" }, { "type": "shards", @@ -1047,7 +1047,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{W}" + "cardText": "\\{W\\}" } ], "colors": "W", @@ -2174,7 +2174,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" } ], "colors": "U", @@ -3461,7 +3461,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -3536,7 +3536,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -3878,7 +3878,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" }, { "type": "shards", @@ -10808,7 +10808,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" } ], "colors": "R", @@ -13213,7 +13213,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -22381,7 +22381,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -22456,7 +22456,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{C}" + "cardText": "\\{C\\}" }, { "type": "shards", @@ -27509,7 +27509,7 @@ "Land" ], "colorType": "Colorless", - "cardText": "{R}" + "cardText": "\\{R\\}" } ], "colors": "R", diff --git a/forge-gui/res/cardsfolder/w/wilson_bear_comrade.txt b/forge-gui/res/cardsfolder/w/wilson_bear_comrade.txt index 31eff954423..c71a97aa815 100644 --- a/forge-gui/res/cardsfolder/w/wilson_bear_comrade.txt +++ b/forge-gui/res/cardsfolder/w/wilson_bear_comrade.txt @@ -75,7 +75,7 @@ SVar:DBEffect:DB$ Effect | Name$ Wilson, Ardent Bear's Perpetual Effect | Static SVar:PerpetualDoubleStrike:Mode$ Continuous | Affected$ Card.IsRemembered | AddKeyword$ Double Strike | AffectedZone$ All | Description$ This creature perpetually gains double strike. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Keyword$Double Strike -Oracle:Double strike, reach, trample\nWard {2}\n{1}{B}{G}, Exile Wilson, Ardent Bear from your graveyard: Target creature you control perpetually gains double strike. Activate only as a sorcery. +Oracle:Double strike, reach, trample\nWard {2}\n{1}{R}{G}, Exile Wilson, Ardent Bear from your graveyard: Target creature you control perpetually gains double strike. Activate only as a sorcery. SPECIALIZE:GREEN From 6f384791905ad93f40be96f767c08de4db7c0ed6 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 19 Oct 2025 20:57:11 +0200 Subject: [PATCH 156/230] Fix AI not targeting triggered Random Charm (#8955) --- forge-ai/src/main/java/forge/ai/AiController.java | 3 ++- .../src/main/java/forge/ai/PlayerControllerAi.java | 10 ++++++++-- .../main/java/forge/ai/ability/ChangeZoneAi.java | 6 ++---- .../src/main/java/forge/ai/ability/CharmAi.java | 13 +++++++++---- .../java/forge/ai/ability/ChooseCardNameAi.java | 6 ++---- .../main/java/forge/ai/ability/CountersPutAi.java | 2 +- .../main/java/forge/game/ability/AbilityUtils.java | 9 +++++---- .../main/java/forge/game/card/CardFactoryUtil.java | 2 +- forge-gui/res/cardsfolder/d/disruptor_flute.txt | 4 ++-- .../upcoming/michelangelo_on_the_scene.txt | 2 +- 10 files changed, 33 insertions(+), 24 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index c9d698c7582..1430d8285bf 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -377,7 +377,7 @@ public class AiController { if (card.isSaga()) { for (final Trigger tr : card.getTriggers()) { - if (tr.getMode() != TriggerType.CounterAdded || !tr.isChapter()) { + if (!tr.isChapter()) { continue; } @@ -393,6 +393,7 @@ public class AiController { return false; } + // usually later chapters make use of an earlier one break; } } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 05f92ee72f2..5276b419e5f 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -1274,9 +1274,15 @@ public class PlayerControllerAi extends PlayerController { } } - private boolean prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory) { + private boolean prepareSingleSa(final Card host, SpellAbility sa, boolean isMandatory) { if (sa.getApi() == ApiType.Charm) { - return CharmEffect.makeChoices(sa); + if (!CharmEffect.makeChoices(sa)) { + return false; + } + if (!sa.hasParam("Random")) { + return true; + } + sa = sa.getSubAbility(); } if (sa.hasParam("TargetingPlayer")) { Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0); diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 590c4285a96..0753244b0ce 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -634,6 +634,7 @@ public class ChangeZoneAi extends SpellAbilityAi { } // not urgent, get the largest creature possible + // TODO checkETBEffects return ComputerUtilCard.getBestCreatureAI(list); } @@ -1546,10 +1547,7 @@ public class ChangeZoneAi extends SpellAbilityAi { if (fetchList.isEmpty()) { return null; } - String type = sa.getParam("ChangeType"); - if (type == null) { - type = "Card"; - } + String type = sa.getParamOrDefault("ChangeType", ""); Card c = null; final Player activator = sa.getActivatingPlayer(); diff --git a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java index 0e39198a81e..7a4c9d6bcf2 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java @@ -94,18 +94,21 @@ public class CharmAi extends SpellAbilityAi { private List chooseOptionsAi(SpellAbility sa, List choices, final Player ai, boolean isTrigger, int num, int min) { List chosenList = Lists.newArrayList(); AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); - boolean allowRepeat = sa.hasParam("CanRepeatModes"); // FIXME: unused for now, the AI doesn't know how to effectively handle repeated choices + // TODO unused for now, the AI doesn't know how to effectively handle repeated choices + boolean allowRepeat = sa.hasParam("CanRepeatModes"); // Pawprint final int pawprintLimit = sa.hasParam("Pawprint") ? AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Pawprint"), sa) : 0; if (pawprintLimit > 0) { - Collections.reverse(choices); // try to pay for the more expensive subs first + // try to pay for the more expensive subs first + Collections.reverse(choices); } int pawprintAmount = 0; // First pass using standard canPlayAi() for good choices for (AbilitySub sub : choices) { sub.setActivatingPlayer(ai); + // TODO refactor to obtain the AiAbilityDecision instead, then we can check all to sort by value if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) { if (pawprintLimit > 0) { int curPawprintAmount = AbilityUtils.calculateAmount(sub.getHostCard(), sub.getParamOrDefault("Pawprint", "0"), sub); @@ -116,7 +119,8 @@ public class CharmAi extends SpellAbilityAi { } chosenList.add(sub); if (chosenList.size() == num) { - return chosenList; // maximum choices reached + // maximum choices reached + return chosenList; } } } @@ -145,7 +149,8 @@ public class CharmAi extends SpellAbilityAi { } } if (chosenList.size() < min) { - chosenList.clear(); // not enough choices + // not enough choices + chosenList.clear(); } return chosenList; } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java index 972c79ab11a..aeebb278cfb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseCardNameAi.java @@ -72,16 +72,14 @@ public class ChooseCardNameAi extends SpellAbilityAi { // 5 percent chance to cast per opposing card with a non mana ability if (MyRandom.getRandom().nextFloat() <= .05 * oppPerms.size()) { return new AiAbilityDecision(100, AiPlayDecision.WillPlay); - } else { - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } + return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } if (mandatory) { return new AiAbilityDecision(100, AiPlayDecision.WillPlay); - } else { - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } + return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } /* (non-Javadoc) * @see forge.card.ability.SpellAbilityAi#chooseSingleCard(forge.card.spellability.SpellAbility, java.util.List, boolean) diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index 77dfc9c9443..b697bbc3445 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -1175,7 +1175,7 @@ public class CountersPutAi extends CountersAi { @Override public int chooseNumber(Player player, SpellAbility sa, int min, int max, Map params) { - if (sa.hasParam("ReadAhead")) { + if (sa.isKeyword(Keyword.READ_AHEAD)) { return 1; } return max; 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 58ed6ff4e81..d1b0a3f7154 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -982,6 +982,9 @@ public class AbilityUtils { for (final Card c : getDefinedCards(card, "Targeted", sa)) { players.add(c.getOwner()); } + for (final SpellAbility s : getDefinedSpellAbilities(card, "Targeted", sa)) { + players.add(s.getHostCard().getOwner()); + } } else if (defined.equals("TargetedAndYou") && sa instanceof SpellAbility) { final SpellAbility saTargeting = ((SpellAbility)sa).getSATargetingPlayer(); if (saTargeting != null) { @@ -1118,10 +1121,8 @@ public class AbilityUtils { final String replacingType = defined.substring(8); o = root.getReplacingObject(AbilityKey.fromString(replacingType)); } - if (o != null) { - if (o instanceof Player) { - players.add((Player) o); - } + if (o instanceof Player) { + players.add((Player) o); } } else if (defined.startsWith("Non")) { players.addAll(game.getPlayersInTurnOrder()); 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 534deb267fb..92acb84d8e5 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -2496,7 +2496,7 @@ public class CardFactoryUtil { inst.addReplacement(re); } else if (keyword.equals("Read ahead")) { String repeffstr = "Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | Secondary$ True | ReplacementResult$ Updated | Description$ Choose a chapter and start with that many lore counters."; - String effStr = "DB$ PutCounter | Defined$ Self | CounterType$ LORE | ETB$ True | UpTo$ True | UpToMin$ 1 | ReadAhead$ True | CounterNum$ FinalChapterNr"; + String effStr = "DB$ PutCounter | Defined$ Self | CounterType$ LORE | ETB$ True | UpTo$ True | UpToMin$ 1 | CounterNum$ FinalChapterNr"; SpellAbility saCounter = AbilityFactory.getAbility(effStr, card); saCounter.setSVar("FinalChapterNr", "Count$FinalChapterNr"); diff --git a/forge-gui/res/cardsfolder/d/disruptor_flute.txt b/forge-gui/res/cardsfolder/d/disruptor_flute.txt index 9891e5e8d69..4d7e4bc599d 100644 --- a/forge-gui/res/cardsfolder/d/disruptor_flute.txt +++ b/forge-gui/res/cardsfolder/d/disruptor_flute.txt @@ -4,7 +4,7 @@ Types:Artifact K:Flash K:ETBReplacement:Other:DBNameCard SVar:DBNameCard:DB$ NameCard | Defined$ You | SpellDescription$ As CARDNAME enters, choose a card name. -S:Mode$ RaiseCost | EffectZone$ Battlefield | ValidCard$ Card.NamedCard | Type$ Spell | Activator$ Player | Amount$ 3 | Description$ Spells with the chosen name cost 3 more to cast. +S:Mode$ RaiseCost | EffectZone$ Battlefield | ValidCard$ Card.NamedCard | Type$ Spell | Activator$ Player | Amount$ 3 | Description$ Spells with the chosen name cost {3} more to cast. S:Mode$ CantBeActivated | ValidCard$ Card.NamedCard | ValidSA$ Activated.!ManaAbility | Description$ Activated abilities of sources with the chosen name can't be activated unless they're mana abilities. AI:RemoveDeck:Random -Oracle:Flash\nAs Disruptor Flute enters, choose a card name.\nSpells with the chosen name cost 3 more to cast.\nActivated abilities of sources with the chosen name can't be activated unless they're mana abilities. +Oracle:Flash\nAs Disruptor Flute enters, choose a card name.\nSpells with the chosen name cost {3} more to cast.\nActivated abilities of sources with the chosen name can't be activated unless they're mana abilities. diff --git a/forge-gui/res/cardsfolder/upcoming/michelangelo_on_the_scene.txt b/forge-gui/res/cardsfolder/upcoming/michelangelo_on_the_scene.txt index 2f02e6215a7..627abbd0aa4 100644 --- a/forge-gui/res/cardsfolder/upcoming/michelangelo_on_the_scene.txt +++ b/forge-gui/res/cardsfolder/upcoming/michelangelo_on_the_scene.txt @@ -5,7 +5,7 @@ PT:2/2 K:Trample K:etbCounter:P1P1:X:no Condition:NICKNAME enters with a +1/+1 counter on him for each land you control. SVar:X:Count$Valid Land.YouCtrl -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When NICKNAME dies, return this card to your hand. +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When NICKNAME dies, return this card to your hand. SVar:TrigReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Hand DeckHas:Ability$Counters Oracle:Trample (This creature can deal excess combat damage to the player it's attacking.)\nMichelangelo enters with a +1/+1 counter on him for each land you control.\nWhen Michelangelo dies, return this card to your hand. \ No newline at end of file From 7367e931401e8157b6578ac7fbfbe0b993b414be Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 19 Oct 2025 22:50:57 +0200 Subject: [PATCH 157/230] Fix not being able to freely divide combat damage to attackers (#8961) --- forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java | 2 +- forge-game/src/main/java/forge/game/combat/Combat.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index b70f478fcc3..676bfd5ea18 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2051,7 +2051,7 @@ public class ComputerUtilCombat { } // Order the combatants in preferred order in case legacy ordering is disabled - if (!self.getGame().getRules().hasOrderCombatants()) { + if (!overrideOrder) { if (combatant.isAttacking()) { opposedCombatants = AiBlockController.orderBlockers(combatant, new CardCollection(opposedCombatants)); } else { diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java index 4f81d53ef7e..734deb62c4c 100644 --- a/forge-game/src/main/java/forge/game/combat/Combat.java +++ b/forge-game/src/main/java/forge/game/combat/Combat.java @@ -744,7 +744,7 @@ public class Combat { assigningPlayer = attackingPlayer; assignedDamage = true; - Map map = assigningPlayer.getController().assignCombatDamage(blocker, attackers, null, damage, defender, divideCombatDamageAsChoose || assigningPlayer != blocker.getController()); + Map map = assigningPlayer.getController().assignCombatDamage(blocker, attackers, null, damage, defender, divideCombatDamageAsChoose || assigningPlayer != blocker.getController() || !this.legacyOrderCombatants); for (Entry dt : map.entrySet()) { // Butcher Orgg if (dt.getKey() == null && dt.getValue() > 0) { From a8d494d2d86ceecc200343ed89343f783877a1ab Mon Sep 17 00:00:00 2001 From: tool4ever Date: Mon, 20 Oct 2025 07:58:03 +0200 Subject: [PATCH 158/230] Update ComputerUtilCombat.java --- forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 676bfd5ea18..978f55e9625 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -2051,7 +2051,7 @@ public class ComputerUtilCombat { } // Order the combatants in preferred order in case legacy ordering is disabled - if (!overrideOrder) { + if (isAttacking && overrideOrder) { if (combatant.isAttacking()) { opposedCombatants = AiBlockController.orderBlockers(combatant, new CardCollection(opposedCombatants)); } else { From 5efd7e6e7cb4487b28a3d0932fd03d956214a604 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Mon, 20 Oct 2025 09:45:57 +0200 Subject: [PATCH 159/230] ColorSet: add combine function (#8960) --- .../src/main/java/forge/card/CardRules.java | 2 +- forge-core/src/main/java/forge/card/ColorSet.java | 8 ++++++++ .../src/main/java/forge/game/GameActionUtil.java | 5 +++-- .../forge/game/ability/SpellAbilityEffect.java | 6 +++--- .../src/main/java/forge/game/card/Card.java | 6 +++--- .../java/forge/game/card/CardCopyService.java | 2 +- .../main/java/forge/game/card/CardFactory.java | 12 +++++------- .../src/main/java/forge/game/card/CardState.java | 10 +++++----- .../src/main/java/forge/game/card/CardView.java | 2 +- .../java/forge/game/card/token/TokenInfo.java | 15 +++++++++------ .../forge/game/spellability/AbilityManaPart.java | 2 +- .../staticability/StaticAbilityContinuous.java | 2 +- .../limited/CardThemedCommanderDeckBuilder.java | 4 ++-- .../limited/CardThemedConquestDeckBuilder.java | 4 ++-- .../gamemodes/limited/CardThemedDeckBuilder.java | 6 +++--- .../gamemodes/limited/LimitedDeckBuilder.java | 2 +- 16 files changed, 49 insertions(+), 39 deletions(-) diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index 3d3ffe0648c..5d103b779a5 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -235,7 +235,7 @@ public final class CardRules implements ICardCharacteristics { public ColorSet getColor() { switch (splitType.getAggregationMethod()) { case COMBINE: - return ColorSet.fromMask(mainPart.getColor().getColor() | otherPart.getColor().getColor()); + return ColorSet.combine(mainPart.getColor(), otherPart.getColor()); default: return mainPart.getColor(); } diff --git a/forge-core/src/main/java/forge/card/ColorSet.java b/forge-core/src/main/java/forge/card/ColorSet.java index e4b3fe19a5f..9a04094c6ba 100644 --- a/forge-core/src/main/java/forge/card/ColorSet.java +++ b/forge-core/src/main/java/forge/card/ColorSet.java @@ -123,6 +123,14 @@ public enum ColorSet implements Iterable, Serializable { return fromMask(mana.getColorProfile()); } + public static ColorSet combine(final ColorSet... colors) { + byte mask = 0; + for (ColorSet c : colors) { + mask |= c.getColor(); + } + return fromMask(mask); + } + /** * Checks for any color. * diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index b7e2ec6d469..85c10ceec85 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -19,8 +19,9 @@ package forge.game; import com.google.common.collect.Lists; import com.google.common.collect.Sets; + +import forge.card.ColorSet; import forge.card.GamePieceType; -import forge.card.MagicColor; import forge.card.mana.ManaCost; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityUtils; @@ -787,7 +788,7 @@ public final class GameActionUtil { eff.setOwner(controller); eff.setImageKey(sourceCard.getImageKey()); - eff.setColor(MagicColor.COLORLESS); + eff.setColor(ColorSet.C); eff.setGamePieceType(GamePieceType.EFFECT); // try to get the SpellAbility from the mana ability //eff.setEffectSource((SpellAbility)null); 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 b63c7e759e7..809bb14f04b 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -6,8 +6,8 @@ import com.google.common.collect.Maps; import com.google.common.collect.Table; import forge.GameCommand; import forge.card.CardRarity; +import forge.card.ColorSet; import forge.card.GamePieceType; -import forge.card.MagicColor; import forge.game.Game; import forge.game.GameEntity; import forge.game.GameObject; @@ -617,10 +617,10 @@ public abstract class SpellAbilityEffect { if (name.startsWith("Emblem")) { eff.setEmblem(true); // Emblem needs to be colorless - eff.setColor(MagicColor.COLORLESS); + eff.setColor(ColorSet.C); eff.setRarity(CardRarity.Common); } else { - eff.setColor(hostCard.getColor().getColor()); + eff.setColor(hostCard.getColor()); eff.setRarity(hostCard.getRarity()); } 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 0dce93c1862..a9e9d8d924e 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -4409,9 +4409,9 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr } public final void setColor(final String... color) { - setColor(ColorSet.fromNames(color).getColor()); + setColor(ColorSet.fromNames(color)); } - public final void setColor(final byte color) { + public final void setColor(final ColorSet color) { currentState.setColor(color); } @@ -4419,7 +4419,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr return getColor(currentState); } public final ColorSet getColor(CardState state) { - byte colors = state.getColor(); + byte colors = state.getColor().getColor(); for (final CardColor cc : Iterables.concat(changedCardColorsByText.values(), changedCardColorsCharacterDefining.values(), changedCardColors.values())) { if (cc.additional()) { colors |= cc.color().getColor(); diff --git a/forge-game/src/main/java/forge/game/card/CardCopyService.java b/forge-game/src/main/java/forge/game/card/CardCopyService.java index ebf6c3a6009..ba1496dfea9 100644 --- a/forge-game/src/main/java/forge/game/card/CardCopyService.java +++ b/forge-game/src/main/java/forge/game/card/CardCopyService.java @@ -303,7 +303,7 @@ public class CardCopyService { newCopy.setCounters(Maps.newHashMap(copyFrom.getCounters())); - newCopy.setColor(copyFrom.getColor().getColor()); + newCopy.setColor(copyFrom.getColor()); newCopy.setPhasedOut(copyFrom.getPhasedOut()); newCopy.setTapped(copyFrom.isTapped()); newCopy.setTributed(copyFrom.isTributed()); diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index b800d8e0a25..334c20ce2fe 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -339,12 +339,10 @@ public class CardFactory { card.setName(rules.getName()); // Combined mana cost - ManaCost combinedManaCost = ManaCost.combine(rules.getMainPart().getManaCost(), rules.getOtherPart().getManaCost()); - card.setManaCost(combinedManaCost); + card.setManaCost(rules.getManaCost()); // Combined card color - final byte combinedColor = (byte) (rules.getMainPart().getColor().getColor() | rules.getOtherPart().getColor().getColor()); - card.setColor(combinedColor); + card.setColor(rules.getColor()); card.setType(new CardType(rules.getType())); // Combined text based on Oracle text - might not be necessary @@ -407,7 +405,7 @@ public class CardFactory { // Super and 'middle' types should use enums. c.setType(new CardType(face.getType())); - c.setColor(face.getColor().getColor()); + c.setColor(face.getColor()); if (face.getIntPower() != Integer.MAX_VALUE) { c.setBasePower(face.getIntPower()); @@ -593,11 +591,11 @@ public class CardFactory { } if (cause.hasParam("AddColors")) { - state.addColor(colors.getColor()); + state.addColor(colors); } if (cause.hasParam("SetColor") || cause.hasParam("SetColorByManaCost")) { - state.setColor(colors.getColor()); + state.setColor(colors); } if (cause.hasParam("NonLegendary")) { diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index 0ddad73e783..2250fec1abe 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -60,7 +60,7 @@ public class CardState implements GameObject, IHasSVars, ITranslatable { private String name = ""; private CardType type = new CardType(false); private ManaCost manaCost = ManaCost.NO_COST; - private byte color = MagicColor.COLORLESS; + private ColorSet color = ColorSet.C; private String oracleText = ""; private String functionalVariantName = null; private int basePower = 0; @@ -191,14 +191,14 @@ public class CardState implements GameObject, IHasSVars, ITranslatable { view.updateManaCost(this); } - public final byte getColor() { + public final ColorSet getColor() { return color; } - public final void addColor(final byte color) { - this.color |= color; + public final void addColor(final ColorSet color) { + this.color = ColorSet.combine(this.color, color); view.updateColors(card); } - public final void setColor(final byte color) { + public final void setColor(final ColorSet color) { this.color = color; view.updateColors(card); } diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index 773b82204aa..1be63d44d66 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -1308,7 +1308,7 @@ public class CardView extends GameEntityView { set(TrackableProperty.Colors, c.getColor()); } void updateColors(CardState c) { - set(TrackableProperty.Colors, ColorSet.fromMask(c.getColor())); + set(TrackableProperty.Colors, c.getColor()); } void setOriginalColors(Card c) { set(TrackableProperty.OriginalColors, c.getColor()); diff --git a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java index 0cc09a46642..35032412eeb 100644 --- a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java +++ b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java @@ -6,8 +6,11 @@ import com.google.common.collect.Lists; import forge.ImageKeys; import forge.StaticData; import forge.card.CardType; +import forge.card.ColorSet; import forge.card.GamePieceType; import forge.card.MagicColor; +import forge.card.mana.ManaCost; +import forge.card.mana.ManaCostParser; import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.card.Card; @@ -32,14 +35,14 @@ public class TokenInfo { final String[] intrinsicKeywords; final int basePower; final int baseToughness; - final String color; + final ColorSet color; public TokenInfo(Card c) { // TODO: Figure out how to handle legacy images? this.name = c.getName(); this.imageName = ImageKeys.getTokenImageName(c.getImageKey()); this.manaCost = c.getManaCost().toString(); - this.color = MagicColor.toShortString(c.getCurrentState().getColor()); + this.color = c.getCurrentState().getColor(); this.types = getCardTypes(c); List list = Lists.newArrayList(); @@ -60,7 +63,7 @@ public class TokenInfo { String[] types = null; String[] keywords = null; String imageName = null; - String color = ""; + ColorSet color = null; for (String info : tokenInfo) { int index = info.indexOf(':'); if (index == -1) { @@ -80,7 +83,7 @@ public class TokenInfo { } else if (info.startsWith("Image:")) { imageName = remainder; } else if (info.startsWith("Color:")) { - color = remainder; + color = ColorSet.fromNames(remainder); } } @@ -115,7 +118,7 @@ public class TokenInfo { c.setName(name); c.setImageKey(ImageKeys.getTokenKey(imageName)); - c.setColor(color.isEmpty() ? manaCost : color); + c.setColor(color == null ? ColorSet.fromManaCost(new ManaCost(new ManaCostParser(manaCost))) : color); c.setGamePieceType(GamePieceType.TOKEN); for (final String t : types) { @@ -189,7 +192,7 @@ public class TokenInfo { } } - result.setColor(color); + result.setColor(ColorSet.fromMask(color)); } } if (!typeMap.isEmpty()) { 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 fd1b57c2969..6c09e994721 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -253,7 +253,7 @@ public class AbilityManaPart implements java.io.Serializable { eff.setOwner(sourceCard.getController()); eff.setImageKey(sourceCard.getImageKey()); - eff.setColor(MagicColor.COLORLESS); + eff.setColor(ColorSet.C); eff.setGamePieceType(GamePieceType.EFFECT); String cantcounterstr = "Event$ Counter | ValidSA$ Spell.IsRemembered | Description$ That spell can't be countered."; 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 bd394802db2..e75514ab2e8 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -623,7 +623,7 @@ public final class StaticAbilityContinuous { // Mana cost affectedCard.addChangedManaCost(state.getManaCost(), se.getTimestamp(), stAb.getId()); // color - affectedCard.addColorByText(ColorSet.fromMask(state.getColor()), se.getTimestamp(), stAb); + affectedCard.addColorByText(state.getColor(), se.getTimestamp(), stAb); // type affectedCard.addChangedCardTypesByText(new CardType(state.getType()), se.getTimestamp(), stAb.getId()); // abilities diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedCommanderDeckBuilder.java b/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedCommanderDeckBuilder.java index c410df4f374..8063ffa59cb 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedCommanderDeckBuilder.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedCommanderDeckBuilder.java @@ -34,9 +34,9 @@ public class CardThemedCommanderDeckBuilder extends CardThemedDeckBuilder { this.availableList.removeAll(aiPlayables); targetSize=format.getMainRange().getMinimum(); colors = keyCard.getRules().getColorIdentity(); - colors = ColorSet.fromMask(colors.getColor() | keyCard.getRules().getColorIdentity().getColor()); + colors = ColorSet.combine(colors, keyCard.getRules().getColorIdentity()); if (secondKeyCard != null && !format.equals(DeckFormat.Oathbreaker)) { - colors = ColorSet.fromMask(colors.getColor() | secondKeyCard.getRules().getColorIdentity().getColor()); + colors = ColorSet.combine(colors, secondKeyCard.getRules().getColorIdentity()); targetSize--; } numSpellsNeeded = ((Double)Math.floor(targetSize*(getCreaturePercentage()+getSpellPercentage()))).intValue(); diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedConquestDeckBuilder.java b/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedConquestDeckBuilder.java index 8e4cc8716de..a9f0160211a 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedConquestDeckBuilder.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedConquestDeckBuilder.java @@ -39,9 +39,9 @@ public class CardThemedConquestDeckBuilder extends CardThemedDeckBuilder { this.availableList.removeAll(aiPlayables); targetSize=format.getMainRange().getMinimum(); colors = keyCard.getRules().getColorIdentity(); - colors = ColorSet.fromMask(colors.getColor() | keyCard.getRules().getColorIdentity().getColor()); + colors = ColorSet.combine(colors, keyCard.getRules().getColorIdentity()); if(secondKeyCard!=null) { - colors = ColorSet.fromMask(colors.getColor() | secondKeyCard.getRules().getColorIdentity().getColor()); + colors = ColorSet.combine(colors, secondKeyCard.getRules().getColorIdentity()); targetSize--; } numSpellsNeeded = ((Double)Math.floor(targetSize*(getCreaturePercentage()+getSpellPercentage()))).intValue(); diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedDeckBuilder.java b/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedDeckBuilder.java index 0045139b11d..265fc5744fd 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedDeckBuilder.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/CardThemedDeckBuilder.java @@ -133,11 +133,11 @@ public class CardThemedDeckBuilder extends DeckGeneratorBase { System.out.println("Pre Colors: " + colors.toEnumSet().toString()); } if(!colors.hasAllColors(keyCard.getRules().getColorIdentity().getColor())){ - colors = ColorSet.fromMask(colors.getColor() | keyCard.getRules().getColorIdentity().getColor()); + colors = ColorSet.combine(colors, keyCard.getRules().getColorIdentity()); } if(secondKeyCard!=null) { if (!colors.hasAllColors(secondKeyCard.getRules().getColorIdentity().getColor())) { - colors = ColorSet.fromMask(colors.getColor() | secondKeyCard.getRules().getColorIdentity().getColor()); + colors = ColorSet.combine(colors, secondKeyCard.getRules().getColorIdentity()); } } numSpellsNeeded = ((Double)Math.floor(targetSize*(getCreaturePercentage()+getSpellPercentage()))).intValue(); @@ -462,7 +462,7 @@ public class CardThemedDeckBuilder extends DeckGeneratorBase { // Want a card that has just one "off" color. final ColorSet off = colors.getOffColors(card.getRules().getColor()); if (off.isMonoColor()) { - colors = ColorSet.fromMask(colors.getColor() | off.getColor()); + colors = ColorSet.combine(colors, off); break; } } diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedDeckBuilder.java b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedDeckBuilder.java index 170a7c3055b..b4b2ec11444 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedDeckBuilder.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedDeckBuilder.java @@ -484,7 +484,7 @@ public class LimitedDeckBuilder extends DeckGeneratorBase { // Want a card that has just one "off" color. final ColorSet off = colors.getOffColors(card.getRules().getColor()); if (off.isMonoColor()) { - colors = ColorSet.fromMask(colors.getColor() | off.getColor()); + colors = ColorSet.combine(colors, off); break; } } From 2fc6935563ab23a029322fdbaccb6f1f820a7ae6 Mon Sep 17 00:00:00 2001 From: Darthseid Date: Mon, 20 Oct 2025 12:20:57 -0400 Subject: [PATCH 160/230] Corrected Traumatic Prank's mana cost. (#8962) It was previously 3R. You can see the actual cost and effects here: https://scryfall.com/card/ysnc/12/traumatic-prank --- forge-gui/res/cardsfolder/t/traumatic_prank.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/t/traumatic_prank.txt b/forge-gui/res/cardsfolder/t/traumatic_prank.txt index e5299e0dd43..90463ba70c1 100644 --- a/forge-gui/res/cardsfolder/t/traumatic_prank.txt +++ b/forge-gui/res/cardsfolder/t/traumatic_prank.txt @@ -1,5 +1,5 @@ Name:Traumatic Prank -ManaCost:3 R +ManaCost:2 R Types:Sorcery A:SP$ GainControl | ValidTgts$ Creature | LoseControl$ EOT | Untap$ True | SubAbility$ DBAnimate | StackDescription$ REP target creature_{c:Targeted} | SpellDescription$ Gain control of target creature until end of turn. Untap that creature. SVar:DBAnimate:DB$ Animate | Defined$ Targeted | Keywords$ Haste | staticAbilities$ CantBlock | Triggers$ PingUpkeep | Duration$ Perpetual | StackDescription$ SpellDescription | SpellDescription$ It perpetually gains haste, "This creature can't block," and "At the beginning of your upkeep, this creature deals 1 damage to you." From 79d1f6f1daf1900a176ac95e8bed0736a2f029d7 Mon Sep 17 00:00:00 2001 From: Darthseid Date: Mon, 20 Oct 2025 13:44:42 -0400 Subject: [PATCH 161/230] Updated MTG Arena Balance Change. --- forge-gui/res/cardsfolder/d/diviner_of_fates.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/d/diviner_of_fates.txt b/forge-gui/res/cardsfolder/d/diviner_of_fates.txt index 00a871df6da..db01af132a0 100644 --- a/forge-gui/res/cardsfolder/d/diviner_of_fates.txt +++ b/forge-gui/res/cardsfolder/d/diviner_of_fates.txt @@ -1,7 +1,7 @@ Name:Diviner of Fates ManaCost:W U B Types:Creature Octopus Wizard -PT:2/1 +PT:2/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigConnive | TriggerDescription$ When CARDNAME enters, it connives. (Draw a card, then discard a card. If you discarded a nonland card, put a +1/+1 counter on this creature.) SVar:TrigConnive:DB$ Connive T:Mode$ DiscardedAll | ValidPlayer$ You | ValidCard$ Card | TriggerZones$ Battlefield | Execute$ TrigSeek | ActivationLimit$ 1 | TriggerDescription$ Whenever you discard one or more cards, seek a card that shares a card type with one of the discarded cards. This ability triggers only once each turn. From 608a1c11eda0ffddb7e8b5e1204a005cd24f4196 Mon Sep 17 00:00:00 2001 From: Darthseid Date: Mon, 20 Oct 2025 13:46:15 -0400 Subject: [PATCH 162/230] Updated Official MTG Arena Change. --- forge-gui/res/cardsfolder/c/cabaretti_revels.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/c/cabaretti_revels.txt b/forge-gui/res/cardsfolder/c/cabaretti_revels.txt index c6ca583db19..3c8c6cf8bdb 100644 --- a/forge-gui/res/cardsfolder/c/cabaretti_revels.txt +++ b/forge-gui/res/cardsfolder/c/cabaretti_revels.txt @@ -1,5 +1,5 @@ Name:Cabaretti Revels -ManaCost:R R G +ManaCost:1 R G Types:Enchantment T:Mode$ SpellCast | ValidCard$ Creature | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSeek | TriggerDescription$ Whenever you cast a creature spell, seek a creature card with lesser mana value, then put it onto the battlefield. SVar:TrigSeek:DB$ Seek | Type$ Creature.cmcLTX | RememberFound$ True | SubAbility$ DBPut From 21977e7dcd4dfc32b05f131d0463a350aa0a1c82 Mon Sep 17 00:00:00 2001 From: Darthseid Date: Mon, 20 Oct 2025 13:59:20 -0400 Subject: [PATCH 163/230] Updated to match MTG Arena changes. https://www.reddit.com/r/MagicArena/comments/16b18t8/a_bunch_of_cards_were_unnerfed_for_rotation_to/ --- forge-gui/res/cardsfolder/t/town_razer_tyrant.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/cardsfolder/t/town_razer_tyrant.txt b/forge-gui/res/cardsfolder/t/town_razer_tyrant.txt index 1c05f2ea154..ffd6ad3623b 100644 --- a/forge-gui/res/cardsfolder/t/town_razer_tyrant.txt +++ b/forge-gui/res/cardsfolder/t/town_razer_tyrant.txt @@ -3,8 +3,8 @@ ManaCost:2 R R Types:Creature Dragon PT:4/4 K:Flying -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigAnimate | TriggerDescription$ When CARDNAME enters, target nonbasic land you don't control loses all abilities except mana abilities and gains "At the beginning of your end step, this permanent deals 2 damage to you unless you sacrifice it." +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigAnimate | TriggerDescription$ When CARDNAME enters, target nonbasic land you don't control loses all abilities except mana abilities and gains "At the beginning of your upkeep, this permanent deals 2 damage to you unless you sacrifice it." SVar:TrigAnimate:DB$ Animate | ValidTgts$ Land.nonBasic+YouDontCtrl | TgtPrompt$ Select target nonbasic land you don't control | RemoveNonManaAbilities$ True | Triggers$ EndofTurnShock | Duration$ Permanent -SVar:EndofTurnShock:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ At the beginning of your end step, CARDNAME deals 2 damage to you unless you sacrifice it. +SVar:EndofTurnShock:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDamage | TriggerDescription$ At the beginning of your upkeep, CARDNAME deals 2 damage to you unless you sacrifice it. SVar:TrigDamage:DB$ DealDamage | Defined$ You | NumDmg$ 2 | UnlessCost$ Sac<1/CARDNAME> | UnlessPayer$ TriggeredPlayer -Oracle:Flying\nWhen Town-Razer Tyrant enters, target nonbasic land you don't control loses all abilities except mana abilities and gains "At the beginning of your end step, this permanent deals 2 damage to you unless you sacrifice it." +Oracle:Flying\nWhen Town-Razer Tyrant enters, target nonbasic land you don't control loses all abilities except mana abilities and gains "At the beginning of your upkeep, this permanent deals 2 damage to you unless you sacrifice it." From 42357d5d6f5858d28277d0b00ef6243f851058b3 Mon Sep 17 00:00:00 2001 From: Darthseid Date: Mon, 20 Oct 2025 14:13:03 -0400 Subject: [PATCH 164/230] Updated to match MTG Arena changes. --- forge-gui/res/cardsfolder/s/sanguine_brushstroke.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/cardsfolder/s/sanguine_brushstroke.txt b/forge-gui/res/cardsfolder/s/sanguine_brushstroke.txt index a927429ad9a..08c8e75f3a8 100644 --- a/forge-gui/res/cardsfolder/s/sanguine_brushstroke.txt +++ b/forge-gui/res/cardsfolder/s/sanguine_brushstroke.txt @@ -4,7 +4,8 @@ Types:Enchantment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters, create a Blood token and conjure a card named Blood Artist onto the battlefield. SVar:TrigToken:DB$ Token | TokenScript$ c_a_blood_draw | SubAbility$ DBMakeCard SVar:DBMakeCard:DB$ MakeCard | Conjure$ True | Name$ A-Blood Artist | Zone$ Battlefield -T:Mode$ Sacrificed | ValidCard$ Blood.token+YouCtrl | Execute$ TrigLoseLife | TriggerZones$ Battlefield | TriggerDescription$ Whenever you sacrifice a Blood token, each opponent loses 1 life. -SVar:TrigLoseLife:DB$ LoseLife | Defined$ Player.Opponent | LifeAmount$ 1 +T:Mode$ Sacrificed | ValidCard$ Blood.token+YouCtrl | Execute$ TrigLoseLife | TriggerZones$ Battlefield | TriggerDescription$ Whenever you sacrifice a Blood token, each opponent loses 1 life and you gain 1 life. +SVar:TrigLoseLife:DB$ LoseLife | Defined$ Player.Opponent | LifeAmount$ 1 | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 DeckHas:Ability$Token|Sacrifice|LifeGain & Type$Blood -Oracle:When Sanguine Brushstroke enters, create a Blood token and conjure a card named Blood Artist onto the battlefield.\nWhenever you sacrifice a Blood token, each opponent loses 1 life. +Oracle:When Sanguine Brushstroke enters the battlefield, create a Blood token and conjure a card named Blood Artist onto the battlefield.\nWhenever you sacrifice a Blood token, each opponent loses 1 life and you gain 1 life. From 6cc2dcf3f01cc140a4c8bb4cb8d72c81864b247d Mon Sep 17 00:00:00 2001 From: Darthseid Date: Mon, 20 Oct 2025 14:21:35 -0400 Subject: [PATCH 165/230] Updated to match MTG Arena changes. --- forge-gui/res/cardsfolder/f/fearsome_whelp.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui/res/cardsfolder/f/fearsome_whelp.txt b/forge-gui/res/cardsfolder/f/fearsome_whelp.txt index f08fb9e3d9a..776cd20ccea 100644 --- a/forge-gui/res/cardsfolder/f/fearsome_whelp.txt +++ b/forge-gui/res/cardsfolder/f/fearsome_whelp.txt @@ -4,8 +4,8 @@ Types:Creature Dragon PT:1/1 K:Flying K:Haste -T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigAnimate | TriggerDescription$ At the beginning of your upkeep, each Dragon card in your hand perpetually gains "This spell costs {1} less to cast." +T:Mode$ Phase | Phase$ EndStep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigAnimate | TriggerDescription$ At the beginning of your end step, each Dragon card in your hand perpetually gains "This spell costs {1} less to cast." SVar:TrigAnimate:DB$ AnimateAll | ValidCards$ Dragon.YouOwn | Zone$ Hand | staticAbilities$ DragonReduceCost | Duration$ Perpetual SVar:DragonReduceCost:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ 1 | EffectZone$ All | Description$ This spell costs {1} less to cast. DeckHints:Type$Dragon -Oracle:Flying, haste\nAt the beginning of your upkeep, each Dragon card in your hand perpetually gains "This spell costs {1} less to cast." +Oracle:Flying, haste\nAt the beginning of your end step, each Dragon card in your hand perpetually gains "This spell costs {1} less to cast." From 1188b6889a254678771ad2c50335c31f9bc66d7b Mon Sep 17 00:00:00 2001 From: tool4ever Date: Tue, 21 Oct 2025 10:58:53 +0200 Subject: [PATCH 166/230] Fix triggered Iterable missing support for LKI (#8968) --- .../src/main/java/forge/game/ability/AbilityUtils.java | 7 ++++++- .../java/forge/game/ability/effects/ChangeZoneEffect.java | 8 +++++++- forge-gui/res/cardsfolder/s/sail_into_the_west.txt | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) 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 d1b0a3f7154..2e07716ef58 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -185,7 +185,12 @@ public class AbilityUtils { if (crd instanceof Card) { c = game.getCardState((Card) crd); } else if (crd instanceof Iterable) { - cards.addAll(IterableUtil.filter((Iterable) crd, Card.class)); + for (Card gameCard : IterableUtil.filter((Iterable) crd, Card.class)) { + if (gameCard.isLKI()) { + gameCard = game.getCardState(gameCard); + } + cards.add(gameCard); + } } } } else if (defined.startsWith("Replaced") && sa instanceof SpellAbility) { 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 885b6ab442d..dada4e8a4d9 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 @@ -999,8 +999,14 @@ public class ChangeZoneEffect extends SpellAbilityEffect { boolean shuffleMandatory = true; boolean searchedLibrary = false; if (defined) { + fetchList = new CardCollection(); String param = chooseFromDef ? "ChooseFromDefined" : "Defined"; - fetchList = AbilityUtils.getDefinedCards(source, sa.getParam(param), sa); + for (Card c : AbilityUtils.getDefinedCards(source, sa.getParam(param), sa)) { + Card gameCard = game.getCardState(c, null); + if (gameCard != null && c.equalsWithGameTimestamp(gameCard) && !gameCard.isPhasedOut()) { + fetchList.add(gameCard); + } + } if (!sa.hasParam("ChangeNum")) { changeNum = fetchList.size(); } diff --git a/forge-gui/res/cardsfolder/s/sail_into_the_west.txt b/forge-gui/res/cardsfolder/s/sail_into_the_west.txt index 73229a8e0f6..3b7001522e6 100644 --- a/forge-gui/res/cardsfolder/s/sail_into_the_west.txt +++ b/forge-gui/res/cardsfolder/s/sail_into_the_west.txt @@ -2,7 +2,7 @@ Name:Sail into the West ManaCost:2 G U Types:Instant A:SP$ Vote | Defined$ Player | Choices$ DBChangeZone,DBWheel | VoteTiedAbility$ DBWheel | StackDescription$ REP you, each player votes_{p:You}, {p:Player} each vote & each player returns_{p:Player} each return & each player may_{p:Player} may each | SpellDescription$ Will of the council — Starting with you, each player votes for return or embark. If return gets more votes, each player returns up to two cards from their graveyard to their hand, then you exile CARDNAME. If embark gets more votes or the vote is tied, each player may discard their hand and draw seven cards. -SVar:DBChangeZone:DB$ ChangeZone | Defined$ Player | Origin$ Graveyard | Destination$ Hand | ChangeNum$ 2 | Hidden$ True | SubAbility$ ExileSelf | SpellDescription$ If return gets more votes, each player returns up to two cards from their graveyard to their hand, then you exile CARDNAME. +SVar:DBChangeZone:DB$ ChangeZone | DefinedPlayer$ Player | Origin$ Graveyard | Destination$ Hand | ChangeNum$ 2 | Hidden$ True | SubAbility$ ExileSelf | SpellDescription$ If return gets more votes, each player returns up to two cards from their graveyard to their hand, then you exile CARDNAME. SVar:ExileSelf:DB$ ChangeZone | Origin$ Stack | Destination$ Exile SVar:DBWheel:DB$ Discard | Mode$ Hand | Defined$ Player | Optional$ True | RememberDiscardingPlayers$ True | SubAbility$ DBDraw | SpellDescription$ If embark gets more votes or the vote is tied, each player may discard their hand and draw seven cards. SVar:DBDraw:DB$ Draw | Defined$ Remembered | NumCards$ 7 | SubAbility$ DBCleanup From f9b6652c2aeb0cbd86891509819936f382156e2b Mon Sep 17 00:00:00 2001 From: Jetz72 Date: Tue, 21 Oct 2025 09:00:59 -0400 Subject: [PATCH 167/230] Flavor Names (#8849) * Make getAllFaces return nonnull list * Optimize Predicates * CardDB and script syntax changes * Apply syntax changes * In-game support for flavor names * Add display names to PaperCards * Support searching by flavor names * Remove some WIP stuff * Update PaperCard translation key. * Update capitalization * Auto-map to variants when edition entry uses a flavor name * Consolidate display name logic. * Added syntax for generating flavor named variants in edition files. * Some examples of new syntax. * Ignore flavored oracle text when searching rules text * add hasFlavorName * Add image key * Get correct variant from card requests with flavor names. --- .../src/main/java/forge/StaticData.java | 21 +- .../src/main/java/forge/card/CardDb.java | 218 +++++++------- .../src/main/java/forge/card/CardEdition.java | 152 +++++----- .../src/main/java/forge/card/CardFace.java | 99 +++---- .../src/main/java/forge/card/CardRules.java | 70 ++++- .../java/forge/card/CardRulesPredicates.java | 22 +- .../main/java/forge/card/CardSplitType.java | 8 +- .../src/main/java/forge/card/ICardFace.java | 2 +- .../src/main/java/forge/deck/DeckFormat.java | 2 +- .../src/main/java/forge/item/IPaperCard.java | 7 +- .../main/java/forge/item/InventoryItem.java | 15 + .../src/main/java/forge/item/PaperCard.java | 86 +++++- .../java/forge/item/PaperCardPredicates.java | 18 ++ .../src/main/java/forge/item/PaperToken.java | 11 + .../main/java/forge/util/CardTranslation.java | 4 + .../src/main/java/forge/util/ImageUtil.java | 2 + .../main/java/forge/util/IterableUtil.java | 5 + .../src/main/java/forge/game/GameFormat.java | 4 +- .../ability/effects/AlterAttributeEffect.java | 4 +- .../src/main/java/forge/game/card/Card.java | 29 +- .../java/forge/game/card/CardFactory.java | 2 + .../main/java/forge/game/card/CardState.java | 16 +- .../main/java/forge/game/card/CardView.java | 46 ++- .../forge/trackable/TrackableProperty.java | 1 + .../main/java/forge/gui/CardDetailPanel.java | 2 +- .../main/java/forge/gui/CardListChooser.java | 2 +- .../forge/itemmanager/views/ImageView.java | 2 +- .../forge/itemmanager/views/StarRenderer.java | 4 +- .../forge/screens/deckeditor/DeckImport.java | 2 +- .../main/java/forge/screens/home/VLobby.java | 2 +- .../java/forge/screens/match/CMatchUI.java | 4 +- .../toolbox/imaging/FCardImageRenderer.java | 2 +- .../forge/toolbox/special/CardZoomer.java | 4 +- .../java/forge/view/arcane/CardPanel.java | 4 +- .../main/java/forge/view/arcane/PlayArea.java | 8 +- .../src/forge/adventure/util/RewardActor.java | 2 +- forge-gui-mobile/src/forge/card/CardZoom.java | 2 +- .../src/forge/deck/FDeckEditor.java | 4 +- .../forge/itemmanager/views/ImageView.java | 8 +- .../screens/constructed/LobbyScreen.java | 2 +- .../screens/constructed/PlayerPanel.java | 2 +- .../src/forge/screens/match/views/VField.java | 6 +- .../forge/screens/match/views/VReveal.java | 2 +- .../ConquestCommandersScreen.java | 2 +- .../ConquestMultiverseScreen.java | 4 +- .../planarconquest/LoadConquestScreen.java | 2 +- .../src/forge/util/CardRendererUtils.java | 4 +- .../cardsfolder/b/blanka_ferocious_friend.txt | 2 +- .../res/cardsfolder/c/chief_jim_hopper.txt | 2 +- .../cardsfolder/c/chun_li_countless_kicks.txt | 2 +- .../cardsfolder/d/daryl_hunter_of_walkers.txt | 2 +- .../d/dhalsim_pliable_pacifist.txt | 2 +- ...c_natures_warden_doric_owlbear_avenger.txt | 4 +- .../cardsfolder/d/dustin_gadget_genius.txt | 2 +- .../cardsfolder/e/e_honda_sumo_champion.txt | 2 +- .../e/edgin_larcenous_lutenist.txt | 2 +- .../res/cardsfolder/e/eleven_the_mage.txt | 2 +- .../f/forge_neverwinter_charlatan.txt | 2 +- .../cardsfolder/g/glenn_the_voice_of_calm.txt | 2 +- .../res/cardsfolder/g/guile_sonic_soldier.txt | 2 +- ...ns_national_laboratory_the_upside_down.txt | 4 +- .../cardsfolder/h/holga_relentless_rager.txt | 2 +- .../res/cardsfolder/k/ken_burning_brawler.txt | 2 +- .../cardsfolder/l/lucas_the_sharpshooter.txt | 2 +- forge-gui/res/cardsfolder/l/lucille.txt | 2 +- .../res/cardsfolder/m/max_the_daredevil.txt | 2 +- .../m/michonne_ruthless_survivor.txt | 2 +- .../cardsfolder/m/mike_the_dungeon_master.txt | 2 +- .../cardsfolder/m/mind_flayer_the_shadow.txt | 2 +- .../cardsfolder/n/negan_the_cold_blooded.txt | 2 +- .../n/no_secret_is_hidden_from_me.txt | 2 +- .../cardsfolder/r/rick_steadfast_leader.txt | 2 +- .../res/cardsfolder/r/ryu_world_warrior.txt | 2 +- .../s/simon_wild_magic_sorcerer.txt | 2 +- forge-gui/res/cardsfolder/w/will_the_wise.txt | 2 +- .../cardsfolder/x/xenk_paladin_unbroken.txt | 2 +- .../cardsfolder/z/zangief_the_red_cyclone.txt | 2 +- .../res/editions/Secret Lair Drop Series.txt | 10 +- forge-gui/res/editions/The List.txt | 2 +- forge-gui/res/editions/Unfinity.txt | 268 +++++++++--------- forge-gui/res/editions/Unstable.txt | 72 ++--- .../gamemodes/limited/LimitedPlayer.java | 34 +-- .../planarconquest/ConquestCommander.java | 12 +- .../planarconquest/ConquestData.java | 6 +- .../quest/QuestTournamentController.java | 2 +- .../java/forge/gui/card/CardDetailUtil.java | 14 +- .../forge/itemmanager/AdvancedSearch.java | 20 +- .../itemmanager/AdvancedSearchParser.java | 36 +-- .../java/forge/itemmanager/ColumnDef.java | 8 +- .../java/forge/itemmanager/SFilterUtil.java | 56 ++-- 90 files changed, 898 insertions(+), 626 deletions(-) diff --git a/forge-core/src/main/java/forge/StaticData.java b/forge-core/src/main/java/forge/StaticData.java index 249724aa50a..86869167285 100644 --- a/forge-core/src/main/java/forge/StaticData.java +++ b/forge-core/src/main/java/forge/StaticData.java @@ -68,10 +68,6 @@ public class StaticData { this(cardReader, null, customCardReader, null, editionFolder, customEditionsFolder, blockDataFolder, "", cardArtPreference, enableUnknownCards, loadNonLegalCards, false, false); } - public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, CardStorageReader customCardReader, CardStorageReader customTokenReader, String editionFolder, String customEditionsFolder, String blockDataFolder, String setLookupFolder, String cardArtPreference, boolean enableUnknownCards, boolean loadNonLegalCards, boolean allowCustomCardsInDecksConformance){ - this(cardReader, tokenReader, customCardReader, customTokenReader, editionFolder, customEditionsFolder, blockDataFolder, setLookupFolder, cardArtPreference, enableUnknownCards, loadNonLegalCards, allowCustomCardsInDecksConformance, false); - } - public StaticData(CardStorageReader cardReader, CardStorageReader tokenReader, CardStorageReader customCardReader, CardStorageReader customTokenReader, String editionFolder, String customEditionsFolder, String blockDataFolder, String setLookupFolder, String cardArtPreference, boolean enableUnknownCards, boolean loadNonLegalCards, boolean allowCustomCardsInDecksConformance, boolean enableSmartCardArtSelection) { this.cardReader = cardReader; this.tokenReader = tokenReader; @@ -81,8 +77,9 @@ public class StaticData { this.enableSmartCardArtSelection = enableSmartCardArtSelection; this.loadNonLegalCards = loadNonLegalCards; lastInstance = this; - List funnyCards = new ArrayList<>(); - List filtered = new ArrayList<>(); + Set funnyCards = new HashSet<>(); + Set filtered = new HashSet<>(); + editions.append(new CardEdition.Collection(new CardEdition.Reader(new File(customEditionsFolder), true))); { @@ -108,7 +105,7 @@ public class StaticData { final String cardName = card.getName(); - if (!loadNonLegalCards && !card.getType().isLand() && funnyCards.contains(cardName)) + if (!loadNonLegalCards && funnyCards.contains(cardName) && !card.getType().isBasicLand()) filtered.add(cardName); if (card.isVariant()) { @@ -131,12 +128,11 @@ public class StaticData { } } - if (!filtered.isEmpty()) { - Collections.sort(filtered); - } + commonCards = new CardDb(regularCards, editions, filtered); + variantCards = new CardDb(variantsCards, editions, filtered); - commonCards = new CardDb(regularCards, editions, filtered, cardArtPreference); - variantCards = new CardDb(variantsCards, editions, filtered, cardArtPreference); + commonCards.setCardArtPreference(cardArtPreference); + variantCards.setCardArtPreference(cardArtPreference); //must initialize after establish field values for the sake of card image logic commonCards.initialize(false, false, enableUnknownCards); @@ -561,7 +557,6 @@ public class StaticData { * @param allowedSetCodes The list of the allowed set codes to consider when looking for alternative card art * candidates. If the list is not null and not empty, will be used in combination with the * isLegal predicate. - * @see CardDb#isLegal(List) * @return an instance of PaperCard that is the selected alternative candidate, or null * if None could be found. */ diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java index 4c4cc36242b..d51d23e0fa1 100644 --- a/forge-core/src/main/java/forge/card/CardDb.java +++ b/forge-core/src/main/java/forge/card/CardDb.java @@ -17,10 +17,7 @@ */ package forge.card; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimaps; +import com.google.common.collect.*; import forge.ImageKeys; import forge.StaticData; import forge.card.CardEdition.EditionEntry; @@ -30,7 +27,6 @@ import forge.item.IPaperCard; import forge.item.PaperCard; import forge.util.Lang; import forge.util.TextUtil; -import forge.util.lang.LangEnglish; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; @@ -53,12 +49,15 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { private final Map facesByName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); private final Map normalizedNames = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); private static Map artPrefs = Maps.newHashMap(); + /** + * Map of flavor names to the identifier of the functional variant on which they appear in their respective card rules. + */ + private final Map flavorNameMappings = Maps.newHashMap(); - private final Map alternateName = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); private final Map artIds = Maps.newHashMap(); private final CardEdition.Collection editions; - private List filtered; + private final Set filtered; private Map nonLegendaryCreatureNames = Maps.newHashMap(); @@ -294,11 +293,15 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { } } - public CardDb(Map rules, CardEdition.Collection editions0, List filteredCards, String cardArtPreference) { + public CardDb(Map rules, CardEdition.Collection editions0, Set filteredCards) { this.filtered = filteredCards; this.rulesByName = rules; this.editions = editions0; + //Collects additional mappings used for flavor names + //Need an extra map for these to avoid ConcurrentModificationException + Map extraRuleMappings = new HashMap<>(); + // create faces list from rules for (final CardRules rule : rules.values()) { if (filteredCards.contains(rule.getName())) @@ -306,8 +309,12 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { for (ICardFace face : rule.getAllFaces()) { addFaceToDbNames(face); } + if (rule.hasFunctionalVariants()){ + cacheFlavorNames(rule, extraRuleMappings); + } } - setCardArtPreference(cardArtPreference); + + rulesByName.putAll(extraRuleMappings); } private void addFaceToDbNames(ICardFace face) { @@ -321,14 +328,40 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { normalizedNames.put(normalName, name); } - final String altName = face.getAltName(); - if (altName != null) { - alternateName.put(altName, face.getName()); - final String normalAltName = StringUtils.stripAccents(altName); - if (!normalAltName.equals(altName)) { - normalizedNames.put(normalAltName, altName); - } + if(face.hasFunctionalVariants()) { + for(ICardFace varFace : face.getFunctionalVariants().values()) + cacheFlavorName(varFace); } + if (face.getFlavorName() != null) //Probably shouldn't be putting a flavor name on the main print? + cacheFlavorName(face); + } + + private void cacheFlavorName(ICardFace face) { + String altName = face.getFlavorName(); + if(altName == null) + return; + facesByName.putIfAbsent(altName, face); + final String normalAltName = StringUtils.stripAccents(altName); + if (!normalAltName.equals(altName)) { + normalizedNames.put(normalAltName, altName); + } + } + + private void cacheFlavorNames(CardRules rules, Map map) { + if(rules.getSupportedFunctionalVariants() == null) + return; + boolean hasFlavorName = false; + String baseName = rules.getName(); + for(String variantName : rules.getSupportedFunctionalVariants()) { + String name = rules.getDisplayNameForVariant(variantName); + if(baseName.equals(name)) + continue; + hasFlavorName = true; + map.put(name, rules); + flavorNameMappings.put(name, variantName); + } + if(hasFlavorName) + flavorNameMappings.put(baseName, IPaperCard.NO_FUNCTIONAL_VARIANT); } private void addSetCard(CardEdition e, EditionEntry cis, CardRules cr) { @@ -337,16 +370,40 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { if (artIds.containsKey(key)) { artIdx = artIds.get(key) + 1; } - artIds.put(key, artIdx); - addCard(new PaperCard(cr, e.getCode(), cis.rarity(), artIdx, false, cis.collectorNumber(), cis.artistName(), cis.functionalVariantName())); + + String variantName = cis.getFunctionalVariantName(); + String flavorName = cis.getFlavorName(); + assert(variantName == null || flavorName == null); //Can't currently assign both this way. + + if(variantName == null && !cr.getName().equals(cis.name())) { + //If an edition entry uses a known flavor name without specifying the variant, swap to that variant. + variantName = flavorNameMappings.get(cis.name()); + //System.out.printf("Auto-mapping flavor name \"%s\" -> \"%s\" $%s\n", cis.name(), cr.getName(), variantName); + } + if(flavorName != null) { + String suggestedFlavorName = e.getCode().startsWith("OM") ? "Alchemy" + : e.getCode().equals("SLX") ? "UniversesWithin" + : null; + variantName = cr.findOrCreateVariantForFlavorName(flavorName, suggestedFlavorName); + String normalizedFlavorName = cr.getDisplayNameForVariant(variantName); + if(!flavorNameMappings.containsKey(normalizedFlavorName)) { + flavorNameMappings.put(normalizedFlavorName, variantName); + rulesByName.put(normalizedFlavorName, cr); + cacheFlavorName(cr.getMainPart().getFunctionalVariant(variantName)); + if(cr.getOtherPart() != null) + cacheFlavorName(cr.getOtherPart().getFunctionalVariant(variantName)); + } + } + + addCard(new PaperCard(cr, e.getCode(), cis.rarity(), artIdx, false, cis.collectorNumber(), cis.artistName(), variantName)); } private boolean addFromSetByName(String cardName, CardEdition ed, CardRules cr) { List cardsInSet = ed.getCardInSet(cardName); // empty collection if not present if (cr.hasFunctionalVariants()) { - cardsInSet = cardsInSet.stream().filter(c -> StringUtils.isEmpty(c.functionalVariantName()) - || cr.getSupportedFunctionalVariants().contains(c.functionalVariantName()) + cardsInSet = cardsInSet.stream().filter(c -> StringUtils.isEmpty(c.getFunctionalVariantName()) + || cr.getSupportedFunctionalVariants().contains(c.getFunctionalVariantName()) ).collect(Collectors.toList()); } if (cardsInSet.isEmpty()) @@ -383,9 +440,6 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { CardEdition upcomingSet = null; Date today = new Date(); - // do this first so they're not considered missing - buildRenamedCards(); - for (CardEdition e : editions.getOrderedEditions()) { boolean coreOrExpSet = e.getType() == CardEdition.Type.CORE || e.getType() == CardEdition.Type.EXPANSION; boolean isCoreExpSet = coreOrExpSet || e.getType() == CardEdition.Type.REPRINT; @@ -403,8 +457,8 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { continue; } if (cr.hasFunctionalVariants()) { - if (StringUtils.isNotEmpty(cis.functionalVariantName()) - && !cr.getSupportedFunctionalVariants().contains(cis.functionalVariantName())) { + if (StringUtils.isNotEmpty(cis.getFunctionalVariantName()) + && !cr.getSupportedFunctionalVariants().contains(cis.getFunctionalVariantName())) { //Supported card, unsupported variant. //Could note the card as missing but since these are often un-cards, //it's likely absent because it does something out of scope. @@ -455,71 +509,28 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { reIndex(); } - private void buildRenamedCards() { - Lang lang = Lang.getInstance(); - if (lang == null) { - // for some tests - lang = new LangEnglish(); - } - // for now just check Universes Within - for (EditionEntry cis : editions.get("SLX").getCards()) { - String orgName = alternateName.get(cis.name()); - if (orgName != null) { - // found original (beyond) print - CardRules org = getRules(orgName); - - CardFace renamedMain = (CardFace) ((CardFace) org.getMainPart()).clone(); - renamedMain.setName(renamedMain.getAltName()); - renamedMain.setAltName(null); - // TODO this could mess up some "named ..." cardname literals but there's no printing like that currently - renamedMain.setOracleText(renamedMain.getOracleText() - .replace(orgName, renamedMain.getName()) - .replace(lang.getNickName(orgName), lang.getNickName(renamedMain.getName())) - ); - facesByName.put(renamedMain.getName(), renamedMain); - CardFace renamedOther = null; - if (org.getOtherPart() != null) { - renamedOther = (CardFace) ((CardFace) org.getOtherPart()).clone(); - orgName = renamedOther.getName(); - renamedOther.setName(renamedOther.getAltName()); - renamedOther.setAltName(null); - renamedOther.setOracleText(renamedOther.getOracleText() - .replace(orgName, renamedOther.getName()) - .replace(lang.getNickName(orgName), lang.getNickName(renamedOther.getName())) - ); - facesByName.put(renamedOther.getName(), renamedOther); - } - - CardRules within = new CardRules(new ICardFace[] { renamedMain, renamedOther, null, null, null, null, null }, org.getSplitType(), org.getAiHints()); - // so workshop can edit same script - within.setNormalizedName(org.getNormalizedName()); - rulesByName.put(cis.name(), within); - } - } - } - public void addCard(PaperCard paperCard) { if (filtered.contains(paperCard.getName())) { return; } - allCardsByName.put(paperCard.getName(), paperCard); + String mainName = paperCard.getName(); + allCardsByName.put(mainName, paperCard); - if (paperCard.getRules().getSplitType() == CardSplitType.None) { + CardRules rules = paperCard.getRules(); + if (rules.getSplitType() == CardSplitType.None && !rules.hasFunctionalVariants()) { return; } - if (paperCard.getRules().getOtherPart() != null) { - //allow looking up card by the name of other faces - allCardsByName.put(paperCard.getRules().getOtherPart().getName(), paperCard); - } - if (paperCard.getRules().getSplitType() == CardSplitType.Split) { - //also include main part for split cards - allCardsByName.put(paperCard.getRules().getMainPart().getName(), paperCard); - } else if (paperCard.getRules().getSplitType() == CardSplitType.Specialize) { - //also include specialize faces - for (ICardFace face : paperCard.getRules().getSpecializeParts().values()) allCardsByName.put(face.getName(), paperCard); - } + //Card may have multiple names. Add it under all of them. + + List allFaces = paperCard.getAllFaces(); + Set namesToAdd = new HashSet<>(); + allFaces.stream().map(ICardCharacteristics::getName).forEach(namesToAdd::add); + allFaces.stream().map(ICardFace::getFlavorName).filter(Objects::nonNull).forEach(namesToAdd::add); + namesToAdd.remove(mainName); + for(String name : namesToAdd) + allCardsByName.put(name, paperCard); } private void reIndex() { @@ -560,11 +571,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { public CardRules getRules(String cardName) { CardRules result = rulesByName.get(cardName); - if (result != null) { - return result; - } else { - return CardRules.getUnsupportedCardNamed(cardName); - } + return Objects.requireNonNullElseGet(result, () -> CardRules.getUnsupportedCardNamed(cardName)); } public CardArtPreference getCardArtPreference(){ return this.defaultCardArtPreference; } @@ -869,10 +876,17 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { cardQueryFilter = card -> card.getArtIndex() == cr.artIndex; cardQueryFilter = cardQueryFilter.and(filter); cards = getAllCards(cr.cardName, cardQueryFilter); - // Note: No need to check whether "cards" is empty; the next for loop will validate condition at L699 + if (cards.isEmpty()) + return null; if (cards.size() == 1) // if only one candidate, there much else we should do return cr.isFoil ? cards.get(0).getFoiled() : cards.get(0); + if (flavorNameMappings.containsKey(cr.cardName)) { + Collection matchingNames = cards.stream().filter(c -> c.getDisplayName().equals(cr.cardName)).collect(Collectors.toSet()); + if(!matchingNames.isEmpty()) + cards.retainAll(matchingNames); + } + /* 2. Retrieve cards based of [Frame]Set Preference ================================================ */ // Collect the list of all editions found for target card @@ -967,11 +981,11 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { } public List getUniqueCardsNoAlt(String cardName) { - return Lists.newArrayList(Maps.filterEntries(uniqueCardsByName, entry -> entry.getKey().equals(entry.getValue().getName())).get(getName(cardName))); + return Lists.newArrayList(Maps.filterEntries(uniqueCardsByName, entry -> entry.getKey().equals(entry.getValue().getName())).get(getNormalizedName(cardName))); } public PaperCard getUniqueByName(final String name) { - return uniqueCardsByName.get(getName(name)); + return uniqueCardsByName.get(getNormalizedName(name)); } public Collection getAllFaces() { @@ -979,7 +993,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { } public ICardFace getFaceByName(final String name) { - return facesByName.get(getName(name)); + return facesByName.get(getNormalizedName(name)); } public boolean isNonLegendaryCreatureName(final String name) { @@ -1050,26 +1064,20 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { return streamAllCardsNoAlt().filter(EDITION_NON_REPRINT).collect(Collectors.toList()); } - public String getName(final String cardName) { - return getName(cardName, false); - } - public String getName(String cardName, boolean engine) { + public String getNormalizedName(final String cardName) { + String cardName1 = cardName; // normalize Names first - cardName = normalizedNames.getOrDefault(cardName, cardName); - if (alternateName.containsKey(cardName) && engine) { - // TODO might want to implement GUI option so it always fetches the Within version - return alternateName.get(cardName); - } - return cardName; + cardName1 = normalizedNames.getOrDefault(cardName1, cardName1); + return cardName1; } @Override public List getAllCards(String cardName) { - return allCardsByName.get(getName(cardName)); + return allCardsByName.get(getNormalizedName(cardName)); } public List getAllCardsNoAlt(String cardName) { - return Lists.newArrayList(Multimaps.filterEntries(allCardsByName, entry -> entry.getKey().equals(entry.getValue().getName())).get(getName(cardName))); + return Lists.newArrayList(Multimaps.filterEntries(allCardsByName, entry -> entry.getKey().equals(entry.getValue().getName())).get(getNormalizedName(cardName))); } /** @@ -1111,7 +1119,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { @Override public boolean contains(String name) { - return allCardsByName.containsKey(getName(name)); + return allCardsByName.containsKey(getNormalizedName(name)); } @Override @@ -1221,7 +1229,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { int artIdx = IPaperCard.DEFAULT_ART_INDEX; for (EditionEntry cis : e.getCardInSet(cardName)) paperCards.add(new PaperCard(rules, e.getCode(), cis.rarity(), artIdx++, false, - cis.collectorNumber(), cis.artistName(), cis.functionalVariantName())); + cis.collectorNumber(), cis.artistName(), cis.getFunctionalVariantName())); } } else { String lastEdition = null; @@ -1241,7 +1249,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { int cardInSetIndex = Math.max(artIdx-1, 0); // make sure doesn't go below zero EditionEntry cds = cardsInSet.get(cardInSetIndex); // use ArtIndex to get the right Coll. Number paperCards.add(new PaperCard(rules, lastEdition, tuple.getValue(), artIdx++, false, - cds.collectorNumber(), cds.artistName(), cds.functionalVariantName())); + cds.collectorNumber(), cds.artistName(), cds.getFunctionalVariantName())); } } if (paperCards.isEmpty()) { diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java index 3a69cfd0b37..e0bd3edefee 100644 --- a/forge-core/src/main/java/forge/card/CardEdition.java +++ b/forge-core/src/main/java/forge/card/CardEdition.java @@ -82,23 +82,17 @@ public final class CardEdition implements Comparable { public static final EnumSet REPRINT_SET_TYPES = EnumSet.of(REPRINT, PROMO, COLLECTOR_EDITION); public String getBoosterBoxDefault() { - switch (this) { - case CORE: - case EXPANSION: - return "36"; - default: - return "0"; - } + return switch (this) { + case CORE, EXPANSION -> "36"; + default -> "0"; + }; } public String getFatPackDefault() { - switch (this) { - case CORE: - case EXPANSION: - return "10"; - default: - return "0"; - } + return switch (this) { + case CORE, EXPANSION -> "10"; + default -> "0"; + }; } public String toString(){ @@ -215,7 +209,7 @@ public final class CardEdition implements Comparable { return sortableCollNr; } - public record EditionEntry(String name, String collectorNumber, CardRarity rarity, String artistName, String functionalVariantName) implements Comparable { + public record EditionEntry(String name, String collectorNumber, CardRarity rarity, String artistName, Map extraParams) implements Comparable { public String toString() { StringBuilder sb = new StringBuilder(); @@ -232,9 +226,9 @@ public final class CardEdition implements Comparable { sb.append(" @"); sb.append(artistName); } - if (functionalVariantName != null) { + if (extraParams != null) { sb.append(" $"); - sb.append(functionalVariantName); + sb.append(extraParams.entrySet().stream().map(e -> String.format("\"%s\"=\"%s\"", e.getKey(), e.getValue())).collect(Collectors.joining(", "))); } return sb.toString(); } @@ -253,6 +247,24 @@ public final class CardEdition implements Comparable { } return rarity.compareTo(o.rarity); } + + public String getFlavorName() { + if(extraParams == null) + return null; + return extraParams.get("flavorname"); + } + + public String getFunctionalVariantName() { + if(extraParams == null) + return null; + return extraParams.get("variant"); + } + + public String getScriptOverride() { + if(extraParams == null) + return null; + return extraParams.get("script"); + } } private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); @@ -589,6 +601,31 @@ public final class CardEdition implements Comparable { } public static class Reader extends StorageReaderFolder { + + public static final Pattern CARD_PATTERN = Pattern.compile( + /* + The following pattern will match the WAR Japanese art entries, + it should also match the Un-set and older alternate art cards + like Merseine from FEM. + */ + // Collector numbers now should allow hyphens for Planeswalker Championship Promos + "(?:^(?.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(?:(?[SCURML])\\s)?(?[^@$]*)(?: @(?[^$]*))?(?: \\$\\{(?.+)})?$" + ); + + public static final Pattern TOKEN_PATTERN = Pattern.compile( + /* + * cnum - grouping #2 + * name - grouping #3 + * artist name - grouping #5 + */ + "(?:^(?.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?(?[^@]*)(?: @(?.*))?$" + ); + + public static final Pattern EXTRA_PARAMS_PATTERN = Pattern.compile( + //Simple JSON string map parser - "key": "value". No support for escaping quotation marks or anything fancy. + "\"([^\"]+)\"\\s*:\\s*\"([^\"]+)\",?" + ); + private final boolean isCustomEditions; public Reader(File path) { @@ -610,38 +647,6 @@ public final class CardEdition implements Comparable { protected CardEdition read(File file) { final Map> contents = FileSection.parseSections(FileUtil.readFile(file)); - final Pattern pattern = Pattern.compile( - /* - The following pattern will match the WAR Japanese art entries, - it should also match the Un-set and older alternate art cards - like Merseine from FEM. - */ - // Collector numbers now should allow hyphens for Planeswalker Championship Promos - //"(^(?[0-9]+.?) )?((?[SCURML]) )?(?.*)$" - /* Ideally we'd use the named group above, but Android 6 and - earlier don't appear to support named groups. - So, untill support for those devices is officially dropped, - we'll have to suffice with numbered groups. - We are looking for: - * cnum - grouping #2 - * rarity - grouping #4 - * name - grouping #5 - * artist name - grouping #7 - * functional variant name - grouping #9 - */ -// "(^(.?[0-9A-Z]+.?))?(([SCURML]) )?(.*)$" - "(^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(([SCURML])\\s)?([^@\\$]*)( @([^\\$]*))?( \\$(.+))?$" - ); - - final Pattern tokenPattern = Pattern.compile( - /* - * cnum - grouping #2 - * name - grouping #3 - * artist name - grouping #5 - */ - "(^(.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?([^@]*)( @(.*))?$" - ); - ListMultimap cardMap = ArrayListMultimap.create(); List boosterSlots = null; Map> customPrintSheetsToParse = new HashMap<>(); @@ -668,18 +673,35 @@ public final class CardEdition implements Comparable { // parse sections of the format " " if (editionSectionsWithCollectorNumbers.contains(sectionName)) { for(String line : contents.get(sectionName)) { - Matcher matcher = pattern.matcher(line); + Matcher matcher = CARD_PATTERN.matcher(line); if (!matcher.matches()) { continue; } - String collectorNumber = matcher.group(2); - CardRarity r = CardRarity.smartValueOf(matcher.group(4)); - String cardName = matcher.group(5); - String artistName = matcher.group(7); - String functionalVariantName = matcher.group(9); - EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, functionalVariantName); + String collectorNumber = matcher.group("cnum"); + CardRarity r = CardRarity.smartValueOf(matcher.group("rarity")); + String cardName = matcher.group("name"); + String artistName = matcher.group("artist"); + String extraParamText = matcher.group("params"); + Map extraParams = null; + if(!StringUtils.isBlank(extraParamText)) { + Matcher paramMatcher = EXTRA_PARAMS_PATTERN.matcher(extraParamText); + if(!paramMatcher.lookingAt()) + System.err.println("Ignoring malformed parameter text: " + extraParamText); + else { + extraParams = new HashMap<>(2); + do { + String k = paramMatcher.group(1).trim().toLowerCase(); + String v = paramMatcher.group(2).trim(); + if(k.isEmpty() || v.isEmpty()) + continue; + extraParams.put(k, v); + } while(paramMatcher.find()); + } + } + + EditionEntry cis = new EditionEntry(cardName, collectorNumber, r, artistName, extraParams); cardMap.put(sectionName, cis); } @@ -701,15 +723,15 @@ public final class CardEdition implements Comparable { for (String line : contents.get("tokens")) { if (StringUtils.isBlank(line)) continue; - Matcher matcher = tokenPattern.matcher(line); + Matcher matcher = TOKEN_PATTERN.matcher(line); if (!matcher.matches()) { continue; } - String collectorNumber = matcher.group(2); - String cardName = matcher.group(3); - String artistName = matcher.group(5); + String collectorNumber = matcher.group("cnum"); + String cardName = matcher.group("name"); + String artistName = matcher.group("artist"); // rarity isn't used for this anyway EditionEntry tis = new EditionEntry(cardName, collectorNumber, CardRarity.Token, artistName, null); tokenMap.put(cardName, tis); @@ -719,14 +741,14 @@ public final class CardEdition implements Comparable { for (String line : contents.get("other")) { if (StringUtils.isBlank(line)) continue; - Matcher matcher = tokenPattern.matcher(line); + Matcher matcher = TOKEN_PATTERN.matcher(line); if (!matcher.matches()) { continue; } - String collectorNumber = matcher.group(2); - String cardName = matcher.group(3); - String artistName = matcher.group(5); + String collectorNumber = matcher.group("cnum"); + String cardName = matcher.group("name"); + String artistName = matcher.group("artist"); EditionEntry tis = new EditionEntry(cardName, collectorNumber, CardRarity.Unknown, artistName, null); otherMap.put(cardName, tis); } @@ -931,7 +953,7 @@ public final class CardEdition implements Comparable { public final Comparator CARD_EDITION_COMPARATOR = Comparator.comparing(c -> Collection.this.get(c.getEdition())); public IItemReader getBoosterGenerator() { - return new StorageReaderBase(null) { + return new StorageReaderBase<>(null) { @Override public Map readAll() { Map map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); diff --git a/forge-core/src/main/java/forge/card/CardFace.java b/forge-core/src/main/java/forge/card/CardFace.java index 7d452132e42..d7997375c71 100644 --- a/forge-core/src/main/java/forge/card/CardFace.java +++ b/forge-core/src/main/java/forge/card/CardFace.java @@ -17,20 +17,12 @@ import java.util.stream.Collectors; * Do not use reference to class except for card parsing.
Always use reference to interface type outside of package.
*/ final class CardFace implements ICardFace, Cloneable { - - public enum FaceSelectionMethod { // - USE_ACTIVE_FACE, - USE_PRIMARY_FACE, - COMBINE - } - - private final static List emptyList = Collections.unmodifiableList(new ArrayList<>()); private final static Map emptyMap = Collections.unmodifiableMap(new TreeMap<>()); private final static Set emptySet = Collections.unmodifiableSet(new HashSet<>()); private String name; - private String altName = null; + private String flavorName = null; private CardType type = null; private ManaCost manaCost = null; private ColorSet color = null; @@ -85,7 +77,7 @@ final class CardFace implements ICardFace, Cloneable { return variables.entrySet(); } - @Override public String getAltName() { return this.altName; } + @Override public String getFlavorName() { return this.flavorName; } public CardFace(String name0) { this.name = name0; @@ -94,7 +86,7 @@ final class CardFace implements ICardFace, Cloneable { } // Here come setters to allow parser supply values void setName(String name) { this.name = name; } - void setAltName(String name) { this.altName = name; } + void setFlavorName(String name) { this.flavorName = name; } void setType(CardType type0) { this.type = type0; } void setManaCost(ManaCost manaCost0) { this.manaCost = manaCost0; } void setColor(ColorSet color0) { this.color = color0; } @@ -187,51 +179,54 @@ final class CardFace implements ICardFace, Cloneable { if(this.functionalVariants != null) { //Copy fields to undefined ones in functional variants for (CardFace variant : this.functionalVariants.values()) { - if(variant.oracleText == null) variant.oracleText = this.oracleText; - if(variant.manaCost == null) variant.manaCost = this.manaCost; - if(variant.color == null) variant.color = ColorSet.fromManaCost(variant.manaCost); - - if(variant.type == null) variant.type = this.type; - - if(variant.power == null) { - variant.power = this.power; - variant.iPower = this.iPower; - } - if(variant.toughness == null) { - variant.toughness = this.toughness; - variant.iToughness = this.iToughness; - } - - if("".equals(variant.initialLoyalty)) variant.initialLoyalty = this.initialLoyalty; - if("".equals(variant.defense)) variant.defense = this.defense; - - //variant.assignMissingFields(); - if(variant.keywords == null) variant.keywords = this.keywords; - else variant.keywords.addAll(0, this.keywords); - - if(variant.abilities == null) variant.abilities = this.abilities; - else variant.abilities.addAll(0, this.abilities); - - if(variant.staticAbilities == null) variant.staticAbilities = this.staticAbilities; - else variant.staticAbilities.addAll(0, this.staticAbilities); - - if(variant.triggers == null) variant.triggers = this.triggers; - else variant.triggers.addAll(0, this.triggers); - - if(variant.replacements == null) variant.replacements = this.replacements; - else variant.replacements.addAll(0, this.replacements); - - if(variant.variables == null) variant.variables = this.variables; - else this.variables.forEach((k, v) -> variant.variables.putIfAbsent(k, v)); - - if(variant.nonAbilityText == null) variant.nonAbilityText = this.nonAbilityText; - if(variant.draftActions == null) variant.draftActions = this.draftActions; - if(variant.attractionLights == null) variant.attractionLights = this.attractionLights; - if(variant.altName == null) variant.altName = this.altName; + assignMissingFieldsToVariant(variant); } } } + void assignMissingFieldsToVariant(CardFace variant) { + if(variant.oracleText == null) variant.oracleText = this.oracleText; + if(variant.manaCost == null) variant.manaCost = this.manaCost; + if(variant.color == null) variant.color = ColorSet.fromManaCost(variant.manaCost); + + if(variant.type == null) variant.type = this.type; + + if(variant.power == null) { + variant.power = this.power; + variant.iPower = this.iPower; + } + if(variant.toughness == null) { + variant.toughness = this.toughness; + variant.iToughness = this.iToughness; + } + + if("".equals(variant.initialLoyalty)) variant.initialLoyalty = this.initialLoyalty; + if("".equals(variant.defense)) variant.defense = this.defense; + + if(variant.keywords == null) variant.keywords = this.keywords; + else variant.keywords.addAll(0, this.keywords); + + if(variant.abilities == null) variant.abilities = this.abilities; + else variant.abilities.addAll(0, this.abilities); + + if(variant.staticAbilities == null) variant.staticAbilities = this.staticAbilities; + else variant.staticAbilities.addAll(0, this.staticAbilities); + + if(variant.triggers == null) variant.triggers = this.triggers; + else variant.triggers.addAll(0, this.triggers); + + if(variant.replacements == null) variant.replacements = this.replacements; + else variant.replacements.addAll(0, this.replacements); + + if(variant.variables == null) variant.variables = this.variables; + else this.variables.forEach((k, v) -> variant.variables.putIfAbsent(k, v)); + + if(variant.nonAbilityText == null) variant.nonAbilityText = this.nonAbilityText; + if(variant.draftActions == null) variant.draftActions = this.draftActions; + if(variant.attractionLights == null) variant.attractionLights = this.attractionLights; + //if(variant.flavorName == null) variant.flavorName = this.flavorName; //Probably shouldn't be setting this on the main variant to begin with? + } + @Override public String toString() { diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index 5d103b779a5..e60061c4fdb 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -27,6 +27,7 @@ import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; import java.util.*; +import java.util.stream.Collectors; import static forge.card.MagicColor.Constant.BASIC_LANDS; import static org.apache.commons.lang3.StringUtils.containsIgnoreCase; @@ -43,8 +44,9 @@ public final class CardRules implements ICardCharacteristics { private CardSplitType splitType; private ICardFace mainPart; private ICardFace otherPart; - private Map specializedParts = Maps.newHashMap(); + private List allFaces; + private CardAiHints aiHints; private ColorSet colorIdentity; private ColorSet deckbuildingColors; @@ -69,6 +71,8 @@ public final class CardRules implements ICardCharacteristics { specializedParts.put(CardStateName.SpecializeG, faces[6]); } + allFaces = Arrays.stream(faces).filter(Objects::nonNull).collect(Collectors.toUnmodifiableList()); + aiHints = cah; meldWith = ""; partnerWith = ""; @@ -93,6 +97,7 @@ public final class CardRules implements ICardCharacteristics { mainPart = newRules.mainPart; otherPart = newRules.otherPart; specializedParts = Maps.newHashMap(newRules.specializedParts); + allFaces = newRules.allFaces; aiHints = newRules.aiHints; colorIdentity = newRules.colorIdentity; meldWith = newRules.meldWith; @@ -163,8 +168,8 @@ public final class CardRules implements ICardCharacteristics { return specializedParts; } - public Iterable getAllFaces() { - return Iterables.concat(Arrays.asList(mainPart, otherPart), specializedParts.values()); + public List getAllFaces() { + return allFaces; } public boolean isTransformable() { @@ -464,6 +469,58 @@ public final class CardRules implements ICardCharacteristics { return this.supportedFunctionalVariants; } + public String getDisplayNameForVariant(String variantName) { + if(supportedFunctionalVariants == null || !supportedFunctionalVariants.contains(variantName)) + return getName(); + + ICardFace mainFace = Objects.requireNonNullElse(mainPart.getFunctionalVariant(variantName), mainPart); + String mainPartName = Objects.requireNonNullElse(mainFace.getFlavorName(), mainFace.getName()); + + if(splitType.getAggregationMethod() == CardSplitType.FaceSelectionMethod.COMBINE) { + ICardFace otherFace = Objects.requireNonNullElse(otherPart.getFunctionalVariant(variantName), otherPart); + String otherPartName = Objects.requireNonNullElse(otherFace.getFlavorName(), otherFace.getName()); + return mainPartName + " // " + otherPartName; + } + else + return mainPartName; + } + + /* package */ String findOrCreateVariantForFlavorName(String flavorName, String suggestedVariantName) { + Objects.requireNonNull(flavorName); + String[] nameParts = flavorName.trim().split("\\s*//\\s*"); + flavorName = String.join(" // ", nameParts); //Normalize this just in case. + if(otherPart != null && nameParts.length < 2) + throw new IllegalArgumentException("Tried to assign a single flavor name to a multi-faced card. Use ' // ' as a separator in the flavorName parameter."); + if(supportedFunctionalVariants == null) + supportedFunctionalVariants = new HashSet<>(); + for(String variantName : this.supportedFunctionalVariants) { + if(getDisplayNameForVariant(variantName).equals(flavorName)) + return variantName; + } + String variantName = suggestedVariantName != null ? suggestedVariantName : "FlavorName" + flavorName.hashCode(); + if(supportedFunctionalVariants.contains(variantName)) + variantName = variantName + flavorName.hashCode(); + + CardFace variantMain = ((CardFace) mainPart).getOrCreateFunctionalVariant(variantName); + variantMain.setFlavorName(nameParts[0]); + //Rudimentary name replacement. Can't do nicknames, pronouns, ability words, or flavored keywords. Need to define variants manually for that. + if(mainPart.getOracleText().contains(mainPart.getName())) + variantMain.setOracleText(mainPart.getOracleText().replace(mainPart.getName(), nameParts[0])); + ((CardFace) mainPart).assignMissingFieldsToVariant(variantMain); + + if(otherPart != null) { + CardFace variantOther = ((CardFace) otherPart).getOrCreateFunctionalVariant(variantName); + variantOther.setFlavorName(nameParts[1]); + if(otherPart.getOracleText().contains(otherPart.getName())) + variantMain.setOracleText(otherPart.getOracleText().replace(otherPart.getName(), nameParts[1])); + ((CardFace) otherPart).assignMissingFieldsToVariant(variantOther); + } + + supportedFunctionalVariants.add(variantName); + + return variantName; + } + public ColorSet getColorIdentity() { return colorIdentity; } @@ -622,8 +679,6 @@ public final class CardRules implements ICardCharacteristics { this.altMode = CardSplitType.smartValueOf(value); } else if ("ALTERNATE".equals(key)) { this.curFace = 1; - } else if ("AltName".equals(key)) { - face.setAltName(value); } break; @@ -648,6 +703,11 @@ public final class CardRules implements ICardCharacteristics { } break; + case 'F': + if("FlavorName".equals(key)) { + face.setFlavorName(value); + } + case 'H': if ("HandLifeModifier".equals(key)) { handLife = value; diff --git a/forge-core/src/main/java/forge/card/CardRulesPredicates.java b/forge-core/src/main/java/forge/card/CardRulesPredicates.java index ef658eff833..f287de10a43 100644 --- a/forge-core/src/main/java/forge/card/CardRulesPredicates.java +++ b/forge-core/src/main/java/forge/card/CardRulesPredicates.java @@ -141,7 +141,7 @@ public final class CardRulesPredicates { * @return the predicate */ public static Predicate hasKeyword(final String keyword) { - return card -> IterableUtil.any(card.getAllFaces(), cf -> cf != null && card.hasStartOfKeyword(keyword, cf)); + return card -> IterableUtil.any(card.getAllFaces(), cf -> card.hasStartOfKeyword(keyword, cf)); } /** @@ -325,12 +325,19 @@ public final class CardRulesPredicates { return false; } if (face.hasFunctionalVariants()) { + //Couple quirks here - an ICardFace doesn't have a specific variant, so they all need to be checked. + //This means text matching the rules of one variant will match prints with any variant. In the case of + //flavor names though, we exclude their oracle modified text from matching, so that searching a flavor + //name will return only the card matching that name. + //TODO: Fix all that someday by doing rules searches by the PaperCard rather than the CardRules. for (Map.Entry v : face.getFunctionalVariants().entrySet()) { - //Not a very pretty implementation, but an ICardFace doesn't have a specific variant, so they all need to be checked. - String origOracle = v.getValue().getOracleText(); + ICardFace vFace = v.getValue(); + if(vFace.getFlavorName() != null) + continue; + String origOracle = vFace.getOracleText(); if(op(origOracle, operand)) return true; - String name = v.getValue().getName() + " $" + v.getKey(); + String name = vFace.getFlavorName() != null ? vFace.getFlavorName() : vFace.getName() + " $" + v.getKey(); if(op(CardTranslation.getTranslatedOracle(name), operand)) return true; } @@ -346,10 +353,11 @@ public final class CardRulesPredicates { } if (face.hasFunctionalVariants()) { for (Map.Entry v : face.getFunctionalVariants().entrySet()) { - String origType = v.getValue().getType().toString(); + ICardFace vFace = v.getValue(); + String origType = vFace.getType().toString(); if(op(origType, operand)) return true; - String name = v.getValue().getName() + " $" + v.getKey(); + String name = vFace.getFlavorName() != null ? vFace.getFlavorName() : vFace.getName() + " $" + v.getKey(); if(op(CardTranslation.getTranslatedType(name, origType), operand)) return true; } @@ -363,7 +371,7 @@ public final class CardRulesPredicates { switch (this.field) { case NAME: for (ICardFace face : card.getAllFaces()) { - if (face != null && checkName(face.getName())) { + if (checkName(face.getName())) { return true; } } diff --git a/forge-core/src/main/java/forge/card/CardSplitType.java b/forge-core/src/main/java/forge/card/CardSplitType.java index 5abeb463fee..381d27d4a54 100644 --- a/forge-core/src/main/java/forge/card/CardSplitType.java +++ b/forge-core/src/main/java/forge/card/CardSplitType.java @@ -1,7 +1,5 @@ package forge.card; -import forge.card.CardFace.FaceSelectionMethod; - import java.util.EnumSet; public enum CardSplitType @@ -41,4 +39,10 @@ public enum CardSplitType public CardStateName getChangedStateName() { return changedStateName; } + + public enum FaceSelectionMethod { + USE_ACTIVE_FACE, + USE_PRIMARY_FACE, + COMBINE + } } diff --git a/forge-core/src/main/java/forge/card/ICardFace.java b/forge-core/src/main/java/forge/card/ICardFace.java index 494e234c6df..dd37cf19231 100644 --- a/forge-core/src/main/java/forge/card/ICardFace.java +++ b/forge-core/src/main/java/forge/card/ICardFace.java @@ -7,7 +7,7 @@ import java.util.Map; * */ public interface ICardFace extends ICardCharacteristics, ICardRawAbilites, Comparable { - String getAltName(); + String getFlavorName(); boolean hasFunctionalVariants(); ICardFace getFunctionalVariant(String variant); diff --git a/forge-core/src/main/java/forge/deck/DeckFormat.java b/forge-core/src/main/java/forge/deck/DeckFormat.java index 0ec67606091..aa14c8966ff 100644 --- a/forge-core/src/main/java/forge/deck/DeckFormat.java +++ b/forge-core/src/main/java/forge/deck/DeckFormat.java @@ -406,7 +406,7 @@ public enum DeckFormat { final CardPool allCards = deck.getAllCardsInASinglePool(hasCommander()); // Should group all cards by name, so that different editions of same card are really counted as the same card - for (final Entry cp : Aggregates.groupSumBy(allCards, pc -> StaticData.instance().getCommonCards().getName(pc.getName(), true))) { + for (final Entry cp : Aggregates.groupSumBy(allCards, pc -> StaticData.instance().getCommonCards().getNormalizedName(pc.getName()))) { IPaperCard simpleCard = StaticData.instance().getCommonCards().getCard(cp.getKey()); if (simpleCard != null && simpleCard.getRules().isCustom() && !allowCustomCards()) return TextUtil.concatWithSpace("contains a Custom Card:", cp.getKey(), "\nPlease Enable Custom Cards in Forge Preferences to use this deck."); diff --git a/forge-core/src/main/java/forge/item/IPaperCard.java b/forge-core/src/main/java/forge/item/IPaperCard.java index af12c23c78e..160cb727778 100644 --- a/forge-core/src/main/java/forge/item/IPaperCard.java +++ b/forge-core/src/main/java/forge/item/IPaperCard.java @@ -6,6 +6,7 @@ import forge.card.ColorSet; import forge.card.ICardFace; import java.io.Serializable; +import java.util.List; public interface IPaperCard extends InventoryItem, Serializable { @@ -31,6 +32,7 @@ public interface IPaperCard extends InventoryItem, Serializable { boolean hasBackFace(); ICardFace getMainFace(); ICardFace getOtherFace(); + List getAllFaces(); String getCardImageKey(); String getCardAltImageKey(); String getCardWSpecImageKey(); @@ -43,9 +45,10 @@ public interface IPaperCard extends InventoryItem, Serializable { @Override default String getTranslationKey() { - if(!NO_FUNCTIONAL_VARIANT.equals(getFunctionalVariant())) + //Cards with flavor names will use that flavor name as their translation key. Other variants are just appended as a suffix. + if(!NO_FUNCTIONAL_VARIANT.equals(getFunctionalVariant()) && getAllFaces().stream().noneMatch(pc -> pc.getFlavorName() != null)) return getName() + " $" + getFunctionalVariant(); - return getName(); + return getDisplayName(); } @Override diff --git a/forge-core/src/main/java/forge/item/InventoryItem.java b/forge-core/src/main/java/forge/item/InventoryItem.java index 4de644d0489..5833bfa7595 100644 --- a/forge-core/src/main/java/forge/item/InventoryItem.java +++ b/forge-core/src/main/java/forge/item/InventoryItem.java @@ -27,6 +27,21 @@ public interface InventoryItem extends ITranslatable { String getItemType(); String getImageKey(boolean altState); + /** + * Supplies the user-facing name of this item. Usually the same as `getName()`, but may be overwritten in cases such + * as flavor names for cards. + */ + default String getDisplayName() { + return getName(); + } + + /** + * @return true if this item's display name is different from its actual name. False otherwise. + */ + default boolean hasFlavorName() { + return false; + } + @Override default String getUntranslatedType() { return getItemType(); diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index 63e5642ba3e..b4448097636 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -20,10 +20,7 @@ package forge.item; import forge.ImageKeys; import forge.StaticData; import forge.card.*; -import forge.util.CardTranslation; -import forge.util.ImageUtil; -import forge.util.Localizer; -import forge.util.TextUtil; +import forge.util.*; import org.apache.commons.lang3.StringUtils; import java.io.*; @@ -56,7 +53,6 @@ public class PaperCard implements Comparable, InventoryItemFromSet, private final int artIndex; private final boolean foil; private final PaperCardFlags flags; - private final String sortableName; private final String functionalVariant; // Calculated fields are below: @@ -64,6 +60,9 @@ public class PaperCard implements Comparable, InventoryItemFromSet, // Reference to a new instance of Self, but foiled! private transient PaperCard foiledVersion, noSellVersion, flaglessVersion; private transient Boolean hasImage; + private transient String displayName; + private transient String sortableName; + private transient boolean hasFlavorName; @Override public String getName() { @@ -252,10 +251,12 @@ public class PaperCard implements Comparable, InventoryItemFromSet, this.rarity = rarity; this.artist = artist; this.collectorNumber = (collectorNumber != null && !collectorNumber.isEmpty()) ? collectorNumber : IPaperCard.NO_COLLECTOR_NUMBER; + this.functionalVariant = functionalVariant != null ? functionalVariant : IPaperCard.NO_FUNCTIONAL_VARIANT; + this.displayName = rules.getDisplayNameForVariant(functionalVariant); + this.hasFlavorName = !name.equals(displayName); // If the user changes the language this will make cards sort by the old language until they restart the game. // This is a good tradeoff - sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(rules.getName())); - this.functionalVariant = functionalVariant != null ? functionalVariant : IPaperCard.NO_FUNCTIONAL_VARIANT; + this.sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(displayName)); if(flags == null || flags.equals(PaperCardFlags.IDENTITY_FLAGS)) this.flags = PaperCardFlags.IDENTITY_FLAGS; @@ -313,6 +314,67 @@ public class PaperCard implements Comparable, InventoryItemFromSet, return name; } + @Override + public String getDisplayName() { + return this.displayName; + } + + @Override + public boolean hasFlavorName() { + return this.hasFlavorName; + } + + private transient Set searchableNames = null; + private transient String searchableNameLang = null; + + public Set getAllSearchableNames() { + if(this.searchableNames != null && CardTranslation.getLanguageSelected().equals(searchableNameLang)) + return searchableNames; + if(searchableNameLang != null) //Changed the language. May as well update this. + sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(displayName)); + searchableNameLang = CardTranslation.getLanguageSelected(); + searchableNames = computeSearchableNames(searchableNameLang); + return searchableNames; + } + + private Set computeSearchableNames(String language) { + ICardFace otherFace = this.getOtherFace(); + if(otherFace == null && NO_FUNCTIONAL_VARIANT.equals(this.functionalVariant)) + { + //99% of cases will land here. This could possibly be optimized further by computing and storing this on + //the CardRules instead, but flavor names will still need to work per-print, or at least per-variant. + if("en-US".equals(language)) + return Set.of(this.name); + else { + String translatedName = CardTranslation.getTranslatedName(this.name); + return Set.of(this.name, translatedName, StringUtils.stripAccents(translatedName)); + } + } + Set names = new HashSet<>(); + ICardFace mainFace = this.getMainFace(); + names.add(mainFace.getName()); + String mainFlavor = mainFace.getFlavorName(); + if(mainFlavor != null) + names.add(mainFlavor); + if(otherFace != null) { + names.add(otherFace.getName()); + String otherFlavor = otherFace.getFlavorName(); + if(otherFlavor != null) + names.add(otherFlavor); + + names.add(mainFace.getName() + " // " + otherFace.getName()); + if(mainFlavor != null && otherFlavor != null) + names.add(mainFlavor + " // " + otherFlavor); + } + if(!"en-US".equals(language)) { + Set translated = names.stream().map(CardTranslation::getTranslatedName).filter(Objects::nonNull).collect(Collectors.toSet()); + names.addAll(translated); + } + Set noAccents = names.stream().map(StringUtils::stripAccents).collect(Collectors.toSet()); + names.addAll(noAccents); + return names; + } + /* * This (utility) method transform a collectorNumber String into a key string for sorting. * This method proxies the same strategy implemented in CardEdition.CardInSet class from which the @@ -383,6 +445,9 @@ public class PaperCard implements Comparable, InventoryItemFromSet, } rules = pc.getRules(); rarity = pc.getRarity(); + displayName = pc.getDisplayName(); + hasFlavorName = pc.hasFlavorName(); + sortableName = TextUtil.toSortableName(CardTranslation.getTranslatedName(displayName)); } private IPaperCard readObjectAlternate(String name, String edition) throws ClassNotFoundException, IOException { @@ -516,9 +581,16 @@ public class PaperCard implements Comparable, InventoryItemFromSet, @Override public ICardFace getOtherFace() { ICardFace face = this.rules.getOtherPart(); + if(face == null) + return null; return this.getVariantForFace(face); } + @Override + public List getAllFaces() { + return StreamUtil.stream(this.rules.getAllFaces()).map(this::getVariantForFace).collect(Collectors.toList()); + } + private ICardFace getVariantForFace(ICardFace face) { if(!face.hasFunctionalVariants() || this.functionalVariant.equals(NO_FUNCTIONAL_VARIANT)) return face; diff --git a/forge-core/src/main/java/forge/item/PaperCardPredicates.java b/forge-core/src/main/java/forge/item/PaperCardPredicates.java index 449d191b9c6..e089e2cb27c 100644 --- a/forge-core/src/main/java/forge/item/PaperCardPredicates.java +++ b/forge-core/src/main/java/forge/item/PaperCardPredicates.java @@ -42,6 +42,10 @@ public abstract class PaperCardPredicates { return new PredicatePrintedWithRarity(rarity); } + public static Predicate searchableName(final PredicateString.StringOp op, final String what) { + return new PredicateSearchableName(op, what); + } + public static Predicate name(final String what) { return new PredicateName(what); } @@ -184,6 +188,20 @@ public abstract class PaperCardPredicates { } } + private static final class PredicateSearchableName extends PredicateString { + private final String operand; + + PredicateSearchableName(final StringOp operator, final String operand) { + super(operator); + this.operand = operand; + } + + @Override + public boolean test(PaperCard paperCard) { + return paperCard.getAllSearchableNames().stream().anyMatch(name -> this.op(name, this.operand)); + } + } + private static final class PredicateName extends PredicateString { private final String operand; diff --git a/forge-core/src/main/java/forge/item/PaperToken.java b/forge-core/src/main/java/forge/item/PaperToken.java index f60c5befe5c..df5f23cf3bc 100644 --- a/forge-core/src/main/java/forge/item/PaperToken.java +++ b/forge-core/src/main/java/forge/item/PaperToken.java @@ -6,6 +6,7 @@ import forge.util.MyRandom; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; +import java.util.List; import java.util.Locale; public class PaperToken implements InventoryItemFromSet, IPaperCard { @@ -88,6 +89,11 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard { return name; } + @Override + public String getDisplayName() { + return name; + } + @Override public String toString() { return name; @@ -169,6 +175,11 @@ public class PaperToken implements InventoryItemFromSet, IPaperCard { return this.getRules().getOtherPart(); } + @Override + public List getAllFaces() { + return this.cardRules.getAllFaces(); + } + @Override public boolean isToken() { return true; diff --git a/forge-core/src/main/java/forge/util/CardTranslation.java b/forge-core/src/main/java/forge/util/CardTranslation.java index f035dadb324..ffd6f2f01e7 100644 --- a/forge-core/src/main/java/forge/util/CardTranslation.java +++ b/forge-core/src/main/java/forge/util/CardTranslation.java @@ -258,6 +258,10 @@ public class CardTranslation { return translations; } + public static String getLanguageSelected() { + return languageSelected; + } + private static boolean needsTranslation() { return !languageSelected.equals("en-US"); } diff --git a/forge-core/src/main/java/forge/util/ImageUtil.java b/forge-core/src/main/java/forge/util/ImageUtil.java index a6594512378..1d8432ee306 100644 --- a/forge-core/src/main/java/forge/util/ImageUtil.java +++ b/forge-core/src/main/java/forge/util/ImageUtil.java @@ -185,6 +185,8 @@ public class ImageUtil { } } else if (CardSplitType.Split == cp.getRules().getSplitType()) { return card.getMainPart().getName() + card.getOtherPart().getName(); + } else if (cp.hasFlavorName()) { + return cp.getDisplayName(); } else if (!IPaperCard.NO_FUNCTIONAL_VARIANT.equals(cp.getFunctionalVariant())) { return cp.getName() + " " + cp.getFunctionalVariant(); } diff --git a/forge-core/src/main/java/forge/util/IterableUtil.java b/forge-core/src/main/java/forge/util/IterableUtil.java index f52e0a4e69d..0bcf47446a6 100644 --- a/forge-core/src/main/java/forge/util/IterableUtil.java +++ b/forge-core/src/main/java/forge/util/IterableUtil.java @@ -1,6 +1,7 @@ package forge.util; import java.util.Collection; +import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; @@ -19,6 +20,8 @@ public class IterableUtil { * which requires the subject to match each of the component predicates. */ public static Predicate and(Iterable> components) { + if(components instanceof List && ((List) components).size() == 1) + return ((List>) components).get(0)::test; return x -> all(components, i -> i.test(x)); } @@ -27,6 +30,8 @@ public class IterableUtil { * which requires the subject to match at least one of the component predicates. */ public static Predicate or(Iterable> components) { + if(components instanceof List && ((List) components).size() == 1) + return ((List>) components).get(0)::test; return x -> any(components, i -> i.test(x)); } diff --git a/forge-game/src/main/java/forge/game/GameFormat.java b/forge-game/src/main/java/forge/game/GameFormat.java index c467f0e04c8..c24bf8c0b09 100644 --- a/forge-game/src/main/java/forge/game/GameFormat.java +++ b/forge-game/src/main/java/forge/game/GameFormat.java @@ -271,7 +271,7 @@ public class GameFormat implements Comparable { if (erroneousCI.size() > 0) { final StringBuilder sb = new StringBuilder("contains the following illegal cards:\n"); for (final PaperCard cp : erroneousCI) { - sb.append("\n").append(cp.getName()); + sb.append("\n").append(cp.getDisplayName()); } return sb.toString(); } @@ -290,7 +290,7 @@ public class GameFormat implements Comparable { if (erroneousRestricted.size() > 0) { final StringBuilder sb = new StringBuilder("contains more than one copy of the following restricted cards:\n"); for (final PaperCard cp : erroneousRestricted) { - sb.append("\n").append(cp.getName()); + sb.append("\n").append(cp.getDisplayName()); } return sb.toString(); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/AlterAttributeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AlterAttributeEffect.java index 14ae5d11caf..e2c5d03802c 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AlterAttributeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AlterAttributeEffect.java @@ -91,10 +91,10 @@ public class AlterAttributeEffect extends SpellAbilityEffect { } p.addCommander(gameCard); //Seems important enough to mention in the game log. - gameCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, String.format("%s is now %s's commander.", gameCard.getPaperCard().getName(), p)); + gameCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, String.format("%s is now %s's commander.", gameCard.getPaperCard().getDisplayName(), p)); } else { p.removeCommander(gameCard); - gameCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, String.format("%s is no longer %s's commander.", gameCard.getPaperCard().getName(), p)); + gameCard.getGame().getGameLog().add(GameLogEntryType.STACK_RESOLVE, String.format("%s is no longer %s's commander.", gameCard.getPaperCard().getDisplayName(), p)); } altered = true; break; 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 a9e9d8d924e..e7f2d603de7 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -952,23 +952,28 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr @Override public final String getName() { - return getName(currentState, false); + return getName(currentState); } - public final String getName(boolean alt) { - return getName(currentState, alt); - } - public final String getName(CardStateName stateName) { - return getName(getState(stateName), false); - } - public final String getName(CardState state, boolean alt) { + public final String getName(CardState state) { String name = state.getName(); for (CardChangedName change : this.changedCardNames.values()) { if (change.isOverwrite()) { name = change.newName(); } } - return alt ? StaticData.instance().getCommonCards().getName(name, true) : name; + return name; + } + + public final String getDisplayName() { + return getDisplayName(currentState); + } + + public final String getDisplayName(CardState state) { + //If this card has a changed name, don't use flavor names. + if(state.getFlavorName() == null || hasNameOverwrite()) + return getName(state); + return state.getFlavorName(); } public final boolean hasNameOverwrite() { @@ -6058,7 +6063,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr return true; } } - return sharesNameWith(c1.getName(true)); + return sharesNameWith(c1.getName()); } public final boolean sharesNameWith(final String name) { @@ -6067,7 +6072,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr return false; } - boolean shares = getName(true).equals(name); + boolean shares = getName().equals(name); // Split cards has extra logic to check if it does share a name with if (!shares && !hasNameOverwrite()) { @@ -7844,7 +7849,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr } @Override public String getUntranslatedName() { - return this.getName(); + return this.getDisplayName(); } @Override public String getUntranslatedType() { diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index 334c20ce2fe..efda56fa831 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -402,6 +402,8 @@ public class CardFactory { c.getCurrentState().setOracleText(face.getOracleText()); + c.getCurrentState().setFlavorName(face.getFlavorName()); + // Super and 'middle' types should use enums. c.setType(new CardType(face.getType())); diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index 2250fec1abe..54624699f6b 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -63,6 +63,7 @@ public class CardState implements GameObject, IHasSVars, ITranslatable { private ColorSet color = ColorSet.C; private String oracleText = ""; private String functionalVariantName = null; + private String flavorName = null; private int basePower = 0; private int baseToughness = 0; private String basePowerString = null; @@ -221,6 +222,15 @@ public class CardState implements GameObject, IHasSVars, ITranslatable { view.setFunctionalVariantName(functionalVariantName); } + public String getFlavorName() { + return flavorName; + } + + public void setFlavorName(String flavorName) { + this.flavorName = flavorName; + view.updateName(this); + } + public final int getBasePower() { return basePower; } @@ -716,6 +726,7 @@ public class CardState implements GameObject, IHasSVars, ITranslatable { setBaseLoyalty(source.getBaseLoyalty()); setBaseDefense(source.getBaseDefense()); setAttractionLights(source.getAttractionLights()); + setFlavorName(source.getFlavorName()); setSVars(source.getSVars()); abilities.clear(); @@ -930,9 +941,10 @@ public class CardState implements GameObject, IHasSVars, ITranslatable { @Override public String getTranslationKey() { + String displayName = flavorName == null ? name : flavorName; if(StringUtils.isNotEmpty(functionalVariantName)) - return name + " $" + functionalVariantName; - return name; + return displayName + " $" + functionalVariantName; + return displayName; } @Override diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index 1be63d44d66..02cb4019aeb 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -7,10 +7,7 @@ import forge.ImageKeys; import forge.StaticData; import forge.card.*; import forge.card.mana.ManaCost; -import forge.game.Direction; -import forge.game.EvenOdd; -import forge.game.GameEntityView; -import forge.game.GameType; +import forge.game.*; import forge.game.combat.Combat; import forge.game.keyword.Keyword; import forge.game.player.Player; @@ -100,6 +97,25 @@ public class CardView extends GameEntityView { set(TrackableProperty.Controller, ownerAndController); set(TrackableProperty.ImageKey, imageKey); } + + @Override + protected void updateName(GameEntity e) { + //Name reflects the current display name, as modified by any flavor names. + //OracleName can be used to find the true name of a card. + if (e instanceof Card c) { + set(TrackableProperty.Name, c.getDisplayName()); + set(TrackableProperty.OracleName, c.getName()); + } + else { + super.updateName(e); + set(TrackableProperty.OracleName, e.getName()); + } + } + + public String getOracleName() { + return get(TrackableProperty.OracleName); + } + public PlayerView getOwner() { return get(TrackableProperty.Owner); } @@ -1280,16 +1296,26 @@ public class CardView extends GameEntityView { } void updateName(CardState c) { Card card = c.getCard(); - setName(card.getName(c, false)); + setName(card.getDisplayName(c)); + setOracleName(card.getName(c)); if (CardView.this.getCurrentState() == this) { - if (card != null) { - CardView.this.updateName(card); - } + CardView.this.updateName(card); } } - private void setName(String name0) { - set(TrackableProperty.Name, name0); + private void setName(String name) { + set(TrackableProperty.Name, name); + } + + /** + * @return The name of the card, unaltered by flavor names. + */ + public String getOracleName() { + return get(TrackableProperty.OracleName); + } + + private void setOracleName(String name) { + set(TrackableProperty.OracleName, name); } public ColorSet getColors() { diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java index 5cef4898c2c..12ef4f8e4d9 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java +++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java @@ -131,6 +131,7 @@ public enum TrackableProperty { FunctionalVariant(TrackableTypes.StringType), OracleText(TrackableTypes.StringType), RulesText(TrackableTypes.StringType), + OracleName(TrackableTypes.StringType), Power(TrackableTypes.IntegerType), Toughness(TrackableTypes.IntegerType), Loyalty(TrackableTypes.StringType), diff --git a/forge-gui-desktop/src/main/java/forge/gui/CardDetailPanel.java b/forge-gui-desktop/src/main/java/forge/gui/CardDetailPanel.java index 24e32480b8b..4a0650500b4 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/CardDetailPanel.java +++ b/forge-gui-desktop/src/main/java/forge/gui/CardDetailPanel.java @@ -142,7 +142,7 @@ public class CardDetailPanel extends SkinnedPanel { } public final void setItem(final InventoryItemFromSet item) { - nameCostLabel.setText(item.getName()); + nameCostLabel.setText(item.getDisplayName()); typeLabel.setVisible(false); powerToughnessLabel.setVisible(false); idLabel.setText(""); diff --git a/forge-gui-desktop/src/main/java/forge/gui/CardListChooser.java b/forge-gui-desktop/src/main/java/forge/gui/CardListChooser.java index 3b32a91e29b..49fc008ee62 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/CardListChooser.java +++ b/forge-gui-desktop/src/main/java/forge/gui/CardListChooser.java @@ -90,7 +90,7 @@ public class CardListChooser extends FDialog { public void windowClosing(final WindowEvent e) { //CardTranslation.getTranslatedName if (FOptionPane.showConfirmDialog( - Localizer.getInstance().getMessage("lblAreYouSureWantPickCard", CardTranslation.getTranslatedName(jList.getSelectedValue().getName())), + Localizer.getInstance().getMessage("lblAreYouSureWantPickCard", CardTranslation.getTranslatedName(jList.getSelectedValue().getDisplayName())), Localizer.getInstance().getMessage("lblSelectThisCardConfirm"), false) ) { dispose(); diff --git a/forge-gui-desktop/src/main/java/forge/itemmanager/views/ImageView.java b/forge-gui-desktop/src/main/java/forge/itemmanager/views/ImageView.java index 0136367ac69..d01b3eec022 100644 --- a/forge-gui-desktop/src/main/java/forge/itemmanager/views/ImageView.java +++ b/forge-gui-desktop/src/main/java/forge/itemmanager/views/ImageView.java @@ -1215,7 +1215,7 @@ public class ImageView extends ItemView { g.setColor(Color.white); Shape clip = g.getClip(); g.setClip(bounds); - g.drawString(item.getName(), bounds.x + 10, bounds.y + 20); + g.drawString(item.getDisplayName(), bounds.x + 10, bounds.y + 20); g.setClip(clip); } diff --git a/forge-gui-desktop/src/main/java/forge/itemmanager/views/StarRenderer.java b/forge-gui-desktop/src/main/java/forge/itemmanager/views/StarRenderer.java index 96a2d66c883..ba2c421c41e 100644 --- a/forge-gui-desktop/src/main/java/forge/itemmanager/views/StarRenderer.java +++ b/forge-gui-desktop/src/main/java/forge/itemmanager/views/StarRenderer.java @@ -102,11 +102,11 @@ public class StarRenderer extends ItemCellRenderer { return; } if (favorite == 0) { - this.setToolTipText(localizer.getMessage("lblClickToAddTargetToFavorites", CardTranslation.getTranslatedName(card.getName()))); + this.setToolTipText(localizer.getMessage("lblClickToAddTargetToFavorites", CardTranslation.getTranslatedName(card.getDisplayName()))); skinImage = FSkin.getImage(FSkinProp.IMG_STAR_OUTLINE); } else { //TODO: consider supporting more than 1 star - this.setToolTipText(localizer.getMessage("lblClickToRemoveTargetToFavorites", CardTranslation.getTranslatedName(card.getName()))); + this.setToolTipText(localizer.getMessage("lblClickToRemoveTargetToFavorites", CardTranslation.getTranslatedName(card.getDisplayName()))); skinImage = FSkin.getImage(FSkinProp.IMG_STAR_FILLED); } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/DeckImport.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/DeckImport.java index 54bfaff992e..a4d3294dd56 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/DeckImport.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/DeckImport.java @@ -622,7 +622,7 @@ public class DeckImport extends FDialog { cardPreviewLabel.setText(String.format("%s %s
%s", STYLESHEET, editionLbl, statusLbl)); // set tooltip - String tooltip = String.format("%s [%s] #%s", card.getName(), card.getEdition(), + String tooltip = String.format("%s [%s] #%s", card.getDisplayName(), card.getEdition(), card.getCollectorNumber()); cardImagePreview.setToolTipText(tooltip); } 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 a7093b3d3ae..d5f113f25ba 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 @@ -524,7 +524,7 @@ public class VLobby implements ILobbyView { PaperCard vanguardAvatar = null; final Deck deck = decks[playerIndex]; if (selected instanceof PaperCard) { - pp.setVanguardButtonText(((PaperCard) selected).getName()); + pp.setVanguardButtonText(((PaperCard) selected).getDisplayName()); cdp.setCard(CardView.getCardForUi((PaperCard) selected)); cdp.setVisible(true); refreshPanels(false, true); diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index bd76f15756b..ba61d401ee5 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -1438,8 +1438,8 @@ public final class CMatchUI private int getRotation(CardView cardView) { final int rotation; if (cardView.isSplitCard()) { - String cardName = cardView.getName(); - if (cardName.isEmpty()) { cardName = cardView.getAlternateState().getName(); } + String cardName = cardView.getOracleName(); + if (cardName.isEmpty()) { cardName = cardView.getAlternateState().getOracleName(); } PaperCard pc = StaticData.instance().getCommonCards().getCard(cardName); boolean hasKeywordAftermath = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH); diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/imaging/FCardImageRenderer.java b/forge-gui-desktop/src/main/java/forge/toolbox/imaging/FCardImageRenderer.java index 5f0934b2c60..cebe2a8dcaa 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/imaging/FCardImageRenderer.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/imaging/FCardImageRenderer.java @@ -797,7 +797,7 @@ public class FCardImageRenderer { if (state.isBasicLand()) { //draw icons for basic lands String imageKey; - switch (state.getName().replaceFirst("^Snow-Covered ", "")) { + switch (state.getOracleName().replaceFirst("^Snow-Covered ", "")) { case "Plains": imageKey = "W"; break; diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/special/CardZoomer.java b/forge-gui-desktop/src/main/java/forge/toolbox/special/CardZoomer.java index 2c79dd1ba6a..9ac05fc7654 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/special/CardZoomer.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/special/CardZoomer.java @@ -230,8 +230,8 @@ public enum CardZoomer { return 0; } if (thisCard.getCard().isSplitCard()) { - String cardName = thisCard.getCard().getName(); - if (cardName.isEmpty()) { cardName = thisCard.getCard().getAlternateState().getName(); } + String cardName = thisCard.getCard().getOracleName(); + if (cardName.isEmpty()) { cardName = thisCard.getCard().getAlternateState().getOracleName(); } PaperCard pc = StaticData.instance().getCommonCards().getCard(cardName); boolean isAftermath = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH); diff --git a/forge-gui-desktop/src/main/java/forge/view/arcane/CardPanel.java b/forge-gui-desktop/src/main/java/forge/view/arcane/CardPanel.java index 29bded8c8eb..d2377eb3739 100644 --- a/forge-gui-desktop/src/main/java/forge/view/arcane/CardPanel.java +++ b/forge-gui-desktop/src/main/java/forge/view/arcane/CardPanel.java @@ -504,8 +504,8 @@ public class CardPanel extends SkinnedPanel implements CardContainer, IDisposabl } else { if (!card.isFaceDown()) { // no need to draw mana symbols on face down split cards (e.g. manifested) PaperCard pc = null; - if (!card.getName().isEmpty()) { - pc = StaticData.instance().getCommonCards().getCard(card.getName()); + if (!card.getOracleName().isEmpty()) { + pc = StaticData.instance().getCommonCards().getCard(card.getOracleName()); } int ofs = pc != null && Card.getCardForUi(pc).hasKeyword(Keyword.AFTERMATH) ? -12 : 12; diff --git a/forge-gui-desktop/src/main/java/forge/view/arcane/PlayArea.java b/forge-gui-desktop/src/main/java/forge/view/arcane/PlayArea.java index 70ad3afb851..4cebb525099 100644 --- a/forge-gui-desktop/src/main/java/forge/view/arcane/PlayArea.java +++ b/forge-gui-desktop/src/main/java/forge/view/arcane/PlayArea.java @@ -107,7 +107,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen for (int i = 0, n = allLands.size(); i < n; i++) { final CardStack stack = allLands.get(i); final CardPanel firstPanel = stack.get(0); - if (firstPanel.getCard().getCurrentState().getName().equals(state.getName())) { + if (firstPanel.getCard().getCurrentState().getOracleName().equals(state.getOracleName())) { if (!firstPanel.getAttachedPanels().isEmpty() || firstPanel.getCard().hasCardAttachments()) { // Put this land to the left of lands with the same name // and attachments. @@ -162,7 +162,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen final CardPanel firstPanel = stack.get(0); final CardView firstCard = firstPanel.getCard(); - if (firstPanel.getCard().getCurrentState().getName().equals(state.getName())) { + if (firstPanel.getCard().getCurrentState().getOracleName().equals(state.getOracleName())) { if (!firstPanel.getAttachedPanels().isEmpty()) { // Put this token to the left of tokens with the same // name and attachments. @@ -220,7 +220,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen final CardStack stack = allCreatures.get(i); final CardPanel firstPanel = stack.get(0); final CardView firstCard = firstPanel.getCard(); - if (firstCard.getName().equals(card.getName())) { + if (firstCard.getOracleName().equals(card.getOracleName())) { if (!firstPanel.getAttachedPanels().isEmpty()) { // Put this creature to the left of creatures with the same // name and attachments. @@ -335,7 +335,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen final CardStateView otherState = otherCard.getCurrentState(); final CardView thisCard = panel.getCard(); final CardStateView thisState = thisCard.getCurrentState(); - if (otherState.getName().equals(thisState.getName()) && s.size() < STACK_MAX_OTHERS) { + if (otherState.getOracleName().equals(thisState.getOracleName()) && s.size() < STACK_MAX_OTHERS) { if (panel.getAttachedPanels().isEmpty() && thisCard.hasSameCounters(otherCard) && (thisCard.isSick() == otherCard.isSick()) diff --git a/forge-gui-mobile/src/forge/adventure/util/RewardActor.java b/forge-gui-mobile/src/forge/adventure/util/RewardActor.java index 58b6977afb8..783fe209977 100644 --- a/forge-gui-mobile/src/forge/adventure/util/RewardActor.java +++ b/forge-gui-mobile/src/forge/adventure/util/RewardActor.java @@ -993,7 +993,7 @@ public class RewardActor extends Actor implements Disposable, ImageFetcher.Callb Reward.Type rewardType = reward.getType(); switch (rewardType) { case Card: - display = reward.getCard() != null ? CardTranslation.getTranslatedName(reward.getCard().getName()) : ""; + display = reward.getCard() != null ? CardTranslation.getTranslatedName(reward.getCard().getDisplayName()) : ""; //alignment = Align.topLeft; labelStyle = "dialog"; break; diff --git a/forge-gui-mobile/src/forge/card/CardZoom.java b/forge-gui-mobile/src/forge/card/CardZoom.java index ce1b07dd26b..47c56282d65 100644 --- a/forge-gui-mobile/src/forge/card/CardZoom.java +++ b/forge-gui-mobile/src/forge/card/CardZoom.java @@ -219,7 +219,7 @@ public class CardZoom extends FOverlay { } if (item instanceof InventoryItem) { InventoryItem ii = (InventoryItem)item; - return new CardView(-1, null, ii.getName(), null, ii.getImageKey(false)); + return new CardView(-1, null, ii.getDisplayName(), null, ii.getImageKey(false)); } return new CardView(-1, null, item.toString()); } diff --git a/forge-gui-mobile/src/forge/deck/FDeckEditor.java b/forge-gui-mobile/src/forge/deck/FDeckEditor.java index 0307b1a9f7d..734ed8865a6 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckEditor.java +++ b/forge-gui-mobile/src/forge/deck/FDeckEditor.java @@ -1866,7 +1866,7 @@ public class FDeckEditor extends TabPageScreen { sortedOptions.add(option); } } - GuiChoose.oneOrNone(Forge.getLocalizer().getMessage("lblSelectPreferredArt") + " " + card.getName(), sortedOptions, result -> { + GuiChoose.oneOrNone(Forge.getLocalizer().getMessage("lblSelectPreferredArt") + " " + card.getDisplayName(), sortedOptions, result -> { if (result != null) { if (result != card) { cardManager.replaceAll(card, result); @@ -2104,7 +2104,7 @@ public class FDeckEditor extends TabPageScreen { } final Localizer localizer = Forge.getLocalizer(); String lblReplaceCard = localizer.getMessage("lblReplace"); - String prompt = localizer.getMessage("lblSelectReplacementCard") + " " + card.getName(); + String prompt = localizer.getMessage("lblSelectReplacementCard") + " " + card.getDisplayName(); String promptQuantity = String.format("%s - %s %s", card, lblReplaceCard, localizer.getMessage("lblHowMany")); //First have the player choose which card to swap in. GuiChoose.oneOrNone(prompt, sortedOptions, replacement -> { diff --git a/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java b/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java index 324a6460923..3beece0b830 100644 --- a/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java +++ b/forge-gui-mobile/src/forge/itemmanager/views/ImageView.java @@ -1251,9 +1251,9 @@ public class ImageView extends ItemView { g.drawImage(Forge.getAssets().getTexture(getDefaultSkinFile("cover.png"), false), x + (w - w * scale) / 2, y + (h - h * scale) / 1.5f, w * scale, h * scale); } //fake labelname shadow - g.drawText(item.getName(), GROUP_HEADER_FONT, Color.BLACK, (x + PADDING) - 1f, (y + PADDING * 2) + 1f, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false); + g.drawText(item.getDisplayName(), GROUP_HEADER_FONT, Color.BLACK, (x + PADDING) - 1f, (y + PADDING * 2) + 1f, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false); //labelname - g.drawText(item.getName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING * 2, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false); + g.drawText(item.getDisplayName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING * 2, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false); } else { if (!deckProxy.isGeneratedDeck()) { if (deckProxy.getDeck().isEmpty()) { @@ -1326,13 +1326,13 @@ public class ImageView extends ItemView { String key = item.getImageKey(false); if (key.startsWith(ImageKeys.PRECON_PREFIX) || key.startsWith(ImageKeys.FATPACK_PREFIX) || key.startsWith(ImageKeys.BOOSTERBOX_PREFIX) || key.startsWith(ImageKeys.BOOSTER_PREFIX) || key.startsWith(ImageKeys.TOURNAMENTPACK_PREFIX)) { - CardView cv = new CardView(-1, null, item.getName(), null, item.getImageKey(false)); + CardView cv = new CardView(-1, null, item.getDisplayName(), null, item.getImageKey(false)); CardImageRenderer.drawCardImage(g, cv, false, x, y, w, h, CardStackPosition.Top, false, false); return; } } g.fillRect(Color.BLACK, x, y, w, h); - g.drawText(item.getName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false); + g.drawText(item.getDisplayName(), GROUP_HEADER_FONT, Color.WHITE, x + PADDING, y + PADDING, w - 2 * PADDING, h - 2 * PADDING, true, Align.center, false); } } diff --git a/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java b/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java index c1e187aee64..47cc36ca3fd 100644 --- a/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java +++ b/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java @@ -777,7 +777,7 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView { CardPool avatarPool = new CardPool(); avatarPool.add(playerPanel.getVanguardAvatar()); playerDeck.putSection(DeckSection.Avatar, avatarPool); - VanguardAvatar = Forge.getLocalizer().getMessage("lblVanguard") + ": " + playerPanel.getVanguardAvatar().getName(); + VanguardAvatar = Forge.getLocalizer().getMessage("lblVanguard") + ": " + playerPanel.getVanguardAvatar().getDisplayName(); playerPanel.setVanguarAvatarName(VanguardAvatar); } diff --git a/forge-gui-mobile/src/forge/screens/constructed/PlayerPanel.java b/forge-gui-mobile/src/forge/screens/constructed/PlayerPanel.java index 67aa9d77944..7c9cf7981cd 100644 --- a/forge-gui-mobile/src/forge/screens/constructed/PlayerPanel.java +++ b/forge-gui-mobile/src/forge/screens/constructed/PlayerPanel.java @@ -204,7 +204,7 @@ public class PlayerPanel extends FContainer { }); lstVanguardAvatars = new FVanguardChooser(isAi, e -> { btnVanguardAvatar.setText(Forge.getLocalizer().getMessage("lblVanguard") - + ":" + (Forge.isLandscapeMode() ? " " : "\n") + ((CardManager)e.getSource()).getSelectedItem().getName()); + + ":" + (Forge.isLandscapeMode() ? " " : "\n") + ((CardManager)e.getSource()).getSelectedItem().getDisplayName()); if (allowNetworking && btnVanguardAvatar.isEnabled() && humanAiSwitch.isToggled()) { screen.updateMyDeck(index); } diff --git a/forge-gui-mobile/src/forge/screens/match/views/VField.java b/forge-gui-mobile/src/forge/screens/match/views/VField.java index c562bc5c832..e7c0ab733f2 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VField.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VField.java @@ -128,12 +128,12 @@ public class VField extends FContainer { if (!this.stackNonTokenCreatures && cardState.isCreature() && !card.isToken()) { return false; } - final String cardName = cardState.getName(); + final String cardName = cardState.getOracleName(); for (CardView c : cardsOfType) { CardStateView cState = c.getCurrentState(); if (cState.isCreature()) { if (!c.hasCardAttachments() && - cardName.equals(cState.getName()) && + cardName.equals(cState.getOracleName()) && card.hasSameCounters(c) && card.hasSamePT(c) && //don't stack token with different PT cardState.getKeywordKey().equals(cState.getKeywordKey()) && @@ -145,7 +145,7 @@ public class VField extends FContainer { } } else { if (!c.hasCardAttachments() && - cardName.equals(cState.getName()) && + cardName.equals(cState.getOracleName()) && card.hasSameCounters(c) && cardState.getKeywordKey().equals(cState.getKeywordKey()) && cardState.getColors() == cState.getColors() && diff --git a/forge-gui-mobile/src/forge/screens/match/views/VReveal.java b/forge-gui-mobile/src/forge/screens/match/views/VReveal.java index ae0a15d99ca..8352ab1ea4f 100644 --- a/forge-gui-mobile/src/forge/screens/match/views/VReveal.java +++ b/forge-gui-mobile/src/forge/screens/match/views/VReveal.java @@ -130,7 +130,7 @@ public class VReveal extends FDropDown { private RevealEntryDisplay(PaperCard pc, boolean isAltRow) { paperCard = pc; altRow = isAltRow; - text = CardTranslation.getTranslatedName(pc.getName()) + "\n" + formatType(); + text = CardTranslation.getTranslatedName(pc.getDisplayName()) + "\n" + formatType(); } public float getMinHeight(float width) { diff --git a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestCommandersScreen.java b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestCommandersScreen.java index 3578dd88cd2..fb5b381308e 100644 --- a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestCommandersScreen.java +++ b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestCommandersScreen.java @@ -202,7 +202,7 @@ public class ConquestCommandersScreen extends FScreen { float imageSize = CardRenderer.MANA_SYMBOL_SIZE; ColorSet cardColor = card.getRules().getColorIdentity(); float availableWidth = w - cardArtWidth - CardFaceSymbols.getWidth(cardColor, imageSize) - FList.PADDING; - g.drawText(card.getName(), font, foreColor, x, y, availableWidth, imageSize, false, Align.left, true); + g.drawText(card.getDisplayName(), font, foreColor, x, y, availableWidth, imageSize, false, Align.left, true); CardFaceSymbols.drawColorSet(g, cardColor, x + availableWidth + FList.PADDING, y, imageSize); if (compactModeHandler.isCompactMode()) { diff --git a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMultiverseScreen.java b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMultiverseScreen.java index 55de463ad31..caee36822ec 100644 --- a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMultiverseScreen.java +++ b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMultiverseScreen.java @@ -684,10 +684,10 @@ public class ConquestMultiverseScreen extends FScreen { float labelHeight = playerAvatar.getTop(); if (playerAvatar.card != null) { - g.drawText(playerAvatar.card.getName(), AVATAR_NAME_FONT, Color.WHITE, PADDING, 0, labelWidth, labelHeight, false, Align.left, true); + g.drawText(playerAvatar.card.getDisplayName(), AVATAR_NAME_FONT, Color.WHITE, PADDING, 0, labelWidth, labelHeight, false, Align.left, true); } if (opponentAvatar.card != null) { - g.drawText(opponentAvatar.card.getName(), AVATAR_NAME_FONT, Color.WHITE, getWidth() - labelWidth - PADDING, getHeight() - labelHeight, labelWidth, labelHeight, false, Align.right, true); + g.drawText(opponentAvatar.card.getDisplayName(), AVATAR_NAME_FONT, Color.WHITE, getWidth() - labelWidth - PADDING, getHeight() - labelHeight, labelWidth, labelHeight, false, Align.right, true); } } diff --git a/forge-gui-mobile/src/forge/screens/planarconquest/LoadConquestScreen.java b/forge-gui-mobile/src/forge/screens/planarconquest/LoadConquestScreen.java index 8b3b30a2edb..704f21fa4c6 100644 --- a/forge-gui-mobile/src/forge/screens/planarconquest/LoadConquestScreen.java +++ b/forge-gui-mobile/src/forge/screens/planarconquest/LoadConquestScreen.java @@ -282,7 +282,7 @@ public class LoadConquestScreen extends LaunchScreen { font = FSkinFont.get(12); float cardsWidth = font.getBounds(cards).width + iconSize + SettingsScreen.SETTING_PADDING; float shardsWidth = font.getBounds(shards).width + iconSize + SettingsScreen.SETTING_PADDING; - g.drawText(value.getPlaneswalker().getName() + " - " + value.getCurrentPlane().getName().replace("_", " "), font, SettingsScreen.DESC_COLOR, x, y, w - shardsWidth - cardsWidth, h, false, Align.left, false); + g.drawText(value.getPlaneswalker().getDisplayName() + " - " + value.getCurrentPlane().getName().replace("_", " "), font, SettingsScreen.DESC_COLOR, x, y, w - shardsWidth - cardsWidth, h, false, Align.left, false); g.drawImage(FSkinImage.SPELLBOOK, x + w - shardsWidth - cardsWidth + iconOffset, y - SettingsScreen.SETTING_PADDING, iconSize, iconSize); g.drawText(cards, font, SettingsScreen.DESC_COLOR, x + w - shardsWidth - cardsWidth + iconSize + SettingsScreen.SETTING_PADDING, y, w, h, false, Align.left, false); g.drawImage(FSkinImage.AETHER_SHARD, x + w - shardsWidth + iconOffset, y - SettingsScreen.SETTING_PADDING, iconSize, iconSize); diff --git a/forge-gui-mobile/src/forge/util/CardRendererUtils.java b/forge-gui-mobile/src/forge/util/CardRendererUtils.java index bb239862039..a44e878553a 100644 --- a/forge-gui-mobile/src/forge/util/CardRendererUtils.java +++ b/forge-gui-mobile/src/forge/util/CardRendererUtils.java @@ -9,7 +9,7 @@ import forge.screens.match.MatchController; public class CardRendererUtils { public static boolean needsRotation(final CardView card) { return needsRotation(card.isSplitCard() ? ForgePreferences.FPref.UI_ROTATE_SPLIT_CARDS - : ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON, card, canShowAlternate(card, card.getName())); + : ForgePreferences.FPref.UI_ROTATE_PLANE_OR_PHENOMENON, card, canShowAlternate(card, card.getOracleName())); } public static boolean needsRotation(final CardView card, final boolean altState) { return needsRotation(card.isSplitCard() ? ForgePreferences.FPref.UI_ROTATE_SPLIT_CARDS @@ -52,7 +52,7 @@ public class CardRendererUtils { showAlt = card.getAlternateState().getOracleText().contains(reference.trim()); else { if (card.isRoom()) // special case for room cards - showAlt = card.getAlternateState().getName().equalsIgnoreCase(reference); + showAlt = card.getAlternateState().getOracleName().equalsIgnoreCase(reference); else showAlt = reference.contains(card.getAlternateState().getAbilityText()); } diff --git a/forge-gui/res/cardsfolder/b/blanka_ferocious_friend.txt b/forge-gui/res/cardsfolder/b/blanka_ferocious_friend.txt index 236ae1b6974..fa2a1e23e84 100644 --- a/forge-gui/res/cardsfolder/b/blanka_ferocious_friend.txt +++ b/forge-gui/res/cardsfolder/b/blanka_ferocious_friend.txt @@ -1,5 +1,5 @@ Name:Blanka, Ferocious Friend -AltName:The Howling Abomination +Variant:UniversesWithin:FlavorName:The Howling Abomination ManaCost:3 R G Types:Legendary Creature Human Beast Warrior PT:5/5 diff --git a/forge-gui/res/cardsfolder/c/chief_jim_hopper.txt b/forge-gui/res/cardsfolder/c/chief_jim_hopper.txt index 44fd889e8ec..1be9f871a52 100644 --- a/forge-gui/res/cardsfolder/c/chief_jim_hopper.txt +++ b/forge-gui/res/cardsfolder/c/chief_jim_hopper.txt @@ -1,5 +1,5 @@ Name:Chief Jim Hopper -AltName:Sophina, Spearsage Deserter +Variant:UniversesWithin:FlavorName:Sophina, Spearsage Deserter ManaCost:2 R W Types:Legendary Creature Human Soldier PT:4/4 diff --git a/forge-gui/res/cardsfolder/c/chun_li_countless_kicks.txt b/forge-gui/res/cardsfolder/c/chun_li_countless_kicks.txt index d55e3982020..2805c981fa7 100644 --- a/forge-gui/res/cardsfolder/c/chun_li_countless_kicks.txt +++ b/forge-gui/res/cardsfolder/c/chun_li_countless_kicks.txt @@ -1,5 +1,5 @@ Name:Chun-Li, Countless Kicks -AltName:Zethi, Arcane Blademaster +Variant:UniversesWithin:FlavorName:Zethi, Arcane Blademaster ManaCost:1 W U Types:Legendary Creature Human Soldier PT:3/3 diff --git a/forge-gui/res/cardsfolder/d/daryl_hunter_of_walkers.txt b/forge-gui/res/cardsfolder/d/daryl_hunter_of_walkers.txt index dfb82083e1f..d41119ecec4 100644 --- a/forge-gui/res/cardsfolder/d/daryl_hunter_of_walkers.txt +++ b/forge-gui/res/cardsfolder/d/daryl_hunter_of_walkers.txt @@ -1,5 +1,5 @@ Name:Daryl, Hunter of Walkers -AltName:Hansk, Slayer Zealot +Variant:UniversesWithin:FlavorName:Hansk, Slayer Zealot ManaCost:2 R G Types:Legendary Creature Human Archer PT:4/4 diff --git a/forge-gui/res/cardsfolder/d/dhalsim_pliable_pacifist.txt b/forge-gui/res/cardsfolder/d/dhalsim_pliable_pacifist.txt index 98403677176..50715870f8b 100644 --- a/forge-gui/res/cardsfolder/d/dhalsim_pliable_pacifist.txt +++ b/forge-gui/res/cardsfolder/d/dhalsim_pliable_pacifist.txt @@ -1,5 +1,5 @@ Name:Dhalsim, Pliable Pacifist -AltName:Tadeas, Juniper Ascendant +Variant:UniversesWithin:FlavorName:Tadeas, Juniper Ascendant ManaCost:2 G W Types:Legendary Creature Human Monk PT:1/3 diff --git a/forge-gui/res/cardsfolder/d/doric_natures_warden_doric_owlbear_avenger.txt b/forge-gui/res/cardsfolder/d/doric_natures_warden_doric_owlbear_avenger.txt index 6ba01daa33e..2e83ac21350 100644 --- a/forge-gui/res/cardsfolder/d/doric_natures_warden_doric_owlbear_avenger.txt +++ b/forge-gui/res/cardsfolder/d/doric_natures_warden_doric_owlbear_avenger.txt @@ -1,5 +1,5 @@ Name:Doric, Nature's Warden -AltName:Casal, Lurkwood Pathfinder +Variant:UniversesWithin:FlavorName:Casal, Lurkwood Pathfinder ManaCost:3 G Types:Legendary Creature Tiefling Druid PT:3/3 @@ -15,7 +15,7 @@ Oracle:Vigilance\nWhen Doric, Nature's Warden enters, search your library for a ALTERNATE Name:Doric, Owlbear Avenger -AltName:Casal, Pathbreaker Owlbear +Variant:UniversesWithin:FlavorName:Casal, Pathbreaker Owlbear ManaCost:no cost Colors:green Types:Legendary Creature Bird Bear diff --git a/forge-gui/res/cardsfolder/d/dustin_gadget_genius.txt b/forge-gui/res/cardsfolder/d/dustin_gadget_genius.txt index 02810ad435e..93f1b73ace5 100644 --- a/forge-gui/res/cardsfolder/d/dustin_gadget_genius.txt +++ b/forge-gui/res/cardsfolder/d/dustin_gadget_genius.txt @@ -1,5 +1,5 @@ Name:Dustin, Gadget Genius -AltName:Hargilde, Kindly Runechanter +Variant:UniversesWithin:FlavorName:Hargilde, Kindly Runechanter ManaCost:2 W U Types:Legendary Creature Human PT:2/3 diff --git a/forge-gui/res/cardsfolder/e/e_honda_sumo_champion.txt b/forge-gui/res/cardsfolder/e/e_honda_sumo_champion.txt index 2b8391ee18e..f5dba371fa6 100644 --- a/forge-gui/res/cardsfolder/e/e_honda_sumo_champion.txt +++ b/forge-gui/res/cardsfolder/e/e_honda_sumo_champion.txt @@ -1,5 +1,5 @@ Name:E. Honda, Sumo Champion -AltName:Baldin, Century Herdmaster +Variant:UniversesWithin:FlavorName:Baldin, Century Herdmaster ManaCost:4 W W Types:Legendary Creature Human Warrior PT:0/7 diff --git a/forge-gui/res/cardsfolder/e/edgin_larcenous_lutenist.txt b/forge-gui/res/cardsfolder/e/edgin_larcenous_lutenist.txt index 94da000808b..8dcad7daa88 100644 --- a/forge-gui/res/cardsfolder/e/edgin_larcenous_lutenist.txt +++ b/forge-gui/res/cardsfolder/e/edgin_larcenous_lutenist.txt @@ -1,5 +1,5 @@ Name:Edgin, Larcenous Lutenist -AltName:Bohn, Beguiling Balladeer +Variant:UniversesWithin:FlavorName:Bohn, Beguiling Balladeer ManaCost:1 U R Types:Legendary Creature Human Bard PT:3/3 diff --git a/forge-gui/res/cardsfolder/e/eleven_the_mage.txt b/forge-gui/res/cardsfolder/e/eleven_the_mage.txt index aad933cbe3a..f13ce2166d2 100644 --- a/forge-gui/res/cardsfolder/e/eleven_the_mage.txt +++ b/forge-gui/res/cardsfolder/e/eleven_the_mage.txt @@ -1,5 +1,5 @@ Name:Eleven, the Mage -AltName:Cecily, Haunted Mage +Variant:UniversesWithin:FlavorName:Cecily, Haunted Mage ManaCost:1 U B R Types:Legendary Creature Human Wizard PT:3/5 diff --git a/forge-gui/res/cardsfolder/f/forge_neverwinter_charlatan.txt b/forge-gui/res/cardsfolder/f/forge_neverwinter_charlatan.txt index d06fdf7ef3c..0c933844cb8 100644 --- a/forge-gui/res/cardsfolder/f/forge_neverwinter_charlatan.txt +++ b/forge-gui/res/cardsfolder/f/forge_neverwinter_charlatan.txt @@ -1,5 +1,5 @@ Name:Forge, Neverwinter Charlatan -AltName:Evin, Waterdeep Opportunist +Variant:UniversesWithin:FlavorName:Evin, Waterdeep Opportunist ManaCost:3 B Types:Legendary Creature Human Rogue PT:2/4 diff --git a/forge-gui/res/cardsfolder/g/glenn_the_voice_of_calm.txt b/forge-gui/res/cardsfolder/g/glenn_the_voice_of_calm.txt index 193a31690fd..4a4eaeed5c1 100644 --- a/forge-gui/res/cardsfolder/g/glenn_the_voice_of_calm.txt +++ b/forge-gui/res/cardsfolder/g/glenn_the_voice_of_calm.txt @@ -1,5 +1,5 @@ Name:Glenn, the Voice of Calm -AltName:Gregor, Shrewd Magistrate +Variant:UniversesWithin:FlavorName:Gregor, Shrewd Magistrate ManaCost:1 W U Types:Legendary Creature Human Advisor PT:1/3 diff --git a/forge-gui/res/cardsfolder/g/guile_sonic_soldier.txt b/forge-gui/res/cardsfolder/g/guile_sonic_soldier.txt index 79667e55526..adaa05823ec 100644 --- a/forge-gui/res/cardsfolder/g/guile_sonic_soldier.txt +++ b/forge-gui/res/cardsfolder/g/guile_sonic_soldier.txt @@ -1,5 +1,5 @@ Name:Guile, Sonic Soldier -AltName:Immard, the Stormcleaver +Variant:UniversesWithin:FlavorName:Immard, the Stormcleaver ManaCost:1 U R W Types:Legendary Creature Human Soldier PT:4/4 diff --git a/forge-gui/res/cardsfolder/h/hawkins_national_laboratory_the_upside_down.txt b/forge-gui/res/cardsfolder/h/hawkins_national_laboratory_the_upside_down.txt index f53fc7d0c35..a9198d44c46 100644 --- a/forge-gui/res/cardsfolder/h/hawkins_national_laboratory_the_upside_down.txt +++ b/forge-gui/res/cardsfolder/h/hawkins_national_laboratory_the_upside_down.txt @@ -1,5 +1,5 @@ Name:Hawkins National Laboratory -AltName:Havengul Laboratory +Variant:UniversesWithin:FlavorName:Havengul Laboratory ManaCost:no cost Types:Legendary Land A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. @@ -15,7 +15,7 @@ Oracle:{T}: Add {C}.\n{4}, {T}: Investigate.\nAt the beginning of your end step, ALTERNATE Name:The Upside Down -AltName:Havengul Mystery +Variant:UniversesWithin:FlavorName:Havengul Mystery ManaCost:no cost Types:Legendary Land T:Mode$ Transformed | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When this land transforms into CARDNAME, return target creature card from your graveyard to the battlefield. diff --git a/forge-gui/res/cardsfolder/h/holga_relentless_rager.txt b/forge-gui/res/cardsfolder/h/holga_relentless_rager.txt index 22f0543f923..f14c0b3a337 100644 --- a/forge-gui/res/cardsfolder/h/holga_relentless_rager.txt +++ b/forge-gui/res/cardsfolder/h/holga_relentless_rager.txt @@ -1,5 +1,5 @@ Name:Holga, Relentless Rager -AltName:Jurin, Leading the Charge +Variant:UniversesWithin:FlavorName:Jurin, Leading the Charge ManaCost:4 R R Types:Legendary Creature Human Barbarian PT:4/6 diff --git a/forge-gui/res/cardsfolder/k/ken_burning_brawler.txt b/forge-gui/res/cardsfolder/k/ken_burning_brawler.txt index 7200be357be..42d62a3621f 100644 --- a/forge-gui/res/cardsfolder/k/ken_burning_brawler.txt +++ b/forge-gui/res/cardsfolder/k/ken_burning_brawler.txt @@ -1,5 +1,5 @@ Name:Ken, Burning Brawler -AltName:Aisha of Sparks and Smoke +Variant:UniversesWithin:FlavorName:Aisha of Sparks and Smoke ManaCost:1 R R Types:Legendary Creature Human Warrior PT:4/2 diff --git a/forge-gui/res/cardsfolder/l/lucas_the_sharpshooter.txt b/forge-gui/res/cardsfolder/l/lucas_the_sharpshooter.txt index 0a580f6191e..a7da35e49bc 100644 --- a/forge-gui/res/cardsfolder/l/lucas_the_sharpshooter.txt +++ b/forge-gui/res/cardsfolder/l/lucas_the_sharpshooter.txt @@ -1,5 +1,5 @@ Name:Lucas, the Sharpshooter -AltName:Bjorna, Nightfall Alchemist +Variant:UniversesWithin:FlavorName:Bjorna, Nightfall Alchemist ManaCost:U R Types:Legendary Creature Human PT:1/3 diff --git a/forge-gui/res/cardsfolder/l/lucille.txt b/forge-gui/res/cardsfolder/l/lucille.txt index 3806acc3687..e2f5652d516 100644 --- a/forge-gui/res/cardsfolder/l/lucille.txt +++ b/forge-gui/res/cardsfolder/l/lucille.txt @@ -1,5 +1,5 @@ Name:Lucille -AltName:Gisa's Favorite Shovel +Variant:UniversesWithin:FlavorName:Gisa's Favorite Shovel ManaCost:1 B Types:Legendary Artifact Equipment S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 2 | AddKeyword$ Menace | Description$ Equipped creature gets +2/+0 and has menace. diff --git a/forge-gui/res/cardsfolder/m/max_the_daredevil.txt b/forge-gui/res/cardsfolder/m/max_the_daredevil.txt index 0f3930452f0..8f2dc6b2dc7 100644 --- a/forge-gui/res/cardsfolder/m/max_the_daredevil.txt +++ b/forge-gui/res/cardsfolder/m/max_the_daredevil.txt @@ -1,5 +1,5 @@ Name:Max, the Daredevil -AltName:Elmar, Ulvenwald Informant +Variant:UniversesWithin:FlavorName:Elmar, Ulvenwald Informant ManaCost:1 R G Types:Legendary Creature Human PT:3/2 diff --git a/forge-gui/res/cardsfolder/m/michonne_ruthless_survivor.txt b/forge-gui/res/cardsfolder/m/michonne_ruthless_survivor.txt index 5d06c354989..304a56c992f 100644 --- a/forge-gui/res/cardsfolder/m/michonne_ruthless_survivor.txt +++ b/forge-gui/res/cardsfolder/m/michonne_ruthless_survivor.txt @@ -1,5 +1,5 @@ Name:Michonne, Ruthless Survivor -AltName:Enkira, Hostile Scavenger +Variant:UniversesWithin:FlavorName:Enkira, Hostile Scavenger ManaCost:3 B G Types:Legendary Creature Human Warrior PT:3/3 diff --git a/forge-gui/res/cardsfolder/m/mike_the_dungeon_master.txt b/forge-gui/res/cardsfolder/m/mike_the_dungeon_master.txt index 00ee1fe6e75..f20f78008f2 100644 --- a/forge-gui/res/cardsfolder/m/mike_the_dungeon_master.txt +++ b/forge-gui/res/cardsfolder/m/mike_the_dungeon_master.txt @@ -1,5 +1,5 @@ Name:Mike, the Dungeon Master -AltName:Othelm, Sigardian Outcast +Variant:UniversesWithin:FlavorName:Othelm, Sigardian Outcast ManaCost:1 G W Types:Legendary Creature Human PT:2/2 diff --git a/forge-gui/res/cardsfolder/m/mind_flayer_the_shadow.txt b/forge-gui/res/cardsfolder/m/mind_flayer_the_shadow.txt index c714b429de5..4bb0d0648a8 100644 --- a/forge-gui/res/cardsfolder/m/mind_flayer_the_shadow.txt +++ b/forge-gui/res/cardsfolder/m/mind_flayer_the_shadow.txt @@ -1,5 +1,5 @@ Name:Mind Flayer, the Shadow -AltName:Arvinox, the Mind Flail +Variant:UniversesWithin:FlavorName:Arvinox, the Mind Flail ManaCost:4 B B B Types:Legendary Enchantment Creature Horror PT:9/9 diff --git a/forge-gui/res/cardsfolder/n/negan_the_cold_blooded.txt b/forge-gui/res/cardsfolder/n/negan_the_cold_blooded.txt index 2fa9953b813..2dcf33c7261 100644 --- a/forge-gui/res/cardsfolder/n/negan_the_cold_blooded.txt +++ b/forge-gui/res/cardsfolder/n/negan_the_cold_blooded.txt @@ -1,5 +1,5 @@ Name:Negan, the Cold-Blooded -AltName:Malik, Grim Manipulator +Variant:UniversesWithin:FlavorName:Malik, Grim Manipulator ManaCost:2 R W B Types:Legendary Creature Human Rogue PT:4/3 diff --git a/forge-gui/res/cardsfolder/n/no_secret_is_hidden_from_me.txt b/forge-gui/res/cardsfolder/n/no_secret_is_hidden_from_me.txt index e67382295cc..35f9b503258 100644 --- a/forge-gui/res/cardsfolder/n/no_secret_is_hidden_from_me.txt +++ b/forge-gui/res/cardsfolder/n/no_secret_is_hidden_from_me.txt @@ -1,4 +1,4 @@ -Name:No Secret Is Hidden From Me +Name:No Secret Is Hidden from Me ManaCost:no cost Types:Scheme T:Mode$ SetInMotion | ValidCard$ Card.Self | Execute$ TrigDigUntil | TriggerZones$ Command | TriggerDescription$ When you set this scheme in motion, exile cards from the top of your library until you exile a nonland card. You may cast that card without paying its mana cost. Then if you control six or more lands, repeat this process once. diff --git a/forge-gui/res/cardsfolder/r/rick_steadfast_leader.txt b/forge-gui/res/cardsfolder/r/rick_steadfast_leader.txt index a562b0f271e..03117ee8799 100644 --- a/forge-gui/res/cardsfolder/r/rick_steadfast_leader.txt +++ b/forge-gui/res/cardsfolder/r/rick_steadfast_leader.txt @@ -1,5 +1,5 @@ Name:Rick, Steadfast Leader -AltName:Greymond, Avacyn's Stalwart +Variant:UniversesWithin:FlavorName:Greymond, Avacyn's Stalwart ManaCost:2 W W Types:Legendary Creature Human Soldier PT:3/4 diff --git a/forge-gui/res/cardsfolder/r/ryu_world_warrior.txt b/forge-gui/res/cardsfolder/r/ryu_world_warrior.txt index 306db9b9d21..58f284adfd6 100644 --- a/forge-gui/res/cardsfolder/r/ryu_world_warrior.txt +++ b/forge-gui/res/cardsfolder/r/ryu_world_warrior.txt @@ -1,5 +1,5 @@ Name:Ryu, World Warrior -AltName:Vikya, Scorching Stalwart +Variant:UniversesWithin:FlavorName:Vikya, Scorching Stalwart ManaCost:2 W Types:Legendary Creature Human Warrior PT:2/4 diff --git a/forge-gui/res/cardsfolder/s/simon_wild_magic_sorcerer.txt b/forge-gui/res/cardsfolder/s/simon_wild_magic_sorcerer.txt index e840d68cd4c..7aaa5ab78f8 100644 --- a/forge-gui/res/cardsfolder/s/simon_wild_magic_sorcerer.txt +++ b/forge-gui/res/cardsfolder/s/simon_wild_magic_sorcerer.txt @@ -1,5 +1,5 @@ Name:Simon, Wild Magic Sorcerer -AltName:Mathise, Surge Channeler +Variant:UniversesWithin:FlavorName:Mathise, Surge Channeler ManaCost:2 U Types:Legendary Creature Human Elf Shaman PT:1/1 diff --git a/forge-gui/res/cardsfolder/w/will_the_wise.txt b/forge-gui/res/cardsfolder/w/will_the_wise.txt index 289983b17cb..848029df7fa 100644 --- a/forge-gui/res/cardsfolder/w/will_the_wise.txt +++ b/forge-gui/res/cardsfolder/w/will_the_wise.txt @@ -1,5 +1,5 @@ Name:Will the Wise -AltName:Wernog, Rider's Chaplain +Variant:UniversesWithin:FlavorName:Wernog, Rider's Chaplain ManaCost:W B Types:Legendary Creature Human PT:1/2 diff --git a/forge-gui/res/cardsfolder/x/xenk_paladin_unbroken.txt b/forge-gui/res/cardsfolder/x/xenk_paladin_unbroken.txt index ab77d0c5ee5..dbe199dbcec 100644 --- a/forge-gui/res/cardsfolder/x/xenk_paladin_unbroken.txt +++ b/forge-gui/res/cardsfolder/x/xenk_paladin_unbroken.txt @@ -1,5 +1,5 @@ Name:Xenk, Paladin Unbroken -AltName:Rashel, Fist of Torm +Variant:UniversesWithin:FlavorName:Rashel, Fist of Torm ManaCost:2 W W Types:Legendary Creature Human Knight PT:2/4 diff --git a/forge-gui/res/cardsfolder/z/zangief_the_red_cyclone.txt b/forge-gui/res/cardsfolder/z/zangief_the_red_cyclone.txt index fba681f03a2..c73dae1de26 100644 --- a/forge-gui/res/cardsfolder/z/zangief_the_red_cyclone.txt +++ b/forge-gui/res/cardsfolder/z/zangief_the_red_cyclone.txt @@ -1,5 +1,5 @@ Name:Zangief, the Red Cyclone -AltName:Maarika, Brutal Gladiator +Variant:UniversesWithin:FlavorName:Maarika, Brutal Gladiator ManaCost:2 B R G Types:Legendary Creature Human Warrior PT:7/4 diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index d321805ff3d..e9721459971 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -686,7 +686,7 @@ TokensCode=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 @Joana LaFuente +710 R Command Tower @Joana LaFuente ${"flavorName": "Cybertron"} 711 R Tireless Tracker @Nils Hamm 713 R Swords to Plowshares @Justin Hernandez & Alexis Hernandez 714 R Merfolk of the Pearl Trident @Dani Pendergast @@ -1577,7 +1577,7 @@ F1540 M Rainbow Dash @John Thacker 1609 R Beastmaster Ascension @Greg Staples 1610 R Howl of the Night Pack @Johannes Voss 1611 R Second Harvest @Miranda Meeks -1612 R Tovolar, Dire Overlord @Zoltan Boros +1612 R Tovolar, Dire Overlord @Zoltan Boros ${"flavorName": "NOT A WOLF // KING OF WOLVES"} 1614 R Brash Taunter @Mark Behm & Scott Okumura 1615 R Goblin Chieftain @Jakub Kasper & Scott Okumura 1616 R Goblin Ringleader @Svetlin Velinov & Scott Okumura @@ -1902,7 +1902,7 @@ F1540 M Rainbow Dash @John Thacker 1929 M Toxrill, the Corrosive @Gregg Schigiel 1930 M Toski, Bearer of Secrets @Caleb Meurer 1931 R Barktooth Warbeard @Caleb Meurer -1932 M Jodah, the Unifier @Caleb Meurer +1932 M Jodah, the Unifier @Caleb Meurer ${"flavorName": "SpongeBob SquarePants"} 1933 R Counterspell @Tyler Walpole 1934 R Daze @Tyler Walpole 1935 R Inevitable Betrayal @Tyler Walpole @@ -2113,7 +2113,7 @@ F1540 M Rainbow Dash @John Thacker 2176 R Descent into Avernus @Stephen Andrade 2177 R Reckless Endeavor @Stephen Andrade 2178 M Sneak Attack @Stephen Andrade -2179 R Abrade @Stephen Andrade +2179 R Abrade @Stephen Andrade ${"flavorName": "You're Gonna Need a Bigger Boat"} 2181 M Bruvac the Grandiloquent @Akirant 2182 R Windfall @Dan Mumford 2183 M Captain N'ghathrod @Akirant @@ -2212,7 +2212,7 @@ F1540 M Rainbow Dash @John Thacker 7027 R Tibalt's Trickery @Babs Webb 7028 R Minds Aglow @Evan Geltosky 7029 R Command Tower @Dan Black -7030 R Command Tower @Sylvain Sarrailh +7030 R Command Tower @Sylvain Sarrailh ${"flavorName": "Master Emerald Shrine"} 7031 M Lotus Petal @Mike Burns 7032 M Lotus Petal @Mike Burns 7033 M Lotus Petal @Mike Burns diff --git a/forge-gui/res/editions/The List.txt b/forge-gui/res/editions/The List.txt index 6bf6910267c..615a94c9d25 100644 --- a/forge-gui/res/editions/The List.txt +++ b/forge-gui/res/editions/The List.txt @@ -4185,7 +4185,7 @@ SHM-258 C Pili-Pala @Ron Spencer SHM-260 R Reaper King @Jim Murray SHM-263 C Scuttlemutt @Jeremy Jarvis SHM-267 U Umbral Mantle @Richard Sardinha -SLX-4 R Elmar, Ulvenwald Informant @Eliz Roxs +SLX-4 R Elmar, Ulvenwald Informant @Eliz Roxs ${"variant": "UniversesWithin"} SNC-14 R Giada, Font of Hope @Eric Deschamps SNC-15 M Halo Fountain @Anastasia Ovchinnikova SNC-18 C Inspiring Overseer @Irina Nordsol diff --git a/forge-gui/res/editions/Unfinity.txt b/forge-gui/res/editions/Unfinity.txt index 95647642f26..18d01fccea3 100644 --- a/forge-gui/res/editions/Unfinity.txt +++ b/forge-gui/res/editions/Unfinity.txt @@ -102,54 +102,54 @@ F192 R Souvenir T-Shirt @Michael Phillippi F197 U The Big Top @Kirsten Zirngibl F198 C Nearby Planet @Bruce Brenneise F199 R Urza's Fun House @Dmitry Burmak -F203a R Centrifuge @Greg Staples $A -F203b R Centrifuge @Greg Staples $B -F207a C Cover the Spot @Jeff Miracola $A -F207b C Cover the Spot @Jeff Miracola $B -F207c C Cover the Spot @Jeff Miracola $C -F207d C Cover the Spot @Jeff Miracola $D -F208a C Dart Throw @Gaboleps $A -F208b C Dart Throw @Gaboleps $B -F208c C Dart Throw @Gaboleps $C -F208d C Dart Throw @Gaboleps $D -F213a R Gallery of Legends @Jakub Kasper $A -F213b R Gallery of Legends @Jakub Kasper $B -F214a R Gift Shop @Matt Gaser $A -F214b R Gift Shop @Matt Gaser $B -F215a U Guess Your Fate @Bruce Brenneise $A -F215b U Guess Your Fate @Bruce Brenneise $B -F215c U Guess Your Fate @Bruce Brenneise $C -F215d U Guess Your Fate @Bruce Brenneise $D -F220a R Log Flume @Marco Bucci $A -F220b R Log Flume @Marco Bucci $B -F221a R Memory Test @Setor Fiadzigbey $A -F221b R Memory Test @Setor Fiadzigbey $B -F224a R Push Your Luck @Sebastian Giacobino $A -F224b R Push Your Luck @Sebastian Giacobino $B -F226a U Scavenger Hunt @Jamroz Gary $A -F226b U Scavenger Hunt @Jamroz Gary $B -F226c U Scavenger Hunt @Jamroz Gary $C -F226d U Scavenger Hunt @Jamroz Gary $D -F226e U Scavenger Hunt @Jamroz Gary $E -F226f U Scavenger Hunt @Jamroz Gary $F -F228a U Squirrel Stack @Andrea Radeck $A -F228b U Squirrel Stack @Andrea Radeck $B -F228c U Squirrel Stack @Andrea Radeck $C -F228d U Squirrel Stack @Andrea Radeck $D -F228e U Squirrel Stack @Andrea Radeck $E -F228f U Squirrel Stack @Andrea Radeck $F -F230a U The Superlatorium @Simon Dominic $A -F230b U The Superlatorium @Simon Dominic $B -F230c U The Superlatorium @Simon Dominic $C -F230d U The Superlatorium @Simon Dominic $D -F230e U The Superlatorium @Simon Dominic $E -F230f U The Superlatorium @Simon Dominic $F -F233a U Trivia Contest @Caroline Gariba $A -F233b U Trivia Contest @Caroline Gariba $B -F233c U Trivia Contest @Caroline Gariba $C -F233d U Trivia Contest @Caroline Gariba $D -F233e U Trivia Contest @Caroline Gariba $E -F233f U Trivia Contest @Caroline Gariba $F +F203a R Centrifuge @Greg Staples ${"variant": "A"} +F203b R Centrifuge @Greg Staples ${"variant": "B"} +F207a C Cover the Spot @Jeff Miracola ${"variant": "A"} +F207b C Cover the Spot @Jeff Miracola ${"variant": "B"} +F207c C Cover the Spot @Jeff Miracola ${"variant": "C"} +F207d C Cover the Spot @Jeff Miracola ${"variant": "D"} +F208a C Dart Throw @Gaboleps ${"variant": "A"} +F208b C Dart Throw @Gaboleps ${"variant": "B"} +F208c C Dart Throw @Gaboleps ${"variant": "C"} +F208d C Dart Throw @Gaboleps ${"variant": "D"} +F213a R Gallery of Legends @Jakub Kasper ${"variant": "A"} +F213b R Gallery of Legends @Jakub Kasper ${"variant": "B"} +F214a R Gift Shop @Matt Gaser ${"variant": "A"} +F214b R Gift Shop @Matt Gaser ${"variant": "B"} +F215a U Guess Your Fate @Bruce Brenneise ${"variant": "A"} +F215b U Guess Your Fate @Bruce Brenneise ${"variant": "B"} +F215c U Guess Your Fate @Bruce Brenneise ${"variant": "C"} +F215d U Guess Your Fate @Bruce Brenneise ${"variant": "D"} +F220a R Log Flume @Marco Bucci ${"variant": "A"} +F220b R Log Flume @Marco Bucci ${"variant": "B"} +F221a R Memory Test @Setor Fiadzigbey ${"variant": "A"} +F221b R Memory Test @Setor Fiadzigbey ${"variant": "B"} +F224a R Push Your Luck @Sebastian Giacobino ${"variant": "A"} +F224b R Push Your Luck @Sebastian Giacobino ${"variant": "B"} +F226a U Scavenger Hunt @Jamroz Gary ${"variant": "A"} +F226b U Scavenger Hunt @Jamroz Gary ${"variant": "B"} +F226c U Scavenger Hunt @Jamroz Gary ${"variant": "C"} +F226d U Scavenger Hunt @Jamroz Gary ${"variant": "D"} +F226e U Scavenger Hunt @Jamroz Gary ${"variant": "E"} +F226f U Scavenger Hunt @Jamroz Gary ${"variant": "F"} +F228a U Squirrel Stack @Andrea Radeck ${"variant": "A"} +F228b U Squirrel Stack @Andrea Radeck ${"variant": "B"} +F228c U Squirrel Stack @Andrea Radeck ${"variant": "C"} +F228d U Squirrel Stack @Andrea Radeck ${"variant": "D"} +F228e U Squirrel Stack @Andrea Radeck ${"variant": "E"} +F228f U Squirrel Stack @Andrea Radeck ${"variant": "F"} +F230a U The Superlatorium @Simon Dominic ${"variant": "A"} +F230b U The Superlatorium @Simon Dominic ${"variant": "B"} +F230c U The Superlatorium @Simon Dominic ${"variant": "C"} +F230d U The Superlatorium @Simon Dominic ${"variant": "D"} +F230e U The Superlatorium @Simon Dominic ${"variant": "E"} +F230f U The Superlatorium @Simon Dominic ${"variant": "F"} +F233a U Trivia Contest @Caroline Gariba ${"variant": "A"} +F233b U Trivia Contest @Caroline Gariba ${"variant": "B"} +F233c U Trivia Contest @Caroline Gariba ${"variant": "C"} +F233d U Trivia Contest @Caroline Gariba ${"variant": "D"} +F233e U Trivia Contest @Caroline Gariba ${"variant": "E"} +F233f U Trivia Contest @Caroline Gariba ${"variant": "F"} F245 R Katerina of Myra's Marvels @David Semple F246 R Solaflora, Intergalactic Icon @Scooter F247 R Fluros of Myra's Marvels @David Semple @@ -391,93 +391,93 @@ F538 R Water Gun Balloon Game @Ralph Horsley 194 C Ticket Turbotubes @Matt Gaser 195 C Ticketomaton @Michael Phillippi 196 U Wicker Picker @Igor Grechanyi -200a U Balloon Stand @Jakub Kasper $A -200b U Balloon Stand @Jakub Kasper $B -200c U Balloon Stand @Jakub Kasper $C -200d U Balloon Stand @Jakub Kasper $D -201a U Bounce Chamber @Dmitry Burmak $A -201b U Bounce Chamber @Dmitry Burmak $B -201c U Bounce Chamber @Dmitry Burmak $C -201d U Bounce Chamber @Dmitry Burmak $D -202a U Bumper Cars @Gabor Szikszai $A -202b U Bumper Cars @Gabor Szikszai $B -202c U Bumper Cars @Gabor Szikszai $C -202d U Bumper Cars @Gabor Szikszai $D -202e U Bumper Cars @Gabor Szikszai $E -202f U Bumper Cars @Gabor Szikszai $F -204a C Clown Extruder @Marco Bucci $A -204b C Clown Extruder @Marco Bucci $B -204c C Clown Extruder @Marco Bucci $C -204d C Clown Extruder @Marco Bucci $D -205a U Concession Stand @David Sladek $A -205b U Concession Stand @David Sladek $B -205c U Concession Stand @David Sladek $C -205d U Concession Stand @David Sladek $D -206a C Costume Shop @Raluca Marinescu $A -206b C Costume Shop @Raluca Marinescu $B -206c C Costume Shop @Raluca Marinescu $C -206d C Costume Shop @Raluca Marinescu $D -206e C Costume Shop @Raluca Marinescu $E -206f C Costume Shop @Raluca Marinescu $F -209a C Drop Tower @Dmitry Burmak $A -209b C Drop Tower @Dmitry Burmak $B -209c C Drop Tower @Dmitry Burmak $C -209d C Drop Tower @Dmitry Burmak $D -209e C Drop Tower @Dmitry Burmak $E -209f C Drop Tower @Dmitry Burmak $F +200a U Balloon Stand @Jakub Kasper ${"variant": "A"} +200b U Balloon Stand @Jakub Kasper ${"variant": "B"} +200c U Balloon Stand @Jakub Kasper ${"variant": "C"} +200d U Balloon Stand @Jakub Kasper ${"variant": "D"} +201a U Bounce Chamber @Dmitry Burmak ${"variant": "A"} +201b U Bounce Chamber @Dmitry Burmak ${"variant": "B"} +201c U Bounce Chamber @Dmitry Burmak ${"variant": "C"} +201d U Bounce Chamber @Dmitry Burmak ${"variant": "D"} +202a U Bumper Cars @Gabor Szikszai ${"variant": "A"} +202b U Bumper Cars @Gabor Szikszai ${"variant": "B"} +202c U Bumper Cars @Gabor Szikszai ${"variant": "C"} +202d U Bumper Cars @Gabor Szikszai ${"variant": "D"} +202e U Bumper Cars @Gabor Szikszai ${"variant": "E"} +202f U Bumper Cars @Gabor Szikszai ${"variant": "F"} +204a C Clown Extruder @Marco Bucci ${"variant": "A"} +204b C Clown Extruder @Marco Bucci ${"variant": "B"} +204c C Clown Extruder @Marco Bucci ${"variant": "C"} +204d C Clown Extruder @Marco Bucci ${"variant": "D"} +205a U Concession Stand @David Sladek ${"variant": "A"} +205b U Concession Stand @David Sladek ${"variant": "B"} +205c U Concession Stand @David Sladek ${"variant": "C"} +205d U Concession Stand @David Sladek ${"variant": "D"} +206a C Costume Shop @Raluca Marinescu ${"variant": "A"} +206b C Costume Shop @Raluca Marinescu ${"variant": "B"} +206c C Costume Shop @Raluca Marinescu ${"variant": "C"} +206d C Costume Shop @Raluca Marinescu ${"variant": "D"} +206e C Costume Shop @Raluca Marinescu ${"variant": "E"} +206f C Costume Shop @Raluca Marinescu ${"variant": "F"} +209a C Drop Tower @Dmitry Burmak ${"variant": "A"} +209b C Drop Tower @Dmitry Burmak ${"variant": "B"} +209c C Drop Tower @Dmitry Burmak ${"variant": "C"} +209d C Drop Tower @Dmitry Burmak ${"variant": "D"} +209e C Drop Tower @Dmitry Burmak ${"variant": "E"} +209f C Drop Tower @Dmitry Burmak ${"variant": "F"} 210 R Ferris Wheel @Kirsten Zirngibl -211a C Foam Weapons Kiosk @Matt Gaser $A -211b C Foam Weapons Kiosk @Matt Gaser $B -211c C Foam Weapons Kiosk @Matt Gaser $C -211d C Foam Weapons Kiosk @Matt Gaser $D -212a C Fortune Teller @Jamroz Gary $A -212b C Fortune Teller @Jamroz Gary $B -212c C Fortune Teller @Jamroz Gary $C -212d C Fortune Teller @Jamroz Gary $D -212e C Fortune Teller @Jamroz Gary $E -212f C Fortune Teller @Jamroz Gary $F -216a R Hall of Mirrors @Vincent Christiaens $A -216b R Hall of Mirrors @Vincent Christiaens $B -217a R Haunted House @Dmitry Burmak $A -217b R Haunted House @Dmitry Burmak $B -218a U Information Booth @Gaboleps $A -218b U Information Booth @Gaboleps $B -218c U Information Booth @Gaboleps $C -218d U Information Booth @Gaboleps $D -219a C Kiddie Coaster @Marco Bucci $A -219b C Kiddie Coaster @Marco Bucci $B -219c C Kiddie Coaster @Marco Bucci $C -219d C Kiddie Coaster @Marco Bucci $D -219e C Kiddie Coaster @Marco Bucci $E -219f C Kiddie Coaster @Marco Bucci $F -222a R Merry-Go-Round @Carl Critchlow $A -222b R Merry-Go-Round @Carl Critchlow $B -223a C Pick-a-Beeble @Dave Greco $A -223b C Pick-a-Beeble @Dave Greco $B -223c C Pick-a-Beeble @Dave Greco $C -223d C Pick-a-Beeble @Dave Greco $D -223e C Pick-a-Beeble @Dave Greco $E -223f C Pick-a-Beeble @Dave Greco $F -225a U Roller Coaster @Gabor Szikszai $A -225b U Roller Coaster @Gabor Szikszai $B -225c U Roller Coaster @Gabor Szikszai $C -225d U Roller Coaster @Gabor Szikszai $D -227a C Spinny Ride @Aaron J. Riley $A -227b C Spinny Ride @Aaron J. Riley $B -227c C Spinny Ride @Aaron J. Riley $C -227d C Spinny Ride @Aaron J. Riley $D -227e C Spinny Ride @Aaron J. Riley $E -227f C Spinny Ride @Aaron J. Riley $F -229a R Storybook Ride @Dmitry Burmak $A -229b R Storybook Ride @Dmitry Burmak $B -231a R Swinging Ship @Mike Burns $A -231b R Swinging Ship @Mike Burns $B -232a U Trash Bin @Greg Bobrowski $A -232b U Trash Bin @Greg Bobrowski $B -232c U Trash Bin @Greg Bobrowski $C -232d U Trash Bin @Greg Bobrowski $D -234a R Tunnel of Love @Vladimir Krisetskiy $A -234b R Tunnel of Love @Vladimir Krisetskiy $B +211a C Foam Weapons Kiosk @Matt Gaser ${"variant": "A"} +211b C Foam Weapons Kiosk @Matt Gaser ${"variant": "B"} +211c C Foam Weapons Kiosk @Matt Gaser ${"variant": "C"} +211d C Foam Weapons Kiosk @Matt Gaser ${"variant": "D"} +212a C Fortune Teller @Jamroz Gary ${"variant": "A"} +212b C Fortune Teller @Jamroz Gary ${"variant": "B"} +212c C Fortune Teller @Jamroz Gary ${"variant": "C"} +212d C Fortune Teller @Jamroz Gary ${"variant": "D"} +212e C Fortune Teller @Jamroz Gary ${"variant": "E"} +212f C Fortune Teller @Jamroz Gary ${"variant": "F"} +216a R Hall of Mirrors @Vincent Christiaens ${"variant": "A"} +216b R Hall of Mirrors @Vincent Christiaens ${"variant": "B"} +217a R Haunted House @Dmitry Burmak ${"variant": "A"} +217b R Haunted House @Dmitry Burmak ${"variant": "B"} +218a U Information Booth @Gaboleps ${"variant": "A"} +218b U Information Booth @Gaboleps ${"variant": "B"} +218c U Information Booth @Gaboleps ${"variant": "C"} +218d U Information Booth @Gaboleps ${"variant": "D"} +219a C Kiddie Coaster @Marco Bucci ${"variant": "A"} +219b C Kiddie Coaster @Marco Bucci ${"variant": "B"} +219c C Kiddie Coaster @Marco Bucci ${"variant": "C"} +219d C Kiddie Coaster @Marco Bucci ${"variant": "D"} +219e C Kiddie Coaster @Marco Bucci ${"variant": "E"} +219f C Kiddie Coaster @Marco Bucci ${"variant": "F"} +222a R Merry-Go-Round @Carl Critchlow ${"variant": "A"} +222b R Merry-Go-Round @Carl Critchlow ${"variant": "B"} +223a C Pick-a-Beeble @Dave Greco ${"variant": "A"} +223b C Pick-a-Beeble @Dave Greco ${"variant": "B"} +223c C Pick-a-Beeble @Dave Greco ${"variant": "C"} +223d C Pick-a-Beeble @Dave Greco ${"variant": "D"} +223e C Pick-a-Beeble @Dave Greco ${"variant": "E"} +223f C Pick-a-Beeble @Dave Greco ${"variant": "F"} +225a U Roller Coaster @Gabor Szikszai ${"variant": "A"} +225b U Roller Coaster @Gabor Szikszai ${"variant": "B"} +225c U Roller Coaster @Gabor Szikszai ${"variant": "C"} +225d U Roller Coaster @Gabor Szikszai ${"variant": "D"} +227a C Spinny Ride @Aaron J. Riley ${"variant": "A"} +227b C Spinny Ride @Aaron J. Riley ${"variant": "B"} +227c C Spinny Ride @Aaron J. Riley ${"variant": "C"} +227d C Spinny Ride @Aaron J. Riley ${"variant": "D"} +227e C Spinny Ride @Aaron J. Riley ${"variant": "E"} +227f C Spinny Ride @Aaron J. Riley ${"variant": "F"} +229a R Storybook Ride @Dmitry Burmak ${"variant": "A"} +229b R Storybook Ride @Dmitry Burmak ${"variant": "B"} +231a R Swinging Ship @Mike Burns ${"variant": "A"} +231b R Swinging Ship @Mike Burns ${"variant": "B"} +232a U Trash Bin @Greg Bobrowski ${"variant": "A"} +232b U Trash Bin @Greg Bobrowski ${"variant": "B"} +232c U Trash Bin @Greg Bobrowski ${"variant": "C"} +232d U Trash Bin @Greg Bobrowski ${"variant": "D"} +234a R Tunnel of Love @Vladimir Krisetskiy ${"variant": "A"} +234b R Tunnel of Love @Vladimir Krisetskiy ${"variant": "B"} 235 L Plains @Adam Paquette 236 L Island @Adam Paquette 237 L Swamp @Adam Paquette diff --git a/forge-gui/res/editions/Unstable.txt b/forge-gui/res/editions/Unstable.txt index c7e51a7bcb8..009ba35bda4 100644 --- a/forge-gui/res/editions/Unstable.txt +++ b/forge-gui/res/editions/Unstable.txt @@ -21,12 +21,12 @@ ScryfallCode=UST 9 U Half-Kitten, Half- @Andrea Radeck 10 C Humming- @Mark Behm 11 R Jackknight @Ben Wootten -12a U Knight of the Kitchen Sink @Mark A. Nelson $A -12b U Knight of the Kitchen Sink @Mark A. Nelson $B -12c U Knight of the Kitchen Sink @Mark A. Nelson $C -12d U Knight of the Kitchen Sink @Mark A. Nelson $D -12e U Knight of the Kitchen Sink @Mark A. Nelson $E -12f U Knight of the Kitchen Sink @Mark A. Nelson $F +12a U Knight of the Kitchen Sink @Mark A. Nelson ${"variant": "A"} +12b U Knight of the Kitchen Sink @Mark A. Nelson ${"variant": "B"} +12c U Knight of the Kitchen Sink @Mark A. Nelson ${"variant": "C"} +12d U Knight of the Kitchen Sink @Mark A. Nelson ${"variant": "D"} +12e U Knight of the Kitchen Sink @Mark A. Nelson ${"variant": "E"} +12f U Knight of the Kitchen Sink @Mark A. Nelson ${"variant": "F"} 13 U Knight of the Widget @Emrah Elmasli 14 U Midlife Upgrade @Hector Ortiz 15 R Oddly Uneven @Ben Wootten @@ -66,12 +66,12 @@ ScryfallCode=UST 46 U Spy Eye @Ben Wootten 47 U Suspicious Nanny @Chris Seaman 48 C Time Out @Dave Allsop -49a R Very Cryptic Command @Wayne England $A -49b R Very Cryptic Command @Zoltan Boros $B -49c R Very Cryptic Command @Zoltan Boros $C -49d R Very Cryptic Command @Zoltan Boros $D -49e R Very Cryptic Command @Zoltan Boros $E -49f R Very Cryptic Command @Zoltan Boros $F +49a R Very Cryptic Command @Wayne England ${"variant": "A"} +49b R Very Cryptic Command @Zoltan Boros ${"variant": "B"} +49c R Very Cryptic Command @Zoltan Boros ${"variant": "C"} +49d R Very Cryptic Command @Zoltan Boros ${"variant": "D"} +49e R Very Cryptic Command @Zoltan Boros ${"variant": "E"} +49f R Very Cryptic Command @Zoltan Boros ${"variant": "F"} 50 C Wall of Fortune @Tom Babbey 51 C Big Boa Constrictor @Kari Christensen 52 C capital offense @Matt Dixon @@ -92,12 +92,12 @@ ScryfallCode=UST 64 U Overt Operative @Bram Sels 65 U "Rumors of My Death..." @Alex Konstad 66 U Skull Saucer @Mike Burns -67a U Sly Spy @Michael Phillippi $A -67b U Sly Spy @Michael Phillippi $B -67c U Sly Spy @Michael Phillippi $C -67d U Sly Spy @Michael Phillippi $D -67e U Sly Spy @Michael Phillippi $E -67f U Sly Spy @Michael Phillippi $F +67a U Sly Spy @Michael Phillippi ${"variant": "A"} +67b U Sly Spy @Michael Phillippi ${"variant": "B"} +67c U Sly Spy @Michael Phillippi ${"variant": "C"} +67d U Sly Spy @Michael Phillippi ${"variant": "D"} +67e U Sly Spy @Michael Phillippi ${"variant": "E"} +67f U Sly Spy @Michael Phillippi ${"variant": "F"} 68 C Snickering Squirrel @Michael Phillippi 69 R Spike, Tournament Grinder @Zoltan Boros 70 U Squirrel-Powered Scheme @Even Amundsen @@ -112,12 +112,12 @@ ScryfallCode=UST 79 C Common Iguana @Brynn Metheney 80 R The Countdown Is at One @Jesper Ejsing 81 C Feisty Stegosaurus @Kari Christensen -82a U Garbage Elemental @Hector Ortiz $A -82b U Garbage Elemental @Hector Ortiz $B -82c U Garbage Elemental @Hector Ortiz $C -82d U Garbage Elemental @Hector Ortiz $D -82e U Garbage Elemental @Hector Ortiz $E -82f U Garbage Elemental @Hector Ortiz $F +82a U Garbage Elemental @Hector Ortiz ${"variant": "A"} +82b U Garbage Elemental @Hector Ortiz ${"variant": "B"} +82c U Garbage Elemental @Hector Ortiz ${"variant": "C"} +82d U Garbage Elemental @Hector Ortiz ${"variant": "D"} +82e U Garbage Elemental @Hector Ortiz ${"variant": "E"} +82f U Garbage Elemental @Hector Ortiz ${"variant": "F"} 83 U Goblin Haberdasher @Jesper Ejsing 84 U Half-Orc, Half- @Kev Walker 85 C Hammer Helper @Dave Allsop @@ -153,12 +153,12 @@ ScryfallCode=UST 110 C Ground Pounder @Warren Mahy 111 U Half-Squirrel, Half- @Andrea Radeck 112 R Hydradoodle @Mathias Kollros -113a R Ineffable Blessing @Milivoj Ćeran $A -113b R Ineffable Blessing @Milivoj Ćeran $B -113c R Ineffable Blessing @Milivoj Ćeran $C -113d R Ineffable Blessing @Milivoj Ćeran $D -113e R Ineffable Blessing @Milivoj Ćeran $E -113f R Ineffable Blessing @Milivoj Ćeran $F +113a R Ineffable Blessing @Milivoj Ćeran ${"variant": "A"} +113b R Ineffable Blessing @Milivoj Ćeran ${"variant": "B"} +113c R Ineffable Blessing @Milivoj Ćeran ${"variant": "C"} +113d R Ineffable Blessing @Milivoj Ćeran ${"variant": "D"} +113e R Ineffable Blessing @Milivoj Ćeran ${"variant": "E"} +113f R Ineffable Blessing @Milivoj Ćeran ${"variant": "F"} 114 C Joyride Rigger @Wayne Reynolds 115 U Monkey- @Andrea Radeck 116 C Mother Kangaroo @Andrea Radeck @@ -195,12 +195,12 @@ ScryfallCode=UST 145c C Despondent Killbot @Alex Konstad 145d C Enraged Killbot @Alex Konstad 146 U Entirely Normal Armchair @Tom Babbey -147a R Everythingamajig @Chris Seaman $A -147b R Everythingamajig @Chris Seaman $B -147c R Everythingamajig @Chris Seaman $C -147d R Everythingamajig @Chris Seaman $D -147e R Everythingamajig @Chris Seaman $E -147f R Everythingamajig @Chris Seaman $F +147a R Everythingamajig @Chris Seaman ${"variant": "A"} +147b R Everythingamajig @Chris Seaman ${"variant": "B"} +147c R Everythingamajig @Chris Seaman ${"variant": "C"} +147d R Everythingamajig @Chris Seaman ${"variant": "D"} +147e R Everythingamajig @Chris Seaman ${"variant": "E"} +147f R Everythingamajig @Chris Seaman ${"variant": "F"} 148 C Gnome-Made Engine @Sean Murray 149 R Handy Dandy Clone Machine @Mike Burns 150 R Kindslaver @Zoltan Boros diff --git a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java index 401cf05985f..4f046e60e2a 100644 --- a/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java +++ b/forge-gui/src/main/java/forge/gamemodes/limited/LimitedPlayer.java @@ -152,7 +152,7 @@ public class LimitedPlayer { removedFromPool = true; removedFromCardPool.add(bestPick); if (choice.equals("Animus of Predation")) { - addLog(name() + " removed " + bestPick.getName() + " from the draft with " + choice + "."); + addLog(name() + " removed " + bestPick.getDisplayName() + " from the draft with " + choice + "."); } else if (choice.equals("Cogwork Grinder")) { addLog(name() + " removed a card face down from the draft with " + choice + "."); } @@ -169,14 +169,14 @@ public class LimitedPlayer { // But just log that a reveal "happened" addLog(this.name() + " revealed a card to " + fromPlayer.name() + " via Cogwork Spy."); } else { - addLog(this.name() + " revealed " + bestPick.getName() + " to you with Cogwork Spy."); + addLog(this.name() + " revealed " + bestPick.getDisplayName() + " to you with Cogwork Spy."); } fromPlayer.playerFlags &= ~SpyNextCardDrafted; } if ((playerFlags & SearcherNoteNext) == SearcherNoteNext) { - addLog(name() + " revealed " + bestPick.getName() + " for Aether Searcher."); + addLog(name() + " revealed " + bestPick.getDisplayName() + " for Aether Searcher."); playerFlags &= ~SearcherNoteNext; List note = noted.computeIfAbsent("Aether Searcher", k -> Lists.newArrayList()); note.add(String.valueOf(bestPick.getName())); @@ -184,7 +184,7 @@ public class LimitedPlayer { if ((playerFlags & SmugglerCaptainActive) == SmugglerCaptainActive) { if (revealWithSmuggler(bestPick)) { - addLog(name() + " revealed " + bestPick.getName() + " for Smuggler Captain."); + addLog(name() + " revealed " + bestPick.getDisplayName() + " for Smuggler Captain."); playerFlags &= ~SmugglerCaptainActive; List note = noted.computeIfAbsent("Smuggler Captain", k -> Lists.newArrayList()); note.add(String.valueOf(bestPick.getName())); @@ -297,7 +297,7 @@ public class LimitedPlayer { List note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList()); note.add(String.valueOf(draftedThisRound)); - addLog(name() + " revealed " + bestPick.getName() + " and noted " + draftedThisRound + " cards drafted this round."); + addLog(name() + " revealed " + bestPick.getDisplayName() + " and noted " + draftedThisRound + " cards drafted this round."); } else if (Iterables.contains(draftActions, "As you draft CARDNAME, the player to your right chooses a color, you choose another color, then the player to your left chooses a third color.")) { List chosenColors = new ArrayList<>(); @@ -320,7 +320,7 @@ public class LimitedPlayer { List note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList()); note.add(String.join(",", chosenColors)); - addLog(name() + " revealed " + bestPick.getName() + " and noted " + String.join(",", chosenColors) + " chosen colors."); + addLog(name() + " revealed " + bestPick.getDisplayName() + " and noted " + String.join(",", chosenColors) + " chosen colors."); } else { if (Iterables.contains(draftActions, "You may look at the next card drafted from this booster pack.")) { @@ -328,7 +328,7 @@ public class LimitedPlayer { } else if (fromPlayer != null && Iterables.contains(draftActions, "Note the player who passed CARDNAME to you.")) { List note = noted.computeIfAbsent(bestPick.getName(), k -> Lists.newArrayList()); note.add(String.valueOf(fromPlayer.order)); - addLog(name() + " revealed " + bestPick.getName() + " and noted " + fromPlayer.name() + " passed it."); + addLog(name() + " revealed " + bestPick.getDisplayName() + " and noted " + fromPlayer.name() + " passed it."); } else if (Iterables.contains(draftActions, "Reveal the next card you draft and note its name.")) { playerFlags |= SearcherNoteNext; } else if (Iterables.contains(draftActions, "The next time a player drafts a card from this booster pack, guess that card's name. Then that player reveals the drafted card.")) { @@ -337,12 +337,12 @@ public class LimitedPlayer { addSingleBoosterPack(); } - addLog(name() + " revealed " + bestPick.getName() + " as " + name() + " drafted it."); + addLog(name() + " revealed " + bestPick.getDisplayName() + " as " + name() + " drafted it."); } } if (Iterables.contains(draftActions, "Draft CARDNAME face up.")) { faceUp.add(bestPick); - addLog(name() + " drafted " + bestPick.getName() + " face up."); + addLog(name() + " drafted " + bestPick.getDisplayName() + " face up."); if (!alreadyRevealed) { showRevealedCard(bestPick); } @@ -564,7 +564,7 @@ public class LimitedPlayer { List note = noted.computeIfAbsent(found.getName(), k -> Lists.newArrayList()); revealed.add(bestPick); note.add(bestPick.getName()); - addLog(name() + " revealed " + bestPick.getName() + " and noted its name for Noble Banneret."); + addLog(name() + " revealed " + bestPick.getDisplayName() + " and noted its name for Noble Banneret."); addLog(name() + " has flipped Noble Banneret face down."); alreadyRevealed = true; @@ -613,7 +613,7 @@ public class LimitedPlayer { List note = noted.computeIfAbsent(found.getName(), k -> Lists.newArrayList()); revealed.add(bestPick); note.addAll(bestPick.getRules().getType().getCreatureTypes()); - addLog(name() + " revealed " + bestPick.getName() + " and noted - " + TextUtil.join(bestPick.getRules().getType().getCreatureTypes(), ",") + " for Paliano Vanguard."); + addLog(name() + " revealed " + bestPick.getDisplayName() + " and noted - " + TextUtil.join(bestPick.getRules().getType().getCreatureTypes(), ",") + " for Paliano Vanguard."); addLog(name() + " has flipped Paliano Vanguard face down."); alreadyRevealed = true; @@ -702,12 +702,12 @@ public class LimitedPlayer { LimitedPlayer guesser = pack.getAwaitingGuess().getKey(); PaperCard guess = pack.getAwaitingGuess().getValue(); - addLog(name() + " reveals " + drafted.getName() + " from " + guesser.name() + "'s guess of " + guess.getName() + " with Spire Phantasm."); + addLog(name() + " reveals " + drafted.getDisplayName() + " from " + guesser.name() + "'s guess of " + guess.getDisplayName() + " with Spire Phantasm."); if (guess.equals(drafted)) { - addLog(guesser.name() + " correctly guessed " + guess.getName() + " with Spire Phantasm."); + addLog(guesser.name() + " correctly guessed " + guess.getDisplayName() + " with Spire Phantasm."); guesser.getDraftNotes().computeIfAbsent("Spire Phantasm", k -> Lists.newArrayList()).add(guess.getName()); } else { - addLog(guesser.name() + " incorrectly guessed " + guess.getName() + " with Spire Phantasm."); + addLog(guesser.name() + " incorrectly guessed " + guess.getDisplayName() + " with Spire Phantasm."); } pack.resetAwaitingGuess(); @@ -774,7 +774,7 @@ public class LimitedPlayer { continue; } - addLog(player.name() + " offered " + offer.getName() + " to " + name() + " for " + exchangeCard.getName()); + addLog(player.name() + " offered " + offer.getDisplayName() + " to " + name() + " for " + exchangeCard.getName()); offers.put(offer, player); } @@ -795,11 +795,11 @@ public class LimitedPlayer { return SGuiChoose.oneOrNone("Choose a card to offer for trade: ", deckCards); } - return SGuiChoose.oneOrNone("Choose a card to trade for " + offer.getName() + ": ", deckCards); + return SGuiChoose.oneOrNone("Choose a card to trade for " + offer.getDisplayName() + ": ", deckCards); } protected PaperCard chooseCardToExchange(PaperCard exchangeCard, Map offers) { - return SGuiChoose.oneOrNone("Choose a card to accept trade of " + exchangeCard + ": ", offers.keySet(), null, (card) -> card.getName() + " (" + offers.get(card).getName() + ")"); + return SGuiChoose.oneOrNone("Choose a card to accept trade of " + exchangeCard + ": ", offers.keySet(), null, (card) -> card.getDisplayName() + " (" + offers.get(card).getName() + ")"); } protected void exchangeAcceptedOffer(PaperCard exchangeCard, LimitedPlayer player, PaperCard offer) { diff --git a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestCommander.java b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestCommander.java index 884533ec61e..d6f85bc873b 100644 --- a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestCommander.java +++ b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestCommander.java @@ -68,8 +68,18 @@ public class ConquestCommander implements InventoryItem, IXmlWritable { return card.getName(); } + @Override + public String getDisplayName() { + return card.getDisplayName(); + } + + @Override + public boolean hasFlavorName() { + return card.hasFlavorName(); + } + public String getPlayerName() { - String name = card.getName(); + String name = card.getDisplayName(); int idx = name.indexOf(','); if (idx != -1) { //trim everything after the comma name = name.substring(0, idx); diff --git a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestData.java b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestData.java index 778f989cab3..b0c3bdb3e2c 100644 --- a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestData.java +++ b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestData.java @@ -291,16 +291,16 @@ public final class ConquestData { commandersBeingExiled.add(commander); //cache commander to make it easier to remove later } if (commander.getDeck().getMain().contains(card)) { - commandersUsingCard.append("\n").append(CardTranslation.getTranslatedName(commander.getName())); + commandersUsingCard.append("\n").append(CardTranslation.getTranslatedName(commander.getDisplayName())); } } // Android API StringBuilder isEmpty() is unavailable. https://developer.android.com/reference/java/lang/StringBuilder if (commandersUsingCard.length() != 0) { - SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblCommandersCardCannotBeExiledByCard", CardTranslation.getTranslatedName(card.getName()), commandersUsingCard), title, SOptionPane.INFORMATION_ICON); + SOptionPane.showMessageDialog(Localizer.getInstance().getMessage("lblCommandersCardCannotBeExiledByCard", CardTranslation.getTranslatedName(card.getDisplayName()), commandersUsingCard), title, SOptionPane.INFORMATION_ICON); return false; } - message.append("\n").append(CardTranslation.getTranslatedName(card.getName())); + message.append("\n").append(CardTranslation.getTranslatedName(card.getDisplayName())); } if (SOptionPane.showConfirmDialog(message.toString(), title, Localizer.getInstance().getMessage("lblOK"), Localizer.getInstance().getMessage("lblCancel"))) { diff --git a/forge-gui/src/main/java/forge/gamemodes/quest/QuestTournamentController.java b/forge-gui/src/main/java/forge/gamemodes/quest/QuestTournamentController.java index e98032e23fc..c0b6e05c4e5 100644 --- a/forge-gui/src/main/java/forge/gamemodes/quest/QuestTournamentController.java +++ b/forge-gui/src/main/java/forge/gamemodes/quest/QuestTournamentController.java @@ -143,7 +143,7 @@ public class QuestTournamentController { final PaperCard card = GuiBase.getInterface().chooseCard(localizer.getMessage("lblSelectACard"), localizer.getMessage("lblSelectKeepCard"), prizes.selectRareCards); prizes.addSelectedCard(card); - SOptionPane.showMessageDialog("'" + card.getName() + "' " + localizer.getMessage("lblAddToCollection"), localizer.getMessage("lblCardAdded"), FSkinProp.ICO_QUEST_STAKES); + SOptionPane.showMessageDialog("'" + card.getDisplayName() + "' " + localizer.getMessage("lblAddToCollection"), localizer.getMessage("lblCardAdded"), FSkinProp.ICO_QUEST_STAKES); } if (draft.getPlayerPlacement() == 1) { 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 335f37dcb50..a934eb753bf 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java @@ -153,7 +153,7 @@ public class CardDetailUtil { if (item instanceof PreconDeck) { return ((PreconDeck) item).getDescription(); } - return item.getName(); + return item.getDisplayName(); } public static String formatCardName(final CardView card, final boolean canShow, final boolean forAltState) { @@ -245,13 +245,13 @@ public class CardDetailUtil { PaperCard origPaperCard = null; Card origCard = null; try { - if (!card.getName().isEmpty()) { - origPaperCard = FModel.getMagicDb().getCommonCards().getCard(card.getName()); + if (!card.getOracleName().isEmpty()) { + origPaperCard = FModel.getMagicDb().getCommonCards().getCard(card.getOracleName()); } else { // probably a morph or manifest, try to get its identity from the alternate state - String altName = card.getAlternateState().getName(); + String altName = card.getAlternateState().getOracleName(); if (!altName.isEmpty()) { - origPaperCard = FModel.getMagicDb().getCommonCards().getCard(card.getAlternateState().getName()); + origPaperCard = FModel.getMagicDb().getCommonCards().getCard(altName); } } if (origPaperCard != null) { @@ -259,7 +259,7 @@ public class CardDetailUtil { } origIdent = origCard != null ? getCurrentColors(origCard.isFaceDown() ? CardView.get(origCard).getState(false) : CardView.get(origCard).getCurrentState()) : ""; } catch(Exception ex) { - System.err.println("Unexpected behavior: card " + card.getName() + "[" + card.getId() + "] tripped an exception when trying to process current card colors."); + System.err.println("Unexpected behavior: card " + card.getOracleName() + "[" + card.getId() + "] tripped an exception when trying to process current card colors."); } isChanged = !curColors.equals(origIdent); } @@ -420,7 +420,7 @@ public class CardDetailUtil { if (pl != null) { Map notes = pl.getDraftNotes(); if (notes != null) { - String note = notes.get(card.getName()); + String note = notes.get(card.getOracleName()); if (note != null) { area.append("\n"); area.append("Draft Notes: ").append(note); diff --git a/forge-gui/src/main/java/forge/itemmanager/AdvancedSearch.java b/forge-gui/src/main/java/forge/itemmanager/AdvancedSearch.java index e58bb3be26b..dfbb5673310 100644 --- a/forge-gui/src/main/java/forge/itemmanager/AdvancedSearch.java +++ b/forge-gui/src/main/java/forge/itemmanager/AdvancedSearch.java @@ -60,17 +60,7 @@ public class AdvancedSearch { @Override protected Set getItemValues(PaperCard input) { - Set names = new HashSet<>(); - names.add(input.getName()); - names.add(CardTranslation.getTranslatedName(input.getName())); - CardSplitType cardSplitType = input.getRules().getSplitType(); - if (cardSplitType != CardSplitType.None && cardSplitType != CardSplitType.Split) { - if (input.getRules().getOtherPart() != null) { - names.add(input.getRules().getOtherPart().getName()); - names.add(CardTranslation.getTranslatedName(input.getRules().getOtherPart().getName())); - } - } - return names; + return input.getAllSearchableNames(); } }), CARD_RULES_TEXT("lblRulesText", PaperCard.class, FilterOperator.STRINGS_OPS, new StringEvaluator() { @@ -310,7 +300,7 @@ public class AdvancedSearch { INVITEM_NAME("lblName", InventoryItem.class, FilterOperator.STRING_OPS, new StringEvaluator() { @Override protected String getItemValue(InventoryItem input) { - return input.getName(); + return input.getDisplayName(); } }), INVITEM_RULES_TEXT("lblRulesText", InventoryItem.class, FilterOperator.STRING_OPS, new StringEvaluator() { @@ -573,7 +563,7 @@ public class AdvancedSearch { DECK_NAME("lblName", DeckProxy.class, FilterOperator.STRING_OPS, new StringEvaluator() { @Override protected String getItemValue(DeckProxy input) { - return input.getName(); + return input.getDisplayName(); } }), DECK_FOLDER("lblFolder", DeckProxy.class, FilterOperator.STRING_OPS, new StringEvaluator() { @@ -675,7 +665,7 @@ public class AdvancedSearch { COMMANDER_NAME("lblName", ConquestCommander.class, FilterOperator.STRING_OPS, new StringEvaluator() { @Override protected String getItemValue(ConquestCommander input) { - return input.getName(); + return input.getDisplayName(); } }), COMMANDER_ORIGIN("lblOrigin", ConquestCommander.class, FilterOperator.SINGLE_LIST_OPS, new CustomListEvaluator(ImmutableList.copyOf(FModel.getPlanes())) { @@ -1391,7 +1381,7 @@ public class AdvancedSearch { Integer amount = -1; if (operator == FilterOperator.CONTAINS_X_COPIES_OF_CARD) { //prompt for quantity if needed - amount = SGuiChoose.getInteger(Localizer.getInstance().getMessage("lblHowManyCopiesOfN", CardTranslation.getTranslatedName(card.getName())), 0, 4); + amount = SGuiChoose.getInteger(Localizer.getInstance().getMessage("lblHowManyCopiesOfN", CardTranslation.getTranslatedName(card.getDisplayName())), 0, 4); if (amount == null) { return null; } diff --git a/forge-gui/src/main/java/forge/itemmanager/AdvancedSearchParser.java b/forge-gui/src/main/java/forge/itemmanager/AdvancedSearchParser.java index 73d3c9071b3..874588dccc3 100644 --- a/forge-gui/src/main/java/forge/itemmanager/AdvancedSearchParser.java +++ b/forge-gui/src/main/java/forge/itemmanager/AdvancedSearchParser.java @@ -287,23 +287,6 @@ public abstract class AdvancedSearchParser { } break; - case "name": - switch(opUsed) { - case "!": - predicate = CardRulesPredicates.name(StringOp.EQUALS_IC, valueStr); - break; - - case "!=": - predicate = CardRulesPredicates.name(StringOp.EQUALS_IC, valueStr).negate(); - break; - - case "=": - case ":": - predicate = CardRulesPredicates.name(StringOp.CONTAINS_IC, valueStr); - break; - } - break; - case "is": if (opUsed.equals(":")) { switch(valueStr) { @@ -444,6 +427,25 @@ public abstract class AdvancedSearchParser { } break; + + + case "name": + switch(opUsed) { + case "!": + predicate = PaperCardPredicates.searchableName(StringOp.EQUALS_IC, valueStr); + break; + + case "!=": + predicate = PaperCardPredicates.searchableName(StringOp.EQUALS_IC, valueStr).negate(); + break; + + case "=": + case ":": + predicate = PaperCardPredicates.searchableName(StringOp.CONTAINS_IC, valueStr); + break; + } + break; + case "is": if (opUsed.equals(":")) { switch(valueStr) { diff --git a/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java b/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java index f5c2f080810..cce82a1e39d 100644 --- a/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java +++ b/forge-gui/src/main/java/forge/itemmanager/ColumnDef.java @@ -56,14 +56,14 @@ public enum ColumnDef { if (from.getKey() instanceof PaperCard) { String spire = ((PaperCard) from.getKey()).getMarkedColors() == null ? "" : ((PaperCard) from.getKey()).getMarkedColors().toString(); String sortableName = ((PaperCard)from.getKey()).getSortableName(); - return sortableName == null ? TextUtil.toSortableName(from.getKey().getName() + spire) : sortableName + spire; + return sortableName == null ? TextUtil.toSortableName(from.getKey().getDisplayName() + spire) : sortableName + spire; } - return TextUtil.toSortableName(from.getKey().getName()); + return TextUtil.toSortableName(from.getKey().getDisplayName()); }, from -> { if (from.getKey() instanceof PaperCard) - return from.getKey().toString(); - return from.getKey().getName(); + return CardTranslation.getTranslatedName(from.getKey().getDisplayName()); + return from.getKey().getDisplayName(); }), /** diff --git a/forge-gui/src/main/java/forge/itemmanager/SFilterUtil.java b/forge-gui/src/main/java/forge/itemmanager/SFilterUtil.java index 6e97592adf8..88642e02483 100644 --- a/forge-gui/src/main/java/forge/itemmanager/SFilterUtil.java +++ b/forge-gui/src/main/java/forge/itemmanager/SFilterUtil.java @@ -155,33 +155,28 @@ public class SFilterUtil { } } - Predicate textFilter; - if (advancedCardRulesPredicates.isEmpty()) { - if (BooleanExpression.isExpression(segment)) { - BooleanExpression expression = new BooleanExpression(segment, inName, inType, inText, inCost); - - try { - Predicate filter = expression.evaluate(); - if (filter != null) { - textFilter = filter; - } else { - textFilter = buildRegularTextPredicate(regularTokens, inName, inType, inText, inCost); - } + if (advancedCardRulesPredicates.isEmpty() && BooleanExpression.isExpression(segment)) { + BooleanExpression expression = new BooleanExpression(segment, inName, inType, inText, inCost); + try { + Predicate filter = expression.evaluate(); + if (filter != null) { + if(advancedPaperCardPredicates.isEmpty()) + return PaperCardPredicates.fromRules(filter); + return IterableUtil.and(advancedPaperCardPredicates).and(PaperCardPredicates.fromRules(filter)); } - catch (Exception e) { - e.printStackTrace(); - textFilter = buildRegularTextPredicate(regularTokens, inName, inType, inText, inCost); - } - } else { - textFilter = buildRegularTextPredicate(regularTokens, inName, inType, inText, inCost); } - } else { - Predicate advancedCardRulesPredicate = IterableUtil.and(advancedCardRulesPredicates); - Predicate regularPredicate = buildRegularTextPredicate(regularTokens, inName, inType, inText, inCost); - textFilter = advancedCardRulesPredicate.and(regularPredicate); + catch (Exception e) { + e.printStackTrace(); + } } - return PaperCardPredicates.fromRules(textFilter).and(IterableUtil.and(advancedPaperCardPredicates)); + Predicate cardFilter = buildRegularTextPredicate(regularTokens, inName, inType, inText, inCost); + if(!advancedPaperCardPredicates.isEmpty()) + cardFilter = cardFilter.and(IterableUtil.and(advancedPaperCardPredicates)); + if(!advancedCardRulesPredicates.isEmpty()) + cardFilter = cardFilter.and(PaperCardPredicates.fromRules(IterableUtil.and(advancedCardRulesPredicates))); + + return cardFilter; } private static List getSplitText(String text) { @@ -224,21 +219,28 @@ public class SFilterUtil { return splitText; } - private static Predicate buildRegularTextPredicate(List tokens, boolean inName, boolean inType, boolean inText, boolean inCost) { + private static Predicate buildRegularTextPredicate(List tokens, boolean inName, boolean inType, boolean inText, boolean inCost) { if (tokens.isEmpty()) { return x -> true; } - List> terms = new ArrayList<>(); + List> terms = new ArrayList<>(); for (String s : tokens) { List> subands = new ArrayList<>(); - if (inName) { subands.add(CardRulesPredicates.name(StringOp.CONTAINS_IC, s)); } if (inType) { subands.add(CardRulesPredicates.joinedType(StringOp.CONTAINS_IC, s)); } if (inText) { subands.add(CardRulesPredicates.rules(StringOp.CONTAINS_IC, s)); } if (inCost) { subands.add(CardRulesPredicates.cost(StringOp.CONTAINS_IC, s)); } - terms.add(IterableUtil.or(subands)); + Predicate term; + if (inName && subands.isEmpty()) + term = PaperCardPredicates.searchableName(StringOp.CONTAINS_IC, s); + else if (inName) + term = PaperCardPredicates.searchableName(StringOp.CONTAINS_IC, s).or(PaperCardPredicates.fromRules(IterableUtil.or(subands))); + else + term = PaperCardPredicates.fromRules(IterableUtil.or(subands)); + + terms.add(term); } return IterableUtil.and(terms); } From 1cb873a1daed352093acc30081a3951d3cae4b1f Mon Sep 17 00:00:00 2001 From: JoeMoCode <51722326+JoeMoCode@users.noreply.github.com> Date: Wed, 22 Oct 2025 00:26:46 -0700 Subject: [PATCH 168/230] Fixing issue 8925 - AI has trouble casting spells that create X-toughness creatures. (#8972) --- .../src/main/java/forge/ai/ability/TokenAi.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java index 62b2fd988fc..e1cd252d84a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java @@ -69,17 +69,20 @@ public class TokenAi extends SpellAbilityAi { Card actualToken = spawnToken(ai, sa); - if (actualToken == null || (actualToken.isCreature() && actualToken.getNetToughness() < 1)) { - // planeswalker plus ability or sub-ability is useful - return pwPlus || sa.getSubAbility() != null; - } - String tokenAmount = sa.getParamOrDefault("TokenAmount", "1"); String tokenPower = sa.getParamOrDefault("TokenPower", actualToken.getBasePowerString()); String tokenToughness = sa.getParamOrDefault("TokenToughness", actualToken.getBaseToughnessString()); + // Don't check toughness yet if token has variable P/T based on X + boolean tokenHasX = "X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness); + + if (!tokenHasX && (actualToken == null || (actualToken.isCreature() && actualToken.getNetToughness() < 1))) { + // planeswalker plus ability or sub-ability is useful + return pwPlus || sa.getSubAbility() != null; + } + // X-cost spells - if ("X".equals(tokenAmount) || "X".equals(tokenPower) || "X".equals(tokenToughness)) { + if (tokenHasX) { int x = AbilityUtils.calculateAmount(sa.getHostCard(), tokenAmount, sa); if (source.getSVar("X").equals("Count$Converge")) { x = ComputerUtilMana.getConvergeCount(sa, ai); From 8f12828617acb525b0c4aed07e81406648dbdd0f Mon Sep 17 00:00:00 2001 From: Jetz Date: Wed, 22 Oct 2025 08:31:59 -0400 Subject: [PATCH 169/230] Revert to numbered capturing groups in CardEdition regexes --- .../src/main/java/forge/card/CardEdition.java | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java index e0bd3edefee..9706d607eb4 100644 --- a/forge-core/src/main/java/forge/card/CardEdition.java +++ b/forge-core/src/main/java/forge/card/CardEdition.java @@ -608,17 +608,30 @@ public final class CardEdition implements Comparable { it should also match the Un-set and older alternate art cards like Merseine from FEM. */ + /* Ideally we'd use the named group above, but Android *25* and + earlier doesn't appear to support named groups. + So, untill support for those devices is officially dropped, + we'll have to suffice with numbered groups. + We are looking for: + * cnum - grouping #1 + * rarity - grouping #2 + * name - grouping #3 + * artist name - grouping #4 + * extra parameters - grouping #5 + */ // Collector numbers now should allow hyphens for Planeswalker Championship Promos - "(?:^(?.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(?:(?[SCURML])\\s)?(?[^@$]*)(?: @(?[^$]*))?(?: \\$\\{(?.+)})?$" + "(?:^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(?:([SCURML])\\s)?(?[^@$]*)(?: @([^$]*))?(?: \\$\\{(.+)})?$" + //"(?:^(?.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(?:(?[SCURML])\\s)?(?[^@$]*)(?: @(?[^$]*))?(?: \\$\\{(?.+)})?$" ); public static final Pattern TOKEN_PATTERN = Pattern.compile( /* - * cnum - grouping #2 - * name - grouping #3 - * artist name - grouping #5 + * cnum - grouping #1 + * name - grouping #2 + * artist name - grouping #3 */ - "(?:^(?.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?(?[^@]*)(?: @(?.*))?$" + //"(?:^(?.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?(?[^@]*)(?: @(?.*))?$" + "(?:^(.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?([^@]*)(?: @(.*))?$" ); public static final Pattern EXTRA_PARAMS_PATTERN = Pattern.compile( @@ -679,11 +692,11 @@ public final class CardEdition implements Comparable { continue; } - String collectorNumber = matcher.group("cnum"); - CardRarity r = CardRarity.smartValueOf(matcher.group("rarity")); - String cardName = matcher.group("name"); - String artistName = matcher.group("artist"); - String extraParamText = matcher.group("params"); + String collectorNumber = matcher.group(1); + CardRarity r = CardRarity.smartValueOf(matcher.group(2)); + String cardName = matcher.group(3); + String artistName = matcher.group(4); + String extraParamText = matcher.group(5); Map extraParams = null; if(!StringUtils.isBlank(extraParamText)) { Matcher paramMatcher = EXTRA_PARAMS_PATTERN.matcher(extraParamText); @@ -729,9 +742,9 @@ public final class CardEdition implements Comparable { continue; } - String collectorNumber = matcher.group("cnum"); - String cardName = matcher.group("name"); - String artistName = matcher.group("artist"); + String collectorNumber = matcher.group(1); + String cardName = matcher.group(2); + String artistName = matcher.group(3); // rarity isn't used for this anyway EditionEntry tis = new EditionEntry(cardName, collectorNumber, CardRarity.Token, artistName, null); tokenMap.put(cardName, tis); @@ -746,9 +759,9 @@ public final class CardEdition implements Comparable { if (!matcher.matches()) { continue; } - String collectorNumber = matcher.group("cnum"); - String cardName = matcher.group("name"); - String artistName = matcher.group("artist"); + String collectorNumber = matcher.group(1); + String cardName = matcher.group(2); + String artistName = matcher.group(3); EditionEntry tis = new EditionEntry(cardName, collectorNumber, CardRarity.Unknown, artistName, null); otherMap.put(cardName, tis); } From e67365e0d6e9d5acbd2aee0f75d2b0c524ecf9b7 Mon Sep 17 00:00:00 2001 From: Jetz Date: Wed, 22 Oct 2025 08:57:25 -0400 Subject: [PATCH 170/230] Removed one last named group. Change name group to require at least one character --- forge-core/src/main/java/forge/card/CardEdition.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java index 9706d607eb4..3e114ba8a37 100644 --- a/forge-core/src/main/java/forge/card/CardEdition.java +++ b/forge-core/src/main/java/forge/card/CardEdition.java @@ -620,7 +620,7 @@ public final class CardEdition implements Comparable { * extra parameters - grouping #5 */ // Collector numbers now should allow hyphens for Planeswalker Championship Promos - "(?:^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(?:([SCURML])\\s)?(?[^@$]*)(?: @([^$]*))?(?: \\$\\{(.+)})?$" + "(?:^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(?:([SCURML])\\s)?([^@$]+)(?: @([^$]*))?(?: \\$\\{(.+)})?$" //"(?:^(?.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(?:(?[SCURML])\\s)?(?[^@$]*)(?: @(?[^$]*))?(?: \\$\\{(?.+)})?$" ); @@ -631,7 +631,7 @@ public final class CardEdition implements Comparable { * artist name - grouping #3 */ //"(?:^(?.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?(?[^@]*)(?: @(?.*))?$" - "(?:^(.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?([^@]*)(?: @(.*))?$" + "(?:^(.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?([^@]+)(?: @(.*))?$" ); public static final Pattern EXTRA_PARAMS_PATTERN = Pattern.compile( From 21a293a458fd1cb63b8ce296c74e0c28188c8624 Mon Sep 17 00:00:00 2001 From: Jetz Date: Wed, 22 Oct 2025 09:21:48 -0400 Subject: [PATCH 171/230] Revert non-capturing groups too --- .../src/main/java/forge/card/CardEdition.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java index 3e114ba8a37..39e2e13ec07 100644 --- a/forge-core/src/main/java/forge/card/CardEdition.java +++ b/forge-core/src/main/java/forge/card/CardEdition.java @@ -613,25 +613,25 @@ public final class CardEdition implements Comparable { So, untill support for those devices is officially dropped, we'll have to suffice with numbered groups. We are looking for: - * cnum - grouping #1 - * rarity - grouping #2 - * name - grouping #3 - * artist name - grouping #4 - * extra parameters - grouping #5 + * cnum - grouping #2 + * rarity - grouping #4 + * name - grouping #5 + * artist name - grouping #7 + * extra parameters - grouping #9 */ // Collector numbers now should allow hyphens for Planeswalker Championship Promos - "(?:^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(?:([SCURML])\\s)?([^@$]+)(?: @([^$]*))?(?: \\$\\{(.+)})?$" + "(^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(([SCURML])\\s)?([^@$]+)( @([^$]*))?( \\$\\{(.+)})?$" //"(?:^(?.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(?:(?[SCURML])\\s)?(?[^@$]*)(?: @(?[^$]*))?(?: \\$\\{(?.+)})?$" ); public static final Pattern TOKEN_PATTERN = Pattern.compile( /* - * cnum - grouping #1 - * name - grouping #2 - * artist name - grouping #3 + * cnum - grouping #2 + * name - grouping #3 + * artist name - grouping #5 */ //"(?:^(?.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?(?[^@]*)(?: @(?.*))?$" - "(?:^(.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?([^@]+)(?: @(.*))?$" + "(^(.?[0-9A-Z-]+\\S?[A-Z☇]*)\\s)?([^@]+)( @(.*))?$" ); public static final Pattern EXTRA_PARAMS_PATTERN = Pattern.compile( @@ -692,11 +692,11 @@ public final class CardEdition implements Comparable { continue; } - String collectorNumber = matcher.group(1); - CardRarity r = CardRarity.smartValueOf(matcher.group(2)); - String cardName = matcher.group(3); - String artistName = matcher.group(4); - String extraParamText = matcher.group(5); + String collectorNumber = matcher.group(2); + CardRarity r = CardRarity.smartValueOf(matcher.group(4)); + String cardName = matcher.group(5); + String artistName = matcher.group(7); + String extraParamText = matcher.group(9); Map extraParams = null; if(!StringUtils.isBlank(extraParamText)) { Matcher paramMatcher = EXTRA_PARAMS_PATTERN.matcher(extraParamText); @@ -742,9 +742,9 @@ public final class CardEdition implements Comparable { continue; } - String collectorNumber = matcher.group(1); - String cardName = matcher.group(2); - String artistName = matcher.group(3); + String collectorNumber = matcher.group(2); + String cardName = matcher.group(3); + String artistName = matcher.group(5); // rarity isn't used for this anyway EditionEntry tis = new EditionEntry(cardName, collectorNumber, CardRarity.Token, artistName, null); tokenMap.put(cardName, tis); @@ -759,9 +759,9 @@ public final class CardEdition implements Comparable { if (!matcher.matches()) { continue; } - String collectorNumber = matcher.group(1); - String cardName = matcher.group(2); - String artistName = matcher.group(3); + String collectorNumber = matcher.group(2); + String cardName = matcher.group(3); + String artistName = matcher.group(5); EditionEntry tis = new EditionEntry(cardName, collectorNumber, CardRarity.Unknown, artistName, null); otherMap.put(cardName, tis); } From 7ea1c51990b9a166596ef7bce36378871b2fe080 Mon Sep 17 00:00:00 2001 From: Jetz72 Date: Wed, 22 Oct 2025 11:07:06 -0400 Subject: [PATCH 172/230] Escape closed bracket character (#8978) I *think* this might be what's breaking Android? Digging up docs for old versions is a pain. --- forge-core/src/main/java/forge/card/CardEdition.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-core/src/main/java/forge/card/CardEdition.java b/forge-core/src/main/java/forge/card/CardEdition.java index 39e2e13ec07..9deb865eab1 100644 --- a/forge-core/src/main/java/forge/card/CardEdition.java +++ b/forge-core/src/main/java/forge/card/CardEdition.java @@ -620,7 +620,7 @@ public final class CardEdition implements Comparable { * extra parameters - grouping #9 */ // Collector numbers now should allow hyphens for Planeswalker Championship Promos - "(^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(([SCURML])\\s)?([^@$]+)( @([^$]*))?( \\$\\{(.+)})?$" + "(^(.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(([SCURML])\\s)?([^@$]+)( @([^$]*))?( \\$\\{(.+)\\})?$" //"(?:^(?.?[0-9A-Z-]+\\S*[A-Z]*)\\s)?(?:(?[SCURML])\\s)?(?[^@$]*)(?: @(?[^$]*))?(?: \\$\\{(?.+)})?$" ); From 8c2c32a3d898d38c1469e07c8a05c46ac558c97d Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Wed, 22 Oct 2025 19:27:33 +0200 Subject: [PATCH 173/230] CardRules: fix toUnmodifiableList for Android --- forge-core/src/main/java/forge/card/CardRules.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/forge-core/src/main/java/forge/card/CardRules.java b/forge-core/src/main/java/forge/card/CardRules.java index e60061c4fdb..d0268c6a3c5 100644 --- a/forge-core/src/main/java/forge/card/CardRules.java +++ b/forge-core/src/main/java/forge/card/CardRules.java @@ -71,7 +71,8 @@ public final class CardRules implements ICardCharacteristics { specializedParts.put(CardStateName.SpecializeG, faces[6]); } - allFaces = Arrays.stream(faces).filter(Objects::nonNull).collect(Collectors.toUnmodifiableList()); + // Android doesn't support toUnmodifiableList + allFaces = Arrays.stream(faces).filter(Objects::nonNull).collect(Collectors.toList()); aiHints = cah; meldWith = ""; From 43525ce6578555ec0abf7fe9512eddd788d9fb7f Mon Sep 17 00:00:00 2001 From: Cees Timmerman Date: Wed, 22 Oct 2025 19:53:58 +0200 Subject: [PATCH 174/230] Fix AI helps opponent create an army (#8901) (#8902) --- .../src/main/java/forge/ai/AiAttackController.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 6f94142741b..b49c5d11ee4 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -974,6 +974,16 @@ public class AiAttackController { return aiAggression; } + // Only do decisive attacks against token-generating players + if (!bAssault && defender instanceof Player) { + Player opponent = (Player)defender; + if (CardLists.count(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Rabble Rousing")) + - CardLists.count(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Darien, King of Kjeldor")) + - CardLists.count(opponent.getCardsIn(ZoneType.Battlefield), CardPredicates.nameEquals("Kazuul, Tyrant of the Cliffs")) < 0) { + return aiAggression; + } + } + if (bAssault && defender == defendingOpponent) { // in case we are forced to attack someone else if (LOG_AI_ATTACKS) System.out.println("Assault"); From 4cb581152e4c2ebb2f5921f4fd2adaa21cd9b155 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Thu, 23 Oct 2025 11:27:43 +0200 Subject: [PATCH 175/230] Update fearsome_whelp.txt Closes #8982 --- forge-gui/res/cardsfolder/f/fearsome_whelp.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/f/fearsome_whelp.txt b/forge-gui/res/cardsfolder/f/fearsome_whelp.txt index 776cd20ccea..94b246b92e9 100644 --- a/forge-gui/res/cardsfolder/f/fearsome_whelp.txt +++ b/forge-gui/res/cardsfolder/f/fearsome_whelp.txt @@ -4,7 +4,7 @@ Types:Creature Dragon PT:1/1 K:Flying K:Haste -T:Mode$ Phase | Phase$ EndStep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigAnimate | TriggerDescription$ At the beginning of your end step, each Dragon card in your hand perpetually gains "This spell costs {1} less to cast." +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigAnimate | TriggerDescription$ At the beginning of your end step, each Dragon card in your hand perpetually gains "This spell costs {1} less to cast." SVar:TrigAnimate:DB$ AnimateAll | ValidCards$ Dragon.YouOwn | Zone$ Hand | staticAbilities$ DragonReduceCost | Duration$ Perpetual SVar:DragonReduceCost:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ 1 | EffectZone$ All | Description$ This spell costs {1} less to cast. DeckHints:Type$Dragon From 4063ac55e68c2054d493431773e9328fdf36cd7d Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Thu, 23 Oct 2025 23:04:22 +0200 Subject: [PATCH 176/230] AbilityUtils: refactor Party types (#8967) --- .../src/main/java/forge/card/CardType.java | 15 ++- .../main/java/forge/card/CardTypeView.java | 1 + .../src/main/java/forge/game/ForgeScript.java | 2 + .../java/forge/game/ability/AbilityUtils.java | 94 ++++++++++--------- .../ability/effects/ChooseCardEffect.java | 3 +- .../java/forge/game/card/CardProperty.java | 12 --- 6 files changed, 68 insertions(+), 59 deletions(-) diff --git a/forge-core/src/main/java/forge/card/CardType.java b/forge-core/src/main/java/forge/card/CardType.java index b8998629be2..5a92fe00436 100644 --- a/forge-core/src/main/java/forge/card/CardType.java +++ b/forge-core/src/main/java/forge/card/CardType.java @@ -547,7 +547,14 @@ public final class CardType implements Comparable, CardTypeView { if (!isCreature() && !isKindred()) { return false; } - return !Collections.disjoint(getCreatureTypes(), Constant.OUTLAW_TYPES); + return Constant.OUTLAW_TYPES.stream().anyMatch(s -> hasCreatureType(s)); + } + @Override + public boolean isParty() { + if (!isCreature() && !isKindred()) { + return false; + } + return Constant.PARTY_TYPES.stream().anyMatch(s -> hasCreatureType(s)); } @Override @@ -916,6 +923,12 @@ public final class CardType implements Comparable, CardTypeView { "Pirate", "Rogue", "Warlock"); + + public static final Set PARTY_TYPES = Sets.newHashSet( + "Cleric", + "Rogue", + "Warrior", + "Wizard"); } public static class Predicates { public static Predicate IS_LAND_TYPE = CardType::isALandType; diff --git a/forge-core/src/main/java/forge/card/CardTypeView.java b/forge-core/src/main/java/forge/card/CardTypeView.java index 4667c48704b..048e9d4cb74 100644 --- a/forge-core/src/main/java/forge/card/CardTypeView.java +++ b/forge-core/src/main/java/forge/card/CardTypeView.java @@ -64,6 +64,7 @@ public interface CardTypeView extends Iterable, Serializable { boolean isSaga(); boolean isHistoric(); boolean isOutlaw(); + boolean isParty(); CardTypeView getTypeWithChanges(Iterable changedCardTypes); } diff --git a/forge-game/src/main/java/forge/game/ForgeScript.java b/forge-game/src/main/java/forge/game/ForgeScript.java index cf7a461f92b..4215d0bc1fd 100644 --- a/forge-game/src/main/java/forge/game/ForgeScript.java +++ b/forge-game/src/main/java/forge/game/ForgeScript.java @@ -93,6 +93,8 @@ public class ForgeScript { } } else if (property.equals("Outlaw")) { return type.isOutlaw(); + } else if (property.equals("Party")) { + return type.isParty(); } else if (property.startsWith("non")) { // ... Other Card types return !type.hasStringType(property.substring(3)); 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 2e07716ef58..da85d2910c6 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -4,6 +4,7 @@ import com.google.common.collect.*; import com.google.common.math.IntMath; import forge.card.CardStateName; import forge.card.CardType; +import forge.card.CardTypeView; import forge.card.ColorSet; import forge.card.MagicColor; import forge.card.mana.ManaAtom; @@ -42,6 +43,7 @@ import java.util.*; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; public class AbilityUtils { private final static ImmutableList cmpList = ImmutableList.of("LT", "LE", "EQ", "GE", "GT", "NE"); @@ -2601,61 +2603,65 @@ public class AbilityUtils { } if (sq[0].contains("Party")) { - CardCollection adventurers = CardLists.getValidCards(player.getCardsIn(ZoneType.Battlefield), - "Creature.Cleric,Creature.Rogue,Creature.Warrior,Creature.Wizard", player, c, ctb); - - Set partyTypes = Sets.newHashSet("Cleric", "Rogue", "Warrior", "Wizard"); - int partySize = 0; - - HashMap chosenParty = new HashMap<>(); - List wildcard = Lists.newArrayList(); - HashMap> multityped = new HashMap<>(); + Set chosenParty = Sets.newHashSet(); + int wildcard = 0; + ListMultimap multityped = MultimapBuilder.hashKeys().arrayListValues().build(); + List chosenMulti = Lists.newArrayList(); // Figure out how to count each class separately. - for (Card card : adventurers) { - // cards with all creature types will just return full list - Set creatureTypes = card.getType().getCreatureTypes(); - creatureTypes.retainAll(partyTypes); - - if (creatureTypes.size() == 4) { - wildcard.add(card); - - if (wildcard.size() >= 4) { - break; - } + for (Card card : player.getCardsIn(ZoneType.Battlefield)) { + if (!card.isCreature()) { continue; - } else if (creatureTypes.size() == 1) { - String type = (String)(creatureTypes.toArray()[0]); + } + CardTypeView type = card.getType(); + Set creatureTypes; - if (!chosenParty.containsKey(type)) { - chosenParty.put(type, card); + // extra logic for "all creature types" cards + if (type.hasAllCreatureTypes()) { + // one of the party types could be excluded, so check each of them separate + creatureTypes = CardType.Constant.PARTY_TYPES.stream().filter(p -> type.hasCreatureType(p)).collect(Collectors.toSet()); + } else { // shortcut for others + creatureTypes = type.getCreatureTypes(); + creatureTypes.retainAll(CardType.Constant.PARTY_TYPES); + } + + switch (creatureTypes.size()) { + case 0: + continue; + case 4: + wildcard++; + break; + case 1: + chosenParty.addAll(creatureTypes); + break; + default: + for (String t : creatureTypes) { + multityped.put(t, card); } - } else { - multityped.put(card, creatureTypes); + } + + // found enough + if (chosenParty.size() + wildcard >= 4) { + break; } } - partySize = Math.min(chosenParty.size() + wildcard.size(), 4); + if (chosenParty.size() + wildcard < 4) { + multityped.keySet().removeAll(chosenParty); - if (partySize < 4) { - partyTypes.removeAll(chosenParty.keySet()); - - // Here I'm left with just the party types that I haven't selected. - for (Card multi : multityped.keySet()) { - Set types = multityped.get(multi); - types.retainAll(partyTypes); - - for (String type : types) { - chosenParty.put(type, multi); - partyTypes.remove(type); - break; - } - } + // sort by amount of members + Multimaps.asMap(multityped).entrySet().stream() + .sorted(Map.Entry.>comparingByValue(Comparator.>comparingInt(Collection::size))) + .forEach(e -> { + e.getValue().removeAll(chosenMulti); + if (e.getValue().size() > 0) { + chosenParty.add(e.getKey()); + chosenMulti.add(e.getValue().get(0)); + } + }); } - partySize = Math.min(chosenParty.size() + wildcard.size(), 4); - - return doXMath(partySize, expr, c, ctb); + return doXMath(Math.min(chosenParty.size() + wildcard, 4), expr, c, ctb); } // TODO make AI part to understand Sunburst better so this isn't needed diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java index 19ee339ae2b..6c66183eb8c 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseCardEffect.java @@ -131,8 +131,7 @@ public class ChooseCardEffect extends SpellAbilityEffect { } } else if (sa.hasParam("ChooseEach")) { final String s = sa.getParam("ChooseEach"); - final String[] types = s.equals("Party") ? new String[]{"Cleric","Rogue","Warrior","Wizard"} - : s.split(" & "); + final Collection types = s.equals("Party") ? CardType.Constant.PARTY_TYPES : Arrays.asList(s.split(" & ")); for (final String type : types) { CardCollection valids = CardLists.filter(pChoices, CardPredicates.isType(type)); if (!valids.isEmpty()) { 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 e27a4f8fed7..0be78b8237f 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -2,7 +2,6 @@ package forge.game.card; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; -import com.google.common.collect.Sets; import forge.StaticData; import forge.card.CardDb; import forge.card.ColorSet; @@ -720,17 +719,6 @@ public class CardProperty { return false; } } - } else if (property.equals("Party")) { - boolean isParty = false; - Set partyTypes = Sets.newHashSet("Cleric", "Rogue", "Warrior", "Wizard"); - Set cTypes = card.getType().getCreatureTypes(); - for (String t : partyTypes) { - if (cTypes.contains(t)) { - isParty = true; - break; - } - } - return isParty; } else if (property.startsWith("sharesCreatureTypeWith")) { if (property.equals("sharesCreatureTypeWith")) { if (!card.sharesCreatureTypeWith(source)) { From 0eb3062c64d5336cc83b1569cf9ba5eb9a1a9cc0 Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Thu, 23 Oct 2025 23:02:18 +0200 Subject: [PATCH 177/230] CardCloneStates: EmptyRoom should be affected by CloneStates --- .../src/main/java/forge/game/card/Card.java | 5 +- .../java/forge/game/card/CardCloneStates.java | 4 + .../java/forge/game/card/CardFactory.java | 89 ++++++------------- .../main/java/forge/game/card/CardState.java | 14 ++- 4 files changed, 44 insertions(+), 68 deletions(-) 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 e7f2d603de7..9f8bd28db45 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -499,9 +499,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr if (state == CardStateName.FaceDown) { return getFaceDownState(); } - if (state == CardStateName.EmptyRoom) { - return getEmptyRoomState(); - } CardCloneStates clStates = getLastClonedState(); if (clStates == null) { return getOriginalState(state); @@ -560,7 +557,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr boolean needsTransformAnimation = transform || rollback; // faceDown has higher priority over clone states // while text change states doesn't apply while the card is faceDown - if (state != CardStateName.FaceDown && state != CardStateName.EmptyRoom) { + if (state != CardStateName.FaceDown) { CardCloneStates cloneStates = getLastClonedState(); if (cloneStates != null) { if (!cloneStates.containsKey(state)) { diff --git a/forge-game/src/main/java/forge/game/card/CardCloneStates.java b/forge-game/src/main/java/forge/game/card/CardCloneStates.java index 4cb5b0c51c9..152f8ff5235 100644 --- a/forge-game/src/main/java/forge/game/card/CardCloneStates.java +++ b/forge-game/src/main/java/forge/game/card/CardCloneStates.java @@ -49,6 +49,10 @@ public class CardCloneStates extends ForwardingMap { return result; } + public void add(CardState state) { + put(state.getStateName(), state); + } + public CardCloneStates copy(final Card host, final boolean lki) { CardCloneStates result = new CardCloneStates(origin, ctb); for (Map.Entry e : dataMap.entrySet()) { diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index efda56fa831..7ae219899ce 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -477,6 +477,7 @@ public class CardFactory { final CardCloneStates result = new CardCloneStates(in, cause); final String newName = cause.getParam("NewName"); + ManaCost manaCost = null; ColorSet colors = null; if (cause.hasParam("AddTypes")) { @@ -508,11 +509,10 @@ public class CardFactory { colors = ColorSet.fromNames(cause.getParam("SetColor").split(",")); } - if (cause.hasParam("SetColorByManaCost")) { - if (cause.hasParam("SetManaCost")) { - colors = ColorSet.fromManaCost(new ManaCost(new ManaCostParser(cause.getParam("SetManaCost")))); - } else { - colors = ColorSet.fromManaCost(host.getManaCost()); + if (cause.hasParam("SetManaCost")) { + manaCost = new ManaCost(new ManaCostParser(cause.getParam("SetManaCost"))); + if (cause.hasParam("SetColorByManaCost")) { + colors = ColorSet.fromManaCost(manaCost); } } @@ -521,57 +521,34 @@ public class CardFactory { if (in.isFaceDown()) { // if something is cloning a facedown card, it only clones the // facedown state into original - final CardState ret = new CardState(out, CardStateName.Original); - ret.copyFrom(in.getFaceDownState(), false, cause); - result.put(CardStateName.Original, ret); + result.add(in.getFaceDownState().copy(out, CardStateName.Original, cause)); } else if (in.isFlipCard()) { // if something is cloning a flip card, copy both original and // flipped state - final CardState ret1 = new CardState(out, CardStateName.Original); - ret1.copyFrom(in.getState(CardStateName.Original), false, cause); - result.put(CardStateName.Original, ret1); - - final CardState ret2 = new CardState(out, CardStateName.Flipped); - ret2.copyFrom(in.getState(CardStateName.Flipped), false, cause); - result.put(CardStateName.Flipped, ret2); + result.add(in.getState(CardStateName.Original).copy(out, cause)); + result.add(in.getState(CardStateName.Flipped).copy(out, cause)); } else if (in.hasState(CardStateName.Secondary)) { - final CardState ret1 = new CardState(out, CardStateName.Original); - ret1.copyFrom(in.getState(CardStateName.Original), false, cause); - result.put(CardStateName.Original, ret1); - - final CardState ret2 = new CardState(out, CardStateName.Secondary); - ret2.copyFrom(in.getState(CardStateName.Secondary), false, cause); - result.put(CardStateName.Secondary, ret2); + result.add(in.getState(CardStateName.Original).copy(out, cause)); + result.add(in.getState(CardStateName.Secondary).copy(out, cause)); } else if (in.isTransformable() && cause instanceof SpellAbility sa && ( ApiType.CopyPermanent.equals(sa.getApi()) || ApiType.CopySpellAbility.equals(sa.getApi()) || ApiType.ReplaceToken.equals(sa.getApi()))) { // CopyPermanent can copy token - final CardState ret1 = new CardState(out, CardStateName.Original); - ret1.copyFrom(in.getState(CardStateName.Original), false, cause); - result.put(CardStateName.Original, ret1); - - final CardState ret2 = new CardState(out, CardStateName.Backside); - ret2.copyFrom(in.getState(CardStateName.Backside), false, cause); - result.put(CardStateName.Backside, ret2); + result.add(in.getState(CardStateName.Original).copy(out, cause)); + result.add(in.getState(CardStateName.Backside).copy(out, cause)); } else if (in.isSplitCard()) { // for split cards, copy all three states - final CardState ret1 = new CardState(out, CardStateName.Original); - ret1.copyFrom(in.getState(CardStateName.Original), false, cause); - result.put(CardStateName.Original, ret1); - final CardState ret2 = new CardState(out, CardStateName.LeftSplit); - ret2.copyFrom(in.getState(CardStateName.LeftSplit), false, cause); - result.put(CardStateName.LeftSplit, ret2); - - final CardState ret3 = new CardState(out, CardStateName.RightSplit); - ret3.copyFrom(in.getState(CardStateName.RightSplit), false, cause); - result.put(CardStateName.RightSplit, ret3); + result.add(in.getState(CardStateName.Original).copy(out, cause)); + result.add(in.getState(CardStateName.LeftSplit).copy(out, cause)); + result.add(in.getState(CardStateName.RightSplit).copy(out, cause)); + if (in.isPermanent()) { + result.add(in.getState(CardStateName.EmptyRoom).copy(out, cause)); + } } else { // in all other cases just copy the current state to original - final CardState ret = new CardState(out, CardStateName.Original); - ret.copyFrom(in.getState(in.getCurrentStateName()), false, cause); - result.put(CardStateName.Original, ret); + result.add(in.getState(in.getCurrentStateName()).copy(out, CardStateName.Original, cause)); } // update all states, both for flip cards @@ -614,21 +591,9 @@ public class CardFactory { state.setCreatureTypes(creatureTypes); } - List finalizedKWs = KWifNew ? Lists.newArrayList() : keywords; + List finalizedKWs = keywords; if (KWifNew) { - for (String k : keywords) { - Keyword toAdd = Keyword.getInstance(k).getKeyword(); - boolean match = false; - for (KeywordInterface kw : state.getIntrinsicKeywords()) { - if (kw.getKeyword().equals(toAdd)) { - match = true; - break; - } - } - if (!match) { - finalizedKWs.add(k); - } - } + finalizedKWs = keywords.stream().filter(k -> !state.hasIntrinsicKeyword(Keyword.getInstance(k).getKeyword())).collect(Collectors.toList()); } state.addIntrinsicKeywords(finalizedKWs); for (String kw : removeKeywords) { @@ -656,7 +621,7 @@ public class CardFactory { } if (cause.hasParam("SetManaCost")) { - state.setManaCost(new ManaCost(new ManaCostParser(cause.getParam("SetManaCost")))); + state.setManaCost(manaCost); } // SVars to add to clone @@ -793,11 +758,11 @@ public class CardFactory { public static CardCloneStates getMutatedCloneStates(final Card card, final CardTraitBase sa) { final Card top = card.getTopMergedCard(); final CardStateName state = top.getCurrentStateName(); - final CardState ret = new CardState(card, state); + CardState ret; if (top.isCloned()) { - ret.copyFrom(top.getState(state), false, sa); + ret = top.getState(state).copy(card, sa); } else { - ret.copyFrom(top.getOriginalState(state), false, sa); + ret = top.getOriginalState(state).copy(card, sa); } boolean first = true; @@ -814,9 +779,7 @@ public class CardFactory { // For face down, flipped, transformed, melded or MDFC card, also copy the original state to avoid crash if (state != CardStateName.Original) { - final CardState ret1 = new CardState(card, CardStateName.Original); - ret1.copyFrom(top.getState(CardStateName.Original), false, sa); - result.put(CardStateName.Original, ret1); + result.add(top.getState(CardStateName.Original).copy(card, sa)); } return result; diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index 54624699f6b..8080402c16d 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -309,6 +309,9 @@ public class CardState implements GameObject, IHasSVars, ITranslatable { public final boolean hasIntrinsicKeyword(String k) { return intrinsicKeywords.contains(k); } + public final boolean hasIntrinsicKeyword(Keyword k) { + return intrinsicKeywords.contains(k); + } public final void setIntrinsicKeywords(final Iterable intrinsicKeyword0, final boolean lki) { intrinsicKeywords.clear(); for (KeywordInterface k : intrinsicKeyword0) { @@ -841,8 +844,17 @@ public class CardState implements GameObject, IHasSVars, ITranslatable { } public CardState copy(final Card host, CardStateName name, final boolean lki) { + return copy(host, name, lki, null); + } + public CardState copy(final Card host, final CardTraitBase ctb) { + return copy(host, this.getStateName(), false, ctb); + } + public CardState copy(final Card host, CardStateName name, final CardTraitBase ctb) { + return copy(host, name, false, ctb); + } + public CardState copy(final Card host, CardStateName name, final boolean lki, final CardTraitBase ctb) { CardState result = new CardState(host, name); - result.copyFrom(this, lki); + result.copyFrom(this, lki, ctb); return result; } From ed3bc665e204388803a5909f67ceb9243ba74fa2 Mon Sep 17 00:00:00 2001 From: Jetz72 Date: Fri, 24 Oct 2025 11:08:08 -0400 Subject: [PATCH 178/230] Update grolnoks_skin.txt Fix a couple card names in the spellbook. Closes #8989 --- forge-gui/res/adventure/common/custom_cards/grolnoks_skin.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/adventure/common/custom_cards/grolnoks_skin.txt b/forge-gui/res/adventure/common/custom_cards/grolnoks_skin.txt index c790bf2e5ba..1717fb514d6 100644 --- a/forge-gui/res/adventure/common/custom_cards/grolnoks_skin.txt +++ b/forge-gui/res/adventure/common/custom_cards/grolnoks_skin.txt @@ -4,6 +4,6 @@ Colors:black,green,blue Types:Enchantment T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | Execute$ TrigMill | TriggerDescription$ At the beginning of your upkeep mill two cards SVar:TrigMill:DB$ Mill | Defined$ You | NumCards$ 2 -A:AB$ Draft | Cost$ G U PayShards<2> | SubAbility$ DBExileSelf | ActivationZone$ Command | Spellbook$ Mystic Snake,Sporefrog,Froghemoth,Frilled Mystic,Whiptongue Frog,Excavating Anurid,Chub Toad,Satyr Wayfinder,Lotus Cobra,Endurance,Turn to Frog,Polymorphist's Jest,Mulch,Crawling Infestation,Arcane Adaption,Grolnok; the Omnivore | Zone$ Hand | SubAbility$ Eject | SpellDescription$ Draft a card from Grolnok's spellbook. Exile Grolnok's Skin. +A:AB$ Draft | Cost$ G U PayShards<2> | SubAbility$ DBExileSelf | ActivationZone$ Command | Spellbook$ Mystic Snake,Spore Frog,Froghemoth,Frilled Mystic,Whiptongue Frog,Excavating Anurid,Chub Toad,Satyr Wayfinder,Lotus Cobra,Endurance,Turn to Frog,Polymorphist's Jest,Mulch,Crawling Infestation,Arcane Adaptation,Grolnok; the Omnivore | Zone$ Hand | SubAbility$ Eject | SpellDescription$ Draft a card from Grolnok's spellbook. Exile Grolnok's Skin. SVar:DBExileSelf:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile Oracle:At the beginning of your upkeep mill two card\n{G}{U},{M}{M} Draft a card from Grolnok's spellbook. Exile Grolnok's Skin. From 42a14cfad0b0cb61c4a1e81572b85a7d1ce9e7cc Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:52:29 +0200 Subject: [PATCH 179/230] Sozin's Comet (TLA) (#8993) --- forge-gui/res/cardsfolder/upcoming/sozins_comet.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/sozins_comet.txt diff --git a/forge-gui/res/cardsfolder/upcoming/sozins_comet.txt b/forge-gui/res/cardsfolder/upcoming/sozins_comet.txt new file mode 100644 index 00000000000..866d28e01d3 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sozins_comet.txt @@ -0,0 +1,6 @@ +Name:Sozin's Comet +ManaCost:3 R R +Types:Sorcery +A:SP$ PumpAll | ValidCards$ Creature.YouCtrl | KW$ Firebending:5 | SpellDescription$ Each creature you control gains firebending 5 until end of turn. (Whenever it attacks, add {R}{R}{R}{R}{R}. This mana lasts until end of combat.) +K:Foretell:2 R +Oracle:Each creature you control gains firebending 5 until end of turn. (Whenever it attacks, add {R}{R}{R}{R}{R}. This mana lasts until end of combat.)\nForetell {2}{R} (During your turn, you may pay {2} and exile this card from your hand face down, Cast it on a later turn for its foretell cost.) From f291e9ea591e5cfd940286623a28bb347d27c2aa Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sat, 25 Oct 2025 15:26:12 +0200 Subject: [PATCH 180/230] Fix Multiple Replacement Effects not working correctly --- .../src/main/java/forge/game/replacement/ReplacementHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java index ac7912a1f44..2733b285101 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -234,6 +234,7 @@ public class ReplacementHandler { // if its updated, try to call event again if (res == ReplacementResult.Updated) { Map params = AbilityKey.newMap(runParams); + params.remove(AbilityKey.ReplacementResult); if (params.containsKey(AbilityKey.EffectOnly)) { params.put(AbilityKey.EffectOnly, true); From 76b9da7e226cb213fb74e74cb9f4bc2ac4742d8a Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 26 Oct 2025 17:17:04 +0100 Subject: [PATCH 181/230] Update PhaseHandler.java Add missing break --- forge-game/src/main/java/forge/game/phase/PhaseHandler.java | 1 + 1 file changed, 1 insertion(+) 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 035ee178385..8678a8a635b 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -490,6 +490,7 @@ public class PhaseHandler implements java.io.Serializable { case UNTAP: game.getUntap().executeUntilEndOfPhase(playerTurn); + break; case COMBAT_END: GameEventCombatEnded eventEndCombat = null; From 136940a16c123108641abd22c7b9b8c38cee6a06 Mon Sep 17 00:00:00 2001 From: Eradev Date: Sun, 26 Oct 2025 17:48:54 -0400 Subject: [PATCH 182/230] Tokens cleanup (Sets T-Z) (#8159) * Update TPR * Update TMP * Update 10E * Update BRC * Update BRO * Update LTR * Update ELD * Update TSB * Update TSP * Update TOR * Update PZ2 * Add r_1_1_soldier * Update UNF * Update UGL * Update UNH * Update 2ED * Update UND * Update UDS * Update ULG * Update USG * Update VIS * Update VMA * Update 40K * Update WAR * Update WTH * Update WWK * Update PZEN * Update ZEN * Update ZNR * Update ZNC * Update WOE WOC * Add mention needing to flip tokens * Rename w_2_2_cat flying.txt to w_2_2_cat_flying.txt fix name * Fix squad token names --------- Co-authored-by: Hans Mackowiak --- forge-gui/res/editions/Tempest Remastered.txt | 12 ---------- forge-gui/res/editions/Tempest.txt | 9 ------- forge-gui/res/editions/Tenth Edition.txt | 1 - .../editions/The Brothers War Commander.txt | 3 +++ forge-gui/res/editions/The Brothers War.txt | 5 +++- ...ord of the Rings Tales of Middle-earth.txt | 8 +++---- forge-gui/res/editions/Theros.txt | 2 ++ forge-gui/res/editions/Throne of Eldraine.txt | 7 +++--- .../res/editions/Time Spiral Timeshifted.txt | 8 ------- forge-gui/res/editions/Time Spiral.txt | 15 ------------ forge-gui/res/editions/Torment.txt | 3 --- forge-gui/res/editions/Treasure Chests.txt | 13 ---------- forge-gui/res/editions/Ultimate Masters.txt | 5 ++-- forge-gui/res/editions/Unfinity.txt | 10 +++++--- forge-gui/res/editions/Unglued.txt | 1 + forge-gui/res/editions/Unhinged.txt | 6 ----- forge-gui/res/editions/Unlimited Edition.txt | 3 --- forge-gui/res/editions/Unsanctioned.txt | 3 ++- forge-gui/res/editions/Urza's Destiny.txt | 3 --- forge-gui/res/editions/Urza's Legacy.txt | 3 --- forge-gui/res/editions/Urza's Saga.txt | 8 ------- forge-gui/res/editions/Vintage Masters.txt | 21 ---------------- forge-gui/res/editions/Visions.txt | 6 ----- forge-gui/res/editions/War of the Spark.txt | 1 - .../editions/Warhammer 40,000 Commander.txt | 14 ++++++----- forge-gui/res/editions/Weatherlight.txt | 3 --- .../editions/Wilds of Eldraine Commander.txt | 16 ++++++++----- forge-gui/res/editions/Wilds of Eldraine.txt | 24 ++++++++++++------- forge-gui/res/editions/Worldwake.txt | 1 - forge-gui/res/editions/Zendikar Promos.txt | 3 --- .../editions/Zendikar Rising Commander.txt | 2 +- forge-gui/res/editions/Zendikar Rising.txt | 4 +++- forge-gui/res/editions/Zendikar.txt | 1 - .../res/tokenscripts/c_2_2_teddy_bear.txt | 5 ++++ .../tokenscripts/c_5_5_giant_teddy_bear.txt | 5 ++++ forge-gui/res/tokenscripts/g_2_2_sheep.txt | 6 +++++ forge-gui/res/tokenscripts/r_1_1_soldier.txt | 6 +++++ .../res/tokenscripts/w_2_2_cat_flying.txt | 7 ++++++ 38 files changed, 95 insertions(+), 158 deletions(-) create mode 100644 forge-gui/res/tokenscripts/c_2_2_teddy_bear.txt create mode 100644 forge-gui/res/tokenscripts/c_5_5_giant_teddy_bear.txt create mode 100644 forge-gui/res/tokenscripts/g_2_2_sheep.txt create mode 100644 forge-gui/res/tokenscripts/r_1_1_soldier.txt create mode 100644 forge-gui/res/tokenscripts/w_2_2_cat_flying.txt diff --git a/forge-gui/res/editions/Tempest Remastered.txt b/forge-gui/res/editions/Tempest Remastered.txt index 2b3ea10d954..0bdcd709b1d 100644 --- a/forge-gui/res/editions/Tempest Remastered.txt +++ b/forge-gui/res/editions/Tempest Remastered.txt @@ -278,15 +278,3 @@ ScryfallCode=TPR 267 L Forest @Douglas Shuler 268 L Forest @Douglas Shuler 269 L Forest @Douglas Shuler - -[tokens] -c_1_1_sliver -w_1_1_pegasus_flying -w_2_2_reflection -w_1_1_spirit_flying -b_1_1_rat -b_2_2_zombie -r_1_1_goblin -g_1_1_saproling -g_1_1_spike -c_1_1_a_thopter_flying diff --git a/forge-gui/res/editions/Tempest.txt b/forge-gui/res/editions/Tempest.txt index fb2ac830c49..e2e23447ca1 100644 --- a/forge-gui/res/editions/Tempest.txt +++ b/forge-gui/res/editions/Tempest.txt @@ -360,12 +360,3 @@ ScryfallCode=TMP 348 L Forest @Douglas Shuler 349 L Forest @Douglas Shuler 350 L Forest @Douglas Shuler - -[tokens] -carnivore -w_1_1_pegasus_flying -w_2_2_reflection -w_1_1_spirit_flying -b_2_2_zombie -g_1_1_dog -g_1_1_saproling diff --git a/forge-gui/res/editions/Tenth Edition.txt b/forge-gui/res/editions/Tenth Edition.txt index 60478f1bcf0..0ed072458a2 100644 --- a/forge-gui/res/editions/Tenth Edition.txt +++ b/forge-gui/res/editions/Tenth Edition.txt @@ -2,7 +2,6 @@ Code=10E Date=2007-07-13 Name=Tenth Edition -Code2=10E Type=Core BoosterCovers=5 Booster=10 Common, 3 Uncommon, 1 Rare, 1 BasicLand diff --git a/forge-gui/res/editions/The Brothers War Commander.txt b/forge-gui/res/editions/The Brothers War Commander.txt index e67e50360e1..2bb3bc32588 100644 --- a/forge-gui/res/editions/The Brothers War Commander.txt +++ b/forge-gui/res/editions/The Brothers War Commander.txt @@ -229,3 +229,6 @@ ScryfallCode=BRC 12 scrap @Drew Tucker 13 c_1_1_a_servo @Igor Kieryluk 14 g_5_3_elemental @Nils Hamm + +[other] +1 copy @David Palumbo diff --git a/forge-gui/res/editions/The Brothers War.txt b/forge-gui/res/editions/The Brothers War.txt index 2f210ae91cb..d8bdca69c8e 100644 --- a/forge-gui/res/editions/The Brothers War.txt +++ b/forge-gui/res/editions/The Brothers War.txt @@ -450,6 +450,9 @@ A-289 R A-Geology Enthusiast @Fajareka Setiawan 6 c_x_x_a_golem @Olivier Bernard 7 c_a_powerstone @Karl Kopinski 8 c_1_1_a_soldier @Gabor Szikszai -8 c_1_1_a_soldier @Gabor Szikszai +9 c_1_1_a_soldier @Kev Walker 10 c_1_1_a_thopter_flying @Samuel Perin 11 c_3_3_a_zombie @Kieran Yanner + +[other] +12 emblem_saheeli_filigree_master @Aurore Folny diff --git a/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth.txt b/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth.txt index 485fd9c94be..7fd3a223250 100644 --- a/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth.txt +++ b/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth.txt @@ -867,14 +867,14 @@ A-246 M A-The One Ring @Veli Nyström [tokens] 1 w_1_1_human_soldier @Anastasia Balakchina -1 w_1_1_human_soldier @Anastasia Balakchina +2 w_1_1_human_soldier @Axel Sauerwald 3 w_1_1_spirit_flying @Nino Is 4 u_1_1_tentacle @Chris Cold 5 b_0_0_orc_army @Veli Nyström -5 b_0_0_orc_army @Veli Nyström +6 b_0_0_orc_army @Veli Nyström 7 smaug @Jarel Threat 8 ballistic_boulder @Alexander Forssberg 9 c_a_food_sac @Randy Gallegos -10 c_a_food_sac @Randy Gallegos -11 c_a_food_sac @Randy Gallegos +10 c_a_food_sac @Claudiu-Antoniu Magherusan +11 c_a_food_sac @L J Koh 12 c_a_treasure_sac @Valera Lutfullina diff --git a/forge-gui/res/editions/Theros.txt b/forge-gui/res/editions/Theros.txt index 0e04031f355..43747443a5a 100644 --- a/forge-gui/res/editions/Theros.txt +++ b/forge-gui/res/editions/Theros.txt @@ -267,6 +267,8 @@ ScryfallCode=THS 4 u_2_2_bird_flying @Peter Mohrbacher 5 u_1_0_elemental @Karl Kopinski 6 b_1_1_harpy_flying @Nils Hamm +7 r_1_1_soldier @Johann Bodin +# Duplicated as there is no r_1_1_soldier_haste token yet 7 r_1_1_soldier_haste @Johann Bodin 8 g_2_2_boar @James Ryman 9 rg_2_2_satyr_haste @Johann Bodin diff --git a/forge-gui/res/editions/Throne of Eldraine.txt b/forge-gui/res/editions/Throne of Eldraine.txt index a78e02cc50b..150501898ab 100644 --- a/forge-gui/res/editions/Throne of Eldraine.txt +++ b/forge-gui/res/editions/Throne of Eldraine.txt @@ -2,7 +2,6 @@ Code=ELD Date=2019-10-04 Name=Throne of Eldraine -Code2=ELD Type=Expansion BoosterCovers=3 Booster=10 Common:fromSheet("ELD cards"), 3 Uncommon:fromSheet("ELD cards"), 1 RareMythic:fromSheet("ELD cards"), 1 BasicLand @@ -443,9 +442,9 @@ A-125 R A-Fires of Invention @Stanton Feng 13 rw_3_1_human_warrior_trample_haste @Suzanne Helmigh 14 bg_2_2_wolf_garruk @David Gaillet 15 c_a_food_sac @Steven Belledin -16 c_a_food_sac @Steven Belledin -17 c_a_food_sac @Steven Belledin -18 c_a_food_sac @Steven Belledin +16 c_a_food_sac @Randy Gallegos +17 c_a_food_sac @Donato Giancola +18 c_a_food_sac @Lucas Graciano [other] 19 emblem_garruk_cursed_huntsman @Eric Deschamps diff --git a/forge-gui/res/editions/Time Spiral Timeshifted.txt b/forge-gui/res/editions/Time Spiral Timeshifted.txt index ddb5c4e04dc..60550827551 100644 --- a/forge-gui/res/editions/Time Spiral Timeshifted.txt +++ b/forge-gui/res/editions/Time Spiral Timeshifted.txt @@ -2,7 +2,6 @@ Code=TSB Date=2006-10-06 Name=Time Spiral "Timeshifted" -Code2=TSB Type=Expansion ScryfallCode=TSB BoosterBox=0 @@ -130,10 +129,3 @@ FatPack=0 119 S Gemstone Mine @Brom 120 S Pendelhaven @Bryon Wackwitz 121 S Safe Haven @Christopher Rush - -[tokens] -w_1_1_pegasus_flying -b_0_1_serf -g_3_3_elephant -g_1_1_saproling -g_0_1_sheep diff --git a/forge-gui/res/editions/Time Spiral.txt b/forge-gui/res/editions/Time Spiral.txt index 47b64b43a5d..a4875a229db 100644 --- a/forge-gui/res/editions/Time Spiral.txt +++ b/forge-gui/res/editions/Time Spiral.txt @@ -2,7 +2,6 @@ Code=TSP Date=2006-10-06 Name=Time Spiral -Code2=TSP Type=Expansion BoosterCovers=5 Booster=10 Common, 3 Uncommon, 1 Rare, 1 TimeShifted TSB @@ -313,17 +312,3 @@ ScryfallCode=TSP 299 L Forest @Vance Kovacs 300 L Forest @Craig Mullins 301 L Forest @Stephen Tappin - -[tokens] -kobolds_of_kher_keep -w_1_1_citizen -w_2_2_griffin_flying -u_1_1_camarid -b_1_2_bat_flying_nosferatu -b_2_4_spider_reach -b_1_1_thrull -r_1_1_goblin -g_1_1_saproling -g_x_x_wurm -c_2_2_a_assembly_worker -c_1_1_a_triskelavite_flying_ammo diff --git a/forge-gui/res/editions/Torment.txt b/forge-gui/res/editions/Torment.txt index 11ef06ecb0b..fe0234a2ae5 100644 --- a/forge-gui/res/editions/Torment.txt +++ b/forge-gui/res/editions/Torment.txt @@ -156,6 +156,3 @@ ScryfallCode=TOR 141 U Tainted Isle @Alan Pollack 142 U Tainted Peak @Tony Szczudlo 143 U Tainted Wood @Rob Alexander - -[tokens] -g_1_1_squirrel diff --git a/forge-gui/res/editions/Treasure Chests.txt b/forge-gui/res/editions/Treasure Chests.txt index 398d29f712a..20ee5139c3f 100644 --- a/forge-gui/res/editions/Treasure Chests.txt +++ b/forge-gui/res/editions/Treasure Chests.txt @@ -288,16 +288,3 @@ ScryfallCode=PZ2 70861 R Journey for the Elixir @Qiu De En 70863 R Screeching Phoenix @Tingting Yeh 70865 C Cleansing Screech @Tingting Yeh - -[tokens] -w_1_1_bird_flying -w_1_1_soldier -w_1_1_spirit_flying -u_1_1_a_thopter_flying -b_1_1_assassin_deathtouch_haste -b_0_0_phyrexian_germ -r_5_5_dragon_flying -r_1_1_goblin_all_attack -r_8_8_lizard -g_1_1_saproling -c_1_1_a_construct_defender diff --git a/forge-gui/res/editions/Ultimate Masters.txt b/forge-gui/res/editions/Ultimate Masters.txt index a320b944315..b18a0c51c68 100644 --- a/forge-gui/res/editions/Ultimate Masters.txt +++ b/forge-gui/res/editions/Ultimate Masters.txt @@ -2,7 +2,6 @@ Code=UMA Date=2018-12-07 Name=Ultimate Masters -Code2=UMA Type=Reprint BoosterCovers=3 Booster=11 Common, 3 Uncommon, 1 RareMythic @@ -277,7 +276,9 @@ ScryfallCode=UMA 7 b_6_6_wurm_trample @Daarken 8 b_2_2_zombie @Anna Steinbauer 9 r_1_1_elemental @Jaime Jones -9 r_1_1_elemental @Jaime Jones +10 r_1_1_elemental @Winona Nelson +11 r_1_1_soldier @Johann Bodin +# Duplicated as there is no r_1_1_soldier_haste token yet 11 r_1_1_soldier_haste @Johann Bodin 12 spark_elemental @John Avon 13 g_4_4_elemental @Brandon Kitkouski diff --git a/forge-gui/res/editions/Unfinity.txt b/forge-gui/res/editions/Unfinity.txt index 18d01fccea3..4104924009a 100644 --- a/forge-gui/res/editions/Unfinity.txt +++ b/forge-gui/res/editions/Unfinity.txt @@ -649,13 +649,17 @@ F538 R Water Gun Balloon Game @Ralph Horsley 537 R Breeding Pool @Bruce Brenneise [tokens] +1 w_2_2_cat_flying @Gaboleps 2 w_1_1_a_clown_robot @Ralph Horsley -2 w_1_1_a_clown_robot @Ralph Horsley +3 w_1_1_a_clown_robot @Matt Dixon +# Not implemented +# 4 contortionist @Tomek Larek 5 storm_crow @Greg Staples 6 b_2_2_zombie_employee @Sebastian Giacobino 7 r_1_1_balloon_flying @Greg Bobrowski 8 g_1_1_squirrel @Dave Greco +9 c_2_2_teddy_bear @Ernanda Souza 10 c_a_food_sac @Matt Gaser -11 c_a_food_sac @Matt Gaser +11 c_a_food_sac @Chuck Lukacs 12 c_a_treasure_sac @Ben Maier -13 c_a_treasure_sac @Ben Maier +13 c_a_treasure_sac @Michael Phillippi diff --git a/forge-gui/res/editions/Unglued.txt b/forge-gui/res/editions/Unglued.txt index d0a067a228f..3a31a0fbfff 100644 --- a/forge-gui/res/editions/Unglued.txt +++ b/forge-gui/res/editions/Unglued.txt @@ -104,4 +104,5 @@ ScryfallCode=UGL 2 w_1_1_soldier @Daren Bader 3 b_2_2_zombie @Christopher Rush 4 r_1_1_goblin @Pete Venters +5 g_2_2_sheep @Kev Walker 6 g_1_1_squirrel @Ron Spencer diff --git a/forge-gui/res/editions/Unhinged.txt b/forge-gui/res/editions/Unhinged.txt index ecd990e8791..f6a386c93d9 100644 --- a/forge-gui/res/editions/Unhinged.txt +++ b/forge-gui/res/editions/Unhinged.txt @@ -151,9 +151,3 @@ ScryfallCode=UNH 139 L Mountain @John Avon 140 L Forest @John Avon 141 R Super Secret Tech @Dan Frazier - -[tokens] -r_1_1_goblin -g_1_1_ape -g_3_3_ape -g_1_1_squirrel diff --git a/forge-gui/res/editions/Unlimited Edition.txt b/forge-gui/res/editions/Unlimited Edition.txt index d7a4241a86c..7e2c09ddf95 100644 --- a/forge-gui/res/editions/Unlimited Edition.txt +++ b/forge-gui/res/editions/Unlimited Edition.txt @@ -314,6 +314,3 @@ ScryfallCode=2ED 300 L Forest @Christopher Rush 301 L Forest @Christopher Rush 302 L Forest @Christopher Rush - -[tokens] -wasp diff --git a/forge-gui/res/editions/Unsanctioned.txt b/forge-gui/res/editions/Unsanctioned.txt index 6fdf84b1cb4..5fe9256fea5 100644 --- a/forge-gui/res/editions/Unsanctioned.txt +++ b/forge-gui/res/editions/Unsanctioned.txt @@ -1,6 +1,5 @@ [metadata] Code=UND -Code2=UND Date=2020-02-29 Name=Unsanctioned Type=Funny @@ -109,3 +108,5 @@ ScryfallCode=UND 1 u_1_1_beeble @Jeff Miracola 2 r_1_1_goblin @Dave Allsop 3 g_1_1_squirrel @Daniel Ljunggren +4 c_4_4_dragon_flying @Autumn Rain Turkel +5 c_5_5_giant_teddy_bear @Andrea Radeck diff --git a/forge-gui/res/editions/Urza's Destiny.txt b/forge-gui/res/editions/Urza's Destiny.txt index 605e66a27e6..014e9ff95e4 100644 --- a/forge-gui/res/editions/Urza's Destiny.txt +++ b/forge-gui/res/editions/Urza's Destiny.txt @@ -154,6 +154,3 @@ ScryfallCode=UDS 141 R Thran Golem @Ron Spears 142 R Urza's Incubator @Pete Venters 143 R Yavimaya Hollow @Douglas Shuler - -[tokens] -r_1_1_goblin diff --git a/forge-gui/res/editions/Urza's Legacy.txt b/forge-gui/res/editions/Urza's Legacy.txt index 6884279a607..d905049764d 100644 --- a/forge-gui/res/editions/Urza's Legacy.txt +++ b/forge-gui/res/editions/Urza's Legacy.txt @@ -154,6 +154,3 @@ ScryfallCode=ULG 141 U Ghitu Encampment @Don Hazeltine 142 U Spawning Pool @Rob Alexander 143 U Treetop Village @Anthony S. Waters - -[tokens] -g_1_1_squirrel diff --git a/forge-gui/res/editions/Urza's Saga.txt b/forge-gui/res/editions/Urza's Saga.txt index 004224b7e2c..7a6dcc9a140 100644 --- a/forge-gui/res/editions/Urza's Saga.txt +++ b/forge-gui/res/editions/Urza's Saga.txt @@ -360,11 +360,3 @@ ScryfallCode=USG 348 L Forest @Anthony S. Waters 349 L Forest @Anthony S. Waters 350 L Forest @Anthony S. Waters - -[tokens] -c_1_1_a_gnome -w_2_2_knight -b_x_x_phyrexian_minion -r_1_1_goblin -g_1_1_saproling -g_3_3_beast diff --git a/forge-gui/res/editions/Vintage Masters.txt b/forge-gui/res/editions/Vintage Masters.txt index 3d3211b6eab..b332e03d249 100644 --- a/forge-gui/res/editions/Vintage Masters.txt +++ b/forge-gui/res/editions/Vintage Masters.txt @@ -335,24 +335,3 @@ ScryfallCode=VMA 323 R Underground Sea @Cliff Childs 324 R Volcanic Island @Noah Bradley 325 R Yavimaya Hollow @Douglas Shuler - -[tokens] -w_4_4_angel_flying -w_1_1_bird_flying -w_2_2_reflection -w_1_1_soldier -w_1_1_spirit_flying -b_x_x_demon_flying -b_6_6_wurm_trample -b_2_2_zombie -r_1_1_goblin -g_2_2_bear -g_3_3_boar -g_3_3_elephant -g_1_1_insect -g_1_1_saproling -g_x_x_saproling_burst -g_1_1_squirrel -g_6_6_wurm -rw_1_1_goblin_soldier -c_1_1_a_thopter_flying diff --git a/forge-gui/res/editions/Visions.txt b/forge-gui/res/editions/Visions.txt index a3405caeabe..4f2287a9cd6 100644 --- a/forge-gui/res/editions/Visions.txt +++ b/forge-gui/res/editions/Visions.txt @@ -177,9 +177,3 @@ ScryfallCode=VIS 165 U Karoo @Zina Saunders 166 U Quicksand @Roger Raupp 167 R Undiscovered Paradise @David O'Connor - -[tokens] -butterfly -c_0_1_a_prism -g_0_1_sheep -g_1_1_snake diff --git a/forge-gui/res/editions/War of the Spark.txt b/forge-gui/res/editions/War of the Spark.txt index 865f57d9e27..32f470a3b22 100644 --- a/forge-gui/res/editions/War of the Spark.txt +++ b/forge-gui/res/editions/War of the Spark.txt @@ -2,7 +2,6 @@ Code=WAR Date=2019-05-03 Name=War of the Spark -Code2=WAR Type=Expansion BoosterCovers=3 Booster=10 Common:fromSheet("WAR cards"), 3 Uncommon:fromSheet("WAR cards"), 1 RareMythic:fromSheet("WAR cards"), 1 BasicLand diff --git a/forge-gui/res/editions/Warhammer 40,000 Commander.txt b/forge-gui/res/editions/Warhammer 40,000 Commander.txt index 4f6319c6feb..18f1eafb8c6 100644 --- a/forge-gui/res/editions/Warhammer 40,000 Commander.txt +++ b/forge-gui/res/editions/Warhammer 40,000 Commander.txt @@ -331,24 +331,26 @@ ScryfallCode=40K [tokens] 1 w_2_2_astartes_warrior_vigilance @Dmitriy Mironov 2 w_1_1_soldier @Lixin Yin -2★ w_1_1_soldier @Lixin Yin 3 w_1_1_soldier @Lixin Yin -3★ w_1_1_soldier @Lixin Yin 4 w_1_1_soldier @Lixin Yin -4★ w_1_1_soldier @Lixin Yin 9 u_1_1_tyranid_gargoyle_flying @David Astruga 12 b_2_2_astartes_warrior_menace @Helge C. Balzer 13 cherubael @Irina Nordsol 14 b_2_2_a_necron_warrior @Games Workshop 15 plaguebearer_of_nurgle @Games Workshop -15★ plaguebearer_of_nurgle @Games Workshop 16 r_3_3_spawn @Oleg Bulakh -16★ r_3_3_spawn @Oleg Bulakh 17 g_1_1_tyranid @Slawomir Maniak -17★ g_1_1_tyranid @Slawomir Maniak 18 g_5_5_tyranid @Antonio José Manzanedo 19 g_3_3_tyranid_warrior_trample @Sergio Cosmai 20 blue_horror @Andrey Nyarl 21 c_a_clue_draw @Mirko Failoni 22 c_1_1_a_insect_flying @Bartek Fedyczak 23 c_4_4_a_robot_noblock @Philip Helliwell + +[other] +5 squad_space_marine_devastator @Dmitriy Mironov +6 squad_ultramarines_honour_guard @Jake Murray +7 squad_zephyrim @Jon Neimeister +8 squad_sicarian_infiltrator @Xavier Ribeiro +10 squad_vanguard_suppressor @Fajareka Setiawan +11 squad_arco-flagellant @Xavier Ribeiro diff --git a/forge-gui/res/editions/Weatherlight.txt b/forge-gui/res/editions/Weatherlight.txt index 8a5a402d794..75d444c7b44 100644 --- a/forge-gui/res/editions/Weatherlight.txt +++ b/forge-gui/res/editions/Weatherlight.txt @@ -178,6 +178,3 @@ ScryfallCode=WTH 165 R Lotus Vale @John Avon 166 R Scorched Ruins @John Avon 167 R Winding Canyons @John Avon - -[tokens] -g_1_1_squirrel diff --git a/forge-gui/res/editions/Wilds of Eldraine Commander.txt b/forge-gui/res/editions/Wilds of Eldraine Commander.txt index 8d03fe34cec..f4dc47c1ac8 100644 --- a/forge-gui/res/editions/Wilds of Eldraine Commander.txt +++ b/forge-gui/res/editions/Wilds of Eldraine Commander.txt @@ -4,6 +4,7 @@ Date=2023-09-08 Name=Wilds of Eldraine Commander Type=Commander ScryfallCode=WOC +TokensFallbackCode=WOE [cards] 1 M Tegwyll, Duke of Splendor @Ekaterina Burmak @@ -181,12 +182,12 @@ ScryfallCode=WOC 173 U Vitu-Ghazi, the City-Tree @Martina Pilcerova [tokens] -0 role_monster @Unknown -0 role_royal @Unknown -0 role_sorcerer @Unknown -0 role_virtuous @Unknown -0 role_virtuous @Unknown -0 role_virtuous @Unknown +1 role_monster @Rovina Cai +# Need to flip it +1 role_virtuous @Rovina Cai +2 role_royal @Rovina Cai +# Need to flip it +2 role_virtuous @Rovina Cai 7 w_1_1_human_soldier @Kimonas Theodossiou 9 w_2_2_pegasus_flying @Lars Grant-West 10 u_1_1_faerie_flying @Irina Nordsol @@ -194,3 +195,6 @@ ScryfallCode=WOC 12 r_4_2_pirate_noblock @Igor Krstic 15 g_1_1_saproling @Joseph Meehan 16 ub_1_1_faerie_rogue_flying @E. M. Gist + +[other] +4 copy @David Palumbo diff --git a/forge-gui/res/editions/Wilds of Eldraine.txt b/forge-gui/res/editions/Wilds of Eldraine.txt index e25a4f300d8..7af67434cba 100644 --- a/forge-gui/res/editions/Wilds of Eldraine.txt +++ b/forge-gui/res/editions/Wilds of Eldraine.txt @@ -405,12 +405,6 @@ ScryfallCode=WOE 381 R Expel the Interlopers @Awanqi (Angela Wang) [tokens] -0 role_cursed @Unknown -0 role_monster @Unknown -0 role_royal @Unknown -0 role_sorcerer @Unknown -0 role_wicked @Unknown -0 role_young_hero @Unknown 1 w_1_1_bird_flying @Kisung Koh 2 w_1_1_human @Julia Griffin 3 w_2_2_knight_vigilance @Dominik Mayer @@ -421,7 +415,19 @@ ScryfallCode=WOE 8 g_3_3_beast @Vincent Christiaens 9 wu_4_4_elemental @Camille Alquier 10 c_a_food_sac @Ovidio Cartagena -11 c_a_food_sac @Ovidio Cartagena -12 c_a_food_sac @Ovidio Cartagena -13 c_a_food_sac @Ovidio Cartagena +11 c_a_food_sac @Gaboleps +12 c_a_food_sac @Irvin Rodriguez +13 c_a_food_sac @Jokubas Uogintas 14 c_a_treasure_sac @Zezhou Chen +15 role_monster @Rovina Cai +# Need to flip it +15 role_sorcerer @Rovina Cai +16 role_royal @Rovina Cai +# Need to flip it +16 role_young_hero @Rovina Cai +17 role_wicked @Rovina Cai +# Need to flip it +17 role_cursed @Rovina Cai + +[other] +18 adventure @Dmitry Burmak diff --git a/forge-gui/res/editions/Worldwake.txt b/forge-gui/res/editions/Worldwake.txt index 6ff4e802a97..971e9488e95 100644 --- a/forge-gui/res/editions/Worldwake.txt +++ b/forge-gui/res/editions/Worldwake.txt @@ -2,7 +2,6 @@ Code=WWK Date=2010-02-05 Name=Worldwake -Code2=WWK Type=Expansion BoosterCovers=3 Booster=10 Common, 3 Uncommon, 1 RareMythic, 1 BasicLand ZEN diff --git a/forge-gui/res/editions/Zendikar Promos.txt b/forge-gui/res/editions/Zendikar Promos.txt index 24f0ad8590f..195435dec03 100644 --- a/forge-gui/res/editions/Zendikar Promos.txt +++ b/forge-gui/res/editions/Zendikar Promos.txt @@ -11,6 +11,3 @@ A5 R Terra Stomper @Goran Josic 178★ M Rampaging Baloths @Eric Deschamps 221★ R Oran-Rief, the Vastwood @Mike Bierek 228★ R Valakut, the Molten Pinnacle @James Paick - -[tokens] -g_4_4_beast \ No newline at end of file diff --git a/forge-gui/res/editions/Zendikar Rising Commander.txt b/forge-gui/res/editions/Zendikar Rising Commander.txt index 85d579df86f..5c6df32a500 100644 --- a/forge-gui/res/editions/Zendikar Rising Commander.txt +++ b/forge-gui/res/editions/Zendikar Rising Commander.txt @@ -153,7 +153,7 @@ ScryfallCode=ZNC 1 w_1_1_bird_flying @Howard Lyon 2 w_1_1_kor_ally @Jeremy Wilson 3 b_1_1_faerie_rogue_flying @Dave Allsop -4 b_0_0_phyrexian_germ @Igor Kieryluk +4 b_0_0_germ @Igor Kieryluk 5 b_1_1_goblin_rogue @Dave Kendall 6 b_1_1_rat @Mike Bierek 7 g_4_4_beast @Steve Prescott diff --git a/forge-gui/res/editions/Zendikar Rising.txt b/forge-gui/res/editions/Zendikar Rising.txt index 468a0a67691..538141018ff 100644 --- a/forge-gui/res/editions/Zendikar Rising.txt +++ b/forge-gui/res/editions/Zendikar Rising.txt @@ -2,7 +2,6 @@ Code=ZNR Date=2020-09-25 Name=Zendikar Rising -Code2=ZNR Type=Expansion BoosterCovers=3 Booster=10 Common:!dfc:fromSheet("ZNR cards"), 3 Uncommon:!dfc:fromSheet("ZNR cards"), 1 RareMythic:!dfc:fromSheet("ZNR cards"), 1 BasicLand @@ -486,3 +485,6 @@ A-257 U A-Base Camp @Jokubas Uogintas 9 bg_x_x_hydra @Filip Burburan 10 c_1_1_a_construct @Jokubas Uogintas 11 c_0_1_a_goblin_construct_noblock_ping @Johan Grenier + +[other] +12 copy @Jason Rainville diff --git a/forge-gui/res/editions/Zendikar.txt b/forge-gui/res/editions/Zendikar.txt index d8dc5892e01..e03a74e079a 100644 --- a/forge-gui/res/editions/Zendikar.txt +++ b/forge-gui/res/editions/Zendikar.txt @@ -2,7 +2,6 @@ Code=ZEN Date=2009-10-02 Name=Zendikar -Code2=ZEN Type=Expansion BoosterCovers=5 Booster=10 Common, 3 Uncommon, 1 RareMythic, 1 BasicLand diff --git a/forge-gui/res/tokenscripts/c_2_2_teddy_bear.txt b/forge-gui/res/tokenscripts/c_2_2_teddy_bear.txt new file mode 100644 index 00000000000..b540a6afa3d --- /dev/null +++ b/forge-gui/res/tokenscripts/c_2_2_teddy_bear.txt @@ -0,0 +1,5 @@ +Name:Teddy Bear Token +ManaCost:no cost +Types:Creature Teddy bear +PT:2/2 +Oracle: diff --git a/forge-gui/res/tokenscripts/c_5_5_giant_teddy_bear.txt b/forge-gui/res/tokenscripts/c_5_5_giant_teddy_bear.txt new file mode 100644 index 00000000000..4c75338636b --- /dev/null +++ b/forge-gui/res/tokenscripts/c_5_5_giant_teddy_bear.txt @@ -0,0 +1,5 @@ +Name:Giant Teddy Bear Token +ManaCost:no cost +Types:Creature Giant Teddy Bear +PT:5/5 +Oracle: diff --git a/forge-gui/res/tokenscripts/g_2_2_sheep.txt b/forge-gui/res/tokenscripts/g_2_2_sheep.txt new file mode 100644 index 00000000000..5bc28b0d0c9 --- /dev/null +++ b/forge-gui/res/tokenscripts/g_2_2_sheep.txt @@ -0,0 +1,6 @@ +Name:Sheep Token +ManaCost:no cost +Colors:green +Types:Creature Sheep +PT:2/2 +Oracle: diff --git a/forge-gui/res/tokenscripts/r_1_1_soldier.txt b/forge-gui/res/tokenscripts/r_1_1_soldier.txt new file mode 100644 index 00000000000..7ba8e203e75 --- /dev/null +++ b/forge-gui/res/tokenscripts/r_1_1_soldier.txt @@ -0,0 +1,6 @@ +Name:Soldier Token +ManaCost:no cost +Colors:red +Types:Creature Soldier +PT:1/1 +Oracle: diff --git a/forge-gui/res/tokenscripts/w_2_2_cat_flying.txt b/forge-gui/res/tokenscripts/w_2_2_cat_flying.txt new file mode 100644 index 00000000000..bf1f9dd3d24 --- /dev/null +++ b/forge-gui/res/tokenscripts/w_2_2_cat_flying.txt @@ -0,0 +1,7 @@ +Name:Cat Token +ManaCost:no cost +Colors:white +Types:Creature Cat +PT:2/2 +K:Flying +Oracle:Flying From a6e8ac5ccfa55cb8a6a4c12825d53eca361cdddc Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 26 Oct 2025 22:52:53 +0100 Subject: [PATCH 183/230] Subtlety fix (#9005) --- .../ability/effects/ChangeZoneEffect.java | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) 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 dada4e8a4d9..28bcbe1c627 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 @@ -475,11 +475,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect { origin.addAll(ZoneType.listValueOf(sa.getParam("Origin"))); } - int libraryPosition = sa.hasParam("LibraryPosition") ? AbilityUtils.calculateAmount(hostCard, sa.getParam("LibraryPosition"), sa) : 0; + int libPos = sa.hasParam("LibraryPosition") ? AbilityUtils.calculateAmount(hostCard, sa.getParam("LibraryPosition"), sa) : 0; if (sa.hasParam("DestinationAlternative")) { - Pair pair = handleAltDest(sa, hostCard, destination, libraryPosition, activator); + Pair pair = handleAltDest(sa, hostCard, destination, libPos, activator); destination = pair.getKey(); - libraryPosition = pair.getValue(); + libPos = pair.getValue(); } final GameEntityCounterTable counterTable = new GameEntityCounterTable(); @@ -497,7 +497,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { continue; } - removeFromStack(tgtSA, sa, si, game, triggerList, counterTable); + removeFromStack(tgtSA, sa, si, destination, libPos, game, triggerList, counterTable); } // End of change from stack final String remember = sa.getParam("RememberChanged"); @@ -729,7 +729,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { handleExiledWith(gameCard, sa); } - movedCard = game.getAction().moveTo(destination, gameCard, libraryPosition, sa, moveParams); + movedCard = game.getAction().moveTo(destination, gameCard, libPos, sa, moveParams); if (destination.equals(ZoneType.Exile) && lastStateBattlefield.contains(gameCard) && hostCard.equals(gameCard)) { // support Parallax Wave returning itself @@ -1582,16 +1582,17 @@ public class ChangeZoneEffect extends SpellAbilityEffect { * object. * @param game */ - private void removeFromStack(final SpellAbility tgtSA, final SpellAbility srcSA, final SpellAbilityStackInstance si, final Game game, CardZoneTable triggerList, GameEntityCounterTable counterTable) { + private void removeFromStack(final SpellAbility tgtSA, final SpellAbility srcSA, final SpellAbilityStackInstance si, final ZoneType destination, final int libPos, + final Game game, CardZoneTable triggerList, GameEntityCounterTable counterTable) { final Card tgtHost = tgtSA.getHostCard(); game.getStack().remove(si); - Map params = AbilityKey.newMap(); - params.put(AbilityKey.StackSa, tgtSA); - AbilityKey.addCardZoneTableParams(params, triggerList); + if (destination != null) { + Map params = AbilityKey.newMap(); + params.put(AbilityKey.StackSa, tgtSA); + AbilityKey.addCardZoneTableParams(params, triggerList); - Card movedCard = null; - if (srcSA.hasParam("Destination")) { + Card movedCard = null; final boolean remember = srcSA.hasParam("RememberChanged"); final boolean imprint = srcSA.hasParam("Imprint"); if (tgtSA.isAbility()) { @@ -1604,15 +1605,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } movedCard = game.getAction().exile(tgtHost, srcSA, params); handleExiledWith(movedCard, srcSA); - } else if (srcSA.getParam("Destination").equals("TopOfLibrary")) { - movedCard = game.getAction().moveToLibrary(tgtHost, srcSA, params); } else if (srcSA.getParam("Destination").equals("Hand")) { movedCard = game.getAction().moveToHand(tgtHost, srcSA, params); - } else if (srcSA.getParam("Destination").equals("BottomOfLibrary")) { - movedCard = game.getAction().moveToBottomOfLibrary(tgtHost, srcSA, params); } else if (srcSA.getParam("Destination").equals("Library")) { - final int libraryPos = srcSA.hasParam("LibraryPosition") ? AbilityUtils.calculateAmount(tgtHost, srcSA.getParam("LibraryPosition"), srcSA) : 0; - movedCard = game.getAction().moveToLibrary(tgtHost, libraryPos, srcSA, params); + movedCard = game.getAction().moveToLibrary(tgtHost, libPos, srcSA, params); if (srcSA.hasParam("Shuffle") && "True".equals(srcSA.getParam("Shuffle"))) { tgtHost.getOwner().shuffle(srcSA); } From b272612c41f0fbdcdcf3ad165ca26ab2ed2e4153 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 26 Oct 2025 23:02:08 +0100 Subject: [PATCH 184/230] Clean up checks (#9006) --- .../java/forge/game/ability/effects/ChangeZoneEffect.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 28bcbe1c627..a049b411baf 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 @@ -1597,17 +1597,17 @@ public class ChangeZoneEffect extends SpellAbilityEffect { final boolean imprint = srcSA.hasParam("Imprint"); if (tgtSA.isAbility()) { // Shouldn't be able to target Abilities but leaving this in for now - } else if (srcSA.getParam("Destination").equals("Graveyard")) { + } else if (destination == ZoneType.Graveyard) { movedCard = game.getAction().moveToGraveyard(tgtHost, srcSA, params); - } else if (srcSA.getParam("Destination").equals("Exile")) { + } else if (destination == ZoneType.Exile) { if (!tgtHost.canExiledBy(srcSA, true)) { return; } movedCard = game.getAction().exile(tgtHost, srcSA, params); handleExiledWith(movedCard, srcSA); - } else if (srcSA.getParam("Destination").equals("Hand")) { + } else if (destination == ZoneType.Hand) { movedCard = game.getAction().moveToHand(tgtHost, srcSA, params); - } else if (srcSA.getParam("Destination").equals("Library")) { + } else if (destination == ZoneType.Library) { movedCard = game.getAction().moveToLibrary(tgtHost, libPos, srcSA, params); if (srcSA.hasParam("Shuffle") && "True".equals(srcSA.getParam("Shuffle"))) { tgtHost.getOwner().shuffle(srcSA); From bfb0c9d41fe3c698f3f6c8a208c31cf87d36d6ca Mon Sep 17 00:00:00 2001 From: tool4ever Date: Mon, 27 Oct 2025 10:11:46 +0100 Subject: [PATCH 185/230] Fix logic (#9008) --- .../forge/game/ability/effects/ChangeZoneEffect.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 a049b411baf..6577532f699 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 @@ -1297,7 +1297,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { moveParams.put(AbilityKey.FoundSearchingLibrary, searchedLibrary); AbilityKey.addCardZoneTableParams(moveParams, triggerList); - if (destination.equals(ZoneType.Library)) { + if (destination == null) { + movedCard = c; + } + else if (destination.equals(ZoneType.Library)) { movedCard = game.getAction().moveToLibrary(c, libraryPos, sa, moveParams); } else if (destination.equals(ZoneType.Battlefield)) { @@ -1423,9 +1426,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect { movedCard.addMayLookFaceDownExile(sa.getActivatingPlayer()); } } - else if (destination == null) { - movedCard = c; - } else { movedCard = game.getAction().moveTo(destination, c, 0, sa, moveParams); } @@ -1503,7 +1503,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { game.getAction().reveal(movedCards, player); } - if ((origin.contains(ZoneType.Library) && !destination.equals(ZoneType.Library) && !defined && shuffleMandatory) + if ((origin.contains(ZoneType.Library) && !ZoneType.Library.equals(destination) && !defined && shuffleMandatory) || (sa.hasParam("Shuffle") && "True".equals(sa.getParam("Shuffle")))) { player.shuffle(sa); } From 2ee356461cc09d4f3e398fc25968ae22a6fbd870 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Mon, 27 Oct 2025 16:38:09 +0100 Subject: [PATCH 186/230] Update fire_lord_ozai.txt Closes #9010 --- forge-gui/res/cardsfolder/f/fire_lord_ozai.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/f/fire_lord_ozai.txt b/forge-gui/res/cardsfolder/f/fire_lord_ozai.txt index 9320faf533f..201d5532850 100644 --- a/forge-gui/res/cardsfolder/f/fire_lord_ozai.txt +++ b/forge-gui/res/cardsfolder/f/fire_lord_ozai.txt @@ -11,7 +11,7 @@ SVar:Play1:Mode$ SpellCast | ValidCard$ Card.IsRemembered | ValidActivatingPlaye SVar:Play2:Mode$ LandPlayed | ValidCard$ Land.IsRemembered | TriggerZones$ Command | Execute$ ExileSelf | Static$ True SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Sacrificed$CardManaCost +SVar:X:Sacrificed$CardPower SVar:HasAttackEffect:TRUE DeckHas:Ability$Sacrifice Oracle:Whenever Fire Lord Ozai attacks, you may sacrifice another creature. If you do, add an amount of {R} equal to the sacrificed creature's power. Until end of combat, you don't lose this mana as steps end.\n{6}: Exile the top card of each opponent's library. Until end of turn, you may play one of those cards without paying its mana cost. From 5fc1d4214b5c169af9661061e84f84dd9c60ab0c Mon Sep 17 00:00:00 2001 From: tool4ever Date: Mon, 27 Oct 2025 18:03:15 +0100 Subject: [PATCH 187/230] Restore check to treat empty room as dynamic state (#9009) --- forge-game/src/main/java/forge/game/card/Card.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 9f8bd28db45..9fd05a88e70 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -557,18 +557,16 @@ public class Card extends GameEntity implements Comparable, IHasSVars, ITr boolean needsTransformAnimation = transform || rollback; // faceDown has higher priority over clone states // while text change states doesn't apply while the card is faceDown - if (state != CardStateName.FaceDown) { + if (state != CardStateName.FaceDown && state != CardStateName.EmptyRoom) { CardCloneStates cloneStates = getLastClonedState(); if (cloneStates != null) { if (!cloneStates.containsKey(state)) { throw new RuntimeException(getName() + " tried to switch to non-existant cloned state \"" + state + "\"!"); //return false; // Nonexistant state. } - } else { - if (!states.containsKey(state)) { - System.out.println(getName() + " tried to switch to non-existant state \"" + state + "\"!"); - return false; // Nonexistant state. - } + } else if (!states.containsKey(state)) { + System.out.println(getName() + " tried to switch to non-existant state \"" + state + "\"!"); + return false; // Nonexistant state. } } From 364845ab4ea4ba86a095a36b8fae7133ccd47aa6 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Mon, 27 Oct 2025 20:25:41 +0100 Subject: [PATCH 188/230] Fix Rock Hydra crash (#9011) --- .../forge/game/ability/effects/CountersRemoveEffect.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 41b4c840054..12406970c4d 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 @@ -3,6 +3,7 @@ package forge.game.ability.effects; import java.util.Map; import forge.game.card.*; +import forge.game.replacement.ReplacementType; import org.apache.commons.lang3.tuple.Pair; import com.google.common.collect.ImmutableList; @@ -105,12 +106,11 @@ public class CountersRemoveEffect extends SpellAbilityEffect { if (sa.hasParam("Choices")) { ZoneType choiceZone = sa.hasParam("ChoiceZone") ? ZoneType.smartValueOf(sa.getParam("ChoiceZone")) : ZoneType.Battlefield; - srcCards = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"), - activator, source, sa); + srcCards = CardLists.getValidCards(game.getCardsIn(choiceZone), sa.getParam("Choices"), activator, source, sa); } else { srcCards = getTargetCards(sa); } - if (sa.isReplacementAbility()) { + if (sa.isReplacementAbility() && sa.getReplacementEffect().getMode() == ReplacementType.Moved) { srcCards = new CardCollection(srcCards).filter(c -> !c.isInPlay() || sa.getLastStateBattlefield().contains(c)); } From 954031cdb22955df364a35471a527f3a0c09f26e Mon Sep 17 00:00:00 2001 From: tool4ever Date: Tue, 28 Oct 2025 05:26:18 +0100 Subject: [PATCH 189/230] Refactor AiProfile handling (#9012) Co-authored-by: tool4EvEr --- .../java/forge/ai/AiAttackController.java | 20 ++--- .../main/java/forge/ai/AiBlockController.java | 15 ++-- .../src/main/java/forge/ai/AiController.java | 30 ++----- .../src/main/java/forge/ai/AiProfileUtil.java | 18 ++++ .../src/main/java/forge/ai/ComputerUtil.java | 89 ++++++++----------- .../main/java/forge/ai/ComputerUtilCard.java | 22 ++--- .../java/forge/ai/ComputerUtilCombat.java | 13 +-- .../main/java/forge/ai/ComputerUtilCost.java | 2 +- .../java/forge/ai/PlayerControllerAi.java | 18 ++-- .../src/main/java/forge/ai/SpecialCardAi.java | 2 +- .../main/java/forge/ai/ability/AttachAi.java | 20 ++--- .../java/forge/ai/ability/ChangeZoneAi.java | 24 ++--- .../forge/ai/ability/ChangeZoneAllAi.java | 20 ++--- .../forge/ai/ability/CopySpellAbilityAi.java | 4 +- .../main/java/forge/ai/ability/CounterAi.java | 24 +++-- .../ai/ability/CountersProliferateAi.java | 2 +- .../java/forge/ai/ability/CountersPutAi.java | 8 +- .../java/forge/ai/ability/DamageDealAi.java | 19 ++-- .../main/java/forge/ai/ability/DestroyAi.java | 11 ++- .../main/java/forge/ai/ability/DrawAi.java | 2 +- .../main/java/forge/ai/ability/EndureAi.java | 2 +- .../main/java/forge/ai/ability/ExploreAi.java | 10 +-- .../ai/ability/LifeExchangeVariantAi.java | 6 +- .../forge/ai/ability/PermanentCreatureAi.java | 15 ++-- .../ai/ability/RearrangeTopOfLibraryAi.java | 9 +- .../forge/ai/ability/RollPlanarDiceAi.java | 9 +- .../main/java/forge/ai/ability/SurveilAi.java | 2 +- .../src/main/java/forge/ai/ability/TapAi.java | 3 +- .../main/java/forge/ai/ability/TokenAi.java | 13 +-- .../game/ability/effects/VentureEffect.java | 4 +- 30 files changed, 175 insertions(+), 261 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index b49c5d11ee4..a3c6dde0856 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -393,7 +393,7 @@ public class AiAttackController { if (ai.getController().isAI()) { PlayerControllerAi aic = ((PlayerControllerAi) ai.getController()); pilotsNonAggroDeck = aic.pilotsNonAggroDeck(); - playAggro = !pilotsNonAggroDeck || aic.getAi().getBooleanProperty(AiProps.PLAY_AGGRO); + playAggro = !pilotsNonAggroDeck || aic.getAi().getBoolProperty(AiProps.PLAY_AGGRO); } // TODO make switchable via AI property int thresholdMod = 0; @@ -614,13 +614,7 @@ public class AiAttackController { // if true, the AI will attempt to identify which blockers will already be taken, // thus attempting to predict how many creatures with evasion can actively block - boolean predictEvasion = false; - if (ai.getController().isAI()) { - AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); - if (aic.getBooleanProperty(AiProps.COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION)) { - predictEvasion = true; - } - } + boolean predictEvasion = AiProfileUtil.getBoolProperty(ai, AiProps.COMBAT_ASSAULT_ATTACK_EVASION_PREDICTION); CardCollection accountedBlockers = new CardCollection(this.blockers); CardCollection categorizedAttackers = new CardCollection(); @@ -860,12 +854,12 @@ public class AiAttackController { AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); simAI = aic.usesSimulation(); if (!simAI) { - playAggro = aic.getBooleanProperty(AiProps.PLAY_AGGRO); + playAggro = aic.getBoolProperty(AiProps.PLAY_AGGRO); chanceToAttackToTrade = aic.getIntProperty(AiProps.CHANCE_TO_ATTACK_INTO_TRADE); - tradeIfTappedOut = aic.getBooleanProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT); + tradeIfTappedOut = aic.getBoolProperty(AiProps.ATTACK_INTO_TRADE_WHEN_TAPPED_OUT); extraChanceIfOppHasMana = aic.getIntProperty(AiProps.CHANCE_TO_ATKTRADE_WHEN_OPP_HAS_MANA); - tradeIfLowerLifePressure = aic.getBooleanProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE); - predictEvasion = aic.getBooleanProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION); + tradeIfLowerLifePressure = aic.getBoolProperty(AiProps.RANDOMLY_ATKTRADE_ONLY_ON_LOWER_LIFE_PRESSURE); + predictEvasion = aic.getBoolProperty(AiProps.COMBAT_ATTRITION_ATTACK_EVASION_PREDICTION); } } @@ -1423,7 +1417,7 @@ public class AiAttackController { // Check if maybe we are too reckless in adding this attacker if (canKillAllDangerous) { boolean avoidAttackingIntoBlock = ai.getController().isAI() - && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK); + && ((PlayerControllerAi) ai.getController()).getAi().getBoolProperty(AiProps.TRY_TO_AVOID_ATTACKING_INTO_CERTAIN_BLOCK); boolean attackerWillDie = defPower >= attacker.getNetToughness(); boolean uselessAttack = !hasCombatEffect && !hasAttackEffect; boolean noContributionToAttack = attackers.size() <= defenders.size() || attacker.getNetPower() <= 0; diff --git a/forge-ai/src/main/java/forge/ai/AiBlockController.java b/forge-ai/src/main/java/forge/ai/AiBlockController.java index e8a3933dac1..d8a8ada92e6 100644 --- a/forge-ai/src/main/java/forge/ai/AiBlockController.java +++ b/forge-ai/src/main/java/forge/ai/AiBlockController.java @@ -861,10 +861,9 @@ public class AiBlockController { return; } - AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); - final int evalThresholdToken = aic.getIntProperty(AiProps.THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER); - final int evalThresholdNonToken = aic.getIntProperty(AiProps.THRESHOLD_NONTOKEN_CHUMP_TO_SAVE_PLANESWALKER); - final boolean onlyIfLethal = aic.getBooleanProperty(AiProps.CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL); + final int evalThresholdToken = AiProfileUtil.getIntProperty(ai, AiProps.THRESHOLD_TOKEN_CHUMP_TO_SAVE_PLANESWALKER); + final int evalThresholdNonToken = AiProfileUtil.getIntProperty(ai, AiProps.THRESHOLD_NONTOKEN_CHUMP_TO_SAVE_PLANESWALKER); + final boolean onlyIfLethal = AiProfileUtil.getBoolProperty(ai, AiProps.CHUMP_TO_SAVE_PLANESWALKER_ONLY_ON_LETHAL); if (evalThresholdToken > 0 || evalThresholdNonToken > 0) { // detect how much damage is threatened to each of the planeswalkers, see which ones would be @@ -1047,7 +1046,7 @@ public class AiBlockController { clearBlockers(combat, possibleBlockers); diff = (ai.getLife() * 2) - 5; // This is the minimal gain for an unnecessary trade - if (ai.getController().isAI() && diff > 0 && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO)) { + if (diff > 0 && AiProfileUtil.getBoolProperty(ai, AiProps.PLAY_AGGRO)) { diff = 0; } @@ -1284,9 +1283,9 @@ public class AiBlockController { AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); // simulation must get same results or it may crash if (!aic.usesSimulation()) { - enableRandomTrades = aic.getBooleanProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK); - randomTradeIfBehindOnBoard = aic.getBooleanProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS); - randomTradeIfCreatInHand = aic.getBooleanProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT); + enableRandomTrades = aic.getBoolProperty(AiProps.ENABLE_RANDOM_FAVORABLE_TRADES_ON_BLOCK); + randomTradeIfBehindOnBoard = aic.getBoolProperty(AiProps.RANDOMLY_TRADE_EVEN_WHEN_HAVE_LESS_CREATS); + randomTradeIfCreatInHand = aic.getBoolProperty(AiProps.ALSO_TRADE_WHEN_HAVE_A_REPLACEMENT_CREAT); minRandomTradeChance = aic.getIntProperty(AiProps.MIN_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK); maxRandomTradeChance = aic.getIntProperty(AiProps.MAX_CHANCE_TO_RANDOMLY_TRADE_ON_BLOCK); chanceModForEmbalm = aic.getIntProperty(AiProps.CHANCE_DECREASE_TO_TRADE_VS_EMBALM); diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 1430d8285bf..1d8acfcbc68 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -764,7 +764,7 @@ public class AiController { return predictSpellToCastInMain2(exceptSA, true); } private SpellAbility predictSpellToCastInMain2(ApiType exceptSA, boolean handOnly) { - if (!getBooleanProperty(AiProps.PREDICT_SPELLS_FOR_MAIN2)) { + if (!getBoolProperty(AiProps.PREDICT_SPELLS_FOR_MAIN2)) { return null; } @@ -930,7 +930,7 @@ public class AiController { final Card card = sa.getHostCard(); // Trying to play a card that has Buyback without a Buyback cost, look for possible additional considerations - if (getBooleanProperty(AiProps.TRY_TO_PRESERVE_BUYBACK_SPELLS)) { + if (getBoolProperty(AiProps.TRY_TO_PRESERVE_BUYBACK_SPELLS)) { if (card.hasKeyword(Keyword.BUYBACK) && !sa.isBuyback() && !canPlaySpellWithoutBuyback(card, sa)) { return AiPlayDecision.NeedsToPlayCriteriaNotMet; } @@ -1299,27 +1299,13 @@ public class AiController { } public String getProperty(AiProps propName) { - return AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName); + return AiProfileUtil.getProperty(getPlayer(), propName); } - public int getIntProperty(AiProps propName) { - String prop = AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName); - - if (prop == null || prop.isEmpty()) { - return Integer.parseInt(propName.getDefault()); - } - - return Integer.parseInt(prop); + return AiProfileUtil.getIntProperty(getPlayer(), propName); } - - public boolean getBooleanProperty(AiProps propName) { - String prop = AiProfileUtil.getAIProp(getPlayer().getLobbyPlayer(), propName); - - if (prop == null || prop.isEmpty()) { - return Boolean.parseBoolean(propName.getDefault()); - } - - return Boolean.parseBoolean(prop); + public boolean getBoolProperty(AiProps propName) { + return AiProfileUtil.getBoolProperty(getPlayer(), propName); } public AiPlayDecision canPlayFromEffectAI(Spell spell, boolean mandatory, boolean withoutPayingManaCost) { @@ -1330,7 +1316,7 @@ public class AiController { final Card card = spell.getHostCard(); if (spell instanceof SpellApiBased) { - boolean chance = false; + boolean chance; if (withoutPayingManaCost) { chance = SpellApiToAi.Converter.get(spell).doTriggerNoCostWithSubs(player, spell, mandatory).willingToPlay(); } else { @@ -1468,7 +1454,7 @@ public class AiController { CardCollection inHand = CardLists.filter(player.getCardsIn(ZoneType.Hand), CardPredicates.NON_LANDS); CardCollectionView otb = player.getCardsIn(ZoneType.Battlefield); - if (getBooleanProperty(AiProps.HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS)) { + if (getBoolProperty(AiProps.HOLD_LAND_DROP_ONLY_IF_HAVE_OTHER_PERMS)) { if (!otb.anyMatch(CardPredicates.NON_LANDS)) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/AiProfileUtil.java b/forge-ai/src/main/java/forge/ai/AiProfileUtil.java index 5fe94c6e22c..03ab37f9654 100644 --- a/forge-ai/src/main/java/forge/ai/AiProfileUtil.java +++ b/forge-ai/src/main/java/forge/ai/AiProfileUtil.java @@ -18,6 +18,7 @@ package forge.ai; import forge.LobbyPlayer; +import forge.game.player.Player; import forge.util.Aggregates; import forge.util.FileUtil; import forge.util.TextUtil; @@ -113,6 +114,23 @@ public class AiProfileUtil { return profileMap; } + public static String getProperty(final Player p, final AiProps propName) { + String prop = AiProfileUtil.getAIProp(p.getLobbyPlayer(), propName); + + if (prop == null || prop.isEmpty()) { + // TODO if p is human try to predict some values from previous plays or something + return propName.getDefault(); + } + + return prop; + } + public static int getIntProperty(final Player p, final AiProps propName) { + return Integer.parseInt(getProperty(p, propName)); + } + public static boolean getBoolProperty(final Player p, final AiProps propName) { + return Boolean.parseBoolean(getProperty(p, propName)); + } + /** * Returns an AI property value for the current profile. * diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index bad4b3c3bbc..c88cc23f1a1 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -345,27 +345,24 @@ public class ComputerUtil { return sacMeList.getFirst(); } else { // empty sacMeList, so get some viable average preference if the option is enabled - if (ai.getController().isAI()) { - AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); - boolean enableDefaultPref = aic.getBooleanProperty(AiProps.SACRIFICE_DEFAULT_PREF_ENABLE); - if (enableDefaultPref) { - int minCMC = aic.getIntProperty(AiProps.SACRIFICE_DEFAULT_PREF_MIN_CMC); - int maxCMC = aic.getIntProperty(AiProps.SACRIFICE_DEFAULT_PREF_MAX_CMC); - int maxCreatureEval = aic.getIntProperty(AiProps.SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL); - boolean allowTokens = aic.getBooleanProperty(AiProps.SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS); - List dontSac = Arrays.asList("Black Lotus", "Mox Pearl", "Mox Jet", "Mox Emerald", "Mox Ruby", "Mox Sapphire", "Lotus Petal"); - CardCollection allowList = CardLists.filter(typeList, card -> { - if (card.isCreature() && ComputerUtilCard.evaluateCreature(card) > maxCreatureEval) { - return false; - } - - return (allowTokens && card.isToken()) - || (card.getCMC() >= minCMC && card.getCMC() <= maxCMC && !dontSac.contains(card.getName())); - }); - if (!allowList.isEmpty()) { - CardLists.sortByCmcDesc(allowList); - return allowList.getLast(); + boolean enableDefaultPref = AiProfileUtil.getBoolProperty(ai, AiProps.SACRIFICE_DEFAULT_PREF_ENABLE); + if (enableDefaultPref) { + int minCMC = AiProfileUtil.getIntProperty(ai, AiProps.SACRIFICE_DEFAULT_PREF_MIN_CMC); + int maxCMC = AiProfileUtil.getIntProperty(ai, AiProps.SACRIFICE_DEFAULT_PREF_MAX_CMC); + int maxCreatureEval = AiProfileUtil.getIntProperty(ai, AiProps.SACRIFICE_DEFAULT_PREF_MAX_CREATURE_EVAL); + boolean allowTokens = AiProfileUtil.getBoolProperty(ai, AiProps.SACRIFICE_DEFAULT_PREF_ALLOW_TOKENS); + List dontSac = Arrays.asList("Black Lotus", "Mox Pearl", "Mox Jet", "Mox Emerald", "Mox Ruby", "Mox Sapphire", "Lotus Petal"); + CardCollection allowList = CardLists.filter(typeList, card -> { + if (card.isCreature() && ComputerUtilCard.evaluateCreature(card) > maxCreatureEval) { + return false; } + + return (allowTokens && card.isToken()) + || (card.getCMC() >= minCMC && card.getCMC() <= maxCMC && !dontSac.contains(card.getName())); + }); + if (!allowList.isEmpty()) { + CardLists.sortByCmcDesc(allowList); + return allowList.getLast(); } } } @@ -1999,8 +1996,7 @@ public class ComputerUtil { else if ((threatApi == ApiType.Attach && (topStack.isCurse() || "Curse".equals(topStack.getParam("AILogic")))) && (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll || saviourApi == ApiType.Protection || saviourApi == null)) { - AiController aic = aiPlayer.isAI() ? ((PlayerControllerAi)aiPlayer.getController()).getAi() : null; - boolean enableCurseAuraRemoval = aic != null ? aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE) : false; + boolean enableCurseAuraRemoval = AiProfileUtil.getBoolProperty(aiPlayer, AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE); if (enableCurseAuraRemoval) { for (final Object o : objects) { if (o instanceof Card c) { @@ -2041,17 +2037,14 @@ public class ComputerUtil { // a creature will [hopefully] die from a spell on stack boolean willDieFromSpell = false; boolean noStackCheck = false; - if (ai.getController().isAI()) { - AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); - if (aic.getBooleanProperty(AiProps.DONT_EVAL_KILLSPELLS_ON_STACK_WITH_PERMISSION)) { - // See if permission is on stack and ignore this check if there is and the relevant AI flag is set - // TODO: improve this so that this flag is not needed and the AI can properly evaluate spells in presence of counterspells. - for (SpellAbilityStackInstance si : game.getStack()) { - SpellAbility sa = si.getSpellAbility(); - if (sa.getApi() == ApiType.Counter) { - noStackCheck = true; - break; - } + if (AiProfileUtil.getBoolProperty(ai, AiProps.DONT_EVAL_KILLSPELLS_ON_STACK_WITH_PERMISSION)) { + // See if permission is on stack and ignore this check if there is and the relevant AI flag is set + // TODO: improve this so that this flag is not needed and the AI can properly evaluate spells in presence of counterspells. + for (SpellAbilityStackInstance si : game.getStack()) { + SpellAbility sa = si.getSpellAbility(); + if (sa.getApi() == ApiType.Counter) { + noStackCheck = true; + break; } } } @@ -2080,8 +2073,7 @@ public class ComputerUtil { * @return a filtered list with no dying creatures in it */ public static CardCollection filterCreaturesThatWillDieThisTurn(final Player ai, final CardCollection list, final SpellAbility excludeSa) { - AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); - if (aic.getBooleanProperty(AiProps.AVOID_TARGETING_CREATS_THAT_WILL_DIE)) { + if (AiProfileUtil.getBoolProperty(ai, AiProps.AVOID_TARGETING_CREATS_THAT_WILL_DIE)) { // Try to avoid targeting creatures that are dead on board List willBeKilled = CardLists.filter(list, card -> card.isCreature() && predictCreatureWillDieThisTurn(ai, card, excludeSa)); list.removeAll(willBeKilled); @@ -2254,25 +2246,14 @@ public class ComputerUtil { boolean bottom = false; // AI profile-based toggles - int maxLandsToScryLandsToTop = 3; - int minLandsToScryLandsAway = 8; - int minCreatsToScryCreatsAway = 5; - int minCreatEvalThreshold = 160; // just a bit higher than a baseline 2/2 creature or a 1/1 mana dork - int lowCMCThreshold = 3; - int maxCreatsToScryLowCMCAway = 3; - boolean uncastablesToBottom = false; - int uncastableCMCThreshold = 1; - if (player.getController().isAI()) { - AiController aic = ((PlayerControllerAi)player.getController()).getAi(); - maxLandsToScryLandsToTop = aic.getIntProperty(AiProps.SCRY_NUM_LANDS_TO_STILL_NEED_MORE); - minLandsToScryLandsAway = aic.getIntProperty(AiProps.SCRY_NUM_LANDS_TO_NOT_NEED_MORE); - minCreatsToScryCreatsAway = aic.getIntProperty(AiProps.SCRY_NUM_CREATURES_TO_NOT_NEED_SUBPAR_ONES); - minCreatEvalThreshold = aic.getIntProperty(AiProps.SCRY_EVALTHR_TO_SCRY_AWAY_LOWCMC_CREATURE); - lowCMCThreshold = aic.getIntProperty(AiProps.SCRY_EVALTHR_CMC_THRESHOLD); - maxCreatsToScryLowCMCAway = aic.getIntProperty(AiProps.SCRY_EVALTHR_CREATCOUNT_TO_SCRY_AWAY_LOWCMC); - uncastablesToBottom = aic.getBooleanProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM); - uncastableCMCThreshold = aic.getIntProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF); - } + int maxLandsToScryLandsToTop = AiProfileUtil.getIntProperty(player, AiProps.SCRY_NUM_LANDS_TO_STILL_NEED_MORE); + int minLandsToScryLandsAway = AiProfileUtil.getIntProperty(player, AiProps.SCRY_NUM_LANDS_TO_NOT_NEED_MORE); + int minCreatsToScryCreatsAway = AiProfileUtil.getIntProperty(player, AiProps.SCRY_NUM_CREATURES_TO_NOT_NEED_SUBPAR_ONES); + int minCreatEvalThreshold = AiProfileUtil.getIntProperty(player, AiProps.SCRY_EVALTHR_TO_SCRY_AWAY_LOWCMC_CREATURE); + int lowCMCThreshold = AiProfileUtil.getIntProperty(player, AiProps.SCRY_EVALTHR_CMC_THRESHOLD); + int maxCreatsToScryLowCMCAway = AiProfileUtil.getIntProperty(player, AiProps.SCRY_EVALTHR_CREATCOUNT_TO_SCRY_AWAY_LOWCMC); + boolean uncastablesToBottom = AiProfileUtil.getBoolProperty(player, AiProps.SCRY_IMMEDIATELY_UNCASTABLE_TO_BOTTOM); + int uncastableCMCThreshold = AiProfileUtil.getIntProperty(player, AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF); CardCollectionView allCards = player.getAllCards(); CardCollectionView cardsInHand = player.getCardsIn(ZoneType.Hand); diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 37d3f51d887..b7e81dc00ba 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -1060,7 +1060,6 @@ public class ComputerUtilCard { public static boolean useRemovalNow(final SpellAbility sa, final Card c, final int dmg, ZoneType destination) { final Player ai = sa.getActivatingPlayer(); - final AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); final Game game = ai.getGame(); final PhaseHandler ph = game.getPhaseHandler(); final PhaseType phaseType = ph.getPhase(); @@ -1207,7 +1206,7 @@ public class ComputerUtilCard { } } else if (c.isPlaneswalker()) { threat = 1; - } else if (aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS) && ((c.isArtifact() && !c.isCreature()) || (c.isEnchantment() && !c.isAura()))) { + } else if (AiProfileUtil.getBoolProperty(ai, AiProps.ACTIVELY_DESTROY_ARTS_AND_NONAURA_ENCHS) && ((c.isArtifact() && !c.isCreature()) || (c.isEnchantment() && !c.isAura()))) { // non-creature artifacts and global enchantments with suspicious intrinsic abilities boolean priority = false; if (c.getOwner().isOpponentOf(ai) && c.getController().isOpponentOf(ai)) { @@ -1311,7 +1310,7 @@ public class ComputerUtilCard { AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); simAI = aic.usesSimulation(); if (!simAI) { - holdCombatTricks = aic.getBooleanProperty(AiProps.TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK); + holdCombatTricks = aic.getBoolProperty(AiProps.TRY_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK); chanceToHoldCombatTricks = aic.getIntProperty(AiProps.CHANCE_TO_HOLD_COMBAT_TRICKS_UNTIL_BLOCK); } } @@ -1640,7 +1639,7 @@ public class ComputerUtilCard { // if we got here, Berserk will result in the pumped creature dying at EOT and the opponent will not lose // (other similar cards with AILogic$ Berserk that do not die only when attacking are excluded from consideration) if (ai.getController() instanceof PlayerControllerAi) { - boolean aggr = ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.USE_BERSERK_AGGRESSIVELY) + boolean aggr = ((PlayerControllerAi) ai.getController()).getAi().getBoolProperty(AiProps.USE_BERSERK_AGGRESSIVELY) || sa.hasParam("AtEOT"); if (!aggr) { return false; @@ -1907,17 +1906,10 @@ public class ComputerUtilCard { return oppCards; } - boolean enablePriorityRemoval = false; - boolean priorityRemovalOnlyInDanger = false; - int priorityRemovalThreshold = 0; - int lifeInDanger = 5; - if (ai.getController().isAI()) { - AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); - enablePriorityRemoval = aic.getBooleanProperty(AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE); - priorityRemovalThreshold = aic.getIntProperty(AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD); - priorityRemovalOnlyInDanger = aic.getBooleanProperty(AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR); - lifeInDanger = aic.getIntProperty(AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR); - } + boolean enablePriorityRemoval = AiProfileUtil.getBoolProperty(ai, AiProps.ACTIVELY_DESTROY_IMMEDIATELY_UNBLOCKABLE); + int priorityRemovalThreshold = AiProfileUtil.getIntProperty(ai, AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_THRESHOLD); + boolean priorityRemovalOnlyInDanger = AiProfileUtil.getBoolProperty(ai, AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_ONLY_IN_DNGR); + int lifeInDanger = AiProfileUtil.getIntProperty(ai, AiProps.DESTROY_IMMEDIATELY_UNBLOCKABLE_LIFE_IN_DNGR); if (!enablePriorityRemoval) { // Nothing to do here, the profile does not allow prioritizing diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index 978f55e9625..4f59b5eaef6 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -443,13 +443,12 @@ public class ComputerUtilCombat { } } - int threshold = 0; - int maxTreshold = 0; - if (ai.getController().isAI()) { - threshold = ((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD); - maxTreshold = ((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_MAX_THRESHOLD) - threshold; + if (resultingPoison(ai, combat) > Math.max(7, ai.getPoisonCounters())) { + return true; } + int threshold = AiProfileUtil.getIntProperty(ai, AiProps.AI_IN_DANGER_THRESHOLD); + int maxTreshold = AiProfileUtil.getIntProperty(ai, AiProps.AI_IN_DANGER_MAX_THRESHOLD) - threshold; int chance = MyRandom.getRandom().nextInt(80) + 5; while (maxTreshold > 0) { if (MyRandom.getRandom().nextInt(100) < chance) { @@ -458,10 +457,6 @@ public class ComputerUtilCombat { maxTreshold--; } - if (resultingPoison(ai, combat) > Math.max(7, ai.getPoisonCounters())) { - return true; - } - return !ai.cantLoseForZeroOrLessLife() && lifeThatWouldRemain(ai, combat) - payment < Math.min(threshold, ai.getLife()); } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index 556fdac384a..e657d9a5ce7 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -356,7 +356,7 @@ public class ComputerUtilCost { final boolean beforeNextTurn = ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) && ai.getGame().getPhaseHandler().getNextTurn().equals(ai) && ComputerUtilCard.evaluateCreature(source) <= 150; final boolean creatureInDanger = ComputerUtil.predictCreatureWillDieThisTurn(ai, source, sourceAbility, false) && !ComputerUtilCombat.willOpposingCreatureDieInCombat(ai, source, combat); - final int lifeThreshold = ai.getController().isAI() ? (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD)) : 4; + final int lifeThreshold = AiProfileUtil.getIntProperty(ai, AiProps.AI_IN_DANGER_THRESHOLD); final boolean aiInDanger = ai.getLife() <= lifeThreshold && ai.canLoseLife() && !ai.cantLoseForZeroOrLessLife(); if (creatureInDanger && !ComputerUtilCombat.isDangerousToSacInCombat(ai, source, combat)) { return true; diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 5276b419e5f..53336b7b63a 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -139,9 +139,9 @@ public class PlayerControllerAi extends PlayerController { } } - boolean sbLimitedFormats = getAi().getBooleanProperty(AiProps.SIDEBOARDING_IN_LIMITED_FORMATS); - boolean sbSharedTypesOnly = getAi().getBooleanProperty(AiProps.SIDEBOARDING_SHARED_TYPE_ONLY); - boolean sbPlaneswalkerException = getAi().getBooleanProperty(AiProps.SIDEBOARDING_PLANESWALKER_EQ_CREATURE); + boolean sbLimitedFormats = getAi().getBoolProperty(AiProps.SIDEBOARDING_IN_LIMITED_FORMATS); + boolean sbSharedTypesOnly = getAi().getBoolProperty(AiProps.SIDEBOARDING_SHARED_TYPE_ONLY); + boolean sbPlaneswalkerException = getAi().getBoolProperty(AiProps.SIDEBOARDING_PLANESWALKER_EQ_CREATURE); int sbChanceOnWin = getAi().getIntProperty(AiProps.SIDEBOARDING_CHANCE_ON_WIN); int sbChancePerCard = getAi().getIntProperty(AiProps.SIDEBOARDING_CHANCE_PER_CARD); @@ -1372,7 +1372,7 @@ public class PlayerControllerAi extends PlayerController { @Override public CardCollectionView cheatShuffle(CardCollectionView list) { - return brains.getBooleanProperty(AiProps.CHEAT_WITH_MANA_ON_SHUFFLE) ? brains.cheatShuffle(list) : list; + return brains.getBoolProperty(AiProps.CHEAT_WITH_MANA_ON_SHUFFLE) ? brains.cheatShuffle(list) : list; } @Override @@ -1544,12 +1544,10 @@ public class PlayerControllerAi extends PlayerController { } // Don't choose Tomb of Annihilation when life in danger unless we can win right away or can't lose for 0 life - if (ai.getController().isAI()) { // FIXME: is this needed? Can simulation ever run this for a non-AI player? - int lifeInDanger = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD)); - if ((ai.getLife() <= lifeInDanger && !ai.cantLoseForZeroOrLessLife()) - && !(ai.getLife() > 1 && ai.getWeakestOpponent().getLife() == 1)) { - dungeonNames.remove("Tomb of Annihilation"); - } + int lifeInDanger = AiProfileUtil.getIntProperty(player, AiProps.AI_IN_DANGER_THRESHOLD); + if ((ai.getLife() <= lifeInDanger && !ai.cantLoseForZeroOrLessLife()) + && !(ai.getLife() > 1 && ai.getWeakestOpponent().getLife() == 1)) { + dungeonNames.remove("Tomb of Annihilation"); } try { diff --git a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java index 6caeb28aa86..eb056144291 100644 --- a/forge-ai/src/main/java/forge/ai/SpecialCardAi.java +++ b/forge-ai/src/main/java/forge/ai/SpecialCardAi.java @@ -798,7 +798,7 @@ public class SpecialCardAi { public static class Intuition { public static CardCollection considerMultiple(final Player ai, final SpellAbility sa) { if (ai.getController().isAI()) { - if (!((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.INTUITION_ALTERNATIVE_LOGIC)) { + if (!((PlayerControllerAi) ai.getController()).getAi().getBoolProperty(AiProps.INTUITION_ALTERNATIVE_LOGIC)) { return new CardCollection(); // fall back to standard ChangeZoneAi considerations } } diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java index 49fad8a85c1..b5d65b29b77 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java @@ -73,11 +73,8 @@ public class AttachAi extends SpellAbilityAi { } } - // Flash logic - boolean advancedFlash = false; - if (ai.getController().isAI()) { - advancedFlash = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.FLASH_ENABLE_ADVANCED_LOGIC); - } + boolean advancedFlash = AiProfileUtil.getBoolProperty(ai, AiProps.FLASH_ENABLE_ADVANCED_LOGIC); + if ((source.hasKeyword(Keyword.FLASH) || (!ai.canCastSorcery() && sa.canCastTiming(ai))) && source.isAura() && advancedFlash && !doAdvancedFlashAuraLogic(ai, sa, sa.getTargetCard())) { return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); @@ -108,9 +105,8 @@ public class AttachAi extends SpellAbilityAi { Card source = sa.getHostCard(); Game game = ai.getGame(); Combat combat = game.getCombat(); - AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); - if (!aic.getBooleanProperty(AiProps.FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS)) { + if (!AiProfileUtil.getBoolProperty(ai, AiProps.FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS)) { // Currently this only works with buff auras, so if the relevant toggle is disabled, just return true // for instant speed use. To be improved later. return true; @@ -190,9 +186,9 @@ public class AttachAi extends SpellAbilityAi { return false; } - int chanceToCastAtEOT = aic.getIntProperty(AiProps.FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT); - int chanceToCastEarly = aic.getIntProperty(AiProps.FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY); - int chanceToRespondToStack = aic.getIntProperty(AiProps.FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK); + int chanceToCastAtEOT = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT); + int chanceToCastEarly = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY); + int chanceToRespondToStack = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK); boolean hasFloatMana = ai.getManaPool().totalMana() > 0; boolean willDiscardNow = game.getPhaseHandler().is(PhaseType.END_OF_TURN, ai) @@ -912,7 +908,7 @@ public class AttachAi extends SpellAbilityAi { if (sa.getHostCard().getAttachedTo() != null && sa.getHostCard().getAttachedTo().isCreature() && sa.getPayCosts() != null && sa.getPayCosts().hasSpecificCostType(CostSacrifice.class)) { final int oldEvalRating = ComputerUtilCard.evaluateCreature(sa.getHostCard().getAttachedTo()); - final int threshold = ai.isAI() ? ((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD) : Integer.MAX_VALUE; + final int threshold = AiProfileUtil.getIntProperty(ai, AiProps.SAC_TO_REATTACH_TARGET_EVAL_THRESHOLD); prefList = CardLists.filter(prefList, c -> { if (!c.isCreature()) { return false; @@ -1388,7 +1384,7 @@ public class AttachAi extends SpellAbilityAi { } // make sure to prioritize casting spells in main 2 (creatures, other equipment, etc.) rather than moving equipment around - boolean decideMoveFromUseless = uselessCreature && aic.getBooleanProperty(AiProps.PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS); + boolean decideMoveFromUseless = uselessCreature && aic.getBoolProperty(AiProps.PRIORITIZE_MOVE_EQUIPMENT_IF_USELESS); if (!decideMoveFromUseless && AiCardMemory.isMemorySetEmpty(aiPlayer, AiCardMemory.MemorySet.HELD_MANA_SOURCES_FOR_MAIN2)) { SpellAbility futureSpell = aic.predictSpellToCastInMain2(ApiType.Attach); diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 0753244b0ce..25d0213930d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -1300,15 +1300,9 @@ public class ChangeZoneAi extends SpellAbilityAi { } // Reload planeswalkers else if (!aiPlaneswalkers.isEmpty() && (sa.getHostCard().isSorcery() || !game.getPhaseHandler().isPlayerTurn(ai))) { - int maxLoyaltyToConsider = 2; - int loyaltyDiff = 2; - int chance = 30; - if (ai.getController().isAI()) { - AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); - maxLoyaltyToConsider = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY); - loyaltyDiff = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF); - chance = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE); - } + int maxLoyaltyToConsider = AiProfileUtil.getIntProperty(ai, AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY); + int loyaltyDiff = AiProfileUtil.getIntProperty(ai, AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF); + int chance = AiProfileUtil.getIntProperty(ai, AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE); if (MyRandom.percentTrue(chance)) { aiPlaneswalkers.sort(CardPredicates.compareByCounterType(CounterEnumType.LOYALTY)); for (Card pw : aiPlaneswalkers) { @@ -1670,15 +1664,9 @@ public class ChangeZoneAi extends SpellAbilityAi { } if (card.hasCounters()) { if (card.isPlaneswalker()) { - int maxLoyaltyToConsider = 2; - int loyaltyDiff = 2; - int chance = 30; - if (decider.getController().isAI()) { - AiController aic = ((PlayerControllerAi) decider.getController()).getAi(); - maxLoyaltyToConsider = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY); - loyaltyDiff = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF); - chance = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE); - } + int maxLoyaltyToConsider = AiProfileUtil.getIntProperty(decider, AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY); + int loyaltyDiff = AiProfileUtil.getIntProperty(decider, AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF); + int chance = AiProfileUtil.getIntProperty(decider, AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE); if (MyRandom.percentTrue(chance)) { int curLoyalty = card.getCounters(CounterEnumType.LOYALTY); int freshLoyalty = Integer.parseInt(card.getCurrentState().getBaseLoyalty()); diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java index 4157894e361..4e50c772f70 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java @@ -140,17 +140,15 @@ public class ChangeZoneAllAi extends SpellAbilityAi { } computerType = new CardCollection(); } - int creatureEvalThreshold = 200; // value difference (in evaluateCreatureList units) - int nonCreatureEvalThreshold = 3; // CMC difference - if (ai.getController().isAI()) { - AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); - if (destination == ZoneType.Hand) { - creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF); - nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF); - } else { - creatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF); - nonCreatureEvalThreshold = aic.getIntProperty(AiProps.BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF); - } + + int creatureEvalThreshold; // value difference (in evaluateCreatureList units) + int nonCreatureEvalThreshold; // CMC difference + if (destination == ZoneType.Hand) { + creatureEvalThreshold = AiProfileUtil.getIntProperty(ai, AiProps.BOUNCE_ALL_TO_HAND_CREAT_EVAL_DIFF); + nonCreatureEvalThreshold = AiProfileUtil.getIntProperty(ai, AiProps.BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF); + } else { + creatureEvalThreshold = AiProfileUtil.getIntProperty(ai, AiProps.BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF); + nonCreatureEvalThreshold = AiProfileUtil.getIntProperty(ai, AiProps.BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF); } // mass zone change for creatures: if in dire danger, do it; otherwise, only do it if the opponent's diff --git a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java index d238bc9fe08..d3965115e49 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CopySpellAbilityAi.java @@ -19,8 +19,8 @@ public class CopySpellAbilityAi extends SpellAbilityAi { @Override protected AiAbilityDecision checkApiLogic(Player aiPlayer, SpellAbility sa) { Game game = aiPlayer.getGame(); - int chance = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK); - int diff = ((PlayerControllerAi)aiPlayer.getController()).getAi().getIntProperty(AiProps.ALWAYS_COPY_SPELL_IF_CMC_DIFF); + int chance = AiProfileUtil.getIntProperty(aiPlayer, AiProps.CHANCE_TO_COPY_OWN_SPELL_WHILE_ON_STACK); + int diff = AiProfileUtil.getIntProperty(aiPlayer, AiProps.ALWAYS_COPY_SPELL_IF_CMC_DIFF); String logic = sa.getParamOrDefault("AILogic", ""); if (game.getStack().isEmpty()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java index f8c6060117a..31d41c6e110 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CounterAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CounterAi.java @@ -148,18 +148,16 @@ public class CounterAi extends SpellAbilityAi { } // Specific constraints for the AI to use/not use counterspells against specific groups of spells - // (specified in the AI profile) - AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); - boolean ctrCmc0ManaPerms = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS); - boolean ctrDamageSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_DAMAGE_SPELLS); - boolean ctrRemovalSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_REMOVAL_SPELLS); - boolean ctrPumpSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_PUMP_SPELLS); - boolean ctrAuraSpells = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_AURAS); - boolean ctrOtherCounters = aic.getBooleanProperty(AiProps.ALWAYS_COUNTER_OTHER_COUNTERSPELLS); - int ctrChanceCMC1 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_1); - int ctrChanceCMC2 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_2); - int ctrChanceCMC3 = aic.getIntProperty(AiProps.CHANCE_TO_COUNTER_CMC_3); - String ctrNamed = aic.getProperty(AiProps.ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS); + boolean ctrCmc0ManaPerms = AiProfileUtil.getBoolProperty(ai, AiProps.ALWAYS_COUNTER_CMC_0_MANA_MAKING_PERMS); + boolean ctrDamageSpells = AiProfileUtil.getBoolProperty(ai, AiProps.ALWAYS_COUNTER_DAMAGE_SPELLS); + boolean ctrRemovalSpells = AiProfileUtil.getBoolProperty(ai, AiProps.ALWAYS_COUNTER_REMOVAL_SPELLS); + boolean ctrPumpSpells = AiProfileUtil.getBoolProperty(ai, AiProps.ALWAYS_COUNTER_PUMP_SPELLS); + boolean ctrAuraSpells = AiProfileUtil.getBoolProperty(ai, AiProps.ALWAYS_COUNTER_AURAS); + boolean ctrOtherCounters = AiProfileUtil.getBoolProperty(ai, AiProps.ALWAYS_COUNTER_OTHER_COUNTERSPELLS); + int ctrChanceCMC1 = AiProfileUtil.getIntProperty(ai, AiProps.CHANCE_TO_COUNTER_CMC_1); + int ctrChanceCMC2 = AiProfileUtil.getIntProperty(ai, AiProps.CHANCE_TO_COUNTER_CMC_2); + int ctrChanceCMC3 = AiProfileUtil.getIntProperty(ai, AiProps.CHANCE_TO_COUNTER_CMC_3); + String ctrNamed = AiProfileUtil.getProperty(ai, AiProps.ALWAYS_COUNTER_SPELLS_FROM_NAMED_CARDS); boolean dontCounter = false; if (tgtCMC == 1 && !MyRandom.percentTrue(ctrChanceCMC1)) { @@ -170,7 +168,7 @@ public class CounterAi extends SpellAbilityAi { dontCounter = true; } - if (tgtSA != null && tgtCMC < aic.getIntProperty(AiProps.MIN_SPELL_CMC_TO_COUNTER)) { + if (tgtSA != null && tgtCMC < AiProfileUtil.getIntProperty(ai, AiProps.MIN_SPELL_CMC_TO_COUNTER)) { dontCounter = true; Card tgtSource = tgtSA.getHostCard(); if ((tgtSource != null && tgtCMC == 0 && tgtSource.isPermanent() && !tgtSource.getManaAbilities().isEmpty() && ctrCmc0ManaPerms) diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersProliferateAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersProliferateAi.java index ef11dc61c80..7b18cbe1da8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersProliferateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersProliferateAi.java @@ -112,7 +112,7 @@ public class CountersProliferateAi extends SpellAbilityAi { final CounterType poison = CounterEnumType.POISON; - boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO); + boolean aggroAI = AiProfileUtil.getBoolProperty(ai, AiProps.PLAY_AGGRO); // because countertype can't be chosen anymore, only look for poison counters for (final Player p : IterableUtil.filter(options, Player.class)) { if (p.isOpponentOf(ai)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index b697bbc3445..4e22ce5b56b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -141,7 +141,7 @@ public class CountersPutAi extends CountersAi { final boolean isClockwork = "True".equals(sa.getParam("UpTo")) && "Self".equals(sa.getParam("Defined")) && "P1P0".equals(sa.getParam("CounterType")) && "Count$xPaid".equals(source.getSVar("X")) && sa.hasParam("MaxFromEffect"); - boolean playAggro = ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.PLAY_AGGRO); + boolean playAggro = AiProfileUtil.getBoolProperty(ai, AiProps.PLAY_AGGRO); if ("ExistingCounter".equals(type)) { final boolean eachExisting = sa.hasParam("EachExistingCounter"); @@ -219,10 +219,8 @@ public class CountersPutAi extends CountersAi { } else if ("PayEnergy".equals(logic)) { return new AiAbilityDecision(100, AiPlayDecision.WillPlay); } else if ("PayEnergyConservatively".equals(logic)) { - boolean onlyInCombat = ai.getController().isAI() - && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT); - boolean onlyDefensive = ai.getController().isAI() - && ((PlayerControllerAi) ai.getController()).getAi().getBooleanProperty(AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_DEFENSIVELY); + boolean onlyInCombat = AiProfileUtil.getBoolProperty(ai, AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_IN_COMBAT); + boolean onlyDefensive = AiProfileUtil.getBoolProperty(ai, AiProps.CONSERVATIVE_ENERGY_PAYMENT_ONLY_DEFENSIVELY); if (playAggro) { // aggro profiles ignore conservative play for this AI logic diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java index d83cddd4bb8..a6a60df9069 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -108,16 +108,13 @@ public class DamageDealAi extends DamageAiBase { dmg = ComputerUtilCost.getMaxXValue(sa, ai, sa.isTrigger()); // Try not to waste spells like Blaze or Fireball on early targets, try to do more damage with them if possible - if (ai.getController().isAI()) { - AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); - int holdChance = aic.getIntProperty(AiProps.HOLD_X_DAMAGE_SPELLS_FOR_MORE_DAMAGE_CHANCE); - if (MyRandom.percentTrue(holdChance)) { - int threshold = aic.getIntProperty(AiProps.HOLD_X_DAMAGE_SPELLS_THRESHOLD); - boolean inDanger = ComputerUtil.aiLifeInDanger(ai, false, 0); - boolean isLethal = sa.usesTargeting() && sa.getTargetRestrictions().canTgtPlayer() && dmg >= ai.getWeakestOpponent().getLife() && !ai.getWeakestOpponent().cantLoseForZeroOrLessLife(); - if (dmg < threshold && ai.getGame().getPhaseHandler().getTurn() / 2 < threshold && !inDanger && !isLethal) { - return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); - } + int holdChance = AiProfileUtil.getIntProperty(ai, AiProps.HOLD_X_DAMAGE_SPELLS_FOR_MORE_DAMAGE_CHANCE); + if (MyRandom.percentTrue(holdChance)) { + int threshold = AiProfileUtil.getIntProperty(ai, AiProps.HOLD_X_DAMAGE_SPELLS_THRESHOLD); + boolean inDanger = ComputerUtil.aiLifeInDanger(ai, false, 0); + boolean isLethal = sa.usesTargeting() && sa.getTargetRestrictions().canTgtPlayer() && dmg >= ai.getWeakestOpponent().getLife() && !ai.getWeakestOpponent().cantLoseForZeroOrLessLife(); + if (dmg < threshold && ai.getGame().getPhaseHandler().getTurn() / 2 < threshold && !inDanger && !isLethal) { + return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } } @@ -1086,7 +1083,7 @@ public class DamageDealAi extends DamageAiBase { } Game game = ai.getGame(); - int chance = ((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.CHANCE_TO_CHAIN_TWO_DAMAGE_SPELLS); + int chance = AiProfileUtil.getIntProperty(ai, AiProps.CHANCE_TO_CHAIN_TWO_DAMAGE_SPELLS); if (chance > 0 && (ComputerUtilCombat.lifeInDanger(ai, game.getCombat()) || ComputerUtil.aiLifeInDanger(ai, true, 0))) { chance = 100; // in danger, do it even if normally the chance is low (unless chaining is completely disabled) diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java index bae0fee23b5..104f1e55b7d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java @@ -409,12 +409,11 @@ public class DestroyAi extends SpellAbilityAi { int oppLandsOTB = tgtPlayer.getLandsInPlay().size(); // AI profile-dependent properties - AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); - int amountNoTempoCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK); - int amountNoTimingCheck = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK); - int amountLandsInHand = aic.getIntProperty(AiProps.STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE); - int amountLandsToManalock = aic.getIntProperty(AiProps.STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING); - boolean highPriorityIfNoLandDrop = aic.getBooleanProperty(AiProps.STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP); + int amountNoTempoCheck = AiProfileUtil.getIntProperty(ai, AiProps.STRIPMINE_MIN_LANDS_OTB_FOR_NO_TEMPO_CHECK); + int amountNoTimingCheck = AiProfileUtil.getIntProperty(ai, AiProps.STRIPMINE_MIN_LANDS_FOR_NO_TIMING_CHECK); + int amountLandsInHand = AiProfileUtil.getIntProperty(ai, AiProps.STRIPMINE_MIN_LANDS_IN_HAND_TO_ACTIVATE); + int amountLandsToManalock = AiProfileUtil.getIntProperty(ai, AiProps.STRIPMINE_MAX_LANDS_TO_ATTEMPT_MANALOCKING); + boolean highPriorityIfNoLandDrop = AiProfileUtil.getBoolProperty(ai, AiProps.STRIPMINE_HIGH_PRIORITY_ON_SKIPPED_LANDDROP); // if the opponent didn't play a land and has few lands OTB, might be worth mana-locking him PhaseHandler ph = ai.getGame().getPhaseHandler(); diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index 0a9cc6285b7..6623ad3f812 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -264,7 +264,7 @@ public class DrawAi extends SpellAbilityAi { if (sa.getPayCosts().hasSpecificCostType(CostPayLife.class)) { // [Necrologia, Pay X Life : Draw X Cards] // Don't draw more than what's "safe" and don't risk a near death experience - boolean aggroAI = (((PlayerControllerAi) ai.getController()).getAi()).getBooleanProperty(AiProps.PLAY_AGGRO); + boolean aggroAI = AiProfileUtil.getBoolProperty(ai, AiProps.PLAY_AGGRO); while (ComputerUtil.aiLifeInDanger(ai, aggroAI, numCards) && numCards > 0) { numCards--; } diff --git a/forge-ai/src/main/java/forge/ai/ability/EndureAi.java b/forge-ai/src/main/java/forge/ai/ability/EndureAi.java index 0bda7e14c19..f9e59ab053b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EndureAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EndureAi.java @@ -41,7 +41,7 @@ public class EndureAi extends SpellAbilityAi { return new AiAbilityDecision(0, AiPlayDecision.AnotherTime); } int curLife = aiPlayer.getLife(); - int dangerLife = (((PlayerControllerAi) aiPlayer.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD)); + int dangerLife = AiProfileUtil.getIntProperty(aiPlayer, AiProps.AI_IN_DANGER_THRESHOLD); if (curLife <= dangerLife) { return new AiAbilityDecision(0, AiPlayDecision.CantAffordX); } diff --git a/forge-ai/src/main/java/forge/ai/ability/ExploreAi.java b/forge-ai/src/main/java/forge/ai/ability/ExploreAi.java index 75fe2533f10..e80bc69eacb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ExploreAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ExploreAi.java @@ -37,14 +37,8 @@ public class ExploreAi extends SpellAbilityAi { CardCollection landsOTB = CardLists.filter(cardsOTB, CardPredicates.LANDS_PRODUCING_MANA); CardCollection landsInHand = CardLists.filter(cardsInHand, CardPredicates.LANDS_PRODUCING_MANA); - int maxCMCDiff = 1; - int numLandsToStillNeedMore = 2; - - if (ai.getController().isAI()) { - AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); - maxCMCDiff = aic.getIntProperty(AiProps.EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD); - numLandsToStillNeedMore = aic.getIntProperty(AiProps.EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE); - } + int maxCMCDiff = AiProfileUtil.getIntProperty(ai, AiProps.EXPLORE_MAX_CMC_DIFF_TO_PUT_IN_GRAVEYARD); + int numLandsToStillNeedMore = AiProfileUtil.getIntProperty(ai, AiProps.EXPLORE_NUM_LANDS_TO_STILL_NEED_MORE); if (landsInHand.isEmpty() && landsOTB.size() <= numLandsToStillNeedMore) { // We need more lands to improve our mana base, explore away the non-lands diff --git a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java index 44e7020bb91..551227631a3 100644 --- a/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/LifeExchangeVariantAi.java @@ -93,9 +93,9 @@ public class LifeExchangeVariantAi extends SpellAbilityAi { if (game.getCombat().isUnblocked(source) && def.canLoseLife() && aiLife >= def.getLife() && source.getNetPower() < def.getLife()) { // Unblocked Evra which can deal lethal damage return new AiAbilityDecision(100, AiPlayDecision.WillPlay); - } else if (ai.getController().isAI() && aiLife > source.getNetPower() && source.hasKeyword(Keyword.LIFELINK)) { - int dangerMin = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_THRESHOLD)); - int dangerMax = (((PlayerControllerAi) ai.getController()).getAi().getIntProperty(AiProps.AI_IN_DANGER_MAX_THRESHOLD)); + } else if (aiLife > source.getNetPower() && source.hasKeyword(Keyword.LIFELINK)) { + int dangerMin = AiProfileUtil.getIntProperty(ai, AiProps.AI_IN_DANGER_THRESHOLD); + int dangerMax = AiProfileUtil.getIntProperty(ai, AiProps.AI_IN_DANGER_MAX_THRESHOLD); int dangerDiff = dangerMax - dangerMin; int lifeInDanger = dangerDiff <= 0 ? dangerMin : MyRandom.getRandom().nextInt(dangerDiff) + dangerMin; if (source.getNetPower() >= lifeInDanger && ai.canGainLife() && ComputerUtil.lifegainPositive(ai, source)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java index 200d5648d91..21ead406822 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentCreatureAi.java @@ -65,10 +65,7 @@ public class PermanentCreatureAi extends PermanentAi { } // Flash logic - boolean advancedFlash = false; - if (ai.getController().isAI()) { - advancedFlash = ((PlayerControllerAi)ai.getController()).getAi().getBooleanProperty(AiProps.FLASH_ENABLE_ADVANCED_LOGIC); - } + boolean advancedFlash = AiProfileUtil.getBoolProperty(ai, AiProps.FLASH_ENABLE_ADVANCED_LOGIC); if (card.hasKeyword(Keyword.FLASH) || (!ai.canCastSorcery() && sa.canCastTiming(ai) && !sa.isCastFromPlayEffect())) { if (advancedFlash) { return doAdvancedFlashLogic(card, ai, sa); @@ -135,11 +132,11 @@ public class PermanentCreatureAi extends PermanentAi { } } - int chanceToObeyAmbushAI = aic.getIntProperty(AiProps.FLASH_CHANCE_TO_OBEY_AMBUSHAI); - int chanceToAddBlocker = aic.getIntProperty(AiProps.FLASH_CHANCE_TO_CAST_AS_VALUABLE_BLOCKER); - int chanceToCastForETB = aic.getIntProperty(AiProps.FLASH_CHANCE_TO_CAST_DUE_TO_ETB_EFFECTS); - int chanceToRespondToStack = aic.getIntProperty(AiProps.FLASH_CHANCE_TO_RESPOND_TO_STACK_WITH_ETB); - int chanceToProcETBBeforeMain1 = aic.getIntProperty(AiProps.FLASH_CHANCE_TO_CAST_FOR_ETB_BEFORE_MAIN1); + int chanceToObeyAmbushAI = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_CHANCE_TO_OBEY_AMBUSHAI); + int chanceToAddBlocker = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_CHANCE_TO_CAST_AS_VALUABLE_BLOCKER); + int chanceToCastForETB = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_CHANCE_TO_CAST_DUE_TO_ETB_EFFECTS); + int chanceToRespondToStack = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_CHANCE_TO_RESPOND_TO_STACK_WITH_ETB); + int chanceToProcETBBeforeMain1 = AiProfileUtil.getIntProperty(ai, AiProps.FLASH_CHANCE_TO_CAST_FOR_ETB_BEFORE_MAIN1); boolean canCastAtOppTurn = true; for (Card c : ai.getGame().getCardsIn(ZoneType.Battlefield)) { for (StaticAbility s : c.getStaticAbilities()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java b/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java index 57454123c44..aae77e5179b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RearrangeTopOfLibraryAi.java @@ -100,13 +100,8 @@ public class RearrangeTopOfLibraryAi extends SpellAbilityAi { return false; } - int uncastableCMCThreshold = 2; - int minLandsToScryLandsAway = 4; - if (player.getController().isAI()) { - AiController aic = ((PlayerControllerAi)player.getController()).getAi(); - minLandsToScryLandsAway = aic.getIntProperty(AiProps.SCRY_NUM_LANDS_TO_NOT_NEED_MORE); - uncastableCMCThreshold = aic.getIntProperty(AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF); - } + int minLandsToScryLandsAway = AiProfileUtil.getIntProperty(player, AiProps.SCRY_NUM_LANDS_TO_NOT_NEED_MORE); + int uncastableCMCThreshold = AiProfileUtil.getIntProperty(player, AiProps.SCRY_IMMEDIATELY_UNCASTABLE_CMC_DIFF); int landsOTB = CardLists.count(p.getCardsIn(ZoneType.Battlefield), CardPredicates.LANDS_PRODUCING_MANA); int cmc = top.isSplitCard() ? Math.min(top.getCMC(Card.SplitCMCMode.LeftSplitCMC), top.getCMC(Card.SplitCMCMode.RightSplitCMC)) diff --git a/forge-ai/src/main/java/forge/ai/ability/RollPlanarDiceAi.java b/forge-ai/src/main/java/forge/ai/ability/RollPlanarDiceAi.java index 9e9aefbabb6..761fe5124d0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/RollPlanarDiceAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/RollPlanarDiceAi.java @@ -29,14 +29,13 @@ public class RollPlanarDiceAi extends SpellAbilityAi { } private boolean willRollOnPlane(Player ai, Card plane) { - AiController aic = ((PlayerControllerAi)ai.getController()).getAi(); boolean decideToRoll = false; boolean rollInMain1 = false; String modeName = "never"; - int maxActivations = aic.getIntProperty(AiProps.DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN); - int chance = aic.getIntProperty(AiProps.DEFAULT_PLANAR_DIE_ROLL_CHANCE); - int hesitationChance = aic.getIntProperty(AiProps.PLANAR_DIE_ROLL_HESITATION_CHANCE); - int minTurnToRoll = aic.getIntProperty(AiProps.DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE); + int maxActivations = AiProfileUtil.getIntProperty(ai, AiProps.DEFAULT_MAX_PLANAR_DIE_ROLLS_PER_TURN); + int chance = AiProfileUtil.getIntProperty(ai, AiProps.DEFAULT_PLANAR_DIE_ROLL_CHANCE); + int hesitationChance = AiProfileUtil.getIntProperty(ai, AiProps.PLANAR_DIE_ROLL_HESITATION_CHANCE); + int minTurnToRoll = AiProfileUtil.getIntProperty(ai, AiProps.DEFAULT_MIN_TURN_TO_ROLL_PLANAR_DIE); if (plane.hasSVar("AIRollPlanarDieParams")) { String[] params = plane.getSVar("AIRollPlanarDieParams").toLowerCase().trim().split("\\|"); diff --git a/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java b/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java index be10645fcd6..840e9081d2e 100644 --- a/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/SurveilAi.java @@ -70,7 +70,7 @@ public class SurveilAi extends SpellAbilityAi { // Only Surveil for life when at decent amount of life remaining final Cost cost = sa.getPayCosts(); if (cost != null && cost.hasSpecificCostType(CostPayLife.class)) { - final int maxLife = ((PlayerControllerAi)ai.getController()).getAi().getIntProperty(AiProps.SURVEIL_LIFEPERC_AFTER_PAYING_LIFE); + final int maxLife = AiProfileUtil.getIntProperty(ai, AiProps.SURVEIL_LIFEPERC_AFTER_PAYING_LIFE); if (!ComputerUtilCost.checkLifeCost(ai, cost, sa.getHostCard(), ai.getStartingLife() * maxLife / 100, sa)) { return new AiAbilityDecision(0, AiPlayDecision.CostNotAcceptable); } diff --git a/forge-ai/src/main/java/forge/ai/ability/TapAi.java b/forge-ai/src/main/java/forge/ai/ability/TapAi.java index f58046f3de7..d83457565b8 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TapAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TapAi.java @@ -30,8 +30,7 @@ public class TapAi extends TapAiBase { // Cast it if it's a sorcery. } else if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_BLOCKERS)) { // Aggro Brains are willing to use TapEffects aggressively instead of defensively - AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); - if (!aic.getBooleanProperty(AiProps.PLAY_AGGRO)) { + if (!AiProfileUtil.getBoolProperty(ai, AiProps.PLAY_AGGRO)) { return new AiAbilityDecision(0, AiPlayDecision.CantPlayAi); } } else { diff --git a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java index e1cd252d84a..c755bee4cfd 100644 --- a/forge-ai/src/main/java/forge/ai/ability/TokenAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/TokenAi.java @@ -186,16 +186,9 @@ public class TokenAi extends SpellAbilityAi { } } - double chance = 1.0F; // 100% - boolean alwaysFromPW = true; - boolean alwaysOnOppAttack = true; - - if (ai.getController().isAI()) { - AiController aic = ((PlayerControllerAi) ai.getController()).getAi(); - chance = (double)aic.getIntProperty(AiProps.TOKEN_GENERATION_ABILITY_CHANCE) / 100; - alwaysFromPW = aic.getBooleanProperty(AiProps.TOKEN_GENERATION_ALWAYS_IF_FROM_PLANESWALKER); - alwaysOnOppAttack = aic.getBooleanProperty(AiProps.TOKEN_GENERATION_ALWAYS_IF_OPP_ATTACKS); - } + double chance = (double)AiProfileUtil.getIntProperty(ai, AiProps.TOKEN_GENERATION_ABILITY_CHANCE) / 100; + boolean alwaysFromPW = AiProfileUtil.getBoolProperty(ai, AiProps.TOKEN_GENERATION_ALWAYS_IF_FROM_PLANESWALKER); + boolean alwaysOnOppAttack = AiProfileUtil.getBoolProperty(ai, AiProps.TOKEN_GENERATION_ALWAYS_IF_OPP_ATTACKS); if (sa.isPwAbility() && alwaysFromPW) { return new AiAbilityDecision(100, AiPlayDecision.WillPlay); diff --git a/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java b/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java index 8b1c6ac6566..647faba9c8e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/VentureEffect.java @@ -27,7 +27,7 @@ import forge.item.PaperCardPredicates; import forge.util.Localizer; import forge.util.PredicateString.StringOp; -public class VentureEffect extends SpellAbilityEffect { +public class VentureEffect extends SpellAbilityEffect { private Card getDungeonCard(SpellAbility sa, Player player, Map moveParams) { final Game game = player.getGame(); @@ -44,7 +44,7 @@ public class VentureEffect extends SpellAbilityEffect { } } - List dungeonCards = null; + List dungeonCards; if (sa.hasParam("Dungeon")) { String dungeonType = sa.getParam("Dungeon"); Predicate rulesPredicate = CardRulesPredicates.IS_DUNGEON.and(CardRulesPredicates.subType(StringOp.EQUALS, dungeonType)); From fdc65ffd85e8d2f392bd4886ff14bebd7b7c0670 Mon Sep 17 00:00:00 2001 From: Paul Hammerton Date: Tue, 28 Oct 2025 09:45:15 +0000 Subject: [PATCH 190/230] Archived formats update --- forge-gui/res/formats/Archived/Historic/2025-09-23.txt | 2 +- forge-gui/res/formats/Archived/Historic/2025-10-28.txt | 8 ++++++++ forge-gui/res/formats/Archived/Legacy/2025-06-13.txt | 3 ++- forge-gui/res/formats/Archived/Legacy/2025-07-14.txt | 3 ++- forge-gui/res/formats/Archived/Legacy/2025-08-01.txt | 3 ++- forge-gui/res/formats/Archived/Legacy/2025-09-26.txt | 3 ++- forge-gui/res/formats/Archived/Legacy/2025-10-13.txt | 9 +++++++++ forge-gui/res/formats/Archived/Legacy/2025-10-27.txt | 9 +++++++++ forge-gui/res/formats/Archived/Legacy/2025-11-21.txt | 9 +++++++++ forge-gui/res/formats/Archived/Legacy/2025-12-05.txt | 8 ++++++++ forge-gui/res/formats/Archived/Vintage/2025-06-13.txt | 3 ++- forge-gui/res/formats/Archived/Vintage/2025-07-14.txt | 3 ++- forge-gui/res/formats/Archived/Vintage/2025-08-01.txt | 3 ++- forge-gui/res/formats/Archived/Vintage/2025-09-26.txt | 3 ++- forge-gui/res/formats/Archived/Vintage/2025-10-13.txt | 10 ++++++++++ forge-gui/res/formats/Archived/Vintage/2025-10-27.txt | 10 ++++++++++ forge-gui/res/formats/Archived/Vintage/2025-11-21.txt | 10 ++++++++++ forge-gui/res/formats/Archived/Vintage/2025-12-05.txt | 9 +++++++++ forge-gui/res/formats/Sanctioned/Historic.txt | 2 +- 19 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 forge-gui/res/formats/Archived/Historic/2025-10-28.txt create mode 100644 forge-gui/res/formats/Archived/Legacy/2025-10-13.txt create mode 100644 forge-gui/res/formats/Archived/Legacy/2025-10-27.txt create mode 100644 forge-gui/res/formats/Archived/Legacy/2025-11-21.txt create mode 100644 forge-gui/res/formats/Archived/Legacy/2025-12-05.txt create mode 100644 forge-gui/res/formats/Archived/Vintage/2025-10-13.txt create mode 100644 forge-gui/res/formats/Archived/Vintage/2025-10-27.txt create mode 100644 forge-gui/res/formats/Archived/Vintage/2025-11-21.txt create mode 100644 forge-gui/res/formats/Archived/Vintage/2025-12-05.txt diff --git a/forge-gui/res/formats/Archived/Historic/2025-09-23.txt b/forge-gui/res/formats/Archived/Historic/2025-09-23.txt index dc175f25ab4..ebeac9bc7e8 100644 --- a/forge-gui/res/formats/Archived/Historic/2025-09-23.txt +++ b/forge-gui/res/formats/Archived/Historic/2025-09-23.txt @@ -4,5 +4,5 @@ Type:Archived Subtype:Arena Effective:2025-09-23 Sets:KTK, XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3, WOE, WOT, YWOE, LCI, YLCI, MKM, YMKM, OTJ, OTP, BIG, YOTJ, MH3, BLB, YBLB, DSK, YDSK, FDN, J25, PIO, DFT, YDFT, TDM, YTDM, PA1, FIN, FCA, EOE, EOS, YEOE, AA1, AA2, OM1, OMB, AA3, AA4 -Banned:Agent of Treachery; Ancient Tomb; Arid Mesa; Blood Moon; Bloodstained Mire; Brainstorm; Channel; Chrome Mox; Commandeer; Dark Ritual; Demonic Tutor; Endurance; Field of the Dead; Flare of Cultivation; Flare of Denial; Flare of Duplication; Flare of Fortitude; Flare of Malice; Flooded Strand; Force of Vigor; Fury; Grief; Harbinger of the Seas; Intruder Alarm; Land Tax; Lightning Bolt; Magus of the Moon; Mana Drain; Marsh Flats; Memory Lapse; Mishra's Bauble; Misty Rainforest; Mox Opal; Natural Order; Necropotence; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Polluted Delta; Ragavan, Nimble Pilferer; Reanimate; Scalding Tarn; Show and Tell; Sneak Attack; Solitude; Spreading Seas; Strip Mine; Subtlety; Swords to Plowshares; Temporal Manipulation; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Verdant Catacombs; Wilderness Reclamation; Windswept Heath; Winter Moon; Wooded Foothills +Banned:Agent of Treachery; Ancient Tomb; Arid Mesa; Blood Moon; Bloodstained Mire; Brainstorm; Broadside Bombardiers; Channel; Chrome Mox; Commandeer; Dark Ritual; Demonic Tutor; Endurance; Field of the Dead; Flare of Cultivation; Flare of Denial; Flare of Duplication; Flare of Fortitude; Flare of Malice; Flooded Strand; Force of Vigor; Fury; Grief; Gut, True Soul Zealot; Harbinger of the Seas; Intruder Alarm; Land Tax; Lightning Bolt; Magus of the Moon; Mana Drain; Marsh Flats; Memory Lapse; Mishra's Bauble; Misty Rainforest; Mox Opal; Natural Order; Necropotence; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Polluted Delta; Ragavan, Nimble Pilferer; Reanimate; Scalding Tarn; Show and Tell; Sneak Attack; Solitude; Spreading Seas; Strip Mine; Subtlety; Swords to Plowshares; Temporal Manipulation; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Verdant Catacombs; Wilderness Reclamation; Windswept Heath; Winter Moon; Wooded Foothills Additional:Admiral Brass, Unsinkable; Burden of Guilt; Clavileño, First of the Blessed; Crashing Footfalls; Crumbling Vestige; Desert; Desertion; Dismember; Duskmantle, House of Shadow; Enlisted Wurm; Evolutionary Leap; Fabricate; Gamble; Ghostly Prison; Gonti, Canny Acquisitor; Goro-Goro and Satoru; Ixidor, Reality Sculptor; Katilda and Lier; Kuldotha Rebirth; Leonin Relic-Warder; Magmaw; Mass Hysteria; Metalspinner's Puzzleknot; Mistveil Plains; Molten Psyche; Monologue Tax; Mystery Key; Mystic Snake; Notion Thief; Nyx Weaver; Olivia, Opulent Outlaw; Pantlaza, Sun-Favored; Persist; Port Razer; Possibility Storm; Prismatic Ending; Prismatic Vista; Putrid Warrior; Shard of Broken Glass; Slimefoot and Squee; Smuggler's Copter; Spell Snare; Stella Lee, Wild Card; Stoneforge Mystic; Timeless Dragon; Treacherous Terrain; Victimize; Xolatoyac, the Smiling Flood; Yuma, Proud Protector diff --git a/forge-gui/res/formats/Archived/Historic/2025-10-28.txt b/forge-gui/res/formats/Archived/Historic/2025-10-28.txt new file mode 100644 index 00000000000..9fd8aacedfe --- /dev/null +++ b/forge-gui/res/formats/Archived/Historic/2025-10-28.txt @@ -0,0 +1,8 @@ +[format] +Name:Historic (2025-10-28) +Type:Archived +Subtype:Arena +Effective:2025-10-28 +Sets:KTK, XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3, WOE, WOT, YWOE, LCI, YLCI, MKM, YMKM, OTJ, OTP, BIG, YOTJ, MH3, BLB, YBLB, DSK, YDSK, FDN, J25, PIO, DFT, YDFT, TDM, YTDM, PA1, FIN, FCA, EOE, EOS, YEOE, AA1, AA2, OM1, OMB, AA3, AA4 +Banned:Agent of Treachery; Ancient Tomb; Arid Mesa; Blood Moon; Bloodstained Mire; Brainstorm; Broadside Bombardiers; Channel; Chrome Mox; Commandeer; Dark Ritual; Demonic Tutor; Endurance; Field of the Dead; Fireblast; Flare of Cultivation; Flare of Denial; Flare of Duplication; Flare of Fortitude; Flare of Malice; Flooded Strand; Force of Vigor; Fury; Grief; Gut, True Soul Zealot; Harbinger of the Seas; Intruder Alarm; Land Tax; Lightning Bolt; Magus of the Moon; Mana Drain; Marsh Flats; Memory Lapse; Mishra's Bauble; Misty Rainforest; Mox Opal; Natural Order; Necropotence; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Polluted Delta; Preordain; Pyrokinesis; Ragavan, Nimble Pilferer; Reanimate; Scalding Tarn; Seething Song; Show and Tell; Sneak Attack; Solitude; Spreading Seas; Strip Mine; Subtlety; Swords to Plowshares; Temporal Manipulation; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Verdant Catacombs; Wilderness Reclamation; Windswept Heath; Winter Moon; Wooded Foothills +Additional:Admiral Brass, Unsinkable; Burden of Guilt; Clavileño, First of the Blessed; Crashing Footfalls; Crumbling Vestige; Desert; Desertion; Dismember; Duskmantle, House of Shadow; Enlisted Wurm; Evolutionary Leap; Fabricate; Gamble; Ghostly Prison; Gonti, Canny Acquisitor; Goro-Goro and Satoru; Ixidor, Reality Sculptor; Katilda and Lier; Kuldotha Rebirth; Leonin Relic-Warder; Magmaw; Mass Hysteria; Metalspinner's Puzzleknot; Mistveil Plains; Molten Psyche; Monologue Tax; Mystery Key; Mystic Snake; Notion Thief; Nyx Weaver; Olivia, Opulent Outlaw; Pantlaza, Sun-Favored; Persist; Port Razer; Possibility Storm; Prismatic Ending; Prismatic Vista; Putrid Warrior; Shard of Broken Glass; Slimefoot and Squee; Smuggler's Copter; Spell Snare; Stella Lee, Wild Card; Stoneforge Mystic; Timeless Dragon; Treacherous Terrain; Victimize; Xolatoyac, the Smiling Flood; Yuma, Proud Protector diff --git a/forge-gui/res/formats/Archived/Legacy/2025-06-13.txt b/forge-gui/res/formats/Archived/Legacy/2025-06-13.txt index b8e2511bbfd..8eb920aefc3 100644 --- a/forge-gui/res/formats/Archived/Legacy/2025-06-13.txt +++ b/forge-gui/res/formats/Archived/Legacy/2025-06-13.txt @@ -4,5 +4,6 @@ Type:Archived Subtype:Legacy Effective:2025-06-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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA -Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Ambassador Blorpityblorpboop; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Baaallerina; Backup Plan; Balance; Bazaar of Baghdad; Bioluminary; Black Lotus; Brago's Favor; Bronze Tablet; Carnival Carnivore; Channel; Chaos Orb; Chicken Troupe; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Deathrite Shaman; Dee Kay, Finder of the Lost; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Dreadhorde Arcanist; Earthcraft; Echoing Boon; Emissary's Ploy; Expressive Iteration; Falling Star; Fastbond; Fight the _____ Fight; Finishing Move; Flash; Frantic Search; Gitaxian Probe; Glitterflitter; Goblin Recruiter; Grief; 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; Last Voyage of the _____; Library of Alexandria; Line Cutter; Lineprancers; Lurrus of the Dream-Den; Make a _____ Splash; Mana Crypt; Mana Drain; Mana Vault; Memory Jar; Mental Misstep; Mind Twist; Minotaur de Force; Mishra's Workshop; Monitor Monitor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Myra the Magnificent; Mystical Tutor; Natural Unity; Necropotence; Oath of Druids; Oko, Thief of Crowns; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Psychic Frog; Quick Fixer; Rad Rascal; Ragavan, Nimble Pilferer; Rebirth; Ride Guide; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Soul Swindler; Sovereign's Realm; Sowing Mycospawn; Spinnerette, Arachnobat; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Sword-Swallowing Seraph; Tempest Efreet; The Most Dangerous Gamer; Ticketomaton; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Troll of Khazad-dûm; Tusk and Whiskers; Underworld Breach; Unexpected Potential; Vampiric Tutor; Vexing Bauble; Weight Advantage; Wheel of Fortune; White Plume Adventurer; Wicker Picker; Windfall; Wizards of the _____; Wolf in _____ Clothing; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Baaallerina; Backup Plan; Balance; Bazaar of Baghdad; Bioluminary; Black Lotus; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Channel; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Deathrite Shaman; Dee Kay, Finder of the Lost; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Dreadhorde Arcanist; Duelist's Flame; Earthcraft; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Expressive Iteration; Falling Star; Fastbond; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash; Flash Photography; Frantic Search; Garland, Royal Kidnapper; Gitaxian Probe; Glitterflitter; Goblin Recruiter; Grief; 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; Judgment of Alexander; Last Voyage of the _____; Library of Alexandria; Line Cutter; Lineprancers; Lurrus of the Dream-Den; Make a _____ Splash; Mana Crypt; Mana Drain; Mana Vault; Mega Flare; Memory Jar; Mental Misstep; Mind Twist; Minotaur de Force; Mishra's Workshop; Monitor Monitor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Myra the Magnificent; Mystical Tutor; Natural Unity; Necropotence; Noctis, Heir Apparent; Oath of Druids; Oko, Thief of Crowns; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Psychic Frog; Quick Fixer; Rad Rascal; Ragavan, Nimble Pilferer; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Soul Swindler; Sovereign's Realm; Sowing Mycospawn; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Troll of Khazad-dûm; Tusk and Whiskers; Underworld Breach; Unexpected Potential; Vampiric Tutor; Vexing Bauble; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wheel of Fortune; White Plume Adventurer; Wicker Picker; Windfall; Wizards of the _____; Wolf in _____ Clothing; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker Additional:Aisha of Sparks and Smoke; Arden Angel; Arvinox, the Mind Flail; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jurin, Leading the Charge; Ken, Burning Brawler; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Simon, Wild Magic Sorcerer; Sophina, Spearsage Deserter; Storm, Force of Nature; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Legacy/2025-07-14.txt b/forge-gui/res/formats/Archived/Legacy/2025-07-14.txt index 1953350f886..57f15dcfd0c 100644 --- a/forge-gui/res/formats/Archived/Legacy/2025-07-14.txt +++ b/forge-gui/res/formats/Archived/Legacy/2025-07-14.txt @@ -4,5 +4,6 @@ Type:Archived Subtype:Legacy Effective:2025-07-14 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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA -Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Ambassador Blorpityblorpboop; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Baaallerina; Backup Plan; Balance; Bazaar of Baghdad; Bioluminary; Black Lotus; Brago's Favor; Bronze Tablet; Carnival Carnivore; Channel; Chaos Orb; Chicken Troupe; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Deathrite Shaman; Dee Kay, Finder of the Lost; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Dreadhorde Arcanist; Earthcraft; Echoing Boon; Emissary's Ploy; Expressive Iteration; Falling Star; Fastbond; Fight the _____ Fight; Finishing Move; Flash; Frantic Search; Gitaxian Probe; Glitterflitter; Goblin Recruiter; Grief; 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; Last Voyage of the _____; Library of Alexandria; Line Cutter; Lineprancers; Lurrus of the Dream-Den; Make a _____ Splash; Mana Crypt; Mana Drain; Mana Vault; Memory Jar; Mental Misstep; Mind Twist; Minotaur de Force; Mishra's Workshop; Monitor Monitor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Myra the Magnificent; Mystical Tutor; Natural Unity; Necropotence; Oath of Druids; Oko, Thief of Crowns; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Psychic Frog; Quick Fixer; Rad Rascal; Ragavan, Nimble Pilferer; Rebirth; Ride Guide; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Soul Swindler; Sovereign's Realm; Sowing Mycospawn; Spinnerette, Arachnobat; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Sword-Swallowing Seraph; Tempest Efreet; The Most Dangerous Gamer; Ticketomaton; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Troll of Khazad-dûm; Tusk and Whiskers; Underworld Breach; Unexpected Potential; Vampiric Tutor; Vexing Bauble; Weight Advantage; Wheel of Fortune; White Plume Adventurer; Wicker Picker; Windfall; Wizards of the _____; Wolf in _____ Clothing; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Baaallerina; Backup Plan; Balance; Bazaar of Baghdad; Bioluminary; Black Lotus; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Channel; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Deathrite Shaman; Dee Kay, Finder of the Lost; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Dreadhorde Arcanist; Duelist's Flame; Earthcraft; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Expressive Iteration; Falling Star; Fastbond; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash; Flash Photography; Frantic Search; Garland, Royal Kidnapper; Gitaxian Probe; Glitterflitter; Goblin Recruiter; Grief; 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; Judgment of Alexander; Last Voyage of the _____; Library of Alexandria; Line Cutter; Lineprancers; Lurrus of the Dream-Den; Make a _____ Splash; Mana Crypt; Mana Drain; Mana Vault; Mega Flare; Memory Jar; Mental Misstep; Mind Twist; Minotaur de Force; Mishra's Workshop; Monitor Monitor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Myra the Magnificent; Mystical Tutor; Natural Unity; Necropotence; Noctis, Heir Apparent; Oath of Druids; Oko, Thief of Crowns; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Psychic Frog; Quick Fixer; Rad Rascal; Ragavan, Nimble Pilferer; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Soul Swindler; Sovereign's Realm; Sowing Mycospawn; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Troll of Khazad-dûm; Tusk and Whiskers; Underworld Breach; Unexpected Potential; Vampiric Tutor; Vexing Bauble; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wheel of Fortune; White Plume Adventurer; Wicker Picker; Windfall; Wizards of the _____; Wolf in _____ Clothing; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker Additional:Aisha of Sparks and Smoke; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Legacy/2025-08-01.txt b/forge-gui/res/formats/Archived/Legacy/2025-08-01.txt index 061ef75168d..b67789b5765 100644 --- a/forge-gui/res/formats/Archived/Legacy/2025-08-01.txt +++ b/forge-gui/res/formats/Archived/Legacy/2025-08-01.txt @@ -4,5 +4,6 @@ Type:Archived Subtype:Legacy Effective:2025-08-01 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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA, EOE, EOC, EOS -Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Ambassador Blorpityblorpboop; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Baaallerina; Backup Plan; Balance; Bazaar of Baghdad; Bioluminary; Black Lotus; Brago's Favor; Bronze Tablet; Carnival Carnivore; Channel; Chaos Orb; Chicken Troupe; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Deathrite Shaman; Dee Kay, Finder of the Lost; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Dreadhorde Arcanist; Earthcraft; Echoing Boon; Emissary's Ploy; Expressive Iteration; Falling Star; Fastbond; Fight the _____ Fight; Finishing Move; Flash; Frantic Search; Gitaxian Probe; Glitterflitter; Goblin Recruiter; Grief; 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; Last Voyage of the _____; Library of Alexandria; Line Cutter; Lineprancers; Lurrus of the Dream-Den; Make a _____ Splash; Mana Crypt; Mana Drain; Mana Vault; Memory Jar; Mental Misstep; Mind Twist; Minotaur de Force; Mishra's Workshop; Monitor Monitor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Myra the Magnificent; Mystical Tutor; Natural Unity; Necropotence; Oath of Druids; Oko, Thief of Crowns; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Psychic Frog; Quick Fixer; Rad Rascal; Ragavan, Nimble Pilferer; Rebirth; Ride Guide; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Soul Swindler; Sovereign's Realm; Sowing Mycospawn; Spinnerette, Arachnobat; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Sword-Swallowing Seraph; Tempest Efreet; The Most Dangerous Gamer; Ticketomaton; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Troll of Khazad-dûm; Tusk and Whiskers; Underworld Breach; Unexpected Potential; Vampiric Tutor; Vexing Bauble; Weight Advantage; Wheel of Fortune; White Plume Adventurer; Wicker Picker; Windfall; Wizards of the _____; Wolf in _____ Clothing; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Baaallerina; Backup Plan; Balance; Bazaar of Baghdad; Bioluminary; Black Lotus; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Channel; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Deathrite Shaman; Dee Kay, Finder of the Lost; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Dreadhorde Arcanist; Duelist's Flame; Earthcraft; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Expressive Iteration; Falling Star; Fastbond; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash; Flash Photography; Frantic Search; Garland, Royal Kidnapper; Gitaxian Probe; Glitterflitter; Goblin Recruiter; Grief; 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; Judgment of Alexander; Last Voyage of the _____; Library of Alexandria; Line Cutter; Lineprancers; Lurrus of the Dream-Den; Make a _____ Splash; Mana Crypt; Mana Drain; Mana Vault; Mega Flare; Memory Jar; Mental Misstep; Mind Twist; Minotaur de Force; Mishra's Workshop; Monitor Monitor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Myra the Magnificent; Mystical Tutor; Natural Unity; Necropotence; Noctis, Heir Apparent; Oath of Druids; Oko, Thief of Crowns; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Psychic Frog; Quick Fixer; Rad Rascal; Ragavan, Nimble Pilferer; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Soul Swindler; Sovereign's Realm; Sowing Mycospawn; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Troll of Khazad-dûm; Tusk and Whiskers; Underworld Breach; Unexpected Potential; Vampiric Tutor; Vexing Bauble; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wheel of Fortune; White Plume Adventurer; Wicker Picker; Windfall; Wizards of the _____; Wolf in _____ Clothing; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker Additional:Aisha of Sparks and Smoke; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Legacy/2025-09-26.txt b/forge-gui/res/formats/Archived/Legacy/2025-09-26.txt index 33c966ec7d3..f926582d542 100644 --- a/forge-gui/res/formats/Archived/Legacy/2025-09-26.txt +++ b/forge-gui/res/formats/Archived/Legacy/2025-09-26.txt @@ -4,5 +4,6 @@ Type:Archived Subtype:Legacy Effective:2025-09-26 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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA, EOE, EOC, EOS, SPM, SPE, MAR -Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Ambassador Blorpityblorpboop; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Baaallerina; Backup Plan; Balance; Bazaar of Baghdad; Bioluminary; Black Lotus; Brago's Favor; Bronze Tablet; Carnival Carnivore; Channel; Chaos Orb; Chicken Troupe; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Deathrite Shaman; Dee Kay, Finder of the Lost; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Dreadhorde Arcanist; Earthcraft; Echoing Boon; Emissary's Ploy; Expressive Iteration; Falling Star; Fastbond; Fight the _____ Fight; Finishing Move; Flash; Frantic Search; Gitaxian Probe; Glitterflitter; Goblin Recruiter; Grief; 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; Last Voyage of the _____; Library of Alexandria; Line Cutter; Lineprancers; Lurrus of the Dream-Den; Make a _____ Splash; Mana Crypt; Mana Drain; Mana Vault; Memory Jar; Mental Misstep; Mind Twist; Minotaur de Force; Mishra's Workshop; Monitor Monitor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Myra the Magnificent; Mystical Tutor; Natural Unity; Necropotence; Oath of Druids; Oko, Thief of Crowns; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Psychic Frog; Quick Fixer; Rad Rascal; Ragavan, Nimble Pilferer; Rebirth; Ride Guide; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Soul Swindler; Sovereign's Realm; Sowing Mycospawn; Spinnerette, Arachnobat; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Sword-Swallowing Seraph; Tempest Efreet; The Most Dangerous Gamer; Ticketomaton; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Troll of Khazad-dûm; Tusk and Whiskers; Underworld Breach; Unexpected Potential; Vampiric Tutor; Vexing Bauble; Weight Advantage; Wheel of Fortune; White Plume Adventurer; Wicker Picker; Windfall; Wizards of the _____; Wolf in _____ Clothing; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Baaallerina; Backup Plan; Balance; Bazaar of Baghdad; Bioluminary; Black Lotus; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Channel; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Deathrite Shaman; Dee Kay, Finder of the Lost; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Dreadhorde Arcanist; Duelist's Flame; Earthcraft; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Expressive Iteration; Falling Star; Fastbond; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash; Flash Photography; Frantic Search; Garland, Royal Kidnapper; Gitaxian Probe; Glitterflitter; Goblin Recruiter; Grief; 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; Judgment of Alexander; Last Voyage of the _____; Library of Alexandria; Line Cutter; Lineprancers; Lurrus of the Dream-Den; Make a _____ Splash; Mana Crypt; Mana Drain; Mana Vault; Mega Flare; Memory Jar; Mental Misstep; Mind Twist; Minotaur de Force; Mishra's Workshop; Monitor Monitor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Myra the Magnificent; Mystical Tutor; Natural Unity; Necropotence; Noctis, Heir Apparent; Oath of Druids; Oko, Thief of Crowns; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Psychic Frog; Quick Fixer; Rad Rascal; Ragavan, Nimble Pilferer; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Soul Swindler; Sovereign's Realm; Sowing Mycospawn; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Troll of Khazad-dûm; Tusk and Whiskers; Underworld Breach; Unexpected Potential; Vampiric Tutor; Vexing Bauble; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wheel of Fortune; White Plume Adventurer; Wicker Picker; Windfall; Wizards of the _____; Wolf in _____ Clothing; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker Additional:Aisha of Sparks and Smoke; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Legacy/2025-10-13.txt b/forge-gui/res/formats/Archived/Legacy/2025-10-13.txt new file mode 100644 index 00000000000..10ffdd080e4 --- /dev/null +++ b/forge-gui/res/formats/Archived/Legacy/2025-10-13.txt @@ -0,0 +1,9 @@ +[format] +Name:Legacy (2025-10-13) +Type:Archived +Subtype:Legacy +Effective:2025-10-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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA, EOE, EOC, EOS, SPM, SPE, MAR +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Baaallerina; Backup Plan; Balance; Bazaar of Baghdad; Bioluminary; Black Lotus; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Channel; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Deathrite Shaman; Dee Kay, Finder of the Lost; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Dreadhorde Arcanist; Duelist's Flame; Earthcraft; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Expressive Iteration; Falling Star; Fastbond; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash; Flash Photography; Frantic Search; Garland, Royal Kidnapper; Gitaxian Probe; Glitterflitter; Goblin Recruiter; Grief; 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; Judgment of Alexander; Last Voyage of the _____; Library of Alexandria; Line Cutter; Lineprancers; Lurrus of the Dream-Den; Make a _____ Splash; Mana Crypt; Mana Drain; Mana Vault; Mega Flare; Memory Jar; Mental Misstep; Mind Twist; Minotaur de Force; Mishra's Workshop; Monitor Monitor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Myra the Magnificent; Mystical Tutor; Natural Unity; Necropotence; Noctis, Heir Apparent; Oath of Druids; Oko, Thief of Crowns; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Psychic Frog; Quick Fixer; Rad Rascal; Ragavan, Nimble Pilferer; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Soul Swindler; Sovereign's Realm; Sowing Mycospawn; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Troll of Khazad-dûm; Tusk and Whiskers; Underworld Breach; Unexpected Potential; Vampiric Tutor; Vexing Bauble; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wheel of Fortune; White Plume Adventurer; Wicker Picker; Windfall; Wizards of the _____; Wolf in _____ Clothing; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker +Additional:Aisha of Sparks and Smoke; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jaws, Relentless Predator; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Legacy/2025-10-27.txt b/forge-gui/res/formats/Archived/Legacy/2025-10-27.txt new file mode 100644 index 00000000000..b675f1735d7 --- /dev/null +++ b/forge-gui/res/formats/Archived/Legacy/2025-10-27.txt @@ -0,0 +1,9 @@ +[format] +Name:Legacy (2025-10-27) +Type:Archived +Subtype:Legacy +Effective:2025-10-27 +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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA, EOE, EOC, EOS, SPM, SPE, MAR +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Baaallerina; Backup Plan; Balance; Bazaar of Baghdad; Bioluminary; Black Lotus; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Channel; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Deathrite Shaman; Dee Kay, Finder of the Lost; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Dreadhorde Arcanist; Duelist's Flame; Earthcraft; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Expressive Iteration; Falling Star; Fastbond; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash; Flash Photography; Frantic Search; Garland, Royal Kidnapper; Gitaxian Probe; Glitterflitter; Goblin Recruiter; Grief; 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; Judgment of Alexander; Last Voyage of the _____; Library of Alexandria; Line Cutter; Lineprancers; Lurrus of the Dream-Den; Make a _____ Splash; Mana Crypt; Mana Drain; Mana Vault; Mega Flare; Memory Jar; Mental Misstep; Mind Twist; Minotaur de Force; Mishra's Workshop; Monitor Monitor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Myra the Magnificent; Mystical Tutor; Natural Unity; Necropotence; Noctis, Heir Apparent; Oath of Druids; Oko, Thief of Crowns; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Psychic Frog; Quick Fixer; Rad Rascal; Ragavan, Nimble Pilferer; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Soul Swindler; Sovereign's Realm; Sowing Mycospawn; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Troll of Khazad-dûm; Tusk and Whiskers; Underworld Breach; Unexpected Potential; Vampiric Tutor; Vexing Bauble; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wheel of Fortune; White Plume Adventurer; Wicker Picker; Windfall; Wizards of the _____; Wolf in _____ Clothing; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker +Additional:Abby, Merciless Soldier; Aisha of Sparks and Smoke; Aloy, Savior of Meridian; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Atreus, Impulsive Son; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Ellie, Brick Master; Ellie, Vengeful Hunter; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jaws, Relentless Predator; Jin Sakai, Ghost of Tsushima; Joel, Resolute Survivor; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Kratos, God of War; Kratos, Stoic Father; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Nathan Drake, Treasure Hunter; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Legacy/2025-11-21.txt b/forge-gui/res/formats/Archived/Legacy/2025-11-21.txt new file mode 100644 index 00000000000..7c80ba74898 --- /dev/null +++ b/forge-gui/res/formats/Archived/Legacy/2025-11-21.txt @@ -0,0 +1,9 @@ +[format] +Name:Legacy (TLA) +Type:Archived +Subtype:Legacy +Effective:2025-11-21 +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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA, EOE, EOC, EOS, SPM, SPE, MAR, TLA, TLE +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Baaallerina; Backup Plan; Balance; Bazaar of Baghdad; Bioluminary; Black Lotus; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Channel; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Deathrite Shaman; Dee Kay, Finder of the Lost; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Dreadhorde Arcanist; Duelist's Flame; Earthcraft; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Expressive Iteration; Falling Star; Fastbond; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash; Flash Photography; Frantic Search; Garland, Royal Kidnapper; Gitaxian Probe; Glitterflitter; Goblin Recruiter; Grief; 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; Judgment of Alexander; Last Voyage of the _____; Library of Alexandria; Line Cutter; Lineprancers; Lurrus of the Dream-Den; Make a _____ Splash; Mana Crypt; Mana Drain; Mana Vault; Mega Flare; Memory Jar; Mental Misstep; Mind Twist; Minotaur de Force; Mishra's Workshop; Monitor Monitor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Myra the Magnificent; Mystical Tutor; Natural Unity; Necropotence; Noctis, Heir Apparent; Oath of Druids; Oko, Thief of Crowns; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Psychic Frog; Quick Fixer; Rad Rascal; Ragavan, Nimble Pilferer; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Soul Swindler; Sovereign's Realm; Sowing Mycospawn; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Troll of Khazad-dûm; Tusk and Whiskers; Underworld Breach; Unexpected Potential; Vampiric Tutor; Vexing Bauble; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wheel of Fortune; White Plume Adventurer; Wicker Picker; Windfall; Wizards of the _____; Wolf in _____ Clothing; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker +Additional:Abby, Merciless Soldier; Aisha of Sparks and Smoke; Aloy, Savior of Meridian; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Atreus, Impulsive Son; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Ellie, Brick Master; Ellie, Vengeful Hunter; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jaws, Relentless Predator; Jin Sakai, Ghost of Tsushima; Joel, Resolute Survivor; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Kratos, God of War; Kratos, Stoic Father; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Nathan Drake, Treasure Hunter; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Legacy/2025-12-05.txt b/forge-gui/res/formats/Archived/Legacy/2025-12-05.txt new file mode 100644 index 00000000000..39e4daf1d79 --- /dev/null +++ b/forge-gui/res/formats/Archived/Legacy/2025-12-05.txt @@ -0,0 +1,8 @@ +[format] +Name:Legacy (2025-12-05) +Type:Archived +Subtype:Legacy +Effective:2025-12-05 +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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA, EOE, EOC, EOS, SPM, SPE, MAR, TLA, TLE +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Ambassador Blorpityblorpboop; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Baaallerina; Backup Plan; Balance; Bazaar of Baghdad; Bioluminary; Black Lotus; Brago's Favor; Bronze Tablet; Carnival Carnivore; Channel; Chaos Orb; Chicken Troupe; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Deathrite Shaman; Dee Kay, Finder of the Lost; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Dreadhorde Arcanist; Earthcraft; Echoing Boon; Emissary's Ploy; Expressive Iteration; Falling Star; Fastbond; Fight the _____ Fight; Finishing Move; Flash; Frantic Search; Gitaxian Probe; Glitterflitter; Goblin Recruiter; Grief; 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; Last Voyage of the _____; Library of Alexandria; Line Cutter; Lineprancers; Lurrus of the Dream-Den; Make a _____ Splash; Mana Crypt; Mana Drain; Mana Vault; Memory Jar; Mental Misstep; Mind Twist; Minotaur de Force; Mishra's Workshop; Monitor Monitor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Myra the Magnificent; Mystical Tutor; Natural Unity; Necropotence; Oath of Druids; Oko, Thief of Crowns; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Psychic Frog; Quick Fixer; Rad Rascal; Ragavan, Nimble Pilferer; Rebirth; Ride Guide; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Soul Swindler; Sovereign's Realm; Sowing Mycospawn; Spinnerette, Arachnobat; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Sword-Swallowing Seraph; Tempest Efreet; The Most Dangerous Gamer; Ticketomaton; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Troll of Khazad-dûm; Tusk and Whiskers; Underworld Breach; Unexpected Potential; Vampiric Tutor; Vexing Bauble; Weight Advantage; Wheel of Fortune; White Plume Adventurer; Wicker Picker; Windfall; Wizards of the _____; Wolf in _____ Clothing; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker +Additional:Abby, Merciless Soldier; Aisha of Sparks and Smoke; Aloy, Savior of Meridian; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Atreus, Impulsive Son; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Ellie, Brick Master; Ellie, Vengeful Hunter; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jaws, Relentless Predator; Jin Sakai, Ghost of Tsushima; Joel, Resolute Survivor; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Kratos, God of War; Kratos, Stoic Father; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Nathan Drake, Treasure Hunter; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Vintage/2025-06-13.txt b/forge-gui/res/formats/Archived/Vintage/2025-06-13.txt index c2ee7da92d3..dd93eade737 100644 --- a/forge-gui/res/formats/Archived/Vintage/2025-06-13.txt +++ b/forge-gui/res/formats/Archived/Vintage/2025-06-13.txt @@ -5,5 +5,6 @@ Subtype:Vintage Effective:2025-06-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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA 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; Sol Ring; Strip Mine; Thorn of Amethyst; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Trinisphere; Urza's Saga; Vampiric Tutor; Vexing Bauble; Wheel of Fortune; Windfall; Yawgmoth's Will -Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Ambassador Blorpityblorpboop; Amulet of Quoz; Assemble the Rank and Vile; Baaallerina; Backup Plan; Bioluminary; Brago's Favor; Bronze Tablet; Carnival Carnivore; Chaos Orb; Chicken Troupe; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Dee Kay, Finder of the Lost; Demonic Attorney; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Echoing Boon; Emissary's Ploy; Falling Star; Fight the _____ Fight; Finishing Move; Glitterflitter; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Last Voyage of the _____; Line Cutter; Lineprancers; Make a _____ Splash; Minotaur de Force; Monitor Monitor; Muzzio's Preparations; Myra the Magnificent; Natural Unity; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Quick Fixer; Rad Rascal; Rebirth; Ride Guide; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Shahrazad; Soul Swindler; Sovereign's Realm; Spinnerette, Arachnobat; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Summoner's Bond; Sword-Swallowing Seraph; Tempest Efreet; The Most Dangerous Gamer; Ticketomaton; Timmerian Fiends; Tusk and Whiskers; Unexpected Potential; Weight Advantage; Wicker Picker; Wizards of the _____; Wolf in _____ Clothing; Worldknit +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Assemble the Rank and Vile; Baaallerina; Backup Plan; Bioluminary; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Dee Kay, Finder of the Lost; Demonic Attorney; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Duelist's Flame; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Falling Star; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash Photography; Garland, Royal Kidnapper; Glitterflitter; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Judgment of Alexander; Last Voyage of the _____; Line Cutter; Lineprancers; Make a _____ Splash; Mega Flare; Minotaur de Force; Monitor Monitor; Muzzio's Preparations; Myra the Magnificent; Natural Unity; Noctis, Heir Apparent; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Quick Fixer; Rad Rascal; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sentinel Dispatch; Shahrazad; Soul Swindler; Sovereign's Realm; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Summoner's Bond; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Timmerian Fiends; Tusk and Whiskers; Unexpected Potential; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wicker Picker; Wizards of the _____; Wolf in _____ Clothing; Worldknit Additional:Aisha of Sparks and Smoke; Arden Angel; Arvinox, the Mind Flail; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jurin, Leading the Charge; Ken, Burning Brawler; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Simon, Wild Magic Sorcerer; Sophina, Spearsage Deserter; Storm, Force of Nature; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Vintage/2025-07-14.txt b/forge-gui/res/formats/Archived/Vintage/2025-07-14.txt index 97e7e9fe865..10f64963695 100644 --- a/forge-gui/res/formats/Archived/Vintage/2025-07-14.txt +++ b/forge-gui/res/formats/Archived/Vintage/2025-07-14.txt @@ -5,5 +5,6 @@ Subtype:Vintage Effective:2025-07-14 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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA 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; Sol Ring; Strip Mine; Thorn of Amethyst; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Trinisphere; Urza's Saga; Vampiric Tutor; Vexing Bauble; Wheel of Fortune; Windfall; Yawgmoth's Will -Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Ambassador Blorpityblorpboop; Amulet of Quoz; Assemble the Rank and Vile; Baaallerina; Backup Plan; Bioluminary; Brago's Favor; Bronze Tablet; Carnival Carnivore; Chaos Orb; Chicken Troupe; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Dee Kay, Finder of the Lost; Demonic Attorney; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Echoing Boon; Emissary's Ploy; Falling Star; Fight the _____ Fight; Finishing Move; Glitterflitter; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Last Voyage of the _____; Line Cutter; Lineprancers; Make a _____ Splash; Minotaur de Force; Monitor Monitor; Muzzio's Preparations; Myra the Magnificent; Natural Unity; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Quick Fixer; Rad Rascal; Rebirth; Ride Guide; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Shahrazad; Soul Swindler; Sovereign's Realm; Spinnerette, Arachnobat; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Summoner's Bond; Sword-Swallowing Seraph; Tempest Efreet; The Most Dangerous Gamer; Ticketomaton; Timmerian Fiends; Tusk and Whiskers; Unexpected Potential; Weight Advantage; Wicker Picker; Wizards of the _____; Wolf in _____ Clothing; Worldknit +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Assemble the Rank and Vile; Baaallerina; Backup Plan; Bioluminary; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Dee Kay, Finder of the Lost; Demonic Attorney; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Duelist's Flame; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Falling Star; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash Photography; Garland, Royal Kidnapper; Glitterflitter; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Judgment of Alexander; Last Voyage of the _____; Line Cutter; Lineprancers; Make a _____ Splash; Mega Flare; Minotaur de Force; Monitor Monitor; Muzzio's Preparations; Myra the Magnificent; Natural Unity; Noctis, Heir Apparent; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Quick Fixer; Rad Rascal; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sentinel Dispatch; Shahrazad; Soul Swindler; Sovereign's Realm; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Summoner's Bond; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Timmerian Fiends; Tusk and Whiskers; Unexpected Potential; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wicker Picker; Wizards of the _____; Wolf in _____ Clothing; Worldknit Additional:Aisha of Sparks and Smoke; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Vintage/2025-08-01.txt b/forge-gui/res/formats/Archived/Vintage/2025-08-01.txt index 941de8416c7..112327d82df 100644 --- a/forge-gui/res/formats/Archived/Vintage/2025-08-01.txt +++ b/forge-gui/res/formats/Archived/Vintage/2025-08-01.txt @@ -5,5 +5,6 @@ Subtype:Vintage Effective:2025-08-01 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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA, EOE, EOC, EOS 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; Sol Ring; Strip Mine; Thorn of Amethyst; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Trinisphere; Urza's Saga; Vampiric Tutor; Vexing Bauble; Wheel of Fortune; Windfall; Yawgmoth's Will -Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Ambassador Blorpityblorpboop; Amulet of Quoz; Assemble the Rank and Vile; Baaallerina; Backup Plan; Bioluminary; Brago's Favor; Bronze Tablet; Carnival Carnivore; Chaos Orb; Chicken Troupe; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Dee Kay, Finder of the Lost; Demonic Attorney; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Echoing Boon; Emissary's Ploy; Falling Star; Fight the _____ Fight; Finishing Move; Glitterflitter; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Last Voyage of the _____; Line Cutter; Lineprancers; Make a _____ Splash; Minotaur de Force; Monitor Monitor; Muzzio's Preparations; Myra the Magnificent; Natural Unity; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Quick Fixer; Rad Rascal; Rebirth; Ride Guide; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Shahrazad; Soul Swindler; Sovereign's Realm; Spinnerette, Arachnobat; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Summoner's Bond; Sword-Swallowing Seraph; Tempest Efreet; The Most Dangerous Gamer; Ticketomaton; Timmerian Fiends; Tusk and Whiskers; Unexpected Potential; Weight Advantage; Wicker Picker; Wizards of the _____; Wolf in _____ Clothing; Worldknit +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Assemble the Rank and Vile; Baaallerina; Backup Plan; Bioluminary; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Dee Kay, Finder of the Lost; Demonic Attorney; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Duelist's Flame; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Falling Star; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash Photography; Garland, Royal Kidnapper; Glitterflitter; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Judgment of Alexander; Last Voyage of the _____; Line Cutter; Lineprancers; Make a _____ Splash; Mega Flare; Minotaur de Force; Monitor Monitor; Muzzio's Preparations; Myra the Magnificent; Natural Unity; Noctis, Heir Apparent; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Quick Fixer; Rad Rascal; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sentinel Dispatch; Shahrazad; Soul Swindler; Sovereign's Realm; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Summoner's Bond; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Timmerian Fiends; Tusk and Whiskers; Unexpected Potential; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wicker Picker; Wizards of the _____; Wolf in _____ Clothing; Worldknit Additional:Aisha of Sparks and Smoke; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Vintage/2025-09-26.txt b/forge-gui/res/formats/Archived/Vintage/2025-09-26.txt index 7c46b50af15..6f73a45865e 100644 --- a/forge-gui/res/formats/Archived/Vintage/2025-09-26.txt +++ b/forge-gui/res/formats/Archived/Vintage/2025-09-26.txt @@ -5,5 +5,6 @@ Subtype:Vintage Effective:2025-09-26 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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA, EOE, EOC, EOS, SPM, SPE, MAR 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; Sol Ring; Strip Mine; Thorn of Amethyst; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Trinisphere; Urza's Saga; Vampiric Tutor; Vexing Bauble; Wheel of Fortune; Windfall; Yawgmoth's Will -Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Ambassador Blorpityblorpboop; Amulet of Quoz; Assemble the Rank and Vile; Baaallerina; Backup Plan; Bioluminary; Brago's Favor; Bronze Tablet; Carnival Carnivore; Chaos Orb; Chicken Troupe; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Dee Kay, Finder of the Lost; Demonic Attorney; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Echoing Boon; Emissary's Ploy; Falling Star; Fight the _____ Fight; Finishing Move; Glitterflitter; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Last Voyage of the _____; Line Cutter; Lineprancers; Make a _____ Splash; Minotaur de Force; Monitor Monitor; Muzzio's Preparations; Myra the Magnificent; Natural Unity; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Quick Fixer; Rad Rascal; Rebirth; Ride Guide; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Shahrazad; Soul Swindler; Sovereign's Realm; Spinnerette, Arachnobat; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Summoner's Bond; Sword-Swallowing Seraph; Tempest Efreet; The Most Dangerous Gamer; Ticketomaton; Timmerian Fiends; Tusk and Whiskers; Unexpected Potential; Weight Advantage; Wicker Picker; Wizards of the _____; Wolf in _____ Clothing; Worldknit +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Assemble the Rank and Vile; Baaallerina; Backup Plan; Bioluminary; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Dee Kay, Finder of the Lost; Demonic Attorney; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Duelist's Flame; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Falling Star; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash Photography; Garland, Royal Kidnapper; Glitterflitter; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Judgment of Alexander; Last Voyage of the _____; Line Cutter; Lineprancers; Make a _____ Splash; Mega Flare; Minotaur de Force; Monitor Monitor; Muzzio's Preparations; Myra the Magnificent; Natural Unity; Noctis, Heir Apparent; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Quick Fixer; Rad Rascal; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sentinel Dispatch; Shahrazad; Soul Swindler; Sovereign's Realm; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Summoner's Bond; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Timmerian Fiends; Tusk and Whiskers; Unexpected Potential; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wicker Picker; Wizards of the _____; Wolf in _____ Clothing; Worldknit Additional:Aisha of Sparks and Smoke; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Vintage/2025-10-13.txt b/forge-gui/res/formats/Archived/Vintage/2025-10-13.txt new file mode 100644 index 00000000000..e0ffbeff036 --- /dev/null +++ b/forge-gui/res/formats/Archived/Vintage/2025-10-13.txt @@ -0,0 +1,10 @@ +[format] +Name:Vintage (2025-10-13) +Type:Archived +Subtype:Vintage +Effective:2025-10-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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA, EOE, EOC, EOS, SPM, SPE, MAR +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; Sol Ring; Strip Mine; Thorn of Amethyst; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Trinisphere; Urza's Saga; Vampiric Tutor; Vexing Bauble; Wheel of Fortune; Windfall; Yawgmoth's Will +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Assemble the Rank and Vile; Baaallerina; Backup Plan; Bioluminary; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Dee Kay, Finder of the Lost; Demonic Attorney; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Duelist's Flame; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Falling Star; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash Photography; Garland, Royal Kidnapper; Glitterflitter; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Judgment of Alexander; Last Voyage of the _____; Line Cutter; Lineprancers; Make a _____ Splash; Mega Flare; Minotaur de Force; Monitor Monitor; Muzzio's Preparations; Myra the Magnificent; Natural Unity; Noctis, Heir Apparent; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Quick Fixer; Rad Rascal; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sentinel Dispatch; Shahrazad; Soul Swindler; Sovereign's Realm; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Summoner's Bond; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Timmerian Fiends; Tusk and Whiskers; Unexpected Potential; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wicker Picker; Wizards of the _____; Wolf in _____ Clothing; Worldknit +Additional:Aisha of Sparks and Smoke; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jaws, Relentless Predator; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Vintage/2025-10-27.txt b/forge-gui/res/formats/Archived/Vintage/2025-10-27.txt new file mode 100644 index 00000000000..37850f8dd78 --- /dev/null +++ b/forge-gui/res/formats/Archived/Vintage/2025-10-27.txt @@ -0,0 +1,10 @@ +[format] +Name:Vintage (2025-10-27) +Type:Archived +Subtype:Vintage +Effective:2025-10-27 +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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA, EOE, EOC, EOS, SPM, SPE, MAR +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; Sol Ring; Strip Mine; Thorn of Amethyst; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Trinisphere; Urza's Saga; Vampiric Tutor; Vexing Bauble; Wheel of Fortune; Windfall; Yawgmoth's Will +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Assemble the Rank and Vile; Baaallerina; Backup Plan; Bioluminary; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Dee Kay, Finder of the Lost; Demonic Attorney; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Duelist's Flame; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Falling Star; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash Photography; Garland, Royal Kidnapper; Glitterflitter; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Judgment of Alexander; Last Voyage of the _____; Line Cutter; Lineprancers; Make a _____ Splash; Mega Flare; Minotaur de Force; Monitor Monitor; Muzzio's Preparations; Myra the Magnificent; Natural Unity; Noctis, Heir Apparent; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Quick Fixer; Rad Rascal; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sentinel Dispatch; Shahrazad; Soul Swindler; Sovereign's Realm; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Summoner's Bond; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Timmerian Fiends; Tusk and Whiskers; Unexpected Potential; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wicker Picker; Wizards of the _____; Wolf in _____ Clothing; Worldknit +Additional:Abby, Merciless Soldier; Aisha of Sparks and Smoke; Aloy, Savior of Meridian; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Atreus, Impulsive Son; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Ellie, Brick Master; Ellie, Vengeful Hunter; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jaws, Relentless Predator; Jin Sakai, Ghost of Tsushima; Joel, Resolute Survivor; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Kratos, God of War; Kratos, Stoic Father; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Nathan Drake, Treasure Hunter; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Vintage/2025-11-21.txt b/forge-gui/res/formats/Archived/Vintage/2025-11-21.txt new file mode 100644 index 00000000000..15d3e61f496 --- /dev/null +++ b/forge-gui/res/formats/Archived/Vintage/2025-11-21.txt @@ -0,0 +1,10 @@ +[format] +Name:Vintage (TLA) +Type:Archived +Subtype:Vintage +Effective:2025-11-21 +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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA, EOE, EOC, EOS, SPM, SPE, MAR, TLA, TLE +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; Sol Ring; Strip Mine; Thorn of Amethyst; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Trinisphere; Urza's Saga; Vampiric Tutor; Vexing Bauble; Wheel of Fortune; Windfall; Yawgmoth's Will +// Some FIC cards are "banned" in this format until 5 December 2025 due to an additional product "Holiday" release +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Amarant Coral; Ambassador Blorpityblorpboop; Amulet of Quoz; Assemble the Rank and Vile; Baaallerina; Backup Plan; Bioluminary; Brago's Favor; Brilliant Wings; Bronze Tablet; Campsite Cuisine; Carnival Carnivore; Chaos Orb; Chaos Shrine's Black Crystal; Chicken Troupe; Chocobo Camp; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Dee Kay, Finder of the Lost; Demonic Attorney; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Duelist's Flame; Echoing Boon; Edea, Possessed Sorceress; Emissary's Ploy; Falling Star; Fated Clash; Fight the _____ Fight; Finishing Move; Fishing Gear; Flash Photography; Garland, Royal Kidnapper; Glitterflitter; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Judgment of Alexander; Last Voyage of the _____; Line Cutter; Lineprancers; Make a _____ Splash; Mega Flare; Minotaur de Force; Monitor Monitor; Muzzio's Preparations; Myra the Magnificent; Natural Unity; Noctis, Heir Apparent; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Quick Fixer; Rad Rascal; Rebirth; Ride Guide; Rinoa, Angel Wing; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Search for Dagger; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Seifer, Balamb Rival; Sentinel Dispatch; Shahrazad; Soul Swindler; Sovereign's Realm; Spinnerette, Arachnobat; Squall, Gunblade Duelist; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Summoner's Bond; Sword-Swallowing Seraph; Tempest Efreet; The Destined Black Mage; The Destined Thief; The Destined Warrior; The Destined White Mage; The Most Dangerous Gamer; Ticketomaton; Timmerian Fiends; Tusk and Whiskers; Unexpected Potential; Vivi's Persistence; Warrior's Resolve; Weight Advantage; Wicker Picker; Wizards of the _____; Wolf in _____ Clothing; Worldknit +Additional:Abby, Merciless Soldier; Aisha of Sparks and Smoke; Aloy, Savior of Meridian; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Atreus, Impulsive Son; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Ellie, Brick Master; Ellie, Vengeful Hunter; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jaws, Relentless Predator; Jin Sakai, Ghost of Tsushima; Joel, Resolute Survivor; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Kratos, God of War; Kratos, Stoic Father; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Nathan Drake, Treasure Hunter; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Archived/Vintage/2025-12-05.txt b/forge-gui/res/formats/Archived/Vintage/2025-12-05.txt new file mode 100644 index 00000000000..6639451bc8a --- /dev/null +++ b/forge-gui/res/formats/Archived/Vintage/2025-12-05.txt @@ -0,0 +1,9 @@ +[format] +Name:Vintage (2025-12-05) +Type:Archived +Subtype:Vintage +Effective:2025-12-05 +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, HOP, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, ARC, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, PC2, 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, PCA, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, E01, HOU, C17, XLN, DDT, IMA, V17, E02, 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, Q06, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR, ONE, ONC, MOM, MOC, MUL, MAT, LTR, LTC, CMM, WOE, WOC, WOT, WHO, LCI, LCC, REX, RVR, MKM, MKC, CLU, PIP, OTJ, OTC, OTP, BIG, MH3, M3C, H2R, ACR, BLB, BLC, MB2, DSK, DSC, FDN, FDC, J25, INR, DFT, DRC, TDM, TDC, FIN, FIC, FCA, EOE, EOC, EOS, SPM, SPE, MAR, TLA, TLE +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; Sol Ring; Strip Mine; Thorn of Amethyst; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Trinisphere; Urza's Saga; Vampiric Tutor; Vexing Bauble; Wheel of Fortune; Windfall; Yawgmoth's Will +Banned:"Lifetime" Pass Holder; _____ _____ _____ Trespasser; _____ _____ Rocketship; _____ Balls of Fire; _____ Bird Gets the Worm; _____ Goblin; _____-o-saurus; Adriana's Valor; Advantageous Proclamation; Aerialephant; Ambassador Blorpityblorpboop; Amulet of Quoz; Assemble the Rank and Vile; Baaallerina; Backup Plan; Bioluminary; Brago's Favor; Bronze Tablet; Carnival Carnivore; Chaos Orb; Chicken Troupe; Clandestine Chameleon; Cleanse; Coming Attraction; Command Performance; Complaints Clerk; Contract from Below; Crusade; Darkpact; Deadbeat Attendant; Dee Kay, Finder of the Lost; Demonic Attorney; Discourtesy Clerk; Done for the Day; Double Stroke; Draconian Gate-Bot; Echoing Boon; Emissary's Ploy; Falling Star; Fight the _____ Fight; Finishing Move; Glitterflitter; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Last Voyage of the _____; Line Cutter; Lineprancers; Make a _____ Splash; Minotaur de Force; Monitor Monitor; Muzzio's Preparations; Myra the Magnificent; Natural Unity; Park Bleater; Petting Zookeeper; Pin Collection; Power Play; Pradesh Gypsies; Prize Wall; Proficient Pyrodancer; Quick Fixer; Rad Rascal; Rebirth; Ride Guide; Robo-Piñata; Roxi, Publicist to the Stars; Scampire; Seasoned Buttoneer; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Shahrazad; Soul Swindler; Sovereign's Realm; Spinnerette, Arachnobat; Squirrel Squatters; Step Right Up; Stiltstrider; Stone-Throwing Devils; Summoner's Bond; Sword-Swallowing Seraph; Tempest Efreet; The Most Dangerous Gamer; Ticketomaton; Timmerian Fiends; Tusk and Whiskers; Unexpected Potential; Weight Advantage; Wicker Picker; Wizards of the _____; Wolf in _____ Clothing; Worldknit +Additional:Abby, Merciless Soldier; Aisha of Sparks and Smoke; Aloy, Savior of Meridian; Amy Rose; Arden Angel; Arvinox, the Mind Flail; Atreus, Impulsive Son; Baldin, Century Herdmaster; Bjorna, Nightfall Alchemist; Black Panther, Wakandan King; Blanka, Ferocious Friend; Bohn, Beguiling Balladeer; Captain America, First Avenger; Casal, Lurkwood Pathfinder; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Deadpool, Trading Card; Dhalsim, Pliable Pacifist; Doric, Nature's Warden; Dr. Eggman; Dustin, Gadget Genius; E. Honda, Sumo Champion; Edgin, Larcenous Lutenist; Eleven, the Mage; Ellie, Brick Master; Ellie, Vengeful Hunter; Elmar, Ulvenwald Informant; Enkira, Hostile Scavenger; Evin, Waterdeep Opportunist; Forge, Neverwinter Charlatan; Gisa's Favorite Shovel; Glenn, the Voice of Calm; Gregor, Shrewd Magistrate; Greymond, Avacyn's Stalwart; Guile, Sonic Soldier; Hansk, Slayer Zealot; Hargilde, Kindly Runechanter; Havengul Laboratory; Hawkins National Laboratory; Holga, Relentless Rager; Immard, the Stormcleaver; Iron Man, Titan of Innovation; Jaws, Relentless Predator; Jin Sakai, Ghost of Tsushima; Joel, Resolute Survivor; Jurin, Leading the Charge; Ken, Burning Brawler; Knuckles the Echidna; Kratos, God of War; Kratos, Stoic Father; Lara Croft, Tomb Raider; Lucas, the Sharpshooter; Lucille; Maarika, Brutal Gladiator; Malik, Grim Manipulator; Mathise, Surge Channeler; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Miles "Tails" Prower; Mind Flayer, the Shadow; Nathan Drake, Treasure Hunter; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rashel, Fist of Torm; Rick, Steadfast Leader; Rose Noble; Ryu, World Warrior; Shadow the Hedgehog; Simon, Wild Magic Sorcerer; Sonic the Hedgehog; Sophina, Spearsage Deserter; Storm, Force of Nature; Super State; Tadeas, Juniper Ascendant; The Celestial Toymaker; The Fifteenth Doctor; The Fourteenth Doctor; The Howling Abomination; The Meep; Themberchaud; Vikya, Scorching Stalwart; Wernog, Rider's Chaplain; Will the Wise; Wolverine, Best There Is; Xenk, Paladin Unbroken; Zangief, the Red Cyclone; Zethi, Arcane Blademaster diff --git a/forge-gui/res/formats/Sanctioned/Historic.txt b/forge-gui/res/formats/Sanctioned/Historic.txt index dac663817b9..47635330c83 100644 --- a/forge-gui/res/formats/Sanctioned/Historic.txt +++ b/forge-gui/res/formats/Sanctioned/Historic.txt @@ -5,5 +5,5 @@ Subtype:Arena Effective:2019-11-21 Order:142 Sets:KTK, XLN, RIX, DOM, M19, ANA, PANA, GRN, G18, RNA, WAR, M20, ELD, HA1, THB, HA2, IKO, HA3, M21, JMP, AJMP, AKR, ANB, ZNR, KLR, KHM, HA4, STX, STA, HA5, AFR, J21, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG, HA6, EA1, DMU, YDMU, BRO, BRR, YBRO, EA2, ONE, YONE, SIR, SIS, MOM, MUL, MAT, LTR, HA7, EA3, WOE, WOT, YWOE, LCI, YLCI, MKM, YMKM, OTJ, OTP, BIG, YOTJ, MH3, BLB, YBLB, DSK, YDSK, FDN, J25, PIO, DFT, YDFT, TDM, YTDM, PA1, FIN, FCA, EOE, EOS, YEOE, AA1, AA2, OM1, OMB, AA3, AA4 -Banned:Agent of Treachery; Ancient Tomb; Arid Mesa; Blood Moon; Bloodstained Mire; Brainstorm; Channel; Chrome Mox; Commandeer; Dark Ritual; Demonic Tutor; Endurance; Field of the Dead; Flare of Cultivation; Flare of Denial; Flare of Duplication; Flare of Fortitude; Flare of Malice; Flooded Strand; Force of Vigor; Fury; Grief; Harbinger of the Seas; Intruder Alarm; Land Tax; Lightning Bolt; Magus of the Moon; Mana Drain; Marsh Flats; Memory Lapse; Mishra's Bauble; Misty Rainforest; Mox Opal; Natural Order; Necropotence; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Polluted Delta; Ragavan, Nimble Pilferer; Reanimate; Scalding Tarn; Show and Tell; Sneak Attack; Solitude; Spreading Seas; Strip Mine; Subtlety; Swords to Plowshares; Temporal Manipulation; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Verdant Catacombs; Wilderness Reclamation; Windswept Heath; Winter Moon; Wooded Foothills +Banned:Agent of Treachery; Ancient Tomb; Arid Mesa; Blood Moon; Bloodstained Mire; Brainstorm; Broadside Bombardiers; Channel; Chrome Mox; Commandeer; Dark Ritual; Demonic Tutor; Endurance; Field of the Dead; Fireblast; Flare of Cultivation; Flare of Denial; Flare of Duplication; Flare of Fortitude; Flare of Malice; Flooded Strand; Force of Vigor; Fury; Grief; Gut, True Soul Zealot; Harbinger of the Seas; Intruder Alarm; Land Tax; Lightning Bolt; Magus of the Moon; Mana Drain; Marsh Flats; Memory Lapse; Mishra's Bauble; Misty Rainforest; Mox Opal; Natural Order; Necropotence; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Polluted Delta; Preordain; Pyrokinesis; Ragavan, Nimble Pilferer; Reanimate; Scalding Tarn; Seething Song; Show and Tell; Sneak Attack; Solitude; Spreading Seas; Strip Mine; Subtlety; Swords to Plowshares; Temporal Manipulation; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Verdant Catacombs; Wilderness Reclamation; Windswept Heath; Winter Moon; Wooded Foothills Additional:Admiral Brass, Unsinkable; Burden of Guilt; Clavileño, First of the Blessed; Crashing Footfalls; Crumbling Vestige; Desert; Desertion; Dismember; Duskmantle, House of Shadow; Enlisted Wurm; Evolutionary Leap; Fabricate; Gamble; Ghostly Prison; Gonti, Canny Acquisitor; Goro-Goro and Satoru; Ixidor, Reality Sculptor; Katilda and Lier; Kuldotha Rebirth; Leonin Relic-Warder; Magmaw; Mass Hysteria; Metalspinner's Puzzleknot; Mistveil Plains; Molten Psyche; Monologue Tax; Mystery Key; Mystic Snake; Notion Thief; Nyx Weaver; Olivia, Opulent Outlaw; Pantlaza, Sun-Favored; Persist; Port Razer; Possibility Storm; Prismatic Ending; Prismatic Vista; Putrid Warrior; Shard of Broken Glass; Slimefoot and Squee; Smuggler's Copter; Spell Snare; Stella Lee, Wild Card; Stoneforge Mystic; Timeless Dragon; Treacherous Terrain; Victimize; Xolatoyac, the Smiling Flood; Yuma, Proud Protector From 2fa753d583d87317afd0d645f8de016622fcf832 Mon Sep 17 00:00:00 2001 From: Paul Hammerton Date: Tue, 28 Oct 2025 10:22:47 +0000 Subject: [PATCH 191/230] Edition updates: LMAR, PF25, PMEI, PSPL, PW25, SLC, SLD, TLA, TLE --- .../editions/Avatar The Last Airbender Eternal.txt | 2 ++ forge-gui/res/editions/Avatar The Last Airbender.txt | 3 +++ forge-gui/res/editions/MagicFest 2025.txt | 2 ++ .../res/editions/Marvel Legends Series Inserts.txt | 12 ++++++++++++ .../res/editions/Media and Collaboration Promos.txt | 5 +---- .../Secret Lair 30th Anniversary Countdown Kit.txt | 5 +++-- forge-gui/res/editions/Secret Lair Drop Series.txt | 5 ++++- forge-gui/res/editions/Spotlight Series.txt | 1 + forge-gui/res/editions/Wizards Play Network 2025.txt | 5 ++++- 9 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 forge-gui/res/editions/Marvel Legends Series Inserts.txt diff --git a/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt b/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt index 2bd102bd231..bf8dbdd1e00 100644 --- a/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt +++ b/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt @@ -6,8 +6,10 @@ Type=Expansion ScryfallCode=TLE [cards] +5 M Release to Memory @Viacom 13 M Force of Negation @Viacom 41 M The Great Henge @Viacom +61 M Valakut, the Molten Pinnacle @Viacom 74 M Aang, Airbending Master @Tomoyo Asatani 93 M Katara, Waterbending Master @Yueko 104 M Fire Lord Ozai @Ryuichi Sakuma diff --git a/forge-gui/res/editions/Avatar The Last Airbender.txt b/forge-gui/res/editions/Avatar The Last Airbender.txt index 75e3c4efd2d..b8964ce3072 100644 --- a/forge-gui/res/editions/Avatar The Last Airbender.txt +++ b/forge-gui/res/editions/Avatar The Last Airbender.txt @@ -57,6 +57,7 @@ ScryfallCode=TLA 148 C Mongoose Lizard @Joseph Weston 151 R Redirect Lightning @Toni Infante 152 C Rough Rhino Cavalry @Yuhong Ding +154 M Sozin's Comet @Salvatorre Zee Yazzie 161 C Yuyan Archers @Domco. 163 U Zuko, Exiled Prince @Nijihayashi 166 C Badgermole @Matteo Bassini @@ -100,8 +101,10 @@ ScryfallCode=TLA 289 L Swamp @John Di Giovanni 290 L Mountain @Lorenzo Lanfranconi 291 L Forest @Luc Courtois +309 M Sozin's Comet @Dominik Mayer 316 M Appa, Steadfast Guardian @Ilse Gort 317 R Momo, Friendly Flier @Filip Burburan +332 M Sozin's Comet @JungShan 336 R Aang's Iceberg @Brigitte Roka & Clifton Stommel 338 R Yue, the Moon Spirit @Barbara Rosiak 341 M Fated Firepower @Brigitte Roka diff --git a/forge-gui/res/editions/MagicFest 2025.txt b/forge-gui/res/editions/MagicFest 2025.txt index c28a241c3fb..b6f14376a4e 100644 --- a/forge-gui/res/editions/MagicFest 2025.txt +++ b/forge-gui/res/editions/MagicFest 2025.txt @@ -23,3 +23,5 @@ F8 R All-You-Can-Eat Buffet @Mark Rosewater 14 R Scourge of Valkas @Svetlin Velinov 15 M The Ur-Dragon @Jaime Jones F16 R Spaghetti Junction @Mark Rosewater +17 R J. Jonah Jameson @Borja Pindado +18 R Sokka, Bold Boomeranger @Brian Yuen diff --git a/forge-gui/res/editions/Marvel Legends Series Inserts.txt b/forge-gui/res/editions/Marvel Legends Series Inserts.txt new file mode 100644 index 00000000000..4282b94c48a --- /dev/null +++ b/forge-gui/res/editions/Marvel Legends Series Inserts.txt @@ -0,0 +1,12 @@ +[metadata] +Code=LMAR +Date=2025-09-30 +Name=Marvel Legends Series Inserts +Type=Promo +ScryfallCode=LMAR + +[cards] +1 M Anti-Venom, Horrifying Healer @Lordigan +2 R Spectacular Spider-Man @Alex Horley-Orlandelli +3 R Huntmaster of the Fells @Mark Spears ${"flavorName": "Colonel John Jameson // Man-Wolf"} +4 R Iron Spider, Stark Upgrade @Bachzim diff --git a/forge-gui/res/editions/Media and Collaboration Promos.txt b/forge-gui/res/editions/Media and Collaboration Promos.txt index 13cf87faf04..ea2aedb085e 100644 --- a/forge-gui/res/editions/Media and Collaboration Promos.txt +++ b/forge-gui/res/editions/Media and Collaboration Promos.txt @@ -79,10 +79,7 @@ ScryfallCode=PMEI 2025-12 R Lightning, Security Sergeant @Ramza Psyru 2025-13 M Cloud, Planet's Champion @Magali Villeneuve 2025-14 M Sephiroth, Planet's Heir @Magali Villeneuve -2025-15 M Anti-Venom, Horrifying Healer @Lordigan -2025-16 R Spectacular Spider-Man @Alex Horley-Orlandelli -2025-17 R Huntmaster of the Fells @Mark Spears -2025-18 R Iron Spider, Stark Upgrade @Bachzim 2025-19 M Kaalia of the Vast @Justyna Dura 2025-20 R Chrome Host Seedshark @Donato Giancola 2025-21 M Cloud, Midgar Mercenary @Square Enix +2025-22 M Peter Parker @Randy Gallegos diff --git a/forge-gui/res/editions/Secret Lair 30th Anniversary Countdown Kit.txt b/forge-gui/res/editions/Secret Lair 30th Anniversary Countdown Kit.txt index 38a858e5343..f32810897d7 100644 --- a/forge-gui/res/editions/Secret Lair 30th Anniversary Countdown Kit.txt +++ b/forge-gui/res/editions/Secret Lair 30th Anniversary Countdown Kit.txt @@ -6,9 +6,10 @@ Type=Collector_Edition ScryfallCode=SLC [cards] +4 R Demonic Consultation @Edward Steed 1993 R Shivan Dragon @Justine Jones 1994 R Mishra's Factory @DXTR -1995 M Necropotence @Rafal Wechterowicz +1995 M Necropotence @Rafal Wechterowicz (Too Many Skulls) 1996 R Lim-Dûl's Vault @Wizard of Barge 1997 R Tradewind Rider @BrooklynSnobs 1998 R Smokestack @Jaime A. Zuverza @@ -35,5 +36,5 @@ ScryfallCode=SLC 2019 R Emry, Lurker of the Loch @Brandi Milne 2020 R Shark Typhoon @Edgar Sánchez Hidalgo 2021 R Elite Spellbinder @Alexis Ziritt -2022 M Nashi, Moon Sage's Scion @Death Burger +2022 M Nashi, Moon Sage's Scion @Deathburger 2023 M Lotus Field @ZIUK diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index e9721459971..898222dcf68 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -876,6 +876,7 @@ F869 R Blacker Lotus @Scott Okumura 908 R Arcane Signet @Alexander Forssberg 909 R Gilded Lotus @Kasia 'Kafis' Zielińska 910 R Sol Ring @6VCR +912 R Sol Ring @Bill McConkey 1001 M Elspeth, Knight-Errant @Volkan Baǵa 1002 R Patron Wizard @Volkan Baǵa 1003 M Berserk @Volkan Baǵa @@ -2220,7 +2221,9 @@ F1540 M Rainbow Dash @John Thacker 7035 M Lotus Petal @Mike Burns 7036 M Lotus Petal @Mike Burns 7037 M Lotus Petal @Mike Burns -7040 R Vision Charm @ +7040 R Vision Charm @Bernard Lee +7048 R Scute Swarm @Jason Loik & Matthew Cohen +7055 R Tetsuko Umezawa, Fugitive @Mitch Mohrhauser ${"flavorName": "Atsu, Ghost of Yōtei"} 8001 M Jace, the Mind Sculptor @Wizard of Barge 9990 R Doom Blade @Cynthia Sheppard 9991 R Massacre @Andrey Kuzinskiy diff --git a/forge-gui/res/editions/Spotlight Series.txt b/forge-gui/res/editions/Spotlight Series.txt index ffffa1d646c..0cb857490e1 100644 --- a/forge-gui/res/editions/Spotlight Series.txt +++ b/forge-gui/res/editions/Spotlight Series.txt @@ -11,3 +11,4 @@ ScryfallCode=PSPL 3 M Sword of Forge and Frontier @Sam Guay 5 M Cloud, Midgar Mercenary @Tetsuya Nomura 6 R Get Lost @Dominik Mayer +7 R Spectacular Spider-Man @Julian Totino Tedesco diff --git a/forge-gui/res/editions/Wizards Play Network 2025.txt b/forge-gui/res/editions/Wizards Play Network 2025.txt index d49584b8b92..773943cd5a5 100644 --- a/forge-gui/res/editions/Wizards Play Network 2025.txt +++ b/forge-gui/res/editions/Wizards Play Network 2025.txt @@ -13,6 +13,9 @@ ScryfallCode=PW25 4 R Culling Ritual @AKAGI 5 R Zidane, Tantalus Thief @Eiji Kaneda 6 R Palladium Myr @Alan Pollack -7 R Spectacular Spider-Man @Julian Totino Tedesco 8 R Trinket Mage @Scott Chou 9 R Monstrous Rage @Borja Pindado +10 R Spider-Ham, Peter Porker @Paolo Rivera +11 R Mary Jane Watson @Paolo Rivera +12 R Ultimate Green Goblin @Tyler Walpole +13 R Carnage, Crimson Chaos @Lucio Parrillo From 4673ec23bd851b4bf77be2f0ec922a9122225b95 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:22:55 +0100 Subject: [PATCH 192/230] Two TLA cards (#9020) --- .../upcoming/the_mechanist_aerial_artisan.txt | 9 +++++++++ .../res/cardsfolder/upcoming/ty_lee_chi_blocker.txt | 12 ++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/the_mechanist_aerial_artisan.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/ty_lee_chi_blocker.txt diff --git a/forge-gui/res/cardsfolder/upcoming/the_mechanist_aerial_artisan.txt b/forge-gui/res/cardsfolder/upcoming/the_mechanist_aerial_artisan.txt new file mode 100644 index 00000000000..a5115e735ca --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_mechanist_aerial_artisan.txt @@ -0,0 +1,9 @@ +Name:The Mechanist, Aerial Artisan +ManaCost:2 U +Types:Legendary Creature Human Artificer Ally +PT:1/3 +T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you cast a noncreature spell, create a Clue token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You +A:AB$ Animate | Cost$ T | ValidTgts$ Artifact.token+YouCtrl | TgtPrompt$ Select target artifact token you control | Power$ 3 | Toughness$ 1 | Types$ Artifact,Creature,Construct | Keywords$ Flying | SpellDescription$ Until end of turn, target artifact token you control becomes a 3/1 Construct artifact creature with flying. +SVar:PlayMain1:TRUE +Oracle:Whenever you cast a noncreature spell, create a Clue token.\n{T}: Until end of turn, target artifact token you control becomes a 3/1 Construct artifact creature with flying. diff --git a/forge-gui/res/cardsfolder/upcoming/ty_lee_chi_blocker.txt b/forge-gui/res/cardsfolder/upcoming/ty_lee_chi_blocker.txt new file mode 100644 index 00000000000..b84e924dcc8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ty_lee_chi_blocker.txt @@ -0,0 +1,12 @@ +Name:Ty Lee, Chi Blocker +ManaCost:2 U +Types:Legendary Creature Human Performer Ally +PT:2/1 +K:Flash +K:Prowess +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTap | TriggerDescription$ When NICKNAME enters, tap up to one target creature. It doesn't untap during its controller's untap step for as long as you control NICKNAME. +SVar:TrigTap:DB$ Tap | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target creature | SubAbility$ DBEffect +SVar:DBEffect:DB$ Effect | RememberObjects$ Targeted | ForgetOnMoved$ Battlefield | ReplacementEffects$ DontUntap | Duration$ AsLongAsControl +SVar:DontUntap:Event$ Untap | ValidCard$ Card.IsRemembered | ValidStepTurnToController$ You | Layer$ CantHappen | Description$ That creature doesn't untap during its controller's untap step for as long as you control EFFECTSOURCE. +SVar:PlayMain1:TRUE +Oracle:Flash\nProwess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.)\nWhen Ty Lee enters, tap up to one target creature. It doesn't untap during its controller's untap step for as long as you control Ty Lee. From c8fd36682f2699074173e86d58c98e0f6bbec391 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Wed, 29 Oct 2025 10:50:02 +0100 Subject: [PATCH 193/230] Energybending (TLA) --- forge-gui/res/cardsfolder/upcoming/energybending.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/energybending.txt diff --git a/forge-gui/res/cardsfolder/upcoming/energybending.txt b/forge-gui/res/cardsfolder/upcoming/energybending.txt new file mode 100644 index 00000000000..060d9cca085 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/energybending.txt @@ -0,0 +1,7 @@ +Name:Energybending +ManaCost:2 +Types:Instant +A:SP$ Effect | StaticAbilities$ Energybend | RememberObjects$ Valid Land.YouCtrl | SubAbility$ DBDraw | SpellDescription$ Lands you control gain all basic land types until end of turn. Draw a card. +SVar:Energybend:Mode$ Continuous | Affected$ Permanent.IsRemembered | AddType$ AllBasicLandType | Description$ Lands you control gain all basic land types until end of turn. +SVar:DBDraw:DB$ Draw +Oracle:Lands you control gain all basic land types until end of turn.\nDraw a card. From 1cf6633b13afbd179cb4b360504f85eed3862029 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Wed, 29 Oct 2025 10:54:10 +0100 Subject: [PATCH 194/230] Fix corner case with Perplexing Chimera --- .../game/ability/effects/ControlExchangeEffect.java | 4 ++-- .../game/ability/effects/ControlSpellEffect.java | 13 +++---------- .../main/java/forge/game/card/CardFactoryUtil.java | 6 ++---- forge-gui/res/cardsfolder/f/fire_lord_zuko.txt | 2 +- forge-gui/res/cardsfolder/s/soulherder.txt | 2 +- 5 files changed, 9 insertions(+), 18 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java index 65fb4634f6f..2a97b2e1b2f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ControlExchangeEffect.java @@ -73,8 +73,8 @@ public class ControlExchangeEffect extends SpellAbilityEffect { object2 = tgts.get(1); } - if (object1 == null || object2 == null || !object1.isInPlay() - || !object2.isInPlay()) { + if (object1 == null || object2 == null || !object1.isInPlay() || !object2.isInPlay() + || object1.isPhasedOut() || object2.isPhasedOut()) { return; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlSpellEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlSpellEffect.java index e7aa100d75c..7b774804237 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ControlSpellEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ControlSpellEffect.java @@ -58,22 +58,19 @@ public class ControlSpellEffect extends SpellAbilityEffect { boolean remember = sa.hasParam("Remember"); final List controllers = getDefinedPlayersOrTargeted(sa, "NewController"); - final Player newController = controllers.isEmpty() ? sa.getActivatingPlayer() : controllers.get(0); + Player newController = controllers.isEmpty() ? sa.getActivatingPlayer() : controllers.get(0); final Game game = newController.getGame(); - // If an Exchange needs to happen, make sure both parties are still in the right zones - for (SpellAbility spell : getTargetSpells(sa)) { Card tgtC = spell.getHostCard(); long tStamp = game.getNextTimestamp(); SpellAbilityStackInstance si = game.getStack().getInstanceMatchingSpellAbilityID(spell); if (exchange) { // Currently the only Exchange Control for Spells is a Permanent Trigger - // Expand this area as it becomes needed // Use "DefinedExchange" to Reference Object that is Exchanging the other direction GameObject obj = Iterables.getFirst(getDefinedOrTargeted(sa, "DefinedExchange"), null); if (obj instanceof Card c) { - if (!c.isInPlay() || si == null) { + if (!c.isInPlay() || c.isPhasedOut() || si == null) { // Exchanging object isn't available, continue continue; } @@ -82,14 +79,10 @@ public class ControlSpellEffect extends SpellAbilityEffect { continue; } - if (c.getController().equals(si.getActivatingPlayer())) { - // Controllers are already the same, no exchange needed - continue; - } - if (remember) { source.addRemembered(c); } + newController = c.getController(); c.addTempController(si.getActivatingPlayer(), tStamp); c.runChangeControllerCommands(); } 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 92acb84d8e5..4a5cb10adf5 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1178,13 +1178,11 @@ public class CardFactoryUtil { StringBuilder desc = new StringBuilder("Firebending "); desc.append(n); if (k.length > 2) { - desc.append(" ").append(k[2]); + desc.append(k[2]); } - desc.append(" (").append(inst.getReminderText()).append(")"); - final String trigStr = "Mode$ Attacks | ValidCard$ Card.Self | TriggerDescription$ " + desc.toString(); - + final String trigStr = "Mode$ Attacks | ValidCard$ Card.Self | TriggerDescription$ " + desc; final String manaStr = "DB$ Mana | Defined$ You | CombatMana$ True | Produced$ R | Amount$ " + n; final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); diff --git a/forge-gui/res/cardsfolder/f/fire_lord_zuko.txt b/forge-gui/res/cardsfolder/f/fire_lord_zuko.txt index a4dee053bb0..7a780298cc4 100644 --- a/forge-gui/res/cardsfolder/f/fire_lord_zuko.txt +++ b/forge-gui/res/cardsfolder/f/fire_lord_zuko.txt @@ -2,7 +2,7 @@ Name:Fire Lord Zuko ManaCost:R W B Types:Legendary Creature Human Noble Ally PT:2/4 -K:Firebending:X +K:Firebending:X:, where X is Fire Lord Zuko's power. T:Mode$ SpellCast | ValidCard$ Card.wasCastFromExile | ValidActivatingPlayer$ You | Execute$ TrigPutCounterAll | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control. T:Mode$ ChangesZone | Origin$ Exile | Destination$ Battlefield | ValidCard$ Permanent.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounterAll | Secondary$ True | TriggerDescription$ Whenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control. SVar:TrigPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 diff --git a/forge-gui/res/cardsfolder/s/soulherder.txt b/forge-gui/res/cardsfolder/s/soulherder.txt index 47d592a38b0..ae684d6616c 100644 --- a/forge-gui/res/cardsfolder/s/soulherder.txt +++ b/forge-gui/res/cardsfolder/s/soulherder.txt @@ -4,11 +4,11 @@ Types:Creature Spirit PT:1/1 T:Mode$ Exiled | Origin$ Battlefield | ValidCard$ Creature | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever a creature is exiled from the battlefield, put a +1/+1 counter on CARDNAME. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 -SVar:PlayMain1:TRUE T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ ConjurerExile | OptionalDecider$ You | TriggerDescription$ At the beginning of your end step, you may exile another target creature you control, then return it to the battlefield under its owner's control. SVar:ConjurerExile:DB$ ChangeZone | ValidTgts$ Creature.Other+YouCtrl | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ ConjurerReturn SVar:ConjurerReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:PlayMain1:TRUE AI:RemoveDeck:Random DeckHas:Ability$Counters Oracle:Whenever a creature is exiled from the battlefield, put a +1/+1 counter on Soulherder.\nAt the beginning of your end step, you may exile another target creature you control, then return that card to the battlefield under its owner's control. From 124c595af56141cd9dc6369d3d98775c0df24510 Mon Sep 17 00:00:00 2001 From: Eradev Date: Wed, 29 Oct 2025 06:53:41 -0400 Subject: [PATCH 195/230] Heist fix (#9032) --- .../java/forge/game/ability/effects/HeistEffect.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/HeistEffect.java b/forge-game/src/main/java/forge/game/ability/effects/HeistEffect.java index 876b80e465b..a2ef6131fce 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/HeistEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/HeistEffect.java @@ -1,7 +1,5 @@ package forge.game.ability.effects; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; @@ -38,17 +36,19 @@ public class HeistEffect extends SpellAbilityEffect { List choices = Aggregates.random(CardLists.getNotType(target.getCardsIn(ZoneType.Library), "Land"), 3); if (choices.isEmpty()) continue; //nothing to heist - Card chosenCard = player.getController().chooseSingleCardForZoneChange(ZoneType.Exile, - new ArrayList(Arrays.asList(ZoneType.Exile)), sa, new CardCollection(choices), + Card chosenCard = player.getController().chooseSingleCardForZoneChange(ZoneType.Exile, + List.of(ZoneType.Library), sa, new CardCollection(choices), null, Localizer.getInstance().getMessage("lblChooseCardHeist"), false, player); if (!chosenCard.canExiledBy(sa, true)) { continue; } Card exiled = game.getAction().moveTo(ZoneType.Exile, chosenCard, sa, moveParams); + exiled.turnFaceDown(true); + exiled.addMayLookFaceDownExile(player); handleExiledWith(exiled, sa); heisted.add(exiled); - if (chosenCard != null) triggerList.put(ZoneType.Library, exiled.getZone().getZoneType(), exiled); + triggerList.put(ZoneType.Library, exiled.getZone().getZoneType(), exiled); } if (!heisted.isEmpty()) { From 61f197b93cddf2ce4f91d0481ad022a6c22d5ab5 Mon Sep 17 00:00:00 2001 From: Paul Hammerton Date: Wed, 29 Oct 2025 19:06:01 +0000 Subject: [PATCH 196/230] Edition updates: SLD, TLA, TLE --- .../Avatar The Last Airbender Eternal.txt | 27 ++++++ .../editions/Avatar The Last Airbender.txt | 95 +++++++++++++++++++ .../res/editions/Secret Lair Drop Series.txt | 5 + 3 files changed, 127 insertions(+) diff --git a/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt b/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt index bf8dbdd1e00..fab5e2b1041 100644 --- a/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt +++ b/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt @@ -7,13 +7,37 @@ ScryfallCode=TLE [cards] 5 M Release to Memory @Viacom +9 M Agent of Treachery @Viacom +10 M Bribery @Viacom 13 M Force of Negation @Viacom +16 M Mystic Remora @Viacom +24 M Cruel Tutor @Viacom 41 M The Great Henge @Viacom +43 M Heroic Intervention @Viacom +48 M Eladamri's Call @Viacom +56 M Dark Depths @Viacom 61 M Valakut, the Molten Pinnacle @Viacom +63 R Katara's Reversal @Fahmi Fauzi +69 R Aang and Katara @Brian Yuen +70 R Toph, Greatest Earthbender @Brian Yuen +71 R Sokka and Suki @Brian Yuen +72 R Momo's Heist @Brian Yuen +73 R Uncle's Musings @Brian Yuen 74 M Aang, Airbending Master @Tomoyo Asatani +83 M Sokka, Swordmaster @Jason Kiantoro 93 M Katara, Waterbending Master @Yueko +98 M Wan Shi Tong, All-Knowing @Axel Sauerwald +101 M Azula, Ruthless Firebender @Rose Benjamin 104 M Fire Lord Ozai @Ryuichi Sakuma +112 M Avatar Roku, Firebender @Toni Infante +115 R Fang, Roku's Companion @Douzen +127 M Zuko, Firebending Master @Kinota Eishi +130 M Avatar Kyoshi, Earthbender @Tetsuko 134 R The Cabbage Merchant @Patrick Gañas +139 M Hei Bai, Forest Guardian @TAPIOCA +145 M Toph, Earthbending Master @Phima +165 R Fiery Confluence @Salvatorre Zee Yazzie +167 R Descendants' Path 210 R Aang, Air Nomad @Jinho Bae 211 C Aang's Defense @Jo Cordisco 212 C Aardvark Sloth @Ionomycin @@ -109,3 +133,6 @@ ScryfallCode=TLE 302 L Plains @Slawek Fedorczuk 303 L Plains @Slawek Fedorczuk 304 L Plains @Slawek Fedorczuk +315 R Arcane Signet @Shane Beresford +316 R Sol Ring @Barbara Rosiak +317 R Swiftfoot Boots @Barbara Rosiak diff --git a/forge-gui/res/editions/Avatar The Last Airbender.txt b/forge-gui/res/editions/Avatar The Last Airbender.txt index b8964ce3072..e83f893f69e 100644 --- a/forge-gui/res/editions/Avatar The Last Airbender.txt +++ b/forge-gui/res/editions/Avatar The Last Airbender.txt @@ -7,8 +7,11 @@ ScryfallCode=TLA [cards] 1 C Aang's Journey @Kotakan +2 U Energybending @Hisashi Momose +3 C Zuko's Exile @Eiji Kaneda 4 U Aang, the Last Airbender @Yueko 5 R Aang's Iceberg @Matteo Bassini +6 R Airbender Ascension @Shiren 8 C Airbending Lesson @Pisukev 10 M Appa, Steadfast Guardian @Maël Ollivier-Henry 11 C Avatar Enthusiasts @Leanna Crossan @@ -20,77 +23,123 @@ ScryfallCode=TLA 32 C Rabaroo Troop @Mizutametori 33 C Razor Rings @Norikatsu Miyoshi 36 U Southern Air Temple @Salvatorre Zee Yazzie +42 U Water Tribe Rallier @Boell Oyino +43 C Yip Yip! @Cinkai 49 C First-Time Flyer @Mizutametori 50 C Flexible Waterbender @Rafater 52 C Geyser Leaper @Norikatsu Miyoshi 53 C Giant Koi @Nathaniel Himawan +54 U Gran-Gran @Arou +55 U Honest Work @ikeda_cpt 56 C Iguana Parrot @Tyler Smith 58 C It'll Quench Ya! @Nathaniel Himawan 59 U Katara, Bending Prodigy @Mephisto +61 M The Legend of Kuruk @Toshiaki Takayama 63 U Master Pakku @Olena Richards +65 U North Pole Patrol @Rose Benjamin 67 C Otter-Penguin @Eilene Cherie 68 C Rowdy Snowballers @Mizutametori +69 M Secret of Bloodbending @Olena Richards 70 U Serpent of the Pass @Eiji Kaneda 71 U Sokka's Haiku @Bun Toujo +75 R Tiger-Seal @Jinho Bae +78 M Wan Shi Tong, Librarian @Ryota Murayama 80 C Waterbending Lesson @Sylvain Sarrailh 82 C Watery Grasp @Rose Benjamin 83 R Yue, the Moon Spirit @Yuumei 84 C Azula Always Lies @Robin Har +85 U Azula, On the Hunt @Toraji 86 C Beetle-Headed Merchants @Norikatsu Miyoshi +87 R Boiling Rock Rioter @Airi Yoshihisa 88 U Buzzard-Wasp Colony @Thomas Chamberlain-Keen 91 U Cat-Gator @Joseph Weston 92 C Corrupt Court Official @Norikatsu Miyoshi 93 C Dai Li Indoctrination @Lius Lasahido +94 R Day of Black Sun @Matteo Bassini 96 U Epic Downfall @Hristo D. Chukov +98 R The Fire Nation Drill @Brandon L. Hunt 99 U Fire Nation Engineer @Norikatsu Miyoshi 103 U Heartless Act @Sylvain Sarrailh 104 C Hog-Monkey @Miho Midorikawa 110 C Merchant of Many Hats @Boell Oyino 113 U Ozai's Cruelty @ikeda_cpt 117 M The Rise of Sozin @Mitori +126 U The Cave of Two Lovers @Ittoku +129 U Crescent Island Temple @Luc Courtois +130 C Cunning Maneuver @Robin Har 131 C Deserter's Disciple @HAIKEI 132 M Fated Firepower @Toshiaki Takayama 133 U Fire Nation Attacks @Claudiu-Antoniu Magherusan 136 U Fire Sages @Yuu Fujiki +139 R Firebending Student @Kozato 140 C How to Start a Riot @Robin Olausson +144 R The Last Agni Kai @Pablo Rivera +145 M The Legend of Roku @Song Qijin 146 C Lightning Strike @Jo Cordisco 148 C Mongoose Lizard @Joseph Weston +150 R Ran and Shaw @Miho Midorikawa 151 R Redirect Lightning @Toni Infante 152 C Rough Rhino Cavalry @Yuhong Ding +153 U Solstice Revelations @Kotakan 154 M Sozin's Comet @Salvatorre Zee Yazzie +159 U War Balloon @Matteo Bassini 161 C Yuyan Archers @Domco. 163 U Zuko, Exiled Prince @Nijihayashi +164 U Allies at Last @Evan Shipard 166 C Badgermole @Matteo Bassini +167 M Badgermole Cub @Nathaniel Himawan +170 C Cycle of Renewal @Jocelin Carmes 174 U Earth Rumble @Olena Richards 176 C Earthbending Lesson @Toni Infante 179 U Flopsie, Bumi's Buddy @Alexandr Leskinen 182 U Haru, Hidden Talent @Mitori +183 U Invasion Tactics @Eduardo Francisco +185 U Leaves from the Vine @Ittoku 188 C Ostrich-Horse @Pablo Rivera 189 C Pillar Launch @Jo Cordisco 190 C Raucous Audience @ikeda_cpt 191 C Rebellious Captives @Ittoku 193 C Rocky Rebuke @Hokyoung Kim 194 C Saber-Tooth Moose-Lion @Shiren +195 U Seismic Sense @Jo Cordisco +197 U Sparring Dummy @Gemi 198 U Toph, the Blind Bandit @Yueko 200 C Turtle-Duck @Sylvain Sarrailh +203 R Aang, at the Crossroads @Evan Shipard 205 C Abandon Attachments @Shahab Alizadeh 207 M Avatar Aang @Fahmi Fauzi +208 R Azula, Cunning Usurper @Evyn Fong 212 C Cat-Owl @Thomas Chamberlain-Keen +214 U Dai Li Agents @Eduardo Francisco +215 U Dragonfly Swarm @John Di Giovanni 216 C Earth Kingdom Soldier @Rafater +217 R Earth King's Lieutenant @Nathaniel Himawan 219 C Earth Village Ruffians @Dom Lay +220 R Fire Lord Azula @Fahmi Fauzi 221 R Fire Lord Zuko @Jo Cordisco +223 U Guru Pathik @Dee Nguyen +224 U Hama, the Bloodbender @Le Vuong 225 U Hei Bai, Spirit of Balance @Tyler Smith +227 R Iroh, Grand Lotus @Fahmi Fauzi 230 R Katara, the Fearless @Hisashi Momose 231 R Katara, Water Tribe's Hope @Toraji +232 R The Lion-Turtle @Yuumei 233 U Long Feng, Grand Secretariat @Robin Har +235 M Ozai, the Phoenix King @Kekai Kotaki 237 C Pretending Poxbearers @Salvatorre Zee Yazzie 240 R Sokka, Bold Boomeranger @Toni Infante 241 U Sokka, Lateral Strategist @Axel Sauerwald +242 R Sokka, Tenacious Tactician @Robin Har 243 U Suki, Kyoshi Warrior @Yuhong Ding +244 U Sun Warriors @Boell Oyino 247 R Toph, the First Metalbender @Eilene Cherie +248 U Uncle Iroh @Kieran Yanner 249 C Vindictive Warden @Jo Cordisco +251 U White Lotus Reinforcements @Kotakan 254 C Barrels of Blasting Jelly @Salvatorre Zee Yazzie 255 C Bender's Waterskin @Dee Nguyen +260 U Trusty Boomerang @Toni Infante +262 M White Lotus Tile @Dee Nguyen 282 L Plains @Slawek Fedorczuk 283 L Island @Maojin Lee 284 L Swamp @Matteo Bassini @@ -101,21 +150,67 @@ ScryfallCode=TLA 289 L Swamp @John Di Giovanni 290 L Mountain @Lorenzo Lanfranconi 291 L Forest @Luc Courtois +292 L Plains @Maojin Lee +293 L Island @Slawek Fedorczuk +294 L Swamp @Robin Olausson +295 L Mountain @Maojin Lee +296 L Forest @Slawek Fedorczuk +297 M Fated Firepower @Claudiu-Antoniu Magherusan +299 U Fire Nation Attacks @Claudiu-Antoniu Magherusan +303 R Azula, Cunning Usurper @Toni Infante +304 R Aang, at the Crossroads @Toni Infante +305 R Katara, the Fearless @Toni Infante +306 U Dai Li Agents @Toni Infante +308 M Avatar Aang @Dominik Mayer 309 M Sozin's Comet @Dominik Mayer +311 M Ozai, the Phoenix King @Dominik Mayer +313 R Fire Lord Azula @Dominik Mayer +314 R The Last Agni Kai @Dominik Mayer +315 R Fire Lord Zuko @Dominik Mayer 316 M Appa, Steadfast Guardian @Ilse Gort 317 R Momo, Friendly Flier @Filip Burburan +318 R Tiger-Seal @Andrea Piparo +320 M Wan Shi Tong, Librarian @Andrea Piparo +321 R The Fire Nation Drill @Ben Hill +325 R Ran and Shaw @Antonio José Manzanedo +326 M Badgermole Cub @Filip Burburan +328 R The Lion-Turtle @Filip Burburan +330 M White Lotus Tile @Antonio José Manzanedo 332 M Sozin's Comet @JungShan +334 R Fire Lord Azula @JungShan +335 M Ozai, the Phoenix King @Sidharth Chaturvedi 336 R Aang's Iceberg @Brigitte Roka & Clifton Stommel +337 M Secret of Bloodbending @Barbara Rosiak +330 M White Lotus Tile @Antonio José Manzanedo +332 M Sozin's Comet @JungShan +334 R Fire Lord Azula @JungShan +335 M Ozai, the Phoenix King @Sidharth Chaturvedi +336 R Aang's Iceberg @Brigitte Roka & Clifton Stommel +337 M Secret of Bloodbending @Barbara Rosiak 338 R Yue, the Moon Spirit @Barbara Rosiak +339 R Foggy Swamp Visions @Sija Hong 341 M Fated Firepower @Brigitte Roka +342 R Firebending Student @Ina Wong 343 R Redirect Lightning @Perci Chen +346 R Aang, at the Crossroads @Brigitte Roka & Clifton Stommel +349 R Iroh, Grand Lotus @Dalton Pencarinha 350 R Katara, the Fearless @Barbara Rosiak 351 R Katara, Water Tribe's Hope @Chun Lo +352 R Sokka, Tenacious Tactician @Faustine Dumontier 353 R Toph, the First Metalbender @Barbara Rosiak +355 M The Legend of Kuruk @Barbara Rosiak 356 M The Rise of Sozin @Barbara Rosiak +357 M The Legend of Roku @Barbara Rosiak 360 R Fire Lord Zuko @Flavio Girón 361 R Katara, the Fearless @Flavio Girón 362 R Toph, the First Metalbender @Flavio Girón 363 M Avatar Aang @Bryan Konietzko +364 R Airbender Ascension @Shiren 366 R Hakoda, Selfless Commander @Rafater +372 R Boiling Rock Rioter @Airi Yoshihisa +373 R Day of Black Sun @Matteo Bassini +380 R Earth King's Lieutenant @Nathaniel Himawan 383 R Sokka, Bold Boomeranger @Toni Infante +385 M Planetarium of Wan Shi Tong @Robin Olausson +393 R Firebending Student @Airi Yoshihisa +394 R Momo, Friendly Flier @Ryota Murayama diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 898222dcf68..7ebb4595d77 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -2169,6 +2169,11 @@ F1540 M Rainbow Dash @John Thacker 2284 M Voja, Jaws of the Conclave @Ryan Roadkill 2285 M Wilhelt, the Rotcleaver @Sam Heimer 2286 R Liberator, Urza's Battlethopter @Ryan Roadkill +2295 R Chain of Vapor @Kozato +2296 R Meltdown @Airi Yoshihisa +2297 R Nature's Claim @Nijihayashi +2298 R Anguished Unmaking @Yosuke Adachi +2299 R Putrefy @Yosuke Adachi 2311 R Distant Melody @Paul Scott Canavan 2312 R Explore @Natalie Andrewson 2313 R Inspiring Call @Rian Gonzales From 149c14eea13ddbeff032167fefafb7319d57e3b3 Mon Sep 17 00:00:00 2001 From: Jetz72 Date: Wed, 29 Oct 2025 16:55:11 -0500 Subject: [PATCH 197/230] Fix CardRequest for unflavored name not preferring unflavored prints (#9040) --- forge-core/src/main/java/forge/card/CardDb.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-core/src/main/java/forge/card/CardDb.java b/forge-core/src/main/java/forge/card/CardDb.java index d51d23e0fa1..165148a3e0f 100644 --- a/forge-core/src/main/java/forge/card/CardDb.java +++ b/forge-core/src/main/java/forge/card/CardDb.java @@ -389,6 +389,7 @@ public final class CardDb implements ICardDatabase, IDeckGenPool { String normalizedFlavorName = cr.getDisplayNameForVariant(variantName); if(!flavorNameMappings.containsKey(normalizedFlavorName)) { flavorNameMappings.put(normalizedFlavorName, variantName); + flavorNameMappings.put(cr.getName(), IPaperCard.NO_FUNCTIONAL_VARIANT); rulesByName.put(normalizedFlavorName, cr); cacheFlavorName(cr.getMainPart().getFunctionalVariant(variantName)); if(cr.getOtherPart() != null) From 8a7f28858fcc4f76a04178631b5b19ff57d1e048 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Wed, 29 Oct 2025 22:57:39 +0100 Subject: [PATCH 198/230] TLA/TLE cards, 29th October (#9039) --- .../res/cardsfolder/upcoming/aang_and_katara.txt | 10 ++++++++++ .../res/cardsfolder/upcoming/mai_and_zuko.txt | 7 +++++++ forge-gui/res/cardsfolder/upcoming/momos_heist.txt | 7 +++++++ .../cardsfolder/upcoming/northern_air_temple.txt | 12 ++++++++++++ .../cardsfolder/upcoming/teo_spirited_glider.txt | 14 ++++++++++++++ .../upcoming/toph_greatest_earthbender.txt | 9 +++++++++ .../res/cardsfolder/upcoming/uncles_musings.txt | 7 +++++++ 7 files changed, 66 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/aang_and_katara.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/mai_and_zuko.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/momos_heist.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/northern_air_temple.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/teo_spirited_glider.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/toph_greatest_earthbender.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/uncles_musings.txt diff --git a/forge-gui/res/cardsfolder/upcoming/aang_and_katara.txt b/forge-gui/res/cardsfolder/upcoming/aang_and_katara.txt new file mode 100644 index 00000000000..9ec77b1642a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/aang_and_katara.txt @@ -0,0 +1,10 @@ +Name:Aang and Katara +ManaCost:3 G W U +Types:Legendary Creature Human Avatar Ally +PT:5/5 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ Whenever CARDNAME enter or attack, create X 1/1 white Ally creature tokens, where X is the number of tapped artifacts and/or creatures you control. +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigToken | Secondary$ True | TriggerDescription$ Whenever CARDNAME enter or attack, create X 1/1 white Ally creature tokens, where X is the number of tapped artifacts and/or creatures you control. +SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ w_1_1_ally | TokenOwner$ You +SVar:X:Count$Valid Artifact.YouCtrl+tapped,Creature.YouCtrl+tapped +DeckHas:Ability$Token +Oracle:Whenever Aang and Katara enter or attack, create X 1/1 white Ally creature tokens, where X is the number of tapped artifacts and/or creatures you control. diff --git a/forge-gui/res/cardsfolder/upcoming/mai_and_zuko.txt b/forge-gui/res/cardsfolder/upcoming/mai_and_zuko.txt new file mode 100644 index 00000000000..97120120cd8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/mai_and_zuko.txt @@ -0,0 +1,7 @@ +Name:Mai and Zuko +ManaCost:1 U B R +Types:Legendary Creature Human Noble Ally +PT:3/5 +K:Firebending:3 +S:Mode$ CastWithFlash | ValidCard$ Ally,Artifact | ValidSA$ Spell | Caster$ You | Description$ You may cast Ally spells and artifact spells as though they had flash. +Oracle:Firebending 3\nYou may cast Ally spells and artifact spells as though they had flash. diff --git a/forge-gui/res/cardsfolder/upcoming/momos_heist.txt b/forge-gui/res/cardsfolder/upcoming/momos_heist.txt new file mode 100644 index 00000000000..b4ce7fd28c1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/momos_heist.txt @@ -0,0 +1,7 @@ +Name:Momo's Heist +ManaCost:2 R +Types:Sorcery +A:SP$ GainControl | ValidTgts$ Artifact | TgtPrompt$ Select target artifact | Untap$ True | AddKWs$ Haste | SubAbility$ DBPump | SpellDescription$ Gain control of target artifact. Untap it. It gains haste. At the beginning of the next end step, sacrifice it. +SVar:DBPump:DB$ Pump | Defined$ Targeted | AtEOT$ Sacrifice +SVar:PlayMain1:TRUE +Oracle:Gain control of target artifact. Untap it. It gains haste. At the beginning of the next end step, sacrifice it. diff --git a/forge-gui/res/cardsfolder/upcoming/northern_air_temple.txt b/forge-gui/res/cardsfolder/upcoming/northern_air_temple.txt new file mode 100644 index 00000000000..9170c77fa76 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/northern_air_temple.txt @@ -0,0 +1,12 @@ +Name:Northern Air Temple +ManaCost:B +Types:Legendary Enchantment Shrine +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigLoseLife1 | TriggerDescription$ When CARDNAME enters, each opponent loses X life and you gain X life, where X is the number of Shrines you control. +SVar:TrigLoseLife1:DB$ LoseLife | Defined$ Opponent | LifeAmount$ X | SubAbility$ DBGainLife1 +SVar:DBGainLife1:DB$ GainLife | Defined$ You | LifeAmount$ X +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Shrine.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigLoseLife2 | TriggerDescription$ Whenever another Shrine you control enters, each opponent loses 1 life and you gain 1 life. +SVar:TrigLoseLife2:DB$ LoseLife | Defined$ Opponent | LifeAmount$ 1 | SubAbility$ DBGainLife2 +SVar:DBGainLife2:DB$ GainLife | Defined$ You | LifeAmount$ 1 +SVar:X:Count$Valid Shrine.YouCtrl +DeckHints:Type$Shrine +Oracle:When Northern Air Temple enters, each opponent loses X life and you gain X life, where X is the number of Shrines you control.\nWhenever another Shrine you control enters, each opponent loses 1 life and you gain 1 life. diff --git a/forge-gui/res/cardsfolder/upcoming/teo_spirited_glider.txt b/forge-gui/res/cardsfolder/upcoming/teo_spirited_glider.txt new file mode 100644 index 00000000000..ec39bb63949 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/teo_spirited_glider.txt @@ -0,0 +1,14 @@ +Name:Teo, Spirited Glider +ManaCost:3 U +Types:Legendary Creature Human Pilot Ally +PT:1/4 +K:Flying +T:Mode$ AttackersDeclared | AttackingPlayer$ You | ValidAttackers$ Creature.withFlying | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever one or more creatures you control with flying attack, draw a card, then discard a card. When you discard a nonland card this way, put a +1/+1 counter on target creature you control. +SVar:DBDraw:DB$ Draw | SubAbility$ DBDiscard | SpellDescription$ Draw a card, then discard a card. +SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose | RememberDiscarded$ True | SubAbility$ DBImmediateTrig +SVar:DBImmediateTrig:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card.nonLand | ConditionCompare$ GE1 | Execute$ TrigPutCounter | SubAbility$ DBCleanup | SpellDescription$ When you discard a nonland card this way, put a +1/+1 counter on target creature you control. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ P1P1 | CounterNum$ 1 +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:PlayMain1:TRUE +DeckHints:Keyword$Flying +Oracle:Flying\nWhenever one or more creatures you control with flying attack, draw a card, then discard a card. When you discard a nonland card this way, put a +1/+1 counter on target creature you control. diff --git a/forge-gui/res/cardsfolder/upcoming/toph_greatest_earthbender.txt b/forge-gui/res/cardsfolder/upcoming/toph_greatest_earthbender.txt new file mode 100644 index 00000000000..a39f5f4c4d0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/toph_greatest_earthbender.txt @@ -0,0 +1,9 @@ +Name:Toph, Greatest Earthbender +ManaCost:2 R G +Types:Legendary Creature Human Warrior Ally +PT:3/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEarthbend | TriggerDescription$ When NICKNAME enters, earthbend X, where X is the amount of mana spent to cast her. +SVar:TrigEarthbend:DB$ Earthbend | Num$ X +S:Mode$ Continuous | Affected$ Creature.Land+YouCtrl| AddKeyword$ Double Strike | Description$ Land creatures you control have double strike. +SVar:X:Count$CastTotalManaSpent +Oracle:When Toph enters, earthbend X, where X is the amount of mana spent to cast her.\nLand creatures you control have double strike. diff --git a/forge-gui/res/cardsfolder/upcoming/uncles_musings.txt b/forge-gui/res/cardsfolder/upcoming/uncles_musings.txt new file mode 100644 index 00000000000..e3fb33356f9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/uncles_musings.txt @@ -0,0 +1,7 @@ +Name:Uncle's Musings +ManaCost:2 G G +Types:Sorcery +A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Hand | Hidden$ True | ChangeType$ Permanent.YouOwn | ChangeNum$ X | SubAbility$ DBChangeZone | SpellDescription$ Converge — Return up to X permanent cards from your graveyard to your hand, where X is the number of colors of mana spent to cast this spell. Exile CARDNAME. +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Stack | Destination$ Exile +SVar:X:Count$Converge +Oracle:Converge — Return up to X permanent cards from your graveyard to your hand, where X is the number of colors of mana spent to cast this spell.\nExile Uncle's Musings. From 2fc1c120f37dc4e9e021efbd77f681a9759bd99d Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Wed, 29 Oct 2025 22:57:53 +0100 Subject: [PATCH 199/230] TLA cards, 28th October, Batch 1 (#9023) --- forge-gui/res/cardsfolder/b/bakersbane_duo.txt | 2 +- .../res/cardsfolder/s/sinkhole_surveyor.txt | 1 + .../cardsfolder/upcoming/airbender_ascension.txt | 13 +++++++++++++ .../cardsfolder/upcoming/azula_on_the_hunt.txt | 9 +++++++++ .../cardsfolder/upcoming/boiling_rock_rioter.txt | 10 ++++++++++ .../cardsfolder/upcoming/day_of_black_sun.txt | 9 +++++++++ forge-gui/res/cardsfolder/upcoming/gran_gran.txt | 9 +++++++++ .../res/cardsfolder/upcoming/honest_work.txt | 12 ++++++++++++ .../upcoming/the_mechanist_aerial_artisan.txt | 4 ++-- .../res/cardsfolder/upcoming/tiger_seal.txt | 10 ++++++++++ .../upcoming/unlucky_cabbage_merchant.txt | 11 +++++++++++ .../upcoming/wan_shi_tong_librarian.txt | 16 ++++++++++++++++ forge-gui/res/cardsfolder/upcoming/yip_yip.txt | 6 ++++++ 13 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/airbender_ascension.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/azula_on_the_hunt.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/boiling_rock_rioter.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/day_of_black_sun.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/gran_gran.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/honest_work.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/tiger_seal.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/unlucky_cabbage_merchant.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/wan_shi_tong_librarian.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/yip_yip.txt diff --git a/forge-gui/res/cardsfolder/b/bakersbane_duo.txt b/forge-gui/res/cardsfolder/b/bakersbane_duo.txt index 6dac66264c0..be2aa6ce9e2 100644 --- a/forge-gui/res/cardsfolder/b/bakersbane_duo.txt +++ b/forge-gui/res/cardsfolder/b/bakersbane_duo.txt @@ -2,7 +2,7 @@ Name:Bakersbane Duo ManaCost:1 G Types:Creature Squirrel Raccoon PT:2/2 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigFood | TriggerDescription$ When CARDNAME enters, create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this artifact: You gain 3 life.") +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigFood | TriggerDescription$ When CARDNAME enters, create a Food token. SVar:TrigFood:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_food_sac | TokenOwner$ You T:Mode$ ManaExpend | Amount$ 4 | Player$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever you expend 4, CARDNAME gets +1/+1 until end of turn. (You expend 4 as you spend your fourth total mana to cast spells during a turn.) SVar:TrigPump:DB$ Pump | NumAtt$ +1 | NumDef$ +1 | Defined$ Self diff --git a/forge-gui/res/cardsfolder/s/sinkhole_surveyor.txt b/forge-gui/res/cardsfolder/s/sinkhole_surveyor.txt index aceec83eaf0..abe09d88855 100644 --- a/forge-gui/res/cardsfolder/s/sinkhole_surveyor.txt +++ b/forge-gui/res/cardsfolder/s/sinkhole_surveyor.txt @@ -6,4 +6,5 @@ K:Flying T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigLoseLife | TriggerDescription$ Whenever this creature attacks, you lose 1 life and this creature endures 1. (Put a +1/+1 counter on it or create a 1/1 white Spirit creature token.) SVar:TrigLoseLife:DB$ LoseLife | Defined$ You | LifeAmount$ 1 | SubAbility$ DBEndure SVar:DBEndure:DB$ Endure | Num$ 1 +SVar:HasAttackEffect:TRUE Oracle:Flying\nWhenever this creature attacks, you lose 1 life and this creature endures 1. (Put a +1/+1 counter on it or create a 1/1 white Spirit creature token.) diff --git a/forge-gui/res/cardsfolder/upcoming/airbender_ascension.txt b/forge-gui/res/cardsfolder/upcoming/airbender_ascension.txt new file mode 100644 index 00000000000..2dc267af66b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/airbender_ascension.txt @@ -0,0 +1,13 @@ +Name:Airbender Ascension +ManaCost:1 W +Types:Enchantment +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigAirbend | TriggerDescription$ When this enchantment enters, airbend up to one target creature. +SVar:TrigAirbend:DB$ Airbend | ValidTgts$ Creature | TgtPrompt$ Select up to one target creature | TargetMin$ 0 | TargetMax$ 1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever a creature you control enters, put a quest counter on this enchantment. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ QUEST | CounterNum$ 1 +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ TrigChangeZone | TriggerZones$ Battlefield | IsPresent$ Card.Self+counters_GE4_QUEST | PresentCompare$ EQ1 | TriggerDescription$ At the beginning of your end step, if this enchantment has four or more quest counters on it, exile up to one target creature you control, then return it to the battlefield under its owner's control. +SVar:TrigChangeZone:DB$ ChangeZone | ValidTgts$ Creature.YouCtrl | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target creature you control | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBChangeZone +SVar:DBChangeZone:DB$ ChangeZone | Defined$ Remembered | Origin$ All | Destination$ Battlefield | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:BuffedBy:Creature +Oracle:When this enchantment enters, airbend up to one target creature.\nWhenever a creature you control enters, put a quest counter on this enchantment.\nAt the beginning of your end step, if this enchantment has four or more quest counters on it, exile up to one target creature you control, then return it to the battlefield under its owner's control. diff --git a/forge-gui/res/cardsfolder/upcoming/azula_on_the_hunt.txt b/forge-gui/res/cardsfolder/upcoming/azula_on_the_hunt.txt new file mode 100644 index 00000000000..6f7ae4a4ac0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/azula_on_the_hunt.txt @@ -0,0 +1,9 @@ +Name:Azula, On the Hunt +ManaCost:3 B +Types:Legendary Creature Human Noble +PT:4/3 +K:Firebending:2 +T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigLoseLife | TriggerDescription$ Whenever NICKNAME attacks, you lose 1 life and create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:TrigLoseLife:DB$ LoseLife | Defined$ You | LifeAmount$ 1 | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You +Oracle:Firebending 2 (Whenever this creature attacks, add {R}{R}. This mana lasts until end of combat.)\nWhenever Azula attacks, you lose 1 life and create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") diff --git a/forge-gui/res/cardsfolder/upcoming/boiling_rock_rioter.txt b/forge-gui/res/cardsfolder/upcoming/boiling_rock_rioter.txt new file mode 100644 index 00000000000..5ee49af32fe --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/boiling_rock_rioter.txt @@ -0,0 +1,10 @@ +Name:Boiling Rock Rioter +ManaCost:2 B +Types:Creature Human Rogue Ally +PT:3/3 +K:Firebending:1 +A:AB$ ChangeZone | Cost$ tapXType<1/Ally> | Origin$ Graveyard | Destination$ Exile | TgtPrompt$ Choose target card in a graveyard | ValidTgts$ Card | SpellDescription$ Exile target card from a graveyard. +T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigCast | OptionalDecider$ You | TriggerDescription$ Whenever this creature attacks, you may cast an Ally spell from among cards you own exiled with this creature. +SVar:TrigCast:DB$ Play | ValidZone$ Exile | Valid$ Ally.YouOwn+ExiledWithSource | ValidSA$ Spell | Controller$ You | Optional$ True | Amount$ 1 +DeckHints:Type$Ally +Oracle:Firebending 1 (Whenever this creature attacks, add {R}. This mana lasts until end of combat.)\nTap an untapped Ally you control: Exile target card from a graveyard.\nWhenever this creature attacks, you may cast an Ally spell from among cards you own exiled with this creature. diff --git a/forge-gui/res/cardsfolder/upcoming/day_of_black_sun.txt b/forge-gui/res/cardsfolder/upcoming/day_of_black_sun.txt new file mode 100644 index 00000000000..906c4dc4977 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/day_of_black_sun.txt @@ -0,0 +1,9 @@ +Name:Day of Black Sun +ManaCost:X B B +Types:Sorcery +A:SP$ PumpAll | ValidCards$ Creature.cmcLEX | RememberPumped$ True | StackDescription$ None | SubAbility$ DBAnimateAll +SVar:DBAnimateAll:DB$ AnimateAll | ValidCards$ Card.IsRemembered | RemoveAllAbilities$ True | SubAbility$ DBDestroyAll | SpellDescription$ Each creature with mana value X or less loses all abilities until end of turn. Destroy those creatures. +SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Card.IsRemembered | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Count$xPaid +Oracle:Each creature with mana value X or less loses all abilities until end of turn. Destroy those creatures. diff --git a/forge-gui/res/cardsfolder/upcoming/gran_gran.txt b/forge-gui/res/cardsfolder/upcoming/gran_gran.txt new file mode 100644 index 00000000000..b470659c51d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/gran_gran.txt @@ -0,0 +1,9 @@ +Name:Gran-Fran +ManaCost:U +Types:Legendary Creature Human Peasant Ally +PT:1/2 +T:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME becomes tapped, draw a card, then discard a card. +SVar:TrigDraw:DB$ Draw | SubAbility$ DBDiscard +SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose +S:Mode$ ReduceCost | ValidCard$ Card.nonCreature | Type$ Spell | Amount$ 1 | IsPresent$ Lesson.YouOwn | PresentZone$ Graveyard | PresentCompare$ GE3 | Description$ Noncreature spells you cast cost {1} less to cast as long as there are three or more Lesson cards in your graveyard. +Oracle:Whenever Gran-Gran becomes tapped, draw a card, then discard a card.\nNoncreature spells you cast cost {1} less to cast as long as there are three or more Lesson cards in your graveyard. diff --git a/forge-gui/res/cardsfolder/upcoming/honest_work.txt b/forge-gui/res/cardsfolder/upcoming/honest_work.txt new file mode 100644 index 00000000000..c77843a0efa --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/honest_work.txt @@ -0,0 +1,12 @@ +Name:Honest Work +ManaCost:U +Types:Enchantment Aura +K:Enchant:Creature.OppCtrl:creature an opponent controls +SVar:AttachAILogic:Curse +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTap | TriggerDescription$ When this Aura enters, tap enchanted creature and remove all counters from it. +SVar:TrigTap:DB$ Tap | Defined$ Enchanted | SubAbility$ DBRemoveCounter +SVar:DBRemoveCounter:DB$ RemoveCounter | Defined$ Enchanted | CounterType$ All | CounterNum$ All +S:Mode$ Continuous | Affected$ Creature.EnchantedBy | RemoveAllAbilities$ True | AddType$ Citizen | RemoveCreatureTypes$ True | SetPower$ 1 | SetToughness$ 1 | AddAbility$ Mana | SetName$ Humble Merchant | Description$ Enchanted creature loses all abilities and is a Citizen with base power and toughness 1/1 and "{T}: Add {C}" named Humble Merchant. (It loses all other creature types and names.) +SVar:Mana:AB$ Mana | Cost$ T | Produced$ C | Amount$ 1 | SpellDescription$ Add {C}. +SVar:NonStackingAttachEffect:True +Oracle:Enchant creature an opponent controls\nWhen this Aura enters, tap enchanted creature and remove all counters from it.\nEnchanted creature loses all abilities and is a Citizen with base power and toughness 1/1 and "{T}: Add {C}" named Humble Merchant. (It loses all other creature types and names.) diff --git a/forge-gui/res/cardsfolder/upcoming/the_mechanist_aerial_artisan.txt b/forge-gui/res/cardsfolder/upcoming/the_mechanist_aerial_artisan.txt index a5115e735ca..f5760e0f21a 100644 --- a/forge-gui/res/cardsfolder/upcoming/the_mechanist_aerial_artisan.txt +++ b/forge-gui/res/cardsfolder/upcoming/the_mechanist_aerial_artisan.txt @@ -2,8 +2,8 @@ Name:The Mechanist, Aerial Artisan ManaCost:2 U Types:Legendary Creature Human Artificer Ally PT:1/3 -T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you cast a noncreature spell, create a Clue token. +T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you cast a noncreature spell, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You A:AB$ Animate | Cost$ T | ValidTgts$ Artifact.token+YouCtrl | TgtPrompt$ Select target artifact token you control | Power$ 3 | Toughness$ 1 | Types$ Artifact,Creature,Construct | Keywords$ Flying | SpellDescription$ Until end of turn, target artifact token you control becomes a 3/1 Construct artifact creature with flying. SVar:PlayMain1:TRUE -Oracle:Whenever you cast a noncreature spell, create a Clue token.\n{T}: Until end of turn, target artifact token you control becomes a 3/1 Construct artifact creature with flying. +Oracle:Whenever you cast a noncreature spell, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.")\n{T}: Until end of turn, target artifact token you control becomes a 3/1 Construct artifact creature with flying. diff --git a/forge-gui/res/cardsfolder/upcoming/tiger_seal.txt b/forge-gui/res/cardsfolder/upcoming/tiger_seal.txt new file mode 100644 index 00000000000..b642627cedd --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tiger_seal.txt @@ -0,0 +1,10 @@ +Name:Tiger-Seal +ManaCost:U +Types:Creature Cat Seal +PT:3/3 +K:Vigilance +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigTap | TriggerDescription$ At the beginning of your upkeep, tap this creature. +SVar:TrigTap:DB$ Tap | Defined$ Self +T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 2 | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ Whenever you draw your second card each turn, untap this creature. +SVar:TrigUntap:DB$ Untap | Defined$ Self +Oracle:Vigilance\nAt the beginning of your upkeep, tap this creature.\nWhenever you draw your second card each turn, untap this creature. diff --git a/forge-gui/res/cardsfolder/upcoming/unlucky_cabbage_merchant.txt b/forge-gui/res/cardsfolder/upcoming/unlucky_cabbage_merchant.txt new file mode 100644 index 00000000000..bb02039ac85 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/unlucky_cabbage_merchant.txt @@ -0,0 +1,11 @@ +Name:Unlucky Cabbage Merchant +ManaCost:1 G +Types:Creature Human Citizen +PT:2/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When this creature enters, create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this token: You gain 3 life.) +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_food_sac | TokenOwner$ You +T:Mode$ Sacrificed | ValidCard$ Food | ValidPlayer$ You | Execute$ TrigSearch | TriggerZones$ Battlefield | OptionalDecider$ You | TriggerDescription$ Whenever you sacrifice a Food, you may search your library for a basic land card and put it onto the battlefield tapped. If you search your library this way, put this creature on the bottom of its owner's library, then shuffle. +SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | NoShuffle$ True | SubAbility$ DBChangeZone +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Battlefield | Destination$ Library | LibraryPosition$ -1 | Shuffle$ True +DeckHints:Ability$Food +Oracle:When this creature enters, create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this token: You gain 3 life.)\nWhenever you sacrifice a Food, you may search your library for a basic land card and put it onto the battlefield tapped. If you search your library this way, put this creature on the bottom of its owner's library, then shuffle. diff --git a/forge-gui/res/cardsfolder/upcoming/wan_shi_tong_librarian.txt b/forge-gui/res/cardsfolder/upcoming/wan_shi_tong_librarian.txt new file mode 100644 index 00000000000..c5427ae1d4a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/wan_shi_tong_librarian.txt @@ -0,0 +1,16 @@ +Name:Wan Shi Tong, Librarian +ManaCost:X U U +Types:Legendary Creature Bird Spirit +PT:1/1 +K:Flash +K:Flying +K:Vigilance +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounter1 | TriggerDescription$ When NICKNAME enters, put X +1/+1 counters on him. Then draw half X cards, rounded down. +SVar:TrigPutCounter1:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBDraw1 +SVar:DBDraw1:DB$ Draw | NumCards$ Y +T:Mode$ SearchedLibrary | ValidPlayer$ Player.Opponent | SearchOwnLibrary$ True | Execute$ TrigPutCounter2 | TriggerZones$ Battlefield | TriggerDescription$ Whenever an opponent searches their library, put a +1/+1 counter on NICKNAME and draw a card. +SVar:TrigPutCounter2:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBDraw2 +SVar:DBDraw2:DB$ Draw +SVar:X:Count$xPaid +SVar:Y:SVar$X/HalfDown +Oracle:Flash\nFlying, vigilance\nWhen Wan Shi Tong enters, put X +1/+1 counters on him. Then draw half X cards, rounded down.\nWhenever an opponent searches their library, put a +1/+1 counter on Wan Shi Tong and draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/yip_yip.txt b/forge-gui/res/cardsfolder/upcoming/yip_yip.txt new file mode 100644 index 00000000000..fc5e9557054 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/yip_yip.txt @@ -0,0 +1,6 @@ +Name:Yip Yip! +ManaCost:W +Types:Instant +A:SP$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +2 | NumDef$ +2 | SubAbility$ DBPump | SpellDescription$ Target creature you control gets +2/+2 until end of turn. If that creature is an Ally, it also gains flying until end of turn. +SVar:DBPump:DB$ Pump | Defined$ Targeted | ConditionDefined$ Targeted | ConditionPresent$ Ally | KW$ Flying +Oracle:Target creature you control gets +2/+2 until end of turn. If that creature is an Ally, it also gains flying until end of turn. From 106afcc2008056d173ca6f75cf5ddd1c184ba431 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Wed, 29 Oct 2025 22:58:06 +0100 Subject: [PATCH 200/230] TLA cards, 28th October, Batch 2 (#9024) --- forge-gui/res/cardsfolder/b/badgermole.txt | 2 +- .../res/cardsfolder/f/fire_lord_zuko.txt | 2 +- .../cardsfolder/upcoming/allies_at_last.txt | 7 ++++++ .../cardsfolder/upcoming/badgermole_cub.txt | 9 ++++++++ .../upcoming/crescent_island_temple.txt | 11 +++++++++ .../cardsfolder/upcoming/cunning_maneuver.txt | 6 +++++ .../cardsfolder/upcoming/cycle_of_renewal.txt | 7 ++++++ .../upcoming/firebending_student.txt | 8 +++++++ .../res/cardsfolder/upcoming/ran_and_shaw.txt | 12 ++++++++++ .../upcoming/solstice_revelations.txt | 10 ++++++++ .../upcoming/the_cave_of_two_lovers.txt | 9 ++++++++ .../upcoming/the_fire_nation_drill.txt | 11 +++++++++ .../upcoming/the_last_agni_kai.txt | 9 ++++++++ .../the_legend_of_roku_avatar_roku.txt | 23 +++++++++++++++++++ .../r_4_4_dragon_flying_firebending_4.txt | 8 +++++++ 15 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/allies_at_last.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/badgermole_cub.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/crescent_island_temple.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/cunning_maneuver.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/cycle_of_renewal.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/firebending_student.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/ran_and_shaw.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/solstice_revelations.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/the_cave_of_two_lovers.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/the_fire_nation_drill.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/the_last_agni_kai.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/the_legend_of_roku_avatar_roku.txt create mode 100644 forge-gui/res/tokenscripts/r_4_4_dragon_flying_firebending_4.txt diff --git a/forge-gui/res/cardsfolder/b/badgermole.txt b/forge-gui/res/cardsfolder/b/badgermole.txt index 9e24582a0b9..c67a6d39dce 100644 --- a/forge-gui/res/cardsfolder/b/badgermole.txt +++ b/forge-gui/res/cardsfolder/b/badgermole.txt @@ -5,5 +5,5 @@ PT:4/4 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEarthbend | TriggerDescription$ When this creature enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) SVar:TrigEarthbend:DB$ Earthbend | Num$ 2 S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddKeyword$ Trample | Description$ Creatures you control with +1/+1 counters on them have trample. -DeckHas:Ability$Counters +DeckHints:Ability$Counters Oracle:When this creature enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)\nCreatures you control with +1/+1 counters on them have trample. diff --git a/forge-gui/res/cardsfolder/f/fire_lord_zuko.txt b/forge-gui/res/cardsfolder/f/fire_lord_zuko.txt index 7a780298cc4..0a786e64795 100644 --- a/forge-gui/res/cardsfolder/f/fire_lord_zuko.txt +++ b/forge-gui/res/cardsfolder/f/fire_lord_zuko.txt @@ -2,7 +2,7 @@ Name:Fire Lord Zuko ManaCost:R W B Types:Legendary Creature Human Noble Ally PT:2/4 -K:Firebending:X:, where X is Fire Lord Zuko's power. +K:Firebending:X:, where X is CARDNAME's power. T:Mode$ SpellCast | ValidCard$ Card.wasCastFromExile | ValidActivatingPlayer$ You | Execute$ TrigPutCounterAll | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control. T:Mode$ ChangesZone | Origin$ Exile | Destination$ Battlefield | ValidCard$ Permanent.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounterAll | Secondary$ True | TriggerDescription$ Whenever you cast a spell from exile and whenever a permanent you control enters from exile, put a +1/+1 counter on each creature you control. SVar:TrigPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 diff --git a/forge-gui/res/cardsfolder/upcoming/allies_at_last.txt b/forge-gui/res/cardsfolder/upcoming/allies_at_last.txt new file mode 100644 index 00000000000..21e74ba9157 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/allies_at_last.txt @@ -0,0 +1,7 @@ +Name:Allies at Last +ManaCost:2 G +Types:Instant +K:Affinity:Ally +A:SP$ Pump | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select up to two target creatures you control | AILogic$ PowerDmg | SubAbility$ DBEachDamage | StackDescription$ {c:ThisTargetedCard} | SpellDescription$ Up to two target creatures you control +SVar:DBEachDamage:DB$ EachDamage | DefinedDamagers$ ParentTarget | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | NumDmg$ Count$CardPower | StackDescription$ REP another target creature_{c:ThisTargetedCard} | SpellDescription$ each deal damage equal to their power to target creature an opponent controls. +Oracle:Affinity for Allies (This spell costs {1} less to cast for each Ally you control.)\nUp to two target creatures you control each deal damage equal to their power to target creature an opponent controls. diff --git a/forge-gui/res/cardsfolder/upcoming/badgermole_cub.txt b/forge-gui/res/cardsfolder/upcoming/badgermole_cub.txt new file mode 100644 index 00000000000..cf7814255e6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/badgermole_cub.txt @@ -0,0 +1,9 @@ +Name:Badgermole Cub +ManaCost:1 G +Types:Creature Badger Mole +PT:2/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEarthbend | TriggerDescription$ When this creature enters, earthbend 1. (Target land you control becomes a 0/0 creature with haste that's still a land. Put a +1/+1 counter on it. When it dies or is exiled, return it to the battlefield tapped.) +SVar:TrigEarthbend:DB$ Earthbend | Num$ 1 +T:Mode$ TapsForMana | ValidCard$ Creature | Activator$ You | Execute$ TrigMana | TriggerZones$ Battlefield | Static$ True | TriggerDescription$ Whenever you tap a creature for mana, add an additional {G}. +SVar:TrigMana:DB$ Mana | Produced$ G | Amount$ 1 +Oracle:When this creature enters, earthbend 1. (Target land you control becomes a 0/0 creature with haste that's still a land. Put a +1/+1 counter on it. When it dies or is exiled, return it to the battlefield tapped.)\nWhenever you tap a creature for mana, add an additional {G}. diff --git a/forge-gui/res/cardsfolder/upcoming/crescent_island_temple.txt b/forge-gui/res/cardsfolder/upcoming/crescent_island_temple.txt new file mode 100644 index 00000000000..55be1dbc6de --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/crescent_island_temple.txt @@ -0,0 +1,11 @@ +Name:Crescent Island Temple +ManaCost:3 R +Types:Legendary Enchantment Shrine +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken1 | TriggerDescription$ When CARDNAME enters, for each Shrine you control, create a 1/1 red Monk creature token with prowess. (Whenever you cast a noncreature spell, it gets +1/+1 until end of turn.) +SVar:TrigToken1:DB$ Token | TokenAmount$ X | TokenOwner$ You | TokenScript$ r_1_1_monk_prowess +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Shrine.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigToken2 | TriggerDescription$ Whenever another Shrine you control enters, create a 1/1 red Monk creature token with prowess. +SVar:TrigToken2:DB$ Token | TokenAmount$ 1 | TokenOwner$ You | TokenScript$ r_1_1_monk_prowess +SVar:X:Count$Valid Shrine.YouCtrl +DeckHas:Ability$Counters +DeckHints:Type$Shrine +Oracle:When Crescent Island Temple enters, for each Shrine you control, create a 1/1 red Monk creature token with prowess. (Whenever you cast a noncreature spell, it gets +1/+1 until end of turn.)\nWhenever another Shrine you control enters, create a 1/1 red Monk creature token with prowess. diff --git a/forge-gui/res/cardsfolder/upcoming/cunning_maneuver.txt b/forge-gui/res/cardsfolder/upcoming/cunning_maneuver.txt new file mode 100644 index 00000000000..1c24d7c32eb --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/cunning_maneuver.txt @@ -0,0 +1,6 @@ +Name:Cunning Maneuver +ManaCost:1 R +Types:Instant +A:SP$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +3 | NumDef$ +1 | SubAbility$ DBToken | SpellDescription$ Target creature gets +3/+1 until end of turn. Create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:DBToken:DB$ Token | TokenScript$ c_a_clue_draw | TokenOwner$ You | TokenAmount$ 1 +Oracle:Target creature gets +3/+1 until end of turn.\nCreate a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") diff --git a/forge-gui/res/cardsfolder/upcoming/cycle_of_renewal.txt b/forge-gui/res/cardsfolder/upcoming/cycle_of_renewal.txt new file mode 100644 index 00000000000..c860d709576 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/cycle_of_renewal.txt @@ -0,0 +1,7 @@ +Name:Cycle of Renewal +ManaCost:2 G +Types:Instant Lesson +A:SP$ Sacrifice | Defined$ You | SacValid$ Land | SubAbility$ DBChangeZone | StackDescription$ SpellDescription | SpellDescription$ Sacrifice a land. Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | ChangeNum$ 2 | Tapped$ True | StackDescription$ None +SVar:AIPreference:SacCost$Land.Basic+tapped +Oracle:Sacrifice a land. Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/upcoming/firebending_student.txt b/forge-gui/res/cardsfolder/upcoming/firebending_student.txt new file mode 100644 index 00000000000..f3c68248540 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/firebending_student.txt @@ -0,0 +1,8 @@ +Name:Firebending Student +ManaCost:1 R +Types:Creature Human Monk +PT:1/2 +K:Prowess +K:Firebending:X:, where X is this creature's power. +SVar:X:Count$CardPower +Oracle:Prowess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.)\nFirebending X, where X is this creature's power. (Whenever this creature attacks, add X {R}. This mana lasts until end of combat.) diff --git a/forge-gui/res/cardsfolder/upcoming/ran_and_shaw.txt b/forge-gui/res/cardsfolder/upcoming/ran_and_shaw.txt new file mode 100644 index 00000000000..735297b8398 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ran_and_shaw.txt @@ -0,0 +1,12 @@ +Name:Ran and Shaw +ManaCost:3 R R +Types:Legendary Creature Dragon +PT:4/4 +K:Flying +K:Firebending:2 +T:Mode$ ChangesZone | ValidCard$ Card.Self+wasCastByYou | CheckSVar$ X | SVarCompare$ GE3 | Destination$ Battlefield | Execute$ TrigCopy | TriggerDescription$ When CARDNAME enter, if you cast them and there are three or more Dragon and/or Lesson cards in your graveyard, create a token that's a copy of CARDNAME, except it's not legendary. +SVar:TrigCopy:DB$ CopyPermanent | Defined$ Self | NumCopies$ 1 | NonLegendary$ True +A:AB$ PumpAll | Cost$ 3 R | ValidCards$ Dragon.YouCtrl | NumAtt$ +2 | SpellDescription$ Dragons you control get +2/+0 until end of turn. +SVar:X:Count$ValidGraveyard Dragon.YouOwn,Lesson.YouOwn +DeckHints:Type$Dragon|Lesson +Oracle:Flying, firebending 2\nWhen Ran and Shaw enter, if you cast them and there are three or more Dragon and/or Lesson cards in your graveyard, create a token that's a copy of Ran and Shaw, except it's not legendary.\n{3}{R}: Dragons you control get +2/+0 until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/solstice_revelations.txt b/forge-gui/res/cardsfolder/upcoming/solstice_revelations.txt new file mode 100644 index 00000000000..1fb0a49d84b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/solstice_revelations.txt @@ -0,0 +1,10 @@ +Name:Solstice Revelations +ManaCost:2 R +Types:Instant Lesson +A:SP$ DigUntil | Valid$ Card.nonLand | ValidDescription$ nonland | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | SubAbility$ DBCast | NoPutDesc$ True | SpellDescription$ Exile cards from the top of your library until you exile a nonland card. You may cast that card without paying its mana cost if the spell's mana value is less than the number of Mountains you control. If you don't cast that card this way, put it into your hand. +SVar:DBCast:DB$ Play | Defined$ Remembered | ValidSA$ Spell.cmcLEX | WithoutManaCost$ True | Optional$ True | SubAbility$ DBChangeZone +SVar:DBChangeZone:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Hand | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +K:Flashback:6 R +SVar:X:Count$Valid Mountain.YouCtrl +Oracle:Exile cards from the top of your library until you exile a nonland card. You may cast that card without paying its mana cost if the spell's mana value is less than the number of Mountains you control. If you don't cast that card this way, put it into your hand.\nFlashback {6}{R} (You may cast this card from your graveyard for its flashback cost. Then exile it.) diff --git a/forge-gui/res/cardsfolder/upcoming/the_cave_of_two_lovers.txt b/forge-gui/res/cardsfolder/upcoming/the_cave_of_two_lovers.txt new file mode 100644 index 00000000000..efa85895712 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_cave_of_two_lovers.txt @@ -0,0 +1,9 @@ +Name:The Cave of Two Lovers +ManaCost:3 R +Types:Enchantment Saga +K:Chapter:3:DBToken,DBSearch,DBEarthbend +SVar:DBToken:DB$ Token | TokenAmount$ 2 | TokenScript$ w_1_1_ally | TokenOwner$ You | SpellDescription$ Create two 1/1 white Ally creature tokens. +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Mountain,Cave | ChangeNum$ 1 | SpellDescription$ Search your library for a Mountain or Cave card, reveal it, put it into your hand, then shuffle. +SVar:DBEarthbend:DB$ Earthbend | Num$ 3 | SpellDescription$ Earthbend 3. (Target land you control becomes a 0/0 creature with haste that's still a land. Put three +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) +DeckHas:Ability$Token +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI — Create two 1/1 white Ally creature tokens.\nII — Search your library for a Mountain or Cave card, reveal it, put it into your hand, then shuffle.\nIII — Earthbend 3. (Target land you control becomes a 0/0 creature with haste that's still a land. Put three +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) diff --git a/forge-gui/res/cardsfolder/upcoming/the_fire_nation_drill.txt b/forge-gui/res/cardsfolder/upcoming/the_fire_nation_drill.txt new file mode 100644 index 00000000000..f123fe54b28 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_fire_nation_drill.txt @@ -0,0 +1,11 @@ +Name:The Fire Nation Drill +ManaCost:2 B B +Types:Legendary Artifact Vehicle +PT:6/3 +K:Trample +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigImmediateTrigger | TriggerDescription$ When CARDNAME enters, you may tap it. When you do, destroy target creature with power 4 or less. +SVar:TrigImmediateTrigger:DB$ ImmediateTrigger | Cost$ tapXType<1/Card.Self/CARDNAME> | Execute$ TrigDestroy +SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Creature.powerLE4 | TgtPrompt$ Select target creature with power 4 or less +A:AB$ AnimateAll | Cost$ 1 | ValidCards$ Permanent.OppCtrl | RemoveKeywords$ Hexproof & Indestructible | SpellDescription$ Permanents your opponents control lose hexproof and indestructible until end of turn. +K:Crew:2 +Oracle:Trample\nWhen The Fire Nation Drill enters, you may tap it. When you do, destroy target creature with power 4 or less.\n{1}: Permanents your opponents control lose hexproof and indestructible until end of turn.\nCrew 2 diff --git a/forge-gui/res/cardsfolder/upcoming/the_last_agni_kai.txt b/forge-gui/res/cardsfolder/upcoming/the_last_agni_kai.txt new file mode 100644 index 00000000000..eb1628e55c8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_last_agni_kai.txt @@ -0,0 +1,9 @@ +Name:The Last Agni Kai +ManaCost:1 R +Types:Instant +A:SP$ Pump | ValidTgts$ Creature.YouCtrl | AILogic$ Fight | TgtPrompt$ Select target creature you control | SubAbility$ DBFight | SpellDescription$ Target creature you control fights target creature an opponent controls. If the creature the opponent controls is dealt excess damage this way, add that much {R}. Until end of turn, you don't lose unspent red mana as steps and phases end. +SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.OppCtrl | ExcessSVar$ Excess | ExcessSVarCondition$ Creature.targetedBy+OppCtrl | SubAbility$ DBMana | TgtPrompt$ Select target creature an opponent controls. +SVar:DBMana:DB$ Mana | Produced$ R | Amount$ Excess | SubAbility$ DBEffect +SVar:DBEffect:DB$ Effect | StaticAbilities$ Unspent +SVar:Unspent:Mode$ UnspentMana | ValidPlayer$ You | ManaType$ Red | Description$ You don't lose unspent red mana as steps and phases end. +Oracle:Target creature you control fights target creature an opponent controls. If the creature the opponent controls is dealt excess damage this way, add that much {R}.\nUntil end of turn, you don't lose unspent red mana as steps and phases end. diff --git a/forge-gui/res/cardsfolder/upcoming/the_legend_of_roku_avatar_roku.txt b/forge-gui/res/cardsfolder/upcoming/the_legend_of_roku_avatar_roku.txt new file mode 100644 index 00000000000..c2afb17518c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_legend_of_roku_avatar_roku.txt @@ -0,0 +1,23 @@ +Name:The Legend of Roku +ManaCost:2 R R +Types:Enchantment Saga +K:Chapter:3:DBExile,DBMana,DBTransform +SVar:DBExile:DB$ Dig | Defined$ You | DigNum$ 3 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Exile the top three cards of your library. Until the end of your next turn, you may play those cards. +SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ MayPlay | Duration$ UntilTheEndOfYourNextTurn | SubAbility$ DBCleanup | ForgetOnMoved$ Exile +SVar:MayPlay:Mode$ Continuous | Affected$ Card.IsRemembered | AffectedZone$ Exile | MayPlay$ True | Description$ Until the end of your next turn, you may play those cards. +SVar:DBMana:DB$ Mana | Produced$ Any | SpellDescription$ Add one mana of any color. +SVar:DBTransform:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | SubAbility$ DBReturn | SpellDescription$ Exile this Saga, then return it to the battlefield transformed under your control. +SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | Transformed$ True | GainControl$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +AlternateMode:DoubleFaced +Oracle:(As this Saga enters and after your draw step, add a lore counter.)\nI — Exile the top three cards of your library. Until the end of your next turn, you may play those cards.\nII — Add one mana of any color.\nIII — Exile this Saga, then return it to the battlefield transformed under your control. + +ALTERNATE + +Name:Avatar Roku +ManaCost:no cost +Types:Legendary Creature Avatar +PT:4/4 +K:Firebending:4 +A:AB$ Token | Cost$ 8 | TokenAmount$ 1 | TokenScript$ r_4_4_dragon_flying_firebending_4 | TokenOwner$ You | SpellDescription$ Create a 4/4 red Dragon creature token with flying and firebending 4. +Oracle:Firebending 4 (Whenever this creature attacks, add {R}{R}{R}{R}. This mana lasts until end of combat.)\n{8}: Create a 4/4 red Dragon creature token with flying and firebending 4. diff --git a/forge-gui/res/tokenscripts/r_4_4_dragon_flying_firebending_4.txt b/forge-gui/res/tokenscripts/r_4_4_dragon_flying_firebending_4.txt new file mode 100644 index 00000000000..bd7df3cfeff --- /dev/null +++ b/forge-gui/res/tokenscripts/r_4_4_dragon_flying_firebending_4.txt @@ -0,0 +1,8 @@ +Name:Dragon Token +ManaCost:no cost +Colors:red +Types:Creature Dragon +PT:4/4 +K:Flying +K:Firebending:4 +Oracle:Flying\nFirebending 4 (Whenever this creature attacks, add {R}{R}{R}{R}. This mana lasts until end of combat.) From 107aa63c07af35ee9902cab0ef01196ba9006ce7 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Wed, 29 Oct 2025 22:58:27 +0100 Subject: [PATCH 201/230] TLA/TLE cards, 28th October, Batch 4 (#9031) --- .../res/cardsfolder/l/last_night_together.txt | 2 +- .../upcoming/avatar_kyoshi_earthbender.txt | 9 +++++++++ .../upcoming/avatar_roku_firebender.txt | 8 ++++++++ .../upcoming/azula_ruthless_firebender.txt | 12 ++++++++++++ .../res/cardsfolder/upcoming/bumi_unleashed.txt | 14 ++++++++++++++ .../cardsfolder/upcoming/diligent_zookeeper.txt | 7 +++++++ .../cardsfolder/upcoming/fang_rokus_companion.txt | 13 +++++++++++++ .../upcoming/hei_bai_forest_guardian.txt | 10 ++++++++++ .../res/cardsfolder/upcoming/kataras_reversal.txt | 6 ++++++ .../res/cardsfolder/upcoming/sokka_and_suki.txt | 11 +++++++++++ .../res/cardsfolder/upcoming/sokka_swordmaster.txt | 12 ++++++++++++ .../res/cardsfolder/upcoming/sparring_dummy.txt | 10 ++++++++++ .../upcoming/toph_earthbending_master.txt | 10 ++++++++++ .../res/cardsfolder/upcoming/trusty_boomerang.txt | 9 +++++++++ .../cardsfolder/upcoming/ty_lee_artful_acrobat.txt | 9 +++++++++ .../upcoming/wan_shi_tong_all_knowing.txt | 11 +++++++++++ forge-gui/res/cardsfolder/upcoming/war_balloon.txt | 9 +++++++++ .../res/cardsfolder/upcoming/white_lotus_tile.txt | 8 ++++++++ .../cardsfolder/upcoming/zhao_ruthless_admiral.txt | 8 ++++++++ .../upcoming/zuko_firebending_master.txt | 10 ++++++++++ 20 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/avatar_kyoshi_earthbender.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/avatar_roku_firebender.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/azula_ruthless_firebender.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/bumi_unleashed.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/diligent_zookeeper.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/fang_rokus_companion.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/hei_bai_forest_guardian.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/kataras_reversal.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sokka_and_suki.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sokka_swordmaster.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sparring_dummy.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/toph_earthbending_master.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/trusty_boomerang.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/ty_lee_artful_acrobat.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/wan_shi_tong_all_knowing.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/war_balloon.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/white_lotus_tile.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/zhao_ruthless_admiral.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/zuko_firebending_master.txt diff --git a/forge-gui/res/cardsfolder/l/last_night_together.txt b/forge-gui/res/cardsfolder/l/last_night_together.txt index 560c3ff75d6..020141ca595 100644 --- a/forge-gui/res/cardsfolder/l/last_night_together.txt +++ b/forge-gui/res/cardsfolder/l/last_night_together.txt @@ -8,6 +8,6 @@ SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Vigilance & Indestructible & Hast SVar:DBAddCombat:DB$ AddPhase | ExtraPhase$ Combat | ExtraPhaseDelayedTrigger$ DelTrigStatic | ExtraPhaseDelayedTriggerExcute$ TrigEffect | ConditionPhases$ Main1,Main2 | SpellDescription$ After this main phase, there is an additional combat phase. Only the chosen creatures can attack during that combat phase. SVar:DelTrigStatic:Mode$ Phase | Static$ True | Phase$ BeginCombat | TriggerDescription$ After this main phase, there is an additional combat phase. Only the chosen creatures can attack during that combat phase. SVar:TrigEffect:DB$ Effect | StaticAbilities$ ForbidAttack | Duration$ UntilEndOfCombat -SVar:ForbidAttack:Mode$ CantAttack | ValidCard$ Creature.YouCtrl+nonChosenCard | Description$ CARDNAME can't attack. +SVar:ForbidAttack:Mode$ CantAttack | ValidCard$ Creature.nonChosenCard | Description$ CARDNAME can't attack. DeckHas:Ability$Counters Oracle:Choose two target creatures. Untap them. Put two +1/+1 counters on each of them. They gain vigilance, indestructible, and haste until end of turn. After this main phase, there is an additional combat phase. Only the chosen creatures can attack during that combat phase. diff --git a/forge-gui/res/cardsfolder/upcoming/avatar_kyoshi_earthbender.txt b/forge-gui/res/cardsfolder/upcoming/avatar_kyoshi_earthbender.txt new file mode 100644 index 00000000000..d5bc72a4434 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/avatar_kyoshi_earthbender.txt @@ -0,0 +1,9 @@ +Name:Avatar Kyoshi, Earthbender +ManaCost:5 G G G +Types:Legendary Creature Human Avatar +PT:6/6 +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Hexproof | Condition$ PlayerTurn | Description$ During your turn, NICKNAME has hexproof. +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | Execute$ TrigEarthbend | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of combat on your turn, earthbend 8, then untap that land. (Target land you control becomes a 0/0 creature with haste that's still a land. Put eight +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) +SVar:TrigEarthbend:DB$ Earthbend | Num$ 8 | SubAbility$ DBUntap +SVar:DBUntap:DB$ Untap | Defined$ Targeted +Oracle:During your turn, Avatar Kyoshi has hexproof.\nAt the beginning of combat on your turn, earthbend 8, then untap that land. (Target land you control becomes a 0/0 creature with haste that's still a land. Put eight +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) diff --git a/forge-gui/res/cardsfolder/upcoming/avatar_roku_firebender.txt b/forge-gui/res/cardsfolder/upcoming/avatar_roku_firebender.txt new file mode 100644 index 00000000000..f5d817eac3d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/avatar_roku_firebender.txt @@ -0,0 +1,8 @@ +Name:Avatar Roku, Firebender +ManaCost:3 R R R +Types:Legendary Creature Human Avatar +PT:6/6 +T:Mode$ AttackersDeclared | TriggerZones$ Battlefield | Execute$ TrigMana | TriggerDescription$ Whenever a player attacks, add six {R}. Until end of combat, you don't lose this mana as steps end. +SVar:TrigMana:AB$ Mana | Produced$ R | Amount$ 6 | CombatMana$ True +A:AB$ Pump | Cost$ R R R | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +3 | SpellDescription$ Target creature gets +3/+0 until end of turn. +Oracle:Whenever a player attacks, add six {R}. Until end of combat, you don't lose this mana as steps end.\n{R}{R}{R}: Target creature gets +3/+0 until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/azula_ruthless_firebender.txt b/forge-gui/res/cardsfolder/upcoming/azula_ruthless_firebender.txt new file mode 100644 index 00000000000..5f83a528831 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/azula_ruthless_firebender.txt @@ -0,0 +1,12 @@ +Name:Azula, Ruthless Firebender +ManaCost:2 B +Types:Legendary Creature Human Noble +PT:3/3 +K:Firebending:1 +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigDiscard | TriggerDescription$ Whenever NICKNAME attacks, you may discard a card. Then you get an experience counter for each player who discarded a card this turn. +SVar:TrigDiscard:DB$ Discard | Defined$ You | Mode$ TgtChoose | Optional$ True | NumCards$ 1 | SubAbility$ DBExperience +SVar:DBExperience:DB$ PutCounter | Defined$ You | CounterType$ Experience | CounterNum$ X +A:AB$ Pump | Cost$ 2 B | Defined$ Self | NumAtt$ +Y | NumDef$ +Y | KW$ Menace | SpellDescription$ Until end of turn, NICKNAME gets +1/+1 for each experience counter you have and gains menace. +SVar:X:PlayerCountPlayers$ConditionGE1 CardsDiscardedThisTurn +SVar:Y:Count$YourCountersExperience +Oracle:Firebending 1 (Whenever this creature attacks, add {R}. This mana lasts until end of combat.)\nWhenever Azula attacks, you may discard a card. Then you get an experience counter for each player who discarded a card this turn.\n{2}{B}: Until end of turn, Azula gets +1/+1 for each experience counter you have and gains menace. diff --git a/forge-gui/res/cardsfolder/upcoming/bumi_unleashed.txt b/forge-gui/res/cardsfolder/upcoming/bumi_unleashed.txt new file mode 100644 index 00000000000..dd9a1c6530e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bumi_unleashed.txt @@ -0,0 +1,14 @@ +Name:Bumi, Unleashed +ManaCost:3 R G +Types:Legendary Creature Human Noble Ally +PT:5/4 +K:Trample +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEarthbend | TriggerDescription$ When NICKNAME enters, earthbend 4. +SVar:TrigEarthbend:DB$ Earthbend | Num$ 4 +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | Execute$ TrigUntapAll | CombatDamage$ True | TriggerDescription$ Whenever NICKNAME deals combat damage to a player, untap all lands you control. After this phase, there is an additional combat phase. Only land creatures can attack during that combat phase. +SVar:TrigUntapAll:DB$ UntapAll | ValidCards$ Land.YouCtrl | SubAbility$ DBAddCombat +SVar:DBAddCombat:DB$ AddPhase | ExtraPhase$ Combat | AfterPhase$ EndCombat | ExtraPhaseDelayedTrigger$ DelTrigStatic | ExtraPhaseDelayedTriggerExcute$ TrigEffect +SVar:DelTrigStatic:Mode$ Phase | Static$ True | Phase$ BeginCombat | TriggerDescription$ After this phase, there is an additional combat phase. Only land creatures can attack during that combat phase. +SVar:TrigEffect:DB$ Effect | StaticAbilities$ ForbidAttack | Duration$ UntilEndOfCombat +SVar:ForbidAttack:Mode$ CantAttack | ValidCard$ Creature.nonLand | Description$ CARDNAME can't attack. +Oracle:Trample\nWhen Bumi enters, earthbend 4.\nWhenever Bumi deals combat damage to a player, untap all lands you control. After this phase, there is an additional combat phase. Only land creatures can attack during that combat phase. diff --git a/forge-gui/res/cardsfolder/upcoming/diligent_zookeeper.txt b/forge-gui/res/cardsfolder/upcoming/diligent_zookeeper.txt new file mode 100644 index 00000000000..f5834ae0bf9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/diligent_zookeeper.txt @@ -0,0 +1,7 @@ +Name:Diligent Zookeeper +ManaCost:3 G +Types:Creature Human Citizen Ally +PT:4/4 +S:Mode$ Continuous | Affected$ Creature.nonHuman+YouCtrl | AddPower$ AffectedX | AddToughness$ AffectedX | Description$ Each non-Human creature you control gets +1/+1 for each of its creature types, to a maximum of 10. +SVar:AffectedX:Count$CreatureType/LimitMax.10 +Oracle:Each non-Human creature you control gets +1/+1 for each of its creature types, to a maximum of 10. diff --git a/forge-gui/res/cardsfolder/upcoming/fang_rokus_companion.txt b/forge-gui/res/cardsfolder/upcoming/fang_rokus_companion.txt new file mode 100644 index 00000000000..319cc2e9d0b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fang_rokus_companion.txt @@ -0,0 +1,13 @@ +Name:Fang, Roku's Companion +ManaCost:3 R R +Types:Legendary Creature Dragon +PT:4/4 +K:Flying +T:Mode$ Attacks | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever NICKNAME attacks, another target legendary creature you control gets +X/+0 until end of turn, where X is NICKNAME's power. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.Other+Legendary+YouCtrl | TgtPrompt$ Select another target legendary creature you control | NumAtt$ +X +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+nonSpirit | Execute$ DBReturn | TriggerDescription$ When NICKNAME dies, if he wasn't a Spirit, return this card to the battlefield under your control. He's a Spirit in addition to his other types. +SVar:DBReturn:DB$ ChangeZone | Defined$ TriggeredNewCardLKICopy | Origin$ Graveyard | Destination$ Battlefield | StaticEffect$ Animate +SVar:Animate:Mode$ Continuous | Affected$ Card.IsRemembered | AddType$ Spirit +SVar:X:Count$CardPower +SVar:HasAttackEffect:TRUE +Oracle:Flying\nWhenever Fang attacks, another target legendary creature you control gets +X/+0 until end of turn, where X is Fang's power.\nWhen Fang dies, if he wasn't a Spirit, return this card to the battlefield under your control. He's a Spirit in addition to his other types. diff --git a/forge-gui/res/cardsfolder/upcoming/hei_bai_forest_guardian.txt b/forge-gui/res/cardsfolder/upcoming/hei_bai_forest_guardian.txt new file mode 100644 index 00000000000..2a2824464f1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/hei_bai_forest_guardian.txt @@ -0,0 +1,10 @@ +Name:Hei Bai, Forest Guardian +ManaCost:3 G +Types:Legendary Creature Bear Spirit +PT:4/4 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDigUntil | TriggerDescription$ When NICKNAME enters, reveal cards from the top of your library until you reveal a Shrine card. You may put that card onto the battlefield. Then shuffle. +SVar:TrigDigUntil:DB$ DigUntil | Valid$ Shrine | FoundDestination$ Battlefield | OptionalFoundMove$ True | RevealedDestination$ Library | RevealedLibraryPosition$ -1 | Shuffle$ True +A:AB$ Token | Cost$ W U B R G T | TokenAmount$ X | TokenScript$ c_1_1_spirit_spiritshadow | TokenOwner$ You | SpellDescription$ For each legendary enchantment you control, create a 1/1 colorless Spirit creature token with "This token can't block or be blocked by non-Spirit creatures." +SVar:X:Count$Valid Enchantment.Legendary+YouCtrl +DeckHints:Type$Shrine +Oracle:When Hei Bai enters, reveal cards from the top of your library until you reveal a Shrine card. You may put that card onto the battlefield. Then shuffle.\n{W}{U}{B}{R}{G}, {T}: For each legendary enchantment you control, create a 1/1 colorless Spirit creature token with "This token can't block or be blocked by non-Spirit creatures." diff --git a/forge-gui/res/cardsfolder/upcoming/kataras_reversal.txt b/forge-gui/res/cardsfolder/upcoming/kataras_reversal.txt new file mode 100644 index 00000000000..fcd6022c44c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kataras_reversal.txt @@ -0,0 +1,6 @@ +Name:Katara's Reversal +ManaCost:2 U U +Types:Instant +A:SP$ Counter | TargetType$ Spell,Activated,Triggered | TgtPrompt$ Select up to four target spells and/or abilities | ValidTgts$ Card,Emblem | TargetMin$ 0 | TargetMax$ 4 | SubAbility$ DBUntap | SpellDescription$ Counter up to four target spells and/or abilities. Untap up to four target artifacts and/or creatures. +SVar:DBUntap:DB$ Untap | ValidTgts$ Artifact,Creature | TargetMin$ 0 | TargetMax$ 4 | TgtPrompt$ Select up to four target artifacts and/or creatures +Oracle:Counter up to four target spells and/or abilities.\nUntap up to four target artifacts and/or creatures. diff --git a/forge-gui/res/cardsfolder/upcoming/sokka_and_suki.txt b/forge-gui/res/cardsfolder/upcoming/sokka_and_suki.txt new file mode 100644 index 00000000000..91786733996 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sokka_and_suki.txt @@ -0,0 +1,11 @@ +Name:Sokka and Suki +ManaCost:U R W +Types:Legendary Creature Human Warrior Ally +PT:3/3 +T:Mode$ ChangesZone | ValidCard$ Card.Self,Creature.Other+Ally+YouCtrl | Origin$ Any | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigAttach | TriggerDescription$ Whenever CARDNAME or another Ally you control enters, attach up to one target Equipment you control to that creature. +SVar:TrigAttach:DB$ Attach | Defined$ TriggeredCardLKICopy | Object$ Targeted | ValidTgts$ Equipment.YouCtrl | TgtPrompt$ Select up to one target Equipment you control | TargetMin$ 0 | TargetMax$ 1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Equipment.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever an Equipment you control enters, create a 1/1 white Ally creature token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_ally | TokenOwner$ You +DeckHas:Ability$Token +DeckNeeds:Type$Equipment|Ally +Oracle:Whenever Sokka and Suki or another Ally you control enters, attach up to one target Equipment you control to that creature.\nWhenever an Equipment you control enters, create a 1/1 white Ally creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/sokka_swordmaster.txt b/forge-gui/res/cardsfolder/upcoming/sokka_swordmaster.txt new file mode 100644 index 00000000000..284775d7a5a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sokka_swordmaster.txt @@ -0,0 +1,12 @@ +Name:Sokka, Swordmaster +ManaCost:2 W +Types:Legendary Creature Human Warrior Ally +PT:3/3 +K:Vigilance +S:Mode$ ReduceCost | ValidCard$ Equipment| Type$ Spell | Activator$ You | Amount$ X | Description$ Equipment spells you cast cost {1} less to cast for each Ally you control. +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ DBAttach | TriggerDescription$ At the beginning of combat on your turn, attach up to one target Equipment you control to NICKNAME. +SVar:DBAttach:DB$ Attach | ValidTgts$ Equipment.YouCtrl | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target Equipment you control | Object$ Targeted | Defined$ Self +SVar:X:Count$Valid Ally.YouCtrl +SVar:BuffedBy:Ally +DeckHints:Type$Equipment +Oracle:Vigilance\nEquipment spells you cast cost {1} less to cast for each Ally you control.\nAt the beginning of combat on your turn, attach up to one target Equipment you control to Sokka. diff --git a/forge-gui/res/cardsfolder/upcoming/sparring_dummy.txt b/forge-gui/res/cardsfolder/upcoming/sparring_dummy.txt new file mode 100644 index 00000000000..37d0bf45555 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sparring_dummy.txt @@ -0,0 +1,10 @@ +Name:Sparring Dummy +ManaCost:1 G +Types:Artifact Creature Scarecrow +PT:1/3 +K:Defender +A:AB$ Mill | Cost$ T | NumCards$ 1 | Defined$ You | RememberMilled$ True | SubAbility$ DBChangeZone | SpellDescription$ Mill a card. You may put a land card milled this way into your hand. You gain 2 life if a Lesson card is milled this way. (To mill a card, put the top card of your library into your graveyard.) +SVar:DBChangeZone:DB$ ChangeZone | Hidden$ True | Origin$ Graveyard,Exile | Destination$ Hand | ChangeType$ Land.IsRemembered | SelectPrompt$ You may select an instant or sorcery card | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | Defined$ You | IsPresent$ Lesson.IsRemembered | PresentZone$ Graveyard,Exile | LifeAmount$ 2 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:Defender\n{T}: Mill a card. You may put a land card milled this way into your hand. You gain 2 life if a Lesson card is milled this way. (To mill a card, put the top card of your library into your graveyard.) diff --git a/forge-gui/res/cardsfolder/upcoming/toph_earthbending_master.txt b/forge-gui/res/cardsfolder/upcoming/toph_earthbending_master.txt new file mode 100644 index 00000000000..a7e91ef48aa --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/toph_earthbending_master.txt @@ -0,0 +1,10 @@ +Name:Toph, Earthbending Master +ManaCost:3 G +Types:Legendary Creature Human Warrior Ally +PT:2/4 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigExperience | TriggerDescription$ Landfall — Whenever a land you control enters, you get an experience counter. +SVar:TrigExperience:DB$ PutCounter | Defined$ You | CounterType$ Experience | CounterNum$ 1 +T:Mode$ AttackersDeclared | AttackingPlayer$ You | Execute$ TrigEarthbend | TriggerZones$ Battlefield | TriggerDescription$ Whenever you attack, earthbend X, where X is the number of experience counters you have. (Target land you control becomes a 0/0 creature with haste that's still a land. Put X +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) +SVar:TrigEarthbend:DB$ Earthbend | Num$ X +SVar:X:Count$YourCountersExperience +Oracle:Landfall — Whenever a land you control enters, you get an experience counter.\nWhenever you attack, earthbend X, where X is the number of experience counters you have. (Target land you control becomes a 0/0 creature with haste that's still a land. Put X +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) diff --git a/forge-gui/res/cardsfolder/upcoming/trusty_boomerang.txt b/forge-gui/res/cardsfolder/upcoming/trusty_boomerang.txt new file mode 100644 index 00000000000..b5c51ab7127 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/trusty_boomerang.txt @@ -0,0 +1,9 @@ +Name:Trusty Boomerang +ManaCost:1 +Types:Artifact Equipment +S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddAbility$ BoomerangTap | Description$ Equipped creature has "{1}, {T}: Tap target creature. Return CARDNAME to its owner's hand." +SVar:BoomerangTap:AB$ Tap | Cost$ 1 T | ValidTgts$ Creature | SubAbility$ DBChangeZone | SpellDescription$ Tap target creature. Return ORIGINALHOST to its owner's hand. +SVar:DBChangeZone:DB$ ChangeZone | Defined$ OriginalHost | Origin$ Battlefield | Destination$ Hand +K:Equip:1 +SVar:NonStackingAttachEffect:True +Oracle:Equipped creature has "{1}, {T}: Tap target creature. Return Trusty Boomerang to its owner's hand."\nEquip {1} ({1}: Attach to target creature you control. Equip only as a sorcery.) diff --git a/forge-gui/res/cardsfolder/upcoming/ty_lee_artful_acrobat.txt b/forge-gui/res/cardsfolder/upcoming/ty_lee_artful_acrobat.txt new file mode 100644 index 00000000000..1015acce920 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ty_lee_artful_acrobat.txt @@ -0,0 +1,9 @@ +Name:Ty Lee, Artful Acrobat +ManaCost:2 R +Types:Legendary Creature Human Performer +PT:3/2 +K:Prowess +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigImmediateTrig | TriggerDescription$ Whenever NICKNAME attacks, you may pay {1}. When you do, target creature can't block this turn. +SVar:TrigImmediateTrig:AB$ ImmediateTrigger | Cost$ 1 | Execute$ TrigPump | TriggerDescription$ When you do, target creature can't block this turn. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | KW$ HIDDEN CARDNAME can't block. +Oracle:Prowess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.)\nWhenever Ty Lee attacks, you may pay {1}. When you do, target creature can't block this turn. diff --git a/forge-gui/res/cardsfolder/upcoming/wan_shi_tong_all_knowing.txt b/forge-gui/res/cardsfolder/upcoming/wan_shi_tong_all_knowing.txt new file mode 100644 index 00000000000..a6c03fe26df --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/wan_shi_tong_all_knowing.txt @@ -0,0 +1,11 @@ +Name:Wan Shi Tong, All-Knowing +ManaCost:3 U U +Types:Legendary Creature Bird Spirit +PT:4/4 +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When NICKNAME enters, target nonland permanent's owner puts it into their library second from the top or on the bottom. +SVar:TrigChangeZone:DB$ ChangeZone | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | AlternativeDecider$ TargetedOwner | Origin$ Battlefield | Destination$ Library | LibraryPosition$ 1 | DestinationAlternative$ Library | LibraryPositionAlternative$ -1 +T:Mode$ ChangesZoneAll | ValidCards$ Card.!token | Origin$ Any | Destination$ Library | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever one or more cards are put into a library from anywhere, create two 1/1 colorless Spirit creature tokens with "This token can't block or be blocked by non-Spirit creatures." +SVar:TrigToken:DB$ Token | TokenAmount$ 2 | TokenScript$ c_1_1_spirit_spiritshadow | TokenOwner$ You +DeckHas:Ability$Token +Oracle:Flying\nWhen Wan Shi Tong enters, target nonland permanent's owner puts it into their library second from the top or on the bottom.\nWhenever one or more cards are put into a library from anywhere, create two 1/1 colorless Spirit creature tokens with "This token can't block or be blocked by non-Spirit creatures." diff --git a/forge-gui/res/cardsfolder/upcoming/war_balloon.txt b/forge-gui/res/cardsfolder/upcoming/war_balloon.txt new file mode 100644 index 00000000000..09a18b1d668 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/war_balloon.txt @@ -0,0 +1,9 @@ +Name:War Balloon +ManaCost:2 R +Types:Artifact Vehicle +PT:4/3 +K:Flying +A:AB$ PutCounter | Cost$ 1 | CounterType$ FIRE | CounterNum$ 1 | SpellDescription$ Put a fire counter on this Vehicle. +S:Mode$ Continuous | Affected$ Card.Self+counters_GE3_FIRE | AddType$ Artifact,Creature | Description$ As long as this Vehicle has three or more fire counters on it, it's an artifact creature. +K:Crew:3 +Oracle:Flying\n{1}: Put a fire counter on this Vehicle.\nAs long as this Vehicle has three or more fire counters on it, it's an artifact creature.\nCrew 3 (Tap any number of creatures you control with total power 3 or more: This Vehicle becomes an artifact creature until end of turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/white_lotus_tile.txt b/forge-gui/res/cardsfolder/upcoming/white_lotus_tile.txt new file mode 100644 index 00000000000..04f355ad314 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/white_lotus_tile.txt @@ -0,0 +1,8 @@ +Name:White Lotus Tile +ManaCost:4 +Types:Artifact +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ This artifact enters tapped. +SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True +A:AB$ Mana | Cost$ T | Produced$ Any | Amount$ X | SpellDescription$ Add X mana of any one color, where X is the greatest number of creatures you control that have a creature type in common. +SVar:X:Count$MostProminentCreatureType Creature.YouCtrl +Oracle:This artifact enters tapped.\n{T}: Add X mana of any one color, where X is the greatest number of creatures you control that have a creature type in common. diff --git a/forge-gui/res/cardsfolder/upcoming/zhao_ruthless_admiral.txt b/forge-gui/res/cardsfolder/upcoming/zhao_ruthless_admiral.txt new file mode 100644 index 00000000000..e173da17e89 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/zhao_ruthless_admiral.txt @@ -0,0 +1,8 @@ +Name:Zhao, Ruthless Admiral +ManaCost:2 BR BR +Types:Legendary Creature Human Soldier +PT:2/2 +K:Firebending:2 +T:Mode$ Sacrificed | ValidCard$ Permanent.Other | Execute$ TrigPumpAll | TriggerZones$ Battlefield | ValidPlayer$ You | TriggerDescription$ Whenever you sacrifice another permanent, creatures you control get +1/+0 until end of turn. +SVar:TrigPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl | NumAtt$ +1 +Oracle:Firebending 2 (Whenever this creature attacks, add {R}{R}. This mana lasts until end of combat.)\nWhenever you sacrifice another permanent, creatures you control get +1/+0 until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/zuko_firebending_master.txt b/forge-gui/res/cardsfolder/upcoming/zuko_firebending_master.txt new file mode 100644 index 00000000000..707e837d5bf --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/zuko_firebending_master.txt @@ -0,0 +1,10 @@ +Name:Zuko, Firebending Master +ManaCost:1 R +Types:Legendary Creature Human Noble Ally +PT:2/2 +K:First Strike +K:Firebending:X:, where X is the number of experience counters you have. +T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | Phase$ BeginCombat->EndCombat | Execute$ TrigExperience | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a spell during combat, you get an experience counter. +SVar:TrigExperience:DB$ PutCounter | Defined$ You | CounterType$ Experience | CounterNum$ 1 +SVar:X:Count$YourCountersExperience +Oracle:First strike\nFirebending X, where X is the number of experience counters you have. (Whenever this creature attacks, add X {R}. This mana lasts until end of combat.)\nWhenever you cast a spell during combat, you get an experience counter. From 45e9bee6e51ede849d5a107e516816cca2df938d Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Thu, 30 Oct 2025 08:01:48 +0100 Subject: [PATCH 202/230] Update gran_gran.txt (#9043) --- forge-gui/res/cardsfolder/upcoming/gran_gran.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/gran_gran.txt b/forge-gui/res/cardsfolder/upcoming/gran_gran.txt index b470659c51d..08e4b547c47 100644 --- a/forge-gui/res/cardsfolder/upcoming/gran_gran.txt +++ b/forge-gui/res/cardsfolder/upcoming/gran_gran.txt @@ -1,4 +1,4 @@ -Name:Gran-Fran +Name:Gran-Gran ManaCost:U Types:Legendary Creature Human Peasant Ally PT:1/2 From e5483f03c5ee7c737350165922af4f158ae16a98 Mon Sep 17 00:00:00 2001 From: Paul Hammerton <18243520+paulsnoops@users.noreply.github.com> Date: Thu, 30 Oct 2025 07:52:37 +0000 Subject: [PATCH 203/230] Add flavorName to editions (#9019) --- .../Avatar The Last Airbender Eternal.txt | 8 +- forge-gui/res/editions/Celebration Cards.txt | 2 +- forge-gui/res/editions/Fallout.txt | 36 +- .../res/editions/Final Fantasy Commander.txt | 2 +- .../Final Fantasy Through the Ages.txt | 130 ++--- .../res/editions/Ikoria Lair of Behemoths.txt | 38 +- .../res/editions/Innistrad Crimson Vow.txt | 36 +- forge-gui/res/editions/Love Your LGS 2020.txt | 2 +- .../res/editions/Magic Online Promos.txt | 36 +- forge-gui/res/editions/MagicFest 2023.txt | 2 +- forge-gui/res/editions/MagicFest 2025.txt | 4 +- forge-gui/res/editions/Marvel Universe.txt | 10 +- .../res/editions/Secret Lair Drop Series.txt | 456 +++++++++--------- .../res/editions/Store Championships.txt | 6 +- ... Rings Tales of Middle-earth Commander.txt | 130 ++--- ...ord of the Rings Tales of Middle-earth.txt | 2 +- 16 files changed, 450 insertions(+), 450 deletions(-) diff --git a/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt b/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt index fab5e2b1041..5b28779adee 100644 --- a/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt +++ b/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt @@ -12,11 +12,11 @@ ScryfallCode=TLE 13 M Force of Negation @Viacom 16 M Mystic Remora @Viacom 24 M Cruel Tutor @Viacom -41 M The Great Henge @Viacom +41 M The Great Henge @Viacom ${"flavorName": "The Banyan Tree"} 43 M Heroic Intervention @Viacom -48 M Eladamri's Call @Viacom -56 M Dark Depths @Viacom -61 M Valakut, the Molten Pinnacle @Viacom +48 M Eladamri's Call @Viacom ${"flavorName": "Lifelong Friendship"} +56 M Dark Depths @Viacom ${"flavorName": "The Boy in the Iceberg"} +61 M Valakut, the Molten Pinnacle @Viacom ${"flavorName": "Volcano of Roku's Island"} 63 R Katara's Reversal @Fahmi Fauzi 69 R Aang and Katara @Brian Yuen 70 R Toph, Greatest Earthbender @Brian Yuen diff --git a/forge-gui/res/editions/Celebration Cards.txt b/forge-gui/res/editions/Celebration Cards.txt index b264c653959..3f7cb4d6aa1 100644 --- a/forge-gui/res/editions/Celebration Cards.txt +++ b/forge-gui/res/editions/Celebration Cards.txt @@ -16,4 +16,4 @@ ScryfallCode=PCEL 9 M Deb Thomas @Kieran Yanner [eternal] -8 R Zur the Enchanter @Chase Stone +8 R Zur the Enchanter @Chase Stone ${"flavorName": "Post the Enchanter"} diff --git a/forge-gui/res/editions/Fallout.txt b/forge-gui/res/editions/Fallout.txt index 90ca2731c56..ab8b01d82a4 100644 --- a/forge-gui/res/editions/Fallout.txt +++ b/forge-gui/res/editions/Fallout.txt @@ -349,15 +349,15 @@ ScryfallCode=PIP 341 M Dr. Madison Li @John Stanko 342 R Inventory Management @Tim Brumley 343 M The Wise Mothman @David Gaillet -344 R Hullbreaker Horror @Tomas Duchek -345 R Lord of the Undead @John Stanko -346 M Grave Titan @Mark Poole -347 R Vigor @Tomas Duchek -348 R Ayula, Queen Among Bears @Mark Poole -349 M Tarmogoyf @Robin Olausson -350 R Hornet Queen @Robin Olausson -351 R Gemrazer @Evan Shipard -352 R Walking Ballista @Evan Shipard +344 R Hullbreaker Horror @Tomas Duchek ${"flavorName": "Prime Mirelurk Queen"} +345 R Lord of the Undead @John Stanko ${"flavorName": "Centurion of the Marked"} +346 M Grave Titan @Mark Poole ${"flavorName": "West Tek Tyrant"} +347 R Vigor @Tomas Duchek ${"flavorName": "Fog Crawler"} +348 R Ayula, Queen Among Bears @Mark Poole ${"flavorName": "Ruzka, Terror of Point Lookout"} +349 M Tarmogoyf @Robin Olausson ${"flavorName": "Scrounging Deathclaw"} +350 R Hornet Queen @Robin Olausson ${"flavorName": "Specimen 73"} +351 R Gemrazer @Evan Shipard ${"flavorName": "Toxic Sheepsquatch"} +352 R Walking Ballista @Evan Shipard ${"flavorName": "Assaultron Invader"} 353 R Farewell @AKQA 354 M Ravages of War @AKQA 355 U Vandalblast @AKQA @@ -877,15 +877,15 @@ ScryfallCode=PIP 869 M Dr. Madison Li @John Stanko 870 R Inventory Management @Tim Brumley 871 M The Wise Mothman @David Gaillet -872 R Hullbreaker Horror @Tomas Duchek -873 R Lord of the Undead @John Stanko -874 M Grave Titan @Mark Poole -875 R Vigor @Tomas Duchek -876 R Ayula, Queen Among Bears @Mark Poole -877 M Tarmogoyf @Robin Olausson -878 R Hornet Queen @Robin Olausson -879 R Gemrazer @Evan Shipard -880 R Walking Ballista @Evan Shipard +872 R Hullbreaker Horror @Tomas Duchek ${"flavorName": "Prime Mirelurk Queen"} +873 R Lord of the Undead @John Stanko ${"flavorName": "Centurion of the Marked"} +874 M Grave Titan @Mark Poole ${"flavorName": "West Tek Tyrant"} +875 R Vigor @Tomas Duchek ${"flavorName": "Fog Crawler"} +876 R Ayula, Queen Among Bears @Mark Poole ${"flavorName": "Ruzka, Terror of Point Lookout"} +877 M Tarmogoyf @Robin Olausson ${"flavorName": "Scrounging Deathclaw"} +878 R Hornet Queen @Robin Olausson ${"flavorName": "Specimen 73"} +879 R Gemrazer @Evan Shipard ${"flavorName": "Toxic Sheepsquatch"} +880 R Walking Ballista @Evan Shipard ${"flavorName": "Assaultron Invader"} 881 R Farewell @AKQA 882 M Ravages of War @AKQA 883 U Vandalblast @AKQA diff --git a/forge-gui/res/editions/Final Fantasy Commander.txt b/forge-gui/res/editions/Final Fantasy Commander.txt index 38511d0b529..464be5edc4b 100644 --- a/forge-gui/res/editions/Final Fantasy Commander.txt +++ b/forge-gui/res/editions/Final Fantasy Commander.txt @@ -488,7 +488,7 @@ ScryfallCode=FIC 480 L Swamp @Ryuichi Sakuma 481 L Mountain @Nao Miyoshi 482 L Forest @ZOUNOSE -483 R Birds of Paradise @Takotto +483 R Birds of Paradise @Takotto ${"flavorName": "Paradise Chocobo"} 484 C Command Tower @Anthony Devine 485 C Command Tower @Eduardo Francisco 486 C Command Tower @Jonas De Ro diff --git a/forge-gui/res/editions/Final Fantasy Through the Ages.txt b/forge-gui/res/editions/Final Fantasy Through the Ages.txt index 34b57416c20..a6c58d3f3b7 100644 --- a/forge-gui/res/editions/Final Fantasy Through the Ages.txt +++ b/forge-gui/res/editions/Final Fantasy Through the Ages.txt @@ -6,70 +6,70 @@ Type=Collector_Edition ScryfallCode=FCA [cards] -1 R Adeline, Resplendent Cathar @Yoshitaka Amano -2 M Ranger-Captain of Eos @Yoshitaka Amano -3 R Sram, Senior Edificer @Yoshitaka Amano -4 U Counterspell @Yoshitaka Amano -5 M Urza, Lord High Artificer @Yoshitaka Amano -6 R Venser, Shaper Savant @Yoshitaka Amano -7 R Bolas's Citadel @Yoshitaka Amano -8 R Dark Ritual @Yoshitaka Amano -9 U Fatal Push @Yoshitaka Amano -10 U Syr Konrad, the Grim @Yoshitaka Amano -11 M Yawgmoth, Thran Physician @Yoshitaka Amano -12 M Ancient Copper Dragon @Yoshitaka Amano -13 R Godo, Bandit Warlord @Yoshitaka Amano -14 M Purphoros, God of the Forge @Yoshitaka Amano -15 R Azusa, Lost but Seeking @Yoshitaka Amano -16 M Nyxbloom Ancient @Yoshitaka Amano -17 R Jodah, the Unifier @Yoshitaka Amano -18 R Tymna the Weaver @Yoshitaka Amano -19 R Winota, Joiner of Forces @Yoshitaka Amano -20 R Traxos, Scourge of Kroog @Yoshitaka Amano -21 M Akroma's Will @Square Enix -22 U Danitha Capashen, Paragon @Square Enix -23 R Kenrith, the Returned King @Square Enix -24 R Loran of the Third Path @Square Enix -25 R Mangara, the Diplomat @Toshitaka Matsuda -26 U Stroke of Midnight @Square Enix -27 U Wall of Omens @ISAMU KAMIKOKURYO -28 U Brainstorm @Akihiko Yoshida -29 R Cryptic Command @Kazuya Takahashi -30 U Laboratory Maniac @Tetsuya Nomura -31 M Rhystic Study @Square Enix -32 R Teferi, Mage of Zhalfir @Square Enix -33 U Deadly Dispute @Airi Yoshioka -34 R Diabolic Intent @Fumio Minagawa -35 M Gix, Yawgmoth Praetor @SUEMI Jun -36 R K'rrik, Son of Yawgmoth @Yusuke Mogi -37 R Varragoth, Bloodsky Sire @Square Enix -38 U Captain Lannery Storm @Square Enix -39 U Light Up the Stage @Square Enix -40 U Lightning Bolt @Toshitaka Matsuda -41 R Mizzix's Mastery @Toshitaka Matsuda -42 M Najeela, the Blade-Blossom @Tetsuya Nomura -43 M Ragavan, Nimble Pilferer @SHUKO MURASE -44 R Carpet of Flowers @ISAMU KAMIKOKURYO -45 U Farseek @Yusuke Mogi -46 U Fynn, the Fangbearer @Square Enix -47 U Nature's Claim @Square Enix -48 R Primeval Titan @Tomohiro Hasegawa -49 M Atraxa, Grand Unifier @Tetsuya Nomura -50 R Bruse Tarl, Boorish Herder @Square Enix -51 U Dovin's Veto @Akihiko Yoshida -52 R Inalla, Archmage Ritualist @Square Enix -53 R Ishai, Ojutai Dragonspeaker @Square Enix -54 R Isshin, Two Heavens as One @Tetsuya Nomura -55 M Kinnan, Bonder Prodigy @Square Enix -56 R Kraum, Ludevic's Opus @Square Enix -57 R Muldrotha, the Gravetide @Chikako Nakano -58 M Thrasios, Triton Hero @Square Enix -59 M Vial Smasher the Fierce @Square Enix -60 R Yuriko, the Tiger's Shadow @Tetsuya Nomura -61 R Chromatic Lantern @Kiyoshi Arai -62 R Smuggler's Copter @Square Enix -63 U Strixhaven Stadium @Square Enix -64 R Command Beacon @Square Enix +1 R Adeline, Resplendent Cathar @Yoshitaka Amano ${"flavorName": "Hero of Light"} +2 M Ranger-Captain of Eos @Yoshitaka Amano ${"flavorName": "Knights of San d'Oria"} +3 R Sram, Senior Edificer @Yoshitaka Amano ${"flavorName": "Firion, Swordmaster"} +4 U Counterspell @Yoshitaka Amano ${"flavorName": "Wild Rose Rebellion"} +5 M Urza, Lord High Artificer @Yoshitaka Amano ${"flavorName": "Terra Branford"} +6 R Venser, Shaper Savant @Yoshitaka Amano ${"flavorName": "Master Xande"} +7 R Bolas's Citadel @Yoshitaka Amano ${"flavorName": "Kefka's Tower"} +8 R Dark Ritual @Yoshitaka Amano ${"flavorName": "Darkness of Eternity"} +9 U Fatal Push @Yoshitaka Amano ${"flavorName": "Battle at the Big Bridge"} +10 U Syr Konrad, the Grim @Yoshitaka Amano ${"flavorName": "Golbez, Clad In Darkness"} +11 M Yawgmoth, Thran Physician @Yoshitaka Amano ${"flavorName": "The Emperor, Hell Tyrant"} +12 M Ancient Copper Dragon @Yoshitaka Amano ${"flavorName": "Dragon of Mount Gulg"} +13 R Godo, Bandit Warlord @Yoshitaka Amano ${"flavorName": "Gilgamesh, Weapon Collector"} +14 M Purphoros, God of the Forge @Yoshitaka Amano ${"flavorName": "Kefka Palazzo"} +15 R Azusa, Lost but Seeking @Yoshitaka Amano ${"flavorName": "Princess Sarah"} +16 M Nyxbloom Ancient @Yoshitaka Amano ${"flavorName": "The Cloudsea Djinn"} +17 R Jodah, the Unifier @Yoshitaka Amano ${"flavorName": "Warrior of Light"} +18 R Tymna the Weaver @Yoshitaka Amano ${"flavorName": "Cecil Harvey"} +19 R Winota, Joiner of Forces @Yoshitaka Amano ${"flavorName": "Bartz Klauser"} +20 R Traxos, Scourge of Kroog @Yoshitaka Amano ${"flavorName": "Giant of Babil"} +21 M Akroma's Will @Square Enix ${"flavorName": "Blessing of the Oracle"} +22 U Danitha Capashen, Paragon @Square Enix ${"flavorName": "Squall Leonhart"} +23 R Kenrith, the Returned King @Square Enix ${"flavorName": "Noctis Lucis Caelum"} +24 R Loran of the Third Path @Square Enix ${"flavorName": "Garnet Til Alexandros 17th"} +25 R Mangara, the Diplomat @Toshitaka Matsuda ${"flavorName": "Minwu, Rebellion Strategist"} +26 U Stroke of Midnight @Square Enix ${"flavorName": "Memories of Nibelheim"} +27 U Wall of Omens @ISAMU KAMIKOKURYO ${"flavorName": "The Imperial City of Archades"} +28 U Brainstorm @Akihiko Yoshida ${"flavorName": "Endwalker"} +29 R Cryptic Command @Kazuya Takahashi ${"flavorName": "To the Crystal Tower"} +30 U Laboratory Maniac @Tetsuya Nomura ${"flavorName": "Vana'diel Adventurers"} +31 M Rhystic Study @Square Enix ${"flavorName": "Stay with Me"} +32 R Teferi, Mage of Zhalfir @Square Enix ${"flavorName": "Edea Kramer"} +33 U Deadly Dispute @Airi Yoshioka ${"flavorName": "Baron Rivalry"} +34 R Diabolic Intent @Fumio Minagawa ${"flavorName": "Shantotto's Coercion"} +35 M Gix, Yawgmoth Praetor @SUEMI Jun ${"flavorName": "The Shadow Lord"} +36 R K'rrik, Son of Yawgmoth @Yusuke Mogi ${"flavorName": "Emet-Selch, Ascian"} +37 R Varragoth, Bloodsky Sire @Square Enix ${"flavorName": "Ardyn Izunia"} +38 U Captain Lannery Storm @Square Enix ${"flavorName": "Vaan, Aspiring Sky Pirate"} +39 U Light Up the Stage @Square Enix ${"flavorName": "A Promise Fulfilled"} +40 U Lightning Bolt @Toshitaka Matsuda ${"flavorName": "Thrum of the Vestige"} +41 R Mizzix's Mastery @Toshitaka Matsuda ${"flavorName": "Dawn Warriors' Legacy"} +42 M Najeela, the Blade-Blossom @Tetsuya Nomura ${"flavorName": "Cloud Strife"} +43 M Ragavan, Nimble Pilferer @SHUKO MURASE ${"flavorName": "Zidane Tribal"} +44 R Carpet of Flowers @ISAMU KAMIKOKURYO ${"flavorName": "Fal'Cie Paradise"} +45 U Farseek @Yusuke Mogi ${"flavorName": "Newfound Adventure"} +46 U Fynn, the Fangbearer @Square Enix ${"flavorName": "Vayne Carudas Solidor"} +47 U Nature's Claim @Square Enix ${"flavorName": "Search for the Frozen Esper"} +48 R Primeval Titan @Tomohiro Hasegawa ${"flavorName": "Astral Titan"} +49 M Atraxa, Grand Unifier @Tetsuya Nomura ${"flavorName": "Sephiroth, the Savior"} +50 R Bruse Tarl, Boorish Herder @Square Enix ${"flavorName": "Hugo Kupka"} +51 U Dovin's Veto @Akihiko Yoshida ${"flavorName": "Shadowbringers"} +52 R Inalla, Archmage Ritualist @Square Enix ${"flavorName": "Kuja, Mage Manufacturer"} +53 R Ishai, Ojutai Dragonspeaker @Square Enix ${"flavorName": "Benedikta Harman"} +54 R Isshin, Two Heavens as One @Tetsuya Nomura ${"flavorName": "Lightning, Lone Commando"} +55 M Kinnan, Bonder Prodigy @Square Enix ${"flavorName": "Seymour Guado"} +56 R Kraum, Ludevic's Opus @Square Enix ${"flavorName": "Barnabas Tharmr"} +57 R Muldrotha, the Gravetide @Chikako Nakano ${"flavorName": "Orphan, Cocoon fal'Cie"} +58 M Thrasios, Triton Hero @Square Enix ${"flavorName": "Tidus, Zanarkand Fayth"} +59 M Vial Smasher the Fierce @Square Enix ${"flavorName": "Clive Rosfield"} +60 R Yuriko, the Tiger's Shadow @Tetsuya Nomura ${"flavorName": "Yuffie Kisaragi"} +61 R Chromatic Lantern @Kiyoshi Arai ${"flavorName": "Crystal of Altar Cave"} +62 R Smuggler's Copter @Square Enix ${"flavorName": "The Strahl"} +63 U Strixhaven Stadium @Square Enix ${"flavorName": "Luka Stadium"} +64 R Command Beacon @Square Enix ${"flavorName": "Balamb Garden"} [rebalanced] -A-19 R A-Winota, Joiner of Forces @Yoshitaka Amano \ No newline at end of file +A-19 R A-Winota, Joiner of Forces @Yoshitaka Amano ${"flavorName": "Bartz Klauser"} diff --git a/forge-gui/res/editions/Ikoria Lair of Behemoths.txt b/forge-gui/res/editions/Ikoria Lair of Behemoths.txt index dd3fbf2859f..b25ceb5dfe3 100644 --- a/forge-gui/res/editions/Ikoria Lair of Behemoths.txt +++ b/forge-gui/res/editions/Ikoria Lair of Behemoths.txt @@ -285,7 +285,7 @@ FatPackExtraSlots=20 BasicLands, 20 BasicLands+ 274 L Forest @Yeong-Hao Han [buy a box] -275 M Zilortha, Strength Incarnate @Antonio José Manzanedo +275 M Zilortha, Strength Incarnate @Antonio José Manzanedo ${"flavorName": "Godzilla, King of the Monsters"} 275y M Zilortha, Strength Incarnate @Chase Stone [borderless] @@ -389,24 +389,24 @@ FatPackExtraSlots=20 BasicLands, 20 BasicLands+ 367 C Forbidden Friendship @Jakub Kasper 368 U Migration Path @Grzegorz Rutkowski 369 U Sprite Dragon @Gabor Szikszai -370 U Huntmaster Liger @Slawomir Maniak -371 M Luminous Broodmoth @Nick Southam -372 U Pollywog Symbiote @Simon Dominic -373 U Void Beckoner @Zezhou Chen -374 R Everquill Phoenix @JOZ -375 R Yidaro, Wandering Monster @Yigit Koroglu -376 R Gemrazer @Sam Rowan -377 U Titanoth Rex @Lius Lasahido -378 M Brokkos, Apex of Forever @Svetlin Velinov -379 M Illuna, Apex of Wishes @Nicholas Gregory -380 M Nethroi, Apex of Death @Torstein Nordstrand -381 M Snapdax, Apex of the Hunt @YW Tang -382 U Sprite Dragon @Simon Dominic -383 M Vadrok, Apex of Thunder @Johann Bodin -384 R Gyruda, Doom of Depths @Jesper Ejsing -385 C Mysterious Egg @Yoroikoji -386 R Dirge Bat @Kohei Hayama -387 R Crystalline Giant @Kotakan +370 U Huntmaster Liger @Slawomir Maniak ${"flavorName": "King Caesar, Ancient Guardian"} +371 M Luminous Broodmoth @Nick Southam ${"flavorName": "Mothra, Supersonic Queen"} +372 U Pollywog Symbiote @Simon Dominic ${"flavorName": "Babygodzilla, Ruin Reborn"} +373 U Void Beckoner @Zezhou Chen ${"flavorName": "Spacegodzilla, Death Corona"} +374 R Everquill Phoenix @JOZ ${"flavorName": "Destoroyah, Perfect Lifeform"} +375 R Yidaro, Wandering Monster @Yigit Koroglu ${"flavorName": "Godzilla, Doom Inevitable"} +376 R Gemrazer @Sam Rowan ${"flavorName": "Anguirus, Armored Killer"} +377 U Titanoth Rex @Lius Lasahido ${"flavorName": "Godzilla, Primeval Champion"} +378 M Brokkos, Apex of Forever @Svetlin Velinov ${"flavorName": "Bio-Quartz Spacegodzilla"} +379 M Illuna, Apex of Wishes @Nicholas Gregory ${"flavorName": "Ghidorah, King of the Cosmos"} +380 M Nethroi, Apex of Death @Torstein Nordstrand ${"flavorName": "Biollante, Plant Beast Form"} +381 M Snapdax, Apex of the Hunt @YW Tang ${"flavorName": "King Caesar, Awoken Titan"} +382 U Sprite Dragon @Simon Dominic ${"flavorName": "Dorat, the Perfect Pet"} +383 M Vadrok, Apex of Thunder @Johann Bodin ${"flavorName": "Rodan, Titan of Winged Fury"} +384 R Gyruda, Doom of Depths @Jesper Ejsing ${"flavorName": "Gigan, Cyberclaw Terror"} +385 C Mysterious Egg @Yoroikoji ${"flavorName": "Mothra's Great Cocoon"} +386 R Dirge Bat @Kohei Hayama ${"flavorName": "Battra, Dark Destroyer"} +387 R Crystalline Giant @Kotakan ${"flavorName": "Mechagodzilla, the Weapon"} [rebalanced] A-216 M A-Winota, Joiner of Forces @Magali Villeneuve diff --git a/forge-gui/res/editions/Innistrad Crimson Vow.txt b/forge-gui/res/editions/Innistrad Crimson Vow.txt index 5eeb6195b94..cb7681922c8 100644 --- a/forge-gui/res/editions/Innistrad Crimson Vow.txt +++ b/forge-gui/res/editions/Innistrad Crimson Vow.txt @@ -340,23 +340,23 @@ Prerelease=6 Boosters, 1 RareMythic+ 326 R Old Rutstein @Mark Riddick 327 R Runo Stromkirk @Nico Delort 328 R Torens, Fist of the Angels @Cabrol -329 U Circle of Confinement @Dan Murayama Scott -330 M Savior of Ollenbock @Johannes Voss -331 R Thalia, Guardian of Thraben @Magali Villeneuve -332 M Jacob Hauken, Inspector @Slawomir Maniak -333 U Thirst for Discovery @Dan Murayama Scott -334 R Falkenrath Forebear @Greg Staples -335 M Henrika Domnathi @Nils Hamm -336 U Innocent Traveler @Igor Kieryluk -337 M Sorin the Mirthless @Bastien L. Deharme -338 R Voldaren Bloodcaster @Johann Bodin -339 U Vampires' Vengeance @Dave Kendall -340 U Reclusive Taxidermist @Tomasz Jedruszek -341 R Edgar, Charmed Groom @Lucas Graciano -342 R Eruth, Tormented Prophet @Dave Kendall -343 M Olivia, Crimson Bride @Bastien L. Deharme -344 R Torens, Fist of the Angels @Greg Staples -345 R Investigator's Journal @Alix Branwyn +329 U Circle of Confinement @Dan Murayama Scott ${"flavorName": "Van Helsing's Holy Ward"} +330 M Savior of Ollenbock @Johannes Voss ${"flavorName": "Abraham Van Helsing"} +331 R Thalia, Guardian of Thraben @Magali Villeneuve ${"flavorName": "Mina Harker"} +332 M Jacob Hauken, Inspector @Slawomir Maniak ${"flavorName": "Jonathan Harker // Harker's Obsessive Inquiry"} +333 U Thirst for Discovery @Dan Murayama Scott ${"flavorName": "Search the Count's Castle"} +334 R Falkenrath Forebear @Greg Staples ${"flavorName": "Dracula, Blood Immortal"} +335 M Henrika Domnathi @Nils Hamm ${"flavorName": "The Three Weird Sisters // Fiends of Darkest Night"} +336 U Innocent Traveler @Igor Kieryluk ${"flavorName": "Lucy Westenra // Lucy, Arisen Vampire"} +337 M Sorin the Mirthless @Bastien L. Deharme ${"flavorName": "Count Dracula"} +338 R Voldaren Bloodcaster @Johann Bodin ${"flavorName": "Dracula, Lord of Blood // Dracula, Lord of Bats"} +339 U Vampires' Vengeance @Dave Kendall ${"flavorName": "Mysterious Blood Illness"} +340 U Reclusive Taxidermist @Tomasz Jedruszek ${"flavorName": "Quincey Harker"} +341 R Edgar, Charmed Groom @Lucas Graciano ${"flavorName": "Dracula the Voyager // Casket of Native Earth"} +342 R Eruth, Tormented Prophet @Dave Kendall ${"flavorName": "Renfield, Delusional Minion"} +343 M Olivia, Crimson Bride @Bastien L. Deharme ${"flavorName": "Sisters of the Undead"} +344 R Torens, Fist of the Angels @Greg Staples ${"flavorName": "Dr. John Seward"} +345 R Investigator's Journal @Alix Branwyn ${"flavorName": "Harker's Journal"} [extended art] 346 R By Invitation Only @Micah Epstein @@ -420,7 +420,7 @@ Prerelease=6 Boosters, 1 RareMythic+ 402 L Forest @Sam White [buy a box] -403 R Voldaren Estate @Cliff Childs +403 R Voldaren Estate @Cliff Childs ${"flavorName": "Castle Dracula"} [promo] 404 R Sigarda's Summons @Wisnu Tan diff --git a/forge-gui/res/editions/Love Your LGS 2020.txt b/forge-gui/res/editions/Love Your LGS 2020.txt index 33388813837..898b9148d6e 100644 --- a/forge-gui/res/editions/Love Your LGS 2020.txt +++ b/forge-gui/res/editions/Love Your LGS 2020.txt @@ -8,4 +8,4 @@ ScryfallCode=PLG20 [cards] 1 R Reliquary Tower @Daniel Ljunggren -2 R Hangarback Walker @Nicholas Gregory +2 R Hangarback Walker @Nicholas Gregory ${"flavorName": "Mechagodzilla, Battle Fortress"} diff --git a/forge-gui/res/editions/Magic Online Promos.txt b/forge-gui/res/editions/Magic Online Promos.txt index bcb74abd235..dfef3d3fe92 100644 --- a/forge-gui/res/editions/Magic Online Promos.txt +++ b/forge-gui/res/editions/Magic Online Promos.txt @@ -1492,24 +1492,24 @@ ScryfallCode=PRM 80899 R Inspired Ultimatum @Tyler Jacobson 80901 R Genesis Ultimatum @Jason Rainville 80903 R Bonders' Enclave @Cliff Childs -80905 M Nethroi, Apex of Death @Torstein Nordstrand -80907 M Vadrok, Apex of Thunder @Johann Bodin -80909 M Brokkos, Apex of Forever @Svetlin Velinov -80911 M Snapdax, Apex of the Hunt @YW Tang -80913 M Illuna, Apex of Wishes @Nicholas Gregory -80915 M Luminous Broodmoth @Nick Southam -80917 R Gemrazer @Sam Rowan -80919 R Gyruda, Doom of Depths @Jesper Ejsing -80921 R Yidaro, Wandering Monster @Yigit Koroglu -80923 U Void Beckoner @Zezhou Chen -80925 U Titanoth Rex @Lius Lasahido -80927 U Huntmaster Liger @Slawomir Maniak -80929 R Everquill Phoenix @JOZ -80931 U Pollywog Symbiote @Simon Dominic -80933 U Sprite Dragon @Simon Dominic -80935 R Dirge Bat @Kohei Hayama -80937 R Crystalline Giant @Kotakan -80939 C Mysterious Egg @Yoroikoji +80905 M Nethroi, Apex of Death @Torstein Nordstrand ${"flavorName": "Biollante, Plant Beast Form"} +80907 M Vadrok, Apex of Thunder @Johann Bodin ${"flavorName": "Rodan, Titan of Winged Fury"} +80909 M Brokkos, Apex of Forever @Svetlin Velinov ${"flavorName": "Bio-Quartz Spacegodzilla"} +80911 M Snapdax, Apex of the Hunt @YW Tang ${"flavorName": "King Caesar, Awoken Titan"} +80913 M Illuna, Apex of Wishes @Nicholas Gregory ${"flavorName": "Ghidorah, King of the Cosmos"} +80915 M Luminous Broodmoth @Nick Southam ${"flavorName": "Mothra, Supersonic Queen"} +80917 R Gemrazer @Sam Rowan ${"flavorName": "Anguirus, Armored Killer"} +80919 R Gyruda, Doom of Depths @Jesper Ejsing ${"flavorName": "Gigan, Cyberclaw Terror"} +80921 R Yidaro, Wandering Monster @Yigit Koroglu ${"flavorName": "Godzilla, Doom Inevitable"} +80923 U Void Beckoner @Zezhou Chen ${"flavorName": "Spacegodzilla, Death Corona"} +80925 U Titanoth Rex @Lius Lasahido ${"flavorName": "Godzilla, Primeval Champion"} +80927 U Huntmaster Liger @Slawomir Maniak ${"flavorName": "King Caesar, Ancient Guardian"} +80929 R Everquill Phoenix @JOZ ${"flavorName": "Destoroyah, Perfect Lifeform"} +80931 U Pollywog Symbiote @Simon Dominic ${"flavorName": "Babygodzilla, Ruin Reborn"} +80933 U Sprite Dragon @Simon Dominic ${"flavorName": "Dorat, the Perfect Pet"} +80935 R Dirge Bat @Kohei Hayama ${"flavorName": "Battra, Dark Destroyer"} +80937 R Crystalline Giant @Kotakan ${"flavorName": "Mechagodzilla, the Weapon"} +80939 C Mysterious Egg @Yoroikoji ${"flavorName": "Mothra's Great Cocoon"} 80941 R Colossification @Mathias Kollros 80943 R Raugrin Triome @Robbie Trevino 80945 R Indatha Triome @Robbie Trevino diff --git a/forge-gui/res/editions/MagicFest 2023.txt b/forge-gui/res/editions/MagicFest 2023.txt index 4177ec33da9..a7ef206dbfa 100644 --- a/forge-gui/res/editions/MagicFest 2023.txt +++ b/forge-gui/res/editions/MagicFest 2023.txt @@ -7,6 +7,6 @@ ScryfallCode=PF23 [cards] 1 R Gandalf, Friend of the Shire @Bakshi Productions -2 R Tranquil Thicket @Alayna Danner +2 R Tranquil Thicket @Alayna Danner ${"flavorName": "Caras Galadhon"} 3 R Reliquary Tower @Carissa Susilo 4 R TARDIS @Chris Ostrowski diff --git a/forge-gui/res/editions/MagicFest 2025.txt b/forge-gui/res/editions/MagicFest 2025.txt index b6f14376a4e..ca9b37c6ce7 100644 --- a/forge-gui/res/editions/MagicFest 2025.txt +++ b/forge-gui/res/editions/MagicFest 2025.txt @@ -11,13 +11,13 @@ ScryfallCode=PF25 2 R Ponder @Mark Zug 3 M The First Sliver @Svetlin Velinov F4 R Second City @Mark Rosewater -5 M Yoshimaru, Ever Faithful @Kazuya Takahashi +5 M Yoshimaru, Ever Faithful @Kazuya Takahashi ${"flavorName": "Torgal, Clive's Companion"} 6 M Ugin, the Spirit Dragon @Raymond Swanland 7 R Sliver Hive @Igor Kieryluk F8 R All-You-Can-Eat Buffet @Mark Rosewater 9 R Tifa Lockhart @Nijihayashi 10 R Arcane Signet @David Astruga -11 R Rograkh, Son of Rohgahh @Square Enix +11 R Rograkh, Son of Rohgahh @Square Enix ${"flavorName": "Joshua Rosfield"} 12 R Swords to Plowshares @Kodai Okamoto 13 R Lightning Bolt @Christopher Moeller 14 R Scourge of Valkas @Svetlin Velinov diff --git a/forge-gui/res/editions/Marvel Universe.txt b/forge-gui/res/editions/Marvel Universe.txt index 679e31df132..221e69700e5 100644 --- a/forge-gui/res/editions/Marvel Universe.txt +++ b/forge-gui/res/editions/Marvel Universe.txt @@ -15,7 +15,7 @@ ScryfallCode=MAR 7 M Wedding Ring @John Romita Sr. 8 M Clever Impersonator @Mark Bagley & Andrew Hennessy 9 M Counterspell @Steve Ditko -10 M Lorthos, the Tidemaker @John Romita Sr. +10 M Lorthos, the Tidemaker @John Romita Sr. ${"flavorName": "Doc Ock, Armed and Dangerous"} 11 M Mindbreak Trap @Todd McFarlane 12 M Mystic Confluence @Mike Zeck 13 M Ponder @Moebius @@ -27,9 +27,9 @@ ScryfallCode=MAR 19 M Opposition Agent @Tyler Kirkham 20 M Reanimate @Mike Zeck & Bob McLeod 21 M Saw in Half @John Romita Jr. & John Romita Sr. -22 M Skithiryx, the Blight Dragon @Ryan Stegman & JP Mayer +22 M Skithiryx, the Blight Dragon @Ryan Stegman & JP Mayer ${"flavorName": "Venom, King in Black"} 23 M Goblin Bombardment @John Romita Sr. -24 M Najeela, the Blade-Blossom @Cliff Chiang +24 M Najeela, the Blade-Blossom @Cliff Chiang ${"flavorName": "Spider-Gwen, Web-Warrior"} 25 M Relentless Assault @Federico Vicentini & Alejandro Sánchez 26 M Savage Beating @Todd McFarlane 27 M Shock @John Romita Sr. @@ -37,12 +37,12 @@ ScryfallCode=MAR 29 M Unexpected Windfall @John Romita Sr. 30 M Winds of Change @Ron Frenz & Klaus Janson 31 M Arachnogenesis @Todd McFarlane -32 M Arasta of the Endless Web @David Baldeon & Walden Wong +32 M Arasta of the Endless Web @David Baldeon & Walden Wong ${"flavorName": "Master Weaver, Web Protector"} 33 M Beast Within @Todd McFarlane 34 M Heroic Intervention @Jack Kirby & Steve Ditko 35 M Hunter's Insight @Sergio Davila & Sean Parsons 36 M Parallel Lives @Jim Cheung & Justin Ponsor 37 M Silkguard @Dave Johnson 38 M Tangle @Humberto Ramos & Edgar Delgado -39 M Alibou, Ancient Witness @Michael Turner & Mark Roslan +39 M Alibou, Ancient Witness @Michael Turner & Mark Roslan ${"flavorName": "Iron Spider, Civil Warrior"} 40 M Terminate @Mark Bagley & Randy Emberlin diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 7ebb4595d77..43c29953ace 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -194,9 +194,9 @@ TokensCode=SLD 203 R Commander's Sphere @Yosuke Ueno 204 R Darksteel Ingot @Theodoru 205 R Gilded Lotus @Mab Graves -206 R Exquisite Blood @Nils Hamm -207 R Night's Whisper @Tomas Duchek -208 R Phyrexian Tower @Nicholas Gregory +206 R Exquisite Blood @Nils Hamm ${"flavorName": "Hunger of the Ancient One"} +207 R Night's Whisper @Tomas Duchek ${"flavorName": "Nightfeeder's Visitation"} +208 R Phyrexian Tower @Nicholas Gregory ${"flavorName": "Dracula's Tomb"} 209 M Elesh Norn, Grand Cenobite @Igor Kieryluk 210 M Jin-Gitaxias, Core Augur @Eric Deschamps 211 M Sheoldred, Whispering One @Jana Schirmer & Johannes Voss @@ -427,13 +427,13 @@ TokensCode=SLD 438 R Howltooth Hollow @Shie Nanahara 439 R Spinerock Knoll @Nagano 440 R Mosswort Bridge @Maiko Aoji -441 R Wrath of God @Kevin Gnutzmans -442 R Dance of Many @Caroline Gariba -443 R Etherium Sculptor @Rudy Siswanto -444 M Grim Tutor @Roberto Gatto -445 R Triumph of the Hordes @Felipe Martini -446 R Smuggler's Copter @Brandon L. Hunt -447 M Planar Bridge @Alexandre Leoni +441 R Wrath of God @Kevin Gnutzmans ${"flavorName": "Shrinking Storm"} +442 R Dance of Many @Caroline Gariba ${"flavorName": "Dance Battle"} +443 R Etherium Sculptor @Rudy Siswanto ${"flavorName": "Supply Llama"} +444 M Grim Tutor @Roberto Gatto ${"flavorName": "Crack the Vault"} +445 R Triumph of the Hordes @Felipe Martini ${"flavorName": "Battle Royale"} +446 R Smuggler's Copter @Brandon L. Hunt ${"flavorName": "Battle Bus"} +447 M Planar Bridge @Alexandre Leoni ${"flavorName": "The Cube"} 448 R Plains @Alexander Kintner 449 R Island @Roberto Gatto 450 R Swamp @Kevin Gnutzmans @@ -464,12 +464,12 @@ TokensCode=SLD 475 R Elvish Mystic @Magali Villeneuve 476 R Forest @Magali Villeneuve 477 R Path to Exile @Riot Games -478 R Rhystic Study @Riot Games +478 R Rhystic Study @Riot Games ${"flavorName": "Unstable Harmonics"} 479 R Duress @Riot Games -480 R Seize the Day @Riot Games -481 R Krosan Grip @Riot Games +480 R Seize the Day @Riot Games ${"flavorName": "Round Two"} +481 R Krosan Grip @Riot Games ${"flavorName": "Denting Blows"} 482 R Counterflux @Riot Games -483 R Thran Dynamo @Riot Games +483 R Thran Dynamo @Riot Games ${"flavorName": "The Hexcore"} 484 R Plains @Riot Games 485 R Island @Riot Games 486 R Swamp @Riot Games @@ -653,9 +653,9 @@ TokensCode=SLD 672 R Questing Phelddagrif @Mark Rosewater 673 C Island @Daniel Warren Johnson 674 C Mountain @Daniel Warren Johnson -675 R Lightning Bolt @Jason Rainville -676 R Pyrite Spellbomb @Alexandre Leoni -677 R Command Tower @Kevin Gnutzmans +675 R Lightning Bolt @Jason Rainville ${"flavorName": "Hadoken"} +676 R Pyrite Spellbomb @Alexandre Leoni ${"flavorName": "Boogie Bomb"} +677 R Command Tower @Kevin Gnutzmans ${"flavorName": "The Spire"} 678 R Torbran, Thane of Red Fell @Wayne Reynolds 679 R Ghost Quarter @Sidharth Chaturvedi 680 R Shadowborn Apostle @AlbaBG @@ -674,8 +674,8 @@ TokensCode=SLD 693 R Sorcerous Spyglass @Cosmin Podar 694 R Tangle Wire @Cosmin Podar 695 R Reliquary Tower @Cosmin Podar -696 R Spore Frog @Riot Games -697 R Command Tower @Riot Games +696 R Spore Frog @Riot Games ${"flavorName": "Gromp"} +697 R Command Tower @Riot Games ${"flavorName": "Summoner's Rift"} 698 R Dakkon Blackblade @Richard Kane Ferguson 699 M Olivia, Mobilized for War @Livia Prima 700 M Huntmaster of the Fells @Chris Rahn @@ -701,7 +701,7 @@ TokensCode=SLD 723 R Brainstorm @John Avon 724 R Lightning Strike @Frank Frazetta 725 R Fleshbag Marauder @Kev Walker -726 R Zur the Enchanter @Chase Stone +726 R Zur the Enchanter @Chase Stone ${"flavorName": "Post the Enchanter"} 727 R Fabled Passage @Warren Mahy & Post Malone 728 R Themberchaud @Yang Luo 729 R Braid of Fire @Sam Burley @@ -711,14 +711,14 @@ TokensCode=SLD 733 R Seraph Sanctuary @Alayna Danner 734 R Gríma Wormtongue @Bakshi Productions 735 R Gaea's Blessing @Ryan Alexander Lee -736 R Colossus Hammer @Jason Kang +736 R Colossus Hammer @Jason Kang ${"flavorName": "Shovel of Decapitation"} 737 R Mountain Goat @John Darnielle 738 R Woodland Cemetery @Alexander Gering 739 R Isolated Chapel @Helge C. Balzer -740 R Colossal Dreadmaw @Dan Mumford & Thomas Roach -740★ R Colossal Dreadmaw @Dan Mumford & Thomas Roach -741 R Chaos Warp @Zack Stella -741★ R Chaos Warp @Zack Stella +740 R Colossal Dreadmaw @Dan Mumford & Thomas Roach ${"flavorName": "Brachiosaurus"} +740★ R Colossal Dreadmaw @Dan Mumford & Thomas Roach ${"flavorName": "Brachiosaurus"} +741 R Chaos Warp @Zack Stella ${"flavorName": "Chaos Theory"} +741★ R Chaos Warp @Zack Stella ${"flavorName": "Chaos Theory"} 744 R Command Tower @Serena Malyon 745 M Ajani Goldmane @Fay Dalton & Scott Okumura 745☇ M Ajani Goldmane @Fay Dalton & Scott Okumura @@ -766,7 +766,7 @@ TokensCode=SLD 790★ R Codex Shredder @AKQA 791 R Solemn Simulacrum @Dani Pendergast 791★ R Solemn Simulacrum @Dani Pendergast -792 R Command Tower @Kamila Szutenberg +792 R Command Tower @Kamila Szutenberg ${"flavorName": "Croft Manor"} 793 R Nine Lives @Fiona Hsieh 794 M Yoshimaru, Ever Faithful @Fiona Hsieh 795★ R Wastes @AKQA @@ -781,8 +781,8 @@ F798 M Discord, Lord of Disharmony @Narendra Bintara Adi 804 R Beloved Princess @Evyn Fong 805 R Elvish Mystic @Ekaterina Burmak 806 R Command Tower @Rudy Siswanto -807 M Chandra, Flamecaller @Mila Pesic -808 M Snapcaster Mage @Raita Kazama +807 M Chandra, Flamecaller @Mila Pesic ${"flavorName": "MEIKO, Explosive Entertainer"} +808 M Snapcaster Mage @Raita Kazama ${"flavorName": "Encore Electromancer"} 809 R Immerwolf @Sage Coffey 810 M Thrun, the Last Troll @Jakub Kasper & Scott Okumura 811 M Elesh Norn, Grand Cenobite @Josh Newton & Scott Okumura @@ -798,8 +798,8 @@ F798 M Discord, Lord of Disharmony @Narendra Bintara Adi 822 R Hive Mind @Apolo Cacho 823 R Chaos Warp @Jackson Epstein 824 R Evolving Wilds @Jul Quanouai -825 R Goblin Bombardment @Craig J Spearing -826 R Kezzerdrix @Bill McConkey +825 R Goblin Bombardment @Craig J Spearing ${"flavorName": "Cow-tapult"} +826 R Kezzerdrix @Bill McConkey ${"flavorName": "Killer Rabbit of Caerbannog"} 827 R Norin the Wary @Jarel Threat 827☇ R Norin the Wary @Jarel Threat 828 R Keen Duelist @Thanh Tuấn @@ -839,9 +839,9 @@ F798 M Discord, Lord of Disharmony @Narendra Bintara Adi 867 R Nature's Lore @Nicholas Elias 868 R Harmless Offering @Narendra Bintara Adi F869 R Blacker Lotus @Scott Okumura -870 R Abundant Growth @Ryan Yee -871 R Soul-Guide Lantern @Tyler Walpole -872 R Yargle and Multani @Warren Mahy +870 R Abundant Growth @Ryan Yee ${"flavorName": "Abundant Groot"} +871 R Soul-Guide Lantern @Tyler Walpole ${"flavorName": "Ghost Trap"} +872 R Yargle and Multani @Warren Mahy ${"flavorName": "Slimer and Stay Puft"} 873 R Dark Deal @Tyler Jacobson 874 R Archivist of Oghma @Cory Trego-Erdner 875 M Battle Angels of Tyr @Raymond Swanland @@ -849,7 +849,7 @@ F869 R Blacker Lotus @Scott Okumura 877 R Druid of Purification @Alexander Mokhov 878 R Prosperous Innkeeper @Tyler Jacobson 879 M Minsc & Boo, Timeless Heroes @Justin Gerard -880 R Stuffy Doll @Robin Olausson +880 R Stuffy Doll @Robin Olausson ${"flavorName": "Patchwork Chucky"} 881 R Silence @Desmuncubic 882 R Winds of Abandon @Desmuncubic 883 R Culling the Weak @Desmuncubic @@ -873,7 +873,7 @@ F869 R Blacker Lotus @Scott Okumura 905 R Cryptic Command @Alexander Khabbazi 906 R Ignoble Hierarch @John F. Malta 907 R Seedborn Muse @Princess Hidir -908 R Arcane Signet @Alexander Forssberg +908 R Arcane Signet @Alexander Forssberg ${"flavorName": "Earth's Mightiest Emblem"} 909 R Gilded Lotus @Kasia 'Kafis' Zielińska 910 R Sol Ring @6VCR 912 R Sol Ring @Bill McConkey @@ -900,22 +900,22 @@ F869 R Blacker Lotus @Scott Okumura 1022 R Solve the Equation @Ryo Kamei 1023 R Praetor's Grasp @Ryo Kamei 1024 R Veil of Summer @Ryo Kamei -1025 R Merciless Executioner @Paolo Parente +1025 R Merciless Executioner @Paolo Parente ${"flavorName": "Ork Kommando"} 1026 R Aggravated Assault @Helge C. Balzer -1027 R Krenko, Tin Street Kingpin @Paolo Parente -1028 M Zurgo Helmsmasher @Johan Grenier -1029 M Skysovereign, Consul Flagship @Evan Shipard +1027 R Krenko, Tin Street Kingpin @Paolo Parente ${"flavorName": "Makari the Lucky Grot"} +1028 M Zurgo Helmsmasher @Johan Grenier ${"flavorName": "Ghazghkull, Prophet of the Waaagh!"} +1029 M Skysovereign, Consul Flagship @Evan Shipard ${"flavorName": "Da Vulcha"} 1030 R Blind Obedience @Antonio José Manzanedo -1031 R Danitha Capashen, Paragon @Lie Setiawan -1032 M Najeela, the Blade-Blossom @Antonio José Manzanedo -1033 M Scourge of the Throne @Billy Christian -1034 R Loxodon Warhammer @Lie Setiawan -1035 R Approach of the Second Sun @Filipe Pagliuso -1036 R Rewind @Helge C. Balzer -1037 R Bone Splinters @Fajareka Setiawan -1038 R Fling @Johan Grenier -1039 R Defense of the Heart @Fajareka Setiawan -1040 R Fellwar Stone @Johan Grenier +1031 R Danitha Capashen, Paragon @Lie Setiawan ${"flavorName": "Neave Blacktalon"} +1032 M Najeela, the Blade-Blossom @Antonio José Manzanedo ${"flavorName": "Archaeon the Everchosen"} +1033 M Scourge of the Throne @Billy Christian ${"flavorName": "Stardrake"} +1034 R Loxodon Warhammer @Lie Setiawan ${"flavorName": "Ghal Maraz, the Great Shatterer"} +1035 R Approach of the Second Sun @Filipe Pagliuso ${"flavorName": "Touchdown!"} +1036 R Rewind @Helge C. Balzer ${"flavorName": "Re-Roll"} +1037 R Bone Splinters @Fajareka Setiawan ${"flavorName": "Both Down"} +1038 R Fling @Johan Grenier ${"flavorName": "Throw Team-Mate"} +1039 R Defense of the Heart @Fajareka Setiawan ${"flavorName": "Perfect Defense"} +1040 R Fellwar Stone @Johan Grenier ${"flavorName": "The Ball"} 1041 R Narset, Parter of Veils @Alex Horley-Orlandelli 1042 R Nissa, Who Shakes the World @Scott M. Fischer 1043 M Tezzeret, Agent of Bolas @Kev Walker @@ -954,12 +954,12 @@ F869 R Blacker Lotus @Scott Okumura 1076 R Goblin Settler @Roman Klonek 1077 R Collector Ouphe @Roman Klonek 1078 M Vengevine @Roman Klonek -1079 M Blightsteel Colossus @Volta Creation -1079☇ M Blightsteel Colossus @Volta Creation -1080 R Doubling Cube @Volta Creation -1080☇ R Doubling Cube @Volta Creation -1081 M Darksteel Colossus @Volta Creation -1081☇ M Darksteel Colossus @Volta Creation +1079 M Blightsteel Colossus @Volta Creation ${"flavorName": "Megatron"} +1079☇ M Blightsteel Colossus @Volta Creation ${"flavorName": "Megatron"} +1080 R Doubling Cube @Volta Creation ${"flavorName": "The AllSpark"} +1080☇ R Doubling Cube @Volta Creation ${"flavorName": "The AllSpark"} +1081 M Darksteel Colossus @Volta Creation ${"flavorName": "Optimus Prime"} +1081☇ M Darksteel Colossus @Volta Creation ${"flavorName": "Optimus Prime"} 1082 R True Conviction @Volta Creation 1083 R Dramatic Reversal @Volta Creation 1084 R Fabricate @Volta Creation @@ -1062,9 +1062,9 @@ F869 R Blacker Lotus @Scott Okumura 1183 R Descendants' Path @Tatamepi 1184 M Lord Windgrace @Tatamepi 1185 R Violent Outburst @Tatamepi -1186 R K'rrik, Son of Yawgmoth @Darren Tan -1187 R Bolas's Citadel @Lorenzo Mastroianni -1188 R Leshrac's Sigil @Livia Prima +1186 R K'rrik, Son of Yawgmoth @Darren Tan ${"flavorName": "Post, Son of Rich"} +1187 R Bolas's Citadel @Lorenzo Mastroianni ${"flavorName": "Post's Citadel"} +1188 R Leshrac's Sigil @Livia Prima ${"flavorName": "Post's Sigil"} 1189 R Jet Medallion @Randy Vargas 1190 R Plains @Mark Poole & Post Malone 1191 R Island @Fred Fields & Post Malone @@ -1230,11 +1230,11 @@ F869 R Blacker Lotus @Scott Okumura 1349 C Plains @Rob Alexander 1350 C Plains @Rob Alexander 1351 C Plains @Rob Alexander -1352 R Puresteel Paladin @Fay Dalton -1353 R Vanquish the Horde @Josh Newton -1354 R Zombie Apocalypse @Warren Mahy -1355 M Varina, Lich Queen @Fay Dalton -1356 R Field of the Dead @Warren Mahy +1352 R Puresteel Paladin @Fay Dalton ${"flavorName": "Ash, Destined Survivor"} +1353 R Vanquish the Horde @Josh Newton ${"flavorName": "Destroy the Dead"} +1354 R Zombie Apocalypse @Warren Mahy ${"flavorName": "Knowby's Incantation"} +1355 M Varina, Lich Queen @Fay Dalton ${"flavorName": "Linda, Kandarian Queen"} +1356 R Field of the Dead @Warren Mahy ${"flavorName": "Cabin of the Dead"} 1358 R Mountain @Darrell Riche 1359 R Mountain @Victor Adame Minguez 1360 R Mountain @Mark Zug @@ -1266,24 +1266,24 @@ F869 R Blacker Lotus @Scott Okumura 1386 R Forest @Gary Baseman 1387 M Gisela, the Broken Blade @Scott M. Fischer 1388 R Bruna, the Fading Light @Scott M. Fischer -1389 R Etali, Primal Storm @Dan Mumford & Thomas Roach -1389★ R Etali, Primal Storm @Dan Mumford & Thomas Roach -1390 R Rampaging Ferocidon @Dan Mumford & Thomas Roach -1390★ R Rampaging Ferocidon @Dan Mumford & Thomas Roach -1391 M Polyraptor @Dan Mumford & Thomas Roach -1391★ M Polyraptor @Dan Mumford & Thomas Roach -1392 R Wayward Swordtooth @Dan Mumford & Thomas Roach -1392★ R Wayward Swordtooth @Dan Mumford & Thomas Roach -1393 R Regisaur Alpha @Dan Mumford & Thomas Roach -1393★ R Regisaur Alpha @Dan Mumford & Thomas Roach -1394 R Laboratory Maniac @Narendra Bintara Adi -1394★ R Laboratory Maniac @Narendra Bintara Adi -1395 R Tasha's Hideous Laughter @Laurel Austin -1395★ R Tasha's Hideous Laughter @Laurel Austin -1396 R Tasigur, the Golden Fang @Bartek Fedyczak -1396★ R Tasigur, the Golden Fang @Bartek Fedyczak -1397 M Atla Palani, Nest Tender @Brian Valeza -1397★ M Atla Palani, Nest Tender @Brian Valeza +1389 R Etali, Primal Storm @Dan Mumford & Thomas Roach ${"flavorName": "Tyrannosaurus Rex"} +1389★ R Etali, Primal Storm @Dan Mumford & Thomas Roach ${"flavorName": "Tyrannosaurus Rex"} +1390 R Rampaging Ferocidon @Dan Mumford & Thomas Roach ${"flavorName": "Velociraptor"} +1390★ R Rampaging Ferocidon @Dan Mumford & Thomas Roach ${"flavorName": "Velociraptor"} +1391 M Polyraptor @Dan Mumford & Thomas Roach ${"flavorName": "Indominus Rex"} +1391★ M Polyraptor @Dan Mumford & Thomas Roach ${"flavorName": "Indominus Rex"} +1392 R Wayward Swordtooth @Dan Mumford & Thomas Roach ${"flavorName": "Triceratops"} +1392★ R Wayward Swordtooth @Dan Mumford & Thomas Roach ${"flavorName": "Triceratops"} +1393 R Regisaur Alpha @Dan Mumford & Thomas Roach ${"flavorName": "Spinosaurus"} +1393★ R Regisaur Alpha @Dan Mumford & Thomas Roach ${"flavorName": "Spinosaurus"} +1394 R Laboratory Maniac @Narendra Bintara Adi ${"flavorName": "Chaotic Chaotician"} +1394★ R Laboratory Maniac @Narendra Bintara Adi ${"flavorName": "Chaotic Chaotician"} +1395 R Tasha's Hideous Laughter @Laurel Austin ${"flavorName": "Malcolm's Mercurial Mirth"} +1395★ R Tasha's Hideous Laughter @Laurel Austin ${"flavorName": "Malcolm's Mercurial Mirth"} +1396 R Tasigur, the Golden Fang @Bartek Fedyczak ${"flavorName": "Ian, Convalescent Charmer"} +1396★ R Tasigur, the Golden Fang @Bartek Fedyczak ${"flavorName": "Ian, Convalescent Charmer"} +1397 M Atla Palani, Nest Tender @Brian Valeza ${"flavorName": "Dr. Ian Malcolm"} +1397★ M Atla Palani, Nest Tender @Brian Valeza ${"flavorName": "Dr. Ian Malcolm"} 1399 R Plains @JungShan 1400 R Island @JungShan 1401 R Swamp @JungShan @@ -1333,15 +1333,15 @@ F869 R Blacker Lotus @Scott Okumura 1434★ M Mycosynth Lattice @Signalnoise 1435 R Mycosynth Wellspring @Signalnoise 1435★ R Mycosynth Wellspring @Signalnoise -1444 R Sisay, Weatherlight Captain @Marta Nael +1444 R Sisay, Weatherlight Captain @Marta Nael ${"flavorName": "Buttercup, Provincial Princess"} 1445 R Silence @Sam Hogg 1446 R Battle of Wits @Anna Pavleeva -1447 R Baral, Chief of Compliance @Dmitry Burmak -1448 R Pack Rat @Néstor Ossandón Leal -1449 R Fynn, the Fangbearer @Magali Villeneuve -1450 R Brion Stoutarm @Lius Lasahido -1451 M Samut, Voice of Dissent @Ekaterina Burmak -1452 R Marchesa, the Black Rose @Andreas Zafiratos +1447 R Baral, Chief of Compliance @Dmitry Burmak ${"flavorName": "Vizzini, Criminal Mastermind"} +1448 R Pack Rat @Néstor Ossandón Leal ${"flavorName": "Rodents of Unusual Size"} +1449 R Fynn, the Fangbearer @Magali Villeneuve ${"flavorName": "Westley, Dread Pirate Roberts"} +1450 R Brion Stoutarm @Lius Lasahido ${"flavorName": "Fezzik, Rhyming Giant"} +1451 M Samut, Voice of Dissent @Ekaterina Burmak ${"flavorName": "Inigo, Avenging Swordsman"} +1452 R Marchesa, the Black Rose @Andreas Zafiratos ${"flavorName": "Miracle Max, Unemployed"} 1453 M Ajani Goldmane @Fay Dalton & Scott Okumura 1453☇ M Ajani Goldmane @Fay Dalton & Scott Okumura 1454 M Jace Beleren @Fay Dalton & Scott Okumura @@ -1392,8 +1392,8 @@ F869 R Blacker Lotus @Scott Okumura 1480 R Swamp @Scott Balmer 1481 R Mountain @Scott Balmer 1482 R Forest @Scott Balmer -1483 R Grand Arbiter Augustin IV @AKQA -1483★ R Grand Arbiter Augustin IV @AKQA +1483 R Grand Arbiter Augustin IV @AKQA ${"flavorName": "Vault Boy, Cap Collector"} +1483★ R Grand Arbiter Augustin IV @AKQA ${"flavorName": "Vault Boy, Cap Collector"} 1484 R Sphere of Resistance @AKQA 1484★ R Sphere of Resistance @AKQA 1485 M Trinisphere @AKQA @@ -1429,11 +1429,11 @@ F869 R Blacker Lotus @Scott Okumura 1500 R Time Stop @Micha Huigen 1500★ R Time Stop @Micha Huigen 1501 M Lara Croft, Tomb Raider @Greg Staples -1502 R Search for Azcanta @Jonas De Ro -1503 R Anger of the Gods @Eliz Roxs -1504 R Bow of Nylea @Eliz Roxs -1505 R Shadowspear @Bartek Fedyczak -1506 R Academy Ruins @Eliz Roxs +1502 R Search for Azcanta @Jonas De Ro ${"flavorName": "Heart of the Explorer // The Lost Valley"} +1503 R Anger of the Gods @Eliz Roxs ${"flavorName": "Storms of Yamatai"} +1504 R Bow of Nylea @Eliz Roxs ${"flavorName": "The Grim Whisper"} +1505 R Shadowspear @Bartek Fedyczak ${"flavorName": "Totec's Spear"} +1506 R Academy Ruins @Eliz Roxs ${"flavorName": "Kitezh, Sunken City"} 1508 M Rin and Seri, Inseparable @Andrea Radeck 1508☇ M Rin and Seri, Inseparable @Andrea Radeck 1509 M Jetmir, Nexus of Revels @Nicole Gustafsson @@ -1508,11 +1508,11 @@ F1540 M Rainbow Dash @John Thacker 1555☇ M Jetmir, Nexus of Revels @Nicole Gustafsson 1556 R Jinnie Fay, Jetmir's Second @Jack Hughes 1556☇ R Jinnie Fay, Jetmir's Second @Jack Hughes -1557 M Najeela, the Blade-Blossom @Clint Cearley & Diana Cearley -1558 M Kelsien, the Plague @Dan Watson -1559 M Queen Marchesa @Narendra Bintara Adi -1560 R Ramses, Assassin Lord @Zezhou Chen -1561 M Admiral Beckett Brass @Fajareka Setiawan +1557 M Najeela, the Blade-Blossom @Clint Cearley & Diana Cearley ${"flavorName": "Eivor, Raven Clan Champion"} +1558 M Kelsien, the Plague @Dan Watson ${"flavorName": "Altaïr, Brotherhood Mentor"} +1559 M Queen Marchesa @Narendra Bintara Adi ${"flavorName": "Amunet, Tyrants' End"} +1560 R Ramses, Assassin Lord @Zezhou Chen ${"flavorName": "Basim, Master Assassin"} +1561 M Admiral Beckett Brass @Fajareka Setiawan ${"flavorName": "Edward, Jackdaw Captain"} 1562 M Skithiryx, the Blight Dragon @Abigail Larson 1563 M Kaalia of the Vast @Abigail Larson 1564 R Angel of Despair @Abigail Larson @@ -1545,29 +1545,29 @@ F1540 M Rainbow Dash @John Thacker 1582 R The Celestial Toymaker @Jake Murray 1583 R The Fourteenth Doctor @Justyna Dura 1584 R The Fifteenth Doctor @Kieran Yanner -1585 M Elspeth Tirel @Raita Kazama -1586 R Giada, Font of Hope @Miguel Mercado +1585 M Elspeth Tirel @Raita Kazama ${"flavorName": "Miku, Divine Diva"} +1586 R Giada, Font of Hope @Miguel Mercado ${"flavorName": "Miku, Font of Pop"} 1587 R Shelter @Mandy Jurgens 1587★ R Shelter @Mandy Jurgens 1588 R Youthful Valkyrie @Anna Podedworna 1589 R Counterspell @Julia Metzger -1590 M Jace, Unraveler of Secrets @Naomi Baker +1590 M Jace, Unraveler of Secrets @Naomi Baker ${"flavorName": "KAITO, Mysterious Maestro"} 1591 R Swan Song @Kelogsloops 1592 R Diabolic Tutor @Leonardo Santanna -1593 M Liliana of the Dark Realms @Livia Prima -1594 R Chandra's Ignition @Justyna Dura +1593 M Liliana of the Dark Realms @Livia Prima ${"flavorName": "Luka, the Traveling Sound"} +1594 R Chandra's Ignition @Justyna Dura ${"flavorName": "Miku's Spark"} 1594★ R Chandra's Ignition @Justyna Dura 1595 R Chord of Calling @Kekai Kotaki 1596 R Harmonize @Syutsuri 1596★ R Harmonize @Syutsuri -1597 R Azusa, Lost but Seeking @Jehan Choo -1597★ R Azusa, Lost but Seeking @Jehan Choo -1598 M Freyalise, Llanowar's Fury @Jesper Ejsing -1599 M Child of Alara @Aya Kato -1600 M The Royal Scions @Clint Cearley -1601 R Brago, King Eternal @Micah Epstein -1602 R Feather, the Redeemed @Yuko Shimizu -1602★ R Feather, the Redeemed @Yuko Shimizu +1597 R Azusa, Lost but Seeking @Jehan Choo ${"flavorName": "Miku, Lost but Singing"} +1597★ R Azusa, Lost but Seeking @Jehan Choo ${"flavorName": "Miku, Lost but Singing"} +1598 M Freyalise, Llanowar's Fury @Jesper Ejsing ${"flavorName": "Miku, Voice of Power"} +1599 M Child of Alara @Aya Kato ${"flavorName": "Miku, Child of Song"} +1600 M The Royal Scions @Clint Cearley ${"flavorName": "Len and Rin, Harmony Incarnate"} +1601 R Brago, King Eternal @Micah Epstein ${"flavorName": "Miku, Queen Electric"} +1602 R Feather, the Redeemed @Yuko Shimizu ${"flavorName": "Miku, the Renowned"} +1602★ R Feather, the Redeemed @Yuko Shimizu ${"flavorName": "Miku, the Renowned"} 1603 R Song of Creation @Penekor 1604 R Sol Ring @Narendra Bintara Adi 1605 R Inspiring Vantage @Dani Pendergast @@ -1657,15 +1657,15 @@ F1540 M Rainbow Dash @John Thacker 1669 R Psychic Corrosion @Peach Momoko 1670 R Visions of Beyond @Peach Momoko 1671 R Time Sieve @Peach Momoko -1672 R Prodigal Sorcerer @Andreas Zafiratos -1673 R Buried Alive @Filipe Pagliuso -1674 R Dismember @Greg Staples -1675 R Birds of Paradise @Stephen Andrade -1675☇ R Birds of Paradise @Stephen Andrade -1676 R Three Visits @Chris Rallis -1678 R Door to Nothingness @Sam Hogg -1679 R Ashnod's Altar @Dmitry Burmak -1680 M Dark Depths @Craig J Spearing +1672 R Prodigal Sorcerer @Andreas Zafiratos ${"flavorName": "Tim the Enchanter"} +1673 R Buried Alive @Filipe Pagliuso ${"flavorName": "Bring Out Your Dead!"} +1674 R Dismember @Greg Staples ${"flavorName": "'Tis But a Scratch!"} +1675 R Birds of Paradise @Stephen Andrade ${"flavorName": "African Swallow"} +1675☇ R Birds of Paradise @Stephen Andrade ${"flavorName": "European Swallow"} +1676 R Three Visits @Chris Rallis ${"flavorName": "We Want . . . A SHRUBBERY!"} +1678 R Door to Nothingness @Sam Hogg ${"flavorName": "The Bridge of Death"} +1679 R Ashnod's Altar @Dmitry Burmak ${"flavorName": "Sir Bedivere's Scales"} +1680 M Dark Depths @Craig J Spearing ${"flavorName": "Castle of Aaargh"} 1682 R Reya Dawnbringer @Uta Natsume 1683 M Orvar, the All-Form @Uta Natsume 1684 M Drana, the Last Bloodchief @Uta Natsume @@ -1679,7 +1679,7 @@ F1540 M Rainbow Dash @John Thacker 1692 R Eladamri's Vineyard @Alayna Danner 1693 R Greater Good @Johannes Voss 1694 R Inkshield @Donato Giancola -1695 M Ruhan of the Fomori @Livia Prima +1695 M Ruhan of the Fomori @Livia Prima ${"flavorName": "Sheldon, the Commander"} 1696 R Sol Ring @Lindsey Look 1697 R Command Tower @Titus Lunter 1698 M Sorin Markov @Leonardo Santanna @@ -1711,10 +1711,10 @@ F1540 M Rainbow Dash @John Thacker 1724 R Realmwalker @Luke Pearson 1725 R Solemn Simulacrum @Luke Pearson 1726 M Captain America, First Avenger @Ryan Pancoast -1727 R Sigarda's Aid @Howard Lyon +1727 R Sigarda's Aid @Howard Lyon ${"flavorName": "Captain America's Aid"} 1728 R Flawless Maneuver @Chris Rahn 1729 M In the Trenches @Livia Prima -1730 M Sword of War and Peace @Anthony Devine +1730 M Sword of War and Peace @Anthony Devine ${"flavorName": "Shield of War and Peace"} 1731 M Iron Man, Titan of Innovation @Justyna Dura 1732 R Galvanic Blast @Bud Cook 1733 M Commander's Plate @Marco Teixeira @@ -1724,17 +1724,17 @@ F1540 M Rainbow Dash @John Thacker 1738 M Berserk @Alexander Mokhov 1739 R Rite of Passage @Eliz Roxs 1740 R Rhythm of the Wild @Anna Podedworna -1741 R The Ozolith @Robin Olausson +1741 R The Ozolith @Robin Olausson ${"flavorName": "Adamantium Bonding Tank"} 1742 M Storm, Force of Nature @Magali Villeneuve 1743 R Lightning Bolt @Francisco Miyara -1744 R Jeska's Will @Miguel Mercado +1744 R Jeska's Will @Miguel Mercado ${"flavorName": "Storm's Will"} 1745 R Ice Storm @Kevin Sidharta -1746 R Manamorphose @Pauline Voss +1746 R Manamorphose @Pauline Voss ${"flavorName": "Ororo Borealis"} 1747 M Black Panther, Wakandan King @Tyler Jacobson 1748 R Secure the Wastes @Ernanda Souza -1749 R Primal Vigor @Swayart +1749 R Primal Vigor @Swayart ${"flavorName": "Bast's Blessing"} 1750 R Heroic Intervention @Jake Murray -1751 R Karn's Bastion @Salvatorre Zee Yazzie +1751 R Karn's Bastion @Salvatorre Zee Yazzie ${"flavorName": "Wakandan Skyscraper"} 1753 M Deadpool, Trading Card @Justine Cruz 1754 R Deadly Rollick @Kevin Sidharta 1755 R Saw in Half @Slawomir Maniak @@ -1751,14 +1751,14 @@ F1540 M Rainbow Dash @John Thacker 1766 M Mazirek, Kraul Death Priest @Ed Repka 1767 M Uril, the Miststalker @Ed Repka 1768 R Careful Study @Tyler Walpole -1769 M Living End @Tyler Walpole -1770 R Eladamri's Call @Tyler Walpole -1771 R Boros Charm @Tyler Walpole -1772 R Unlicensed Hearse @Tyler Walpole -1773 R The Mimeoplasm @Leonardo Santanna -1774 R Trickbind @Jason Rainville -1775 R Windfall @Simon Dominic -1776 R Incarnation Technique @Bill McConkey +1769 M Living End @Tyler Walpole ${"flavorName": "Total Containment Failure"} +1770 R Eladamri's Call @Tyler Walpole ${"flavorName": "Answer the Call"} +1771 R Boros Charm @Tyler Walpole ${"flavorName": "Ghostbuster's Patch"} +1772 R Unlicensed Hearse @Tyler Walpole ${"flavorName": "Ecto-1"} +1773 R The Mimeoplasm @Leonardo Santanna ${"flavorName": "Slimer, Voracious Apparition"} +1774 R Trickbind @Jason Rainville ${"flavorName": "Slimed"} +1775 R Windfall @Simon Dominic ${"flavorName": "Slimer's Feast"} +1776 R Incarnation Technique @Bill McConkey ${"flavorName": "Escape Containment"} 1777 R Pernicious Deed @Clint Cearley 1778 R Fell the Mighty @Greg Bell 1779 R Faithless Looting @Dave Trampier @@ -1768,17 +1768,17 @@ F1540 M Rainbow Dash @John Thacker 1783 R Ponder @Wayne Reynolds 1784 M Acererak the Archlich @Tyler Jacobson 1785 M Xanathar, Guild Kingpin @Ricardo Cavolo -1786 R Bribery @Natalie Andrewson -1787 R Stifle @Doodleskelly -1788 R Delay @Jordan Speer -1789 M Blood Money @Ricardo Diseño -1790 R Drown in the Loch @Wojtek Łebski +1786 R Bribery @Natalie Andrewson ${"flavorName": "Beholder's Charm Ray"} +1787 R Stifle @Doodleskelly ${"flavorName": "Beholder's Sleep Ray"} +1788 R Delay @Jordan Speer ${"flavorName": "Beholder's Slowing Ray"} +1789 M Blood Money @Ricardo Diseño ${"flavorName": "Beholder's Petrification Ray"} +1790 R Drown in the Loch @Wojtek Łebski ${"flavorName": "Beholder's Fear Ray"} 1791 M Karazikar, the Eye Tyrant @Skinner -1792 R Snuff Out @Ed Repka -1793 R Defile @Cabrol -1794 R Oubliette @Will Sweeney -1795 R Fling @Jon Vermilyea -1796 R Fire Covenant @Dan Mumford +1792 R Snuff Out @Ed Repka ${"flavorName": "Beholder's Death Ray"} +1793 R Defile @Cabrol ${"flavorName": "Beholder's Enervation Ray"} +1794 R Oubliette @Will Sweeney ${"flavorName": "Beholder's Paralyzing Ray"} +1795 R Fling @Jon Vermilyea ${"flavorName": "Beholder's Telekinetic Ray"} +1796 R Fire Covenant @Dan Mumford ${"flavorName": "Beholder's Disintegration Ray"} 1797 R Astarion, the Decadent @Nereida 1798 R Exquisite Blood @Justyna Dura 1799 R Sanguine Bond @Livia Prima @@ -1789,12 +1789,12 @@ F1540 M Rainbow Dash @John Thacker 1804 R Stranglehold @Bartek Fedyczak 1805 R Thrill of Possibility @Johannes Voss 1806 R Dolmen Gate @Justyna Dura -1807 M Kardur, Doomscourge @Tomasz Zarucki & Thomas Roach -1807☇ M Kardur, Doomscourge @Tomasz Zarucki & Thomas Roach -1808 R Phyrexian Reclamation @Justine Cruz -1809 R Varragoth, Bloodsky Sire @Domenico Cava -1810 R Twinflame @Sean Vo -1811 R Genesis Chamber @Eddie Mendoza +1807 M Kardur, Doomscourge @Tomasz Zarucki & Thomas Roach ${"flavorName": "Chucky"} +1807☇ M Kardur, Doomscourge @Tomasz Zarucki & Thomas Roach ${"flavorName": "Chucky"} +1808 R Phyrexian Reclamation @Justine Cruz ${"flavorName": "Sorry, Jack . . . Chucky's Back"} +1809 R Varragoth, Bloodsky Sire @Domenico Cava ${"flavorName": "Tiffany, Bride of Chucky"} +1810 R Twinflame @Sean Vo ${"flavorName": "Friends to the End"} +1811 R Genesis Chamber @Eddie Mendoza ${"flavorName": "Play Pals Factory"} 1816 R Silence @Desmuncubic 1817 R Winds of Abandon @Desmuncubic 1818 R Culling the Weak @Desmuncubic @@ -1835,21 +1835,21 @@ F1540 M Rainbow Dash @John Thacker 1855 R Northern Paladin @Kieran Yanner 1856 M Demonic Tutor @Kieran Yanner 1857 R Lord of the Pit @Kieran Yanner -1858 R Day of Judgment @Néstor Ossandón Leal -1859 R Temporal Extortion @Aurore Folny -1860 R Toxic Deluge @Evyn Fong -1861 R Praetor's Grasp @Néstor Ossandón Leal -1862 R Star of Extinction @Joshua Raphael -1863 R Staff of the Storyteller @Luisa J. Preissler -1864 R Blade of Selves @Joshua Raphael -1865 R Umezawa's Jitte @Magali Villeneuve -1866 R Colossus Hammer @Marie Magny -1867 M Sword of Truth and Justice @Yongjae Choi -1868 R Prismatic Ending @Anna Steinbauer -1869 R Cyclonic Rift @Kasia 'Kafis' Zielińska -1870 R Damn @Magali Villeneuve -1871 R Lightning Bolt @Evyn Fong -1872 R Heroic Intervention @Justyna Dura +1858 R Day of Judgment @Néstor Ossandón Leal ${"flavorName": "Spira's Punishment"} +1859 R Temporal Extortion @Aurore Folny ${"flavorName": "Absorb into Time"} +1860 R Toxic Deluge @Evyn Fong ${"flavorName": "Merciless Poisoning"} +1861 R Praetor's Grasp @Néstor Ossandón Leal ${"flavorName": "Unseat the Usurper"} +1862 R Star of Extinction @Joshua Raphael ${"flavorName": "Meteorfall"} +1863 R Staff of the Storyteller @Luisa J. Preissler ${"flavorName": "Yuna's Sending Staff"} +1864 R Blade of Selves @Joshua Raphael ${"flavorName": "Clive's Invictus Blade"} +1865 R Umezawa's Jitte @Magali Villeneuve ${"flavorName": "Cloud's Buster Sword"} +1866 R Colossus Hammer @Marie Magny ${"flavorName": "Gaia's Dark Hammer"} +1867 M Sword of Truth and Justice @Yongjae Choi ${"flavorName": "Tidus's Brotherhood Sword"} +1868 R Prismatic Ending @Anna Steinbauer ${"flavorName": "Yuna's Holy Magic"} +1869 R Cyclonic Rift @Kasia 'Kafis' Zielińska ${"flavorName": "Hope's Aero Magic"} +1870 R Damn @Magali Villeneuve ${"flavorName": "Noctis's Death Magic"} +1871 R Lightning Bolt @Evyn Fong ${"flavorName": "Vivi's Thunder Magic"} +1872 R Heroic Intervention @Justyna Dura ${"flavorName": "Aerith's Curaga Magic"} 1873 M Aesi, Tyrant of Gyre Strait @Stephen Andrade & Scott Okumura 1873☇ M Aesi, Tyrant of Gyre Strait @Stephen Andrade & Scott Okumura 1874 M Anje Falkenrath @Stephen Andrade & Scott Okumura @@ -1897,12 +1897,12 @@ F1540 M Rainbow Dash @John Thacker 1923 R Titanic Ultimatum @Narendra Bintara Adi 1924 R Arcane Signet @Nana Qi 1925 R Basilisk Collar @Rhonda Libbey -1926 R Skrelv, Defector Mite @Caleb Meurer -1927 R Charix, the Raging Isle @Gregg Schigiel -1928 R Grazilaxx, Illithid Scholar @Gregg Schigiel -1929 M Toxrill, the Corrosive @Gregg Schigiel -1930 M Toski, Bearer of Secrets @Caleb Meurer -1931 R Barktooth Warbeard @Caleb Meurer +1926 R Skrelv, Defector Mite @Caleb Meurer ${"flavorName": "Plankton, Tiny Tyrant"} +1927 R Charix, the Raging Isle @Gregg Schigiel ${"flavorName": "Mr. Krabs, Penny Pincher"} +1928 R Grazilaxx, Illithid Scholar @Gregg Schigiel ${"flavorName": "Squidward, Sarcastic Snob"} +1929 M Toxrill, the Corrosive @Gregg Schigiel ${"flavorName": "Gary, the Snail"} +1930 M Toski, Bearer of Secrets @Caleb Meurer ${"flavorName": "Sandy Cheeks, Martial Astronaut"} +1931 R Barktooth Warbeard @Caleb Meurer ${"flavorName": "Patrick Star"} 1932 M Jodah, the Unifier @Caleb Meurer ${"flavorName": "SpongeBob SquarePants"} 1933 R Counterspell @Tyler Walpole 1934 R Daze @Tyler Walpole @@ -2022,12 +2022,12 @@ F1540 M Rainbow Dash @John Thacker 2054 R Lava Spike @K Wroten 2055 R Rift Bolt @Deb JJ Lee 2056 R Skewer the Critics @Yuko Shimizu -2057 R Tireless Provisioner @Josu Hernaiz -2058 R Sylvan Library @Мónica Paola Rodríguez -2059 M Ancient Greenwarden @Ernanda Souza -2060 R Expressive Iteration @LeDania -2061 M Xenagos, God of Revels @Alexis Ziritt -2062 R Lightning Greaves @Izzy +2057 R Tireless Provisioner @Josu Hernaiz ${"flavorName": "La abuela, siempre generosa"} +2058 R Sylvan Library @Мónica Paola Rodríguez ${"flavorName": "La abundancia de Yucahú"} +2059 M Ancient Greenwarden @Ernanda Souza ${"flavorName": "Pastor da Selva"} +2060 R Expressive Iteration @LeDania ${"flavorName": "La danza del pueblo"} +2061 M Xenagos, God of Revels @Alexis Ziritt ${"flavorName": "La Madre Tierra"} +2062 R Lightning Greaves @Izzy ${"flavorName": "Chancla relámpagos"} 2063 R Sol Ring @Izzy 2066 R Counterbalance @Diego Andrade 2067 R Gitaxian Probe @Pedro Correa @@ -2057,12 +2057,12 @@ F1540 M Rainbow Dash @John Thacker 2091 R Deadly Dispute @Evan Stanley 2092 R Unexpected Windfall @Nathalie Fourdraine 2093 R Sol Ring @Evan Stanley -2095 R The Reaver Cleaver @Jack Teagle -2096 R Swiftfoot Boots @Mike Burns -2097 R Myr Battlesphere @Sylvain Sarrailh -2098 R Hammer of Nazahn @Tyler Walpole -2099 R Lightning Greaves @Evan Stanley -2100 M Weatherlight @Tracy Yardley +2095 R The Reaver Cleaver @Jack Teagle ${"flavorName": "Knuckles's Gloves"} +2096 R Swiftfoot Boots @Mike Burns ${"flavorName": "Air Shoes"} +2097 R Myr Battlesphere @Sylvain Sarrailh ${"flavorName": "Egg Hammer"} +2098 R Hammer of Nazahn @Tyler Walpole ${"flavorName": "Piko Piko Hammer"} +2099 R Lightning Greaves @Evan Stanley ${"flavorName": "Power Sneakers"} +2100 M Weatherlight @Tracy Yardley ${"flavorName": "Tornado, Sonic's Biplane"} 2102 R Ethersworn Canonist @Deathburger 2103 R Goblin Engineer @Deathburger 2104 R Dance of the Manse @Deathburger @@ -2100,25 +2100,25 @@ F1540 M Rainbow Dash @John Thacker 2162 C Remote Isle @Ciruelo 2163 C The Surgical Bay @Sarah Finnigan 2164 U Svyelunite Temple @Liz Danforth -2165 M Heliod, Sun-Crowned @Magali Villeneuve -2166 R Steelshaper's Gift @Greg Staples +2165 M Heliod, Sun-Crowned @Magali Villeneuve ${"flavorName": "Dwight Schrute, Hay King"} +2166 R Steelshaper's Gift @Greg Staples ${"flavorName": "Dwight's Weapon Stash"} 2167 R Swords to Plowshares @Lie Setiawan -2168 R Baral, Chief of Compliance @Kieran Yanner -2169 M Garruk Relentless @Justyna Dura -2170 R Reaper King @Alexander Mokhov +2168 R Baral, Chief of Compliance @Kieran Yanner ${"flavorName": "Dwight, Assistant (to the) King"} +2169 M Garruk Relentless @Justyna Dura ${"flavorName": "Recyclops, Eco-friendly // Recyclops, Nature's Vengeance"} +2170 R Reaper King @Alexander Mokhov ${"flavorName": "Dwight o' Lantern"} 2171 R Glen Elendra Archmage @Jim Woodring 2172 M Guardian Project @Jim Woodring 2173 R Roon of the Hidden Realm @Jim Woodring 2174 R Soulherder @Jim Woodring 2175 M Jaws, Relentless Predator @Stephen Andrade -2176 R Descent into Avernus @Stephen Andrade +2176 R Descent into Avernus @Stephen Andrade ${"flavorName": "Panic on Amity Island"} 2177 R Reckless Endeavor @Stephen Andrade 2178 M Sneak Attack @Stephen Andrade 2179 R Abrade @Stephen Andrade ${"flavorName": "You're Gonna Need a Bigger Boat"} -2181 M Bruvac the Grandiloquent @Akirant +2181 M Bruvac the Grandiloquent @Akirant ${"flavorName": "Eddie the Judge"} 2182 R Windfall @Dan Mumford -2183 M Captain N'ghathrod @Akirant -2184 R Nekusar, the Mindrazer @Akirant +2183 M Captain N'ghathrod @Akirant ${"flavorName": "Eddie, Ghost of the Navigator"} +2184 R Nekusar, the Mindrazer @Akirant ${"flavorName": "Eddie, Lord of Light"} 2185 R Iron Maiden @Dan Mumford 2186 R Mindcrank @Dan Mumford 2187 R Lethal Scheme @Iron Maiden @@ -2133,37 +2133,37 @@ F1540 M Rainbow Dash @John Thacker 2196 M Wurmcoil Engine @Jason Loik & Matthew Cohen 2197 M Ellie, Brick Master @Irvin Rodriguez 2198 M Joel, Resolute Survivor @Yongjae Choi -2199 R Cabal Ritual @Bastien L. Deharme +2199 R Cabal Ritual @Bastien L. Deharme ${"flavorName": "Cordyceps Excision"} 2200 M Haunted One @Bastien L. Deharme 2202 M Abby, Merciless Soldier @Wayne Wu 2203 M Ellie, Vengeful Hunter @Irvin Rodriguez -2204 R Dictate of Erebos @Bastien L. Deharme -2205 R Mycoloth @Yongjae Choi +2204 R Dictate of Erebos @Bastien L. Deharme ${"flavorName": "Ellie's Rage"} +2205 R Mycoloth @Yongjae Choi ${"flavorName": "Cordyceps Rat King"} 2207 M Kratos, God of War @Magali Villeneuve -2208 R World at War @Lie Setiawan -2209 R Rite of Flame @Johan Grenier -2210 R Sulfuric Vortex @Aleksi Briclot -2211 R Pyrohemia @Chris Rahn +2208 R World at War @Lie Setiawan ${"flavorName": "Battle of Olympus"} +2209 R Rite of Flame @Johan Grenier ${"flavorName": "The Blades of Chaos Bond"} +2210 R Sulfuric Vortex @Aleksi Briclot ${"flavorName": "Hades Grip"} +2211 R Pyrohemia @Chris Rahn ${"flavorName": "Kratos' Rage"} 2212 M Atreus, Impulsive Son @Nathaniel Himawan 2213 M Kratos, Stoic Father @Nathaniel Himawan -2214 R Teferi's Ageless Insight @Aleksi Briclot & Alix Branwyn -2214☇ R Teferi's Ageless Insight @Aleksi Briclot & Alix Branwyn -2215 M Iroas, God of Victory @Joshua Raphael +2214 R Teferi's Ageless Insight @Aleksi Briclot & Alix Branwyn ${"flavorName": "Mimir's Ancient Wisdom"} +2214☇ R Teferi's Ageless Insight @Aleksi Briclot & Alix Branwyn ${"flavorName": "Mimir's Ancient Wisdom"} +2215 M Iroas, God of Victory @Joshua Raphael ${"flavorName": "Freya, Queen of the Valkyries"} 2216 M Nathan Drake, Treasure Hunter @Piotr Dura -2217 R Midnight Clock @Nereida -2218 R Whip of Erebos @Justyna Dura +2217 R Midnight Clock @Nereida ${"flavorName": "Kings Bay Clock Tower"} +2218 R Whip of Erebos @Justyna Dura ${"flavorName": "El Dorado Sarcophagus"} 2219 R Chain Reaction @Lie Setiawan 2220 M Passionate Archaeologist @Justyna Dura 2221 M Aloy, Savior of Meridian @Crystal Fae 2222 R Farseek @Toni Infante -2223 M Blightsteel Colossus @Narendra Bintara Adi -2224 R Tarrian's Soulcleaver @Gaboleps -2225 R Meteor Golem @Narendra Bintara Adi +2223 M Blightsteel Colossus @Narendra Bintara Adi ${"flavorName": "FAS-BOR7 Horus"} +2224 R Tarrian's Soulcleaver @Gaboleps ${"flavorName": "Regalla's Wrath"} +2225 R Meteor Golem @Narendra Bintara Adi ${"flavorName": "Thunderjaw"} 2226 M Jin Sakai, Ghost of Tsushima @Dominik Mayer 2227 R Path to Exile @Dominik Mayer 2228 R Borne Upon a Wind @Matteo Bassini 2229 R Ghostly Flicker @Dominik Mayer -2230 R Eiganjo Castle @Matteo Bassini +2230 R Eiganjo Castle @Matteo Bassini ${"flavorName": "Castle Shimura"} 2282 R Vito, Thorn of the Dusk Rose @Sam Heimer 2283 R Satoru Umezawa @Sam Heimer 2284 M Voja, Jaws of the Conclave @Ryan Roadkill @@ -2189,14 +2189,14 @@ F1540 M Rainbow Dash @John Thacker 2323 R Serum Visions @Justin Gerard 2324 M Umbris, Fear Manifest @Antonio José Manzanedo 2325 R Spellskite @Madeline Boni -7001 R Feed the Swarm @Stanislav Sherbakov -7002 R Forge Anew @Yongjae Choi -7003 R Silence @Evyn Fong -7004 M Solitude @Alexander Mokhov -7005 M Subtlety @Luisa J. Preissler -7006 M Grief @Anna Pavleeva -7007 M Fury @Joshua Raphael -7008 M Endurance @Christina Kraus +7001 R Feed the Swarm @Stanislav Sherbakov ${"flavorName": "Primogenesis"} +7002 R Forge Anew @Yongjae Choi ${"flavorName": "Armiger Unleashed"} +7003 R Silence @Evyn Fong ${"flavorName": "Porom's Silence Magic"} +7004 M Solitude @Alexander Mokhov ${"flavorName": "Yojimbo"} +7005 M Subtlety @Luisa J. Preissler ${"flavorName": "Shiva"} +7006 M Grief @Anna Pavleeva ${"flavorName": "Anima"} +7007 M Fury @Joshua Raphael ${"flavorName": "Ifrit"} +7008 M Endurance @Christina Kraus ${"flavorName": "Magus Sisters"} 7009 R Smothering Tithe @Tyler Walpole 7010 R Counterspell @Tyler Walpole 7011 R Dismember @Gregg Schigiel @@ -2215,17 +2215,17 @@ F1540 M Rainbow Dash @John Thacker 7024 R Resculpt @Imiri Sakabashira 7025 R Mirage Mirror @Imiri Sakabashira 7026 M Scion of Draco @Imiri Sakabashira -7027 R Tibalt's Trickery @Babs Webb +7027 R Tibalt's Trickery @Babs Webb ${"flavorName": "Ya viene el coco"} 7028 R Minds Aglow @Evan Geltosky 7029 R Command Tower @Dan Black 7030 R Command Tower @Sylvain Sarrailh ${"flavorName": "Master Emerald Shrine"} -7031 M Lotus Petal @Mike Burns -7032 M Lotus Petal @Mike Burns -7033 M Lotus Petal @Mike Burns -7034 M Lotus Petal @Mike Burns -7035 M Lotus Petal @Mike Burns -7036 M Lotus Petal @Mike Burns -7037 M Lotus Petal @Mike Burns +7031 M Lotus Petal @Mike Burns ${"flavorName": "Chaos Emerald"} +7032 M Lotus Petal @Mike Burns ${"flavorName": "Chaos Emerald"} +7033 M Lotus Petal @Mike Burns ${"flavorName": "Chaos Emerald"} +7034 M Lotus Petal @Mike Burns ${"flavorName": "Chaos Emerald"} +7035 M Lotus Petal @Mike Burns ${"flavorName": "Chaos Emerald"} +7036 M Lotus Petal @Mike Burns ${"flavorName": "Chaos Emerald"} +7037 M Lotus Petal @Mike Burns ${"flavorName": "Chaos Emerald"} 7040 R Vision Charm @Bernard Lee 7048 R Scute Swarm @Jason Loik & Matthew Cohen 7055 R Tetsuko Umezawa, Fugitive @Mitch Mohrhauser ${"flavorName": "Atsu, Ghost of Yōtei"} diff --git a/forge-gui/res/editions/Store Championships.txt b/forge-gui/res/editions/Store Championships.txt index e962d7dad27..bd6e7e41f2e 100644 --- a/forge-gui/res/editions/Store Championships.txt +++ b/forge-gui/res/editions/Store Championships.txt @@ -44,9 +44,9 @@ ScryfallCode=SCH 36 R Gleeful Demolition @Vincent Proce 37 R Charming Scoundrel @Fariba Khamseh 38 R Goddric, Cloaked Reveler @Irvin Rodriguez -39 R Preordain @Toshiaki Takayama -40 R Death's Shadow @Nottsuo -41 R City of Brass @Yo Shimizu +39 R Preordain @Toshiaki Takayama ${"flavorName": "Prophetic Beginning"} +40 R Death's Shadow @Nottsuo ${"flavorName": "Diabolos, Guardian Force"} +41 R City of Brass @Yo Shimizu ${"flavorName": "Bhujerba, Floating City"} 42 R Bitter Triumph @Aaron J. Riley 43 R Slickshot Show-Off @Wayne Reynolds 44 R Fable of the Mirror-Breaker @Warren Mahy diff --git a/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth Commander.txt b/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth Commander.txt index 6017786a724..6e52ba8272a 100644 --- a/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth Commander.txt +++ b/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth Commander.txt @@ -353,66 +353,66 @@ ScryfallCode=LTC 345 R Windbrisk Heights @Jokubas Uogintas 346 R Woodland Cemetery @Hristo D. Chukov 347 C Woodland Stream @Lucas Terryn -348 M The Great Henge @Jeremy Paillotin -349 M Cloudstone Curio @Erikas Perl -350 M Ensnaring Bridge @Stephen Stark -351 M The Ozolith @Calder Moore -352 M Rings of Brighthearth @Jonas De Ro -353 M Shadowspear @Campbell White -354 M Sword of Hearth and Home @Randy Gallegos -355 M Sword of the Animist @Pavel Kolomeyets -356 M Thorn of Amethyst @Igor Krstic -357 M Ancient Tomb @Anastasia Balakchina -358 M Bojuka Bog @Calder Moore -359 M Boseiju, Who Shelters All @Jeremy Paillotin -360 M Cabal Coffers @Sean Vo -361 M Castle Ardenvale @Jeremy Paillotin -362 M Cavern of Souls @Logan Feliciano -363 M Deserted Temple @Calder Moore -364 M Gemstone Caverns @Jonas De Ro -365 M Homeward Path @Erikas Perl -366 M Horizon Canopy @Randy Gallegos -367 M Karakas @Stephen Stark -368 M Kor Haven @Erikas Perl -369 M Minamo, School at Water's Edge @Stephen Stark -370 M Mouth of Ronom @Marina Ortega Lorente -371 M Oboro, Palace in the Clouds @Jonas De Ro -372 M Pillar of the Paruns @Randy Gallegos -373 M Reflecting Pool @Marco Gorlei -374 M Shinka, the Bloodsoaked Keep @Logan Feliciano -375 M Urborg, Tomb of Yawgmoth @Erikas Perl -376 M Wasteland @Hristo D. Chukov -377 M Yavimaya, Cradle of Growth @Erikas Perl -378 M The Great Henge @Jeremy Paillotin -379 M Cloudstone Curio @Erikas Perl -380 M Ensnaring Bridge @Stephen Stark -381 M The Ozolith @Calder Moore -382 M Rings of Brighthearth @Jonas De Ro -383 M Shadowspear @Campbell White -384 M Sword of Hearth and Home @Randy Gallegos -385 M Sword of the Animist @Pavel Kolomeyets -386 M Thorn of Amethyst @Igor Krstic -387 M Ancient Tomb @Anastasia Balakchina -388 M Bojuka Bog @Calder Moore -389 M Boseiju, Who Shelters All @Jeremy Paillotin -390 M Cabal Coffers @Sean Vo -391 M Castle Ardenvale @Jeremy Paillotin -392 M Cavern of Souls @Logan Feliciano -393 M Deserted Temple @Calder Moore -394 M Gemstone Caverns @Jonas De Ro -395 M Homeward Path @Erikas Perl -396 M Horizon Canopy @Randy Gallegos -397 M Karakas @Stephen Stark -398 M Kor Haven @Erikas Perl -399 M Minamo, School at Water's Edge @Stephen Stark -400 M Mouth of Ronom @Marina Ortega Lorente -401 M Oboro, Palace in the Clouds @Jonas De Ro -402 M Pillar of the Paruns @Randy Gallegos -403 M Reflecting Pool @Marco Gorlei -404 M Shinka, the Bloodsoaked Keep @Logan Feliciano -405 M Urborg, Tomb of Yawgmoth @Erikas Perl -406 M Wasteland @Hristo D. Chukov -407 M Yavimaya, Cradle of Growth @Erikas Perl +348 M The Great Henge @Jeremy Paillotin ${"flavorName": "The Party Tree"} +349 M Cloudstone Curio @Erikas Perl ${"flavorName": "Elessar, the Elfstone"} +350 M Ensnaring Bridge @Stephen Stark ${"flavorName": "Bridge of Khazad-dûm"} +351 M The Ozolith @Calder Moore ${"flavorName": "Argonath, Pillars of the Kings"} +352 M Rings of Brighthearth @Jonas De Ro ${"flavorName": "Three Rings for the Elven-Kings"} +353 M Shadowspear @Campbell White ${"flavorName": "Morgul-Knife"} +354 M Sword of Hearth and Home @Randy Gallegos ${"flavorName": "Herugrim, Sword of Rohan"} +355 M Sword of the Animist @Pavel Kolomeyets ${"flavorName": "Ring of Barahir"} +356 M Thorn of Amethyst @Igor Krstic ${"flavorName": "Shards of Narsil"} +357 M Ancient Tomb @Anastasia Balakchina ${"flavorName": "Balin's Tomb"} +358 M Bojuka Bog @Calder Moore ${"flavorName": "Barrow-Downs"} +359 M Boseiju, Who Shelters All @Jeremy Paillotin ${"flavorName": "Isengard, Saruman's Fortress"} +360 M Cabal Coffers @Sean Vo ${"flavorName": "Minas Morgul"} +361 M Castle Ardenvale @Jeremy Paillotin ${"flavorName": "Meduseld, Golden Hall of Edoras"} +362 M Cavern of Souls @Logan Feliciano ${"flavorName": "Paths of the Dead"} +363 M Deserted Temple @Calder Moore ${"flavorName": "Weathertop"} +364 M Gemstone Caverns @Jonas De Ro ${"flavorName": "Glittering Caves of Aglarond"} +365 M Homeward Path @Erikas Perl ${"flavorName": "Green Dragon Inn"} +366 M Horizon Canopy @Randy Gallegos ${"flavorName": "Bag End"} +367 M Karakas @Stephen Stark ${"flavorName": "White Tower of Ecthelion"} +368 M Kor Haven @Erikas Perl ${"flavorName": "Osgiliath, Fallen Capital"} +369 M Minamo, School at Water's Edge @Stephen Stark ${"flavorName": "Dol Amroth"} +370 M Mouth of Ronom @Marina Ortega Lorente ${"flavorName": "Redhorn Pass"} +371 M Oboro, Palace in the Clouds @Jonas De Ro ${"flavorName": "Bucklebury Ferry"} +372 M Pillar of the Paruns @Randy Gallegos ${"flavorName": "Inn of the Prancing Pony"} +373 M Reflecting Pool @Marco Gorlei ${"flavorName": "Henneth Annûn"} +374 M Shinka, the Bloodsoaked Keep @Logan Feliciano ${"flavorName": "Helm's Deep"} +375 M Urborg, Tomb of Yawgmoth @Erikas Perl ${"flavorName": "The Dead Marshes"} +376 M Wasteland @Hristo D. Chukov ${"flavorName": "Valley of Gorgoroth"} +377 M Yavimaya, Cradle of Growth @Erikas Perl ${"flavorName": "Fangorn Forest"} +378 M The Great Henge @Jeremy Paillotin ${"flavorName": "The Party Tree"} +379 M Cloudstone Curio @Erikas Perl ${"flavorName": "Elessar, the Elfstone"} +380 M Ensnaring Bridge @Stephen Stark ${"flavorName": "Bridge of Khazad-dûm"} +381 M The Ozolith @Calder Moore ${"flavorName": "Argonath, Pillars of the Kings"} +382 M Rings of Brighthearth @Jonas De Ro ${"flavorName": "Three Rings for the Elven-Kings"} +383 M Shadowspear @Campbell White ${"flavorName": "Morgul-Knife"} +384 M Sword of Hearth and Home @Randy Gallegos ${"flavorName": "Herugrim, Sword of Rohan"} +385 M Sword of the Animist @Pavel Kolomeyets ${"flavorName": "Ring of Barahir"} +386 M Thorn of Amethyst @Igor Krstic ${"flavorName": "Shards of Narsil"} +387 M Ancient Tomb @Anastasia Balakchina ${"flavorName": "Balin's Tomb"} +388 M Bojuka Bog @Calder Moore ${"flavorName": "Barrow-Downs"} +389 M Boseiju, Who Shelters All @Jeremy Paillotin ${"flavorName": "Isengard, Saruman's Fortress"} +390 M Cabal Coffers @Sean Vo ${"flavorName": "Minas Morgul"} +391 M Castle Ardenvale @Jeremy Paillotin ${"flavorName": "Meduseld, Golden Hall of Edoras"} +392 M Cavern of Souls @Logan Feliciano ${"flavorName": "Paths of the Dead"} +393 M Deserted Temple @Calder Moore ${"flavorName": "Weathertop"} +394 M Gemstone Caverns @Jonas De Ro ${"flavorName": "Glittering Caves of Aglarond"} +395 M Homeward Path @Erikas Perl ${"flavorName": "Green Dragon Inn"} +396 M Horizon Canopy @Randy Gallegos ${"flavorName": "Bag End"} +397 M Karakas @Stephen Stark ${"flavorName": "White Tower of Ecthelion"} +398 M Kor Haven @Erikas Perl ${"flavorName": "Osgiliath, Fallen Capital"} +399 M Minamo, School at Water's Edge @Stephen Stark ${"flavorName": "Dol Amroth"} +400 M Mouth of Ronom @Marina Ortega Lorente ${"flavorName": "Redhorn Pass"} +401 M Oboro, Palace in the Clouds @Jonas De Ro ${"flavorName": "Bucklebury Ferry"} +402 M Pillar of the Paruns @Randy Gallegos ${"flavorName": "Inn of the Prancing Pony"} +403 M Reflecting Pool @Marco Gorlei ${"flavorName": "Henneth Annûn"} +404 M Shinka, the Bloodsoaked Keep @Logan Feliciano ${"flavorName": "Helm's Deep"} +405 M Urborg, Tomb of Yawgmoth @Erikas Perl ${"flavorName": "The Dead Marshes"} +406 M Wasteland @Hristo D. Chukov ${"flavorName": "Valley of Gorgoroth"} +407 M Yavimaya, Cradle of Growth @Erikas Perl ${"flavorName": "Fangorn Forest"} 408 M Sol Ring @Randy Gallegos 409 M Sol Ring @Erikas Perl 410 M Sol Ring @Anastasia Balakchina @@ -520,11 +520,11 @@ ScryfallCode=LTC 512 R Mordor on the March @Campbell White 513 R Fell Beast of Mordor @Campbell White 514 R Minas Morgul, Dark Fortress @Campbell White -515 M Kenrith, the Returned King @Greg Hildebrandt & Tim Hildebrandt -516 M Ishkanah, Grafwidow @Greg Hildebrandt & Tim Hildebrandt -517 R Doran, the Siege Tower @Greg Hildebrandt & Tim Hildebrandt -518 R Hammerheim @Greg Hildebrandt & Tim Hildebrandt -519 R Urborg @Greg Hildebrandt & Tim Hildebrandt +515 M Kenrith, the Returned King @Greg Hildebrandt & Tim Hildebrandt ${"flavorName": "Théoden, Strength Restored"} +516 M Ishkanah, Grafwidow @Greg Hildebrandt & Tim Hildebrandt ${"flavorName": "Shelob, Whose Lair Is Death"} +517 R Doran, the Siege Tower @Greg Hildebrandt & Tim Hildebrandt ${"flavorName": "Treebeard, Eldest of Ents"} +518 R Hammerheim @Greg Hildebrandt & Tim Hildebrandt ${"flavorName": "Edoras, Capital of Rohan"} +519 R Urborg @Greg Hildebrandt & Tim Hildebrandt ${"flavorName": "Tower of Cirith Ungol"} 520 U Soul's Attendant @Greg Hildebrandt & Tim Hildebrandt 521 R Stonehewer Giant @Greg Hildebrandt & Tim Hildebrandt 522 R Worship @Tim Hildebrandt & Greg Hildebrandt diff --git a/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth.txt b/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth.txt index 7fd3a223250..de819fa9e99 100644 --- a/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth.txt +++ b/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth.txt @@ -854,7 +854,7 @@ ScryfallCode=LTR 833 R Mirkwood Channeler @Irina Nordsol [buy a box] -398 R Trailblazer's Boots @Alexander Gering +398 R Trailblazer's Boots @Alexander Gering ${"flavorName": "Lórien Brooch"} [promo] 299 M Gandalf the White @Leonardo Borazio From 3a1c3bf632f0e563dca66b221773e110136d79e0 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Thu, 30 Oct 2025 11:36:40 +0100 Subject: [PATCH 204/230] TLA cards, 28th October, Batch 3 (#9025) --- .../cardsfolder/p/pyromancer_ascension.txt | 2 +- ...at_the_crossroads_aang_destined_savior.txt | 24 +++++++++++++++++++ .../upcoming/azula_cunning_usurper.txt | 10 ++++++++ .../cardsfolder/upcoming/dai_li_agents.txt | 14 +++++++++++ .../cardsfolder/upcoming/dragonfly_swarm.txt | 13 ++++++++++ .../upcoming/earth_kings_lieutenant.txt | 12 ++++++++++ .../cardsfolder/upcoming/fire_lord_azula.txt | 8 +++++++ .../res/cardsfolder/upcoming/guru_pathik.txt | 11 +++++++++ .../cardsfolder/upcoming/invasion_tactics.txt | 10 ++++++++ .../cardsfolder/upcoming/iroh_grand_lotus.txt | 10 ++++++++ .../upcoming/leaves_from_the_vine.txt | 10 ++++++++ .../upcoming/ozai_the_phoenix_king.txt | 12 ++++++++++ .../cardsfolder/upcoming/seismic_sense.txt | 6 +++++ .../upcoming/sokka_tenacious_tactician.txt | 11 +++++++++ .../res/cardsfolder/upcoming/sun_warriors.txt | 8 +++++++ .../cardsfolder/upcoming/the_lion_turtle.txt | 12 ++++++++++ .../res/cardsfolder/upcoming/uncle_iroh.txt | 8 +++++++ .../upcoming/white_lotus_reinforcements.txt | 9 +++++++ .../res/cardsfolder/upcoming/zukos_exile.txt | 7 ++++++ 19 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/aang_at_the_crossroads_aang_destined_savior.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/azula_cunning_usurper.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/dai_li_agents.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/dragonfly_swarm.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/earth_kings_lieutenant.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/fire_lord_azula.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/guru_pathik.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/invasion_tactics.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/iroh_grand_lotus.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/leaves_from_the_vine.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/ozai_the_phoenix_king.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/seismic_sense.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sokka_tenacious_tactician.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sun_warriors.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/the_lion_turtle.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/uncle_iroh.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/white_lotus_reinforcements.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/zukos_exile.txt diff --git a/forge-gui/res/cardsfolder/p/pyromancer_ascension.txt b/forge-gui/res/cardsfolder/p/pyromancer_ascension.txt index 6074cfbb000..31afd55436e 100644 --- a/forge-gui/res/cardsfolder/p/pyromancer_ascension.txt +++ b/forge-gui/res/cardsfolder/p/pyromancer_ascension.txt @@ -2,7 +2,7 @@ Name:Pyromancer Ascension ManaCost:1 R Types:Enchantment T:Mode$ SpellCast | ValidCard$ Instant.sharesNameWith YourGraveyard,Sorcery.sharesNameWith YourGraveyard | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | OptionalDecider$ You | TriggerDescription$ Whenever you cast an instant or sorcery spell that has the same name as a card in your graveyard, you may put a quest counter on CARDNAME. -T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | IsPresent$ Card.Self+counters_GE2_QUEST | Execute$ TrigCopySpell | OptionalDecider$ You | TriggerDescription$ Whenever you cast an instant or sorcery spell while CARDNAME has two or more quest counters on it, you may copy that spell. You may choose new targets for the copy. +T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | IsPresent$ Card.Self+counters_GE2_QUEST | NoResolvingCheck$ True | Execute$ TrigCopySpell | OptionalDecider$ You | TriggerDescription$ Whenever you cast an instant or sorcery spell while CARDNAME has two or more quest counters on it, you may copy that spell. You may choose new targets for the copy. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ QUEST | CounterNum$ 1 | ConditionDefined$ TriggeredCard SVar:TrigCopySpell:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | AILogic$ Always | MayChooseTarget$ True SVar:MaxQuestEffect:2 diff --git a/forge-gui/res/cardsfolder/upcoming/aang_at_the_crossroads_aang_destined_savior.txt b/forge-gui/res/cardsfolder/upcoming/aang_at_the_crossroads_aang_destined_savior.txt new file mode 100644 index 00000000000..0d72c88bd72 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/aang_at_the_crossroads_aang_destined_savior.txt @@ -0,0 +1,24 @@ +Name:Aang, at the Crossroads +ManaCost:2 G W U +Types:Legendary Creature Human Avatar Ally +PT:3/3 +K:Flying +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigDig | TriggerDescription$ When NICKNAME enters, look at the top five cards of your library. You may put a creature card with mana value 4 or less from among them onto the battlefield. Put the rest on the bottom of your library in a random order. +SVar:TrigDig:DB$ Dig | DigNum$ 5 | ChangeNum$ 1 | ChangeValid$ Creature.cmcLE4 | Optional$ True | DestinationZone$ Battlefield | DestinationZone2$ Library | LibraryPosition$ -1 | RestRandomOrder$ True +T:Mode$ ChangesZone | ValidCard$ Creature.Other+YouCtrl | Origin$ Battlefield | Destination$ Any | Execute$ TrigDelayTransform | TriggerZones$ Battlefield | TriggerDescription$ When another creature you control leaves the battlefield, transform NICKNAME at the beginning of the next upkeep. +SVar:TrigDelayTransform:DB$ DelayedTrigger | Mode$ Phase | Phase$ Upkeep | Execute$ TrigTransform | TriggerDescription$ CARDNAME — Transform him at the beginning of the next end step. +SVar:TrigTransform:DB$ SetState | Defined$ Self | Mode$ Transform +AlternateMode:DoubleFaced +Oracle:Flying\nWhen Aang enters, look at the top five cards of your library. You may put a creature card with mana value 4 or less from among them onto the battlefield. Put the rest on the bottom of your library in a random order.\nWhen another creature you control leaves the battlefield, transform Aang at the beginning of the next upkeep. + +ALTERNATE + +Name:Aang, Destined Savior +ManaCost:no cost +Types:Legendary Creature Avatar Ally +PT:4/4 +K:Flying +S:Mode$ Continuous | Affected$ Creature.Land+YouCtrl | AddKeyword$ Vigilance | Description$ Land creatures you control have vigilance. +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | Execute$ TrigEarthbend | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of combat on your turn, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) +SVar:TrigEarthbend:DB$ Earthbend | Num$ 2 +Oracle:Flying\nLand creatures you control have vigilance.\nAt the beginning of combat on your turn, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) diff --git a/forge-gui/res/cardsfolder/upcoming/azula_cunning_usurper.txt b/forge-gui/res/cardsfolder/upcoming/azula_cunning_usurper.txt new file mode 100644 index 00000000000..756039c3565 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/azula_cunning_usurper.txt @@ -0,0 +1,10 @@ +Name:Azula, Cunning Usurper +ManaCost:2 U B B +Types:Legendary Creature Human Noble Rogue +PT:4/4 +K:Firebending:2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When NICKNAME enters, target opponent exiles a nontoken creature they control, then they exile a nonland card from their graveyard. +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Opponent | DefinedPlayer$ Targeted | Mandatory$ True | ChangeType$ Creature.!token | ChangeNum$ 1 | Hidden$ True | IsCurse$ True | Chooser$ Targeted | TgtPrompt$ Select target opponent | SubAbility$ DBChangeZone +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | DefinedPlayer$ Targeted | Chooser$ Targeted | ChangeType$ Card.nonLand | ChangeNum$ 1 | Hidden$ True | IsCurse$ True | Mandatory$ True +S:Mode$ Continuous | Affected$ Card.ExiledWithSource | Condition$ PlayerTurn | MayPlay$ True | MayPlayWithFlash$ True | MayPlayIgnoreType$ True | EffectZone$ Battlefield | AffectedZone$ Exile | Description$ During your turn, you may cast cards exiled with NICKNAME and you may cast them as though they had flash. Mana of any type can be spent to cast those spells. +Oracle:Firebending 2 (Whenever this creature attacks, add {R}{R}. This mana lasts until end of combat.)\nWhen Azula enters, target opponent exiles a nontoken creature they control, then they exile a nonland card from their graveyard.\nDuring your turn, you may cast cards exiled with Azula and you may cast them as though they had flash. Mana of any type can be spent to cast those spells. diff --git a/forge-gui/res/cardsfolder/upcoming/dai_li_agents.txt b/forge-gui/res/cardsfolder/upcoming/dai_li_agents.txt new file mode 100644 index 00000000000..7fbb8e7e1a5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/dai_li_agents.txt @@ -0,0 +1,14 @@ +Name:Dai Li Agents +ManaCost:3 B G +Types:Creature Human Soldier +PT:3/4 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEarthbend | TriggerDescription$ When this creature enters, earthbend 1, then earthbend 1. (To earthbend 1, target land you control becomes a 0/0 creature with haste that's still a land. Put a +1/+1 counter on it. When it dies or is exiled, return it to the battlefield tapped.) +SVar:TrigEarthbend:DB$ Earthbend | Num$ 1 | SubAbility$ DBEarthbend +SVar:DBEarthbend:DB$ Earthbend | Num$ 1 +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigLoseLife | TriggerDescription$ Whenever this creature attacks, each opponent loses X life and you gain X life, where X is the number of creatures you control with +1/+1 counters on them. +SVar:TrigLoseLife:DB$ LoseLife | Defined$ Player.Opponent | LifeAmount$ X | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X +SVar:X:Count$Valid Creature.YouCtrl+counters_GE1_P1P1 +SVar:HasAttackEffect:TRUE +DeckHints:Ability$Counters +Oracle:When this creature enters, earthbend 1, then earthbend 1. (To earthbend 1, target land you control becomes a 0/0 creature with haste that's still a land. Put a +1/+1 counter on it. When it dies or is exiled, return it to the battlefield tapped.)\nWhenever this creature attacks, each opponent loses X life and you gain X life, where X is the number of creatures you control with +1/+1 counters on them. diff --git a/forge-gui/res/cardsfolder/upcoming/dragonfly_swarm.txt b/forge-gui/res/cardsfolder/upcoming/dragonfly_swarm.txt new file mode 100644 index 00000000000..4c5048c2e8e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/dragonfly_swarm.txt @@ -0,0 +1,13 @@ +Name:Dragonfly Swarm +ManaCost:1 U R +Types:Creature Dragon Insect +PT:*/3 +K:Flying +K:Ward:1 +S:Mode$ Continuous | CharacteristicDefining$ True | SetPower$ X | Description$ This creature's power is equal to the number of noncreature, nonland cards in your graveyard. +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | CheckSVar$ Y | SVarCompare$ GE1 | Execute$ TrigDraw | TriggerDescription$ When this creature dies, if there's a Lesson card in your graveyard, draw a card. +SVar:TrigDraw:DB$ Draw +SVar:X:Count$ValidGraveyard Card.YouOwn+nonCreature+nonLand +SVar:Y:Count$ValidGraveyard Lesson.YouOwn +DeckHints:Type$Lesson +Oracle:Flying, ward {1} (Whenever this creature becomes the target of a spell or ability an opponent controls, counter it unless that player pays {1}.)\nThis creature's power is equal to the number of noncreature, nonland cards in your graveyard.\nWhen this creature dies, if there's a Lesson card in your graveyard, draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/earth_kings_lieutenant.txt b/forge-gui/res/cardsfolder/upcoming/earth_kings_lieutenant.txt new file mode 100644 index 00000000000..5d04227cf2c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/earth_kings_lieutenant.txt @@ -0,0 +1,12 @@ +Name:Earth King's Lieutenant +ManaCost:G W +Types:Creature Human Soldier Ally +PT:1/1 +K:Trample +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounterAll | TriggerDescription$ When this creature enters, put a +1/+1 counter on each other Ally creature you control. +SVar:TrigPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.Ally+Other+YouCtrl | CounterType$ P1P1 | CounterNum$ 1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Ally.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever another Ally you control enters, put a +1/+1 counter on this creature. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +SVar:BuffedBy:Ally +DeckHints:Type$Ally +Oracle:Trample\nWhen this creature enters, put a +1/+1 counter on each other Ally creature you control.\nWhenever another Ally you control enters, put a +1/+1 counter on this creature. diff --git a/forge-gui/res/cardsfolder/upcoming/fire_lord_azula.txt b/forge-gui/res/cardsfolder/upcoming/fire_lord_azula.txt new file mode 100644 index 00000000000..a3c05365761 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fire_lord_azula.txt @@ -0,0 +1,8 @@ +Name:Fire Lord Azula +ManaCost:1 U B R +Types:Legendary Creature Human Noble +PT:4/4 +K:Firebending:2 +T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | IsPresent$ Card.Self+attacking | NoResolvingCheck$ True | Execute$ TrigCopy | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a spell while CARDNAME is attacking, copy that spell. You may choose new targets for the copy. (A copy of a permanent spell becomes a token.) +SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True +Oracle:Firebending 2 (Whenever this creature attacks, add {R}{R}. This mana lasts until end of combat.)\nWhenever you cast a spell while Fire Lord Azula is attacking, copy that spell. You may choose new targets for the copy. (A copy of a permanent spell becomes a token.) diff --git a/forge-gui/res/cardsfolder/upcoming/guru_pathik.txt b/forge-gui/res/cardsfolder/upcoming/guru_pathik.txt new file mode 100644 index 00000000000..3bfdbdcab7f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/guru_pathik.txt @@ -0,0 +1,11 @@ +Name:Guru Pathik +ManaCost:2 GU GU +Types:Legendary Creature Human Monk Ally +PT:2/4 +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Any | Destination$ Battlefield | Execute$ TrigDig | TriggerDescription$ When CARDNAME enters, look at the top five cards of your library. You may reveal a Lesson, Saga, or Shrine card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. +SVar:TrigDig:DB$ Dig | DigNum$ 5 | ChangeNum$ 1 | Optional$ True | ForceRevealToController$ True | ChangeValid$ Lesson,Saga,Shrine | RestRandomOrder$ True +T:Mode$ SpellCast | ValidCard$ Lesson,Saga,Shrine | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you cast a Lesson, Saga, or Shrine spell, put a +1/+1 counter on another target creature you control. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.Other+YouCtrl | TgtPrompt$ Select another target creature you control | CounterType$ P1P1 | CounterNum$ 1 +SVar:BuffedBy:Lesson,Saga,Shrine +DeckHints:Type$Lesson|Saga|Shrine +Oracle:When Guru Pathik enters, look at the top five cards of your library. You may reveal a Lesson, Saga, or Shrine card from among them and put it into your hand. Put the rest on the bottom of your library in a random order.\nWhenever you cast a Lesson, Saga, or Shrine spell, put a +1/+1 counter on another target creature you control. diff --git a/forge-gui/res/cardsfolder/upcoming/invasion_tactics.txt b/forge-gui/res/cardsfolder/upcoming/invasion_tactics.txt new file mode 100644 index 00000000000..658d1b63604 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/invasion_tactics.txt @@ -0,0 +1,10 @@ +Name:Invasion Tactics +ManaCost:4 G +Types:Enchantment +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPumpAll | TriggerDescription$ When this enchantment enters, creatures you control get +2/+2 until end of turn. +SVar:TrigPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl | NumAtt$ +2 | NumDef$ +2 +T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Ally.YouCtrl | TriggerZones$ Battlefield | ValidTarget$ Player | Execute$ TrigDraw | TriggerDescription$ Whenever one or more Allies you control deal combat damage to a player, draw a card. +SVar:TrigDraw:DB$ Draw +SVar:PlayMain1:ALWAYS +DeckHints:Type$Ally +Oracle:When this enchantment enters, creatures you control get +2/+2 until end of turn.\nWhenever one or more Allies you control deal combat damage to a player, draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/iroh_grand_lotus.txt b/forge-gui/res/cardsfolder/upcoming/iroh_grand_lotus.txt new file mode 100644 index 00000000000..bea12a2255d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/iroh_grand_lotus.txt @@ -0,0 +1,10 @@ +Name:Iroh, Grand Lotus +ManaCost:3 G U R +Types:Legendary Creature Human Noble Ally +PT:5/5 +K:Firebending:2 +S:Mode$ Continuous | Affected$ Instant.nonLesson+YouOwn,Sorcery.nonLesson+YouOwn | AffectedZone$ Graveyard | AddKeyword$ Flashback | Condition$ PlayerTurn | Description$ During your turn, each non-Lesson instant and sorcery card in your graveyard has flashback. The flashback cost is equal to that card's mana cost. (You may cast a card from your graveyard for its flashback cost. Then exile it.) +S:Mode$ Continuous | Affected$ Lesson.YouOwn | AffectedZone$ Graveyard | AddKeyword$ Flashback:1 | Condition$ PlayerTurn | Description$ During your turn, each Lesson card in your graveyard has flashback {1}. +DeckHas:Ability$Graveyard +DeckHints:Type$Instant|Sorcery +Oracle:Firebending 2\nDuring your turn, each non-Lesson instant and sorcery card in your graveyard has flashback. The flashback cost is equal to that card's mana cost. (You may cast a card from your graveyard for its flashback cost. Then exile it.)\nDuring your turn, each Lesson card in your graveyard has flashback {1}. diff --git a/forge-gui/res/cardsfolder/upcoming/leaves_from_the_vine.txt b/forge-gui/res/cardsfolder/upcoming/leaves_from_the_vine.txt new file mode 100644 index 00000000000..035c58a6e01 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/leaves_from_the_vine.txt @@ -0,0 +1,10 @@ +Name:Leaves from the Vine +ManaCost:1 G +Types:Enchantment Saga +K:Chapter:3:DBMill,DBPutCounter,DBDraw +SVar:DBMill:DB$ Mill | Defined$ You | NumCards$ 3 | SubAbility$ DBToken | SpellDescription$ Mill three cards, then create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this token: You gain 3 life.") +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_food_sac | TokenOwner$ You +SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select up to two target creatures you control | CounterType$ P1P1 | CounterNum$ 1 | TargetMin$ 0 | TargetMax$ 2 | SpellDescription$ Put a +1/+1 counter on each of up to two target creatures you control. +SVar:DBDraw:DB$ Draw | ConditionCheckSVar$ X | ConditionSVarCompare$ GE1 | SpellDescription$ Draw a card if there's a creature or Lesson card in your graveyard. +SVar:X:Count$ValidGraveyard Creature.YouOwn,Lesson.YouOwn +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI — Mill three cards, then create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this token: You gain 3 life.")\nII — Put a +1/+1 counter on each of up to two target creatures you control.\nIII — Draw a card if there's a creature or Lesson card in your graveyard. diff --git a/forge-gui/res/cardsfolder/upcoming/ozai_the_phoenix_king.txt b/forge-gui/res/cardsfolder/upcoming/ozai_the_phoenix_king.txt new file mode 100644 index 00000000000..d544dc9ef00 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ozai_the_phoenix_king.txt @@ -0,0 +1,12 @@ +Name:Ozai, the Phoenix King +ManaCost:2 B B R R +Types:Legendary Creature Human Noble +PT:7/7 +K:Trample +K:Firebending:4 +K:Haste +R:Event$ LoseMana | ValidPlayer$ You | ReplacementResult$ Replaced | ReplaceWith$ ConvertMana | ActiveZones$ Battlefield | Description$ If you would lose unspent mana, that mana becomes red instead. +SVar:ConvertMana:DB$ ReplaceMana | ReplaceType$ Red +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Flying & Indestructible | CheckSVar$ X | SVarCompare$ GE6 | Description$ NICKNAME has flying and indestructible as long as you have six or more unspent mana. +SVar:X:Count$ManaPool:All +Oracle:Trample, firebending 4, haste\nIf you would lose unspent mana, that mana becomes red instead.\nOzai has flying and indestructible as long as you have six or more unspent mana. diff --git a/forge-gui/res/cardsfolder/upcoming/seismic_sense.txt b/forge-gui/res/cardsfolder/upcoming/seismic_sense.txt new file mode 100644 index 00000000000..8e7eff26a49 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/seismic_sense.txt @@ -0,0 +1,6 @@ +Name:Seismic Sense +ManaCost:G +Types:Sorcery Lesson +A:SP$ Dig | DigNum$ X | ChangeNum$ 1 | Optional$ True | ForceRevealToController$ True | ChangeValid$ Creature,Land | RestRandomOrder$ True | SpellDescription$ Look at the top X cards of your library, where X is the number of lands you control. You may reveal a creature or land card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. +SVar:X:Count$Valid Land.YouCtrl +Oracle:Look at the top X cards of your library, where X is the number of lands you control. You may reveal a creature or land card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/cardsfolder/upcoming/sokka_tenacious_tactician.txt b/forge-gui/res/cardsfolder/upcoming/sokka_tenacious_tactician.txt new file mode 100644 index 00000000000..d02a0c0d912 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sokka_tenacious_tactician.txt @@ -0,0 +1,11 @@ +Name:Sokka, Tenacious Tactician +ManaCost:1 U R W +Types:Legendary Creature Human Warrior Ally +PT:3/3 +K:Menace +K:Prowess +S:Mode$ Continuous | Affected$ Ally.YouCtrl+Other | AddKeyword$ Menace & Prowess | Description$ Other Allies you control have menace and prowess. +T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you cast a noncreature spell, create a 1/1 white Ally creature token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_ally | TokenOwner$ You +DeckHints:Type$Ally +Oracle:Menace, prowess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.)\nOther Allies you control have menace and prowess.\nWhenever you cast a noncreature spell, create a 1/1 white Ally creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/sun_warriors.txt b/forge-gui/res/cardsfolder/upcoming/sun_warriors.txt new file mode 100644 index 00000000000..2ae9dd5931c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sun_warriors.txt @@ -0,0 +1,8 @@ +Name:Sun Warriors +ManaCost:2 R W +Types:Creature Human Warrior Ally +PT:3/5 +K:Firebending:X:, where X is the number of creatures you control. +A:AB$ Token | Cost$ 5 | TokenAmount$ 1 | TokenScript$ w_1_1_ally | TokenOwner$ You | SpellDescription$ Create a 1/1 white Ally creature token. +SVar:X:Count$Valid Creature.YouCtrl +Oracle:Firebending X, where X is the number of creatures you control. (Whenever this creature attacks, add X {R}. This mana lasts until end of combat.)\n{5}: Create a 1/1 white Ally creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/the_lion_turtle.txt b/forge-gui/res/cardsfolder/upcoming/the_lion_turtle.txt new file mode 100644 index 00000000000..677b64aa3e6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_lion_turtle.txt @@ -0,0 +1,12 @@ +Name:The Lion-Turtle +ManaCost:1 G U +Types:Legendary Creature Elder Cat Turtle +PT:3/6 +K:Vigilance +K:Reach +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigGainLife | TriggerDescription$ When CARDNAME enters, you gain 3 life. +SVar:TrigGainLife:DB$ GainLife | LifeAmount$ 3 +S:Mode$ CantAttack,CantBlock | ValidCard$ Card.Self | CheckSVar$ X | SVarCompare$ LT3 | Description$ CARDNAME can't attack or block unless there are three or more Lesson cards in your graveyard. +A:AB$ Mana | Cost$ T | Produced$ Any | Amount$ 1 | SpellDescription$ Add one mana of any color. +SVar:X:Count$ValidGraveyard Lesson.YouOwn +Oracle:Vigilance, reach\nWhen The Lion-Turtle enters, you gain 3 life.\nThe Lion-Turtle can't attack or block unless there are three or more Lesson cards in your graveyard.\n{T}: Add one mana of any color. diff --git a/forge-gui/res/cardsfolder/upcoming/uncle_iroh.txt b/forge-gui/res/cardsfolder/upcoming/uncle_iroh.txt new file mode 100644 index 00000000000..2ca1358d57f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/uncle_iroh.txt @@ -0,0 +1,8 @@ +Name:Uncle Iroh +ManaCost:1 RG RG +Types:Legendary Creature Human Noble Ally +PT:4/2 +K:Firebending:1 +S:Mode$ ReduceCost | ValidCard$ Lesson | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Lesson spells you cast cost {1} less to cast. +DeckHints:Type$Lesson +Oracle:Firebending 1 (Whenever this creature attacks, add {R}. This mana lasts until end of combat.)\nLesson spells you cast cost {1} less to cast. diff --git a/forge-gui/res/cardsfolder/upcoming/white_lotus_reinforcements.txt b/forge-gui/res/cardsfolder/upcoming/white_lotus_reinforcements.txt new file mode 100644 index 00000000000..832121b930b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/white_lotus_reinforcements.txt @@ -0,0 +1,9 @@ +Name:White Lotus Reinforcements +ManaCost:1 G W +Types:Creature Human Soldier Ally +PT:2/3 +K:Vigilance +S:Mode$ Continuous | Affected$ Ally.Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other Allies you control get +1/+1. +SVar:PlayMain1:TRUE +SVar:BuffedBy:Ally +Oracle:Vigilance\nOther Allies you control get +1/+1. diff --git a/forge-gui/res/cardsfolder/upcoming/zukos_exile.txt b/forge-gui/res/cardsfolder/upcoming/zukos_exile.txt new file mode 100644 index 00000000000..d083b2f083f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/zukos_exile.txt @@ -0,0 +1,7 @@ +Name:Zuko's Exile +ManaCost:5 +Types:Instant Lesson +A:SP$ ChangeZone | ValidTgts$ Artifact,Creature,Enchantment | TgtPrompt$ Select target artifact, creature, or enchantment | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBToken | SpellDescription$ Exile target artifact, creature, or enchantment. Its controller creates a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ TargetedController +DeckHas:Ability$Token +Oracle:Exile target artifact, creature, or enchantment. Its controller creates a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") From 4c44b6b3377acb6a5abcfc563a30fcc90b184ab8 Mon Sep 17 00:00:00 2001 From: Eradev Date: Wed, 29 Oct 2025 07:23:03 -0400 Subject: [PATCH 205/230] Proper land selection when sacrificing --- forge-ai/src/main/java/forge/ai/ComputerUtilCard.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index b7e81dc00ba..01b492f4a7f 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -500,7 +500,7 @@ public class ComputerUtilCard { } List lands = CardLists.filter(list, CardPredicates.LANDS); - if (lands.size() > 6) { + if (lands.size() > 6 || lands.size() == Iterables.size(list)) { return getWorstLand(lands); } From fc89ca7093aba7e6b8c56543616744bbf9985582 Mon Sep 17 00:00:00 2001 From: Paul Hammerton Date: Thu, 30 Oct 2025 18:01:05 +0000 Subject: [PATCH 206/230] Edition updates: TLA, TLE --- .../Avatar The Last Airbender Eternal.txt | 5 +++ .../editions/Avatar The Last Airbender.txt | 32 +++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt b/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt index 5b28779adee..b613fad7a8c 100644 --- a/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt +++ b/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt @@ -6,15 +6,20 @@ Type=Expansion ScryfallCode=TLE [cards] +1 M Brought Back @Viacom 5 M Release to Memory @Viacom 9 M Agent of Treachery @Viacom 10 M Bribery @Viacom 13 M Force of Negation @Viacom 16 M Mystic Remora @Viacom +17 M Prosperity @Viacom 24 M Cruel Tutor @Viacom +33 M Mirrorwing Dragon @Viacom 41 M The Great Henge @Viacom ${"flavorName": "The Banyan Tree"} 43 M Heroic Intervention @Viacom +45 M Rites of Flourishing @Viacom 48 M Eladamri's Call @Viacom ${"flavorName": "Lifelong Friendship"} +52 M Rhys the Redeemed @Viacom 56 M Dark Depths @Viacom ${"flavorName": "The Boy in the Iceberg"} 61 M Valakut, the Molten Pinnacle @Viacom ${"flavorName": "Volcano of Roku's Island"} 63 R Katara's Reversal @Fahmi Fauzi diff --git a/forge-gui/res/editions/Avatar The Last Airbender.txt b/forge-gui/res/editions/Avatar The Last Airbender.txt index e83f893f69e..316d14db869 100644 --- a/forge-gui/res/editions/Avatar The Last Airbender.txt +++ b/forge-gui/res/editions/Avatar The Last Airbender.txt @@ -15,10 +15,14 @@ ScryfallCode=TLA 8 C Airbending Lesson @Pisukev 10 M Appa, Steadfast Guardian @Maël Ollivier-Henry 11 C Avatar Enthusiasts @Leanna Crossan +13 C Compassionate Healer @Kuno +19 U Fancy Footwork @Mizutametori 21 C Glider Kids @ikeda_cpt +22 U Glider Staff @Eduardo Francisco 25 C Jeong Jeong's Deserters @Leonardo Borazio 28 U Master Piandao @Brian Yuen 29 R Momo, Friendly Flier @Brandon L. Hunt +30 U Momo, Playful Pet @Awanqi (Angela Wang) 31 C Path to Redemption @Hokyoung Kim 32 C Rabaroo Troop @Mizutametori 33 C Razor Rings @Norikatsu Miyoshi @@ -27,6 +31,7 @@ ScryfallCode=TLA 43 C Yip Yip! @Cinkai 49 C First-Time Flyer @Mizutametori 50 C Flexible Waterbender @Rafater +51 C Forecasting Fortune Teller @Hisashi Momose 52 C Geyser Leaper @Norikatsu Miyoshi 53 C Giant Koi @Nathaniel Himawan 54 U Gran-Gran @Arou @@ -35,14 +40,18 @@ ScryfallCode=TLA 58 C It'll Quench Ya! @Nathaniel Himawan 59 U Katara, Bending Prodigy @Mephisto 61 M The Legend of Kuruk @Toshiaki Takayama +62 C Lost Days @Matteo Bassini 63 U Master Pakku @Olena Richards +64 R The Mechanist, Aerial Artisan @Le Vuong 65 U North Pole Patrol @Rose Benjamin 67 C Otter-Penguin @Eilene Cherie 68 C Rowdy Snowballers @Mizutametori 69 M Secret of Bloodbending @Olena Richards 70 U Serpent of the Pass @Eiji Kaneda 71 U Sokka's Haiku @Bun Toujo +74 U Teo, Spirited Glider @Robin Har 75 R Tiger-Seal @Jinho Bae +76 R Ty Lee, Chi Blocker @Gemi 78 M Wan Shi Tong, Librarian @Ryota Murayama 80 C Waterbending Lesson @Sylvain Sarrailh 82 C Watery Grasp @Rose Benjamin @@ -59,11 +68,14 @@ ScryfallCode=TLA 96 U Epic Downfall @Hristo D. Chukov 98 R The Fire Nation Drill @Brandon L. Hunt 99 U Fire Nation Engineer @Norikatsu Miyoshi +102 R Foggy Swamp Visions @Hori Airi 103 U Heartless Act @Sylvain Sarrailh 104 C Hog-Monkey @Miho Midorikawa 110 C Merchant of Many Hats @Boell Oyino +111 U Northern Air Temple @Slawek Fedorczuk 113 U Ozai's Cruelty @ikeda_cpt 117 M The Rise of Sozin @Mitori +121 U Tundra Tank @Shishizaru 126 U The Cave of Two Lovers @Ittoku 129 U Crescent Island Temple @Luc Courtois 130 C Cunning Maneuver @Robin Har @@ -82,6 +94,7 @@ ScryfallCode=TLA 152 C Rough Rhino Cavalry @Yuhong Ding 153 U Solstice Revelations @Kotakan 154 M Sozin's Comet @Salvatorre Zee Yazzie +158 U Ty Lee, Artful Acrobat @Rose Benjamin 159 U War Balloon @Matteo Bassini 161 C Yuyan Archers @Domco. 163 U Zuko, Exiled Prince @Nijihayashi @@ -89,6 +102,7 @@ ScryfallCode=TLA 166 C Badgermole @Matteo Bassini 167 M Badgermole Cub @Nathaniel Himawan 170 C Cycle of Renewal @Jocelin Carmes +171 R Diligent Zookeeper @Maojin Lee 174 U Earth Rumble @Olena Richards 176 C Earthbending Lesson @Toni Infante 179 U Flopsie, Bumi's Buddy @Alexandr Leskinen @@ -104,11 +118,14 @@ ScryfallCode=TLA 195 U Seismic Sense @Jo Cordisco 197 U Sparring Dummy @Gemi 198 U Toph, the Blind Bandit @Yueko +199 U True Ancestry @Chibi 200 C Turtle-Duck @Sylvain Sarrailh +201 U Unlucky Cabbage Merchant @Thomas Chamberlain-Keen 203 R Aang, at the Crossroads @Evan Shipard 205 C Abandon Attachments @Shahab Alizadeh 207 M Avatar Aang @Fahmi Fauzi 208 R Azula, Cunning Usurper @Evyn Fong +211 M Bumi, Unleashed @Toni Infante 212 C Cat-Owl @Thomas Chamberlain-Keen 214 U Dai Li Agents @Eduardo Francisco 215 U Dragonfly Swarm @John Di Giovanni @@ -136,10 +153,14 @@ ScryfallCode=TLA 248 U Uncle Iroh @Kieran Yanner 249 C Vindictive Warden @Jo Cordisco 251 U White Lotus Reinforcements @Kotakan +253 R Zuko, Conflicted @Halil Ural 254 C Barrels of Blasting Jelly @Salvatorre Zee Yazzie 255 C Bender's Waterskin @Dee Nguyen +259 M Planetarium of Wan Shi Tong @Robin Olausson 260 U Trusty Boomerang @Toni Infante 262 M White Lotus Tile @Dee Nguyen +278 R Secret Tunnel @Alexander Forssberg +281 U White Lotus Hideout @Luc Courtois 282 L Plains @Slawek Fedorczuk 283 L Island @Maojin Lee 284 L Swamp @Matteo Bassini @@ -174,6 +195,7 @@ ScryfallCode=TLA 321 R The Fire Nation Drill @Ben Hill 325 R Ran and Shaw @Antonio José Manzanedo 326 M Badgermole Cub @Filip Burburan +327 R Diligent Zookeeper @Andrea Piparo 328 R The Lion-Turtle @Filip Burburan 330 M White Lotus Tile @Antonio José Manzanedo 332 M Sozin's Comet @JungShan @@ -181,18 +203,13 @@ ScryfallCode=TLA 335 M Ozai, the Phoenix King @Sidharth Chaturvedi 336 R Aang's Iceberg @Brigitte Roka & Clifton Stommel 337 M Secret of Bloodbending @Barbara Rosiak -330 M White Lotus Tile @Antonio José Manzanedo -332 M Sozin's Comet @JungShan -334 R Fire Lord Azula @JungShan -335 M Ozai, the Phoenix King @Sidharth Chaturvedi -336 R Aang's Iceberg @Brigitte Roka & Clifton Stommel -337 M Secret of Bloodbending @Barbara Rosiak 338 R Yue, the Moon Spirit @Barbara Rosiak 339 R Foggy Swamp Visions @Sija Hong 341 M Fated Firepower @Brigitte Roka 342 R Firebending Student @Ina Wong 343 R Redirect Lightning @Perci Chen 346 R Aang, at the Crossroads @Brigitte Roka & Clifton Stommel +348 M Bumi, Unleashed @Brigitte Roka 349 R Iroh, Grand Lotus @Dalton Pencarinha 350 R Katara, the Fearless @Barbara Rosiak 351 R Katara, Water Tribe's Hope @Chun Lo @@ -207,10 +224,13 @@ ScryfallCode=TLA 363 M Avatar Aang @Bryan Konietzko 364 R Airbender Ascension @Shiren 366 R Hakoda, Selfless Commander @Rafater +369 R The Mechanist, Aerial Artisan @Le Vuong +371 R Ty Lee, Chi Blocker @Gemi 372 R Boiling Rock Rioter @Airi Yoshihisa 373 R Day of Black Sun @Matteo Bassini 380 R Earth King's Lieutenant @Nathaniel Himawan 383 R Sokka, Bold Boomeranger @Toni Infante 385 M Planetarium of Wan Shi Tong @Robin Olausson +392 R Secret Tunnel @Alexander Forssberg 393 R Firebending Student @Airi Yoshihisa 394 R Momo, Friendly Flier @Ryota Murayama From 0de568ce71a843f7bd74efa836b92398dbea6638 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:38:44 +0100 Subject: [PATCH 207/230] TLA cards, 30th October (#9045) --- .../upcoming/compassionate_healer.txt | 9 +++++++++ .../upcoming/earth_rumble_wrestlers.txt | 10 ++++++++++ .../res/cardsfolder/upcoming/fancy_footwork.txt | 6 ++++++ .../upcoming/foggy_swamp_spirit_keeper.txt | 9 +++++++++ .../upcoming/forecasting_fortune_teller.txt | 7 +++++++ .../res/cardsfolder/upcoming/glider_staff.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/lost_days.txt | 6 ++++++ .../res/cardsfolder/upcoming/messenger_hawk.txt | 10 ++++++++++ .../cardsfolder/upcoming/momo_playful_pet.txt | 12 ++++++++++++ .../cardsfolder/upcoming/north_pole_gates.txt | 9 +++++++++ .../upcoming/origin_of_metalbending.txt | 9 +++++++++ .../upcoming/professor_zei_anthropologist.txt | 7 +++++++ .../res/cardsfolder/upcoming/rumble_arena.txt | 10 ++++++++++ .../cardsfolder/upcoming/sandbenders_storm.txt | 7 +++++++ .../res/cardsfolder/upcoming/secret_tunnel.txt | 9 +++++++++ forge-gui/res/cardsfolder/upcoming/sold_out.txt | 6 ++++++ .../res/cardsfolder/upcoming/true_ancestry.txt | 7 +++++++ .../res/cardsfolder/upcoming/tundra_tank.txt | 10 ++++++++++ .../cardsfolder/upcoming/white_lotus_hideout.txt | 9 +++++++++ .../res/cardsfolder/upcoming/zuko_conflicted.txt | 16 ++++++++++++++++ 20 files changed, 176 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/compassionate_healer.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/earth_rumble_wrestlers.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/fancy_footwork.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/foggy_swamp_spirit_keeper.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/forecasting_fortune_teller.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/glider_staff.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/lost_days.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/messenger_hawk.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/momo_playful_pet.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/north_pole_gates.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/origin_of_metalbending.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/professor_zei_anthropologist.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/rumble_arena.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sandbenders_storm.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/secret_tunnel.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sold_out.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/true_ancestry.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/tundra_tank.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/white_lotus_hideout.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/zuko_conflicted.txt diff --git a/forge-gui/res/cardsfolder/upcoming/compassionate_healer.txt b/forge-gui/res/cardsfolder/upcoming/compassionate_healer.txt new file mode 100644 index 00000000000..4e2331fafef --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/compassionate_healer.txt @@ -0,0 +1,9 @@ +Name:Compassionate Healer +ManaCost:1 W +Types:Creature Human Cleric Ally +PT:2/2 +T:Mode$ Taps | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigGainLife | TriggerDescription$ Whenever this creature becomes tapped, you gain 1 life and scry 1. (Look at the top card of your library. You may put it on the bottom.) +SVar:TrigGainLife:DB$ GainLife | LifeAmount$ 1 | SubAbility$ DBScry +SVar:DBScry:DB$ Scry | ScryNum$ 1 +DeckHas:Ability$LifeGain +Oracle:Whenever this creature becomes tapped, you gain 1 life and scry 1. (Look at the top card of your library. You may put it on the bottom.) diff --git a/forge-gui/res/cardsfolder/upcoming/earth_rumble_wrestlers.txt b/forge-gui/res/cardsfolder/upcoming/earth_rumble_wrestlers.txt new file mode 100644 index 00000000000..dae53c73d79 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/earth_rumble_wrestlers.txt @@ -0,0 +1,10 @@ +Name:Earth Rumble Wrestlers +ManaCost:3 RG +Types:Creature Human Warrior Performer +PT:3/4 +K:Reach +S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 1 | AddKeyword$ Trample | CheckSVar$ X | Description$ This creature gets +1/+0 and has trample as long as you control a land creature or a land entered the battlefield under your control this turn. +SVar:Y:Count$Valid Creature.Land+YouCtrl +SVar:Z:Count$ThisTurnEntered_Battlefield_Land.YouCtrl +SVar:X:SVar$Y/Plus.Z +Oracle:Reach\nThis creature gets +1/+0 and has trample as long as you control a land creature or a land entered the battlefield under your control this turn. diff --git a/forge-gui/res/cardsfolder/upcoming/fancy_footwork.txt b/forge-gui/res/cardsfolder/upcoming/fancy_footwork.txt new file mode 100644 index 00000000000..6b7b9983b82 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fancy_footwork.txt @@ -0,0 +1,6 @@ +Name:Fancy Footwork +ManaCost:2 W +Types:Instant Lesson +A:SP$ Untap | ValidTgts$ Creature | TargetMin$ 1 | TargetMax$ 2 | TgtPrompt$ Choose one or two target creatures | SubAbility$ DBPump | SpellDescription$ Untap one or two target creatures. They each get +2/+2 until end of turn. +SVar:DBPump:DB$ Pump | Defined$ Targeted | NumAtt$ +2 | NumDef$ +2 +Oracle:Untap one or two target creatures. They each get +2/+2 until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/foggy_swamp_spirit_keeper.txt b/forge-gui/res/cardsfolder/upcoming/foggy_swamp_spirit_keeper.txt new file mode 100644 index 00000000000..47cb978412e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/foggy_swamp_spirit_keeper.txt @@ -0,0 +1,9 @@ +Name:Foggy Swamp Spirit Keeper +ManaCost:1 U B +Types:Creature Human Druid Ally +PT:2/4 +K:Lifelink +T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 2 | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you draw your second card each turn, create a 1/1 colorless Spirit creature token with "This token can't block or be blocked by non-Spirit creatures." +SVar:TrigToken:DB$ Token | TokenScript$ c_1_1_spirit_spiritshadow +DeckHas:Ability$Token & Type$Spirit +Oracle:Lifelink\nWhenever you draw your second card each turn, create a 1/1 colorless Spirit creature token with "This token can't block or be blocked by non-Spirit creatures." diff --git a/forge-gui/res/cardsfolder/upcoming/forecasting_fortune_teller.txt b/forge-gui/res/cardsfolder/upcoming/forecasting_fortune_teller.txt new file mode 100644 index 00000000000..5840f9e400d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/forecasting_fortune_teller.txt @@ -0,0 +1,7 @@ +Name:Forecasting Fortune Teller +ManaCost:1 U +Types:Creature Human Advisor Ally +PT:1/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When this creature enters, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You +Oracle:When this creature enters, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") diff --git a/forge-gui/res/cardsfolder/upcoming/glider_staff.txt b/forge-gui/res/cardsfolder/upcoming/glider_staff.txt new file mode 100644 index 00000000000..32442fcb918 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/glider_staff.txt @@ -0,0 +1,8 @@ +Name:Glider Staff +ManaCost:2 W +Types:Artifact Equipment +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigAirbend | TriggerDescription$ When this Equipment enters, airbend up to one target creature. (Exile it. While it's exiled, its owner may cast it for {2} rather than its mana cost.) +SVar:TrigAirbend:DB$ Airbend | ValidTgts$ Creature | TgtPrompt$ Select up to one target creature | TargetMin$ 0 | TargetMax$ 1 +S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 1 | AddToughness$ 1 | AddKeyword$ Flying | Description$ Equipped creature gets +1/+1 and has flying. +K:Equip:2 +Oracle:When this Equipment enters, airbend up to one target creature. (Exile it. While it's exiled, its owner may cast it for {2} rather than its mana cost.)\nEquipped creature gets +1/+1 and has flying.\nEquip {2} diff --git a/forge-gui/res/cardsfolder/upcoming/lost_days.txt b/forge-gui/res/cardsfolder/upcoming/lost_days.txt new file mode 100644 index 00000000000..0e6c87aecf6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/lost_days.txt @@ -0,0 +1,6 @@ +Name:Lost Days +ManaCost:4 U +Types:Instant Lesson +A:SP$ ChangeZone | ValidTgts$ Creature,Enchantment | TgtPrompt$ Select target creature or enchantment | AlternativeDecider$ TargetedOwner | Origin$ Battlefield | Destination$ Library | LibraryPosition$ 1 | DestinationAlternative$ Library | LibraryPositionAlternative$ -1 | SubAbility$ DBToken | SpellDescription$ The owner of target creature or enchantment puts it into their library second from the top or on the bottom. You create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You +Oracle:The owner of target creature or enchantment puts it into their library second from the top or on the bottom. You create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") diff --git a/forge-gui/res/cardsfolder/upcoming/messenger_hawk.txt b/forge-gui/res/cardsfolder/upcoming/messenger_hawk.txt new file mode 100644 index 00000000000..0f65a0d4730 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/messenger_hawk.txt @@ -0,0 +1,10 @@ +Name:Messenger Hawk +ManaCost:2 UB +Types:Creature Bird Scout +PT:1/2 +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When this creature enters, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You +S:Mode$ Continuous | Affected$ Card.Self | CheckSVar$ X | SVarCompare$ GE2 | AddPower$ 2 | Description$ This creature gets +2/+0 as long as you've drawn two or more cards this turn. +SVar:X:Count$YouDrewThisTurn +Oracle:Flying\nWhen this creature enters, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.")\nThis creature gets +2/+0 as long as you've drawn two or more cards this turn. diff --git a/forge-gui/res/cardsfolder/upcoming/momo_playful_pet.txt b/forge-gui/res/cardsfolder/upcoming/momo_playful_pet.txt new file mode 100644 index 00000000000..9ab6bb075bb --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/momo_playful_pet.txt @@ -0,0 +1,12 @@ +Name:Momo, Playful Pet +ManaCost:W +Types:Legendary Creature Lemur Bat Ally +PT:1/1 +K:Flying +K:Vigilance +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When NICKNAME leaves the battlefield, ABILITY +SVar:TrigCharm:DB$ Charm | Choices$ DBToken,DBPutCounter,DBScry +SVar:DBToken:DB$ Token | TokenScript$ c_a_food_sac | SpellDescription$ Create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this artifact: You gain 3 life.") +SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | CounterType$ P1P1 | TgtPrompt$ Select target creature you control | StackDescription$ Put a +1/+1 counter on {c:ThisTargetedCard}. | SpellDescription$ Put a +1/+1 counter on target creature you control. +SVar:DBScry:DB$ Scry | ScryNum$ 2 | SpellDescription$ Scry 2. +Oracle:Flying, vigilance\nWhen Momo leaves the battlefield, choose one —\n• Create a Food token. (It's an artifact with "{2}, {T}, Sacrifice this token: You gain 3 life.")\n• Put a +1/+1 counter on target creature you control.\n• Scry 2. diff --git a/forge-gui/res/cardsfolder/upcoming/north_pole_gates.txt b/forge-gui/res/cardsfolder/upcoming/north_pole_gates.txt new file mode 100644 index 00000000000..cad6c8644a8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/north_pole_gates.txt @@ -0,0 +1,9 @@ +Name:North Pole Gates +ManaCost:no cost +Types:Land Gate +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ This land enters tapped. +SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True +A:AB$ Mana | Cost$ T | Produced$ Combo W U | SpellDescription$ Add {W} or {U}. +A:AB$ Draw | Cost$ 4 T Sac<1/CARDNAME> | SpellDescription$ Draw a card. +DeckHas:Ability$Sacrifice +Oracle:This land enters tapped.\n{T}: Add {W} or {U}.\n{4}, {T}, Sacrifice this land: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/origin_of_metalbending.txt b/forge-gui/res/cardsfolder/upcoming/origin_of_metalbending.txt new file mode 100644 index 00000000000..c696be82eea --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/origin_of_metalbending.txt @@ -0,0 +1,9 @@ +Name:Origin of Metalbending +ManaCost:1 G +Types:Instant Lesson +A:SP$ Charm | Choices$ DBDestroy,DBPutCounter +SVar:DBDestroy:DB$ Destroy | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy target artifact or enchantment. +SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | CounterType$ P1P1 | TgtPrompt$ Select target creature you control | SubAbility$ DBPump | SpellDescription$ Put a +1/+1 counter on target creature you control. It gains indestructible until end of turn. (Damage and effects that say "destroy" don't destroy it.) +SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Indestructible +DeckHas:Ability$Counters +Oracle:Choose one —\n• Destroy target artifact or enchantment.\n• Put a +1/+1 counter on target creature you control. It gains indestructible until end of turn. (Damage and effects that say "destroy" don't destroy it.) diff --git a/forge-gui/res/cardsfolder/upcoming/professor_zei_anthropologist.txt b/forge-gui/res/cardsfolder/upcoming/professor_zei_anthropologist.txt new file mode 100644 index 00000000000..79aa698195b --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/professor_zei_anthropologist.txt @@ -0,0 +1,7 @@ +Name:Professor Zei, Anthropologist +ManaCost:UR UR +Types:Legendary Creature Human Advisor Ally +PT:0/3 +A:AB$ Draw | Cost$ T Discard<1/Card> | Defined$ You | SpellDescription$ Draw a card. +A:AB$ ChangeZone | Cost$ 1 T Sac<1/CARDNAME/NICKNAME> | TgtPrompt$ Select target instant or sorcery card in your graveyard | ValidTgts$ Instant.YouOwn,Sorcery.YouOwn | Origin$ Graveyard | Destination$ Hand | PlayerTurn$ True | SpellDescription$ Return target instant or sorcery card from your graveyard to your hand. Activate only during your turn. +Oracle:{T}, Discard a card: Draw a card.\n{1}, {T}, Sacrifice Professor Zei: Return target instant or sorcery card from your graveyard to your hand. Activate only during your turn. diff --git a/forge-gui/res/cardsfolder/upcoming/rumble_arena.txt b/forge-gui/res/cardsfolder/upcoming/rumble_arena.txt new file mode 100644 index 00000000000..d871494e62e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/rumble_arena.txt @@ -0,0 +1,10 @@ +Name:Rumble Arena +ManaCost:no cost +Types:Land +K:Vigilance +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigScry | TriggerDescription$ When this land enters, scry 1. (Look at the top card of your library. You may put it on the bottom.) +SVar:TrigScry:DB$ Scry | ScryNum$ 1 +A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. +A:AB$ Mana | Cost$ 1 T | Produced$ Any | SpellDescription$ Add one mana of any color. +DeckHas:Ability$Mana.Colorless +Oracle:Vigilance\nWhen this land enters, scry 1. (Look at the top card of your library. You may put it on the bottom.)\n{T}: Add {C}.\n{1}, {T}: Add one mana of any color. diff --git a/forge-gui/res/cardsfolder/upcoming/sandbenders_storm.txt b/forge-gui/res/cardsfolder/upcoming/sandbenders_storm.txt new file mode 100644 index 00000000000..0e4608c492f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sandbenders_storm.txt @@ -0,0 +1,7 @@ +Name:Sandbenders' Storm +ManaCost:3 W +Types:Instant +A:SP$ Charm | Choices$ DBDestroy,DBEarthbend +SVar:DBDestroy:DB$ Destroy | ValidTgts$ Creature.powerGE4 | TgtPrompt$ Select target creature with power 4 or greater | SpellDescription$ Destroy target creature with power 4 or greater. +SVar:DBEarthbend:DB$ Earthbend | Num$ 3 | SpellDescription$ Earthbend 3. (Target land you control becomes a 0/0 creature with haste that's still a land. Put three +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) +Oracle:Choose one —\n• Destroy target creature with power 4 or greater.\n• Earthbend 3. (Target land you control becomes a 0/0 creature with haste that's still a land. Put three +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) diff --git a/forge-gui/res/cardsfolder/upcoming/secret_tunnel.txt b/forge-gui/res/cardsfolder/upcoming/secret_tunnel.txt new file mode 100644 index 00000000000..051277ebc07 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/secret_tunnel.txt @@ -0,0 +1,9 @@ +Name:Secret Tunnel +ManaCost:no cost +Types:Land Cave +S:Mode$ CantBlockBy | ValidAttacker$ Creature.Self | Description$ This land can't be blocked. +A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. +A:AB$ Effect | Cost$ 4 T | ValidTgts$ Creature.YouCtrl | RememberObjects$ Targeted | TargetsWithSameCreatureType$ True | TargetMin$ 2 | TargetMax$ 2 | TgtPrompt$ Select two target creatures you control that share a creature type | ExileOnMoved$ Battlefield | StaticAbilities$ Unblockable | AILogic$ Pump | SpellDescription$ Two target creatures you control that share a creature type can't be blocked this turn. +SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. +DeckHas:Ability$Mana.Colorless +Oracle:This land can't be blocked.\n{T}: Add {C}.\n{4}, {T}: Two target creatures you control that share a creature type can't be blocked this turn. diff --git a/forge-gui/res/cardsfolder/upcoming/sold_out.txt b/forge-gui/res/cardsfolder/upcoming/sold_out.txt new file mode 100644 index 00000000000..e46fb553662 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sold_out.txt @@ -0,0 +1,6 @@ +Name:Sold Out +ManaCost:3 B +Types:Instant +A:SP$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Creature | SubAbility$ DBToken | SpellDescription$ Exile target creature. If it was dealt damage this turn, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You | ConditionDefined$ Targeted | ConditionPresent$ Creature.wasDealtDamageThisTurn +Oracle:Exile target creature. If it was dealt damage this turn, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") diff --git a/forge-gui/res/cardsfolder/upcoming/true_ancestry.txt b/forge-gui/res/cardsfolder/upcoming/true_ancestry.txt new file mode 100644 index 00000000000..b723d80443d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/true_ancestry.txt @@ -0,0 +1,7 @@ +Name:True Ancestry +ManaCost:1 G +Types:Sorcery Lesson +A:SP$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Permanent.YouOwn | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Choose up to one target creature card in your graveyard | SubAbility$ DBToken | SpellDescription$ Return up to one target permanent card from your graveyard to your hand. Create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You +DeckHints:Ability$Graveyard|Token +Oracle:Return up to one target permanent card from your graveyard to your hand.\nCreate a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") diff --git a/forge-gui/res/cardsfolder/upcoming/tundra_tank.txt b/forge-gui/res/cardsfolder/upcoming/tundra_tank.txt new file mode 100644 index 00000000000..eba5c7dd943 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tundra_tank.txt @@ -0,0 +1,10 @@ +Name:Tundra Tank +ManaCost:2 B +Types:Artifact Vehicle +PT:4/4 +K:Firebending:1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ When this Vehicle enters, target creature you control gains indestructible until end of turn. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | KW$ Indestructible +K:Crew:1 +SVar:PlayMain1:TRUE +Oracle:Firebending 1 (Whenever this creature attacks, add {R}. This mana lasts until end of combat.)\nWhen this Vehicle enters, target creature you control gains indestructible until end of turn.\nCrew 1 (Tap any number of creatures you control with total power 1 or more: This Vehicle becomes an artifact creature until end of turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/white_lotus_hideout.txt b/forge-gui/res/cardsfolder/upcoming/white_lotus_hideout.txt new file mode 100644 index 00000000000..f65b09dabd9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/white_lotus_hideout.txt @@ -0,0 +1,9 @@ +Name:White Lotus Hideout +ManaCost:no cost +Types:Land +A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. +A:AB$ Mana | Cost$ T | Produced$ Any | RestrictValid$ Spell.Lesson,Spell.Shrine | SpellDescription$ Add one mana of any color. Spend this mana only to cast a Lesson or Shrine spell. +A:AB$ Mana | Cost$ 1 T | Produced$ Any | SpellDescription$ Add one mana of any color. +DeckHas:Ability$Mana.Colorless +DeckHints:Type$Lesson|Shrine +Oracle:{T}: Add {C}.\n{T}: Add one mana of any color. Spend this mana only to cast a Lesson or Shrine spell.\n{1}, {T}: Add one mana of any color. diff --git a/forge-gui/res/cardsfolder/upcoming/zuko_conflicted.txt b/forge-gui/res/cardsfolder/upcoming/zuko_conflicted.txt new file mode 100644 index 00000000000..2838e31939d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/zuko_conflicted.txt @@ -0,0 +1,16 @@ +Name:Zuko, Conflicted +ManaCost:B R +Types:Legendary Creature Human Rogue +PT:2/3 +T:Mode$ Phase | Phase$ Main1 | ValidPlayer$ You | Execute$ TrigCharm | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your first main phase, ABILITY +SVar:TrigCharm:DB$ Charm | Choices$ DBDraw,DBPutCounter,DBMana,DBChangeZone | ChoiceRestriction$ ThisGame | CharmNum$ 1 | AdditionalDescription$ and you lose 2 life +SVar:DBDraw:DB$ Draw | SubAbility$ DBLoseLife | SpellDescription$ Draw a card. +SVar:DBPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 1 | SubAbility$ DBLoseLife | SpellDescription$ Put a +1/+1 counter on NICKNAME. +SVar:DBMana:DB$ Mana | Produced$ R | SubAbility$ DBLoseLife | SpellDescription$ Add {R}. +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | SubAbility$ DBChooseOpp | RememberChanged$ True | SpellDescription$ Exile NICKNAME, then return it to the battlefield under an opponent's control. +SVar:DBChooseOpp:DB$ ChoosePlayer | Defined$ You | Choices$ Opponent | ChoiceTitle$ Choose an opponent | SubAbility$ MoveToPlay +SVar:MoveToPlay:DB$ ChangeZone | Hidden$ True | Origin$ All | Destination$ Battlefield | Defined$ Remembered | GainControl$ ChosenPlayer | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenPlayer$ True | SubAbility$ DBLoseLife +SVar:DBLoseLife:DB$ LoseLife | Defined$ You | LifeAmount$ 2 +DeckHas:Ability$Counters +Oracle:At the beginning of your first main phase, choose one that hasn't been chosen and you lose 2 life —\n• Draw a card.\n• Put a +1/+1 counter on Zuko.\n• Add {R}.\n• Exile Zuko, then return him to the battlefield under an opponent's control. From 7d53fa88cfe6c0199a94711a86d9df41478edeb4 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Thu, 30 Oct 2025 22:49:55 +0100 Subject: [PATCH 208/230] TLA cards, 30th October, Batch 2 (#9048) --- forge-gui/res/cardsfolder/upcoming/bumi_bash.txt | 9 +++++++++ .../res/cardsfolder/upcoming/deadly_precision.txt | 8 ++++++++ .../res/cardsfolder/upcoming/fire_nation_cadets.txt | 8 ++++++++ .../res/cardsfolder/upcoming/fire_nation_warship.txt | 10 ++++++++++ forge-gui/res/cardsfolder/upcoming/octopus_form.txt | 7 +++++++ 5 files changed, 42 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/bumi_bash.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/deadly_precision.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/fire_nation_cadets.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/fire_nation_warship.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/octopus_form.txt diff --git a/forge-gui/res/cardsfolder/upcoming/bumi_bash.txt b/forge-gui/res/cardsfolder/upcoming/bumi_bash.txt new file mode 100644 index 00000000000..cc5f8467d71 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bumi_bash.txt @@ -0,0 +1,9 @@ +Name:Bumi Bash +ManaCost:3 R +Types:Sorcery +A:SP$ Charm | Choices$ DBDamage,DBDestroy +SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ X | SpellDescription$ CARDNAME deals damage equal to the number of lands you control to target creature. +SVar:DBDestroy:DB$ Destroy | ValidTgts$ Creature.Land,Land.nonBasic | TgtPrompt$ Select target land creature or nonbasic land. | SpellDescription$ Destroy target land creature or nonbasic land. +SVar:X:Count$Valid Land.YouCtrl +SVar:BuffedBy:Land +Oracle:Choose one —\n• Bumi Bash deals damage equal to the number of lands you control to target creature.\n• Destroy target land creature or nonbasic land. diff --git a/forge-gui/res/cardsfolder/upcoming/deadly_precision.txt b/forge-gui/res/cardsfolder/upcoming/deadly_precision.txt new file mode 100644 index 00000000000..272c81fa0e2 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/deadly_precision.txt @@ -0,0 +1,8 @@ +Name:Deadly Precision +ManaCost:B +Types:Sorcery +K:AlternateAdditionalCost:Sac<1/Creature;Artifact/artifact or creature>:4 +SVar:AIPreference:SacCost$Creature.token,Creature.cmcLE3 +A:SP$ Destroy | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Destroy target creature. +DeckHas:Ability$Sacrifice +Oracle:As an additional cost to cast this spell, pay {4} or sacrifice an artifact or creature.\nDestroy target creature. diff --git a/forge-gui/res/cardsfolder/upcoming/fire_nation_cadets.txt b/forge-gui/res/cardsfolder/upcoming/fire_nation_cadets.txt new file mode 100644 index 00000000000..0a889f86939 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fire_nation_cadets.txt @@ -0,0 +1,8 @@ +Name:Fire Nation Cadets +ManaCost:R +Types:Creature Human Soldier +PT:1/2 +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Firebending:2 | IsPresent$ Lesson.YouOwn | PresentZone$ Graveyard | Description$ This creature has firebending 2 as long as there's a Lesson card in your graveyard. (Whenever this creature attacks, add {R}{R}. This mana lasts until end of combat.) +A:AB$ Pump | Cost$ 2 | NumAtt$ +1 | SpellDescription$ This creature gets +1/+0 until end of turn. +DeckHints:Type$Lesson +Oracle:This creature has firebending 2 as long as there's a Lesson card in your graveyard. (Whenever this creature attacks, add {R}{R}. This mana lasts until end of combat.)\n{2}: This creature gets +1/+0 until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/fire_nation_warship.txt b/forge-gui/res/cardsfolder/upcoming/fire_nation_warship.txt new file mode 100644 index 00000000000..1bdf523db05 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fire_nation_warship.txt @@ -0,0 +1,10 @@ +Name:Fire Nation Warship +ManaCost:3 +Types:Artifact Vehicle +PT:4/4 +K:Reach +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigToken | TriggerDescription$ When this Vehicle dies, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:TrigToken:DB$ Token | TokenScript$ c_a_clue_draw | TokenAmount$ 1 +K:Crew:2 +DeckHas:Ability$Token +Oracle:Reach\nWhen this Vehicle dies, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.")\nCrew 2 (Tap any number of creatures you control with total power 2 or more: This Vehicle becomes an artifact creature until end of turn.) diff --git a/forge-gui/res/cardsfolder/upcoming/octopus_form.txt b/forge-gui/res/cardsfolder/upcoming/octopus_form.txt new file mode 100644 index 00000000000..9ad859eca7d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/octopus_form.txt @@ -0,0 +1,7 @@ +Name:Octopus Form +ManaCost:U +Types:Instant Lesson +A:SP$ Pump | ValidTgts$ Creature.YouCtrl | NumAtt$ +1 | NumDef$ +1 | TgtPrompt$ Select target creature you control | KW$ Hexproof | SubAbility$ DBUntap | SpellDescription$ Target creature you control gets +1/+1 and gains hexproof until end of turn. Untap it. (It can't be the target of spells or abilities your opponents control.) +SVar:DBUntap:DB$ Untap | Defined$ Targeted +DeckHas:Keyword$Hexproof +Oracle:Target creature you control gets +1/+1 and gains hexproof until end of turn. Untap it. (It can't be the target of spells or abilities your opponents control.) From 759c592b00102f83dc02f6efb99f811f8b56efe4 Mon Sep 17 00:00:00 2001 From: Chris H Date: Fri, 31 Oct 2025 10:18:41 -0400 Subject: [PATCH 209/230] Fix trigger mana variable from SVar to DB (#9051) --- forge-gui/res/cardsfolder/upcoming/avatar_roku_firebender.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/avatar_roku_firebender.txt b/forge-gui/res/cardsfolder/upcoming/avatar_roku_firebender.txt index f5d817eac3d..20a959401d6 100644 --- a/forge-gui/res/cardsfolder/upcoming/avatar_roku_firebender.txt +++ b/forge-gui/res/cardsfolder/upcoming/avatar_roku_firebender.txt @@ -3,6 +3,6 @@ ManaCost:3 R R R Types:Legendary Creature Human Avatar PT:6/6 T:Mode$ AttackersDeclared | TriggerZones$ Battlefield | Execute$ TrigMana | TriggerDescription$ Whenever a player attacks, add six {R}. Until end of combat, you don't lose this mana as steps end. -SVar:TrigMana:AB$ Mana | Produced$ R | Amount$ 6 | CombatMana$ True +SVar:TrigMana:DB$ Mana | Produced$ R | Amount$ 6 | CombatMana$ True A:AB$ Pump | Cost$ R R R | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +3 | SpellDescription$ Target creature gets +3/+0 until end of turn. Oracle:Whenever a player attacks, add six {R}. Until end of combat, you don't lose this mana as steps end.\n{R}{R}{R}: Target creature gets +3/+0 until end of turn. From 650bf097204f0d7645a1c86666c27590531ed679 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Fri, 31 Oct 2025 21:43:22 +0100 Subject: [PATCH 210/230] June, Bounty Hunter (TLA) (#9052) --- forge-gui/res/cardsfolder/upcoming/june_bounty_hunter.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/june_bounty_hunter.txt diff --git a/forge-gui/res/cardsfolder/upcoming/june_bounty_hunter.txt b/forge-gui/res/cardsfolder/upcoming/june_bounty_hunter.txt new file mode 100644 index 00000000000..fe3139ccfc5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/june_bounty_hunter.txt @@ -0,0 +1,8 @@ +Name:June, Bounty Hunter +ManaCost:1 B +Types:Legendary Creature Human Mercenary +PT:2/2 +S:Mode$ CantBlockBy | ValidAttacker$ Card.Self+attacking | CheckSVar$ X | SVarCompare$ GE2 | Description$ NICKNAME can't be blocked as long as you've drawn two or more cards this turn. +A:AB$ Token | Cost$ 1 Sac<1/Creature.Other/another creature> | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You | PlayerTurn$ True | SpellDescription$ Create a Clue token. Activate only during your turn. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:X:Count$YouDrewThisTurn +Oracle:June can't be blocked as long as you've drawn two or more cards this turn.\n{1}, Sacrifice another creature: Create a Clue token. Activate only during your turn. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") From d53d9a4ded9a94d34009bd6ea633f2153509a832 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Fri, 31 Oct 2025 21:50:10 +0100 Subject: [PATCH 211/230] TLA cards, 31st October (#9053) --- .../cardsfolder/upcoming/appa_loyal_sky_bison.txt | 12 ++++++++++++ .../res/cardsfolder/upcoming/jet_freedom_fighter.txt | 10 ++++++++++ .../res/cardsfolder/upcoming/jets_brainwashing.txt | 8 ++++++++ .../res/cardsfolder/upcoming/price_of_freedom.txt | 7 +++++++ .../cardsfolder/upcoming/suki_courageous_rescuer.txt | 8 ++++++++ .../upcoming/treetop_freedom_fighters.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/united_front.txt | 7 +++++++ 7 files changed, 60 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/appa_loyal_sky_bison.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/jet_freedom_fighter.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/jets_brainwashing.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/price_of_freedom.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/suki_courageous_rescuer.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/treetop_freedom_fighters.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/united_front.txt diff --git a/forge-gui/res/cardsfolder/upcoming/appa_loyal_sky_bison.txt b/forge-gui/res/cardsfolder/upcoming/appa_loyal_sky_bison.txt new file mode 100644 index 00000000000..d054ed6dfb4 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/appa_loyal_sky_bison.txt @@ -0,0 +1,12 @@ +Name:Appa, Loyal Sky Bison +ManaCost:4 W W +Types:Legendary Creature Bison Ally +PT:4/4 +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ Whenever NICKNAME enters or attacks, ABILITY +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerZones$ Battlefield | Secondary$ True | TriggerDescription$ Whenever NICKNAME enters or attacks, ABILITY +SVar:TrigCharm:DB$ Charm | Choices$ DBPump,DBAirbend +SVar:DBPump:DB$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | KW$ Flying | SpellDescription$ Target creature you control gains flying until end of turn. +SVar:DBAirbend:DB$ Airbend | ValidTgts$ Permanent.Other+nonLand+YouCtrl | TgtPrompt$ Select another target nonland permanent you control +SVar:HasAttackEffect:TRUE +Oracle:Flying\nWhenever Appa enters or attacks, choose one —\n• Target creature you control gains flying until end of turn.\n• Airbend another target nonland permanent you control. (Exile it. While it's exiled, its owner may cast it for {2} rather than its mana cost.) diff --git a/forge-gui/res/cardsfolder/upcoming/jet_freedom_fighter.txt b/forge-gui/res/cardsfolder/upcoming/jet_freedom_fighter.txt new file mode 100644 index 00000000000..a3dc99f0af2 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/jet_freedom_fighter.txt @@ -0,0 +1,10 @@ +Name:Jet, Freedom Fighter +ManaCost:2 RW RW RW +Types:Legendary Creature Human Rebel Ally +PT:3/1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDealDamage | TriggerDescription$ When NICKNAME enters, he deals damage equal to the number of creatures you control to target creature an opponent controls. +SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls | NumDmg$ X +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ When NICKNAME dies, put a +1/+1 counter on each of up to two target creatures. +SVar:TrigPutCounter:DB$ PutCounter | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Creature | TgtPrompt$ Select up to two target creatures | CounterType$ P1P1 | CounterNum$ 1 +SVar:X:Count$Valid Creature.YouCtrl +Oracle:When Jet enters, he deals damage equal to the number of creatures you control to target creature an opponent controls.\nWhen Jet dies, put a +1/+1 counter on each of up to two target creatures. diff --git a/forge-gui/res/cardsfolder/upcoming/jets_brainwashing.txt b/forge-gui/res/cardsfolder/upcoming/jets_brainwashing.txt new file mode 100644 index 00000000000..d0546790dfc --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/jets_brainwashing.txt @@ -0,0 +1,8 @@ +Name:Jet's Brainwashing +ManaCost:R +Types:Sorcery +K:Kicker:3 +A:SP$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN CARDNAME can't block. | IsCurse$ True | SubAbility$ DBGainControl | SpellDescription$ Target creature can't block this turn. If this spell was kicked, gain control of that creature until end of turn, untap it, and it gains haste until end of turn. Create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:DBGainControl:DB$ GainControl | Defined$ Targeted | LoseControl$ EOT | Untap$ True | AddKWs$ Haste | Condition$ Kicked | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You +Oracle:Kicker {3} (You may pay an additional {3} as you cast this spell.)\nTarget creature can't block this turn. If this spell was kicked, gain control of that creature until end of turn, untap it, and it gains haste until end of turn.\nCreate a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") diff --git a/forge-gui/res/cardsfolder/upcoming/price_of_freedom.txt b/forge-gui/res/cardsfolder/upcoming/price_of_freedom.txt new file mode 100644 index 00000000000..b976dac7aba --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/price_of_freedom.txt @@ -0,0 +1,7 @@ +Name:Price of Freedom +ManaCost:1 R +Types:Sorcery Lesson +A:SP$ Destroy | ValidTgts$ Artifact.OppCtrl,Land.OppCtrl | TgtPrompt$ Select target artifact or land an opponent controls | SubAbility$ DBChangeZone | SpellDescription$ Destroy target artifact or land an opponent controls. Its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. Draw a card. +SVar:DBChangeZone:DB$ ChangeZone | Optional$ True | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | DefinedPlayer$ TargetedController | ShuffleNonMandatory$ True | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw +Oracle:Destroy target artifact or land an opponent controls. Its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle.\nDraw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/suki_courageous_rescuer.txt b/forge-gui/res/cardsfolder/upcoming/suki_courageous_rescuer.txt new file mode 100644 index 00000000000..afac0e1b787 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/suki_courageous_rescuer.txt @@ -0,0 +1,8 @@ +Name:Suki, Courageous Rescuer +ManaCost:1 W W +Types:Legendary Creature Human Warrior Ally +PT:2/4 +S:Mode$ Continuous | Affected$ Creature.Other+YouCtrl | AddPower$ 1 | Description$ Other creatures you control get +1/+0. +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Permanent.YouCtrl+Other | TriggerZones$ Battlefield | PlayerTurn$ True | Execute$ TrigToken | ActivationLimit$ 1 | TriggerDescription$ Whenever another permanent you control leaves the battlefield during your turn, create a 1/1 while Ally creature token. This ability triggers only once each turn. +SVar:TrigToken:DB$ Token | TokenScript$ w_1_1_ally +Oracle:Other creatures you control get +1/+0.\nWhenever another permanent you control leaves the battlefield during your turn, create a 1/1 white Ally creature token. This ability triggers only once each turn. diff --git a/forge-gui/res/cardsfolder/upcoming/treetop_freedom_fighters.txt b/forge-gui/res/cardsfolder/upcoming/treetop_freedom_fighters.txt new file mode 100644 index 00000000000..e8a6514d368 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/treetop_freedom_fighters.txt @@ -0,0 +1,8 @@ +Name:Treetop Freedom Fighters +ManaCost:2 R +Types:Creature Human Rebel Ally +PT:2/1 +K:Haste +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When this creature enters, create a 1/1 white Ally creature token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_ally | TokenOwner$ You +Oracle:Haste\nWhen this creature enters, create a 1/1 white Ally creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/united_front.txt b/forge-gui/res/cardsfolder/upcoming/united_front.txt new file mode 100644 index 00000000000..ad617edc438 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/united_front.txt @@ -0,0 +1,7 @@ +Name:United Front +ManaCost:X W W +Types:Sorcery +A:SP$ Token | TokenAmount$ X | TokenScript$ w_1_1_soldier | TokenOwner$ You | SubAbility$ DBPutCounterAll | SpellDescription$ Create X 1/1 white Ally creature tokens, then put a +1/+1 counter on each creature you control. +SVar:DBPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl | CounterType$ P1P1 | CounterNum$ 1 | StackDescription$ None +SVar:X:Count$xPaid +Oracle:Create X 1/1 white Ally creature tokens, then put a +1/+1 counter on each creature you control. From 1445fc2621b1eecc544efd345e2ce26fcfb53ead Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Fri, 31 Oct 2025 21:52:32 +0100 Subject: [PATCH 212/230] The Spirit Oasis (TLA) (#9054) --- .../res/cardsfolder/upcoming/the_spirit_oasis.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/the_spirit_oasis.txt diff --git a/forge-gui/res/cardsfolder/upcoming/the_spirit_oasis.txt b/forge-gui/res/cardsfolder/upcoming/the_spirit_oasis.txt new file mode 100644 index 00000000000..3ce682b9c84 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_spirit_oasis.txt @@ -0,0 +1,10 @@ +Name:The Spirit Oasis +ManaCost:2 U +Types:Legendary Enchantment Shrine +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw1 | TriggerDescription$ When CARDNAME enters, draw a card for each Shrine you control. +SVar:TrigDraw1:DB$ Draw | NumCards$ X +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Shrine.Other+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigDraw2 | TriggerDescription$ Whenever another Shrine you control enters, draw a card. +SVar:TrigDraw2:DB$ Draw +SVar:X:Count$Valid Shrine.YouCtrl +DeckHints:Type$Shrine +Oracle:When The Spirit Oasis enters, draw a card for each Shrine you control.\nWhenever another Shrine you control enters, draw a card. From 54b8094211813c9fbec58c11ef52b6a4dbeb915e Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sat, 1 Nov 2025 15:44:47 +0100 Subject: [PATCH 213/230] Some cleanup (#9056) --- .../main/java/forge/ai/ComputerUtilMana.java | 23 ++++++++-------- .../main/java/forge/ai/ability/AnimateAi.java | 16 +++++------ .../main/java/forge/game/GameActionUtil.java | 12 ++++----- .../java/forge/game/ability/AbilityUtils.java | 1 - .../ability/effects/ManaReflectedEffect.java | 27 ++++++++----------- .../game/spellability/AbilityManaPart.java | 4 +-- .../forge/game/trigger/TriggerHandler.java | 4 --- .../g/general_kreat_the_boltbringer.txt | 2 +- .../cardsfolder/s/synchronized_eviction.txt | 4 +-- 9 files changed, 39 insertions(+), 54 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index 60af06cc6ba..cb96486b256 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -487,14 +487,14 @@ public class ComputerUtilMana { String originalProduced = manaProduced.toString(); if (originalProduced.isEmpty()) { - return manaProduced.toString(); + return originalProduced; } // Run triggers like Nissa final Map runParams = AbilityKey.mapFromCard(hostCard); runParams.put(AbilityKey.Activator, ai); // assuming AI would only ever gives itself mana runParams.put(AbilityKey.AbilityMana, saPayment); - runParams.put(AbilityKey.Produced, manaProduced.toString()); + runParams.put(AbilityKey.Produced, originalProduced); for (Trigger tr : ai.getGame().getTriggerHandler().getActiveTrigger(TriggerType.TapsForMana, runParams)) { SpellAbility trSA = tr.ensureAbility(); if (trSA == null) { @@ -1292,11 +1292,11 @@ public class ComputerUtilMana { * @return ManaCost */ public static ManaCostBeingPaid calculateManaCost(final Cost cost, final SpellAbility sa, final boolean test, final int extraMana, final boolean effect) { - Card card = sa.getHostCard(); + Card host = sa.getHostCard(); Zone castFromBackup = null; - if (test && sa.isSpell() && !card.isInZone(ZoneType.Stack)) { - castFromBackup = card.getCastFrom(); - card.setCastFrom(card.getZone() != null ? card.getZone() : null); + if (test && sa.isSpell() && !host.isInZone(ZoneType.Stack)) { + castFromBackup = host.getCastFrom(); + host.setCastFrom(host.getZone() != null ? host.getZone() : null); } Cost payCosts; @@ -1319,7 +1319,7 @@ public class ComputerUtilMana { final int multiplicator = Math.max(xCounter, 1); manaToAdd = extraMana * multiplicator; } else { - manaToAdd = AbilityUtils.calculateAmount(card, sa.getParamOrDefault("XAlternative", "X"), sa) * xCounter; + manaToAdd = AbilityUtils.calculateAmount(host, sa.getParamOrDefault("XAlternative", "X"), sa) * xCounter; } if (manaToAdd < 1 && payCosts != null && payCosts.getCostMana().getXMin() > 0) { @@ -1331,7 +1331,7 @@ public class ComputerUtilMana { if (xColor == null) { xColor = "1"; } - if (card.hasKeyword("Spend only colored mana on X. No more than one mana of each color may be spent this way.")) { + if (host.hasKeyword("Spend only colored mana on X. No more than one mana of each color may be spent this way.")) { xColor = "WUBRGX"; } if (xCounter > 0) { @@ -1356,14 +1356,14 @@ public class ComputerUtilMana { mCost = ManaCost.combine(mCost, mkCost); ManaCostBeingPaid mcbp = new ManaCostBeingPaid(mCost); if (!canPayManaCost(mcbp, sa, sa.getActivatingPlayer(), true)) { - sa.getHostCard().setSVar("NumTimes", "Number$" + i); + host.setSVar("NumTimes", "Number$" + i); break; } } } - if (test && sa.isSpell()) { - sa.getHostCard().setCastFrom(castFromBackup); + if (test && sa.isSpell() && !host.isInZone(ZoneType.Stack)) { + host.setCastFrom(castFromBackup); } return manaCost; @@ -1596,6 +1596,7 @@ public class ComputerUtilMana { } // don't use abilities with dangerous drawbacks + // TODO this has already been checked earlier AbilitySub sub = m.getSubAbility(); if (sub != null && !SpellApiToAi.Converter.get(sub).chkDrawbackWithSubs(ai, sub).willingToPlay()) { continue; diff --git a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java index 54085982811..a5f1c7d5f15 100644 --- a/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/AnimateAi.java @@ -411,15 +411,13 @@ public class AnimateAi extends SpellAbilityAi { } if (logic.equals("ValuableAttackerOrBlocker")) { - if (ph.inCombat()) { - final Combat combat = ph.getCombat(); - for (Card c : list) { - Card animated = becomeAnimated(c, sa); - boolean isValuableAttacker = ph.is(PhaseType.MAIN1, ai) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animated); - boolean isValuableBlocker = combat != null && combat.getDefendingPlayers().contains(ai) && ComputerUtilCard.doesSpecifiedCreatureBlock(ai, animated); - if (isValuableAttacker || isValuableBlocker) - return new AiAbilityDecision(100, AiPlayDecision.WillPlay); - } + final Combat combat = ph.getCombat(); + for (Card c : list) { + Card animated = becomeAnimated(c, sa); + boolean isValuableAttacker = ph.is(PhaseType.COMBAT_BEGIN, ai) && ComputerUtilCard.doesSpecifiedCreatureAttackAI(ai, animated); + boolean isValuableBlocker = combat != null && combat.getDefendingPlayers().contains(ai) && ComputerUtilCard.doesSpecifiedCreatureBlock(ai, animated); + if (isValuableAttacker || isValuableBlocker) + return new AiAbilityDecision(100, AiPlayDecision.WillPlay); } } diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 85c10ceec85..645855f7b65 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -879,14 +879,12 @@ public final class GameActionUtil { } else if (abMana.isComboMana()) { // amount is already taken care of in resolve method for combination mana, just append baseMana sb.append(baseMana); + } else if (StringUtils.isNumeric(baseMana)) { + sb.append(amount * Integer.parseInt(baseMana)); } else { - if (StringUtils.isNumeric(baseMana)) { - sb.append(amount * Integer.parseInt(baseMana)); - } else { - sb.append(baseMana); - for (int i = 1; i < amount; i++) { - sb.append(" ").append(baseMana); - } + sb.append(baseMana); + for (int i = 1; i < amount; i++) { + sb.append(" ").append(baseMana); } } return sb.toString(); 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 da85d2910c6..828dfbf6d60 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -3419,7 +3419,6 @@ public class AbilityUtils { } public static int playerXProperty(final Player player, final String s, final Card source, CardTraitBase ctb) { - final String[] l = s.split("/"); final String m = CardFactoryUtil.extractOperators(s); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java index fc8a0203f35..deb22df6359 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ManaReflectedEffect.java @@ -103,12 +103,10 @@ public class ManaReflectedEffect extends SpellAbilityEffect { return "0"; } else if (colors.size() == 1) { baseMana = MagicColor.toShortString(colors.iterator().next()); + } else if (colors.contains("colorless")) { + baseMana = MagicColor.toShortString(player.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa.getHostCard(), ColorSet.fromNames(colors))); } else { - if (colors.contains("colorless")) { - baseMana = MagicColor.toShortString(player.getController().chooseColorAllowColorless(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa.getHostCard(), ColorSet.fromNames(colors))); - } else { - baseMana = MagicColor.toShortString(player.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, ColorSet.fromNames(colors))); - } + baseMana = MagicColor.toShortString(player.getController().chooseColor(Localizer.getInstance().getMessage("lblSelectManaProduce"), sa, ColorSet.fromNames(colors))); } } else { colorMenu = ColorSet.fromMask(mask); @@ -122,19 +120,16 @@ public class ManaReflectedEffect extends SpellAbilityEffect { if (amount == 0) { sb.append("0"); + } else if (StringUtils.isNumeric(baseMana)) { + // if baseMana is an integer(colorless), just multiply amount and baseMana + final int base = Integer.parseInt(baseMana); + sb.append(base * amount); } else { - if (StringUtils.isNumeric(baseMana)) { - // if baseMana is an integer(colorless), just multiply amount - // and baseMana - final int base = Integer.parseInt(baseMana); - sb.append(base * amount); - } else { - for (int i = 0; i < amount; i++) { - if (i != 0) { - sb.append(" "); - } - sb.append(baseMana); + for (int i = 0; i < amount; i++) { + if (i != 0) { + sb.append(" "); } + sb.append(baseMana); } } return sb.toString(); 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 6c09e994721..73138f215dc 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -192,10 +192,8 @@ public class AbilityManaPart implements java.io.Serializable { } } - // add the mana produced to the mana pool manaPool.add(this.lastManaProduced); - // Run triggers final Map runParams = AbilityKey.mapFromCard(source); runParams.put(AbilityKey.Player, player); runParams.put(AbilityKey.Produced, afterReplace); @@ -512,7 +510,7 @@ public class AbilityManaPart implements java.io.Serializable { * @return a {@link java.lang.String} object. */ public final String mana(SpellAbility sa) { - if (isComboMana()) { // when asking combo, just go there + if (isComboMana()) { return getComboColors(sa); } String produced = this.getOrigProduced(); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java index 013e3a5b60a..3c2e7c66fbb 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -58,10 +58,6 @@ public class TriggerHandler { game = gameState; } - public final boolean hasDelayedTriggers() { - return !delayedTriggers.isEmpty(); - } - public final void registerDelayedTrigger(final Trigger trig) { delayedTriggers.add(trig); } diff --git a/forge-gui/res/cardsfolder/g/general_kreat_the_boltbringer.txt b/forge-gui/res/cardsfolder/g/general_kreat_the_boltbringer.txt index 7e2d769296a..6625560d0cc 100644 --- a/forge-gui/res/cardsfolder/g/general_kreat_the_boltbringer.txt +++ b/forge-gui/res/cardsfolder/g/general_kreat_the_boltbringer.txt @@ -2,7 +2,7 @@ Name:General Kreat, the Boltbringer ManaCost:2 R Types:Legendary Creature Goblin Soldier PT:2/2 -T:Mode$ AttackersDeclared | ValidAttackers$ Goblin.YouCtrl | Execute$ TrigToken | TriggerZones$ Battlefield | ActivationLimit$ 1 | TriggerDescription$ Whenever one or more Goblins you control attack, create a 1/1 red Goblin creature token that's tapped and attacking. +T:Mode$ AttackersDeclared | ValidAttackers$ Goblin.YouCtrl | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever one or more Goblins you control attack, create a 1/1 red Goblin creature token that's tapped and attacking. SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ r_1_1_goblin | TokenOwner$ You | TokenTapped$ True | TokenAttacking$ True T:Mode$ ChangesZone | ValidCard$ Creature.Other+YouCtrl | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigDealDamage | TriggerDescription$ Whenever another creature you control enters, CARDNAME deals 1 damage to each opponent. SVar:TrigDealDamage:DB$ DealDamage | Defined$ Player.Opponent | NumDmg$ 1 diff --git a/forge-gui/res/cardsfolder/s/synchronized_eviction.txt b/forge-gui/res/cardsfolder/s/synchronized_eviction.txt index a7fe42fe16d..96869b9193b 100644 --- a/forge-gui/res/cardsfolder/s/synchronized_eviction.txt +++ b/forge-gui/res/cardsfolder/s/synchronized_eviction.txt @@ -1,7 +1,7 @@ Name:Synchronized Eviction ManaCost:4 U Types:Instant -S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ 2 | EffectZone$ All | CheckSVar$ X | SVarCompare$ GE2 | Description$ This spell costs {2} less to cast if you control two at least two creatures that share a creature type. +S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ 2 | EffectZone$ All | CheckSVar$ X | SVarCompare$ GE2 | Description$ This spell costs {2} less to cast if you control at least two creatures that share a creature type. SVar:X:Count$MostProminentCreatureType Creature.YouCtrl A:SP$ ChangeZone | Origin$ Battlefield | Destination$ Library | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | LibraryPosition$ 1 | SpellDescription$ Put target nonland permanent into its owner's library second from the top. -Oracle:This spell costs {2} less to cast if you control two at least two creatures that share a creature type.\nPut target nonland permanent into its owner's library second from the top. +Oracle:This spell costs {2} less to cast if you control at least two creatures that share a creature type.\nPut target nonland permanent into its owner's library second from the top. From ab4c91dd2d43b188f324a8727ae8da8d815aa0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20Gr=C3=B6hl?= Date: Sat, 1 Nov 2025 17:56:55 +0100 Subject: [PATCH 214/230] Implemented Commander variant of Adventure Mode (#8970) * Added UI elements for commander mode in NewGame screen. - still missing random pool of commander candidates sharing a color with each selected color identity. * Added rudimentary commander choice with preview image. For now, it is 5 suggestions per color identity with the requirement that the chosen color is part of the commander's identity. * Commander card is now added to the player inventory and cannot be sold. The save game adds a flag whether the game is in commander mode, which will be important for the future changes. * Commander is now auto-added to the initial deck. Commander cannot be removed. Commander is properly saved and loaded. * Duels now feature the command zone and all rules * The chosen commander is now set to every deck if the game mode is commander. * Only cards can be added to the deck that fulfill the color identity. * heuristically add a pile of singleton cards to function as the starter for the commander's journey. * Fix land color identity bug in commander pile * Items are put on autosell if they do not match commander identity. * Autosell is now properly set with the card flip timing rules. When visiting a shop, the deck conformaty message does not show anymore. * Remove strict necessity for a fixed commander. This way, a player may choose to switch to a different legendary creature they find. * 15 Streamlined commander choices without having to swap colors individually. * Clean up to remove now redundant i18n label. More focus on creatures for starter commander decks. * Refactor adventure commander deck generation into the DeckgenUtil class * Refactorings as suggested by @Jetz72. * Remove custom deck generator * handle DeckFormat logic through AdventureDeckEditor. Fix bugs, where partner commanders would be overwritten when using the stock DeckgenUtil. * Remove random new line * Code cleanup * Fix unused import error * Fix unused import error * Revert "organise import" changes and random space linebreak changes. * Removed another import package.* statement * Implement fixed commander deck to start with. Removed UI edits and random commander choice. * Add commander-specific cards and a two white overrun effects * Revert to prior string formatting. * Remove import * * Formatting nitpicking to change as little original code as possible. --------- Co-authored-by: Agetian --- .../forge/adventure/data/DifficultyData.java | 1 + .../adventure/player/AdventurePlayer.java | 44 +++++++++- .../adventure/scene/AdventureDeckEditor.java | 53 ++++++++---- .../src/forge/adventure/scene/DuelScene.java | 3 + .../forge/adventure/scene/NewGameScene.java | 20 ++++- .../forge/adventure/util/AdventureModes.java | 3 +- .../src/forge/adventure/util/Config.java | 6 ++ .../src/forge/adventure/util/RewardActor.java | 20 ++++- .../src/forge/adventure/world/WorldSave.java | 4 +- forge-gui/res/adventure/common/config.json | 28 +++++++ .../decks/starter/commander/black_01.dck | 81 +++++++++++++++++++ .../decks/starter/commander/blue_01.dck | 81 +++++++++++++++++++ .../decks/starter/commander/green_01.dck | 81 +++++++++++++++++++ .../common/decks/starter/commander/red_01.dck | 81 +++++++++++++++++++ .../decks/starter/commander/white_01.dck | 81 +++++++++++++++++++ 15 files changed, 566 insertions(+), 21 deletions(-) create mode 100644 forge-gui/res/adventure/common/decks/starter/commander/black_01.dck create mode 100644 forge-gui/res/adventure/common/decks/starter/commander/blue_01.dck create mode 100644 forge-gui/res/adventure/common/decks/starter/commander/green_01.dck create mode 100644 forge-gui/res/adventure/common/decks/starter/commander/red_01.dck create mode 100644 forge-gui/res/adventure/common/decks/starter/commander/white_01.dck diff --git a/forge-gui-mobile/src/forge/adventure/data/DifficultyData.java b/forge-gui-mobile/src/forge/adventure/data/DifficultyData.java index d7aa97e0b4d..a9ddbc01e62 100644 --- a/forge-gui-mobile/src/forge/adventure/data/DifficultyData.java +++ b/forge-gui-mobile/src/forge/adventure/data/DifficultyData.java @@ -25,5 +25,6 @@ public class DifficultyData { public ObjectMap starterDecks = null; public ObjectMap constructedStarterDecks= null; public ObjectMap pileDecks= null; + public ObjectMap commanderDecks = null; } diff --git a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java index 450d98d328e..9d19aac6078 100644 --- a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java +++ b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java @@ -49,10 +49,13 @@ public class AdventurePlayer implements Serializable, SaveFileContent { // Deck data private Deck deck; - private final ArrayList decks = new ArrayList(MIN_DECK_COUNT); + private final ArrayList decks = new ArrayList<>(MIN_DECK_COUNT); private int selectedDeckIndex = 0; private final DifficultyData difficultyData = new DifficultyData(); + // Commander mode + private AdventureModes adventureMode; + // Game data. private float worldPosX; private float worldPosY; @@ -114,6 +117,7 @@ public class AdventurePlayer implements Serializable, SaveFileContent { fantasyMode = false; announceFantasy = false; usingCustomDeck = false; + adventureMode = null; blessing = null; gold = 0; maxLife = 20; @@ -147,11 +151,14 @@ public class AdventurePlayer implements Serializable, SaveFileContent { public final ItemPool autoSellCards = new ItemPool<>(PaperCard.class); public final Set favoriteCards = new HashSet<>(); - public void create(String n, Deck startingDeck, boolean male, int race, int avatar, boolean isFantasy, boolean isUsingCustomDeck, DifficultyData difficultyData) { + public void create(String n, Deck startingDeck, boolean male, int race, int avatar, boolean isFantasy, + boolean isUsingCustomDeck, DifficultyData difficultyData, AdventureModes adventureMode) { clear(); + this.adventureMode = adventureMode; announceFantasy = fantasyMode = isFantasy; //Set Chaos mode first. announceCustom = usingCustomDeck = isUsingCustomDeck; + clearDecks(); // Reset the empty decks to now already have the commander in the command zone. deck = startingDeck; decks.set(0, deck); @@ -288,6 +295,10 @@ public class AdventurePlayer implements Serializable, SaveFileContent { return life; } + public AdventureModes getAdventureMode(){ + return adventureMode; + } + public int getMaxLife() { return maxLife; } @@ -334,7 +345,6 @@ public class AdventurePlayer implements Serializable, SaveFileContent { this.colorIdentity = set; } - @Override public void load(SaveFileData data) { boolean migration = false; @@ -391,6 +401,13 @@ public class AdventurePlayer implements Serializable, SaveFileContent { heroRace = data.readInt("heroRace"); avatarIndex = data.readInt("avatarIndex"); isFemale = data.readBool("isFemale"); + + String _mode = data.readString("adventure_mode"); + if (_mode == null) + adventureMode = AdventureModes.Standard; + else + adventureMode = AdventureModes.valueOf(_mode); + if (data.containsKey("colorIdentity")) { String temp = data.readString("colorIdentity"); if (temp != null) @@ -505,6 +522,11 @@ public class AdventurePlayer implements Serializable, SaveFileContent { deck.getOrCreate(DeckSection.Contraptions).addAll(contraptionDeckCards.getFilteredPool(isValid)); unsupportedCards.addAll(contraptionDeckCards.getFilteredPool(isUnsupported).toFlatList()); } + if (data.containsKey("commanderCards")) { + CardPool commanderCards = CardPool.fromCardList(List.of((String[]) data.readObject("commanderCards"))); + deck.getOrCreate(DeckSection.Commander).addAll(commanderCards.getFilteredPool(isValid)); + unsupportedCards.addAll(commanderCards.getFilteredPool(isUnsupported).toFlatList()); + } if (data.containsKey("characterFlagsKey") && data.containsKey("characterFlagsValue")) { String[] keys = (String[]) data.readObject("characterFlagsKey"); Byte[] values = (Byte[]) data.readObject("characterFlagsValue"); @@ -573,6 +595,11 @@ public class AdventurePlayer implements Serializable, SaveFileContent { decks.get(i).getOrCreate(DeckSection.Contraptions).addAll(contraptionCards.getFilteredPool(isValid)); unsupportedCards.addAll(contraptionCards.getFilteredPool(isUnsupported).toFlatList()); } + if (data.containsKey("commanderCards_" + i)) { + CardPool commanderCards = CardPool.fromCardList(List.of((String[]) data.readObject("commanderCards_" + i))); + decks.get(i).getOrCreate(DeckSection.Commander).addAll(commanderCards.getFilteredPool(isValid)); + unsupportedCards.addAll(commanderCards.getFilteredPool(isUnsupported).toFlatList()); + } } // In case we allow removing decks from the deck selection GUI, populate up to the minimum for (int i = dynamicDeckCount++; i < MIN_DECK_COUNT; i++) { @@ -595,6 +622,11 @@ public class AdventurePlayer implements Serializable, SaveFileContent { decks.get(i).getOrCreate(DeckSection.Sideboard).addAll(sideBoardCards.getFilteredPool(isValid)); unsupportedCards.addAll(sideBoardCards.getFilteredPool(isUnsupported).toFlatList()); } + if (data.containsKey("commanderCards_" + i)) { + CardPool commanderCards = CardPool.fromCardList(List.of((String[]) data.readObject("commanderCards_" + i))); + decks.get(i).getOrCreate(DeckSection.Commander).addAll(commanderCards.getFilteredPool(isValid)); + unsupportedCards.addAll(commanderCards.getFilteredPool(isUnsupported).toFlatList()); + } } } @@ -715,6 +747,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent { data.store("isFemale", isFemale); data.store("colorIdentity", colorIdentity.getColor()); + data.store("adventure_mode", adventureMode.toString()); + data.store("fantasyMode", fantasyMode); data.store("announceFantasy", announceFantasy); data.store("usingCustomDeck", usingCustomDeck); @@ -772,6 +806,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent { data.storeObject("attractionDeckCards", deck.get(DeckSection.Attractions).toCardList("\n").split("\n")); if (deck.get(DeckSection.Contraptions) != null) data.storeObject("contraptionDeckCards", deck.get(DeckSection.Contraptions).toCardList("\n").split("\n")); + if (deck.get(DeckSection.Commander) != null) + data.storeObject("commanderCards", deck.get(DeckSection.Commander).toCardList("\n").split("\n")); // save decks dynamically data.store("deckCount", getDeckCount()); @@ -784,6 +820,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent { data.storeObject("attractionDeckCards_" + i, decks.get(i).get(DeckSection.Attractions).toCardList("\n").split("\n")); if (decks.get(i).get(DeckSection.Contraptions) != null) data.storeObject("contraptionDeckCards_" + i, decks.get(i).get(DeckSection.Contraptions).toCardList("\n").split("\n")); + if (decks.get(i).get(DeckSection.Commander) != null) + data.storeObject("commanderCards_" + i, decks.get(i).get(DeckSection.Commander).toCardList("\n").split("\n")); } data.store("selectedDeckIndex", selectedDeckIndex); data.storeObject("cards", cards.toCardList("\n").split("\n")); diff --git a/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java b/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java index d3425a17e33..af966105328 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java +++ b/forge-gui-mobile/src/forge/adventure/scene/AdventureDeckEditor.java @@ -9,6 +9,7 @@ import forge.adventure.data.AdventureEventData; import forge.adventure.data.ItemData; import forge.adventure.player.AdventurePlayer; import forge.adventure.util.AdventureEventController; +import forge.adventure.util.AdventureModes; import forge.adventure.util.Config; import forge.adventure.util.Current; import forge.assets.FImage; @@ -50,7 +51,7 @@ public class AdventureDeckEditor extends FDeckEditor { @Override public DeckFormat getDeckFormat() { - return DeckFormat.Adventure; + return AdventurePlayer.current().getAdventureMode() == AdventureModes.Commander ? DeckFormat.Commander : DeckFormat.Adventure; } @Override @@ -65,17 +66,29 @@ public class AdventureDeckEditor extends FDeckEditor { @Override protected DeckEditorPage[] getInitialPages() { - return new DeckEditorPage[]{ - new CollectionCatalogPage(), - new AdventureDeckSectionPage(DeckSection.Main, ItemManagerConfig.ADVENTURE_EDITOR_POOL), - new AdventureDeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.ADVENTURE_SIDEBOARD), - new CollectionAutoSellPage() - }; + if (AdventurePlayer.current().getAdventureMode() == AdventureModes.Commander) + return new DeckEditorPage[]{ + new CollectionCatalogPage(), + new AdventureDeckSectionPage(DeckSection.Commander, ItemManagerConfig.ADVENTURE_EDITOR_POOL), + new AdventureDeckSectionPage(DeckSection.Main, ItemManagerConfig.ADVENTURE_EDITOR_POOL), + new AdventureDeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.ADVENTURE_SIDEBOARD), + new CollectionAutoSellPage() + }; + else { + return new DeckEditorPage[]{ + new CollectionCatalogPage(), + new AdventureDeckSectionPage(DeckSection.Main, ItemManagerConfig.ADVENTURE_EDITOR_POOL), + new AdventureDeckSectionPage(DeckSection.Sideboard, ItemManagerConfig.ADVENTURE_SIDEBOARD), + new CollectionAutoSellPage() + }; + } } @Override public ItemPool getCardPool(boolean wantUnique) { - return Current.player().getCards(); + ItemPool pool = new ItemPool<>(PaperCard.class); + pool.addAll(Current.player().getCards()); + return pool; } @Override @@ -113,6 +126,13 @@ public class AdventureDeckEditor extends FDeckEditor { } } + @Override + public boolean isCommanderEditor() { + if (AdventurePlayer.current().getAdventureMode() == AdventureModes.Commander) + return true; + return super.isCommanderEditor(); + } + protected static class ShopConfig extends AdventureEditorConfig { @Override protected DeckEditorPage[] getInitialPages() { @@ -896,12 +916,17 @@ public class AdventureDeckEditor extends FDeckEditor { return; } - String deckError = GameType.Adventure.getDeckFormat().getDeckConformanceProblem(getDeck()); - if (deckError != null) { - //Allow the player to close the editor with an invalid deck, but warn them that cards may be swapped out. - String warning = localizer.getMessage("lblAdventureDeckError", deckError); - FOptionPane.showConfirmDialog(warning, localizer.getMessage("lblInvalidDeck"), false, result -> resolveClose(canCloseCallback, result == true)); - return; + String deckError; + if (!(getEditorConfig() instanceof ShopConfig)) + { + deckError = getEditorConfig().getDeckFormat().getDeckConformanceProblem(getDeck()); + + if (deckError != null) { + //Allow the player to close the editor with an invalid deck, but warn them that cards may be swapped out. + String warning = localizer.getMessage("lblAdventureDeckError", deckError); + FOptionPane.showConfirmDialog(warning, localizer.getMessage("lblInvalidDeck"), false, result -> resolveClose(canCloseCallback, result == true)); + return; + } } resolveClose(canCloseCallback, true); diff --git a/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java b/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java index c974b02e9c1..9883e399f9e 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/DuelScene.java @@ -14,6 +14,7 @@ import forge.adventure.player.AdventurePlayer; import forge.adventure.stage.GameHUD; import forge.adventure.stage.IAfterMatch; import forge.adventure.util.AdventureEventController; +import forge.adventure.util.AdventureModes; import forge.adventure.util.Config; import forge.adventure.util.Current; import forge.assets.FBufferedImage; @@ -197,6 +198,8 @@ public class DuelScene extends ForgeScene { String isDeckMissingMsg = ""; if (eventData != null && eventData.eventRules != null) { mainGameType = eventData.eventRules.gameType; + } else if (AdventurePlayer.current().getAdventureMode() == AdventureModes.Commander){ + mainGameType = GameType.Commander; } else { mainGameType = GameType.Adventure; } diff --git a/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java b/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java index 774f710c309..8cae52997a3 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java @@ -9,6 +9,7 @@ import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; import com.badlogic.gdx.utils.Array; + import com.github.tommyettinger.textra.TextraLabel; import forge.Forge; import forge.adventure.data.DialogData; @@ -34,6 +35,7 @@ import java.util.Random; * NewGame scene that contains the character creation */ public class NewGameScene extends MenuScene { + TextField selectedName; ColorSet[] colorIds; CardEdition[] editionIds; @@ -97,6 +99,11 @@ public class NewGameScene extends MenuScene { AdventureModes.Pile.setSelectionName(colorIdLabel); AdventureModes.Pile.setModes(colorNames); } + if (diff.commanderDecks != null) { + modes.add(AdventureModes.Commander); + AdventureModes.Commander.setSelectionName(colorIdLabel); + AdventureModes.Commander.setModes(colorNames); + } break; } @@ -122,6 +129,12 @@ public class NewGameScene extends MenuScene { AdventureModes.Custom.setSelectionName("[BLACK]" + Forge.getLocalizer().getMessage("lblDeck") + ":"); AdventureModes.Custom.setModes(custom); } + + // Commander game mode in selection screen + modes.add(AdventureModes.Commander); + AdventureModes.Commander.setSelectionName(colorIdLabel); + AdventureModes.Commander.setModes(colorNames); + String[] modeNames = new String[modes.size]; int constructedIndex = -1; @@ -205,6 +218,9 @@ public class NewGameScene extends MenuScene { }); } + // class field + private RewardActor previewActor; + private static NewGameScene object; public static NewGameScene instance() { @@ -302,7 +318,6 @@ public class NewGameScene extends MenuScene { @Override public void enter() { updateAvatar(); - if (Forge.createNewAdventureMap) { FModel.getPreferences().setPref(ForgePreferences.FPref.UI_ENABLE_MUSIC, false); WorldSave.generateNewWorld(selectedName.getText(), @@ -451,6 +466,9 @@ public class NewGameScene extends MenuScene { case Custom: summaryText.append("Mode: Custom\n\nChoose your own preconstructed deck. Enemies can receive a random genetic AI deck (difficult).\n\nWarning: This will make encounter difficulty vary wildly from the developers' intent"); break; + case Commander: + summaryText.append("Mode: Commander\n\nYou will given a preconstructed commander deck based on the chosen color theme to start the playthrough.\n\nGood luck on your quest of creating a coherent deck that can win consistently and defeat the bosses."); + break; default: summaryText.append("No summary available for your this game mode."); break; diff --git a/forge-gui-mobile/src/forge/adventure/util/AdventureModes.java b/forge-gui-mobile/src/forge/adventure/util/AdventureModes.java index 94ce2af5251..42599827ef7 100644 --- a/forge-gui-mobile/src/forge/adventure/util/AdventureModes.java +++ b/forge-gui-mobile/src/forge/adventure/util/AdventureModes.java @@ -8,7 +8,8 @@ public enum AdventureModes { Constructed(Forge.getLocalizer().getMessage("lblConstructed")), Chaos("[GOLD]"+Forge.getLocalizer().getMessage("lblChaos")), Pile(Forge.getLocalizer().getMessage("lblPile")), - Custom(Forge.getLocalizer().getMessage("lblCustom")); + Custom(Forge.getLocalizer().getMessage("lblCustom")), + Commander(Forge.getLocalizer().getMessage("lblCommander")); private final String name; private String selectionName; diff --git a/forge-gui-mobile/src/forge/adventure/util/Config.java b/forge-gui-mobile/src/forge/adventure/util/Config.java index ec37458d516..8eb01a319e7 100644 --- a/forge-gui-mobile/src/forge/adventure/util/Config.java +++ b/forge-gui-mobile/src/forge/adventure/util/Config.java @@ -255,6 +255,12 @@ public class Config { return CardUtil.getDeck(entry.value, false, false, "", false, false); } } + case Commander: + for (ObjectMap.Entry entry : difficultyData.commanderDecks) { + if (ColorSet.fromNames(entry.key.toCharArray()).getColor() == color.getColor()) { + return CardUtil.getDeck(entry.value, false, false, "", false, false); + } + }; } return null; } diff --git a/forge-gui-mobile/src/forge/adventure/util/RewardActor.java b/forge-gui-mobile/src/forge/adventure/util/RewardActor.java index 783fe209977..cbf5aa299f3 100644 --- a/forge-gui-mobile/src/forge/adventure/util/RewardActor.java +++ b/forge-gui-mobile/src/forge/adventure/util/RewardActor.java @@ -33,6 +33,7 @@ import forge.Graphics; import forge.ImageKeys; import forge.StaticData; import forge.adventure.data.ItemData; +import forge.adventure.player.AdventurePlayer; import forge.adventure.scene.RewardScene; import forge.adventure.scene.Scene; import forge.adventure.scene.UIScene; @@ -925,6 +926,16 @@ public class RewardActor extends Actor implements Disposable, ImageFetcher.Callb getColor().a = 0.5f; } + private static boolean inCollectionLike(PaperCard pc) { + var coll = AdventurePlayer.current().getCollectionCards(true).toFlatList(); + String name = pc.getName(); + for (PaperCard c : coll) { + if (c.equals(pc) || c.getName().equals(name)) + return true; + } + return false; + } + @Override public void act(float delta) { super.act(delta); @@ -943,8 +954,15 @@ public class RewardActor extends Actor implements Disposable, ImageFetcher.Callb addListener(tooltip); } } - if (autoSell != null && !autoSell.isVisible() && flipProcess == 1) + if (autoSell != null && !autoSell.isVisible() && flipProcess == 1) { autoSell.setVisible(true); + if (AdventurePlayer.current().getAdventureMode() == AdventureModes.Commander) { + PaperCard pc = reward.getCard(); + if (pc != null) { + setAutoSell(inCollectionLike(pc)); + } + } + } // flipProcess=(float)Gdx.input.getX()/ (float)Gdx.graphics.getWidth(); } diff --git a/forge-gui-mobile/src/forge/adventure/world/WorldSave.java b/forge-gui-mobile/src/forge/adventure/world/WorldSave.java index ecb951c1f86..4119f15cf9e 100644 --- a/forge-gui-mobile/src/forge/adventure/world/WorldSave.java +++ b/forge-gui-mobile/src/forge/adventure/world/WorldSave.java @@ -132,8 +132,10 @@ public class WorldSave { currentSave.pointOfInterestChanges.clear(); boolean chaos = mode == AdventureModes.Chaos; boolean custom = mode == AdventureModes.Custom; + Deck starterDeck = Config.instance().starterDeck(startingColorIdentity, diff, mode, customDeckIndex, starterEdition); - currentSave.player.create(name, starterDeck, male, race, avatarIndex, chaos, custom, diff); + currentSave.player.create(name, starterDeck, male, race, avatarIndex, chaos, custom, diff, mode); + currentSave.player.setWorldPosY((int) (currentSave.world.getData().playerStartPosY * currentSave.world.getData().height * currentSave.world.getTileSize())); currentSave.player.setWorldPosX((int) (currentSave.world.getData().playerStartPosX * currentSave.world.getData().width * currentSave.world.getTileSize())); currentSave.onLoadList.emit(); diff --git a/forge-gui/res/adventure/common/config.json b/forge-gui/res/adventure/common/config.json index 09f97b41f22..bd1197692e1 100644 --- a/forge-gui/res/adventure/common/config.json +++ b/forge-gui/res/adventure/common/config.json @@ -132,6 +132,13 @@ "R":"decks/starter/pile_red_e.json", "G":"decks/starter/pile_green_e.json" }, + "commanderDecks": { + "W":"decks/starter/commander/white_01.dck", + "B":"decks/starter/commander/black_01.dck", + "U":"decks/starter/commander/blue_01.dck", + "R":"decks/starter/commander/red_01.dck", + "G":"decks/starter/commander/green_01.dck" + }, "startItems": [ "Manasight Amulet", "Leather Boots" @@ -170,6 +177,13 @@ "R":"decks/starter/pile_red_n.json", "G":"decks/starter/pile_green_n.json" }, + "commanderDecks": { + "W":"decks/starter/commander/white_01.dck", + "B":"decks/starter/commander/black_01.dck", + "U":"decks/starter/commander/blue_01.dck", + "R":"decks/starter/commander/red_01.dck", + "G":"decks/starter/commander/green_01.dck" + }, "startItems": [ "Leather Boots" ] @@ -205,6 +219,13 @@ "U":"decks/starter/pile_blue_h.json", "R":"decks/starter/pile_red_h.json", "G":"decks/starter/pile_green_h.json" + }, + "commanderDecks": { + "W":"decks/starter/commander/white_01.dck", + "B":"decks/starter/commander/black_01.dck", + "U":"decks/starter/commander/blue_01.dck", + "R":"decks/starter/commander/red_01.dck", + "G":"decks/starter/commander/green_01.dck" } },{ "name": "Insane", @@ -238,6 +259,13 @@ "U":"decks/starter/pile_blue_h.json", "R":"decks/starter/pile_red_h.json", "G":"decks/starter/pile_green_h.json" + }, + "commanderDecks": { + "W":"decks/starter/commander/white_01.dck", + "B":"decks/starter/commander/black_01.dck", + "U":"decks/starter/commander/blue_01.dck", + "R":"decks/starter/commander/red_01.dck", + "G":"decks/starter/commander/green_01.dck" } } ], diff --git a/forge-gui/res/adventure/common/decks/starter/commander/black_01.dck b/forge-gui/res/adventure/common/decks/starter/commander/black_01.dck new file mode 100644 index 00000000000..d3fd549e5eb --- /dev/null +++ b/forge-gui/res/adventure/common/decks/starter/commander/black_01.dck @@ -0,0 +1,81 @@ +[metadata] +Name=Starter Commander - Black +[Avatar] + +[Commander] +1 Tymaret, Chosen from Death|thb|1 + +[Main] +1 Blood Artist|inr|1 +1 Burglar Rat|fdn|1 +1 Changeling Outcast|otc|1 +1 Creeping Bloodsucker|j25|1 +1 Gifted Aetherborn|jmp|1 +1 Gravedigger|m20|1 +1 Graveshifter|moc|1 +1 Gray Merchant of Asphodel|dsc|1 +1 Infestation Sage|fdn|1 +1 Marauding Blight-Priest|fdn|1 +1 Vampire Nighthawk|fdn|1 +1 Vampire of the Dire Moon|m20|1 +1 Vengeful Bloodwitch|fdn|1 +1 Vindictive Vampire|clu|1 +1 Yargle, Glutton of Urborg|cmm|1 +1 Zulaport Cutthroat|clb|1 +1 Arbiter of Woe|fdn|1 +1 Dockside Chef|neo|1 +1 Dusk Legion Zealot|lcc|1 +1 Faerie Dreamthief|woe|1 +1 Foulmire Knight|moc|1 +1 Indulgent Tormentor|ima|1 +1 Morbid Opportunist|tdc|1 +1 Novice Occultist|mid|1 +1 Phyrexian Rager|moc|1 +1 Refurbished Familiar|mh3|1 +1 Silversmote Ghoul|c21|1 +1 Troll of Khazad-dûm|ltr|1 +1 Vampire Gourmand|fdn|1 +1 Command Beacon|tdc|1 +1 Command Tower|eoc|1 +37 Swamp|tla|1 +1 Lightning Greaves|tdc|1 +1 Swiftfoot Boots|tdc|1 +1 Arcane Signet|eoc|1 +1 Burnished Hart|dsc|1 +1 Commander's Sphere|drc|1 +1 Darksteel Ingot|otc|1 +1 Manalith|m19|1 +1 Mind Stone|dsc|1 +1 Patchwork Banner|blb|1 +1 Prismatic Lens|otc|1 +1 Relic of Legends|dmu|1 +1 Sol Ring|eoc|1 +1 Solemn Simulacrum|tdc|1 +1 Thought Vessel|dsc|1 +1 Accursed Marauder|mh3|1 +1 Bake into a Pie|fdn|1 +1 Banewhip Punisher|hou|1 +1 Black Dragon|afr|1 +1 Doom Blade|ima|1 +1 Elspeth's Nightmare|thb|1 +1 Hero's Downfall|fdn|1 +1 Murder|dsk|1 +1 Nekrataal|ema|1 +1 Ob Nixilis, the Hate-Twisted|war|1 +1 Ravenous Chupacabra|mkc|1 +1 Sephiroth's Intervention|fin|1 +1 Stinkweed Imp|dvd|1 +1 Withering Torment|dsk|1 +1 Commander's Plate|cmr|1 +1 Path of Ancestry|tdc|1 +1 Campfire|cmm|1 + +[Sideboard] + +[Planes] + +[Schemes] + +[Conspiracy] + +[Dungeon] diff --git a/forge-gui/res/adventure/common/decks/starter/commander/blue_01.dck b/forge-gui/res/adventure/common/decks/starter/commander/blue_01.dck new file mode 100644 index 00000000000..aa2c7258dc7 --- /dev/null +++ b/forge-gui/res/adventure/common/decks/starter/commander/blue_01.dck @@ -0,0 +1,81 @@ +[metadata] +Name=Starter Commander - Blue +[Avatar] + +[Commander] +1 Callaphe, Beloved of the Sea|thb|1 + +[Main] +1 Brineborn Cutthroat|fdn|1 +1 Cloudfin Raptor|rvr|1 +1 Air Elemental|m20|1 +1 Augury Raven|khm|1 +1 Aven Fateshaper|dmr|1 +1 Aven Reedstalker|hou|1 +1 Baithook Angler|mid|1 +1 Bay Falcon|mir|1 +1 Boreal Elemental|m20|1 +1 Curio Vendor|kld|1 +1 Foul Watcher|mh2|1 +1 Gossamer Phantasm|tsr|1 +1 Ancestral Reminiscence|lci|1 +1 Arrester's Admonition|rna|1 +1 Artificer's Assistant|dom|1 +1 Artificer's Epiphany|ddu|1 +1 Azure Mage|mm3|1 +1 Behold the Multiverse|khm|1 +1 Bellowing Crier|blb|1 +1 Birthday Escape|ltr|1 +1 Chart a Course|fdn|1 +1 Cloudkin Seer|clu|1 +1 Cryogen Relic|eoe|1 +1 Curate|mkc|1 +1 Spectral Sailor|fdn|1 +1 Delver of Secrets|inr|1 +1 Spyglass Siren|lci|1 +1 Command Beacon|tdc|1 +1 Command Tower|eoc|1 +37 Island|tla|1 +1 Aegis Turtle|fdn|1 +1 Cogwork Wrestler|lci|1 +1 Lightning Greaves|tdc|1 +1 Swiftfoot Boots|tdc|1 +1 Animating Faerie|eld|1 +1 Aven Wind Mage|gn2|1 +1 Aarakocra Sneak|clb|1 +1 Arcane Signet|eoc|1 +1 Burnished Hart|dsc|1 +1 Commander's Sphere|drc|1 +1 Darksteel Ingot|otc|1 +1 Manalith|m19|1 +1 Mind Stone|dsc|1 +1 Patchwork Banner|blb|1 +1 Prismatic Lens|otc|1 +1 Relic of Legends|dmu|1 +1 Sol Ring|eoc|1 +1 Solemn Simulacrum|tdc|1 +1 Thought Vessel|dsc|1 +1 Academy Journeymage|dom|1 +1 Aether Adept|ddm|1 +1 Aetherize|fdn|1 +1 Angler Drake|akh|1 +1 Aven Augur|fut|1 +1 Banishing Knack|eve|1 +1 Beluna's Gatekeeper|woe|1 +1 Bounce Off|dft|1 +1 Clutch of Currents|bfz|1 +1 Cunning Geysermage|znr|1 +1 Exclusion Mage|fdn|1 +1 Commander's Plate|cmr|1 +1 Path of Ancestry|tdc|1 +1 Campfire|cmm|1 + +[Sideboard] + +[Planes] + +[Schemes] + +[Conspiracy] + +[Dungeon] diff --git a/forge-gui/res/adventure/common/decks/starter/commander/green_01.dck b/forge-gui/res/adventure/common/decks/starter/commander/green_01.dck new file mode 100644 index 00000000000..d82e41cf962 --- /dev/null +++ b/forge-gui/res/adventure/common/decks/starter/commander/green_01.dck @@ -0,0 +1,81 @@ +[metadata] +Name=Starter Commander - Green +[Avatar] + +[Commander] +1 Renata, Called to the Hunt|thb|1 + +[Main] +1 Bashful Beastie|dsk|1 +1 Beanstalk Giant|dsc|1 +1 Bitterbow Sharpshooters|hou|1 +1 Candlelit Cavalry|mid|1 +1 Coliseum Behemoth|fin|1 +1 Colossal Dreadmaw|m21|1 +1 Crash of Rhino Beetles|cmm|1 +1 Disciple of Freyalise|mh3|1 +1 Drove of Elves|cma|1 +1 Duskdale Wurm|ima|1 +1 Generous Ent|ltr|1 +1 Adventure Awaits|znr|1 +1 Carven Caryatid|tdc|1 +1 Earthshaker Dreadmaw|lci|1 +1 Elven Farsight|ltr|1 +1 Fecundity|uma|1 +1 Joraga Visionary|znr|1 +1 Masked Admirers|mma|1 +1 Mild-Mannered Librarian|fdn|1 +1 Owlbear Shepherd|clb|1 +1 Summon: Fenrir|fin|1 +1 Command Beacon|tdc|1 +1 Command Tower|eoc|1 +37 Forest|tla|1 +1 Lightning Greaves|tdc|1 +1 Swiftfoot Boots|tdc|1 +1 Arcane Signet|eoc|1 +1 Boseiju Reaches Skyward|neo|1 +1 Burnished Hart|dsc|1 +1 Commander's Sphere|drc|1 +1 Darksteel Ingot|otc|1 +1 Druid of the Cowl|fdn|1 +1 Elvish Mystic|cmm|1 +1 Golden Hind|jou|1 +1 Goobbue Gardener|fin|1 +1 Hardbristle Bandit|otj|1 +1 Ilysian Caryatid|cmm|1 +1 Leafkin Druid|ncc|1 +1 Llanowar Elves|fdn|1 +1 Manalith|m19|1 +1 Mind Stone|dsc|1 +1 Overgrown Battlement|roe|1 +1 Patchwork Banner|blb|1 +1 Prismatic Lens|otc|1 +1 Relic of Legends|dmu|1 +1 Sol Ring|eoc|1 +1 Solemn Simulacrum|tdc|1 +1 Thought Vessel|dsc|1 +1 Vine Trellis|gvl|1 +1 Affectionate Indrik|fdn|1 +1 Aggressive Instinct|gs1|1 +1 Atzocan Archer|xln|1 +1 Bite Down|fdn|1 +1 Bushwhack|fdn|1 +1 Clash of the Eikons|fin|1 +1 Ent's Fury|ltr|1 +1 Hunter's Talent|blb|1 +1 Kapow!|spm|1 +1 Longstalk Brawl|blb|1 +1 Rabid Bite|blb|1 +1 Commander's Plate|cmr|1 +1 Path of Ancestry|tdc|1 +1 Campfire|cmm|1 + +[Sideboard] + +[Planes] + +[Schemes] + +[Conspiracy] + +[Dungeon] diff --git a/forge-gui/res/adventure/common/decks/starter/commander/red_01.dck b/forge-gui/res/adventure/common/decks/starter/commander/red_01.dck new file mode 100644 index 00000000000..3b52f6e4880 --- /dev/null +++ b/forge-gui/res/adventure/common/decks/starter/commander/red_01.dck @@ -0,0 +1,81 @@ +[metadata] +Name=Starter Commander - Red +[Avatar] + +[Commander] +1 Anax, Hardened in the Forge|cmm|1 + +[Main] +1 Viashino Pyromancer|fdn|1 +1 Bolrac-Clan Basher|mkm|1 +1 Charging Monstrosaur|xln|1 +1 Sabotender|fin|1 +1 Akoum Hellhound|znr|1 +1 Goblin Bushwhacker|zen|1 +1 Skitter of Lizards|cns|1 +1 Alania's Pathmaker|blb|1 +1 Anep, Vizier of Hazoret|j25|1 +1 Battlefield Scavenger|akh|1 +1 Blazing Crescendo|one|1 +1 Boundary Lands Ranger|woe|1 +1 Burning-Tree Vandal|rvr|1 +1 Cleon, Merry Champion|j25|1 +1 Clockwork Percussionist|dsk|1 +1 Dragon Mage|fdn|1 +1 Experimental Synthesizer|neo|1 +1 Fissure Wizard|znr|1 +1 Goblin Researcher|j25|1 +1 Heroes' Hangout|spm|1 +1 Irascible Wolverine|otj|1 +1 Light Up the Stage|dsc|1 +1 Tuskeri Firewalker|khm|1 +1 Voldaren Epicure|inr|1 +1 Command Beacon|tdc|1 +1 Command Tower|eoc|1 +37 Mountain|tle|1 +1 Belligerent Whiptail|bfz|1 +1 Lightning Greaves|tdc|1 +1 Swiftfoot Boots|tdc|1 +1 Battle Squadron|ddt|1 +1 Most Valuable Slayer|dsk|1 +1 Arcane Signet|eoc|1 +1 Burnished Hart|dsc|1 +1 Commander's Sphere|drc|1 +1 Darksteel Ingot|otc|1 +1 Manalith|m19|1 +1 Mind Stone|dsc|1 +1 Patchwork Banner|blb|1 +1 Prismatic Lens|otc|1 +1 Relic of Legends|dmu|1 +1 Sol Ring|eoc|1 +1 Solemn Simulacrum|tdc|1 +1 Thought Vessel|dsc|1 +1 Young Red Dragon|clb|1 +1 Abrade|tdc|1 +1 Fear of Being Hunted|dsk|1 +1 Fire Prophecy|j25|1 +1 Flame Slash|plst|1 +1 Forge Devil|jmp|1 +1 Lava Coil|2x2|1 +1 Lightning Axe|inr|1 +1 Lightning Strike|tla|1 +1 Magma Spray|plst|1 +1 Rolling Thunder|plst|1 +1 Shatter|plst|1 +1 Shock|spm|1 +1 Smash to Smithereens|plst|1 +1 Stoke the Flames|mom|1 +1 Goblin Instigator|moc|1 +1 Commander's Plate|cmr|1 +1 Path of Ancestry|tdc|1 +1 Campfire|cmm|1 + +[Sideboard] + +[Planes] + +[Schemes] + +[Conspiracy] + +[Dungeon] diff --git a/forge-gui/res/adventure/common/decks/starter/commander/white_01.dck b/forge-gui/res/adventure/common/decks/starter/commander/white_01.dck new file mode 100644 index 00000000000..8d02815bd7d --- /dev/null +++ b/forge-gui/res/adventure/common/decks/starter/commander/white_01.dck @@ -0,0 +1,81 @@ +[metadata] +Name=Starter Commander - White +[Avatar] + +[Commander] +1 Daxos, Blessed by the Sun|plst|1 + +[Main] +1 Accorder Paladin|mbs|1 +1 Ajani's Pridemate|fdn|1 +1 Akroan Jailer|ori|1 +1 Alpine Watchdog|m21|1 +1 Battlefield Raptor|khm|1 +1 Cheeky House-Mouse|woe|1 +1 Doomed Traveler|clu|1 +1 Faerie Guidemother|eld|1 +1 Goldnight Commander|tdc|1 +1 Healer's Hawk|fdn|1 +1 Hinterland Sanctifier|fdn|1 +1 Lunarch Veteran|inr|1 +1 Selfless Savior|m21|1 +1 Stonehorn Dignitary|m12|1 +1 Thraben Watcher|mh2|1 +1 Valiant Changeling|clb|1 +1 Errand-Rider of Gondor|ltr|1 +1 Helpful Hunter|fdn|1 +1 Inspiring Overseer|fdn|1 +1 Mentor of the Meek|inr|1 +1 Outlaw Medic|otj|1 +1 Roving Harper|clb|1 +1 Rumor Gatherer|clb|1 +1 Salt Road Packbeast|tdm|1 +1 Search Party Captain|mid|1 +1 Spirited Companion|cmm|1 +1 The Fall of Lord Konda|neo|1 +1 Wall of Omens|tdc|1 +1 Command Beacon|tdc|1 +1 Command Tower|eoc|1 +37 Plains|tle|1 +1 Lightning Greaves|tdc|1 +1 Swiftfoot Boots|tdc|1 +1 Arcane Signet|eoc|1 +1 Burnished Hart|dsc|1 +1 Commander's Sphere|drc|1 +1 Darksteel Ingot|otc|1 +1 Manalith|m19|1 +1 Mind Stone|dsc|1 +1 Patchwork Banner|blb|1 +1 Prismatic Lens|otc|1 +1 Relic of Legends|dmu|1 +1 Sol Ring|eoc|1 +1 Solemn Simulacrum|tdc|1 +1 Thought Vessel|dsc|1 +1 Alabaster Host Intercessor|mom|1 +1 Angelic Edict|jmp|1 +1 Baffling End|rix|1 +1 Banish from Edoras|ltr|1 +1 Banisher Priest|moc|1 +1 Borrowed Time|mid|1 +1 Cathar Commando|inr|1 +1 Celestial Purge|mm2|1 +1 Circle of Confinement|vow|1 +1 Citizen's Arrest|dmu|1 +1 Citizen's Crowbar|snc|1 +1 Githzerai Monk|clb|1 +1 Palace Jailer|cmm|1 +1 Commander's Plate|cmr|1 +1 Path of Ancestry|tdc|1 +1 Campfire|cmm|1 +1 Inspired Charge|mom|1 +1 Coordinated Charge|iko|1 + +[Sideboard] + +[Planes] + +[Schemes] + +[Conspiracy] + +[Dungeon] From ab3c66fd0a6a1250ffc1eb309063273105b15ea8 Mon Sep 17 00:00:00 2001 From: Agetian Date: Sat, 1 Nov 2025 20:26:38 +0300 Subject: [PATCH 215/230] Add puzzle PS_SPM3. (#9060) * - Added puzzle PS_SPM3. * - Minor mistype fix. --- .../forge/adventure/scene/NewGameScene.java | 2 +- forge-gui/res/puzzle/PS_SPM3.pzl | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 forge-gui/res/puzzle/PS_SPM3.pzl diff --git a/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java b/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java index 8cae52997a3..184899e904e 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/NewGameScene.java @@ -467,7 +467,7 @@ public class NewGameScene extends MenuScene { summaryText.append("Mode: Custom\n\nChoose your own preconstructed deck. Enemies can receive a random genetic AI deck (difficult).\n\nWarning: This will make encounter difficulty vary wildly from the developers' intent"); break; case Commander: - summaryText.append("Mode: Commander\n\nYou will given a preconstructed commander deck based on the chosen color theme to start the playthrough.\n\nGood luck on your quest of creating a coherent deck that can win consistently and defeat the bosses."); + summaryText.append("Mode: Commander\n\nYou will be given a preconstructed commander deck based on the chosen color theme to start the playthrough.\n\nGood luck on your quest of creating a coherent deck that can win consistently and defeat the bosses."); break; default: summaryText.append("No summary available for your this game mode."); diff --git a/forge-gui/res/puzzle/PS_SPM3.pzl b/forge-gui/res/puzzle/PS_SPM3.pzl new file mode 100644 index 00000000000..9f2ccaa2cab --- /dev/null +++ b/forge-gui/res/puzzle/PS_SPM3.pzl @@ -0,0 +1,20 @@ +[metadata] +Name:Possibility Storm - Marvel's Spider-Man #03 +URL:https://i2.wp.com/www.possibilitystorm.com/wp-content/uploads/2025/10/latest-scaled.jpg?ssl=1 +Goal:Win +Turns:1 +Difficulty:Rare +Description:Win this turn. Ensure your solution satisfies all possible opponent decisions. Your opponent has no cards in hand. Good luck! NOTE: In order to correctly set up the three Mysterio, Master of Illusion's tokens, an extra Mysterio's Phantasm is added to the battlefield temporarily and is immediately destroyed once the battlefield is set up. Assume this extra Mysterio's Phantasm in your graveyard is irrelevant to the puzzle. +[state] +turn=1 +activeplayer=p0 +activephase=MAIN1 +p0life=5 +p0hand=Spider-Sense;Splash Portal;The Apprentice's Folly;Enduring Courage +p0battlefield=Unyielding Gatekeeper|FaceDown;Mysterio's Phantasm|Id:1;Mysterio, Master of Illusion|ExecuteScript:TrigToken;Mysterio's Phantasm;Kavaron, Memorial World|Counters:CHARGE=6;Inspiring Vantage;Inspiring Vantage;Inspiring Vantage;Island;Island;Island +# FIXME: This is needed to set up the third Illusion token. +p0precast=Lightning Bolt->1 +p1life=21 +p1graveyard=Electro, Assaulting Battery;Electro, Assaulting Battery +p1battlefield=Electro, Assaulting Battery;Impostor Syndrome;Mountain;Mountain;Mountain;Mountain;Mountain +removesummoningsickness=true From 586e30af0908c49a1d53d3eab92876acadad6946 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sat, 1 Nov 2025 17:46:42 +0000 Subject: [PATCH 216/230] Update the_rise_of_sozin_fire_lord_sozin.txt Closes #9059 --- .../res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui/res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt b/forge-gui/res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt index 5c28f921a82..4d15931986a 100644 --- a/forge-gui/res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt +++ b/forge-gui/res/cardsfolder/t/the_rise_of_sozin_fire_lord_sozin.txt @@ -22,7 +22,7 @@ K:Menace K:Firebending:3 T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigImmediateTrig | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, you may pay {X}. When you do, put any number of target creature cards with total mana value X or less from that player's graveyard onto the battlefield under your control. SVar:TrigImmediateTrig:AB$ ImmediateTrigger | Cost$ X | Execute$ TrigChangeZone | TriggerDescription$ When you do, put any number of target creature cards with total mana value X or less from that player's graveyard onto the battlefield under your control. -SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TargetMin$ 0 | TargetMax$ Y | ValidTgts$ Creature.OwnedBy TriggeredTarget | TgtPrompt$ Select any number of target creature cards with total mana value X or less from the damaged player's graveyard | MaxTotalTargetCMC$ X | GainControl$ True +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TargetMin$ 0 | TargetMax$ Y | ValidTgts$ Creature.OwnedBy Spawner>TriggeredTarget | TgtPrompt$ Select any number of target creature cards with total mana value X or less from the damaged player's graveyard | MaxTotalTargetCMC$ X | GainControl$ True SVar:X:Count$xPaid -SVar:Y:Count$ValidGraveyard Creature.OwnedBy TriggeredTarget +SVar:Y:Count$ValidGraveyard Creature.OwnedBy Spawner>TriggeredTarget Oracle:Menace, firebending 3 (Whenever this creature attacks, add {R}{R}{R}. This mana lasts until end of combat.)\nWhenever Fire Lord Sozin deals combat damage to a player, you may pay {X}. When you do, put any number of target creature cards with total mana value X or less from that player's graveyard onto the battlefield under your control. From 6bf288af38af5ecaf6aee62d4c724f2266144b31 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sat, 1 Nov 2025 20:39:58 +0000 Subject: [PATCH 217/230] Update trigger zones for Noctis card text --- forge-gui/res/cardsfolder/upcoming/noctis_heir_apparent.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/noctis_heir_apparent.txt b/forge-gui/res/cardsfolder/upcoming/noctis_heir_apparent.txt index d3605fb39c6..c48a92a5b46 100644 --- a/forge-gui/res/cardsfolder/upcoming/noctis_heir_apparent.txt +++ b/forge-gui/res/cardsfolder/upcoming/noctis_heir_apparent.txt @@ -2,7 +2,7 @@ Name:Noctis, Heir Apparent ManaCost:W U B Types:Legendary Creature Human Noble PT:2/3 -T:Mode$ ChangesZone | Phase$ BeginCombat->EndCombat | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | Execute$ EquipmentSelection | OptionalDecider$ You | TriggerDescription$ Whenever a creature you control enters during combat, you may attach target Equipment you control to target creature you control. +T:Mode$ ChangesZone | Phase$ BeginCombat->EndCombat | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ EquipmentSelection | OptionalDecider$ You | TriggerDescription$ Whenever a creature you control enters during combat, you may attach target Equipment you control to target creature you control. SVar:EquipmentSelection:DB$ Pump | ValidTgts$ Equipment.YouCtrl | TgtPrompt$ Select target equipment you control | SubAbility$ DBAttach | StackDescription$ None SVar:DBAttach:DB$ Attach | Object$ ParentTarget | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control. A:AB$ ChangeZone | PrecostDesc$ Warp-Strike — | Cost$ 3 | Defined$ Self | Origin$ Battlefield | Destination$ Exile | SubAbility$ DelTrig | RememberChanged$ True | SpellDescription$ Exile NICKNAME. Return it to the battlefield under its owner's control tapped and attacking at the beginning of that player's next declare attackers step. It can't be blocked that combat. From 15fca4243d33da0a31a9f86ecc728626b96b4b07 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 2 Nov 2025 11:50:16 +0100 Subject: [PATCH 218/230] Fix reflected only checking activated mana abilities (#9058) --- .../java/forge/game/ability/AbilityUtils.java | 2 +- .../src/main/java/forge/game/card/CardUtil.java | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) 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 828dfbf6d60..04003aa5d43 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1340,7 +1340,7 @@ public class AbilityUtils { resolvePreAbilities(sa, game); // count times ability resolves this turn - if (!sa.isWrapper()) { + if (!sa.isWrapper() && sa.isAbility()) { final Card host = sa.getHostCard(); if (host != null) { host.addAbilityResolved(sa); diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index cd393a0d8c2..829845df1a4 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -34,6 +34,7 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityPredicates; import forge.game.spellability.TargetRestrictions; +import forge.game.trigger.Trigger; import forge.game.zone.ZoneType; import forge.util.TextUtil; import forge.util.collect.FCollection; @@ -304,20 +305,26 @@ public final class CardUtil { } else if (reflectProperty.equals("Produce")) { final FCollection abilities = new FCollection<>(); for (final Card c : cards) { - abilities.addAll(c.getManaAbilities()); + abilities.addAll(c.getSpellAbilities()); + for (Trigger trig : c.getTriggers()) { + abilities.add(trig.ensureAbility()); + } } final List reflectAbilities = Lists.newArrayList(); for (final SpellAbility ab : abilities) { + if (ab.isSpell()) { + continue; + } if (maxChoices == colors.size()) { break; } - if (ab.getApi() == ApiType.ManaReflected) { + // Recursion! Set Activator to controller for appropriate valid comparison + ab.setActivatingPlayer(ab.getHostCard().getController()); + if (ab.getApi() == ApiType.ManaReflected && !"Produced".equals(ab.getParam("ReflectProperty"))) { if (!parents.contains(ab.getHostCard())) { - // Recursion! Set Activator to controller for appropriate valid comparison - ab.setActivatingPlayer(ab.getHostCard().getController()); reflectAbilities.add(ab); parents.add(ab.getHostCard()); } From e33ddee5bfd87093c1aacb1aae043fd2be9b66ea Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Sun, 2 Nov 2025 15:59:16 +0100 Subject: [PATCH 219/230] Agna Qel's (TLA leak) (#9062) * Create mai_jaded_edge.txt * Create iroh_tea_master.txt * Update zedruu_the_greathearted.txt * Update zedruus_lantern.txt * Modify Agna Qel'a card attributes Updated the land card description and abilities. --- .../common/custom_cards/zedruus_lantern.txt | 2 +- .../res/cardsfolder/upcoming/agna_qela.txt | 10 ++++++++++ .../cardsfolder/upcoming/iroh_tea_master.txt | 17 +++++++++++++++++ .../res/cardsfolder/upcoming/mai_jaded_edge.txt | 8 ++++++++ .../cardsfolder/z/zedruu_the_greathearted.txt | 2 +- 5 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/agna_qela.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/iroh_tea_master.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/mai_jaded_edge.txt diff --git a/forge-gui/res/adventure/common/custom_cards/zedruus_lantern.txt b/forge-gui/res/adventure/common/custom_cards/zedruus_lantern.txt index 4174f444841..50c67ddcf96 100644 --- a/forge-gui/res/adventure/common/custom_cards/zedruus_lantern.txt +++ b/forge-gui/res/adventure/common/custom_cards/zedruus_lantern.txt @@ -4,7 +4,7 @@ Types:Artifact Equipment T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Command | Execute$ GainLife | TriggerDescription$ At the beginning of your upkeep, you gain X life and draw X cards, where X is the number of permanents you own that your opponents control. SVar:GainLife:DB$ GainLife | LifeAmount$ X | SubAbility$ DBDraw SVar:DBDraw:DB$ Draw | NumCards$ X -SVar:X:Count$Valid Permanent.YouOwn+OwnerDoesntControl +SVar:X:Count$Valid Permanent.YouOwn+OppCtrl A:AB$ MakeCard | Cost$ 3 PayShards<2> | Conjure$ True | ActivationLimit$ 1 | Zone$ Hand | ActivationZone$ Command | AtRandom$ True | SubAbility$ Eject | Spellbook$ Donate,Paradox Haze,Akroan Horse,Ghostly Prison,Howling Mine,Decanter of Endless Water,Solemn Simulacrum,Gilded Drake,Burnished Hart,Sudden Substitution | SpellDescription$ Conjure a card from Zedruu's spellbook into your hand. Activate this ability only once each game. Exile Zedruu's Lantern. SVar:Eject:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile Oracle:At the beginning of your upkeep, you gain X life and draw X cards, where X is the number of permanents you own that your opponents control.\n{M}{M},{3} Conjure a card from Zedruu's spellbook into your hand. Activate this ability only once each game. Exile Zedruu's Lantern. diff --git a/forge-gui/res/cardsfolder/upcoming/agna_qela.txt b/forge-gui/res/cardsfolder/upcoming/agna_qela.txt new file mode 100644 index 00000000000..241d7b4f10c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/agna_qela.txt @@ -0,0 +1,10 @@ +Name:Agna Qel'a +ManaCost:no cost +Types:Land +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplaceWith$ LandTapped | ReplacementResult$ Updated | Description$ This land enters tapped unless you control a basic land. +SVar:LandTapped:DB$ Tap | Defined$ Self | ETB$ True | ConditionPresent$ Land.Basic+YouCtrl | ConditionCompare$ EQ0 +A:AB$ Mana | Cost$ T | Produced$ U | SpellDescription$ Add {U}. +A:AB$ Draw | Cost$ 2 U T | SpellDescription$ Draw a card, then discard a card. | SubAbility$ DBDiscard +SVar:DBDiscard:DB$ Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 1 +DeckHas:Ability$Discard +Oracle:This land enters tapped unless you control a basic land.\n{T}: Add {U}.\n{2}{U}, {T}: Draw a card, then discard a card. diff --git a/forge-gui/res/cardsfolder/upcoming/iroh_tea_master.txt b/forge-gui/res/cardsfolder/upcoming/iroh_tea_master.txt new file mode 100644 index 00000000000..398178032c5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/iroh_tea_master.txt @@ -0,0 +1,17 @@ +Name:Iroh, Tea Master +ManaCost:1 R W +Types:Legendary Creature Human Citizen Ally +PT:2/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigFood | TriggerDescription$ When NICKNAME enters, create a Food token. +SVar:TrigFood:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_food_sac | TokenOwner$ You +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPump | TriggerDescription$ At the beginning of combat on your turn, you may have target opponent gain control of target permanent you control. When you do, create a 1/1 white Ally creature token. Put a +1/+1 counter on that token for each permanent you own that your opponents control. +SVar:TrigPump:DB$ Pump | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | SubAbility$ DBGiveControl +SVar:DBGiveControl:DB$ GainControl | ValidTgts$ Permanent.YouCtrl | TgtPrompt$ Select target permanent you control | Defined$ Targeted | NewController$ ParentTarget | RememberControlled$ True | SubAbility$ DBImmediateTrig +SVar:DBImmediateTrig:DB$ ImmediateTrigger | Execute$ TrigToken | ConditionDefined$ Remembered | ConditionPresent$ Card | TriggerDescription$ When you do, create a 1/1 white Ally creature token. Put a +1/+1 counter on that token for each permanent you own that your opponents control. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_ally | RememberTokens$ True | ForgetOtherRemembered$ True | TokenOwner$ You | SubAbility$ DBPutCounter +SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:Count$Valid Permanent.YouOwn+OppCtrl +SVar:PlayMain1:TRUE +DeckHas:Ability$LifeGain|Token|Food +Oracle:When Iroh enters, create a Food token.\nAt the beginning of combat on your turn, you may have target opponent gain control of target permanent you control. When you do, create a 1/1 white Ally creature token. Put a +1/+1 counter on that token for each permanent you own that your opponents control. diff --git a/forge-gui/res/cardsfolder/upcoming/mai_jaded_edge.txt b/forge-gui/res/cardsfolder/upcoming/mai_jaded_edge.txt new file mode 100644 index 00000000000..08bb3d9bb0f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/mai_jaded_edge.txt @@ -0,0 +1,8 @@ +Name:Mai, Jaded Edge +ManaCost:1 R +Types:Legendary Creature Human Noble +PT:1/3 +K:Prowess +A:AB$ PutCounter | Cost$ 3 | Defined$ Self | CounterType$ Double Strike | CounterNum$ 1 | Exhaust$ True | SpellDescription$ Put a double strike counter on NICKNAME. (Activate each exhaust ability only once.) +DeckHas:Ability$Counters +Oracle:Prowess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.)\nExhaust — {3}: Put a double strike counter on Mai. (Activate each exhaust ability only once.) diff --git a/forge-gui/res/cardsfolder/z/zedruu_the_greathearted.txt b/forge-gui/res/cardsfolder/z/zedruu_the_greathearted.txt index 03ced227bf5..b487997411c 100644 --- a/forge-gui/res/cardsfolder/z/zedruu_the_greathearted.txt +++ b/forge-gui/res/cardsfolder/z/zedruu_the_greathearted.txt @@ -7,6 +7,6 @@ SVar:DBGainControl:DB$ GainControl | ValidTgts$ Permanent.YouCtrl | TgtPrompt$ S T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ GainLife | TriggerDescription$ At the beginning of your upkeep, you gain X life and draw X cards, where X is the number of permanents you own that your opponents control. SVar:GainLife:DB$ GainLife | LifeAmount$ X | SubAbility$ DBDraw SVar:DBDraw:DB$ Draw | NumCards$ X -SVar:X:Count$Valid Permanent.YouOwn+OwnerDoesntControl +SVar:X:Count$Valid Permanent.YouOwn+OppCtrl AI:RemoveDeck:All Oracle:At the beginning of your upkeep, you gain X life and draw X cards, where X is the number of permanents you own that your opponents control.\n{U}{R}{W}: Target opponent gains control of target permanent you control. From def3fa5d2311ceb73b396808138da78e2c7095fe Mon Sep 17 00:00:00 2001 From: Hans Mackowiak Date: Sun, 2 Nov 2025 17:44:54 +0100 Subject: [PATCH 220/230] build ValidTgtsDesc via Lang (#9063) * build ValidTgtsDesc via Lang * use buildValidDesc for AuraSpells * Add ValidTgtsDesc to GUI message --- forge-core/src/main/java/forge/util/Lang.java | 18 ++++ .../main/java/forge/game/CardTraitBase.java | 2 +- .../forge/game/ability/AbilityFactory.java | 85 +---------------- .../ability/effects/ChangeZoneEffect.java | 5 +- .../game/ability/effects/EarthbendEffect.java | 3 +- .../main/java/forge/game/card/CardState.java | 2 +- .../forge/game/keyword/KeywordWithType.java | 18 ++-- .../game/spellability/TargetRestrictions.java | 92 ++++++++++++++++++- .../res/cardsfolder/c/corrupted_roots.txt | 2 +- .../res/cardsfolder/m/malicious_advice.txt | 2 +- .../match/input/InputSelectTargets.java | 2 +- 11 files changed, 125 insertions(+), 106 deletions(-) diff --git a/forge-core/src/main/java/forge/util/Lang.java b/forge-core/src/main/java/forge/util/Lang.java index 67f2cf1bccd..219bee6b16e 100644 --- a/forge-core/src/main/java/forge/util/Lang.java +++ b/forge-core/src/main/java/forge/util/Lang.java @@ -2,6 +2,8 @@ package forge.util; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; + +import forge.card.CardType; import forge.util.lang.*; import org.apache.commons.lang3.StringUtils; @@ -9,6 +11,7 @@ import java.util.Collection; import java.util.List; import java.util.function.Function; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Static library containing language-related utility methods. @@ -216,4 +219,19 @@ public abstract class Lang { } return name.split(" ")[0]; } + + public String buildValidDesc(List valid, boolean multiple) { + return joinHomogenous(valid.stream().map(s -> formatValidDesc(s)).collect(Collectors.toList()), null, multiple ? "and/or" : "or"); + } + + public String formatValidDesc(String valid) { + List commonStuff = List.of( + //list of common one word non-core type ValidTgts that should be lowercase in the target prompt + "Player", "Opponent", "Card", "Spell", "Permanent" + ); + if (commonStuff.contains(valid) || CardType.isACardType(valid)) { + valid = valid.toLowerCase(); + } + return valid; + } } diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index 109d007a1ae..ff253dd111e 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -63,7 +63,7 @@ public abstract class CardTraitBase implements GameObject, IHasCardView, IHasSVa /** Keys of descriptive (text) parameters. */ private static final ImmutableList descriptiveKeys = ImmutableList.builder() .add("Description", "SpellDescription", "StackDescription", "TriggerDescription") - .add("ChangeTypeDesc") + .add("ChangeTypeDesc", "ValidTgtsDesc") .build(); /** diff --git a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java index f0c89aa25d2..04a596de7c7 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -20,7 +20,6 @@ package forge.game.ability; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import forge.card.CardStateName; -import forge.card.CardType; import forge.game.CardTraitBase; import forge.game.IHasSVars; import forge.game.ability.effects.CharmEffect; @@ -34,7 +33,6 @@ import forge.util.FileSection; import io.sentry.Breadcrumb; import io.sentry.Sentry; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -317,89 +315,8 @@ public final class AbilityFactory { } private static TargetRestrictions readTarget(Map mapParams) { - final String min = mapParams.getOrDefault("TargetMin", "1"); - final String max = mapParams.getOrDefault("TargetMax", "1"); - // TgtPrompt should only be needed for more complicated ValidTgts - String tgtWhat = mapParams.get("ValidTgts"); - final String prompt; - if (mapParams.containsKey("TgtPrompt")) { - prompt = mapParams.get("TgtPrompt"); - } else if (tgtWhat.equals("Any")) { - prompt = "Select any target"; - } else { - final String[] commonStuff = new String[] { - //list of common one word non-core type ValidTgts that should be lowercase in the target prompt - "Player", "Opponent", "Card", "Spell", "Permanent" - }; - if (Arrays.asList(commonStuff).contains(tgtWhat) || CardType.CoreType.isValidEnum(tgtWhat)) { - tgtWhat = tgtWhat.toLowerCase(); - } - prompt = "Select target " + tgtWhat; - } - - TargetRestrictions abTgt = new TargetRestrictions(prompt, mapParams.get("ValidTgts").split(","), min, max); - - if (mapParams.containsKey("TgtZone")) { - // if Targeting something not in play, this Key should be set - abTgt.setZone(ZoneType.listValueOf(mapParams.get("TgtZone"))); - } - - if (mapParams.containsKey("MaxTotalTargetCMC")) { - // only target cards up to a certain total max CMC - abTgt.setMaxTotalCMC(mapParams.get("MaxTotalTargetCMC")); - } - - if (mapParams.containsKey("MaxTotalTargetPower")) { - // only target cards up to a certain total max power - abTgt.setMaxTotalPower(mapParams.get("MaxTotalTargetPower")); - } - - // TargetValidTargeting most for Counter: e.g. target spell that targets X. - if (mapParams.containsKey("TargetValidTargeting")) { - abTgt.setSAValidTargeting(mapParams.get("TargetValidTargeting")); - } - - if (mapParams.containsKey("TargetUnique")) { - abTgt.setUniqueTargets(true); - } - if (mapParams.containsKey("TargetsWithoutSameCreatureType")) { - abTgt.setWithoutSameCreatureType(true); - } - if (mapParams.containsKey("TargetsWithSameCreatureType")) { - abTgt.setWithSameCreatureType(true); - } - if (mapParams.containsKey("TargetsWithSameCardType")) { - abTgt.setWithSameCardType(true); - } - if (mapParams.containsKey("TargetsWithSameController")) { - abTgt.setSameController(true); - } - if (mapParams.containsKey("TargetsWithDifferentControllers")) { - abTgt.setDifferentControllers(true); - } - if (mapParams.containsKey("TargetsForEachPlayer")) { - abTgt.setForEachPlayer(true); - } - if (mapParams.containsKey("TargetsWithDifferentCMC")) { - abTgt.setDifferentCMC(true); - } - if (mapParams.containsKey("TargetsWithDifferentNames")) { - abTgt.setDifferentNames(true); - } - if (mapParams.containsKey("TargetsWithEqualToughness")) { - abTgt.setEqualToughness(true); - } - if (mapParams.containsKey("TargetsAtRandom")) { - abTgt.setRandomTarget(true); - } - if (mapParams.containsKey("RandomNumTargets")) { - abTgt.setRandomNumTargets(true); - } - if (mapParams.containsKey("TargetingPlayer")) { - abTgt.setMandatory(true); - } - return abTgt; + return new TargetRestrictions(mapParams); } /** 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 6577532f699..00d296dc59d 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 @@ -4,7 +4,6 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import forge.card.CardStateName; -import forge.card.CardType; import forge.game.*; import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityKey; @@ -31,7 +30,6 @@ import org.apache.commons.lang3.tuple.Pair; import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; public class ChangeZoneEffect extends SpellAbilityEffect { @@ -119,8 +117,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { type = Lang.joinHomogenous(tgts); defined = true; } else if (sa.hasParam("ChangeType") && !sa.getParam("ChangeType").equals("Card")) { - List typeList = Arrays.stream(sa.getParam("ChangeType").split(",")).map(ct -> CardType.isACardType(ct) ? ct.toLowerCase() : ct).collect(Collectors.toList()); - type = Lang.joinHomogenous(typeList, null, num == 1 ? "or" : "and/or"); + type = Lang.getInstance().buildValidDesc(Arrays.asList(sa.getParam("ChangeType").split(",")), num != 1); } final String cardTag = type.contains("card") ? "" : " card"; diff --git a/forge-game/src/main/java/forge/game/ability/effects/EarthbendEffect.java b/forge-game/src/main/java/forge/game/ability/effects/EarthbendEffect.java index 723bf4522e9..ea7219d3cde 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/EarthbendEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/EarthbendEffect.java @@ -2,6 +2,7 @@ package forge.game.ability.effects; import java.util.Arrays; import java.util.EnumSet; +import java.util.Map; import forge.card.RemoveType; import forge.game.Game; @@ -37,7 +38,7 @@ public class EarthbendEffect extends SpellAbilityEffect { @Override public void buildSpellAbility(final SpellAbility sa) { - TargetRestrictions abTgt = new TargetRestrictions("Select target land you control", "Land.YouCtrl".split(","), "1", "1"); + TargetRestrictions abTgt = new TargetRestrictions(Map.of("ValidTgtsDesc", "land you control", "ValidTgts", "Land.YouCtrl")); sa.setTargetRestrictions(abTgt); } diff --git a/forge-game/src/main/java/forge/game/card/CardState.java b/forge-game/src/main/java/forge/game/card/CardState.java index 8080402c16d..549421395e6 100644 --- a/forge-game/src/main/java/forge/game/card/CardState.java +++ b/forge-game/src/main/java/forge/game/card/CardState.java @@ -529,7 +529,7 @@ public class CardState implements GameObject, IHasSVars, ITranslatable { if (hasSVar("AttachAIValid")) { // TODO combine with AttachAITgts extra += " | AIValid$ " + getSVar("AttachAIValid"); } - String st = "SP$ Attach | ValidTgts$ Card.CanBeEnchantedBy,Player.CanBeEnchantedBy | TgtZone$ Battlefield,Graveyard | TgtPrompt$ Select target " + desc + extra; + String st = "SP$ Attach | ValidTgts$ Card.CanBeEnchantedBy,Player.CanBeEnchantedBy | TgtZone$ Battlefield,Graveyard | ValidTgtsDesc$ " + desc + extra; auraAbility = AbilityFactory.getAbility(st, this); auraAbility.setIntrinsic(true); } diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordWithType.java b/forge-game/src/main/java/forge/game/keyword/KeywordWithType.java index 2bf274f1272..902edff6e20 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordWithType.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordWithType.java @@ -1,6 +1,8 @@ package forge.game.keyword; -import forge.card.CardType; +import java.util.Arrays; + +import forge.util.Lang; public class KeywordWithType extends KeywordInstance { protected String type = null; @@ -31,17 +33,19 @@ public class KeywordWithType extends KeywordInstance { } } else { descType = type = details; + boolean multiple = switch(getKeyword()) { + case AFFINITY -> true; + default -> false; + }; + descType = Lang.getInstance().buildValidDesc(Arrays.asList(type.split(",")), multiple); } - if (CardType.isACardType(descType) || "Permanent".equals(descType) || "Player".equals(descType) || "Opponent".equals(descType)) { - descType = descType.toLowerCase(); - } else if (descType.equalsIgnoreCase("Outlaw")) { + if (descType.equalsIgnoreCase("Outlaw")) { reminderType = "Assassin, Mercenary, Pirate, Rogue, and/or Warlock"; } else if (type.equalsIgnoreCase("historic permanent")) { reminderType = "artifact, legendary, and/or Saga permanent"; - } - if (reminderType == null) { - reminderType = type; + } else { + reminderType = descType; } } diff --git a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java index 5f1713723d5..c07853dba64 100644 --- a/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java +++ b/forge-game/src/main/java/forge/game/spellability/TargetRestrictions.java @@ -19,6 +19,7 @@ package forge.game.spellability; import java.util.Arrays; import java.util.List; +import java.util.Map; import org.apache.commons.lang3.ObjectUtils; @@ -31,6 +32,7 @@ import forge.game.ability.AbilityUtils; import forge.game.card.Card; import forge.game.player.Player; import forge.game.zone.ZoneType; +import forge.util.Lang; import forge.util.TextUtil; /** @@ -48,6 +50,7 @@ public class TargetRestrictions { private String[] originalValidTgts, validTgts; private String uiPrompt = ""; + private String validTgtsDesc = ""; private List tgtZone = Arrays.asList(ZoneType.Battlefield); // The target SA of this SA must be targeting a Valid X @@ -125,12 +128,87 @@ public class TargetRestrictions { * @param max * a {@link java.lang.String} object. */ - public TargetRestrictions(final String prompt, final String[] valid, final String min, final String max) { - this.uiPrompt = prompt; - this.originalValidTgts = valid; + public TargetRestrictions(Map mapParams) { + this.originalValidTgts = mapParams.get("ValidTgts").split(","); this.validTgts = this.originalValidTgts.clone(); - this.minTargets = min; - this.maxTargets = max; + this.minTargets = mapParams.getOrDefault("TargetMin", "1"); + this.maxTargets = mapParams.getOrDefault("TargetMax", "1"); + + if (mapParams.containsKey("ValidTgtsDesc")) { + this.validTgtsDesc = mapParams.get("ValidTgtsDesc"); + } else if ("Any".equals(mapParams.get("ValidTgts"))) { + this.validTgtsDesc = "damage target"; + } else { + this.validTgtsDesc = Lang.getInstance().buildValidDesc(Arrays.asList(this.validTgts), maxTargets != "1"); + } + + if (mapParams.containsKey("TgtPrompt")) { + this.uiPrompt = mapParams.get("TgtPrompt"); + } else if ("Any".equals(mapParams.get("ValidTgts"))) { + this.uiPrompt = "Select any target"; + } else { + this.uiPrompt = "Select target " + validTgtsDesc; + } + + if (mapParams.containsKey("TgtZone")) { + // if Targeting something not in play, this Key should be set + setZone(ZoneType.listValueOf(mapParams.get("TgtZone"))); + } + + if (mapParams.containsKey("MaxTotalTargetCMC")) { + // only target cards up to a certain total max CMC + setMaxTotalCMC(mapParams.get("MaxTotalTargetCMC")); + } + + if (mapParams.containsKey("MaxTotalTargetPower")) { + // only target cards up to a certain total max power + setMaxTotalPower(mapParams.get("MaxTotalTargetPower")); + } + + // TargetValidTargeting most for Counter: e.g. target spell that targets X. + if (mapParams.containsKey("TargetValidTargeting")) { + setSAValidTargeting(mapParams.get("TargetValidTargeting")); + } + + if (mapParams.containsKey("TargetUnique")) { + setUniqueTargets(true); + } + if (mapParams.containsKey("TargetsWithoutSameCreatureType")) { + setWithoutSameCreatureType(true); + } + if (mapParams.containsKey("TargetsWithSameCreatureType")) { + setWithSameCreatureType(true); + } + if (mapParams.containsKey("TargetsWithSameCardType")) { + setWithSameCardType(true); + } + if (mapParams.containsKey("TargetsWithSameController")) { + setSameController(true); + } + if (mapParams.containsKey("TargetsWithDifferentControllers")) { + setDifferentControllers(true); + } + if (mapParams.containsKey("TargetsForEachPlayer")) { + setForEachPlayer(true); + } + if (mapParams.containsKey("TargetsWithDifferentCMC")) { + setDifferentCMC(true); + } + if (mapParams.containsKey("TargetsWithDifferentNames")) { + setDifferentNames(true); + } + if (mapParams.containsKey("TargetsWithEqualToughness")) { + setEqualToughness(true); + } + if (mapParams.containsKey("TargetsAtRandom")) { + setRandomTarget(true); + } + if (mapParams.containsKey("RandomNumTargets")) { + setRandomNumTargets(true); + } + if (mapParams.containsKey("TargetingPlayer")) { + setMandatory(true); + } } public final boolean getMandatory() { @@ -175,6 +253,10 @@ public class TargetRestrictions { return this.validTgts; } + public final String getValidDesc() { + return this.validTgtsDesc; + } + /** *

* getVTSelection. diff --git a/forge-gui/res/cardsfolder/c/corrupted_roots.txt b/forge-gui/res/cardsfolder/c/corrupted_roots.txt index fab9aae21df..141cfb78b36 100644 --- a/forge-gui/res/cardsfolder/c/corrupted_roots.txt +++ b/forge-gui/res/cardsfolder/c/corrupted_roots.txt @@ -1,7 +1,7 @@ Name:Corrupted Roots ManaCost:B Types:Enchantment Aura -K:Enchant:Land +K:Enchant:Forest,Plains SVar:AttachAILogic:Curse T:Mode$ Taps | ValidCard$ Card.AttachedBy | TriggerZones$ Battlefield | Execute$ TrigLose | TriggerDescription$ Whenever enchanted land becomes tapped, its controller loses 2 life. SVar:TrigLose:DB$ LoseLife | Defined$ TriggeredCardController | LifeAmount$ 2 diff --git a/forge-gui/res/cardsfolder/m/malicious_advice.txt b/forge-gui/res/cardsfolder/m/malicious_advice.txt index 2063fd3bcf0..b19f6b80ba1 100644 --- a/forge-gui/res/cardsfolder/m/malicious_advice.txt +++ b/forge-gui/res/cardsfolder/m/malicious_advice.txt @@ -1,7 +1,7 @@ Name:Malicious Advice ManaCost:X U B Types:Instant -A:SP$ Tap | TargetMin$ X | TargetMax$ X | ValidTgts$ Artifact,Creature,Land | TgtPrompt$ Select X target artifacts, creatures, or lands | SpellDescription$ Tap X target artifacts, creatures, and/or lands. You lose X life. | SubAbility$ Drain +A:SP$ Tap | TargetMin$ X | TargetMax$ X | ValidTgts$ Artifact,Creature,Land | SpellDescription$ Tap X target artifacts, creatures, and/or lands. You lose X life. | SubAbility$ Drain SVar:Drain:DB$ LoseLife | LifeAmount$ X SVar:X:Count$xPaid AI:RemoveDeck:All diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectTargets.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectTargets.java index 6861b922ddd..22d60f67e02 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectTargets.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectTargets.java @@ -297,7 +297,7 @@ public final class InputSelectTargets extends InputSyncronizedBase { } if (!choices.contains(card)) { - showMessage(sa.getHostCard() + " - The selected card is not a valid choice to be targeted."); + showMessage(sa.getHostCard() + " - The selected card is not " + Lang.nounWithAmount(1, tgt.getValidDesc()) + "."); return false; } From f0b937a5f0c7ea35467a65d246c66ab6faa6d663 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 2 Nov 2025 18:51:51 +0100 Subject: [PATCH 221/230] Sync wiki from main repo (#8320) --- .github/workflows/sync-wiki.yml | 27 + docs/AI.md | 68 + docs/Adventure-Mode-Shops.md | 1 + ...re-Mode-Starting-your-first-playthrough.md | 1 + docs/Adventure-Mode.md | 12 + docs/Adventure-Quests.md | 2 + docs/Android-Debugging.md | 139 ++ docs/Biome-Data.md | 2 + docs/Card-Images.md | 109 + docs/Card-scripting-API/AbilityFactory.md | 942 +++++++++ docs/Card-scripting-API/Card-scripting-API.md | 361 ++++ docs/Card-scripting-API/Costs.md | 186 ++ docs/Card-scripting-API/Replacements.md | 90 + docs/Card-scripting-API/Restrictions.md | 103 + docs/Card-scripting-API/Targeting.md | 219 ++ docs/Card-scripting-API/Triggers.md | 918 +++++++++ docs/Configure-Planes.md | 90 + docs/Configure-Sets.md | 23 + docs/Configure-starting-sets.md | 14 + docs/Create-Enemies.md | 108 + docs/Create-Rewards.md | 178 ++ docs/Create-new-Maps.md | 42 + docs/Creating-a-Custom-Card.md | 178 ++ docs/Creating-a-custom-set.md | 258 +++ docs/Credit-and-Thanks.md | 34 + docs/Custom-Music.md | 35 + docs/Development/Android-Builds.md | 46 + docs/Development/Deck-Generation.md | 8 + docs/Development/DevMode.md | 183 ++ .../IntelliJ-setup/01-01-initial-prompt.png | Bin 0 -> 10682 bytes .../01-02-ui-theme-selection.png | Bin 0 -> 83902 bytes .../01-03-checkout-from-git.png | Bin 0 -> 28644 bytes .../IntelliJ-setup/01-04-fill-in-the-form.png | Bin 0 -> 15982 bytes .../IntelliJ-setup/01-05-do-not-open-it.png | Bin 0 -> 10946 bytes .../IntelliJ-setup/01-06-click-open.png | Bin 0 -> 24647 bytes .../IntelliJ-setup/01-07-open-the-pom.png | Bin 0 -> 35629 bytes .../IntelliJ-setup/01-08-open-as-project.png | Bin 0 -> 11827 bytes .../IntelliJ-setup/02-01-project-settings.png | Bin 0 -> 65664 bytes .../IntelliJ-setup/02-02-new-jdk.png | Bin 0 -> 68965 bytes .../02-03-jdk-homedirectory.png | Bin 0 -> 35364 bytes .../IntelliJ-setup/02-04-after-jdk-setup.png | Bin 0 -> 67573 bytes .../02-05-edit-configurations.png | Bin 0 -> 76019 bytes .../IntelliJ-setup/02-06-application.png | Bin 0 -> 52108 bytes .../IntelliJ-setup/02-07-debug-setup.png | Bin 0 -> 65952 bytes .../IntelliJ-setup/IntelliJ-setup.md | 141 ++ docs/Development/Network-Play-Rewrite.md | 44 + docs/Development/mana-conversion-matrices.md | 119 ++ docs/Development/ownership.md | 64 + docs/Different-Planes.md | 40 + docs/Docker-Setup.md | 97 + docs/Dungeons.md | 82 + docs/Equipments-and-Items.md | 562 ++++++ docs/File-Formats.md | 31 + docs/Forge-historical-reference.md | 115 ++ docs/Forge_DevMode.textile | 5 + docs/Frequently-Asked-Questions.md | 93 + docs/Future-Ways-to-Play.md | 54 + docs/Gameplay-Guide.md | 35 + ...tting-Started---Creating-your-character.md | 7 + docs/Home.md | 57 + docs/How-to-Build-a-Cube.md | 48 + docs/Keyboard-Shortcuts.md | 11 + docs/Mana-Shards.md | 13 + docs/Missing-Cards-in-Forge.md | 77 + docs/Modding-and-Development.md | 28 + docs/Network-FAQ.md | 34 + docs/Network-Play.md | 216 ++ docs/Networking-Extras.md | 83 + ...onverted)-Duplicate-decks-checking-tool.md | 23 + .../(SM-autoconverted)-maven-build-system.md | 125 ++ docs/Retired/cardscripting.md | 73 + docs/Retired/images.md | 5 + docs/Snapshots-and-Releases.md | 46 + docs/Sprite-Atlas.md | 2 + docs/Steam-Deck-and-Bazzite-Install.md | 69 + docs/Themes.md | 3 + docs/Towns-&-Capitals.md | 101 + docs/Transfer-PC-saves-to-Android.md | 14 + docs/Tutorial-1-Create-your-First-Plane.md | 6 + docs/Tutorial-2-A-New-Look.md | 221 ++ docs/Tutorial-3-Configuration.md | 56 + ...ds,-Playtest-Cards,-and-Other-Funny-Cards.md | 1793 +++++++++++++++++ docs/User-Guide.md | 119 ++ docs/_sidebar.md | 61 + 84 files changed, 9150 insertions(+) create mode 100644 .github/workflows/sync-wiki.yml create mode 100644 docs/AI.md create mode 100644 docs/Adventure-Mode-Shops.md create mode 100644 docs/Adventure-Mode-Starting-your-first-playthrough.md create mode 100644 docs/Adventure-Mode.md create mode 100644 docs/Adventure-Quests.md create mode 100644 docs/Android-Debugging.md create mode 100644 docs/Biome-Data.md create mode 100644 docs/Card-Images.md create mode 100644 docs/Card-scripting-API/AbilityFactory.md create mode 100644 docs/Card-scripting-API/Card-scripting-API.md create mode 100644 docs/Card-scripting-API/Costs.md create mode 100644 docs/Card-scripting-API/Replacements.md create mode 100644 docs/Card-scripting-API/Restrictions.md create mode 100644 docs/Card-scripting-API/Targeting.md create mode 100644 docs/Card-scripting-API/Triggers.md create mode 100644 docs/Configure-Planes.md create mode 100644 docs/Configure-Sets.md create mode 100644 docs/Configure-starting-sets.md create mode 100644 docs/Create-Enemies.md create mode 100644 docs/Create-Rewards.md create mode 100644 docs/Create-new-Maps.md create mode 100644 docs/Creating-a-Custom-Card.md create mode 100644 docs/Creating-a-custom-set.md create mode 100644 docs/Credit-and-Thanks.md create mode 100644 docs/Custom-Music.md create mode 100644 docs/Development/Android-Builds.md create mode 100644 docs/Development/Deck-Generation.md create mode 100644 docs/Development/DevMode.md create mode 100644 docs/Development/IntelliJ-setup/01-01-initial-prompt.png create mode 100644 docs/Development/IntelliJ-setup/01-02-ui-theme-selection.png create mode 100644 docs/Development/IntelliJ-setup/01-03-checkout-from-git.png create mode 100644 docs/Development/IntelliJ-setup/01-04-fill-in-the-form.png create mode 100644 docs/Development/IntelliJ-setup/01-05-do-not-open-it.png create mode 100644 docs/Development/IntelliJ-setup/01-06-click-open.png create mode 100644 docs/Development/IntelliJ-setup/01-07-open-the-pom.png create mode 100644 docs/Development/IntelliJ-setup/01-08-open-as-project.png create mode 100644 docs/Development/IntelliJ-setup/02-01-project-settings.png create mode 100644 docs/Development/IntelliJ-setup/02-02-new-jdk.png create mode 100644 docs/Development/IntelliJ-setup/02-03-jdk-homedirectory.png create mode 100644 docs/Development/IntelliJ-setup/02-04-after-jdk-setup.png create mode 100644 docs/Development/IntelliJ-setup/02-05-edit-configurations.png create mode 100644 docs/Development/IntelliJ-setup/02-06-application.png create mode 100644 docs/Development/IntelliJ-setup/02-07-debug-setup.png create mode 100644 docs/Development/IntelliJ-setup/IntelliJ-setup.md create mode 100644 docs/Development/Network-Play-Rewrite.md create mode 100644 docs/Development/mana-conversion-matrices.md create mode 100644 docs/Development/ownership.md create mode 100644 docs/Different-Planes.md create mode 100644 docs/Docker-Setup.md create mode 100644 docs/Dungeons.md create mode 100644 docs/Equipments-and-Items.md create mode 100644 docs/File-Formats.md create mode 100644 docs/Forge-historical-reference.md create mode 100644 docs/Forge_DevMode.textile create mode 100644 docs/Frequently-Asked-Questions.md create mode 100644 docs/Future-Ways-to-Play.md create mode 100644 docs/Gameplay-Guide.md create mode 100644 docs/Getting-Started---Creating-your-character.md create mode 100644 docs/Home.md create mode 100644 docs/How-to-Build-a-Cube.md create mode 100644 docs/Keyboard-Shortcuts.md create mode 100644 docs/Mana-Shards.md create mode 100644 docs/Missing-Cards-in-Forge.md create mode 100644 docs/Modding-and-Development.md create mode 100644 docs/Network-FAQ.md create mode 100644 docs/Network-Play.md create mode 100644 docs/Networking-Extras.md create mode 100644 docs/Retired/(SM-autoconverted)-Duplicate-decks-checking-tool.md create mode 100644 docs/Retired/(SM-autoconverted)-maven-build-system.md create mode 100644 docs/Retired/cardscripting.md create mode 100644 docs/Retired/images.md create mode 100644 docs/Snapshots-and-Releases.md create mode 100644 docs/Sprite-Atlas.md create mode 100644 docs/Steam-Deck-and-Bazzite-Install.md create mode 100644 docs/Themes.md create mode 100644 docs/Towns-&-Capitals.md create mode 100644 docs/Transfer-PC-saves-to-Android.md create mode 100644 docs/Tutorial-1-Create-your-First-Plane.md create mode 100644 docs/Tutorial-2-A-New-Look.md create mode 100644 docs/Tutorial-3-Configuration.md create mode 100644 docs/Un‐cards,-Playtest-Cards,-and-Other-Funny-Cards.md create mode 100644 docs/User-Guide.md create mode 100644 docs/_sidebar.md diff --git a/.github/workflows/sync-wiki.yml b/.github/workflows/sync-wiki.yml new file mode 100644 index 00000000000..b171c2f2d7f --- /dev/null +++ b/.github/workflows/sync-wiki.yml @@ -0,0 +1,27 @@ +name: Publish wiki +on: + push: + branches: [wiki] + paths: + - docs/** + - .github/workflows/sync-wiki.yml +concurrency: + group: publish-wiki + cancel-in-progress: true +permissions: + contents: write +jobs: + publish-wiki: + if: github.repository_owner == 'tool4ever' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: fix md links for Gollum + run: find ${{ github.workspace }}/docs/ -type f -name "*.md" -exec sed -i -E 's|(\[[^]]+]\()([^)]+\/)*([^).]+).md\)|\1\3)|g' '{}' \; + - name: fix image links for Gollum + run: find ${{ github.workspace }}/docs/ -type f -name "*.png" -exec mv '{}' ${{ github.workspace }}/docs/ \; + - uses: Andrew-Chen-Wang/github-wiki-action@v5 + with: + path: docs + preprocess: false + strategy: init diff --git a/docs/AI.md b/docs/AI.md new file mode 100644 index 00000000000..5da8a4225e0 --- /dev/null +++ b/docs/AI.md @@ -0,0 +1,68 @@ +# About Forge's Artificial Intelligence + +The AI is *not* "trained". It uses basic rules and can be easy to overcome knowing it's weaknesses. + +The AI is; +* Best with Aggro and midrange decks +* Poor to Ok in control decks +* Pretty bad for most combo decks + +If you want to train a model for the AI, please do. We would love to see something like that implemented in Forge. + +# AI Matches from Command Line + +The AI can battle itself in the command line, allowing the tests to be performed on headless servers or on computers that have poor graphic performance, and when you just don't need to see the match. This can be useful if you want to script testing of decks, test a large tournament, or just bash 100's of games out to see how well a deck performs. + +Please understand, the AI is still the AI, and it's limitations exist even against itself. Games can lag and become almost unbearably long when the AI has a lot to think about, and you can't see what's on the table for it to play against. It's best if you set up the tournament and walk away, you can analyze logs later, results are printed at the end. + +## Syntax + +`sim -d ... -D [path] -n [N] -f [F] -t [T] -p [P] -q` + +In linux and mac, command line arguments are not currently passed through the sh script, please call `java -jar` manually, instead of the exe. +- `sim` - "Simulation Mode" forces Forge to not start the GUI and automatically runs the AI matches in command line. Enables all other switches for simulation mode. +- `-d ... ` - Space separated list of deck files, in `-f` game type path. (For example; If `-f` is set to Commander, decks from `/decks/commander/` will be searched. If `-f` is not set then default is `/decks/constructed/`.) Names must use quote marks when they contain spaces. + - `deck1.dck` - Literal deck file name, when the value has ".dck" extension. + - `deck` - A meta deck name of a deck file. +- `-D [path]` - [path] is absolute directory path to load decks from. (Overrides path for `-d`.) +- `-n [N]` - [N] number of games, just flat test the AI multiple times. Default is 1. +- `-m [M]` - [M] number of matches, best of [M] matches. (Overrides -n) Recommended 1, 3, or 5. Default is 1. +- `-f [F]` - Runs [F] format of game. Default is "constructed" (other options may not work, list extracted from code) + - `Commander` + - `Oathbreaker` + - `TinyLeaders` + - `Brawl` + - `MomirBasic` + - `Vanguard` + - `MoJhoSto` +- `-t [T]` - for Tournament Mode, [T] for type of tournament. + - `Bracket` - See wikipedia for [Bracket Tournament](https://en.wikipedia.org/wiki/Bracket_(tournament)) + - `RoundRobin` - See wikipedia for [Round Robin Tournaments](https://en.wikipedia.org/wiki/Round-robin_tournament) + - `Swiss` - See wikipedia for [Swiss Pairing Tournaments](https://en.wikipedia.org/wiki/Swiss-system_tournament) +- `-p [P]` - [P] number of players paired, only used in tournament mode. Default is 2. +- `-q` - Quiet Mode, only prints the result not the entire log. + +## Examples: +In linux and macos you must run forge by evoking java and calling the jar, currently command line parameters are not passed through the script. The forge jar filename is truncated in these examples from `forge-whatever-version-youre-on.jar` to `forge.jar`. + +In Windows, if you use the EXE file as described below, the simulation runs in the background and output is sent to the forge log file only. If you want to have output to the console, please use the `java -jar` evocation of forge. + +To simulate a basic three games of two decks (deck1 and deck2 must be meta deck names of decks in `\decks\constructed\`): +- Windows/Linux/MacOS: `java -jar forge.jar sim -d deck1 deck2 -n 3` +- Windows: `.\forge.exe sim -d deck1 deck2 -n 3` + +To simulate a single 3-player Commander game (deck1, deck2, and deck3 must be meta deck names of decks in `\decks\commander\`): +- Windows/Linux/MacOS: `java -jar forge.jar sim -d deck1 deck2 deck3 -f commander` +- Windows: `.\forge.exe sim -d deck1 deck2 deck3 -f commander` + +To simulate a round robin tournament; best of three, with all decks in a directory: +- Windows/Linux/MacOS: `java -jar forge.jar sim -D /path/to/DecksFolder/ -m 3 -t RoundRobin` +- Windows: `.\forge.exe sim -D C:\DecksFolder\ -m 3 -t RoundRobin` + +To simulate a swiss tournament; best of three, all decks in a directory, 3 player pairings: +- Windows/Linux/MacOS: `java -jar forge.jar sim -D /path/to/DecksFolder/ -m 3 -t Swiss -p 3` +- Windows: `.\forge.exe sim -D C:\DecksFolder\ -m 3 -t Swiss -p 3` + +*** + +Each game ends with an announcement of the winner, and the current status of the match. diff --git a/docs/Adventure-Mode-Shops.md b/docs/Adventure-Mode-Shops.md new file mode 100644 index 00000000000..48cdce85287 --- /dev/null +++ b/docs/Adventure-Mode-Shops.md @@ -0,0 +1 @@ +placeholder diff --git a/docs/Adventure-Mode-Starting-your-first-playthrough.md b/docs/Adventure-Mode-Starting-your-first-playthrough.md new file mode 100644 index 00000000000..bfbd3ec68c7 --- /dev/null +++ b/docs/Adventure-Mode-Starting-your-first-playthrough.md @@ -0,0 +1 @@ +This is where I'm going to outline "starting a game" and "how to play" in adventure mode. \ No newline at end of file diff --git a/docs/Adventure-Mode.md b/docs/Adventure-Mode.md new file mode 100644 index 00000000000..69e9c1e29c8 --- /dev/null +++ b/docs/Adventure-Mode.md @@ -0,0 +1,12 @@ +# What is Adventure Mode? + +Adventure mode is a work-in-progress game mode where you explore the ever-changing landscape of Shandalar, duel creatures to earn gold and new cards to battle the various bosses. You can visit towns to buy equipment and cards, and crawl through dungeons to find artifacts and loot to help you on your journey. Adventure mode is an awesome reimagining of the original "Shandalar" 1997 PC Game in Forge, even though the scope of adventure is much broader and we don't constrain ourselves to the lore of the plane of Shandalar. You can play Shandalar on Desktop (Linux, Windows, IOS) or on Android devices. + +# How to run? + +1. Step 1: Download the latest version of Forge https://downloads.cardforge.org/dailysnapshots/ +2. Step 2: Make sure you have the required version of the JDK (https://www.oracle.com/be/java/technologies/downloads/) +3. Step 3: Launch the adventure mode from the "adventure.exe" file included in the forge version (on android, the adventure version should be embedded into the main Forge program) +4. Step 4: In order to have pictures show up make sure you enable "automatically download missing card images" in the settings. This should automatically download the picture of the card as you encounter them in game. + + diff --git a/docs/Adventure-Quests.md b/docs/Adventure-Quests.md new file mode 100644 index 00000000000..e9fba530b86 --- /dev/null +++ b/docs/Adventure-Quests.md @@ -0,0 +1,2 @@ +# Adventure Quests +(Placeholder, actual content coming soon) \ No newline at end of file diff --git a/docs/Android-Debugging.md b/docs/Android-Debugging.md new file mode 100644 index 00000000000..cf84b8786f8 --- /dev/null +++ b/docs/Android-Debugging.md @@ -0,0 +1,139 @@ +1. Setup IntelliJ with Android (plugin, SDKs etc). +2. Get the Android 13 image for the emulator (important: Android 13), then run the emulator. *Skip this step if you use a physical device.* +3. Make sure you can build the APK. + a. `mvn clean verify -P android-debug` using the working directory forge-gui-android/ +4. Use `android-debug` profile for creating the debug version from below in the forge-gui-android/pom.xml +5. Use this maven command: `mvn android:deploy-apk android:run` +6. Look at the emulator (or device), it should say "Waiting for debugger to connect". +7. CRTL+SHIFT+A in IntelliJ and type in `attach android` and click on "Attach Debugger to Android Process". +8. Tick "Show all processes" and you should be able to pick `forge.app` from the list and click "Okay". +9. Ready to debug + +Profile XML for debugging: +``` + + android-debug + + true + true + target\forge-android-${snapshot-version}-aligned-debugSigned.apk + apk + + + + + exec-maven-plugin + 3.4.1 + org.codehaus.mojo + + + SignV2 + verify + + exec + + + + + ${pom.basedir} + java + + -jar + ${pom.basedir}/tools/uber-apk-signer.jar + -a + ${pom.basedir}/target/ + --debug + + + + + com.github.cardforge.maven.plugins + android-maven-plugin + + + javax.xml.bind + jaxb-api + 2.3.1 + + + com.sun.xml.bind + jaxb-impl + 2.3.4 + + + sun + misc + 1 + system + ${pom.basedir}/libs/sun-misc.jar + + + 4.6.2 + true + + + false + + + ${androidPlatform} + ${androidBuildTools} + + true + ${project.basedir}/assets + ${project.basedir}/res + ${project.basedir}/libs + true + + false + ${project.basedir}/proguard.cfg + + ${pom.basedir}/tools/proguard.jar + false + d8 + + 26 + + ${build.min.memory} + ${build.max.memory} + + + + true + + ${build.min.memory} + ${build.max.memory} + + --min-sdk-version=26 + + + + + update-manifest + + manifest-merger + + + + + + se.bjurr.gitchangelog + git-changelog-maven-plugin + 2.2.0 + false + + + GenerateGitChangelog + generate-sources + + git-changelog + + + true + + + + + + + +``` \ No newline at end of file diff --git a/docs/Biome-Data.md b/docs/Biome-Data.md new file mode 100644 index 00000000000..5e7d840d294 --- /dev/null +++ b/docs/Biome-Data.md @@ -0,0 +1,2 @@ +# Adventure Biome Data +(Placeholder, actual content coming soon) \ No newline at end of file diff --git a/docs/Card-Images.md b/docs/Card-Images.md new file mode 100644 index 00000000000..0067216f162 --- /dev/null +++ b/docs/Card-Images.md @@ -0,0 +1,109 @@ +# About + +Card images are assets used to represent the real cards in game. You DO NOT need images to play forge, however representing real cards is a nice ability forge provides. These images can be any image set you like, or custom images too. + +Primarily there are two types of images you'll care about; cards, and tokens. + +**Cards** - are the primary card image files, and can be generic (as all cards of the same name have the same rules) or per set since there may be different art work in different editions. Typically these are scans of the original cards and are provided by forge's FTP, or scryfall. You can customize a full generic set, or any edition if you desire... (you could for example, blur every card image and play a literal "blind" or "farsighted" game.) + +**Tokens** - are the images for the cards replacing a generic "1/1 zombie" for example. These are less frequently updated, and are typically the bulk of what is missing when doing an audit. However, these are probably where the more true "custom" replacements are available, with either custom artwork, or modified of other existing. + +# Downloading + +Due to charges in Forges hosting and scryfall terms you can no longer predownload card images. Turn on auto download in forge to download card images when first viewed. + +## In Forge Auto Download: + +**Download Missing Images - Setting** +- This will download the images from the sources as the game requests the image in situ. +- This can be useful if you don't want to have copies of every card... You can do small pre-caching by loading your decks in the deck editor prior to playing to download just those images. + +## Bulk Download Sites: (Not in game) + +- [http://download.austeregrim.net](http://download.austeregrim.net) + - Note from user AustereGrim; +> I provide my site for free for bulk downloading the entire image catalog. So you don't need to give those spam sites more advertising spots. If the server is loaded bandwidth is shared, right now it's not heavily used so please feel free to download the 4+gb zips, or the individual zips if you need sets. They are the images Kev has uploaded to my site, and the Zips are updated nightly automatically. + +**(I'm not gatekeeping, please if you have a private location for bulk downloads or for alternate or custom arts, you can update this wiki too or let us know in the discord. I'll be happy to update the wiki page with additional sources.)** + +# Storage + +Card images are stored in `pics/cards`, and tokens in `pics/tokens`, in the Cache folder for forge: + +- **Windows** - `C:\Users\\appdata\local\forge\Cache\` + - You'll need to enable hidden folders. +- **Android 11+** - `Internal Storage/Android/obb/forge.app/Forge/cache/` + - *_NOTE: You need a third party File Manager to access the obb folder and allow storage access permission_* +- **Android 8 to 10** - `Internal Storage/Forge/cache/` +- **Linux** - `/home//.cache/forge/` +- **MacOS** - `/Users//Library/caches/forge/` + - Use `Command + Shift + .` to show hidden files. + + +# Subfolders + +If you don't care about the edition's version of cards, images can be stored in the root directory of the cards folder. + +`/cache/pics/cards/` + +If you want the edition's versions of the cards, they need to go under the edition's code subfolder. + +`/cache/pics/cards/AFR` for example for Adventures in the Forgotten Realms. + +# File Naming + +**File Names:** +- Cards file names follow a simple principle: `Card Name#.border.ext` + - `Card Name` - Card Name with spaces. + - `#` - Alternate Art number; if more than one art exists for the card. + - `border` - Border Type; fullborder, crop. (I don't know all of them.) + - `ext` - Extension, jpg or png are supported. + +**Alternate images:** + +Alternate images are defined as cards with the same name in the set's edition file, if the edition file does not have the alternate listed forge will not see the alternate there! + +**Standard Alternate Arts:** + +So for example the AFR set (as most sets) shows these 4 versions of swamp; +``` +270 L Swamp @Piotr Dura +271 L Swamp @Sarah Finnigan +272 L Swamp @Titus Lunter +273 L Swamp @Adam Paquette +``` +The file naming would be represented by a number after the name: +``` +Swamp1.fullborder.jpg +Swamp2.fullborder.jpg +Swamp3.fullborder.jpg +Swamp4.fullborder.jpg +``` + +**Additional Alternate Arts:** + +They may also be listed separately as "extended arts", "showcase", or "borderless" in the same editions file: +``` +[cards] +90 U Black Dragon @Mark Zug +``` +and +``` +[borderless] +291 U Black Dragon @Jason A. Engle +``` +Where the files are: +``` +black dragon1.fullborder.jpg +black dragon2.fullborder.jpg +``` + +**Forcing an Alternate:** + +Renaming and creating a second of an existing card **will not work**, for example creating two "Burning hands" which does not have alternate art; +``` +burning hands1.fullborder.jpg +burning hands2.fullborder.jpg +``` +Forge will not see either of those, and will probably download the missing `burning hands.fullborder.jpg` for you. Similarly adding a 3rd black dragon `black dragon3.fullborder.jpg` will **not** work either. + diff --git a/docs/Card-scripting-API/AbilityFactory.md b/docs/Card-scripting-API/AbilityFactory.md new file mode 100644 index 00000000000..959833fe2b5 --- /dev/null +++ b/docs/Card-scripting-API/AbilityFactory.md @@ -0,0 +1,942 @@ +[[_TOC_]] + +_NOTE: These factories are refactored from time to time (often to adapt to new sets), so while some entries could be slightly outdated, the base information should still be correct._ + +AbilityFactory parses differently from the Keyword parser. Your Ability line will look more like this: + +`A:{AB/SP/DB/ST}$ | {Necessary$ Parameters}| {Separated$ By} | {Pipes$ Here}` + +In most cases, each AF subclass implements both the Spell and Ability. +Much of the code is shared, so creating the data object will look very similar. + + - AB is for Activated Abilities. + - SP is for Spell. + - DB is for Drawback and for many abilities that are subsidiary to + other things, like replacements. They are only used to chain AFs + together, and will never be the root AF. + - ST is for Static, this gets used in case the API should resolve without using the stack (e.g. special actions) + +### Common Parameters + +#### Cost / UnlessCost + +`Cost$ ` is the appropriate way to set the Cost of the Ability. Currently for spells, any additional costs including the original Mana cost need to appear in the Cost parameter in the AbilityFactory. For each card that uses it, the order in which the cost is paid will always be the same. + +Secondary abilities such as the DB executed by triggers or replacements don't need costs. (This is one reason to use DB over AB in these cases.) + +Read more about it in [Costs](Costs) + +#### Target / Defined + +There are two different ways to Target. One or the other will need to be used for Spells/Abilities that target. If your effect does not target, but instead defines what will be affected by the Spell/Ability, use the Defined Parameter. + +Read more about it in [Affected / Targets](Targeting) + +#### Restriction + +[Restriction](Restrictions) restricts when spells/abilities can be activated or resolved. Common examples are Putrid Leech's only activate this once per turn or different cards that can activate from Zones like the Hand or the Graveyard. + +#### SpellDescription + +SpellDescription is how the text of the ability will display on the card and in the option dialog for cards with multiple abilities. + +The SpellDescription for secondary abilities (both AB and DB) is now displayed when (and if) the ability prompts for user input in the prompt pane so it is useful to put some nice text there. + +#### StackDescription + +*(Optional)* StackDescription is the description the ability will have on the stack. This is automatically generated by the effect, but may be overridden using this parameter. This is sometimes needed with complex effects, when the generated text can't handle some details. Properties of the spell can be accessed like this: {c:Targeted}. You can reuse the spell text by just putting `SpellDescription` or `None` to leave it empty. + +#### AI instructions + +IsCurse$ True - For effects that are normally treated positive e. g. Pump + +`AITgts$ BetterThanEvalRating.130` + +## Factories + +In Alphabetical Order. + +### AlterLife + +AlterLife is for Abilities that Alter a player's life total. + +#### GainLife + +Have a player gain the specified amount of life. + +`A:AB$ GainLife | Cost$ T | LifeAmount$ 1 | SpellDescription$ You gain 1 life.` + +LifeAmount$ is required. This is how much life you will gain. + +Defined is optional, if it appears the defined player(s) gain life. +Target is optional, if it appears and Defined doesn't then targeted player(s) gain life. + +#### LoseLife + +Have a player lose the specified amount of life. + +`A:AB$ LoseLife | Cost$ Sac<1/CARDNAME> | ValidTgts$ Player | TgtPrompt$ Target a player to lose a life | LifeAmount$ 1 | SpellDescription$ Target player loses 1 life.` +`A:SP$ LoseLife | Cost$ 2 B | Defined$ Opponent | LifeAmount$ 2 | SpellDescription$ Each opponent loses 2 life.` + +LifeAmount$ is required. This is how much life will be lost. + +Target is optional. If Target doesn't appear then Defined will be used. +Remember, if Defined is missing, the default for Players is "You" + +Part of resolving sets the **SVar AFLifeLost** to the amount of life lost by all players. + +#### Poison + +Poison gives a player the specified number of poison counters. + +`A:AB$ Poison | Cost$ B T | ValidTgts$ Player | TgtPrompt$ Select target player | Num$ 2 | SpellDescription$ Target player gets 2 poison counters.` + +Parameters: + + - Num (required) - the number of poison counters to give + +Target is optional and used if available. Defined is optional and used if no Target (defaults to "You"). + +#### SetLife + +SetLife sets one or both player's life total to a specified value (i.e. +"your life total becomes 20" or "Target player's life total is equal to +the number of cards in your graveyard"). + +`A:SP$ SetLife | Cost$ 7 W W | ValidTgts$ Player | TgtPrompt$ Select target player | LifeAmount$ 20 | SpellDescription$ Target player's life total becomes 20.` + +Parameters: +LifeAmount (required) - the value to set the life total(s) to + +Defined is optional. If it exists, it will be used. Target is optional. +If it exists and defined doesn't it will be used. Default player is "You". + +#### ExchangeLife + +ExchangeLife switches the Life total of two players. + +`A:AB$ ExchangeLife | Cost$ 6 T | ValidTgts$ Player | TargetMin$ 2 | TargetMax$ 2 | TgtPrompt$ Select target player | SpellDescription$ Two target players exchange life totals.` + +One of Defined or Target is required, since there needs to be two +Players exchanging life If Defined it will be used. If Target exists and +defined doesn't it will be used. + +If there aren't two determined players by the SA, the activating player is added as the second player. + +### Animate + +Animate handles animation effects like "This card becomes a 5/5 +green creature with flying until end of turn." It is designed to handle +color changing, type changing, P/T setting, and granting/removing abilities. + +`A:SP$Animate | Cost$ G | ValidTgts$ Land | TgtPrompt$ Select target land | Power$ 3 | Toughness$ 3 | Types$ Creature | SpellDescription$ Until end of turn, target land becomes a 3/3 creature that's still a land.` + +`A:AB$Animate | Cost$ 1 B | Defined$ Self | Power$ 1 | Toughness$ 1 | Types$ Creature,Skeleton | Colors$ Black | Abilities$ ABRegen | SpellDescription$ CARDNAME becomes a 1/1 black Skeleton creature with "B: Regenerate this creature" until end of turn. It's still a land. (If it regenerates, the next time it would be destroyed this turn, it isn't. Instead tap it, remove all damage from it, and remove it from combat.)` +`SVar:ABRegen:AB$Regenerate | Cost$ B | SpellDescription$ Regenerate CARDNAME.` + +`A:AB$Animate | Cost$ 2 R G | Defined$ Self | Power$ 3 | Toughness$ 3 | Types$ Creature,Elemental | Colors$ Red,Green | Triggers$ TrigAttack | SpellDescription$ Until end of turn, CARDNAME becomes a 3/3 red and green Elemental creature with "Whenever this creature attacks, put a +1/+1 counter on it." It's still a land.` +`SVar:TrigAttack:Mode$ Attacks | ValidCard$ Creature.Self | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME attacks, put a +1/+1 counter on it.` +`SVar:TrigPutCounter:AB$PutCounter | Cost$ 0 | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1` + +Parameters: + + - Power (required) - the power to assign to the animated card + - Toughness (required) - the toughness to assign to the animated card + - Types (optional) - the additional types to give the animated card; + comma delimited + - OverwriteTypes (optional) - set to True if the animated being should + have these types **instead** as opposed to **in addition to** + - RemoveTypes (optional) - a list of types to Remove from the animated + card + - ChosenType (optional) - overrides types before it and just will add + the ChosenType + - Keywords (optional) - a " & " delimited list of keywords to give the + animated being (just like AB$Pump) + - HiddenKeywords (optional) - a " & " delimited list of hidden + keywords to give the animated being (just like AB$Pump) + - RemoveKeywords (optional) - a " & " delimited list of keywords to + remove from the animated being (just like AB$Debuff) + - Colors (optional) - a comma-delimited list of Colors to give to the + animated being (capitalized and spelled out) (ChosenColor accepted) + - Abilities (optional) - a comma-delimited list of SVar names which + contain abilities that should be granted to the animated being + - OverwriteAbilities - Remove Abilities from animated being + - Triggers (optional) - a comma-delimited list of SVar names which + contain triggers that should be granted to the animated being + - OverwriteTriggers - Remove/suppress triggers from animated being + - staticAbilities (optional) - a comma-delimited list of SVar names + which contain static abilities that should be granted to the + animated being + - OverwriteStatics- Remove static abilities from animated being + - OverwriteReplacements - Remove replacement effects from animated + being + - RemoveAllAbilities - Remove all Abilities, Triggers, Statics, and + Replacement effects + - sVars(optional) - a comma-delimited list of SVars that should be + granted to the animated being + - Duration (Default is end of turn) + - Permanent + - UntilEndOfCombat - if the effect should last only until End of Combat instead of End of Turn + - UntilHostLeavesPlay - if the effect should last as long as the host is still in play + - UntilYourNextUpkeep + - UntilControllerNextUntap + - UntilYourNextTurn + +Target is optional, will be used if possible. Defined is optional, will be used if no Targets (Self by default) + +### Attach + +Attach is being used directly only for Auras, primarily for Aura Spells, but also for Auras entering the battlefield by some effect. + +`AB$ Attach | Cost$ R R | ValidTgts$ Creature | AILogic$ Pump` + +Parameters: + + - Object (optional) - This is the Object that will be Attached + (generally the Source Card for Auras) + - AILogic - AI Logic tells the AI which base AI code it should use for + Attaching + - GainControl - Gains Control of the Attached Permanent (Control + Magic) + - Curse - A Generic Curse the AI has a handful of checks to see + what the most appropriate Target is. + - Pump - A Generic Pump. The AI has a handful of checks to see + what the most appropriate Target is. + - ChangeType - For Attachments that change types. Evil Presence is + a good example. This logic should be expanded. + - KeepTapped - For Attachments that keep a Permanent tapped. The + AI will also check for a few things like Vigilance, and another + KeepTapped Aura. Paralyzing Grasp is a good example. + +Attach separates the actually granting of abilities from the attaching to permanents to streamline how things work. + +### BecomeMonarch + +### Bond + +Soulbonding two creatures + +### Branch +Sometimes, an ability might do certain things when a specific condition is true, and other things if not. This can be implemented by using `Branch`. +The branch evaluates the SVar specified by the property `BranchConditionSVar`, using the comparison defined with `BranchConditionSVarCompare` (such as `GTY`, `LT1`, etc). Depending on whether the condition evaluated to true or false, the subability defined by `TrueSubAbility` or `FalseSubAbility` is executed. + +The example below is for "Composer of Spring", which allows either a "land" or a "land or creature" to be put on the battlefield, depending on the number of enchantments in play under your control. + +``` +SVar:TrigBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$ GE6 | TrueSubAbility$ PutLandCreature | FalseSubAbility$ PutLand +SVar:PutLand:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | Tapped$ True | ChangeType$ Land.YouOwn +SVar:PutLandCreature:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | Tapped$ True | ChangeType$ Creature.YouOwn,Land.YouOwn +SVar:X:Count$Valid Enchantment.YouCtrl +``` + + +### ChangeState + +Changing a cards State. This is mostly for Flip Cards or the Transform mechanic. + +### ChangeZone + +ChangeZone is a united front of any card that changes zone. This does +not include: drawing, discarding, destroying, or milling, as these +represent specific words on which triggers and replacements can react. +There are two primary forms, but the distinction is handled mostly in +the codebase. The only thing that is required is to set appropriate parameters. + +Origin and Destination are both required. + +Origin is where the card is coming from. + +Destination is where the card is going to. If Destination is Library, a +LibraryPosition is recommended, but not required. **Default value of the +LibraryPosition is 0.** 0 represents the top of the library, -1 represents the bottom. + +There are two primary versions of ChangeZone. + +#### Hidden Origin + +The first is hidden, generally used for Origin zones that are not known +information, like the Library or the Hand. The choice of "What card is +changing zones?" happens during resolution. + +`A:SP$ ChangeZone | Cost$ W | Origin$ Library | Destination$ Library | LibraryPosition$ 0 | ChangeType$ Artifact,Enchantment | ChangeNum$ 1 | SpellDescription$ Search your library for an artifact or enchantment card and reveal that card. Shuffle your library, then put the card on top of it.` +`A:AB$ ChangeZone | Cost$ T | Origin$ Hand | Destination$ Battlefield | ChangeType$ Land | ChangeNum$ 1 | Optional$ True | SpellDescription$ You may put a land card from your hand onto the battlefield.` + +For Hidden, things like ChangeType and ChangeNum are used to restrict +what can ChangeZone, and how many do. There are two parameters special +to Hidden Origin: + +"Chooser" defines which player has to decide which card changes zone +(example You, Opponent). + +"Mandatory" most of these abilities are not mandatory, but some are. + +#### Known Origin + +The second is known, generally used for Origin zones that are known +information, like the Battlefield or the Graveyard. The choice of "What +card is changing zones?" happens on activation, generally by targeting. + +`A:AB$ ChangeZone | Cost$ 1 U T | TgtPrompt$ Choose target artifact card in your graveyard | ValidTgts$ Artifact.YouCtrl | Origin$ Graveyard | Destination$ Library | SpellDescription$ Put target artifact card from your graveyard on top of your library.` +`A:SP$ ChangeZone | Cost$ U U | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return target permanent to its owner's hand.` + +For Known, since it just uses Target, normal target parameters are used in this Scenario. + +#### ChangeZoneResolve + +This is a helper AF, for chained effects that create multiple permanents which should enter the battlefield at the same time. + +To use it, you need to set the param "ChangeZoneTable" on the first effect and then call this at the end. + +This is supported by the following effects: + - Amass + - CopyPermanent + - RepeatEach (_NOTE: if you wrap the creation, you don't need to call this AF on its own_) + - Token + +### Charm + +This allows cards that have a mode to be chosen to occur after a trigger. + +Parameters + + - CharmNum - Number of Modes to Choose + - Choices - A Comma delimited list of SVars containing the Modes + +### Choose + +#### ChooseType + +This can be used when you are asked to choose a card type or creature type. + + - Type - Required - Can be Card or Creature + - InvalidTypes - Optional - Use to specify any type that cannot be chosen (ex: "Choose any creature type except Wall") + +The Defined is for target players. + +*NOTE_: Make sure that when this is used with a SubAbility that the DB$AF\_Whatever will support Card.ChosenType. This will need testing on a case by case basis.* + +### Clash + +This AF handles clashing. It takes two special parameters: WinSubAbility and +OtherwiseSubAbility. They are both optional and work the same way, +namely that it contains the name of an SVar that in turn contains a +drawback to be executed. The example below is for Release the Ants. + +`A:SP$ DealDamage | Cost$ 1 R | Tgt$ TgtCP | NumDmg$ 1 | SubAbility$ DBClash | SpellDescription$ Release the Ants deals 1 damage to target creature or player. Clash with an opponent. If you win, return CARDNAME to its owner's hand.` +`SVar:DBClash:DB$ Clash | WinSubAbility$ DBReturn` +`SVar:DBReturn:DB$ ChangeZone | Defined$ Self | Origin$ Stack | Destination$ Hand` + +### Cleanup + +A non-functional, maintenance AF used for Cleaning up certain Variables before a Spell finishes Resolving. + +Parameters + + - ClearRemembered$ (optional) Set to True to clear this card's + remembered list. Generally useful for Cards that Remember a card, do + something to it, then need to forget it once it's done. + - ClearImprinted$ (optional) Set to True to clear the list of + imprinted cards. + - ClearChosenX$ (optional) Set to True to clear the chosen X value. + - ClearTriggered$ (optional) Set to True to clear any delayed triggers + produced by this card. + - ClearCoinFlips$ (optional) Set to True to clear the remembered coin + flip result. + - ClearChosenCard$ (optional) Set to True to clear the chosen cards. + - ForgetDefined$ (optional) If present, remove the specified cards + from this card's remembered list. + +### Copy + +#### CopyPermanent + +Copies a permanent on the battlefield. + +Parameters: + + - NumCopies - optional - the number of copies to put onto the + battlefield. Supports SVar:X:????. + - Keywords - optional - a list of keywords to add to the copies + - AtEOT - optional + - Sacrifice - set to this is copy should be sacrificed at End of + Turn + - Exile - set to this is copy should be exiled at End of Turn + +#### CopySpellAbility + +Copies a spell on the stack (Twincast, etc.). + +### Counter + +Countering Spells or Abilities. + +`A:SP$ Counter | Cost$ 1 U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | UnlessCost$ 3 | SpellDescription$ Counter target spell unless its controller pays 3.` +`A:SP$ Counter | Cost$ U | TgtPrompt$ Select target Activated or Triggered Ability | ValidTgts$ Card | TargetType$ Activated,Triggered | SpellDescription$ Counter target activated or triggered ability.` +`A:SP$ Counter | Cost$ G | TargetType$ Spell | ValidTgts$ Instant,Aura | TargetValidTargeting$ Permanent.YouCtrl | SpellDescription$ Counter target instant or Aura spell that targets a permanent you control. ` + +Parameters + + - TargetType - Can be Spell,Activated,Triggered. If more than one, + just put a comma in between. + - ValidTgts - a "valid" expression for types of source card (if you + don't know what it is it's just "Card") + - TargetValidTargeting- a "valid" expression for targets of this + spell's target + - Destination - send countered spell to: (only applies to Spells; ignored for Abilities) + - Graveyard (Default) + - Exile + - TopDeck + - Hand + - BottomDeck + - Shuffle + +### Counters + +Factories to handle counters on cards. + +#### PutCounter + +Put any type of counter on a game object. + +`A:AB$ PutCounter | Cost$ T | CounterType$ CHARGE | CounterNum$1 | SpellDescription$ Put a charge counter on CARDNAME.` +`A:SP$ PutCounter | Cost$ G | Tgt$ TgtC | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a charge counter on CARDNAME.` + +Target is optional. If no Target is provided, the permanent will put counters on itself. + + - CounterType (required) specifies the type of counter and should + appear in all caps. It should be one of the values in the Counters + enum. + - CounterNum (required) specifies how many counters will be put on + the chosen card. + +#### PutCounterAll + +Put any type of counter on all valid cards. + + - CounterType (required) specifies the type of counter and should + appear in all caps. It should be one of the values in the Counters + enum. + - CounterNum (required) specifies how many counters will be put on + the chosen cards. + - ValidCards (required) specifies the cards to add counters to. + +#### RemoveCounter + +Remove any type of counter from a card. + +Target is optional. If no Target is provided, the permanent will remove +counters from itself. + + - CounterType (required) specifies the type of counter and should + appear in all caps. It should be one of the values in the Counters + enum. + - CounterNum (required) specifies how many counters will be removed + from the chosen card. + - UpTo is optional. If an effect states you may remove "up to X + counters", set this to True. + +#### RemoveCounterAll + +Remove any type of counter from all valid cards. + + - CounterType$ (required) specifies the type of counter and should + appear in all caps. It should be one of the values in the Counters + enum. + - CounterNum$ (required) specifies how many counters will be removed + from the chosen cards. + - ValidCards$ (required) specifies the card to remove counters from. + +#### Proliferate + +No own parameters. + +#### MoveCounters + +Used for cards that Move Counters on Resolution, requiring the Host card +to have Counters for the Move to occur. + +Parameters + + - Source - The Source of the Moving Counters + - Defined - The Destination of the Moving Counters + - CounterType - The type of counter to move. + - CounterNum - The number of counters to move. + +### Damage + +#### DealDamage + +Deal damage to a specified player or permanent. + +`A:AB$ DealDamage | Cost$ T | Tgt$ TgtCP | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to target creature or player.` + +NumDmg is required. This is the amount of damage dealt. + +#### DamageAll + +This is for damaging all applicable cards/players. Spells like +Earthquake fit in this category. + +`A:SP$ DamageAll | Cost$ 1 B | NumDmg$ 1 | ValidCards$ Creature | ValidPlayers$ Each | ValidDescription$ each creature and each player. | SpellDescription$ CARDNAME deals 1 damage to each creature and each player.` + +ValidCards are for specifying the cards that will take damage. +ValidPlayers are for specifying the players. + +### Debuff + +Parameters + + - Keywords + - Duration + +#### Debuff + +An AbilityFactory for Removing Keywords, either temporarily or for longer durations. + +#### DebuffAll + +Same as Debuff, but for all of something + +### DelayedTrigger + +### Destroy + +These APIs handles destruction of cards on the battlefield. + +See [Sacrifice](Forge_AbilityFactory "wikilink") for special case: +'Target opponent chooses \[x cards\]. Destroy those \[cards\]' + +#### Destroy + +`A:SP$Destroy | Cost$ 1 W W | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy target artifact or enchantment.` + +#### DestroyAll + +`A:SP$ DestroyAll | Cost$ 3 U | ValidCards$ Forest | SpellDescription$ Destroy all Forests.` + +### Effect + +Effect is an oddball of the AF family. Where usually AFs have similarities to each other to help with AI use, Effect doesn't fall under that jurisdiction. In general, an effect is some type of SA that +lasts longer than its resolution. + +A good example is High Tide. For the rest of the turn, High Tide makes +all Islands produce an extra mana. It doesn't matter if the Island was +in play, if it turned into an Island after High Tide was cast, any of that. + +`A:SP$ Effect | Cost$ U | Name$ High Tide Effect | Triggers$ IslandTrigger | SVars$ TrigMana | SpellDescription$ Until end of turn, whenever a player taps an Island for mana, that player adds U to his or her mana pool (in addition to the mana the land produces).` +`SVar:IslandTrigger:Mode$ TapsForMana | ValidCard$ Island | Execute$ TrigMana | TriggerDescription$ Whenever a player taps an Island for mana, that player adds U to his or her mana pool (in addition to the mana the land produces).` +`SVar:TrigMana:AB$Mana | Cost$ 0 | Produced$ U | Amount$ 1` + +Effect is most similar to Token as it creates a pseudo-permanent, except +Effect creates in the command zone rather than the battlefield. It stays +active there for a set Duration. + +Parameters + + - Abilities,Triggers,SVars are comma separated lists which contain + SVars that point to the appropriate type that the Effect will gain. + - Duration is how long the Effect lasts. Right now, most effects will + last Until End of Turn. In the future, they may have other + conditions. + +Duration$ Permanent for effects that have no specific Duration. + + - Stackable$ False - Most Effects are assumed to be Stackable. By + setting the Stackable Flag to False, the AI will know having a + second one in play is useless, so will save it's Resource for + something else. + - Image - a file\_name\_without\_extension (image needs to reside in + the tokens directory) + +### Game outcome + +#### GameDraw + +#### GameLoss + +#### GameWin + +#### RestartGame + +Used in the script of Karn Liberated + +### Explore + +### Fight + +### Fog + +Fog is an ability based on the original Fog spell. "Prevent all combat +damage that would be dealt this turn." While this could be done with an +effect, the specialized nature of the AI gives it it's own AF. + +### GainControl + +Example: Act of Aggression + +`A:SP$ GainControl | Cost$ 3 PR PR | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls. | LoseControl$ EOT | Untap$ True | AddKWs$ Haste | SpellDescription$ Gain control of target creature an opponent controls until end of turn. Untap that creature. It gains haste until end of turn.` + +Parameters: + + - NewController(Targeted player, if there is no target player, the + default is the ActivatingPlayer) + - AllValid(all valid types, no targets) + - LoseControl(LeavesPlay, Untap, LoseControl, EOT(end of turn)) + +### Goad + +### Investigate + +### Mana + +For lands or other permanent to produce mana. + +`A:AB$ Mana | Cost$ T | Produced$ | SpellDescription$ Add W to your mana pool.` + +In this example ManaType would be W. + +### Manifest + +### PermanentState + +API for things that alter a permanent's state: tap, untap, or phase in/out. + +#### Untap + +`A:AB$ Untap | Cost$ G | ActivationLimit$ 1| SpellDescription$ Untap CARDNAME. Activate this ability only once each turn.` +`A:SP$ Untap | Cost$ W | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | SpellDescription$ Untap target permanent.` + +Target is optional. If not provided, will untap the permanent with this ability. + +#### UntapAll + +`A:SP$ UntapAll | Cost$ G | ValidCards$ Creature.YouCtrl | SpellDescription$ Untap all creatures you control.` + +#### Tap + +`A:AB$ Tap | Cost$ R | ValidTgts$ Wall | TgtPrompt$ Select target wall | SpellDescription$ Tap target wall.` + +#### TapAll + +`A:AB$ TapAll | Cost$ U U U | ValidCards$ Creature.withoutFlying | SpellDescription$ Tap all creatures without flying.` + +#### TapOrUntap + +#### Phases + +### Play + +Playing a card or cards as part of SA. The player may have to make a +choice about which card to play if there are more choices than the +number of cards to play. + +Sunbird's Invocation uses many of the parameters. + +Amount - How many cards can be played (a non-negative integer or "All"). + +Valid - Selection criteria for valid cards from the zone to cast. + +ValidSA - Applied after Valid, this will filter based on all spells of the cards. + +ValidZone - The zone to look in to determine the valid cards. + +ShowCards - Other cards in the zone to show when selecting valid cards. + +Optional - Casting the card is optional. + +RememberPlayed - Remember the card played. + +ForgetRemembered - Remove all remembered cards from the source (but only +if a card was successfully played) + +ForgetTargetRemembered - Remove the played card from remembered cards +(but only if it was successfully played) + +WithoutManaCost - The card can be cast without mana payment. + +### PreventDamage + +AF damage prevention effects. + +#### PreventDamage + + - A:SP$ PreventDamage | Cost$ W | ValidTgts$ Creature | Amount$ 3 + | TgtPrompt$ Select target creature | SpellDescription$ Prevent + the next 3 damage that would be dealt to target creature this + turn. + +#### PreventDamageAll + +Same as PreventDamage, but for all ValidCards and/or ValidPlayers + +### Protection + +#### Protection + +Protection grants protection from colors or cards types, or creature +types. Anything that has "Protection from ..." + +Gains - required - the thing to gain protection from (green, artifacts, +Demons, etc.) or "Choice" if you can choose one of... + +Choices - optional +- if Gains$ Choice then this is a comma-delimited list of choices + +#### ProtectionAll + +Same as Protection, but for all ValidCards and/or ValidPlayers + +### Pump + +#### Pump + +This factory handles pumping creatures power/toughness or granting +abilities to permanents (usually creatures). + + - A:AB$ Pump | Cost$ R | NumAtt$ +1 | SpellDescription$ CARDNAME + gets +1/+0 until end of turn. + - A:SP$ Pump | Cost$ 1 U | ValidTgts$ Creature | KW$ Shroud| + SpellDescription$ Target creature gains shroud until end of + turn. | TgtPrompt$ Select target creature. + +Target is optional. If it's not provided, the activating permanent will be pumped. + +NumAtt$ is optional, will pump Power. + +NumDef$ is optional, will pump Toughness. + +KW$ is optional, will give temporary keywords. + +#### PumpAll + +### Regenerate + +#### Regenerate + +Regenerate is similar to abilities like Pump. But for creating +Regeneration shields. + + - A:AB$ Regenerate | Cost$ B | SpellDescription$ Regenerate + CARDNAME + - A:SP$ Regenerate | Cost$ W | ValidTgts$ Creature | TgtPrompt$ + Select target creature | SpellDescription$ Regenerate target + creature. + +Target is optional. If not provided, will regenerate the permanent with +this ability. + +#### RegenerateAll + +Same as regenerate, but for all. + +ValidCards - required - a valid expression for cards to regenerate + +### Repeat + +Repeat the specified ability. + +#### Repeat + +`A:SP$ Repeat | Cost$ 3 B B | RepeatSubAbility$ DBDig | RepeatOptional$ True` + + - MaxRepeat - optional - the maxium times to repeat, execute repeat + ability at least once + - RepeatSubAbility - required - setup subability to repeat + - RepeatOptional - optional - you make the choice whether to repeat + the process + - RepeatPresent, RepeatCompare, RepeatDefined, RepeatCheckSVar, + RepeatSVarCompare - optional - condition check + +#### RepeatEach + + - RepeatSubAbility - required - to set up repeat subability + - RepeatCards - to repeat for each valid card (zone: present zone of + the valid repeat cards, default: battlefield) + - DefinedCards + - RepeatPlayers - to repeat for each valid player + - RepeatCounters - to repeat for each valid counters + +### Reveal + +#### Dig + +Dig is for an ability that does basically this: "You look at the X cards +of your Library, put Y of them somewhere, then put the rest somewhere." +Think of Impulse. + + - DigNum - Required - look at the top number of cards of your library. + - Reveal - Optional - for abilities that say "Reveal the top X cards + of your library". Default is false. + - SourceZone - Optional - the zone to dig in. Default is Library. + - DestinationZone - Optional - the zone to put the Y cards in. Default + is Hand. + - LibraryPosition - Optional - if DestinationZone is Library, use this + to specify position. Default is -1 (bottom of library). + - ChangeNum - Optional - the number of cards to move to the + DestinationZone (or "All" when it's for things like "put all lands + revealed this way into your hand"). Default is 1. + - ChangeValid - Optional - use this to specify if "you may move an + artifact to DestinationZone". Default is any Card. + - AnyNumber - Optional - use if you can move any number of Cards to + DestinationZone. Default is false. (think of Lead the Stampede) + - Optional - Optional - set this if you "may" move a card to + DestinationZone. Default is false. + - DestinationZone2 - Optional - the zone to put the rest of the cards + in. If it is library, you are prompted for the order. Default is + Library. + - LibraryPosition2 - Optional - if DestinationZone2 is Library, use + this to specify position. Default is -1 (bottom of library). + +#### DigUntil + +#### RevealHand + +Look at a player's hand. + +Target or Defined is required. + +`A:AB$ RevealHand | Cost$ T | ValidTgts$ Player | TgtPrompt$ Select target player | SpellDescription$ Look at target player's hand.` + +#### Scry + +`A:AB$ Scry | Cost$ 1 T | ScryNum$ 2` + +#### RearrangeTopOfLibrary + +#### Reveal + +`A:AB$ Reveal | Cost$ 2 U T | Defined$ You | RevealValid$ Card.Blue | AnyNumber$ True | RememberRevealed$ True` + +Parameters: + + - RevealValid: to limit the valid cards. + - AnyNumber + - Random + - RememberRevealed: to remember the cards revealed + +#### PeekAndReveal + +This AF is very similar to things that Dig can do, but handle a much +simpler form, with less complex coding underneath. Similar to how +RearrangeTopOfLibrary could be handled with Dig. + +Primarily used with cards that allow you to Peek at the top card of your +library, and allow you to reveal it if it's of a certain type. The +Kinship cards fit this bill perfectly, so they are used to simplify the +complex popups that would be required if using multiple Dig +SubAbilities. + +RevealOptional - Whether or not the Reveal is optional. + +RememberRevealed - Whether to remember the revealed cards (after +filtering by Valid) + +RememberPeeked - Whether to remember the peeked cards (only if they are +not revealed\!) + +RevealValid - defaults to Card, but allows you to set a specific +ValidType if you can only have certain things + +PeekAmount - defaults to 1, but allows you to peek at multiple cards if +possible + +### RollDice + +### Sacrifice + +#### Sacrifice + +Usually you choose a player and that player has to sacrifice something + +`A:SP$ Sacrifice | Cost$ 1 B | ValidTgts$ Player | SacValid$ Creature | SacMessage$ Creature | Amount$ 2 | SpellDescription$ Target player sacrifices a creature.` + +Destroy$ True - An optional parameter for destroying permanents target +player chooses (eg: Burning of Xinye, or Imperial Edict). + +`A:SP$ Sacrifice | Cost$ 1 B | ValidTgts$ Opponent | SacValid$ Creature | SacMessage$ Creature | Destroy$ True | SpellDescription$ Target opponent chooses a creature he or she controls. Destroy it.` + +#### SacrificeAll + +### StoreSVar + +### Token + +Token simply lets you create tokens of any type. + +`A:SP$ Token | Cost$ 3 W U | TokenImage$ W 1 1 Bird Flying | TokenAmount$ X | TokenName$ Bird | TokenTypes$ Creature,Bird | TokenOwner$ You | TokenColors$ Blue | TokenPower$ 1 | TokenToughness$ 1 | TokenKeywords$ Flying` + +This ability factory does not take a target. All the parameters are +mandatory except for TokenKeywords. If you provide a non-integer for +TokenAmount, TokenPower or TokenToughness the AF will attempt to look for +an SVar of that name and interpret it's contents as a Count$ line. Worth +noting is that TokenTypes and TokenColors are simple commaseparated +lists while TokenKeywords is a list where the items are separated by +"\<\>". If TokenImage is not provided, the factory will attempt to +construct a filename on it's own. TokenOwner can use Defined-like +parameters, such as "You" "Opponent" or the new Triggered-Variables. + +You can also use the parameters TokenAbilities$, TokenTriggers$ and +TokenSVars$ to give the created tokens any number of either. For +example, here's how Growth Spasm creates an Eldrazi Spawn token complete +with ability. + + SVar:DBToken:DB$Token | TokenAmount$ 1 | TokenName$ Eldrazi Spawn | TokenTypes$ Creature,Eldrazi,Spawn | TokenOwner$ You | TokenColors$ Colorless | TokenPower$ 0 | TokenToughness$ 1 | TokenAbilities$ ABMana SVar:ABMana:AB$Mana | Cost$ Sac<1/CARDNAME> | Produced$ 1 | Amount$ 1 | SpellDescription$ Add 1 to your mana pool. + +As another example, here's Mitotic Slimes' use of TokenTriggers$: + + T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigTokenSenior | TriggerDescription$ When CARDNAME is put into a graveyard from the battlefield, put two 2/2 green Ooze creature tokens onto the battlefield. They have "When this creature is put into a graveyard, put two 1/1 green Ooze creature tokens onto the battlefield." + SVar:TrigTokenSenior:AB$ Token | Cost$ 0 | TokenImage$ g 2 2 ooze | TokenName$ Ooze | TokenTypes$ Creature,Ooze | TokenColors$ Green | TokenOwner$ You | TokenPower$ 2 | TokenToughness$ 2 | TokenAmount$ 2 | TokenTriggers$ TriggerJunior | TokenSVars$ TrigTokenJunior + SVar:TriggerJunior:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigTokenJunior | TriggerDescription$ When this creature is put into a graveyard, put two 1/1 green Ooze creature tokens onto the battlefield. SVar:TrigTokenJunior:AB$Token | Cost$ 0 | TokenImage$ g 1 1 ooze | TokenName$ Ooze | TokenTypes$ Creature,Ooze | TokenColors$ Green | TokenOwner$ You | TokenPower$ 1 | TokenToughness$ 1 | TokenAmount$ 2 + +### Turn structure + +#### AddPhase + +#### AddTurn + +`A:SP$ AddTurn | Cost$ 1 U | NumTurns$ 1 | SpellDescription$ Take an extra turn after this one.` + +#### EndTurn + +#### ReverseTurnOrder + +#### SkipPhase + +#### SkipTurn + +### ZoneAffecting + +For specific effects that handle zones in a specific manner + +#### Draw + +`A:AB$ Draw | Cost$ 1 Sac<1/CARDNAME> | NumCards$ 1 | SpellDescription$ Draw a card.` + +#### Discard + +`A:AB$ Discard | Cost$ T | ValidTgts$ Opponent | NumCards$ 1 | Mode$ TgtChoose | SpellDescription$ Target opponent discards a card. ` + + - NumCards - the number of cards to be discarded (may be integer or X) + - Mode - the mode of discard - should match spDiscard + - Random + - TgtChoose + - RevealYouChoose + - Hand + - DiscardValid - a ValidCards syntax for acceptable cards to discard + - UnlessType - a ValidCards expression for "discard X unless you + discard " + +#### Mill + +`A:AB$ Mill | Cost$ 2 T | NumCards$ 2 | ValidTgts$ Player | TgtPrompt$ Choose a player to mill | SpellDescription$ Target player puts the top two cards of his or her library into his or her graveyard.` + +#### Shuffle + +Used for shuffling a player's library + + - Optional - Set this parameter if the user should be + prompted Yes/No to shuffle the given library. Default is false. + +#### TwoPiles + +### Vote diff --git a/docs/Card-scripting-API/Card-scripting-API.md b/docs/Card-scripting-API/Card-scripting-API.md new file mode 100644 index 00000000000..cfb8d3ea915 --- /dev/null +++ b/docs/Card-scripting-API/Card-scripting-API.md @@ -0,0 +1,361 @@ +A reference guide for Scripting Cards using the API parsed by the Forge +Engine. + +### Base Structure + +By opening any file in the /res/cardsfolder folder you can see the basic +structure of how the data is created. Here's an example of a vanilla +creature: + +``` +Name:Vanilla Creature +ManaCost:2 G +Types:Creature Beast +Text:no text +PT:2/2 +``` + +The name of this card is Vanilla Creature. It's casting cost is 2G. It +has the types Creature and Beast. It will not display any additional +text in the card's template. It has a Power-Toughness of 2/2. The text +line appears only if any rules printed on card are not backed by +abilities defined for the given card (in most cases presence of Text +line means such abilities are hardcoded). + +If a card has two faces, use AlternateMode:{CardStateName} in the front face and separate both by a new line with the text "ALTERNATE". + +There are a few other Parameters that will appear in most, if not all, cards. These are + +| Property | Description +| - | - +|`A`|[Ability effect](AbilityFactory) +|`AI`|RemoveDeck:
* `All`
This will prevent the card from appearing in random AI decks. It is applicable for cards the AI can't use at all like Dark Ritual and also for cards that the AI could use, but only ineffectively like Tortoise Formation. The AI won't draft these cards.
* `Random`
This will prevent the card from appearing in random decks. It is only applicable for cards that are too narrow for random decks like Root Cage or Into the North. The AI won't draft these cards.
* `NonCommander`
+|`Colors`|Color(s) of the card

When a card's color is determined by a color indicator rather than shards in a mana cost, this property must be defined. If no identifier is needed, this property should be omitted.

* `Colors:red` - This is used on Kobolds of Kher Keep, which has a casting cost of {0} and requires a red indicator to make it red.

* `Colors:red,green` - Since Arlinn, Embraced by the Moon has no casting cost (it's the back of a double-faced card), the red and green indicator must be included. +|`DeckHints`|AI-related hints for a deck including this card

To improve synergy this will increase the rank of of all other cards that share some of its DeckHints types. This helps with smoothing the selection so cards without these Entries won't be at an unfair disadvantage.

The relevant code can be found in the [CardRanker](https://git.cardforge.org/core-developers/forge/-/blob/master/forge-gui/src/main/java/forge/gamemodes/limited/CardRanker.java) class. +|`DeckNeeds`|This can be considered a stronger variant when the AI should not put this card into its deck unless it has whatever other type is specified. The way this works is "inverted": it will directly decrease the rank of the card unless other cards are able to satisfy its types.
If a card demands more than one kind of type you can reuse it:
`DeckNeeds:Type$Human & Type$Warrior` will only find Human Warrior compared to `DeckNeeds:Type$Human\|Warrior` which is either +|`DeckHas`|specifies that the deck now has a certain ability (like, token generation or counters) so that the drafting/deckbuilding AI knows that it now meets requirements for DeckHints/DeckNeeds. This is actually very useful since many of these (such as Ability$Graveyard, Ability$Token, Ability$Counters) are not deduced by parsing the abilities, so an explicit hint is necessary. Using the other types is also supported in case the implicit parsing wouldn't find it.
It doesn't require exact matching to have an effect but cards that care about multiple entries for a given type will be judged higher if a card seems to provide even "more" synergy for it.
Example:
Chishiro has two abilities so `DeckHas:Ability$Token & Ability$Counters` is used, therefore score for `DeckNeeds:Ability$Token\|Counters` is increased +|`K`|Keyword +|`Loyalty`|Number of starting loyalty counters +|`ManaCost`|Cost to cast the card shown in mana shards

This property is required. It has a single parameter that is a mana cost.

* `ManaCost:no cost` for cards that cannot be cast
* `ManaCost:1 W W` sets the casting cost to {1}{W}{W} +|`Name`|Name of the card

A string of text that serves as the name of the card. Note that the registered trademark symbol cannot be included, and this property must have at least one character.

Example:
* `Name:A Display of My Dark Power` sets the card's name to "A Display of My Dark Power" +|`Oracle`|The current Oracle text used by the card.

We actually have a Python Script that runs to be able to fill in this information, so don't worry about manually editing a lot of cards when Wizards decides to change the rules.

This field is used by the Deck Editor to allow non-Legendary Creatures to be marked as potential commanders. Make sure "CARDNAME can be your commander." appears in the oracle text. +|`PT`|Power and toughness +|`R`|[Replacement effect](Replacements) +|`S`|[Static ability](static-abilities) +|`SVar`|String variable. Used throughout scripting in a handful of different ways. +|`T`|[Triggered ability](Triggers) +|`Text`|Additional text that needs to be displayed on the CardDetailPanel that doesn't have any spell/ability that generates a description for it, for example "CARDNAME can be your commander." or "X can't be 0.". +|`Types`|Card types and subtypes

Include all card types and subtypes, separated by spaces.

Example:
* `Types:Enchantment Artifact Creature Golem` for a card that reads Enchantment Artifact Creature -- Golem + +Rarity and Set info are now defined in edition definition files. These can be found at /res/reditions path. + +## General SVars + +`SVar:SoundEffect:goblinpolkaband.mp3` + +The sound system supports a special SVar that defines the sound that should be played when the spell is cast. + +`SVar:AltCost:[cost]` + +This SVar is for cards that have an Alternate cost, such as Force of +Will. You are allowed to pay the Alternate Cost instead of the normal +Mana cost when casting this spell. + +`SVar:AIPreference:SacCost$Creature.token,Creature.cmcLE2` + +`SVar:AntiBuffedBy:[ValidCards]` + +If a permanent with this SVar is on the battlefield under human control +the AI will play the specified cards in Main1. Applicable for cards like +Heart Sliver or Timid Drake. + +`SVar:BuffedBy:[ValidCards]` + +If a permanent with this SVar is on the battlefield under its control +the AI will play the specified cards in Main1. Applicable for creatures +with a P/T setting static ability (Kithkin Rabble) or additional buffes +(Radiant, Archangel). + +`SVar:EnchantMe:[Multiple]/[Once]` + +Creatures with "Multiple" in this SVar will always be prefered when the +AI enchants (Rabid Wombat), creatures with "Once" only if they are not +enchanted already (Gate Hound). + +`SVar:EquipMe:[Multiple]/[Once]` + +Creatures with "Multiple" in this SVar will always be prefered when the +AI equippes (Myr Adapter), creatures with "Once" only if they are not +equipped already (Kor Duelist). + +`SVar:AIEvaluationModifier:[ValidAmount]` + +`SVar:EndOfTurnLeavePlay:True` + +`SVar:maxLevel:` + +`SVar:HasCombatEffect:True` + +`SVar:HasAttackEffect:True` + +`SVar:HasBlockEffect:True` + +`SVar:MustAttack:True` + +`SVar:MustBeBlocked:True` + +`SVar:NeedsToPlayVar:[ValidCards]` + +`SVar:ManaNeededToAvoidNegativeEffect:` + +`SVar:NonStackingEffect:True` + +`SVar:PlayMain1:TRUE/ALWAYS/OPPONENTCREATURES` + +The AI will play cards with this SVar in its first main phase. Without other AILogic, it will usually not play any permanents without this in Main1. + +`SVar:SacMe:[number]` + +The AI will sacrifice these cards to pay costs. The higher the number the higher the priority. Example: Hatching Plans has SVar:SacMe:5. + +`SVar:Targeting:Dies` + +`SVar:UntapMe:True` + +The AI will prioritize untapping of this card. + +`SVar:AIUntapPreference:` + +`SVar:X:Count$` + +Count is our general value computation function. It's quite varied with a lot of different things it can calculate and is often being updated. + +## Keywords + +All keywords need to be prepended with "K:" to be parsed correctly. Each +keyword must appear on a separate line. + +### Keywords without Parameters + +This section is for Keywords that require no additional parameters and are one or two words long. Most of these you would see exactly on cards in the game. + +- Cascade +- Changeling +- Cipher +- Conspire +- Convoke +- Deathtouch +- Defender +- Delve +- Devoid +- Double Strike +- Epic +- Exalted +- Fear +- First Strike +- Flanking +- Flash +- Flying +- Forestwalk +- Fuse +- Haste +- Hideaway +- Horsemanship +- Indestructible +- Infect +- Intimidate +- Islandwalk +- Landfall +- Legendary landwalk +- Lifelink +- Living Weapon +- Menace +- Mentor +- Mountainwalk +- Nonbasic landwalk +- Persist +- Plainswalk +- Prowess +- Provoke +- Reach +- Rebound +- Retrace +- Riot +- Shadow +- Shroud +- Snow forestwalk +- Snow islandwalk +- Snow landwalk +- Snow mountainwalk +- Snow plainswalk +- Snow swamp walk +- Soulbond +- Split second +- Storm +- Sunburst +- Swampwalk +- Totem Armor +- Trample +- Unblockable +- Undying +- Vigilance +- Wither + +### Keywords with parameters + +- Adapt:{cost} +- AdjustLandPlays:{params} +- Afterlife:{N} +- AlternateAdditionalCost:{cost} +- Amplify:{cost}:{validType(comma separated)} +- Annihilator:{magnitude} +- Bloodthirst:{magnitude} +- Bestow:{cost} +- Bushido:{magnitude} +- CantBeBlockedByAmount {xMath} +- Champion:{validType} +- CostChange:{params} +- Crew:{cost} +- Cumulative upkeep:{cost}:{Description} +- Cycling:{cost} +- Dash:{cost} +- Devour:{magnitude} +- Dredge:{magnitude} +- Echo:{cost} +- Emerge:{cost} +- Enchant {params} \[Curse\] +- Enchant {Type} +- Entwine:{cost} +- Equip:{cost} +- etbCounter:{CounterType}:{CounterAmount} +- ETBReplacement:{Control/Copy/Other}:{AbilitySVar}\[:Optional\] +- Evoke:{cost} +- Fabricate:{cost} +- Fading:{FadeCounters} +- Flashback:{cost} +- Foretell:{cost} +- Fortify:{cost} +- Graft:{value} +- Haunt:{ability}:{Description} +- Hexproof:{ValidCards}:{Description} +- Kicker:{cost} +- Level up:{cost} +- Madness:{cost} +- ManaConvert: +- maxLevel:{magnitude} +- MayEffectFromOpeningHand:{Effect} +- Miracle:{cost} +- Modular:{magnitude} +- Monstrosity:{cost} +- [Mega]Morph:{cost} +- Multikicker:{magnitude} +- Mutate:{cost} +- Ninjutsu:{cost} +- Outlast:{cost} +- Partner:{CardName} +- Poisonous {magnitude} +- PreventAllDamageBy {ValidCards} +- Protection:{ValidCards}:{Description} +- Prowl:{cost} +- Rampage:{magnitude} +- Recover:{cost} +- Renown:{N} +- Replicate:{cost} +- Ripple:{magnitude} +- Soulshift:{magnitude} +- Strive:{cost} +- Suspend:{turns}:{cost} +- Transmute:{cost} +- Toxic:{poisonCounters} +- TypeCycling:{Type}:{cost} +- Unearth:{cost} +- UpkeepCost:{cost} +- Vanishing:{TimeCounters} + +### Longer Card Properties + +Plaintext properties/triggers. This section is for Keywords that are two +words or longer. CARDNAME is replaced by the card's name. + +- All creatures able to block CARDNAME do so. +- CARDNAME assigns no combat damage +- CARDNAME attacks each turn if able. +- CARDNAME attacks each combat if able. +- CARDNAME blocks each combat if able. +- CARDNAME blocks each turn if able. +- CARDNAME can attack as though it didn't have defender. +- CARDNAME can attack as though it had haste. +- CARDNAME can block as though it were untapped. +- CARDNAME can block creatures with shadow as though they didn't have shadow. +- CARDNAME can block creatures with landwalk abilities as though they didn't have those abilities. +- CARDNAME can block only creatures with flying. +- CARDNAME can only attack alone. +- CARDNAME can't attack. +- CARDNAME can't attack if you cast a spell this turn. +- CARDNAME can't attack if defending player controls an untapped creature with power {rest of text string} +- CARDNAME can't attack or block. +- CARDNAME can't attack or block alone. +- CARDNAME can't be countered. +- CARDNAME can't be enchanted. +- CARDNAME can't be equipped. +- CARDNAME can't be regenerated. +- CARDNAME can't be the target of Aura spells. +- CARDNAME can't block. +- CARDNAME can't block creatures with power {rest of text string} +- CARDNAME can't block unless a creature with greater power also blocks. +- CARDNAME can't block unless at least two other creatures block. +- CARDNAME can't transform +- CARDNAME doesn't untap during your untap step. +- CARDNAME enters the battlefield tapped. +- CARDNAME is {color}. +- CARDNAME must be blocked if able. +- CARDNAME must be blocked by exactly one creature if able. +- CARDNAME must be blocked by two or more creatures if able. +- CARDNAME can't be blocked unless all creatures defending player controls block it. +- CARDNAME's power and toughness are switched +- CARDNAME untaps during each other player's untap step. +- CARDNAME's activated abilities can't be activated. +- Creatures with power greater than CARDNAME's power can't block it. +- Creatures can't attack unless their controller pays:{params} +- Damage that would be dealt by CARDNAME can't be prevented. +- Damage that would reduce your life total to less than 1 reduces it to 1 instead. +- Enchant artifact +- Enchant creature +- Enchant creature with converted mana cost 2 or less +- Enchant creature without flying +- Enchant red or green creature +- Enchant land +- Enchant land you control +- Enchant tapped creature +- No more than one creature can attack each combat. +- No more than one creature can block each combat. +- No more than two creatures can attack you each combat. +- No more than two creatures can block each combat. +- Play with your hand revealed. +- Prevent all combat damage that would be dealt to and dealt by CARDNAME. +- Prevent all combat damage that would be dealt by CARDNAME. +- Prevent all combat damage that would be dealt to CARDNAME. +- Prevent all damage that would be dealt to and dealt by CARDNAME. +- Prevent all damage that would be dealt by CARDNAME. +- Prevent all damage that would be dealt to CARDNAME. +- Protection from {type} +- Remove CARDNAME from your deck before playing if you're not playing for ante. +- Spells and abilities your opponents control can't cause you to sacrifice permanents. +- You can't pay life to cast spells or activate abilities. +- You can't sacrifice creatures to cast spells or activate abilities. +- You can't draw cards. +- You can't gain life. +- You can't lose the game. +- You can't win the game. +- You don't lose the game for having 0 or less life. +- You may choose not to untap CARDNAME during your untap step. +- You may have CARDNAME assign its combat damage as though it weren't blocked. +- Your life total can't change. + +## Developer Mode + +[Forge\_DevMode](Forge_DevMode "wikilink") + +## Remaining Cards + + \ No newline at end of file diff --git a/docs/Card-scripting-API/Costs.md b/docs/Card-scripting-API/Costs.md new file mode 100644 index 00000000000..ff92bda177b --- /dev/null +++ b/docs/Card-scripting-API/Costs.md @@ -0,0 +1,186 @@ +Cost is a class that attempts to streamline costs throughout all cards. It requires that each cost is separated by a space. I will use examples that could be found in Ability, although certain Keyworded abilities do use Cost too. + +# Common + +## Description + +Description is an optional last parameter in the cost. This is to allow +for complex Type definitions to have a nice Description that is readable. + +## CostDesc / PrecostDesc + +## UnlessCost + +UnlessCost allows the player specified with UnlessPayer (same as +Defined, defaults to TargetedController) to pay mana to prevent the +resolving of the ability. If the script has the param "UnlessSwitched", +then the player pays mana to resolve the ability (usually used to handle +"any player may pay ..." ). + +## XChoice + +XChoice is the variable that basically means "You can choose whatever +you want for this variable. But you need to decide what X is before you +start paying." This would commonly appear as an SVar definition of X. + +## xPaid + +xPaid is the amount of Mana Paid for an X Cost. There are a few cards +that will use the X Payment to determine other costs (like Abandon Hope) +This would commonly appear as an SVar definition of X. + +## CARDNAME + +For Costs that do something to themselves (ex. Discard Self, Sacrifice +Self) + +# Types of Cost + +## Discard + +Discard has two required parameters and one optional in the form +Discard + +- The first is how many cards are being discarded. +- The second is what card types can be discarded. (Hand for the whole + hand, Random for chosen randomly) + +## Draw + +## Exert + +## Exile + +Exile has two required parameters and one option in the form of +Exile + +There are also a few sister abilities that all fit under the Exile +umbrella. + +- Exile (for cards on the Battlefield) +- ExileFromGraveyard +- ExileFromHand +- ExileFromTop (for cards on top of your library, this doesn't default + Type to Card, so make sure you add it) + +Some Examples + +- Exile<1/Creature> +- Exile<1/CARDNAME> +- ExileFromHand<1/CARDNAME> +- ExileFromHand<2/Creature> +- ExileFromGrave<1/CARDNAME> +- ExileFromGrave<1/Treefolk> +- ExileFromTop<10/Card> + +## FlipCoin + +Only used by "Karplusan Minotaur". + +## Mana + +- Cost$ 2 + - 2 colorless mana +- Cost$ B R + - 1 black and 1 red mana +- Cost$ WG + - Hybrid White/Green mana +- Cost$ S + - Snow Mana +- Cost$ Mana<2\\Creature> + - 2 colorless produced by a source with type 'creature'. Note the + backslash - it was chosen because hybrid costs already use slash + +Here's some examples: + +- Discard<1/Card> + - "Discard 1 Card" +- Discard<0/Hand> (The number is ignored when Hand is used as a + type.) + - Discard your hand +- Discard<2/Random> + - Discard 2 Cards at Random +- Discard<1/CARDNAME> + - Discard Self (CARDNAME) +- Discard<1/Creature.Black/Black Creature> + - Discard 1 Black Creature + +## Mill + +## Subtract(Remove) Counter + +SubCounter has two required parameters in the form of +SubCounter + +- SubCounter<2/P1P1> +- SubCounter<1/CHARGE> + +Remember the token name should appear all in caps. + +As third parameter you can use a ValidCard. + +## Sacrifice + +Sacrifice has two required parameters and one optional parameter in the +form of Sac + +- Sac<1/Artifact> +- Sac<1/CARDNAME> + +## Tap + +- Cost$ T + +## Untap + +- Cost$ Untap + +\- or - + +- Cost$ Q + +## Unattach + +## PayEnergy + +## PayLife + +PayLife has one required parameter in the form of PayLife + +- PayLife<2> + +## GainLife + +## TapXType + +TapXType has two required parameters and one option in the form of +tapXType + +- tapXType<3/Creature.White> + +## Return + +Return has two required parameters and one optional in the form of +Return + +- Return<1/Land> +- Return<1/CARDNAME> + +## Reveal + +# Putting it Together + +Putting it together is pretty simple. If a card needs to pay mana and tap, it would look like this: + +- Cost$1 W T + +For a spell that has an additional cost of sacrificing a land, put the +mana cost and the additional cost in the cost: + +- Cost$2 G Sac<1/Land> + +One of the features of Cost is you can have more than one of the same Cost type: + +- Cost$ Sac<1/Swamp> Sac<1/Creature> + +There are many examples, but they mostly fall into those categories. diff --git a/docs/Card-scripting-API/Replacements.md b/docs/Card-scripting-API/Replacements.md new file mode 100644 index 00000000000..e88520506a4 --- /dev/null +++ b/docs/Card-scripting-API/Replacements.md @@ -0,0 +1,90 @@ +Replacement create replacement effects, as you'd expect. Their script +follows the format introduced by AbilityFactory; simply begin a line +with "R:", to indicate that it's for a replacement effect, followed by a +collection of name-value pairs (name and value are separated by $) +separated by pipes (|). All Replacement effects expect an "Event$" +parameter, which declares what event should be replaced. Most replacement effects will also have a "ReplaceWith$" parameter which points to an SVar which contains what should replace the event. They may have a "Prevent$True" parameter, instead though, which means that nothing happens instead of the event. + +Similarly to triggers, the replacing code can access special variables +pertaining to the event it replaced (like triggered-variables). These +are specific to each event, and is listed below. Most replacement effects +will also have a "Description$" parameter which is simply the card text +for the ability. + +## DamageDone + +DamageDone events are checked when damage is about to be assigned to a +card or player. There are 5 special parameters: + + - ValidSource - The damage source must match this for the event to be + replaced. + - ValidTarget - The damage target must match this for the event to be + replaced. + - DamageAmount - The amount of damage must match this. + - IsCombat - If true, the damage must be combat damage, if false, it + can't be. + - IsEquipping - If true, the host card must be equipping something. + +There are 3 Replaced-variables: + + - DamageAmount - The amount of damage to be assigned. + - Target - The target of the damage. + - Source - The source of the damage. + +## Discard + +Discard events are checked when a player is about to discard a card. +There are 4 special parameters: + + - ValidPlayer - The player who would discard must match this. + - ValidCard - The the card that would be discarded must match this. + - ValidSource - The card causing the discard must match this. + - DiscardFromEffect - If true, only discards caused by spells/effects + will be replaced. Cleanup/statebased discards will not. + +There are 2 Replaced-variables: + + - Card - The card that would be discarded. + - Player - The player that would have discarded + +## Draw + +Draw events are checked when a player is about to draw a card. There is +1 special parameter: + + - ValidPlayer - The player who would draw must match this. + +There are no Replaced-variables. + +## GainLife + +GainLife events are checked when a player would gain life. There is 1 +special parameter: + + - ValidPlayer - The player who would gain life must match this. + +There is 1 Replaced-variable: + + - LifeGained - The amount of damage to be gained. + +## GameLoss + +GameLoss events are checked when a player would lose. There is 1 special +parameter: + + - ValidPlayer - The player who would lose must match this. + +There are no Replaced-variables. + +## Moved + +Moved events are checked when a card would be moved between zones. There +are 3 special parameters: + + - ValidCard - The moving card must match this. + - Origin - The card must be moving from this zone. + - Destination - The card must be moving to this zone. + +There is 1 Replaced-variable: + + - Card - The moving card. diff --git a/docs/Card-scripting-API/Restrictions.md b/docs/Card-scripting-API/Restrictions.md new file mode 100644 index 00000000000..f9006261059 --- /dev/null +++ b/docs/Card-scripting-API/Restrictions.md @@ -0,0 +1,103 @@ +### Activation + +Activation$

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 literal 0 HcmV?d00001 diff --git a/docs/Development/IntelliJ-setup/01-02-ui-theme-selection.png b/docs/Development/IntelliJ-setup/01-02-ui-theme-selection.png new file mode 100644 index 0000000000000000000000000000000000000000..4abcfd8bc36618380393105967a9b2a48cb97091 GIT binary patch literal 83902 zcmdSB1ydYh*DXpQK!PW@2PY69xI^&Zf#4q8-3NCkcyMMHOLSVCR6}qIU?uD?hm!U4TDt z9fT#7-T^-z?+im=U_QY}iV7*YrX8=kxMIydc3zxW4i?-=?YG_Oh(!BfeGvTy&xF&8 z(?~a$sr@%x|ILkt5C--eY!S>>KXUDW32C2i!amp4PF~)W>Qgn69DzH}eB*qEF2-Z6 zCVR){r;9Zv*ggXT-?7O8FofTci8hIfiG7wbG|Y2YT_ONOE{opEzl~o-bst9CC9-nj)9Qb$!P#NLsa$70i~{I%bef`x@G;f+W7tQI{* zf=m`rM1Xdqrdh56Q*XVB)EPz0>-uy7J~;|TCG4M;7nBth4VZ?*5>~3@uv+?Ry~3qY zZy8!zN^fRnmc|cCRMqvwu36Mt-JHb>$7SkYU(XZ^$GL!>b*Q>PPoXpjJ1M@^B>Pz$KR7xc*7x}Y`j3vXxP~kfG@P$8TioM-5nAU zA)0G9=8~!Fwu_y@VKr1***=uP4@y%swkwcHRxI94QZ|}hRlm`yHT?j(JI!~1jE$tQ zhXWtE+)K5=39HNF#I#zft)iKQfkQ+ye7M||3zdJTP*bMW^wDQ9j)u2wWJ~E=tu>Ef-l}sAW@nf0K!vUtVslG&xu<{Piu@Y`DB# zw)t79!w*9OI%bAxe|fY}snn6x)B7}CC|_u`T;JH(h=GaObGbXrZNKyG$Gx!yN2;Wh z6gdM!gJ<=aZ`2O9y z0hcEOaMZ;EB-7zyODuRy_q57BS%w;ogl~ZNtX0I=m>O9AFZHKmJh#V-HBI)rVIoj0 z8h-xtljVkO;K9n&YZ@ULpvJRyC{g=5IuRF!4{(dqd6JlqcW2{a*z`Y?AG&nO%;Q?` zb`t1SdDxpOklYLy8IqYoMYQE`!r{aFVk=E}&SF9SU+!BmwjO8etu$9wSKlnOfn180 zag%5P!-~Zb7Lu0t?cjX3pB}a z3li{rJYNHyF$0DM*oNy~vWe+LmQefUe7PoA+hsLy(2$&#Hl(cVi}85ID6qK)v!%HH zi0F(g73sV#EN+*E)bbf4z#3V4uUT^B&L6n}SJ+=}U^?q~6_6Krpk&aj-vL&vArn)= zYPC5bnbpL8tM`4g^T|-i2MU<5@)p+U6PYHf&iVPI0@+la+Gw51Le)wg7!Tl9^9lFGX{f4A3&Ux7NEZjIPGlxsDy=&tN?M~Q^iEn4T~2OqDqkALa=AY8 z`r1)0Qvmhb_wV0B?Ha6CQ-BAAwFMll-fCI=-<6P&w->4mh`4Nh0MD=Xe)R%k+QDjz zOEHAJvZ#o5YI>U6{kllxg6nEwEIuvG@^yK6nRmSzk;Jw0g;eph5DP}T#ksGi$A4$X z5RI6}kW@H!Ih-M&@g*Vt8!vBD#TPNEiCx*XVKywosXWQNygXp5Wis3kKl}Ony8&w` zDJ{Ldo1~klV@_rsvmz>0IlsNPxA((dJHF%fB^7wWi7EpL64#A4FdsjDbOrn|G(4Q% zK68b+2?$1kTZ6nhxStppP#bTxqi1H6HZ*c{r_|1Ud|*ht?tcM`qFSM~Im)@-t6#Mc zJz1h$D*VbE5E#h)1jbemN|$Ln%x zxY6l*eY%?Z`f}GX^@gI?sY36}dd+UI$y+zNN0_cVyO-x~Z@vw5=P^E>${Fo;wZ|<; zMr_G}B}n_gV8oimk)z5EYk53}XuJFKUm2da z>V-s=v{bWg)zR82{O;EM@9PG$~Efjhc2@Bn^kA4h<8s<hg|rzPO& z0ReCOqlkw^(D?&^M0jv;(A(R)04h!TqTU^W&+2-=8LSZBUHM2M5rsiQG6sb6V%16# zAYTIczv@>H0?b^6wzR1!t;>oXBCE-m;mgy*@p?xlu8ynkvUN)&kUL5JyS~ZDpwO$7 zZ2)J>6{qS{HoQJ}!l5q&7w?QAU3McgeRVoiG9F6{1=bHTo@uh(ADPBw*ZmnB9D8&+ zVsPZR*zj2G?7HIp`uu0qI5sX02vpu`HO8nk@)=)hwb!l?&-)2&COW*mKS3jfb)dH= z29MVV|2B?^i3yte4;9F{VIH=Ms1HenMnot{A9^k<8?YCMWy5H-do&GL!NS3Xd#vsH zay6e4=vH(B2uCR*hxVVT0G9lVCE4}73$e-l#@s2GDgdJ{R<4kK8Ski7SH~_udePvc z@ViY)Aou12J1r_9akc8QE~&^6fRRupQ{xA~oJOzKn>o!{Vm}Pwna^lca{^bf((B*< z;UD1;p0b%mH*2IfIgsk-^7&_QCmN%^!#`S1(f>qRO_D zN9H!sH$4udTZ@I6c_Pr;S>iB^oWws)5R_)eQ?1osN=+&ZnjdwTA{y4zdxNC~Do$-n zWVGAXavXv4?Hm(4eL!a!V&84QBEE1Bpj*trZk(Qu<-R7vb(YLpJU}+O^cxuVFC<9& z^}b?(TEp$^Jgx@|{KIK0Q7@9%-5LlCR8YYysIapOOPhV}GH)$!z7`78Qy~oi%BCe+yNPAZAVZJMp7Gnz-k_ z${?i8)>mHOnFt--gv%;HFy`Kn9ScnPe{WxG(*XES;8%7wQJ8>? zt81GFX)+WFb$C4nx!$#*A45v`=-jQ4Q+SM67*aSTl$8|~6&G4;1UqbAd)CAu0@pAW zb1#Nxqj$Hr#;5*wSBHc)F$oFETMw{b@wD*xOfkwe>w5LR+R{FLJ>s;kj*dY=LD{%H zm=0TMsI6ByZ+*SLVj|*m9MIvEM@N5pe!9PHQNEp@8T?szvYUjXtFU`_I_u95x~s8y z^#Y;v)pcwwNxLb}AMrFq!Z@DIC9F4CLFUUllp9a5%2FvHC1sU&Q~Kw(m--|73T&1? zG64+Ze6l!rD+J~N?Ey&SubJ5MiklAD_zOHARpAa14L=A|u zKt8=YNH*2&wTEh?9m3e`d>}KjSVMFV`J_=^KU3Y)EsW2t&t-RU(RwpRuBD}Ay4Z@( z-{N90U8zZ@1hHCv)jKS2*x23e%jA72`Js-8Mv^BDk$Za&b$A%Urj8&CS1H}Ss!RlI zq$}$UT%*yHsKDb{;XM?}>amZ}es7MCyS?=e^?kNyi*ukg2@vyE+=nR{wIU2wnG}jj zhVBH*s6TafOh`n;QLhCI8X8}KGqY$3`lH)o-f#C`+=N;1NAWj1?WWpvK=fC{nq7|i zYc&hcPobp0V`3C4cw7!Ad~H z2>Mi(mnT%V4K&Y~#^+QbEXmGww*>3arcVVEmV@!}V|QLC=64yXtxW%5sl}_5t+Aw% z5+H!1adN6Amfr`>%&;xhS&78;M{+wqHNru0*5~Z)8$2NVoY}((88Kb6rKWwA#V| zu=D2S1)w!cwKLV)4*j9f$NPyYPu?vis`K$|(_rvd4(pi^Uh7nqEe?txfavU52KIk1 z%*%t_*xL(gXy61~bf?^9jf$Ne)h9BQ^J;-28UqU}V$iVLxaJpI@?W(tK&IIBu(WKy z^z(zo|H%A%6?9hx7`QZFeP$*T5K9d=bM8*S;Tj#ji+3x5g@rU`W{cw|OEpQX%*-&B zuiNrGF2~89#^}Z{rcYfsg;jkQc{+I zV=ler&h}Kx`J)v}GS~UI;Pw7mf&*kcch>x1rkFzTxwWGAaj&GrA{DR&Aahqa%v)>J zn<^CS6g6Jun__9yS$5^z3GSHdlYh&$P+V!Jl?t_DaG{QAg|rp%cwWr1Jl-7*2V59j zT-@$;_i??fRQdNvL~U(vYnyveC1jSCom5h?UpQ$ml`&wO@$PPxU7egkB>W$5)T=Ie zSy)(rt}aEE|7Ww zXXrtVp*UfFY1(EAb}t+6?LB{zz6#gE{YW%2qiORCtjkNE=IRClz4@J-9GY2)4)f}^ z-zB{K@aXCQWRiBx8#usDx>i@y@9(9L?=5I9+^pxcd0faTDEzhaJ<1R_PIeQwecw^C-mm!Z8oPIPXWiE)rov$Hox$hl6)XvQvfyd$FJ!~TQOoTN=MVoau2O0ZiEZyskiUx1-~&`_Oo8ii`m zm5<^_QUk)=!NGim>}gvEWnGn3$dNn%aRvtm1A+nok1_Th;~!rK_>I_!%7MM^j&V)0 z>bh&`k60~iBk$K9bFUYwu-_Y>G}vt902H%7-+&I}%3R^~_Ota$CPGdt=b=BXQ76lB zv9a9=rK-%DBSmzr7jw&ohQ>He&sEzrT+UB#!(dOA>rD2D8?N@c18_eN7} zmuofE>z`6R3>d8HpKQ7n6@ON*hupiyrKRzU&#jN6cfN2zxz|0Q4-b#_*9#X&^KG>7 z1|!Pn&j!NXM*2)zSw9P-YRy)|0Ig;to?~ys;^<(r3y4&DyDp_%o_i?NaiKc=%>WDK zg>nG;Q@Wgm_wnZDsqF*s^oyAo505<8nff}CgM<0??6e+h()w&E3w0oLPxqS~A^%4p zb!94UJLEmZF6r8xbN(tlN>?P{>rELxUnv;_fN z*my;Ne|_`|fY&E}>#?OAp8y`h1-c~yGR)S&K`0P*V<;qSpL!)hHtgRTDzwdJ{sPIx zx4jLt38eH*3W;Iu*I2BGD#{k;vuxc=0RZPVc>k);0npgh)ZTSBVWhg+7o5Z3`COMu zoriir#mSY1j5RhlZyjy-{3Y`zW)$S+4m1Eqv)=YI12Ve)SG@hDd{zd{T3gfg*rgPT z*0$Ehn?y-AvlTo*mOw#(TWxXb15~+%1!U|nrb=KB)t98rN{*bg4b>ASoQe2VjojLj zuzF^DMaR;kcY|Vj>|9*HCsKv_oQ3qqO|67Dt@Y0I+@uS>ZV>r zghhOrt}C{%5}A=nSTpdvANZhB`5a2J+F}R3J^&W(pC)!bF66cPsM+KtG>BiOLZ9h* zferU~m#Z9(^AF_#s@qs^n$hswI$hf_z>LyDL&@xWZH4{(fe`Vh2D0$D{F=4|(!psm zxCG7yRE6F&ez3P7lzbf=T{{2L+xg}>nZpPvBZEXs>ifo>mA77<1~(?A2p~927sz!D zPqRX{D`)@66nFsOT@58ENb1%oox%ICpsrfn>213Q5C zi0_1K_V%VRI7FA%U@Jq=lRQQWMmy}M11=hyn(Y8q5FAaCFZ65+6}TtbP_|&9~Un@}!`qa`4vLNLr14_zd9HwVG%EQDNvX zp@>b|vR}|ycs)%DV15(Z8dg?>(o2JizhHZ6<5F>RFCJKdCmoa%=Rs!79u0?x)5eo&%O~P(Tq{Sk6R^gv$sHYj8AC@u_V9hM4Jk z1%payB*1Y^&=K)?dd+$mdv9cyf@qokRU6*Q3*6GGtE-niA3)3rmnZ^g7mI%uHj&v= z8h|6Z4O#5&2NVEIrLp=e6c7Y}lkkzq|67TIcWcI{^H#z5uUv8n3ta6#Yqsn$%^r$dk+5+Qf;ySO+) zQl-}J%)vB(HVapF=KrzHpL>+C%7S_41~WYWHlz1;7r>hu7DFX`R!S|lO4 z1T5e@Zf0GjXlF;qYX4ay(NX1t&^oz7gZ`80VqBcWGdC9(7obw{9tEX$c(_LRe--Lx z%cn4p7}I$d;|?7l1`h`iC#@!TY(Uk8eR?)stfum67f2uu`fpJ$p#=DB5bELbs{nxi z0Jnt!bm_?2S~mT$40@ig=}{T0Et5b&1QzaT^5B}Xkka9LpSk||f(9QSA7+Jn+nHdd z!x^PbXS_yU(9FAaXZ!UdE~zfQGJVZu|KaAQ+)l`cTGKV6nm!qs5%oEnmG9=}8to@U z{3b9T;1Iu>Zoqs35#7zbK1WY-iYf}W3KW|a|5Fa2uL3^N(LnGHn$EK`kfCO!kx$p( zE*J&!cOrD*KPz6R0N@3VRP)ys)p3%%f&%+T86dM3FL(mtM3cdzlCX%#75^CkCo|XD zOy}!S{{=fBrzYUI$ZqcK^{2C*b^^H^#=k4j*=iYM;|whE`i#X6xmMXpb8~n7x?O+Z z>h8XDhwrkDr*-ZAcyr?4(BLvwh3a@Z^TEc32@rr*+B>wE{@HN|-S@KbbU}GA+R2-D zDDU)!b|e96moNQ3jo&2-5HPqwjNX+_Ks>soJUL!;D0{e|0ib0F9^3CVe$d9r$!H@X z_`Gk*$m8)LX?_$Jb{Ny5ra<=I0>b!m2jw`NEd|F9asr_IJt}Ixmn#=!3=C_;YXZ-1 zy%G|c>TNW>)`oJtp4sl{4reQ}^Bvf^-EDb6@cMW(euKP(0#*u#nO{eTAW;7)Kd<_a zC7FjyWrE$$+MfeInusa9d;h*b;?8=cb(;eqv=+LB^G9au73t3x(MMjJ0Jay;jfqU< zFyKh_JvN*G7v4sX%E`(ah>T5yW75dm12h=Q(~+ig9F|ej+e`U!?|>+t8%hC=+SP1$ zm}kh#&$qi>;rvQovJ(lBq3tn+Cnc^&)TJ@2M=HkxxKdD zBBG0JpPePyJa?S6wJcQo`}=*b=-z}cDM@UPiR)&KJ(Q7eVrj#kquH7=kin*0aP!^6XLx*6MtzpLGy{X^-j@fp15 z-81@k85z2IKH7!2viQ##rJ)DTd_c3ne|rH!A4m`p5NvOc>+rw$!NOHqrLq<@lGA;( zup4q1OUvhp21IH=b&gYd#5?*Wg5Yo^iwOX8exO7Fz!8Ar|ARKDmoj}W0vu3jR{$@I zOwPMk&_obRtEtiQq@&5p%M-8_jsFf2T4DA~@l0)PYX;=7iHRRreN}nvWSiBVNZ))m zMS?N#Fnj>~Xr<9m01$>-r(iTvM(BImEb=OxLO#fdddby)w~$>j|4xk%1>v76&{A&| zwz~I^yZ-ug#A!)M$@S;=Z@qtCxBA*#Ga=wsXe})xMfsTm%%x8+wjtOx+kf78?RaCZzsvT}GF!FN7gQZ`r5FE%af`+UUhP;kii--VV{I0(Ht zZ36*9TP^^H0jRlox);=?*9l}Dz!r1RNq^0s@&l|LDuADy>~XmW0a$G$N4X{@5mty? zAq^1T@^&brGPwq`_buAMB}f30=cfop&(~4IV`MZU93}*9l9PH}e`o{UK&27csq;darBGLSp0CEa`0L*Q+cEnHj_y1G{xQ3=j ze>@ocPl78#{=Y}x|G)809cPJsFzWh%Jfyx#2J8rJ_Z8D<$++$m^q*mDcts)g8hwtBm&)|jSWYJ2MOPQJ*q#wdISqq0; z{_fRyr(m#P$)Tlnt@b7H5UtPb|9rEk1c`RPR%682!6|qftFB7U$ybY$Ow{eWK0`o} zLYWHkDaq-d2=Q;_r?zQ1EQj$K8FEXmfnNmUP*jTm0?dF!Mwdi6Cmf46+@u-WUP|`JGCa3FGPSn0%7AGHCh>YakZe>lj;I5QTJpdeAq4ui zyt?E|P-Pc9cH!Fz1bBIQbQV^YVP$dGyX=`Gfte;>7sE{nh1g9xfk zihgtz7>}!MW+T>&hLz0bIJpEmXXM>gJV>rHahxCjdF56PTt21U>d37))mv6$_r(T8 zNQHL_F5jb_h_8EG=u3G6=>BzyuEei~_rx%4Ul?3E;iyO}EDAo|ooo15`ZtcEF zrOGb`I4>`6()ipys&aB-e-qp>-ojuxXm!3McaJgf#=_*BUKBV2=2Y z)DdbK0?dWlA_8nrqWzb@J{6Yy1Ii~;UZ1*QydlII%<MWA7VaA&hgfMsxSB{6l@LQ10FdYLj)*ISNrpFI3`42GEsK?)@ z^=usLWfU3D_EzN^5?bVl#bs$`)-iwlUF!UkJXR?!xUn#{e!PtG=N1O1?L%MD#L9yI zH>xy|rAj&vG_55xgDIa|HWXViW@@{O{mil_JHZ2^@6`OwE1h`)R`2enf60wl8wj2Z zn;E43%sQggyQw4Qxd{-(Lo6HoImV^>eQisxDc&OzY2M)-=beQ_4~+bEJsTr9gSK8^ zb`mELW1dE`OsqUHCPoP80x02)Am?6!(_Z;w>4PSFwClDbWP9|^x>j&*b3O|>?sRn& znUoSHD`;(m!ayTyP%`$~3-!Rh$N#>ULGK{&7&4<$)5yEEbZ^?wxfQL8V?H6v$J378 zyz+LGKAr>aY-c@5ckg*WC$1jIhr+_bAyEPW!NDe>{DD!o-*7pp2{-2OpTDa&1#rq! z>odFltQ@S{cCuLgO%;w~&(2OsFjy{*f2Z2mC!6#p}7DPgb3!|W{<;!ys*g%WN5L@1woZesa5 z69hV)RMq@;>aQ$~HGOt*Qa%s?4;dxPj?#~s$L3m9G8VtG8S_G$a`nYv4W)NJ4R zWN;$B`Q5t9n)rl|*e?6WWj!7Vys$TRocFljKzhDfWvG=JvA0=F{Ql1bYR(-_Q}nF4 zn6ed6;E79#^=pWh58S2=W%~Ww5k5}Vcf@c^0-{TrlqPihH{eUGJa6cD>6nS8 zqFhcwOm`qfA8Ahv&u2!GqA)*pli28*NhqG(JRj5mrlzl(c#}z?!_A_Whu?f@RzEr! zY9?&9w7wW7sFOOrwj$3JJO20iC;E7Vh7UQ+6lVog%@^5}ZYH5*$(vM(# zhR|0%f#sj}hskcqKS7*5MLK=tJ0 z#Wg?mKabHIMPGHUMo(Sd3%o3~2ewt^qo)$&2UmXlJpFZJ>d;VmFjJJU=5NWO?&a{T zXYakovH`tUN!ye${2;q9@|wJWkqumv65`q?2th^Qc-(-uK} z%hltd4t*e;6r?jX+LcqXj|H!*OjR5o-HWX(ri>N#@`I$auJ;)g0j^Lkow`-X|6_v# z!tm*t>yaLXJ1X(OU;L~6hxwK-Yf6`?A`E&A+jdf=akGD-77wGTP2Afaty*k%#&ykR zZA4?1mo4dV3=VAcrE_6)+wWNl>D~VL-?TWoW(od{%qq72qoRX=H8MOt`KW@cW^8QB z|J@TTZscL75cqM);G(1&@VW~5x+_BM98o}1A`xIM>^fGWp%2LY z;@C6H1m`ARoDw3tzUc1s)D#}KkCl1_oXXpaSp1Gcb+9t}%6?sO#nmwj2 zK800HB4IAaw?>xio$)XM>OLwpPTE4h3(^k`*lq2e9ZVqK zBWoq(J>rq-ZiO2>#V;=utTs97O78W`A{RUlSCKy2sh z3BZrMz~hROxb#@__F8;n4??)}bW0&TGxeMAG#mNMMg0j}B0?u$ulkP26IVWh(LGiB zn_$-6G4I0prODHCpF;o2Vi-=$wCM*oD_-T*E@Z?oxu})Zl}5+UZ{+32>5>i(T%=+T~mS zgKcfij;%UO2SRVkSx~e46ye>__@VET7;<%;#;a&;ht%(SKHU@124%Fqf53f-X#bXj zuf+!|^(FUz2R5t^AG#iIj^2F53w!*=1{EyUjdNPbA)?@JM2}J98l}oo&MtqJ<%nv+ z=69)(t27u5V~K?`5(KGI^9u7m&S}eJx+?An)Aa&QOIia*g#KkC2=BDC zF9l2DI>_Z=Pxyr=?P<+#xqMAcC4Q8QkcnYwK6#s{cP3+23Ymk>SybSWo}H^>Xgb>HRoN%G*Yuk>7pI3uNf*`&rfX zDE+ziWRh05(y4#}F0Oy)hyF>a(z8S<9C;8|9iN0ZL37^0)hC)Nk{8jKqmpe2*^=;< zR6;YeVnbu2&CN~qW9MDjqS9?G9*sgR21NPJ!sW=fh_0L4r~0H0RyD9hS455VX}wk-Wik80p$M^|D4n0ME1N_D5DK|nv3!8niztQl5!uQ3!87zj_e()!xJ0ZcRJJ2DQ|GwX!WX2ngX_Ff@l(h-$<*%v?)il4zk zsi>&i3+5WqEW9pFT4*>Pm5Pvw&*P{UBNN??vC#ttIA#iaOv^`va(3066Brc_hds-KzpQUWDLJVv)`blcqNm2 zLGqETRoPGNPxG?x6i8y#kjczZBE|hDO@#}m74SP6Rf+#kqODfWfh8tX$}o;DH!t(Q zdG%Y=45djEhj4a784sAY}28w zzccWIoZJt&+$ACrbovnP1B1Iaeg>;HjL9W#0ApOktFFFrh?JaM^1S?5QE@&Ya`*LD zx03rEN$*^_0?@&m0gt@a{|5pVQ&#SqJ$WQ#b=w_EI7AbgVP0DkfB`zqRU9aV-`RQo zW}b6O!I>~&;=s1;O1wIoD^I##zoqxd7#Ohi@VI`u)%5ce)YZk+(slronA;D1F5%t< z%7N9Ce(=c7kj2v_FS?;RHJ{@dG|-zrtPmX!PwW-GF6GR0LniI!09~(# zH$E{G6wG*9%y{-WbN)%VuJ?4UHxq5U{ARPl%F617#g~0@F=B~Ft{-xcBmste+lvWR zYrA53V|VdofhW$l3t3@S;zvBETBGf|Oq<4mAZOjn1@viV0;}8SoE&|(tSE=qC3kFvO|AIILUZnbB z%fV*2s);QbSqz%P+{QdD));B&%+--h;74MzlHY9nWC6AjlygphN=p-~{DsedJS|=+ zvu(f{-l>(+MeJjTNZ63xGDeEe>VURM@$rXry?4>1Czub{B?c5t;?ke?h_@7;c(4PV zNvP`=R=Ap+(dWSA3n1Ere&Qa}xvCFl2b%W=s&vtU>j{26;&YX{k&}P=fUWn~-xO*4 z#@|uqik<{9&M8@BZ*)#OZscDj6X?r)2p2RN6CvI9T4r20ZFNw`PD@q;T8DnKsdSkn zA&g_GCO>=wBrRK5d?ZEib4pns{{vrgJDFi&w(5WtP6xHc#rM#@ePS8N7sAOB;oI{M z3<&-R{z7o3cqiHC+Bxvs4_Dkr4#k0U5BfbJ$VfbgPGMV359Kh(CWJw@564)-Ec$NS z7yc11m*Qw;HsD$Ti_iBjOyuOTz+g>kNXU1DKttPU1EzD;8GXg6&K#e)qAm)^e6VKn zNN=-HB6h@qStHf6-yn!~=u#xXT_?S~m2|fSiZEq#`RthaOwhL1DqqQcHf?Zg!A8pW z!P5uPOaxV{BGY%$wAZ*s`EkNe)4HRu7tC&F$JRgCu&#Yl#RD3ghBb=W<r)s&tlZHxC^Y1;lM|~_^&k6HLgj;pvxKg3N6--rZ{rJ zL0*og`w~#&&9|K9zWTO8XGgwO4TMQNgoD)t$O(3@&v!|#5mR%xSo`_(7j|a-&(QS| z$8$j`<)%3ASCD1$5K~w#GlCoT^N~l%k{|DBOCR7?ovwx4HFLs7u!Wm;V}6*Aa)Ed+K`c#rJ51no4i>Y1B&(|@qY&YRjv~MzipOTW z>#_F6f%z%-vTDBnTnlT-@m=-sYR2z1Hs;MsWbg$b;1Hk?Ed=+GChevvS9bG0zr9HsAU{uN`e83JM**pQ_D!cD$FHeBodsgE zMUB*^cFVa!>{tTgvQlMCuc+7a*(5FX(IvNqLVq|*PE3AO)V)b+?`)?pDltjkqL&i; zXu;Lpa^(WFmIT_TQy0}i6MzAPF^bSS+Y*|L1(Z%dcoD!JyD;S~sadMIk#SJq^-foA zU|OGV2R;smhJ}%{uuQ$!=m1k?LJC-GUQnx?oSa5rqJ$e5KFVf!!NuU;v`61Oa_02R%XAWl$9(D>L2&05;C*pLO zJX#^KS7Y4+CfB`=ny(YJ?~mwH=;~AJ0Y)o%K9?)D9lFQo-mX*eTb!SW{MB`4GyopDx&0rpkN$>| z)U!Q%01xQ&VlFYU=ziv#Se$BdzfcN=93B*A?xnk|RaKI5KkKqxTy%;t$0gc^t(N9; z=_%noZTM5RU!g&-(ksXKu78uBwYTK=dP;AdoJ0ZzsU#-d)E)s^T4173)ZX4c6bf6! zBB%V0jL6>8i}5p^;nm(~nQlk>3e;xRJ;*8ZWz3=S7`B3H*a1=j27`SVZehpUs#CLL zM)3vP*()j*&(?Et;i|EwA0IvLw3GXYy=$$js)T-e(>ckM_SrY`!pWtp zpkzTQ0X36>ve6Ei1wNyomFRn{~Ll27^ofjp!MD3!msqN^A7nFI(HaxxigmNH!)wn#7jiOb@(&rnj{^%nFR1XZ^6hF5;kpWXaQeroAkQu5@A zf>2+zC#cinYkLh=ykjt9#zVP(eEf)o6;M(F4A5?FZ~N<1x||P(&+?0_;HcA8zf*Af zCs_xf5o=^WdqWw5Owi0OTAyetJ!40@0~d9%v>Lx6sj3pE`loCrpCgX9yM12wiisIz z#;YyU_5SD++9i+ac=oQKPM*mz0#IZ2#?qygl(19|3+V_x>ICcmwj@=2TIf=j!vgglf^tY_<$5_x`E^Hhd|P3ig=zS*~ZVDb%18 z$v8ZaS6D~?TheD&__x{3jku|5qsgp=3sg3iqlFxhaTgT_uC9=-9=NuLkIRW({`InX zJ)us+l98cMJ$yvQWZN6ZKF0%?;kD@8i=E>gg;IVn$)|2!5Ga))plZob2WSPp*kEW)oyp*nugUH}i9EY&-0X)r>sfc^@cKz`tEJy2%M%sfkD5T+I z<ju z$pi!hD510tU4zZSh4sM+|ApFC@8@k9kBcKi6tt zTVz-+n6`3Q42&Ppts}y`VL(b?_Ih%~`J(0n%$FT8A$)j#dd;5JP9>&X=k4aZlT~G6 z`XRn)@5MZaQ)ydgK%k04%cg~1TnQ;_xg==X9;w7NdO#=VbQ?3`F#e@n)3K9O3Jtk< zs8=oTOL|>#7{>;c0ia^6T{i%yHPb!lO`tv}guZ3N?oxmxFK`zhp_XxRn!~(K10`9| z-x;aIoyg|~H+%Uz{b2)Az0XCXp;y>!mcc{Qt;~$&1`XkY_yL(S#Rg!ZXbGB1d`;dE zPex&6T@xL8X$34t9_<(>`VuYW`G7Z^w*|_71W8VLER)S>rEUoc8A`eG_=K63*N=Z- zDb+EFaP<(O*JL8w#RocZI5hQ z5HU}D+hKEYXD7%3SbT?GkePY%<*a;MMsLrz=fnH|)eG=iPCat9B!lsC;a$T_7q%G_ z!^fY6&$;G6D#-ZY<1V1;=6{xb5W_%V}Q^h@53--`AUxd=R-H_30&IJ z+UKi^Bgc8yl(T1Fn|Ts4j=q3?cAr#lHOMgBBC#Y!2cNFN(Ny0?;Kh-^O+)HqlmNR5EHKqgc7NphXyZ|CdC%w4 z=a|=C=!2d9t=Apjotrh8j8-+4Qi7^L)bMFVU1jO&bg=qIIXfyY4DZTg-9da?sFv63 zA+eWk^FaIP2zo_ZQ)5bE;I7oBJjsx`KeqMhVP7y!IO(r~5^ik>_n=D`IMwp&5@NgD zXieO>>0hhAf;0KAKFMZmD$%Unt0N^`{Ea?q^|9NLh5A2SrqEfXS)CRIsSovzEv1qJC zu`p^;J_s3x`uKel!cyOQJRFBdGDog9L|MB>G&K#K7 zb^!kWKp6xW48g?wS&bP(DH(ei6Y4}2pP(cmCiH%9_GC#ke6ROdh7~EEN4Lx zM?Bwa;tn3Rww;s^`RgM|UnRQ$iM1&^lJ%Oho8H2X7PJW8#db+aXe7aqeBK)}V`}wl zIrbSEN2@Ljj9xhT2Rq|kg@AL|Z-wETP{+VR0>RMKo7cvy>FjsDa)|51B|RbHZ3Fq z6Zk0WCzdq{f-G5oO*0hp(AC+0f+e}owVl!%;EB%BM+P!_Xms?rRE~kZK8CPfm0DVJ zh4n6HPAsLQktFq8g1QoxX`R?vl7uZLLnymP7G>QM0$gKLHZ^5du_D!xQOuu^t*n5o z1vVZu%lky>Q&hM847q%pN(21MED(_oZX17J9uH-+Wv1nyf4^myZ ziDT4r))yAZP1SJG&SzcIt0kMo^H~~-k`d78-NIv=?)K*g7$z4`S`r9UZF_p`F}|w* z8Xesef#SN4?N@4AMb&Hj#S(4P&f2tY@j2v)nfMOkQRU^GGQwjHGn%qG<64P1cQ&@; zzPjj4oNm!BKwa@`k2Bq^1T4}zlZ%8<9_jcKiPVwH=#WeY>9#Lx z;X7x(J=LR3#j`Ti3t#jspb*OffK8fiA0AX5MKvja(HXg4OuIm?H>q?+M`lf#?JCv9B1t=lg z&BuP{{+l?^fn32Y5415VlWNa6r;Y~o>35met(%t6B59Q4prfOA>a5#vqykfkq@eBs`{)Rll)W1yU;x zLgO0z)X_wz44`!s=+=T!{Q2|$Ve2h`@(P-+K?oAu9ReY^ySoPu?hxGFo!}58!QCy` zgS)%CySuxym-qd?-Tk+=iYlt8;@-J4Jw4s0PoJqKEGfp`?rZHiS~bXfUaljg;=b4UIho5wdyc(51DU1NlyUXEEfw)I(odHDsk&u1m(tYkUk#Sp z_hti~8r_c`kF0XK5xJ|m*KXe?VlY7s&FIV~2wKR5J8QScOm`<5W~m&pF*5j=jmB&l zA@zoy7w+`)Fp#eD7%F~bP`5+4W2Ns2GY<{I%NLiIR5uOo^#H65@K(Rv5uH@q8&S7A}J3(5sYc-hk`rb;Y1x(m2<5U>P!rq0;kmO}lo513$SzI);=> zf_yG!*ebF~JaI3wg0u-7E8>2oyCp(QoH6xsEX`|CWPHK)uF_$RB_iz-K0JoY)|tBq zPJYdmqMKZv_;6zGTeOCHDyEpCA{w~!r|=bdat@A6vRDeBxCHcdG+H#;vRCzK#*hwj zF;^yN7f2)GS7VRtb(#_dlt|tj#cl5iS93kPJ5U=EmX7@?l}FupFChgRWkb zUU3DC*}L_;5Q$0~kz=aUzb&oQAI;-?cF9dxumNn{h`gP=9M=RJ?zOeG-PIsw;`JHH z|9(?^@~q|!f7F82-Q$u~UC#aJU>UdJXT&-IIqB|&Y!MfHuFPdC$kk^~A+VifTOK;Z z%%jMpM+=e;1o!4YA1`|82nxGR`fF=KrV&daH{h{qqSWYdO6~iCt7_JwouURTV+=Ka zvbvhd{W<_6=8;nF9z{Z-V(^aNL@w;X5smBpD0WL$2c}q3v6lkf@}H4Z(oRrN5Go*c z(Bgg#=z?^cvFJQXlW*vjB%R&1H8!rXq{TO++3DpGCN@v79S~CrZ9Y*n~RZ- z#k+W_*>2fw^-IYWFuW~t$JytSEBZoJJbLvfAt4wPjXdu_Tdq_qg*hc-9@ID3qe+d7 z&7ezrPX9fIbXD2NfUYqk#`o866MG&Q+;_xd4$!B*DbQd1pFJq|Oq-Imp&3tv+&Xuo z2|y!++SlyiP-`=4qVHS#@smFv2ISl>_&V0gePu>;FM zUu`^3&;Cv*9#QnU1JEe}d!{^7H}sTg*xb}_C++T@yS9$QNr$yL1f68llau3v#0J;I7Uf-F|f`B&VZD8*gJp*foJI zO_S`rog>RnuL=s1b)2!D%WbfW>ylCUaXuyfFGD|YV=X?$O>OzHUCC)_OFI?;T_8B! zulW_6rkqWepq8U}qBH9rK#+!PxLvSh_LJSIChyzZ+wN4R3{X_2pWps4`KdC&55IyF zDgNO-ZD<%Pv2rFFK22{&^l*`qLW(K;zz7nL#AC9Uq5QCa+nyB_po2 zANzN*#`6?A!))6voZnBWB%%7{f(wDNMZn#nob$Yh5m4@u`u-gzJ3E{AdW2;pjcc0F z?Zs3r&4wT7Z9{fNo|>u}F9d|2di0_hNRRp~SDT#w0^vR!aqCTu6f=vJse8F0)yM2$ z#u5OtBenD3DwWH8iFIn&|;>&K8_BecC7Xq`DefSH)6dme>_KdU8> z&M;=i>fmd~2)rMxRJWDevd5!y$eZJW7QXI0^Y!NVnQ^v61tQC%)&hsm^25z>s4K6T*hZ)$MRMbx{=ZnK{7g{Y*AtiA1 zgw=LAZNEOZZ1(WO6ZKL*+%hrR>u^7xO-%nIKcMyRE~KN47_kz`K0f7+@YyH~Xdvw@ zfH(}yHz9JgA%ZvuN$yA0X%ZIaW(Uim)OU9+SXMOD<9j@NXN?>t>ilM4Pr^x}jQHYtZ|c^+xEwz0=6>Hi#oNR zy+*5k^RJ{yRU9%?M+Kz|x`Scx(_hFM#ew3e_xUb{$~)MeT*Fc?|6hLuAE<>H|3!7R zty`a9-n}BHOYJ9OL9bnf2qjiWPqB7&JtUoE`tHcO(E(?JWPKf+2Y!qb4IDgtu0pOj zQtWnDK?e=(ShAtX8;`@zcXFAej)`oM->Y)wK*wIGhAhCBzISO0+3XHx)L&5#M#dN3 z_v8jgvFAoBo2zY)0rYh8ZP(i#3D?b@r+*cLd9+{zt z&ZJ}Cgyan0mRa!Y*UQ5+gV)U^ug}XNHqa6yT)**Lxo9<_brB~0Tn#v=QnWv5(Y7^h z9zSxyrX(@_L#|%PJk*iDHF44{N%VOS7x@CCk5NX$WUge#S*a+{`d*)Od4ErFa93B; zd`+)eV-}W~Nd$wbWMR=*mZA2iGXUB_qS*zCKe39cg8@7Bs*^{2ji4DEnYKsZl^h5f zkl|<%Uej=6o38uvM1tzB+ljXuuhTUUtXwe%zL>J&xdS>zK+Df)lPb_F4ffYDWOd@2 z=KFK=nbEARWoYE@Zi`wpeC+HTqPKb@_ksy?8H+tMk4KC zxz*d1L%^Vfez5S#6DMhvqFThG3p18hLfeXEP1DpkGpwzL1g}0_9grYT6#KQ)^D5E;;b!Cq!n``7O)DrLd|)2fK|{~F&NJD^4{;nK5jY8 zX$mwu@wa<7`aQSvbdObRc-fnidwxs5$AfKGhUauQ9J?fU>Xs;G5d5LVH~s*p7z=4x zm+wfqZHiXgKayCF?^ay>3)Q0`#%wzzl06Dz`(9H4!{kIEnIMnihP@&Q5_?>e6Jyg0 z8#D5gFx5pu|CCK4u#7g|nADYXX0>?*q^LjY>DyFFP<{D`=l_$}eKMEAyTxZ02F;^8 zZ+^Zo-r~nF?$Z(KQ|2 z&vc+;%1CjY*&hjepP*!Pp0g^SD}v~dCTVuqDCOXm^=RW1Q*Z;s)E=&e85XOqQJ&?* zmD=sSL+nBIh#QysBoB`)o?&8|ns{K{;;X~&8LN9o)BBmPpWX7#y93L#;fnY2A zvNPl4)wF&1ImS_n&y;GJ4q@akH+A?>N3BIve=l z=L!;|M;TQ3_nLThnrU{76>)_zn}^8iJEYY=r+|G=m4SrG=D5qS(o^wOO6I?o`H_`^ z2m0mhU|2jo0-%s_>70`jcPqRnb-eOzzeL5JJ&N#d!27cDhL3y;cOnHr-(pC4`^yyn zq1FXoI?PzZ)}OvjoVpg4{)L_i`2Ro*?qY$?oVM9fS?>P6H{0~2vVit zyiMXA$+s)3@_j6&V(Dmx4|h?Y0~fEZHBATf`53Zmr7f)+F>mjmkLMqlQJNU=-PCrb zy_{p*9?z0wMF|WJQoZ03jvWrL@UFcp96dZ=o3-=vE`gA-b8|K749I}a?=zhM^Tiyf zvoo8t=z$Bqs9dL42d%~H=%*e&Zzx0i zTSD2gYuxZ9{8W>(Wnm)QhZT_yRsy3-l_dwJWSWBvj@ZueUJ$1CApMIOK1NS;`-`xZ z6>U1VOWr$4^u^`N4K{aG>X=QuR`Bgf$xMtk!m|phv^L8 z9S)6bjp!Wx!?r9TmY9+f43MV&;nuEUiWz^Yl(V~`#8IZk`Z{>(Z@HAl9i!^6E2~y!Qpina}8>#niB0E2+_B z&JRH4b^AgcPmGw#P#m{RJOC7e4~YG&6q>bQpmzg;Lj*Q7W-8(Uj4xUHv54`tz}(@l ztbCW2wYNx!mZ2J?I=D6V-OefAlj|s_ApHtClFS3@iez7-jSwdRcwr-{9LN>ze^dDF zwfp9d2_B|<>dplMBpI%*hPM?^NC}erx6t8kE%4P_x6lW^mx3ce2Xt>26-#{y=sMX@ zttmPib#~3e%Bta_zX-|G^dn^*cBA)tY_STQSY06zFd1Q`q-Nx`t-!H|=C1-X-#LD#IVt><;1nC3>6sM`z zXyL~IEv~N=?>xP8*2_8`D`lx12~CkbJ)(d*>2MYYnX7Avi*ku7+q~txBAr#+bR4_r=a*t^i0Rpb-=l~~xTq%Eb{bi`TJji~y;4rKtBZ>cgTXdc zS#op$lPo_KC6=m!%l+71l8q`wiYQl5*taPv2B6J;c8i05T$%cbA>h z)4ykGSQF=m5`riQJ>#HnkE^}+>BrLev_3`w|DHOYbc*^?QFQTF({w>v$l?8k&snp} zKFnD}%kZl3Z7Wg4Ie6B}?t2i)QUT?WnDEq89^JJyT<4iPRH>3#@g}zPxS1I^mRw=`*N5xkt25z708}!3 zNdp189`InB#~c%kuLPE$+KsaPbCdrE5HgV);5<9A7F}+`?_}F7Y>d4)<3lhU=H}X~ z!P+TPoTsc{?o40Gn3*Ei`x`oCCGx;J&(NIdRo{@19eZ;X^EVH|(wBDf}Nzh0ebZJsew3OM{1 zKa!F!z>1O1-&NP{kK48{rb0xwadTT(KL(_qpVd;5^>fd5g<}u{61|0R#1wG&y*Tff zQEEFFh#h=)*CmEX{|8Xv;{tNT86eKD7Eg2JZ#zxqczD76xn~4OckPWNFA9BXON!;& zuSM|(gu0n*PRw48X6WqoRA28rsmAh*pxYjJ!D!l_3MS^YWuyL!Djot5ngbVEfB0`M zBGV!5Q)W+!{>y0ID@xgvT1tC?A(Bd$INn54;dDvz+5b9-DhyGFvYF}#LhgnZPX+zm zG_s!f9p3-(9v^_uQMD^JGjN!DRlDz`{Uf<&4bQnMd)7q)u)zNuKN+-8e?L&62r?~p z`){OyKu@-KBtmD23D|t@7}L)Sl)9J3@M_2^r>WnpW;=4tYta)q-jvmzL>vE*-}+WT zTzaw|jV4ST|e z@3*n>vx7g=`Ty=Dh90j`p~x)A?AGwJ(picB^>*yUye9$&~pax=I&u_gM`N z^C?mC`Twl0)W-r!{me|@cGOS8AI+LIoWQx5a{pPUaXIIiO^HWayc8&7#;xY_C&KdHirF6JtMDIxe2whD9Li{tis9A>>QM@dK_!%p4E zbu)8X^d433lI~jgvH>O}pBwW$srdhjr^GB;LJ2V|e&aZ$~ zh>aQ2HOi=~hhvkxnD!LYJ>6@f*tk#g#O#sILgPb&sGLb%|ga*CLHZo1eI?FtzDMK8?(b=?{;i{ zTU+g#y@z`4ZU#@n!J#YOo=#7~;xPM9&(A#^4;QPbBiws#?>TkY1*bPb5oJf5XUT`~ z?gNATSOu)FG+6DED+;FY>)xy>(I04uBKQX)6ftR*Hy?n^%)p944CxDc4)9$uG46xo3^0bsK=iNqH<#+3qEuG;aWL!OLr`OMhbb7|Z$;e<9hdN{RV?xgbmN^FW$9Dpp3nP%*?9G zJWZ!8^uP=9qIVhqx`{p_#47K9>;FZhR&`$NZ;d@638DXK^OA6T?KL%;+yYV#&AkLz`fdR{5 z+4{YtDpdQQQ??#1%MdH>9?&Ui+2*DX{3%Q<2<(|n>djk0cUqO1kH=w+r9uY(; zjStGB^dd}teNhkm%7@|su@2r7P=xF{8(f3afCIWv@rr^DUpfmcX2D{jAGb`NpCUn? zCAGQ3xFcN{ANM+PP0(SwQpM-g9)jWsy4cQF)s&NY3sYi;VnlzOM|eAH`U7sgk?ZUF z=^iZPVCe=2zrKDi(xPIbo?V+a4CO7TZ+vXB-LDK?!-w@cU5+3z?U7aJ;nIKwk%IiS z!?*YokWr0;vWEi=(sKRhrZY`iS34Gubz<_Qq(2VUUD|Cs<*Dq+9je#yYH0=Iui-g6 zwz+9}UzS9dvSk}}uRTviST*U`Q(tr4e))&{Q`&&L<5HiMe*Fvpq;Z^+oMup7(paf# z6m&)T>aK+~m%=b9^8ucgLf#BTXVB1MofNqh+F>detU(7XSu*Q zHP3va!=Fd&b}T}o|HP9XIzHKcKw{A*?;Y;Ka~9r(gx}jHK(cJrC+8$VnrW~dLS`)z zlQ$#`e5z%BhVjk8;fPMlN${6%Cpjnp4jy_Q{^rmC>6n(zUd9B2~7;KyjyJtl{tlF+Nl5^0Nd4_bdLBjCOf7t59 zo@j4!?bPB{{{p3jzEK6ccJ{Y9n3PyuVom*11r zm;qvcYoFbY0^05GEyvVib?MKiDX|y=gfWqy2Y%-CzJX>7az>ei)9>BgDII#a@oX#+ zI2&_}YFSM$g=3wvzMvW0NHr zGW~hO%wW=64GnXRiyCJErJ&|h8RA%fm;4|n+ov-pFYs3_PcVPY&J*^Zk^#Ai8){iX z;tLtw6GgZP$br-z6-gULq+TiZ+a?Oj($cHkLAG(fZrpb0{7V|L3v|ey9#rWe{&BhT zGN-=YBX0hK)tSuewcji;7u5x*m@A2~u)Y8RO*gsIkkDj{A=5zV06T4vAW{IX%r@FY zPMoFxZIZd%8;;17mw|fC9O~wLr$4jWcLJT4?M#97pF-LSb81l*Rqwm_Q?robWjCp0 zvG)I25~qI*$w~fCO`Oi>?@5tDZqi5ipDPh%boTOyt5sfYw?xbT>I2^&eQIhL?FMm?K{?NkXkV()>jBr^ z90W6LD5^&~mu2`L_Wv9Z<8lQlyN3nC-a}+)v}51s^C?P!z13U=hsUF47qJvc_Tgj)>2(rBC^C6&V#} zGMqrC!NKg|?mk&4pIOBpDg32XFpJvHX>aJ*@ zsF>{&xtxP$_6D!oqc=?M&!gq$R?=Sm(ZixIw;r&W%-9?x9VKH3{!^H3^|*C-c!-|} zlfmQ2=vb=;he1MuKDpN5V8VN}o>X?3^^MSfO%qTR6a)h*`2eR9&@B7t<`~{14JR&> zPe`m%?eDVGtDnVeaeV1? zeirmvWJo;+UKy5Hu29J|G1%<@IjjAhWSYu{u1nF}je~S4ymayQ91ViKzTQV}dT0W? z>E8!_CB@B?A`Pe+Pgm%REY(`7(f7MHJ3(5fmYdyp1kU=0@`Qvg1UYbhvn1M`k+Y)X z&P}6tn-$C6(L-ycWka;;sWi;bm{I@8si@h2B&in*tL+!p@Y@!M$P}y95em33r{966 zj^2Sn^lT)BX<`x|)w;(ebYUz~HXWYgf3qu%EH&6No#r~_%RXJchO&$_$vss7jz7LS z^Zyv_>dj85Gucd`JP~lsiB>T#Gr6m!Og(mR)!}ik@j`W#{sa}RhA$$WLm#9mU{Jt+ zNK*3wWFuj0dTl|_d}47VQI~w>)p0L?fl@E5UfOsdX1=2SZf?6cJl^TeEOS1dtG9AE zo$v1m2^mG;J%8Z-GSx4*A7QCZnu^mfcZ9a#b!4<>J8q^_vwM4g_Ve|#9*RP^m}{Wf zNVjdvko9fxcKI~3@5oQC)eKU}#{tDZQboXZGwun93~lIL!Xia>lC{dr^%jb!5##fk zZsw86*tx&o(tm*EN4#6`^LWDA?mT*XIozR%^E~o2d7N0vcpj@15IVQ`DlEf0?w^p! zl%8>(Bm8_U&wQNR#7;D*QarBADJ zWCSNXL)dAg6A8tek=TuPIE>%kQ4gh$I4Cr{)hf9nYcfKxEP1Dx6)<&U}2XzR^tlRT1`y82;`Vok}Dpy*#=$5V^0AsKRPLZ+w$ z{MP#Vd3=q@OZ|dNcr4Y-!?1x}YOe@h7pY*e5Gv)yV?Tn3FLFI_}_|H~pbh(hFHqmNKE=^zFsWoZUzH!#L@E=FT zkMcnZKREQqiZm7)>sYv{hqimg__k>cm2*NZyKLwpmSVz0AIhZEh28htfHh^=rE!W; zvGOYooRF2e9qXjD-Ga+o9a1)(z%@Q`Lp+Z5%6AKkrvbt)kdRzH&!gepbM_`U_SXt zj!dO+@s(ksr_;wfVB>XnL7ejEVY zQE?AJyw?gs?d%7CB0mD8$nM=J0e|#&suoqigc076e7ZXE2N6n_pras*J#Oq(PiUVY zRW!WoMmeRGG{yYEr7F5pGX!S~5`6U~R(b=^E_~NJOy@BopD6fp+DEYe`|~W?4Egf% zdP?Ez%TdlBMW?WSwhrWndQNZkjzIIX(7f1E-*!&>7*Z8+0Qbt1Ix+d4^X0JQGuzPH z0>Z=IN)Z*e`*9)(zclPH`(5!wekAnJPu1To7`?)f8+_)B!;04#Y8m_A-lfHC1p*~+ z_I2rAVsed|t9e4ZvnU4%grujJ#yjL~g`dzyz+XEd%HOq}yec_Q)+LpR%|4gXus(eI z#LyB%$=g_tq)l(q?iP!ybTrYhObXqC)XYz1)AEt90$?fWzb(uElj(p9tJnYsep)*- z1;gu&5qvjcFve4+>BxtyjCsHcXPt83&MV=SfCH}Up?L)55g@EZQE0+bBO+#ZSZ-O9HrCtf!4Ho|=ZQMPGg zh8{iy?e|%V!|Koh1h=krj^Um)oM!6Ai820r;`ii{kwTl44me}RRziIF`(m3SbG6Vn zSNKG~oirZ8q`@|N5;q$uS61`NG(Gmep+!0M{q*?b=kl$65F+~*aUsbMNwF_%o>YY_ zZ33_%8+APNS3=?}A@^pK_>@aG%jPu2Jo^*FV~3$9s_wi-odV+f=ZYwi6ik$4`wEHsCiamY+5!}F%JeZn%ror3PcW!~jzwK>ny)P5( z3MxWoMW|@y%#YmxyiZ9I*{>1*4a&_}@9v|48XPifT*!%xNc0|6$$#WRFOn9VPHt27o^*8f@7)V42$66pu}(ri?0vP z_1^8!!@W8Cj767U!xx#i25>H0Vivqqhl`&Go12BeWhR8t^BDtvj@JjV4%BMTa1*8G zl*ft!*}J{5ysYa3Efb8wB9Sn}3$Z)HGq`*UZMWyb&$uWket>K;AUOf(ex3n0S)yfL zmz8Is9#fMmaovy$XpN?rfwZa4Z9dp^irGd~-^rXdyMh$!Cv&B^|EXwhCEy&oY0V}y zkcsAuvTy-+jSPFdc!qBb;%!W3X_R|I+U>1xdfgtu74rjf(<;wqu{o5qr+mM$#moAq zs}JsjfB?S3qE@j`?hC;7QEiDW6{WuVZ^>wt*2q8yR-;T6xDZ#Pn0@ZNt)uC@esb%O z$E~vbGl{?=q&icob?Ks4@WpwZeZONm&g^93u4UA zOk$xL%WLCH;bD*VT9S{W@X*t!P53zDQCzi_r_Jd|s`1&_?QBuaqK(dM1fE~E*j>!A= z;s;~0pjH^VWor&&wt{TRstf*8@m-U>el>{spP7@}jO}+8B0cg82HSV+fZnjbmdVQ4 zGDWL6kB3<+ps-k4H$#tK%iui)`|20hvT<=P86t_Qsp@Rj)Yxcj53IJ7u@=}3@#PG% z^~negr$nkU(-lUCoHdJ0PSfvV|1&dam+>yxAQa6F81 zXuddYg6kLntW z&kOY2J9^Ob>~Dm$C-U06w~i}LKB`-8^yf47sL)?Y>yNRMb?me$t6yoxMn}Kco=5a& z%s$#*!bD|88V=ywCQc{Efspvm8s^I*E3kC;Hg%iXzl`bGQCB8N-bS+7ZK}O+dLcyW zdc`9N{d#ElN9x0z#6ZFdGtP5(q0W#}Zz1YT#CLdU`gqU?#)Ia-HbtKlG@d98-H*XZg<#;-a%#oR1S3ya0&2TKik$V!$xi}(tJK@(C7e)^k zu3$wP)!gs*?X@*$Xb45Sc38Wh@Jcb|1L^t?ZvOGiMIk@5bVk$|w5 z=bwo!RMQxV_=WY9oWY z5^>-50FMkxAs}ds2PLj#()1Wf8NR)^0|Fsy%1<{4CNC736^_l0)&46EkVSJ&g)X(Q z`)ghUc}+LE4>!xD{lcXNo%roLOS=0Yj)%R>5mTlf6H=(5-p4@W$hLgcS`X&AjO#;~ z1$am_7=eV~AIRYLCyi!eqW>=JAT}_NZ$YE+lDUhS3SIh-_Y!SU$HSq`8|qOC^HWWW z&w51*kg928->0@lUIOq}f7$)~!Gk#2PEkcX+O~>esV^SG<9OUP=KYC~8BDcF1)DZZ z#Z3u<$ei!j40D+RXaHc8Sx&;?4-?#IO{Q&3a6k!sw(~4QKfUVFV1Bcn84Rd7M{*=% z+kU`iu;#}-xiho{m&GRm4iokg!4#-4yk?p5RI$%ZTH(?1^)jII$GTC-WS^USo8|r+x z3;H#BI;A_iNm={@apv?1r%a0@KS9shtK=a|El#^60ZX*V2J%zNeLETHd7WSu0{V3L z()(0`KiHp`XY+4A!2;og{0ijRuy#)MWH$`(Hhx8?h%!tq*ewKjlz|~b+O*-yE|cz+)Sd_Wc*kIa_s;sg7jk zf;T@81*z*+WDyo?30&_@3f2U2LDi?)dP0dCeJPJo251xIfgfP;n$91o&B6sz?nmwX zwobpf?(R)~b|t*Ta_#Lx^?eImU@ba@ElP_v|+rDU^0hTT&$v!y^qBi6Mm zK35Y1oQ@#ACO=C|v0!QJWjm;`EA(Ds+NSs2{-lS@ieJ`jjxNbjfPrS3wD2p3=lk!f zn~I?peqE>KzkOrsm?f#fJgJTLFLrp6c!=wZ8>=QBGM?ZvY4aYJj1}b7eT^{nUAOsW zTbxk|&wjVYbHE1klv&}=;$mY@+t;jtU1=@y(4s?g&FoFMe&Qs_QV?lDP{_03yOfd~ zee*H>~Z ze%*S-t&^ED;yt^fFufJZRrOc>YnhCPdP8dKyO_ee^q%30+ZH|1lN&Y6@1aIBbOk{) zlzRrVbLEF^3`*d$7j9%G3aUp2kGEAD+t>+yab(O_zxmcNAG(s;@4!&R3m&a`;ybsp z!Bi*5T{GoDMc}|8A1eIBctOBrpLk+8dD`~2Ks9N``-zz4@m{u!tzq%#bDYR(>gd3 zrPmXHKal=o#TbQFE%*4dsg(KE<4V2?L8Q25C!RqP?W14rqy5n-w%VGY|Neb#)bHK& zylp$TcnUh|eaxCAR)(8c$m2Y#;`qn_N;z*~ncg4KTgP=Ct|@1$i8evZ72v63x?2yN zEmdZ#qbQ4_#udj+e1b*!&Jas=pK%{8DU62p9cCnO$4h`HZh6f6%xHl3KG|`ci)ZOC zrPNJPcx?`4u|XN-kiy^Vz09Os3l}Mx0yIl8g`+Q+c@wJ@$@tN145JiSVhR{y|Dv19 zl;dC=8fnGTJ(23)FYi};&-uXe@@tK|9@a(K=v69J0jb5f0;RCp9MOrSYl+QEx1?8J zh8(q#w4}sDc@GZ{20hKlDTsq8fnlYc%eC}093 zzz{R%p6%CE_(q8WvtjRf@rV?GyjmcOe+7S)ixZ#X&Xf{kI<|LzZEN&2MUoo*n3G(M zOWM^x^ctJ=X12X#esnn*rN>qC{oQLx@4}RR-20CP=6HRv&0qHM9uKTKGwYp+xBSS$?TKhIwEnvQJYbtPPlC`vr)L)R*G^jgM=EAmDaYvd zk>qNw>L}`ire^X>7l`6qb3?i_@AYv{iWKb$ITGEDyL6wosc21bo*J{if8D0l<)7YMLKX&FVVN@+fA-Pk@55Z$ySknPB@3>&)>qVJ z9IxKB+y3PDL0*d{D#(lS9^+p%^2f%ag}EpwwxH=}`p=iiRrlM>{J@BDw&Z@{HxUX)bv@M+5PDkq~QqY;`Q0wgfB6~^+ME#<4u1|%Yg+xUoed+ z;8?<_`k?o4WSFvJ+LLwL)-5h=P)Qv#3^Fgdzujm^miC{e2+5Kh?{u%<$X8!oOOcqd zb$Ms|OHF}um|q7LSAq;~*Ucy_*I!x&xnt#>E04VeGS5T80{lQi_>id+2h?O*lLxPFtk?y1A~Qtw21g z#TGK}>&dJZ=@*pQxx1}6n9LNP54Cx^i@$db^3H8^MJd%R*%F|>1KRIB#e|da4Y;uO zKb?cs-(jCTjf{WZ{e%*?%e|uZX}qv=YZguXz_0s&ba4G1w!q@T&jyVB7tG27Ye;5v z92G*3$(;V(Gd=bgK3J)M+p?)1^dkbEvq&E0=-=WG8$VsE4xw^gRoJf!d3&-8%;gdU z7U>I>EH^TKzofUvpS3!~%S-ztVY?!CQjW)8cVF|x7zCSrk)|!`moY;H4VG56Z7tML zz@#kbvB2oNMD#OY%=R1H8|R3bp%yG5pu?ud)vfp+7PFLGJ4n6`7aYO185X&}crB4o zmR0|jKy(+xb0(h)pHeL90UXMK0H~Gl6y+!{=IBW>Ik=C|$ao4R7V^a6;?mmIC&qGR zz2e|FsB~mWOh}g)-IUjHz);RHKpR#VasaGlnw+Vy)MZN1QiHIiSmB7Pt+C&iwaI@* z0;W^*>*lrtAp#*mI|@>&UrEKq39=wDBLX)Z*8H3E9u*KIR-Pfk!>|@WH=$7gYolZY)leK^CrDNRkU_N0-wz7OcR0N79yX*9u zA+yI+Gt;0~ zlk1={9%WZu{^Nw1ZonGxKzLKs`f7K?_0|yfj+1;Fn|BQpJ4xk_L(D`sc<7CQMziJF z863+4?^ObBRd-CDXmOFyu76q|c%O*U_TDyIXJz?%KG!-p;@GzVh939?nC*&^+{-bIn;*V~iSAB@T4R5dx$@XrG>JE?%ld zee!MXOEh@Ctz#WXm4hP6cO|_I0bRx@9RNTUv+&nom{eZk>8`XMdjA{)z5rY6uPnOnti z{aD>NcSTSC>row>p)+EKpZciMJcOYs*WA`@$k)wGA0=jB5Dt5AY8-^@ttV!PK2~}o zJ!L%sT|LAX9qao!Tw<>JWywGXWWFbJf!d(I!bgEdzT3}-bC3GUvHrLmR>}0&99N6& z34aHoWMpS%!f;4B0VinpNsF#A-c(yd196l57Hc0}9eCUhU-5KWxY{>{k7lc78x(4~ z^qD9rDZ{pum6LzP-^g8fyb%g!`H-BRw3Z*ajNAA%++!2Jwq6Vp2xl8#8>i-3yAyA8 zAA@>8BTv}qWlneQTh%mC#UrTZ*^if6m^$1qBdiD~dCD~~OP(=|;S_u9ov*ua&KIb>|OFJ|2qmI8_BU9-p{mgS2R`^Oyl)I$kszVGeSo?b-hO-oBRgG<7Rr}3{eGS2@MN)KOL zi)AWwy2pZFg54Ipwm5GH9z!n~@fX~RdY*9Z83~8nPPcZ#dB3r5e{~EmDQ)z9N=2?4 zNwBqAZacBu{^bIRCRt4>Zi)GyYkgq>g^!0P17SG!utZZoMn$c{cw>%|(o0%K2C{bv z(fmUVlqS%s8E?Q3Vflg1H2xrR2rloLTC>k9$56lP-!&!$T*tp_{OChNL)kMI!M}z< zF))>mc2?Hd7@|G{(V&bbaxSraupNt6U(k?44>)3<_63`enuRO1#u_xGJZU#^_t$X2 zx5dap&_B9-tFsFs9Eos)t)9|r*@|2uxY+yg4lc3g_Gfb3tWT0el8znpw(C?Be`kV& zyB?XGY%HW1yH=oFtd5tdBEO8#R+Qii7E{4GebHlg<_!8ae30@=&GJ4wEP$HA@66eg z2OytMBVGwolNb0zqh3%!hlPZN6kBc%+T*s!46U)&Dh)8xmNZ+vI&!{xg@6=8_KJpy z+eELCR!+81j&P;h=SD9O2M!d~k=zuD`#6v|Oa@?_a;d-Hy-6NaQ#`8}7vM@RM`qo#}xl7ELCo zcf)Cbh2I)C^w&}$Jke0H#%CXSn8Nm8;keRZ5fOeHAKor)rwX z44En&Qu%pg84X|3aF+bG`$0_6#$Odp(?D`T#@Nocw+hz?IJl=VU*%{S^{On<|4pv- z^6G{Q+~Us+&{LTmZ&>bpqTST>*%p}b8;ptOHA6jy;|+MntNJj+&UtmsWt(8^|$0~b~;zmM;|Y5kw4)2vWgC@@aQ^I zaccgYm_q^HPE|k{8%LX@=`P)`N_ooVrlcyM@^y2G@n#C!`>hO3N(ymvxBq&4DMnPn zd$) z9+Zby+|M_pxI{Kj_)0PlRoW5?)GN00ePY}a&!_jWwiP;tewUFeVA87d-dr_q=xl42 zA#GiWv`iWMt}d9!*m$24$T;eisr6|wz{T%HKUa&0Y%}m?&dDbLwIFh?d4>TUT_C-m zR7pdjoawuiOmOL5&`V}M=e;Gmbo(mkIH?-RTXEu1v-It5iL zb4W~WE1>~%@Okjsj0;iPh`h~$C)hU=IP^sS(!B?dD=(Mei~gLRaC3bp=O3a}c^}&& zCdbJ*uUAlnHrk$gyo`$6exMdkn0^-;I-0@>hotNyQAy+1_vGYfxaA#Rj*UTr%RX27 z3XuIUVd}qEv35yTSC=reRM`;dAfGu*Ahg=bew0QgB|l5U=x~(Z=cD7Gf!BCR|HMxI zJy!85%8qwK@3)~zxF{`dd|^U7%@MwXmNs(FvGworm>BJ^8C`3Akq=N@avB&0hXjab^M^{Q6nf)}S_)~X z?NoR6cyFP0UVznl!qXLGOv%Pm)&7CHso!lR__@lJ=?&9Y(Ig*NTYz;c%>3usvaT`3 z3=*M#8f-rqYWWgjj)}6_sz?6W%tJm0qm$9bfaQ4e`;vIWTEz9Oh=mlKb|Yrv7Rl{XubjEh7=Z9dS_c|VuslV$MQlmPjDoqgLY zl)k(seDUP}`uPIRm(4!ah@J&HYQZ;_yWo2`QxvCg+G-q~r;+(L;O7PY)bU#G9V89}?4j*m z*5l#LTpCY0ho5Jut+l7nU51zGW?>Xc4X~oT? zd>@p}{);Y3Ad|M!#p)uHk=UmPUj8ggHpgg@uLD1YGpF7-I04+!@dtCThti>gy!`cx zJ9%ZeZ*(|bz%Y*zsl6Ri=oY4yHnU$-6HGxqz@U8prdNvE8d#$aDE+QySEdLmT1rs) zn0uKDAURjEQo=eVr!J8O8trkHE27M;HG`~NkyT;W!?=GYkyxOp{J4W?d<8OByLIRV z6eVhL+4LAP4`!5K2g^h`h>Czcq^`aB9w=P!))BoYnk=hrn;gciyctPR&u#4Zik66H zr=08*bqUH9u|&A)^$@VHJ4R3p1q)?HI!D67MKG*6oczH!wgZE_4wGB|0WMC8BS5u2 z;tMPWBbA8^^icI;-5vIphF>4Y5Q21ALw1uRNR$&A3PHXI)>?hwiQ1Bf`~5z}wW8c* zRXxOFv6~}zW-Gko^i$x0F{8gf6%O&q3`Kz%j9R#@G2C`3?$$n2qV~y>|H%;?JCJy3 zefkS|Vr9*=+{Kjkl3a=Ro7lP#U|M8rcZGs^8)w5pbA=K6V!tNa^IR|dtRtgKeeo$7 z85>85FtGQ7l~8{7_AQaNB~OtADZ8KW0ovFi>8u}^v~5*4@JT1r@Gm^laBzSIn_pc) zhz8-RDm^>?`l1cKyeHMj3Z6;f9T#LK=7xy)ZT88A%OZ2&12QK5ONlnJ4us*cAw()P z@s~5NM5|-&W&Z-pFkR03)TOlI7u>~#^RY3NqCj?c^pg|BwTDCMw&8cAsBZl~V^w;X z`&g`w!)#$TC+u8*QoeroN9^UZMRy*v;?lQn%f02IzZ!71$%?fLQ2h~Fg`4Osy!O2` znlCBilId`cF`~1fD}GHPZ%SA>Se5XC{DPa4$XI~8d1Kpo0ZX$*TJh?o*oqFmXJ=)p z#mGZz%Vo;m!EdzL=?&^Ce9BXE1kM2lm2rC7lW%}745Okt;o$X@E zzgC|=`9oIlu-`!gh4V&=XWt8>j$c>Us_qfZ-@;9MMI;L&qjVBC<58CbO5Cw`bj0ZGK7s~GRH~o&lHz(?6<)S>eEZ~Tw7QmXX z`y0caxkn_}bUnHl5ew?A#3P_nv|2<@muji0sl#H4_%x~;Cay-ZOTsD%Q$Ev>5zj4m zIStpQNjDiCJ+|qS&YVV5U6F~;x5J2qrW-QcDnxz;3^?Q6e&wB$W5+DZrw z-n^c&kzHARUlarQKIOYIrVUm1^iaaj66SwfMEboN`k^^Xdx%sYW9LGSD6pmz7f0Hv@eN%9xXWp9A!wp*YSEddvV^tm2_dqdIJVC*Ehk-u6}PaG|u zq(P%`OhX2H#5&uNQA6b8*DWN%)L^U53ntT!>h~MyYe}ZdXEae}S5Iqgo@yw&R@pOT zi*pqrJEg#xSmF4E2J;4o9+A$nnkB{U8_4CSYv38~tqyMo-#oRrxrvH6{lTf$l^%GF zN6>~WP6A6Eb=RZPU(uJj&<#561aD+u ziHq5s(C6r%gD8*7>bPglCDKK;cVSFQ{C}lIMyd2jED`Ou3YrDntG6&t{V%x&SY(q3 z4FE{kU84L3W9RrZ1SmTUISRlt?{`=S0#xgM`O32^rWbgg6=c8|B)e5rty-%k(xp?r zsNNPxxvaHBi3v~C7Y;9(CR0JMJtC>)_(FpF3EZ*hCS(P!D-J6>-IUZGJ6`9kdzOnc z3nfLTT-Jx{{1V)E`B=B%HDFCavm}1w!SlKQo1`vXcRQq}kDA1Nndps|d(?k@PVN1U z?)HTei5pOIwS0%)G+!Qi*e(hi>FcN49Fzy#kQ`LYl~7?te_I=2(3whNs*f>$o<$yU2qfKGuua;_4sw6WZg3)TLhCC-p7K8!jIL z>N?j$I%3}`{E2?acp*X7uMSCx5N$EDZ=XVw{{G@NwB;Hc&+%h1SXd^494dcrw)ytP z7o}mZ5TVs@AFMr_KUGKZ11jgzJABC*lEq3_P&|VB-vKNsDM?OEUDPtigVC%4Kl4q@ z6AQ#`IP6X#sZ0z?GNNEd5A2W-S+lzEN&c2D_?EJ7} zR1q59^QVh+LIoA>VHA&iReUnGaHN06$NAo3n_?}ctsd(hUf)eG)<^j+yGTmH`_)uH zl^tlo3Qm1ebQC_-;tL!YIEOI?s>xqxxZ=ee3>Y`|&@+5CX0M>GZu0=0L;gK_GHG^cPHX8L za=rk?Cci2DDZeWKPm}I$4Q^%a_Be|6X^R6?_uf|2@J%lWKz1CL`*3Dc(?E0s+#y%K zzw|9R&RDB>hxEc;7(Zi=N_WYHZn8vaj)`h@GqqjT02&(h7T!!=RtB-p%>fxAYc60} z*;VMp7X2;{psfBMn2okDhkh_Tw$c)p2Aql-n@9^Q{I z2gTiIBBt%8o@dozD70ZqKo3(!AN=!26@3Cd?W{xFokW}{>G$|IfuTQtxq}a>770hI{wq657RVDvs(n-q1s~EPwy*;gq@hAFHYDVMAJyKw(I@7Fl#0bO&&_Wu| z13X=Fr;tnlB1;(Ua|FEx-yfRqO;btu*K?EJ;S{?Z;T3f^a~0AQ_RmV<>V9AK5}UPA zrqf;<;{0~J&f}l#rf@^$^2! z`Jf{zq8SLh(-Q&Qv4D5-!$d`@^-Fy-P@x)QHH0MZtIY*^{M_OlPapGK=?(p1X2vV#2?4^(liX6MRyjWNYrcYpSY2Kttxk`mh(D<1EpUhSJy@wN2Re_vPcih#iq-5@lwUI7+ z?AJJLvz>@#=qUA}D?>Lbqkg7i!X3@RII9jzwtW01lnKVOGblmQ?L;N%8+50Kpv zl49j8Bl6-gTa5)gD$;_BDR5zTQC6{%T7qAFb?k5$zXU__q4sLp@k0$GxZ_J8cVAL+h zuOXg>sC}yIr3ZXa-E|jk36EPq^H1hH=>8g@bU?uh&B8*}j!`^eZo-GP04Olhq9R`E z^F7yT@GcVsnOIClI!m+^#dz8EbVz0r{btlZ+5$A zCgWe@&?Ct1wDIV{uz=IzW1mYlk8YY+mC1h8m|Bf6IJvkiFV)Y;$VzgKhqFY-zM%in z0*n_pm&G1sewR@$LK>)NKFN%z*!7sK53e>a{T3k+z2t1D9r&{?qSh;Zu;B1Kq&K>p z$9gZo8f9d++P9`+%(2>e!P8E&$yt-QZ13R+CN8EXV{mZs`lMV21UAQjoSP^ELB@~+ z$sHHJoORggzI`jVYr|ig)>v{GL*-YF|1jrdE~gTb-p7C?JgInefvMQXSuFg zDr?DudvCN_T~gFRt>s(`NV3XgziKjYl9H0z+;$g~!wEdR@mrD8Cq*d;Aoyl^GqLjK_Yos!;AOh7P@3CAiP)y$ zi3Bn`H94Lu$#xo}JzGtYe|x~(CTsEfTi`8HrCMIoHfpsn?%V<4Y(;w^WuoBM9DQYA< zcx>tOBeD5_%c;hQ23Twx^Y=H&=c}?4=}qRT9#8f!9mR!d=5?Oes-11zJyzDQXk4r( z$s)znN01NZzh8p1eQs0-hIxgOZ*EN3Bt?qVu_0{MhMR2>2e$W<6c6`85O^*-Z(&zB zX-B0kGG%J8#3LC0#mnOR6x}4KPewR^ma9~Vk)H4xlc%qwhn}a>%@)G;*dr86jF05P zg1&0w@e0lP~baf-}HUdW9+;V)g@-K zRavV5VHe52K(%S3(+_yWsDN>Akw~~4)%>C_A~1d#5OZi0gT%|-d^q|mP-=2B+cE(J z(D0ku)W2PXMb}J6OS^knsjxn zru`aGpzFj){Z<;73gYt~g2LF|I+Z<;gkHHmCnU!;#pa^|Vs`JnXzAgnqpD8vbNF0M zXLl)+)6yDzhGcl%PcT)Ee3oDY>24(sRU>% zPHTG<7N_KHoF4bMW4%D;-^sr~+p@?Arx2aM-z~!I9KIjK8qf{HNygI_v+Yd~3|DL8 zpCLx+{;AY}uGWo`I~{mUP7!bpA)xLg|Be3*<4zjZ9jV+eSdPcT+f^lOo$zUpI%5`O zS%Q|0MwihH-A)NVWAb+9$@+Qz zJhCm$-+ALC)NY507%F9(B;)5NqV9x>2<_N`lBvp2s5bQSN7{1EbD3M=WIpJw*)|+B zv;lK>Tl=PBWgliWC>gs8?EHgAu5!ap)9{=H;k8vwyRPO8S;cZ9ou%_`PFC0Zya~1- zAvwG1+froVcaQ_04j;I;6I3cvheP6+Mw3 zd~nomDRQ{qR=VLd4*Pj}QgydbB8$IUFt-NnpOEgH6C-_(Eq3=F<*-Z?7AN>A&J~X& zBnC{P(0oW;|0}Y{NVC+|<>j5r4SIaLFESig%^`HFvWRe{aGTBb!%pXsEkDy+1z1`) zh;>6pHKR1YcDQ~oOwc?OSrUG`W6KFsh#jodIhddB$(IFAtvjBQ{H9N;?a|g~VqhT+ zh{@vo_xzyY#5fDLBsl*c4*AG#rYub1xkdKL=-vzd-UV9UjbyYbj>zweSjS+IwxtWv zGuXiBZV%SN(Gt#C*@kb6gH@i(dT4L>6aT-HqR<~)L7T>s>dV#u~I>6OhxFs74$%DB(@s+3@p)f6et=y>JU@WY!0 zpFf!r6@nd{5$U5%a~mubx!oUdQDU|7Yq8s8;KQ~y-nk+S#GLCK&UM6%?$&+=oJ%3j zi+5mf7!qCjwnL=j2Hc;YdYzp=35n6rF~IOGFRbX< z=})$^5FX#ocv^Sq$dJ1eumrpBO^#Kgdz{R!;3aM{g+_g4dE7i;lXo z#%r7)`cKzmCj=84+i52R$7x=k>v}r~AOR~PYK-1ad_jn<@LT7`Hnb3=90A=21;Stb zw+vW7enW4l8xDoTk#1m<;W8B|dwn%}9j&{ObPmOXPHi4HqtyQlh}+&D);ta~%%v49 zi+&oZVNzE7J@=u*oJAobfLLhfSw&8U8W?z12O<#V0PC}r;++ie3gUk`^Q_k7cyKH! zbiVvPC6m9e(};yh({sZ^my#6U6+ zI=HX-JUsfo0);gl6R^#nGsj-Tx3Qa<3$vI4vz;^UDkJLJq@=pG`F@OwXdD)pJ=Y=B zgd%+kATzNCB+in{p6JFlr|Em9fIj#Cet>#a%3eSzL2*f*KNn+c@Zfh{J1#|mdrU`1 zM?_-w@WMh-j#!XtPr2kDVoXXJ;C8+`3tA1TmXZ>yY_!VjEI(dbdg&##7zIv9TW(5q zG2X=QmjUP6uY~%`Mw;?7y9x_Wd8E+xZ%5m6U~rbU>@`Q$Gt>{;<}Ygx#KK`psMJ~s zT47_y-1~%6L;|VYMcKXtOSgS3LV*}HN=y1~5F%bVtf6Q?Wc!;YuHm3ZMYHwv*)XEo zm%i6B37W!8wV@xD7rd?)q8%8k7^nBfxX|r2Nt-nSaRLvx<|rgnnN8M?+8Wj*2};Vz zG^(I<5OlP~-P|6OEoCVXLW^bewXE4Ze~WlkNJvVeJ8UO6@63J=7Wa6KfIetqHQUk; zGw(qubf1Ui{r*6M+0|M4ggYQS0` z(UKG--Ja_%kn`Zz*FS_OdGJDDBKebwYtwIjZmX1L)9K%TUAUa)rsq-=L~N zxdG;N0Ld)6%_)ZsxWn?T=|Ocnr|zML)`Rgr_iEqdOmU<%xS2w!L|aJRdb`oH>wIN= zz!|wYh|DfE0FXbw`=$E65`j|KY-FO<`7(EVQ1p?s%EJ8l;j1yYS6-ZGRl-xrq+KKU z)72R>Epa#=p|&q)5ozEY>gzsTsm(ODMwd|9?t0hA+4@Gi3%JrKs7_4(cb_aTYZWI; zmWfvMMNW$7Gh(*N&(yV@J*fsAe!>sU>=6{=XL14*n2MoSyRwO%C{bP&n=HroaO zKe(z=VAFpb&$yA?aDq;~L|*a_}kpbcV*8dvWq!0h`aQ^w;mxeeW{^x0~GI`slr z9UL5(lv5F*^nCV)5Zd-H%B3%o`f_D#Ol9sJPZh!K^#s|dfu^-l!MF$;?Sybuw#H6+ zX>^QTkp37jNNWGY#31#IPQh5Mdvv`3*>1#FQ31aDCcJ%5$bSmwkX}r)qUrKEz;!gf1{38 z7>NtuP6*U>ECO@Scdzrj&@w91lw0km(Z1`ojD369c`*WEns=Z8nv%4;oxp`(vir1W z`*+#UGXp=h53+Bs>KGF+J*HE64t;I+d~DDI6|1aExHS zm{$E5i_N>m^IPDC#P`~<64Y+R78CN}i$P<4xyiCD^03`e_V8#3`G+S&q}d%P3x8O0 z&a+anyibnl)MvK*G~e*!%H_CCe{6Bxhf%Qr)-F>z;#!K2YhC?qIQ!;0Z?>;i(|rM+ zDwnIk0%jtm??D~+e6h2sb7?v+M0NXp>(lIJkl5i7dk~MU)$coJlz#S9XEX$Vmn7Rq zsN`q{e~7yBMWjlG7KuFx1yZ%}(h~Kn(nRA0#jqAPR?=Uen_YT^qm{f{D*2k#k#f!e zdE@AijMeH{VEL5yu`f(Dou{=#(yAKf!5>2V9+VX9VTXg;*-rs6&z0xs*SskkJ&b;$ zyX#HsTU0V#N}H|o@5a23WYr$z2J~AppAFNL49dT)`uNd1-U6S9sdv}fQaMZG%*FwQ z1GP-0KsZJX#wU~#5yhC>kkv=?K`c$9gIb7_kJT0q6=~ObS1li|>6#k3^oK;fguBQy zbB{`^WsmwC9=6i@#zF*{=g{Xj>n)25 zBFM@`iHny9eRR=zl<01wcD+Hh{d*`3xxj;u3ftK8_y@N=A^ng2n<6fUV3ajSkH42z zcRb%HT~5T88v5VkTNwPZ`X}Ti*EL-GBgNqZ^sY7jyd6tUbzNUn90Z}DO3^h-1J$G( z3)x9PxJsd{RjaWvI07ty*&X){VTxmK<$6BM=7TgznM=X&Bf8vX3WlK>BcyT*7`FbP z>wKVYx@P|S?LH09x#=s{^*7^vi5G09{f)G!*8Rt&hV6yVzqs0ie{_dn=%AUs9ve`- zFIjr{H!u{(w(ABC65sB0?SJ0c!mdJSxHFF*Cdr`ZVYNGBSG&` zwNrj9e0?%iPFT!dv7msI{aWbL6So{G_Z?w&%E@GKNxI^BoUZv*MsP!##jKhiY#|HjG zDmN~bg|uC0ba%qV>jTo=)%*Wku zAtxdl_!~brP#>~2b3RpRD2e=BQfJ|MH=P<9QXqclD680UVZmY-Y?P13xf$Dq%XWQI zaSoy<6$*7mSRq9=@(o1kO~ol1@j)oV^6|V_+1n)L+izY1@@lRxOs`)?g~jai^g%M` z*Ncu2Rbcl(9G*AakW0Z9URmA!yCzM!1}O>1>n2)(!SJ{X9j2k_V*TBZ}7H)fQv$+wKQ) zmxf)PM^B38rYF-HFgxnL8|KehykHxc0jHu8|xj;iB^|S$dxI@R+;y zAVV?Nl?O1Yyz?uS<0j=JEB|cB-kRdW3*{NKOvO1hs9yZ;ge=O38*_3qs~q2!pkB-G z^mlFJ>AHty6^UYMPjm^A(VmPP@Msw0n;i+RXCqnfZ2(7r-T6aO7O5=JLfldiM1_yR z(Vm{+tQxA2S&ZS7LsY&5GpG!(R+d1k?6Ou)a+AC0=AK}51_MP=fID{a=mSn9FEO@S z>3_nbP4uod*0|eask`_hl2xwGbvT+o&D|OG=2jE6ehh{ts_0Mhwm0-}Bv)vWXd;*iRPTt)q7XQ1>yh*zH2y6j0;hbOj?b?)yNjSBX1&7%88JTIbsT z7Y}$eX*!Mz%vf&&}G-^SDq}~ZY@ub_N1PwN;-7XZI?_V=t%8P&SpvS5{=bvAvXpvZFb*H1i28xZ$a#F?y<5pHoH|0X6kQMkL~jTWm&7Z{xPu$*b0qLFyRWU)VSE$2#8MDA!2b)>K< z^NxRbviZE|9VK{PIctDH_5jGT3%{7KH9vZu*nw1}(>4oX2mp6A0}hg$>3HZpx58an z>BUm5L8F+`zoxXkT$!qrUx_LM=MjAvCJQ_PfsJ!CS}pM{f?7l3CN;fvMBi;#F1v1d zdfJ@$b5)1`U)5uPaIjY~VL7rB4iN^W?V87NI?XYKyEq>ONb3EsmF>X7qFYZSSojE9 z)I#aK+)9#+h=7m)uiUR|j0Ge2D^>9M5$yT)Sas^rVX`yT92Xakz3|8)(u4gA-Z;~! z?Uyc9$ZOhxw$8k;xd|)_g;rdKl9=ygC-_4J#@_5Ma)G#9xS>Io!ez;SG^b7rU$p2z zT6CFtj-J6Eu)#)!8C31Z5?lK6VZ=@VWHl+1DvuAVMRqKzV!*(x$>n*o4XhN zS<7{;6$(xkIA*z`n8Kj?=|GfcR*Hc`GlYW?$Al{kD}8CiWf*%R^Wa^3bB-L;y<`B} z@sMPI9aIz4v?8o+D*&SQ<9MB>`j0;ma0dPAtx5o zQ&Xv?0sTPvr7Cpmf@9e6eg|(B0ezS8kPD|GZAM}Lb5!_emiL2Ya3K)`7E%K|)_o!7 z-EJpcv7PEAHb;c?*SL&e zn}{7JBMW>+Zw5u7PQr4X)`=?}U5FH4`Kp;8xtVp@`xy4oPxpSmy9vb*<|xI@y=x}D zt!>E!I9&BWG3BaQ&UD$e<+@V;crX%-l84}FX)CXQKX6at-|9~4G)Py4loqwEYt==h zv(%+W*fM`S?U>nT~7&kh^A*IO&zsMsE88k+snlex^2zE_9R5U2e#1>=HGO!@+Y$XFH0wJ zss*#55K!v{=oM>S;77M6ouNk<&k8<{8Nniuwds?YFl1y^LKG4D%EEy$|JwnE6%N-j zx#VsjkHqg|&Opr{F`2}DL)6dRQmFfrHVjMXGR}>LA+g5V#Ud}X=CnfbgY;LV>z8b^ zzRacNAE*kYw_NtIPQoJ!a=N1bIW;_t98rTm*y!aH`Aey>-E9yZL$-=i&P86Ltk0-( zP=kH8nfh;%i*|yF3#3N+kfC9DIfEgT+u;wS2<=4EO66e5L7o)2?DDG8_eQ^p+x>1< zsoPpwB8>c{vIZJK^50L+h-01|ir40h22Z0K-@DEm{7|GL(ZKP7?KYvCJB@&7yoqBI z$(bH^^VLCZNQCAFzmFoHrzGJq+4Khw|60~CwyGeBsW7&c>+uerHrx1f=gZv%*PVy8 zxYh*aGDI=~5$fMUK?@`kyGeO}xm^D3to*H1l{~@*Q4}3ZNP7Q)MR#^k;t787_tXPp zZcdiB;G`K6YRp08Cx6RCav%o^+b{5foIEl{an@$iA}Yh{KAX)M4*SH zl7}Uyv}>ugzn2eHHNgR(6*G+#g2vn=sIeW*Xp9^*1Mgs0ly?f9(^SuX8Q|DY7l zOc~V4?lR9srq*qjo3E&!l#c%?*Y(}_LZ88(b7kMgLc!FTG~)BS>J9>W#1)ji)O_mL<-lV6HC#p1WCu2N)>Ym&@(1z+V2(RphZqKU}v+&lNfzb1~kB zxKI8rj>N0*f>0Oe&614{I)y*mMCMO704u9q>yKxTZ)Q^{oo{_VWyS*6eWVRuT;=(H zq!qHOoWtqFJ_IwX$PkW{UrWkFOluO4j@vwdI1m>i?5)XDgDSFGieSVu$_}z)tp61= zRzV)+;hhv`c!<9(kZMZ8CRFi1S^yZ@*9q`haR2df)e&5k~723}Wm? z?Eimb_A+8vA# zXw8g5wJ(v~)Vajx%$OxZ7T6I@r36Q%1eL|8$B@=Grwc`VdVKB>`HH$Igd;|(!>M2R zg2!faAP|cPL?(anWXOG~T1_6G5iD<%c4(;0&(_2aCXqsiSS=a6%DVN6*W5S1KU}Xc z3B9u%0ASmZI@`0T^atO5=5_JK#Z^|}2z_||oTAS#t>?6yE47>}o}7zGy+FW3EuV2x z*S+$B>LPP`))Q~_uh&Yehx6+4>e5wH6BZWV#)hPa-H=^MzOc?Lwrwx5Y*6?q1biGZ zouYi zI_~jPL=$ME9C?aiqf2aUW*Uv;-(bfy#7J`a*X!CnA!GTs8wWy7=eIN7fnPr#!_1Z!9R1B zX-3nyLG!@=lkkyhOB!bj(e%?xk7QN*8uQ!sYFtkfaMkf(X4}U{f_T6CN&dTI$|pDj zcyxBev&~S8yoY!dH8sq0Mjsoj{fuiH{qb16*IyiEY|qXp3F}es2A*Y+(P0;+b|SoP z@&!XcX0uVv@KaQJg`U?m{^L4|?JFZp;sN_;P!-JX-&eA0NoN?R_ID~ky`W^W`zL4`qVzq%&qcHti6uI2x% zNpF0ZeY_7WHR`xRTN{5DxO`)2u^)M{a2E*|#6!okM*K1`!;(yvoM7Q=!tHYPHHli$ z?)hDKWFm7kus{y?D4!tT>#;Nu)f_y379(@lOT`XdQ-NeCd~jwd1#D!h5vAV>|A!>8 zw95|`#9uX!4=1g95&NJOwWW5cvyL>d?{0-Nn)10aCl)c{$bu6{Tixok;K*7wn8%G# zC90<|5pY-(XbI??ULO$P(!SOtDJy$y>30w)U2V{8oMDB=$tzXfck$-ckE| zyz`1biTCfjgC818C_Yqr;Q`DP5Y}QSg|A(ZY7jpsp#v5P#+z+6LXB|ufxrguN4p0S z{=x9Z#TJSZ?dz=UBSCu$Zow}1fO|6xm0z}3%Ic?7Q${<7*0&zd8|bmQRRcg2R`(mJ z(pbggnVrQjj(fr6K#H;$1lOFPQ{fq5wwCUNp<%7|GugqB&t=P;nGzwYVC76Gf=T=B z93WDCmi*f^9){04lQol6jO+AYjFyJZD3xG?b$9RO7b_595B2f;GUNea3&FP5Bj@0; zi+EL`YwG4T^Fi6Yksq47?L^zJr^005JWlhi!Csm0=kw4@%bNXw-DNx3`nlWfggs!X zk~8beDaiBLm^FijiOF8OG`aBCFWAZj8EI*_%kEdk`BM44rwEK3kd@r5!s0I%_Oga? zZ)^6lq^f9_8vCod!qtsRzh(w3TA@xH&7vrYG!%O<_OPVnX!?3dY9mYM$uCXRJZ`(} zz^SDm@(9qMog}FB)GS9v>x2F-aL#Ny?F~hfyJ5~VjmJKDj6B)_ZEOYfZMc(Iag*}o z>v`7S!D6xn8eufY)SOhsK7j|6-sQEG0|yW2y&|GA_~dL2Qc>1IO2YYvkY*!_j@_$a zJhUmw+HKJaHoiTl<4xHjnQt#rp|K_ud6f#ZLlNmg894H^ z;AoIeAhZYG^kbIFS zVu4n(DFC0}nI&946{q)??$J*jOPnS-iHV{tV3EV%9?l$(Q2qYR#cv=Webi>v>`BCSORyV5=i$xm6ojb% z99|F6VrQcYg!GZ9jZNdUgD*P6o`AIA?+J@*rmQnmctsZ*&=G&`mNC9Dk0)5eXV=W; zADMX83`YOnPGaz`EfdwUoxzqypJjD2g2s8trp#^2yJswSbYb;PaJ6@K94Ep0lCW3O zi6P+k{pG%v@;)7lw&F?xtt(aK0LY$-)#$^+~qGA>EL9&L0|Thp3ww2ck6iY}lNYs#eL(`gW};w1k!k_4xWM_RzUy8$SO|srmhoS9uV2k|keA8JZ zOnBY0D4u6SO8S+3ndtXP(6+etp!Bq$sp@fz@nDthh;wKdxwr>qFdz@4%9!-4y>X~N zZ6F=_0Xhm^R=qB&2hi@#2Bz3-mOV?y??2yVfIGST-CqGZfasoTp+(D$5MHJpJ;&c0 z^$f4ybcjDMtNJo0i_V+CWz} zXk;_jy2BR?YK1{l_>{?Nt-8RmF`2{3tT2^|(;g%8yB%KiKG^Qbvo3Z+3{-gN@h?g} zc1V;t&GSE+kAIa6_A$@ggZU{6EKqa)uCxNbA4?4<=yI)@$+OhnrddzTAV|=HT;0ovCSY z(%F&M1w3h?kp<);xKO|_uUsaSS+npPz3~F$|Hau`M%C3dS);fUEVu^?7M$P*f(H!{ z+z##%+%>pEaCdiiCj<+@-QC>|@a;TrzxQ_c=x>a^_cw>N*RH)*)vTH&qlp%1wy+HQ zs51(T-)rZ4@D#dJKN&)dU3ES{B0s=nB19nR=@0z2Km9GVTI zhC}tUwTVgOTaX7-FN-QS?rC6(ceV40=H=z(&xr{{eV|I{KSmycYt&macyA)+f~Stq zBT;71sR*{yn_kz*6aM(#E?kaOs*7opAC~-3s`$vdt2aLBBuC8MuvGY-?t9;W)^C<98E0*3ud`PzqduJxyV2pTlv;`a)FMWn){2BW zoL$A_$(s(DPiT!f_OLCLc^+Q(N0#SYDqH5)e_}Y39_LHoGPWowag+`4sJGXh@P4nS zM={#dX12OR(4FqJxd?>xk`^r3JUJCGuNJlVAFk2$gfSZBF|P_(yi<~S^-L_Dfi6W? zY1+>VRqIE)UCSpI)kX&(aHurW6P2d4BD4GD?2?b|Fv(Ht_!8sViG$YI8Z`|qZCqN~ z$Ouc9tdkShj~_qUZx<~Atk5*A`|-7y6<)h-*}MJgR{}rbCeUgrgL4a_&_!HD&&K16 z89fx%G9vcyMHKfZ5qB82)F2J|(cSSAT2ndj;PjViJbZ`NHvAcr7wq4cn%<;kB1Kp* z&jlxq2h51;2b+RADO*y*O!|ZA+T-M_b9;tJY?wh}gGAlGVc`UA(5OPl&hwtylda_; z0WgtCOCdJ`@s2P)_?hK*Tc=+F+9ue22V` zkShIs74`;7SB-fu`gACE?#e18mV`wsAP)7zCAQ(DtncK&K7HtdYzo-qt`R%^>T=Pz zM?uewf|nAZjQzZ&jJM%RASDUEv0NdUJGEo!Ga(yuczbp%qf%sEaDwoy@TLi};h_r_T5d8;hrm%85!5 z?)N~6WZ|#uiQGN?hK(yBJDtw-+Frh)kRhJE!0>{T&q8gYB5PU;R?0y(Ul)1FEvxwi%Dh#Ehm{4Yeqp;&vqU z(GfJYIJ$pmk?_<}NpNg+tCm*dV0ZSgX|yVw;2AlHFCY=nmg)HDYvQmeOETQg>gNQQ z9$>KT`V|6FHrSzm-HtX^!1>LTvb(oO)V3>?i9)Z=x8w{&%L@cft0?t2C63-EeEuN% z_~_~jVL{X|%`=o~mW0|fVdltWd)k6HZgVn1Yl$btDCE%(0`d`btHYOi8X=`PQaEu6*IhebmJ$boALiH{8~xx~1Pb}f&l5%2m~tCvn(JMgba*w5XT z0U5groX~|_G^9`r=2NU+)u&Mup1g2}2VtFWSrXB&XLUJpl+F1N+!n_QfhzW1u_m2C zuJdK)}Egsn{-fb9A=%#%~qWdt& zUC%yiP1yRTmF0NOXLHL$pEl$|j1?{kiSU=mO$^%+R^_nXkPOHzy%vEZls83`ub8Z~ zoSX40{ijeQXUk7LjyJJXUrv9YZ(lqDBfVP!Zc4$`tvcto?r^xS^pTs`FSh7*y3)ncS zG%3@C`%#HF%|%qb#35cXn-LYqPShS>7Vapl4qpCJG;^I(?%KG!6Y9TbS^Wi(UZ9?L zwD5jr_WoMRN}{aMyiG*A8*Lmo71mXl?!}n7e)5dcch5B9eQ5Z6QI?Wro_l#x#KV2% zZBp<+|GMRKzPYOSlvnkD5WXi%DcBQHRT`*c`;32fLY`S*=O-L1pYQF&Wo<`cI9)Zm?WUv1oy`iq4_W`kF! z)1=EOoyna{`}MYNsTaMGYZ|w7`ozyxx%R8ISDEi4^0#zk0Ft=%nq6&6|1D+yF$Quf z2`dWu6RQwDZq-P=z{lcWGxEw0U+A7kVXyI)ROyS&{HlZ6I-T=~Yq^0F5_ zuLo-~L|}2-3G^UC>%2)x<0+gl+&W^pYDtqz4}T@88N@1__30k5JiPy9t+!F-^T4Po zy?K*rRL-(GoZ&uIoqa+W^u6XPiM@g%`nn#ge~7>4J?`XwTM*b7M&JEjBhg7a3HJCP zI4bxHXoC)Rsv7ePWdSh8N=lzO_ZO>HNR3&tb&mNkw$VI}%=&8D}tG&x-@Y%N-dOEqiIf@0rTZhdw#7}&{L<6Gbl=DEJ(W%VV{=uuYoDLPG^ zz0hEQZVfBr*wVgZzOw)faqjrVD!FUl+h7y+YF-1kO_m98U7)|^oaEWXkz57N;QJTZ zOC&~Hhz9f;)i6;XxzCGA8?xcQ!Z4Ig!V>cAv#_Qs4#Yp7Mg^B0;&FX$>_3OC>EpV@ zk!`#@9wI(;v_>8YJwd6_E&g8ln!ebSI3>IUy`LZrO*@=;-EA@3V)BXW2RdGuLjPTR z0OqG_f4JFqhiGH|vjVs=;Ci?jsP_)R$c9V=2xfRt{6S#HR8|SvkORSzn$r~bP3J_H zFWwj7Pc|4@dg$4=9o^)&9lyOdJaSHOtLh+~bD~Qna~j@yQ$mMPi9=9yYJwwYYED+o z&lh1nx6EOEOs~xj2qSi?RsA|T5)}Sj4+<-GR9hT%3oeTv{G32f(KHQ^rdDI0Hu=y^EHMS4-m*!2N&yM8Mx``0NX>RfLh3Bw^I4&)IO7S7*?)8uq<1_mT>;3Lm@Hm?!B30qlH_BXDc0k6t zY$6e43GA_rz$pk~>|mr*dt7OBTZI*I9+&9~Qp%M-3Z0iXz+MC$U*?%tU-5Fv!ol)u`uH%2Ak+?`$x}du~(j zzt2huL*bvWoijsHzRe!uJin1;RCu0|H^wl=@;Z>wyV)ga#r`-uR`FeEkf%qls^-Wy zKrNc5nd>vm zXeN3&G`9A9dkrVUfbE1^%Z$;U8?3B@BaiLT*S1e+c)$&s9)@F^q$51aB$Rk})#Z#| zN0*ON=EcMlT>X%?t zvmJgWJq!Ac9@cq76psgWw4qF|c(?28EE*yduVo=VO_&NlHvJ68rdkFcKJX_F;7m&o z?9KcMJY>;Q>8~ACQ{lBRwid!}sf(k!%O#s>U!iOT-0-8lUp)j~XP>ZDFtEm)WGHfX(5p2mhetO)Lp8EvHZ7m8+ZEi09-8=g~_!c2%$Iy@0j;fj!&vHfza z2H<4Jc55P0ZmCNe#Ns-?e*Z&6F3~LqfYiy;SYPL!S}=i;sVNQ)P9IgYKySQ)kTlQ~ zDkUW)qmfv4=>D;*sbWMAwIKP}llBbuZP+9YGafoNTY#Z>FS zG&a4Da(z}dgL(|<=_wI|$w+tYXD>A=dcZw3d6N!#_V)9vSctTFJ0|P>9vZ&9U}B#f zxvt#ji?&**wa+eKltHD{T~TZ56BIV}Z4J60xU8Ybs9u{SG*RF}wptFV`3%E8;Blf) z|1{NbcAL7Svo&-x-XD9eEb{IU~9STYY3%T>_ocp!fN)oA0--RxalNwOc?7ZUgSL;faZ}w^DP2=EbX zt3^QAq3aAFy*l#6W{28vkG*}**O@^1E@je@OM{P1qvq>DJQ>0PgPEIfFMq(>QP0G< zY~g4yM~ia_M@z@~-jhq_E%d$4-wDtxE;a`w0+#qGBA=8hNn@M`#7ouvMs^O93W0iB zo2IRs+h^YGzlfgy$;Y1{dZ-WNb6}!c^;$i}mo*2QmnbnEbprvtTRcU?0R4<6b1PT# zI+mVUn3ByDNQwJ%>22MV*KWTc%GVqf9-fzzLs{y@+IdHu_`@?`r^?y8uJfp5O;PiU z%;;&7tIyKEJb($i&-4*rTrOMa$!nah8u6$N$g*Duq!(U<3&%H=>1(c|B_0FQlo^dCwnXW6x|aXIq9M-$|#2jf>`V?%r$dITT;*J$Eorw zAro&tVTjj1U!yiWzbrC|@z}F*0}y>bY!;3!wGPvxS_ z-G)TU_?3jtD+0eVpI+SVC8+6(NASm>8e*P_MP=`Zcs{0axTwpAf)W*>M+)M!THDM3 zM%Uft8v#~$=f)I@EhXc8=FRJrjs#L(Kd@NLfErO0HK~%x8_&H!%p{EAsdnGvYcLKOyQ8EXtV@%qBeHrLxz zo6zAHI*Y5&gR{-h2EX7)k<4S=0jpB~T~*xDp8(e?;cohWOa3UaJ0ajYcXle#-wJ zAUe9+{{zv9wy{_OF+bJEMKpx)rBKj5{{JC3J2I40-s;C$g#w;-?0*1ji94MB`dWR0 zYz-()x)AdxF+fohuOkPNQ>i-Aa?J`VD=d#+e&{^7AMk1luBbv#e9~RVc+xfNI`8;> z8wSAXwWFMq=_97DDsyrt$q_@}__EBMyD~oRWgz(j^n^D6PQ3emb?(B_icT36g_ySj zZI2wg9rPDGQwCz@k^^qmKMHMV>%dKvjUs;#P{@JJu1YBHa>p%)_tIQ$Ug6l@eT%EX z`-DXKA=BvJAo6y?u!J|%UjL&IykQ+Gy=HTuWfAWBl(lU7D}80h3j#JLK5xrb%I$F4 zPZlQqjRY%yZw$jiy%D-9J%8o+2p2tR0Lvs2v}FYHAf~cY?4Ml>Aw9DB`^itmh06^2 zWgO+Lz*D}XCA7r)$}Tecn}X1iO`Rz<6c~m)7)cylcmu(Eiv-8xxs5c#)u3*@Q8Y98 zmdSB31?8ff?GvB%*xr;b_*)_ZVBK7lZJHccD}}DD840>;A#Ug87{Y* z^E_=7&e**z|hajhZAqolPDC1&uulZ zg<-eXpvL;gX6MzoKM(zCVsYjsqptBY4V%-;HS~z;$0=Eqg107h@E%s|2j8cVNRsBz zD0=}~Af(ky7Sk}9G~k;}-*=;O4-YbV!w$kIs@ zxG9*W2gs?R+G1=hs)(9SGR}Bm0RSsLu&oU%Ylho#6cn}lUyW?<0dAm`%cvJc0o7*yIa|s zn7trkwW&^vUoQ{+wpehu2B(PErIUXPAR)Kih~>t~sd-r!hDSGQP0r-acY|RwVV)h_;h^;nRH_-b&y~mL463 z%p=Y~z256{$=Kej%pI+r@i@rcEt=%8%#QL9vkeau_(7Qj!`ha|Tiwg*dWG2yV04I z#0=4I!puA?kOlZt&)Zr$&F-_@-FxoFEcKu1<-Zx#28JC*(|zDm7vTO2Evt3pP+nAv z+vgBC_9XjOK5e|20H?JrH7uIj?)p-Edr|cN$|T=Wim>j}ty^b5-pgApTrE{F@_VNe zI^Y{jjwT~vpA%wbfrJ;E=_6{jSaGs1AY?*|iT(b(p(hg)|A{VkkEH7G{)C1A1Y|z_ zET)K6H~PNflT$w!jAf_nwJlZe!TJ7k6xq%vC#QL*Jr?bCGYyTZrDIE8DanO5#Iyy% zb2ZVwwg)hpl<`djAjB+;o2`N8H{Haum8aKJvh8P?~oky67W5hLh8T@-G)L_T7H{KF-ARQ>oOA8)ts zqVknIt!ktYofwGv)6win#%E0P>NjX{jnaN+H&;JS)XnHx0b3u&!w-c>uM1AwbliO+ z^oVXIwAY|KsL9}%e71iM+{XYkv8eyVmV~6!(~mj=p17LR)QcItOW{s8!mj&gbDWiI3Um>5TDdTK5$_!!#Qa!c$xO#e~C!DIFfw6xwdz z3Rj_{DT=u0?YFIMn5fPx`FT9|Uoecgeft{9yzSonZFWlac4%zYoh9#YK8bvEGTnhL z)@urilegs5_nhM&eCzAN!kV}A;v}G2)Turwl3nEd$5&40w&;O~Flf>pb}JP9xHN5I zdi2y*KVln#{@)l|q9yB&e=)XxQ*Xfttr&9(Aum8I>0dTIA3*bmEw)#1`krSWnHXCl zcdEF%YMOik^sY0WLKmK~Sp-{>Ec8$aR}1B^_c&^8aLyoiuSy%W@z0@%4@p&CSq*m| z=n@p#{I*@rHt7C=jb|I@6K6Z7-(UMWnznnMCBLW%{OZzm2>a@zJ}yJU)xWJbk<|49 z_v3DLXk3IroY&<=U`Re)|2UbAdF0nfwEc_cJ^0u?_vra5C%b)8vUVt-te(q?gP(-w z3f0YF&f6wEed14O3nvM$m&&`WPI&^twKm_J zCu|-scgi(ngtyg(#Yq0lzAJOQtF<}VA!X`k8as5^0^0vb(9S7!#^6sYG}qN?3B}>ELG$4%I1`ZYWvVn=sEeAzXuBX^LvJ4j zkSNjGoQN^Z`9DDPfsvc6q~GwqSg6_GbeyKv@z;9hnwITr2hCLZ5ua_9XVZlBO~G5S zN#5ke)%89dod7JPdWbqsJP%+}kV}R=8TbQQJ3NX7-4jmGJ3DW_Ruwvqh?Gy6uvu%O zNpdGCmNiMtEfA5M(YB7yuG{G`U_)rKcN!pvPy&KRk3$xUtM)w9KIU!Cj|x!x55$8~ z_KCqY$5-^=R^O-aeV1(@P=U7f_&funm7|#U)Yy+!gPK zICs{NDmGzR&!c^@A{Ne=bj*OSg!juc>5Fceqa&zVv(eB|Eil^y3@{%^wY2xJBLdCy z7TPKju%>FItXkYImB7ozXzzL*G&EPPHBtP9uw17rXTth@!EYzQ<8wD1 zdoFGR6lE=9)^(rpX@?@EZ5m#C6H|Z(H&V$M{HhgRRTCR`=$>~N@3IBTu{G%1F+&HDvlHR2Si zI0R>cPgVpu=Z>02V57^A`_k+yE4h}%SRyU&Y`fw+K}_KrG=>$>mm+pK&nv5Nz#AB{ z4Urtp3~bwG<;R%aL^qo!si7_~3AAN#MpLFG2XFbgE+%_hS9og;8WM7JT8WyBb~$Ry^p8Lw?tp6v|L6!#m6RmGc%m@zxk}%S(<%=h@BIM z(4`%-P8s-mFIVCX;D!ZWm&!1J4}FIM3;-dglTWAS9uEcmZrRPHExy_wCc>NbXr>$t zz3HZm(2r5p1!?IB&Ca>CqLq5I+OvH)W59D%FHGo7X{uiN#ti1h@YCky=v4F8@#V7& zO&P_HaZ~(GYg|w!JShVE;r}b7NPr&l0HR&#P7~G?XB>BS6LPAMnIU?5?|`q%((V&& zFxmfedMaoAf;`~85nX;9SGQIz{gA#VHV}#=^XaYqI1dvZYTfJ$JflE zPa*q%qqZ5cm^fa5iT*F-2xyP{e@Bk+kN5rA__r5_!9`XTJw457#jPbaz=W`V;aXf~ zyKk>W85qKiTs|QFCm7^HPyRu&L4= zU6uo_V}nOBYqTp4cz~Om()wP=VbRfa1QAA>0htejdV_miFGvnWzmmxNQjE$0S~w(} z&hLh%I5*e7yu92SxsuuV$#tj6hl-NFg4~@pSDbwo@D2M1;=3P#Y3HgECMGTcpd0^T z|0xUF^|R-E3CCn z9TnIWz5>jV%9c3U$hF;OZ;REM>NSWXfsrrV%MULh5k(V zm?`f!yj*5J1F&z{*y?)z+8^*gB$eqc){-p@mpwHZ)(q$;o`Hf2fY8$MT9?mHgt^1e zB{#8f!~W^u<}J%Q*R4+L$tozRJQ2p+MJ@Di(c95JEhHO52Vh7;6jelCaDfOcy)h6~sU}!q? zPut2{!DZ{`oSx(OK!@1Jdj9l`brSzC=hGdnoOH_Y{Ooc)nG7~f;CXTdYPv`>%2IZ#hJ&3T=K#!J?g}zn|3CiZzT2)#pB-yoVciu(l5?>T zYf0gw2ZoD$TFVrB!FUb$1aDlrQ4XES14CG5Xc)J6Ai;uH2%7yY&v*00{Nkv*#7%~e+h3dWE=8(^9x4#&x%Qa-A)3yi( zU4?3Xjv-ny@{x`w@I?(TP)R$RVO}e`oD#HQWDZol9q)x7ttZi+sVq%YfD3{?NMg?a z*%Uv6n97&PnK;aQkl(MthSh(>H$J!-77_)`YJSMMX-GQHM4OPa<)pK&asd6Sbm{H} zM@?tp^8R_oQY(=QgixIyZF)J+iG7T+qCB)<_{8g4*nrSkbVS#0M*}d62FW}TJ!7(j zR{~FrrN{)AyNqT@-%k~!lg#akQ55fmN+r|MppLX`@8xBp_WCeBZeGA6*LzEtysM(>>~oSb z45I0iwVydDs2a&l-)|&4pm+u+jN=LdXZVkV>Cv9xDl-kX_D^+=%PxEu!l)+6FW8{| z#BVJ$dz0F9y-CdooR&o?{Lo!vEcmos;blWC9Bjx#S7YbjlarzKP%PxTD_eA4+al{! zVW`#-whx#KZE5-t%%tkv++2B(=no~Iy-3l#mWw5Y%kFsI$V zsRGX_O8L15RSP5H(uCntRwNVG2comSgMU-!q} zdSUFs0;3h$y<1ZB2i65!Y74Bydq%QrC6C?rX`ss(AqTO*R~*WNskoB0+N5}=7sV5g z6?126do}}2i){eKgFgdWNV_*kw?%ReZEM7n?-jG^c}|blvo$eF17v@2co*TcNG+>k zS59yC^|PsDL#ViUb?XxQt6?{!(2Hn(kzm3K(aI5)ZA#i~S;RV-J=hax9-sMLQ{T5f zq(%YNQa@PYXx6JRH*IMu#97k9967D;wDTKl0FwQrD1#kE>jy9!_l#c30W`sgZ&rfgbbliygj zRbHw65-yoddlDGnn%^7j3v$P1aZ#*mF|1#xC$ILJA+m5UW#&#{1M6ph>Pu_nJ$ci0 z`%I95rmG<_^!MxnL?>`|i{USQl_yU_2n-$_uMF_X$I++)-ofw9mV=WfZwEz_J!d{i z5S#K4n%fc0j}cl!9}H5WF#jljgL%ub0Vbs_>V33{YYC!{G`||!SZK?vklikIi$5=4 z8)+#NN+e)iKv?qHxQpH<056~lvzR~3*b?*;Ae`D%_JCn&7r<#v*8Dgs$nv|1wc z265#Z6Q+_};|uWwq_!f2C^DW1%BDl#D7F1sFv&SCKhX5k6zA8subc@f7&`J{`|Z*L zq|WE4_7=Xy6=33$5{k~82bW+Akhd3454qj?27N~sVRu3*fUIWR7it*mQhkj%_!Wnz zJ?#TyvdWf-st;#sF3?&r88cuYpVi-60DW&><*ULU<2nMJrs%IbFNy2RKAxy;ah3X~ z#(VerYNVykEJts4S?TZCzmclG6={$BVYTc{oomR+5j|=;2*AEesM7QlamgZ{5ApdFeHyrFSdjc?&%{OFZ*q?q59uD~O{VHCHd%pNpcapB?v3S{FNuc0Qru{|z;xEo5tk zWd0|dWm$pyZ=B`P{aCPo3oV$5%rT_ggmr+Swv6!VwY zBn1BUuLye}kgSdgIQk=>W+d0IP5f`+r+L+!?WdUF%{|FK^{L z<;K)UFQG*p0``CXm$m?MIg3aTSgZ}zAcUTQA#VMj(7n`OAmgYL?Tp`#NhR|4vGy1L zMD@A;qBLM@5diuEdLeJ`?4bXufwYo$ZA9zKzy|X=cRx|}#{aWX`wQyoPB>g#T%fFE z1*k3J+JR!yOdxfB3g4I|Hb>*WZO-uPL6WiPBG(g>u=o#%SU{1KohKPx`)%hlKzAbu z8|0F<>EtbW5b>8EHJ?O#*C&!cn_0g)tDq49AK)KUNq8iqXl2FZusaIg`SpW@XMN8P zQAqz{Q($*=SWI~h+>^AoS@qfQ05@z8#3S)5C2#q4H=0fNp0tZM=R7)P!~?`2R$^;E zhXIP*vm|WNGnBH*tWuD)RNQhYN~%!2-E7nC{=3{`;N-;rMlC zlevj&*4!N2X)S%KIcV3Vn&^@zU<=YMby;6_20w_rHSLpQ=_u(E(j*g+ZPzV#D$O-- z<*^i@vE;ACs;O-bT~4obNqEAR{$CCnI&-{U4xvBxk``UPOCt9w58e6u6g{8fRbN~x zBVYmQ>D@nHI~Oow(f$1Rz}Fk`GQ22oP?@x`-Ve9 zwj)PQ?bBp<=if-u2k5o$wX5`1yp6K-iD*;OaZ zat}HDU`O`3fRZXn-R)iwv{a;#%mKrh)pMFB^wM?^v_Q^Wv-y&iM3)Mq(5KjLm-j<* z?b@!HhJefdhy>LkPUPz7Sm@App^HvjrrfhhPSH~P{Cph1CKpz zw*xsmeKZ9DH`)Nci8+&$pKE+>qexNn=mfdKd!X%gio!xQ=U0vAYYC4dZ*tdri@e_I z_b(;CN6R_ITje%fh^-d3wptvpLr^LE_2d5<{%zi>$zqmUhdb4b?O43TiRz0V>?}T< z@6gETxKFwY?w-zkMusZnq9Y$vGta75?aUx|vIFBC&}1O8{8bx+9#;-SIgwvfCCkZH z8$tO{AV~MRx}vFLT*}cPFxrWJpsV5&`QWUH&{iVIFo6oS(IXq6Z-BrGpgF4$UEzeU8p=znsBzG5} z9X?Yhal^bGk2aX{NY4E(eSP)OQKtW zrYS&M+G7Fwsh@^Pdw9sMxMFn!5u4@PYv9Ylh&IQ*$75#~-_G7?c4!*=*fPI#07Aft zv9~TSu7(`|2)-MRF_DC@gYmePA^!9=OR3rUqZ{ch~c%{Y^UN zhrARWHDfQ#k38#x+@yH&oIR_Z(eqy%RQFs=ozVIy82d(7IIN5^uP?KhjmdKwxBMJP zZ^zOtcRW&iERCUOr+XJ9G7zhpZdzCXwfHFiStJlsR4?;8;}$Nh9Gjc}_X+TMcwNrd zvqM&kpcIvOzrK>@Eh3K9sJ=gP)#ssNuw(hj$(9?1+)qtBhQj6FVZ!|eN|P~E%A5Y3 z3qZ8-+qq3>da{mrIkrHG?fY$pTDE-^NA2^Tp7}-DeCFSfl z*fhk5&5WRnK_YOxhi`3B|3gWj@#`L9JTt@q`(6NnZp7=D!S8j6NdNL}q@L{8dm<-i zH8?lY$QnU%z%Yq{fbc-vHgsw3N~qA4JL$b=l$nd+JSx66#&9-K_tu`;(UL@owg31a zRJ50Hn-h8aPC#Mz!?BjoDWZB&@Jg3)D7&k7Ue#DJIk<6Qf2$0fIG2ugK0Pdt?(D{A zO?bn{Vm;+n5`3a!>`U&#Gx7Ud#?IN%$wwh^_#-=JN|RmZX3j=C1Eaj zHfR22r@4<0qoyw@ybhH{W2ryNHLgBHgI*lkS7*7=QhMH)k!xcB@CC5(1hn~sk8!35 zOgZE$Q04n_uaz3g!>y5f(5T_~(X^C3y^9Tdnzu5t^#)BJ$F%)QN;2#eN_L$Px?XPH zk@wS;f%$P26xIb4tVnJ@6eAI1 zLet~9$FGWFP^Wd$@OOa{1F7>(l_}2lwy>m}O*)Nt`RlTtPhC1;Yid+sU;~f@_}RNB z$b0)JIX`1e{8XK;#>B$HqUVL5b`61-r%`&svC0-wBPdHTC$CXGVy*qLwQ`S_s7@O2>z%(k?5nbS|s%0b?5zr zf`)V&){>O2y>E{w9{UZsW=n_> z8D#Gj37IuE?vp%5V;}Sz$wC(nAJ^BNwuh%Ys(W`e_~TJjb*DF_3ZpLH?kK%5v9V$g z6+}fWnzJcDK|^9V7NujY7_U$;=kh1GzTob4T_@xY_P87JXTJ>>o9U97Mc*}Vey>{= zR8jH;k-`hs)eX{?VAr>uqupXfigYr9J;%>Bq#E&$HQrh%%&naHPD#7@h0Mk`sc$n~ z8xbFZBl`!&`Zj(VEwB^Y21)!Wt^}cio-m*%BKlY_$J0&LnO)S8(xocL!s~Wf!4Uq= zrBHmo!&O64JUj`YTJ$TvsF+y3)L{*tckL6FK{Oo|1CcF(d+KrFMf^(i?`53)4p|eo^ zy-myH+gbXTTc4HY%U^r~9-+b8nkTN0uZOIc1GCy&dj>ftNZdS z?_0LzrsF|Sm$x60CE$5o@eFGnYlB(#K^VZ^GfT^}5uF1kXt%*Sr&XEd$OuCGp;|*NKS6M0 zyPkBJY+Tnjh#y9-^8`QzoD+SW_qf+^5`RxPY5a@@ml6lbKovcHB*va>fjH`!ELl9A zEzEjBQmd+Q>}ML@7BXj#j&DT{N2IWxim48nnU~ZPFI(bSg(-HM1WCH?^d7gQ=jN>s zyc|=EoX?kk6k}@LY!2gPU(ebK7HI_sfno?RhTbl@!#MJ`q@AVa1~Q1^G`4i-HK zyFb)kHl0AtijO;udE&K2E}7LY6o+n?$#yaq9v*%Mv}a|hU-zuk8a7RVQd(A}rRSCg z0y2KQju~yQl6MX8u}YlZs?P~8HCh`E$ICg81w6%kowk+fh=PI)-ZfwQ4=pln`IWpbgdd=(v2T7mDRS) zffSVxsR#P%N8qodxy!!`E;Pqvn>8Cx+Seu;W`B^Z4bm{(39H8(zJw-63HCizE;&xZ(*Yr5kq_Nfcoq1Sg9V-rjoKSYdXW&ms^lFZ^HLS8Zz&mSwVRwnWbI=m_&w8$ zW(idn49D8wu)4ZATfrrOwKi0n*r^n!AE zD4uJ%=zeOsE17dU%zE9a^6`l1N!tJO;hhq$<4uL%=H@0af`Y|`50*+NU(%U^)lIKw zB$p4@` zOk)hsKDT2c&Q|kc;L{Z->+8S{3=VEOw;lY>5pDUS0nri& z7t)e`N^5m6-O{Ipn9{EgZv5PQGJXnua->)$WP@YCGvX z9_f1UTvhIw+|~WiNcgx6h>eYH?^TQJNYwK)9(2y$VDOw!P3VB>kfOrDQYskZ1=&|3 z)-?I@-0o568WXuOcG-zBP@6DPFK>C|MYXb3&uZFGCjt)fRY*T(n@x)EWYs2^Cl<_9yyw}g~r*gRpZ05 zvu%juXv4}p_=e|U;6V;OSYkyp z8B2xlF4w4I6+n3PxCvXfJfEfm?LfYIjOKZQ2HREwCPs(l1KDi7;DUPUU7`ZO!sG18 zenwN}6azm$PU&G|hBS3J!Hx#_yMoqeB}muB@boEv+1UwdjIP<_XjoFr!%5b@KVaex zjb0l%Y6&<{TP>v5CxEVp9aUa6ws?VPB{n<1d$&o2Qf`^u;c=HqZ0L2*nk@4=Al^0W zZG?c>7fUrzsg;O)erc<$O;qUlDs%9@_yckLa6cZ!pTyy*$}6pk;cYO!9S1ZV9Z%02 zZFD)gn8!SpOP5+iq%?Q(v%;*bSBI5fS-~M83F$CLM8!@=vwJT*o&*pGie3vQTg9_f zCC(dT!ZK1!C_g{fW9QM>Ju)#cHJu^DhT4`myViX5wqX=Jm#Ntq<4Be+dN$cnZQ4a} z&#~VNnoj&cEeDzQi=8o}PotGftPwjKKdP>k%{xOJVLU_#gb>#Aq?;}au1h@z7_#^r zx=KE7+?R>dEUF14ZhOt&JtOWYXp4z`7WZpV;ai#jUHz^%CsD5p!b{Q{FZO=^`E%50 zcDiKU6dHt+U*yM&2{RUxxPdD=H}77Hq3tIpB0?a zd5?+gnkY1EHRaVGl{xBLEG$5XOtR9LZYU(J18h+=nzPDJD4pZ!81}30Oxeh^VdRkL zw+ue9^p2kDdfM^ip6et%lgqW&ahZ79opv(8eLga>w|%~hmHNX$=tjKqe;QtmR9W0J z(2K@n-Y%y{PLXZ&-DMi?#VR`z^JWlh@=nl@5d6GP(lrP>#GW0#&?WP2iBfL`Z1r@Sy z(IFlsbz%esCnw6Wf`iVHUOw6idOhBl>?}96Y+STW&>Y#ZUL6#r+(WL^tFGU`(sRm@ zPkq+{kdeuhqlZ$7Dv1zm4;D{6EHvHhYDbC?7^W`M6^7(ZZ|(Sa2l4TQ9!PTp)rV9( zh#yyWb3Y4TC~Mrgzi;ogMk{AFSxw1&IGZC{*D7$eOXt{P@E+?0ULae*rS*G$i`CQkpE=+?#PXq_ zU+^MpDnlI$xa$_IWYc-r_U`Ir+|h zn*+s%!fn-9yoesy#3mz3dBJBRJi95wU$pL9Kv z|HHn1=E}?~<`f5>>M0FeA9Uq&HIIEF^Vgo(A7$!yEO>>@b{cc@19{5K{O3p-v%s&|JR6O9B8AqIH zzyt^qghhydPZ~Wk{H#*RH-&~Iy&S&=@Y@!hoQ=x`VRy*=1z;d#X>ph}>-9JLLsKU- z|4L0>R3!h;PNv1#!Coc};})xqWS6LvBIZOp1m%gH>S6r=@!oDX?|9iI!3M*#4?P1V zGZ#buZu!pD_DTeX^#U?AvAgi$xH=M-QI*vj;%YLUKFr*-+$+P<0VllX4eau-jPuAW zOB^MDoW+h##-Nf9M_t~bqocB!Dc5R;w4|`>K4(lrB$KH>Y&()o|4(mU9TasN@4JYI zw19%Nf`D{4h^Qz?BOu+~%_7p$3eq60goHFK-JR00EZwkl=Y95l&zZS%=iKwhoio?b z8I|S7^LxJa`F8Eq)wTZ~UD$N|jPtCkT7ImRp<#lM69eRMwf{o|0Byl4_rVKp2n%UH zTdMcoE4tNg!QpuECa_ojds;}=OUrB=pORKX=+5~^=uMITfpxr?ny|-u*8=~ymMq|$d;xdBIZr_e-Jm#>0}?Se9PaHM-C=L# zZ5W74XMe&AHV^YWw!i#Ez%$vja+g4=(3;r z)ta9I(DgZ|px1gv5H?;O4t#(U7c88kl1~+tc&QJ;5Bl{tt?#oqcouDg3LE;CkI%~) zc1HFb|9g1k_7?Gl&Rav#CrFV8R8)9@L48NgVtY$>jQ_#X59d=>=-k%~Q-7SiV1FXW z8ikj3og^uu;Dgp3G=8}3RA#>hXBPmbn8L2F4e7eH4;{E{=9KF0T8l;}A7i;qcaZ#VJo@cY&i<&yOy}|K)jPYTT<|9p6HN^CS(U#0C!v3Rsin zyEsk$mb>FzU0(}P(kMuotA`BR&HR4qS=jKyR4(bk zrKjG3u=iMF{U|5m@RS}IT$<3`$N5;_@BC>qcn~AD z_aaxAp6^@D5T2mJ;$2%9PFU9ZlU$pm@fqHed#DXLQE7383i1r1tY5((BL}&;{KQkl z=hW-YFrlqEXsbClH~0BX5Jo^Y84*#-9|1)0$cpg*Ce5O~pp4!Ix%vCKaa@l*h0nL+ z-a^sIQivV7sYWAGQ({pOh-~c)E)@rx@LpdYblzDk%_)F!c)T0t7qb&=B5x z%5+mx;b7apK1S~%UJ?3MbQ>?$JXPbjo_RB~NaLg{9-YvY*!G6sn)TCf2?- z%yHYPnpie1*l?bS%uW?DPC3?TiDCC|WbIB}O2mts7^BXJHH{nO>EZdw<++coE^Akk zLj8bM*=+mL{<4tky|qtShcE7TKw4GWh;?xfW~S~Z$Z>X3zwzX*nyH`(LR*c-EIHFi z_@P1?(Jp?wj=G#k$6PgEmzVla$TlqKQ&-TRu<_w}-~P1ImIV@tu+8bsN(m8n%&cp_ z+f-@JC7%;ryHoWoLi{?pb$|bb^r@j)*%Z{{T-o(2&0t?5PokiKXod?#r1RMmfw|Ks zy`%#xkUvnf%@kfM!Z)y|bmrbuvbtoYz7%?AiRtUEpE+H$Q+aWc;-?qaI>Szcop~{b zhZdi-bC$J`tx`qk_3b@H9dS5M&9|a`E^RrpjRN9OD6KH-!t&YAu zTNW=w^I0k_zi2VFcIO!P1Lg~XA( zkgwXUUbD5{M09iRVfD2fn59d)#0kl`mdhCvZ5DMWUB$`AHvAO8!26kEbVmoJK79sE zQ*62>#yEaXv}T2s6YtG~Av|&-jeJ+%EwaEMx*&q+$hf!l>*m8e*lCBtFuHdU`{(g` z@#YAQhKuZ`3Gs{NvP0cVPo%JyUTtC1@rMr|_!@-MCMPHFcsIJOKFpT?A)EiS2=aiK z82zQGDZ`&XY7HJs8eTTSKZ1hRWbBG#-+oJqtD)5Ud}u@Vo&%glne*RBUEbiR5sA3? zoja__!tSBz>H3yqxv!Xr(25%zWRtZpSg`-u0Q-D?~O07IHjD?F!%gegG zEof=?WYyJd!Le3KMuB7B-#GJA$gT0ch@$CMQoF01yfAPU+C`qRPr7%5Mx-q@g}a%U zCZj(9T|zHLhaTViC;j4%N!irnn@vpK$}_YFFesJ?N#E*Ngc=&VX(M-RmWTYEz0EV} zD`TymgS)$xz!pD(pBSCz6p(-;<&xyN=(Ca100!NjZ2&L;=eCfwowr(>zac=SZ}P%XNDM#d)Knw7`^^t{ ztIfBkd54OcRM#Q@iR{Zycz+q`vuNKWg@~v{&=1~Fg|)+z{@AgCZ_lhz(r1fhr=z1= zJMGhM%rP_lF8!prc{bw~w0zH|;bIaf)crV%VZHMT9b%{GHKF4cE4&L4_tw?B6~A*8 z>ZO?jloH1z!Rp@)L@{*#=Y>)0=t<0V^tFNmn0pAjCRC~cAj9-a%Zp&f@_Px$^4 zNHZ}IGk#?(x2221r&(UTq&7ZpsisVSv@2U5ZOZ1i&To7d$&u8}@|xd%2}hjxAtIb0 zJDkO(VStqs{9gF7C@XUhR(r>i5j>nP5zt5l2yj)EV75XUz7H1hZ36ek_V6=));{AE zGrN{i5i$W=ZU)?Kt!y5vpb&;*^aO!RamBzU;iT3j!mo#T#N4S2kk; zpaNmHoEY#442_HcgquzLkRWWq{U@;Q&Py5tdvBueM0>qf;~Q%}JRq_9xD-98{vt3G?&%qSRtH zAWrj!ITgj6`)Pgi`>Gyii{EZl?B(yVX;l`?i-NDdsdGO3slp6`!FwOY(@WY3e5%@I zCQJ1Bi@eIQ?YS;vb6F8iL_Qxo6&4xc%5jWP*!5;b?qnqm%W3jBifE4{fFsNN|1LwW zvrGLb&dNha`AL-cyZxnLEF3Q^2u}W5Wi6vz_=0$eQM`N6qzug*Y4NtRNv-NifY_~o zS|;^cgd_|mAhH`x_RjocTQ0U@VTaGc| z19Aw||DblKCjZTO$_vjQU{*^KsV>wvUe9YPh!0SQQ0UzVa_r@pK~4P(kKaRHkzla8 z)_MS>%RQJQ?^JSF*VF@ZtnCVnJ}#n>s;zS{ImlnJe*B7HxCs9=gU0-?8hA2Z@PqBk(uK166G-)Fkbw!|7G0f z&G3T%%;!0ORz1OZ^zy}&T({u@$p?tQuj$t&Jnkl4_iR5enECyL?>CP+sOsQ>heaMf zFOGX}QlS|V7G?zeOGpCeegsqW0K1%FYm2O!*9ijzU-r`fRE_T`oKe9;XV1%OZ9qZl^2#{!Ih_Sqt#;M3DZ!w4@b(%jM!AlsL1^ zHp9V;sMmsmP|7d}l9QZW*7M|Wx(pj@;<;n^fX3%0ku(>p{Mg-DXq;YB`0By}eP7pN zM9HRA@!gApZ4?^@42h2ODY_(dPRC;>bqc?D8cAX|rw~W~y9pUsAe+Db69k2%@G{&B zte%Ip3j~SL(u&?XY2|_WR-Ahe8-JoiOvqx-03ek6Nuhx+BOpPPZ0P4SW#|tHu zAUp&pD6N6+GelOJP?hu{Hi@1I&F&94Th| z`U}|<p;Nt9Qi&hJ=9RUX-qlodaZ(-@9NZP~&bLB)6u0$~ z4)xighw8oUy~tn zy60a$04IJ4dI|rz1@o^J+DGzfFZ!1v#N|N-twfy_Os~n!3qwIB(x=Djy^=-pDZ*_X z9bki|{LKlhQPsr(yz??`R#VMAF!((0?-8SdZVO23L5inN{?=b=33;P~bY3UO>Sf8p zSmDMfNrIj&1mzzuO#m)jZpjAUISnRC%EYlgWiQdgXWH^jcobZFT*2eyEtJ`x8@Q7v-KYF)eWeNXjcLm9z06H@4o1maV+0fdI`8)l4uOD8utd>kbMaY zc+medIVYTGCh}h<=X%RhvCQG740NgCS$4}G~ ziBPjHUh2=5SoZf@GeH9nP|2wv_3)gZp9BV%3&N-Bi5*y+Thut3dOtlknj%7h$+I9X zgC>Cgj%1Dlt}v6svf%}fmEC^S>@jDz)>$PsY3-39V2243S51}c5b-*%N1k>+I>mK$ zV;F6S_by;Tzefu2kAGgFRplz(hl$lZ@F_QDJpL*6l7XBb6-5H`Ni8eLbIHQ<*n?Dl z60G%g7;_KRxgT=p>~EL#a%o&8T&U1+;$nZZ#p9Hn^6X#H{~jG|@Yvn<)z$Tcs&ifD zp?eZ#c*I(ijPRo$#&cfiJzC5tL58&Wq4Pz!$d$Y8&N@bvfW=Z6D4Zo8y2*VasfhP1 zXL(>%JCp<`Va(*8WBB|>0%k>?jv-qJ4A6SjADXV zNuz=+O)l(2VY`+uLBXNigpW)NTfSg&X+055CQ_(CseR=kpbKIx50>d!b=?xuN8VLk zPBkjp^^?}Q^`wDX_3McupEMp4AuJH-y1nBwBH#-P#FUX6&-)4xhJ zv)6b!==s07Q9ri&cc&(^s^a$m15ZtEbEw|72h0)fB(gqT%p@*$3`iu@A4VYF5o`7; zq^edU73t^y8oC_Gi2Cze3r3RWT}tT$weq^%8)MEx1<$2)fhIm6oAu^Km|jFRUwiLH zT&|-VALYJQ3o5k!Y=!|_N$R8H;qkW^$>y?|22|uJTyT*5p&F4o|f5pV?LK zslI`$COwR_jtd*i?|04iBIt=KxKyE$vyzbbbYqTsi^JAa~O$MAH4w^A|;M|REk za%TZHH&JSFxEjh{DpkUqC)Ob1Azxit^dX0T0)JhhEL#o)W7}wiT6$FTg`n&9A(b4z zbusGIW*yZ6tuD0XZoRnrE_XcmZDy6n+NJ!KRXrh6USKZxA*3+c{CcW`Sk7JcK7gbGAr>;Izg@WskT^mobTLc;Ty5k*dN_(VGAqsYVCOc-2FIsu${TdQB^xFVk`NN3%W75u9&r7uX_wVb!YsvL%>Jq3AKVsd`- zrc-fN%f?7$X=mfuQIF5OUbDl|VMnSII%WSptSDb=n6mB|9E<=%>E|7* zC(GP8JWh7l#<)O-0!lx34bhDjedJg>j*7`Dn22Lx-;zGvK|r@sP92BApy)-8h^vsq zTX*zYgqJQH4-cXI%AD@3gi}o)f_RP7MITvU8~$oI+|~e215Z3Y5xI*Q#JMv7-x=f4Oo&@= zDzvZ{@7KG?)YOixThHhwpO+n92WmYXLYch{kR{J9VOkUHpZAXi+B#{NjvlXm9OnNX zmpYKhV-{SUO^~m4+p|y12nN^C&)Nly>8lKmBkXEF1eaBDnm>kqrMM$adksX7-5e@q=2|8?{5 zG(ohNr-eF#(7h)oukKWq7`4?apE$A|H~{XZ(U?&IM-1v!G?;QK(nJ5R;0UMedS;Ri*x%fu<>tJwg_ zODnrBYAjlq++PBLN}%puy?hC&l$@On1s+tf-7Ok9zx92^Mcy*Ww{QP8=x8-u+rG$= zrw7B|yK9aq9Pf-+2O8L4ojG{vmBqQ*z~g><1!$DniechYw=LM0f@Di(=ChHK*O%B# z9@0H(aW!K=-m>L>6m!~gZES2jV_?`{8qN}eKl&IyU12#?r||&m?)(@1=OE+7S_00BHF3BR{OzgiwpH%VTxu{D$+%~I`uAMVPD%| zQaCteOq-u|a4T_|uyx&@!HTKFi#wi<0@o9sQoBB0fisVHzt*`r1}};Kk@~o1#ZU9u z0y1&d^2n&D_h5iwWtAk#v=nQf#{xRa*_2+P=%}%*Q2B5 z$#Z2MYpf(V3k$Phiy$NAILogTNti6^PUKTwek2O7MeHPJ;7=8uYt_}}s;L-<4EK?c zkpZCwGWJOQL`K*(eRu4mQjUB|GLH4h#HaSLBF>Ny_rv$H?`cIvXVy$O3awXiwV+Tz z`5c~{cn0tK(EPC0g|-#P6`@KqB4V6B?u_zWlxH(XG3g6Ofjb zZP{>k-0BzLI)&RgR3kF8vK%&57Q`l}j0)r!b~(RBwgz$;P9FDNpVSh?XNgY4eM{o! z0enKjq4BCLyZ=5uw&7)X3Pv0st>~SI&geCDV?7pHIyx`|9izu}b~T$n@uLMivnwF8 z1%0cn?b+sNVai$sIK7{r-$1R@!}cX69R-OfM7Tjyevurb7~L-w_yx0EEh4HLL&8K4vatudo_xnVU=d`}ceDe2rSewesx2nq34vqA~XTQBLcEasRZ- z(aso!vqto!TVclKvYy8piz5**Qs5TVR@wkNAf{Dm z+dHBWrRDgZ+q5jPtLy%@>#ipjc(XfQ=OVT2CRzL%H~dVFPjmM&o&W%s3GzG^bA@am z1OWKL9`RjWK2^9q^4B|=WO++VX1t$UM*Q&wU9KM7B5p|Iw!N)ffaGp&NXP{&yMTq7 z|M-&NlVqj^jIqq>)HaZyHWh#!B9Qfs*g=?2RI+)QdFWAw%g)#eprBWeZhenR;moI# zb%{gM{`HP&@jr&+w8ESu?7b54x6{bS-!rU&Q zftr6XYr16tHMTxe@vEyobylwg*{s1Lxvg=?+VO8{QRnaL7stQ#Zf7R?0#Z=e z!B#vj>pK24nQ8Mgn+;r6UV7ngMk_9jtJu)-*3zzB^M<1VjFIrsBbV8#>lRQR0yueN zruxGGj9^7f;eBZuE;>r517(%-`V!xob#c}Q@H9S^aG#bfLOn)KqHO;f?EQa3xI;ZlJOA*wF1HPGO>2WNzxAXP*jU)r;~4*&UC&c2eyfOL6LGU$@Z=kX0JG98 zJ(ITtvV?Y8%L3&6i(L`-3LEkS0Ca9M=5}J($3^9rFHbynhdBzrVvOapuIYM7QVpVG z5)7wHpxf3So3^yJ2!iDF$GN`N4d*!Fv{bQA#U6vHhq{A32gSEd66{+;VrIXA(4g%9 zbcBh;ZG)g$sIZ<4oL~I%K<1*e%^qkgB69MMjS(yqeqCOE*bz<^(7K?1n(AkUwSmHy zHH(g*O-W45A625T-bv#W7r!RTwBa>fWx?ZF)X)R>b;?}FPv&S$l-W-A6}@Wmglr>V z1n{3UVKdphqYR!S43g{*8S=+0Gr=mB8cJG8OL#WluXo||DMGcSb{i zpK;LqE-gmrHi)_^0ua)qcl?iuCVN!UbY?K*3kIg{;}N8E|48)+y|Ib=B+Oe}DKb)9 z#2%sryH*-&kK!KQe+a^{AhM+*1NuhIg8AYWCJ5R$oJ&1B<>Od&)c&{6yp{l>{N_y$ z6RY4}i}y=_LVzFb@;~BZ{{MbR(_+}0JLu@Ssw|=N=}-DhqdB4;64G$v0_8p#9SzLf zpUuVK;D^!<0V-d5pd3fi`cyiN?9{DqG_zYtyF;Wfiq8^5z6Lj~I7e>$t!Abji(kaf zeg5cLcRf1e$qk8jvMr1wiYO9$ao}LSuLsx>V^vZ~(A*KqlKE5R$Rk7HQvn*N!BJJF zRrX~iY0P8y3zjN*_)$tS@Av5|MBVd$9emIV0;1QJ?;HZOgRiE`Ee^kqq^K6Yu^bD1 zHnMS`6j^6mZGej(A3c&Yc*d)ftC{iisi^%R!g{I(Zvx|m*MK!YPq7}Z|HbJqne%g#l&zjJfmT26M3*u1fl5LMjMcU=EJ`nB(pxwmbxIOQhRHCcS ze0`b69yl0VsY|-$RrR3Ua>x%Tw6nQ)X?q*-djAGBGegf!p9d>l6j}fLNn4%wk%OGi z+Gomm@hl{|08t$It25k$5i3y1yhiUc4@_H<+5w^XgU{xU4;${PbcJ1?leu1y1BXNJM=c@Dnx*Skm7O(=>0Vo$i3IV&7<=hYS zg)#`?3L&q>XZUII6Gr;W!fEf7X+WWhJlSICw$&v}oOS;1!ddc!U4sEH@Zk)uv-@8# zX=)VqHhh|6dhg)y!K5y2xzNn^aP_un;f$``)|;zt?cDk+UzR9ZAOTs+el+9Rc5jh` za0+j5;h+f+HpI}cDXdRg(gEK}xF^d^Sj3C81KJHUK6K*aQctFV-SYBjLle&*F|0w! zF{t5-a7l-awEkaRu1-6XN&U0utQ;GYz9qlj7TXUQo*=~)e-_-ibEnI(mL>h$DzkUU zCLOIKA@i}z;1eF6=ocS#yK{mXg-P{S>yT&b!kXDzrFOG!p=|0(4j;1W(vtJd$OIba z=MF!+9U3x?<6&VjaC7@%gpq)$SX6V4d-kH3PM$4&@*@xcGGE`*3G22tG?+2ewiOWg zM%F$qURh>yi4U=@-vG_eZ_GWT`WZwFs?h#+Ki9>it}P(N9<8 z^s{8Vg6r$+4bS$Ka$x$Jd?29aV4(C7i!>;G={~f-BK)~Ek)vB*1E75ASxy$O<)qJ! zPVIvS4^S}X%;J1d=p3I$_zOvY5~z;^ef-w&h7`Unfn{w7cHTq-I;I6E(FA^XWL#?V z!6O!-hctTG$E~kklRa?4`$o?HgpVzS4PHq&vSvA(c=&>hEO8B)v)H4h?FGP=Hk^Zj zv9VtZwklMaWkV`SMZA@6mHV-X9fXGlE6^{T7MG=8++JK!U(a;D=v`WnRVHHbK95aL ze`$DeVle0H|2R;|Sa<3YBi$)SM!n1nz47YAqvUccRm9G0U!hmnyr;c53yq+RO*me0@;RRbM`-=M< zT4?*48xkm39Rx^d5`rcQ4UZ44PwYYhBHeAk4w9Tmc81<4V z6yVvaGOO@KDcRDIB0HM5c(vg=9w!I;W_k}EK5WDL_4eXh1l()C6hI8D zNqBu)^pds_@Q1aRe<3{`oxc!`_j~=9kn!e#wU1G*N~2sFRpwm499qwm1|UUTNuYg) zG?z1#yEFatRKgF)2qg@al_T9fFI(?i=#7v@cgL*xe~>=;xKp%wK=7^GQxqpp;m_~q zg*%?~dV`nYA9CcCV75Bb6&C#=(TrgQMIil@S?(ao4}Urnvj@3_9dl0b4pWf@5 znYj8{h(rytAmLaL-qHv7O49lIQ@Q#0SA~W%?1;_}>z7}Z{J7&!J#&mFZvB?BTYAL6 znqO^OQm_?<9%PT3(Jy(q^^GW$|fox7yp%mF`<=ixHFn6_mKPzEvJkwiu~V&De6=a%5V%J$eu!YER=? zw9|hT;P7k;=>uadap{e_sNZ z^mJ`aHNVSQP^uJ;g6Dv9A5{^^(?2VA=q1stu+}xM=eM100RbI{T7>m%O)Na*Y`5pN5 zgFA=;Eww1C%ZxK9zx8xqafaH(F|xI#T?SRp!#!BBC9rqXiE*k%n#H>-LSKCo&tMz+ zHF&S#9fzKTrY3oAZZ4}{NhnGGkChOv9&)Xf8!UYM9u3{;gnRnHvQdh;Qnqq}iDlc9 z6=CctB53n#Yn}8+co{MRDVeRLk!MZ46*D@ToZ8?6@fNPM8gv;~Z}@S_w*Rhf$wI_s z`{moW3=}YHHs0%*B6xK4;H3!fv&?xLyxnG9^Tx{BD{~z z_78g!E_chz%SGKG3D zeai^=efmSR7e_mC_Vy33uyQmSEqJhU6i9XTK$(?du1CQjKGz9U^s3I1?-z)P$JxNU z8rI`i>pc(0ZM0|0FgwH3y2xtc1%4@?{Jxy`G`scnZ`ZvI3r{+T6XE537y)`@3Uz0J znM%F_#ace4ooWG0!r4sFx$Ml$Wp|sAIJOzbC3Rp3rNd0+_MxKNaPM^4Ima{2*naG& zB;c|y#jzc&&&CGgkuT2>G5EVcAos4N-$9IMyISCoazt&$kO3yXL4q+wQ{xOYz|$jP;X2t#`M6HqBU zuv4}NR39uV7FKn|dKVKTQrGY5pm&bFa`(uKPi2&AA9^s5rCy;7Y3Mb(QGE`xlelH zr0I)Dn`A_oUW)fEWnyi-D`U;i91c zvN23dx!Y5)h)F982ZgKi4n5}c7KSKqJ{jF`{Imgs}-1xhS$-5fa-AyU<=_?x7!~=4oS5WoG*F_t> zH~yH1xSmW3)~cFW?gVq??kw@g+Y+t!hF-Y8Oe#8CUu_@ubd-^nHaU#@qdoph1Qprg zP6YbAgiW`l`E^je1^H|YUaS64R9f(c2Op>wU*2+`Tj_fox%MGq;d*m*bM~V$%$91~ zRphYY_GwPe(G~aFH8Zt-Ylqy?`gFhml}M?@mYQ`~y#2~bJeTF~OEjvv}Lto>&3os#b6l02(z20h9=d&Y7%t;{dSnc^zg|u&86vhvDNjj;}_4eQsu) z){vRmS!Kr&;^q&izQK_%b|1%qlH^shFeAlVFSA1#qt@(x-r#WdJz^W_X-S99*udQ$Qt&)f>9~R$!$JD$`r_p2O_vhUAihB^zN}jQm{kAsIdpbSUb~Ks zG9$D69xB*O-}Lbo$GUWT{;S^Aell{AV1ZdL7^rBCea6iCrS;wWlO`&{$UH?V;2O`C z#HOXu5X_2%r^T_Ebrueb&lJ(l;+SBnL|P z=H-PRI}k@_pC9+=`Y3~+Q2bRRfoMDpbZjC2{DGEM?}N^I8JPN;avO^Hj~}^CH=WY$ zYy4iY8j-9P_&K}DH$!|L*7#){?9oCX&mN3JAm3lAqeEWaF~EaBu)gs9xBr(XK6=iTRpJJk*py0ffl~hJSL4}~8+}nAG z4nFxDpzs z=;N0svLT<}-}5hT*O%vbRAU#u;;AI-9U4vP0D+ueDO@{!4UH=$;E!l0o#kQnH4@^J z!Z~dy+MA)bVprL}x#2W6bv7+BsgW)15i}f_zf68x)pTbh^e z=$vYDs~jI4eWRu}@v&A0`3JUVR#sN4>+7G<2>GOt7w*3y8{_NRdyJ2dAAQmOnb%_CBmdf0-g9B9JyZOc@b)Aoh<7Ws@1&j&rLW6^S7Pf$YU`te z)}wCni_6K@!CZyZ<@r}J;Ujrsr;OIEH_r2}D>zghyPPOgo`K9Up(Os1nxOWi9VF zwgJC)*hzi+ZOHqq((}M$0n5hU@72?K1xfn-&5@QwSn_Psr=Eat48h~9-XsM zl)`)$k&yd$CrWp}zdpb^A9QKy+=n%?hj=V_Az*g(gIUpHf6_%gG^)*s@WjrascYLk zxI1RQG(kmXZV$juc3?-8WZ zWMFwLretq)}k@lI-Y zswjyD8gDH*&>TIvXguD^%Siu_N$_)4zQ zpviOtGQM-VeCnPo?jwc*W@y`hAJq?eMLF$E*qx56kByE#5o0@f5)+E0%9_Zc`J}{X zf2KM-C@3i3s=B%wY-OeUsd-pfn1J`Sz!l;kFEi86>*_2fKE6v;U59t|vPiwu&n^P-Q5)da}l&%L;-)uEi3z0SjcpCu%M)(Lf~`WNo^}YG3UAx`C-y+uX^%} zjZI!b!6V<=1@Ba_5+-KmdoN$Ue7ImEvPVAXI>VQesOuiOzrTOzLTSDui-_tm4XHgl zJ3G9Jh=|Dd(!2Ph-f>s%x*W>TF*{kTDPeBTw70iMA>{aSj)=O@J*@YFj8UVT)-mW- z7Y`8@tkG`&lW4}ysbIlvBhAs#@eu6wp^Fc~Wp!f%o_63FTvEbzyF`8aT2&!NK*)KQ zlz+|^ZEbDsZHHv?cifx)YhA-`nF~5Guw7c%m8ZBwld*|O z(1J{m5~(-#Mi34;%2cxle}eJR?bSi0-HLQc!#30HRqLIW6j}_NqwXoG;6CSxH@$Od zMl|Px!D04vC|e_o`Bf*Lct3bk+^L1i3dklTBrL-(&R3}KNawv_PLp>^A(_q1l);^^m6et2X88#vRa8{`{7Mi&mI51ROPh}0%~|sU@m$Od zrV2~hqpEN@(B0nI0eky-XX00Oc6OOHOh~OnyKlbP!_3kWd=MbVRv&PE?Fp1PdT7IN zV=#^2Qe?%78vCOh91hQ#p**OGJe?=Fzz+Q$8{09it~UUlGID5RMf?sf-t%{?%KOHh z0v96~mvR|sA!e&Lut8|sY9uk4sx$3|LOmApM1#ZFV@F){jtL(3DmaFsQ#MX$}z1+gWFknHM=HkV5!8+RDL*hyX2G0=- zKH_*HhbVXF(RaNqURTTWHAnLvA{hoexH5~Ahpu-qw`=S^WWZ#5cnD+QQY_YQq_6xf zP?3nb`lH^`q26*Fe>ik^J#^O(tSQ=kRLAUQ55)17l23|iky=c8Iu$OA#{MV_J0(T} zwHt~A6TG9XAqeeTmUI~`zbV7!XU>TfzB|mOCoE3*F{Id?`g=3gNZaQsLUjwKg;bgj zDLPe3N-hsB*+$r4o36HqNYPakSCq_ekz6>NZ_*S8DXYLhXf!op~{Pd$T8smKH8&@ zI{!*yMBW*yU)b@VA9C9MjgS7nI<6&cqlbh*q64#!l+veprmlIWTlWEJoK&q%{HPADK{)NTKHLx`II1+5JgoB6sDhl913 zXV)kR_YXFC0@1@PwU>~m=%z=bUtG;{Gl)faMwGa{cvhB*$HS?a*}#MY^aUr+SsGKb zgElrTytvsWiSU#6|IPc!#`qgCCm{^DI21HX;-nxV+%!Kc`dkC7eXMk&*ELIYQ2X*; zS*)P*~Q?B)?@tS zTOKo6Nt0sM+p6cY*eux0!{yeMZ{i1gt?1F|`I!G+SU)&3#IdZDe-krioouwSE>~l$ zZ4OO)FKkt!t^AIv!$hvD?5oxN-D&kj$er=r*$MoLMp80Y@PQ(Ja*VyKNqq8C2R+Bl zD)cv|AIl;hrl}PVlg*&>$y1Pewrl(`*Sf8nF>^7B(>->x96UL^>9j)IWqd(ud>8%^ z0fFBtf~lDQO;vL~#vwgL&Fm-$uAxMx!EwQpPv!AIK+qcvhJQ~pQ+tU>@wUqG(;Zl->jDKoQNIQS&mExeR<>E zVKedo1@mM#(?c69h2kJ`=&)0roRaAKjE)ro`%>nCLA2zT;{XGUNN(5&%>=7<4TsJmlZWR&ZYH9+kcn^ z_(V0lJ=_@frgdx?2xZ^kQf_WOL8vNha)*b=^J>{D7~#i|8K&BNHsCh;Q7FJ60prWJ zH}hY7n(lAfkm^5iLXqz{FTtrNG)qyE5Yz}I%TJM3B7Q1jn08qGlc|*SWaxsFu?wNk ze^QK#pPc+pgOaSWD|>}n`*=p=o+RUAwWof#=b*oI9&!}FG)%T>!a(CV!xH}U)_LDz ziua`>ZhsVYlBjr{u-6yF)#7{eb!|Vx_QbanG)FXCXpFXDy%BahZfg7zKPOisup>=Q zlw~`E2K`nrf(J+U)%LBXC`H)_f0j!9=B1rniTIQ)=FSy!kCgF6Quu9{d?IluNlbDV z(v7UueixQ9a<4R4VLU+PV)f|1A=dvyJbC4!WZn6M?(W#9hpWS3tvq7bk)}+Z+AJ^Q zM~=q=VXd;Hk)tzhSx|XMZ&mF?FBzZZn`f5ptl|mA3ZILf^qjbHQYc0Y-=}5fNbc=G zN%b#zkYO;9ObM}?kLn6MN|svf$QjKI>3>-3E<@G0RV(kVus~N$e; z>rZO7yg6+{^ASLS^=O-ygB>qFq$EG9BsPS|TcD%Y{x);Ilz$@VFWPiw z`a9yRy>ij&j82HyC;evxj>?=peoW@aCBhi9Uw;!MIB})774aVKMyoE(Rhf;2m{N&( zCgtYl+Ep`)i)R=^6QryxEG<`iV`x_z25o6bBBM*JgBt$&X9!dYyGrj|3omxoE{~$) z$GCm~mcwk1FT+E-rw0@A4-krM%r4yW3A^l5OnM>F)@1ALX}%TseHDCuR#(CeIh;wm zFFy8d*i65(<9AzG8`oDu5d|gfeRi5BN=T)&62u`%y=GhyGNmyTV70h56b}{_(>0W( zr4A+|@bkZiqq7XQ>Zx z7v~W97Vm_I(h1S&IW#2nO{QP!(ngsUeY3N$H|@EjE0hA^LQfO><=?@7H-vu9i5J<2 zcWb#pzDU_CGeH90Z<#$okyuzRR-t0?Jn$~V%^Xa|} z`_s$0CcBt=#m~NIPL%aT)6Y+m_m{PCuweW9ZRZ*+RZg2Z z=ROhifpMX~$;(uYk^HY`*+4ksF<4O@%ahk9M2bL+$Q8L*JU;ef|57$1tHCOfOB;?j z(RUkb>#(UDS(L}x$8oI2`^fKqs^p^wK?;dDQb_iiOw*P`Nh@qqVYc5)vOlbrojd1J zYT3}6=sn3@XbWf?eMQon{X7*PzW|2=hrGHZB4p`4+H>yrDmeF#Y+D;cdU<{#VNKA& z@rZSGd1ij(Dv`pu-C{LFL*UTO>I6Zmp``bcXwc6NLjOMgyVRF7vCA#5~Qi8E%^m^w7<`5hFdYnLr{9rpZg%(F$3Yq2_c5`jAZq-TS ze|aiHNj3ffeJ5@v%kKJ)Ek~gvcSG@15pHgWUcQVWl1V24=GcnE&(TSNVqdP;f9w(a zZiW9x+VQd<$B@x}gz=Ocd}{Y4!%oQ@CMz|~$ji+=hG*PCX001p*KZ@q`El!Yy}Z2U z(sR6p7{hfNxergW^T{sIy=f0WzVJD$ym};J+{=S)iQIdf0)UaZ-{d!t>5NBsPM4Fm z$a#p&rYSNk-0ZtBva@e!EnE^O>Nt~$UyelQ=jT6r@gi`@bDbN|9@f2X9*F%&twi_Q zfj~+$tuNc(@HxrTUK1%9>UTF7beHsog~-1^LK^&^J=RY@#08?VmlebHi2F<`k9ujN zZP((}gKA;!JHO_@``lBEB5JCYFEu5_^`IHCJdtCK8e||DF-sw2_T>w|@YMT270Fy` z0r^V@`T>OXeepYISLyLT! z52=+XChrm9hmxoQp!|tk8CLbP(yRIN&`mx5hNK8aJ(Jaq#DA1+3QaUY=|q?qf3vZl=lc zM$4U4*zwYi#@y%g)Yv-ZOeuOd+v+k+J;Bu80;^5)^SV@CCm)csDw0O3FoyQ_^|h}Y z4bRQ>gYZ$n?bt{=gN4vcKf>fUElVV+p&*)1K<#YRE)Z6Ue-?Zihuj6DD3!Rz)@kf1 zA>Xe8-M6>Z0EX5}_JgV8Vmb?r-B5Y3xB1N|{laJWH82s1MyRLc6Z zfowWb>~QzQuP~1i>+S)80PEKk0a{{DJS*z`@&sb^q^F(8gEJ`z@2xt$Yi^8)+k4B| zZHXJPf*rYoEo#M}t8|&Xok*r_F@{~zMTYrfX?iCJACg9u!b6Cio{*l+2JU7;dZ}s; zk8{0)@5g~ek(Nng)Ta-jZ4sw_i;wu^EzTKm26z$-Bvl0@We!V1Cv5JFU(Z`uO^$#WLc@Of3{l|X)ppN z%~kSu9glLxMUCk3_UV+$C7k^9en@wuH?IKRN0j zuoHmoFCHxPEh@3^8NOwcFe%Vl59F4E-M5qs7|0;E>ivFKmDC6Bi{?32UR zX7dj!gmSQO8z|i-TV_sg2JwO;-TaU^@uOP6|K494vn3b&Wbc0uB{Yh9cUF?&p(gmn zg1KAz>Ve7<4-T4^?OVEtcAYJ>16q==B>2fC$ZMsAXVKUx&g=tWZ6^4Y9n&*2PZK@7 zkZfUg5QSxK=LH8d7q9=9|OJk=0m;WVqq#Y>Q7jWODAeh)V1AD2& zf##M1$bz(f9==^uNU!5RKcJh=%*$c$vK^~cE3U50Q~wF!W2#!g&E#!t-G@|`#OFM6 zI+D51k!v2o#y(D~EW2vb_+4TM{GFQhOgWsP}8PDDv`jwnv8DbZO zXlwji1k`!3+iHsA(2zV=?xwc&W{m3zojH z{I=G%)BT$a=noEze`}}^q!K{W&~f>vc01L$gCVBA0}I0KHY$>I<^K-qk2CR7N7TUa z=coEpy8^Z`yIi(H*XOJbQ+<2^JyN)&0(?Tf7gl`E1AXiA_U1p_L($NL7wA|;&irg?drQ22*$PBVmIDbUcH3i>ft?zELHvle{{%-kXDf3uKcL2K z60p<2WuZuFPo!kXU27x8PT_<2qIKHqKWO}$^|b{5HbcLSr^5%=u{!uoHtSO#F0X%~-S?gTE|cVu6$+d(sgKx((LNMUxkM8ZFK>kk zuPnZt{9W!(_7{fJPVMf8l-QddHgjt%IqGXFvQRw6Ol>a&vIc(4mK@RyQK0$1F}2obq@%4-(?lB>nzlf=B-K-#&QO%W5{?W$|W9kxlt*nDCHMSEiaud@k5Upb~ku7-eo-%_; zm(!25$0JtOnnu->KeG3w>_08^bc~`gNv(d(LfVaYHQXe^xYxeMucKRopW5(_d#P4k zJP}Xso*0wgpZCf6WyH79B6i`N$=Hcqf5+nCJ_&w3 z$G_#qZuDmj?dEl>NI3cMnl=w;#8Hg1K<%CJ1|*chKt_p%(3A{Cyt6M*pO6ln|NM9V#K6L z;Ak0JO$dYj9$7+Hapgf}+1UeSAza1O8bGt4HyR+BU~aj@R)@mW$6Xao{bCmanygm> z%D2WMCD(_$Zh5u-Ke>f9J|a-P59-^FRA#mDV;D0r^Fm}HQQw zHiqVV^7!?=NZ|ZLCU+Vdn{~%^2q&yT(r+I|nc3w3yB5Gq1(|+-@(!HO{~O0YoZ~ov z77Z!&I)w_PDc&yl+$7c1)YKi2;K!VU__Q7cf8o4rIXr|@zc1WD40CQDbg zPu0us+Ft9ryY&Q=a!=d|&sR8cgDf+pvyYyhxTq+Mhqw0qhYx*61Pn*6B8JvIUnZ#s zv~2GD#9OZBlcMjIB{FDPS>q>led6$(_tFo&q>L_+rZ7CVX9cIFGPjCyBdfA9E|aSOSnG!wozk_VkLuz3jXUDe~4^y4nfrmh!I)uGD)s6>GF$``+GqAKN=H_73Hh7cgcb2xI#4-b&DL8Ste> z_D5iIadVZ;p6nP2m9vDW%zUb8J>e3+>rw%^Qk5FpdoJF~hEscI1=duL^M5DAvu5Yj zCh{-1V?6vQ2)L=NyEh>r+KW%04j#f8(tlPKg$s6dY5TycRbX@|32cl#OSZPVQ+Bx2 zf?;MUELsglGxtB^ha!WZnDAE~G8pVlWt7kgjpl7-%wu?7!`mhE^`%ak&4YuDk5VuC z`uh6WS(BF&LS>u4#)N3oneS{1IG#U5*_qVi&9Un18#HnLfNy2B_jjBh#dfv#1BzO) zhFH7;LCpQ(?B_AzzjuGqhSw(~uaeZ5Aj7H1mdphO8z;bX0rym>{1!B#_GAHX%sEg3;_h@O}C!M}n6 z;rKkIN|%G+m6R~>*ME>Pcqlo|Ujd*pdC zD5i%AwKf)|{uxt!rBCl@)+d5eSY&aSYI0teK0gKBs)JcuHydMNtq)@;yY!6HPtz@yIDI_G++uN(Z5BaR9`cd(%a015Ug^1^>hHL8N z#>#nU9FUoa7i(*~FvQaLDh0`9B|nu_nic2zI-@mAG5eL%-78!1M(4dHZOK4(aq;LK zejR9&n?wCt!msNSQzcuFK0myjX%O;0?YBX(t=GQp%J6yQyP zUtcNUu}@Lja)wTX@5dm@8d+4*@O8Y1&3JZVASt11B&$cd?x(z@ju#pk8Cib{uf;o5 zQ!}$iTU%Q^e0)+jcf@T=v@)9Io@C5wEGj_PeZOGm^-9rmGso?eHYTLC`b1I+>qTOw zq9`Z~h}`ts%}RUvMl+h& zX8`SyBv<|Y7?mL)`1!P#M`8YAr4fZi_hXO&>4w)rMIe?&oxYfEMl=vayWdjFE_?IP ztua+_Zfd0st4#;2BbS$`3(d28=_ekI+HAzEOq~@hQBlNF(I~n)aZPWQ&klp zWo+C84|W;anSV0-*)>(}h6ufK@2>=t<)m3>@#pR2>VS@xbDEr}Tt)dfos~~B@ z2J8V@!X$y~dJS|>sB~v_V+|0`xn@xpg40yd7^RcpV_S5BjG==iC2(_p?1!p+tk9=N zb*|!aqRhEd~O2=d)B3&u! zLL`{>konJ+P-`3W$XvV6s|cPbK72KF5=U{T^4vW7&lb z^dkeQWhYfUV(vS9yUxPY`{J)Uley!Z58~UwDN%^<>^mr>7yc+MjgE-GUMD2!0lLfe z_MgLLf&LUeuS&XZYQm2ewLc*pALj*yWtC1lCcDm8Aw79?43)-kg{7PrdN~INxpTo| z{sqUC5Y_7M-wA6hTORr1A*dgUXL&aC4Gpb~A?6iKq|tG3Os6icCh7O)H8qn7Nrt)) z5Ycs0SGL#Q;xbX_)K2xEHJefyXyT9ncu+3Cu-2|~+{nZv!gUv`b#@Tw^Gm&^5G9xw z34DEk(q10SsIJ|TAbvG=rCI-35R0T=Nw!hu_&6#J)GmNyT1i)$?}G@A#Pg;ObpNaf zyza|8crMHwo-m;{?+V|0YqKE>${m2m1W!(jSa4wn#`NRI^z}sG<-A*cMrX=DU)W=J z{`mPbc7DEGy!z#6yMORK@C;u@@9xn6O=zvBvDXEb$AwX=&UbE3TMV(70SrOt0LK@b zgGCZ0QA#IE7Z*IG>t)6oD_F6X zR9r<)j#=sUSl#m^!Ky!5r9W@K1uMEo0zdcLbG)qwCz{ zBQpVqpo9csdj|*2rhLmc@yhtgpMXBt>kg)i_OPdWrr=B#o9@pEX6NLNj*UrZd3f}% zAm?76Y(*MSW4Xlp2~B;2-6)_WtRTNQyP_h@KNwf(GR~HUmyho#E8{xi__&LONlO+u z-Dyot(xBH$N_g>kMM%7or>H8gNzTnR+G(LBee5dY=z;+Hl@j)6j6&Sb{>{Rg?Ig8= zmjoJ$!v3yaqB**5YqiNdAn5BD&yj1Bkx6!CzUA{^TS`uHN)WqzcWTm>yW@dzC z?He`g$_SrP2!)56ax*ie?x$1j)?}tBL@EsM@bThJrza=3?{<7mG9W;Ls!?SX0sGzi zH(oc{*|}w3mQX!)**9ipWNthQ7#WR>3zb=Dj&cAe8X7)W!Uj>qu&%Y}ZHb6*bicYh zbZt@@t~lq!_6N8^z^>x!{Ja=27N$ehcDd!D;Iob%A`+taT3Sjw(+=l1-VGk^0`9wX zQh|X}UzaKxaub@VsTR*e0d_}3Y~A7p`;WRg2x{87PZ?q!I|w>26N-v9pDV--4zd+m zm}kxK$AZJl|5ZdiR!&1>m;<84-wFU9+M5piENOXe?y+A?k)kyyZN<#eo9F6`&z2Zh zQew@-RJ6JO=zeKDG||3rndb$3{abQ;pC4NA7Q!`2PbeB-Y;0^HVc~+}!c2Y*Rc-AW z-}Quvd?wvRsz88tz$)_#e&yxl@HY9Z9p^wqRQu-YyH<@%mk?KiV^fj}R=aJ+r097? z<{S##<{qHxbZg6ODPwGGEWhMexKKA>{udUM?3Ibn5+Up&R7o&(1H>jHJ- zrC`d>4pgDWhAl|8d3lJo2r|)JXJ&5w+~xg?0S;{Yib_F`Gi_I`4+i(70v`bV3&V3h z$SSB0e6beZF)scv;-a`nywl_0AQ!l@Kp@)(h%(ayZ|A*f87&WCIVcIvY{IluAR!U4 z?XuB>1Fy2+KYz^Mtq+hXZ!v2LWUMEsiA^;}cgjS^WhCDq!!wZdW6BxVH#Rnnf()b^$}ru^8dYgxD#9b69hv_eYdW{^q30GTAZk`%2^eNf7Ln)UU; zutLn-oLo@J8|=vJ;^++Ol%L$X9o{BPRI;$@Q6y3B+oQf;u%$Oa*qNJSdfnVCm6453 zj|V=e(<#Zfin+YJw5tz_l8EeDvMsB#yvM-I)I)Kfp~IvyCOMgN{V{f#>B7C+^LaL0 zO0n)a!7f_G`+xfSx=*gmEU2$er6jv0h=6S#nlFot(Q*-s(p4mn{%@vZc^fD4Lp1))htngUx+?Vej5;3`CN>+;bKN z_)Nfa^}KHO1r3)P@JCP7siH|ThGgEoKU7cCfnIq2w23 zCr58ueCKFR_;lkz+czdqGafFeMb)-tAJn+U@P`ANIG**#uV4OPFD1^dJax4dP5-*S zLrEM9bwNc8{^`&58WzeZ2N*4OZvXl7rrKJ(+F43CcAZ*lu!e^xb~3W*w~LxhfRMMr>T1oxpBq$qT_Gn04MWadwfTt_l`##EdNc z(PU-7QNDfmjt0Q?`4H>Vs;ZCJ5e7x6hP*cW6ivDd-=@!>1C1~;H65|d`H^p70}6 zXm*qCXB5-Z$-sHz7W60Qz6F?%g;7u5{2gYH!8geF-#;rJ&@sH|YDzatAtYW^(fAhK zxzuJ|8)ybcgzvn42MC;G(^;cP5Z5w^EF{tC6;DQ z6-6TR;pV`0iV%=79FM3F*`H+pAuTb}{#CL1ICp*o?x+HSP4L@s)UtGN$*iY`y4F$Uw z0Dv5w#g4<#_-XuHO>s=Ik=_qs(LNcqSoXPU@7W6JmWGzt8nMZb{Oa9XEglC;vGL5g zNW?W3R_+Ejf!Z*-gvsa5Ch%4i=A-%6h+$~HZFzox;59m&Tl)H;RQ2P>-iD%*Y}YeO z3PlVMXj6!J28eo`EuFqF(cZj4z@Yo09)hl$hXJK_8w~sU-e!+l-Pid)rk8{@oW*3x zxZU}~j=pRs&7dqExqh)WTN~hC$jzM`9OXq)4+vb)b&L~ zOb@mxAicz#5IPWHmna7BBf@C!JB+5oWoKj=JPBKfMJhpNZEdtdD&J?)?(fwGi1g;` z1E1U0>noq@Z{6L}V%|Mgpa@=G(*v}7Nk$b|`}TJA?j6dga$|t4T68`q+bYdBAe}pj zA)oSDd{)|gYvSqwLI<0cQ^oTQjQ!SV6(S-KVdXv(&q&mp?kfm;$ERJH4tNisS%8t4 zu7|762&1Y_MlaY2gTSe$f(1a73){Qb^IivDEYRhwTFaat&oX{#S)1%6q?&yZ=v+|} z35BjbJxVF35OECY3MVNCT-+J804Y2vrbMg76`!0>d%))gp@cGJJK#Q{UtjjEiGcthI4M0Zqb&8xMvVtWj@c@5>r8|6nuNfrfh9J`uBCZl%xp_U=H8L z#_z8Dso&6uo#eF~#->hPUCCrFG~02otrCnxm{*wXMBeTQF*5{%Ko>OpaBVlM7i$bU z47JL7d7*)JjPhCWFqp|opPrb4jSaPWX=~W5O>2C{?d~HK@0(OpzgPJKL(r9)XzQ%z z=Cn?ksN?JK*}lO+s?qUAtDSZHQnK)&jg4Qm^9EzNkrblN6wy80t6*b18VXZp#oV#T z$hJUJin20Sw8O^COU7gwt=W6;`Q*y+^*Tgq+vl#!9Kea)T|?3&BMo$sg{9OWe=V`gEYd9Rb9UA?w;N3#iNGZVGNH&2}B}Jeon_ z3kvYZo4dOdNAr}FXjk0a+$iTD^FVH|Ep386z{(2X#&6KI^jguGPce_cx1r(Epj14; z0v8CkuE&kSeghm=?E0{+t`vDFqp@x$r)SFQ3$Ki&K=W4PJ-`nG8p?V1`lbwluF+`1 zYcW~ey>mJ`I{L>Z-79^6-)}9SrnT{;$`LRtw4ak+X=!=<5CzBT;u2kS173he0ugeE z6(N4|7GO|vU$qX2eXey^i;ecq@cM00l`bkLBZC1=WREQD?fR^#cJB3(MHFOVHuv_( z?JWVSqi%L09}>HxQB@rWA>c%bX4mKoxsIMBHAj-uFOw7sRyvd*44$?f7+-0zR#oLi z3I%X&;BrSNm$C8jff?SSt-Zah>eJm2#C)?Ud#lgssR2U9+a*mWZOXF7mPG88nwm-3 zFA0geFUS+=;Q+}FZZiuD@(Q0r1Pcw9N`ah?5vI0qEAMs7uOWQ6*D7bZMnV^}u=mg( zB51wy3VKClC1~;j&8HQBn*o@D0^SrArKkuI=$jbekBdu->|FW*Xy9m=GqZCeWuxeD z5J=occF5KL}({SoIu;>CSkif=@r~aVF9R?_JZEF%}B5O`z;Zxr+ z)p2dNE@KFlq06NPXjN0u(&~}1u1o*>_ah)A>W?mWK=B%dGK@m*N?1rp(5dk2FHm$n zQA`0WY@%)jS5rsZ;sH{$l#!wZJdTFy-}aUu?D>31;Ox>yCNt{mEg)N4i0OJ~Z|0&f zA=fPXiEn*xVwjke;H&IDU6(~AtH9l~??3$2>E`&`&bRd{BT=(;pGoZKC^{x3-AzyBv)qSY zDo706&@QYt13FPc<)C#BY>!j56Ufa4Hts8Blq^?N7j@p_Nl*7#a?y5yRf`-ZUvcL0&dZ;!{L zT<-|OJ7+6my^LPC33w@5ycwkXRIDROmjdHZT_Q}jEJs}(SqCi?GwzhMYW&8RZxs}9 zqI&+)J;RTYy(~@ckucFlL^^g#G=56JFn zBQF8jM~<2Q3u7*4Fidb<0W?Aj+909GM|Wl7RAQ?ffg+>AQ~=xTb&-mhd6Lai!qQ9} z4C@>1P*s6Z0PYA53XQl1B!d((7N8z^3rXAb1EdI;2VP>aHs|=jWBi!c|7jX9karT6 zWc#Gf-V_rQ^;8}jLB=P1H4`L?7*v{j4Pxl{_&kQvc0=1cFT^t93dGq|XGy~1b{Msm zu>SF#NxKVU3(Y#)=T;jtAlH_3=!KvbkTp~p3mA&#Nf>%YLz7iWmcRs|QDG(Ia!_{Y zA>w1d4|1bxK6miDwS~KtV36!NJ`%R3eeuG$F!coujomu3RY+Ujy6H43Kf|Tvko?(W zq0*PI`?k6UaKEdAtir;Sj-RN2Cg0lJG`f;w=gzB3g0gA(&#o=^ju_1WHj;*3mtkJ2p*S+p3AmB8?#g3`#il`_R%|z($+=u9rOMPfCt#p*C_$ThJ9)R=fQ{^j7mzU(0^%t}&x7eU} zT?Dv!GZUo&;SJSk?WVej1u+Eb#$dG_NEM#1rcrNz^<1AbM8`oOzeQaZq%M}F0H;dh zIS8KQc-AyyL*9^}NL{BR4RlT3U35~9%+5lss9`~qJ8OW>r|2vzw}KALRakKkU0+Tg zUVuE>M*W6fc1z1WXhq#!i^nubMpf7UcP)S^JK!S4GD<)9Ivvzs4Stw?Vg@pGQ&xQG z*K1=SZE1IUSyXp(rggD3l#a5PV5KGd&K}-hnvqk~7!H`x{#5?N4ZS%{neJ|#~D9vgJKlID$(m7t~zPf9zf@;-R*;KK&ULp}#VIN-SF zJ$y*g(sWpKwWZb6=p8otm*Oc)N5{t%KsT$fo>$}p_(ohxijeK?ycg(-H`JU6nAx?j zuXA4E1^#85{OM<1mtw0&Z83W>E+ z=W$<_dIMK^GPJjsotG1no)Bf0@`1qZO^=CD_81I?^GZf$qi*(5ne%`-aCwoS)juaE zenI3)1ctvC-L$VU`y@6w`Cc0d_2t~RD-$CVKzBtOYgU0MeVQGR@Sp{dt4mktr2iwo zUvY8&W(GI6pxZX#KaYoGk2;sw9W9MWDYU0~SRLdMu>Wz3{}%t~!I%v7)jg1dmN+v7 zIdD?+^P624HHbW{Vr#2}7tk&?=|;NlrNSaqN`Kw}8jpEN@so$F*B_PfhVe9<4Y-9BW~H zCn7`r5Wyjc+aM%8&vf*y%M*q@x;^LRbGxS5-Ed;({+|!-e;z?a4RHRbI+GK-eSxMT zE&K58d%`CflY(No`!}$Kn|Uqu&CUaL#6$yc5y#WWDN85N=3YtTL?#gwu)Mc7P<1ZQ z9YwiPVfYx|?UPx6ie#^7T+j6?T}NkUnMSea?b;*{FYmoFt1Crf)=zB{lYE9KCEBdd zvw}ao^#$5~G5UFmDC`O?t(V5G`jr? zV17UXdmkAR(z^nRabL5_md{D=t{nS z|Ewn}!&bEq>_szZE#-PehK-)Us<31^FBnB7ysz!O?4hjdt z!oovCL+NQmAfLhmt4`$K$=RSTuB}NOH`PZ_^W%c_jrI3=I#yP+q5<@_3D#6b7A6`Y zrm)`L%)k6R-xAXM#*1E93p(TDlOYRo=YBl9qlh=o!5i=SHvAqH4NcHRtQFdTMZ#Xb zT)!4UA^5e@Z387Kog=ceWnS7%>ctqr@^qOu<-yIq7vRM->S>6wf-$Ilju}Cz1KmK{ zz)W=!_-+Dm`!hkOKZGsQE&d>YF%gUFg)=L=A^2MXWh(O54s7ir5Q5{RY94-q6{j>8 z-7Wnm+IBaFwEV;Cl9p&^CM`uQu0(X0d{16UzV1xFFc2@83qX^g?^OURPaT#<7 zgoT%KdCzHh-;}mGoyEH?KK4PJ=@C#7ORGBZqG(PFevgkw`5bsC+RS=t+MA;Bg|4Gu)5ixU@a(z2!0iH^+~g|cKx=61$jfmEQjQ-&wGEn zKUY6lJWvA1g(B|0Gr)HB5F67GeK08Kz~9Q_YVR_V{)Pw~AztnRF-`PH+Q`7;)_Em~ zNxrAOY+IMTb17Q2Wiyt)71Nd}+?ouJvqMR-(cir*5lno5f#GCMKtU*J@BzQ(KrHV9 z>f$4zqT-;33%+?KTm59wC8lTdlihR1IBIW40&Atzyc|<5RD4;;l}aW8_OCYOiWD8mJ9IxKopd=sxp2Ehs5?F1qq6F*VeF zXq8i3F!b<{gN2wm!{?&f;gk2qelzk?lF8Ce%d@P8h9Dr|j*4mFz(`<&?~f?Vf@S(C z#`G*5sLWmx3bgieostT8SnMPW016`P8tYP;79ZcODIy3L`2>oHc{5-ej(LWMbCEP( z-S_{t8UMKe`I*Ik(u*N-|6617oQ`kr*zR!QUE}N;y!K8!y8a1Bg-(t<%Fc|_Gj2V~rD7@>9_w9pxkz4=!l#<&{eqPp-F zzusJtncryE_8K@{_act*1D#t&DCFGkggX-@^ui2@Xc)K#MMbm%0wIiX44mMLJJ@s^ zcK4r93j?#VtMqDka6lCLe|r1ws3y0yPY?wa6cME;f)oLz3P_iT(iEhKA|O?PfOL@F zL`0ez5e4abID#I!NDYK8Jv8YhL`tZE&_kUk-tXR-JKwo${+KoMSJrZc_`dJn`+0tC z?>FH}drV67P4|@zybK@T2mIwuD=Q8cb5};?zNGNgFv?Y1i?bOPJ-Lir!AARxy>a*P z@HhtM=E6_U#y7gsm{p$W=o??1ZO@Bbjmf;PNd*&@$7j^!2kLBCaWSjRE$6qfvB5ny zXymS8d3ky1lNkwiy5n7NhL0T=e@sMArok_~KhO2Z@9=Id&&ID&x-Z#LhP zkYMZ@9%iUw#BB+AE{?YJytSt~c<>;cOH^Jh7Pw{~D`M2z@rj92QiW0;Xd4;%#C4Xh ziDg;Of9Ry*(~2T&77I|qJP(Q;(!Z2^n4T7Vp{A1OWq#&DU(SYM3#Fc3ELEGx`^4}n zyvio?QC~_$or*e~#}MJ|=i`e1bzz;TIk&!h5#tt`{)0bVJ6_AcGsm6$ebd`1_PxH* zy`Ib!oasVfU|_31KNIX*Y8JYr(Jp&URrNxL$E{sufIoLyx(vP_~QJ8MHw2=cr1bep$RSeu2xdpmd(7xmIk zGhvBDnpzRVq+>p%L@*18Kdf_sjbO>U+lg)Ec}~YBPoJLGX{o}yJbBQN&?5w5#yD<& ziJLZrPAK&Oc11{^yc3nn<;^hUInTq#_h-Y`UNOv0!e`Ap;%p4_L#d;7rVjIWF#|;* zi83vqY?OnpL1P~;ZpmRDP>?)mOdqAsvb_9a&7{OTB4~f?hJQeSxAnNDR+4y%W*mK4 zHUS#rukrDu;O^Smkdpq3$u)x^*!_yau!D33YlUq)5-%;hxoN79LY z0oq$a0s?VeU5Tp_=p^r`e$#lSmkF*$y z`_m_evn)rdo*x;%%%+&sjb)VIAL$t#?bHUzD_bNR%r%&7u47}LoHlSn~QytvgrZm4t=Esg5!y<}{i)k#R;+HlmccaMa z&J_dJiB%u3K~aHGGtBPYUO&frR#RV}SFRN4m&IL$cnYeF1Mc=|8bjgY?PClK(|6N^ z`We?pBSCzRn=*?w`rdb^iEe^XL(hE41mD6I>1C9eQm0 z2iil24s0Oy@AdSUJ}x$PbQsEy3Z*BE6XrCn@0|v$_4Tj6h+XQ=sFlFJh6-QzwyU!N zquGc>OP#lbHtX4UO{TG_?Uj(uY~U=Z2wzPBljy9X;1eZ5hXX%{%R<|19?kx28Wt9s zyMN9r&}e^o1lRHN=bF1S3?y{rXV0GH848cAyDoKDs-ZaRHP4!j6|`5DEso$l);zqu zy-%>QjWHaD(Zuz*ZhN3dMlU|uB$GX7(`q|AGse+FBfPm4UnlkCpo(9~GM^jL!6%DbkR{?+D|o!P!ck5Z#qJuakkPN{l+`*uY;YU`?^+p4Th zLA)NXx@?@ONa+vAi1-y!ZU$Q7@I+wuT*Z+@m+iPkm|Au9+Sb)OR@bjTyBx3eJT#Qj zq;E0{DCRLHX1{*+?7G39q5@KU2V0@)4qK9aQbTtn*`q}XJ_a1 z`UXm&M{)tubin0D&CZ@N8`(SwC2EA&@-W|RT+>CtHH=!-Um$Xt`jaUG>c#CanJXs&AA$w(fv`Bk}=l=uY# zBif}-Lzo!VD3&~^DQr)L?rX|l<(wYF^JZz7Vo{^M%u)5Xw2ATBX*v*$29VsMp``_} zwdGGKa^#Q3oSzGjD;gR7ntMURC+4`?DwT~Tx5+uM)67UnrNh{6I#JMMo$ z70C1r*Afeg7gy3`rO_kUoU7X!M&F;lYz@(=<1JL7<^30wV)80`mq+_i#}f8KSe+L? zBQI5RahXrI;}lbLwbl)a;KuXo4)BlPp9yzZeXd_<^XVPApm^mqCt`+pQbmRK3iJ*E zL*prBuQ>5(5u@)_BNxKM8GPZ(0ArZqU=zXr{u!-4n=OMoFDl(TVhy038Nz}S7ngp_ zmoMwuqzRjD&Lvz=#{{++t0BX59Ljk0oJ_h!agVwip^%CTG zqji+FpM6`FNQu83ay!>yf^~5(0sO_dc^ws%6v~Eins=$WTvSD6@`P_C{f1_S=t1&U z(?4PU$}W5o=$L=v#EA|?2?;gtiea5RSRa1jQR()fwV+k164_v!MoTe<+>*!cJj z7u^$@cUU~^BzZ4KNAs$Uf!r}XPyp0rtfpQwU^EWq-f@_F>{UmPMs1FvxFp2vM01_V zn&lM%a95Nny*%HBK6lkK2{7OCd@E8=NNh2Gp`uGd!l>Z!X@n2K@v%r@l6-Q{OpeFX ztRxPW3}Gu$i`n)y3~=pqi{Z=oNi1p)beo){D$%1eP1paQo`D;QE;1vI@0m^SA76T))y zUzY$*`}fg;J{pdSnuH6-N=u&1a8Fz12gwaM_460cmj(#t4+{pU1TSH(>yh3!_$tB_ z^`4RZx23X(apXAO*wkkIWAz|GBCRvz`KB!J7Ajj?hl$?o2f`l^s1MV&TKu~TcU4q4 zMRskN*gx+0?^5OU^sM!ZD#*l!&;dxjX9hr5R8nDO?z83W%PAbmB z{yy_5w#K$^Zm)y3MOa16+{phF86>VJxtuR$8HER=QhVLa~wkuk`8zgf~x zSJc*w<@TIC#ly?XF6Z{#UA{s=L?jy~V`%+D%gv<^>PFvy2~(yKtzcMf^=nfU@`ODYlS|QQBRN8X!he3u%Nz25w!)|h&)|1^`DdH?PoHI)rV~dDWbj-grZxH& zacKGzOzf-BuPA^0cWU4ADQ~}*v98wSA~O2(8pp+@U#s`Tyxs}(@aVArULecj3J>0%ku#XC&nX!F zDShV1sw2|DTR}79`!XR0Llo76bq;s?QG#tOl>K&6`LCuT6E4DXf-motH8pKBGG<{& z^Ng(CL{o&2*-rhu+QaLE1Dg)aXHy-24T|W;G3ml|P`^oeUXh2V&)4OKWGQJ!?aw?w z)ExLNaH3GZDeyXxs%p^6MO2expgFT3At`r%ulr+z8C1Q-3d*eb@*;MKG5+wfhlO2v zg)q7^imMQc6}!fFp4f*8;6Z<0Of zQWa-}Y2_HHS?NwV!Z=+ufj?i>P~Gq1hZ-78xEg+xSXJBcNRo23*C~^?KU9Lb61$2d zP1x-5f9B-U$&)%$qGvx(!mhMNnUzZw#QThA4}a-_!o~B8l24}@UVEUXBs=JCQ7)rv zmMu-H^xQ^&69$YRaQiFhJmLRmDP~600}w~m(V-?XJcH1P>Xk)zxiW!e6G~iEZr2sZ%U@w$%stZznx1E|wvzh~;?&odLsnXEV8S5+n*ijN757 zRaMslch+%BD8w#%TU*=6x{P117L>o_wESEr{|M6f54?*kbUZ758Ep>rOhQBa0tGgmy3^+$s!c* zuPb>io7Q~yR@(ig=`<`ziKvo3AKTp8TIx(ie9Fm50nJd-_otvJ>#5bhzWZ$_UbVN$ zSlh>?A`Vqqh>Jg&)|JjOSI@4$K3clM7-RnWgvblu%L20KZWm4hh^-nnRjB1TM2*}$#) zo|9f&$Q6K)IKTx;D|=2F-}YRBfv@YPsA+J_vWBuHccwv3Rd-9wAHN=gI7BevItf0e z&ZrN%rKnmt_^!d|UU|kEddB1F$B*aXKPqI=teh)DR}!`26(c78EM;JqI0!(WDhF56 zxwsn<-WWq!wQppT-?!NZmB#Pd&6uj45-|`uP6t~D5wJaV1`{w`5#RH;-a`DuNsiX+ z!9h_+$jS1@bdm=h*5~po{m7t$Y}Uvt?e1vim{i;XF`&Rk>4wAnPtMnOphNS(x?ng7 zQqc--zX9PgKg4X>Ym_YwLO@+YPrIa*hG0qxKNMgRh#YLMemMD}E7~TX9WcoCP07m> zFR!S$nUpe?v@9wnhV;FDKuJmIAl;#SK5Mgb!z;v{M}@*Fcq_fJ1g_7c=BfUXzB%X5-X-nO`$A_2eD@_0OMa z-o!>X!I`C-O$yHX_FpG|&IrS1m617LfQ5E5>>%}sV3aWL-p9G(yDq$FP(iHbN5bQ) z|Nn=jQXV>2kZE1>e=DXcv~o}>d>)qnuN5ME2K`f&Hjn$pb)&c6!kKEr#_{;-S6z2r zb6izxDx4#VW}E-39++H%fv58Oj~GbwHbB4MC4Cw6o?}Pw!20WK3aEAO`iK7UO9(4X z;Rg<~-q}4;1|qGrl>I5l;zdpatsUWqAKTjx6e79$oQKB-kT&80 zz6Zc3!6og2(!a89z8@Y$?m-)78-S{*VN~)Im^!FgZH04vu!Bg9CG&L2E_)}i_7{vO zOOS?#yof&>)*oTBkfaq!eFL)}fa5L-3DG&}d_`Il-o_--KOLA?pZ#VHxc_Jv&C#gH zesmB5Y#8jGn5n3Rw-$q5Ezj3nXA6iYC1Zm@R8Li^1(WFtL}uoCMOa!>)xoUe*WcgU zm4yNcsupR*eCMq@nH9tdHPF6bkE|lz(o*I4xmgE=0As}q7cLl9(!ZPQ&El7mddH}= z!wg%s#lFJM!pUQEm)S6j2rwXK;^{Vn_BRlEaKT;m7yU^+GCR3JTkIe}hbPOgVG11$ z_YVM`*_m4mY8LI|4vno^+AjtPSJRyMMpEtT2$py2f|-_X|1&N|3fo}2sA^DuSU zZG?|})LftQ80-#oUi=8>;ln;Q*wZI-OLHJKcGS_7o4fiY$K_|q=ndWb_feoLwT}jX z{l*d$6of=GXK3p|wJwrY$k0YwN^di{^!Y-&Lr|)N1+YKZ&p{9Uf|caz>R?!zg4mnk z&53=t`WI@@Sd-AlLQhRiO*N4s*8(>OB^4Wv=UrMO;ej+!JwHfCy%gg$O6)?q=$?Q7 z{yi18QgfhG{}6V87G!+v$g_sJu)LBV)~0^dAa3PwY2<}K&l_F*>5-h5ejT(3~yOy zyIN%>@lgE9ZswPw-kL)DJ}}QXU|eCM-ZaI$d{)Clmq1RXr(6(O`G{^|Ta6VquId~Y7%_bT zpv+q8iR~>@z{V;jzj1!Z2jm-8?(PuMS-T(h;jC(0ybG^9jsJS^MJTJm_s4ZV{SB^Q z2rb+NpawD(&2xb;Nh>Ed+D8OB{Mk1ComT!GwS*%&N7Ed<~0_5L@<@?(mrSjWA zmXyBDd8x(=;X_Lxt^E$tqoDPB#qm{q_vsc&l1pqDD0e4{e(s~7X^F$CqObUb<;pet z%j3dO(bI*U&-q>`rZ>~daB(@GWd223UY=c5G>mC_4yY~S2x)WE8=&AKoZ}a386CYd zRe@8eotnCxdxDE~ZmQWzybIj7#`EJc&03AE9q8D*?qmUmFL&D8PcpmqJ*HtW zWw|+Cs0O!Mw%}eHB_{6svr)7Ku{%$n6Kmr4Zqm!XW$Lz-HNl1Gk7?Oh6HnJl(D3$V zbGK=mUu76R)a0H&G(;kiY>vK3_FLae_DA;TX-u`Is6M%SieHDBB`S1hhu#WF=&~tB zxNOr!9A?bODo=`yUBs<0BDOX^ZJ!H&p{gd~w_X6o5`J0{iQLiGyZa2g)^@f+qvi(!gosii(OP9Cl6WtfY+;GzA_30t3Xf(y<0%1GrQW zzCS1+SpYeEctwOf14_i~0GbQHbK3KjY`8#2B&Rj8&oiQ%nIbKOAoiL{g#`uQaP9Q$ zS6A+>;{Xo>ERR-n79DSRRXa2kb@sE&P0g07$LQT`J&HS*_a%J#iy#kg*d1KQHDh1? z`?;}sTjbLW=gw4v`C0(NEEo$bcy%sGURQa^)SGqf?WpC*qg|56b@e*e?C)@8fM)@n zI4M`tIhOjwxho7*Vq!WRuU~U+gIfj1g?s>Mpi$AKv9uh_?HgezdGl>DIbYVAJ5B91 zSYW#Tlb6`z>Fk`GXnmD3=FAOS`Y#u!HYJLSq>STICV)MgDxGB;TWnvp)(nq`NC)UW zEQvHs`W9|pv7HNfa8hmYUgQXRLted<=D>kXJmcsXD{O{tYZGx8`4$hD_m8wRwlhrC zqXBlOPMgn=jJ=Z>XlYtv|8$XF&%0V$ti>dS6JO8PS8F z*ifg!%*3e;Fy7eLCJrfprkKghx7uI4+mqN@Tg>&;zqVhbkQx-MN>5;BVgj}CG!=j? z6SU{KA+O5w_j5Grr}5RG1>Dltm%QnYEbv^o1beShNxNEq>njcbE92(oPEHrPapSsS z$$5A~#+DpVE|uqDl0IF0#k5!jnZqM`z6CGc31YJ5>gva8!NPkN3aLq)h7_OFJK#uI zT%n^^Z=M9>($u6s6sijD%ONM!ncK^2GT`)9 zU|2Zgh}bn)wDP9o9ozOLGG*IxXQYL@N->q&&Ho70*g85+)-`p*k4swClqmH~-Ga8p z#BO!QS&B)V`{Ux!d+GHOOh&s)3UvDSLMgl}Rork*W8(|At0xbPt*^>gfhSVX;u#E# z?B0DEU~Xe3frL4|SLwu{+dzJb7W(UJLRUyDYoEXU71?bxaQEIluOTHc{J|X`wkCt` z$D!mlucSUK1HHjQBHr+gB5O>O%FjFv7g;K&eRe=19E$!~4ei>a?9oS;HZpQ@a)zX- z_o!iyhphS=YT?)+RMAle2A7l;Oj38w_EJV+;rkzGwMOE6nwUe;BI;9dU(JXKdDO!z zXGP3}Z@)$r@&eVGXL~o$#O)a(!4`rSNkPO#3^BkNa18a{-bhgZ2ZC{fVu-L?J=Pg1 z6L9IhvzhAtwPd$b>DC^SzYke=nII;Qiv7uvJ+q+{m1|Nz>V$;z)3iQ_r$Bg0Zt+hH zb7UZy=nRKT5by7w8}TRRgY5?X-`M(>pXv9_rtVh;<_?K_O-#GC+DJl4aS=ruyS?I7 z=UOnW=H%r3J^=WO9~OLPr(SYzabY1I-?=jJur2!mG|Y7Ep278Xw> zJAhRu6&3Kxe42m0Ir@H*NUY zp`qekBANB-%$2v0MLbAH_ekmtN!)jMZ$F?WsTL1`kZv-2cXRV4u>TZ7KdEzjHuL!h z_}e-;&7ATFCJyQeC>bXetnZ)#z$9E>&=C;zyhJ}GzetAj^l!%jMdR|IO0OZ{IVa z&+%UbVPU&VB0MU&x#rLcaKma_sfW_rfReRRXE7UR7IOFY${{#{1iocvW~Qj%PD@R_ zXM!>D#*GKUB=Q110|3WeOl8vg)Vfop-4_MUQBDgg-_3yEjp<~DmvNE(h8f}ZNgo1M z(yzDR?*04v1_q;FN3bJ)@d`+3h*O3E(F5rjmm)ipG!^jX--4e_>RdrJ04B|RUC-GB z1qm0jz=~6aS3?DC!^)W-EI_?erEhFhoPMi*VA8z2?vxXP^D7XV`Y0#d`6o+DmlYKi z!;>Wve6eX^uYdqPuY zF6JewSVsE6IY^cy6QzQ5M>fpT7~SDzH&{JlyduRZ=Hz@)|8R0etddB2@u`8d;v(}u zEz#)eM#;vgvF8@8X_#$M4|Cq*esos^oI5)Ql{a@>7&wb*?8rG1kKG@Sn2^Sn`GN|q zg8>(F=iGnA%@}MPdky|NZg9sVZ+p#yY;b^| z_Lt0H>YMmaPriFvT+Lm)7j&1_wHAoe6K*`5dLMVTyUz+iL*oo7=Q5j_VcQ9~V7-YoP3DpKgGY`u{ZO7Ei^o zkWI|FK>_)+yfHUHB&Ept6s?!?`7igFpDLzlf+$b1ZJ MYOCa2w|MqH065{IcK`qY literal 0 HcmV?d00001 diff --git a/docs/Development/IntelliJ-setup/01-04-fill-in-the-form.png b/docs/Development/IntelliJ-setup/01-04-fill-in-the-form.png new file mode 100644 index 0000000000000000000000000000000000000000..5dc700b748c98eabb26ea13969fa60569ae58398 GIT binary patch literal 15982 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%0A`B=m#4NS+rZ1(u(wMs-c4ZiLT~-OrR7pE|*lP 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 bHUbx~-QnL=wSqcuC>5kArzTq^Z5I3=N%ubd literal 0 HcmV?d00001 diff --git a/docs/Development/IntelliJ-setup/01-06-click-open.png b/docs/Development/IntelliJ-setup/01-06-click-open.png new file mode 100644 index 0000000000000000000000000000000000000000..c5b13faf6a41e40d6967a4a3a63014be2f1a7085 GIT binary patch literal 24647 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 NY8u=ty8Gz2{{b(v0k8l7 literal 0 HcmV?d00001 diff --git a/docs/Development/IntelliJ-setup/01-07-open-the-pom.png b/docs/Development/IntelliJ-setup/01-07-open-the-pom.png new file mode 100644 index 0000000000000000000000000000000000000000..4b87b8adca38b3ce18bed760e5369b9e4f7bfa67 GIT binary patch literal 35629 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 literal 0 HcmV?d00001 diff --git a/docs/Development/IntelliJ-setup/02-01-project-settings.png b/docs/Development/IntelliJ-setup/02-01-project-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..82d5c90601c57bfbaaaf80c234babb2273b0bbd2 GIT binary patch literal 65664 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 literal 0 HcmV?d00001 diff --git a/docs/Development/IntelliJ-setup/02-02-new-jdk.png b/docs/Development/IntelliJ-setup/02-02-new-jdk.png new file mode 100644 index 0000000000000000000000000000000000000000..f1ed1cbf3533e3cedbada25596ce51fc8185c7d5 GIT binary patch literal 68965 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 literal 0 HcmV?d00001 diff --git a/docs/Development/IntelliJ-setup/02-03-jdk-homedirectory.png b/docs/Development/IntelliJ-setup/02-03-jdk-homedirectory.png new file mode 100644 index 0000000000000000000000000000000000000000..5e049151d6cede6f559857db6de56d6ef255f161 GIT binary patch literal 35364 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 literal 0 HcmV?d00001 diff --git a/docs/Development/IntelliJ-setup/02-04-after-jdk-setup.png b/docs/Development/IntelliJ-setup/02-04-after-jdk-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..d3d5b6e259333211968672cee86d2f116b8fb82d GIT binary patch literal 67573 zcmce7bySq?*Y1D_D*d8>fTR-A(yfAol!A13cQ=Z3iAWBifP{2|G()E}!_YO7LwB4T zeShcs);iz$|}D8ed)?_7QyZoRnK0sq`j2Q~JCHo{%X6dWSRUF9eciQBJH4R;Cd@VseK z+d7(~dP#EEg@$0|(p&hz{r5nMz`lT)sWXq+fPfKX+7)4AAn~^?Cm)?6)5$w3EJj*Q zOiXIDi_)T`{=LRVSdEZ3G^8blafF12Z>+ih=b3-P!oy)4I4qDLg9B$_+P}^5p*jxi zjktfvpVsBl%n@-G1-i2Q+1xJ*0+VCB zkFI~Zw*s+7R7IkEP+l>QM{*RAfh4-~141b4szOhUkPvCN^LBRM7cXDFTrNj!(F+Md ztLJ>785tSjJqT-Fqi0DqDNes1GcfE=8b!y%#RZP6wQU?Ksj7Aq`yMib*>ln`Bp@V= z*$@v7!qiW^gBbv+KU>cizC0MIKNyycF@b!0b0@ZY)K~*nWqF5yFlO8ISrSK*+{G$i zlR$J;+j_}ERz`+(Gfke-nuxV~?r9R!~2;PMvIe&D7y1qi;kaGB^Uv8Rh&NkGI zIS-0m3tyfeBZI{*XaocV4z_2Y0#2*?<#1X~XYS%{$J%2CT3W~2n@&K4PnTn7(#)*b zT=fdur{l^bbg3p4o}#~pKyKf@ohV1|=;Q=_d^Z@DD3>k$vjY(p65`AUBK58h^*j~=Np{XJ>rWQLjg5^(>TSSO*SC|L#gIhj+DARp6lq$agf^@Gm)2a`Tf=YFx< z$u5WrLUm&ATTUX|XGz3%+lXT7XWf?~={G3-E_M}G=Du~K8xrIeH?{ zCu)K_CGNY1^zzAVtku&mJv=-f>%gMHt*<1O|1tMovfza#ahc%-8Q33fYAB?O;-)mo zT+RpJ=N1)V>h~pbX658KogG*lq2?4F95}VAtXnKApIMC;&?6oOwIp&Hb#-;kPnH^O z&NZcHWY8XMA*5cuL?>cUFb2=w>~k)&ffC1+*Or!+lRsUUvG6F&obLab!$H3*r)OMTbZeTu`QIIRbt&C{u0%7P5U>rbwX-d+L^+Ix{Z9j%cpvm>uS61!kd$&0erLW0ne#_En}B~!R+aL z`TY5V0jaxiTGz8`M5F;}j7ww_33)v5th=jFJKj(l5fPEkZWb06P`#@yp9wats)$q} zw`UHE5*<;GWak=vWM02sY&_pynrOOmfqGQF8wgrqwVBu%c~dpeF{|5H*gmSOvwnS> zkg)b0+1zh-?HZ1~+Fg`g<0(Bh3ESqLD48FYtMO_3c24EsqyEfWP8cav=VBgv%k=q; z`>CiMU`jdtHo$xS)Dm|)(7QgHD9~$aZ0C*)#^+{aWoxv;Y})0h4zE46V;ziQL4^g?y0d9l z>9>4PK#uu}LojuVt4AL4Su@kp4$lbgeQEf9Sm(O6xCt*m*qDfZn<@GB-Mh9gOvd=;W5Y@rjg`>bp8GbF zk%h$=thoHJ>yx9XWrXI9<-v?2f!QTYeg7Re{s}q!UA9gHg}v`{FeC+K(fa9ZVXPtL zVs8E%CLx(Rmbyd(f)<(fByK|?cjU;~rLXT!09A?k5N+d8WwAjuLcnGHe$&}laaV7z zRh7K>Stq4egjt#gX3mI=t}Z38poNp&C5(U)Q^mVQ|I>5oX0AbDacymiAcHqbO07budRW*4X4=wfU|2dQ zdcLP7X`bs$ji>!~Rg;k%1b3X7j*j+hinq1$WiOk+zE6;KbDKa(>yp$J2W?S-(RHD) ziCIFpF`eoCK&W2rhmzRxRyD<>@-+mbnnoauOiUJ>@{W#<;k`C~YHc+($1eQTz%!>A zlVaL691;f^?5wc+CH3_5q~XbcImyz}V2O{y!KL0i%c!ZTQP^r8KTQOmd-i>O5kMO| zok#c_{8yy%$nuVPekTAJU6uMrn{XE0`os?(&~Jdju`~Y-cu0^7CPQCrEX)CaJKAWK zo8p35TU+A*PfM`Xx{{-Fcj(>x4R+wvkPkc0eZoI2hB=K4#DhPri0&qS|7o>RximK~ z25ni;5N#(g(^C+yw&O`{!Tki41835kH`3BhRFa0jx(O$LS`owl{CGwXu#H8)@+j*xZ{x*+qI2JuWLZQ~||9&r9I2#{EO@@txP{IB9cW!3F@>vX0$~qgJ zI$L$d=bxBI)Y7!%ibqGg%$nvDza#o*ea_)E7DO%jxmcN2Gp)CyIgEGot8C_4q?pwX z|4tSyZNKwRnr6tN;TC^p0cnuK-ItaX>@eyM3z+q@w+nf6-ZIDZY8egZNBap)jf##+ z|0s&=@^j@V!q%M2EDw{z2hMZEu1)wP82`+Jqs!VmaeuV7&M5~$Yk)dh7i7JxYyk?w zxs?Nh(8&cN@;m%D-pN=n1xK558BD87PICwk6a4zs;5V|$3W7#VOxghGofG#t<4CrG zN!O>AwvM*>U3Y%(WybVkz37Pzt1=tdFaO+J#_Sp1xNqW^MC3%WCMLIq+)xJl@r5x? zB2S-&Y30b#Gg~n_u#uRB%c?iHvewq-oFbY|pXD(zL8iG4g!$x273FPMuMbDktJSlE zE{o@CLJumq&knXDC$u`v^a{+7MKjMw(g=a&goV9!-JELA?$@F{t>5Xln|J0vF}%b) zJKU1>^b}lquu`B~9z_#?#pyXJ4Z))n>JdU7X~?B<$0*m<*6*!b&3MY) z09J`CGv^wbW)7Tx3~eOfg&%7xM9|pocQhMR8`gZCUaDKhC2$P1T&lsRif@O?&MjCw zaGI8fB8mM%N59+`JvS7A^4b}!My$yt35PcNaGk=K{Ve0-;MS{23?wliE|QS*mKiM3 zul6VPry$H?2&e|Zd>ZgVJ{q9j&rLjZ(7(uIGmVG|8UCXVoI7rn*Ud<-yJ7kFin?o+Z3gjcqU^q5&E$(;7MHan;3)833kGb@xU~@~^o(#sT65a~{w+@+f zI(r~!mhXM~@};Y!;2UI9%sm%=W+UY4SXD#n|Kb+b=j6{X`6*0c9rErCy1drY7|+vZ z8@e}R;nwGWt|q_cAYBeWB;WCDutqfA^50$RFsw$9a@$ikc#ess>NOfZ85`sI5+A?2 z%aE@74(_@=6$$c$^GSz!?M$tg-m%;Z_KACY_oC9f(5S6`|1v$@gx5tUCzIW~caIif zJyVtXW8Ly~Zx2NJwcMt?2byZJPG{5geDFFh@SngL;aXHn`NNGr&6gQfCf*JDT2U6H z&mqBhzm+3G`uGx4$4AqS7YbcABE<|cAh;KJ2SfaR!o;Wh$YxWX*!i%G)}<$;N`wAZ zGr2Kzq&$uo@1OgJ^}N2Z=OB4#^7noBAGA%K?d@pu`=`6-mymaAe7dL)Bl6YJ;ZA6W z$c6Chlb5kB^s%A*YTAz@=TJG>*{h4#ZVD?e8I&~-v3gl`g$#OwjYcM~1*X1<|qlZjnDztd{4m|>GRFypU187Ict z4POp5Rr~moX83aoD;^I=5)QaZfVYJI(SG8D-BQnSOVD=_0|5C$#nBwO4%=#{-Y?yd zWhBWbuQO+C#IiluZTVw81rJ9`5jHj2Y+$xpGV#UkcOUm9l|!aII)yVbQykQZz)qcJ)IqBM? ztml6XC2hhs+W!rsTvA<>gxY=L@X(K>%yDDyr6(>iahu!@L(TaqY~#%yOP7b%m}3)% z-}R%G0JnY$x-HH8p@qQD`;h0xL%yYxwC`P(sS%;%wV7>2Ztzv`BlUq9MEhyBj%wM_ zSalK5M^H(*xbQG2uIzQ^q`w-jFI(oZO?{SRprPS?X{DE$Rq;_Je?oE(UT?x|(csBx z-we50Y4=+^2_>pO>d?}8+W8F@JXv3fpD7i!EITOt%GQ>xGb&www2M%ur?~t-b|1@W zH(PHX*>stJ#a++SY{^N86IhZ$KBA|t>&enM(Bk#cz7G`yP2c}IWRme0@KMMg3BCO-s^wJVAHYae3b zlSz1LlCd{j2-WM_{qJdcHS!BmyZ${p>y5g4mgHZ=5`zag9qq$SSMvAIzvjUHVx!up z=H~v|W0dtMQA}J4NOR&)=46Elv!3;&|Hzv0_`%u%Y9hg&zb`bF;q+u=FgHK{)_f8} z+PwR~T~9OPU(7slDpnJp6A}hNFGq6R?1WWRHfPI&HMmT7HWGZrL44+gM&>DJa~OB& zZ}O6RoZc?DJTZDYvz=Ce{&yV|-hy6@&HA83Ob3%g-?yy0Y|!?i8a5Oze8v2<%P?RZ z4#@i2X_rpntBZtVS_{c(cRIhzdGUMqumKe!+&KSs+7`d*@{+0qVLdJ4=Ns!YBVb1f z!Yv=*g{GziZ9$AZ->!v(XRGkFh8qpo$WF39Q^zVA2}5j9*{RyQu(zu zqumOLii*lYRC@89-o6dO8nFDe)R>l^ilX1Ds^WpW7S(DJ{mjeD8xIgesbfFcQ|6fy zZ&A_HO{aermtP*0359tLpd8^(`yZxb%T!w~Hv^&k29X!dm?$m-D0 z7Uce0Wi5m?xql5m3FI18i%i}cTycD>tu)*a`0pA?j1o{!P(*hIbQ`xmAP+|<`;}8s zkzjE*oZ=ISHp#0z;DzGmzz8jH%qKCMbu{L3cjnj|By4PEp0Ee}9q;#yZ)lGFdAS9T z*~QY~<);5UdL(ac%~Cy>(h{1IB5mhyig@?#lir)If`?r8{1jkfIJd^N=r(33@!_3O zoPOKd{}BCCqRx_)^WEWE#Lb8o%C0u_FQ84n8ROa|egc=#6nC1(-d!AOBYe<&DIt{l=KQUa%U@JtHAeb|0IwmJ+Qgfd@Fe>amb&A{ISRTz#bngmAExZHUB2ZBHD@w^TrEo zb@veslVxUPWTCl_S6%AT2|^0w!guZ!a!mi^m+#}Yb|vSk2%?;+#%~qseFqyO;U_if z)~eXw>=U0;OTRLC(5bkpcOa^z<;&E=`iPH(IozI6Vjxest-UtU7x4~{QgnL6AoS0e zobwgWoQKiO0|yQQ+u*+ZP#j)YjYAsoapyg`#O;O z4&3LY>3p7nU;n(dtMY5F5qh5~mkg5yMgUsa!2J>Sa=z|RhnOJ$AlvQRub#9?2!*%` zc6GCUEtnQJMZNoH(XbR=aB|`Mx_4%o1Su~hhc3*BLE+{u zI=ik6KU1=|uH*~~5A;bf`Al)CryjmCc+2U9Pti)a^3|_NZaU3U^wOJ%s3Ve^=Sfm* z&fcWkJV#KsgrSmU`5tgGA6lr1!vlxtg5T;cv~1;Nx9Yso(YYkLh-w?15|t0i1Cv5Q z_NxZ3dTUbFIwID=NRlJh57Xp~J*tV?AzLyzirNLj37QLndeOOok^V%h*hL{i(b zZdUf=CFjvW_D^c|#A@2KIr|%41Z^Gsp`i}E2+@`Y0V9O3_$lc5itm7n9>}8+<|k<3 zaJdi6^v~>C7B-0_I@^pA3n@h6Z$0-md=~Up?_GKOOQqME;gJwrU>1`66dl&jVp2#u zn|&WfkPCdFxpl7Z%#l5Q1c$3b6joM^s;2Uagsw5>Y%Ax(92P@7@1-8Y-bwQ5gLGKML&lJx0?tzH%>R>?HNWTf^YvY9Yi??@k_LBLg|MVNc z-NIg6G~C$RhNOvdrl6!@&z}MP&LxMm+qSaWfRt#mB=)bKK{cSPWsL&}Z`!6Ho0s1R zs0zt%-kyr?Hnea_PxUGNV8Crh{eIsCc=b~CK8ygGjLjo@ivm^hbNokUbMNVD42`7a zWej(|UL7bX(iE3Ges;Pv_EirRk%sr;PYhW8;WiX|+f_0!C+x=$OGCqqwbXdImL#R& zk=Tt1m7lxsRE$a|ldYAvi(0qxKm>!;lxB7_GjH1>kaLeCsXy!cW5=h(zQ5n2rsXc97=?lixa&OZte1D*1D63)RtcCuGVo^mV*2?} zKMzERQxjP8Wmg@+lhmgck4E39=!nZz#75|L-%n$GLdRMwlWd+7@Z!=-*}3X|bxzo8 zMIOle=Q}?vF@6d8{mJPies;c6B%aJmO)9jW`U!2hb^dvrIc|2X+ug@E3YkKSG^QL# ztT9EXv#M5E&-FL^CV^=9IX?O0ATqPQwf`GkCr4k^8Mn|8$5%V5;Sn!RTW8|RN?klE z5yEbUV`ntZA-sQx7L!uGJP)i33y@N%A~lnj$Mt!pRHDh6C{GXUxc$j@iv9r_>oOO{ ze+N>a7#?UNbo6X7=v!9KlbA2RY1x}XMwchqM?}8xNAo!+*m5So3ua_xMw+|g zk*b=1uYUh`KWRh%jiQh2@N*g2pOEetMLSC9exLYUHeCKuoW{^_sYFVEwiot|fQLur z#9J5aCi>XBP}X~9&T?7U7aut_CJR*YC&orWSaT0j^hs@Kiy8Ih$rJX=hfh0#~;y zfOvna#081=;Tt91?|TU`@ezZ_oK~GcxV7nyKhC0|5q}(1QP+SeXe~nzXNVP`tv_*M zc;ArXs8@*ltH?u(?i*K{T@`QCskpe0MXNaR-6Urpn{-qx7R_gbQw|ew4x2N6AV#f7 zbUJSp-u?3hw2oUpGq%g3k;`RvbG~w>TwvPXz8y%zTMIKIB|f4O4jV)U*ptS><=%Y} zkA(bvW;lcS2Tk*W{i6Nv)g~@EdS$2+&)@Y7;WkCfr8p@Tc@(U@^!c^#=m^c<3wP{I z`W}Na;-8if_&?K<|BV9uZyaV9&j`WJ#nzY{#r(!!dKFl7k$HUDLd4#`}6Zt8(aL1*S;(s zE5wc5l|iIlH94(+lO@7hwgc&C|BiaJ0Q}?0q-R~SP0J9hy+Us%+Xqib9#G3A!UBnB=eE9fm_ZK3Z&guLmp#e3VrrcE$8@$!Q1S?mzyvo| z+0YcOfVDD~O}`;^06w`}j;x3~K9<<+`lxyK7(>I}i51(n=t*_oaA5=PU)(C;sT9qJ zQL&nhrmH(c+MlPywZL)F^lOrgRDA zEuKWK*4`0i%aitG^Hx%ply~7H#H()UQcFc#q^WB?x+3KN(E^y+TYj406_SY>`{lpU z!q>FxVwjPsaWs3riD+s9i2YBiW$~=1`MS_u&j+dfj)cKK*&vNWANpq{65LWozvv7u zt+Coj|J_vG+fu!%N=W{oRe8esqj)`6U&yR)<8{mF0o8WZegKwtR(oLXA^CLT0#b^q z{f(wdlkdL6%z>Uv9T4PrL;k06UFw>ifX z)vW!|U!f2j@NguD6OhcnkKYY!9UULHzi7KVd%Z*V)j|ZoV{cKa6~G({B=@hfYVCS9 z#9WXKZ_iYxH(eb1Ksurx!e-nCIyv;WI@3!Gma=8vIsP4_=7G9+^C*pOHs0&_d{&_4BygSO%FU0xtihfrQb;OOZd+B^9 zc#xVRsLayIjaY{5eGqhNIqPwn=c0qDLV6I$RmawONK%g#{jPd#LrONb8<1>4cn@1U z(|tQYKkWrmaVR+auP)DcWb54Kd}pui1!O`)`%*X~*Q(`B4!UA4cE>;^@P}rfUr$zO zU!xE40tbs;>E|sWSKW2mKR%^P*YBk!+b&F8&iv}1>Ams}+wjGhgToSLs#MwwEXOpWH@JbaconMf*3i60zIv9f=-vFqt0Uu8o| zM)2T4dj%rr*81HCX5{u_-o z(}``7&8dc*c0r(yNMU7L-4bJ8%GdLVYHO1Kj5JIL#by=nx_V$C2P!}SwxSxYY0mK} zLJFWx=PdM!$z6HV+PWTxmErtP|2QN^p7q^tXpi1%nk-6$24Z!L?G3nMnG#L&RmF=3 z#+$;>Ax|ykD7yH{=U2%@CzrlvbWpRhwG}M5)K_)OyowABxJ^vikQXn8vR^DJOs8vB zR%@EpF{7mu{n+jQiR^X}g-yjTod2&x`_3=G=}Ax%G+XnQ?+i)tJ039B=IrzPUSOE4 ztk|fEGu~g^iOIYn_Y1y>b3pqs0$LuP`eP!YD;D{)A( z6?y8kNOrtke<3!{4Es{! zyVEvflLHMr^8q5N5K#f^r5NaXjf?aXj}=d=<~V95rWjBUK%4a2c~gFfaNS%6We7+B zCf;M7CxL*{35tXQWrOu(O(>KhB1aK5YkbuG`1oLr9)VBB|7mpe9avkvf;s;=lo{ga zQN%-$Fu=8!ZO1D9o~Wh*WP9|)4cX4a!}CsUVh51zu6c6#6GcW>^|k7Ssy8eRoozj# zzt8zbuz1B7SNl>P)sZ1DG&HOX=rV{j%qqwA#!pXgr*sLx7k$-=btb+i{()i3>l1Xi zXzYHoN0En$YVF)wt3_PyK2E)McOV=O4L&UF!yxMyI}2%yAhmWhdXYM(4?KUbCMmB^ zSzGIMb%qCy<7p(Bz=yYVQUG-1<`x)(-dj(OdbiefEG;iBE<$!rFHSCfAwTlTi``y~ zxcZ-&jLxYyokX*9YnVMt3UO+GH4Lr;*+PxFklWVkNhn1Sey<=O-*deJRc1P=2z^xJ zEefI8v)GHtN%gP3HP3ZYtGj=KngU>U&W=%?fJklwMiM^yFZrS}Lid%m^exTj4Dm0j zJn8t)i)8Q=!2S=rUY1Ihlas)hbx*9}&W zy>80WxGblH?IsUI9!V0hU%=*;oCQ6QGbR+m&NcrVIV=N!n(t@sFRhl9^?1pi|8DR? zAG}=T(Z7kXiPIQ9F2P!X4BuQ^3cOl*t;qN?C=%4h4xOd{1&fh?34OgSG)D_i4_BN1 z^)Fex5gCN}FYMGd`wt?fb$rB$Pbqxjb@gfL*7C69RR8GcxQmvro`)@`ZgF&Z^6z35 zPEJm5Bx24GSl!9xmFoz!-*mg-oNc@5m{KyTCgvufI^%?=`y|_whJ}R**7tu8$)sb8 zsAMjcbxIY%S3%wMEFr$%a>)3-qmAap=E&w`EmWt@?bw+ybvV|+4END%RD`qdQSWuf z#-Z-frJjYu!N>f=`kP6N$?xTvAkeQyBnmm*W1g^SMp|93jf{3Lb@ekZkFK@$5~Mn} zHC^t97Zza$!@RG>d`E35o6cd(I6yTC7|q^gQi|#j|RLZ z29Q&|QBirdy(U~`JtO&I;dI}8sP*=jFJH(8()z1ar5`_j{N&jV zj{^zh#fJ~Fp8J>6i)QdL7a^~s?X4mHqIWYM7eY!CH$!#w`MFAlzPx^n5<4+iK9-{N z^ap&EsQT5A3E~>m`#-0r58C-1V*%lmkEa>1OQw__9Wt46lMP?FuaV-mv(ggN0H4lCE|b^j7p=_5^$c& zkp4P##rjivdd2#OIb6xD881O!#0?DgIlDNAhCtzLXCOUc!L<&J-JeH`U(Jv6^qbEJq@4{W*4~ z^njHwM7VKl*pB=jn0wN&3v}>~K)7bR4Y7@>1Ay_CtOIL7j z*CE+%e)h2MC5xchu}1wiX8@F{!My*~N0|NQiGymPhWJ*k2g41ZE-R0YNrDH1$3)c| zKU`wrRV2DunXDBQ3s)y*!aILY@Wtxp<$Xy=*dOW{1JKHfCp#rpz$SjFb!l##GvJK! z4Oh0jk^1rT9$^uG5jv6HF{mX@Ro?!P_XD z4||M<=Sy51kOgSLOB}dfnGFCz(dOF0?B)4GAyaMupMjG^2Qt3agfx#ZpYx+~)0xiH zpKF_P(-3?SdodoKv5VvD#*Z_m$QG<^tFo&rFI0;)?|#qciQK|M1AuUa&vpVX=1|wo zede#d(yWhaw5kc9GK(W!Wvp2PSZV!}k*H~Qe$2ab? z1-=TDBNa>Qq|$0GzC!xt<>gs$X5Un7Z3W1zHWCA2_SX%^i}n(`XIio#9jMt2TEO`| z*Cg&TqkmGua`O$}O-!aa-S8BJuSmhdE%IF-Kf?=-H3PnU#{kCf{+r|1RjD~jc(b5t zotm@MY2uF)9o;CMxShhp)^1w|JG zB<0QLDHH9nHCy}`)0Zw1_fXl6mX7s%_mLCO>mm>0 z@aiyL9c+vXC6ZjUPq9lR{Z-6Ytq5&Pb+TI;moW-;YS(twk)E)IS4Cc2pkG`;5LGB@ z!6_MQx&0+VoS-{_FF_py=!|c?oX)T_48*?*`DKg(fQ-D*%e1l z7Ar<77CnVmuWo}N!Lu(`cbrGO6tF<|nijgWXS&e}TxHdTNSF3*lkPQ&8d zg;i>1Y*mXi`+<3fSUA^L+s?JDjd*O0JGsT1Afi%JgkA0xPjueESodl9fw&G%mkJ8p zDIrq_2ez^xyoF+vpR|L+T^AP@5J18+;qcU_u+>5YBg@lPg{PSgxnsASA#G6ma?Jf91o)SZqJSKo?oI_j-Lj zS%HApcQX>>)(cP%d<5gTSvn&rT%+D7ABY^O*DV)astr*@1l-V|HF1ID!9>kl7-y24 z^<+JElUVbwmdhHC={urxC8@y8@ZpiSb+Fdu*{cyE!@jwBvOk^aXYh%YE&lOCaL)^- z>=pj~lu769+m`f%%RqtZ!L~JLM*z8a?chC0s=6Y-kK9kpSDt=t+&BcX5|5J>ERcRp z2zac&%ipchE`@*`UNU}bwj9g<59BCrk}+V=4INLaF)}g&Dh+_&CF0}a5~O?;L!xiZ zQ#D?Bm)k5YX%{vIf$a}V^z6O=`Sa(JG%J6r-)FwHHHJEX26#H?ayxk6Bs|>d6}^si zedCja43?FZ2l8TS?+D1r$xo;Gv!miOUwCyE3|KKx-~k&Rvi33K7BvIg4Ah^yt**cy z<4zMQUM3}ptTt)Esbjj2DEdXm^Q^RPCVu_!xAFTNN9;r%8(ORa)wARN`S}>4#EAWk z%v8M3L`xdyaMSex`JyV+Jg2E#Lnve2qgc3v;Y*7Aahv9i{eZ#2Z*&53jULVkiy-BQ z9cNA&qq2+GM9nWO^+A1@OG`O1(9r;+_})qJTMzT}7quGppOnkPLdiZ6z<+b#kUA{g zN%L5j@jVOIs}N#Jo!Bq}h(<1rKc=rw)P@%$f{YU#9ICL;(1=}ORx1W&+}%;4=>0RO ztdPq-dc0=B`+QSE!J%lRPZTZ=&7Z^%oJcrjff)uVPZ(TpM z==;z;hn?ZgE?2a9*xqSQNm8ysMy*Xdbvkc18VRa~-wuW;$lE+ZxKS+!8QiM1>2dFf zqR9S+ruH7Fw>X?SoI!eUQy=N4XCdYa8oD?DfMahm6QAp=pC71rlpwZ%dyWFAU~eBg z`8yx9|1rZ8POot}kUeyVspM;ICSUF5q?ZCMN1FQuC*}caZp$GdF2PBVJsJpz7MulT zq(hKL54mVJn3-oN1Ux@_p=!gkvLpaJYv3C8I9R~qg!cFE-Qr01y0>{aPIw37_lQt{ z3=spbj4}`+PjVWCgoV+HvPB`x2F#iALvFOip5G0hxm+kJdU)Ih8pI?qJ2rrEfoN#8 zKUMswbcwa`LutOr+D&#mRBWdQ0=hE2{9{n_7Y~khmK6XFDK5@7%GAthaKYW#z=-an3b)8^IP`@b~Tn)@n)*5;$=I z_ht8T^4uvw_8^1#kTPX2+pgyo<-Qj#Hiaay@Q-lo4%>1`vRf%p*+jUQ0W^K9``336 zHmaJ+T3S-1>b0&u`HHavDXc4fa3!3tiYV@EISSai3;Eq3&Gfsvp5xuhInv<#!)_pU zNMz`@g8CFOb}#(&;#1~}X-a&A+vZ|CQ!G$HL>VM9GP5rgQ+X34tx&9+JxzDtm%ria zfBfK&^Gcq(-c`JRpP09|yyXn|4YGPU-Lr_9V6b=yXiHP@mPZ}IMh-xF|N zzLVtRDK!jaBt#&uu)SK?7zEB`plVBqlwbdOzR6UH1JUuc4+GyE_xsk23}Uk~yKbT0 ztJZ0F`3V9^eCvkI1Yi~M%|=KwzG$-4ilp3BAxF*fDd2AEYY(=%B;L=wjXH{gmmSZV zfmBzx)E-SWKk^n9e%aYH>1omUjVX!WM2W$qs`ans{|0!j8}W3}uHNjCzYJewMn)@a zVRN{RXd0NX0E}1h*G*ArB}0mCnY+uY50tbvzPouo&6L71S^0^JfzCQ7C)4mp zk`+$g%i{Rtw8ts@2Ho9Al&gf(wJun+s%(u0XMJYkb8e7}v=OMI3?APxA3U1DddTU` zF=ChcJdwjJyqDkpNjU~1tn)Ydrf{F{SkSo{bT;aF@l==V+3kkBMv zD#@pL&W6te&Whc%gL=~72D3S`za<@zStk6q^2lmS0G8h%m6Dggwx&W|Qu5T#G`yvZ z_O`a2R6D(p0H+W+%)t3iUSEs#>bpU!6iJ)+x19q{w_NnOJfAU|m z!6RqJt)H4v4qg-UV@gpSKhK5NEFWphBdr294(;~FGRix_nLIXp=$IoovG81it*L!b zv_C4f4i3~t5DoF*VIjKI^vk^_K&-Q7$K)d_9V4`!b{e<$awGnsJCH{f!VefA@Klkt5$O_#&Alcxq&8y*%H+%f#iQ{?S!-M|5Hc+HaNFO}I zB^QYEBJ+QRO~PS5{~Q7Wu!@Sxa@v4V@3;-HTFE^Pkm^2p^1VR20TYrx)(XQHeGXJY z#Z9$efPJ%dx;LzC8%--~f(~WVx$OK`O4|?-(;c^ISwH(qB5MPX1!^oXe+Q$^5~m5< zj_61ELH?A+(P6D9d}l3RkgvV=^%ZTpoS^~|-h;K&7FOkK`=@RekNe{b5$d&`xInt~ z@dN{$qJiDy6>hUS9Sd|)(3Lf3K@Ff>KDiLp^>DeXTU?A-Y@l>L=Gtw7=@OY|gt76mX}lvva?#^)}c-dlUGg)TB6-iE3UB zykB@I@RnTb;I&qKKo>{~p8!V``alA2e6%Jm=}88JT?OiuKgK4}l$1VutmZNNbdCUr zguIZBTBU9Wq;-ZywHpJc2mPA?ol6D)x8Gz>*N5eLZY^ z*>aAe1KBcA`Gw#70LGxI1ShTTr!ln|C+teTq+^FFeFYv<5o;m|j) zSPf{zG;Yjk{KUZs4rGrl`XPX9;P9PRS1r(-S>=TVo0J};x-qdbFGbZlxi9W^@`I^w zI&DgD;7<|WU#1PhC+`4ad^C4}xdE+wvAQy6KuQl-1Y5v&=>necs{243e_Byqp6B|q ziQS+Y?y>X!ob89)@thYvph5xbC~0GZ3yw*w03vny+V>iZY-R%ea0Y2MnB1KnU$0>B zmheWeyVyYvy;yP|IdNd|LF#}Flf>aG94%JmuXj0kt#mV!jW=(kh+@?Bvtj~y8{aHP z3fkql6Sq*E4250&AukwsI|ojDu{vB5E*P)pB99BsvdPtLsREI`+)xr12Kpl}&sw)@ zUr@u7S2Cmpd<`TkJNt%KGMsC?EYLW1h{gOc&~e(KWp@jRoEkh27Z>Y&44YpJro1e zfpFhx9MHozl8j#50(Fsu1Vw&|RWK8l<#>WlxK*lAQKS8V4m!CFMq{(JBeRD?N&7zv z_nep28_yGtK@Mr{VE$ao!THELF#p`n(a`YRFM!-?gW`AB+&;EWkJb0gqK~8?i5vd4 zfxaAepPXDAXl1eRAEE1aMy29V@q&`f)T%4A+BUN!_fGm?XIjH~3Ea9sTj!b^l+_}L z1Q=u#6|tykXwrOr&da>pUkj%_1v*@2wL;Ub`4`~)@~6a4&w;hxR0`ME$$ENv0a}S= zOX&W;C%xqdV3Pqg=me6E;La!Dm6IzslC$7dMhpy!sjVA_rQqC$FeM+@d6jj=KoSaK z!p_98SXNEV$Dcn1T{ox4HnSTt1!MKVL@CPZb}r>DUY&0*r0bs04~kyi|MMJGHR6^( zY$1fDS9-A7vb4l&aMqO^O_0otpCUUq7i{q=F6XXK^cz*y2uWXGG+#e|5b6jXZ9V3t z&mQw}JtTFZKROy%De9P2H++enIjpFxf-Gp<^S|uZJvRYPL z1S|@-Z67HpC0t*m`dOD2%!P#C^z`Pt_`S^&NU{)AqUHHriTIFziIq{e71p`xN~dt7me9TOA7 zS$t40N@YjoNh0l$G$^`{BX+hcTTxZn@0kWOzyj_#%1+2@n*ySDJ%xR7@;!Z+ ztFuf$Z&ACcDD2X0n@&i`tA5C$fc%=Y${)!L_Ox|fN+`EO zW2vHXH6m1k76V;P3}3l7J}Q(m`$vF1zhi%Ti}OYYm+bM8^Wr87mA*eK15R;oSISfi zWAkWHxO$bv(9E2>^z~n)PXC(xvM1!8QM>)#xp6S(=TLgw3RwWb%a9EtI3ET@jS~ZF zVXCupcXCrT_N(_cTGe}ee#f^kF)=%<)r!RB4MA^wAgCcOSC>0$GJmW|1 z`gbn1f1Svj*eKOxT@tfS8R(O-Xfxz=7Th|SQ_VLSzez=Y+?zX{O(#s;p%zLA>Yyhh zgTA>xSz|!gbb464JA?A`^QJ8+U;@Y6xbp1G<&xASgKoWgYj0}GHWl3-m`#Ew#joO% zEOfN_t)jwsAjrR;oNw{a^C1~!TE*qBh2szjTJOvJ!Tn)Hy|km9G=&u2{5W=RP$awR z$=9~9dGb$aiP?k#I0?^DyeC)r%ZfXFQhBB~9Ay6n9Hc-Wy%dRteJU*Hy^!_;uPkNY zOM1(@;X-JwU~E;r1+-GVAiWRb%{;o8u2aVXS%kWkJUi+ph}x za%_v#a9-Y_iH|nnZQCpx}%rJBAGCLF{NO=?3CUW|Emv)DLO?DAQi%BXd;7Ml2Y$2)?HZaqR zPP33Dh||4e9ERZWerNV%6b-*PASt$&yRwE z7T-)NYHtkL)CWjHkIuYDKFkmCfb7&;Yu;GV zGA{Ui426MhKPO$B8VubphNMoWu=SGE8o??QML$_1EODDK1*|CvJC8i#c&(I6mSAXj zg*h8h>LU~>KZ1&M7jB6g=XJH6v&ga&YjSIHJ%vnk*}ZkHFJ}D zWGAK1N4v|qxrga!wKX;7-q+_;zHPD5Vgo3jbFG?bHU*XRY{&L6jeW1nNWNODYoz;C zEwTh&aDb1qvt}k0+PO3mWn*KN`Sq(mvT2XP6&h)f5U!@{lPbNuUgn--Wm$uot?M4_ zHJV&m%wfOwzOb?RQLpQBiF*S{8qea8zcz43$HyG_BX^6-W%xvj2c-_z$hN25u-ab* zGKK{iOmH+^j1_m7l?N^3AiP{rw3N|H$LAJSsCg>C6L#n0gFZ5tnN&1ksKg0a&XqVd zz07Y8XFl9oDMRL8j>S$s4T@dfH;qk7o26tMn^4wNs-G8pZ&fF`)`6FYT4~?u0aJ65 ziO(R0?+Xe9W=w2sw3LdCLn$aEn$pCJAA|aW#F@6MVIxbsS@mwj`}kCArd?k%`Yi-8x~{@}dZDbimjfJ&QZVhu z#8%Vt-Sg`Ot)*V9d*J(wtWB5ppmaBuXo1@NR=BjTB?j$pMIPD045~T*x#Re)>b-%R zMj*lZrFS20+wc&?f?jvK)8c&lDyk8l)Wc9F^k=@kf6_aEvT6AY;;7>{6+4i*$%wSAI9k?5zokSJd zwVyXNz2_RV3ktQ?OFj~S9oi<$S{LM>^zI9>!(QL3dR?&KC~lr}I@8b8fOIieehF$Wl`k zfx2qD1LpK$|0%~fdPwB7){OEt~G5jyKzB;U`u6Y{-=?3Xi5RmTfM!J#ikP_(z zDe3MwG)i}ON|$tZH;0DvZJy_Sf7e%k=%vIud#^QX;+~m%h=>juxg5{^#{oAHxb(o& z+c@bNCNG5#w{f#MBFC0PE&_bD4l#OdsW4D3!{_u34w(Zntbm%9rwiZb_yROBU~gER zRoP)--^3^XMuZhhkqegbRa8?;88{qbwb8h`v+3_qCH-5J#TMIV^Jcxv^HzSzf|Kar z=vLLcJ_@X!h=1+j)sCsX$Xg)g_qx@%%u1N7sK&CWTz zR46sp6;t+q)<$p$$ZzmF!J=cL`#?>HovAQfJft#0h27>Iz(!g!Lluq_*(XD(#>#eF zs;kUf@$@5y8TgjXQY%{e%nyyc_iR05{S&HoHT+>BZZ!&L>ul*)VydJ-_bwYd0KVqPKZ+&>W6Zcnr`{q3(M{T9a zFz+>-0BLk|)+%&HYLt7QrsE5iwT4Ee#U7QMB#izVLgc6|B^F|E@UkiQ@$pI(LHyuP zU&_vd8s}c)@P?%7|9Lk78hCV4HJHhC<3PX0P&2-3ln8}8n*ThMvDN%Yf4WH&)O$He ziZMBO&Jt&@H`4EW%Ra?QV}wdxxOq1&vsIkF@ypGgBv9h;U=M>+ZxRT$_*DE^R9^T) zFk!eKt>g6T2^st3zZYJ{K|;Pfck{kRt&NXCBnZ-S(a=bkTwZLav9e+(q&H{dSy1TS zK=3#Gw#X8eo1JoODW7UNiZ($oLBrKo%QZ)zL#1SlX>25P5rfBZtjLM*&)Mh85WeY^ z@3LfG|L&=z?)+N}Q&iU?RQwI(4??VT;M8V!DiOXDRqZbQHNjiUNR8{LHxmSfWUh2! z14>&W#;YQ^B6!N0T%p;>9D-N0T>r?Es*IRlqm?v;A4Ws6G<%qS1;_!EJCnm^VLYNI z^I`jqCJbCA?x=00hpgx%){GYOZW-mQcI51#cw|KW42B^)p#v`)OS&AFgx+sj@ei2% z`b>f#-`_o$z~XiUQy9RRZ|>$B9Bm8Vom4SGQkmSVne1HETaP{QSD|v^22swxwEaxNZ95O^4W5ycL`44fhWU)|cpY zQOS^{Bi*bcSZe~1=Q*iM>7Sm$$juY$&3Vs|->0eWnf879!d31+A2#mxlbCIbgmP^4 zl~+f~UERS6c1u!{d(?kvkD4m}^bJ^e{)8TzajncVbsFTU138iz7#o)7qok^pYKu4| zy~~Ru-zLj#`yHxa;I+H;j-rWfuCkaMyM{`#b4K3Vo3C8@5WcF`L{%=qc4aVa1VK|F zGvEEN_mJ%r)TV%SD(JyM^vOYgW9oeLqVw1HbWT`i2?{%LV837Ctn&7aHCkNB> zmf`{8moE;tY+e^lkN6KSe7dO{w+#$j@bM+@=1y2w?pZg#^7lVR3g2j-t|WZTPe^*2 zA<{BBE-ZZDZ%^?reRs4_>;BO{4^__BT%$74)=<^%=deZeG#Q6#tqY6}S1-55v_$b< zo|8#Uf|_#N`8vqVP^S2K`W~NDV?HK*u)s(&Z4ZHkh-fM6g|#5rr~cWK^ zou%Q}*Vog3Cx#4Zpi{n7S#za|p$4*s4OZ!#>k58OH{!7VTvK_V-u?1^ktqG(y3d@3 zi6$6jNJCxaxYn35!oD#ixR9|`*;(a5L<-T}g+jd5i9^58-ECJNFT5vkw6JGsCWsFQ z{s)Q^DhAD9zq#Lr)i}3HSeUKblJ&ujlt0bM=nBpmWB$ZcUt;Gq@vdxDLUj(ec+Xp= zcP+BJZN8j>9b{s$FvyI!@8#NGX7+y5Fo7J_g2M6K!C;w$+hkFDX2R8 z=LmGZ{^0!i`HP^6%IXX1r>A9BgBpGHcu4xK`xOZxvxcT>gH+?)uF#e89(@%Hr)keX zs-|z}jL4TkUdHBgF&}lZO#ACsy`@D7=$77G!orP9D84!SqS6iS6(c%27qyN3M(KF_ z!Yx#M6pYXjFjRW>-V^mT6peO7J+X%UeCOhX%-F32BKVfIwm3snXUVPMn~sQ}hAUTF z&fh)7L|y@bw@3Ar+S6S@HOc3a&fLRuH4Y}9iWC}HSRW!{^-{#1`;gh)dU{9Aj#tdg z*KIf~yd19UZHBIDSneKD^uJSlB;r&iwcpVNyG!Em6{sIAv@`ZkUbSrvSnTjM>3U}M zW4_Zl@Tjk#v)H$!I6ay@;`y`QiSV&0$P~?KGxBJVcBpUvz{Ij}xTwL1OY+=}x)vki z?!2F_a)BMAv7eDVEHTVk0yb@o8Gp3ExczYtBUD@&6)vKZ1{J!Z>*l%bszM zz0?;umSW9{x$hMwfT(Zc)>EQFd%D_EF;QDj)xUHZMS*yI^{(2i*U+?=S}f1xt_C77 zu5gR4{-hJ%Z`M5ZH{9ewIVi%|LrT-S`tC+c`Vk)=ERX58$ByL^+M)mMLkR?wXz8Vh z$mYSTSE;HQn(u#xT!$Q|j)6SnimxNw6L#U6zSqKN;*<9q%F;c9bR_)2U$^`#N>b6r z_r)sH$jhmI4s20kv52k;9u|S4@Xf~ZXxN08subdiTAHTjCAv;UFC*KWBB%}yYznQf z-!Dzigt$sTqro&5wWUT+DPJLyK>fY&f$$%C#=&OQdn|I=8V|$Pf4p*l>07$!9AsbRa*qbA>wC44Q zkuy<;{z3E0JllozvlTDx?KcZ`l8#n(Rt9;f>o002tHV)4{KUaDSy@z3J|1Bj25)S4 zyq|7J$(SJ5lw!hTr%V@#B+5~(*R(}#!mIi|M~G}>FVj-B`XM<&$ae5r+gCWUh@NNd z0pK7P{DY#4O^l&C9?p89EB*p!$Osct(3j^<&G}^HTM^FEkt*3Lmy_BjqXXJDK%nL_hj?^H~o812L*4X!!b+71oF%lz` z`JvFMp5W>oy?bKmr{O-M$-KF*aYEGsF#G-ph;SZozqAd6HEKdOB1Dm#0(1#~7P!Bt zeVSrRXW245X@@Dzbjtc1jfiiHfDlB*9}C~os8v+(eTT~BNWI&K%R4jk`p$x;W-?F) z6)WY=3m*lu*KZIZf{G|oZhsU*5ko3qx0&(h*Bq}S+^_)o41V5DX}Rb;%XjGw_T&zi zZv<3{$c-J0EhiPUA9O<$;H&QH1m4lks&L{2S^gqT*?1^e1%-8GONGK}jbN@66)V>G z=*`?=_&e^6PtVtKLr_y7#n5@v$9>H_KCJ<3jgEL`kz|v&nRnINZ{?Cj0uNZHzHClW9w(s+&#N7u5C!E}7)i_DVb3B3Q^E?v^_`_2aRY z84QUsSx$A_E8=n!K9=4uEL?Vq6#tNN+bW>q4}Rk!bVvRcMTyHJs#ss3W8+-$n<>BI zWRUN6CqN- zy6VHI{}m`s%$v6Q(oh)pMh-$Kwo8l7$g?hAyND+xRQnNN`xmX=Kes2YA?;*I1Dnqy)c-g3X^2>0 zg6;?vK1g}orduvBry#>x4kQvqF($0a)XpqS>?*}2M#mYqrL<3w^4&Ji$IA$Y0F5F7 z6}@uN!BT1;Y+xehHyX8)(zDBlF1MJ~bOiPGgoF5MybU6KWwXK(2XtgBD-PtMAg5T` z{1gUyBLJKYj|Uut$VsyIcbBQ!cgcNGQNfC{s?6vJ34I2lBDRMh95_oc!`{{yUAIug z!}(Wapi?g)iI8VnSM+yHJhqh6vZ}_9^7{CxI2#Y0=EirVuD0o*&82i|yMv7$vX?mI z;KWjLq4e}j>=QSJuC^@S4YT+9tfAQXYinuaNdTj42_i$;UmHx9Ln72 zt<*Om1&EA?PVXTGQQ*AWj!n;$v4>zRaaD-rO@7;UL!GpY;3yedX!EA%5L6ijLL4oI zT^TVLfDnMFSmXP3@0VqLAp$g_zM%UljBnwoRv-OR`2o+BiRM{7`bBV@PiR}wS67T? zLbhQ*awcjt!!Z(!FyHlAo=NJ#+y{A&jQB%qpG+YubI-_?pJaHFX~Tn?XxBcCsr+x4 z4*{Cd$^uTelJn{+L0zPId!>(uM1P#dKwrzRMW3Be605%Ied;RI=Qny6Fe78s1mkPV z_RJ=*Wv*WNkxg1>oVlHuhER!pF}1_*2aHq8@NX^Eh@$pr%s$D3L*ocBhU=B}V87!! z*Xq&bI9+X~!vP}J7)>={#Y#|YBdMnm=0X^VvxE29&CAI(Qc0yb?t?4#GbXkKT{v^o z(tcAv^|81C!`ZF;ydjP8UgpQf{$zN$I$uVY>WqoC_4ayl3uaF7&XXJZp%_0FS^sM=ThsEgr+md1=7b3Jvo^!B0Ym>~!u8-+qy!&}&Jp-q4ADp_dkUaU^ zi`0h;z58$u)rOFCYkfC61jRP+<|)YC51)>Pd|#YjHm0)d!%Z?I5q7G?gC(VetjVZ+f1D8y@{FW=mpb! za-k4(AMYQrvE)#;Br8xUlD8x;=QKIrF3S{FcFfTP&v^hA75%kqgAO42Q&$h|!v-Q? zT{|Ym+hzNq-cf2kYV=z?$W_KbFJ#x1=j8&P?Uc>dJ4(py_%JTO;2{qz%xCX?HlU{H znSHd82Ws=bd1>?7aV708j@%?W%-+STaPUe>oL4hI&+ID0F}Efl%`g&pP+|g)5|rw6 zgsbR%?|+UGL3fWD47vC4B=}??GAm67)^}drQ{=Gr5Z`Ze!lkAC($)kUF7lp#v#{X= zNvx)A-{p47s>E;xSQr(lDmx@=k;=n@hB0uzwC$%07>V;#+g~Jd-LcNG-k~t`ZAE1H zH*8l;z03;JVF7o)^kGnDp5vmmE*ZlfQ{ET#?%9=+32Q9zH>AzhN0#Vg3C&?0ZAr+@ zGo`D|A++)tekI|KbpoG~(K38(!lefPP#BzSWE?&4=VulRw(VOnx>MkqQJ7me&<~`o~el8Mce^%kAdZ&`dI_ zNt=v9D<>iK=5Ej$0q`Vp=4ZUr{~h_Uut5L_yVO8sCf@2F;o3++_JK+D3I~|q!Y7Fe zM-ws9Ut#;wt@9t+`88${li?dTb>N!v zsxht%sYP&b-eMijt2(17N-+%ZQGsRzIZ}oBs0GvNIi9bk70`da-M4L)K8p}*4bbHQ z4{pncVu+n26Qr>;{To|$<8o{k*3}2k60m_jJqwQc{N>u3bPNUSu0Ip#e~%(47V7Rr zJHCh&JRAorgLW@|y@yLUb&DYHkX;n-(j#!JMYKIqG6-?0 zb?uy9(hwbflxwA1xo0TbC5942!AkyOXTFl`yi57k(_k?ARbY(9GY)XbtdB1QUuoKWz zBWFR`ce@uq5`%Q-|0LI^T}0TEZA^Jq6O=r{$&stq0-hpZEc0sU2*tP zXdNu8{}rt*awm(={lWTCN$TOZkfyAn&uV@ap(cbR@8H^@_|!AS-i9-WjC*Je(kiKe z`l`3=n0MB@0=|-am@L8EoGwPJk);5I{s5c9nw}@O%OQUcBRXJp_!?}&4T{QMD0sCo6*smmiX>?p$Rd5(r=DS)Wne}bh6=5p5er1MOVVioy9IXnqt8E z&$s!7785qAcua`2-?YV1KIXc{HVM_mgF&AAc0(s8@9CnD+~$d|D9N$*;yl$Q`^7

0}NTcB5a=k0~TRL4?`2!=xl1iLx2dI#2aBIJ9+pXQ%qewLFZ{hKaXb0?{!i7 z4*+auhXkH z7y4G0@&ojg);A14w+~U0dGAR(nyC6X2vGw?H~zuu0~^g=F}Y9I=xBi4ji)8jCGq0C z@1-x+sL)MtQd9qkk2E&$%z;JD%lebi6*bfiXSCH~tVPBOY{@n}VTOxo zRYrw*o!xK#yXey!3Xu^wffvK4LQ8&SoVW2J(aIGThUdFF8mh0}e)&I8@#e)Hu{~T6 zMgtKC?t4;Vs$Iuy4qoPjQ|f3-a9hgbB`zbH`iU?%}U zquRRPMkKRN7bEy6Q z`G_P!)I3QuES}d>`FY+x(Me8s$PkBnf=cYQ1qNPliP zna3PdZh`LyM=EQ8pbChsHokk_U(crk4*1RSvN#Z!LA^7RU}vv$Xu24>b5U1EgzKqw zy_uMwiYlg>)1Uo+<2Uo8!aBCyrrg$)#CHYw@^Jj5WO#>!7jDn8D$)P*WM1@N#Dpcj zumBYelxm5yH+Hl`NKHy}><1~;aiR({e>cB-Dyt{9+?-_BwT0h|trAT0D|`FPA8F>% zAOX=Jl`Z`L8D|{88!qkAqQD7ID}bnRqLkEPr0iU%ipFP+>Qbn8tZKC9mFnX1sK+*M z9tOY7(=d)C2m7Tfe2RWL)m$(OdYFUo3AMQ8?Zs;yJ|(vllz`SCEA}Qe+OLRT!r#WKiePrM$+@!Xw~Vp`G)5a8t? zT{`X`9t_z0lYxWp5bXLRjp0`xb^2vmni30emguv{W2J$_2are%$IX*|*yYlv6k@V} zvgaV|-XqCA>1Gk+o(-gK59}KKJdm{af~{Eg#uqH>b*r?b0#ErL5t^o#5fgT|!ZeSM z9RNJXshiF2RvG(8n68Q3ZO#AY$fo(;u_u;0VGe~9PGS92>vCgGw@LS8O1|D za18-))~6`ed3k&{K2$tkYT{ROU0iFDnM|(~;OM+i`+7D8SbIt>cP;teP`9>8JG^Fb zf%EJg-3;b}r4jyO^&UujVS$90+XbkX2AetPs5oG0Xy!6Nla_`9NzQ6>e_5HgfxL7j zAQ_w8+e^E`jNTGjUzj`1-fp35!gi8By4K&|x35{|pZkfNtgU1}JH}s3^ zb^d5~WOetDILYk`u6}=FUhO_9r5`EWn{d7VVuHJLLuNR4=v8e4_o4lL^P%F!h6UaM zdXJpm_&ZfWozv#H|d22P>XOM6wLa0Z@j*o4$mBet-$Oras0dB=%ewn|jR` zWO71<_H<+KNY3S+VGEShCua(uqd+cK#A*xa^Wt0DFZh<(77;}wr4Sk)EsO6{T%%oM zTG=Z4m%XlSgOOdT>$#D|-2D0#*EW+mo_nFoYQo@2c{=+)5IHDvC6A-2T(2{JK6f|{ z@g3YKuj9Y!5bBZq*xv+tcGb6kL)_wap@Xh80!8+GvUYWp0 zv`J&BtGff06!Y!2ba>WBcYW3sFfky|02gF59I!Pn$4oC_TJ-(?EkN5KnD&^Hk2D$> z7%(1L#1jA7kI8p)np4)6>H9JnJ&msQuSA<20>`(#^O5>0eI6hK;6Db6b~h`R&Vksu zg%l|WU~;7iV?Bz#`CYrX-HkA*l_4(r5l~`7?+Sogd#KlB>9d&4l3L&YC=_Oob!4LHOg;($l__vzwfR%|dJ1@lB1_lPiQ|?nek*;~g0NCC~r{K+RL>8X>ad z)mJY}+0GD#jz<2bfzLaOq6)3oCW3T)f9!?ovgD)rg*|&@)k2V(Zkvi0;xn`N{wTT~ zq(mp8cXXZ-T&bnDeV+a)l`g4&@YkAmhRvGyc4K5@`$X*Q`-2?nQiHA8N!kp4q$JP` zrNe3aCOfAp)R6fP*GuvmB@l~UiBPRGi)+K zOYC|-mm!PDSyu}bJoK5Muk^j})fXiikYjABh#>>1@vT_++Zu-zS`QC+DiX)t<&UlH zn1Clc^6Yx{nzinX@$Bah30@Y@W4SqIo|bUQd`Y>2$=N2l7|J{zo$*^78q6b!2=rzr6_Dl1W?JnTi#y=4q;svNRz$v_ zit?TH7dqZiaVD|rZ1P4wip>7KJV>Eos0GPL=HBM~8 zkCf`M39Of=<*|E+xut6#z$;Y`et%=8Gap+9b&|om*d_gE^nMG^VLVR#L*`BG!ln#G zh0`GiNlgJvrO6{_ge1(0fWYZ}27+;HQDz#E0ERBznV&__WY6#-jF<wgdi`#zPKnpzqTofwY&>4eJG>tXXf z58-EXD5AnHf}uAV5Z7y@cC~DB2@87Xx;*YLzq4(w#X`is1!HoJ22W*Z4DySuD&WLM zjFVSFB77S`K!6XO>+`fR8SqST=Qkn|B_;p5L5YJW-`UdWNFpW0f++%C{GrGG{yn$c z=mBm3V#iwasCm!U#l-T|$QTJ9)Wp}irwy_v`}iqP%75jq`J;}Y(oZK!YGf}H{XJL# z{an9Q!=7UJT)&yHcYaW$XV)ym{#A~-bx+599r-@NXryU!=$M_^u5nh-g&c6d9MKF? z!d+&(z3x{d_bD{FuX{R*Jeb7~x*(l%u3Ex*`76%U#}^oI5gy1IFc{D_8prWbVR=+g zWw@CxPVs%gwpkajQ-A0(J>;dD4|IQ(?Esz4MY*jgX0&}cGK3UC6{Wm+T!rp9tuya2a51B zt)?Lh?}F0+9P2KTBB6VMYQ;ecL*{5Y=VgIK2eXeZH}QtCElF6elCr+ShuG}hV;-n9 zo$?7GW`#jeoOZCZ`_3iEHgmG>wxclp3pW|`;BPYIdz!rUwXKO9tdDWZR=Zwc9v9l$Gl&a7HK3UZ(x3i^MP=o& zMmF1t_Ulp4kMkNvxsTg2g#3dn2&9@ZrJG(EABmcdI(<$VFC30VnWonS6vrS>1&na= z?l`-NH9kvl8ayky1S6&r?U?+d!~%0-PKB3WuNJ)a2&;ciIiJGH+X>l@2H;;I{nBa( zAW;tzPvf0b!!@E7e@s=ay$iU$k^1v#gu=d0vc7!OEWaK5x!Cv^*0M~nCowAP@5L{x z?HzDY`J7Yv3H=;(&i>tX4MhZOxy$(Z<3IKh3XF=ng$i9<{Jp9k4RF&JXG)s(I`wR{ zI?yDPsS2vjqT29L;28DFi#j?$00T@yQo`#v1O~CN|tbceLNoR%Ow4}+oByH{l=_v9&%&vmc7T#ZTudO9lqk- zcRfk&zQ&P&jn*QbdMCW>a&x-c7l2OOxdlGojh)WOYERpdA9dZJ7;zb;>h6f4C=GHY>fTyXXA#Tpki?KUJ?=hw^*rxjNpI%ExK$D#B2WzaT+84T>5VY zr<`5-|MmiE`moTX_Rr1?YS{-`?{qr+i$(42&vJ{r*%~KzI%4k^Y&c)8d>^7SiUsA_ z3FX~eHA;2{2gtS#s6mF){itdnLB~-R`ux^8iYOeg`Cf`jZskg3E1f8WoDn%p_P+(dYn&wL++~PnIDZ=U~*D<>*f1YSaE1u4p`? zVE1^T08K(Sub?%_dag95@mI{*^SuEUT8O>d%P>P!0XfL*b>DyXah(PGF%W40Q9wUL zfl{seHLSF2_Jou9b+~`Cov|@#NqJH`SN3Tj)u^b!Hzle5^y1`g(-%`d5+I)>$mfC- zsUan4KI=^!>scq#*20gy{6U+yD8 zb@ls>mnBF%Yoza8k*|L5&t4VnA?q(OASq4PTW9aPm;UgMzR>uY3_f2UnJuky+^dHo z)wzFyqlN%?F4*2J2PobakKa75m?fh_nVzmn8X3_FB?WaR8+v#qd3uyZg7b&YgE`3l zh8Pwaj5I?(t{O=-)N$f>oRrq)MXwTF0FU0iX<>VNq@v2W{&Kwjxh+~yOrElGfsHwB zWKQYF&d#e@jDS3b8{F{&y%Pp);2SJSN;^%-_)tss`oBi>>D>Ri_hm+1VlcJZ3=2(f zd$(9T9^q)17^tY}OlE`qLIn^300Adzf@?ju#TCP^j7Zajg$SC4fib(b(NBwhOqy9d zcm+w=NQ5-#t@|}`~TU^89ALI5;{6Yxrj;^Qx>pmxi0|1S$en)YQ~G zuNVe;ugvqrUxn|0!~8%$EZddK`L&}P~yvbsJ)}Ki)h$Sw13_U&4o@Hl4C_~ z-rOA=5|9(i2qb|ehXx0Ct(o{0m5rc%AP8rzkBp-Xntlf>p|{7YeI%YPCRJJo2PYdq zYHt5PregAtZrnR|rbHvBm{z|cnT^BZ{`!yd-^o>`wWt2@zg+Ap+A6Fj^k^-auLaH3 zQ&<2gV37i7V9heN(BZWuuU#HNdiL$SW$nS>e)`NST8Sos6{2=P zgaQY&??M6W@y4tKR%iR`C$6h~Cl~qZFHK`>ZI19u^|m`_FE3;R(c^%#sslM)yWH~y z&jACutj8eQb;j~qTNg9*?%gk1b`GwqQ_nL@;>_@^^fkBG{(h>@ zva&SvH0zr7Z9RR9iv-hIW%O6zQTtYDF%#?^vx4otosI8JZjTd2K#jdcuU|3UKi&Yeey{Z+W@ZJ) zD>X!UUDCFCB=+skoF+2~huZng=zBo5zAF6Or8o7fc3A4+7{|<>Ih?SHAN)1)vUL$Z zu7DB!k&Kc-1KHEXt{}J8?;H0}%y7>@hhYEM3DMy2Xii;OJaS#KGCN_dHVM&OS#E9q z&(kbHg*n|$%j)%Srj-sWNc85|k)vhob+uJ?C$zLfVGi^3x~+eI49%QgjiC?$n8c<_ zOVuKALgF@zHY+i%LYAPSnFJ!;knER^`X9lCoQ$*jo1HHL+k3nh50d620Tsox7jDFv z3Ij><>W#~SfRhg-M01obQGp9!Q~J4o@$ZzRi6dS5Yz6SdQ1O$(vO2e=J99K?1PvBu zd-6nt%vGg;91YmjRC*st0us)0e1jF<2V~fZYX+rN9qBX|`4(!8R@}a9Rsl3AsbN!Z zfYcqx%jFHwwg8CseUrjs3$CwmyLFY>G}Q}uM)Tn8%;GFdfd6?r-s|?!SMcOL6(3(X z{Ao+F?>&fVa(YQt#7B^b%SQS!GJB}zq-FZ z+NBnH+RJwo%0I%ZXj5=;nax<4u?XKeI-sVE~9tfI#SFCYODub^k3!-CZ?Bq0M8mQJmVL5zRz5+ zZ~O^N9wIB+VycO;3z|W%YmAsJlZBG4Y8aX6~3vTxCYiW{61ZPV<#1 z@&LjGpp#J%^t@6>w15g#D9NmQs<8#h>UiR5x5dUz%y8XFwxNZL{#Zyj)|*(Ej_ynA zN~|CIWI;`a3)~}XjfxLUOdx*|ykw%Zy%EPx8Qjic<>hv{llVXZj7gm~FrH#MDtzEI zQw-SlDy@jIaz=pMDZqWNsF=2`tu2FJeKc*RWIF8Q@IiiUexU2BcQn8RGQ6|<94)A= zOT0F}yf5FMr(daO7nJT6wn0T+udwp+g>R z#2&ZIC7NZuoEvQ4svp=%NHRaIoimw#QOFb&v(_Qn_!dYZ6}@rg{zbFSr9WkgVIW0q zj-H~YYmuYmeuv1Om6=%@c#i8-LPA2KJ@w!%YQ@)U2DAt)-y;KVEE~K6Bxy~Udw{Ny^%a* znV4EKfVAx9FrZGqqY}d@&q~~c1x1n#sN|kGq&^1mM+<+*@`}l#&7mT~?EK;)6sttEy^SwZAy7i@j((gIBDnsYmU@dS7ruEAi(r(T(dDP50m&&{+qYfaI> zxWelPQ<5lWXN7m+D|Xaap%ERZZ@)F|Hgxys-WPpdOHCzCyYyUpnXPqVVA}i+#SrPdV zi+|<}tvQ?&yU}@e3nRBE6n~_g^!v79_yib|6b%tzDRdfUp_<`94vPF$95(E2Tk=b; z)xx+6ze>wyiq*GD3*~v=Rlb;YzqXm@tmpo-Iy`Em{M_|_t<(>qH~mz|;>r`2wE$80 z$ZjeT&@|vOBpYwCB*f=|RWUxk8>yaG#^T?UN{jgA|*C(@Ob0rmAa z$BTpgkwjBNbN|!vhkm4Bv72uKY!UCu3>-lT_^W-A+Uud6Tx1smV>+J+{0*7QQm8-g zJ?$o$13w06idTios9Q?nYuP;eWD$2-cYW`*5B4b47`_vmubY+Uz3tJRbEFFGeHz&E z^wwu5L?s|V<0oZ`IfjOgfS@}1y6PUa38a^Px@}-%i+>0%vEQcdPq5?Z&u=y-Ac&fD z*I~8rn%9@sD~c;dfot;_d&Jmv9nW`MW4wM;cnzQ$qTiyU;-}Ly^HGyoXrP&g+s?vU zaBMy)Hq*xAccp8=JjpK&YIBUc32TXYy7`W zh+A#KyR0s@-5VHyCXL(taC5p4zgb%HPt#+I+g%Pk(2U8QnY!#dE)XAeaB^a{G+PEp zBr6+hBV@JYQFRAI>m5BH>n$drD0qRtu<&Ic%o`StbU#%yo;{;*ipm=AG^VM}?fUWt zXoAcAYWvQDI-;X}_NgdsyiRUbo;sSCztPe;e(aff4xAAR3}Pb}LXV_+t}sa+miP38 zf3@D}qrp8J>+53ZQ}ciMPZ%C-%E7ByqyJaq(qZt1}S7N1tD=Dx)5(cKd$hEK5cm{>$5dAu1-HH%@-ANjO=_Gyf*z1psffuoQ;`VTx{!|S|oIv!g%Om zjapK&idKx2h(2emG;H$HaKQr=PMOiz<#>_)ZtnB}xAOx((OgANC3*TWJ$$BK+x8CS zn*MKEw^n@|<5C*BuCFXW5ELA&$Ih+~EzxnDWG5#DSQcQ&H9sT~=oGa9v=%UJ@ksnb zY{H%gKj|uDT}%*it(qcl?&0CtsAiyOCF8Jo-vJC{T*U#^2V@|t8l!voQ>>-dTnm~K30j_6le$=3Nk{(aD_Gwr{|@l1eClSFs; zHuDu`u_GG-XlrJK*`IHq8mxc1(T7c%k{S@&Q}u{F$ImvBT|6R$#IN9kJz&CeG0@&l z9-x|Sy_1j?t7hoFbK6B2aA*}u$haKd;+|!$jIf6@sdvqOI(tp=#2dK2KAEJtj zX$VnyGfBhyg6*7^%YeL{WWRA_-lQeaahk#~dohS%@rpeZB^e#A6&JJerx4Gb{TUIh z;KGO=Ah>Y<*_Ug{u6>DoIbUTa003dF4v<0y^8{1msJrjrc zQewP$5Bp}s825~G9-pYSU$(X10c$`ugWW2w(6)a|#-4Ammd*9l-sABRJmx)We_GOE zHC@Z5H|?@0lWy*;!nJxrAL-iJKMzM_V^ah8Br^1GD08kJVJK=WrSO^8ZjTZ*=?^mY z?gfW=Hu?A5qnsu^*wYXc)z?*jh=`e7P0XIGGMUSP`muEy&fCs9c&VoGy5o~wPS=4E zJQ*F|-kVyTE?;~oQo#FGzU}pA3g^|U!5_a?UhK4h?Ipl|y7+a^{@+@FMZ5L*lRPks z_>sv&8`n4T_w@A3whs>}0MLj=TbE|p27RkY^^Gi~=(cWAx)%FkTxp*q`@G<6J&>9< zvA78B=S5>=(WR${uOR_kyT)%dN)#GZNo`h69sb=U&hGB+6v2YMay50Ze1^i?MC}<> zyhUk{8P>~SDqH%g9d+VdIauyduOSH;Pj*1=K{NuVO+T82k#QZ~=3gZD>~wGnip4{1 zZq;;g8>P^+Fju9-(!N3E`vz76p?m<`S8+=<4gIayNc0-_>AZOx&LKv<-9OZzsMPBq ze8f}&0{+{+qE9VOp~Z)XvHw&^U~INyodavu^JFTNo|Fk3NxXaOtc4#Nt1K?*?puAY z1bpDXP-Gy!y9fg+hDo}1+0BCu{C~cPaoaHQ931yA?g{LyWLxFIbPaV_v?3uK3Fy+5d|-c)~!B@ zk?;KL0el}N1VO%$9w^j8so(0WP;x3v0T|4nM$&lVYeRpxKIHipm6QyhaEdP5pCoF>EqaO?diIMZ~yc=Ad^oYATt&nOH5eq`O|C3i5uGRur_bVR9 z`jofs?i3_rjfIgBy>_k3|L%CTiYEcBjpbMj{Bjt%c2!B zW%m<8D7XNB`}QZ)_SdADoEz5IdE7BH3HLA3UHh!_BAUEth;%F?;2 zoUf?9+OImTpx)q0Wj}*`?y@d#+Sp?%1Tmzwb9gmKSMDm;2KG9)yc?r6f#56IlpGKX zY@Yh3$|M?v|~QU1$wxc2L1ye2K}{&C-XUS(z8 z_J{5(QwuX?Z5KqpqIx0!QS-gM3J6l-=h4~G=o;H$+*Pk5Tc|8AVtAyQm|5uXc;$J?LvhjtA-PFwG1`JjHO(Z9 zr*OG4o}DgAh>5|-Ces@&B{4j$*OBGWASv3d1@{?m3<%v50JDmg8sKIX+AnVteaKG^ zqZ*!N8Ffv2#9p7Y@MV?(>T-AI`xe0G?)ox8?D3MdWcSNl#do5L2@2>Y9YSJVRLSIS|@;lm!Tn$^PfOt;r2+cS_Fgcr9S3Dq0fV*wf_RlU8u`_EyDU^}$bQ%>f2=uWGYm5(D663GDt!k2)cBq?G{b={ZWe z`1A0NUXMH3ZEIz%-k_N#c{IAz{EDJv;rcwF)SxW^AZ+aIBYY^RaZ+aFS(w(V==u9K zi9Tca@EvmK->+w1+{O3%diQ}bggLdPD1N!QY{04KOj@>{x?Va#JA=W16&Op*00t5P z#Mqvv8SGqKs_rMe0M{}Uexw6%XH9`^udT12oSN!>fq_qD@h0?W%W}($l}RNyUT6T_ zxX_JKVTVL}VL7Fuz`=Z}F~8gs@C4>)I5{tH1N@JYCj8h`l&FjCElZ)(2l>P1JsaPY zRCR`PdZZp~ftiHCM_hJ*{@$9uzy>}gCX-{>LcgjC&U`eZ3*_P}Zn9;|%g6VL*KxKY z+{INiF)Nqm@7$bYb9PKBoB8$Qv6={UT^(1|5(z+whe=XKv8%vmmS0qJ*7+V7|3DR= z+5>`}Q#gG+?CcZXn2KjUKE5WWri}Zm@BUdrzZ@O+zTYf2#cZG1FTI`S%z|CAhY4Mo zyCAVS_6{mO%Vea_ZEl8>Q1+S?p&65XnU!B_ghR?NTLzQ(K4f;-ZrQv4*!dJ?7~NL& zB(O7n8^?G0Ak1vFa%5sw+Ycnga+p7`y-8xskc}VgwipNlW_99kp^T)Y4gSt;K@TWt z)$a+aru(H%>(ngGI!#@D>CcZerl0>Zdm>^bDQ>A%ZO9mPY8RDB-+vlY=V$We<>?-9 zf7-7UA$e<$H8nMDf`5LutsFiy^lShlsIJq~PQP(a1l~G%ibX&dO&>IuAWhh?LJRoj z2ZsciG%P(9EIhh1M>q%s=&gC|?1+H+`Uv5k`CliQx9O2p8a-<}@sc#yaeTY{Yh?#s z7&B$PT^cau#Ky+nVyhTq1mxV1H67lYG-7^!{(S)#fXpTiCj;!z>x9P#nY61VWo0X1 z`+>|YK7RfTfDRHMY}PnhaUs-V5*u98eDJS75`H;0@G1|q)!}m7|APtGn$WO_{Nt$| zVEao>f~Wr3e)yz5o#XbXv-bQ)Xjcv-zoO3HawCJ#>j{MP+y?`qoMDXzc2B|uQMNX`gc z3@>&gzLzwM`}ZpcAkOqjxY>JXWPW{9qJfeDKs_z5_TB>)7va`J!Nxak1ax%Awy3CC z_mOd%s%)C`XHDOqb$LA%_``JNO8O3O6Oq2}ZlBLEb#$WZdyax9O75U9@V7P_b@eyc zD(1CdXcLi4banr{Fe7macssEx3RXqBt}gY}9nDoO*wBBejmgO1@i|#rTm)VamN#?E zSp-*_4>0eV4_&p^1~13Hb(gdNSP9%H;QobQ;W0Kd5(fixP_Jgs4a=qjmD0svz?BB2 z?hJhB*|VZa(n}O6I4;|Mr^B_Hlz105t+_@p#6tmiX`4pU>?d{1P z_l_^;TxI$>f*Nmrr*DLb06yT5fB~Ul&4N{f3$t~(p{Urp}ef20{En`!1e zSy(1Zy~sfiIt@H*h8V(RWhM%<>|+^pW@|Zlx~)!$wSTu`lB5DxudSw`9#G#vE&pm3 z`91|37w4tudJ{5eT3B41_G&a<#@NRr(e1N0htwAr7cDtcf$oKk$w;Ef11CVal^WC( z8W4iP;O$Y6qNypRLseC&K})}woq%tx9%IKy(rCK0^iQcgL~2`OT1BC{%*x72ktM}+ z-k>^b4QzY%T#XP++(IgTew4(+QDhtaYvP zI@ft#S1hBZu(ULmVI0#%Z;zg71}M7M7j8|?!t$_KMG7*1`9`tniaq-1=g#ce#>Ph5 zqRrP-j))wo*wYkeIo`2@U#+-z$Nnx*H!(}_f*vE8o_ip&J@{8*jHqo|%lG#yyZPH+ zrvEKoVTf$I@0=E(di6^+ea^Jem)?6Qc!JWZcg!%yKA>u>WkafnSQ0G+}UB{ zXpbt{J<4JoJ#5E0ELE(V(yRcGSO}eAym(79dq{W-5UX2CEUdXkk!eXXpjeHR6p<=j zVLo*AEN~BngoK~<9yyzHYglmT8^559>GL<61SzuAL7Bg6D{%S@tXd3k`cj^=c;KzT z6m3_#(@&t#l#ft|uiu#~=)3ov(zOw~iAoAFA<>?xL-j6p=R1IN$_- z!NkKy=H~cKX262-H8S$2{eXZ-h?ZPMSDQOjpv|bFu9}v|J=W1&H7Ys!EqIk|0n+Jc zJglk(6`1X-2Xy!in>6&+27qN>ND<@8g;3M^6bw0dO&lV~&bQU2~q_ z-9Gp3By-QsOPQYLa<7gD$LipGGOK(rE_=PQOMfLaJx1bn%q*b#vwO7%VD#+jW1}Az z?#ubxTTT~WXF5_cjx@aV(HCxJRnD&KQX!5PZ=I=vVk0Nff(ufO9ta?H;$PE--V6OQKJG+kJ^E|d`{7re!NE!5M8tY#=S{k?bt=)XUHfV{ zBZP-FS8h45SmSj5M4KcE9$Y4M{^dbd9FQLEB6kcY20&e=HkX|6;jqgA^l1{DlDTh& z8!HZvmh?>z?8@Q=SQ7gCnWl@Ky-rl*pD;yhwk)n2O)a@5Gtgwz4QL`Yuw`b=OoZ=( zdZXVhx;$`5xn*lcxRcu-3D1gf+Z&C{ZD$`$SEa&JHplH2_2BUm+98_5oHtupEO>K= zKHeat)Y%)^ZM$Kw#i~5HJ4{WLF(Nu-@r=(>yI3VFcsMeFB_gEq30Qa?E)>P$=t_p> z=K4^BVYc<_w?k#9s3@OFNWW50R)ndmJjhkaQYaMaxb?Z#`hQEK+b3Uof>K7H5Y-Ss zaqm*U{uu3ox3zb8V@%KR);eXyZcMwO#4>fiq-7yp{{UNN@$T)CgToaH(ajN;O z)1A!*TC0oc?u$MnN=o2)=_>)x2Pu}Rd5r>OmB-Nv0(p(Fj%MO6mW@A9Q4!MzPkp zHPTU<=8u^<$jehw5x_Ufb+^~=Nz~sQ908=9O(^94bih4nqhPGtK=(}p>cX3vTf>az z7!{(ZLmaqn-geI$>rMcRh&6rN@N|@y*Vdx6p)wybLzq&6#wN(mfLfk zRn#--?so|H3*{bs(0AHy;DD%K?O*Q?SbSB0e!C;`Jq~$@pGW(vLFV7>yLX}F@9LuAWDaS0<2^KgBYyyqNawjwjzR>l zJC!oKoSjj2 zKZ_-nO9TtX^pT36amw6d=Qx3fyHUGoaZ%S`AX!q8n!pZYX z8lUbpVIHgvZm+LZWch*2^}wzI>})M54Y^KmM6puYo%h~VSD%iL=8kw7Y~ zkxaKWC7Yw58xUx;c*$L!* z(_Smzs!$26F1G2-RO zyJ6bOaaEh8-?|u^mkB$TU=nImROkuD_O=_m6kUF$Z0ClAgpGlAd>&+LA)>v~66tOO z1K%_c7CtI=V`HYlVj9dZR}lKnXmZ}QIa`-*95CdvAh+&)<9Sx(uW`8du#C7A#l!%q z?rs@TCG9Ks883ZJjPGY;r#nW!*B(MnlN2jm7%ML9q??>Rj|Uzm#Oetd*7Xm8=JQ=2 zTeGB$A_;~hG~o}Owi(-Jc?H&A;-1*Xpgu^AZ`B`QenHQU=BU9aihkYO8Dsfkoyezh z^+*1d^K^j+bFRZl8MYzv_q3@E($rLC=&)#v`C#s@&Bc1@Sy!Hn*2O)P>HEFfRk1l0 z!>`Ps-Y3 zSNU~**N+Eix8GYSxK#U=t_}QNp2o;rW71Ja5MEm(w2732Vlhl#V@X-2?e}kbWxt8} zxzsmvHXO@zy2!Mv0Q{Xe_N6uWBZeceqTAhH_sfOpg)ZctJ6q@WqglnKz5*y zI=y9aT?K_-3}F+^0+RZd!W^Z+uwkOoL#P{3{YnBu8Y_wB+}&nlacPZLNff(HUE!Ng z74R&U;LHNDJ#zDlmnnC0`;xk@PZpMRWH33}f0Vy;KC!H`2`j`c)`1+b2sE*x#j{y6 zNpJ?yl%a5Oos2iz;RxltN#hF!UX8=}GgyOXizFOfaP{5KAMkO3?k(NSft8}bEyxMB zfZ6z~t3{a0Mjgp4!(0z~^K9@-Gdv9~muSQXRoBv@9%Is-<=3}gIqoP4Oe4^7*trtE z8}Mq+mw%!!-OiIF)LVE*;HKdgW@&PPo0D>WLKfe!|1?!$du`!Z(|&ccI9!OWPNS*q z%Qse!)A6*rp@J8##~5qUOc@#Ebe}uLUwQu2u*foow*r=>ZQ&B3{SfTj$)OgGb*Flw zi_y^Ctm;_)?$?F48l77yD5^?M3d}dB8g~`L(ga|(&EMYL@BvlxjUppf+pgbqlt$+& zi^i{UpH(_JJR+Q8J$=Q(9talPgUK&377zn=d*gxUbY`ci^*fBQ^AnzDj~9M;o)6DW zIkiq3-uyj@>_gvjc#+s+Qdy90n2S^UwdeHqCwBYx;CckER;$F8(+$XX5D{B4TuKtg>4^)x}WHV`bj7LKZRe z0gFg#aP1qO9-5{i{rLtgC6t1|mDOkYT&j{6KP;c}S|aedlm>r=5JtIGZk(a zbx(>rmT;Ep#mR*Mo}+0mG`k6LJ$p623cqEjU{1r3q-vgD0jv9Iw($M!{fE+Ndw4T6 zcF_F5aKS5!!3(nOx$rTb=)1~)f`#SIS%`P_F;3CAEP%)84xIH_6__!SqDtAZVZ0Uh zLA>BkbDT}lOeeBgED8?FzE*h*m=w%4Lq(Tka)6QRp()Y{(PHXm3il*eQ}fi^^3Ojm zof4&W9~wRcf+U(B*RA4!p|h%~8yjpZ{z9cB9kJ}do8p6Sqh!8x3f+BN zX8Tn8bXq-=xG$Q7w-eEIR;hs=$Ib_nxQ2}w5&8`mBUdC#=dH{Hzh@T$gv zyP>T*Fx>0{jj|-CVK4V+NYP62`pN}QgCknf|DiYbfLiae1m{bdq zd|cCK<%GTZubXDS;cyFZeIKRnfDlgurNrHP3U1mC=S_f@e**xKo7FZ*>21gp156n{aY^fP6*7qG9oaDVWIe_pxTB}}CT$~!v zGrjQ~ftgDRAdCAaJ$lA>U$Ia=U)iQP6cuGI79*@dvn7JJl){LwpFV6exsKaKqtox? z6o%=Oz4O~{$d#k%``7VS!7Quap^f8s>Z6ZYI&L@Odx%kpsn_b=Vq>f{$$gBE-B{B$ zCPqf9iG4>emXCG|&Z7At&FDM&q@dAsC)$$#1hx*uifLZ_W=q59n&D%K}M)CgAVHB3i zC*|Pqgg$m{e;t%PQcLs~Q11jufXi&@_uA;49V?YmwO_tH_=7`J?Fo_PkeS(UB^2i4 zN*qNW#Ln9cWbtAL4J&l3oD@{Fx<0v1@zv|T1e`zc31e{`=SPibj_kN2gNLBae1}hh zc{74`EhGp*u5q&+(1GYl;B`kEV|V0AkxfkOo!rKWW%{)mfuN7);Z6a!k~r+*uxpl3 zinH9IJmOaX%@Q^9QefzZ_%Kgd(|`_0va##BsRKQb3WmT{)2NyX`}`8>6}i z6+M~OyWSZ4=xT%qq{N84hqIqr6yIl@E2hePEz|y`&YFrR=g8Us1&e|&2ZwKrIc(YX zTd$pu;_73yDpy*?M-C=z5d_pmWgGbj1OHo!WGe~m+y(@WCZlvpy6HIKLhi<&BrePG z&vqszzAoi7+&|k?i!c=`9E++udV$xj^xlrvo8Bhp%TgpqIi9sqR}qt>WXS@F7e@H= zPgYx+nhxBfZP}}3wh{~eu^&t=6<<+NFRM>27WQWRX2s>SJ$pUcw3x*xKK=^q2@2@d z3F-Rz7>1V`iDib}EA|O#5dXk0!oIScm8NsT0nFD=05nn`bX_!a+xr$G3k#y7n9 z`IooLDR0v3TCX|4p*8l#s$lfHG~ea5d7oa`BPMT~-?=TwtFq4CNFOr>0M;>YvyUuZ zG;sHns?B=t&9H-F!ZB0#> zUKQ3uy|B{y>5DttS<6qjrgfI938UQB$Iw)c4f^myf8UqRxpf*`h->$^g4JQLrY0Qq z&D!@avD20FQnp0+g7dA}ofCVirA3`aC2JmwcQ{DGpm&limV}r0D+&52KU8Us5 z8DW=gY8*hM3we#D&}W#QndP!U`sb$woV^*_w|FC4axIz5xP~RnJ-PFkRU@{~<+0G? z!n`DRmS@f`M0wZ{{Bn9H>T6&30c*omLc1?;%bS&~Z zei@P|yCE5VFr@33K|+RlcZ6EjHPF(5$M#_Ko&U|O3((D;uC>k5s|ufV@g|^tgaz6U z_-a2-V%L8bM~8-M74ZI@?v)g-?%6WYliI5{L8)oZNiLVj&e3zcsq^&%@6TmO%pl-=2`0=<4cnrJSt!f4HG;9zFwHM#r7Jkw6Y|z3f)- z3X70%LxfYI{@yRPw4`k7%utbvdnz0-Bvh;t75_laA|!Q2Cifo=$E~m0 zK$s!ix@|P95S74f+5OI+957Vs;~}OmB*f*P09!5vD}q4I-%J{PV?gb8|{# zv&3*vsQc#C2YS6xuH%0mf&bdL$Wg&b{ynw-uP00Ux;{Yr0z;zE-F4hGA4@qwiBb3K zu95*AP_z~#VErZhrDJqTG$njbMDIH$e8z8dEI|o+>}IYlZ_^k)dOCjgoz%`w+Cc zBY@j<@Be_q6m z{`J~EILW&(dMYp_<9gzr&9c}D}-<)aA?yM2v>$z?XK;PXf!peAs5H8j^QC824lYT z3EcWTEawCac&x@j10Xz@M)USOC#RtJP_}i;s4r2Xs6|L**%F6TKw4RuUHc-bnacF% z9TYsqk3TZ~Fo8a4&sy*_W9GH16y|d}x?y9&>x%`i$C^q{ZgeTxz0#ne6H9^&X!ZUX z2v%5_otaQ_4C78sb_GN|`FL?dAXZ5MoW~b(5(?*ZtMKV1t zVRWkC>Xp|uOfW+s-{r8_`CF~sUEh`;oZ8oDD1v8G%q|;U)O^)Gf`+7$=fLK%?2{u^ zE3e#cjodsDxTyR)94HLmqJf>}KeRH6e^%0XwNp2fsvUHS@6lq2xmi`>>F4Y>yUg)v zqO!-Mklm}DaCda-bOo?`+uK)9)QapyVTKzU_br86H%p1=Q|RiNIqjU%M^Z`ut{4cn zTUwzVFZs|{jvgDCkkDccxq!Zr@bmNAAHz?jrlk!8DjXQtzPFg&djO8Fl>omJ2z>4V zg$TX2Tk6URrDPA&{tVL1_t&p?YpZJt%F2BMkp23Fkq*zCr+B$*^voVzyRUoW1wuLJ zJ%W{SOAPjZ0&VFQD>0wP=d{Z$e2;DG>%_~3v)OM2nJXSh%=0490DEqCp$+^J=73<& zh#Y^{fHsmH8yj0I>1W#c!KlE_2xd{k?w>$Rc}Tha;VYgoJ~$ z^WoW$KA4U2@73=7021EH>3g{{DUx<}1L<98jV253;9m2*zc#HLyq8?H!5s*^c7j5&71 z!pb(u+Qo%niZw4XGC6>XFN7+}FEaQkD6n>$e}K4l&Z2{WNTldZcvV)^(qUX6XUA(x zJ09Id-O({lyPZqJiN;o1P)vb?8`aX_4xSV}Op5+{WhJ&fHV)WvWMYG_JG34YdC%5y zuk`n|EZXQ)tI^OfEKLSJ_N_LzY}6PiD=R~pv~b#TJ{Na)Ul_oCKj(!6o8g1@c^5Ot z<>r$M>0*_~<^hhDK>JXp4unf_rIoiCMW9v2reeDyPb@izzyfzQtdy_hdthn_O)}DO zcW-XE3S7|eL^SU0&fE;5hQ3cvEfW5Yo?7jYQz38a1bF_F!m*-x1?3>vS~ti(Cf zNEqJ&!aFWWRLcr4FQ^}SoRG{CF=EUF?h}X*feas_dpgrna(j%3q9LI45txra(5+X) z8_(_eDEQF+w5LpX!q3x8f!wbhAMEtJ=5$BHWk~GoJ4X}XR}T<-+#i(Vnw?8!TTyb$ zvoehP!wA^cBe0huDnsKp&F{*BJby0ym@v_STsW#vOx_t>@&zKxoinRr8p{ z)44-fs^@o0#=Ix!{c2V_(i8}eK$8h zJZvYfhVyz<_4y@Z=}7(qyj*gIB4a$MN~=Ag*iw!?m9_WT%zuzUn+d0$6snu)&(?$# z_E4ZxrRtgE?VczwGv)XI$a@pbCxQF6$;^f66+L3&#`rL)(yz(ULfI;t)3Cp6<0RM> zR8kq5ni7>f`0f8&RLl&Bv9eDuUxJLb=di`Xv>gECg6h=ouh_vC z=~T=B6~W-70r~h~MdJwkt^5<>IoV`cYASoooVZdP{XV}hlKiJkwy#DD9_~sqsnXNe zd0ueP#RRSPkb(rF++zwAbljfLE^yl`UM-B70DHB-Vf5Xq&kW2`pl7PHw|6U0 z{dj4Ib!mc#r}w;iW=3c495K8EwK5pob-L0kJ*s?aUgN&J!rq(!-LO28N*ne(eyrmE z!7oc%&&mjIDN^ULI@WghXAu!s6LhHSnQl8;)@o3TL1TG&b@d%-{pu-_l9QDFRLsjJXMq%1zwf;G zx54%l@Oywe7xN$6y&>m70kHJ;>8D_qyTPezMJP__VE21LP|= z8}l083wTjnd zdwYeqoYjggXRV43PhEj#eVOJ$IRJTG+$@qevcGcyrD24!4KL0rHbI{4LD)!p82-*W zp>vw;U(DEh;?OEzu*Gi=>!nmEd`YL!Gdnc`+$m0dXec)uN^PfHKU-Vl1=xMR{d%`y zJx3a?1tuY)#wsc@Kt+&zX7?@sCyYm#PGQ540kx`Zq z9Cu#Y(k!njK3vw!(uK z3s$v(W1S)1L$!Yjir<_~M-oc~n-Es%I38;;V~b{Xl5~wH&h3AtY6ScSblxaG49wbw zEUB58T1vGa+VG^XakA@FRiDqQ{3{(=f5_o^euOl1?ruZ5Rb3)GJ);gLYn8g6da+ne z{l#~Y_I#ZR$KmmD;gM}Oi)tq3d_>;J7sQ3cuM9U+7*2h?1zSi2`c-I^%N1C5|4kSV zMB?AdfN(vX=DoJ|?r~M^Nj5fG6wginmX5r?%JwTqXWsAeN#9BfbL%(glsV}>B9&FO z7gap58DrPd$qHObw_Nt=f#gYWSoe-)Q75z5yvwdgtNiu8R^CnIsrC8=^!^(jC%g;C zrwN%fm%GRlHq4 z3ckDUO7&w+=0;=w258LlFoXH3!}IC`{wJM1Y3hqb+IgK3FK~dFZFA+SfkYhxC0xIX$IkE_V(uZFL4~dxo!FRW~`^m6Id((!6&Katy zh@7XnPU3a+rk(3uLO2{ka?)}nW8GO%YX1g^)ro)A-IT?ApznVpw=kcdt!sdq;{#mH zpji%do7H>16A-p_JqFNY)qlKr)!F*e2Pl&iBBOvorE`<40)XkmW9Y7;s&?nkyO=IJ z!2^5CX_rsHrc#o1BdWJ#4mme>LSD6Xs{x1o^-s|otJtGQOLMZN2+nz*Pl1Ayl|TtD zt)@oM?74j6;Gp)Q%I@Wj@J{>soRbEF@2riZXx!=gDo;>^D8umLu?GLzYCcoy^z|K% zq?EMgYkDO*b`4I!{%S@IkVw^_207Ef2h!qtcz1U*b8|smubZX3G1mRLwdZrS`SWCG zeDlH2L&yZhsnrZPy9ZC7c9G4yC5?}Bto8OAb+2@3T{s|*=61EUfj=U)(8^~Pb-3m6KG&Aus;Sn?;tkeKc~~TDDwHhYP<&wB14ldN&Wq@LH)@&4Vgbn zCmXkSt&6DT@ZYOygBoGWAG=)1$;l_ZzQcCh?Cn{BhbbyC@!9*7Gm1%$qneLCq$fT{ zeeY9=W(jxxq-j8_B<4*}0rZ-hNGC30l+XUSJHw#+@`;n@k1q&wAl;m;AN$dzm{+&D zns+HpWsjUNVr5}rLFw)9*IPX>q@<)wA2LDtI1(Z9(3)lmmx@vUBLm7G#q$MFJcFwU zTGW|ZH@nwVO&MU&c>NJe2j&b!U(B=V>0^V64`(F1?}v9U>PqdP7hRn?7v7xn=d(ZJ zQH#a34Y#(2GiflWDqII)*8!oSh}!1nvg2)49Dh{*)}dW$S`r-e#9N)gr96j5-z?Xy z{}}*8xH50N;I3p`i%6uGW|dS9$U*z2ecYlKBawLVjQr*e=N<)a)jg< zR;#^#a(Z1T*fu@HLNn)*^$=8VbExch95Nz)FNca<6#Va+WYv1_O~>bkxExna73H2W z?$5n|ew`1&?aYF%9^TYVKx7VtJh~yl_Pn*rg>o7Uc;TRe@Y?l(vT^F?B`(quySvkz zjU;4E{dZC8=Uh6WLBm&CWR4DN>8q0LQAsSRg` zfgvH3OdPQY^NG`#*!JO{s|P)x7+oN+oUkR@3{&ME94XEKYYOOgo>`y!MV!;>ZQh73 zTSpnB?^z9tO95t7*A2xJZskD$jB_nII?Js@fNgOc)U@#G78pIcl?H-vn!p1rInVwS zp&oc)2%Z$?>eHtd5Rb=!fq@_Z9~`bhbGt7#M&^%kb7j6{^`B$3+wvu^Zmbs0d61CJ z_ut*YVyGxRBJ-N%0f!V8vxFsK3><9RKwa8=uvjaFQS!ZWXl-)+;tt3+S=r~}YpD0Dk&XEDl z-=Xy`&J*Z-mxDY_)%NHpAX%F7+Ov6YNM?54=n(^D4sKtnz`fI|k!DXlXm?JxT!QOd z0WzhVDk>&y4?i+6uKrFz{JuUiwFbk4>iS+~zuVc!m+NTP+U~Kq8mB`?4;NniTGsEq z$TKJs<8+qx1|O8Ysj^)bYd4GAM`3CBOVVq2d6YI*+M{=drLA20VN#F;5m0~08rc4K zn1fAxi%&ALEo17ehla(ekW6~L{{lfbCQa5bl+O{;@$314jWUFUBq{;;_2qDZjlODS+o**rI z&hN-)|IPFV@WXp)V5ybgZh6{sw&UE?*DzZrBD)E&7Nc=+u*icLcV6d)Uk$Ufx>DMA z#HTf}oXPIJgv;@N%J+}3FgE1`2d*D_^952lp8!La%&pP;siEN?+8>`D!OiP|pirBh zDIq!ziP2$h=Q|k*-OV)_i@Aot=_JmxF>8pFLuFVf5yvaXZi6RxOFlMs`-u3judh20 zwYCqx9v-GTsL*cM3E~wHNVNu}-|4>B<>*s!?Nq(;neAIJBsc`MvP&QDO~bAwQNHg6 zEIgWQjtEB}9uKImcT*(=Q8qq00kGYVK*!|93tRzQF#T5fOg z+5o}%#3w6fYdE2v-sI6_mVr_B2(U)H86qTgbwNcQZ)|9%RlkUOLL3-{{7WW-VR^?UT(H3nF=xEZN5ZUgZ|yshM4m5 z5toa_HGp#0@87b>l}_!wAoF-9EX)U7)OID=Z86u1Gig7JDh>}Py!<3{j5gOgr@+Uo zn-JAA1fW31O`HO5O-(Ovrrv>NGsB$DU}8Cz7*(Y^$<3KGMQYbVLWoCXWCJ3b;|EaA zYavZfe%h|CmEMGrUECc}%mdepxs&|DQvJnGcY$Ns_!Lxw3K}y*(091n!wz;zK&b+@ z?+#s$0GRxVt99`(DD%$Pj@HOb6E?8M0@EQY;C8C55y|uKryIS9td6I5f!UD+MTzcd zS63Hbl_wEZMFMcaoNu8vb#KZ#jWf(cPCOG6lU#v))B|3}3=Fj)X~+Y|%uELlgdeP{ zzdN&XD^hzEJ2$jpUs-#elJaz?^y=_HtHvi)q@gM&?|P-MA)`c_V44LccY#$@)5JIC zTUvXLQJKGk_L(DI9vPC!FJr6zg1YrbH-HPi=q zcpVL{b;;bDkh#K|_)lGe@ow-PrzHm-csY>)2vGGbprQg;O^1+L5*~y z$C$M?sAQ_GJplhCY}6lr%VqcGE_8K`+#Ma8(65-{QngD3?&D*!t-98Q*;(n;s?BWB z$5MG-XXaOw&_)li=9x2lZE$?m&tZ19B$Rd)(QIz~#J_e~jOq+a$!KGL|9pG4XT-ID z*=vSz@Fp?`xXT!U5K8NH?E&!f!dV`2X!mMrYRY$~7NxDNP3I3>$0GtqUzWsm;vZG z4FEqBfc$)KV%d~MHAD&ptz##+*${vc2+^}=@hK_oz>nO&nyR{eg4}ClkK08YfZtj@lEL~rRXek(MZ>n6*G4pL zkrpC-#>abnTm?+~p&_0LvTtJg$pa1N^C7P_7-%p&kYi+e+v^0CmKH^~VkG@onEJQL zre%$hyc&$fjNiu0!01(DU=$kYz54^*Q95!^$mU`jZ5}?^-J6FF27Pm(hP!81wLc@wNtf_vFb=m(CyXEOp#RJXB;{B=%e`WyLD@;?1pz4a<@=KN>^SfGWP z-=KICDRaJ)O%zfX{ta&iP!JFqeD$^zJSjL}vLejK??jjW@36P^zk1D%K@pCo5_+b=W_!pH6E2k7F&y9A5T6?x*$K zOpX)*(c@5?bi;v1`DO|kM(UfBfcwCu&P^C?a$EtxbJySmulwWp#-)YK1n@kJDgN&gp zv93j%^;lQ?nAFq~{Z6TRO?`%r#?9ky$AL&_>%5k8=Ujo;wzj9+{nUk) zVijoQ@ZJ8R>CK(fJDdLRL|^w?ej>@sB&@M1Qma-9=RdbmxlnLUp}5W`c>}; z>Q|1f7SMTKcF7{C4~RIdhBNOTR$VVTO1^tH8j&TAm{&bXfz%(e7Jn?0wl^A35H-w! zAeO9;roV7jt?Y=NuPMq&M#{#-nD)5OFm7^b*$KA??~YGZz!(Hqv6U4ox5bBY~bI(PI_13O94Cx<2hLyq24VYTR7b)?Bk$0g6+u{AS{7IDx@d!9xhi%F<#crP4n^5-Da4*%z)ZQ zU3eAhfc~8WM9TjCG2Dm%hvb~W&U}*&Y!vV7UX_m@x6Ey#(9?}qjVQlum>Ha? zC-qv@Z0^a{nvY5s+)GNOZVif{ zo!erdc)!bj$y-Z~vJToeC9UtcJRslN86{rTmSVD zD{P_C-{auZC@Aahrd>GKW`7=|P`$YK=fRKXcWx1Uy*cW+SB_?-l`*lgxyWN?WKnc< zw3aEZ3G;AWzsEKifq&S!yVvBlQ?XW^F!Bf&DY0;w)V#B4M?So8QO${{Xhi4w=g2)S`d0nV><7pCs%BcHHY<6C%hj7~N zbe69_4#I@Dkv7qDqYFYB;z`^qlu*jAY3v>@&>*Yd84^KR_mN4n{$maa7yzIE9zq>E z;@)UykISXCj5`U%aOJUXE97xHEj#g4uQZWH@OG#dy&<1L1YN}r6kuMfXoPU1q zwmn@@)4H5ea|Mb(6jUB%i-i$q=-$2SW_}3ekykJ9JKP=34tyLFy)syL2t)KkHIx-K z=q2p99##GB=Vdoty2DM3L&EJQtx;n+0hVjFqI97`Ri=rJm>y-we&)?Ik4{X?Txt(y z&t2y^^B4xqSEb_&2RjGH-J(TM)QXOQcz*HGL~=5*=S7lN7TMK`s~5@k-NLSIC;u@> zSD->DF-vBT%PiE$@rS3I%N*)6L91jBfiz$}uCzwp^1Ou0o_RE-UlA9DzU$z6&!4t) z8H~pmUz*mi$0{-W>CAZ}Y{qIV9CF@JP-w2ic<}uic9NzjB^7Mw<$zaE-RypB zjmVR+F5Gh1gcukYtna3tRbM0A2S61GM|Tl_oT23fwbIK5K4n$a%@oF6I7sS!jhdRO z&NTayc%dRGfLwWDjxm;3GiO(kE0>l_hCK4~ULMl2uaKH?-WuFWU}ej+`79}$d{1ow z&YCB(6E()A7HXgEIqUinS|Ns7rkuY>$~uKgZtb4w)2u~u=@AgABqhNJ>mWJNv!6%H^&GPfQjp?p6>2p)`j!%^F=vG0pKhH z*3OFvN7&tuivs2;^lZrQ{qS}i!RB1A!X|P!J1_*Nt7@)NJbdD{CxW~Wm9er4+tI3g z-|PuLDfm{T^aj!d?9Gpk4D1%B+09I9bIAmepNIo-=AcehhKwUt_41`nWpD7AqI+P6 z@Rp0+s5z?%YpwH?XGBB}un@X{DFi^%=L`XCx)N zd51#^9X+O-H?GP^;PCMGX4^Sh{=(SU*zcWLGTDdB zU#D1&*JCE;--{ahf%+f$;-u<%WLHP7>}+gUL1!C>XKdCxTR7|`AF5uP$Z&Jd?T)+y zekoFSwiN+URveNO&T@gBB3|>#Ll#90YtJv!TFw_)=E%34WIy4}f(@4iv3Sf8eMTc| z&NXjG^HN|E&ojoY(=FpUChF~X$~J=EA5sn4Al4di#~1jyCyM*UBa&x6!THiA_-(wv zK!7iMP{QhMrZKBxi`Ueh$LBx=lgiT_sM6Q8(r&3ZN{JEcdU@l4ROM~eT{wBdraunZ zkY+HR=n?5d-^bc`0k<{}=b*}35P7Q_ap=jGvYA;BScmKR1~x6_!AwfjWOC&r)udsb zsNUwiZN*B>tC1xhItmVUeYy%wv$$mZn1oDiyxe?1D@D)?6h)FFBqU5^v;8zb-SB~( zo!!jJ$#f=+q`SX=XfVUIySI0+?f&wgNm;NFl$e-U>AV0*p<%?X-QL<_2qk5gOj?-B zJa@~x2?K_k?jR@>4Nq@dUp)KB6o=!v?*~lCFY*yYW!Cj@vF#bJ=Q>0gW*9YBHl10l zX#;uP-ICkWjpC0x!g0@Lbu8b$?(kt_XV1?p7Y0lFiVdaN`qvld1~B7iiVi81rqV-> z*K$HXx5GK+ie)05cIWay^PqydsT?_HNbdPLk4!3??O^Xjf00@F#)zB~1aEu{MPW0K zEf|mCYtO2JtE=me^P*!~(82hzDVg(llsVhzIaYP!&at@hTng?`Ks%q+V~Z=a=`W`C z#k?@m!lv(uoEb-B4ZH9<(hWpJ>dYro!8DOjYwM!Tcm0lwmG~Bno#%Yh9~2d%!s3z# zE0e6EN1rLpxh!K(d+nuX|AM(??>=O40jLg*A@48|xj-tn*_g7wubJl{BNM8%u$POs zQ1zacmbTsjUVeRW239$bx*q$Hk&#s=DDNz_M^*iFdF-^)eKRMOTuL|qJ|nBH9})=7 z%$$C~4)g1CJM5s5(GjT`VC>^qR*pp{B)rHQnaV+vm~M0~O+Ckzqj~9?)DcDkCI?;$ z4L{-c(B9tObauZPNbT~_+Id%$!$olo4JW7S{rmSLSLAYi?~iwkZESq+DhMLv{R1Jf^0i-mwNIyPfr+E$?YGXe(T7P`G&v`OI6ESEE>?6 z33k##8zDN(s8H)&=4ctWCW?ZoJNNUec+cPl?Hi ze4OKJ)>Lh*9Q9<%1%3F~(5I!cbigbPNNE{5UyO43t9vQ@KAYBJ7&mdB(@Y!c@6oB} zB`Z2-{`$}5|F>{vvnJ#2$+F9P;%wcE&?&_=wrJ(nu1Xuo=&0cgJB&!){AtQOsUC)K zW&5gVn76X;!lrxa0%oGTHn9MC%?!zLlOtZ2`&%rn*=OEwtheIH89%<#=AN1g3a;p} z3Rm=K(B#mVfO~;F0>=DtaCk{CCp(*lmsiS}QSE`edsUSq9k+a@GJPC9C#U%R`}=gn z0@AqiUZtSEvYGj&LuRECy(+`R*B2ryHe77_=WNJTf7v`mKv)I^n-`bX*NegWQznL& zUV_|6QB3ShP#l8_yh}0m{?!h@yW?gbr}KGmM;@*3F2%fb$nm z3OzB5iG^>F2(hVW#Kz8fNPB}tDfF%(tA1!{rz(;+odPirF*Z#N&0|VpeSL-CLVx^WYic&jT z+13JxY)ZJ&hlJ{enz{0@<(Je-4X0Kq{Og0@YI7Kn@qpGT^Ijyv^__%FHV}Z{jUOsIFaw5eGCetL+_I(AZJI8)wWyOt6 z@h3j#P-$aXjFK*FfE zlqYy0svY+#eV_EgKF^ zV1j3D+Fda7s~IS20>GOEPkXA2#w0N)&!^^&2$`;dH5mjHEe&2-_XuS|k8&1>uc zg@mucue6c5x4q-)j}zZ8Qp73x-5@+0tCUxPg=@Lc(Ct1; z%eN&zTB_=^@W|2!ea|VRfGU%O*Bdr+^KL=E0VfJ3jDOL`wp{yDMH>%@IT44cQ+;^6 z}NU8Jq3IFA2OdMTq-X+$$pz>MvB;q7#ezF7v3|ru$)K9 ze%<|uw!`b@tb5;1Q2u!-wzpKpF9hZ&WD7V?yQwhXDOHQlQK zSL@)C4{0WMys)(Nf$tc20B1efob($nv^%bkYH4fh0xjY~l@Q7NQeq;d9s@tFME&Ux z(w?u){g-Gc+(-Y<4*^q|-n(7ea3gv77tc%qh}HG*hz};O@gqun9a@PA2417yfbbZ~ zjQ;Of>*YYzpGsSz8S+jX2Rq1zh^A`Ix_Kq^Zu1F9^`qqw`a$#idzXGPySnJRxM+RB zJ@nCoB2h9t_ zFSLtSmuHyqPb|=iP+hrFEkZ@o4G2hieTiXiuGBg{2OYR&zm}s4sM}LJwGE3yMG>uP zfCyui+IU4rPmh{L_iJ+(gY!&j)SU)ytLpmrBq_x(<+iQ3=s?kW-rn9rWkl&Hb}>W1 zGqtv~_s{9vK<$hQ)cO=XFhU|xQ0z&a6-rCQt(o?*O4QX5K$4VH$WWyLryR!x+RU@l(?gX|W!UWESe-V71qQ4NAbB*1MDG+S`>bcCz&@RF|4IEp z#k{kD0YnGfN;a2=+5aSOx+#>QA9bLZ5=7(PmK#_YpY|Q`SsFyH&k7ye=ejmRicqT9 zyF?HLWD*}gKgw~7P4kNEm`5snWvpmzEz+Y-dI)V>0xH`(wvR&J(oHjmXsZMcNnWf~ zv9d{EAm}D36vNnPttPnK5XHq^o3tFVuTk(}L+FLbNt$t^4bOBc<~# zM0P~9%B*8*SP&b{Z2eW0kKz~FDgy$GHt(4n zqQh)4O8*JoNf>Vwa|RD#YQheaWWhb@ zm~%;9rJ)Rb13i0?44yR8h*(vaR|UwHH&ru5;u!)IX6xMp+TdSg-NH02BAB7Ja7v-@ zFf%CxQ+sQ~Aajh{UqS3<{kq$#Rz1nMc-NG-o>Z2iE+7!bd1!CPcaSnoD4){w`v%G@ElTRdjgB9!A+MlDI zkQem6&dhKF@+#^kOEEBDDE0l%NLR2m9?Emp9_Ji~^B+Mkw7d7H-P&zNXd!Fpan?^i zHA{4(7$U8c6AyeSSN34D!+C|WB0GmD@9_cENqWYhM(*tgOtO4an=LKN~g!L`0{8ke;2!4=v=cCLN(s*x1q8bUr=WK#NY z9J+bc0I%=3m4OE2Btr!{Ds`-%wQ4+1xxYxy!~`K|WH3Le?bAvi`uHz@D7Q6u^ys59 zn|c05#XSl!M_?9R^OTGHmlpttKs)eqUvjX3$$g6*fB#w&NRcdN4M9$jMOKPBjpQhv zopB@q#1V3j-f>!1Rz?EfkS;|(06cJYiTeE3 z2}cWJb1+RmM!0X=-5TXXKZ@xn^MKW3t5{)YUqz4(guQqF=aA(4Qw3 z$;YXvcCY-uG0*eAK+w5|U}InjeQ@f?;fvd+9vuFqIx(?w!b;BpRd&mP6!hU3+l*#bX$IHPx{OIi!A)*!KTcP4$yLS6T`WU3! z_GgUXjhsp*i`_K$P#d8SBghFhSqb(=(=my)f){K4oQcpxB|ie!C$RBqEAd*hA{R!t4aVLl;&Ktrk}C7Ip}-oGzSNAI1vp9Qv#aUd)l z>90PRfO-AtynLXIU4fuYtg#F&fQALkAs8UrZy_^2P2R-YIV_AcPZIEisX{#w zkic(m?-C`vYc+11Ih!cvRRHei6@Zq|4_>u`Vb8*7DI7$l%Xl%+Herjy?II}G@*-MJ z`yoxSh4KzxP%&g_9P1_U?h_Adl18A(N(_vMvH9Mx7bj76UyqQcUcn19OYi(%U6TzN zdCVoj!pAF~xh1=+0{Rb}Sp^gBgK(bX=G&_vG&<9{>O-p&Fbdr~ZEYw53lIRI?PPmE zMTsodVFg1vfahz}n1=-HQChezAedF`XIPOoW*cHVW_u9RxGA4HqN@;^6G%O{h-AAR z>81cuThsww`--fVD?rb{Xkt=fiD9c>F=yrH?P?(HsZ)|vo>t~#IDIn`+>JTyjonKZ zH}8{H`;7Vq269YYGxGcRjpnKv_Mn|i!-3Y;j>DpmZ6 z5?w*_yx3@tn3%KfbS^F>xF@PMWQmEij~7Cemg>ac^KNAz%@|vreSWBk2MzV7_ zVdf#8*=bt&+*)ZL0pY!%Z)~h!{UIT}Xs2qtIN2f)0_X`tkjp7j?(u-MXIZ<0Oer#_ zX!{k|BI+xDSkA-1!ymnCM|p+=x0Z|)6?3K7Vm4XtQ@Xv}+&n&hVLb|s9w<4M+~cna zW{w^@I5>Vo*$Vo8LxlU=n3z6pa?t#4sQV0;w4p zBeV0JJfec-zab5oY>w`dG#&3r6l&1d^DIBlw##98s?+>q=|-hrUw1c0@*!Y|ymV>z zPe({)&`-q7qmttwIuesWXZN#?jPS~=2Q3wjOfD8NTPF__h-&eRtE+0->J%|}VF+@> zoFZrLp8gjgM(x}CNIx$-roKIPFPqVk+rn7X_2l# z^i6iF8qAEYxCbznNRW5m!#%u8T#OMo$)J#cFfXm`%RP*G=Iop<>Kv@RzEUmE%iB_G zW{?X;!W1GF)P7(;Imj3oGH#&2ae`svWQ$!PGBM=rj_dnZ*Qkz;4cxKzTGOA?wms|? z1qHRhs3`=}l{9?^B!ey%nDTXuhIiE$Vl4a~L`7*{#J^982Ud_I`$6_Z)kD8d7rA0&(_oznVDu#hcbK#+X4Ml7QqP&{6HXd^Ja>3%pd?@@eEV~$Ep39@{ zFdxqa0H=((0N^6A6z|nF_(=H9m{KR z8_w0%m)rcDf!eCRj8P9+XU~WQH2`)#B=ZTdknAk@yivgtmVjIvZR*;0bgS3^i0Bk` zv5RW!7dR|n{z!x;=b!8^)^RXNaX>g$*sn`^)RuIgmeTESvpc!yh+${-rp$qJQ=cbQY*iVi)}8 z@l}q|w^;Jlr!U=c&gZENKe!HhF;EN#nxj|=WYZOr!7P)Wo}TH780|W4HbCBW!+Msq z+#<@OnN|=!iKPH2io;`H(q-jO{uTMFr##PqgPA)oc>GJ~Chju1s6_p9FQ)n8t5>zI zZ4LnQ6#_y6`q)5OdNd|%vyNw2DscJJvU&9*U|CB7H4&VOUC%n%>sMOk-s@4q;tN>p z07dOlCh!l{`} z>oVyL?Gbv)+Ehm$dutZGo~BJ~vmtC&nwSKE&!_8s1WqJoHc-48cWT}I{_guEqck;Z z-AcI#PY&FGxwPQ`9#xLgA;zeQEmgOmFQ=;Ce40VUT$ zAR-2*b>*!S0Dr2T4ESKM^uBly+BosZCLl$(r+aGCZ_0VKunErr*VavAV26LqdB-q4 zGc^6F#7Gm8KH_rZuayFHV~$Y+$0e}C>L%cdc3(G#cB~>4og21zxVh{V0#b_2c65pD zj?y}Ghd=U&_87IiM_FP@61S~2YLu?aVd2@V`sgmCc+Ctz%bY9;#cJ-%^sF5}*e zJ=d;D3d{2r+IxtKs>TPo`=Fl2#Cs?qxP-MumDLFJsp;K)9)q};)_yY~)wSIq57B#k z!u;xNCCU6pDZd}g=WCbY012 zgI-0vWq2CK)y@0QY@YsD%39&*OUDq(imvqKOmV{QmX}RA`SOSQwpwvbMLl|GRsVZ( zDWV*eCn_Lt)nj(1(0=>a{1?W-bc1+Kc#dO~CR2PWGH=EPBTNa`9PIZby}qgA<0F3( z8R5O1xBN#|nKBX!Bh-^`1VA4KJzr?p{ne|$<{HTfFPpD>|I~RH z(!d*XlMtS|k$7^0bURb7hH1euF~!0eDUfdWI=m#eM4jqF z?)ziZl-%@}g7jR4Ymc>-33mAMo3q6zyKmm#8Hu9SW39qwznkLhYx)&=NYQ_fFCmsu zDZJ7~Wh;+2DQP^X0)|5=wh|&eSB)o@FL){nm_B-cC)DR}o(#^w@+(_e-D;(r(hK0J z6pXC!pclm}%NFG(%F(PnU6WkWL+xMY>h5V;tj8*`?fE)OjDCWjRbaeF7 zjvlXqv?dE`BG|=^qd8$tE>$U8^*0RCPwxi7gUSYfTj{WYl|tXIsY)NPQj6xEXU~7- ziFI6ibL$z;;Ius^4>v1hw^b~^TVFqqq~+*A&4r^(?&`0x?8uS#H^o)mTkBS}NJQ!E zcZD|T`)+l?dpnniksY?#=X8Lv=w4v67Y+XpBfu{KT3#P`a{&K7e{vi-Lr8bK`P%tu zN4-%@dwchXt&sdGt?wQ-K?d-W5DWB$glk`xJWN-nI=j>~)ZVq(BHTrv2`h>a1@?1-pkc&yk-KnLvA@^EU-t7E^iD^BQ&%Rw0)UiC+cO%6lL~_H3 z3*J%ut>!EdfA%%M0gXhhcdGf2#pv|e);;}~P_cks6#;(pEO}VWTtbIT0-$-oqp?$N z2d70SMd{8L>PhR{aR$r>-mvOc%E;4F>kqfSY~V#YmYc|;yNLPFzC{e#TU{v!F|Rj| zS#)<_t30NCd`XozzKz>RuYA$)dT35{^?iTz1ayURibBVx5+06S8PPm_!>c2{v^6>b zGlJGjjW)vZwD?<9nGZq=R>o3_QW-mX4-(kt-yor^;Y^-bI_Y5CBQAde%5~ik?NMP) zT3TUN==KB@{MqGddF7boxyf|+4w;;YH1eKahaA9-1kuX)C^m5DJiyc*%_5mMy0^J^ zG!@2Pt^Lfl-NEkxX6^O)KGbU^=*B9Kgzv<2PTo7$AFR*(>>tSD0NiB3hW-bc1F!rM zwir4(IXTKVi=5`rWJqT0tI0t)tZ&O($qE5v0s_G4qn$G;jXENlK%bVfr8&8Ak=|=i? zH`)Y-{1BzE5OI{^euPTHA3;#V=jZJu0_r)Z2nQ8A>HQH-qBJdSePvvcfK<;1(gfL) zTRZKeZzU$5-@?3a19-;c>lhc38+Lqmf@B0Rj+yZ+szcTO<;4)k&X?}i29NKX<#(j@ z{Z>2+?i>VSR0uG}Bv8x=&zd5MFe3}s(fpDbd2I_Wx>TXF8lBZG_`=`6*bOF~`!PaN zCV|i}8FKY+WKFjOTuJk4e!A&cw<|J;>F%?zB%#!j-ElNBKJPZ$R!fLuX!+DDR zU*k_2<}QjCbaJOQstbAl$TF)c7BH4L|nDDH8r_fzs5E1m6GH+`)4PJVTZ(U z^2lOA@eE3A91Kc(r@-;R(Nt0z4FzxaI*qq8=Z|x+M8|66`OTuY2KCFr+b&J}ll_g% zbXcRRygc)Shc8=J6oJX*Y4kfx+D~+dtdFbf(6!tVOt;T__9@~uQ%4pyKPSL`hlPm= zSyuO_U2>otr*0JZP{j;RMRig`RpEg?k7LbHo4TT$)J2{Itw|3F)wZ4j4g(Bp{xB!X zpJ(&3r!22!Joa9=DPy5NAA1k|VLa+h25rK8dcPeB^1Hbf-EoU|Gk`n(7%zvl>Swf` z{N&ebH=1&tiY306urtjT&JJBM>*7*$*+ZzSay(8tF85`Hhwei|bdaqk&B?8h#IkPP zpW&KAxEC*km%*4pQKGj*BBV+@?fel%#equAn+zBim!>9aDz?3rnBqFyYZrbkbfT=s zk#Q`>%hIg`=4$CjUth$CY3#Y2sGEV*%0>$BU>VQA@<#bpZl6OPa5zb#zHC38{Mzy~ zPBX+&vTH@`^Ljd4ZDpUH*jUTDbc=}d_1I*EP-pp@ESXtZX_ilyMbBo$xv&`c-jV6{ zZ*;u+I&6G)x;5Eo{f-Fb=r6XEJ58^jDYg=XOsu5^ROXX$mh7$l)&x2M(&X9y{m%7k=WxJkAIg&OvLGYi#t{4+vo1@ z=GKniCIv^2hX@H__W3TKJfV`SyEt428}*j79dQh>B^MS9UN{50bM8g_Z>#5-oo^js z-=0uq>`%hOJ4{Q_Y6f&TnfS6-(!}#A@EuY`2Gti|G~>WjQ2{JZBhY2aM;D*63mw!Ye56UGWtp5> zzPs;2Th-6{dqRHN61zRl&)%K#{;`v$sm?3GdMtfMTBZ>7f%8fK{Vw@lKi9Er>PlzN zg$>gYV~dW`!01ONwWpJDO*0W5(QM)x5N83weELv@yAQY%GCgpNn=8XrbAbvRI<xQHHq}tR5z`WEt?5MZ9>5A z9P%eJ(YeVHB+U_sxQ63! zpNQrWxeUG|+%z#KYI3vELdTWw*$B*|Uq5+dHpO!>hW^~Eb#Z~e(gT?oa?hlI#L#fs zVg?iF)8n>3%tDlC`JR1lGh?!5M%#bI$_Y_3yy)MwD>K#7z~#3HgB7Be1cMK(6B=Yl zqRx)Jnb2A@1aCmV-Zoa2+>}{I`{&0N+qY>ix^u7Q?vuqK($YCS_G9+J@X4*wNPKEh zF{ZPxsehQS#DMB)Rbf69x>W8wT))3SV3GECzmg#pW}gD4=nsotZyTs=<1c~#{`2){ z(Cqi4+a#IS%1YaRCbd3FnYUs8&`AXb;Id2Hk~CEvWbHDPHtaoNQ!mzpdQHOhTncI~FA3^Zc4ql2hl|EhU*;#1bVt>*iD^0%{ao@9|m zGb=I3m)RCqMXIO5?D99Vx1sK$i42ol{;J%1oISjEKRC%>Y`hI-7OB>}%+|x1pe20o z-pfmP_rHsLhmB(DaTh0~?I{y1?fy**Zi`)ahp8-6goHCQ})KBL~Zjq<(gbbXAZ zt1J9{z|4Jw5&mwVebqun4)Xhq?f7ifi()rkpz+#n8hezop47oRJJ^d6+XW4L)VZnrI)U3P=~jn7{XpgX6fuU-xGa zha*`Ef35jOg<6N>@9*Y>-n@8t7C1O`<^B%9#f!Yx)Q%ng$eT9|7r2g4G7v0h-u&lg xzaRf4lI|!?dw4ltyl8wJ@mFO3*L8o8slS9-#7OF=D^vPqRYfg@;)j;c{|^&v@CEZV3em`V9hQN6w!&fQYmz z3iQttBxLqIgKt?G6C#B_jtI)%m%i-f7N=5~goK3rxrGJXH*Y>J z-o5(wM}M5Hmifg+DQjyc%g;ZZ{^x5E40f|{d4Zw3|MUax@jE3f-+%QSh*Mq&A3450 zQ;9yt@95DvLGVBAx7@eb43Jq(PQQ55csouPdS}r67fDwmrM?-xzu)eA{vi@re|zgn z=JV(gqn9v3~tqa$ZNm{#XwxXsOO zzsDUqA%|oAW=9&KrvruN-+{z%nZi&)w%qMO87}M&TVf5?OFaV-#K?Sn?JYO!9@Sdq zf>{KFgu_`ck7k)(*P0WV0(>w$kdx)nBo=b^T^${ru@)zA6ZFyMb#4vBGy6OcUq_Y z!`##UfdM#0O--I59>TXAW)pqpoQ@}}&9Lng_gCPDwrePbzE+LtUgu%@k3Z+;RP|hc zBSw-55LJC&oBtu?{qnE@I9g3(k3WcNeCjQ;U};XiTO{9!mV# zn$P2)536Ah0t}hQ5!;`a_n9BLLA6HHX~#n{zZ<)-uyA~Qe3iv4?L;~cybfXNzN)_d zWL}h@jHM+5w$LN1@j$q6zH;-m&-Id7+x4;)OkZE$cj<@KX29dqsZU`^iRI_@Z75TD z+%M@vb1#P)hrew>*ZR++73&`D?Tw3z8kHlzAog;g`YfO4JFAZLj26(6QFU}y)})=! zgZA?B^6j{tx2Ta3C5+JXc@*qolLM7@gY|(+COi*yLw$XM*TeNIBBBvdY=J(iDE=Pf zD1p7J({$NHCfMwvqF^@TfhzN9N-n$g@Lc+*ktFDfI0%4AZr#DQ9 zLxVGqUg7NdQSD@nK{uS9+wPl8w>`pvDDufk?`sYWB+<|7Dj*m&MMa$1LIu!J43%!= z-Nh)28&ocbCnikqE_Q8wmcY;JZOnKszz+P;r8wPI=krZJY{%zEH`9sq;lue-*xPPw z?@c1T`yO$p1`MK`)k*PWxY|o79ocz2262r@uD>s znfI;X-elHfPXKyxdASoQ=44{*{$WUQ z?R;&wSk+zcwKtaH9~d|@H`g~DN2e=lkwp9NKw0Jyn)GBMkwbyC2OtTisyKbZE?>rlK>*+6&;^H~e<~joRt(~9rNBU;#pQHh-BW}NY z0z9GW#HIb(>8rL`W_o9y6B#X7K6_arTUO22B>({8-!g8)htl< z0!?n~p%bRy2d`_FLE5Y*vP@j94cy*T0iH!TtQN{XtYmrLTV#6QA9R6aplYRQLbv5R z>i%-t5MiDD37{}12iEuO?2HWLr0{TgFw1|l3h)MU^>X(u4V@Oey5o*y^e1}`#3gQ-0v=pb(-NVfq5Ndz z(5(+`3aioK$pcmUF2@{iF}|6a-oL*6qn|5Wyb!nG-u!CC|LN6^Nq7xvhmy^aHZDlV z^k)rUNQJ!9!w6W|-7n1x6td)pe*MY@HAZ_A8M5;7SkltcWqR$Y8GJ4+feJ`Hx98g-!NL1%Iqp!+I=qfn8Yj0Mo>_?^U@*u9`Lo@ysQtP>OGqGrft7=% zwdaLGPs63JNTKw6E}eJ0*pc?WuFYz0sMM?td^l;1qLEFM`b&v7$z#r)X}JIvt_p8* z)L~oK17losFFb?)tk`Om1{43wBV^U*<%$2i4{!g<9l8}4+uS%zOia8oZ~mAZNisM8 zKPv9D=2rBk?z9<1M&@}YjDZ9XH5b+^4G|E~B=V)#Sf)T4)OdX-bUdtsN<|`*riivS zF`vuH~jVk6a??6p|4-yt4i-HMto*1gzpIN`1rgZxo_57 z$PW$<{DXp8e1th2y7ZNJIXx3FHUg3XXe?#dAjay{=SXy-j`?0jNg zX|UObnm9+B6)2-D>r+p?Z(8T(=kF58U7(IY+9x}SeC6|uQ_mn!mQTlEJ#uX2ZhCBN z4~x&8ED#LT_sM`7M*DZQu3w8)8Q3{E{uqdA4JOZ1kBz*J4)3a3nm?k)wqsGRwXs(P z7W@=OC)@#&{xhkkI;g6KTH4;FS~I9%8LqdvX)xi})Yd-9L)JY`ufS~+=(HGhb#GysdxGp6KuI2lnYOx4Rt{7bf#MF%-&YY(Y0zqCv(VG4RLs)S^-QGl1wu zlD_vaJT`w9w$H676Fxd zV_+uyr}-A+XemYY6NRWzz+e6TbvcVv7d4{U{L9(-!)FwVzWF?|BaA-{{#q*Nr6St* zZ%Y3DMl=}V=zo6ne=9<5@ZXLoPA;N(<1#WI`M=Gf&+6Rt)M*_U{`X^w|F`3dy7&eePdXJ(N7+@@m{Ymc%Rg=(3!u zcz#8U(C=XmF?obEc5a&aEQ=l+nC+Lud8lI0TKN1;&8_Il&B;6VlenMtZb@g(Hekc^bNYn4_>=^g9gEEsGxfDhpU^id08$z^{Tinmx72ob z*&iK}9(c+3Xm5{IPws|^2Cf?PdHT~{>H|2`ITXm;UhZ-??=%ZSQo3q((Nj|SG5lIG zT0-F>PgVxcXYe}@sf;F`_&>I$3$@q89geQ_?5$2>s}DMS-UdDMi}-JJ%Mh@V?8mX0vh9y00%OYkJ=53&`MV%}Vzs`fQy^q58<+zNJ;~8?6P-_8#IvZ6rP` zfy>4YxPipUftN9t_VoKJvs4;X-!k%~=+0V{*#SM}JGR->!eJI3gFy>034FI#`q+1`AFFh^|3cuQN3dVZ&xvQZJvf?rnqBU_yIOs+-xW9+O%&{OeeryceFRu-u35`14jPs(YqsL(b z3wC5F^)dP^Nw!1ci`@7hZ>cZzo{5oD-8&S%x)P1rX$reLB7|0lsz|c1Uktq3X}d&P zD(I*LT0J4;G8f}&d_ z*5-O+L;7m6V}%%g=H#OVq1-1E$npZ(n1U%A#|dkz4s5h}BLAbVM8<=EU<@E787HzO z|7Tv7*APJfvunxrNssAzFScpY`QSvaWY9Qh`c(cMXtxu9?f2;l&0oq!kO+ zCq*@8yt}qKSt+cZwT{HDU}Mq`!xQ= zxJkHML$~Y4WMiD}u2+;srJ#PBTVeoh49VKqa3i3|8i`d!#Wh?3ZB1uRJ1LajT5~L! zt(#=X0(qW(MnZRIe=vbbFIwdFS57#pfpCgnlxdXcmyb-BI)j}X*jIOoi_$<8ZYWiuo@8NiLaTxS3HewV82 z4~&)V4N0=F;Nv&L>3pX~Ef&PY`U2vjQ&NemZgkSBG246lDyQuuk;&dz7Ag!w6U}|b zgtH+6HH}n7InopWcWzK4Xv;ghIOu}@Lyolul0{5wkp&u+lN`#`RnD(SwyMr6b- z9GW2d>nrrY@^D=~^t<(u{9hUO!RP|(H4%7eJ`<=g36WK|sAYPsiP}UjLuq%ksSpKw z!+FY??VI!d{z#6%$>f2v*u0$E53Qj4In^Y2-Z@z z+9EuR(~NlQuk1e)2ZZpRdB#WuPabBHIs zr@Fd!#xm7z_AzB|6=H-&vK&rY4CL*tKWsi?ZS7U;{#e0xc>bjQ{_Fh*$cffPl%sP$ z1H0tOZmlDtBAcTrQgy%s-(lMUp7(QBmX4s75nU4=8 z>7PV4fPB?=AWjpntX5U>vlqd3cJGeU3lx)ko10qRowwRtK2jk{l8-y$a?-;lg)7&D z-RXiWazS;3+e`Norh+VX16y`HMfGCTW8Y+q_-F><=1s;M&59aVfk`gzcB4;2(b}0O zE(#B=Au0W-Z+f{Q_xiURmgs@E=S=DB_@ZA01y1YqTp@Q`1L4=(;LKm_u1A!itqJnG?LnR53< z4A)-n=_P6$$Cw0*9lb#;lHMibJn|OSyo4d7l~;JPDJ6oU*9UsfA}7WC%joaR$hx7M zC|iN%V?*%TgDfErEh010KzK-DZc$UzXO&Yqx=sUy+1F~5QIhk(AE60}hn@|j&3#P{ zd3=#JLSH5{;Wzf8rIIJ0(e6elra(GuSX|R!tkL`}DfT*evtm-rhyP z1#Ui3M1;-Y*OIQJsQPnZG+Rj|d=^>3n0fcA&Pe8D4O`?FGsm}?E84y*0x-X4he#X( z{R;Wq59Y`O#`;4z2!Zus#JiFWfU;b~uz4|YXRJYJT_eWHTYu(s#4?Hsz6)@nO}0Cln$ zl@yMSlqd3|tyLPly=z*TD6g@JpL!i`9+n(QU(M`5iYY$byg(TFc_G1}PWR@8wEMDG22kTsra4bbs_b-_tBmt%Y2D zA>pL0IlK1xUHk&6^gPu+`L@(&xaK~1-y^J~M7SK!Mh)AmtUUGljfyc#eiMHP+k2v^ zLRQ+GfS#TX_>uO7>cZ>&VtKen&1Hy76rD!G;vLLOrtp}DrT>O_fGw4wqYTZe9! z`T&1qroQmP7-vUOnUU6j>+%Jc^3+NrOw{hQoS_nx>|nTrnG8j9n}6e^RdPLLfRb=D zZ%ty4!V?K@!5qN+fz5QSHa6Ut+-arQbn%zTYLh)Km9Yrwv<2@^{9Q8?M6D`^>pqJP z=LZaBmw2Ukem{$Dv%5_>mhPWtC_T&(oZs&QOE@{F#LAL@04hbB;_^3mzs3ATD~+>z zseIWwj*p_wg9lx^Bl=T_|%dabP*g*tyP@YFg=Rl4i$*eg5u#9O=R6kp@hKAqL{JP zE3hY64ZX6>C(=9UxCj0LaTkRMXtbZ%vr@?7watTxvmWT;AXVb5&NpfX_`++rB>}KWnlrFmM7p z8R?MN(9sp)@+A`guTTLP{DT;GWlkgB1L=WJ7q~pyd^Ly zn#||G9u01sRf2*17??^{O=lkrI$?4-gMHwm~AN2+NuQOiAnOW8|U47WIoLSx;XwQ%jtkI_n7b zV$HM_?wd%>>EuO*eWD=kh_n&oZ~bP1xqNG7{YwB|u5FoV_o%AcCGDSuXK`s2h{p-u z(5$H$w8g%dI#Eu?Y!a=IkbdDDPq%~nPPVL_z0%GQqIqEH{CoiVt{1KNkv5F{Lx#Lq z1p$P`U1?B2XN02Fks^g77B!O_YdfSZQv2myYsG9NY}@H^LD)B+ zOP=>p8xx1&{OjDNm#6NTBYDLkNb~eYdA@-sbgdRynakneIhb&e>QUXNczf>7(0sBR+i2r!$qo z-|&2g4zXJYl_{*{33zt6LYTl3Y@d{3+q2>To#I-KujA+LVsjW%!`_!@OD`|JuM02R z>1ids@ex&GYgkc9E{6KiYXMp3pC^x}!yatj3CVh#K~f0bkRn#pJLd&Q{wx4^Ii>uS zOTYo9Jx?&Mw-$I}WrP_Yq`HNNuw~C8;$?8h37iT5$J=T!`VD=5zGm_zUtT z?2Z-~?q4C`4d4gt-@0BtdEPXj5AqlijgC~B;p%&*3l_#-wDex|L!q(tFUt0k=c^2JpdNfQv)l35ZT7q`;W z$77Yu*DYsj%ho=?Pb?3}&ulUv%A+4 zSm-oT1ae|J?6Zk$;tStMInyZ%ITPgDlro|nj+jVwlr5Uf(sC$0z``)qxy`xOTs=R@RTHNt*aZa(t5!GgkuhQ4P4skhRMlt;*xd9 z<%9}O`a;9A>?>a`k`G(Yh2oV3c+~_1{m~IMXWwmK1{`Np0`cCIiIzrD^8h}mnWmV7#I``PM z?xEpFgnq8XHlAzyI)=sa?k1dx_itRI;%%2Wo!0QfCZ!jU{+-YQNix;%+mNuoWW`DZP*rg%veeJ*yD{&bc8^Q75!uGUK|7hO; zt6P6s+Sfv#KqBBB;`XV}(N-Zdh_j6z+k-8@L|878rQqy>H8r%yD48Q+E4OCd@7ua* z<8PEK^6{N+o@+9Zqbh$UTFR46ZW}3cI`bJ^d>6R6!YgX{)>4%K)T=Zd&TTI9Iy3F5 zhXN1;?%*7_4{lFI(>Gu@rYJSXhPEH>OK58A5c=sB=Mwqj-MnM8_IhXxDEHC~ z1wTk@hllcKdfS?xhIX+e@a4VF+4Agk2O2v&ab=AO-InfDBhG14(KA=RzkIK(eBS*y z#lLYXIBz43B`LvVGB3bL_^P0wJSQFR)LSA`2u$pD>v?|5RGay2ayo@crz$W%WZJ)& z2@3Q%-uFfvF&$2h7b&W#{-gr1Cq8a5mIg8AzfKHfHTD!341FYjK5iCpSE7T5Ivxo= zlWpjO3LMsV0-koRZyN$Bu{!RGnYjNJpcX|a@cW+G#`}d?2PKNrxN{)N*5z!WQZQ!- z^4q9r>X*X<_5yC*Rb!izawaq%J?%5ilueR*{2;@Ghwt1uPhg~ZdnP4{%#`$A@Da6!mxf(khTajVY3#1u1WvmuTSkH;fD z%5>Z@%&kP{%)H|BAd9nUZgVKGsyeY0Ao$f$(azqqJPFCUy>qC?=<~R_2IO}F3DJ-s z9IAigiP2=vMleMpv6|8KKQ%2J5VVPkxofk^`4p7Fv%kPbXW`qCS*!g!*9?)Km$X?~ zNe)^fD6ihm^!~|r;)uB7g!b_n=Dx9Fh6Zd(eUI0@7U*nBqz>0h^l!%j2e(?oS40 z(oGas)PK`Gg-cE#T3Q@YlBz|5HE3XFCZ$+9zwh^5%sCE@?Rcy$l#^k|;E{I113K9Klh;#xg)&316kbkL}po&Uo?sju#875(x! zzLuO#5+4JCnusUKK%$z~2M`T~U}Q;H5)2CZM;tQzLw8aXitg@kIg;*xEnFDNo}S>a zh@9*yZUZ37ud&Mm*n{@`=DU@m0%o`~MvYJR11PwAL^_qFSSj>(O!n=ZaS|A()#H}6 z)3yybWJEF=8uZ=W-8yZqG0;Mb<&~9b%X(GavToZO7IOkql$`##g^e=!b>M7l0Hj?W zGU|o=I1;8+PHA#!9*c_?jDn4vy=49?w>5JxoQaoS3rBp^F{ zE#7Tf`-+TA9-3z{OhRAPhBDFQn+yY84K}7C7HqEO#hHur>WeHFXoa6#@Y{?t-A}{~ z_n&jdEawvPtw6J@#(!k1DU*D9><^s zADRWOBRb`MYhJGfMd~kwc~irgHEe!lG`1dY(co@v!r=JJ!l{!WCyJ!JK#Y} zUOJEqe#U8ff-Oz+`*16O*$f*4VLn1Viuc;#^OiJBoZ$rp5@l-Wyt>iqARa%bt;4hb zFl^o8N_byHq>7IO;c)U2w*o8#B3WZd2;Co&()vQHwc`0ub1JRtMHLuruoWntH(eA0 z-3n{JyDaN*amxzJ*N$A4C-hW_A`;_wsL^CcV1hBQskltv5`@aOwh8L19;r{s7tZFK zv!n1%g}o^$U>1R2bg>ozOhzeTk|X*mtAsuoZv=HaV=p+Zuk#oraq=cKhb85p&luMy zFSU9xew=?Js9T!bQZNdR33OGj!per`lH=oFXH%J*vEOl(Y3D1ydW9cKLG7xzmnr0v zgX24L7}id!wQ zyRa|YS--*P*uhrySa6y4^W zM5)?Zy4}2mk(th$7{?0>^UzM7h>Q-HsO)ZMK0J})mO@q5@oh?WCy6yjn!V>EQ8GNA zU5=oPUvqUHDYu^!Ov^igFg4Tj6XK@~ItB4BUod-v^aEsfhMccV)@#w)nTd~Cn&KlQe9m<|wLIH-9+|k~ib~f`rxPc^W_S`(?ut7## zzYzs5Lu_{TyX9p$fB&W^N4|_9LC&sTg8;scg}A?SVsK^7#Fnj?rE3x(2L@y;(!I}( z1{sBSU>#}$0-OwZBCT@#1et!;awOwY)Y|?n7dB+DpH4TgnH^n%Oh7JuUFtHcb{RD^ zn$K@R62U|oVJP+N2-GU4rPhd&C@+{^{FzH1E+dm&RztMEI$MFD*sO=0z;n!x2re)k zGnE;zhLI-|5_vwSQg=SA^dzW1UG!I0Av$}6ZXI8!M6Fo)(1-dpHF2vGlYF#PO?tYz zTE}EYf+{*EL~#1CTvwiu-q|;pG}-3xdjRl+mI_vI84|YNb$Rhv&Xo zsh9PM{rxN7S+fae)CC^RaZ~v$o>6FZLAaa@$ssHsJfPza-nK<&qvXBI*=jr=E9qL_ z%R?Q?*C7;kd=``ICfC3KL_@X(C5LMi{iE^10gULeafP+XBSm`tTb^-L4r$ro97}Lm z+tqoZ-MZ$6}-?A?Jsd668*CUMw_;7+@9%oko?oHyXj>Dcxz zFBY#lZs^d``sdjeZ|<9sUx@$S?d}(Lkn1!=M5m+1CAH4!IHKkge2V=^+p2ESx*y2# z5ZS6xBsf;!5@|n8BsP%@7VC6eP`p82U zVK@0+*9GxiGaTHjGIaU=MA{0W#9JfC zKHG6e1u|=~W{sr~wx0;O^9Qv*Tt6f<O@c2PD0pZBunaq=Q{t4SroWn? zqCbcf%Ao;zY?kesC@#jQv8&tw7V&;_d(b}cOQ29bCsqzzLky06nBa)}K2HFvRP0dzr zn+@XWFXL)5Z{TNbTcf_F`n5ssJyh!gqHAuk7S>I6>w(vf(&y=1z6{nFr5z587GJgR zz=a@lHQ!{-GEKH{j>?ySm+}UKVDibH_r`3hyMV)X^`%#{Hd5A#RA%aBlz<&QK}L0$ zH&kY(IND8FI*kIQ0=w-XLO*SVzx`IvD-9bpRW4?=w1jB+6+RaBX)jnbg^DDKYL6T8 zcbuH@A$S8fi!OUdJl7{Hr!Jb8&Y3d#B6-s|BxO*yVAja8*XYXTS~#J^7#70?Shr-o z@5g(ug>>+pnvVANO&wrQA8G~WEki<4@|yGCL`cXgi+Dz5I!*8#KQ>Q%MmU12k+J?} z4Gf_7+(|F|U|0bQ-&Y?8yloz~{~xPs^CzO*6-c6=8`8|p&T*Vp$A z49u*rM;4byEZ-wl&N|Yg)n7$mK+TE9#&iSkWJ;m3)0p^M(pQmcE7g`xQdOC1EcgW| zbCw-2T;c(`<^2UkXWm_vcAj2j7|YQbE}X@+$pnQ+yKq4_Nz;}AKc9G1C$yxMXLL*> z_=gtcdp8&(PbhC@c0)c%j=$rOK#X_%=0jL+IwX)hS@Rw}_%Qx?^*2#f*7oK)yH=S! z@5Ac|jy*g9z=t>&d_ZoJLEZ42gr_IJ*+lv|15Vv>yHCqn$6;h^8w-x_Q1KYy#@MsE zv%|@SfRg)>F|W|XNR%~5LtHYdCG?1&!8Tb~ydLA7BPMpV%oTzia1%;<;TkWT@uL)wetwg z?L{STtTft$F+z&;=iAq|k`PJPA&-dGIL}JDzvHF~NlcX$HiZS8Tt@>J4JB2q5}@XE zRt;XpMy+~;LPeZ!MlQYeN=JsG-;bT0n2KW)6KF9~2WdXJfd4(37>`FKPtfqaOulmK zjDwHO{%H$L2IO?C65_7DSJitA&goLY+21**k{eErkT1!2%LQkkn?XmZTX z#3c*(AR@awHII6?>rL)h%<|Oud~)NFd};~gb8hv+k3$uAm|9qX8kKx#@mkuNyp31h zWc$Wz@tlt8n4vR!^{JB)!77b1PEKgv20uV1pOTw?2wFVN(%n+uSn2TeIFX3G*zAEl zRn3Y3v-ASfwJfQ?@l!24tJ`uASajS%$@2Y}VH4A)ttPMH(X~ zi(WoaVOw5a2Ztl`?H3U*;*nOk`a=A?q(m-qRm0R)I75A%5rTTx2Ws{jV?CsC*M;X> z!UgA!Cf?9}l>nl9)zKA?LvS@^4+|?15|C0io|?IAf!sT+qF9RQ8Z|%{Gj}y z{Qn2nw=>DS+NC&cM>z5<|IZ35(fL_KepL+Qsah$gw?wA+s6{D%eWbkwhIG6{N<0iBSi1Nvx%jB@Ij6P5eLI<1Ej3)C6W_9G$!7nfumGHMXm0kr zd6fn;)T64>C|3pE3d+?3+^4E}t&D45m5w77nSW};BP2U7wKmkuu5JPTyVgAN0o-3Vp9QB@Jv5fa^MglX!uqs_VAG!I~Ldp4Dac;PinFR>AIAS<^3t4y?uqM zmw{JB=KSRgXOYA!Vno*s@a0AGI41**=lKj$WHY+Fq`Q2p^&pabe))E$hmG}mT^c*^ z=^#7DfLt5x`iG3dSUgk5-Dmv0jkZb6(w|!uB_DR9b(^Ed2!|0ANP^D$Hr}67hcI@5tD@lNow_h+4EdMAdBxF8d)+Cwc4Z?uJ0 z0>?*DArCX^?@F5U6B@3aJHwXdOlhS+-c$Ng_N-sVnftK9a;t|ID=DfcC>k)wB}lLW z6h-L^8W{CYc-fLzAaGnYNZQBU0(}JQJs2{&ywj3)!9f$m+ zOn}yS!dZMHBgF$db%O_X>jYq7eqsJjAV^o*f-p~#2JaQw#8v==$7_!%$r762?C&37 zwb(-F5fQbpvKz$N64Y2qqxeX@pywLjTOX(SHmxTeaaf0YQL5+XcU)Mjeu|pWd+z2- z1W7Qv1`bDR3M7`>r758rQqbZKgylx7<@s)b7PduO^G#DQ={kO@B#P4el6p_RuKFPh zdDVH9-QQnP1rSnI#afvZexiWgTHW{ffKw>h)X7a(!*W!fwWyGq!b?T*J0G$fJX> zZ-ux4kn{D&98DRy?qo;0NJ$!L5oh)S610LenYDplY;Z77J;~4+@$@hFJ(L)TYg6>Q z>)~tr%hkBb`r2ye^V~mC1&}e}i}+IZNw|LJsgjqZWNyjM=hM3|zhWBZsPFa=ui zzjm{Jb2?siT>ZKYyWDDWj`W3#qbrlKQoj3z7dbjZxDC~)LSXz8}I=}qRk z&vtS*5x02kY<*ndiwuUbWSLi7nF>C1G?2}oR$iHsI?mS%%<>X1Qq5Mf(h)d)ElwEy z)u{CbT7_wr(Fs`OA;H2ToiLp^lKT_QX7JPwvOygKs4n;wDGmGXWzM2`gaul`BsvaFU7^BRolFc&#-K;!HlH zro|1JCe~0d2Z1-*Qf@wZj-|wsM22gOlz6DIQ_6kO7UiK^e+uVE_x)d~fGaHTC!i=N z{9di(u&zK%cq|Lgv{{re)cqgX_z%ujz$H3&o!L9t!ooT2_6q96b=vE0z7O3Zswts2 zu(Sf#o5c!2VKf9p#NSZx(ek7DoaNu>V)Uj#85f^vnBV;XZ5Eu9yv?zx*2?m<@<25eY&oB=9?92l6A%Eyd>+_oX^Qt??r4H^*yW+mwCZUyo34BHb|NyjgoRwUnH%f+ zopx*cCKm+@!aGmsC5PrteY3u53CbvRci-&rfPuO$3Q0U*+5|iB1@C2-zD33hbLUt^2YDPjkN`tcdn{@4Z>N$0(Q)1g;B+Ou zylE-1Z}A{Ju?CC_#SHFRro_xda|UM>e<_uEF{Cv(a}21> z`o^1q3`jpKP9=DY4Eg=f^lQlK|ji}i5jl@_JYlV+x23DCZs$=$W@l@)aNiWmzA)196t z3_;9h_8*+&(4cIXSzf+D)iD-0Qq7@C0rV=HM##QXHtW{#iGd8>RMQov;|pcNyL;i- z*w{b|vw!+|rI9Fp{}jpR?MM)}Qp$ZXo#yE+XuoEi{||Ed78(fBspx1$_8So%m0|Go z8XHd?exK9HjMhVOxYDrmL=zQzTd5fq^hc47L)0mR6K8)n@1TE8JXLbid+vUH>2IwKVIw(5smx1^@z^Xm-n^f7qCECzXKvYa4SkL``)osWB zeXil3hk_}@&~XS&-yT5^CHDC?+y90`^5X~noAbc%GNkC`gL6J!=Ek6Kmt#-Lz}ZUu z4jWf&Q^oP01dXeBU?19l?oinL|DOSTXAZKUXD;z9r?%}sYqw&A{pZXiQkruW$2#emtHNB=cLTO{pZN6relc*8>)wNqq5>6)@G7D9N|BIBYnZdbFqa>zE^sPo zeT~Y!?DPPCkuA75NV@aRAjW$m-}+U2JfAnK67|+g_dldv?znVKd_NpY5X;>{k�c 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 literal 0 HcmV?d00001 diff --git a/docs/Development/IntelliJ-setup/02-06-application.png b/docs/Development/IntelliJ-setup/02-06-application.png new file mode 100644 index 0000000000000000000000000000000000000000..d2f4952ad6d3447c3e16c68895960aa69eae08f4 GIT binary patch literal 52108 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 aHCBf59rGumF1_eloRpZ{yTZ4+-~R^(U}T&C literal 0 HcmV?d00001 diff --git a/docs/Development/IntelliJ-setup/02-07-debug-setup.png b/docs/Development/IntelliJ-setup/02-07-debug-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..2fb6e0a0b7ee9f75dbe1f6c03a67460500f1097a GIT binary patch literal 65952 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 VeROr&XrO%7P| -Xms768m -XX:+UseParallelGC -Dsun.java2d.xrender=false --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.desktop/javax.swing=ALL-UNNAMED --add-opens java.desktop/java.beans=ALL-UNNAMED --add-opens java.desktop/javax.swing.border=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true + + * set the **Working directory** to %MODULE_WORKING_DIR% + * set **Use classpath of module** to: forge-gui-desktop + * click **Debug** + + * -- wait -- + +If all goes well, you should eventually see the Forge splash screen followed by the main UI. + +### Adventure Mode debugging on Desktop + +Follow the same steps to create a Run Configuration, but use forge-gui-mobile-dev instead of forge-gui-desktop as the module and directory. + + * select **Run** from the top menu + * select **Debug...** from the drop down + * select **Edit Configurations...** + * click the **+** in the upper left + * select "Application" + + * set the **Name** to: Forge + * set the **Main class** to: forge.app.Main + * Latest IntelliJ Versions: click Modify options and check Add VM Options + * set the **VM options** to: + * **(JAVA 17 and above)** + > -Xms768m -XX:+UseParallelGC -Dsun.java2d.xrender=false --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.desktop/javax.swing=ALL-UNNAMED --add-opens java.desktop/java.beans=ALL-UNNAMED --add-opens java.desktop/javax.swing.border=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true + + * set the **Working directory** to %MODULE_WORKING_DIR% + * set **Use classpath of module** to: forge-gui-mobile-dev + * click **Debug** diff --git a/docs/Development/Network-Play-Rewrite.md b/docs/Development/Network-Play-Rewrite.md new file mode 100644 index 00000000000..f0618644751 --- /dev/null +++ b/docs/Development/Network-Play-Rewrite.md @@ -0,0 +1,44 @@ +# Why Rewrite Network Play? + +The current implementation of **Network Play** relies on [Java serialization/deserialization](https://www.geeksforgeeks.org/serialization-in-java/) via [Netty](https://netty.io/). While this does work, it is inefficient, transferring large amounts of unnecessary (duplicate) data. The transferring of duplicate data has two negatives: + +1. increased latency +2. increased bandwidth + +The increased latency is very noticeable throughout a network game. +The increased bandwidth is a potential concern for mobile players, not everyone has an unlimited data plan. + +Testing of the existing **Network Play** implementation has shown an individual **Game** transferring over 300MB of data. + +# The Rewrite + +The rewrite will utilize [protobuf](https://developers.google.com/protocol-buffers) and be approached in phases: + +1. Lobby +2. Match +3. Game + +## Lobby + +The **Lobby** portion will handle: + +* Handshake +* Player + * Name + * Avatar +* Game Rules Selection +* Deck Submission + +The **Handshake** portion of the **Lobby** will be responsible for ensuring that it is a **Forge** client that is connecting **and** that the client is running a compatible **Network Play** implementation. + +## Match + +Number of **Games** that comprise a **Match**, normally first player to win 2 **Games**. This is important, because it is not *technically* best of 3. For example if either of the first two games of a **Match** are a *draw*, it is entirely possible to play a fourth **Game** in a **Match**. (Need a judge ruling reference on this) + +## Game + +This will be broken down in more detail, but the important bits to hand first are: + +* Phases +* Passing of **priority** +* Notification upon receipt of **priority** \ No newline at end of file diff --git a/docs/Development/mana-conversion-matrices.md b/docs/Development/mana-conversion-matrices.md new file mode 100644 index 00000000000..9a2383fe67e --- /dev/null +++ b/docs/Development/mana-conversion-matrices.md @@ -0,0 +1,119 @@ +Spend as other mana: The easiest way would probably be to intercept mana costs before they are paid and replace them with different mana costs (so it only changes what you're asked to pay). + +Effects are: +Celestial Dawn: You may spend white mana as though it were mana of any color. You may spend other mana only as though it were colorless mana. +False Dawn: Until end of turn, you may spend white mana as though it were mana of any color. +Mycosynth Lattice: Players may spend mana as though it were mana of any color. +North Star: For one spell this turn, you may spend mana as though it were mana of any color to pay that spell's mana cost. +Quicksilver Elemental: You may spend blue mana as though it were mana of any color to pay the activation costs of Quicksilver Elemental's abilities. +Sunglasses of Urza: You may spend white mana as though it were red mana. + +Generic mana costs can be paid by any mana... so that's not relevant to any of the conversion matrices. + +The left side of the matrix is Mana in your ManaPool and the top of the matrix is ManaCosts. + +For Colored or Colorless specific costs the tables will look like: + +Identity Matrix + +| | W | U | B | R | G | C | +| - | - | - | - | - | - | - | +W| X | O | O | O | O | O | +U| O | X | O | O | O | O | +B| O | O | X | O | O | O | +R| O | O | O | X | O | O | +G| O | O | O | O | X | O | +C| O | O | O | O | O | X | + +By default, there are no restrictions on what mana can pay for what. So this gives us a restriction MatriX | Of + +| | W | U | B | R | G | C | +| - | - | - | - | - | - | - | +W| X | X | X | X | X | X | +U| X | X | X | X | X | X | +B| X | X | X | X | X | X | +R| X | X | X | X | X | X | +G| X | X | X | X | X | X | +C| X | X | X | X | X | X | + +Since these are the default matrices, we take too mutable matrices. An additive matrix (that starts out as the identity and ORs any modifiers to itself) and a restriction matrix that starts as completely unrestricted and ANDs any restrictions to itself. + +Once all of the modifiers have been assigned you take the additive matrix AND it to the restriction matrix and that's how you determine what Mana you have can pay for a Mana Cost. + + +Celestial Dawn modifies the table to look like this: + +| | W | U | B | R | G | C | +| - | - | - | - | - | - | - | +W| X | X | X | X | X | O | +U| O | O | O | O | O | X | +B| O | O | O | O | O | X | +R| O | O | O | O | O | X | +G| O | O | O | O | O | X | +C| O | O | O | O | O | X | + +The False Dawn table looks like this: + +| | W | U | B | R | G | C | +| - | - | - | - | - | - | - | +W| X | X | X | X | X | O | +U| O | X | O | O | O | O | +B| O | O | X | O | O | O | +R| O | O | O | X | O | O | +G| O | O | O | O | X | O | +C| O | O | O | O | O | X | + +Mycosynth Lattice + +| | W | U | B | R | G | C | +| - | - | - | - | - | - | - | +W| X | X | X | X | X | O | +U| X | X | X | X | X | O | +B| X | X | X | X | X | O | +R| X | X | X | X | X | O | +G| X | X | X | X | X | O | +C| X | X | X | X | X | X | + +North Star (although specific tO | One spell, so might be better in a different location) + +| | W | U | B | R | G | C | +| - | - | - | - | - | - | - | +W| X | X | X | X | X | X | +U| X | X | X | X | X | X | +B| X | X | X | X | X | X | +R| X | X | X | X | X | X | +G| X | X | X | X | X | X | +C| X | X | X | X | X | X | + + +Quicksilver Elemental, the table is similar to False Dawn: + +| | W | U | B | R | G | C | +| - | - | - | - | - | - | - | +W| X | O | O | O | O | O | +U| X | X | X | X | X | X | +B| O | O | X | O | O | O | +R| O | O | O | X | O | O | +G| O | O | O | O | X | O | +C| O | O | O | O | O | X | + + +Sunglasses of Urza + +| | W | U | B | R | G | C | +| - | - | - | - | - | - | - | +W| X | O | O | X | O | O | +U| O | X | O | O | O | O | +B| O | O | X | O | O | O | +R| O | O | O | X | O | O | +G| O | O | O | O | X | O | +C| O | O | O | O | O | X | + + + +RECAP: + +1. Start with original matrix (each color/colorless being able to pay for itself) +2. OR all of the positive allowances to the matrix (which is all of them except for the restriction in Celestial Dawn) +3. AND that to the payment restriction (just second half of Celestial Dawn) +4. Apply the payment matrix when trying to pay mana. \ No newline at end of file diff --git a/docs/Development/ownership.md b/docs/Development/ownership.md new file mode 100644 index 00000000000..52aa826e194 --- /dev/null +++ b/docs/Development/ownership.md @@ -0,0 +1,64 @@ +Everyone is free to work on whatever part they're interested in. This is just meant to be a rough overview for new contributors which areas are currently "understaffed" and could use some love. +The "Ancestors" column is basically there to list currently inactive developers that might still be around to provide implementation details on Discord. In that case please use the Search function and make a decent effort to understand the code first, unnecessary pinging for simple things might give you a bad reputation :P + +## DevOps + +| Concept | Owners | Ancestors | Example tasks | +| - | - | - | - | +| PC Releases | friarsol | Agetian | - update Maven dependencies | +| Android Releases | | kevlahnota | | +| Sentry | JaminCollins | | - watch for rare/unusual crashes | + +## Ingame Engine +| Concept | Owners | Ancestors | Example tasks | +| - | - | - | - | +| Ability Effects | friarsol, Hanmac, Northmoc, TRT | | - support new mechanics
- debug complicated reports
- improve stackdescriptions
- research rulings
- compare with Arena | +| Adventure | TabletopGeneral | Grimm | | + +## Magic Metadata +| Concept | Owners | Ancestors | Example tasks | +| - | - | - | - | +| Deck generation | | Austinio | | +| Netdecks | churrufli | | | +| Editions | Snoops | friarsol | | +| Images | | kevlahnota | | + +## User Interface +| Concept | Owners | Ancestors | Example tasks | +| - | - | - | - | +| Desktop | | | | +| Android | | DrDev, kevlahnota | - test new libGDX versions | +| Localization | | Alumi | - update card translation files
- update engine text (native speaker not required) | +| Sound effects | | | | + +## Forge Script DSL +| Concept | Owners | Ancestors | Example tasks | +| - | - | - | - | +| [Card Scripting](cardscripting) | TRT, Northmoc, Simisays, Fulgur14, Dracontes | a lot | - implement new Sets
- clean up outdated elements
- apply Oracle updates | +| ForgeScribe | | Austinino | | + +## Modes +| Concept | Owners | Ancestors | Example tasks | +| - | - | - | - | +| Quest | friarsol | | | +| [Network Play](network-play) | | JaminCollins | | +| Gauntlet | | | | +| Draft | | | | +| Planar Conquest | | DrDev | | +| Puzzle | Agetian | | | +| Headless | | | | +| Adventure (Content) | TabletopGeneral, Simisays | Grimm | - balancing | + +## Artificial Intelligence +| Concept | Owners | Ancestors | Example tasks | +| - | - | - | - | +| Effect APIs | Hanmac, Agetian | | | +| Combat | Agetian, TRT | | | +| Limited | | | | +| Simulation | | Myrd | | +| DeckHints | | | | + +## Miscellaneous +| Concept | Owners | Ancestors | Example tasks | +| - | - | - | - | +| Documentation | | | - update Wiki | \ No newline at end of file diff --git a/docs/Different-Planes.md b/docs/Different-Planes.md new file mode 100644 index 00000000000..54b0e99d0d6 --- /dev/null +++ b/docs/Different-Planes.md @@ -0,0 +1,40 @@ +# The Different Planes of Adventure Mode + +In Adventure Mode, you can select different planes (AKA worlds) to experience. The default experience is the Shandalar plane, and is the basis of gameplay found in all the others. The other planes are community projects, and are in various states of completion. Below there is a list of each plane in the game (as of this writing), and it's status. + +To change planes, open up Adventure Mode. On the main screen where you can either load a game or start a new one. Go to the Settings menu, and you will find a drop down on the top right with a list of the planes currently available. When changing planes, you will need to restart Forge to apply the change. Save files are unique to each plane, and will load back in when you change to the appropriate plane. + +## Amonkhet + +A plane set in the world of Amonkhet, and is intended to have new quests, story, enemies, etc. based on that Magic The Gathering plane. + +**STATUS:** Very broken. Not recommended for play, unless you want to dip your head in and see a very different overworld map. Pretty much nothing else works properly. _You have been warned._ + + +## Crystal Kingdoms + +A plane with a focus on the Final Fantasy cards. Intended to have a unique story, quests, enemies, etc. based on the Final Fantasy games. + +**STATUS:** Very pre-alpha. The plane 'works' but is almost identical to the default Shandalar plane. The big differences is new starting decks, and the card rewards in shops and from enemies, have a stronger Final Fantasy bent than usual. + +## Innistrad + +A plane set in the lore and world of Magic the Gathering's Innistrad plane. The card pool and enemies are limited to Innistrad, with some minor exceptions. There is also a distinctly different overworld, story, enemies, etc. Every map is new and unique compared to Shandalar. + +**STATUS:** Technically alpha. Everything is 'functional' in that it is all closed off and separate from the Shandalar plane. The enemies are new, the quests include some base and some new (though the tutorial has some strong similarities). The shops are new-ish (they currently use the base shops, but with a limited card pool to just the plane. This is actively being changed to entirely new shops however, and should become notable in the next few updates.) Every town, dungeon, and cave is unique to Innistrad and been custom built for the plane. Every enemy is also new to Innistrad. + +_HOWEVER_: Everything is still very early in design. Most of the biomes are empty. The new quests are limited. The shops under work. The story ends partway through the tutorial. The first bosses are being worked on. The enemies are few in number, but growing rapidly in development. Etc. This plane is best for short plays to see what is going on, but don't expect to spend much time here, Yet. That said, it is the community plane receiving the most work and most consistent updates. With changes coming at the minimum, monthly, preferably weekly. + +In it's current state, expect to find bugs. if you do, please report them to Shenshinoman in the Forge Discord. + +## Shandalar + +The base plane, where all the magic happens, and where the dev team members working on Adventure generally focus their efforts. The story is mostly complete, but there _is_ further work planned. Everything works, and you can easily spend dozens of hours, or more, in this plane. + +**STATUS:** Fully functional and live. Enjoy! + +## Shandalar Old Border + +So, you like the Shandalar plane, but miss "ye good olde days" of yore? Then Shandalar Old Border is for you. This is the Shandalar base plane, but with modifications so that everything is from the Scourge set, or older. + +**STATUS:** 99% functional and live. Enjoy! (There may be the occasional bug found. if so, please report it so we can get it resolved.) \ No newline at end of file diff --git a/docs/Docker-Setup.md b/docs/Docker-Setup.md new file mode 100644 index 00000000000..406573d3f45 --- /dev/null +++ b/docs/Docker-Setup.md @@ -0,0 +1,97 @@ +Directions here to use a docker container to play Forge: + +# FORGE-DESKTOP-SNAPSHOT + +Pull docker image. +``` +- docker pull xanxerdocker/forge-desktop-snapshot +``` + +Run container and remove container after exit: +``` +- docker run --rm -it -p 3002:3000 forge-desktop-snapshot bash +``` + +Run container in detached mode. Will retain data after exit: +``` +- docker run -d -it -p 3002:3000 forge-desktop-snapshot bash +``` +Access container at localhost:3002 in your web browser. + +Once inside the container. Run the setup script using sudo: +``` +- bash setup_forge_desktop.sh +``` +After the setup is complete. To play forge-desktop-SNAPSHOT or forge-adventure mode run: +``` +- bash forge_game_selector.sh +``` + +# FORGE DEV ENVIRONMENT: +Dockerized apps for a forge development environment +- intellij-ce +- Magic Set Editor 2 - Advanced +- Tiled - Map Editor + +Pull docker images. +``` +- docker pull xanxerdocker/intellij-ce-ide +- docker pull xanxerdocker/magic-set-editor-2-advanced +- docker pull xanxerdocker/tiled-map-editor +``` +# Intellij-ce IDE container + +Run Intellij-ce container and remove container after exit: +``` +- docker run --rm -it -p 3003:3000 xanxerdocker/intellij-cd-ide bash +``` +Run detached Intellij-ce container. Will retain data after exit") +``` +- docker run -d -it -p 3003:3000 xanxerdocker/intellij-ce-ide bash +``` +Access container via localhost:3003 in web browser and to start application run: +``` +- bash run_intellij-ce.sh +``` + + +# Magic Set Editor 2 Advanced + +Run Magic Set Editor 2 - Advanced container and remove container after exit. +``` +- docker run --rm -it -p 3004:3000 xanxerdocker/magic-set-editor-2-advanced bash +``` + +Run detached: Magic Set Editor 2 - Advanced container. Will retain data after exit. +``` +- docker run -d -it -p 3004:3000 xanxerdocker/magic-set-editor-2-advanced bash +``` + +Access container via localhost:3004 in web browser. + +Once inside the container to compile the app and finish setup run using sudo: +``` +- bash install-mse-adv-full.sh +``` + +After the app is compiled, to start app run: +``` +- bash run_mse.sh +``` + +# Tiled - Map Editor + +Run Tiled - Map Editor container and remove container after exit: +``` +- docker run --rm -it -p 3005:3000 xanxerdocker/tiled-map-editor bash +``` + +Run detached Tiled - Map Editor container. Will retain data after exit. +``` +- docker run -d -it -p 3005:3000 xanxerdocker/tiled-map-editor bash +``` + +Access container via localhost:3005 in web browser and to start application run: +``` +- bash run_tiled_map_editor.sh +``` \ No newline at end of file diff --git a/docs/Dungeons.md b/docs/Dungeons.md new file mode 100644 index 00000000000..eedeeb0b562 --- /dev/null +++ b/docs/Dungeons.md @@ -0,0 +1,82 @@ +## Story Dungeons + +Currently, the story is very much a work in progress and will change a lot in the future. The end goal will consist of multiple "tiers" of more difficult dungeons, where you will be guided by quests. Currently, only some of the story dungeons are available, and there are no quests to guide you to them. You can enter these dungeons and fight the bosses. + +### Tier 1: Castles +The first tier of story bosses will consist of five dungeons, each found in the center of the five colored biomes. Each boss gives a plethora of loot (cards, life, shards, gold) and if you are able to defeat all five bosses, you can get a Mox card of your choice. + +**Overworld Icons** + +![image](https://github.com/Card-Forge/forge/assets/67333662/f4d0c782-160f-4770-bb7d-788ae889c080) + +### Tier 2: Planeswalker Temples +Currently only the Temple of Chandra (red biome) and Temple of Liliana (black biome) are available. These tend to be harder than the castles mentioned above. + +**Overworld Icons** + +![image](https://github.com/Card-Forge/forge/assets/67333662/86b38d26-7e8d-449e-a6b5-9bee49270074) + +## Generic Dungeons + +### Aerie +### Barbarian Camp + +Comes in Goblin, Orc, Bandit and Kobold variaties +### Cat Lair +### Caves +### Crypt +### Djinn Palace +### Evil Grove +### Factory +### Fort +### Graveyard +### Grove +### Farm +### Hostile Towns +### Lavaforge +### Magetower +### Maze +### Merfolk Pool +### Monastery +### Phyrexian Outposts +![image](https://github.com/Card-Forge/forge/assets/67333662/6ff4620f-e740-414b-9365-80c044cf884a) +### Plains Castles +### Skull Cave +### Snow Abbey +### Vampire Castle +### Demon Tower + +## Sidequest Dungeons + +### The Skep +#### Location: Island Biome +### Forest of Garruk +#### Location: Forest Biome +### Hydra´s Lair +#### Location: Forest Biome +### Outpost of Jace +#### Location: Island Biome +### Unhallowed Abbey +#### Location: Plain Biome +### Nahiri's Encampment +#### Location: Plain Biome +### Tibalt's Fortress +#### Location: Mountain Biome +### City of Zedruu +#### Location: Mountain Biome +### Old Sewers +#### Location: Waste Biome +### Xira's Hive +#### Location: Waste Biome +### Slobads Factory +#### Location: Waste Biome +### Grolnoks Bog +#### Location: Swamp Biome +### Slimefoot's Swamp +#### Location: Swamp Biome +### Teferi's Hideout +#### Location: Island Biome +### Kiora's Island +#### Location: Island Biome +### Sorin's Dungeon +#### Location: Swamp Biome diff --git a/docs/Equipments-and-Items.md b/docs/Equipments-and-Items.md new file mode 100644 index 00000000000..c203e4bc87c --- /dev/null +++ b/docs/Equipments-and-Items.md @@ -0,0 +1,562 @@ +# Quick Links + +1. [Neck item slot](https://github.com/Card-Forge/forge/wiki/Equipments-and-Items#neck) +1. [Left item slot](https://github.com/Card-Forge/forge/wiki/Equipments-and-Items#left) +1. [Right item slot](https://github.com/Card-Forge/forge/wiki/Equipments-and-Items#right) +1. [Body item slot](https://github.com/Card-Forge/forge/wiki/Equipments-and-Items#body) +1. [Boot item slot](https://github.com/Card-Forge/forge/wiki/Equipments-and-Items#boots) +1. [Ability 1 item slot](https://github.com/Card-Forge/forge/wiki/Equipments-and-Items#ability1) +1. [Ability 2 item slot](https://github.com/Card-Forge/forge/wiki/Equipments-and-Items#ability2) + +## NECK + +#### Dark Amulet +1. **Effect** : Extra card starting in your battlefield zone. +1. **Location** : Swamp Capital Cities +1. **Price** : 3000 gold + +![Necropolis of Azar fullborder](https://github.com/Card-Forge/forge/assets/67333662/38324a83-aa57-4aa4-89aa-007cbf76d0a2) + +#### Hallowed Sigil +1. **Effect** : Extra card starting in your command zone. +1. **Location** : Monastery + +![Hallowed Sigil fullborder](https://github.com/Card-Forge/forge/assets/67333662/74052967-d8da-4d79-9999-6fbc903ef3db) + +#### Jeweled Amulet +1. **Effect** : Extra card starting in your command zone. +1. **Location** : Capital Cities +1. **Price** : 1000 gold + +#### Life Amulet +1. **Effect** : +2 starting life +1. **Location** : Capital Cities +1. **Price** : 4000 gold + +#### Manasight Amulet +1. **Effect** : Grants Manasight, letting you know the colors used by your adversaries. +1. **Location** : Capital Cities +1. **Price** : 1000 gold + +#### Phoenix Charm +1. **Effect** : Extra card starting in your command zone. [Spellbook Content](https://scryfall.com/@Simidhimi/decks/9c917d2c-c30d-4638-92ff-2df40120599c) +1. **Location** : Chandra's Temple + +![Phoenix Charm fullborder](https://github.com/Card-Forge/forge/assets/67333662/2d8bcdbd-c020-4ef9-b1cc-20240e197f6e) + +#### Piper's Charm +1. **Effect** : Extra card starting in your command zone. +1. **Location** : Capital Cities +1. **Price** : 4000 gold + +![Piper's Charm fullborder](https://github.com/Card-Forge/forge/assets/67333662/ca4a4270-6e2e-4a80-bf76-19fa99d66c96) + +#### Sorin's Amulet +1. **Effect** : Extra card starting in your battlefield zone. (Sorin's Amulet) +Starting life Modifier: +2 +1. **Location** : Sorin + +#### Traveler's Amulet +1. **Effect** : Extra card starting in your battlefield zone. (Traveler's Amulet) +1. **Location** : Blue Capital City +1. **Price** : 5000 gold + +#### Xira's Fancy Hat +1. **Effect** : +2 starting life + Extra card starting in your command zone. +1. **Location** : Xira's Hive + +![Xira's Hive fullborder](https://github.com/Card-Forge/forge/assets/67333662/5e852d00-60af-4a19-ad0b-3659d751cd19) + +## LEFT + +#### Axt +1. **Effect** : Extra card starting in your battlefield zone. +1. **Cost** : 2500 +1. **Location** : Capital Cities + +![Bonesplitter fullborder](https://github.com/Card-Forge/forge/assets/67333662/f83eebc9-4e6b-4f43-a1f1-c53396cffcf8) + +#### Battle Standard +1. **Effect** : Extra card starting in your battlefield zone. and -1 starting life total +1. **Location** : Goblin encampments + +![r_1_1_goblin_ema](https://github.com/Card-Forge/forge/assets/67333662/75e130fa-5bd1-4136-8c97-e1e3557b6222) + +#### Bronze Sword +1. **Effect** : Extra card starting in your battlefield zone. +1. **Cost** : 2500 +1. **Location** : Waste towns + +![thb-232-bronze-sword](https://github.com/Card-Forge/forge/assets/67333662/e58c82dd-3e70-4651-9620-d0e458c22ec1) + +#### Chandra's Tome +1. **Effect** : Extra card starting in your command zone. [Spellbook Content](https://scryfall.com/@Simidhimi/decks/e88b95ce-dd83-48ed-bf77-df997b71bbb6) +1. **Location** : Chandra's Temple + +![Chandra’s Tome fullborder](https://github.com/Card-Forge/forge/assets/67333662/196a5f91-c51c-4dc4-afe6-9bb847ebffb7) + +#### Dagger +1. **Effect** : Extra card starting in your battlefield zone. +1. **Cost** : 2500 +1. **Location** : Capital Cities + +![afr-250-spare-dagger](https://github.com/Card-Forge/forge/assets/67333662/99a9bcd4-a486-42af-846c-82b34456ce36) + +#### Disrupting Scepter +1. **Effect** : Extra card starting in your battlefield zone. (Disrupting Scepter) +1. **Cost** : 2500 +1. **Location** : Green Capital City Arena + +#### Farmer's Tools +1. **Effect** : Extra card starting in your command zone. +1. **Location** : Capital Cities +1. **Price** : 6000 gold + +![Farmer's Tools fullborder](https://github.com/Card-Forge/forge/assets/67333662/93924eb8-c7d8-4ff6-9993-5489bef9e42e) + +#### Flame Sword +1. **Effect** : Extra card starting in your command zone. +1. **Location** : Capital Cities +1. **Cost** : 3500 + +![Flame Sword fullborder](https://github.com/Card-Forge/forge/assets/67333662/92ba9d44-daad-44c8-ab42-beffb2a8c99a) + +#### Garruk's Mighty Axe +1. **Effect** : Extra card starting in your command zone. [Spellbook Content](https://scryfall.com/@Simidhimi/decks/47041427-b5d3-4faf-8f7b-1e1e004fa8ab?as=visual&with=usd) +Starting life Modifier": +3 +1. **Location** : Garruk Forest + +![Garruk's Mighty Axe fullborder](https://github.com/Card-Forge/forge/assets/67333662/e8db8194-9d3b-44c3-a417-9ee9121686cf) + +#### Giant Scythe +1. **Effect** : Extra card starting in your command zone. +Life Modifier: + 1 +1. **Location** : Scarecrow Farm + +![Giant Scythe fullborder](https://github.com/Card-Forge/forge/assets/67333662/3b9bb726-ee93-453b-a2d3-c64ee768ed72) + +#### Heart-Piercer +1. **Effect** : Extra card starting in your battlefield zone. +1. **Location** : Capital Cities + +![cmm-390-heart-piercer-bow](https://github.com/Card-Forge/forge/assets/67333662/0800bc84-185e-401b-a570-4410693fd25a) + +#### Heavy Arbalest +1. **Effect** : Extra card starting in your battlefield zone. (Heavy Arbalest) +1. **Cost** : 1500 +1. **Location** : Red Capital City + +#### Hivestone +1. **Effect** : Extra card starting in your battlefield zone. +1. **Location** : Skep + +![Hivestone fullborder](https://github.com/Card-Forge/forge/assets/67333662/1bdbc48b-ef3e-43a4-99fb-fbdc7861eb98) + +#### Kiora's Bident +1. **Effect** : Extra card starting in your command zone. [Spellbook Content](https://scryfall.com/@Simidhimi/decks/7fdd8d06-c0c5-487e-a7e7-3050f6d19bed) +Starting life Modifier": -1, +Extra card starting in your battlefield zone: Kraken Hatchling +1. **Location** : Kiora Island + +![Kiora's Bident fullborder](https://github.com/Card-Forge/forge/assets/67333662/bcc003e2-5195-41a3-a277-176ed0d123ec) +![bbd-121-kraken-hatchling](https://github.com/Card-Forge/forge/assets/67333662/b5ae9dd9-7e7b-4cfe-9357-09e8d8c33af0) + +#### Mad Staff +1. **Effect** : Extra card starting in your battlefield zone. (Power Struggle) +1. **Location** : Black Capital City +1. **Price** : 1000 gold + +#### Nine-Ringed Bo +1. **Effect** : Extra card starting in your battlefield zone. (Nine-Ringed Bo) +1. **Location** : White Capital City +1. **Price** : 1000 gold + +#### Presence of the Hydra +1. **Effect** : Extra card starting in your command zone. [Spellbook](https://scryfall.com/@Simidhimi/decks/86798768-3af5-49c0-a120-feb62e1a0e95) +1. **Location** : Hydra boss + +![Presence of the Hydra fullborder](https://github.com/Card-Forge/forge/assets/67333662/0b06d4ea-8e10-46be-b7c4-3aecaf9b6d32) + +#### Sleep Wand +1. **Effect** : Extra card starting in your command zone. +1. **Location** : Capital Cities +1. **Price** : 4000 gold + +![Sleep Wand fullborder](https://github.com/Card-Forge/forge/assets/67333662/37b23723-ad6a-4bff-8919-ab029af23c8d) + +#### Slimefoot's Slimy Staff +1. **Effect** : Extra card starting in your command zone. +Life Modifier: + 2 +1. **Location** : Slimefoot's Swamp + +![Slimefoot's Slimy Staff fullborder](https://github.com/Card-Forge/forge/assets/67333662/89436738-210e-4e41-b3d3-01d3975ad2e3) + +![vma-293-bayou](https://github.com/Card-Forge/forge/assets/67333662/c3bfd5f7-ee72-4784-a7f2-59a7780007c3) + +#### Spell Book +1. **Effect** : Starts with an extra card in your hand. +1. **Location** : Capital Cities +1. **Price** : 3000 gold + +#### Steel Sword +1. **Effect** : Extra card starting in your battlefield zone. +1. **Cost** : 4500 +1. **Location** : Waste towns + +![gn3-116-greatsword](https://github.com/Card-Forge/forge/assets/67333662/3ee57c21-0462-48f7-9a15-a25d361a7465) + +#### Teferi's Staff +1. **Effect** : Extra card starting in your command zone. +Life Modifier: + 1, Card reward bonus +1 +1. **Location** : Teferi Hideout + +![Teferi's Staff fullborder](https://github.com/Card-Forge/forge/assets/67333662/7ce2d17c-4327-4705-b23e-aff2e49ffa70) + +#### The Underworld Cookbook +1. **Effect** : Extra card starting in your battlefield zone. +1. **Location** : Zedruu's City + +![mh2-240-the-underworld-cookbook](https://github.com/Card-Forge/forge/assets/67333662/2fdb3032-8745-4b77-a78e-b4f405a4719d) + +#### Tibalt's Bag of Tricks + +1. **Effect** : Extra card starting in your command zone. +[Tibalt's Devils](https://scryfall.com/@Simidhimi/decks/1d5cc465-ac4a-496f-bac1-dc313c540375) +[Tibalt's Spells](https://scryfall.com/@Simidhimi/decks/8267bb6a-a226-4ec2-946e-a419372b97dc) +[Tibalt's Spellbook](https://scryfall.com/@Simidhimi/decks/b2d248bc-e16f-49fe-94dc-28ac2b6ab5b5) +Life Modifier: + 1 +1. **Location** : Tibalt's Fortress + +![Tibalt's Bag of Tricks fullborder](https://github.com/Card-Forge/forge/assets/67333662/ac9732a4-6276-45b2-bcae-68a9bed4a5f8) + +#### Unerring Sling +1. **Effect** : Extra card starting in your battlefield zone. (Unerring Sling) +1. **Cost** : 4000 +1. **Location** : Green Capital City + +#### Wood Bow + +1. **Effect** : Extra card starting in your battlefield zone. +1. **Location** : Capital Cities + +![ice-318-fyndhorn-bow](https://github.com/Card-Forge/forge/assets/67333662/f42b169f-09ca-40d8-be04-6f21829e897c) + +#### Zedruu's Lantern +1. **Effect** : Extra card starting in your command zone. [Spellbook Content](https://scryfall.com/@Simidhimi/decks/bdaa39ed-b4b3-4157-aa85-98e313750dd0) +Starting life Modifier: +1 +1. **Location** : Zedruu's City + +![Zedruu's Lantern fullborder](https://github.com/Card-Forge/forge/assets/67333662/ac92c5ba-a994-41d8-a6a6-e169a70fa500) + +## Right + +#### Aladdin's Lamp +1. **Effect** : Extra card starting in your battlefield zone. +1. **Location** : Capital Cities +1. **Price** : 3000 gold + +![arn-56-aladdin-s-lamp](https://github.com/Card-Forge/forge/assets/67333662/bb557cfc-342d-4545-823e-7e02c2a057a0) + +#### Aladdin's Ring +1. **Effect** : Extra card starting in your battlefield zone. +1. **Location** : Capital Cities +1. **Price** : 3000 gold + +![9ed-286★-aladdin-s-ring](https://github.com/Card-Forge/forge/assets/67333662/764b5d4f-80a9-4248-9e65-745b0e5a8401) + +#### Chicken Egg +1. **Effect** : Extra card starting in your battlefield zone. +1. **Location** : Secret :) + +![ugl-41-chicken-egg](https://github.com/Card-Forge/forge/assets/67333662/9af1d091-a3a4-4eb3-83bb-3bb34eaf0c92) + +#### Cursed Ring +1. **Effect** : 3 cards starting in **your opponents** battlefield zone. +1. **Location** : Capital Cities +1. **Price** : 3000 gold + +![tok](https://github.com/Card-Forge/forge/assets/67333662/1bf9f4ad-0030-4ea6-9905-f6764fbe278d) + +#### Cursed Treasure +1. **Effect** : Extra card starting in your command zone. +1. **Location** : Capital Cities +1. **Price** : 4000 gold + +![Cursed Treasure fullborder](https://github.com/Card-Forge/forge/assets/67333662/27175670-7feb-4cbf-a98c-a55c1f38d4a9) + +#### Dark Shield +1. **Effect** : Extra card starting in your battlefield zone. (Barrier of Bones) +Lifemodifier: -3 +1. **Location** : Black Capital City + +#### Death Ring +1. **Effect** : Extra card starting in your command zone. +1. **Location** : Capital Cities +1. **Cost** : 3500 + +![Death Ring fullborder](https://github.com/Card-Forge/forge/assets/67333662/7a692e73-f005-45c7-91e7-b1c56ddea13c) + +#### Demonic Contract +1. **Effect** : Extra card starting in your command zone. +1. **Location** : Temple of Liliana + +![Demonic Contract fullborder](https://github.com/Card-Forge/forge/assets/67333662/539a29af-b599-4414-a119-e492078575f3) + +#### Dungeon Map +1. **Effect** : Extra card starting in your battlefield zone. +1. **Location** : Capital Cities +1. **Price** : 3000 gold + +![afr-242-dungeon-map](https://github.com/Card-Forge/forge/assets/67333662/d36d2a07-cb15-4ccf-9414-6b8b75253b90) + +#### Entrancing Lyre +1. **Effect** : Extra card starting in your battlefield zone. (Entrancing Lyre) +1. **Location** : White Capital City +1. **Cost** : 1000 + +#### Gold Shield +1. **Effect** : + 3 starting life total +1. **Location** : Blue Capital Arena + +#### Grolnok's Skin +1. **Effect** : Extra card starting in your battlefield zone. Lifemodifier: +3 [Spellbook](https://scryfall.com/@Simidhimi/decks/bf16e555-4efb-488b-a05c-25296807777c) +1. **Location** : Grolnok's Bog + +![Grolnok's Skin fullborder](https://github.com/Card-Forge/forge/assets/67333662/6d93aa0d-e726-4ecb-a763-fecfeb0cbda5) + +#### Hill Giant Club +1. **Effect** : Extra card starting in your command zone. +1. **Location** : Capital Cities +1. **Price** : 2000 gold + +![Hill Giant Club fullborder](https://github.com/Card-Forge/forge/assets/67333662/f669a07c-b3a5-483e-a5ea-9b958595dd96) + +#### Iron Shield +1. **Effect** : + 2 starting life total +1. **Location** : Waste Town +1. **Cost** : 3500 + +#### Jandor's Ring +1. **Effect** : Extra card starting in your battlefield zone. (Jandor's Ring) +1. **Location** : Red Capital Arena + +#### Jungle Shield +1. **Effect** : Extra card starting in your battlefield zone. +1. **Location** : Capital cities +1. **Cost** : 3500 + +![tncc-27-plant](https://github.com/Card-Forge/forge/assets/67333662/a56d07e3-a059-4e15-ad3f-b7be4695a3aa) + +#### Kite Shield +1. **Effect** : Extra card starting in your battlefield zone. (Kite Shield) +Opponent starting life -2 +1. **Location** : White Capital City +1. **Cost** : 1500 + +#### Magic Shard +1. **Effect** : Extra card starting in your battlefield zone. (Magic Shard) +1. **Location** : Blue Capital City +1. **Cost** : 3500 + +#### Mirror Shield +1. **Effect** : Extra card starting in your battlefield zone. (Mirror Shield) +1. **Location** : Capital Cities +1. **Price** : 2500 gold + +#### Mithril Shield +1. **Effect** : Extra card starting in your battlefield zone. (c_0_4_a_wall_defender) +1. **Location** : Blue Capital City +1. **Cost** : 6500 + +#### Nahiri's Armory +1. **Effect** : Extra card starting in your battlefield zone. Lifemodifier: +3 [Spellbook](https://scryfall.com/@Simidhimi/decks/b7a18cab-c10c-4ad4-80ba-aba88649ef27) +1. **Location** : Nahiri's Outpost + +![Nahiri's Armory fullborder](https://github.com/Card-Forge/forge/assets/67333662/d46e4264-e004-4bcb-8454-8702d5716ae7) + +#### Prism Ring +1. **Effect** : Opponent starting life -2 +1. **Location** : Capital cities + +#### Ring of Immortals +1. **Effect** : Opponent starting life -2 +1. **Location** : Capital cities + +#### Ring of Renewal +1. **Effect** : Opponent starting life -2 +1. **Location** : Capital cities + +#### Ring of Three Wishes +1. **Effect** : Extra card starting in your battlefield zone. (Ring of Three Wishes) +1. **Location** : Green Capital City +1. **Cost** : 2500 + +#### Steel Shield +1. **Effect** : Extra card starting in your battlefield zone +1. **Location** : Waste Town +1. **Cost** : 6500 + +![twar-4-wall](https://github.com/Card-Forge/forge/assets/67333662/8ee1f8ba-0ebf-4fd5-a192-3c27c37ab8fa) + +#### Unhallowed Sigil +1. **Effect** : Extra card starting in your battlefield zone. +1. **Location** : Monastery + +![Sigil of Torment fullborder](https://github.com/Card-Forge/forge/assets/67333662/7a363922-62d7-4ec9-9cce-14e6bcb55782) + +## Boots + +#### Dark Boots +1. **Effect** : Extra card starting in your battlefield zone. Movement speed + 30%. -2 starting life total +1. **Location** : Swamp Capital Shop +1. **Price** : 3000 gold + +![snek](https://github.com/Card-Forge/forge/assets/67333662/ce574fe9-269e-4a62-9ec4-2ad4a43aa04d) + +#### Gold Boots +1. **Effect** : Movement speed + 30%. +2 starting life total +1. **Location** : Plains Capital Arena +1. **Price** : 7500 gold + +#### Iron Boots +1. **Effect** : Movement speed + 20%. +1. **Location** : Waste towns +1. **Price** : 2000 gold + +#### Leather Boots +1. **Effect** : Movement speed + 15%. +1. **Location** : Starting item on Easy and Medium difficulty + +#### Lightbringers Boots +1. **Effect** : Extra card starting in your battlefield zone (Ajani's Mantra). +Opponent starting life +5 +1. **Location** : Forgotten Cave + +#### Mithril Boots +1. **Effect** : Movement speed + 30%. +2 starting life total +1. **Location** : Island Capital Arena +1. **Price** : 10000 gold + +#### Sandals +1. **Effect** : Movement speed + 10%. +1. **Location** : Starting item on Hard and Insane difficulty + +#### Slime-Covered Boots +1. **Effect** : Extra card starting in your battlefield zone. Movement speed + 20%. -1 starting life total +1. **Location** : Ooze Boss + +![snek](https://github.com/Card-Forge/forge/assets/67333662/70dc37f0-6568-43d4-8099-89f31715b7eb) + +#### Slobad's Iron Boots +1. **Effect** : Extra card starting in your command zone. + 1 starting life total + movement speed + 35% +1. **Location** : Slobad's Dungeon + +![Slobad's Iron Boots fullborder](https://github.com/Card-Forge/forge/assets/67333662/df342a9f-b797-41d5-b93f-bec9dc12aa25) + +#### Steel Boots +1. **Effect** : Movement speed + 20%. +1 starting life total +1. **Location** : Waste towns +1. **Price** : 4500 gold + +## Body + +#### Armor of the Hivelord +1. **Effect** : +5 starting life total. And start with an extra card in your hand. +1. **Location** : Skep + +#### Dark Armor +1. **Effect** : Extra card starting in your battlefield zone. And -2 starting life +1. **Location** : Black Capital Shop +1. **Price** : 3000 gold + +![snek](https://github.com/Card-Forge/forge/assets/67333662/5467c610-acf7-49ad-91ae-f581784a0cfe) + +#### Gold Armor +1. **Effect** : +4 starting life total +1. **Location** : Plains Capital Arena +1. **Price** : 7500 gold + +#### Iron Armor +1. **Effect** : +2 starting life total +1. **Location** : Waste towns +1. **Price** : 3000 gold + +#### Jace's Signature Hoodie +1. **Effect** : Extra card starting in your command zone. And -1 starting life. And additional reward multiplier +1. **Location** : Jace Boss + +![Jace's Signature Hoodie fullborder](https://github.com/Card-Forge/forge/assets/67333662/44093afb-b5c1-4ed7-b0c2-bc5602688b5b) + +#### Mantle of Ancient Lore +1. **Effect** : Extra card starting in your command zone. And +1 starting life +1. **Location** : Zedruu dungeon + +![Mantle of Ancient Lore fullborder](https://github.com/Card-Forge/forge/assets/67333662/f06e245c-c2e2-411f-8cac-1fade87bd957) + +#### Mithril Armor +1. **Effect** : +5 starting life total +1. **Location** : Island Capital Arena +1. **Price** : 15000 gold + +#### Steel Armor +1. **Effect** : +3 starting life total +1. **Location** : Waste towns +1. **Price** : 5000 gold + +## ABILITY1 + +#### White Staff +1. **Effect**: Heals half of your life for 5 shards +1. **Location** : Capital cities +1. **Cost**: 5000 + +#### Black Staff +1. **Effect**: Hide from your enemies for 5 shards +1. **Location** : Capital cities +1. **Cost**: 5000 + +#### Blue Staff +1. **Effect**: Let's you fly for 5 shards +1. **Location** : Capital cities +1. **Cost**: 5000 + +#### Red Staff +1. **Effect**: Kills the closest enemy in the overworld (non-dungeon) for 5 shards +1. **Location** : Capital cities +1. **Cost**: 5000 + +#### Green Staff +1. **Effect**: Doubles your speed for 5 shards +1. **Location** : Capital cities +1. **Cost**: 5000 + +## ABILITY2 + +#### Green Rune +1. **Effect**: Teleports you to the forest capital for 1 shard. +1. **Location** : Capital cities +1. **Cost**: 500 + +#### Black Rune +1. **Effect**: Teleports you to the swamp capital for 1 shard. +1. **Location** : Capital cities +1. **Cost**: 500 + +#### White Rune +1. **Effect**: Teleports you to the plains capital for 1 shard. +1. **Location** : Capital cities +1. **Cost**: 500 + +#### Red Rune +1. **Effect**: Teleports you to the mountain capital for 1 shard. +1. **Location** : Capital cities +1. **Cost**: 500 + +#### Blue Rune +1. **Effect**: Teleports you to the island capital for 1 shard. +1. **Location** : Capital cities +1. **Cost**: 500 + +#### Blue Rune +1. **Effect**: Teleports you to the center for 1 shard. +1. **Location** : Starting area \ No newline at end of file diff --git a/docs/File-Formats.md b/docs/File-Formats.md new file mode 100644 index 00000000000..1e09d24f398 --- /dev/null +++ b/docs/File-Formats.md @@ -0,0 +1,31 @@ +This page is a work in progress... sorry, this is super complex, any input and help is definitely welcome! + +# About +There are many different file formats used in forge, due to the longevity of forge and it's growth and change, older formats are used and new ones are being created. This is an attempt to capture the requirements of these files for general creation, and modification. Unfortunately these "formats" may change and this list, or the data in them may become obsolete as changes happen, especially in newer features like Adventure Mode. + +# List +This is a quick hand jammed list of the formats that I have found looking through the /res/ folder. This is not complete, but is a quick reference and a start to identifying the files that can be "customized" or modified to make the engine work for you. + +The list is (roughly identified) "format name", and the folder location and filename or just the extension if a specific filename is not required. Where "plane folder" is used, it is a subfolder of that game mode, `Adventure/Shandalar/` for example, or `Conquest/planes/Shandalar/` for conquests, and `quest/world/Shandalar/` for quests. + +- Deck Files - .dck +- Card Files - .txt +- Editions - .txt +- Draft - .draft +- Blocks - blocks.txt +- Formats - type/name.txt +- Puzzle - .pzl +- Quest + - Theme - .thm + - Worlds - World folder/worlds.txt +- Adventure - Plane Folder + - Plane Configuration - config.json + - Generated Enemy - .json + - Generated Starter Deck - .json +- Conquest - Plane Folder + - Banned Cards - banned_cards.txt + - Cards - cards.txt + - Plane Cards - plane_cards.txt + - Regions - regions.txt + - Sets - sets.txt + - Events - set/_events.txt \ No newline at end of file diff --git a/docs/Forge-historical-reference.md b/docs/Forge-historical-reference.md new file mode 100644 index 00000000000..74cbe9a36a9 --- /dev/null +++ b/docs/Forge-historical-reference.md @@ -0,0 +1,115 @@ +# An oral/written history of Forge + +Here is a roughly written history of some notable points in Forge's history along with some Java 8 playable versions of the game linked for some of the older ones... + +## 2007-04-26 - Forge introduced on https://mtgrares.blogspot.com/ by mtgrares. + +365 cards available. + +`The card set is the best of Magic, and includes such cards like Ancestral Recall, Juzam Djinn, Serendib Efreet, Flametongue Kavu, Man-o'-War, all the moxes, Kokusho, the Evening Star, Keiga, the Tide Star, as well as old favorites like Wrath of God, Psychic Blast, Serra Angel, and a few Planar Chaos cards like Pyrohemia, Serra Sphinx, Damnation.` + +All of the blogposts are still available as of this writing (May 2023), but most of the links are defunct since they are pointing to since abandoned locations. + +## 2008-07/08 - Oldest known historic archive of Forge. + +850 cards available. Original white-gray Java Swing interface. **All** cards live in a cards.txt file. No card "scripting" exists yet. + +## 2010-07 - Improved original interface + +Still using cards.txt file. Beginnings of the "res" folder for resources to be used inside Forge. + +## 2011-09 - Start transitioning from "vintage" Forge to "modern" Forge. + +Mostly old-style interface with new age loading screen. Initial card scripting is being used along with the cardsfolder structure. Although the individual cards scripts, contain lots of vestiges of the past. (SetInfo, End tags, Picture tags, even the original scripting format) + +## 2012-02 - Desktop interface transitions + +Supports over 10,000 cards. We start using a reconstructed UI that allows for better theming. And makes things feel less like an "Application" and more like a "Game" + +## 2014-05 - Android app is published via Maven + +## 2015-04 - Network play is rudimentary but available + +## 2018-04 - Focus on adding automation tooling + +## 2021-12 - Adventure mode functional + +## 2024-07 - _All non-Un set cards have been added to Forge_ + +## 2024-08 - Bloomburrow release + +## 2024-09 - Duskmouth Release 1.6.65 (**Last release that supports Java 1.8**) + +Supports over 28,500 cards. + +### Some significant events that need to be dated + +* Quest mode +* Enable all phases +* AI can cast instants +* Creation of the Ability Factory/Scripting 2.0 system +* Creation of costing system +* Allowance of more than one player + +# Legends + +## Recounting of "Forge 2.0" by @Agetian + +My favorite "Forge legend" is probably the legend about Forge 2.0 (that never came to be, at least thus far). Forge 2.0 was something that was brought up multiple times in Forge history, different targets were set for the hypothetical "future Forge" that would be worthy of increasing the major version number to 2, including, but not limited to, network play support, better UI, better AI (such as Minimax etc.), 100% support for standard cards, and more. Quite amusingly, the absolute majority of those targets have been met since, and even far surpassed, what with the addition of the mobile port, Planar Conquest, Adventure mode, and much more, but Forge is still using the 1.x version scheme it's been using since way back, hehe. Originally, Forge 2.0 was brought up my MtgRares (including a few times on his blog), but the original developer quit the active development before reaching his 2.0 targets, sadly. Thus, at the moment "Forge 2.0" represents a certain hypothetical, future version of Forge that's significantly and unconditionally better than what we currently have, whatever it may be. + +The alternative variation of this historical legend is the legend about "Forge 2" - something that was also brought up a couple times in Forge history, mostly coming, unfortunately, from single developers. The hypothetical "Forge 2" was the complete, from-the-ground-up rewrite of Forge that would solve the many poor design choices that are difficult to solve through refactoring, and would thus pave the way for better UI/UX and AI implementations, make the game more streamlined and easier to code, among other things. Sadly, "Forge 2", just as well as "Forge 2.0", hasn't come to be, since the task to recreate all the game mechanics essentially from scratch, mostly as a single developer effort, has proven to be insurmountable. + +## Original Adventure Mode + +https://www.slightlymagic.net/forum/viewtopic.php?f=52&t=2907 + +For any of you curious about what the first attempt at making a Shandalar-like Adventure mode in Forge looked like (a development effort from 2010-2011), here are a few screenshots posted by the original developer who left circa 2011 or 2012. The project was abandoned, as far as I know. Pretty sure I had a test version somewhere in my backups (where you could walk around a mostly empty World Map and maybe start a battle or something, I don't remember the details), but I can't find it anymore. + +Interestingly, the above-mentioned thread refers to the proposed game mode as "Adventure Mode" a few times (closer to the end of the thread), something that we actually currently have and actively develop 👍 +The interesting aspect of that development was the day/night cycle, I don't know why but I remember I thought that it looked quite atmospheric back in the day + + + +## Abe Sergeant writes about Forge + +2009-09-11 - Here is the article on star city. + +https://articles.starcitygames.com/articles/the-kitchen-table-302-an-mtg-forge-quest/ + +And here is it in archive.org in case that gets taken down + +https://web.archive.org/web/20210707215155/https://articles.starcitygames.com/articles/the-kitchen-table-302-an-mtg-forge-quest/ + +MTG Forge comes with a Quest Mode. In Quest Mode, you begin with a random selection of cards, and have to build a 60-card deck. Then you play against decks by various computer opponents, and as you win, you get more cards, and the difficulty of your opponents increases. + +Quest is the most fun I’ve had playing Magic in a year. + +What I am going to do is show you a quick 10-game win in quest. You can play to 10, 20, 30, or 40 wins, but I’m just going to show you a 10 win in quest. I will show you where the game has bugs too, so you can see what I am talking about when I say WIP. I’m not holding anything back. + +# Major disruptions + +One interesting thing about Forge is the way it grew. Much of the first few years was solely on the back of an Amateur software engineer called "MTG Rares" soon enough "Dennis Bergkamp" came along and was doing a bunch more development. New Software Engineers joined the ranks off and on. From there on people would jump in, help for a handful of years and get too busy with life, or stop really playing magic or whatever. A handful of us have been around the longhaul, but not too many. + +## Sourceforge SVN (2007-2008) + Original location. Moved when the name changed. +## Google Code SVN (2008-2011) + Google code was shutting down "soon" so we used it as an excuse to find a new home +## Bitbucket/GIT (2011-2011) + This one lasted barely a few weeks. It was hated pretty universally, even by the people who suggested it. +## Slightly Magic SVN (2011-2017) + The nice folks at Slightly Magic hosted an SVN for us. +## Hosted Gitlab (2017?-2021) + One of the devs was comfortable in Git/Gitlab and moved everything over. Took a few attempts to get there but we made it. +## Github (2021-Present) + Conversion from Git to Git was a lot easier. + + +## Jendave's modularization (2011) +The initial modularization attempt pulled everything from living under forge-gui/ module and built out some of the Maven structure. + +## Maxmtg's modularization (2013?) +Took this the next step further massively reorganizing the codebase. It caused major issues and took a few months to get resolved. + +## Hanmac's modularization (2017? +This was a smaller modularization mostly within certains areas that ultimately was positive, but led to some headaches during the process. + diff --git a/docs/Forge_DevMode.textile b/docs/Forge_DevMode.textile new file mode 100644 index 00000000000..cc2093f1fae --- /dev/null +++ b/docs/Forge_DevMode.textile @@ -0,0 +1,5 @@ +Name:Vanilla Creature +ManaCost:2 G +Types:Creature Beast +Text:no text +PT:2/2 \ No newline at end of file diff --git a/docs/Frequently-Asked-Questions.md b/docs/Frequently-Asked-Questions.md new file mode 100644 index 00000000000..6ce0996f94f --- /dev/null +++ b/docs/Frequently-Asked-Questions.md @@ -0,0 +1,93 @@ +This is a list of basic troubleshooting questions that come up for most new players, before running to the discord about your issue, please review this FAQ for some of the more common issues. + +### Check the FAQ in Discord + +https://discord.com/channels/267367946135928833/1095026912927154176 + +### Search the help posts in Discord + +https://discord.com/channels/267367946135928833/1047001034788196452 + +### Write a post in help section of Discord + +https://discord.com/channels/267367946135928833/1047001034788196452 + +Note: For now, please also check [this](https://www.slightlymagic.net/forum/viewtopic.php?f=26&t=11825) forum topic for some additional information. + +# General + +### How do I download content? + +Forge has content downloaders within the app itself, you can use those tools to update the graphics assets. More information about card and token image assets can be found here. [Card Images, Downloading](Card-Images#downloading) + + + +* If you have an older Android device for increased performance or to save bandwidth it might be a good idea to use lower resolution images instead: https://www.slightlymagic.net/forum/viewtopic.php?f=15&t=29104 + +### How do I extract Forge? + +* Forge uses a .tar.bz2 format for archiving. Depending on your operating system, different utilities can be used to untar the archive. + * If you use Windows, you may want to try 7-Zip (http://www.7-zip.org/download.html). + +### I think I found a bug in Forge. What do I do? + +*Most users, who are running beta versions of Forge, should continue to use these instructions. As for alpha testers, these instructions have yet to be made congruent with the latest automatic bug reporting from within Forge.* + +Bug reports from users are the lifeblood of Forge. Please keep in mind that "beta" releases *are* test releases. Forge is constantly evolving, so we do not yet have "stable" or "production" releases. Because of the pace at which new cards are added to the multiverse by external forces, this will be the norm for some time. We do not expect everything to work 100%. We have a small number of developers and a handful of slightly less technical people actively improving the game. We simply cannot devote the resources to test every single card, much less the nearly infinite ways the cards can interact. + +For starters, please take note of (1) what you had in play, (2) what your opponent had in play and (3) what you were doing when the error occurred. If you get a Crash Report from inside Forge, please save the data to a file. This information is very important when reporting a problem. Don't worry if you didn't think of that right away, until your next start, the "Forge.log" in the game directory will also provide that information. + +If you did not get a Crash Report, but you have experienced a problem in how Forge handled one or more cards or game rules, *please read the cards (and the Oracle rulings) carefully* to make sure you understand how they work. You may be surprised to find that Forge is actually enforcing the rules +correctly. + +Because duplicate bug reports use up our limited resources, please research your bug with the **Search** box on Forge's [issue tracker](https://git.cardforge.org/core-developers/forge/-/issues) to see if your bug has already been reported there. For Crash Reports, use key words from the second paragraph of the Crash Report. + +* If you find a matching issue, examine it to see if you have anything new to contribute. For example, a different way of reproducing a problem can sometimes be helpful. If the issue was posted to the forum, you may post your additional information there. + +* If you find nothing, please try to reproduce the problem and take notes. If we can use your notes to reproduce the bug for ourselves, it is *much* easier to fix! + +* If you're unsure, you can also post on one of the support channels of the discord. In case you do not get a timely response, please submit a new issue anyway to make sure it doesn't get lost. + +### I have an idea to make Forge better. What do I do? + +Follow the directions in [Bug Reports](Frequently-Asked-Questions#i-think-i-found-a-bug-in-forge-what-do-i-do), keeping in mind that you are not reporting a bug, but rather a **Feature Request**. + +# Development + +### I want to help develop Forge. How do I get started? + +Forge is written in Java, so knowledge in that language (or similar Object Oriented languages like C++ or C\#) is very helpful. However, it is possible to learn the grammar for writing the data objects of cards without programming experience. + +A development environment such as [IntelliJ](https://www.jetbrains.com/idea) is beneficial, as it helps writing, compiling and testing your changes. + +Thanks to the nature of how cards are implemented, you can also contribute these as small plain text files. This is especially helpful during a preview season, when there are a lot of new cards in the backlog. This is mostly coordinated in #card-scripting on the Discord (and the pins there). + +To obtain the source code of Forge, read our [Development Guide]((SM-autoconverted)--how-to-get-started-developing-forge). + +### My system is all setup to help. What now? + +Take a look through the /res/cardsfolder folder. This is where all the card data lives. If you know of cards that are missing from Forge, see if there are similar cards that already exist. + +# Gameplay + +### Where do I use Flashback or a similar ability that is in an External area? + +Click on the Lightning Bolt icon in the player panel. Since cards with External Activations aren't as clear to activate, we created this shortcut for this specific purpose. + +### How do I target a player? + +Just click on the player's Avatar in the Player Panel when prompted to select a Player as a target. + +### Where did my mana go? + +If you have an effect that generated you some mana, and you don't know where it is. Check out the Player Panel. There are 6 different mana subpools one for each color/colorless that should have it. If you accidentally tapped your mana before your Main Phase, your mana is gone. Sorry, we don't have a way at this time to revert these actions. In general, I'd say it's easier/better to start casting a spell first, then activate your mana so this doesn't happen. + +# Quest Mode + +### What is the difference between Fantasy Quest and Normal Quest? + +In Normal Quest, you start with 20 life and only have access to the Card Shop. In Fantasy Quest, you start at 15 life and gain additional access to the Bazaar which allows you to buy things like extra life points, Pets, Plants and more. + +### Sealed Deck Mode + +[HOW-TO: Customize your Sealed Deck games with fantasy blocks](https://www.slightlymagic.net/forum/viewtopic.php?f=26&t=8164) diff --git a/docs/Future-Ways-to-Play.md b/docs/Future-Ways-to-Play.md new file mode 100644 index 00000000000..5b746b16248 --- /dev/null +++ b/docs/Future-Ways-to-Play.md @@ -0,0 +1,54 @@ +There are many ways to play Magic. This page exists to house rulesets that haven't been added to Forge yet. + +

Open House

+2-player, one mentor and one player who has never played before, sealed unsanctioned. Materials, two open house boxes, each containing a pair of monocolor 30-card decks + +1. Each player chooses a box, which contains two 30-card decks. Both decks are prebuilt 30-card decks, and one of the decks in the box is the color shown on the box. The other deck is a different color. +2. Players choose which deck to play with. They may combine both decks from their box if desired. +3. Players reveal their decks to one another before shuffling. +4. The match begins. +5. The mentor should do their best to make sure the new player is enjoying the game. + +

Booster Battle

+2-player, sealed limited unsanctioned. Materials: one Booster Battle blister containing two preconstructed monocolor 30-card decks, two booster packs + +1. Unwrap the decks, and decide which player gets which deck. +2. Each player opens a booster pack and chooses up to five cards to add to their deck. +3. Shuffle up and play! + +

Prerelease (typical)

+Sanctioned, limited, sealed tournament. Materials: 6 boosters and one promo. (Varies by release) Each match is 2-player. + +1. 30 minute timer starts. +2. Each player opens their boosters and receives their promo card. +3. Deck size is 40 minimum, the traditional five basic lands may be added to the pool. Sideboard size is unlimited. +4. After 30 minutes are up, timed matches begin. +5. Players are free to use a different deck each game if they choose as long as it comes from the same starting pool. + +

Duel Decks

+2-player, unsanctioned, sealed. Materials: Duel Decks box containing two 60-card precons. + +1. Unwrap the decks, and decide which player gets which deck. +2. Shuffle up and play! + +

Starter Set

+2-player, unsanctioned, sealed. Materials: Blister or box set containing two prestacked 30-card decks and learn-to-play walkthrough guides. + +1. Unwrap the decks, and decide which player gets which deck. +2. DO NOT SHUFFLE. +3. Play. + +

Unstable

+So you want to assemble a Contraption? + +No problem! Follow these $`\xcancel{\sf{four}}`$ $`\hat{H}|\psi(t)\rangle = i\hbar\frac {\normalsize\,\,\,\circlearrowright\!\!\!\!\!\circlearrowleft\!\!\!\!\!\!\!\!\!\!\small\curlyeqsucc\normalsize\!\!\!\!\!\!\cdotp\!\!\!\!\!\!\!\small\ldotp\normalsize\!\!\!\!\!\!\!\cdotp\!\!\!\!\!\!\!\small\curlyeqprec} {\normalsize\,\,\,\circlearrowright\!\!\!\!\!\circlearrowleft\!\!\!\!\!\!\!\!\!\!\small\curlyeqsucc\normalsize\!\!\!\!\!\!\cdotp\!\!\!\!\!\!\!\small\ldotp\normalsize\!\!\!\!\!\!\!\cdotp\!\!\!\!\!\!\!\small\curlyeqprec\!\!t} |\psi(t)\rangle`$ easy steps and you'll be slapping together devious devices in no time! + +1. Contraptions start each game in a separate deck: the Contraption deck. DON'T mix your Contraptions up with the rest of your cards! + +2. The Contraption deck has three sprockets. Start the game with a CRANK! counter on Sprocket 3. + +3. How do you assemble a Contraption? Reveal the top card of your Contraption deck, then choose one of your sprockets. Your Contraption enters the battlefield under that sprocket. + +4. At the beginning of your upkeep, if you control a Contraption, move the CRANK! counter to your next sprocket and crank any number of Contraptions on that sprocket, putting their triggered abilities onto the stack. Remember the sprocket order: $`\Bbb{1}\to\Bbb{2}\to\Bbb{3}\to\Bbb{1}`$ ... + +In Limited, you can play as many Contraptions as you have in your pool. How and when your Contraptions are assembled may yield wildly varying results from game to game. Experiment with different combinations and sequences and you're $`\xcancel{\sf{likely}}`$ CERTAIN to create something $`\xcancel{\sf{interesting}}`$ ⚡MONUMENTALLY POWERFUL!!!⚡ \ No newline at end of file diff --git a/docs/Gameplay-Guide.md b/docs/Gameplay-Guide.md new file mode 100644 index 00000000000..797ffdc52e6 --- /dev/null +++ b/docs/Gameplay-Guide.md @@ -0,0 +1,35 @@ +# Getting started + + +## Difficulty + +The difficulty you choose will alter your starting health (15 for easy to 7 for extreme), and the amount of cards you start with when you choose the "pile" mode. Also, cards will sell for less in shops and enemies will have more health. + +Another difference is that on hard/extreme difficulty, enemies that use computer-generated decks (using .json files) will instead use completely random decks that will have nothing to do with their original deck theme. + +## Starting cards + +You will have multiple options as to how your starting pool of cards will look + +1. __Pile__. This will give you a random pile of cards based on the color you choose. The harder the difficulty you choose, the fewer cards you will get. +When you use this option, it is recommended that you create a 40-card deck (minimum deck size) out of the 60 cards you get. + +2. __Chaos__. This is a unique gameplay mode that completely randomizes the enemy decks and the deck you start with. + __This setting is not recommended for new players__ + +3. __Standard__. With this start, you will get three 20-card preconstructed jumpstart decks from the set you choose (or all of them). + +4. __Constructed__. With this setting, you start with a custom preconstructed deck based on a popular theme that's easy to expand during your run. (**Recommended for new players**). Your starting color will determine which starter deck you will get + +## General hints and tips + +1. Don't immediately sell cards that are not useful in your current deck. Some cards can be very useful in certain boss fights, for instance, cards with " protection from red" are very strong against most red bosses. +2. Make sure to travel to the biome capitals early in your run. Every capital contains a shop that sells 1 life and an item that allows you to teleport back to that capital. Also, they sell items that can't be found elsewhere. +3. Every boss gives you +1 starting life. So if you're having trouble with a certain boss, it might be better to try your hand with other bosses. +4. Arena challenges in the capitals can be a good source of money, items, and cards. Though you need a rather strong deck to consistently win there. + +### Insane Guide + +If you are playing on Insane and need some pointers, user KingNishi has written up a nice doc about it + +https://docs.google.com/document/d/1RgO20QpdVOpU-g_E1RL8q1r0C5GUy6y-nnxgXNbi9Wg/pub \ No newline at end of file diff --git a/docs/Getting-Started---Creating-your-character.md b/docs/Getting-Started---Creating-your-character.md new file mode 100644 index 00000000000..fc5466099be --- /dev/null +++ b/docs/Getting-Started---Creating-your-character.md @@ -0,0 +1,7 @@ +# Creating new world + +# Choosing difficulty + +# Choosing Mode and starting deck + +# New Game Plus \ No newline at end of file diff --git a/docs/Home.md b/docs/Home.md new file mode 100644 index 00000000000..47b1224bb98 --- /dev/null +++ b/docs/Home.md @@ -0,0 +1,57 @@ +> [!CAUTION] +> - if you want to contribute to this Wiki please only make pull requests against the main repos docs folder or your changes might get lost +> - due to GitHub limitations all filenames should be unique + +# What is Forge? + +Forge is a "Rules Engine" for the game Magic: the Gathering. +Forge is not related in any way with Wizards of the Coast. +Forge is open source software released under the GNU Public License. + +Up to 8 players are supported, with control of each assigned to human or AI control. Player decks can be imported, user-created with the Deck Editor, or automatically generated. Over 99% (and counting) of all cards in Magic's existence are available, with the missing ones mostly being pointless to implement in the context (e. g. the notorious Chaos Orb) or impossible. That's more than the official Magic Online! + +For a complete list of unimplemented cards, either check the most recent release topic on the forums or use the "Audit Card and Image Data" check from "Content Downloaders" menu. + +Forge creates a unique experience by combining this enormous card library with some RPG elements in [**Quest mode**](https://www.slightlymagic.net/forum/viewtopic.php?f=26&t=9258) (comparable to the 'Shandalar', the late 90's PC Game) on the desktop version, and **Planar Conquest** on the mobile version. Forge now includes a Graphical Map Based game mode called "Adventure Mode" which is more akin to 'Shandalar.' + +Forge also features a wide variety of puzzles. For more details and features see the MANUAL.txt in your game folder. + +Currently, Forge functions best in Human vs. AI matches. Playing against another human player over the Internet with Online Multiplayer mode is functional, but may still result in game play errors. See the Network Play section of this wiki. + +# Adventure Mode +Forge now has an Adventure Mode, along with the Classic deck building and match game modes with AI. + +Adventure mode is a work-in-progress game mode where you explore the ever-changing landscape of Shandalar, and duel creatures to gain gold and new cards ~~to become the best and collect them all!~~ to battle the bosses in the castles for each color. You can visit towns to buy equipment and cards, crawl through dungeons to find artifacts and loot to help you on your journey. Adventure mode is an awesome reimagining of the original 'Shandalar' 90's PC Game in Forge. + +Adventure is baked into the Android/Mobile release of Forge, and as a separate executable already provided in the Desktop release package. + +# Download and Install + +* Most users please see the [User Guide.](User-Guide) + +* For SteamDeck or Bazzite Installation see [Steam Deck Install.](Steam-Deck-and-Bazzite-Install) + +# Support +[Basic Troubleshooting](Troubleshooting-FAQ) - Check here first. + +[Join the Discord](https://discord.com/invite/3v9JCVr)! - We're happy to help you get going. + +[Other Frequently Asked Questions](Frequently-Asked-Questions) - For more advanced questions about forge mechanics and gameplay. + +# Forge Developers + +The original programmer can be found at http://mtgrares.blogspot.com. A while +back he open sourced the project and let the other developers improve it +to their heart's content. He's taken a hands off approach for some time +now, but still talks about it on his blog linked above. + +At this time, there are many developers who help in different stages of +the game. Some of us work on the infrastructure of the program, others +work on creating new abilities and cards that use them, but there is a +lot of behind the scenes action going on. For each release, it is common +for the release developer to give a shout out for those that helped +specifically for that version. Feel free to give kudos there. + +If you are curious about the written/oral history of Forge, we're working on recreating some of that [Forge Historical reference](Forge-historical-reference) + +> Note: if you're reading this locally here's the [Table of Contents](_sidebar.md) diff --git a/docs/How-to-Build-a-Cube.md b/docs/How-to-Build-a-Cube.md new file mode 100644 index 00000000000..a62512be066 --- /dev/null +++ b/docs/How-to-Build-a-Cube.md @@ -0,0 +1,48 @@ +Setting up your cube in Forge is relatively straightforward. Forge needs to know two things: the contents of your cube and the contents of a cube pack + +1) Define the contents of your cube +For this you will need to add your cube list to the 'cube' folder, which can be found under the 'res' folder in your Forge installation directory. +Cube lists are defined in .dck files. You can either create a new one with a text editor, or copy an existing one and change its file name and contents. Regardless of your approach, you should end up with a file that roughly looks like this: + +``` +[metadata] +Name=Two Card Cube +[Main] +1 Abbot of Keral Keep|ORI +1 Abrade|HOU +``` + +**Don't change the first and third line**. On the second line you can put in the name of your cube after 'Name=', in this example the name of our cube is Two Card Cube. Note this name, as you will later need to refer to it. +Starting on the fourth line, you can list each card in your cube. Always start with the number of times a given card is included in your cube (important for those breaking singleton), then the name of the card, then a pipe (the '|' character), and finally the three letter set code. +Tip: An easy way to find the three letter code for a card is to look up that card on www.scryfall.com and search up the correct version of the card. The set code will appear at the top of the prints overview behind the full set name. +Note that this example cube contains just two cards. Adding more is simply a matter of following the same pattern and adding a line for each new cards. + +Once you're done with your file, save it in the 'cube' folder. Use the value of the Name field as the filename and give it the .dck file extension. In this example, we would save the file as 'Two Card Cube.dck'. + +2) Define the contents of your boosters +The next step is to define the draft file that instructs Forge how your boosters look. For this you will need to create a .draft file in the 'draft' folder, which can again be found under the 'res' folder in your Forge installation directory. +Note that the options I will describe here are somewhat limited. Unfortunately seeding boosters is not easily achieved. Let's again look at an example: + +``` +Name:The Two Card Cube +DeckFile:Two Card Cube +Singleton:True + +Booster: 15 Any +NumPacks:3 +``` + +The first line contains the name of the cube as it will be presented in Forge. +The second line contains should match the name field of the .dck file you just created. +The third line defines whether this is a singleton draft format or a regular set format. Note that for cubes breaking singleton, you still want to set this value to True, since you define which cards you break singleton on in the .dck file. + +The fifth line defines how many cards appear in a booster. You can change the number, but do not touch the 'Any', that part instructs Forge to pull cards from the entirety of your cube list. +Finally, the sixth line defines the number of packs used when drafting your cube. + +Once you're done with this file, save it in the 'draft' folder. You can again use the value in the name field, this time in conjunction with the file extension 'draft'. In this example, we would save the file as 'The Two Card Cube.draft'. + +3) Backup both files +The next step is to backup both your .dck file and your .draft file. You'll be happy you did when Forge needs to be reinstalled from scratch for some reason. + +4) Draft! +You've done it! Fire up Forge, and select Booster Draft in the left side menu (it's one of the options under Sanctioned Formats). Click the New Booster Draft Game button, select the option Custom Cube, click OK, choose the cube you just created (remember, the name presented here is the value for the field Name you entered in the .draft file), and click OK again. That's it, you're in. Happy drafting! \ No newline at end of file diff --git a/docs/Keyboard-Shortcuts.md b/docs/Keyboard-Shortcuts.md new file mode 100644 index 00000000000..2f8cf6734cb --- /dev/null +++ b/docs/Keyboard-Shortcuts.md @@ -0,0 +1,11 @@ +# Keyboard Shortcuts + +The following shortcuts are available for the player during the match: + +- Space: Confirm (most times, is the button shown on the left) +- Escape: Cancel (most times, is the button shown on the right) +- E: end turn +- Ctrl + Q: concede +- Ctrl + Z: undo +- Y: auto-yield to a trigger (always YES) +- N: auto-yield to a trigger (always NO) diff --git a/docs/Mana-Shards.md b/docs/Mana-Shards.md new file mode 100644 index 00000000000..e6359a117f5 --- /dev/null +++ b/docs/Mana-Shards.md @@ -0,0 +1,13 @@ +# Mana Shards +Mana Shards are a custom resource created for Adventure Mode in Forge. Shards serve as a secondary form of currency in some parts of the game economy, can be bought and sold via dedicated Shard Trader merchants, and are also used to power the player's special abilities both on the Adventure world map as well as in almost all Adventure Mode duels. During a duel your Shards will appear as a usable resource visible alongside your life total akin to how Energy tokens are used. Your Shards are not tokens or counters, and are thus immune to any abilities that would affect tokens or counters outside of using [equipment](adventure-items) that specifically requires their expenditure. Spending your shards is a permanent choice, however, as your final total at the end of the match will follow you back into your Adventure. + +As Mana Shards are not self-replenishing, using them for activated abilities that affect the game world and in-match gameplay is a way of letting the player have a constant challenge of their own comfort level. If the player is comfortable taking on a fight without using extra abilities afforded to them by use of Shards, they will have more of them available to spend on in town, or can simply save their full might for boss-level entities. + +The most consistent source of Mana Shards is from match wins, players receive one Shard per win without any rewards having to be explicitly added to the encounter. However, when used to power equipment and abilities, one Shard per match will definitely not break even, as most custom Adventure cards and items will cost 2-4 per use. However, Mana Shards can also be purchased in towns, picked up in dungeons, and can be received in large quantities from completing [Quests](Adventure-Quests). + +Mana Shards can be used for: +* Activated in-match abilities +* Activated game map abilities +* Refreshing card shop inventories +* Spellsmith card purchases as an alternative to gold +* Event entry fees as an alternative to gold \ No newline at end of file diff --git a/docs/Missing-Cards-in-Forge.md b/docs/Missing-Cards-in-Forge.md new file mode 100644 index 00000000000..1371515d894 --- /dev/null +++ b/docs/Missing-Cards-in-Forge.md @@ -0,0 +1,77 @@ +## Missing tournament legal cards + +Total number of unique cards in Forge (as of the release of Final Fantasy): 30,331 +**For the latest updates during spoiler season** check out the "Projects" tab on Github for information which cards have been added. It takes some time to implement cards, and WotC doesn't send us spoilers ahead of time, so we barely have any time to implement new cards. Please be patient, and don't ask when cards will be ready. + +| Format | Total Cards | Missing Cards | % Completed | +| --------------- | --------------- | --------------- | --------------- | +| Standard | 4,088 | 0 | 100 % | +| Modern | 20,584 | 0 | 100 % | +| Historic (including Alchemy cards) | 12,921 | 0 | 100 % | +| Vintage/Legacy/Commander (including banned & restricted cards)| 29,185 | 46 | 99.8 % | + + +### Stickers [46 Cards] + +Introduces an entirely new type of game piece to track, which comes in four different varieties - name, ability, art, and stats. They were not widely used and are currently banned in all official formats except commander. + + +1. [Baaallerina](https://scryfall.com/card/unf/35/baaallerina) +1. [Bioluminary](https://scryfall.com/card/unf/38/bioluminary) +1. [Croakid Amphibonaut](https://scryfall.com/card/unf/43/croakid-amphibonaut) +1. [Glitterflitter](https://scryfall.com/card/unf/48/glitterflitter) +1. [Make a _____ Splash](https://scryfall.com/card/unf/50/make-a-_____-splash) +1. [Prize Wall](https://scryfall.com/card/unf/57/prize-wall) +1. [_____ _____ _____ Trespasser](https://scryfall.com/card/unf/61/_____-_____-_____-trespasser) +1. [Unlawful Entry](https://scryfall.com/card/unf/62/unlawful-entry) +1. [Wizards of the _____](https://scryfall.com/card/unf/64/wizards-of-the-_____) +1. [Carnival Carnivore](https://scryfall.com/card/unf/68/carnival-carnivore) +1. [Last Voyage of the _____](https://scryfall.com/card/unf/78/last-voyage-of-the-_____) +1. [Scampire](https://scryfall.com/card/unf/89/scampire) +1. [Scared Stiff](https://scryfall.com/card/unf/90/scared-stiff) +1. [Wolf in _____ Clothing](https://scryfall.com/card/unf/95/wolf-in-_____-clothing) +1. [_____ Balls of Fire](https://scryfall.com/card/unf/100/_____-balls-of-fire) +1. [Big Winner](https://scryfall.com/card/unf/101/big-winner) +1. [_____ Goblin](https://scryfall.com/card/unf/107/_____-goblin) +1. [Goblin Airbrusher](https://scryfall.com/card/unf/108/goblin-airbrusher) +1. [Minotaur de Force](https://scryfall.com/card/unf/114/minotaur-de-force) +1. [_____ _____ Rocketship](https://scryfall.com/card/unf/191/_____-_____-rocketship) +1. [Ticketomaton](https://scryfall.com/card/unf/195/ticketomaton) +1. [Wicker Picker](https://scryfall.com/card/unf/196/wicker-picker) +1. [Tusk and Whiskers](https://scryfall.com/card/unf/182/tusk-and-whiskers) +1. [Aerialephant](https://scryfall.com/card/unf/2/aerialephant) +1. [Robo-Piñata](https://scryfall.com/card/unf/25/robo-pi%C3%B1ata) +1. [Sanguine Sipper](https://scryfall.com/card/unf/26/sanguine-sipper) +1. [_____ Bird Gets the Worm](https://scryfall.com/card/unf/5/_____-bird-gets-the-worm) +1. [Park Bleater](https://scryfall.com/card/unf/21/park-bleater) +1. [Pin Collection](https://scryfall.com/card/unf/23/pin-collection) +1. [Sword-Swallowing Seraph](https://scryfall.com/card/unf/30/sword-swallowing-seraph) +1. [Proficient Pyrodancer](https://scryfall.com/card/unf/120/proficient-pyrodancer) +1. [Wee Champion](https://scryfall.com/card/unf/127/wee-champion) +1. [Chicken Troupe](https://scryfall.com/card/unf/133/chicken-troupe) +1. [Clandestine Chameleon](https://scryfall.com/card/unf/134/clandestine-chameleon) +1. [Done for the Day](https://scryfall.com/card/unf/136/done-for-the-day) +1. [Fight the _____ Fight](https://scryfall.com/card/unf/138/fight-the-_____-fight) +1. [Finishing Move](https://scryfall.com/card/unf/139/finishing-move) +1. [Roxi, Publicist to the Stars](https://scryfall.com/card/unf/177/roxi-publicist-to-the-stars) +1. [Grabby Tabby](https://scryfall.com/card/unf/140/grabby-tabby) +1. [Lineprancers](https://scryfall.com/card/unf/146/lineprancers) +1. [_____-o-saurus](https://scryfall.com/card/unf/148/_____-o-saurus) +1. [Stiltstrider](https://scryfall.com/card/unf/157/stiltstrider) +1. [Costume Shop](https://scryfall.com/card/unf/206a/costume-shop) +1. [Ambassador Blorpityblorpboop](https://scryfall.com/card/unf/161/ambassador-blorpityblorpboop) +1. [A Good Day to Pie](https://scryfall.com/card/unf/12/a-good-day-to-pie) +1. [Command Performance](https://scryfall.com/card/unf/42/command-performance) + +## Missing variant cards + +### [Bounty](https://mtg.fandom.com/wiki/Outlaws_of_Thunder_Junction/Commander_decks#Bounty_cards) (Casual Variant) +The release of the Outlaws of Thunder Junction Commander Decks included a set of twelve "Bounty" cards and rules for a variant format that uses them. This format is not defined in the comprehensive rules, and is not currently implemented in Forge. + +### [Hero's Path](https://mtg.fandom.com/wiki/Hero's_Path) (Promotional Variant) +During the Theros Block, a series of promotional events was held. These featured three PvE encounters with unique rules, along with nontraditional cards for use by both the player and enemies in these encounters. The PvE encounters could be reconstructed in Forge, but supporting the Heroes and Godslayer weapons would add many complications. + + +## Missing nonlegal and funny cards + +Complete support for Un-cards and other non-legal cards is a non-goal of Forge, but individual cards may be implemented when they work within the scope of the project. For a complete list, see [Un-cards, Playtest Cards, and Other Funny Cards](https://github.com/Card-Forge/forge/wiki/Un%E2%80%90cards,-Playtest-Cards,-and-Other-Funny-Cards). \ No newline at end of file diff --git a/docs/Modding-and-Development.md b/docs/Modding-and-Development.md new file mode 100644 index 00000000000..ddd3ac62af4 --- /dev/null +++ b/docs/Modding-and-Development.md @@ -0,0 +1,28 @@ +## Modding and Development + +With the addition of new planes in Adventure Mode, comes a framework to allow greater customization and even expansion of the available planes. The details behind the framework and each category can be found below. As well as a new section being built with some basic information on each piece of plane construction and modification. The sections will also define some Best Practices to be maintained. + +### Getting Started + +Modding Adventure mode comes in many fashions. From making small changes to a core plane, such as changing the music that plays on the overworld. To something as complex as an entirely new plane. Regardless of your intended goals, the first thing to do is set-up a back-up method. As any changes you haven't had incorporated into the main game, will potentially be lost on each update of Forge. Since this is a Git project, the method that will be recommended by this wiki, and referenced for the future, is simply to create your own git branch, and use a local repository to control all your files. It is also recommended to follow the directions to [set-up IntelliJ](https://github.com/Card-Forge/forge/wiki/IntelliJ-setup), to manage your local files. (Again, this is the method that will be referenced elsewhere in this wiki.) + +### Tools + +The following additional tools can also be very useful, or even mandatory, to have for your mod, depending on what all you want to do in your mod/addition. + +**[Tiled](https://www.mapeditor.org/)**: If you want to modify or create any maps; (caves, dungeons, towns, etc.) Forge utilizes Tiled. If you want to learn more on this topic, you can find it in the [Create new Maps](https://github.com/Card-Forge/forge/wiki/Create-new-Maps) section of the Wiki. + +**[GIMP](https://www.gimp.org/)**: Many of the art files such as the tilesets used in Tiled, are made in GIMP. (A free graphical manipulation program, similar to Photoshop.) While not required to work in Forge's files, if you want to create your own art assets, this is the program that will be used for examples in this Wiki. + + +### Tutorials + +The following pages on this Wiki contain some basic tutorials on various facets of modding Adventure mode, to help get you started. + +[Tutorial 1, Create your First Plane](https://github.com/Card-Forge/forge/wiki/Tutorial-1-Create-your-First-Plane) + +[Tutorial 2, A New Look (creating your first map.)](https://github.com/Card-Forge/forge/wiki/Tutorial-2-A-New-Look) + +[Tutorial 3, Configuration (Configuring your Plane)](https://github.com/Card-Forge/forge/wiki/Tutorial-3-Configuration) + +UNDER CONSTRUCTION, STAY TUNED \ No newline at end of file diff --git a/docs/Network-FAQ.md b/docs/Network-FAQ.md new file mode 100644 index 00000000000..ddf55b98e69 --- /dev/null +++ b/docs/Network-FAQ.md @@ -0,0 +1,34 @@ +# Frequently Asked Questions About Networking Games + +### Can I play you? + +* No. Thanks for the offer, but right now I don't have time. And the truth is I'm just not that good at magic really, the AI is a better opponent than me. + +### Can Mac Players play against Windows? + +* Yes! Forge is completely cross platform, as it's built in java the same software is running on every system, this allows cross platform compatibility; Android to Windows to MacOS/OSX... potentially anything that can run Java. + +Note: Forge is not built for iOS, as iOS doesn't support or run java. + +### Can I play random players? + +* No. Network play is designed, currently, to play against someone you know. If you want to play against random people, you can make a request in the discord. + +### Why can't my friend connect? + +* There's three major reasons + +1. You're not on the same network and you're trying to use the local IP address. Please read [this part of the guide](network-play#remote-network). +1. You're on the same network and your local firewall isn't configured correctly. Please read [this part of the guide](network-play#host-based-firewall). +1. You're not on the same network and your router ports aren't forwarded. Please read [this part of the guide](network-play#port-forwarding-or-nat). + * IF you're not on the same network and you can't access port forwards, you'll need this [Network Extra](Networking-Extras). + +### Is it possible to play with people not on the same Wi-Fi? + +* Yes. There's two primary ways to handle this: + +1. On a home internet: Open and forward ports on your home router to your computer hosting the game. + * Additional information is here: [Remote Networking](network-play#remote-network) +2. On public Wi-Fi hotspots or cell networks: Use a VPN to create a private network. + * Additional information is here: [Network Extra](Networking-Extras) + * This may be more specific to getting you going: [VPN Providers](Networking-Extras#virtual-private-network-service-providers) \ No newline at end of file diff --git a/docs/Network-Play.md b/docs/Network-Play.md new file mode 100644 index 00000000000..8821fbb9057 --- /dev/null +++ b/docs/Network-Play.md @@ -0,0 +1,216 @@ +### Disclaimer: + +While multiplayer over a network *does* currently work, and complete games *have* been played, it is still very much a "*work in progress*". This means; + +* You **will** find bugs. When they appear; + * They will likely be mid-game. + * They will likely make it impossible to complete the current game/match. + * You **will** need to restart both the client's and host's games. + +**You've been warned!** But please report bugs and issues, this will help make network play more stable. + +*** + +### Support: +**We have a discord channel dedicated for network play related matters: https://discord.gg/nsAhGwD** + +I've created a Networking FAQ, check the sidebar for it. I'll update it as I get **Q's** in discord that are **FA**. + + +Note: This guide has been written to be as comprehensive as possible; from a quick start, to troubleshooting, to network configuration and concepts. It's not possible to identify all network configurations or issues, but the typical and most basic are represented in this guide. I personally want network play to be easy to use, set up, and for it to be stable. The more people playing over network the more its play tested, and more bugs can be found. + +*** + +### Requirements: + +* **At least two devices**, all devices must be running the same version of Forge. + * Host: The device running forge as the "server." + * Client: The device connecting to the Forge server. +* **A Network**; + * Local: Wi-Fi in the same home, "Wi-Fi Direct" between two devices, or Ethernet for PC + * Remote: IPv4 Internet (Home Wi-Fi Router to the internet, Ethernet to Router to internet) +* **Firewall Exclusions and Port Forwards**; + * NOTE: Don't DISABLE your firewall, use exclusions to allow forge app or the port specifically. + * By default Forge (when running as a server) will listen on port **36743**. + * For clients to be able to connect to the server, this port will need to be allowed through the server host's local firewall (Windows Defender Firewall, Ubuntu/Linux UFW, etc.). + * Additionally, if the client is "remote" or outside your local network, the above port will need to be forwarded through the *host's router's firewall* (see below) **and** through the *host's local firewall*. + +*** + +### Quick Start: + +* Start Forge on both devices; please confirm versions numbers are the same before continuing. + * Mobile players: Choose "Classic Mode" +* Go to + * Mobile: "Play Online" + * Desktop: "Online Multiplayer" > "Lobby" > Click "Connect to Server" +* Decide who will "host" the game. + * That person must **not** put anything in the "Connect to Server" popup box, and click OK. + * Forge will attempt to show the IP Address of the Host's machine verify before providing to the other player: + * **For local play,** the IP address would probably start with 192.168... verify with your device's network settings. ```Forge may recommend to use the word "localhost", ignore this. I suspect this is a java issue with naming the network address.``` + * **For remote play,** the IP address should be verified with https://canyouseeme.org/ +* The person **not** hosting is a "client" and MUST to put the IP Address determined from the above step into the "Connect to Server" popup. +* You can then start a network match; + * The host can select the type of match, team configuration, number of matches per game, and other settings. + * All Players can select their decks, their sleeves (if mobile), and avatar. + * Toggle the ready switch to signal you're ready. + * When all players are ready to start, the host can start the match. + +*** + +### Troubleshooting: +Folks in discord are there to help you get your game going! +#### Current Network-based Multiplayer Known Issues + +* Some points where the game is waiting on an opponent's decision/action do not properly indicate this. #158 +* On mobile, if you are in the same room without Wi-Fi access, try "personal hotspot"/"WiFi-Direct" options first, you don't need a port forward that way. + * If you are on the same Private Wi-Fi, you do not need to do this. + * If you are on a Public Wi-Fi or Hotspot, you will probably not be able to communicate, using a "personal hotspot" would be a better option. +* Due to the lack of traffic optimizations, playing over network feels choppy/laggy. A single game can transfer hundreds of megabytes between all clients! _Slow networks will be slow._ +* If your ISP uses IPv6 but uses something called Dual Stack lite, network play won't work. Network play **requires IPv4 addressing** or IPv6 using (full) Dual Stack! + +#### "Disconnected From Lobby" +* A common cause for this is that the client and server resource (**res**) folder content differs. This can be verified by checking the game log and looking for an IOException referring to a "Card ... not found". #175 + +*** + +## Network Configuration - Setup and Testing +_Please consider reading "Hosting a Server" section for the concepts behind the setup and testing, and terminology._ + +### Local Network: + +#### **Host Based Firewall** + +* Mobile Device: There shouldn't be much you need to do, start Forge, host a server. Anyone else on the same local network should be able to connect. +* Desktop Device: Depending on your OS, you may need to allow Forge through the host based firewall. Windows Defender Firewall, Ubuntu would have UFW. + * Either allow the app itself. + * or the default port of 36743. + +#### **Validate** + +* Android Device: You can test from your mobile device with PortDroid, to scan if the port is open: + * Setup + * Install [**PortDroid**](https://play.google.com/store/apps/details?id=com.stealthcopter.portdroid) + * Open **PortDroid** + * From the menu in the upper left, select *Port Scanner* + * Select the three dot menu in the upper right. + * Select *Port Scanner Settings* + * At the bottom under *Port Lists*, select *Add New Scan* + * Enter *Forge* for the *Scan Title* + * Enter *36743* for the ports to scan. + * Select *ADD SCAN* + * Test + * From the menu in the upper left, select *Port Scanner* + * In the first field on the left, enter the internal IP of the host system. + * From the middle drop down, select *Forge* + * Select *SCAN* + * If all goes well, you'll receive an indication that 1 port is open. + +* Windows Device: You can run telnet to test if the port is open. + * Setup + * Install Telnet: As telnet is no longer available in Windows by default, [please follow the instructions found here](https://social.technet.microsoft.com/wiki/contents/articles/38433.windows-10-enabling-telnet-client.aspx) before continuing. + * Test + * Open a **command prompt** + * Type: *telnet INTERNALIP 36743* (where INTERNALIP is your forge host's internal IP address) + * Press *Enter* + * If all goes well, your command prompt title bar will change to *Telnet* followed by the host system's IP address. At this point you can close the window. + * If it doesn't you'll see something like the following: ```Could not open connection to the host, on port 36743: Connect failed``` + +* Mac OSX Device: + * I'll quote Apple "Telnet is insecure and you should move to more secure communications methods, like HTTPS." + * You can't be trusted to use this on your own. + * Maybe check the app store for a "port scanner" follow their instructions for scanning for port "36743" on "localhost". + +* Linux Device: You probably don't need our help. Install telnet from your package distro and run telnet similar to Windows. + +If you've passed the validation, then your configuration should be good and you can provide your partner the IP address of your device. Please confirm your IP address with your devices' network configuration settings. + +### Remote Network: + +A Remote Network setup **MUST** pass the local network setup above. + +#### **Port Forwarding or "NAT"** + +_Each router is different, so instead of specific step by step instructions here, this is general theory._ + +* Setup + * You'll need to find the admin interface for your router. On most consumer models, this is a web page that can be accessed at `http://_._._.1` where the first three parts of the address are the same as your system's internal IP address (as reported by Forge). For example, if your internal IP address is 192.168.0.54, the admin page would likely be at: `http://192.168.0.1/`. + * Once inside the interface, you'll need to look for "port forwarding". This is sometimes buried under different menu options, like "advanced". Once located, you'll want to add a new port forward. The interface will likely ask for several bits of information: + * remote or external IP + * external port + * internal host or IP + * internal port + * Both port values should be set to **36743**. + * The remote or external IP should be left **blank** or set to **any**. + * The internal host or IP would be the machine running Forge. + +Help for your specific router may be here: https://portforward.com/router.htm +*Don't download software you don't know what it does, that website's software is NOT recommended to be used, just use the guides they provide.* + +* Testing + * For the external testing use a site like: [CanYouSeeMe.org](http://canyouseeme.org) + * Your external IP should already be detected by the site. + * Simply enter **36743** for the port to check and click *Check Port*. + * If all goes well, you'll get a *Success* message back. + +If you've passed both of the local and remote testing, then your configuration should be good and you can provide your partner the IP address of your network. Please confirm the IP address with [CanYouSeeMe.org](http://canyouseeme.org). + +*** + +## Network Configuration - Hosting a Server + + Welcome to hosting a server, this is a more descriptive view as to what's going on when you host a server. These concepts can be applied to most server set ups, but we will focus on Forge in three different configurations: + +* **Local Networks (Over your local network)** + * Local Private Wi-Fi, "Wi-Fi Direct", or "Hotspot" from a mobile device, also wired networks for PC players. +* **Remote Networks (Over the Internet)** + * Networks you don't control. (Cell to Cell, or Public Hotspots.) + * Network you do control. (Home internet.) + +#### Local Private Network: + +A local network's IP addresses are typically self managed by your router, with what is called the DHCP server. These addresses are assigned to your devices automatically and are what you would use to connect to each other device. These devices will talk to each other over the Wi-Fi Access Point (which is typically your router.) In a wired environment they will talk across the wire through a switch (also typically your router.) + +When you do a "Wi-Fi direct" in Android, this allows one device to be the Wi-Fi access point, other devices can connect to this network as a local private network. + +#### Remote Network: + +Remote networks are two networks separated by the internet, or by what is called VLANs. On public hotspots, two devices on that "same Wi-Fi" will be separated by these VLANs, and can not talk to each other directly. Similarly, two cell phones on the same provider will be separated by VLANs, or just by given an IPv6 address. + +If you do not control your internet connection, like a public hotspot or a cell provided internet you'll need to do a software defined network (SDN), or a virtual private network (VPN) which is outside the scope of this wiki and any discord help. It is recommended to connect via "Wi-Fi Direct" if you are near each other instead. + +If you control your internet connection's network, such as it's your home network and can access the router configuration, you can host a game by opening the ports on your network, and allowing the game through the router's firewall. This allows the remote player, on practically any other internet connected network to connect through your router to your server. + +### Firewalls: + +Firewalls are kind of archaic, but are still implemented, and are designed to block your connection intentionally. + +A quick history; Computer OS's used to respond with a port being "closed" if no service was available on it, and "open" if a connection was able to be opened. An OS should not report a port as "closed", just ignore the request. (Reporting a port as "closed" is like hearing a knock on the door, opening the door and slamming it shut.) This wasn't a security concern at the time, so attackers were able to identify if a computer was "alive" if it responded with a "closed" port, and would moved on if it didn't get a response at all. Any response from a computer triggered a deeper attack to find which ports were actually "open." Thus firewalls were born... + +Firewalls were initially implemented to prevent open ports from talking to just any random device, limiting access to only approved connection requests and limiting attackers from gaining access, however ports were still reporting as "closed" in the early days. Firewalls then implemented a function to prevent ports without services on them from reporting "closed" at all and just ignored the traffic instead, making it appear as there was no computer at that address. + +In devices with firewalls you need to allow a port for a service through the Host Based Firewall. You'll also need to do this on your router for remote games as well for the Network Based Firewall. I believe, most Linux devices (including Android) no longer report "closed" for ports without services, and firewalls are typically not needed as the only ports that should be open are ones with services needing ports open. In Windows this is not the case, and is why Windows Defender Firewall is implemented and ports will need to be opened to allow Forge to accept a connection. You should however be able to connect to a mobile device without concerns of a firewall blocking you. + +### Port Forwarding: + +In routers, just after the firewall is a routing function called Network Address Translation. "NAT" allows a computer behind the router (from the internet perspective) to provide its service as if it is the router itself. In most routers, when you enable a NAT/Port Forward, it will also allow the Port through the Firewall. + +```[Device](Internet) ---> 36743{Router} ---> 36743[Host]``` + +Once this initial communication is started, the two devices will talk over their designated and determined ports and have a TCP "conversation" (called a stream) until the connection is completed or closed. Once the TCP stream is closed, another device can connect. + +If you've used PlayStation systems you might recognize the term "NAT"; level 1, 2, or 3. This naming is not part of the standard, and is bad and confusing; (I believe) "Level 1" meant no NAT'ing all traffic was redirected through PlayStation servers, "Level 2" meant Half Open or Static NAT (which is what we will do for Forge), and "Level 3" mean the use of UPnP/DLNA which was automatic NAT services and allows ALL software to control your firewall for you. (UPnP should be concerning from a security perspective.) + +### Network Topology: + +Here's how your typical network topology and network flow will look, when connecting and playing with Forge. + +* Typical Wi-Fi Home Network with 2 devices + * A Figure of Local Game Connection through Wi-Fi with PC Host. +![image](https://user-images.githubusercontent.com/1243145/186308954-930e2db5-e428-401f-8117-d223141a5037.png) + * Mobile/Android would be similar, but without the firewall on the host. (Personally, I believe this may change because of how stupid android "security" is becoming, and you'll have a firewall you can't tune, or make exceptions.) + +* Typical Remote Network Connection with 2 devices + * A Figure of Remote Game Connection through Firewall and Port Forward on Router. +![image](https://user-images.githubusercontent.com/1243145/186335351-c2c670bd-599e-497a-a210-ab866d7ebd95.png) + diff --git a/docs/Networking-Extras.md b/docs/Networking-Extras.md new file mode 100644 index 00000000000..2557a732562 --- /dev/null +++ b/docs/Networking-Extras.md @@ -0,0 +1,83 @@ +# Disclaimer + +This page should be considered "experimental" and **at your own risk**. + +Forge, it's developers, and supporters won't be able to help you with this. I'll try to provide some details about setting up the software and networks listed here, but please direct any support to the platform or provider's support and documentation. This should mostly be just a list of options of how to set up a remote game without access to port forwards on your network. + +_Please don't use this guide to get around security of your work/school network on a work/school computer, only use it for accessible networks like hotspots or the like, with your personal devices._ + +_Attempting to subvert security measures may be considered a crime, in the least you'll get fired or expelled, I like you I don't want that on you._ + +# Networks + +So you found out you don't have control over your network to open ports for a remote game connection, what can you do? **Well, hopefully this guide will get you on the right track.** + +Some scenarios where this may be; ++ you're on a cell data connection ++ you're on a public Wi-Fi Hotspot ++ you're at school or work ++ dad is kind of a tightwad and doesn't want games to have open ports on the router. + + sorry, my dad was actually cool about this, and let me manage the network router. + +These connections don't allow you to forward ports to your device's IP address, and that's a problem with Forge for remote connections. You'll need to be able to communicate with a server or service outside of the network first to set up a line of communication, which then allows you to make direct connections to another device. The most common concept is a Virtual Private Network or VPN, allowing a virtual network to be instantiated, and connected to. Additionally newer concepts such as Software Defined Networks can provide similar functions, without the layers of security/encryption a VPN would. + +## Virtual Private Network Software + +VPNs were not designed to subvert security, but to provide a secure communication path on less secure networks. Allowing enterprise or work computers the ability to remotely connect to the work network. + +Today, most VPN service providers provide a secure channel to the internet for "completely legitimate" reasons. However, going back to the original intent you can stand up a virtual private network that you and friends can access using VPN Software. + +**Don't confuse a VPN Provider (PIA, Nord, Etc.) as a VPN Software or Service you run (OpenVPN).** + +### Software: +If you know of other VPN software (NOT SERVICE PROVIDERS), please expand this list. + ++ OpenVPN + - VPN Software you can run on a router or a server. ++ IPSec Tunnel (?) + - A part of VPN that you typically run on two routers? + + +### Notes: + +VPN Software will still require control of **a network**, a place to install the VPN, and have remote access to it. So this is where complexity is introduced, you'll either need to set up the VPN on a home network you can control, or on a server that you can rent from a provider with a public IP address you can access. Once this is done, both players can install the VPN software on their devices, and connect to that server. + +Once you've connected to a VPN Server, you can return to the Network Play guide and test your connections as a "Local Private Network". + +## Virtual Private Network Service Providers + +Some VPN Service Providers provide an actual VPN service, and not just a pipe for "completely legitimate internet access" purposes. Since these are rarer please only put these types of VPN providers here, **everyone can google** the others. + +### Providers: + ++ [RadminVPN](radmin-vpn.com) + - A free VPN provider for actual Private Networking Access. ++ [Hamachi by LogMeIn](https://www.vpn.net/) + - Free for up to 5 computers. + +### Note: +Some "completely legitimate internet access" VPN Service providers may allow you to have port forwards through their networks. Check with the provider before signing up, however both players might need the same provider but might not. You can search google for those VPN providers, **we don't need to list them here.** + + +## Software Defined Networks + +A software defined network is similar to a VPN, but without the encryption and stuff that can slow and prevent a connection out. A Software Defined Network by a provider means you don't need to have your own server, they provide the remote access. Therefore you only need to configure the basic network settings like the IP Schema, and then allow access. + +Once you've configured the "virtual switch", users can connect with the provider's software, you can accept their access, then assign an IP address, and connect to each other. + +### Providers: +Please expand this list if you know of others! + ++ [ZeroTier One](https://www.zerotier.com/download/) - It's free for personal use. + +### Notes: + +Configuring a network can get complex, however a simple network should already be configured by the provider. You should be able to follow the instructions from the provider to stand up your first network, provide your friend the link to the software, the access code to the network, and accept their access request. + +Once you've done this, you can return to the Network Play guide and test your connections as a "Local Private Network". + +## Security + +As a general note, if you use a provider for network traffic including a VPN provider; They have access to **everything** you do on their network! They may not see the data if you use HTTPS for encryption, but they can see where you go, and can collect that meta data. + +Secondly, being on a private network (VPN or SDN) with anyone else does put your device at risk, especially if your device has security vulnerabilities that aren't patched. It is recommended to only provide access to people you TRUST on these private networks you stand up, and disconnect when you're not using them. **Don't create a network and provide access to hundreds of people and stay connected, you're ONLY asking for trouble.** \ No newline at end of file diff --git a/docs/Retired/(SM-autoconverted)-Duplicate-decks-checking-tool.md b/docs/Retired/(SM-autoconverted)-Duplicate-decks-checking-tool.md new file mode 100644 index 00000000000..7e5c56273de --- /dev/null +++ b/docs/Retired/(SM-autoconverted)-Duplicate-decks-checking-tool.md @@ -0,0 +1,23 @@ +**[WARNING!!!]** + +Page imported from the old SlightlyMagic wiki. To be integrated into other wiki pages and/or README... or deleted. + +--- + +## Duplicate decks checking tool + +This python script will check through your deck files and identify which +ones are similar to each other. + +It has been tested with Python 2.6. + +The source can be found on [it's bitbucket +page](https://bitbucket.org/asret/forge/src/tip/deckdupcheck.py), or +[downloaded +directly](https://bitbucket.org/asret/forge/raw/tip/deckdupcheck.py). + +To run it, download it to your forge's "res" directory and invoke the +python interpretor on it. + +It still needs work. At present it checks all decks against every other. +This means it will output each matching pair twice - once for each deck. diff --git a/docs/Retired/(SM-autoconverted)-maven-build-system.md b/docs/Retired/(SM-autoconverted)-maven-build-system.md new file mode 100644 index 00000000000..6b08c191971 --- /dev/null +++ b/docs/Retired/(SM-autoconverted)-maven-build-system.md @@ -0,0 +1,125 @@ +**[WARNING!!!]** + +Page imported from the old SlightlyMagic wiki. To be integrated into other wiki pages and/or README... or deleted. + +--- + +**WORK IN PROGRESS** + +### Install Maven + +. + +To test your installation you should execute the following command: + +`mvn --version` + +You should see something like like this: + +`Apache Maven 3.0.3 (r1075438; 2011-02-28 09:31:09-0800)` +`Maven home: /opt/local/share/java/maven3` +`Java version: 1.6.0_24, vendor: Apple Inc.` +`Java home: /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home` +`Default locale: en_US, platform encoding: MacRoman` +`OS name: "mac os x", version: "10.6.7", arch: "x86_64", family: "mac"` + +#### Mac OSX + +Maven should already be installed. To test installation, open the +Terminal application run the test command above. + +#### Windows + +1. Use the above link to download the zip file. +2. Unzip the file into a directory. +3. Add the directory to your "PATH" variable. +4. Open a command window +5. Execute the test command above. + +#### Linux + +1. Open your package manager e.g. Synaptic on Debian +2. Install the Maven2 package +3. Open a Terminal +4. Execute the test command above. + +### Install SVN + + + +To test your installation you should execute the following command: + +`svn --version` + +You should see something like like this: + +`svn, version 1.6.17 (r1128011)` +`compiled Jun 2 2011, 09:40:34` + +#### Mac OSX + +Maven should already be installed. To test installation, open the +Terminal application run the test command above. + +#### Windows + +1. Use the above link to download the zip file. +2. Unzip the file into a directory. +3. Add the directory to your "PATH" variable. +4. Open a command window +5. Execute the test command above. + +#### Linux + +1. Open your package manager e.g. Synaptic on Debian +2. Install the Subversion package +3. Open a Terminal +4. Execute the test command above. + +### Build Forge + +From a terminal window, go to the directory where forge was checked out +via GIT. Update to the latest version of the code + +`mvn scm:update` + +Use this command to perform a simple build of the jar file + +`mvn -U -B clean install` + +Use this command to do a snapshot package build + +`mvn -U -B clean -P osx,windows-linux install` + +Use this command to do a snapshot package build of the Windows/Linux +package only + +`mvn -U -B clean -P windows-linux install` + +Use this command to do a snapshot package build of the Mac OSX package +only + +`mvn -U -B clean -P osx install` + +Use this command to do a snapshot package build and site deployment + +`mvn -U -B clean -P osx,windows-linux install site deploy site:deploy` + +Use this command to do full package build and upload to GoogleCode + +`mvn -U -B clean -P osx,windows-linux install site release:clean release:prepare release:perform -Dusername="``" -Dpassword="``"` + +where and are your GoogleCode credentials (typically +something like "you@gmail.com" "w4e4sdg") + +### Build System Utilities + +These utilities are used in the build process. They are automatically +included in the build. The links are for reference only. + +[Google Upload](http://code.google.com/p/maven-gcu-plugin/wiki/Usage) + +[Jar Bundler](http://www.informagen.com/JarBundler/) + +[Create DMG Script](http://www.yoursway.com/free/#createdmg) + diff --git a/docs/Retired/cardscripting.md b/docs/Retired/cardscripting.md new file mode 100644 index 00000000000..a2452dbfde1 --- /dev/null +++ b/docs/Retired/cardscripting.md @@ -0,0 +1,73 @@ +What follows is a rough start of an API document for cardscripts. + +| Property | Description +| - | - +|`A`|Ability +|`Colors`|Color(s) of the card

When a card's color is determined by a color indicator rather than shards in a mana cost, this property must be defined. If no identifier is needed, this property should be omitted.

*`Colors:red` - This is used on Kobolds of Kher Keep, which has a casting cost of {0} and requires a red indicator to make it red.

*`Colors:red,green` - Since Arlinn, Embraced by the Moon has no casting cost (it's the back of a double-faced card), the red and green indicator must be included. +|`DeckHints`|AI-related hints for a deck including this card +|`K`|Keyword +|`Loyalty`|Number of starting loyalty counters +|`ManaCost`|Cost to cast the card shown in mana shards

This property is required. It has a single parameter that is a mana cost.

* `ManaCost:no cost` for cards that cannot be cast
* `ManaCost:1 W W` sets the casting cost to {1}{W}{W} +|`Name`|Name of the card

A string of text that serves as the name of the card. Note that the registered trademark symbol cannot be included, and this property must have at least one character.

Example:
* `Name:A Display of My Dark Power` sets the card's name to "A Display of My Dark Power" +|`Oracle`|Oracle text +|`PT`|Power and toughness +|`R`|Replacement effect +|`S`|Static ability +|`SVar`|String variable. Used throughout scripting in a handful of different ways. +|`T`|Triggered ability +|`Text`|Text on card +|`Types`|Card types and subtypes

Include all card types and subtypes, separated by spaces.

Example:
* `Types:Enchantment Artifact Creature Golem` for a card that reads Enchantment Artifact Creature -- Golem + +** Parameters for abilities and variables and known accepted values ** + +(incomplete list): + +* `AB$`: Ability +* `Ability$`: Ability +* `ActiveZones$`: Zone +* `Affected$`: Card +* `AffectedZone$`: Zone +* `ChangeNum$`: Integer +* `ChangeValid$`: CardType +* `ColorOrType$`: `Type` +* `Cost$`: Cost +* `Count$`: `xPaid` +* `DB$`: DB +* `Defined$`: Player, Card +* `Description$`: Text +* `Destination$`: Zone +* `DestinationZone$`: Zone +* `DigNum$`: Integer +* `Duration$`: UntilYourNextTurn +* `Event$`: Event +* `Execute$`: DB +* `Hidden$`: boolean +* `KW$`: Keyword +* `LifeAmount$`: Integer +* `MayPlay$: boolean +* `Mode$`: Mode +* `Name$`: Text +* `NotCause$`: Ability +* `NumAtt$`: `+1` +* `NumCards$`: Integer +* `Optional$`: boolean +* `Origin$`: Zone +* `Produced$`: ManaType +* `References$`: SVar +* `ReflectProperty$`: Property +* `ReplaceWith$`: Text +* `SP$`: Spell +* `SpellDescription$`: Text +* `StackDescription$`: Text +* `Static$`: boolean +* `SVars$`: SVar +* `TargetMax$`: Integer +* `TargetMin$`: Integer +* `TargetPrompt$`: Text +* `TriggerDescription$`: Text +* `TriggeredCard$`: Property +* `Triggers$`: Mode +* `TriggerZones$`: Zone +* `Valid$`: `Triggered` +* `ValidCard$`: Card +* `ValidTgts$`: Player, CardType \ No newline at end of file diff --git a/docs/Retired/images.md b/docs/Retired/images.md new file mode 100644 index 00000000000..7ae17db874d --- /dev/null +++ b/docs/Retired/images.md @@ -0,0 +1,5 @@ +Forge can work with card images of your choice. + +The naming convention is as follows: + +If you have an older Android device for increased performance or to save bandwidth it might be a good idea to use lower resolution images instead: https://www.slightlymagic.net/forum/viewtopic.php?f=15&t=29104 \ No newline at end of file diff --git a/docs/Snapshots-and-Releases.md b/docs/Snapshots-and-Releases.md new file mode 100644 index 00000000000..d32e6e06753 --- /dev/null +++ b/docs/Snapshots-and-Releases.md @@ -0,0 +1,46 @@ +## Snapshots + +Currently (as of Dec 2023), Desktop and Android snapshots have been automated via GithubActions. + +You can see the workflows here. +[![Create Desktop/Android Snapshot](https://github.com/Card-Forge/forge/actions/workflows/snapshot-both-pc-android.yml/badge.svg)](https://github.com/Card-Forge/forge/actions/workflows/snapshot-both-pc-android.yml) +These run daily at around 2pm ET time for people looking for them. The upload scripts should take care of removing the previous files uploaded on the last run. If necessary, you can run these scripts manually. Occasionally these builds fail to upload due to unclear reasons. Retrying the snapshot usually seems to fix the issue. (As of 2/23/2025, the tag is tagging an old SHA. We'll try to resolve that, but know that the snapshot file time represents which code is utilized.) + +The latest daily snapshots can be found [here](https://github.com/Card-Forge/forge/releases/tag/daily-snapshots) + +## Releases + +Currently (as of Dec 2023), Desktop releases run an automated script with a few manual pieces. + +Currently (as of Dec 4, 2023), Android releases are not run. Although, hopefully with the Android snapshot now automated we'll be able to get back to getting a full release for Android as well. + +### Desktop Release Process + +It would be useful for other people to be comfortable with the release process. Currently, GitHub has the secrets required to add a snapshot or full release, so it's mostly just running through the steps below to kick off and validate a release. + +1. Run Build +* Verify test suite is passing on the repo +[![Test build](https://github.com/Card-Forge/forge/actions/workflows/test-build.yaml/badge.svg?branch=master)](https://github.com/Card-Forge/forge/actions/workflows/test-build.yaml) +* Create a new branch from the default branch +* Run the https://github.com/Card-Forge/forge/actions/workflows/maven-publish.yml workflow on the new branch +2. Verify +* After it completes, download the finished package. Extract it and do a quick verification that it works. +3. For snapshot updates, a few files currently need manual updates. Check out this PR for an example: https://github.com/Card-Forge/forge/pull/4293/files +(Hopefully in the next few iterations, we'll have this automated during the release) +* forge-gui-desktop/pom.xml +* fromRef +* forge-gui-android/AndroidManifest.xml +* android::versionCode +* android::versionName +* forge-gui-android/pom.xml +* alpha-version +* forge-gui-mobile/src/forge/Forge.java +* CURRENT_VERSION +4. Merge +* Create a PR from your branch and get it merged as quickly as you can (ideally before other PRs are merged). +* Create a new release from https://github.com/Card-Forge/forge/releases +* Upload the package and its sha to the create new release page + + +4. Marketing +* Advertise in the #announcements channel in the Discord \ No newline at end of file diff --git a/docs/Sprite-Atlas.md b/docs/Sprite-Atlas.md new file mode 100644 index 00000000000..b99388e4c31 --- /dev/null +++ b/docs/Sprite-Atlas.md @@ -0,0 +1,2 @@ +# Adventure Sprite Atlases +(Placeholder, actual content coming soon) \ No newline at end of file diff --git a/docs/Steam-Deck-and-Bazzite-Install.md b/docs/Steam-Deck-and-Bazzite-Install.md new file mode 100644 index 00000000000..716c41db33e --- /dev/null +++ b/docs/Steam-Deck-and-Bazzite-Install.md @@ -0,0 +1,69 @@ +# SteamDeck and Bazzite Support + +_This instruction was written using Bazzite, however should be similar enough for SteamOS._ + +In order to support the SteamDeck "natively" for full Forge Desktop mode, we would likely need to have a flatpack installer for the best user install experience, currently Forge has no intention to have a flatpack. The current **best** and recommended way to have Forge on your SteamDeck is to install and run the Android APK version in Waydroid Android Container. + +* You will need to have installed Waydroid first, this reddit post may work for you; https://www.reddit.com/r/SteamDeck/comments/1ay7ev8/how_to_install_waydroid_android_on_your_steam_deck/ + +## Installing Forge Android in Waydroid (Recommended Method) +Once you've installed Waydroid, you can follow the same steps you would in any Android device. + +1. Open browser to https://github.com/Card-Forge/forge + +1. Open Snapshots from the Link in the ReadMe Page + +1. Download the Snapshot APK + +1. Open the APK from the download notification in your Android OS. + +1. Give permissions to allow the browser to run APK installers. + +1. Once installed, Forge should request image storage and audio permissions. + +1. Forge sends you to the Settings Page to provide permissions. + +1. Run Forge from the Application Drawer (likely Forge is still running) + +1. You can tap anywhere else on the screen and Forge will restart. + +1. Once Forge restarts it will ask to update the Assets. + +1. Congratulations you can play Forge on your SteamDeck. + +### Running Forge Android in Steam GameMode + +My understanding is that Waydroid running detached Android apps doesn't work at the moment, so the best way to run Forge is to start Waydroid itself from Steam Gamemode and then start the Forge App. To do this you will need to add Waydroid to Steam as a non-Steam Game, controller support should be natively supported this way, you should also enable touch screen pass-through in the Steam Controller configuration if you didn't already during install of Waydroid. + +1. Add Waydroid as Non-steam game. + +1. Configure/Enable controller support with touchscreen. + +1. Start Waydroid from Steam GameMode. + +1. Let the Waydroid Android OS boot. + +1. Start Forge + +## Forge Desktop (JAR or BZ2 File) + +### Installing Forge Desktop Natively (Not Recommended) + +Barring a flatpack (and packing Java with Forge), the correct way to install Forge Desktop natively (installer JAR or BZ2 archive) would be to install Java OpenJDK in the OS globally. **This is against Steam and Bazzite Dev recommendations**, however is doable; + +* For Bazzite: simply use `rpm-ostree install java-21-openjdk` + +> You can then install forge as you would in any Linux OS, following the [user guide](user-guide). +> Again, this is against Bazzite devs recommendations, and they will likely belittle you and tell you you can't ask for support, if you tell them you did this like a normal Linux user would. + +* For SteamOS: I believe you need to unlock the OS, install java from the package repo, then lock the OS again. + +> This Wiki will not provide instructions for this, if you feel you can do this you can probably look up guides to help you. + +### Installing Forge in a Container/VM (Boxes or Other) + +Another option is to install Forge in a Linux Container or VM, where you can download and install Java JDK into the container, also download and install Forge into the container then run Forge. This is not recommended as it can be confusing to a novice Linux user, adds system overhead, likely UI problems, and would be difficult to perform on a handheld device. + +> This Wiki will not provide instructions for this, if you feel you can do this you can probably look up guides to help you. + + diff --git a/docs/Themes.md b/docs/Themes.md new file mode 100644 index 00000000000..71adb21071f --- /dev/null +++ b/docs/Themes.md @@ -0,0 +1,3 @@ +This page is a placeholder for Theme creation information. + +I'm working on creating an atlas data file for the skin sprite sheets. I'll separate this into 3 sections; Skins, Music and Sounds, and Card Images. \ No newline at end of file diff --git a/docs/Towns-&-Capitals.md b/docs/Towns-&-Capitals.md new file mode 100644 index 00000000000..dee4360235b --- /dev/null +++ b/docs/Towns-&-Capitals.md @@ -0,0 +1,101 @@ +# Towns & Capitals + +While exploring the world of Shandalar, you will find multiple cities across all biomes. These settlements are a integral part of the game, and can help the player by giving quests, hosting draft/jumpstart events, as well as selling cards and booster packs. + +## Towns + +![White Town](https://github.com/user-attachments/assets/db05a9ab-f731-4fea-8f02-772d933ecaec) +![Blue Town](https://github.com/user-attachments/assets/ae89a6a0-1088-429b-a14c-0521e9750f5a) +![Red Town](https://github.com/user-attachments/assets/5596b9d5-8d21-4d8b-9ad8-aeeace22ed30) +![Green Town](https://github.com/user-attachments/assets/0482ae8a-2dae-4855-9c12-38c9b0f49269) + +Different towns have different buildings, but some of them are common to them. Buildings are identified by the sign right in front of them. + +- The Inn + + ![The Inn](https://github.com/user-attachments/assets/2e2f8454-051a-404d-abcf-645b362e2db6) + + At the inn, players can buy temporary healthpoints, sell cards and join events. Different inns host different events. For example, a town can be hosting a draft for "Urza's Saga Block", while the next one can be hosting a Jumpstart 2025 event. + +- Job Board + + ![Job Board](https://github.com/user-attachments/assets/d913eb70-75f2-424c-8307-5ee6207d92e5) + + Going to the job board the player can accept quests, which will reward them with gold and sometimes local reputation. Local reputation reduces the prices of cards sold by shops. + +- Shard Trader + + ![Shard Trader](https://github.com/user-attachments/assets/19eb6bd0-a002-4ac0-906e-ad9663903f72) + + The shard trader allows player to trade gold for shards and shards for golds. Shards are used to reroll cards at the shop, as well as joining events. + +- Card Shops + + ![Card Shop 1](https://github.com/user-attachments/assets/468ee561-7b57-4246-9f18-91d3a71012a0) + ![Card Shop 2](https://github.com/user-attachments/assets/f6459859-c745-4e58-a17d-bef689a57c2b) + ![Card Shop 3](https://github.com/user-attachments/assets/e80eedeb-7b25-4d57-a285-10f44a18a47d) + + Card shops sells cards according to the biome and the shop itself. For example, shops in a green city will tend to sell green cards, while shops in a black city will focus on black cards. + Different shops sells different types of cards, and most of them can be deduced from their name. For example, the shop called "Control Magic" will sell blue cards used to control opponents (counter, tap, untap, etc), while a shop called "Swords, Plowshares, and Beyond" will sell cards that exile permanents. The shop information can be a found in [this user-friendly JSON](https://github.com/Card-Forge/forge/blob/master/forge-gui/res/adventure/Shandalar/world/shops.json). + + Shops with an Hour Glass sign change their stock daily. All other shops restock only using shards to reroll. + + Shop contents based on sign: + + + shop-icons + +| |A |B |C |D |E |F |G +|---------------|---------------|---------------|---------------|---------------|---------------|---------------|---------------| +|1|-|Instants|Creatures|Green|Red|White|Pirates +|2|-|Basic Lands|Colorless|Black|Blue|Gold|- +|3|-|Selesnya G/W|Azorius U/W|Dimir B/U|Rakdos B/R|Gruul G/R|- +|4|-|Simic G/U|Orzhov B/W|Izzet R/U|Golgari B/G| Boros R/W|- +|5|-|Goblins|Zombies|Humans|Elves|Merfolk|- +|6|-|Item Shop|Artifacts|Angels|Golems|Slivers|Assassins +|7|-|Mardu B/R/W|Jeskai R/W/U|Naya R/G/W|Enchantments|5 Color|Squirrels +|8|Rogues|Non-Basic Land|Space Marines|Necrons|Chaos|Tyranids|Dragons +|9|Assembly Workers/Constructs/Myr|Vampires|Vehicles|Grixis R/U/B|Jund R/G/B|Temur R/G/U|Minotaurs +|10|Dinosaurs|Esper U/W/B|Sultai U/G/B|Bant B/W/G|Abzan G/W/B|Dwarfs|Devils +|11|Ogres|Item Shop|Equipment|Soldiers|Boosters|D&D|Demons +|12|Rotating Stock|Druids|Activated Artifacts|Birds|Wolves|Knights|Walls +|13|Shard Shop|Planeswalkers|Skeletons|Birds|Shamans|Wizards|Sagas +|14|Player Equipment|Phyrexian|Sphinx|Hydras|Spiders|Insects|Changelings +|15|Cats|Eldrazi|Clerics|Mutants/Mutate|Horrors|Transform Cards|Universe Beyond +|16|Battles|Sags|Nobles/Monarch|Giants|Sea Creatures|Snow|Boosters + + + +## Capitals + +![White Capital](https://github.com/user-attachments/assets/02103549-559e-401f-b214-c382e8a94e2c) +![Blue Capital](https://github.com/user-attachments/assets/96d56f88-7dbf-4de9-9b59-790efda00194) +![Green Capital](https://github.com/user-attachments/assets/20931954-e464-4c44-a72c-0b689365d23a) +![Red Capital](https://github.com/user-attachments/assets/3d2d1aac-3293-429e-8e91-ca5d5bff81a2) + +Capitals are bigger and have more buildings than normal towns, but have certain exclusive shops that can really useful for the player. + +- Item Shop + + ![Red Item Shop](https://github.com/user-attachments/assets/3ef4b8dc-a6be-4829-be8d-147ec939d815) + + Item shops sell items and equipments that the players can use on their journey - such as a rune that allows them to teleport to that capital when on the overworld. + +- Arena + + ![Red Arena](https://github.com/user-attachments/assets/3f2ab46d-e5cd-4038-9d59-276889caece8) + + At the Arena players can participate in a tournament awarding gold and equipments. + +- Smith + + ![Red Smith](https://github.com/user-attachments/assets/39edca81-73d7-4130-8967-ed5ea42e1970) + + The Smith is one of the most important buildings while trying to build a specific deck. Through a filtering system, players can try to create specific cards, by entering the set, mana value, colors and rarity of one or more cards. The more filters you use, the more expensive it gets. + +There are other buildings for you to explore on your journey through Shandalar. Have fun! + + + + + diff --git a/docs/Transfer-PC-saves-to-Android.md b/docs/Transfer-PC-saves-to-Android.md new file mode 100644 index 00000000000..9d4c286ed19 --- /dev/null +++ b/docs/Transfer-PC-saves-to-Android.md @@ -0,0 +1,14 @@ + +to copy from pc to android: +on your pc, browse to +C:\Users\ [user]\AppData\Roaming\Forge\adventure\Shandalar +copy whichever saves you want, then on your android, browse to +internal storage\android\obb\forge.app\forge\data\adventure\Shandalar +and paste the saves you want in there + +to copy from android to pc: +on your android, browse to +internal storage\android\obb\forge.app\forge\data\adventure\Shandalar +copy whichever saves you want, then on your pc, browse to +C:\Users\ [user]\AppData\Roaming\Forge\adventure\Shandalar +and paste the saves you want to move in there \ No newline at end of file diff --git a/docs/Tutorial-1-Create-your-First-Plane.md b/docs/Tutorial-1-Create-your-First-Plane.md new file mode 100644 index 00000000000..13f5343b9c7 --- /dev/null +++ b/docs/Tutorial-1-Create-your-First-Plane.md @@ -0,0 +1,6 @@ +## Tutorial: 1 Create your First Plane + +Creating a new plane allows you to fully customize your Adventure mode experience, and can even be shared with the community. The most basic step in doing so, is to add a new directory in `...\YourIntelliJProjectFolder\forge-gui\res\adventure\`, copy the 'config.json' file from `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common`, paste it into your new directory. Then copy the contents of `...\YourIntelliJProjectFolder\forge-gui\res\adventure\Shandalar\world` into your new directory. Congratulations, that is the bare minimum to create a new plane. If you tell IntelliJ to run the Forge project using the Adventure mode settings, you can load up Adventure mode, find your plane in the drop-down list, and load into it. + +Of course, now many of you will come back here and say "nothing looks different". Don't worry, nothing is broken. As you haven't actually changed any files from their default settings, everything will load up in the exact same way as the default game. This is intentional, so that you can make as many, or as few, changes from the default Shandalar plane as you like, without breaking anything. Look to the other tutorials for guides on how to change each piece of a plane to match your desire. + diff --git a/docs/Tutorial-2-A-New-Look.md b/docs/Tutorial-2-A-New-Look.md new file mode 100644 index 00000000000..c7f3d085050 --- /dev/null +++ b/docs/Tutorial-2-A-New-Look.md @@ -0,0 +1,221 @@ +## Tutorial 2: A New Look (Creating your first map) + +Okay, you've got a new plane, now let's make that plane our own. This tutorial is going to teach you how to make a very basic map and get it into the game in your plane. This is a much lengthier tutorial, so we are going to split it up into parts. + +1. [Part 1: Setting up Tiled](https://github.com/Card-Forge/forge/wiki/Tutorial-2-A-New-Look#part-1-setting-up-tiled) + +2. [Part 2: Building a Basic Map](https://github.com/Card-Forge/forge/wiki/Tutorial-2-A-New-Look#part-2-building-a-basic-map) + +3. [Part 3: Adding the Map to your Plane](https://github.com/Card-Forge/forge/wiki/Tutorial-2-A-New-Look#part-3-adding-the-map-to-your-plane) + +4. [Part 4: Put it to the Test!](https://github.com/Card-Forge/forge/wiki/Tutorial-2-A-New-Look#part-4-put-it-to-the-test) + +### Part 1: Setting up Tiled + +To make everything work smoothly, we need to do some set-up of your Tiled project. First, in IntelliJ, navigate to your plane directory (in our case, we named it Test) like so `...\YourIntelliJProjectFolder\forge-gui\res\adventure\Test\`, and create a new directory named 'maps'. Inside that directory make another directory called 'map'. Now open up Tiled, click on New Project, and navigate to the maps directory you just created, (the one with the "map" directory inside it.) Create a new project by picking a name in the file name line, (it is recommended you name it the same as your plane, for ease of bug solving,) and click save. + +Next we need to tell Tiled where to find the files Forge uses. Click on Project > "Add Folder to Project...", navigate to `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\maps`. Select the 'obj' directory, and click "Select Folder". If you are planning to use any of Forge's tilesets, then do the same thing, but this time selecting the 'tileset' directory from `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\maps` aswell. This adds access to the various objects Forge uses to actually interact with the player. (Enemies, rewards, map transfers, etc.) The tilesets provide the visual appearance of your map, _as well as the collision mapping_. **_Any of these you intend to modify, you must create a new entry in an appropriate subdirectory inside your own plane's directory!_** Otherwise you will be changing it for every single map in every plane that touches those objects or tilesets, which quickly causes unintended consequences. (Further details on how to modify these items, what the subdirectory should look like, and what needs to remain unchanged to be properly read by Forge, will be described in their specific Wiki pages. For now, just keep this information in mind.) + +Next click on Project > "Project Properties..." In the 'Paths & Files' section you will a text bow that says 'Extensions Directory' and three dots to the right. Click on those three dots, and navigate to `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\maps`, select the extensions directory, and click the "Select Folder" button. Then click on "OK". This adds the extensions written for Forge to your project. Without this, your maps will throw errors when Forge tries to do certain object actions, and crash the game. (Learn from my fail on this one.) + +Voila! That is the minimum work necessary to prepare your project for adding, or editing, maps unique to your plane. + + + +### Part 2: Building a Basic Map + +Alright, so you've got your fancy project, and while this gray screen with a view of multiple folders on the left is cool and all. We know you really came here because you wanted to actually _make a new map_. (Well, technically, you may want to just slightly modify an existing map. But to have it unique to your plane, it will still be effectively a new map in the eyes of IntelliJ.) So let's get to it, let's make a very basic map for Forge. Step one, click the fancy, tempting, "New Map..." button I know you've been staring at. (And possibly already clicked, then came back here to see what you need to do on that next window.) This will bring up a new dialog box, with a bunch of options. + +1. Orientation should be Orthogonal +2. tile layout format should be CSV +3. Tile render order should Right Down + +Those are the Tiled defaults for those options at the time of this writing, but I want it here in case that changes. + +Next up we have the Map size section, the bigger the map, the more you can add, and the longer it will take you to finish. I recommend for our first map, a simple 15 by 15 tiles. You'll make bigger maps, but for your first, don't get overwhelmed. **_DON'T CLICK OK YET!_** I know you want to get started, but we have to change a couple more things. The Tile size needs to be set to 16 wide by 16 high. This is the Forge standard, and the tilesets use it too. Different settings can lead to very unintended side effects. **NOW** you can click "OK". + +Welcome to the gray box that is Tiled's default map menu. There's a lot here, but we are going to build a map together, so relax and just follow along. First off, you will often find yourself in need of multiple layers, otherwise your map is simply not going to look good. So, on the right side, you will see a small box with a text line that says "Tile Layer 1". Double click that line, and rename it to "Ground" without the quotes. Next, right-click and select New > Tile Layer. Name this one "Clutter". Do it again, but name this one "Walls". Finally right-click one more time and select New > Object Layer, name this one "Objects". This gives a basic series of layers to allow us to make a map. You can add more if really needed, but try to limit yourself to six or fewer Tile layers to preserve loading speeds. **Only have ONE object layer**, having a second one will confuse the heck out of Forge. + +Now that we have our layers, let's make sure they are in the right order. Click and drag each layer so that, from top to bottom, they rea: Objects, Walls, Clutter, Ground. In tiled, like many programs, tiles on layers farther up will appear on top of ones below them. Objects on top ensure nothing gets lost, or accidentally put under a wall, and can easily be seen. Next up, while we have an Object layer, we need to actually tell Forge what layer to render sprites on. (This allows you make roofs or arches, etc, that a character can walk "under"... it also tells Forge where to actually _put_ the sprite, since it doesn't actually default to the Object layer.) For our map, we want the player to be walking on top of any dirt or grime we add to the map, so left click on the Walls layer. On the left side of the screen, you'll see the properties toolbox. Scroll down to the bottom and you will see the "Custom Properties" line. Right click > Add Property. Click the drop-down that says "string" and choose "bool". In the text box, type "spriteLayer" without the quotes. **This is a value read by Forge, so it is case sensitive. Lowercase 's', uppercase 'L', no spaces.** Click "OK". You will see it has added the property to the custom properties, with a check-box. The box is unchecked, check it. (If you had not added this property, made it a bool, set it's name correctly, and checked the box... Any attempts to load this map would cause Forge to bug-out pretty hard and require a restart to just work right again.) + +Now that our map-space is set-up, let's go ahead and save all this work we have done. File > Save As. Open your "map" directory inside the "maps" directory, this is a directory of all your maps you are going to use. To save yourself future head-aches, organize maps into sub-directories from the get-go. In our tutorial case, I will be making a beach, so first I will right-click > new > folder while inside of 'map' and name it "beach". Then I will double-click on that "beach" folder. In this sub-directory I name my tutorial map as "test", but you should name your map as something you'll remember it by, then click save. Now that, that's done, let's get around to the art part of this. Click on the Ground layer. You'll notice you can move your mouse over the map and it'll turn red or blue depending on it you are inside the tile space or not. But no matter where you click, nothing happens. That's because we need to actually choose a tileset to use. On the far left side of the screen, you'll see the various folders we imported into our project. Open the tileset folder, and double click on "main.tsx". This will open a new tab, that shows the entire contents of the "main" tileset from Forge. _Since we don't plan to actually make ANY changes to this tileset_ (gentle reminder.) Simply click on the tab for your map (test.tmx in my case,) and you'll see the "main" tileset has appeared in the bottom-right corner of your screen. + +Since I chose a beach, I'm going to make a beach for this tutorial, so I'm going to click on the sand-colored tile near the top-left of the "main" tileset, right next to the default empty one. If you move your mouse back over your map, you will see it shows that sand-colored tile for every square you move it over. If you click, it will even place that color on that tile. While you could individually click on each tile one at a time, for filling in large areas that's much more exasperating. Clicking and dragging will simply apply each tile the mouse moves over. So, instead, along the top, you will see a paint-bucket. Click on that. now if you move your mouse back to the map, it'll show a ghostly tan color across the entire map, minus any squares you already clicked on. (The highlight will only apply to every tile of the same type that you hover over, which is not completely separated from the mouse by a different tileset tile. If you are confused, this will make more sense as you make or modify maps.) Click on the empty space, and it will fill in the entire map with our sandy ground. + +Now, a blank sandy beach may be more than you had before, but it's not that interesting, so let's add some detail. Click on the Clutter layer. Then, on our "main" tile-set on the bottom right, click on the water tile just below the blank one in the top left. If you move your mouse back to the map, you will see it wants to change every tile to water. That's because we are still in Bucket Fill mode. On the top of the window, you'll see a rubber stamp, two tools to the left of the pain bucket. Click on that. Now we are once again designing our map one tile at a time. I'm going to draw out some water tiles along the edges of the map, leaving just a couple sandy ones at the bottom center of the map. + +Now, The map is clearly a sand-bar jutting out into water, but it looks pretty rudimentary, even for Forge. As such, let's click on the Walls layer. Then, on the tile-set on the bottom right, there are a bunch of sandy edges looking like they are meant for the edge of water. I'm going to carefully select various tiles from among them and make my coast-line look much nicer. Exactly which tiles where will depend on how you built the previous layer. If you ever place a tile and it doesn't look right. You can click on the Eraser tool at the top of the screen, and then click that tile on your map. As long as you are on the right layer, it will erase that tile. + +Huzzah! You've made you little sandbar... But how can you be certain where players and enemies can walk? Well, that's called Collision. Go to View > and make sure there is a check-mark in "Show Tile Collision Shapes". The water tiles will have much strongly defined border now, and depending on your sand borders and how you built it, you may have new lines in them, or not. Collision is defined by the tile in the Tileset. Each tile in the tile set either has collision shapes, or doesn't. **If you are not happy with the collisions as-is, you will need to either change your map some, or make your own tileset.** As a reminder DO NOT MODIFY THE TILESETS FROM MAIN FORGE. If you need to make changes, look for my future tutorial that explains building your own tile-set and modifying it. + +Alright, you have a map, it has water, it has collision... Still looks pretty empty. You could add more details on various layers to make it look nicer, but that won't change the most important things. Namely, there's nothing to _do_ on our sandy beach. Let's fix that. On the left side of the screen, open up the "obj" folder, this has all the objects in Forge. Since they are all Objects, let's click on the Objects layer. Now, click and drag a "gold.tx" object from the obj folder, onto our sand. (make sure its on a portion of sand that isn't blocked by collision.) Unlike the tile layers, objects don't have to be centered to any tile. They can even sit on a grid intersection without issue. + +Alright, we got some gold on our map, but that's a bit boring. let's spice it up, by adding an enemy. So click and drag an "enemy.tx" onto the map. Now, all our objects have many properties that make sure they work. But most of them come pre-populated to some degree, so you can just drag-and-drop them. Enemies, however, are diverse and different enough we need to actually define some things about them in order to even function. So click on your placed enemy object, and scroll down the left side to the Custom Properties section. In here, the only option you _have_ define, is the "enemy" property. (Remember, the one in Custom Properties, not the space earlier in the normal properties... Again, learn from my mistakes.) In our case, click on the text box to the right of "enemy", and type "Ghost" without the quotation marks. (While a later tutorial will give more details on all the properties, just know that for this one. It is looking for an entry in your plane's "enemies.json" file. If your plane does not have that file, like ours, it will instead look for the entry in the enemies.json file found in `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\world`... A later tutorial will guide you through making your own enemies.) You have now defined the enemy, but as-is, it will just stand in place, even if a player gets near. Let's liven things up a little, and make our ghost a bit more annoyed and less sleeping on the job. Go down to the "pursueRange" custom property, and set that text box to 50. Then scroll down further to the "threatRange" box, and set that, also, to 50. Now, if the player gets within 50 pixels of it, the ghost will chase them. The ghost will only move to a distance of 50 pixels from their starting point, doing so, however. (For a more lively enemy that patrols, or jump out, etc. You can find that information in the Configuring Enemies portion of the Wiki.) + +Alright, we got a map, we got a reward, and we got an enemy to protect that reward. All good, except, Forge has no idea where to spawn a player who enters the map, and no idea where the exit to the map is. So, go and drag an "entry_up.tx" object to the little entrance of our sandbar, and resize it so that the arrow takes up the majority of the entry space. Congratulations, your map is made, don't forget to **_SAVE_**. + +### Part 3: Adding the Map to your Plane + +Alright, so you have a map you want to play, it's saved in your plane, why can't you find it? Well, that's because Forge doesn't **actually** know it's ready to _be_ found yet. We need to get some "biomes" and "points of interest" added to your plane, aswell as. So, first, let's give them a place to go in your plane. Go to your plane's directory in IntelliJ, and open the 'world' sub-directory. Inside of 'world' create a new subdirectory called 'biomes'. Navigate to `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\world\biomes` and copy the following files to your plane's biomes directory, 'base.json' and 'colorless.json'. We also need to copy the 'points_of_interest.json' from `...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\world` to our custom plane's 'world' directory. Now, open up the 'points_of_interest.json' from our plane's 'world' sub-directory. Inside you will find all the maps used by Forge. If you want to add a map to the list, but keep all the others, you would add it's info the existing json. But for the sake of simplicity in this tutorial, we are going to delete everything inside the json except for the following. +``` +[ + { + "name": "Aerie", + "displayName": "Aerie", + "type": "dungeon", + "count": 30, + "spriteAtlas": "../common/maps/tileset/buildings.atlas", + "sprite": "Aerie", + "map": "../common/maps/map/aerie/aerie_0.tmx", + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Nest", + "Dungeon", + "Sidequest" + ] + }, +``` +and +``` +{ + "name": "Spawn", + "displayName": "Secluded Encampment", + "type": "town", + "count": 1, + "spriteAtlas": "../common/maps/tileset/buildings.atlas", + "sprite": "Spawn", + "map": "../common/maps/map/main_story/spawn.tmx", + "questTags": [ + "Story", + "Spawn", + "BiomeColorless" + ] + } +] +``` + +For this tutorial, we need to know the following details. + +"name" is the map name in Tiled, so we will change `"name": "Aerie",` to `"name": "YOUR MAP NAME",` ("Test" in our case.) + +"displayName" is the name that appears when the player enters a map. So we will change `"displayName": "Aerie",` to `"displayName": "YOUR MAP DESCRIPTION",` (in our case, I'm going with "Fanciest Beach"). + +"count" is how many of this map can spawn in the overworld. Since we want to actually find it, let's change `"count": 1,` to `"count": 30,` + +and "map" is where we actually STORED the map file. So we are going to change `"map": "../common/maps/map/aerie/aerie_0.tmx",` to `"map": "../YOUR_PLANE_NAME/maps/map/YOUR_FOLDER_NAME/YOUR_MAP.tmx"` . For my tutorial, that becomes `"map": "../Test/maps/map/beach/test.tmx",`. + +You can leave the rest of the Aerie code-block alone for this tutorial. If you followed me exactly, "points_of_interest.json" file should now look like this. +``` +[ + { + "name": "Test", + "displayName": "Fanciest Beach", + "type": "dungeon", + "count": 1, + "spriteAtlas": "../common/maps/tileset/buildings.atlas", + "sprite": "Aerie", + "map": "../Test/maps/map/beach/test.tmx", + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Nest", + "Dungeon", + "Sidequest" + ] + }, + { + "name": "Spawn", + "displayName": "Secluded Encampment", + "type": "town", + "count": 1, + "spriteAtlas": "../common/maps/tileset/buildings.atlas", + "sprite": "Spawn", + "map": "../common/maps/map/main_story/spawn.tmx", + "questTags": [ + "Story", + "Spawn", + "BiomeColorless" + ] + } +] +``` + + + +Now, since we want to play our new map, we still need to make sure it spawns in Forge. Go to "colorless.json" file we copied into our Plane's 'biomes' directory. Scroll down until you see the `"pointsOfInterest":` array. We are going to delete everything in this array except for `"Spawn",` and then we are going to add `"YOUR_MAP_NAME"` ("Test" in my case) immediately afterwards. If you copied this tutorial exactly so far, it would like this. +``` +"pointsOfInterest": [ + "Spawn", + "Test" + ], +``` + +### Part 4: Put it to the test! + +Alright, time for the fun part. If you've come this far, everything is ready to go. Tell IntelliJ to fire up Forge, go into Adventure mode. If you hadn't previously, set your plane to your custom plane, (and restart Forge in that case.) You'll still have the tutorial quest prompts and spawn to work through. But when done, when you leave the portal, things will immediately be visibly different. If you don't see a dungeon right away, bring up the Map, track down your dungeon icon in the Wastes biome, and walk on over. (You may need to avoid a few spawns along the way.) Enter, and, assuming you following perfectly, you'll show up in your new map. If you have questions or run into any problems, reach out to me (shenshinoman) on the Forge discord and I will be happy to help. + +After you've done that, you may notice the entire world is devoid of any map except the overworld, the spawn to the old man, and the map you just built. While this is nice for testing a new map, we should probably add a city back in, that way we actually have a place to heal up and test other changes to the plane. To make life really easy, we are just going to add a basic town back to the "Wastes". Navigate to ``...\YourIntelliJProjectFolder\forge-gui\res\adventure\common\world' and open up the "points_of_interest.json" file. Towards the bottom we will find the code block for "Waste Town Generic", select it, and copy it into the "points_of_interest.json" inside your plane's 'world' directory. So, if you've followed along precisely, your "points_of_interest.json" should look like this. + +``` +[ + { + "name": "Test", + "displayName": "Fanciest Beach", + "type": "dungeon", + "count": 30, + "spriteAtlas": "../common/maps/tileset/buildings.atlas", + "sprite": "Aerie", + "map": "../Test/maps/map/beach/test.tmx", + "radiusFactor": 0.8, + "questTags": [ + "Hostile", + "Nest", + "Dungeon", + "Sidequest" + ] + }, + { + "name": "Spawn", + "displayName": "Secluded Encampment", + "type": "town", + "count": 1, + "spriteAtlas": "../common/maps/tileset/buildings.atlas", + "sprite": "Spawn", + "map": "../common/maps/map/main_story/spawn.tmx", + "questTags": [ + "Story", + "Spawn", + "BiomeColorless" + ] + }, + { + "name": "Waste Town Generic", + "type": "town", + "count": 30, + "spriteAtlas": "../common/maps/tileset/buildings.atlas", + "sprite": "WasteTown", + "map": "../common/maps/map/towns/waste_town_generic.tmx", + "radiusFactor": 0.8, + "questTags": [ + "Town", + "TownGeneric", + "BiomeColorless", + "Sidequest", + "QuestSource" + ] + } +] +``` + +Now we need to go and tell the game to actually spawn this town. Inside your plane's "world\biomes" directory, open back up the "colorless.json" file, and add "Waste Town Generic" to the `"pointsOfInterest"` array. If you followed along, that array should now look like this. + +``` +pointsOfInterest": [ + "Spawn", + "Test", + "Waste Town Generic" + ], +``` + +Now if you open up your plane, you have a bunch of dungeons and a bunch of towns. Congratulations, you have completed this tutorial. Pat yourself on the back, cus it was a long one. (Also, don't forget to save if your system doesn't auto-save.) \ No newline at end of file diff --git a/docs/Tutorial-3-Configuration.md b/docs/Tutorial-3-Configuration.md new file mode 100644 index 00000000000..739eabfb0cf --- /dev/null +++ b/docs/Tutorial-3-Configuration.md @@ -0,0 +1,56 @@ +## Tutorial 3 Configuration, (Configuring your Plane) + +Alright, our third tutorial will be a simpler one again. Customizing your plane. A common desire when making a new plane, is to change things such as which sets are available to play, or which cards an be bought or gained as rewards. Maybe you want to be able to more easily build super powerful decks from the get go, or just have entirely new decks. All these settings, and more, can be adjusted from the 'config.json' file in your plane's directory. For this tutorial, to get a basic idea of what things can be changed, we are going to make some minor adjustments to the plane. Further details on other changes and what they do will be covered in other sections and tutorials. + +For now, in IntelliJ, open up the 'config.json' file found in `...\YourIntelliJProjectFolder\forge-gui\res\adventure\YourPlane`, as a reminder for our tutorials, I am using a directory called Test. Inside the config file, there are a large number of entries, but we are going to start with the `"restrictedCards"` array. After-all, we are here to have fun, so let's just bring in almost all the craziness. We are going to delete every entry in the array, _except_ for "Black Lotus"... Why not Black Lotus you ask? Because it's too rare, clearly. (Or, maybe just because it is the first entry and I'm being lazy, and keeping one entry is a reminder how this array is formatted.) So, when you are done, the array should look like this. +``` +"restrictedCards": [ + "Black Lotus" + ], +``` + +To better explain what that array does, is any card in the array will not appear in any generic rewards or shops. This is the closest thing to banning individual cards that one can. However, it should be noted that if those cards are ever specifically added as a reward or a shop's content. Doing so overrides this setting. Essentially allowing you to award cards for very special cases, or hide a fancy shop somewhere in your game. + +Now, restricting one card at a time is a very laborious process, especially if there are a large number of cards you want removed. In many cases, you will want entire sets removed from your game, which brings us to the `"restrictedEditions"` array. Any card whose set code appears in this array will be restricted from the game in the exact same way as `"restrictedCards"`. There's already several entries in here, but since I'm making a really wacky unbalanced plane here, we're going to remove every entry from this array, except for "UNF". Why not UNF you ask? Because it's a traitor and made some of it's cards legal and others not, clearly. (Or, maybe again it's because I'm lazy, and at the time of this writing, it's the last entry in the list. As well as once again keeping one entry as a reminder how this array is formatted.) So if you follow my flawless example, it will look like this. + +``` +"restrictedEditions": [ + "UNF" + ], +``` + +Now that we've opened the floodgates for our awards, we could go have fun in the wacky world. However, our Inn's drafts and jumpstarts won't be as open as the rest of the game still. Why? Because the array `"restrictedEvents"` tells our Inn what "events" (aka sealed and draft editions) it can't spawn. Just like `"restrictedEditions"` any set in this array won't appear for draft events (but if it is not in the `"restrictedEditions"` it can still appear as rewards.) As such, let's go ahead and open those flood gates further. In my infinite wisdom, I have decided, for no specific reason at all, that we will remove every set from this list except for "CMM". (Yes, you got the picture, I'm lazy but trying to be helpful still.) If you followed me so far, the entries between `"colorIDNames"` and `"difficulties"` should appear as follows. + +``` +"restrictedCards": [ + "Black Lotus" + ], + "restrictedEditions": [ + "UNF" + ], + "restrictedEvents": [ + "CMM" + ], +``` + +Now if you load up the game at this timeframe, and go hopping through towns, you'll notice the occasional card you hadn't seen before. This is good, but man there are a lot of cards in Magic, and it can take a while to find ones to show you've made a change. For a tutorial, that's bad. Also, if you want to make a plane like "Shandalar Old Border" or "Innistrad", you'd have to go through and add every unwanted set to the `"restrictedEditions"` array. including updating this array every time Wizards of the Coast releases another set, and that's **worse** than bad... Luckily, we have a solution for this. We are going to add two new arrays to our 'config.json' file. Where the previous arrays act as a black-list for the entries, the following arrays are a overwrite. If they are present, the plane will only spawn cards and events found in them. The first one is `"allowedEditions"`. For this tutorial, I am going to go completely unhinged and risk making some of our games unstable, but what mod never comes unglued? If you didn't pick up the hints, my array is going to add `"UNG"`, `"UNH"`, and `"UST"`. So my array looks as follows. + +``` + "allowedEditions": [ + "UNG", + "UNH", + "UST" + ], +``` + +The second array I'm going to add is the `"allowedJumpstart"` array. This changes which Jumpstart sets are available, and will allow us to still have them even when the previous arrays have blacklisted them in any way. For this tutorial, I know we all want to get a jump on things, so I'm just going to add `"Jumpstart"`. + +``` +"allowedJumpstart": [ + "Jumpstart" + ], +``` + +You might have noticed that unlike the previous arrays, this one uses the set name, not code. This is a current limitation in Forge, and one we will have to live with. Luckily, there are far fewer jumpstart sets than than other sets. (**NOTE: Adding a set to this list that does not have any official Jumpstart packs will NOT enable them for jumpstart events.** Some of you who have tooled around will have noticed there are Jumpstarts in "Innistrad" that don't exist in normal jumpstarts. Well, that is for a much later tutorial. My apologies.) + +As a final note, while this changes the rewards from enemies and shops, as well as what can appear in the Inns. It has no bearing on your starting decks, or enemy decks, just the rewards and shop options. So yes, if you followed along so far, you will playing with 'normal' cards, against normal cards, to earn Silver-Bordered cards.. yeah, it's a bit crazy, but that works... For those who want to customize their plane even further, the next tutorial will focus on modifying both starting decks, and enemy decks. See you there. For now, enjoy, and again. if you have any problems, please reach out me "Shenshinoman" on the discord and I will be happy to help. \ No newline at end of file diff --git a/docs/Un‐cards,-Playtest-Cards,-and-Other-Funny-Cards.md b/docs/Un‐cards,-Playtest-Cards,-and-Other-Funny-Cards.md new file mode 100644 index 00000000000..160299dbe09 --- /dev/null +++ b/docs/Un‐cards,-Playtest-Cards,-and-Other-Funny-Cards.md @@ -0,0 +1,1793 @@ +Non-legal "funny" cards are considered low priority for implementation in Forge. Many of them require an immense amount of work to support mechanics that only appear on one or two cards. Others are impossible to add without expanding the scope of Forge beyond that of a rules simulator. That said, Forge does support some of these cards, and new ones are implemented on a "whenever someone gets around to doing it" basis. + +Below is a list of all "Funny" cards, organized by estimated viability of implementing them in Forge, and roughly grouped by mechanic. + +#### Disclaimer: + +This is not intended as a to-do list, as an indication of how soon these cards will be implemented, as an indication of whether they will *ever* be implemented, as a thorough assessment of implementation difficulty, as a comprehensive list of complications that may emerge when attempting to implement these cards, nor as indicative of the maintenance costs associated with adding such edge cases to the project's code-base. + +Comparisons between mechanics needed to support these cards and existing mechanics are not indicative of simplicity. Cards within tiers are loosely grouped by mechanic as a reference, so that implementations designed for one might be made flexible enough to account for others. + +The tiers below are not indicative of the amount of overall work needed to implement the cards or features within. They are a broader assessment of how the mechanics and spirit of the card are suited to Forge, to a rules engine, and to a digital environment altogether. + +Potential alterations to keep the spirit of the card are considered, and the position of the card may be adjusted accordingly. For example, Infernal Spawn of Evil has the player say "It's coming!" as a flavorful addition to a cost, and is placed in tier 2 because the card mostly maintains its spirit without the spoken dialog. Toy Boat has the player repeatedly say the words "Toy Boat" quickly as a tongue twister, and is placed down in Tier 6 because removing spoken dialog defeats the purpose of the card. + +This page is subject to updates as challenges to implementing these cards are discovered or resolved. + +Cards prefixed with (P) are playtest cards. Cards prefixed with (Uk) were exclusive to Unknown Events. Cards prefixed with (HTR) are from the Heroes of the Realm series. + + +### Tier 0: Done +1. The Cheese Stands Alone +1. Chicken a la King +1. Clambassadors +1. Denied +1. Fowl Play +1. Free-for-All +1. Jumbo Imp +1. Organ Harvest +1. Poultrygeist +1. Temp of the Damned +1. Chicken Egg +1. Goblin Bowling Team +1. Goblin Tutor +1. Krazy Kow +1. Spark Fiend +1. Strategy, Schmategy +1. Elvish Impersonators +1. Flock of Rabid Sheep +1. Growth Spurt +1. Hungry Hungry Heifer +1. Incoming! +1. Mine, Mine, Mine! +1. Timmy, Power Gamer +1. Jack-in-the-Mox +1. Paper Tiger +1. Rock Lobster +1. Scissors Lizard +1. AWOL +1. Johnny, Combo Player +1. Mise +1. Topsy Turvy +1. Blast from the Past +1. Face to Face +1. Goblin Secret Agent +1. Form of the Squirrel +1. Old Fogey +1. Uktabi Kong +1. Mana Screw +1. Amateur Auteur +1. Crow Storm +1. Novellamental +1. Time Out +1. Inhumaniac +1. Box of Free-Range Goblins +1. Hammer Helper +1. Hammer Jammer +1. Painiac +1. Target Minotaur +1. As Luck Would Have It +1. Beast in Show +1. Chittering Doom +1. Ground Pounder +1. Hydradoodle +1. Willing Test Subject +1. Buzzing Whack-a-Doodle +1. Curious Killbot +1. Delighted Killbot +1. Despondent Killbot +1. Enraged Killbot +1. Lobe Lobber +1. Mad Science Fair Project +1. Steel Squirrel +1. Killer Cosplay +1. (P) Banding Sliver +1. (P) Frontier Explorer +1. (P) Imaginary Friends +1. (P) Sarah's Wings +1. (P) Scaled Destruction +1. (P) Wizened Arbiter +1. (P) Command the Chaff +1. (P) Control Win Condition +1. (P) Enchantmentize +1. (P) Khod, Etlan Shiis Envoy +1. (P) Recycla-bird +1. (P) The Grand Tour +1. (P) Celestine Cave Witch +1. (P) Chimney Goyf +1. (P) Corrupted Key +1. (P) Frogkin Kidnapper +1. (P) Largepox +1. (P) One With Death +1. (P) Underdark Beholder +1. (P) Xyru Specter +1. (P) Yawgmoth's Testament +1. (P) Impatient Iguana +1. (P) Tibalt the Chaotic +1. (P) Domesticated Mammoth +1. (P) Frenemy of the Guildpact +1. (P) Generated Horizons +1. (P) Growth Charm +1. (P) Krosan Adaptation +1. (P) Plane-Merge Elf +1. (P) Vazal, the Compleat +1. (P) Evil Boros Charm +1. (P) Golgari Death Swarm +1. (P) Graveyard Dig +1. (P) How to Keep an Izzet Mage Busy +1. (P) Personal Decoy +1. (P) Sliv-Mizzet, Hivemind +1. (P) Wrath of Sod +1. (P) Lantern of Undersight +1. (P) Mirrored Lotus +1. (P) Puresteel Angel +1. (P) Unicycle +1. (P) Aggressive Crag +1. (P) Domesticated Watercourse +1. (P) Enchanted Prairie +1. (P) Jasconian Isle +1. (P) Noxious Bayou +1. (P) Taiga Stadium +1. (P) Waste Land +1. Fruitcake Elemental +1. Season's Beatings +1. (HTR) Andrios, Roaming Explorer +1. (HTR) Arteeoh, Dread Scavenger +1. (HTR) Chandra, Gremlin Wrangler +1. (HTR) Diabolical Salvation +1. Discord, Lord of Disharmony +1. (HTR) Dungeon Master +1. (HTR) Euroakus +1. (HTR) Inzerva, Master of Insights +1. (HTR) Kharis & the Beholder +1. (HTR) M'Odo, the Gnarled Oracle +1. (HTR) Nira, Hellkite Duelist +1. (HTR) The Cinematic Phoenix +1. (HTR) The Legend of Arena +1. (HTR) Fabled Path of Searo Point +1. (P) Slumbering Waterways +1. (P) Temur Elevator +1. (P) Under-Construction Skyscraper +1. (P) Wrenn and One +1. (P) New Master of Arms +1. (P) Teferi, Druid of Argoth +1. (P) Wisedrafter's Will +1. (P) (Uk) Mox Poison +1. (Uk) Incisor Steed +1. (Uk) Life Cloud +1. (Uk) More of That Strange Oil... +1. (Uk) Phyrexian Broodstar +1. (Uk) Nim Mongoose +1. (Uk) Phyrexian Chimney Imp +1. (Uk) Red Priest of Yawgmoth +1. (Uk) Bringer of Green Zenith's Twilight +1. (Uk) Locus Cobra +1. (Uk) Then, Dreadmaws Ate Everyone +1. (Uk) Forestfolk +1. (Uk) Phyrexian Ornithopter +1. (Uk) Original Skullclamp +1. (Uk) Glorious Dragon-Kin +1. (Uk) Soul of Mirrodin +1. (Uk) The Forgotten Place +1. (Uk) Artifact Unknown Shores +1. (Uk) Theros Charm +1. (Uk) Isamaru and Yoshimaru +1. (Uk) Ulgrotha Charm +1. (Uk) Phyrexian Adapter +1. (Uk) Toothy and Zndrsplt +1. (Uk) Welcome to Mini-apolis +1. (Uk) Innistrad Charm +1. (Uk) Myojin of Night's Reach and Grim Betrayal +1. (Uk) Tarkir Charm +1. (Uk) Jeska and Kamahl +1. (Uk) Joven and Chandler +1. (Uk) Norin and Feldon +1. (Uk) Kamigawa Charm +1. (Uk) Chatterstorm and Awaken the Woods +1. (Uk) Riku and Riku +1. (Uk) Colossal Dreadmaw and Storm Crow +1. (Uk) Riven Turnbull and Princess Lucrezia +1. (Uk) Avacyn and Griselbrand +1. (Uk) Koma and Toski, Compleated +1. (Uk) Knowing // Half the Battle +1. (Uk) Segovian Sword +1. (Uk) Manakin and Millikin +1. (Uk) Luxior and Shadowspear +1. (Uk) Sword of Fire and Ice and War and Peace +1. (Uk) The Belligerent and Useless Island +1. (Uk) Mysterious Confluence +1. (Uk) Try-My-Deck Elemental +1. (Uk) White Rhystic Study +1. (Uk) Ensoul Ring +1. (Uk) Delve Too Deep +1. (Uk) The Dilu Horse +1. (Uk) Eldest Dragon Highlander +1. (Uk) Potatoes +1. (Uk) Sagrada Familiar +1. (Uk) Toe-Breaking Helmet +1. (Uk) Bram, Baguette Brawler +1. (Uk) Really Charming Prince +1. (Uk) Wrath of Oko +1. (Uk) Night Out in Vegas +1. (Uk) Believe in the Cleave +1. (Uk) Force of Rowan +1. (Uk) The Alright Henge +1. (Uk) Garruk's Lost Wolf // Hey, Has Anyone Seen Garruk +1. (Uk) HONK! +1. (P) (Uk) Questing Cosplayer +1. (Uk) Cat Oven +1. (Uk) Cinnamon, Seasoned Steed +1. (Uk) Gingerbehemoth +1. (Uk) Kevin, Questing Dragon +1. (Uk) Elemental, My Dear +1. (Uk) The Value Knight +1. (Uk) Bloodsplatter Vampire +1. (P) (Uk) Halving Season +1. (Uk) The Crafter +1. (Uk) The Crimson Avenger +1. (Uk) Ate-o'-Clock +1. (Uk) The Bear Force Pilot +1. (Uk) Primetime Suspect +1. (Uk) The Roaring Toeclaws +1. (Uk) The Spike Cactus +1. (Uk) The Duke of Midrange +1. (Uk) Deep Dish Pizza +1. (Uk) Karlov's Crossbow +1. (Uk) Simic Slaw +1. (Uk) The Magic Bandit +1. (Uk) The Juzam Master +1. (Uk) The Great Juggernaut +1. (Uk) The Massive Zacatl +1. (Uk) The Keeper of the Yellow Hat +1. (Uk) The Marvelous Scientist +1. (Uk) The Master of Cuisine +1. (Uk) Black Tulip +1. (Uk) Leisure Bicycle +1. Shichifukujin Dragon +1. Robot Chicken +1. Deb Thomas +1. (P) Omenpath to Naya +1. (P) Fetching Garden +1. (P) Gobland +1. (P) The Heron Moon +1. Urza's Science Fair Project +1. (P) Gorilla Tactics +1. (HTR) Ersta, Friend to All +1. (P) Value Town // Take a Trip to... +1. (P) Stone Drake +1. (P) Indicate +1. (P) (Uk) Anax and Cymede & Kynaios and Tiro +1. Cardboard Carapace - Counts copies in the sideboard. Not exactly as written but very little room for improvement. Adventure+Quest Inventories? +1. Shoe Tree - Can't use actual shoes for counters sadly. +1. "Ach! Hans, Run!" - Just have to name the card. +1. Booster Tutor - Might want to expand to letting the player choose the pack instead of a random one, for consistency with Lore Seeker. +1. Urza, Academy Headmaster - Just chooses a random effect from his list, no internet connection required. +1. B.O.B. (Bevy of Beebles) - Link between loyalty counters and beebles is implemented as a state based action and static trigger. Probably fine? May need an exception to implement Rules Lawyer. +1. (P) Slivdrazi Monstrosity - Granting devoid made to work by actually removing their colors. +1. (Uk) Phila, Unsealed - Oracle replaced with the winning team's text rather than an always-on characteristic defining ability. Probably makes more sense this way. +1. (Uk) Disguise Agent - Loses some of its flare when commanders are public knowledge. See "Face Down Commanders", Tier 3. +1. (Uk) The Keeper of Dark Pacts - Done via Intesify. Unclear if this should affect other copies of the same card. +1. (Uk) The Rebellious Intelligence - Random card in sideboard. May need special attention if we ever rework Wish. +1. (Uk) Sojourner's Enforcermite - Affinitycycling's reminder text comes out a bit weird, but the function is there. + +#### Functional Variants +1. Sly Spy F +1. Garbage Elemental C +1. Garbage Elemental D +1. Everythingamajig C + +#### Subgames +1. Enter the Dungeon +1. The Countdown is at One + +#### Token Cards +1. (P) Time Sidewalk +1. (P) Bone Rattler +1. (Uk) Leech Medic + +#### Change Commanders +1. (P) You're in Command +1. (HTR) Champions of Archery // Join the Group + +#### Contraptions +1. Accessories to Murder +1. Applied Aeronautics +1. Arms Depot +1. Auto-Key +1. Bee-Bee Gun +1. Boomflinger +1. Buzz Buggy +1. Deadly Poison Sampler +1. Dictation Quillograph +1. Dispatch Dispensary +1. Division Table +1. Dogsnail Engine +1. Dual Doomsuits +1. Duplication Device +1. Faerie Aerie +1. Genetic Recombinator +1. Gift Horse +1. Gnomeball Machine +1. Goblin Slingshot +1. Guest List +1. Hard Hat Area +1. Head Banger +1. Hypnotic Swirly Disc +1. Inflation Station +1. Insufferable Syphon +1. Jamming Device +1. Lackey Recycler +1. Mandatory Friendship Shackles +1. Neural Network +1. Oaken Power Suit +1. Optical Optimizer +1. Pet Project +1. Quick-Stick Lick Trick +1. Rapid Prototyper +1. Record Store +1. Refibrillator +1. Sundering Fork +1. Targeting Rocket +1. Thud-for-Duds +1. Top-Secret Tunnel +1. Tread Mill +1. Turbo-Thwacking Auto-Hammer +1. Twiddlestick Charger +1. Widget Contraption +1. Sap Sucker + +##### Contraption Support +1. Jackknight +1. Aerial Toastmaster +1. Midlife Upgrade +1. Riveting Rigger +1. Chipper Chopper +1. Incite Insight +1. Spell Suck +1. Suspicious Nanny +1. Finders, Keepers +1. Overt Operative +1. Steady-Handed Mook +1. Garbage Elemental B +1. Steamflogger of the Month +1. Steamflogger Temp +1. Steamfloggery +1. Work a Double +1. Wrench-Rigger +1. First Pick +1. Joyride Rigger +1. Steamflogger Service Rep +1. Clock of DOOOOOOOOOOOOM! +1. Cogmentor +1. Contraption Cannon +1. Steamflogger Boss - (Not an Un-card) + + +### Tier 0.5: Done* + +1. Blacker Lotus - Only removes from match. Should delete from deck and/or collection in limited and adventure-like modes. +1. Ass Whuppin - Implemented as "Destroy target silver bordered permanent," leaving off the Acorn errata and the ability to target other games. +1. Spatula of the Ages - Implemented to look for silver border, but has since been errata'd to include acorn cards. (See also: "Un-Cards", Tier 2.) +1. Border Guardian - Borders are only tracked on an edition level. Silver-bordered SLD cards and borderless prints won't behave correctly. (See also: "Border Color Matters", Tier 3) +1. (P) Planequake - "Uncovered Cavern" plot booster still doesn't exist. +1. Sword of Dungeons & Dragons - Dragon token is colorless, not gold. See "NonWUBRG Colors" in Tier 5. +1. (HTR) Mountain Mover - Cards stored "Underneath" Mountain Mover are put into exile instead. +1. 1996 World Champion - Activated ability in library is implemented as an emblem created at the start of the game. +1. (HTR) War of the Spark - "from War of the Spark" is currently implemented with City in a Bottle logic, which reportedly could use some revision. Unclear if card is meant to look at exact printing or original printing. (See also: "Set Matters", Tier 2) +1. (Uk) The Battle of Dragon Brothers // Fate Reforged - Notes choices by card name similar to a draft choice. + + +#### Banned and Restricted as an effect +(These cards "ban" themselves or other cards via their text, but won't register alongside format bans. Can matter for wish effects.) +1. Once More With Feeling +1. Look at Me, I'm the DCI +1. Gleemox + +#### Host Cards +(These work fine on their own, but can't reach their full potential without the Augment cards) +1. Adorable Kitten +1. Shaggy Camel +1. Mer Man +1. Numbing Jellyfish +1. Big Boa Constrictor +1. Dirty Rat +1. Stinging Scorpion +1. Bumbling Pangolin +1. Feisty Stegosaurus +1. Eager Beaver +1. Mother Kangaroo +1. Wild Crocodile +1. Angelic Rocket +1. Gnome-Made Engine +1. Labro Bot +1. Voracious Vacuum + +#### Keyword or Mechanic Recreated via Card Script +(These cards have unique un-keywords or mechanics which haven't been implemented within the engine, but have been scripted such that they should work the same way for the most part. Edge case interactions may exist however, and a "keyword-matters" un-effect wouldn't be able to include them.) +1. Earl of Squirrel - Squirrellink; Uses a static trigger instead. +1. Nearby Planet - Rangeling; Uses a continuous "is all basic land types and all non-basic land types" ability. +1. Rainbow Dash - Player Coolness; Creates "Coolness Badge" effects to represent every 20% of coolness. +1. (Uk) Phyrexian Esthetician - Oil Scavenge +1. (Uk) Hound of Urabrask - "Oildying" (Undying but oil counters.) +1. (Uk) Incubob - "Incubate Dark Confidant 1" + + +### Tier 1 +**Either works within the rules, or could be scripted without engine changes.** + +1. Gerrymandering +1. GO TO JAIL +1. The Big Idea +1. Krark's Other Thumb +1. Fluros of Myra's Marvels +1. (P) Baneslayer Aspirant +1. (P) Priority Avenger +1. (P) Innocuous Insect +1. (P) Truth or Dare +1. (P) Buried Ogre +1. (P) A Good Thing +1. (P) Barry's Land +1. Gifts Given +1. Svega, the Unconventional +1. (P) Common Black Removal +1. (P) Call from the Grave +1. (P) Chatzuk, Mighty Guitarist +1. (P) Dwarven Confluncer +1. (P) Fludge, Gunk Guardian +1. (P) Flanking Licid +1. (P) Hish of the Snake Cult +1. (P) Jeskai Baller +1. (P) Jund 'Em Out +1. (P) Lazotep Archway +1. (P) Microscope +1. (P) Magus of the Chains +1. (P) Meandered Towershell +1. (P) Noble Ox +1. (P) Pinchy McStingbutt +1. (P) Processing Plant +1. (P) Penumbra Umbra +1. (P) Rin and Seri, Inseparabler +1. (P) Rule with an Even Hand +1. (P) Search Elemental +1. (P) Wrath of Leknif +1. (P) Wowzer, the Aspirational +1. (Uk) Echoing Echo +1. (Uk) Old Way Phyrexian +1. (Uk) Hidetsugu's Poison Rite +1. (Uk) Pulse of the Hunter Maze +1. (Uk) Goblin Savant +1. (Uk) Platinum Persecutor +1. (Uk) Mindslaver Toolkit +1. (Uk) Call Up Emrakul to Help +1. (Uk) Kylem All-Star +1. (Uk) Gerrard and Hanna +1. (Uk) Invasion Specialist +1. (Uk) Nevermind +1. (Uk) Phyrexian Cytoshaper +1. (Uk) Emrakul and Chatterfang +1. (Uk) Halana and Alena and Gisa and Geralf +1. (Uk) Autumn Willow and Baron Sengir +1. (Uk) Jitte and Divining Top +1. (Uk) Bob the Claymore +1. (Uk) Battle Myrsphere +1. (Uk) One Does Not +1. (Uk) Math is for Blockers +1. (Uk) My Deck is About a Seven +1. (Uk) Maitre Tree +1. (Uk) Ano'thr, Equipment Commander +1. (Uk) Rankle, Master of Pranksters +1. (Uk) Princess Snowfall +1. (Uk) Identify the Culprit +1. (Uk) The Knight of Weeks +1. (Uk) Impressive Rat +1. (Uk) The Dogronmaster +1. (Uk) Windmill Slam +1. (Uk) The Joiner of Cats +1. (Uk) Bag of Stroopwafels +1. (Uk) Famous Museum +1. (Uk) Seer of the Bright Side +1. (Uk) The Wiley Speedster +1. (Uk) The Sixth Seraph +1. (Uk) The Powerful Dragon +1. (Uk) The Cobra King +1. (Uk) Another Night in Vegas +1. (Uk) First Stage of Magic Design +1. (Uk) Moth Herb Elixir +1. (Uk) Item Crate +1. (Uk) Aetherflux Car +1. (Uk) Char-Dog +1. (Uk) Mijo, the Bull +1. (Uk) Reflective Gate +1. (Uk) Mana Max, Afterburner +1. (Uk) Catch-Up Mechanic +1. (Uk) The Good Time Sleuth +1. (Uk) Kibo's Bananamobile +1. (Uk) MagicConsecrated Sphinx +1. (Uk) Establishing Shot +1. (Uk) Would You Have Done the Same? +1. (Uk) Where We're Going... +1. (Uk) Peel Out +1. (Uk) Davvol's Birthdaymobile +1. (Uk) Fast // Furious +1. (Uk) Tarkir Omenpath +1. (Uk) Dialogue Tree +1. (Uk) Save Point +1. (Uk) Red and Black Legac +1. (Uk) The Herald of Numot +1. (Uk) The River Warlock +1. (Uk) The Mysterious Sphere +1. (Uk) The Misty Stepper +1. (Uk) Tarkiran Towelsbane +1. (Uk) Stormscale Wurm +1. (Uk) Tragda, the Burner +1. (Uk) Color Pie +1. (Uk) Brutal COmmand +1. Jester's Sombrero - May be able to do this with RemoveFromMatch? +1. Gallery of Legends - (Attraction) Similar to Killer Cosplay? +1. (P) Gunk Slug - Similar to Time Sidewalk and Bone Rattler? +1. (P) Problematic Volcano - Similar to Raging River? +1. (P) Patient Turtle - Player went first this game. Seen with Forsaken Crossroad. +1. (P) Alberix, the Trade Planet - Resources seem mostly equivalent to "card exiled by CARDNAME", barring linked ability issues. +1. (P) Bolshack Dragon - Dunno if strikethrough in oracle text would be formatted, but is functionally all possible. +1. (P) Lich's Duel Mastery - Like Alberix, Shields seem like another "card exiled by CARDNAME". +1. (P) Luxior, Ignited - Nothing in the rules says a Planeswalker can't be attached to a creature... +1. (Uk) Untap, Upkeep, Draw - Similar to Obeka, Splitter of Seconds. +1. (Uk) Compleated Clone Shell - Think there should be some way to look at 4 random cards in the sideboard? +1. (Uk) Azorius Traffic Enforcement - DisableTriggersEffect looks like it could focus on Backup abilities? Detain as a keyword action probably doesn't need special treatment. +1. (Uk) That's No Moonmist - Can use the existing "FrontFace" property. +1. (Uk) Praetorhoof Behemoth - Adds number of revealed cards to number of matching cards in zones. Should be doable. +1. (Uk) Unknown Event Shores - Complicated, but the same support is needed for Cryptic Spires. +1. (Uk) Syr Konrad's Squire - Would probably work fine as separate abilities. +1. (Uk) Life at Stake - Similar to Wheel of Misfortune +1. (Uk) Investi-Gate - Just choose and create two of the ten guildgates? +1. (Uk) The Inspector Inspector - Should be possible with ETBReplacements... +1. (Uk) The Zassacre Zirl - Removing a card's commander status is now totally do-able; (See "You're In Command", Tier 0) +1. (Uk) The Keeper of Kaldra - Companion effect can be written out manually, should work fine. +1. (Uk) The Keeper of Four Scythes - Scythe tokens not defined in rules, but could just be scripted normally. +1. (Uk) Hosting Season - Can ignore the date conditions since they'll never apply again. +1. (Uk) Year-End Review - Might be tricky to choose a card in different ways across three specific zones, but commander promotion is supported. +1. (Uk) Higher Level Zone Monster - Three ETB replacements in one, but should all be possible? +1. (Uk) Rideable Mantis - Unsure if you can use SaddledThisTurn in an xCount. +1. (Uk) The Bean - (Plane) Altered for second printing. May be worth a variant print? +1. (Uk) Black Lotus Lounge - (Plane) +1. (Uk) City Hall - (Plane) +1. (Uk) Shy Town - (Plane) +1. (Uk) Sky Deck - (Plane) +1. (Uk) The Windy City - (Plane) Significantly altered for second printing. May be worth a variant print? +1. (Uk) Bicycle Rack - (Plane) +1. (Uk) Event Horizon - (Plane) +1. (Uk) The Food Court - (Plane) +1. (Uk) Oteclán - (Plane) +1. (Uk) Probability Flux - (Plane) +1. (Uk) Tarnation - (Plane) +1. (Uk) That's Enough Slices - (Phenomenon) Or rather, "Phenome-nom". Could potentially add support for "flavor type-lines". Functionally it's fine. + + + + +#### Rerolling Dice (statically) +(Difficult, but feature is needed for some legal UNF like Monitor Monitor and Xenosquirrels) +1. Clam-I-Am - Option to reroll threes. +1. Wall of Fortune +1. Snickering Squirrel +1. (See also: Socketed Sprocketer, Tier 2) +1. (See also: "Rerolling Dice (using the stack)", Tier 4.) + +#### Card Packs +(We have Booster Tutor. MakeCard can probably support these?) +1. Summon the Pack +1. Opening Ceremony +1. (Uk) Unknown Event - (Phenomenon) + +#### Doesn't Actually Do Anything +1. (P) Abbot of the Sacred Meeple +1. (P) Spuzzem Strategist - Unless R&D's Secret Lair is implemented and they're playing Floral Spuzzem... + +#### City in a Bottle Effect +(i.e. cares about cards that were printed in one or more specific expansions) +1. (Uk) Crux of Mirrodin +1. (Uk) Sheoldred's Terror - May change if Argentum is revisited +1. (Uk) Delphia, Undecided +1. (See also: "Set Matters", Tier 2) + +#### Commander-tron Lands +(Urzatron lands have special support, but could probably be scripted without it.) +1. (Uk) Command Mine +1. (Uk) Command Power Plant + +#### Choose and create random card from a list +(ChooseCardNameEffect should be able to do this, and CopyPermanent with "DefinedName$ NamedCard" looks like it will work.) +1. (P) (Uk) Who's That Praetor? +1. (Uk) Form of the Stax Player +1. (Uk) The Crab Queen +1. (Uk) The Disciple of Vess +1. (Uk) The Scholar of Seas +1. (Uk) The Disciple of Nissa +1. (Uk) The Ash Lizard +1. (Uk) The Wise Sable + + +### Tier 2 +**Spiritually works within rules, new variations on existing features, and/or minor engine changes needed.** + +1. Burning Cinder Fury of Crimson Chaos Fire - Taps trigger needs a way to reference the tapping player that triggered it. +1. Ricochet - Dice roll needs a "RememberLowestPlayer$" flag, plus one to make players reroll until ties are broken. +1. Free-Range Chicken - Might be possible to use YouRolledThisTurn with a ResultSVar? Unsure of the exact syntax. +1. Team Spirit - Defined player logic needs something like TargetedAndTeam. +1. Psychic Network - Top cards of decks visible to all opponents. Maybe not by holding it against the player's head but revealing it would work fine. +1. Get a Life - Exchange life with teammates. Unclear how this exchange works with more than 2 players on a team, but could probably dig that info up somewhere. +1. Curse of the Fire Penguin - Enchantment that replaces typeline, stats, and textbox. Unsure if this is a different layer than normal. +1. Six-y Beast - May need its own AI flag. It would want to favor higher numbers both when placing counters and guessing them. Also needs to account for counter-doublers in play. +1. By Gnome Means - "Choose any kind of counter a printed card refers to" +1. Rules Lawyer - Think there are a few complications to just skipping over SBA's, but seemed doable. +1. Kindly Cognician - Text matters, but only the Oracle text, not the printed text like most Un-cards. +1. Socketed Sprocketer - Already have diceroll storage from Centaur of Attention; need uninstall as a cost and a Monitor Monitor-esque dice replacement that uses the uninstalled die as the replacement value. +1. Spike, Tournament Grinder - Wishes for banned cards. +1. Squirrel-Powered Scheme - +2 to all dice rolls. Might be possible with current replacement effects? +1. Far Out - Makes Modal Choices into "Choose one or more". Maybe just a replacement effect on Charm? +1. Animate Object - Effect doesn't really care what the object is. Could just be a token. Might be fun to have a selection of images of random household items to represent the token. (But also Stickers) +1. Pie-Eating Contest - Can't physically eat food to pay the X-cost, but can still sacrifice food tokens. +1. Tug of War - Start subgame with 3 chosen cards in battlefield. +1. (P) Ral's Vanguard - Vanguard with "Requirement" +1. (P) Enroll in the Coalition - Player is a Flagbearer. +1. (P) Learned Learner - Maximum hand size other than 7. Does this include "no maximum hand size"? +1. (P) Blood Poet - Spark abilities. +1. (P) Everlasting Lich - "Can't die." +1. (P) Swarm of Locus - Creature with Land Type +1. (P) Witty Demon - Starting deck size compared to minimum, similar to Yorion. +1. (P) Lazier Goblin - Motivate keyword. Can't attack or block until one-time cost is paid. +1. (P) Red Herring - Swaps itself from the hand with a permanent in play, swapping targets on the original to itself. Also name collision. +1. (P) Trial and Error - Triggers when countered or fizzled. Elemental Card Type. +1. (P) Geometric Weird - "Greatest number of spells and abilities from different sources that were on the stack simultaneously this turn" +1. (P) Kaya, Ghost Haunter - Haunt target creature, target haunted creature. +1. (P) Pick Your Poison - Nearly identical to point value modal choices seen in Bloomburrow, but no icon and points must add up to exact value. Also name collision. +1. (P) Seek Bolas's Counsel - Planeswalk in a non-planechase game. +1. (P) Rift - Can start in your opening hand. +1. Snow Mercy - Cost of Tap, Untap, Tap, Untap, Tap. +1. Princess Twilight Sparkle - Unclear if everybody winning is the same as a draw, but otherwise works fine within the rules. Shame the cards it depends on don't. +1. (P) Creepy Crawler - "Afraid" status. Needs to know about applicable cards even if they left play already. +1. (P) Catch of the Day - Multiple chosen modes to specify abilities and stats as it enters. May work as is? +1. (P) Don't Worry About It - Animate dead works. Dunno if a hidden zone does, though. +1. (P) Essence of Ajani - Emblem spell. +1. (P) Knight of Lost Causes - "Way behind" state. +1. (P) Lifening Elemental - Creature that splices onto spell. Might work as-is? +1. (P) Liliana's Other Contract // Liliana's Undead Minion - Think it works in the rules, but may need some hack to display it face up when it transforms. +1. (P) Night of the Flying Merfolk - Bedtime Story, puts lore counters on only at end step. +1. (P) No-Regrets Egret - Serum Powder exists, but its implementation is hard-coded. +1. (P) Narod, the Beige Flower - Assign damage equal to mana value rather than power. +1. (P) Orb of Origin - Brings back Mono and Continuous artifact types and their functions. +1. (P) Pokey, the Scallywagg - Replace coin flipping with dice rolling and vice versa. May be possible already? +1. (P) Subgoyf - Number of different non-creature subtypes in graveyards. +1. (P) TL;DR - Check if card has non-keyword abilities. +1. (P) The Many Deeds of Belzenlok - Copy specific numbered chapter ability from a Saga. +1. (P) Toddler's Rage - Tantrum, like trample but for blocking. +1. (P) Wormhole Warp - Reveal cards from sideboard at random until... +1. (P) Your Wish is My Command - Choose an extra mode for a spell cast this way. +1. (Uk) Agoraphobic Phyrexian - Could reasonably be adapted to affect the Planechase plane instead of the Unknown Event plane. Should be possible to RemoveAllAbilities from a Plane? +1. (Uk) Xerex Squire - Square root, or maybe just add hypotenuse support to X-math. +1. (Uk) Occupation of Llanowar - Control Point battle type. +1. (Uk) Phyrexian Incubator - Properties of back face of card in library. +1. (Uk) Fioran Reformist - Remove the Monarch. Democracy can just be implemented in the card script. +1. (Uk) Dockbreacher - Number of treasures created this turn. +1. (Uk) Ring Out - Unsure if TriggerZones supports Library. +1. (Uk) Soul Drainer - Resets commander damage +1. (Uk) Triple Threat - Triples commander damage +1. (Uk) Kallist Rhoka - Becomes a copy of another card while on the stack, other card becomes copy of it. May be possible now? +1. (Uk) Simic, Value Engine - Count opponent's lands played and cards drawn last turn. +1. (Uk) Power Level Analyzer - Difference between two players' chosen numbers. Could probably modify logic from The Toymaker's Trap. +1. (Uk) Mana Conference - Doesn't look like ChooseTypeEffect yet supports multiple players choosing. +1. (Uk) Adventurer Beguiler - Recognizes "on an adventure", distinct from other may-play effects. +1. (Uk) Grek the Ogre - Filter list of choices based on creatures you control. +1. (Uk) Maeve, Wearer of Many Hats - "Any number of Role enchantments may be attached to CARDNAME" +1. (Uk) Guild Pact - Pay mana of chosen colors +1. (Uk) The Curve Keeper - Longest consecutive mana curve. +1. (Uk) The Fish Brewer - Panharmonicon, but tap fish to increase number of triggers. +1. (Uk) Irrefutable Evidence - Triggers when chosen for Collect Evidence +1. (Uk) The Sprinkler of Stardust - There's probably some reason Magar couldn't be written this way... +1. (Uk) The Pleasant Taxer - Taxes searching a library. +1. (Uk) The Karst, Enchanted - Different toughness and different card types while searching. +1. (Uk) Genevieve, Conniving Dragon - Party die. X-Math for number of sides on a regular die roll? +1. (Uk) Artist Alley - (Plane) Choose card with favorite art. Would have to work on the honor system. AI can pick randomly. +1. (Uk) Quest Compleated Beast - Fear of Sleep Paralysis brought some "can't remove counters" support, but doesn't yet include players. +1. (Uk) The Tokenator - Token's power and toughness is any combination that totals a certain value. +1. (Uk) The Weekly Princess - "Stops being day or night" - shouldn't be tough but needs a couple tweaks to work. +1. (Uk) Zimone's Homework - Similar to prime matters, but Fibonacci, lazy caterer's sequence, and square numbers matter instead. +1. (Uk) The Archenemy's Charm - Set scheme in motion in non-archenemy game. +1. (P) Second City - "Spend this mana only to cast your second spell in a turn." +1. (Uk) Htbr, Racetrack Referee - Can't cast spells using alternative costs. Might be doable through CantBeCast. +1. (Uk) Gomif, Fast Racer - Allow increasing speed beyond 4. +1. (Uk) Ghirapur Grand Prix - (Plane) "All players start their engines!" as a keyword action. +1. (Uk) Yet Another Night in Vegas - Reset chosen charm modes. +1. (Uk) You and Ooze Army? - Count creature types across all non-changelings. Similar to Embiggen? +1. (Uk) New Magic Game Plus - Needs a way to leave itself in exile after restarting the game. (Currently can only ignore cards in one zone at a time.) +1. (Uk) Out of Town - Unsure if there's an easy way to refer to the owner of an exiled card in a later trigger. + + +#### Unique but Simple Keywords +(These have custom keywords or named mechanics that could probably be reproduced within a card script. See "Keyword or Mechanic Recreated via Card Script", Tier 0.5) +1. (P) Squidnapper - Ransom keyword ability, a cost an opponent may pay statically to nullify an ongoing effect. +1. (P) Spellmorph Raise Dead - Basically morph for instants and sorceries. +1. (P) Bear with Set's Mechanic - Aggressive keyword. Similar to Last Night Together? Could possibly do it through a unique effect like Gollum, Obsessed Stalker. +1. (P) Avacyn's Collar, the Symbol of Her Church - Shackle keyword is just Equip for enemy creatures. +1. (P) Arcanum Things - Equipment swap keyword. +1. (P) Boltfire - Flashforward, flashback but card goes to bottom of library. +1. (P) Forbidden - Limit to 0 copies per deck. See current implementation of Gleemox, Tier 0.5. +1. (P) Dairy Cow - Grazing type is just an enters with counters RE. +1. (P) Flavor Disaster - Negamorph is megamorph with -1/-1 instead of +1/+1 +1. (P) Immersturm Battlefield - Hosting creatures, should work via imprint. +1. (P) Kozilek, Compleated - Annihinfect: Annihilator equal to defending player's poison counters. +1. (P) Marchesa's Surprise Party - "Conspiracy - Secret Mission." +1. (P) Maestros' Totally Safe Hideout - Land casualty +1. (P) Pyromancy 101 - Teach +1. (P) Phyrexian Seedling - Proliferatelink, might work fine as a static ability? +1. (P) Plain Walker - Planeswalkerwalk +1. (P) Sliver of Hope - Hope, prevent damage to it while attacking. +1. (P) Vuzzle Spaceship - Spaceship and spaceship abilities. +1. (P) You Compleat Me - Maximum life total becomes 10 for the rest of the game. Emblem with activated ability? +1. (Uk) Sawtooth Avenger - Megasunburst +1. (Uk) Oilskelion - Oil Sunburst +1. (Uk) Rattatwotwo - Chef Role +1. (Uk) Wistful Puppeteer - Animated Role +1. (Uk) The Miniaturizer - "Soospect", granting First Strike and can't block. Designation never checked. +1. (Uk) Urza's Hot Dog Stand - Food Crew +1. (Uk) The Judge of Height - "Height", which is toughness plus mana value. +1. (Uk) Sue, Everlasting Dinosaur - "Fossilize", Unearth-like effect with a type change and finality counter. +1. (Uk) The Vegetable Car - "Onionfect", generates food tokens instead of dealing damage. +1. (Uk) Shen, Wish Granter - "Scatter the Dragonstorm Globes" keyword action is trivial except for the need to track it globally. Could use an emblem maybe? +1. (Uk) You're Toast - "Manifest Bread", like Manifest Dread except the manifested card becomes a food and can't be turned face up. + +##### Mono-Eminence Cycle +(Can probably be tested for with existing tools.) +1. (Uk) Theopholos, Order Acolyte +1. (Uk) Lorthos, Tentacled Terror - (Also starting hand size.) +1. (Uk) Auntie Flint +1. (Uk) Mzed, Mercenary Leader +1. (Uk) Mr. Wiggles, Helpful Butterfly - (Also tutor to battlefield before game starts?) + + +#### Variations on Existing Keywords +(More complex than above; need engine changes rather than just scripting them. Nothing unprecedented though.) +1. (P) Friarball - Coststorm keyword, count different mana values among spells and lands this turn. +1. (Uk) Arcbound Mamba - Poison Modular, +1/+1 counters placed on players by it become poison counters. +1. (Uk) Memnarchitect - Affinity for artifact creatures that weren't originally artifact creatures. Check changed types? +1. (Uk) Shadowmoor Draw Spell - Untap Convoke. +1. (Uk) Farseeing Flockmate - Flying backup, variant of backup that gives flying counters. +1. (Uk) Azra Matchthrower - Battlebond, soulbond except it pairs with Battles. +1. (Uk) Merfolk Surveyor - Explore-clash, clash but also effects of explore using revealed cards. +1. (Uk) Groaaaaag, Hungry Monster - Commander Suspend, Suspend from command zone with commander tax applied +1. (Uk) The Companion of the Wilds - Old Companion (Also Set Matters and Playtest Cards matter, Tier 2). +1. (Uk) The Multifaceted Phyrexian - Fixed commander ninjutsu. +1. (Uk) Reverse Ninja - "Ustujnin", like Ninjutsu but for blockers. + +#### Three-color Devotion. +(Currently only supports two. These are also written as "Devotion to Abzan" instead of "Devotion to White, Black, and Green") +1. (Uk) Devoted Abzan +1. (Uk) Devoted Sultai +1. (Uk) Devoted Temur +1. (Uk) Devoted Jeskai +1. (Uk) Devoted Mardu + +#### Unique but Simple Costs +(Needs engine support since costs can't be custom-scripted, but the behavior for these should be straightforward.) +1. (Uk) That Which Was Compleated - Gaining poison counters as a cost. +1. (Uk) Chicago Loop - Reduce speed as an X-cost. + +##### Commander Enchantment +(Enchant commander, but remains attached in all face up zones. Animate Dead seems like precedent.) +1. (Uk) Taught by Serra +1. (Uk) Taught by Narset +1. (Uk) Taught by Vito +1. (Uk) Taught by Bruce Tarl +1. (Uk) Taught by Surrak + +#### Flip Self +(Doesn't care about where it lands, only whether it's face up or down. Essentially a coin flip.) +1. Orcish Paratroopers +1. Trapeze Artist + +#### Specific Action or Dialog as payment +(Could implement these as zero costs. Would be more fun to declare actions with popups or chat messages though.) +1. Knight of the Hokey Pokey +1. Mesa Chicken +1. Infernal Spawn of Evil +1. Urza's Contact Lenses +1. Emcee +1. Rod of Spanking +1. Impounding Lot-Bot +1. The Majestic Duo +1. (Uk) Game Knights Live - (Plane) +1. (See also: Magic Word, Tier 3) + +#### Compliments, Insults, Apologies +(Similar to above, but less specific wording. Could be done in spirit by keeping pre-defined list of phrases and let the player choose one.) +1. Miss Demeanor - Possibly an activated ability. +1. Mother of Goons +1. Chivalrous Chevalier +1. An Incident Has Occurred + +#### Trackers, Markers, and Unique Designations +1. The Fallen Apart - Arms and Legs +1. B-I-N-G-O - Chips on bingo board in art +1. Togglodyte - ON/OFF +1. Baron Von Count - Doom Counter +1. (P) Loopy Lobster - Stages +1. (P) Bucket List - Check off card types +1. (P) Duelists' Convocation International - Check off digits of random 10-digit number. +1. (P) Map to Lorthos's Temple - Checklist of objectives + +#### Maintaining lists +(Similar to noting things, but not just card IDs) +1. (P) Champion of the Hareish - "Buddy List", noting creature types. +1. (Uk) The Keeper of Favorite Cards - "Favorites List", noting card names. +1. (Uk) Retto, Family Racer - "Family" - noting card names secretly. Also dialog as payment. + +#### Panglacial Worm-likes +1. Infernal Spawn of Infernal Spawn of Evil - (Also dialog as payment) +1. (P) Biting Remark - Cast while scrying. +1. (P) Sunimret - Cast from bottom of library. + +#### Split Card made from Two Existing Cards +(Might work fine as-is?) +1. (P) Bind // Liberate +1. (P) Start // Fire - From two halves from other split cards + +#### Cards in Non-owner's Hidden Zones +1. Five-Finger Discount +1. Spy Eye +1. Very Cryptic Command C +1. Sly Spy E +1. X +1. Centrifuge - (Attraction) +1. Naughty // Nice +1. Last-Minute Chopping +1. (P) All-Star Kicker - Via "Pass the Ball". Also Assist Kicker. +1. (Uk) Trash Panda - Also "Opponent dredge". +1. (Uk) X, Mystery Racer + +#### Other Un-Games +(It would be fun to include concurrent games running in other tabs. Aside from that, seeing only the current game misses the spirit of the card but technically works fine, and these would otherwise be in Tier 6 or 7.) +1. Side Quest +1. Exit Through the Grift Shop +1. (See also: "Ass Whuppin", Tier 0.5) + +#### Turn Over Card +(Transform if it's a DFC, becomes a 2/2 if it's a normal card, splits up melded cards...) +1. Very Cryptic Command D - Also put card in controller's hand +1. (HTR) Optimus Prime, Inspiring Leader + +#### Un-Cards +(Looks for Silver Bordered or Acorn cards, but not playtest or Heroes cards. This category probably needs to be defined anyway.) +1. Everythingamajig B +1. Underdome +1. (See also: Spatula of the Ages, Tier 0.5) + +#### Playtest Matters +(Looks for playtest cards. Similar to the above, this should probably be a defined category anyway.) +1. (Uk) Magic Designer +1. (Uk) Playtest Wish +1. (Uk) Stika, Playtestress +1. (Uk) The Unknown Wizard +1. (Uk) Fear of the Unknown +1. (Uk) Rinta, Cousin of Sitka +1. (Uk) The Mystery Raceway - Also event outcome matters. +1. (See Also: The Companion of the Wilds, Tier 3) + +#### Set Matters +(The wording of these often varies and it's sometimes unclear how reprints should be treated. Tempted to throw them all in tier 4.) +1. (HTR) Heroes of Kamigawa - "Name printed in a Kamigawa expansion". Maybe a variation on Apocalypse Chime's set filter, to include reprints? Also booster pack, but of a specific type. May also need future-proofing against new Kamigawa sets. +1. (HTR) Keeper of the Secret Lair - Another set filter like above, but matching the set of the current print. +1. (Uk) Across the Multiverse - Only includes the main set, no bonus sheets. +1. (Uk) Must Be Knights - Specifically printings from Enchanting Tales (WOT). +1. (See also: War of the Spark, Tier 0) +1. (See also: "Apocalypse Chime" and "City in a Bottle", Black Border) +1. (See also: "City in a Bottle Effect", Tier 1) + +#### Legality Matters +1. Standard Procedure - "currently legal in Standard" +1. (Uk) The Zabulous Cosplayer +1. (Uk) The Pro Tour - (Plane) "Differently named Standard-legal cards". +1. (See also, "The Wheeling Runner", tier 2) +1. (See also, "The Mox Painter", tier 5) + +#### Extra Zones +1. (P) Whammy Burn - Whammy deck. +1. (HTR) Myntasha, Honored One - Booster pile, Booster cascade, Open a Booster as a cost. +1. (Uk) Royal Booster - (Phenomenon) Booster deck. Like booster pile but shared? + +#### Partner Variant +(Needs some fleshing out of the Partner conformity checks and deckbuilder logic) +1. (HTR) Sol, Advocate Eternal - Partners with any creature. +1. (HTR) Wizard from Beyond - Background that partners with any nonlegendary creature. +1. (Uk) Barce, Friend Finder - Partner with any legendary creature with 0-1 colors in color identity. +1. (Uk) The Knight of Land Drops - Partner with any legendary knight. +1. (P) Mothers Yamazaki - Might mostly work if just written as "Partners with Mothers Yamazaki", though will need a rule bend to allow 2 copies in singleton formats. +1. (HTR) Ormacar, Relic Wraith - Partners with a legendary noncreature artifact. + +##### Spell Commander +(From the Unknown Event in Barcelona, 2023. These are meant to use the ruling that mono-color legendaries all have Partner in Limited Commander events, which isn't currently implemented. Also they all have normalized name collisions.) +1. (Uk) Gather, the Townsfolk +1. (Uk) Clear, the Mind +1. (Uk) Ransack, the Lab +1. (Uk) Lava, Axe +1. (Uk) Rampant, Growth + +##### Ready to Run +(Normal partner variant) +1. (Uk) The Caffeinated Runner +1. (Uk) The Milling Runner +1. (Uk) The Bus Runner +1. (Uk) The Gunky Runner - Also beginning of first upkeep trigger. (See also: "Fludge, Gunk Guardian", tier 1) +1. (Uk) The Wheeling Runner - Also a Vintage-legal filter on random card creation. (See also: Legality Matters, tier 2) +1. (Uk) The Highland Runner - Also beginning of first upkeep trigger. +1. (Uk) The Bear Force Pilot Runner - Apparently this is an errata on an existing Unknown Event card? + + + +#### Mana from Particular Source +(Similar to snow costs) +1. (P) Experiment Five - Mana from a source that could produce two or more colors of mana. Use some production awareness like Exotic Orchard? +1. (P) Keeper of the Crown // Coronation of the Wilds - {L}, one mana from a legendary source. +1. (P) Yawgmoth's Day Planner - Mana produced by abilities that caused you to lose life. + +#### Unknown Event Team +(During the Philadelphia 2023 Unknown Event, players were split into two teams. These cards changed behavior based on which team you were on. Could be imitated by having the player pick a team once then retain that for all cards.) (They did it again in 2025.) +1. (Uk) Unclaimed Cat +1. (Uk) Unclaimed Bird +1. (Uk) Unclaimed Blessing +1. (Uk) Tricky Mage +1. (Uk) Unclaimed Battle Axe +1. (Uk) Furnace Oriflamme +1. (Uk) Unclaimed Tanadon +1. (Uk) Team Perseverance +1. (Uk) Team Acceleration +1. (Uk) Team Mage +1. (Uk) Draw Team Lines +1. (Uk) Now THIS Is Aether Racing + +##### Event Team Restriction +(Can't be used by certain Unknown Event teams, but that doesn't matter outside of such an event.) +1. (Uk) Winter's Demonmech +1. (Uk) Chandra's Dragonmech + +#### Pledge Mechanic +1. (Uk) Guildmark +1. (Uk) Guilded Lotus + +#### Poison Tolerance +1. (Uk) Rosewater's Nemesis +1. (Uk) Drake with Set's Mechanic +1. (Uk) Long Term Phyresis Study +1. (Uk) Burn the Phyresis +1. (Uk) Naturalize the Phyresis +1. (Uk) Melira's Snacks + +#### Unknown Event Planes +(During the Minneapolis 2023 Unknown Event, Planechase-like planes were in effect for all games across the event. These ones could be implemented as normal Planechase cards.) (These currently aren't on Scryfall) +1. (Uk) Ondu - Alternate land drop +1. (Uk) Mana Flood Ridge +1. (Uk) Sagu +1. (Uk) Shard Boundaries +1. (Uk) The Inversion Zone +1. (Uk) The Moon +1. (Uk) Istfell + +#### Command Tax Matters +1. (Uk) Tax Hounds +1. (Uk) Tax Draw +1. (Uk) Tax Sweeper +1. (Uk) Tax Bolt +1. (Uk) Tax Keeper + +#### Match Score matters +1. (P) Ruff, Underdog Champ +1. (Uk) Fear of Going 0-2 Drop + +#### Enters with Booster +These both enter with a sealed booster "under" them, and open it upon dealing combat damage. Could do something like Mountain Mover - get a booster's cards, put all the cards from it face down in exile, then reveal them later. +1. Stocking Tiger +1. (Uk) The Chaos Keeper + + + +### Tier 3 +**Possible to implement, but needs whole new features and/or substantial engine changes.** + +1. The Ultimate Nightmare of Wizards of the Coast® Customer Service - Y and Z costs. Comet Storm is functionally the same 99% of the time, but why implement this at all if you're not accounting for the other 1%? +1. Collector Protector - Giving away a card. Could work similar to Ante. Only really meaningful in limited and adventure-like formats. +1. Gleemax - Control all targets. +1. Magic Word - Choose arbitrary word, whisper it as cost. Could make it another zero cost but loses spirit of card. +1. The Grand Calcutron - Hands are all public, ordered, have to be played in order, and cards added to hand are put at a position chosen by the player. +1. Infernius Spawnington III, Esq. - Number of cards "revealed" this turn. +1. Goblin Blastronauts - "Copy the spell, ability, or Attraction visit that caused you to roll that die." Might be possible to get this in RollDiceEffect and pass it on? +1. Sole Performer - Taps for {T}{T}, a resource used to pay {T} costs. +1. (P) Metagamer - Could just hard-code the list since the errata promised in the rulings is still forthcoming. If/when it does happen though, would need to be maintained, or a solution for auto-updating it would need to be implemented. +1. (P) Do-Over - Restart the turn. Possible with Undo Restore logic? +1. (P) Memory Bank - Affects future game in match. See also, "Between Games" in Tier 4. +1. (P) High Troller - All targets chosen randomly. (Number of targets still chosen by player) +1. (P) Mana Abundance - ReplaceMana, except it changes who gets it. +1. (P) Siege Elemental - Untapped creatures can't block, tapped creatures can. +1. (P) Maro's Gone Nuts - "Double any effect that doubles." No catch-all way to detect this but there aren't that many effects that this applies to... +1. (P) Zymm, Mesemeric Lord - Opponent orders their hand, reveals it one by one until you stop them. +1. (P) Weaponized Scrap - Upgrade. Similar to mutate for artifacts? Somewhat different merge rules though. +1. Decorated Knight // Present Arms - Swap deck with one outside the game, while still being able to draw from it. Could possibly use the extra zones used by Backup Plan? +1. (P) Boulder Jockey - {D}, a cost that when paid, reduces your potential land drops for the turn. +1. (P) Cleaver Blow - Multicleave. +1. (P) Defender of the Queue - Positioning keyword, orders battlefield from left to right and affects "adjacent" creatures. +1. (P) Heart of a Duelist - Could show the library zone face down and let player pick the card to be drawn? +1. (P) Plant a Sapling // Fully-Grown Treefolk - Transform while in library. +1. (P) Snap Judgment - Winning the game wins the match. +1. (P) Tax Taker - Whenever an opponent pays a cost imposed or increased by one of your cards. +1. (P) Two by Four - "Base power 4 counter" and "Base toughness 4 counter". +1. (P) Zone of Flame - Enchant any zone, shared or individual. +1. (Uk) Sort of ____ and ____ - Assemble a combat trigger from two chosen modes. +1. (Uk) Incubation Triformer - Transform a transformed phyrexian token to a "third side" which is a 3/3. +1. (Uk) Polis the Planeshifter - Copies abilities from a random planechase plane, activates corresponding chaos effects and changes planes via activated ability. +1. (Uk) Shahrazad and Sindbad - "If there haven't been any subgames this match" +1. (Uk) Unicycling Atomaton - Hand Backup, gives abilities to card in hand until end of turn, and its +1/+1 counter when it "eventually" comes into play. +1. (Uk) Huge Truck - Permanently gains abilities shared by a backup ability. Includes backup variants too. +1. (Uk) Shedding Snake - Resets command tax. Probably shouldn't affect "times cast from the command zone this game", though. +1. (Uk) Lona, Tracker of the Known - Number of english sets a card has been printed in. Bit messy but possibly do-able. +1. (Uk) Margle, Cousin of Yargle - "Can't gain abilities" +1. (Uk) Spice Rack - Maximum Commander size. Sounds like an SBA; Could maybe simulate but would want to suppress commander replacement effect. +1. (Uk) Banned Eldraine Card - Choose an additional cost that determines which card it becomes (perpetually). +1. (Uk) Takesies // Backsies - "Treat all counters as -1/-1 counters". +1. (Uk) Trampled Lotus - Activate up to three mana abilities of opponents' lands. "Splitter Second" effectively makes this a mana ability. +1. (Uk) The Asker of More Mana - RaiseCost can cover spells and abilities, but not resolution stuff like UnlessCosts. +1. (Uk) Interrogation Robot - Trivial, but partly dependent on card in this tier. (See "Who // What // When // Where // Why") +1. (Uk) The Horizon Seeker - Count unique keywords and ability words. +1. (Uk) The Convincing General - Make two creature types interchangeable. +1. (Uk) The Magical City, New - Land as commander. +1. (Uk) Mad Labs - (Phenomenon) Choose a keyword counter. +1. (P) Glimpse, the Unthinkable - "Can't be chosen" and name can't be chosen while in play. Also normalized name collision. +1. (P) Muraganda Eldrazi - "Counts as having no abilities.", but still has them. Also Primeval Counter. +1. (P) Omnipresent Impostor - Can be found when searching library regardless of criteria. Has all names. Basic supertype. +1. (P) Oddric, Lunar Marquis - Eater-of-Virtue-ing a specific triggered ability by text. Granting Devoid and Changeling will need special handling since that normally doesn't work due to layers. (See "Slivdrazi Monstrosity", Tier 0) +1. (P) Panglacial Shinobi - "Library ninjutsu" - have to swap it with an attacking creature. Also Panglacial Worm-likes, Tier 2. +1. (P) Toofer, Keeper of the Full Grip - "Card Advantage", the net change in relative hand size between you and all opponents when a spell resolves. +1. (Uk) Second Stage of Magic Design - Looks for unique Keywords and Ability words. +1. (Uk) Fear of Unsleeved Decks - Could look at the player's card back choice and see if it's the default? +1. (Uk) The Fearsome Flock - Split second on the level up ability. Lets you activate abilities of opponents' cards. +1. (Uk) Oozeavite - Keyword counters that also grant +1/+1 +1. (Uk) The Cosplayzer - Changes first letter of name (or adds letter onto beginning of name?). Also, "The first time each turn an ability of this creature is activated or triggered" +1. (Uk) Seventh Stage of Magic Design - Can get most of the way there with existing spell, ability, and mana doublers, but mana abilities with side effects like Chromatic Sphere might be a leap. +1. (Uk) Jiffy, Vehicle Repairer - Prevents Jump-start from exiling cards cast through it. Unsure if there's comparable precedent for suppressing a specific replacement effect. +1. (Uk) Just a Li'l Guy - Preeminent stat modifier. +1. (Uk) The Command Zone - (Plane) Unlike "Shedding Snake" (see above), explicitly resets the number of times a commander has been cast from the command zone. +1. (Uk) Twenty Lessons - Casts spell without paying mana cost, but specifies the value of X if the spell has an X-cost. +1. (Uk) Vamping Vampire - ETB looks back at the number of counters on a card before it was cast from exile (i.e. LKI after two zone changes not one). Might be able to hack something with a cast trigger? +1. (Uk) Blu, Mansion Prince - Create a token that's a copy of half of a randomly chosen room card. +1. (Uk) Fear of Forgetting Names - Remove name from a permanent. +1. (Uk) Surprise, Nerd - Replace pump effects. + + + +#### Cast Cards as Other Cards +(Form of the Mulldrifter rules that the Evoke cost may be paid. Others do not rule Alternative Costs either way.) +1. Richard Garfield, Ph.D. - Any card with same mana cost. Also cast cards cannot be reused; it's been ruled that this persists even if it leaves and reenters. +1. (P) Form of the Mulldrifter - Specifically as Mulldrifter. +1. (P) Starting Town NPC - Adds Adventure onto card. +1. (P) (Uk) The Colossal Dreadmaw - Specifically as Colossal Dreadmaw. +1. (Uk) Elonc, Perfect Clone - As a copy of your commander. +1. (Uk) Fourth Stage of Magic Design - Same as Richard Garfield but only once per turn. Unclear if they share a list of chosen cards. + +#### Super Haste +(Essentially a Pact, but as a keyword and the cost can be paid any time on the next turn. Unclear how this interacts with cost adjustments. FAQ also adamant that Super haste is not a form of Haste.) +1. Rocket-Powered Turbo Slug +1. (Uk) Need for Speed (Not the Odyssey One) + +#### Text Boxes +(Usually just refers to all the abilities and keywords, and Exchange of Words does this in black border. But it has different rulings in Un-Games, where flavor text and watermarks also matter.) +1. Do-It-Yourself Seraph +1. Pheobe, Head of S.N.E.A.K. - (Also Flavor Text Matters) +1. Grusilda, Monster Masher - Merges two dead creatures into one living one. +1. It Came from Planet Glurg - Becomes a copy of X different creatures. + +#### Multi-Split Cards +(Most split card logic currently assumes only two parts) +1. Who // What // When // Where // Why +1. (P) Smelt // Herd // Saw +1. (P) There // They're // Their + +#### Phases out of Turn +1. Clocknapper - Steal phase from opponent +1. (P) Throat Wolf - Create its own combat phase during opponent's turn (Also Firstest Strike) +1. (P) Chronobot - Switch upkeep steps with an opponent +1. (P) Runed Terror - "Players take their phases sequentially" + +#### Last or Triple Strike +(Might not be that difficult, but would want it to only show up in the phase UI when relevant) +1. Extremely Slow Zombie +1. Garbage Elemental F +1. Three-Headed Goblin +1. (See also: Throat Wolf) + +#### Activate in Any Zone +(Including while it's in your library and you're about to search for a card with certain characteristics...) +1. _____ - (Also flavor text matters. Also having no name by default.) +1. Shellephant + +#### People Outside the Game Joining the Game +(Could be done by introducing an AI.) +1. Better Than One - (Needs two-headed giant rules support.) +1. Kindslaver +1. (Uk) The Egotistical Velociraptor + +#### Extend Duration of Effects +(Particularly "until end of turn" or "this turn" effects) +1. Staying Power +1. Grand Marshal Macie - Choose an ongoing effect, it lasts as long as this remains tapped. + +#### Legacy +(Effects that call for the player to make a choice before the game, which is stored with the card and affects its text or characteristics. This often involves marking the physical card. Once in-game, these choices or markings are copyable attributes.) +1. (P) Inspirational Antelope - Also keyword or ability word matters. +1. (P) Gold Mine - Mark nodes during the game, they stay marked in future games. +1. (P) Built Bear - Spree-like effect where you mark abilities and characteristics with point values. Total points marked determines mana cost. +1. (Uk) Common Curve Filler - Choose and write a number; chosen number sets power, toughness, and cost. +1. (Uk) Awoken Nephilim - Choose a Nephilim, acts as a legendary copy of it. +1. (Uk) Your Mana Rock - Cryptic Spires but an Artifact. +1. (P) Convention Maro - Choose a trigger requirement and an X-math function. Also watermark matters, landscape matters, and artist matters. +1. (Uk) The Fifth Alias - List of five other cards in your deck. Unclear what to do if you have multiple copies. +1. (Uk) The Faction Dragon - "Choose a faction" meaning clan, guild, etc. Mana cost is the faction's mana colors. Also the chosen faction's watermark matters. +1. (Uk) Your Favorite Missing Character - Any two colors, up to three creature types, an arbitrary name, and one of four listed abilities. +1. (See Also: "Cryptic Spires", Black Border) +1. (See Also: "The Prismatic Piper", "Faceless One", and "Clara Oswald", Black Border) + +#### Cards Outside the Battlefield become Copies +1. (P) Bombardment - Transform your into Missiles. +1. (P) Transcantation - Transform spell on the stack. + +#### Card Becomes Another Card's Back Face +1. (P) Blurry Visionary - Turn two cards into MDFC. +1. (P) Werewhat - Makes another creature into its own back face, becomes TDFC. + +#### Arbitrary Names +1. (P) A Girl and Her Dogs - Name the tokens anything you want. +1. (Uk) Your Favorite Character - Name of a Magic character. Probably best adapted as picking any string for the name, unless Vorthos (Tier 5) gets added. Rest of it seems possible to script, though complicated. +1. (Uk) ______ - Perpetual name change built from two chosen types and the player's name. Also no name by default. Also name conflict (lack-of-name conflict??) with _____. +1. (Uk) Naming Screen - Arbitrary four-letter names for tokens. + +#### Rulebreaker +(If this is your commander, loosens color identity or deck building restrictions.) +1. (Uk) Arvad of the Weatherlight +1. (Uk) Daxiver, Izzet Electromancer +1. (Uk) Hadran, Naya Sunseeder +1. (Uk) Valko Indorian, Researcher +1. (Uk) The Paradise Bird +1. (Uk) The Covert Blue Mage +1. (Uk) Mister Cheddar, Cheese Sliver - Exempts Slivers from singleton rules. Also "Ratmanship" keyword. + +#### Adjust validity for sacrifices +1. (Uk) Slobad, Actually Just Fine - Make sacrifice creatures and artifacts interchangable. +1. (Uk) Dan, Shrewd Trader - Bargain any permanent +1. (Uk) Stroopwafel Cafe - (Plane) Top card of library, as a food. May be "In Multiple Zones at Once", Tier 4. + +#### Face Down Commanders +(At the Chicago 2024 Unknown Event, games were played with Commanders face down until first played. This could be implemented as a game mode or variant. These cards interact with that mechanic.) +1. (Uk) Illuminating Detective +1. (Uk) Educated Detective +1. (Uk) Demon Detective +1. (Uk) Aggressive Detective +1. (Uk) Growing Detective +1. (See also: "Command From the Shadows", Tier 5) + +#### Change Game Piece Type +1. Claire D'Loon, Joy Sculptor - Tokens become "Tokens that are also Cards", which count as tokens but can exist outside the battlefield. Mystery Booster apparently has "Token Cards" (See: Time Sidewalk); are those the same thing? +1. (P) Intangible Vibes - Reminder text suggests this would continue to apply to creatures as they leave the battlefield. Rules text is not written as such. Cancelled out by Claire D'Loon? + +#### Affects Gameplay Trackers +1. (P) Naturalize 2 - Destroy emblems, monarch, city's blessing, dungeons, day, night, and the initiative. And any future trackers, like Start your Engines. +1. (Uk) Clear, Fair Magic - Destroys all of those, plus planes, schemes, vanguards, bounties and elite creatures (if those ever get added), or conspiracies. + + +#### Augment +(Could possibly use the groundwork laid by Mutate to do these) +1. Half-Kitten, Half- +1. Humming- +1. Rhino- +1. Half-Shark, Half- +1. Ninja +1. Zombified +1. Half-Orc, Half- +1. Half-Squirrel, Half- +1. Monkey- +1. Multi-Headed +1. Serpentine +1. Robo- +1. Steam-Powered +1. Bat- + +##### Augment Support +1. Success! +1. Teacher's Pet +1. Clever Combo +1. Really Epic Punch +1. Dr. Julius Jumblemorph +1. Surgeon ~General~ Commander + +##### Host Cards +1. Ordinary Pony +1. Crafty Octopus +1. Strutting Turkey + +#### Print Characteristics +(Main complication with these is the golden rule of Un-games, that these characteristics are derived from the printing, not the central Oracle info that normal characteristics use. They may vary from one print to another based on language, edition, or just for stylistic reasons. One permanent can also have multiple sets of these merged into one by something like Grusilda. Only saving grace here keeping this out of Tier 5 is that we could in theory extract most of these from the Scryfall API...) + +1. Super Secret Tech - Premium +1. Abstract Iguanart - First letter of artist's name +1. Park Map - Art +1. (HTR) Collectigull // Only the Best - Booster Fun +1. (HTR) Treizeci, Sun of Serra - Retro Frame +1. Stet, Draconic Proofreader - First letter of card name, first letter of player's name, and can also delete letters from either. +1. Topdeck the Halls - Premium, promos, alternate frames, and the vaguely defined "alternate art". +1. (P) Lutri, Pauper Otter - Color of expansion symbol. Not necessarily synonymous with rarity if counting playtest cards... + +##### Artist Matters +(Forge does keep track of this for most sets.) +1. Circle of Protection: Art +1. Drawn Together +1. Erase (Not the Urza's Legacy One) +1. Fascist Art Director +1. Artful Looter +1. Brushstroke Paintermage +1. Bursting Beebles +1. Framed +1. Greater Morphling - (Also Expansion Symbol Matters) +1. Aesthetic Consultation +1. Persecute Artist +1. Zombie Fanboy +1. Mana Flair +1. Graphic Violence +1. Remodel +1. Very Cryptic Command A +1. Ineffable Blessing B +1. Greatest Show in the Multiverse + +##### Expansion Symbol Matters +1. Symbol Status +1. World-Bottling Kit +1. (Uk) Sixth Stage of Magic Design - Also color of symbol. + +##### Rarity Matters +(Currently tracked by Forge) +1. Rare-B-Gone +1. Ineffable Blessing D +1. Well Done +1. Lila, Hospitality Hostess + +##### Collector Number Matters +1. First Come, First Served +1. Knight of the Kitchen Sink B +1. Knight of the Kitchen Sink D +1. Ineffable Blessing E + +##### Name Text Matters +1. Wordmail +1. Double Header +1. Now I Know My ABC's +1. Bloodletter +1. When Fluffy Bunnies Attack +1. Zzzyxas's Abyss +1. Monkey Monkey Monkey +1. Urza's Hot Tub +1. Knight of the Kitchen Sink E +1. Oddly Uneven +1. Very Cryptic Command B +1. Sly Spy A +1. Ineffable Blessing F +1. Everythingamajig E +1. Staff of the Letter Magus +1. Jetpack Janitor - Alpha strike +1. Katerina of Myra's Marvels +1. Knight in _____ Armor - (Also Stickers) +1. Leading Performance +1. Main Event Horizon +1. Busted! +1. Decisions, Decisions +1. How is This a Par Three?! +1. Treacherorus Trapezist +1. Disemvowel +1. Gray Merchant of the Alphabet +1. Aardwolf's Advantage - Alpha strike +1. Alpha Guard +1. Angelic Harold - (Also Stickers) +1. Chea, Friend to Maybe Too Many +1. (Uk) The Water Maro +1. (Uk) Chic // Ago + +##### Text Box Text Matters +1. Tainted Monkey +1. Punctuate +1. Pygmy Giant +1. Meddling Kids + +##### Rules Text Matters +1. capital offense +1. Nocturno of Myra's Marvels +1. Third Stage of Magic Design - (Or type line) + +##### Reminder Text Matters +1. Duh +1. Old Guard + +##### Flavor Text Matters +1. Ineffable Blessing A +1. Plot Armor +1. Carnivorous Death-Parrot - Does this become an unpayable cost if copied onto a card without flavor text? +1. (Uk) The Rhystic Storyteller - Also deck can't use sleeves? Could override their card back choice... + +##### Border Color Matters +(Border color is currently supported on an edition level, but not for individual cards. Some cards that use it to look for silver borders have also since been errata'd to include acorns.) +1. Kinght of the Kitchen Sink A +1. Ineffable Blessing C + +##### Watermark Matters +1. Knight of the Kitchen Sink F +1. S.N.E.A.K. Dispatcher +1. Very Cryptic Command E - Also border color +1. "Rumors of My Death..." +1. Hammerfest Boomtacular +1. Druid of the Sacred Beaker +1. Stamp of Approval +1. Secret Base +1. Watermarket + + +### Tier 4 +**Either poorly defined rules, or other logistical complications exist. Often can be figured out case-by-case but general solutions are hard to come up with.** + +1. B.F.M. (Big Furry Monster) - Maybe "Can only enter the battlefield simultaneously with its other half," plus a SA to cast them both, plus a replacement that merges them? +1. S.N.O.T. - Unclear what sticking them together means. Merging permanents perhaps? +1. Graveyard Busybody - "All graveyards are also your graveyards." (Also Flavor Text Matters) +1. Over My Dead Bodies - Creatures in graveyards can attack as though they were on the battlefield. +1. Mary O'Kill - Switching places with another card. Conveys all statuses, like an advanced Ninjutsu? Gets a dozen times more complicated when someone drops Conspiracy and names Killbot. +1. Split Screen - Multiple Libraries, choose which one each time you interact with one. +1. Mobile Clone - Copying the physically visible characteristics and modifiers of a card and baking them into the copy. +1. Trigger Happy - Trigger an ability. Sounds possible on paper, and rulings cover a lot of edge cases. May just have to try it and see if it breaks. +1. Icing Manipulator - +1/+1 counters are also food tokens. Not even sure how the UI for this would even work. Summon in food tokens to represent them, then add or remove them in lockstep with the counters B.O.B. style? +1. Log Flume - (Attraction) Creatures in a log together can't be blocked unless they're all blocked, and spells that target one of them target all of them. +1. (P) Animate Spell - Enchants a spell on the stack, turns it into a creature. +1. (P) Queue of Beetles - Turns Stack into a FIFO queue. +1. (P) Seasoned Weaponsmith - Creature can be attacked directly +1. (P) Interplanar Brushwagg - Neutral creature that attacks on its own. +1. (P) Soulmates - "Enchant two creatures" +1. (P) Louvaq, the Aberrant - Different, more complicated definition of "Modified" in its reminder text. Unclear if this should be overwritten by game rules. If so, would fall into Tier 1. +1. (P) Pithing Spyglass - Remove ability words and their corresponding abilities. Could possibly search for them in a trigger or ability description? +1. (HTR) Elusen, the Giving - "Whenever a player donates a permanent to another player". Whenever a spell or ability they control would give a permanent they control to another player? +1. (P) Brigid, Who's Seen Some Stuff - Unsure if granting a keyword that grants keywords creates a dependency or layering issue. Also Nimble keyword. +1. (Uk) Chancellor of the Mulligan - Mulligan as an action mid-game. Unclear if this should be affected by format's mulligan rules, or its interaction with Serum Powder. +1. (Uk) The Beleaguered Boxer - Would need special handling in the deck editor, turning a constructed deck into sealed, preventing future editing, then modifying it from in game. Complicated but not impossible. +1. (P) Terry Pin, Turboturtle - Unclear if this includes keywords like Equip, or just when those exact words are printed in the ability text. +1. (Uk) The Zonian Brawler - "Bite" effects matter. (Also Rulebreaker, Tier 3) +1. (Uk) The Game-Changing Host - "Anything that could have changed The Game-Changing Host on the turn it enters applies retroactively." Also game-changer matters. + + +#### Rerolling Dice (using the stack) +(Would probably be best to approximate these as static abilities) +1. Goblin Bookie - Also reflip coin. +1. Pippa, Duchess of Dice +1. Scooch - Even the rulings have no idea how this works. + +#### Between Games +(Effects across multiple games of the same match is straightforward enough. Ideally could include rematches with adventure enemies or similar. Rematches in custom matches however gets iffy - what makes an AI the same individual? Name? Avatar? Human opponents can pose similar problems, and tracking this across different hosts becomes complicated.) +1. Double Dip +1. Double Take +1. Double Cross +1. Double Deal +1. Double Play +1. Ghazban Ogress - Number of games won today +1. Gus - Win streak against player +1. Time Machine + +#### Converting Counters +(Move a counter, and convert it based on the destination's rules text and/or card types.) +1. Giant Fan +1. Everythingamajig A + +#### Swap Player Places +(Exact implementations of these two would be straightforward. However, the FAQ for Unglued and the rulings of the latter suggest that these should swap more than what's described on the card.) +1. Mirror Mirror +1. Everythingamajig F + +#### Lookahead +(Effects that classify cards, spells, or abilities based on what effects they could or would have upon resolution.) +1. Ambiguity - Reacts to counterspells or permanents that would enter with counters. +1. Proper Laboratory Attire - Identify cards that allow or require die rolls. +1. Mishra's Toy Workshop - Generates mana for token-creating abilities. +1. (Uk) The Countering Runner - Discounts spells that would counter things. (Also "Psychic Damage") (Also "Ready to Run", Tier 2) +1. (Uk) The Brewing Chef - "card that creates a token". + +#### Memory +(The AI for "accidentally" forgetting certain bits of info is a bit weird conceptually but not impossible) +1. Squirrel Farm +1. My First Tome +1. Everythingamajig D +1. Memory +1. Common Courtesy - Remembering to ask permission. Possibly implement "Asking" by clicking on Common Courtesy between casting and passing priority? +1. (Uk) The Trivia Mastermind +1. (Uk) The Actualizer +1. (Uk) The Fact Checker - Also unclear scope for "If you would guess a card, instead just choose the outcome you want." + +#### Attack Active Player +1. Party Crasher - Attacks during opponent's combat phase. +1. Evil Presents - Attacks own controller. + +#### In Multiple Zones at Once +1. Yet Another Aether Vortex +1. Masterful Ninja +1. (P) All-You-Can-Eat Buffet - "The top card of your library is a Food token. It's still a card". The "card" part of the card isn't on the battlefield. Apparently meaning this is in two zones at once, with two separate sets of characteristics at once? + +#### Time +(Can use the system clock, but the question is which player's clock to use when online) +1. Elvish House Party +1. Old-Fashioned Vampire - Does isitdarkoutside.com have an API? +1. Syr Cadian, Knight Owl +1. Spirit of the Season - Season of the year. Shifts the complication from timezones to regional calendars... +1. Some Disassembly Required - "If it's December", also distributing keyword abilities. +1. (Uk) May of the Machine - Checks if today is May 6th or May 7th. + +#### Instant Permanent +1. (P) Visitor from Planet Q +1. (P) Lightning Colt +1. (See also: Blue Screen of Death, Tier 5) + +#### Animate Zone +(Attaching an enchantment to a zone is a bit weird. The resulting creature in these cases doesn't seem to be all that complicated to handle in most cases, but some weird edge cases exist, especially with un-cards.) +1. Animate Library +1. Animate Graveyard + +#### Knock-on Effects +(Cards that alter the existing game rules to work as written, and whose changes would influence black-border interactions. Could have a game flag to decide whether or not to use un-rules.) +1. Super-Duper Death Ray - Trample on an instant. Very similar to Flame Spill but nuances of it being a keyword need to be investigated. Option to assign more damage to target? +1. (P) Glade of the Pump Spells - Land with mana cost. Do-able on its own, but may have a knock-on effect for other cards. +1. (P) Madlands - Land with Madness. Played as a land drop when madness cost is paid. + +#### Share attachment effects +(Could possibly get 90% there with a static ability meddling with EquippedBy or AttachedBy, but plenty of edge cases out there.) +1. Solaflora, Intergalactic Icon - Equipment, Auras, plus counters and stickers. +1. (Uk) The Kami Knight - Just Equipment. (Also "Can't be Goaded") + +#### Make things into Planeswalkers without Loyalty Counters +1. (P) Abian, Luvion Usurper - You become a planeswalker, with your life as loyalty counters. B.O.B. style link again? +1. (P) Planeswalkerificate - Creature becomes a planeswalker with loyalty and toughness tied together. Unclear how it works with other toughness modifying effects. + +#### Alters Counting +1. (P) Five Kids in a Trenchcoat - Counts as five creatures when counting creatures. +1. (Uk) The Multiplayer Nitpickers - Effects that count players see an extra one. + +#### Combat damage uses the stack. +(Probably would first need support as a game rule.) +1. (P) Stack of Paperwork +1. (Uk) Rafi, Retro Racer - Also enables mana burn. + +### Tier 5 +**Theoretically possible to do digitally, but in practice there is no good way to do these in Forge** + +1. R&D's Secret Lair - Would have to maintain separate cardscripts for every print that functions differently. +1. Vorthos, Steward of Myth - Named character, appearing in art, name, or flavor text. Besides the art (see Art Elements below), would need the list of named characters and any variants of their name to look for. +1. Spelling Bee - Beyond the issues of "Text Matters" from Tier 3 and "Memory" from Tier 4, the biggest issue here is prompting a player to spell a word without showing them how to spell the word. Would need a Text-to-speech implementation. +1. (Uk) Hero's Uncle - Tutors legendary team up cards. List is short enough to hard code but having to maintain it would not be ideal. +1. (Uk) Minnea, Planar Tourist - Choose a plane, affects cards from chosen plane. Tagger has planes for artwork and character origins. That might cover enough? +1. (Uk) Command From The Shadows - Conspiracy that keeps commanders hidden until first played. Problem is we show commanders in the lobby before the game even starts. Could be its own separate game mode. +1. (Uk) The Mox Painter - Tournament legal moxen. Reminder text gives a fixed list, but not future proof. Also depends on a card in this tier (See: "Mox Lotus"). +1. (Uk) Advanced Tactics - Puts all creatures onto a 5x5 grid and lets their controllers move them around. Unclear what happens when there's more than 25 creatures in play. Biggest hurdle would be the UI overhaul needed to actually display the grid. + +#### Noninteger Numbers +1. Bosom Buddy +1. Little Girl +1. Flaccify +1. Cheap Ass +1. Smart Ass +1. Bad Ass +1. Dumb Ass +1. Fat Ass +1. Assquatch +1. Necro-Impotence +1. Wet Willie of the Damned +1. Mons's Goblin Waiters +1. Saute +1. Fraction Jackson +1. Supersize +1. Letter Bomb +1. Mox Lotus +1. City of Ass +1. Infinity Elemental +1. Just Desserts +1. Octo Opus +1. Omniclown Colossus // Pie-roclasm +1. Urza's Fun House + +#### NonWUBRG Colors +(These could be done colorless like Sword of DnD) +1. Water Gun Balloon Game - Pink token (Also "Pop!" counters) +1. Gift Shop - Pink token, (Also Attraction and Stickers) +1. Push Your Luck - Pink token + +#### Number or Text Adjustment +(There's no direct link between numbers within the text and numbers used in the script.) +1. Look at Me, I'm R&D +1. Magical Hacker +1. More or Less +1. Very Cryptic Command F +1. Truss, Chief Engineer +1. (Uk) You, Magic Playtester +1. (Uk) You, Iterative Playtester + +#### Count Lines of Text +(Scryfall doesn't seem to carry this info.) +1. Lexivore +1. Frazzled Editor - Wordy (4+ lines of rules text) +1. Garbage Elemental A - Wordy +1. Alexander Clamilton - Wordy +1. Hardy of Myra's Marvels - Flavor Text +1. Meet and Greet "Sisay" - Flavor Text (Also Expansion Symbol Matters) +1. (Uk) Clamilton Estate - Wordy + +#### Art Rampage +1. Our Market Research Shows That Players Like Really Long Card Names So We Made this Card to Have the Absolute Longest Card Name Ever Elemental +1. Garbage Elemental E + +#### Art Elements +(If Scryfall included tagger tags in their API, these could go up to tier 3) +1. Knight of the Kitchen Sink C +1. Sly Spy B +1. Sly Spy D +1. Selfie Preservation +1. Acornelia, Fashionable Filcher +1. Don't Try This at Home +1. Ignacio of Myra's Marvels +1. Nightmare Moon - (Also, Friendship) +1. Pinkie Pie + +#### Art Elements, (unsupported by tagger) +1. Bar Entry - Head above the bar in Bar Entry's art. +1. Jermane, Pride of the Circus - Four or more legs. +1. Assembled Ensemble - "artifact creature in its art"; could possibly cross together a bunch of other tags for artificial creature types. +1. (Uk) Unstickerify - The card that a playtest card is stickered onto. Might also consider Unfinity stickers? + +#### Hat Wearing +(Tagger's "Hat" tag doesn't exactly fit this. Per ruling: "The creature that the card represents must be wearing a hat to have menace. A background character or a scale bird wearing a hat wouldn’t count." Could ignore that caveat and have it fit for the majority of cases though.) +1. Goblin Haberdasher +1. Hat Trick +1. Park Re-Entry +1. T.A.P.P.E.R. +1. Haberthrasher +1. Rat in the Hat +1. Goblin Girder Gang + +#### Arbitrary Words +(Cards where to apply the rules, we'd need to maintain a list of all words in the player's language, because the AI doesn't know to complain if a player chooses "qwxbzrkl") +1. Goblin Cruciverbalist +1. Hangman - Guessing letters of a word is the easy part. Getting the AI to play along is harder. + +#### Time Limits +(Interacting with the Forge UI under time pressure will not be a fun experience for anyone. The AI being able to do it instantly just throws salt in the wound.) +1. Hot Fix +1. Modular Monstrosity +1. Scavenger Hunt - (Attraction) +1. Trivia Contest - (Attraction) +1. (Uk) Blue Screen of Death + +#### Unknown Event Scoring +(At the Philadelphia 2023 Unknown Event, players were split into two teams. These cards, when played, scored points for your team. The whole event would have to be recreated for these to be relevant.) (They did more of this in 2025, too.) +1. (Uk) Good Knight +1. (Uk) Point to the Scoreboard +1. (Uk) Bad Knight +1. (Uk) Join the Winning Team +1. (Uk) Snapsail Rider - Affects point value for the surrounding match +1. (Uk) Take the High Ground +1. (Uk) Underdog Racer - Could be adapted per reminder text, but kinda loses the point of the card. + +#### Unknown Event Meta-game Planes +(At the Minneapolis 2023 Unknown Event, Planechase-like planes were in effect across all games in the event. Some of these could be implemented as normal Planechase cards, but the ones here would not translate well.) (These currently aren't on Scryfall) +1. (Uk) The Library - Starting hand size. +1. (Uk) Yargle's Barge - Once per game activation. +1. (Uk) The Underworld - Once per game may-cast. +1. (Uk) Wizards of the Coast - Once per game may-cast. (Also Playtest Card matters, Tier 2) + +#### Universes Beyond Setting +(Think tagger tags could support this) +1. (HTR) Byode, Inverse Sun +1. (Uk) Multiversal High Council - (Plane) Number of unique universes + + +### Tier 6 +**Could at best do a rough imitation, partial implementation, or would require its own minigame to work digitally** + +1. Sorry +1. Jalum Grifter +1. Frankie Peanuts +1. Cramped Bunker +1. Boomstacker +1. Devil K. Nevil +1. Tchotchke Elemental +1. Cover the Spot - (Attraction) +1. Squirrel Stack - (Attraction) +1. Chaos Wrap +1. Nerf War +1. Goblin Mime - Triggered by any dialog. Could at least watch for other dialog-costs being paid. +1. Phone a Friend - Could just be randomly chosen and/or let the opponent choose? +1. Yule Ooze - Could change the cost from eating food to sacrificing a Food to keep the spirit of the card, but that is a rules change. +1. Fluttershy - Stare down could be implemented as a designation. But even so, "creature with a tail" would make this at best a Tier 5. +1. (HTR) The Secret Lair - Can't implement accurately without knowing the secret word. +1. Autograph Book - Collect signatures from opponents? Adventure enemies? (Also Legacy, Tier 3.) +1. (Uk) The Clever Magician - Proposing a rule. Could cover a lot of ground functionally by just opening a text editor and letting them write their rule as a card script, to apply through an effect. UX for that implementation would be a lost cause though, and the AI would never be able to evaluate it. +1. (Uk) At Least It's a Dry Heat - Even without the geolocation needed to determine "sources on the east coast", tracking the causality of damage events through numbers would be Tier 4 at best. +1. (Uk) Drive to Work - Sharing fun facts about cards. +1. (Uk) Sorin's Remastered Manor - (Plane) Would need some combination of tags and types to form a criteria for "could be a horror movie monster". + +#### Dexterity; tossing cards at other cards +1. Landfill +1. Chaos Confetti +1. Slaying Mantis +1. Knife and Death +1. Dart Throw - (Attraction) +1. Goblin Sleigh Ride +1. Ol' Buzzbark - Dice, not cards, but similar in principle. +1. (See also: "Chaos Orb" and "Falling Star", Black Border) + + +#### Dexterity; spin a card +(Could simulate similar to Chaos Orb, would work better than a lot of others.) +1. Pointy Finger of Doom +1. (Uk) Windmill Farm - (Plane) + + +#### Yes or No Questions about cards +1. Head to Head +1. (Uk) Guess Who it Is! + +#### Sneak onto Battlefield +1. Cheatyface +1. Entirely Normal Armchair +1. Surprise Party + +#### Placement of Stickers within Art +1. Astroquarium +1. Juggletron + +#### Toys +(Could provide a pre-defined pool of these) +1. Applejack +1. Rarity +1. Grimlock, Dinobot Leader // Grimlock, Ferocious King - Also looks for Transformers creatures. + + +### Tier 7 +**Impossible in a rules engine, no good way to approximate it.** + +1. Blufferfish +1. D00-DL, Caricaturist +1. Photo Op - Due to restrictions on Twitter's API, this sadly can't go in Tier 5. +1. (Uk) Sharp Eraser + +#### Surrounding Room +1. Sex Appeal +1. Now You See Me... +1. Super-Duper Lost +1. Questionable Cuisine +1. Amped Up +1. Mistakes Were Made + +#### Clothing Matters +1. Prismatic Wardrobe +1. Hurloon Wrangler +1. Ladies' Knight +1. "Brims" Barone, Midway Mobster +1. Souvenir T-Shirt +1. The Big Top +1. (Uk) Pin Collector's Booth - (Plane) +1. (Uk) Pin Trading - (Phenomenon) + +#### Dialog +(Stuff that involves table chat, or just doesn't work in spirit if converted to a zero cost) +1. I'm Rubber, You're Glue +1. Censorship +1. Clam Session +1. Ow +1. Bronze Calendar +1. Atinlay Igpay +1. Loose Lips +1. Question Elemental? +1. Goblin S.W.A.T. Team - (Also time limits) +1. Red-Hot Hottie +1. Keeper of the Sacred Word +1. Land Aid '04 +1. Stone Cold Basilisk +1. Toy Boat +1. Rings a Bell +1. Bog Humbugs +1. Seasonal Sequels +1. (Uk) High Noon At Thunder Junction - (Plane) + +#### Gotcha Cycle +1. Cardpecker +1. Save Life +1. Number Crunch +1. Spell Counter +1. Kill Destroy +1. Stop That +1. Deal Damage +1. Touch and Go +1. Creature Guy +1. Laughing Hyena +1. Name Dropping + +#### Physical Actions +1. Standing Army +1. Charm School +1. Bureaucracy +1. Volrath's Motion Sensor +1. Clay Pigeon +1. Phyrexian Librarian +1. Vile Bile +1. Mouth to Mouth +1. Eye to Eye +1. Side to Side +1. Hazmat Suit (Used) +1. Hoisted Hireling +1. Skull Saucer +1. Form of the Approach of the Second Sun +1. Get Your Head in the Game +1. Plate Spinning +1. (Uk) Shrinking Plane - (Plane) + +#### Food and Drink +1. Ashnod's Coupon +1. Thopter Pie Network +1. Eggnogger's 'Stache + +#### Player Characteristics +1. Man of Measure +1. Avatar of Me - (Also NonWUBRG colors) +1. Moniker Mage +1. Granny's Payback +1. Blurry Beeble +1. (Uk) New Player's Journey - (Plane) (Also core set matters) + +#### Physical Hands Matter +1. Deadhead +1. Handcuffs +1. Farewell to Arms +1. Working Stiff +1. Gluetius Maximus +1. Sly Spy C +1. Handy Dandy Clone Machine +1. A Real Handful + +#### Signatories Matter +1. (Uk) The Collector +1. (Uk) Mark Ritual + +#### Prior Event Attendance +1. (Uk) Windy City Elemental +1. (Uk) Windy City Aven + +#### People Outside the Game +(Solutions involving ChatGPT are probably non-starters...) +1. Gimme Five +1. Sacrifice Play +1. Defective Detective +1. Subcontract +1. It That Gets Left Hanging +1. Squirrel Dealer +1. Flavor Judge +1. Gobsmacked +1. Bag Check +1. Focused Funambulist +1. Art Appreciation +1. Carnival Barker +1. Rock Star +1. Ticking Mime Bomb +1. Pietra, Crafter of Clowns +1. Blue Ribbon +1. Guess Your Fate - (Attraction) +1. The Superlatorium - (Attraction) +1. (Uk) The Knight of Commentary +1. (Uk) Fifth Stage of Magic Design + +#### Commemorative Celebration Cards +(The rules engine is not licensed to officiate marriages.) +1. Proposal +1. Splendid Genesis +1. Fraternal Exaltation +1. Pheonix Heart \ No newline at end of file diff --git a/docs/User-Guide.md b/docs/User-Guide.md new file mode 100644 index 00000000000..958d2ebb983 --- /dev/null +++ b/docs/User-Guide.md @@ -0,0 +1,119 @@ +# Downloads +* **Snapshots**; + * READ THESE NOTES BEFORE DOWNLOADING SNAPSHOTS: + * **Please use snapshots for Adventure Mode!** + * May contain more bugs, bug fixes, **definitely gets newest cards faster** and newer features. + * These are **NOW** automatically released daily. + * If the snapshot isn't in the location below, it's because its in the middle of uploading a new snapshot. Come back later to grab it. + * [_**CLICK HERE FOR DOWNLOAD LINKS - Forge SNAPSHOT Version (DESKTOP/ANDROID)**_](https://github.com/Card-Forge/forge/releases/tag/daily-snapshots) + * For desktop, grab the installer file that ends in .jar + * For android, grab the android file that ends in .apk +* **Android Installation Guide** + * Quick Guide for installing Android Snapshots:
+ + +https://github.com/user-attachments/assets/7a0c7bb8-7cf9-4800-8091-bcc30ff2f4d8 + + + +* **Releases**; + * READ THESE NOTES BEFORE DOWNLOADING RELEASES: + - "Releases" are really intended where "99% cards implemented are working and stable." + - If you are looking for newly spoiled cards as soon as possible, grab the snapshot instead. + - The current release mechanism is failing unexpectedly for Android. So just stick with snapshots for Android users. + * [_**CLICK HERE FOR DOWNLOAD LINKS - RELEASE DESKTOP**_](https://github.com/Card-Forge/forge/releases/latest) + - Grab the installer file that ends in .jar + + +# Java Requirement + +**Forge Requires Java** to run, please make sure you have Java installed on your machine prior to attempting to run. + +* **Java 17** is required as minimum version and can be acquired through the Standard Edition Development Kit (JDK) or the OpenJDK. Continued development provides new features in those editions, therefore you need the Java Development Kit to have those newer editions; + - Download - [https://jdk.java.net/](https://jdk.java.net/) + - Source Code - [https://github.com/openjdk/jdk/](https://github.com/openjdk/jdk/) + +Most people who have problems setting up Forge, do not have Java setup properly. If you are having trouble, open your terminal/command line and run `java --version`. That number should be 17 or higher. + +# Install and Run + +Forge requires Java to run. + +_**Download and unpack /install the package to their own new folder!**_ + +### Install Wizard (jar) +* Run/Double click "**forge-installer**-VERSION.jar" where VERSION is the current release version and click next until the Target Path window appears. If double clicking the .jar file doesn't load the main interface you can run it via terminal/command line ```java -jar FILENAME.jar``` where FILENAME is the name of the installer. + +* Browse to your preferred install directory (create a new directory for clean installation) and click next until installation starts. + +![image](https://github.com/Card-Forge/forge/assets/9781539/b7575f49-f6b3-4933-a15f-726314547c4f) + +* After the installation finishes, close the installer. Run the executable forge|forge-adventure (.exe/.sh/.cmd) + +### Manual Extraction (tar.bz2) + +* **Desktop Windows**: + * Unpack "forge...tar.**bz2**" with any unpacking/unzipping app (e.g. 7-zip, winrar, etc) + * You'll end up with "forge...**tar**". + * Unpack that ".tar" file once more into its own folder. + * Run Forge app/exe +* **Desktop Linux/Mac**: + * Unpack "forge...**tar.bz2**" with any unpacking app. (Check your package repository, or app store.) + * You'll probably end up with just a folder, and fully extracted. + * If you do end up with a ".tar" file, unpack that file also into it's own folder. + * Run Forge script; + * Linux: Run the ".sh" file in a terminal (double clicking might work.) + * MacOS/OSX: Run the ".command" file by double clicking in Finder, or run from the terminal. + * If the command file doesn't appear to do anything, you'll need to [modify the permissions to be executable.](https://support.apple.com/guide/terminal/make-a-file-executable-apdd100908f-06b3-4e63-8a87-32e71241bab4/mac) (This is a temporary bug in the build process.) + * Additionally OSX needs to have a JRE AND a JDK installed because reasons. +* **Android**: + * Sideload/Install "forge...apk" + * Run Forge + +## Play Adventure Mode on Desktop + +* Run the Adventure Mode EXE or Script in the Folder you extracted. +* The game will start with an option for Adventure or Classic Mobile UI. +* Android/Mobile builds are built as the Adventure Mode or Mobile UI and nothing special is needed. + - If adventure mode option does not show up; + - check you're up to date with your version. + - check in the settings that the "Selector Mode" is set to `Default` + +# System Requirements and Historic Details + +Since Forge is written in Java, it is compatible on any Operating System +that can run the Java Runtime Environment. Forge requires +Java 17 (Forge is not backwards compatible with older versions of Java). +If you have difficulties with your System not working with Forge, +please come to the Discord so we can attempt to help. +This program works best with a screen resolution of **1280 by 720** or +better. Forge can now have it's window minimized to **800 by 600** +pixels but this may make the display area cramped and possibly limit +your ability to play. (This means Forge may not compatible with some +netbook computers.) + +The memory requirements for Forge have fluctuated over time. The default +setting on your computer for the java heap space may not be enough to +prevent the above problems. If you launch Forge by double-clicking the +file **run-forge.jar** you will eventually receive a **java heap space +error**. The name of the forge jar file has changed as part of our new +Maven based build and release system. The name format now used is: + +**forge-**{version number}**-jar-with-dependencies.jar** + +We have created several scripts that will launch Forge with a greater +allotment of system resources. (We do this by passing **-Xmx1024m** as +an argument to the Java VM.) People using Windows OS should double click +the **forge.exe** file. People using Apple's Mac OS X should use the Mac +OS version and double click the **forge.command** file. People using one of the +other \*nix OS should double click the **forge.sh** file. + + +# What if double-clicking doesn’t work? + +Sometimes double-clicking will open the jar file in a different program. +In Windows, you may need to right-click and open the properties to +change the launching program to Java. This might be different in OSX or +Linux systems(file permission related). + +Check the [troubleshooting page.](Troubleshooting-FAQ) diff --git a/docs/_sidebar.md b/docs/_sidebar.md new file mode 100644 index 00000000000..1cdeef09071 --- /dev/null +++ b/docs/_sidebar.md @@ -0,0 +1,61 @@ +### [Forge Wiki Home](Home.md) +- [User Guide](User-Guide.md) + - [FAQ](Frequently-Asked-Questions.md) + - [SteamDeck/Bazzite](Steam-Deck-and-Bazzite-Install.md) + - [AI](ai.md) + - [Network Play](network-play.md) + - [Network FAQ](Network-FAQ.md) + - [Network Extra](Networking-Extras.md) + +- [Adventure Mode](Adventure-Mode.md) + + - Gameplay Guide + + - [Getting Started](Gameplay-Guide.md) + - [Different Planes](Different-Planes.md) + - [Towns & Capitals](Towns-&-Capitals.md) + - [Dungeons](Dungeons.md) + - [Equipments and Items](Equipments-and-Items.md) + - [Keyboard Shortcuts](Keyboard-Shortcuts.md) + + - [Modding and Development](Modding-and-Development.md) + + - [Create Enemies](Create-Enemies.md) + - [Create Rewards](Create-Rewards.md) + - [Create Maps](Create-new-Maps.md) + - [Configure Planes](Configure-Planes.md) + - [Configure Starting Sets](Configure-Sets.md) + + - Tutorials + - [Tutorial 1, Create your first Plane](Tutorial-1-Create-your-First-Plane.md) + - [Tutorial 2, A New Look (creating your first map.)](Tutorial-2-A-New-Look.md) + - [Tutorial 3, Configuration (Configuring your Plane)](Tutorial-3-Configuration.md) + + +- [Card Scripting API](Card-scripting-API/Card-scripting-API.md) + - [Ability effects](Card-scripting-API/AbilityFactory.md) + - [Triggers](Card-scripting-API/Triggers.md) + - [Replacements](Card-scripting-API/Replacements.md) + - [Costs](Card-scripting-API/Costs.md) + - [Affected / Targets](Card-scripting-API/Targeting.md) + - [Restrictions](Card-scripting-API/Restrictions.md) + - [Guide: Creating a Custom Card](Card-scripting-API/Creating-a-Custom-Card.md) + +- [Development]((SM-autoconverted)-how-to-get-started-developing-forge.md) + - [IntelliJ Setup](Development/IntelliJ-setup/IntelliJ-setup.md) + - [Snapshots and Releases](Snapshots-and-Releases.md) + - [Android Builds](Development/android-builds.md) + - [Ownership](Development/ownership.md) + - [Docker Container](docker-setup.md) + +- [Customization and Themes.md](Themes.md) + - Skins + - Sounds + - [Music](Custom-Music.md) + - [Card Images](Card-Images.md) + - [File Formats](File-Formats.md) + - [Creating your first custom set](Creating-a-custom-set.md) + +- [Missing Cards in Forge](Missing-Cards-in-Forge.md) + - [Un‐cards, Playtest Cards, and Other Funny Cards](Un‐cards,-Playtest-Cards,-and-Other-Funny-Cards.md) +- [Credit and Thanks](Credit-and-Thanks.md) \ No newline at end of file From 53683f2496319d8151dcfcf1f8cb3dcf0b06224c Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 2 Nov 2025 17:55:14 +0000 Subject: [PATCH 222/230] Change wiki sync branch from 'wiki' to 'master' --- .github/workflows/sync-wiki.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sync-wiki.yml b/.github/workflows/sync-wiki.yml index b171c2f2d7f..984f5cacda6 100644 --- a/.github/workflows/sync-wiki.yml +++ b/.github/workflows/sync-wiki.yml @@ -1,7 +1,7 @@ name: Publish wiki on: push: - branches: [wiki] + branches: [master] paths: - docs/** - .github/workflows/sync-wiki.yml @@ -12,7 +12,7 @@ permissions: contents: write jobs: publish-wiki: - if: github.repository_owner == 'tool4ever' + if: github.repository_owner == 'Card-Forge' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From be77cc1147c638ffb5a1e5dae51a3a74fc625cb1 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sun, 2 Nov 2025 18:01:03 +0000 Subject: [PATCH 223/230] Add workflow_dispatch trigger to sync-wiki.yml --- .github/workflows/sync-wiki.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sync-wiki.yml b/.github/workflows/sync-wiki.yml index 984f5cacda6..0e06a043862 100644 --- a/.github/workflows/sync-wiki.yml +++ b/.github/workflows/sync-wiki.yml @@ -5,6 +5,7 @@ on: paths: - docs/** - .github/workflows/sync-wiki.yml + workflow_dispatch: concurrency: group: publish-wiki cancel-in-progress: true From 65e4fa7fd7ea8b1c4fc418103d569d17fdf5e5d4 Mon Sep 17 00:00:00 2001 From: Eradev Date: Mon, 3 Nov 2025 00:56:02 -0500 Subject: [PATCH 224/230] Minor card fixes (#9069) * Update DDR * Update BLB * Update Realmbreaker, the Invasion Tree (Warning because SVar starts with X) * Update LTR --- .../r/realmbreaker_the_invasion_tree.txt | 4 ++-- forge-gui/res/editions/Bloomburrow.txt | 2 +- .../editions/Duel Decks Nissa vs. Ob Nixilis.txt | 15 +++++++++------ ...he Lord of the Rings Tales of Middle-earth.txt | 3 +++ 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/forge-gui/res/cardsfolder/r/realmbreaker_the_invasion_tree.txt b/forge-gui/res/cardsfolder/r/realmbreaker_the_invasion_tree.txt index 34e81de0877..74d6b48eba1 100644 --- a/forge-gui/res/cardsfolder/r/realmbreaker_the_invasion_tree.txt +++ b/forge-gui/res/cardsfolder/r/realmbreaker_the_invasion_tree.txt @@ -6,9 +6,9 @@ SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield SVar:DBAnimate:DB$ Animate | Replacements$ ReplaceLeaves | Defined$ Remembered | Duration$ Permanent | SubAbility$ DBCleanup | StackDescription$ None SVar:ReplaceLeaves:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | ValidCard$ Card.Self | ReplaceWith$ Exile | Description$ If this land would leave the battlefield, exile it instead of putting it anywhere else. SVar:Exile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | Defined$ ReplacedCard -A:AB$ ChangeZone | Cost$ 10 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Praetor | ChangeNum$ XFetch | StackDescription$ {p:You} searches their library for any number of Praetor cards, puts them onto the battlefield, then shuffles. | SpellDescription$ Search your library for any number of Praetor cards, put them onto the battlefield, then shuffle. +A:AB$ ChangeZone | Cost$ 10 T Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Praetor | ChangeNum$ FetchCount | StackDescription$ {p:You} searches their library for any number of Praetor cards, puts them onto the battlefield, then shuffles. | SpellDescription$ Search your library for any number of Praetor cards, put them onto the battlefield, then shuffle. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:XFetch:Count$ValidLibrary Praetor.YouCtrl +SVar:FetchCount:Count$ValidLibrary Praetor.YouCtrl DeckHas:Ability$Mill|Sacrifice DeckHints:Type$Praetor Oracle:{2}, {T}: Target opponent mills three cards. Put a land card from their graveyard onto the battlefield tapped under your control. It gains "If this land would leave the battlefield, exile it instead of putting it anywhere else."\n{10}, {T}, Sacrifice Realmbreaker, the Invasion Tree: Search your library for any number of Praetor cards, put them onto the battlefield, then shuffle. diff --git a/forge-gui/res/editions/Bloomburrow.txt b/forge-gui/res/editions/Bloomburrow.txt index c3cd89a7846..5efe4ae7dac 100644 --- a/forge-gui/res/editions/Bloomburrow.txt +++ b/forge-gui/res/editions/Bloomburrow.txt @@ -509,6 +509,6 @@ A-138 U A-Heartfire Hero @Jakub Kasper 19 offspring_steampath_charger @Alex Stone 20 offspring_bushy_bodyguard @Jakub Kasper 21 offspring_pawpatch_recruit @Johan Grenier -22 offspring_rust_shield_rampager @Steve Ellis +22 offspring_rust-shield_rampager @Steve Ellis 24 offspring_tender_wildguide @Jakob Eirich 30 emblem_ral_crackling_wit @Rudy Siswanto diff --git a/forge-gui/res/editions/Duel Decks Nissa vs. Ob Nixilis.txt b/forge-gui/res/editions/Duel Decks Nissa vs. Ob Nixilis.txt index c50b7fc29a5..88402982336 100644 --- a/forge-gui/res/editions/Duel Decks Nissa vs. Ob Nixilis.txt +++ b/forge-gui/res/editions/Duel Decks Nissa vs. Ob Nixilis.txt @@ -2,9 +2,9 @@ Code=DDR Date=2016-09-02 Name=Duel Decks: Nissa vs. Ob Nixilis -Code2=DDR Type=Duel_Deck ScryfallCode=DDR +TokensCode=DDR [cards] 1 M Nissa, Voice of Zendikar @Raymond Swanland @@ -79,8 +79,11 @@ ScryfallCode=DDR 70 L Swamp @Tianhua X [tokens] -c_1_1_eldrazi_scion_sac -b_5_5_demon_flying -b_5_5_zombie_giant -g_4_4_elemental -g_0_1_plant +71 c_1_1_eldrazi_scion_sac @Izzy +72 b_5_5_demon_flying @Kev Walker +73 b_5_5_zombie_giant @Igor Kieryluk +74 g_4_4_elemental @Brandon Kitkouski +75 g_0_1_plant @Daren Bader + +[other] +76 emblem_ob_nixilis_reignited @Raymond Swanland diff --git a/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth.txt b/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth.txt index de819fa9e99..56f94a7a92d 100644 --- a/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth.txt +++ b/forge-gui/res/editions/The Lord of the Rings Tales of Middle-earth.txt @@ -878,3 +878,6 @@ A-246 M A-The One Ring @Veli Nyström 10 c_a_food_sac @Claudiu-Antoniu Magherusan 11 c_a_food_sac @L J Koh 12 c_a_treasure_sac @Valera Lutfullina + +[other] +13 the_ring @Viko Menezes \ No newline at end of file From ee045d854db4d7f817bceb6929cf8faec57e817c Mon Sep 17 00:00:00 2001 From: tool4ever Date: Mon, 3 Nov 2025 08:28:06 +0100 Subject: [PATCH 225/230] Wiki cleanup: remove dead files (#9065) --- docs/AI.md | 4 +- docs/Adventure-Mode-Shops.md | 1 - ...re-Mode-Starting-your-first-playthrough.md | 1 - docs/Adventure-Quests.md | 2 - docs/Biome-Data.md | 2 - docs/Configure-starting-sets.md | 14 ----- docs/Create-Enemies.md | 5 +- docs/{ => Development}/Android-Debugging.md | 0 docs/File-Formats.md | 2 - docs/Forge_DevMode.textile | 5 -- docs/Future-Ways-to-Play.md | 54 ------------------- ...tting-Started---Creating-your-character.md | 7 --- docs/Mana-Shards.md | 2 +- docs/Sprite-Atlas.md | 2 - docs/User-Guide.md | 5 +- .../java/forge/ai/ability/CountersMoveAi.java | 2 +- .../ability/effects/BecomesBlockedEffect.java | 3 +- .../game/trigger/TriggerAttackerBlocked.java | 3 +- .../main/java/forge/view/SimulateMatch.java | 1 - forge-gui/release-files/GAMEPAD_README.txt | 2 +- 20 files changed, 11 insertions(+), 106 deletions(-) delete mode 100644 docs/Adventure-Mode-Shops.md delete mode 100644 docs/Adventure-Mode-Starting-your-first-playthrough.md delete mode 100644 docs/Adventure-Quests.md delete mode 100644 docs/Biome-Data.md delete mode 100644 docs/Configure-starting-sets.md rename docs/{ => Development}/Android-Debugging.md (100%) delete mode 100644 docs/Forge_DevMode.textile delete mode 100644 docs/Future-Ways-to-Play.md delete mode 100644 docs/Getting-Started---Creating-your-character.md delete mode 100644 docs/Sprite-Atlas.md diff --git a/docs/AI.md b/docs/AI.md index 5da8a4225e0..121a78ec28d 100644 --- a/docs/AI.md +++ b/docs/AI.md @@ -2,7 +2,7 @@ The AI is *not* "trained". It uses basic rules and can be easy to overcome knowing it's weaknesses. -The AI is; +The AI is: * Best with Aggro and midrange decks * Poor to Ok in control decks * Pretty bad for most combo decks @@ -42,7 +42,7 @@ In linux and mac, command line arguments are not currently passed through the sh - `-p [P]` - [P] number of players paired, only used in tournament mode. Default is 2. - `-q` - Quiet Mode, only prints the result not the entire log. -## Examples: +## Examples In linux and macos you must run forge by evoking java and calling the jar, currently command line parameters are not passed through the script. The forge jar filename is truncated in these examples from `forge-whatever-version-youre-on.jar` to `forge.jar`. In Windows, if you use the EXE file as described below, the simulation runs in the background and output is sent to the forge log file only. If you want to have output to the console, please use the `java -jar` evocation of forge. diff --git a/docs/Adventure-Mode-Shops.md b/docs/Adventure-Mode-Shops.md deleted file mode 100644 index 48cdce85287..00000000000 --- a/docs/Adventure-Mode-Shops.md +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/docs/Adventure-Mode-Starting-your-first-playthrough.md b/docs/Adventure-Mode-Starting-your-first-playthrough.md deleted file mode 100644 index bfbd3ec68c7..00000000000 --- a/docs/Adventure-Mode-Starting-your-first-playthrough.md +++ /dev/null @@ -1 +0,0 @@ -This is where I'm going to outline "starting a game" and "how to play" in adventure mode. \ No newline at end of file diff --git a/docs/Adventure-Quests.md b/docs/Adventure-Quests.md deleted file mode 100644 index e9fba530b86..00000000000 --- a/docs/Adventure-Quests.md +++ /dev/null @@ -1,2 +0,0 @@ -# Adventure Quests -(Placeholder, actual content coming soon) \ No newline at end of file diff --git a/docs/Biome-Data.md b/docs/Biome-Data.md deleted file mode 100644 index 5e7d840d294..00000000000 --- a/docs/Biome-Data.md +++ /dev/null @@ -1,2 +0,0 @@ -# Adventure Biome Data -(Placeholder, actual content coming soon) \ No newline at end of file diff --git a/docs/Configure-starting-sets.md b/docs/Configure-starting-sets.md deleted file mode 100644 index 24f324811fa..00000000000 --- a/docs/Configure-starting-sets.md +++ /dev/null @@ -1,14 +0,0 @@ - - -### Funny -PCEL, PAST, UGL, UNH, HHO, DS0, PHTR, UST, PUST, PH17, PH18, CMB1, UND, PH19, PH20, PH21, UNF, DA1, PH22, MB2 - -### Commander -COM, CM1, C13, C14, C15, C16, C17, CMA, CM2, C18, C19, C20, ZNC, KHC, C21, AFC, MIC, VOC, NEC, NCC, DMC, 40K, BRC, SCD, ONC, MOC, LTC, CMM, WOC, WHO, LCC, MKC, PIP, OTC, M3C, BLC, DSC, PDC -### Starters -CLU, LTR, CLB, CMD, MB1 -### Digital -PRM, MBP, MED, ME2, ME3, TD0, TD1, ME4, VMA, TPR, PZ1, PZ2, ANA, PANA, HA1, HA2, HA3, AJMP, AKR, ANB, KLR, HA4, HA5, J21, YMID, YNEO, YSNC, HBG, EA1, HA6, YDMU, YBRO, EA2, YONE, SIS, SIR, EA3, HA7, YWOE, YMKM, YOTJ, YBLB, YDSK - -### Promos -DRC94, PHPR, PLGM, PMEI, PARL, PRED, PTMP, JGP, PSTH, PEXO, PALP, PUSG, PAL99, G99, PULG, PUDS, PPTK, PGRU, PWOR, PWOS, PMMQ, PSUS, PAL00, FNM, G00, PELP, PNEM, PPCY, PINV, PAL01, F01, G01, MPR, PPLS, PAPC, PSDG, PODY, PAL02, F02, G02, PTOR, PJUD, PHJ, PAL03, F03, PJJT, G03, P03, OVNT, PLGN, PONS, PSCG, P8ED, PAL04, F04, G04, P04, PDST, P5DN, PMRD, PCHK, PAL05, F05, G05, PJSE, P05, PMPS, PBOK, PSOK, P9ED, PRAV, P2HG, PAL06, DCI, F06, G06, PJAS, P06, PMPS06, PGPT, PCMP, PDIS, PCSP, PTSP, F07, G07, PMPS07, PRES, PPLC, PPRO, PGPX, PFUT, P10E, PLRW, F08, G08, P08, PMPS08, PMOR, P15A, PSHM, PEVE, PALA, PDTP, F09, G09, P09, PMPS09, PBOOK, PCON, PURL, PARB, PM10, PZEN, PDP10, F10, G10, P10, PMPS10, PWWK, PROE, PM11, PSOM, PDP12, F11, G11, OLGC, P11, PMPS11, PW11, PMBS, PNPH, PM12, PISD, PDP13, F12, PIDW, J12, PW12, PDKA, PAVR, PHEL, PM13, PRTR, PDP14, F13, J13, PGTC, WMC, PDGM, PM14, PSDC, PTHS, PDP15, F14, J14, PBNG, PJOU, PCNS, PS14, PPC1, PM15, PKTK, F15, J15, UGF, PFRF, PDTK, PTKDF, PS15, PORI, PSS1, PBFZ, F16, J16, POGW, PSOI, PEMN, PKLD, PS16, F17, J17, PAER, PAKH, PHOU, PS17, PXLN, PSS2, PXTC, J18, PRIX, PNAT, PDOM, PM19, PSS3, PS18, PGRN, PRWK, G18, PF19, PRNA, PRW2, J19, PWAR, PMH1, PM20, PPP1, PS19, PWCS, J20, PF20, PLG20, PL21, PW21, PLG21, Q06, P22, PL22, PW22, GDY, PLG22, SCH, PSVC, P30M, P30A, P30H, PRCQ, BOT, PEWK, P23, PR23, PW23, PL23, SLP, PF23, P30T, PMDA, REX, PF24, PW24, PL24, PSS4, PCBB, PLG24, PLTC, PF25, PSPL, PJSC \ No newline at end of file diff --git a/docs/Create-Enemies.md b/docs/Create-Enemies.md index 93bc1633e95..db042eebc79 100644 --- a/docs/Create-Enemies.md +++ b/docs/Create-Enemies.md @@ -59,7 +59,6 @@ Every sprite under will be used as animation for the corresponding action. direction can be added to alter the animation depending on the direction like "IdleRight" Supported directions are "Right","Left","Up","Down","RightDown","LeftDown","LeftUp","RightUp" -See [sprite atlas reference](Sprite-Atlas) for more information ## **deck** Array of strings containing paths to the decks used for this enemy (from `res/`) @@ -80,7 +79,7 @@ Boolean - Not used to any great extent at this time, but a value of true in this Boolean - If true, this enemy ignores terrain collisions and can travel freely in their intended movement direction. ## **spawnRate** -Decimal - Relative frequency with which this enemy will be picked to spawn in appropriate biomes (which are set in the [biome json file](Biome-Data)). Existing values range from 0 to 1.0. +Decimal - Relative frequency with which this enemy will be picked to spawn in appropriate biomes (which are set in the biome json file). Existing values range from 0 to 1.0. ## **difficulty** Decimal - Relative estimated difficulty associated with this enemy. Currently unused, but will likely be factored in as a part of filtering enemies into early/late game appropriate opponents. Existing values range from 0 to 1.0. @@ -105,4 +104,4 @@ Array - A collection of strings representing [equipment items](adventure-items) String - Any combination of "B" (Black), "U" (Blue), "C" (Colorless), "G" (Green), "R" Red, and "W" (White). Used to display color identity alongside the sprite when an active ability allows it. ## **questTags** -Array- A collection of strings associated with this entity for filtering in regards to quests. See [Adventure Quests](Adventure-Quests) for more information on how these are used. \ No newline at end of file +Array- A collection of strings associated with this entity for filtering in regards to quests. \ No newline at end of file diff --git a/docs/Android-Debugging.md b/docs/Development/Android-Debugging.md similarity index 100% rename from docs/Android-Debugging.md rename to docs/Development/Android-Debugging.md diff --git a/docs/File-Formats.md b/docs/File-Formats.md index 1e09d24f398..4d4b917ad56 100644 --- a/docs/File-Formats.md +++ b/docs/File-Formats.md @@ -1,5 +1,3 @@ -This page is a work in progress... sorry, this is super complex, any input and help is definitely welcome! - # About There are many different file formats used in forge, due to the longevity of forge and it's growth and change, older formats are used and new ones are being created. This is an attempt to capture the requirements of these files for general creation, and modification. Unfortunately these "formats" may change and this list, or the data in them may become obsolete as changes happen, especially in newer features like Adventure Mode. diff --git a/docs/Forge_DevMode.textile b/docs/Forge_DevMode.textile deleted file mode 100644 index cc2093f1fae..00000000000 --- a/docs/Forge_DevMode.textile +++ /dev/null @@ -1,5 +0,0 @@ -Name:Vanilla Creature -ManaCost:2 G -Types:Creature Beast -Text:no text -PT:2/2 \ No newline at end of file diff --git a/docs/Future-Ways-to-Play.md b/docs/Future-Ways-to-Play.md deleted file mode 100644 index 5b746b16248..00000000000 --- a/docs/Future-Ways-to-Play.md +++ /dev/null @@ -1,54 +0,0 @@ -There are many ways to play Magic. This page exists to house rulesets that haven't been added to Forge yet. - -

Open House

-2-player, one mentor and one player who has never played before, sealed unsanctioned. Materials, two open house boxes, each containing a pair of monocolor 30-card decks - -1. Each player chooses a box, which contains two 30-card decks. Both decks are prebuilt 30-card decks, and one of the decks in the box is the color shown on the box. The other deck is a different color. -2. Players choose which deck to play with. They may combine both decks from their box if desired. -3. Players reveal their decks to one another before shuffling. -4. The match begins. -5. The mentor should do their best to make sure the new player is enjoying the game. - -

Booster Battle

-2-player, sealed limited unsanctioned. Materials: one Booster Battle blister containing two preconstructed monocolor 30-card decks, two booster packs - -1. Unwrap the decks, and decide which player gets which deck. -2. Each player opens a booster pack and chooses up to five cards to add to their deck. -3. Shuffle up and play! - -

Prerelease (typical)

-Sanctioned, limited, sealed tournament. Materials: 6 boosters and one promo. (Varies by release) Each match is 2-player. - -1. 30 minute timer starts. -2. Each player opens their boosters and receives their promo card. -3. Deck size is 40 minimum, the traditional five basic lands may be added to the pool. Sideboard size is unlimited. -4. After 30 minutes are up, timed matches begin. -5. Players are free to use a different deck each game if they choose as long as it comes from the same starting pool. - -

Duel Decks

-2-player, unsanctioned, sealed. Materials: Duel Decks box containing two 60-card precons. - -1. Unwrap the decks, and decide which player gets which deck. -2. Shuffle up and play! - -

Starter Set

-2-player, unsanctioned, sealed. Materials: Blister or box set containing two prestacked 30-card decks and learn-to-play walkthrough guides. - -1. Unwrap the decks, and decide which player gets which deck. -2. DO NOT SHUFFLE. -3. Play. - -

Unstable

-So you want to assemble a Contraption? - -No problem! Follow these $`\xcancel{\sf{four}}`$ $`\hat{H}|\psi(t)\rangle = i\hbar\frac {\normalsize\,\,\,\circlearrowright\!\!\!\!\!\circlearrowleft\!\!\!\!\!\!\!\!\!\!\small\curlyeqsucc\normalsize\!\!\!\!\!\!\cdotp\!\!\!\!\!\!\!\small\ldotp\normalsize\!\!\!\!\!\!\!\cdotp\!\!\!\!\!\!\!\small\curlyeqprec} {\normalsize\,\,\,\circlearrowright\!\!\!\!\!\circlearrowleft\!\!\!\!\!\!\!\!\!\!\small\curlyeqsucc\normalsize\!\!\!\!\!\!\cdotp\!\!\!\!\!\!\!\small\ldotp\normalsize\!\!\!\!\!\!\!\cdotp\!\!\!\!\!\!\!\small\curlyeqprec\!\!t} |\psi(t)\rangle`$ easy steps and you'll be slapping together devious devices in no time! - -1. Contraptions start each game in a separate deck: the Contraption deck. DON'T mix your Contraptions up with the rest of your cards! - -2. The Contraption deck has three sprockets. Start the game with a CRANK! counter on Sprocket 3. - -3. How do you assemble a Contraption? Reveal the top card of your Contraption deck, then choose one of your sprockets. Your Contraption enters the battlefield under that sprocket. - -4. At the beginning of your upkeep, if you control a Contraption, move the CRANK! counter to your next sprocket and crank any number of Contraptions on that sprocket, putting their triggered abilities onto the stack. Remember the sprocket order: $`\Bbb{1}\to\Bbb{2}\to\Bbb{3}\to\Bbb{1}`$ ... - -In Limited, you can play as many Contraptions as you have in your pool. How and when your Contraptions are assembled may yield wildly varying results from game to game. Experiment with different combinations and sequences and you're $`\xcancel{\sf{likely}}`$ CERTAIN to create something $`\xcancel{\sf{interesting}}`$ ⚡MONUMENTALLY POWERFUL!!!⚡ \ No newline at end of file diff --git a/docs/Getting-Started---Creating-your-character.md b/docs/Getting-Started---Creating-your-character.md deleted file mode 100644 index fc5466099be..00000000000 --- a/docs/Getting-Started---Creating-your-character.md +++ /dev/null @@ -1,7 +0,0 @@ -# Creating new world - -# Choosing difficulty - -# Choosing Mode and starting deck - -# New Game Plus \ No newline at end of file diff --git a/docs/Mana-Shards.md b/docs/Mana-Shards.md index e6359a117f5..af76d41d353 100644 --- a/docs/Mana-Shards.md +++ b/docs/Mana-Shards.md @@ -3,7 +3,7 @@ Mana Shards are a custom resource created for Adventure Mode in Forge. Shards se As Mana Shards are not self-replenishing, using them for activated abilities that affect the game world and in-match gameplay is a way of letting the player have a constant challenge of their own comfort level. If the player is comfortable taking on a fight without using extra abilities afforded to them by use of Shards, they will have more of them available to spend on in town, or can simply save their full might for boss-level entities. -The most consistent source of Mana Shards is from match wins, players receive one Shard per win without any rewards having to be explicitly added to the encounter. However, when used to power equipment and abilities, one Shard per match will definitely not break even, as most custom Adventure cards and items will cost 2-4 per use. However, Mana Shards can also be purchased in towns, picked up in dungeons, and can be received in large quantities from completing [Quests](Adventure-Quests). +The most consistent source of Mana Shards is from match wins, players receive one Shard per win without any rewards having to be explicitly added to the encounter. However, when used to power equipment and abilities, one Shard per match will definitely not break even, as most custom Adventure cards and items will cost 2-4 per use. However, Mana Shards can also be purchased in towns, picked up in dungeons, and can be received in large quantities from completing quests. Mana Shards can be used for: * Activated in-match abilities diff --git a/docs/Sprite-Atlas.md b/docs/Sprite-Atlas.md deleted file mode 100644 index b99388e4c31..00000000000 --- a/docs/Sprite-Atlas.md +++ /dev/null @@ -1,2 +0,0 @@ -# Adventure Sprite Atlases -(Placeholder, actual content coming soon) \ No newline at end of file diff --git a/docs/User-Guide.md b/docs/User-Guide.md index 958d2ebb983..93d332231ec 100644 --- a/docs/User-Guide.md +++ b/docs/User-Guide.md @@ -108,12 +108,9 @@ the **forge.exe** file. People using Apple's Mac OS X should use the Mac OS version and double click the **forge.command** file. People using one of the other \*nix OS should double click the **forge.sh** file. - # What if double-clicking doesn’t work? Sometimes double-clicking will open the jar file in a different program. In Windows, you may need to right-click and open the properties to change the launching program to Java. This might be different in OSX or -Linux systems(file permission related). - -Check the [troubleshooting page.](Troubleshooting-FAQ) +Linux systems (file permission related). diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java index d102ea2065d..d87c6a2a8c5 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersMoveAi.java @@ -424,7 +424,7 @@ public class CountersMoveAi extends SpellAbilityAi { // move counter to opponents creature but only if you can not steal them // try to move to something useless or something that would leave play - boolean isNegative = ComputerUtil.isNegativeCounter(cType, src); + boolean isNegative = cType != null && ComputerUtil.isNegativeCounter(cType, src); List filteredTgtList; filteredTgtList = isNegative ? CardLists.filterControlledBy(tgtCards, ai.getOpponents()) : CardLists.filterControlledBy(tgtCards, ai.getYourTeam()); 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 9752445ce8b..1579c767305 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 @@ -9,6 +9,7 @@ import forge.game.Game; import forge.game.ability.AbilityKey; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; +import forge.game.card.CardCollection; import forge.game.event.GameEventCombatChanged; import forge.game.spellability.SpellAbility; import forge.game.trigger.TriggerType; @@ -39,7 +40,7 @@ public class BecomesBlockedEffect extends SpellAbilityEffect { blocked.add(c); final Map runParams = AbilityKey.newMap(); runParams.put(AbilityKey.Attacker, c); - runParams.put(AbilityKey.Blockers, Lists.newArrayList()); + runParams.put(AbilityKey.Blockers, CardCollection.EMPTY); runParams.put(AbilityKey.Defender, game.getCombat().getDefenderByAttacker(c)); runParams.put(AbilityKey.DefendingPlayer, game.getCombat().getDefenderPlayerByAttacker(c)); game.getTriggerHandler().runTrigger(TriggerType.AttackerBlocked, runParams, false); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java b/forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java index 676283cfffd..4023b968cc8 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerAttackerBlocked.java @@ -23,7 +23,6 @@ import com.google.common.collect.Iterables; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.card.Card; -import forge.game.card.CardCollection; import forge.game.card.CardLists; import forge.game.spellability.SpellAbility; import forge.util.Expressions; @@ -65,7 +64,7 @@ public class TriggerAttackerBlocked extends Trigger { if (hasParam("ValidBlocker")) { String param = getParamOrDefault("ValidBlockerAmount", "GE1"); - int attackers = CardLists.getValidCardCount((CardCollection) runParams.get(AbilityKey.Blockers), getParam("ValidBlocker"), getHostCard().getController(), getHostCard(), this); + int attackers = CardLists.getValidCardCount((Iterable) runParams.get(AbilityKey.Blockers), getParam("ValidBlocker"), getHostCard().getController(), getHostCard(), this); int amount = AbilityUtils.calculateAmount(getHostCard(), param.substring(2), this); if (!Expressions.compare(attackers, param, amount)) { return false; diff --git a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java index b573dadf430..3243e66dc5b 100644 --- a/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java +++ b/forge-gui-desktop/src/main/java/forge/view/SimulateMatch.java @@ -254,7 +254,6 @@ public class SimulateMatch { numPlayers++; } } - } if (numPlayers == 0) { diff --git a/forge-gui/release-files/GAMEPAD_README.txt b/forge-gui/release-files/GAMEPAD_README.txt index a141668182d..8a2af6a6ddb 100644 --- a/forge-gui/release-files/GAMEPAD_README.txt +++ b/forge-gui/release-files/GAMEPAD_README.txt @@ -1,6 +1,6 @@ Basic Gamepad Support for Adventure Mode -Tested using DS4 COntroller on Windows and Android. +Tested using DS4 Cpntroller on Windows and Android. If using on Windows OS and you have DS4Windows installed, you might experience dual input because of Emulated/Virtual Controller. To fix this you must use HidHide (better than exclusive mode). Refer to the guide here: https://vigem.org/projects/HidHide/Simple-Setup-Guide/ From dd35f970350e86a9ad1c00d2117f7b5ed6090e56 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Tue, 4 Nov 2025 09:23:21 +0100 Subject: [PATCH 226/230] Cleanup scripting API first pass (#9070) --- docs/Card-scripting-API/AbilityFactory.md | 1302 ++++++++--------- docs/Card-scripting-API/Card-scripting-API.md | 213 ++- docs/Console-and-cheats.md | 43 + docs/Currency.md | 46 + docs/Deck-Building-Tips.md | 28 + docs/Home.md | 2 +- docs/_sidebar.md | 6 +- forge-gui/res/cardsfolder/p/powerbalance.txt | 2 +- 8 files changed, 858 insertions(+), 784 deletions(-) create mode 100644 docs/Console-and-cheats.md create mode 100644 docs/Currency.md create mode 100644 docs/Deck-Building-Tips.md diff --git a/docs/Card-scripting-API/AbilityFactory.md b/docs/Card-scripting-API/AbilityFactory.md index 959833fe2b5..1305fa3721a 100644 --- a/docs/Card-scripting-API/AbilityFactory.md +++ b/docs/Card-scripting-API/AbilityFactory.md @@ -1,66 +1,73 @@ -[[_TOC_]] - -_NOTE: These factories are refactored from time to time (often to adapt to new sets), so while some entries could be slightly outdated, the base information should still be correct._ - AbilityFactory parses differently from the Keyword parser. Your Ability line will look more like this: -`A:{AB/SP/DB/ST}$ | {Necessary$ Parameters}| {Separated$ By} | {Pipes$ Here}` +`A:{AB/SP/DB/ST}$ | {Necessary$ Parameters} | {Separated$ By} | {Pipes$ Here} | [Optional$ Values]` In most cases, each AF subclass implements both the Spell and Ability. Much of the code is shared, so creating the data object will look very similar. - - AB is for Activated Abilities. - - SP is for Spell. - - DB is for Drawback and for many abilities that are subsidiary to - other things, like replacements. They are only used to chain AFs - together, and will never be the root AF. - - ST is for Static, this gets used in case the API should resolve without using the stack (e.g. special actions) + - **AB** is for Activated Abilities + - **SP** is for Spell + - **DB** is for Drawback and many abilities that are subsidiary to other things, like replacements. They are only used to chain AFs together, and will never be the root AF + - **ST** is for Static, this gets used in case the API should resolve without using the stack
(e.g. the unique *Circling Vultures* special action is directly implemented in the script this way) -### Common Parameters +>*NOTE:* +> - these factories are refactored from time to time (often to adapt to new sets), so while some entries could be slightly outdated, the base information should still be correct +> - a few factories also have _*All_ variants, these are slowly being phased out +> - some parameters are only added for very exotic cards, these won't be included here to keep the focus on understanding the general concepts of scripting +> - when in doubt you can always cross check with the [available APIs](https://github.com/Card-Forge/forge/tree/master/forge-game/src/main/java/forge/game/ability/effects) code -#### Cost / UnlessCost +# Common Parameters -`Cost$ ` is the appropriate way to set the Cost of the Ability. Currently for spells, any additional costs including the original Mana cost need to appear in the Cost parameter in the AbilityFactory. For each card that uses it, the order in which the cost is paid will always be the same. +## Cost / UnlessCost -Secondary abilities such as the DB executed by triggers or replacements don't need costs. (This is one reason to use DB over AB in these cases.) +`Cost$ ` is the appropriate way to set the cost of the ability. Currently for spells, any additional costs including the original Mana cost need to appear in the Cost parameter in the AbilityFactory. For each card that uses it, the order in which the cost is paid will always be the same. + +Secondary abilities such as the DB executed by triggers or replacements (usually) don't need costs. (This is one reason to use DB over AB in these cases.) Read more about it in [Costs](Costs) -#### Target / Defined +## ValidTgts / Defined -There are two different ways to Target. One or the other will need to be used for Spells/Abilities that target. If your effect does not target, but instead defines what will be affected by the Spell/Ability, use the Defined Parameter. +Most effects need to know (at least implicitly) which players or objects they're trying to affect. There are two different ways for that: +- `ValidTgts` will need to be used for abilities that target +- if your ability instead describes on what it's applied use `Defined` Read more about it in [Affected / Targets](Targeting) -#### Restriction +## Restrictions / Conditions -[Restriction](Restrictions) restricts when spells/abilities can be activated or resolved. Common examples are Putrid Leech's only activate this once per turn or different cards that can activate from Zones like the Hand or the Graveyard. +Restrictions limit when abilities can be put on the stack and Conditions apply during resolving. Common examples are Putrid Leech's only activate this once per turn or different cards that can activate from Zones like the Hand or the Graveyard. -#### SpellDescription +Read more about it in [Restriction](Restrictions) + +## SpellDescription SpellDescription is how the text of the ability will display on the card and in the option dialog for cards with multiple abilities. The SpellDescription for secondary abilities (both AB and DB) is now displayed when (and if) the ability prompts for user input in the prompt pane so it is useful to put some nice text there. -#### StackDescription +## StackDescription *(Optional)* StackDescription is the description the ability will have on the stack. This is automatically generated by the effect, but may be overridden using this parameter. This is sometimes needed with complex effects, when the generated text can't handle some details. Properties of the spell can be accessed like this: {c:Targeted}. You can reuse the spell text by just putting `SpellDescription` or `None` to leave it empty. -#### AI instructions +## Remember* -IsCurse$ True - For effects that are normally treated positive e. g. Pump +Remembering is often needed when a card becomes a new object, which is then further affected by the ability. Typical example: [Flicker](https://github.com/Card-Forge/forge/blob/master/forge-gui/res/cardsfolder/f/flicker.txt)
+Because cards keep their remembered parts when changing zones manual [cleanup](#Cleanup) is usually required. + +## AI params + +`IsCurse$ True` - For effects that are normally treated positive e.g. Pump `AITgts$ BetterThanEvalRating.130` -## Factories +# Factories (in Alphabetical Order) -In Alphabetical Order. - -### AlterLife +## AlterLife AlterLife is for Abilities that Alter a player's life total. -#### GainLife +### GainLife Have a player gain the specified amount of life. @@ -71,7 +78,7 @@ LifeAmount$ is required. This is how much life you will gain. Defined is optional, if it appears the defined player(s) gain life. Target is optional, if it appears and Defined doesn't then targeted player(s) gain life. -#### LoseLife +### LoseLife Have a player lose the specified amount of life. @@ -85,19 +92,7 @@ Remember, if Defined is missing, the default for Players is "You" Part of resolving sets the **SVar AFLifeLost** to the amount of life lost by all players. -#### Poison - -Poison gives a player the specified number of poison counters. - -`A:AB$ Poison | Cost$ B T | ValidTgts$ Player | TgtPrompt$ Select target player | Num$ 2 | SpellDescription$ Target player gets 2 poison counters.` - -Parameters: - - - Num (required) - the number of poison counters to give - -Target is optional and used if available. Defined is optional and used if no Target (defaults to "You"). - -#### SetLife +### SetLife SetLife sets one or both player's life total to a specified value (i.e. "your life total becomes 20" or "Target player's life total is equal to @@ -111,7 +106,7 @@ LifeAmount (required) - the value to set the life total(s) to Defined is optional. If it exists, it will be used. Target is optional. If it exists and defined doesn't it will be used. Default player is "You". -#### ExchangeLife +### ExchangeLife ExchangeLife switches the Life total of two players. @@ -123,7 +118,7 @@ defined doesn't it will be used. If there aren't two determined players by the SA, the activating player is added as the second player. -### Animate +## Animate Animate handles animation effects like "This card becomes a 5/5 green creature with flying until end of turn." It is designed to handle @@ -184,7 +179,7 @@ Parameters: Target is optional, will be used if possible. Defined is optional, will be used if no Targets (Self by default) -### Attach +## Attach Attach is being used directly only for Auras, primarily for Aura Spells, but also for Auras entering the battlefield by some effect. @@ -210,13 +205,13 @@ Parameters: Attach separates the actually granting of abilities from the attaching to permanents to streamline how things work. -### BecomeMonarch +## BecomeMonarch -### Bond +## Bond -Soulbonding two creatures +Soulbonding two creatures. Only used internally by the engine. -### Branch +## Branch Sometimes, an ability might do certain things when a specific condition is true, and other things if not. This can be implemented by using `Branch`. The branch evaluates the SVar specified by the property `BranchConditionSVar`, using the comparison defined with `BranchConditionSVarCompare` (such as `GTY`, `LT1`, etc). Depending on whether the condition evaluated to true or false, the subability defined by `TrueSubAbility` or `FalseSubAbility` is executed. @@ -229,11 +224,556 @@ SVar:PutLandCreature:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | SVar:X:Count$Valid Enchantment.YouCtrl ``` +## Charm -### ChangeState +This allows cards that have a mode to be chosen to occur after a trigger. + +Parameters + + - CharmNum - Number of Modes to Choose + - Choices - A Comma delimited list of SVars containing the Modes + +## Choose* + +These can be used to chain effects together. However for common cases many effects already support this directly, e.g. `PutCounter | Choices$``.
+Besides making the script shorter using such shortcuts usually also helps the AI making better use of the effect. + +### ChooseType + +This can be used when you are asked to choose a card type or creature type. + + - Type - Required - Can be Card or Creature + - InvalidTypes - Optional - Use to specify any type that cannot be chosen (ex: "Choose any creature type except Wall") + +The Defined is for target players. + +## Clash + +This AF handles clashing. It takes two special parameters: WinSubAbility and +OtherwiseSubAbility. They are both optional and work the same way, +namely that it contains the name of an SVar that in turn contains a +drawback to be executed. The example below is for Release the Ants. + +`A:SP$ DealDamage | Cost$ 1 R | Tgt$ TgtCP | NumDmg$ 1 | SubAbility$ DBClash | SpellDescription$ Release the Ants deals 1 damage to target creature or player. Clash with an opponent. If you win, return CARDNAME to its owner's hand.` +`SVar:DBClash:DB$ Clash | WinSubAbility$ DBReturn` +`SVar:DBReturn:DB$ ChangeZone | Defined$ Self | Origin$ Stack | Destination$ Hand` + +## Cleanup + +A non-functional, maintenance AF used for Cleaning up certain Variables before a Spell finishes Resolving. + +Parameters + + - ClearRemembered$ (optional) Set to True to clear this card's + remembered list. Generally useful for Cards that Remember a card, do + something to it, then need to forget it once it's done. + - ClearImprinted$ (optional) Set to True to clear the list of + imprinted cards. + - ClearChosenX$ (optional) Set to True to clear the chosen X value. + - ClearTriggered$ (optional) Set to True to clear any delayed triggers + produced by this card. + - ClearCoinFlips$ (optional) Set to True to clear the remembered coin + flip result. + - ClearChosenCard$ (optional) Set to True to clear the chosen cards. + - ForgetDefined$ (optional) If present, remove the specified cards + from this card's remembered list. + +## Control + +### GainControl + +Example: Act of Aggression + +`A:SP$ GainControl | Cost$ 3 PR PR | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls. | LoseControl$ EOT | Untap$ True | AddKWs$ Haste | SpellDescription$ Gain control of target creature an opponent controls until end of turn. Untap that creature. It gains haste until end of turn.` + +Parameters: + +- NewController(Targeted player, if there is no target player, the + default is the ActivatingPlayer) +- AllValid(all valid types, no targets) +- LoseControl(LeavesPlay, Untap, LoseControl, EOT(end of turn)) + +### ControlExchange + +### ControlSpell + +## Copy* + +### CopyPermanent + +Copies a permanent on the battlefield. + +Parameters: + + - NumCopies - optional - the number of copies to put onto the + battlefield. Supports SVar:X:????. + - Keywords - optional - a list of keywords to add to the copies + - AtEOT - optional + - Sacrifice - set to this is copy should be sacrificed at End of + Turn + - Exile - set to this is copy should be exiled at End of Turn + +### CopySpellAbility + +Copies a spell on the stack (Twincast, etc.). + +## Counter + +Countering Spells or Abilities. + +`A:SP$ Counter | Cost$ 1 U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | UnlessCost$ 3 | SpellDescription$ Counter target spell unless its controller pays 3.` +`A:SP$ Counter | Cost$ U | TgtPrompt$ Select target Activated or Triggered Ability | ValidTgts$ Card | TargetType$ Activated,Triggered | SpellDescription$ Counter target activated or triggered ability.` +`A:SP$ Counter | Cost$ G | TargetType$ Spell | ValidTgts$ Instant,Aura | TargetValidTargeting$ Permanent.YouCtrl | SpellDescription$ Counter target instant or Aura spell that targets a permanent you control. ` + +Parameters + + - TargetType - Can be Spell,Activated,Triggered. If more than one, + just put a comma in between. + - ValidTgts - a "valid" expression for types of source card (if you + don't know what it is it's just "Card") + - TargetValidTargeting- a "valid" expression for targets of this + spell's target + - Destination - send countered spell to: (only applies to Spells; ignored for Abilities) + - Graveyard (Default) + - Exile + - TopDeck + - Hand + - BottomDeck + - Shuffle + +## Counters* + +Factories to handle counters on cards. + +### Poison + +Poison gives a player the specified number of poison counters. + +`A:AB$ Poison | Cost$ B T | ValidTgts$ Player | TgtPrompt$ Select target player | Num$ 2 | SpellDescription$ Target player gets 2 poison counters.` + +Parameters: + +- Num (required) - the number of poison counters to give + +Target is optional and used if available. Defined is optional and used if no Target (defaults to "You"). + +### PutCounter + +Put any type of counter on a game object. + +`A:AB$ PutCounter | Cost$ T | CounterType$ CHARGE | CounterNum$1 | SpellDescription$ Put a charge counter on CARDNAME.` +`A:SP$ PutCounter | Cost$ G | Tgt$ TgtC | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a charge counter on CARDNAME.` + +Target is optional. If no Target is provided, the permanent will put counters on itself. + + - CounterType (required) specifies the type of counter and should + appear in all caps. It should be one of the values in the Counters + enum. + - CounterNum (required) specifies how many counters will be put on + the chosen card. + +### PutCounterAll + +Put any type of counter on all valid cards. + + - CounterType (required) specifies the type of counter and should + appear in all caps. It should be one of the values in the Counters + enum. + - CounterNum (required) specifies how many counters will be put on + the chosen cards. + - ValidCards (required) specifies the cards to add counters to. + +### RemoveCounter + +Remove any type of counter from a card. + +Target is optional. If no Target is provided, the permanent will remove +counters from itself. + + - CounterType (required) specifies the type of counter and should + appear in all caps. It should be one of the values in the Counters + enum. + - CounterNum (required) specifies how many counters will be removed + from the chosen card. + - UpTo is optional. If an effect states you may remove "up to X + counters", set this to True. + +### RemoveCounterAll + +Remove any type of counter from all valid cards. + + - CounterType$ (required) specifies the type of counter and should + appear in all caps. It should be one of the values in the Counters + enum. + - CounterNum$ (required) specifies how many counters will be removed + from the chosen cards. + - ValidCards$ (required) specifies the card to remove counters from. + +### Proliferate + +No own parameters. + +### MoveCounters + +Used for cards that Move Counters on Resolution, requiring the Host card +to have Counters for the Move to occur. + +Parameters + + - Source - The Source of the Moving Counters + - Defined - The Destination of the Moving Counters + - CounterType - The type of counter to move. + - CounterNum - The number of counters to move. + +## Damage + +### DealDamage + +Deal damage to a specified player or permanent. + +`A:AB$ DealDamage | Cost$ T | Tgt$ TgtCP | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to target creature or player.` + +NumDmg is required. This is the amount of damage dealt. + +### EachDamage + +## Debuff + +Parameters + + - Keywords + - Duration + +An AbilityFactory for Removing Keywords, either temporarily or for longer durations. + +## Destroy + +These APIs handles destruction of cards on the battlefield. + +`A:SP$Destroy | Cost$ 1 W W | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy target artifact or enchantment.` + +## Effect + +Effect is an oddball of the AF family. Where usually AFs have similarities to each other to help with AI use, Effect doesn't fall under that jurisdiction. In general, an effect is some type of SA that +lasts longer than its resolution. + +A good example is High Tide. For the rest of the turn, High Tide makes +all Islands produce an extra mana. It doesn't matter if the Island was +in play, if it turned into an Island after High Tide was cast, any of that. + +`A:SP$ Effect | Cost$ U | Name$ High Tide Effect | Triggers$ IslandTrigger | SVars$ TrigMana | SpellDescription$ Until end of turn, whenever a player taps an Island for mana, that player adds U to his or her mana pool (in addition to the mana the land produces).` +`SVar:IslandTrigger:Mode$ TapsForMana | ValidCard$ Island | Execute$ TrigMana | TriggerDescription$ Whenever a player taps an Island for mana, that player adds U to his or her mana pool (in addition to the mana the land produces).` +`SVar:TrigMana:AB$Mana | Cost$ 0 | Produced$ U | Amount$ 1` + +Effect is most similar to Token as it creates a pseudo-permanent, except +Effect creates in the command zone rather than the battlefield. It stays +active there for a set Duration. + +Parameters + + - Abilities,Triggers,SVars are comma separated lists which contain + SVars that point to the appropriate type that the Effect will gain. + - Duration is how long the Effect lasts. Right now, most effects will + last Until End of Turn. In the future, they may have other + conditions. + +Duration$ Permanent for effects that have no specific Duration. + + - Stackable$ False - Most Effects are assumed to be Stackable. By + setting the Stackable Flag to False, the AI will know having a + second one in play is useless, so will save it's Resource for + something else. + - Image - a file\_name\_without\_extension (image needs to reside in + the tokens directory) + +## Explore + +## Fight + +## Fog + +Fog is an ability based on the original Fog spell. "Prevent all combat +damage that would be dealt this turn." While this could be done with an +effect, the specialized nature of the AI gives it its own AF. + +## Game outcome + +### GameDraw + +### GameLoss + +### GameWin + +### RestartGame + +Used in the script of *Karn Liberated* + +## Goad + +## Investigate + +## Mana + +For lands or other permanent to produce mana. + +`A:AB$ Mana | Cost$ T | Produced$ | SpellDescription$ Add W to your mana pool.` + +In this example ManaType would be W. + +## Manifest + +## PermanentState + +API for things that alter a permanent's state. + +### Phases + +### SetState Changing a cards State. This is mostly for Flip Cards or the Transform mechanic. +### Tap + +`A:AB$ Tap | Cost$ R | ValidTgts$ Wall | TgtPrompt$ Select target wall | SpellDescription$ Tap target wall.` + +### TapOrUntap + +### Untap + +`A:AB$ Untap | Cost$ G | ActivationLimit$ 1| SpellDescription$ Untap CARDNAME. Activate this ability only once each turn.` +`A:SP$ Untap | Cost$ W | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | SpellDescription$ Untap target permanent.` + +Target is optional. If not provided, will untap the permanent with this ability. + +## Play + +Playing a card or cards as part of SA. The player may have to make a +choice about which card to play if there are more choices than the +number of cards to play. + +Sunbird's Invocation uses many of the parameters. + +Amount - How many cards can be played (a non-negative integer or "All"). + +Valid - Selection criteria for valid cards from the zone to cast. + +ValidSA - Applied after Valid, this will filter based on all spells of the cards. + +ValidZone - The zone to look in to determine the valid cards. + +ShowCards - Other cards in the zone to show when selecting valid cards. + +Optional - Casting the card is optional. + +RememberPlayed - Remember the card played. + +ForgetRemembered - Remove all remembered cards from the source (but only +if a card was successfully played) + +ForgetTargetRemembered - Remove the played card from remembered cards +(but only if it was successfully played) + +WithoutManaCost - The card can be cast without mana payment. + +## PreventDamage + +AF damage prevention effects. + + - A:SP$ PreventDamage | Cost$ W | ValidTgts$ Creature | Amount$ 3 + | TgtPrompt$ Select target creature | SpellDescription$ Prevent + the next 3 damage that would be dealt to target creature this + turn. + +## Protection + +Protection grants protection from colors or cards types, or creature +types. Anything that has "Protection from ..." + +Gains - required - the thing to gain protection from (green, artifacts, +Demons, etc.) or "Choice" if you can choose one of... + +Choices - optional +- if Gains$ Choice then this is a comma-delimited list of choices + +## Pump + +This factory handles pumping creatures power/toughness or granting abilities to permanents (usually creatures). + + - A:AB$ Pump | Cost$ R | NumAtt$ +1 | SpellDescription$ CARDNAME + gets +1/+0 until end of turn. + - A:SP$ Pump | Cost$ 1 U | ValidTgts$ Creature | KW$ Shroud| + SpellDescription$ Target creature gains shroud until end of + turn. | TgtPrompt$ Select target creature. + +Target is optional. If it's not provided, the activating permanent will be pumped. + +NumAtt$ is optional, will pump Power. + +NumDef$ is optional, will pump Toughness. + +KW$ is optional, will give temporary keywords. + +## Regenerate + +Regenerate is similar to abilities like Pump. But for creating +Regeneration shields. + + - A:AB$ Regenerate | Cost$ B | SpellDescription$ Regenerate + CARDNAME + - A:SP$ Regenerate | Cost$ W | ValidTgts$ Creature | TgtPrompt$ + Select target creature | SpellDescription$ Regenerate target + creature. + +Target is optional. If not provided, will regenerate the permanent with this ability. + +## Repeat + +Repeat the specified ability. + +### Repeat + +`A:SP$ Repeat | Cost$ 3 B B | RepeatSubAbility$ DBDig | RepeatOptional$ True` + + - MaxRepeat - optional - the maxium times to repeat, execute repeat + ability at least once + - RepeatSubAbility - required - setup subability to repeat + - RepeatOptional - optional - you make the choice whether to repeat + the process + - RepeatPresent, RepeatCompare, RepeatDefined, RepeatCheckSVar, + RepeatSVarCompare - optional - condition check + +### RepeatEach + + - RepeatSubAbility - required - to set up repeat subability + - RepeatCards - to repeat for each valid card (zone: present zone of + the valid repeat cards, default: battlefield) + - DefinedCards + - RepeatPlayers - to repeat for each valid player + - RepeatCounters - to repeat for each valid counters + +## Reveal + +### RevealHand + +Look at a player's hand. + +Target or Defined is required. + +`A:AB$ RevealHand | Cost$ T | ValidTgts$ Player | TgtPrompt$ Select target player | SpellDescription$ Look at target player's hand.` + +### Reveal + +`A:AB$ Reveal | Cost$ 2 U T | Defined$ You | RevealValid$ Card.Blue | AnyNumber$ True | RememberRevealed$ True` + +Parameters: + + - RevealValid: to limit the valid cards. + - AnyNumber + - Random + - RememberRevealed: to remember the cards revealed + +### PeekAndReveal + +This AF is very similar to things that Dig can do, but handle a much +simpler form, with less complex coding underneath. Similar to how +RearrangeTopOfLibrary could be handled with Dig. + +Primarily used with cards that allow you to Peek at the top card of your +library, and allow you to reveal it if it's of a certain type. The +Kinship cards fit this bill perfectly, so they are used to simplify the +complex popups that would be required if using multiple Dig +SubAbilities. + +RevealOptional - Whether or not the Reveal is optional. + +RememberRevealed - Whether to remember the revealed cards (after +filtering by Valid) + +RememberPeeked - Whether to remember the peeked cards (only if they are +not revealed\!) + +RevealValid - defaults to Card, but allows you to set a specific +ValidType if you can only have certain things + +PeekAmount - defaults to 1, but allows you to peek at multiple cards if +possible + +## RollDice + +## Sacrifice + +Usually you choose a player and that player has to sacrifice something + +`A:SP$ Sacrifice | Cost$ 1 B | ValidTgts$ Player | SacValid$ Creature | SacMessage$ Creature | Amount$ 2 | SpellDescription$ Target player sacrifices a creature.` + +Destroy$ True - An optional parameter for destroying permanents target +player chooses (eg: Burning of Xinye, or Imperial Edict). + +`A:SP$ Sacrifice | Cost$ 1 B | ValidTgts$ Opponent | SacValid$ Creature | SacMessage$ Creature | Destroy$ True | SpellDescription$ Target opponent chooses a creature he or she controls. Destroy it.` + +## Scry + +`A:AB$ Scry | Cost$ 1 T | ScryNum$ 2` + +## StoreSVar + +## Token + +Token simply lets you create tokens of any type. + +`A:SP$ Token | Cost$ 3 W U | TokenImage$ W 1 1 Bird Flying | TokenAmount$ X | TokenName$ Bird | TokenTypes$ Creature,Bird | TokenOwner$ You | TokenColors$ Blue | TokenPower$ 1 | TokenToughness$ 1 | TokenKeywords$ Flying` + +This ability factory does not take a target. All the parameters are +mandatory except for TokenKeywords. If you provide a non-integer for +TokenAmount, TokenPower or TokenToughness the AF will attempt to look for +an SVar of that name and interpret it's contents as a Count$ line. Worth +noting is that TokenTypes and TokenColors are simple commaseparated +lists while TokenKeywords is a list where the items are separated by +"\<\>". If TokenImage is not provided, the factory will attempt to +construct a filename on it's own. TokenOwner can use Defined-like +parameters, such as "You" "Opponent" or the new Triggered-Variables. + +You can also use the parameters TokenAbilities$, TokenTriggers$ and +TokenSVars$ to give the created tokens any number of either. For +example, here's how Growth Spasm creates an Eldrazi Spawn token complete +with ability. + + SVar:DBToken:DB$Token | TokenAmount$ 1 | TokenName$ Eldrazi Spawn | TokenTypes$ Creature,Eldrazi,Spawn | TokenOwner$ You | TokenColors$ Colorless | TokenPower$ 0 | TokenToughness$ 1 | TokenAbilities$ ABMana SVar:ABMana:AB$Mana | Cost$ Sac<1/CARDNAME> | Produced$ 1 | Amount$ 1 | SpellDescription$ Add 1 to your mana pool. + +As another example, here's Mitotic Slimes' use of TokenTriggers$: + + T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigTokenSenior | TriggerDescription$ When CARDNAME is put into a graveyard from the battlefield, put two 2/2 green Ooze creature tokens onto the battlefield. They have "When this creature is put into a graveyard, put two 1/1 green Ooze creature tokens onto the battlefield." + SVar:TrigTokenSenior:AB$ Token | Cost$ 0 | TokenImage$ g 2 2 ooze | TokenName$ Ooze | TokenTypes$ Creature,Ooze | TokenColors$ Green | TokenOwner$ You | TokenPower$ 2 | TokenToughness$ 2 | TokenAmount$ 2 | TokenTriggers$ TriggerJunior | TokenSVars$ TrigTokenJunior + SVar:TriggerJunior:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigTokenJunior | TriggerDescription$ When this creature is put into a graveyard, put two 1/1 green Ooze creature tokens onto the battlefield. SVar:TrigTokenJunior:AB$Token | Cost$ 0 | TokenImage$ g 1 1 ooze | TokenName$ Ooze | TokenTypes$ Creature,Ooze | TokenColors$ Green | TokenOwner$ You | TokenPower$ 1 | TokenToughness$ 1 | TokenAmount$ 2 + +## Trigger + +If possible split the SpellDescription$ of the the effect so the part for the trigger can become the StackDescription directly. + +### DelayedTrigger + +### ImmediateTrigger + +## Turn structure + +### AddPhase + +### AddTurn + +`A:SP$ AddTurn | Cost$ 1 U | NumTurns$ 1 | SpellDescription$ Take an extra turn after this one.` + +### EndTurn + +### ReverseTurnOrder + +### SkipPhase + +### SkipTurn + +## ZoneAffecting + +For specific effects that handle zones in a specific manner + ### ChangeZone ChangeZone is a united front of any card that changes zone. This does @@ -281,638 +821,50 @@ card is changing zones?" happens on activation, generally by targeting. For Known, since it just uses Target, normal target parameters are used in this Scenario. -#### ChangeZoneResolve +### ChangeZoneResolve This is a helper AF, for chained effects that create multiple permanents which should enter the battlefield at the same time. To use it, you need to set the param "ChangeZoneTable" on the first effect and then call this at the end. This is supported by the following effects: - - Amass - - CopyPermanent - - RepeatEach (_NOTE: if you wrap the creation, you don't need to call this AF on its own_) - - Token +- Amass +- CopyPermanent +- RepeatEach (_NOTE: if you wrap the creation, you don't need to call this AF on its own_) +- Token -### Charm - -This allows cards that have a mode to be chosen to occur after a trigger. - -Parameters - - - CharmNum - Number of Modes to Choose - - Choices - A Comma delimited list of SVars containing the Modes - -### Choose - -#### ChooseType - -This can be used when you are asked to choose a card type or creature type. - - - Type - Required - Can be Card or Creature - - InvalidTypes - Optional - Use to specify any type that cannot be chosen (ex: "Choose any creature type except Wall") - -The Defined is for target players. - -*NOTE_: Make sure that when this is used with a SubAbility that the DB$AF\_Whatever will support Card.ChosenType. This will need testing on a case by case basis.* - -### Clash - -This AF handles clashing. It takes two special parameters: WinSubAbility and -OtherwiseSubAbility. They are both optional and work the same way, -namely that it contains the name of an SVar that in turn contains a -drawback to be executed. The example below is for Release the Ants. - -`A:SP$ DealDamage | Cost$ 1 R | Tgt$ TgtCP | NumDmg$ 1 | SubAbility$ DBClash | SpellDescription$ Release the Ants deals 1 damage to target creature or player. Clash with an opponent. If you win, return CARDNAME to its owner's hand.` -`SVar:DBClash:DB$ Clash | WinSubAbility$ DBReturn` -`SVar:DBReturn:DB$ ChangeZone | Defined$ Self | Origin$ Stack | Destination$ Hand` - -### Cleanup - -A non-functional, maintenance AF used for Cleaning up certain Variables before a Spell finishes Resolving. - -Parameters - - - ClearRemembered$ (optional) Set to True to clear this card's - remembered list. Generally useful for Cards that Remember a card, do - something to it, then need to forget it once it's done. - - ClearImprinted$ (optional) Set to True to clear the list of - imprinted cards. - - ClearChosenX$ (optional) Set to True to clear the chosen X value. - - ClearTriggered$ (optional) Set to True to clear any delayed triggers - produced by this card. - - ClearCoinFlips$ (optional) Set to True to clear the remembered coin - flip result. - - ClearChosenCard$ (optional) Set to True to clear the chosen cards. - - ForgetDefined$ (optional) If present, remove the specified cards - from this card's remembered list. - -### Copy - -#### CopyPermanent - -Copies a permanent on the battlefield. - -Parameters: - - - NumCopies - optional - the number of copies to put onto the - battlefield. Supports SVar:X:????. - - Keywords - optional - a list of keywords to add to the copies - - AtEOT - optional - - Sacrifice - set to this is copy should be sacrificed at End of - Turn - - Exile - set to this is copy should be exiled at End of Turn - -#### CopySpellAbility - -Copies a spell on the stack (Twincast, etc.). - -### Counter - -Countering Spells or Abilities. - -`A:SP$ Counter | Cost$ 1 U | TargetType$ Spell | TgtPrompt$ Select target spell | ValidTgts$ Card | UnlessCost$ 3 | SpellDescription$ Counter target spell unless its controller pays 3.` -`A:SP$ Counter | Cost$ U | TgtPrompt$ Select target Activated or Triggered Ability | ValidTgts$ Card | TargetType$ Activated,Triggered | SpellDescription$ Counter target activated or triggered ability.` -`A:SP$ Counter | Cost$ G | TargetType$ Spell | ValidTgts$ Instant,Aura | TargetValidTargeting$ Permanent.YouCtrl | SpellDescription$ Counter target instant or Aura spell that targets a permanent you control. ` - -Parameters - - - TargetType - Can be Spell,Activated,Triggered. If more than one, - just put a comma in between. - - ValidTgts - a "valid" expression for types of source card (if you - don't know what it is it's just "Card") - - TargetValidTargeting- a "valid" expression for targets of this - spell's target - - Destination - send countered spell to: (only applies to Spells; ignored for Abilities) - - Graveyard (Default) - - Exile - - TopDeck - - Hand - - BottomDeck - - Shuffle - -### Counters - -Factories to handle counters on cards. - -#### PutCounter - -Put any type of counter on a game object. - -`A:AB$ PutCounter | Cost$ T | CounterType$ CHARGE | CounterNum$1 | SpellDescription$ Put a charge counter on CARDNAME.` -`A:SP$ PutCounter | Cost$ G | Tgt$ TgtC | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a charge counter on CARDNAME.` - -Target is optional. If no Target is provided, the permanent will put counters on itself. - - - CounterType (required) specifies the type of counter and should - appear in all caps. It should be one of the values in the Counters - enum. - - CounterNum (required) specifies how many counters will be put on - the chosen card. - -#### PutCounterAll - -Put any type of counter on all valid cards. - - - CounterType (required) specifies the type of counter and should - appear in all caps. It should be one of the values in the Counters - enum. - - CounterNum (required) specifies how many counters will be put on - the chosen cards. - - ValidCards (required) specifies the cards to add counters to. - -#### RemoveCounter - -Remove any type of counter from a card. - -Target is optional. If no Target is provided, the permanent will remove -counters from itself. - - - CounterType (required) specifies the type of counter and should - appear in all caps. It should be one of the values in the Counters - enum. - - CounterNum (required) specifies how many counters will be removed - from the chosen card. - - UpTo is optional. If an effect states you may remove "up to X - counters", set this to True. - -#### RemoveCounterAll - -Remove any type of counter from all valid cards. - - - CounterType$ (required) specifies the type of counter and should - appear in all caps. It should be one of the values in the Counters - enum. - - CounterNum$ (required) specifies how many counters will be removed - from the chosen cards. - - ValidCards$ (required) specifies the card to remove counters from. - -#### Proliferate - -No own parameters. - -#### MoveCounters - -Used for cards that Move Counters on Resolution, requiring the Host card -to have Counters for the Move to occur. - -Parameters - - - Source - The Source of the Moving Counters - - Defined - The Destination of the Moving Counters - - CounterType - The type of counter to move. - - CounterNum - The number of counters to move. - -### Damage - -#### DealDamage - -Deal damage to a specified player or permanent. - -`A:AB$ DealDamage | Cost$ T | Tgt$ TgtCP | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to target creature or player.` - -NumDmg is required. This is the amount of damage dealt. - -#### DamageAll - -This is for damaging all applicable cards/players. Spells like -Earthquake fit in this category. - -`A:SP$ DamageAll | Cost$ 1 B | NumDmg$ 1 | ValidCards$ Creature | ValidPlayers$ Each | ValidDescription$ each creature and each player. | SpellDescription$ CARDNAME deals 1 damage to each creature and each player.` - -ValidCards are for specifying the cards that will take damage. -ValidPlayers are for specifying the players. - -### Debuff - -Parameters - - - Keywords - - Duration - -#### Debuff - -An AbilityFactory for Removing Keywords, either temporarily or for longer durations. - -#### DebuffAll - -Same as Debuff, but for all of something - -### DelayedTrigger - -### Destroy - -These APIs handles destruction of cards on the battlefield. - -See [Sacrifice](Forge_AbilityFactory "wikilink") for special case: -'Target opponent chooses \[x cards\]. Destroy those \[cards\]' - -#### Destroy - -`A:SP$Destroy | Cost$ 1 W W | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy target artifact or enchantment.` - -#### DestroyAll - -`A:SP$ DestroyAll | Cost$ 3 U | ValidCards$ Forest | SpellDescription$ Destroy all Forests.` - -### Effect - -Effect is an oddball of the AF family. Where usually AFs have similarities to each other to help with AI use, Effect doesn't fall under that jurisdiction. In general, an effect is some type of SA that -lasts longer than its resolution. - -A good example is High Tide. For the rest of the turn, High Tide makes -all Islands produce an extra mana. It doesn't matter if the Island was -in play, if it turned into an Island after High Tide was cast, any of that. - -`A:SP$ Effect | Cost$ U | Name$ High Tide Effect | Triggers$ IslandTrigger | SVars$ TrigMana | SpellDescription$ Until end of turn, whenever a player taps an Island for mana, that player adds U to his or her mana pool (in addition to the mana the land produces).` -`SVar:IslandTrigger:Mode$ TapsForMana | ValidCard$ Island | Execute$ TrigMana | TriggerDescription$ Whenever a player taps an Island for mana, that player adds U to his or her mana pool (in addition to the mana the land produces).` -`SVar:TrigMana:AB$Mana | Cost$ 0 | Produced$ U | Amount$ 1` - -Effect is most similar to Token as it creates a pseudo-permanent, except -Effect creates in the command zone rather than the battlefield. It stays -active there for a set Duration. - -Parameters - - - Abilities,Triggers,SVars are comma separated lists which contain - SVars that point to the appropriate type that the Effect will gain. - - Duration is how long the Effect lasts. Right now, most effects will - last Until End of Turn. In the future, they may have other - conditions. - -Duration$ Permanent for effects that have no specific Duration. - - - Stackable$ False - Most Effects are assumed to be Stackable. By - setting the Stackable Flag to False, the AI will know having a - second one in play is useless, so will save it's Resource for - something else. - - Image - a file\_name\_without\_extension (image needs to reside in - the tokens directory) - -### Game outcome - -#### GameDraw - -#### GameLoss - -#### GameWin - -#### RestartGame - -Used in the script of Karn Liberated - -### Explore - -### Fight - -### Fog - -Fog is an ability based on the original Fog spell. "Prevent all combat -damage that would be dealt this turn." While this could be done with an -effect, the specialized nature of the AI gives it it's own AF. - -### GainControl - -Example: Act of Aggression - -`A:SP$ GainControl | Cost$ 3 PR PR | ValidTgts$ Creature.OppCtrl | TgtPrompt$ Select target creature an opponent controls. | LoseControl$ EOT | Untap$ True | AddKWs$ Haste | SpellDescription$ Gain control of target creature an opponent controls until end of turn. Untap that creature. It gains haste until end of turn.` - -Parameters: - - - NewController(Targeted player, if there is no target player, the - default is the ActivatingPlayer) - - AllValid(all valid types, no targets) - - LoseControl(LeavesPlay, Untap, LoseControl, EOT(end of turn)) - -### Goad - -### Investigate - -### Mana - -For lands or other permanent to produce mana. - -`A:AB$ Mana | Cost$ T | Produced$ | SpellDescription$ Add W to your mana pool.` - -In this example ManaType would be W. - -### Manifest - -### PermanentState - -API for things that alter a permanent's state: tap, untap, or phase in/out. - -#### Untap - -`A:AB$ Untap | Cost$ G | ActivationLimit$ 1| SpellDescription$ Untap CARDNAME. Activate this ability only once each turn.` -`A:SP$ Untap | Cost$ W | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | SpellDescription$ Untap target permanent.` - -Target is optional. If not provided, will untap the permanent with this ability. - -#### UntapAll - -`A:SP$ UntapAll | Cost$ G | ValidCards$ Creature.YouCtrl | SpellDescription$ Untap all creatures you control.` - -#### Tap - -`A:AB$ Tap | Cost$ R | ValidTgts$ Wall | TgtPrompt$ Select target wall | SpellDescription$ Tap target wall.` - -#### TapAll - -`A:AB$ TapAll | Cost$ U U U | ValidCards$ Creature.withoutFlying | SpellDescription$ Tap all creatures without flying.` - -#### TapOrUntap - -#### Phases - -### Play - -Playing a card or cards as part of SA. The player may have to make a -choice about which card to play if there are more choices than the -number of cards to play. - -Sunbird's Invocation uses many of the parameters. - -Amount - How many cards can be played (a non-negative integer or "All"). - -Valid - Selection criteria for valid cards from the zone to cast. - -ValidSA - Applied after Valid, this will filter based on all spells of the cards. - -ValidZone - The zone to look in to determine the valid cards. - -ShowCards - Other cards in the zone to show when selecting valid cards. - -Optional - Casting the card is optional. - -RememberPlayed - Remember the card played. - -ForgetRemembered - Remove all remembered cards from the source (but only -if a card was successfully played) - -ForgetTargetRemembered - Remove the played card from remembered cards -(but only if it was successfully played) - -WithoutManaCost - The card can be cast without mana payment. - -### PreventDamage - -AF damage prevention effects. - -#### PreventDamage - - - A:SP$ PreventDamage | Cost$ W | ValidTgts$ Creature | Amount$ 3 - | TgtPrompt$ Select target creature | SpellDescription$ Prevent - the next 3 damage that would be dealt to target creature this - turn. - -#### PreventDamageAll - -Same as PreventDamage, but for all ValidCards and/or ValidPlayers - -### Protection - -#### Protection - -Protection grants protection from colors or cards types, or creature -types. Anything that has "Protection from ..." - -Gains - required - the thing to gain protection from (green, artifacts, -Demons, etc.) or "Choice" if you can choose one of... - -Choices - optional -- if Gains$ Choice then this is a comma-delimited list of choices - -#### ProtectionAll - -Same as Protection, but for all ValidCards and/or ValidPlayers - -### Pump - -#### Pump - -This factory handles pumping creatures power/toughness or granting -abilities to permanents (usually creatures). - - - A:AB$ Pump | Cost$ R | NumAtt$ +1 | SpellDescription$ CARDNAME - gets +1/+0 until end of turn. - - A:SP$ Pump | Cost$ 1 U | ValidTgts$ Creature | KW$ Shroud| - SpellDescription$ Target creature gains shroud until end of - turn. | TgtPrompt$ Select target creature. - -Target is optional. If it's not provided, the activating permanent will be pumped. - -NumAtt$ is optional, will pump Power. - -NumDef$ is optional, will pump Toughness. - -KW$ is optional, will give temporary keywords. - -#### PumpAll - -### Regenerate - -#### Regenerate - -Regenerate is similar to abilities like Pump. But for creating -Regeneration shields. - - - A:AB$ Regenerate | Cost$ B | SpellDescription$ Regenerate - CARDNAME - - A:SP$ Regenerate | Cost$ W | ValidTgts$ Creature | TgtPrompt$ - Select target creature | SpellDescription$ Regenerate target - creature. - -Target is optional. If not provided, will regenerate the permanent with -this ability. - -#### RegenerateAll - -Same as regenerate, but for all. - -ValidCards - required - a valid expression for cards to regenerate - -### Repeat - -Repeat the specified ability. - -#### Repeat - -`A:SP$ Repeat | Cost$ 3 B B | RepeatSubAbility$ DBDig | RepeatOptional$ True` - - - MaxRepeat - optional - the maxium times to repeat, execute repeat - ability at least once - - RepeatSubAbility - required - setup subability to repeat - - RepeatOptional - optional - you make the choice whether to repeat - the process - - RepeatPresent, RepeatCompare, RepeatDefined, RepeatCheckSVar, - RepeatSVarCompare - optional - condition check - -#### RepeatEach - - - RepeatSubAbility - required - to set up repeat subability - - RepeatCards - to repeat for each valid card (zone: present zone of - the valid repeat cards, default: battlefield) - - DefinedCards - - RepeatPlayers - to repeat for each valid player - - RepeatCounters - to repeat for each valid counters - -### Reveal - -#### Dig +### Dig Dig is for an ability that does basically this: "You look at the X cards of your Library, put Y of them somewhere, then put the rest somewhere." Think of Impulse. - - DigNum - Required - look at the top number of cards of your library. - - Reveal - Optional - for abilities that say "Reveal the top X cards - of your library". Default is false. - - SourceZone - Optional - the zone to dig in. Default is Library. - - DestinationZone - Optional - the zone to put the Y cards in. Default - is Hand. - - LibraryPosition - Optional - if DestinationZone is Library, use this - to specify position. Default is -1 (bottom of library). - - ChangeNum - Optional - the number of cards to move to the - DestinationZone (or "All" when it's for things like "put all lands - revealed this way into your hand"). Default is 1. - - ChangeValid - Optional - use this to specify if "you may move an - artifact to DestinationZone". Default is any Card. - - AnyNumber - Optional - use if you can move any number of Cards to - DestinationZone. Default is false. (think of Lead the Stampede) - - Optional - Optional - set this if you "may" move a card to - DestinationZone. Default is false. - - DestinationZone2 - Optional - the zone to put the rest of the cards - in. If it is library, you are prompted for the order. Default is - Library. - - LibraryPosition2 - Optional - if DestinationZone2 is Library, use - this to specify position. Default is -1 (bottom of library). +- DigNum - Required - look at the top number of cards of your library. +- Reveal - Optional - for abilities that say "Reveal the top X cards + of your library". Default is false. +- SourceZone - Optional - the zone to dig in. Default is Library. +- DestinationZone - Optional - the zone to put the Y cards in. Default + is Hand. +- LibraryPosition - Optional - if DestinationZone is Library, use this + to specify position. Default is -1 (bottom of library). +- ChangeNum - Optional - the number of cards to move to the + DestinationZone (or "All" when it's for things like "put all lands + revealed this way into your hand"). Default is 1. +- ChangeValid - Optional - use this to specify if "you may move an + artifact to DestinationZone". Default is any Card. +- AnyNumber - Optional - use if you can move any number of Cards to + DestinationZone. Default is false. (think of Lead the Stampede) +- Optional - Optional - set this if you "may" move a card to + DestinationZone. Default is false. +- DestinationZone2 - Optional - the zone to put the rest of the cards + in. If it is library, you are prompted for the order. Default is + Library. +- LibraryPosition2 - Optional - if DestinationZone2 is Library, use + this to specify position. Default is -1 (bottom of library). -#### DigUntil +### DigUntil -#### RevealHand - -Look at a player's hand. - -Target or Defined is required. - -`A:AB$ RevealHand | Cost$ T | ValidTgts$ Player | TgtPrompt$ Select target player | SpellDescription$ Look at target player's hand.` - -#### Scry - -`A:AB$ Scry | Cost$ 1 T | ScryNum$ 2` - -#### RearrangeTopOfLibrary - -#### Reveal - -`A:AB$ Reveal | Cost$ 2 U T | Defined$ You | RevealValid$ Card.Blue | AnyNumber$ True | RememberRevealed$ True` - -Parameters: - - - RevealValid: to limit the valid cards. - - AnyNumber - - Random - - RememberRevealed: to remember the cards revealed - -#### PeekAndReveal - -This AF is very similar to things that Dig can do, but handle a much -simpler form, with less complex coding underneath. Similar to how -RearrangeTopOfLibrary could be handled with Dig. - -Primarily used with cards that allow you to Peek at the top card of your -library, and allow you to reveal it if it's of a certain type. The -Kinship cards fit this bill perfectly, so they are used to simplify the -complex popups that would be required if using multiple Dig -SubAbilities. - -RevealOptional - Whether or not the Reveal is optional. - -RememberRevealed - Whether to remember the revealed cards (after -filtering by Valid) - -RememberPeeked - Whether to remember the peeked cards (only if they are -not revealed\!) - -RevealValid - defaults to Card, but allows you to set a specific -ValidType if you can only have certain things - -PeekAmount - defaults to 1, but allows you to peek at multiple cards if -possible - -### RollDice - -### Sacrifice - -#### Sacrifice - -Usually you choose a player and that player has to sacrifice something - -`A:SP$ Sacrifice | Cost$ 1 B | ValidTgts$ Player | SacValid$ Creature | SacMessage$ Creature | Amount$ 2 | SpellDescription$ Target player sacrifices a creature.` - -Destroy$ True - An optional parameter for destroying permanents target -player chooses (eg: Burning of Xinye, or Imperial Edict). - -`A:SP$ Sacrifice | Cost$ 1 B | ValidTgts$ Opponent | SacValid$ Creature | SacMessage$ Creature | Destroy$ True | SpellDescription$ Target opponent chooses a creature he or she controls. Destroy it.` - -#### SacrificeAll - -### StoreSVar - -### Token - -Token simply lets you create tokens of any type. - -`A:SP$ Token | Cost$ 3 W U | TokenImage$ W 1 1 Bird Flying | TokenAmount$ X | TokenName$ Bird | TokenTypes$ Creature,Bird | TokenOwner$ You | TokenColors$ Blue | TokenPower$ 1 | TokenToughness$ 1 | TokenKeywords$ Flying` - -This ability factory does not take a target. All the parameters are -mandatory except for TokenKeywords. If you provide a non-integer for -TokenAmount, TokenPower or TokenToughness the AF will attempt to look for -an SVar of that name and interpret it's contents as a Count$ line. Worth -noting is that TokenTypes and TokenColors are simple commaseparated -lists while TokenKeywords is a list where the items are separated by -"\<\>". If TokenImage is not provided, the factory will attempt to -construct a filename on it's own. TokenOwner can use Defined-like -parameters, such as "You" "Opponent" or the new Triggered-Variables. - -You can also use the parameters TokenAbilities$, TokenTriggers$ and -TokenSVars$ to give the created tokens any number of either. For -example, here's how Growth Spasm creates an Eldrazi Spawn token complete -with ability. - - SVar:DBToken:DB$Token | TokenAmount$ 1 | TokenName$ Eldrazi Spawn | TokenTypes$ Creature,Eldrazi,Spawn | TokenOwner$ You | TokenColors$ Colorless | TokenPower$ 0 | TokenToughness$ 1 | TokenAbilities$ ABMana SVar:ABMana:AB$Mana | Cost$ Sac<1/CARDNAME> | Produced$ 1 | Amount$ 1 | SpellDescription$ Add 1 to your mana pool. - -As another example, here's Mitotic Slimes' use of TokenTriggers$: - - T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigTokenSenior | TriggerDescription$ When CARDNAME is put into a graveyard from the battlefield, put two 2/2 green Ooze creature tokens onto the battlefield. They have "When this creature is put into a graveyard, put two 1/1 green Ooze creature tokens onto the battlefield." - SVar:TrigTokenSenior:AB$ Token | Cost$ 0 | TokenImage$ g 2 2 ooze | TokenName$ Ooze | TokenTypes$ Creature,Ooze | TokenColors$ Green | TokenOwner$ You | TokenPower$ 2 | TokenToughness$ 2 | TokenAmount$ 2 | TokenTriggers$ TriggerJunior | TokenSVars$ TrigTokenJunior - SVar:TriggerJunior:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigTokenJunior | TriggerDescription$ When this creature is put into a graveyard, put two 1/1 green Ooze creature tokens onto the battlefield. SVar:TrigTokenJunior:AB$Token | Cost$ 0 | TokenImage$ g 1 1 ooze | TokenName$ Ooze | TokenTypes$ Creature,Ooze | TokenColors$ Green | TokenOwner$ You | TokenPower$ 1 | TokenToughness$ 1 | TokenAmount$ 2 - -### Turn structure - -#### AddPhase - -#### AddTurn - -`A:SP$ AddTurn | Cost$ 1 U | NumTurns$ 1 | SpellDescription$ Take an extra turn after this one.` - -#### EndTurn - -#### ReverseTurnOrder - -#### SkipPhase - -#### SkipTurn - -### ZoneAffecting - -For specific effects that handle zones in a specific manner - -#### Draw - -`A:AB$ Draw | Cost$ 1 Sac<1/CARDNAME> | NumCards$ 1 | SpellDescription$ Draw a card.` - -#### Discard +### Discard `A:AB$ Discard | Cost$ T | ValidTgts$ Opponent | NumCards$ 1 | Mode$ TgtChoose | SpellDescription$ Target opponent discards a card. ` @@ -926,17 +878,23 @@ For specific effects that handle zones in a specific manner - UnlessType - a ValidCards expression for "discard X unless you discard " -#### Mill +### Draw + +`A:AB$ Draw | Cost$ 1 Sac<1/CARDNAME> | NumCards$ 1 | SpellDescription$ Draw a card.` + +### Mill `A:AB$ Mill | Cost$ 2 T | NumCards$ 2 | ValidTgts$ Player | TgtPrompt$ Choose a player to mill | SpellDescription$ Target player puts the top two cards of his or her library into his or her graveyard.` -#### Shuffle +### RearrangeTopOfLibrary + +### Shuffle Used for shuffling a player's library - Optional - Set this parameter if the user should be prompted Yes/No to shuffle the given library. Default is false. -#### TwoPiles +### TwoPiles -### Vote +## Vote diff --git a/docs/Card-scripting-API/Card-scripting-API.md b/docs/Card-scripting-API/Card-scripting-API.md index cfb8d3ea915..cd1c7a962a0 100644 --- a/docs/Card-scripting-API/Card-scripting-API.md +++ b/docs/Card-scripting-API/Card-scripting-API.md @@ -1,30 +1,27 @@ -A reference guide for Scripting Cards using the API parsed by the Forge -Engine. +A reference guide for scripting cards using the API parsed by the Forge engine. -### Base Structure +# Base Structure -By opening any file in the /res/cardsfolder folder you can see the basic -structure of how the data is created. Here's an example of a vanilla -creature: +By opening any file in the /res/cardsfolder folder you can see the basic structure of how the data is created.
+Here's an example of a vanilla creature: ``` Name:Vanilla Creature ManaCost:2 G Types:Creature Beast -Text:no text PT:2/2 +Oracle: ``` -The name of this card is Vanilla Creature. It's casting cost is 2G. It -has the types Creature and Beast. It will not display any additional -text in the card's template. It has a Power-Toughness of 2/2. The text -line appears only if any rules printed on card are not backed by -abilities defined for the given card (in most cases presence of Text -line means such abilities are hardcoded). +The name of this card is Vanilla Creature.
+It's casting cost is {2}{G}.
+It has the types Creature and Beast.
+It has a Power-Toughness of 2/2.
+It will not display any additional text in the card's template.
If a card has two faces, use AlternateMode:{CardStateName} in the front face and separate both by a new line with the text "ALTERNATE". -There are a few other Parameters that will appear in most, if not all, cards. These are +There are a few other properties that will appear in many cards. These are | Property | Description | - | - @@ -33,8 +30,8 @@ There are a few other Parameters that will appear in most, if not all, cards. Th |`Colors`|Color(s) of the card

When a card's color is determined by a color indicator rather than shards in a mana cost, this property must be defined. If no identifier is needed, this property should be omitted.

* `Colors:red` - This is used on Kobolds of Kher Keep, which has a casting cost of {0} and requires a red indicator to make it red.

* `Colors:red,green` - Since Arlinn, Embraced by the Moon has no casting cost (it's the back of a double-faced card), the red and green indicator must be included. |`DeckHints`|AI-related hints for a deck including this card

To improve synergy this will increase the rank of of all other cards that share some of its DeckHints types. This helps with smoothing the selection so cards without these Entries won't be at an unfair disadvantage.

The relevant code can be found in the [CardRanker](https://git.cardforge.org/core-developers/forge/-/blob/master/forge-gui/src/main/java/forge/gamemodes/limited/CardRanker.java) class. |`DeckNeeds`|This can be considered a stronger variant when the AI should not put this card into its deck unless it has whatever other type is specified. The way this works is "inverted": it will directly decrease the rank of the card unless other cards are able to satisfy its types.
If a card demands more than one kind of type you can reuse it:
`DeckNeeds:Type$Human & Type$Warrior` will only find Human Warrior compared to `DeckNeeds:Type$Human\|Warrior` which is either -|`DeckHas`|specifies that the deck now has a certain ability (like, token generation or counters) so that the drafting/deckbuilding AI knows that it now meets requirements for DeckHints/DeckNeeds. This is actually very useful since many of these (such as Ability$Graveyard, Ability$Token, Ability$Counters) are not deduced by parsing the abilities, so an explicit hint is necessary. Using the other types is also supported in case the implicit parsing wouldn't find it.
It doesn't require exact matching to have an effect but cards that care about multiple entries for a given type will be judged higher if a card seems to provide even "more" synergy for it.
Example:
Chishiro has two abilities so `DeckHas:Ability$Token & Ability$Counters` is used, therefore score for `DeckNeeds:Ability$Token\|Counters` is increased -|`K`|Keyword +|`DeckHas`|specifies that the deck now has a certain ability (like, token generation or counters) so that the drafting/deckbuilding AI knows that it now meets requirements for DeckHints/DeckNeeds. This is actually very useful since many of these (such as `Ability$Graveyard, Ability$Token, Ability$Counters`) are not deduced by parsing the abilities, so an explicit hint is necessary. Using the other types is also supported in case the implicit parsing wouldn't find it.
It doesn't require exact matching to have an effect but cards that care about multiple entries for a given type will be judged higher if a card seems to provide even "more" synergy for it.
Example:
Chishiro has two abilities so `DeckHas:Ability$Token & Ability$Counters` is used, therefore score for `DeckNeeds:Ability$Token\|Counters` is increased +|`K`|Keyword (see below) |`Loyalty`|Number of starting loyalty counters |`ManaCost`|Cost to cast the card shown in mana shards

This property is required. It has a single parameter that is a mana cost.

* `ManaCost:no cost` for cards that cannot be cast
* `ManaCost:1 W W` sets the casting cost to {1}{W}{W} |`Name`|Name of the card

A string of text that serves as the name of the card. Note that the registered trademark symbol cannot be included, and this property must have at least one character.

Example:
* `Name:A Display of My Dark Power` sets the card's name to "A Display of My Dark Power" @@ -49,95 +46,21 @@ There are a few other Parameters that will appear in most, if not all, cards. Th Rarity and Set info are now defined in edition definition files. These can be found at /res/reditions path. -## General SVars +## Conventions +- filename: all lowercase, skip special characters, underscore for spaces +- Unix(LF) line endings +- use empty lines only when separating multiple faces on a card +- try to avoid writing default params to keep scripts concise + - e.g. just `SP$ Draw` instead of `SP$ Draw | Defined$ You | NumCards$ 1` -`SVar:SoundEffect:goblinpolkaband.mp3` +# Keywords -The sound system supports a special SVar that defines the sound that should be played when the spell is cast. +All keywords need to be prepended with "K:" to be parsed correctly. Each keyword must appear on a separate line. -`SVar:AltCost:[cost]` +## Keywords without Parameters -This SVar is for cards that have an Alternate cost, such as Force of -Will. You are allowed to pay the Alternate Cost instead of the normal -Mana cost when casting this spell. - -`SVar:AIPreference:SacCost$Creature.token,Creature.cmcLE2` - -`SVar:AntiBuffedBy:[ValidCards]` - -If a permanent with this SVar is on the battlefield under human control -the AI will play the specified cards in Main1. Applicable for cards like -Heart Sliver or Timid Drake. - -`SVar:BuffedBy:[ValidCards]` - -If a permanent with this SVar is on the battlefield under its control -the AI will play the specified cards in Main1. Applicable for creatures -with a P/T setting static ability (Kithkin Rabble) or additional buffes -(Radiant, Archangel). - -`SVar:EnchantMe:[Multiple]/[Once]` - -Creatures with "Multiple" in this SVar will always be prefered when the -AI enchants (Rabid Wombat), creatures with "Once" only if they are not -enchanted already (Gate Hound). - -`SVar:EquipMe:[Multiple]/[Once]` - -Creatures with "Multiple" in this SVar will always be prefered when the -AI equippes (Myr Adapter), creatures with "Once" only if they are not -equipped already (Kor Duelist). - -`SVar:AIEvaluationModifier:[ValidAmount]` - -`SVar:EndOfTurnLeavePlay:True` - -`SVar:maxLevel:` - -`SVar:HasCombatEffect:True` - -`SVar:HasAttackEffect:True` - -`SVar:HasBlockEffect:True` - -`SVar:MustAttack:True` - -`SVar:MustBeBlocked:True` - -`SVar:NeedsToPlayVar:[ValidCards]` - -`SVar:ManaNeededToAvoidNegativeEffect:` - -`SVar:NonStackingEffect:True` - -`SVar:PlayMain1:TRUE/ALWAYS/OPPONENTCREATURES` - -The AI will play cards with this SVar in its first main phase. Without other AILogic, it will usually not play any permanents without this in Main1. - -`SVar:SacMe:[number]` - -The AI will sacrifice these cards to pay costs. The higher the number the higher the priority. Example: Hatching Plans has SVar:SacMe:5. - -`SVar:Targeting:Dies` - -`SVar:UntapMe:True` - -The AI will prioritize untapping of this card. - -`SVar:AIUntapPreference:` - -`SVar:X:Count$` - -Count is our general value computation function. It's quite varied with a lot of different things it can calculate and is often being updated. - -## Keywords - -All keywords need to be prepended with "K:" to be parsed correctly. Each -keyword must appear on a separate line. - -### Keywords without Parameters - -This section is for Keywords that require no additional parameters and are one or two words long. Most of these you would see exactly on cards in the game. +This section is for Keywords that require no additional parameters and are one or two words long. Most of these you would see exactly on cards in the game.
+Examples: - Cascade - Changeling @@ -201,7 +124,7 @@ This section is for Keywords that require no additional parameters and are one o - Vigilance - Wither -### Keywords with parameters +## Keywords with parameters - Adapt:{cost} - AdjustLandPlays:{params} @@ -272,10 +195,11 @@ This section is for Keywords that require no additional parameters and are one o - UpkeepCost:{cost} - Vanishing:{TimeCounters} -### Longer Card Properties +## Plaintext keywords -Plaintext properties/triggers. This section is for Keywords that are two -words or longer. CARDNAME is replaced by the card's name. +These are hardcoded but not truly keywords rules-wise and will eventually be turned into static abilities. +Only listing the most common ones here so you can recognize them. +CARDNAME is replaced by the card's name ingame. - All creatures able to block CARDNAME do so. - CARDNAME assigns no combat damage @@ -352,10 +276,81 @@ words or longer. CARDNAME is replaced by the card's name. - You may have CARDNAME assign its combat damage as though it weren't blocked. - Your life total can't change. -## Developer Mode +# General SVars -[Forge\_DevMode](Forge_DevMode "wikilink") +`SVar:SoundEffect:goblinpolkaband.mp3` -## Remaining Cards +The sound system supports a special SVar that defines the sound that should be played when the spell is cast. - \ No newline at end of file +`SVar:X:Count$` + +Count is our general value computation function. It's quite varied with a lot of different things it can calculate and is often being updated. + +# AI specific SVars + +`SVar:AIPreference:SacCost$Creature.token,Creature.cmcLE2` + +`SVar:AntiBuffedBy:[ValidCards]` + +If a permanent with this SVar is on the battlefield under human control +the AI will play the specified cards in Main1. Applicable for cards like +Heart Sliver or Timid Drake. + +`SVar:BuffedBy:[ValidCards]` + +If a permanent with this SVar is on the battlefield under its control +the AI will play the specified cards in Main1. Applicable for creatures +with a P/T setting static ability (Kithkin Rabble) or additional buffes +(Radiant, Archangel). + +`SVar:EnchantMe:[Multiple]/[Once]` + +Creatures with "Multiple" in this SVar will always be prefered when the +AI enchants (Rabid Wombat), creatures with "Once" only if they are not +enchanted already (Gate Hound). + +`SVar:EquipMe:[Multiple]/[Once]` + +Creatures with "Multiple" in this SVar will always be prefered when the +AI equippes (Myr Adapter), creatures with "Once" only if they are not +equipped already (Kor Duelist). + +`SVar:AIEvaluationModifier:[ValidAmount]` + +`SVar:EndOfTurnLeavePlay:True` + +`SVar:maxLevel:` + +`SVar:HasCombatEffect:True` + +`SVar:HasAttackEffect:True` + +`SVar:HasBlockEffect:True` + +`SVar:MustAttack:True` + +`SVar:MustBeBlocked:True` + +`SVar:NeedsToPlayVar:[ValidCards]` + +`SVar:ManaNeededToAvoidNegativeEffect:` + +`SVar:NonStackingEffect:True` + +`SVar:PlayMain1:TRUE/ALWAYS/OPPONENTCREATURES` + +The AI will play cards with this SVar in its first main phase. Without other AILogic, it will usually not play any permanents without this in Main1. + +`SVar:SacMe:[number]` + +The AI will sacrifice these cards to pay costs. The higher the number the higher the priority. Example: Hatching Plans has SVar:SacMe:5. + +`SVar:Targeting:Dies` + +`SVar:UntapMe:True` + +The AI will prioritize untapping of this card. + +`SVar:AIUntapPreference:` + +`SVar:NoZeroToughnessAI:True` diff --git a/docs/Console-and-cheats.md b/docs/Console-and-cheats.md new file mode 100644 index 00000000000..43d75863bf0 --- /dev/null +++ b/docs/Console-and-cheats.md @@ -0,0 +1,43 @@ +Forge provides an in-game console in adventure mode. + +You can access (and close) the console while exploring by pressing F9 (or Fn-F9). + +To scroll the console window, click and drag the text box. + +## Available commands + +| Command Example | Description | +| -- | -- | +| resetMapQuests | Resets the map quests, resulting in all side-quest progress being lost and all side-quest types being re-picked | +| give gold 1000 | Give 1000 gold | +| give shards 1000 | Give 1000 shards | +| give print lea 232 | Add an alpha (LEA set code) black lotus (232 collector number) | +| give item | Adds an in game item such as leather boots | +| give set sld | Give 4 copies of every card in the Secret Lair Drop (set code SLD), flagged as having no sell value | +| give nosell card forest | Gives a forest with no sell value | +| give boosters leb | Add a booster from beta (LEB set code) | +| give quest 123 | Add the quest by its number ID | +| give life 10 | Add 10 life to yourself | +| give card forest | Adds a forest to your inventory | +| debug collision | Displays bounding boxes around entities | +| debug map | TODO | +| debug off | Turns off previously enable debugging | +| teleport to 6000 5000 | Moves you 6000 tiles east and 5000 tiles north from the left bottom corner | +| fullHeal | Returns your health back to baseline | +| sprint 100 | Increases your speed for 100 seconds | +| setColorId R | Sets the player color identity; Probably used for testing and shops | +| clearnosell | Clears the no sell value flag from all cards you own that are not used in a deck | +| remove enemy abc | Remove the enemy from the map with the map ID abc | +| remove enemy all | Remove all the enemies from the map | +| dumpEnemyDeckList | Print the enemy deck lists to terminal output stream | +| getShards amount 100 | Similar to give shards command; Gives 100 shards to the player | +| resetQuests | Resets the world quests. In progress quests are not abandonned or reset including dungeons. Does not reroll abandoned quests. Not really sure what this does. | +| hide 100 | Enemies do not chase you for 100 seconds | +| fly 100 | You can walk over obstacles for 100 seconds | +| crack | Cracks a random item you are wearing | +| spawn enemy Sliver | Spawns a Sliver on your screen | +| listPOI | Prints all locations in terminal output stream as ID-type pairings | +| leave | Gets you out of the current town/dungeon/cave | +| dumpEnemyColorIdentity | Prints all enemies, their colour affinity and deck name to terminal output | +| heal | Recover your full health | +| dumpEnemyDeckColors | Prints all decks available to enemies and their affinities | diff --git a/docs/Currency.md b/docs/Currency.md new file mode 100644 index 00000000000..f460e30f197 --- /dev/null +++ b/docs/Currency.md @@ -0,0 +1,46 @@ +There are many currencies in the game, and most of them can be interchanged. + +# Cards + +Acquired by: +- World drops +- Match reward +- Draft wins +- Shop purchases +- Quests + +Spent on: +- Selling to shops + +# Gold + +Acquired by: +- World drop +- Match reward +- Draft reward +- Quests + +Spent on: +- Cards and items from shops +- Drafts games +- Crafting cards +- Shards + +# Shards + +Acquired by +- World drop +- Quest reward + +Spent on +- Crafting cards +- Shop Re-rolls +- Gold exchange + +# Challenge Coins + +Acquired by +- At the start of the game + +Spent on +- Drafts \ No newline at end of file diff --git a/docs/Deck-Building-Tips.md b/docs/Deck-Building-Tips.md new file mode 100644 index 00000000000..7758414b7e9 --- /dev/null +++ b/docs/Deck-Building-Tips.md @@ -0,0 +1,28 @@ +# Adding basic lands and special arts + +You can add lands by clicking the triple dots icon in the right top of the deck building interface. + +Initially you only have access to jumpstart basic land arts - to get more, you need to purchase the landscape sketch books from the basic land shop (The Cartographers Guild). + +# 40-card deck recommendation + +40-card decks give you a much more predictable curve. + +In a 40-card deck, each individual card has a 2.5% chance of being drawn. + +In a 60-card deck, each individual card has a 1.6% chance of being drawn. + +When you use a smaller deck, the significance of each individual card is much higher and will give you more predictable performance. + +# Autosell + +When you click a card that is not part of any decks, you get the option to move it to auto-sell. + +The numbers in the interface indicate how many copies you have. + +To sell the cards you have marked, go to any town and enter the Inn. +In the Inn, the middle icon of the coin called Sell (E) will sell all your cards you have marked to sell. + +Individual card values vary by rarity and reputation with the town you sell them in. + +Since dying will cause you to lose a percentage of your gold, you may want to not sell all your cards immediately. diff --git a/docs/Home.md b/docs/Home.md index 47b1224bb98..b9b56a03071 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -1,5 +1,5 @@ > [!CAUTION] -> - if you want to contribute to this Wiki please only make pull requests against the main repos docs folder or your changes might get lost +> - if you want to contribute to this Wiki please only make pull requests against the main repositories *docs* folder or your changes might get lost > - due to GitHub limitations all filenames should be unique # What is Forge? diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 1cdeef09071..9820e519a6c 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -13,10 +13,13 @@ - [Getting Started](Gameplay-Guide.md) - [Different Planes](Different-Planes.md) + - [Currency](Currency.md) + - [Deck Building Tips](Deck-Building-Tips.md) - [Towns & Capitals](Towns-&-Capitals.md) - [Dungeons](Dungeons.md) - [Equipments and Items](Equipments-and-Items.md) - [Keyboard Shortcuts](Keyboard-Shortcuts.md) + - [Console and Cheats](Console-and-Cheats.md) - [Modding and Development](Modding-and-Development.md) @@ -36,9 +39,10 @@ - [Ability effects](Card-scripting-API/AbilityFactory.md) - [Triggers](Card-scripting-API/Triggers.md) - [Replacements](Card-scripting-API/Replacements.md) + - Statics - [Costs](Card-scripting-API/Costs.md) - [Affected / Targets](Card-scripting-API/Targeting.md) - - [Restrictions](Card-scripting-API/Restrictions.md) + - [Restrictions / Conditions](Card-scripting-API/Restrictions.md) - [Guide: Creating a Custom Card](Card-scripting-API/Creating-a-Custom-Card.md) - [Development]((SM-autoconverted)-how-to-get-started-developing-forge.md) diff --git a/forge-gui/res/cardsfolder/p/powerbalance.txt b/forge-gui/res/cardsfolder/p/powerbalance.txt index 9c5cc60e009..a7ef286ff66 100644 --- a/forge-gui/res/cardsfolder/p/powerbalance.txt +++ b/forge-gui/res/cardsfolder/p/powerbalance.txt @@ -2,7 +2,7 @@ Name:Powerbalance ManaCost:R R Types:Enchantment T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Opponent | OptionalDecider$ You | TriggerZones$ Battlefield | Execute$ TrigReveal | TriggerDescription$ Whenever an opponent casts a spell, you may reveal the top card of your library. If you do, you may cast that card without paying its mana cost if the two spells have the same mana value. -SVar:TrigReveal:DB$ PeekAndReveal | PeekAmount$ 1 | RevealOptional$ True | RememberRevealed$ True | SubAbility$ DBPlay +SVar:TrigReveal:DB$ PeekAndReveal | PeekAmount$ 1 | NoPeek$ True | RevealOptional$ True | RememberRevealed$ True | SubAbility$ DBPlay SVar:DBPlay:DB$ Play | Defined$ Remembered | ValidSA$ Spell.cmcEQX | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:TriggeredSpellAbility$CardManaCostLKI From 02031e159d8da0d42aa65ee1207347c2a7c91cf1 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:03:56 +0100 Subject: [PATCH 227/230] TLA/TLE cards 3rd November - Aftermath (#9075) --- .../res/cardsfolder/upcoming/appa_the_vigilant.txt | 12 ++++++++++++ .../upcoming/bumi_king_of_three_trials.txt | 12 ++++++++++++ .../res/cardsfolder/upcoming/cruel_administrator.txt | 9 +++++++++ .../cardsfolder/upcoming/earth_kingdom_jailer.txt | 9 +++++++++ forge-gui/res/cardsfolder/upcoming/earthshape.txt | 8 ++++++++ .../res/cardsfolder/upcoming/fire_nation_turret.txt | 9 +++++++++ .../res/cardsfolder/upcoming/fire_navy_trebuchet.txt | 10 ++++++++++ .../cardsfolder/upcoming/jasmine_dragon_tea_shop.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/sokkas_charge.txt | 8 ++++++++ forge-gui/res/cardsfolder/upcoming/swampbenders.txt | 8 ++++++++ .../res/cardsfolder/upcoming/the_earth_king.txt | 10 ++++++++++ 11 files changed, 103 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/appa_the_vigilant.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/bumi_king_of_three_trials.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/cruel_administrator.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/earth_kingdom_jailer.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/earthshape.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/fire_nation_turret.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/fire_navy_trebuchet.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/jasmine_dragon_tea_shop.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sokkas_charge.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/swampbenders.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/the_earth_king.txt diff --git a/forge-gui/res/cardsfolder/upcoming/appa_the_vigilant.txt b/forge-gui/res/cardsfolder/upcoming/appa_the_vigilant.txt new file mode 100644 index 00000000000..9edc1269b96 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/appa_the_vigilant.txt @@ -0,0 +1,12 @@ +Name:Appa, the Vigilant +ManaCost:5 W W +Types:Legendary Creature Bison Ally +PT:6/6 +K:Flying +K:Vigilance +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self,Ally.Other+YouCtrl | Execute$ TrigPumpAll | TriggerDescription$ Whenever NICKNAME or another Ally you control enters, creatures you control get +1/+1 and gain flying and vigilance until end of turn. +SVar:TrigPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl | NumAtt$ +1 | NumDef$ +1 | KW$ Flying & Vigilance +SVar:PlayMain1:TRUE +SVar:BuffedBy:Ally +AI:RemoveDeck:Random +Oracle:Flying, vigilance\nWhenever Appa or another Ally you control enters, creatures you control get +1/+1 and gain flying and vigilance until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/bumi_king_of_three_trials.txt b/forge-gui/res/cardsfolder/upcoming/bumi_king_of_three_trials.txt new file mode 100644 index 00000000000..b5dc24d79b0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bumi_king_of_three_trials.txt @@ -0,0 +1,12 @@ +Name:Bumi, King of Three Trials +ManaCost:5 G +Types:Legendary Creature Human Noble Ally +PT:4/4 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharm | TriggerDescription$ When NICKNAME enters, ABILITY +SVar:TrigCharm:DB$ Charm | MinCharmNum$ 0 | CharmNum$ X | Choices$ DBPutCounter,DBScry,DBEarthbend +SVar:DBPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ 3 | SpellDescription$ Put three +1/+1 counters on NICKNAME. +SVar:DBScry:DB$ Scry | ScryNum$ 3 | ValidTgts$ Player | SpellDescription$ Target player scries 3. +SVar:DBEarthbend:DB$ Earthbend | Num$ 3 | SpellDescription$ Earthbend 3. (Target land you control becomes a 0/0 creature with haste that's still a land. Put three +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) +SVar:X:Count$ValidGraveyard Lesson.YouOwn +DeckHints:Type$Lesson +Oracle:When Bumi enters, choose up to X, where X is the number of Lesson cards in your graveyard —\n• Put three +1/+1 counters on Bumi.\n• Target player scries 3.\n• Earthbend 3. (Target land you control becomes a 0/0 creature with haste that's still a land. Put three +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) diff --git a/forge-gui/res/cardsfolder/upcoming/cruel_administrator.txt b/forge-gui/res/cardsfolder/upcoming/cruel_administrator.txt new file mode 100644 index 00000000000..f1d2eacf957 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/cruel_administrator.txt @@ -0,0 +1,9 @@ +Name:Cruel Administrator +ManaCost:3 B R +Types:Creature Human Soldier +PT:5/4 +K:etbCounter:P1P1:1:CheckSVar$ RaidTest:Raid — CARDNAME enters with a +1/+1 counter on it if you attacked this turn. +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever this creature attacks, create a 2/2 red Soldier creature token with firebending 1. (Whenever it attacks, add {R}. This mana lasts until end of combat.) +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ r_2_2_soldier_firebending_1 | TokenOwner$ You +SVar:RaidTest:Count$AttackersDeclared +Oracle:Raid — This creature enters with a +1/+1 counter on it if you attacked this turn.\nWhenever this creature attacks, create a 2/2 red Soldier creature token with firebending 1. (Whenever it attacks, add {R}. This mana lasts until end of combat.) diff --git a/forge-gui/res/cardsfolder/upcoming/earth_kingdom_jailer.txt b/forge-gui/res/cardsfolder/upcoming/earth_kingdom_jailer.txt new file mode 100644 index 00000000000..0bf76968af5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/earth_kingdom_jailer.txt @@ -0,0 +1,9 @@ +Name:Earth Kingdom Jailer +ManaCost:2 W +Types:Creature Human Soldier Ally +PT:3/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When this creature enters, exile up to one target artifact, creature, or enchantment an opponent controls with mana value 3 or greater until this creature leaves the battlefield. +SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Artifact.OppCtrl+cmcGE3,Creature.OppCtrl+cmcGE3,Enchantment.OppCtrl+cmcGE3 | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target artifact, creature, or enchantment an opponent controls with mana value 3 or greater | Duration$ UntilHostLeavesPlay +SVar:PlayMain1:TRUE +SVar:OblivionRing:TRUE +Oracle:When this creature enters, exile up to one target artifact, creature, or enchantment an opponent controls with mana value 3 or greater until this creature leaves the battlefield. diff --git a/forge-gui/res/cardsfolder/upcoming/earthshape.txt b/forge-gui/res/cardsfolder/upcoming/earthshape.txt new file mode 100644 index 00000000000..06bbc5bbf05 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/earthshape.txt @@ -0,0 +1,8 @@ +Name:Earthshape +ManaCost:2 W +Types:Instant +A:SP$ Earthbend | Num$ 3 | SubAbility$ DBPumpAll | SpellDescription$ Earthbend 3. Then each creature you control with power less than or equal to that land's power gains hexproof and indestructible until end of turn. You gain hexproof until end of turn. +SVar:DBPumpAll:DB$ PumpAll | ValidCards$ Creature.YouCtrl+powerLEX | KW$ Hexproof & Indestructible | SubAbility$ DBPump +SVar:DBPump:DB$ Pump | Defined$ You | KW$ Hexproof +SVar:X:Targeted$CardPower +Oracle:Earthbend 3. Then each creature you control with power less than or equal to that land's power gains hexproof and indestructible until end of turn.\nYou gain hexproof until end of turn. diff --git a/forge-gui/res/cardsfolder/upcoming/fire_nation_turret.txt b/forge-gui/res/cardsfolder/upcoming/fire_nation_turret.txt new file mode 100644 index 00000000000..81cbf173eea --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fire_nation_turret.txt @@ -0,0 +1,9 @@ +Name:Fire Nation Turret +ManaCost:2 R +Types:Artifact +T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ At the beginning of combat on your turn, up to one target creature gets +2/+0 and gains firebending 2 until end of turn. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select up to one target creature | TargetMin$ 0 | TargetMax$ 1 | NumAtt$ +2 | KW$ Firebending:2 +A:AB$ PutCounter | Cost$ R | Defined$ Self | CounterType$ CHARGE | CounterNum$ 1 | SpellDescription$ Put a charge counter on this artifact. +A:AB$ DealDamage | Cost$ SubCounter<50/CHARGE> | ValidTgts$ Any | NumDmg$ 50 | SpellDescription$ It deals 50 damage to any target. +SVar:PlayMain1:TRUE +Oracle:At the beginning of combat on your turn, up to one target creature gets +2/+0 and gains firebending 2 until end of turn.\n{R}: Put a charge counter on this artifact.\nRemove fifty charge counters from this artifact: It deals 50 damage to any target. diff --git a/forge-gui/res/cardsfolder/upcoming/fire_navy_trebuchet.txt b/forge-gui/res/cardsfolder/upcoming/fire_navy_trebuchet.txt new file mode 100644 index 00000000000..8183b76d688 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/fire_navy_trebuchet.txt @@ -0,0 +1,10 @@ +Name:Fire Navy Trebuchet +ManaCost:2 B +Types:Artifact Creature Wall +PT:0/4 +K:Defender +K:Reach +T:Mode$ AttackersDeclared | ValidAttackers$ Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you attack, create a 2/1 colorless Construct artifact creature token with flying named Ballistic Boulder that's tapped and attacking. Sacrifice that token at the beginning of the next end step. +SVar:TrigToken:DB$ Token | TokenScript$ ballistic_boulder | TokenTapped$ True | TokenAttacking$ True | AtEOT$ Sacrifice +DeckHas:Ability$Token & Type$Artifact|Construct +Oracle:Defender, reach\nWhenever you attack, create a 2/1 colorless Construct artifact creature token with flying named Ballistic Boulder that's tapped and attacking. Sacrifice that token at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/upcoming/jasmine_dragon_tea_shop.txt b/forge-gui/res/cardsfolder/upcoming/jasmine_dragon_tea_shop.txt new file mode 100644 index 00000000000..e7ba68eb70f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/jasmine_dragon_tea_shop.txt @@ -0,0 +1,8 @@ +Name:Jasmine Dragon Tea Shop +ManaCost:no cost +Types:Land +A:AB$ Mana | Cost$ T | Produced$ C | SpellDescription$ Add {C}. +A:AB$ Mana | Cost$ T | Produced$ Any | RestrictValid$ Spell.Ally,Activated.Ally | SpellDescription$ Add one mana of any color. Spend this mana only to cast an Ally spell or activate an ability of an Ally source. +A:AB$ Token | Cost$ 5 T | TokenAmount$ 1 | TokenScript$ w_1_1_ally | TokenOwner$ You | SpellDescription$ Create a 1/1 white Ally creature token. +DeckNeeds:Type$Ally +Oracle:{T}: Add {C}.\n{T}: Add one mana of any color. Spend this mana only to cast an Ally spell or activate an ability of an Ally source.\n{5}, {T}: Create a 1/1 white Ally creature token. diff --git a/forge-gui/res/cardsfolder/upcoming/sokkas_charge.txt b/forge-gui/res/cardsfolder/upcoming/sokkas_charge.txt new file mode 100644 index 00000000000..7f9dbefdf65 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sokkas_charge.txt @@ -0,0 +1,8 @@ +Name:Sokka's Charge +ManaCost:3 W +Types:Enchantment +S:Mode$ Continuous | Affected$ Ally.YouCtrl | Condition$ PlayerTurn | AddKeyword$ Double Strike & Lifelink | Description$ During your turn, Allies you control have double strike and lifelink. +SVar:NonStackingEffect:True +SVar:PlayMain1:TRUE +DeckNeeds:Type$Ally +Oracle:During your turn, Allies you control have double strike and lifelink. diff --git a/forge-gui/res/cardsfolder/upcoming/swampbenders.txt b/forge-gui/res/cardsfolder/upcoming/swampbenders.txt new file mode 100644 index 00000000000..36c481dfcb0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/swampbenders.txt @@ -0,0 +1,8 @@ +Name:Swampbenders +ManaCost:4 G G +Types:Creature Human Druid Ally +PT:*/* +S:Mode$ Continuous | CharacteristicDefining$ True | SetPower$ X | SetToughness$ X | Description$ CARDNAME's power and toughness are each equal to the number of Swamps on the battlefield. +S:Mode$ Continuous | Affected$ Land.YouCtrl | AddType$ Swamp | Description$ Lands you control are Swamps in addition to their other types. +SVar:X:Count$Valid Swamp +Oracle:Swampbenders's power and toughness are each equal to the number of Swamps on the battlefield.\nLands you control are Swamps in addition to their other types. diff --git a/forge-gui/res/cardsfolder/upcoming/the_earth_king.txt b/forge-gui/res/cardsfolder/upcoming/the_earth_king.txt new file mode 100644 index 00000000000..70237feb400 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_earth_king.txt @@ -0,0 +1,10 @@ +Name:The Earth King +ManaCost:3 G +Types:Legendary Creature Human Noble Ally +PT:2/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters, create a 4/4 green Bear creature token. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_4_4_bear | TokenOwner$ You +T:Mode$ AttackersDeclared | ValidAttackers$ Creature.YouCtrl+powerGE4 | Execute$ TrigSearch | TriggerZones$ Battlefield | TriggerDescription$ Whenever one or more creatures you control with power 4 or greater attack, search your library for up to that many basic land cards, put them onto the battlefield tapped, then shuffle. +SVar:TrigSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ X +SVar:X:TriggerObjectsAttackers$Amount +Oracle:When The Earth King enters, create a 4/4 green Bear creature token.\nWhenever one or more creatures you control with power 4 or greater attack, search your library for up to that many basic land cards, put them onto the battlefield tapped, then shuffle. From f9c5fc73177ebd7f6e1d208606d725fc766dd753 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:20:02 +0100 Subject: [PATCH 228/230] Iroh's Demonstration (TLA) (#9076) --- forge-gui/res/cardsfolder/upcoming/irohs_demonstration.txt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/irohs_demonstration.txt diff --git a/forge-gui/res/cardsfolder/upcoming/irohs_demonstration.txt b/forge-gui/res/cardsfolder/upcoming/irohs_demonstration.txt new file mode 100644 index 00000000000..073741504d1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/irohs_demonstration.txt @@ -0,0 +1,7 @@ +Name:Iroh's Demonstration +ManaCost:1 R +Types:Sorcery Lesson +A:SP$ Charm | Choices$ DBDamageAll,DBDamage +SVar:DBDamageAll:DB$ DamageAll | ValidCards$ Creature.OppCtrl | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to each creature your opponents control. +SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to target creature. +Oracle:Choose one —\n• Iroh's Demonstration deals 1 damage to each creature your opponents control.\n• Iroh's Demonstration deals 4 damage to target creature. From f25d8c58d9ebe31165265d063010200af0ea1c5b Mon Sep 17 00:00:00 2001 From: Paul Hammerton Date: Tue, 4 Nov 2025 09:25:28 +0000 Subject: [PATCH 229/230] Edition updates: PSPL, PURL, PW25, SLC, SLD, TLA, TLE --- .../Avatar The Last Airbender Eternal.txt | 29 ++++++- .../editions/Avatar The Last Airbender.txt | 76 +++++++++++++++++++ ...et Lair 30th Anniversary Countdown Kit.txt | 25 ++++++ .../res/editions/Secret Lair Drop Series.txt | 1 + forge-gui/res/editions/Spotlight Series.txt | 7 +- .../res/editions/URL Convention Promos.txt | 3 + .../editions/Wizards Play Network 2025.txt | 2 + 7 files changed, 139 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt b/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt index b613fad7a8c..e44ef240995 100644 --- a/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt +++ b/forge-gui/res/editions/Avatar The Last Airbender Eternal.txt @@ -7,22 +7,49 @@ ScryfallCode=TLE [cards] 1 M Brought Back @Viacom +3 M Empty City Ruse @Viacom 5 M Release to Memory @Viacom +6 M Scout's Warning @Viacom +7 M Teferi's Protection ${"flavorName": "Aang's Shelter"} 9 M Agent of Treachery @Viacom 10 M Bribery @Viacom +11 M Clone @Viacom +12 M Clone Legion @Viacom 13 M Force of Negation @Viacom +14 M Imprisoned in the Moon @Viacom 16 M Mystic Remora @Viacom 17 M Prosperity @Viacom +19 M Standstill @Viacom +20 M Training Grounds @Viacom +23 M Bloodchief Ascension @Viacom ${"flavorName": "Bloodbender's Rise"} 24 M Cruel Tutor @Viacom +25 M Noxious Gearhulk @Viacom ${"flavorName": "Fire Nation Tank Train"} +26 M Blasphemous Act @Viacom +28 M Dockside Extortionist @Viacom 33 M Mirrorwing Dragon @Viacom +34 M Rending Volley @Viacom +35 M Searing Blood @Viacom +36 M Shattering Spree @Viacom +39 M Beastmaster Ascension @Viacom 41 M The Great Henge @Viacom ${"flavorName": "The Banyan Tree"} +42 M Heartbeat of Spring @Viacom 43 M Heroic Intervention @Viacom +44 M Return of the Wildspeaker @Viacom ${"flavorName": "Earth Rumble Triumph"} 45 M Rites of Flourishing @Viacom 48 M Eladamri's Call @Viacom ${"flavorName": "Lifelong Friendship"} +51 M Koma, Cosmos Serpent @Viacom ${"flavorName": "The Monstrous Serpent"} 52 M Rhys the Redeemed @Viacom +53 M Cityscape Leveler @Viacom +55 M Sundial of the Infinite @Viacom 56 M Dark Depths @Viacom ${"flavorName": "The Boy in the Iceberg"} 61 M Valakut, the Molten Pinnacle @Viacom ${"flavorName": "Volcano of Roku's Island"} +62 R Appa, the Vigilant @Fahmi Fauzi 63 R Katara's Reversal @Fahmi Fauzi +64 R Fire Nation Turret @Fahmi Fauzi +65 R Swampbenders @Fahmi Fauzi +66 R Sokka's Charge @Fahmi Fauzi +67 R Earthshape @Fahmi Fauzi +68 R Mai and Zuko @Brian Yuen 69 R Aang and Katara @Brian Yuen 70 R Toph, Greatest Earthbender @Brian Yuen 71 R Sokka and Suki @Brian Yuen @@ -42,7 +69,7 @@ ScryfallCode=TLE 139 M Hei Bai, Forest Guardian @TAPIOCA 145 M Toph, Earthbending Master @Phima 165 R Fiery Confluence @Salvatorre Zee Yazzie -167 R Descendants' Path +167 R Descendants' Path @Pauline Voss 210 R Aang, Air Nomad @Jinho Bae 211 C Aang's Defense @Jo Cordisco 212 C Aardvark Sloth @Ionomycin diff --git a/forge-gui/res/editions/Avatar The Last Airbender.txt b/forge-gui/res/editions/Avatar The Last Airbender.txt index 316d14db869..b517dbe0a4c 100644 --- a/forge-gui/res/editions/Avatar The Last Airbender.txt +++ b/forge-gui/res/editions/Avatar The Last Airbender.txt @@ -13,13 +13,19 @@ ScryfallCode=TLA 5 R Aang's Iceberg @Matteo Bassini 6 R Airbender Ascension @Shiren 8 C Airbending Lesson @Pisukev +9 U Appa, Loyal Sky Bison @Tomoyo Asatani 10 M Appa, Steadfast Guardian @Maël Ollivier-Henry 11 C Avatar Enthusiasts @Leanna Crossan +12 R Avatar's Wrath @Ainezu 13 C Compassionate Healer @Kuno +16 U Earth Kingdom Jailer @Danciao +18 U Enter the Avatar State @Shiren 19 U Fancy Footwork @Mizutametori +20 U Gather the White Lotus @Kozato 21 C Glider Kids @ikeda_cpt 22 U Glider Staff @Eduardo Francisco 25 C Jeong Jeong's Deserters @Leonardo Borazio +27 M The Legend of Yangchen @Kuno 28 U Master Piandao @Brian Yuen 29 R Momo, Friendly Flier @Brandon L. Hunt 30 U Momo, Playful Pet @Awanqi (Angela Wang) @@ -27,6 +33,8 @@ ScryfallCode=TLA 32 C Rabaroo Troop @Mizutametori 33 C Razor Rings @Norikatsu Miyoshi 36 U Southern Air Temple @Salvatorre Zee Yazzie +37 R Suki, Courageous Rescuer @Tky +39 M United Front @Mengxuan Li 42 U Water Tribe Rallier @Boell Oyino 43 C Yip Yip! @Cinkai 49 C First-Time Flyer @Mizutametori @@ -37,6 +45,7 @@ ScryfallCode=TLA 54 U Gran-Gran @Arou 55 U Honest Work @ikeda_cpt 56 C Iguana Parrot @Tyler Smith +57 U Invasion Submersible @Sylvain Sarrailh 58 C It'll Quench Ya! @Nathaniel Himawan 59 U Katara, Bending Prodigy @Mephisto 61 M The Legend of Kuruk @Toshiaki Takayama @@ -44,14 +53,18 @@ ScryfallCode=TLA 63 U Master Pakku @Olena Richards 64 R The Mechanist, Aerial Artisan @Le Vuong 65 U North Pole Patrol @Rose Benjamin +66 C Octopus Form @Norikatsu Miyoshi 67 C Otter-Penguin @Eilene Cherie 68 C Rowdy Snowballers @Mizutametori 69 M Secret of Bloodbending @Olena Richards 70 U Serpent of the Pass @Eiji Kaneda 71 U Sokka's Haiku @Bun Toujo +72 U The Spirit Oasis @Slawek Fedorczuk +73 R Spirit Water Revival @Enishi 74 U Teo, Spirited Glider @Robin Har 75 R Tiger-Seal @Jinho Bae 76 R Ty Lee, Chi Blocker @Gemi +77 R The Unagi of Kyoshi Island @Miho Midorikawa 78 M Wan Shi Tong, Librarian @Ryota Murayama 80 C Waterbending Lesson @Sylvain Sarrailh 82 C Watery Grasp @Rose Benjamin @@ -61,54 +74,75 @@ ScryfallCode=TLA 86 C Beetle-Headed Merchants @Norikatsu Miyoshi 87 R Boiling Rock Rioter @Airi Yoshihisa 88 U Buzzard-Wasp Colony @Thomas Chamberlain-Keen +89 C Callous Inspector @Enishi 91 U Cat-Gator @Joseph Weston 92 C Corrupt Court Official @Norikatsu Miyoshi 93 C Dai Li Indoctrination @Lius Lasahido 94 R Day of Black Sun @Matteo Bassini +95 C Deadly Precision @Yuhong Ding 96 U Epic Downfall @Hristo D. Chukov 98 R The Fire Nation Drill @Brandon L. Hunt 99 U Fire Nation Engineer @Norikatsu Miyoshi 102 R Foggy Swamp Visions @Hori Airi 103 U Heartless Act @Sylvain Sarrailh 104 C Hog-Monkey @Miho Midorikawa +106 U June, Bounty Hunter @Shiren +109 R Mai, Scornful Striker @Hori Airi 110 C Merchant of Many Hats @Boell Oyino 111 U Northern Air Temple @Slawek Fedorczuk 113 U Ozai's Cruelty @ikeda_cpt 117 M The Rise of Sozin @Mitori +119 C Sold Out @Nijihayashi 121 U Tundra Tank @Shishizaru +125 C Bumi Bash @Maël Ollivier-Henry 126 U The Cave of Two Lovers @Ittoku 129 U Crescent Island Temple @Luc Courtois 130 C Cunning Maneuver @Robin Har 131 C Deserter's Disciple @HAIKEI 132 M Fated Firepower @Toshiaki Takayama 133 U Fire Nation Attacks @Claudiu-Antoniu Magherusan +134 C Fire Nation Cadets @Rafater 136 U Fire Sages @Yuu Fujiki +137 R Firebender Ascension @Tetsuko 139 R Firebending Student @Kozato 140 C How to Start a Riot @Robin Olausson +142 U Jeong Jeong, the Deserter @Danciao +143 U Jet's Brainwashing @Enishi 144 R The Last Agni Kai @Pablo Rivera 145 M The Legend of Roku @Song Qijin 146 C Lightning Strike @Jo Cordisco +147 U Mai, Jaded Edge @Toraji 148 C Mongoose Lizard @Joseph Weston +149 U Price of Freedom @Kotakan 150 R Ran and Shaw @Miho Midorikawa 151 R Redirect Lightning @Toni Infante 152 C Rough Rhino Cavalry @Yuhong Ding 153 U Solstice Revelations @Kotakan 154 M Sozin's Comet @Salvatorre Zee Yazzie +155 U Tiger-Dillo @John Di Giovanni +156 C Treetop Freedom Fighters @AKAGI +157 U Twin Blades @Jo Cordisco 158 U Ty Lee, Artful Acrobat @Rose Benjamin 159 U War Balloon @Matteo Bassini 161 C Yuyan Archers @Domco. 163 U Zuko, Exiled Prince @Nijihayashi 164 U Allies at Last @Evan Shipard +165 R Avatar Destiny @Iwamoto05 166 C Badgermole @Matteo Bassini 167 M Badgermole Cub @Nathaniel Himawan +169 U Bumi, King of Three Trials @Thomas Chamberlain-Keen 170 C Cycle of Renewal @Jocelin Carmes 171 R Diligent Zookeeper @Maojin Lee +172 R The Earth King @Ryota Murayama +173 U Earth Kingdom General @Alexandr Leskinen 174 U Earth Rumble @Olena Richards 176 C Earthbending Lesson @Toni Infante +178 R Elemental Teachings @Yoshioka 179 U Flopsie, Bumi's Buddy @Alexandr Leskinen 182 U Haru, Hidden Talent @Mitori 183 U Invasion Tactics @Eduardo Francisco 185 U Leaves from the Vine @Ittoku +187 C Origin of Metalbending @Pauline Voss 188 C Ostrich-Horse @Pablo Rivera 189 C Pillar Launch @Jo Cordisco 190 C Raucous Audience @ikeda_cpt @@ -116,50 +150,75 @@ ScryfallCode=TLA 193 C Rocky Rebuke @Hokyoung Kim 194 C Saber-Tooth Moose-Lion @Shiren 195 U Seismic Sense @Jo Cordisco +196 U Shared Roots @Alfven Ato 197 U Sparring Dummy @Gemi 198 U Toph, the Blind Bandit @Yueko 199 U True Ancestry @Chibi 200 C Turtle-Duck @Sylvain Sarrailh 201 U Unlucky Cabbage Merchant @Thomas Chamberlain-Keen 203 R Aang, at the Crossroads @Evan Shipard +204 R Aang, Swift Savior @Tetsuko 205 C Abandon Attachments @Shahab Alizadeh 207 M Avatar Aang @Fahmi Fauzi 208 R Azula, Cunning Usurper @Evyn Fong +209 R Beifong's Bounty Hunters @Alexandr Leskinen +210 U Bitter Work @Bun Toujo 211 M Bumi, Unleashed @Toni Infante 212 C Cat-Owl @Thomas Chamberlain-Keen +213 U Cruel Administrator @Norikatsu Miyoshi 214 U Dai Li Agents @Eduardo Francisco 215 U Dragonfly Swarm @John Di Giovanni 216 C Earth Kingdom Soldier @Rafater 217 R Earth King's Lieutenant @Nathaniel Himawan +218 C Earth Rumble Wrestlers @Thomas Chamberlain-Keen 219 C Earth Village Ruffians @Dom Lay 220 R Fire Lord Azula @Fahmi Fauzi 221 R Fire Lord Zuko @Jo Cordisco +222 U Foggy Swamp Spirit Keeper @Airi Yoshihisa 223 U Guru Pathik @Dee Nguyen 224 U Hama, the Bloodbender @Le Vuong 225 U Hei Bai, Spirit of Balance @Tyler Smith 227 R Iroh, Grand Lotus @Fahmi Fauzi +228 R Iroh, Tea Master @Brian Yuen +229 U Jet, Freedom Fighter @Fahmi Fauzi 230 R Katara, the Fearless @Hisashi Momose 231 R Katara, Water Tribe's Hope @Toraji 232 R The Lion-Turtle @Yuumei 233 U Long Feng, Grand Secretariat @Robin Har 235 M Ozai, the Phoenix King @Kekai Kotaki +236 C Platypus-Bear @Maël Ollivier-Henry 237 C Pretending Poxbearers @Salvatorre Zee Yazzie 240 R Sokka, Bold Boomeranger @Toni Infante 241 U Sokka, Lateral Strategist @Axel Sauerwald 242 R Sokka, Tenacious Tactician @Robin Har 243 U Suki, Kyoshi Warrior @Yuhong Ding 244 U Sun Warriors @Boell Oyino +245 U Tolls of War @Jocelin Carmes 247 R Toph, the First Metalbender @Eilene Cherie 248 U Uncle Iroh @Kieran Yanner 249 C Vindictive Warden @Jo Cordisco 251 U White Lotus Reinforcements @Kotakan +252 U Zhao, Ruthless Admiral @Yuumei 253 R Zuko, Conflicted @Halil Ural 254 C Barrels of Blasting Jelly @Salvatorre Zee Yazzie 255 C Bender's Waterskin @Dee Nguyen +256 U Fire Nation Warship @Hisashi Momose 259 M Planetarium of Wan Shi Tong @Robin Olausson 260 U Trusty Boomerang @Toni Infante 262 M White Lotus Tile @Dee Nguyen +265 C Airship Engine Room @Andreas Rocha +267 C Boiling Rock Prison @Matteo Bassini +268 R Fire Nation Palace @Awanqi (Angela Wang) +269 C Foggy Bottom Swamp @Dom Lay +270 R Jasmine Dragon Tea Shop @Leanna Crossan +271 C Kyoshi Village @Luc Courtois +272 C Meditation Pools @Luc Courtois +273 C Misty Palms Oasis @Andreas Rocha +274 C North Pole Gates @Andreas Rocha +275 C Omashu City @Andreas Rocha 278 R Secret Tunnel @Alexander Forssberg +279 C Serpent's Pass @Matteo Bassini +280 C Sun-Blessed Peak @Dom Lay 281 U White Lotus Hideout @Luc Courtois 282 L Plains @Slawek Fedorczuk 283 L Island @Maojin Lee @@ -177,6 +236,7 @@ ScryfallCode=TLA 295 L Mountain @Maojin Lee 296 L Forest @Slawek Fedorczuk 297 M Fated Firepower @Claudiu-Antoniu Magherusan +298 R Aang, Swift Savior @Claudiu-Antoniu Magherusan 299 U Fire Nation Attacks @Claudiu-Antoniu Magherusan 303 R Azula, Cunning Usurper @Toni Infante 304 R Aang, at the Crossroads @Toni Infante @@ -185,12 +245,14 @@ ScryfallCode=TLA 308 M Avatar Aang @Dominik Mayer 309 M Sozin's Comet @Dominik Mayer 311 M Ozai, the Phoenix King @Dominik Mayer +312 R Firebender Ascension @Dominik Mayer 313 R Fire Lord Azula @Dominik Mayer 314 R The Last Agni Kai @Dominik Mayer 315 R Fire Lord Zuko @Dominik Mayer 316 M Appa, Steadfast Guardian @Ilse Gort 317 R Momo, Friendly Flier @Filip Burburan 318 R Tiger-Seal @Andrea Piparo +319 R The Unagi of Kyoshi Island @Antonio José Manzanedo 320 M Wan Shi Tong, Librarian @Andrea Piparo 321 R The Fire Nation Drill @Ben Hill 325 R Ran and Shaw @Antonio José Manzanedo @@ -198,7 +260,9 @@ ScryfallCode=TLA 327 R Diligent Zookeeper @Andrea Piparo 328 R The Lion-Turtle @Filip Burburan 330 M White Lotus Tile @Antonio José Manzanedo +331 M United Front @JungShan 332 M Sozin's Comet @JungShan +333 R Avatar Destiny @Flavio Girón 334 R Fire Lord Azula @JungShan 335 M Ozai, the Phoenix King @Sidharth Chaturvedi 336 R Aang's Iceberg @Brigitte Roka & Clifton Stommel @@ -208,29 +272,41 @@ ScryfallCode=TLA 341 M Fated Firepower @Brigitte Roka 342 R Firebending Student @Ina Wong 343 R Redirect Lightning @Perci Chen +344 R The Earth King @Brigitte Roka & Clifton Stommel 346 R Aang, at the Crossroads @Brigitte Roka & Clifton Stommel +347 R Aang, Swift Savior @Shane Beresford 348 M Bumi, Unleashed @Brigitte Roka 349 R Iroh, Grand Lotus @Dalton Pencarinha 350 R Katara, the Fearless @Barbara Rosiak 351 R Katara, Water Tribe's Hope @Chun Lo 352 R Sokka, Tenacious Tactician @Faustine Dumontier 353 R Toph, the First Metalbender @Barbara Rosiak +354 M The Legend of Yangchen @Sija Hong 355 M The Legend of Kuruk @Barbara Rosiak 356 M The Rise of Sozin @Barbara Rosiak 357 M The Legend of Roku @Barbara Rosiak +359 R Aang, Swift Savior @Flavio Girón 360 R Fire Lord Zuko @Flavio Girón 361 R Katara, the Fearless @Flavio Girón 362 R Toph, the First Metalbender @Flavio Girón 363 M Avatar Aang @Bryan Konietzko 364 R Airbender Ascension @Shiren +365 R Avatar's Wrath @Ainezu 366 R Hakoda, Selfless Commander @Rafater +368 R Suki, Courageous Rescuer @Tky 369 R The Mechanist, Aerial Artisan @Le Vuong +370 R Spirit Water Revival @Enishi 371 R Ty Lee, Chi Blocker @Gemi 372 R Boiling Rock Rioter @Airi Yoshihisa 373 R Day of Black Sun @Matteo Bassini +374 R Mai, Scornful Striker @Hori Airi +379 R Beifong's Bounty Hunters @Alexandr Leskinen 380 R Earth King's Lieutenant @Nathaniel Himawan +381 R Iroh, Tea Master @Brian Yuen 383 R Sokka, Bold Boomeranger @Toni Infante 385 M Planetarium of Wan Shi Tong @Robin Olausson +389 R Fire Nation Palace @Awanqi (Angela Wang) +390 R Jasmine Dragon Tea Shop @Leanna Crossan 392 R Secret Tunnel @Alexander Forssberg 393 R Firebending Student @Airi Yoshihisa 394 R Momo, Friendly Flier @Ryota Murayama diff --git a/forge-gui/res/editions/Secret Lair 30th Anniversary Countdown Kit.txt b/forge-gui/res/editions/Secret Lair 30th Anniversary Countdown Kit.txt index f32810897d7..c3439e97d4b 100644 --- a/forge-gui/res/editions/Secret Lair 30th Anniversary Countdown Kit.txt +++ b/forge-gui/res/editions/Secret Lair 30th Anniversary Countdown Kit.txt @@ -6,7 +6,32 @@ Type=Collector_Edition ScryfallCode=SLC [cards] +1 R Altar of the Brood @Josh Freydkis +2 R Brain Freeze @Your Cinema +3 R Crop Rotation @Marcuscus 4 R Demonic Consultation @Edward Steed +5 R Eerie Ultimatum @Alexander Khabbazi +6 R Field of the Dead @Colby Nichols +7 R Gray Merchant of Asphodel @Jack Hughes +8 R Hymn to Tourach @Sophy Hollington +9 R Isochron Scepter @Kieran Yanner +10 M Junji, the Midnight Sky @Diego Andrade +11 R Krark-Clan Ironworks @Jimmy Knives +12 R Llanowar Elves @Ampreh +13 R Myrel, Shield of Argive @Molly Mendoza +14 R Narset's Reversal @Ben Newman +15 M Ob Nixilis, the Fallen @Skinner +16 R Phyrexian Altar @See Machine +17 M Questing Beast @Dan Hipp +18 R Retrofitter Foundry @Nikki Radan +19 R Sol Ring @Souther Salazar +20 R Temple of the False God @Jon Vermilyea +21 R Urza's Saga @Sadboi +22 R Vesuva @Stephen Andrade +23 R Wasteland @Aaron J. Riley +24 R Xantcha, Sleeper Agent @Matthew Southworth +25 M Yarok, the Desecrated @Cacho Rubione +26 R Zo-Zu the Punisher @Mattias Lindström 1993 R Shivan Dragon @Justine Jones 1994 R Mishra's Factory @DXTR 1995 M Necropotence @Rafal Wechterowicz (Too Many Skulls) diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 43c29953ace..a2d76690f14 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -792,6 +792,7 @@ F798 M Discord, Lord of Disharmony @Narendra Bintara Adi 816 R Seven Dwarves @Lisa Hanawalt 817 R Seven Dwarves @S.Britt 818 R Seven Dwarves @Keith Shore +819 R Seven Dwarves @Jesse LeDoux 820 R Arcane Signet @Rovina Cai 820★ R Arcane Signet @Rovina Cai 821 R Echo of Eons @Alex Petty diff --git a/forge-gui/res/editions/Spotlight Series.txt b/forge-gui/res/editions/Spotlight Series.txt index 0cb857490e1..80379c7d79a 100644 --- a/forge-gui/res/editions/Spotlight Series.txt +++ b/forge-gui/res/editions/Spotlight Series.txt @@ -9,6 +9,7 @@ ScryfallCode=PSPL 1 R Terror of the Peaks @Richard Kane Ferguson 2 M Kaldra Compleat @Monztre 3 M Sword of Forge and Frontier @Sam Guay -5 M Cloud, Midgar Mercenary @Tetsuya Nomura -6 R Get Lost @Dominik Mayer -7 R Spectacular Spider-Man @Julian Totino Tedesco +4 M Cloud, Midgar Mercenary @Tetsuya Nomura +5 R Get Lost @Dominik Mayer +6 R Spectacular Spider-Man @Julian Totino Tedesco +7 R Day of Black Sun @Dom Lay diff --git a/forge-gui/res/editions/URL Convention Promos.txt b/forge-gui/res/editions/URL Convention Promos.txt index 3a27ae3f5d8..dde7dae0b3c 100644 --- a/forge-gui/res/editions/URL Convention Promos.txt +++ b/forge-gui/res/editions/URL Convention Promos.txt @@ -15,6 +15,9 @@ ScryfallCode=PURL 8 C Aeronaut Tinkerer @Willian Murai 23 R Kor Skyfisher @Dan Murayama Scott 34★ U Shepherd of the Lost @Kekai Kotaki +324 R White Orchid Phantom @Richard Kane Ferguson 445 M Ral, Monsoon Mage @Borja Pindado 2025-1 M Hylda of the Icy Crown @Yakotakos 2025-3 R Katara, the Fearless @Yueko +2025-4 M Behold the Sinister Six! @Jonas De Ro +2025-5 R Unbreakable Formation @Xavier Ribeiro diff --git a/forge-gui/res/editions/Wizards Play Network 2025.txt b/forge-gui/res/editions/Wizards Play Network 2025.txt index 773943cd5a5..80a76fee35f 100644 --- a/forge-gui/res/editions/Wizards Play Network 2025.txt +++ b/forge-gui/res/editions/Wizards Play Network 2025.txt @@ -19,3 +19,5 @@ ScryfallCode=PW25 11 R Mary Jane Watson @Paolo Rivera 12 R Ultimate Green Goblin @Tyler Walpole 13 R Carnage, Crimson Chaos @Lucio Parrillo +14 R Gran-Gran @Mizutametori +15 R Unlucky Cabbage Merchant @Thanh Tuấn From 0bb67ec8d01a4546af2cc521ec8135817e0a25f7 Mon Sep 17 00:00:00 2001 From: Fulgur14 <54345051+Fulgur14@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:20:26 +0100 Subject: [PATCH 230/230] TLA cards, 3rd November (#9072) Firebender Ascension and support --- .../game/ability/effects/ScryEffect.java | 5 +--- .../game/trigger/TriggerAbilityTriggered.java | 24 ++++++++++--------- .../res/cardsfolder/d/drannith_magistrate.txt | 2 +- .../res/cardsfolder/f/fervent_mastery.txt | 3 +-- .../upcoming/airship_engine_room.txt | 9 +++++++ .../cardsfolder/upcoming/avatars_wrath.txt | 9 +++++++ .../upcoming/beifongs_bounty_hunters.txt | 8 +++++++ .../res/cardsfolder/upcoming/bitter_work.txt | 7 ++++++ .../upcoming/boiling_rock_prison.txt | 9 +++++++ .../upcoming/callous_inspector.txt | 9 +++++++ .../upcoming/earth_kingdom_general.txt | 12 ++++++++++ .../upcoming/elemental_teachings.txt | 8 +++++++ .../upcoming/enter_the_avatar_state.txt | 5 ++++ .../upcoming/firebender_ascension.txt | 9 +++++++ .../upcoming/foggy_bottom_swamp.txt | 9 +++++++ .../upcoming/jeong_jeong_the_deserter.txt | 10 ++++++++ .../cardsfolder/upcoming/kyoshi_village.txt | 9 +++++++ .../upcoming/mai_scornful_striker.txt | 8 +++++++ .../cardsfolder/upcoming/meditation_pools.txt | 9 +++++++ .../upcoming/misty_palms_oasis.txt | 9 +++++++ .../cardsfolder/upcoming/north_pole_gates.txt | 2 +- .../res/cardsfolder/upcoming/omashu_city.txt | 9 +++++++ .../cardsfolder/upcoming/platypus_bear.txt | 11 +++++++++ .../cardsfolder/upcoming/serpents_pass.txt | 9 +++++++ .../res/cardsfolder/upcoming/shared_roots.txt | 5 ++++ .../cardsfolder/upcoming/sun_blessed_peak.txt | 9 +++++++ .../res/cardsfolder/upcoming/tiger_dillo.txt | 6 +++++ .../res/cardsfolder/upcoming/tolls_of_war.txt | 8 +++++++ forge-gui/res/lists/TypeLists.txt | 1 + 29 files changed, 214 insertions(+), 19 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/airship_engine_room.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/avatars_wrath.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/beifongs_bounty_hunters.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/bitter_work.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/boiling_rock_prison.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/callous_inspector.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/earth_kingdom_general.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/elemental_teachings.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/enter_the_avatar_state.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/firebender_ascension.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/foggy_bottom_swamp.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/jeong_jeong_the_deserter.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/kyoshi_village.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/mai_scornful_striker.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/meditation_pools.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/misty_palms_oasis.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/omashu_city.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/platypus_bear.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/serpents_pass.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/shared_roots.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sun_blessed_peak.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/tiger_dillo.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/tolls_of_war.txt diff --git a/forge-game/src/main/java/forge/game/ability/effects/ScryEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ScryEffect.java index 2907d82480f..dd2171a7861 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ScryEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ScryEffect.java @@ -11,7 +11,6 @@ import forge.game.spellability.SpellAbility; import forge.util.Lang; import forge.util.Localizer; - public class ScryEffect extends SpellAbilityEffect { @Override protected String getStackDescription(SpellAbility sa) { @@ -37,10 +36,8 @@ public class ScryEffect extends SpellAbilityEffect { } boolean isOptional = sa.hasParam("Optional"); + final List players = Lists.newArrayList(); - final List players = Lists.newArrayList(); // players really affected - - // Optional here for spells that have optional multi-player scrying for (final Player p : getTargetPlayers(sa)) { if (!p.isInGame()) { continue; diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerAbilityTriggered.java b/forge-game/src/main/java/forge/game/trigger/TriggerAbilityTriggered.java index e17b405baf6..804d658be9a 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerAbilityTriggered.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerAbilityTriggered.java @@ -21,11 +21,12 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import forge.game.ability.AbilityKey; import forge.game.card.Card; +import forge.game.card.CardCollection; +import forge.game.card.CardLists; import forge.game.card.CardZoneTable; import forge.game.spellability.SpellAbility; -import forge.game.zone.ZoneType; import forge.util.Localizer; -import forge.util.TextUtil; +import org.apache.commons.lang3.StringUtils; import java.util.*; @@ -84,14 +85,13 @@ public class TriggerAbilityTriggered extends Trigger { return false; } - if (hasParam("TriggeredOwnAbility") && "True".equals(getParam("TriggeredOwnAbility")) && !Iterables.contains(causes, source)) { + if (hasParam("TriggeredOwnAbility") && !Iterables.contains(causes, source)) { return false; } return true; } - /** {@inheritDoc} */ @Override public final void setTriggeringObjects(final SpellAbility sa, Map runParams) { @@ -118,14 +118,16 @@ public class TriggerAbilityTriggered extends Trigger { newRunParams.put(AbilityKey.Cause, ImmutableList.of(runParams.get(AbilityKey.Card))); } else if (regtrig.getMode() == TriggerType.ChangesZoneAll) { final CardZoneTable table = (CardZoneTable) runParams.get(AbilityKey.Cards); - Set destinations = new HashSet<>(); - for (ZoneType dest : ZoneType.values()) { - if (table.containsColumn(dest) && !table.column(dest).isEmpty()) { - destinations.add(dest.toString()); - } - } - newRunParams.put(AbilityKey.Destination, TextUtil.join(destinations, ",")); + newRunParams.put(AbilityKey.Destination, StringUtils.join(table.columnKeySet(), ",")); newRunParams.put(AbilityKey.Cause, table.allCards()); + } else if (regtrig.getMode() == TriggerType.Attacks) { + newRunParams.put(AbilityKey.Cause, ImmutableList.of(runParams.get(AbilityKey.Attacker))); + } else if (regtrig.getMode() == TriggerType.AttackersDeclared || regtrig.getMode() == TriggerType.AttackersDeclaredOneTarget) { + CardCollection attackers = (CardCollection) runParams.get(AbilityKey.Attackers); + if (regtrig.hasParam("ValidAttackers")) { + attackers = CardLists.getValidCards(attackers, regtrig.getParam("ValidAttackers"), regtrig.getHostCard().getController(), regtrig.getHostCard(), regtrig); + } + newRunParams.put(AbilityKey.Cause, attackers); } newRunParams.put(AbilityKey.SpellAbility, sa); diff --git a/forge-gui/res/cardsfolder/d/drannith_magistrate.txt b/forge-gui/res/cardsfolder/d/drannith_magistrate.txt index 8617c9d783e..268567727a0 100644 --- a/forge-gui/res/cardsfolder/d/drannith_magistrate.txt +++ b/forge-gui/res/cardsfolder/d/drannith_magistrate.txt @@ -2,5 +2,5 @@ Name:Drannith Magistrate ManaCost:1 W Types:Creature Human Wizard PT:1/3 -S:Mode$ CantBeCast | ValidCard$ Card | Caster$ Opponent | Origin$ Library,Graveyard,Exile,Command | Description$ Your opponents can't cast spells from anywhere other than their hands. +S:Mode$ CantBeCast | ValidCard$ Card.!wasCastFromTheirHand | Caster$ Opponent | Description$ Your opponents can't cast spells from anywhere other than their hands. Oracle:Your opponents can't cast spells from anywhere other than their hands. diff --git a/forge-gui/res/cardsfolder/f/fervent_mastery.txt b/forge-gui/res/cardsfolder/f/fervent_mastery.txt index f07ac5e2711..b0bafe73463 100644 --- a/forge-gui/res/cardsfolder/f/fervent_mastery.txt +++ b/forge-gui/res/cardsfolder/f/fervent_mastery.txt @@ -7,8 +7,7 @@ SVar:OppDiscard:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | Cho SVar:DBDiscard:DB$ Discard | Defined$ ChosenPlayer | AILogic$ DiscardUncastableAndExcess | AnyNumber$ True | Optional$ True | Mode$ TgtChoose | RememberDiscarded$ True | SubAbility$ DBDraw SVar:DBDraw:DB$ Draw | Defined$ ChosenPlayer | NumCards$ Y | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBSearch -SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Hidden$ True | ChangeNum$ 3 | ChangeType$ Card | Destination$ Hand | SubAbility$ DBResolve -SVar:DBResolve:DB$ ChangeZoneResolve | SubAbility$ DBDiscard2 +SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Hidden$ True | ChangeNum$ 3 | ChangeType$ Card | Destination$ Hand | SubAbility$ DBDiscard2 SVar:DBDiscard2:DB$ Discard | Defined$ You | NumCards$ 3 | Mode$ Random SVar:Y:Remembered$Amount SVar:AltCostPaid:Count$AltCost.1.0 diff --git a/forge-gui/res/cardsfolder/upcoming/airship_engine_room.txt b/forge-gui/res/cardsfolder/upcoming/airship_engine_room.txt new file mode 100644 index 00000000000..29bebd79fa8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/airship_engine_room.txt @@ -0,0 +1,9 @@ +Name:Airship Engine Room +ManaCost:no cost +Types:Land +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ This land enters tapped. +SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True +A:AB$ Mana | Cost$ T | Produced$ Combo U R | SpellDescription$ Add {U} or {R}. +A:AB$ Draw | Cost$ 4 T Sac<1/CARDNAME> | SpellDescription$ Draw a card. +DeckHas:Ability$Sacrifice +Oracle:This land enters tapped.\n{T}: Add {U} or {R}.\n{4}, {T}, Sacrifice this land: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/avatars_wrath.txt b/forge-gui/res/cardsfolder/upcoming/avatars_wrath.txt new file mode 100644 index 00000000000..452392f5145 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/avatars_wrath.txt @@ -0,0 +1,9 @@ +Name:Avatar's Wrath +ManaCost:2 W W +Types:Sorcery +A:SP$ Pump | ValidTgts$ Creature | TgtPrompt$ Select up to one target creature | TargetMin$ 0 | TargetMax$ 1 | SubAbility$ DBAirbend | SpellDescription$ Choose up to one target creature, then airbend all other creatures. (Exile them. While each one is exiled, its owner may cast it for {2} rather than its mana cost.) Until your next turn, your opponents can't cast spells from anywhere other than their hands. Exile CARDNAME. +SVar:DBAirbend:DB$ Airbend | Defined$ Valid Creature.NotDefinedTargeted | SubAbility$ DBEffect +SVar:DBEffect:DB$ Effect | StaticAbilities$ CantCast | Duration$ UntilYourNextTurn | SubAbility$ DBExile +SVar:CantCast:Mode$ CantBeCast | ValidCard$ Card.!wasCastFromTheirHand | Caster$ Opponent | Description$ Your opponents can't cast spells from anywhere other than their hands. +SVar:DBExile:DB$ ChangeZone | Origin$ Stack | Destination$ Exile +Oracle:Choose up to one target creature, then airbend all other creatures. (Exile them. While each one is exiled, its owner may cast it for {2} rather than its mana cost.)\nUntil your next turn, your opponents can't cast spells from anywhere other than their hands.\nExile Avatar's Wrath. diff --git a/forge-gui/res/cardsfolder/upcoming/beifongs_bounty_hunters.txt b/forge-gui/res/cardsfolder/upcoming/beifongs_bounty_hunters.txt new file mode 100644 index 00000000000..a86a81b1bef --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/beifongs_bounty_hunters.txt @@ -0,0 +1,8 @@ +Name:Beifong's Bounty Hunters +ManaCost:2 B G +Types:Creature Human Mercenary +PT:4/4 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.nonLand+YouCtrl | Execute$ TrigEarthbend | TriggerDescription$ Whenever a nonland creature you control dies, earthbend X, where X is that creature's power. (Target land you control becomes a 0/0 creature with haste that's still a land. Put X +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) +SVar:TrigEarthbend:DB$ Earthbend | Num$ X +SVar:X:TriggeredCard$CardPower +Oracle:Whenever a nonland creature you control dies, earthbend X, where X is that creature's power. (Target land you control becomes a 0/0 creature with haste that's still a land. Put X +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) diff --git a/forge-gui/res/cardsfolder/upcoming/bitter_work.txt b/forge-gui/res/cardsfolder/upcoming/bitter_work.txt new file mode 100644 index 00000000000..63edacf6267 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bitter_work.txt @@ -0,0 +1,7 @@ +Name:Bitter Work +ManaCost:1 R G +Types:Enchantment +T:Mode$ AttackersDeclaredOneTarget | ValidAttackers$ Creature.YouCtrl+powerGE4 | AttackedTarget$ Player | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever you attack a player with one or more creatures with power 4 or greater, draw a card. +SVar:TrigDraw:DB$ Draw +A:AB$ Earthbend | Cost$ 4 | Exhaust$ True | PlayerTurn$ True | Num$ 4 | SpellDescription$ Earthbend 4. Activate only during your turn. (Target land you control becomes a 0/0 creature with haste that's still a land. Put four +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped. Activate each exhaust ability only once.) +Oracle:Whenever you attack a player with one or more creatures with power 4 or greater, draw a card.\nExhaust — {4}: Earthbend 4. Activate only during your turn. (Target land you control becomes a 0/0 creature with haste that's still a land. Put four +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped. Activate each exhaust ability only once.) diff --git a/forge-gui/res/cardsfolder/upcoming/boiling_rock_prison.txt b/forge-gui/res/cardsfolder/upcoming/boiling_rock_prison.txt new file mode 100644 index 00000000000..8bf893525a2 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/boiling_rock_prison.txt @@ -0,0 +1,9 @@ +Name:Boiling Rock Prison +ManaCost:no cost +Types:Land +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ This land enters tapped. +SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True +A:AB$ Mana | Cost$ T | Produced$ Combo B R | SpellDescription$ Add {B} or {R}. +A:AB$ Draw | Cost$ 4 T Sac<1/CARDNAME> | SpellDescription$ Draw a card. +DeckHas:Ability$Sacrifice +Oracle:This land enters tapped.\n{T}: Add {B} or {R}.\n{4}, {T}, Sacrifice this land: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/callous_inspector.txt b/forge-gui/res/cardsfolder/upcoming/callous_inspector.txt new file mode 100644 index 00000000000..8255baaa0ca --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/callous_inspector.txt @@ -0,0 +1,9 @@ +Name:Callous Inspector +ManaCost:B +Types:Creature Human Soldier +PT:1/1 +K:Menace +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigDamage | TriggerDescription$ When this creature dies, it deals 1 damage to you. Create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:TrigDamage:DB$ DealDamage | NumDmg$ 1 | Defined$ You | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You +Oracle:Menace (This creature can't be blocked except by two or more creatures.)\nWhen this creature dies, it deals 1 damage to you. Create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") diff --git a/forge-gui/res/cardsfolder/upcoming/earth_kingdom_general.txt b/forge-gui/res/cardsfolder/upcoming/earth_kingdom_general.txt new file mode 100644 index 00000000000..b4af6aa746e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/earth_kingdom_general.txt @@ -0,0 +1,12 @@ +Name:Earth Kingdom General +ManaCost:3 G +Types:Creature Human Soldier Ally +PT:2/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEarthbend | TriggerDescription$ When this creature enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.) +SVar:TrigEarthbend:DB$ Earthbend | Num$ 2 +T:Mode$ CounterAddedOnce | CounterType$ P1P1 | Valid$ Creature | TriggerZones$ Battlefield | Execute$ TrigGainLife | ResolvedLimit$ 1 | OptionalDecider$ You | ValidSource$ You | TriggerDescription$ Whenever you put one or more +1/+1 counters on a creature, you may gain that much life. Do this only once each turn. +SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X +SVar:X:TriggerCount$Amount +DeckHas:Ability$Lifegain +DeckHints:Ability$Counters +Oracle:When this creature enters, earthbend 2. (Target land you control becomes a 0/0 creature with haste that's still a land. Put two +1/+1 counters on it. When it dies or is exiled, return it to the battlefield tapped.)\nWhenever you put one or more +1/+1 counters on a creature, you may gain that much life. Do this only once each turn. diff --git a/forge-gui/res/cardsfolder/upcoming/elemental_teachings.txt b/forge-gui/res/cardsfolder/upcoming/elemental_teachings.txt new file mode 100644 index 00000000000..a974df5a406 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/elemental_teachings.txt @@ -0,0 +1,8 @@ +Name:Elemental Teachings +ManaCost:4 G +Types:Instant +A:SP$ ChangeZone | Origin$ Library | Destination$ Library | ChangeType$ Land | ChangeNum$ 4 | RememberChanged$ True | Reveal$ True | Shuffle$ False | DifferentNames$ True | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for up to four land cards with different names and reveal them. An opponent chooses two of those cards. Put the chosen cards into your graveyard and the rest onto the battlefield tapped, then shuffle. +SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Graveyard | ChangeType$ Card.IsRemembered | Chooser$ Opponent | ChangeNum$ 2 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select two cards to be put into the graveyard of CARDNAME's controller | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None +SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | Tapped$ True | ChangeType$ Card.IsRemembered | ChangeNum$ 2 | Mandatory$ True | NoLooking$ True | StackDescription$ None | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:Search your library for up to four land cards with different names and reveal them. An opponent chooses two of those cards. Put the chosen cards into your graveyard and the rest onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/upcoming/enter_the_avatar_state.txt b/forge-gui/res/cardsfolder/upcoming/enter_the_avatar_state.txt new file mode 100644 index 00000000000..61dfd07495f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/enter_the_avatar_state.txt @@ -0,0 +1,5 @@ +Name:Enter the Avatar State +ManaCost:W +Types:Instant Lesson +A:SP$ Animate | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | Keywords$ Flying & First Strike & Lifelink & Hexproof | Types$ Avatar | SpellDescription$ Until end of turn, target creature you control becomes an Avatar in addition to its other types and gains flying, first strike, lifelink, and hexproof. (A creature with hexproof can't be the target of spells or abilities your opponents control.) +Oracle:Until end of turn, target creature you control becomes an Avatar in addition to its other types and gains flying, first strike, lifelink, and hexproof. (A creature with hexproof can't be the target of spells or abilities your opponents control.) diff --git a/forge-gui/res/cardsfolder/upcoming/firebender_ascension.txt b/forge-gui/res/cardsfolder/upcoming/firebender_ascension.txt new file mode 100644 index 00000000000..3ef31ae95a9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/firebender_ascension.txt @@ -0,0 +1,9 @@ +Name:Firebender Ascension +ManaCost:1 R +Types:Enchantment +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When this enchantment enters, create a 2/2 red Soldier creature token with firebending 1. +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ r_2_2_soldier_firebending_1 | TokenOwner$ You +T:Mode$ AbilityTriggered | TriggerZones$ Battlefield | ValidMode$ Attacks,AttackersDeclared,AttackersDeclaredOneTarget | Execute$ TrigPutCounter | ValidSource$ Creature.YouCtrl | TriggeredOwnAbility$ True | TriggerDescription$ Whenever a creature you control attacking causes a triggered ability of that creature to trigger, put a quest counter on this enchantment. Then if it has four or more quest counters on it, you may copy that ability. You may choose new targets for the copy. +SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ QUEST | CounterNum$ 1 | SubAbility$ DBCopy +SVar:DBCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Optional$ True | MayChooseTarget$ True | ConditionPresent$ Card.Self+counters_GE4_QUEST +Oracle:When this enchantment enters, create a 2/2 red Soldier creature token with firebending 1.\nWhenever a creature you control attacking causes a triggered ability of that creature to trigger, put a quest counter on this enchantment. Then if it has four or more quest counters on it, you may copy that ability. You may choose new targets for the copy. diff --git a/forge-gui/res/cardsfolder/upcoming/foggy_bottom_swamp.txt b/forge-gui/res/cardsfolder/upcoming/foggy_bottom_swamp.txt new file mode 100644 index 00000000000..7be0c10a513 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/foggy_bottom_swamp.txt @@ -0,0 +1,9 @@ +Name:Foggy Bottom Swamp +ManaCost:no cost +Types:Land +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ This land enters tapped. +SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True +A:AB$ Mana | Cost$ T | Produced$ Combo B G | SpellDescription$ Add {B} or {G}. +A:AB$ Draw | Cost$ 4 T Sac<1/CARDNAME> | SpellDescription$ Draw a card. +DeckHas:Ability$Sacrifice +Oracle:This land enters tapped.\n{T}: Add {B} or {G}.\n{4}, {T}, Sacrifice this land: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/jeong_jeong_the_deserter.txt b/forge-gui/res/cardsfolder/upcoming/jeong_jeong_the_deserter.txt new file mode 100644 index 00000000000..8c04f03a6f0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/jeong_jeong_the_deserter.txt @@ -0,0 +1,10 @@ +Name:Jeong Jeong, the Deserter +ManaCost:2 R +Types:Legendary Creature Human Rebel Ally +PT:2/3 +K:Firebending:1 +A:AB$ PutCounter | Cost$ 3 | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | Exhaust$ True | SubAbility$ DBTrigger | SpellDescription$ Put a +1/+1 counter on NICKNAME. When you next cast a Lesson spell this turn, copy it and you may choose new targets for the copy. (Activate each exhaust ability only once.) +SVar:DBTrigger:DB$ DelayedTrigger | Mode$ SpellCast | ValidCard$ Lesson | ValidActivatingPlayer$ You | ThisTurn$ True | Execute$ EffTrigCopy | TriggerDescription$ When you next cast a Lesson spell this turn, copy it and you may choose new targets for the copy. +SVar:EffTrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | MayChooseTarget$ True +DeckHints:Type$Lesson +Oracle:Firebending 1 (Whenever this creature attacks, add {R}. This mana lasts until end of combat.)\nExhaust — {3}: Put a +1/+1 counter on Jeong Jeong. When you next cast a Lesson spell this turn, copy it and you may choose new targets for the copy. (Activate each exhaust ability only once.) diff --git a/forge-gui/res/cardsfolder/upcoming/kyoshi_village.txt b/forge-gui/res/cardsfolder/upcoming/kyoshi_village.txt new file mode 100644 index 00000000000..f2219e3b7ff --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kyoshi_village.txt @@ -0,0 +1,9 @@ +Name:Kyoshi Village +ManaCost:no cost +Types:Land +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ This land enters tapped. +SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True +A:AB$ Mana | Cost$ T | Produced$ Combo G W | SpellDescription$ Add {G} or {W}. +A:AB$ Draw | Cost$ 4 T Sac<1/CARDNAME> | SpellDescription$ Draw a card. +DeckHas:Ability$Sacrifice +Oracle:This land enters tapped.\n{T}: Add {G} or {W}.\n{4}, {T}, Sacrifice this land: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/mai_scornful_striker.txt b/forge-gui/res/cardsfolder/upcoming/mai_scornful_striker.txt new file mode 100644 index 00000000000..f24609e6466 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/mai_scornful_striker.txt @@ -0,0 +1,8 @@ +Name:Mai, Scornful Striker +ManaCost:1 B +Types:Legendary Creature Human Noble Ally +PT:2/2 +K:First Strike +T:Mode$ SpellCast | TriggerZones$ Battlefield | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ Player | Execute$ TrigLoseLife | TriggerDescription$ Whenever a player casts a noncreature spell, they lose 2 life. +SVar:TrigLoseLife:DB$ LoseLife | Defined$ TriggeredActivator | LifeAmount$ 2 +Oracle:First strike\nWhenever a player casts a noncreature spell, they lose 2 life. diff --git a/forge-gui/res/cardsfolder/upcoming/meditation_pools.txt b/forge-gui/res/cardsfolder/upcoming/meditation_pools.txt new file mode 100644 index 00000000000..9f7d5bdd688 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/meditation_pools.txt @@ -0,0 +1,9 @@ +Name:Meditation Pools +ManaCost:no cost +Types:Land +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ This land enters tapped. +SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True +A:AB$ Mana | Cost$ T | Produced$ Combo G U | SpellDescription$ Add {G} or {U}. +A:AB$ Draw | Cost$ 4 T Sac<1/CARDNAME> | SpellDescription$ Draw a card. +DeckHas:Ability$Sacrifice +Oracle:This land enters tapped.\n{T}: Add {G} or {U}.\n{4}, {T}, Sacrifice this land: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/misty_palms_oasis.txt b/forge-gui/res/cardsfolder/upcoming/misty_palms_oasis.txt new file mode 100644 index 00000000000..e42251deda3 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/misty_palms_oasis.txt @@ -0,0 +1,9 @@ +Name:Misty Palms Oasis +ManaCost:no cost +Types:Land +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ This land enters tapped. +SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True +A:AB$ Mana | Cost$ T | Produced$ Combo W B | SpellDescription$ Add {W} or {B}. +A:AB$ Draw | Cost$ 4 T Sac<1/CARDNAME> | SpellDescription$ Draw a card. +DeckHas:Ability$Sacrifice +Oracle:This land enters tapped.\n{T}: Add {W} or {B}.\n{4}, {T}, Sacrifice this land: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/north_pole_gates.txt b/forge-gui/res/cardsfolder/upcoming/north_pole_gates.txt index cad6c8644a8..e13ce02dc54 100644 --- a/forge-gui/res/cardsfolder/upcoming/north_pole_gates.txt +++ b/forge-gui/res/cardsfolder/upcoming/north_pole_gates.txt @@ -1,6 +1,6 @@ Name:North Pole Gates ManaCost:no cost -Types:Land Gate +Types:Land R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ This land enters tapped. SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True A:AB$ Mana | Cost$ T | Produced$ Combo W U | SpellDescription$ Add {W} or {U}. diff --git a/forge-gui/res/cardsfolder/upcoming/omashu_city.txt b/forge-gui/res/cardsfolder/upcoming/omashu_city.txt new file mode 100644 index 00000000000..efb5fa46c7d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/omashu_city.txt @@ -0,0 +1,9 @@ +Name:Omashu City +ManaCost:no cost +Types:Land +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ This land enters tapped. +SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True +A:AB$ Mana | Cost$ T | Produced$ Combo R G | SpellDescription$ Add {R} or {G}. +A:AB$ Draw | Cost$ 4 T Sac<1/CARDNAME> | SpellDescription$ Draw a card. +DeckHas:Ability$Sacrifice +Oracle:This land enters tapped.\n{T}: Add {R} or {G}.\n{4}, {T}, Sacrifice this land: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/platypus_bear.txt b/forge-gui/res/cardsfolder/upcoming/platypus_bear.txt new file mode 100644 index 00000000000..3416cbeb651 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/platypus_bear.txt @@ -0,0 +1,11 @@ +Name:Platypus Bear +ManaCost:1 GU +Types:Creature Platypus Bear +PT:2/3 +K:Defender +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When this creature enters, mill two cards. (Put the top two cards of your library into your graveyard.) +SVar:TrigMill:DB$ Mill | Defined$ You | NumCards$ 2 +S:Mode$ CanAttackDefender | ValidCard$ Card.Self | IsPresent$ Lesson.YouOwn | PresentZone$ Graveyard | Description$ As long as there is a Lesson card in your graveyard, this creature can attack as though it didn't have defender. +DeckHas:Ability$Mill|Graveyard +DeckHints:Type$Lesson +Oracle:Defender\nWhen this creature enters, mill two cards. (Put the top two cards of your library into your graveyard.)\nAs long as there is a Lesson card in your graveyard, this creature can attack as though it didn't have defender. diff --git a/forge-gui/res/cardsfolder/upcoming/serpents_pass.txt b/forge-gui/res/cardsfolder/upcoming/serpents_pass.txt new file mode 100644 index 00000000000..8db52ba09fd --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/serpents_pass.txt @@ -0,0 +1,9 @@ +Name:Serpent's Pass +ManaCost:no cost +Types:Land +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ This land enters tapped. +SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True +A:AB$ Mana | Cost$ T | Produced$ Combo U B | SpellDescription$ Add {U} or {B}. +A:AB$ Draw | Cost$ 4 T Sac<1/CARDNAME> | SpellDescription$ Draw a card. +DeckHas:Ability$Sacrifice +Oracle:This land enters tapped.\n{T}: Add {U} or {B}.\n{4}, {T}, Sacrifice this land: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/shared_roots.txt b/forge-gui/res/cardsfolder/upcoming/shared_roots.txt new file mode 100644 index 00000000000..27462bbc434 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/shared_roots.txt @@ -0,0 +1,5 @@ +Name:Shared Roots +ManaCost:1 G +Types:Sorcery Lesson +A:SP$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeTypeDesc$ basic land | Tapped$ True | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. +Oracle:Search your library for a basic land card, put it onto the battlefield tapped, then shuffle. diff --git a/forge-gui/res/cardsfolder/upcoming/sun_blessed_peak.txt b/forge-gui/res/cardsfolder/upcoming/sun_blessed_peak.txt new file mode 100644 index 00000000000..2f41353fa31 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sun_blessed_peak.txt @@ -0,0 +1,9 @@ +Name:Sun-Blessed Peak +ManaCost:no cost +Types:Land +R:Event$ Moved | ValidCard$ Card.Self | Destination$ Battlefield | ReplacementResult$ Updated | ReplaceWith$ ETBTapped | Description$ This land enters tapped. +SVar:ETBTapped:DB$ Tap | Defined$ Self | ETB$ True +A:AB$ Mana | Cost$ T | Produced$ Combo R W | SpellDescription$ Add {R} or {W}. +A:AB$ Draw | Cost$ 4 T Sac<1/CARDNAME> | SpellDescription$ Draw a card. +DeckHas:Ability$Sacrifice +Oracle:This land enters tapped.\n{T}: Add {R} or {W}.\n{4}, {T}, Sacrifice this land: Draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/tiger_dillo.txt b/forge-gui/res/cardsfolder/upcoming/tiger_dillo.txt new file mode 100644 index 00000000000..f0aa63fe596 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tiger_dillo.txt @@ -0,0 +1,6 @@ +Name:Tiger-Dillo +ManaCost:1 R +Types:Creature Cat Armadillo +PT:4/3 +S:Mode$ CantAttack,CantBlock | ValidCard$ Card.Self | IsPresent$ Creature.Other+YouCtrl+powerGE4 | PresentCompare$ EQ0 | Description$ This creature can't attack or block unless you control another creature with power 4 or greater. +Oracle:This creature can't attack or block unless you control another creature with power 4 or greater. diff --git a/forge-gui/res/cardsfolder/upcoming/tolls_of_war.txt b/forge-gui/res/cardsfolder/upcoming/tolls_of_war.txt new file mode 100644 index 00000000000..0a2c83a16dc --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tolls_of_war.txt @@ -0,0 +1,8 @@ +Name:Tolls of War +ManaCost:W B +Types:Enchantment +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When this enchantment enters, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.") +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_clue_draw | TokenOwner$ You +T:Mode$ Sacrificed | ValidCard$ Permanent.YouCtrl | Execute$ TrigToken | TriggerZones$ Battlefield | ValidPlayer$ You | PlayerTurn$ True | ActivationLimit$ 1 | TriggerDescription$ Whenever you sacrifice a permanent during your turn, create a 1/1 white Ally creature token. This ability triggers only once each turn. +SVar:TrigToken:DB$ Token | TokenScript$ w_1_1_ally +Oracle:When this enchantment enters, create a Clue token. (It's an artifact with "{2}, Sacrifice this token: Draw a card.")\nWhenever you sacrifice a permanent during your turn, create a 1/1 white Ally creature token. This ability triggers only once each turn. diff --git a/forge-gui/res/lists/TypeLists.txt b/forge-gui/res/lists/TypeLists.txt index d3a368b2758..874897ef42a 100644 --- a/forge-gui/res/lists/TypeLists.txt +++ b/forge-gui/res/lists/TypeLists.txt @@ -234,6 +234,7 @@ Pilot:Pilots Pincher:Pinchers Pirate:Pirates Plant:Plants +Platypus:Platypuses Porcupine:Porcupines Possum:Possums Praetor:Praetors