diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 83520cd8c55..a282325a750 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -131,10 +131,9 @@ public class ComputerUtil { sa = GameActionUtil.addExtraKeywordCost(sa); - if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { - if (!CharmEffect.makeChoices(sa)) { - return false; - } + if (sa.getApi() == ApiType.Charm && !CharmEffect.makeChoices(sa)) { + // 603.3c If no mode is chosen, the ability is removed from the stack. + return false; } if (chooseTargets != null) { chooseTargets.run(); @@ -250,6 +249,13 @@ public class ComputerUtil { return false; final Card source = sa.getHostCard(); + + Zone fromZone = game.getZoneOf(source); + int zonePosition = 0; + if (fromZone != null) { + zonePosition = fromZone.getCards().indexOf(source); + } + if (sa.isSpell() && !source.isCopiedSpell()) { sa.setHostCard(game.getAction().moveToStack(source, sa)); } @@ -257,11 +263,18 @@ public class ComputerUtil { sa = GameActionUtil.addExtraKeywordCost(sa); final Cost cost = sa.getPayCosts(); + final CostPayment pay = new CostPayment(cost, sa); + + // do this after card got added to stack + if (!sa.checkRestrictions(ai)) { + GameActionUtil.rollbackAbility(sa, fromZone, zonePosition, pay, source); + return false; + } + if (cost == null) { ComputerUtilMana.payManaCost(ai, sa, false); game.getStack().add(sa); } else { - final CostPayment pay = new CostPayment(cost, sa); if (pay.payComputerCosts(new AiCostDecision(ai, sa, false))) { game.getStack().add(sa); } @@ -292,17 +305,30 @@ public class ComputerUtil { newSA = GameActionUtil.addExtraKeywordCost(newSA); final Card source = newSA.getHostCard(); + + Zone fromZone = game.getZoneOf(source); + int zonePosition = 0; + if (fromZone != null) { + zonePosition = fromZone.getCards().indexOf(source); + } + if (newSA.isSpell() && !source.isCopiedSpell()) { newSA.setHostCard(game.getAction().moveToStack(source, newSA)); - if (newSA.getApi() == ApiType.Charm && !newSA.isWrapper()) { - if (!CharmEffect.makeChoices(newSA)) { - return false; - } + if (newSA.getApi() == ApiType.Charm && !CharmEffect.makeChoices(newSA)) { + // 603.3c If no mode is chosen, the ability is removed from the stack. + return false; } } final CostPayment pay = new CostPayment(newSA.getPayCosts(), newSA); + + // do this after card got added to stack + if (!sa.checkRestrictions(ai)) { + GameActionUtil.rollbackAbility(sa, fromZone, zonePosition, pay, source); + return false; + } + pay.payComputerCosts(new AiCostDecision(ai, newSA, false)); game.getStack().add(newSA); @@ -2798,7 +2824,7 @@ public class ComputerUtil { } return type.is(CounterEnumType.AWAKENING) || type.is(CounterEnumType.MANIFESTATION) || type.is(CounterEnumType.PETRIFICATION) - || type.is(CounterEnumType.TRAINING); + || type.is(CounterEnumType.TRAINING) || type.is(CounterEnumType.GHOSTFORM); } public static Player evaluateBoardPosition(final List listToEvaluate) { diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java index 46f379218ce..2f39a64fbf5 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCost.java @@ -7,6 +7,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import forge.ai.AiCardMemory.MemorySet; import forge.ai.ability.AnimateAi; +import forge.ai.ability.TokenAi; import forge.card.ColorSet; import forge.game.Game; import forge.game.ability.AbilityUtils; @@ -14,6 +15,7 @@ import forge.game.ability.ApiType; import forge.game.card.*; import forge.game.card.CardPredicates.Presets; import forge.game.combat.Combat; +import forge.game.combat.CombatUtil; import forge.game.cost.*; import forge.game.keyword.Keyword; import forge.game.phase.PhaseType; @@ -692,6 +694,72 @@ public class ComputerUtilCost { return false; } else if ("LowPriority".equals(aiLogic) && MyRandom.getRandom().nextInt(100) < 67) { return false; + } else if (aiLogic != null && aiLogic.startsWith("Fabricate")) { + final int n = Integer.valueOf(aiLogic.substring("Fabricate".length())); + + // if host would leave the play or if host is useless, create tokens + if (source.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(payer, source)) { + return false; + } + + // need a copy for one with extra +1/+1 counter boost, + // without causing triggers to run + final Card copy = CardUtil.getLKICopy(source); + copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + n); + copy.setZone(source.getZone()); + + // if host would put into the battlefield attacking + Combat combat = source.getGame().getCombat(); + if (combat != null && combat.isAttacking(source)) { + final Player defender = combat.getDefenderPlayerByAttacker(source); + if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy, true)) { + return true; + } + return false; + } + + // if the host has haste and can attack + if (CombatUtil.canAttack(copy)) { + for (final Player opp : payer.getOpponents()) { + if (CombatUtil.canAttack(copy, opp) && + opp.canLoseLife() && + !ComputerUtilCard.canBeBlockedProfitably(opp, copy, true)) + return true; + } + } + + // TODO check for trigger to turn token ETB into +1/+1 counter for host + // TODO check for trigger to turn token ETB into damage or life loss for opponent + // in this cases Token might be prefered even if they would not survive + final Card tokenCard = TokenAi.spawnToken(payer, sa); + + // Token would not survive + if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) { + return true; + } + + // Special Card logic, this one try to median its power with the number of artifacts + if ("Marionette Master".equals(source.getName())) { + CardCollection list = CardLists.filter(payer.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS); + return list.size() >= copy.getNetPower(); + } else if ("Cultivator of Blades".equals(source.getName())) { + // Cultivator does try to median with number of Creatures + CardCollection list = payer.getCreaturesInPlay(); + return list.size() >= copy.getNetPower(); + } + + // evaluate Creature with +1/+1 + int evalCounter = ComputerUtilCard.evaluateCreature(copy); + + final CardCollection tokenList = new CardCollection(source); + for (int i = 0; i < n; ++i) { + tokenList.add(TokenAi.spawnToken(payer, sa)); + } + + // evaluate Host with Tokens + int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList); + + return evalToken < evalCounter; } // Check for shocklands and similar ETB replacement effects diff --git a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java index a9a58304bd4..75a02f932ef 100644 --- a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java +++ b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java @@ -45,7 +45,7 @@ public class CreatureEvaluator implements Function { value += addValue(toughness * 10, "toughness: " + toughness); // because backside is always stronger the potential makes it better than a single faced card - if (c.hasKeyword(Keyword.DAYBOUND)) { + if (c.hasKeyword(Keyword.DAYBOUND) && c.hasBackSide()) { value += addValue(power * 10, "transforming"); } } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 772f0d51734..cf01b8a09ae 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -34,6 +34,7 @@ import forge.game.GameObject; import forge.game.GameType; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; +import forge.game.ability.effects.CharmEffect; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; @@ -1045,16 +1046,14 @@ public class PlayerControllerAi extends PlayerController { } } - /* FIXME: the new implementation (below) requires implementing setupNewTargets in the AI controller, among other possible changes, otherwise breaks AI if (sa.isMayChooseNewTargets()) { - sa.setupNewTargets(player); - } - */ - if (sa.isMayChooseNewTargets() && !sa.setupTargets()) { - if (sa.isSpell()) { - getGame().getAction().ceaseToExist(sa.getHostCard(), false); + TargetChoices tc = sa.getTargets(); + if (!sa.setupTargets()) { + // if AI can't choose targets need to keep old one even if illegal + sa.setTargets(tc); } - continue; + // FIXME: the new implementation (below) requires implementing setupNewTargets in the AI controller, among other possible changes, otherwise breaks AI + // sa.setupNewTargets(player); } } // need finally add the new spell to the stack @@ -1064,6 +1063,9 @@ public class PlayerControllerAi extends PlayerController { } private boolean prepareSingleSa(final Card host, final SpellAbility sa, boolean isMandatory) { + if (sa.getApi() == ApiType.Charm) { + return CharmEffect.makeChoices(sa); + } if (sa.hasParam("TargetingPlayer")) { Player targetingPlayer = AbilityUtils.getDefinedPlayers(host, sa.getParam("TargetingPlayer"), sa).get(0); sa.setTargetingPlayer(targetingPlayer); @@ -1087,7 +1089,7 @@ public class PlayerControllerAi extends PlayerController { if (tgtSA instanceof Spell) { // Isn't it ALWAYS a spell? Spell spell = (Spell) tgtSA; // TODO if mandatory AI is only forced to use mana when it's already in the pool - if (tgtSA.checkRestrictions(brains.getPlayer()) && (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional)) { + if (brains.canPlayFromEffectAI(spell, !optional, noManaCost) == AiPlayDecision.WillPlay || !optional) { if (noManaCost) { return ComputerUtil.playSpellAbilityWithoutPayingManaCost(player, tgtSA, getGame()); } 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 a78406df498..957dcb8f03d 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CharmAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CharmAi.java @@ -89,7 +89,6 @@ public class CharmAi extends SpellAbilityAi { // First pass using standard canPlayAi() for good choices for (AbilitySub sub : choices) { sub.setActivatingPlayer(ai); - sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone()); if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) { chosenList.add(sub); if (chosenList.size() == num) { @@ -101,8 +100,6 @@ public class CharmAi extends SpellAbilityAi { // Second pass using doTrigger(false) to fulfill minimum choice choices.removeAll(chosenList); for (AbilitySub sub : choices) { - sub.setActivatingPlayer(ai); - sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone()); if (aic.doTrigger(sub, false)) { chosenList.add(sub); if (chosenList.size() == min) { @@ -114,8 +111,6 @@ public class CharmAi extends SpellAbilityAi { if (chosenList.size() < min) { choices.removeAll(chosenList); for (AbilitySub sub : choices) { - sub.setActivatingPlayer(ai); - sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone()); if (aic.doTrigger(sub, true)) { chosenList.add(sub); if (chosenList.size() == min) { @@ -231,7 +226,6 @@ public class CharmAi extends SpellAbilityAi { } else { // Standard canPlayAi() sub.setActivatingPlayer(ai); - sub.getRestrictions().setZone(sub.getParent().getRestrictions().getZone()); if (AiPlayDecision.WillPlay == aic.canPlaySa(sub)) { chosenList.add(sub); if (chosenList.size() == min) { @@ -255,15 +249,6 @@ public class CharmAi extends SpellAbilityAi { return Aggregates.random(opponents); } - @Override - protected boolean doTriggerAINoCost(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) { - // already done by chooseOrderOfSimultaneousStackEntry - if (sa.getChosenList() != null) { - return true; - } - return super.doTriggerAINoCost(aiPlayer, sa, mandatory); - } - @Override public boolean chkDrawbackWithSubs(Player aiPlayer, AbilitySub ab) { // choices were already targeted diff --git a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java index e81059371c7..596ee8f6671 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java @@ -9,7 +9,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import forge.ai.ComputerUtilAbility; -import forge.ai.ComputerUtilCard; import forge.ai.ComputerUtilCost; import forge.ai.SpecialCardAi; import forge.ai.SpellAbilityAi; @@ -19,12 +18,9 @@ import forge.game.Game; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates.Presets; import forge.game.card.CardUtil; import forge.game.card.CounterEnumType; import forge.game.combat.Combat; -import forge.game.combat.CombatUtil; import forge.game.cost.Cost; import forge.game.keyword.Keyword; import forge.game.phase.PhaseHandler; @@ -43,7 +39,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi { protected boolean checkAiLogic(final Player ai, final SpellAbility sa, final String aiLogic) { if ("Khans".equals(aiLogic) || "Dragons".equals(aiLogic)) { return true; - } else if (aiLogic.startsWith("Fabricate") || "Riot".equals(aiLogic)) { + } else if ("Riot".equals(aiLogic)) { return true; } else if ("Pump".equals(aiLogic) || "BestOption".equals(aiLogic)) { for (AbilitySub sb : sa.getAdditionalAbilityList("Choices")) { @@ -265,83 +261,6 @@ public class ChooseGenericEffectAi extends SpellAbilityAi { } // if unsure, random? return Aggregates.random(spells); - } else if (logic.startsWith("Fabricate")) { - final int n = Integer.valueOf(logic.substring("Fabricate".length())); - if(spells.size() < 2) { - // If the creature is no longer on the battlefield, the option - // to add counters is already removed at this point. Return the - // only available option: create servo tokens. - return spells.get(0); - } - SpellAbility counterSA = spells.get(0), tokenSA = spells.get(1); - - // check for something which might prevent the counters to be placed on host - if (!host.canReceiveCounters(CounterEnumType.P1P1)) { - return tokenSA; - } - - // if host would leave the play or if host is useless, create tokens - if (host.hasSVar("EndOfTurnLeavePlay") || ComputerUtilCard.isUselessCreature(player, host)) { - return tokenSA; - } - - // need a copy for one with extra +1/+1 counter boost, - // without causing triggers to run - final Card copy = CardUtil.getLKICopy(host); - copy.setCounters(CounterEnumType.P1P1, copy.getCounters(CounterEnumType.P1P1) + n); - copy.setZone(host.getZone()); - - // if host would put into the battlefield attacking - if (combat != null && combat.isAttacking(host)) { - final Player defender = combat.getDefenderPlayerByAttacker(host); - if (defender.canLoseLife() && !ComputerUtilCard.canBeBlockedProfitably(defender, copy, true)) { - return counterSA; - } - return tokenSA; - } - - // if the host has haste and can attack - if (CombatUtil.canAttack(copy)) { - for (final Player opp : player.getOpponents()) { - if (CombatUtil.canAttack(copy, opp) && - opp.canLoseLife() && - !ComputerUtilCard.canBeBlockedProfitably(opp, copy, true)) - return counterSA; - } - } - - // TODO check for trigger to turn token ETB into +1/+1 counter for host - // TODO check for trigger to turn token ETB into damage or life loss for opponent - // in this cases Token might be prefered even if they would not survive - final Card tokenCard = TokenAi.spawnToken(player, tokenSA); - - // Token would not survive - if (!tokenCard.isCreature() || tokenCard.getNetToughness() < 1) { - return counterSA; - } - - // Special Card logic, this one try to median its power with the number of artifacts - if ("Marionette Master".equals(sourceName)) { - CardCollection list = CardLists.filter(player.getCardsIn(ZoneType.Battlefield), Presets.ARTIFACTS); - return list.size() >= copy.getNetPower() ? counterSA : tokenSA; - } else if ("Cultivator of Blades".equals(sourceName)) { - // Cultivator does try to median with number of Creatures - CardCollection list = player.getCreaturesInPlay(); - return list.size() >= copy.getNetPower() ? counterSA : tokenSA; - } - - // evaluate Creature with +1/+1 - int evalCounter = ComputerUtilCard.evaluateCreature(copy); - - final CardCollection tokenList = new CardCollection(host); - for (int i = 0; i < n; ++i) { - tokenList.add(TokenAi.spawnToken(player, tokenSA)); - } - - // evaluate Host with Tokens - int evalToken = ComputerUtilCard.evaluateCreatureList(tokenList); - - return evalToken >= evalCounter ? tokenSA : counterSA; } else if ("CombustibleGearhulk".equals(logic)) { Player controller = sa.getActivatingPlayer(); List zones = ZoneType.listValueOf("Graveyard, Battlefield, Exile"); diff --git a/forge-core/src/main/java/forge/ImageKeys.java b/forge-core/src/main/java/forge/ImageKeys.java index 3975890cf9b..12cb9493a6b 100644 --- a/forge-core/src/main/java/forge/ImageKeys.java +++ b/forge-core/src/main/java/forge/ImageKeys.java @@ -3,6 +3,7 @@ package forge; import forge.item.PaperCard; import forge.util.FileUtil; import forge.util.TextUtil; +import forge.util.ThreadUtil; import org.apache.commons.lang3.StringUtils; import java.io.File; @@ -30,6 +31,7 @@ public final class ImageKeys { private static Map CACHE_CARD_PICS_SUBDIR; private static Map editionImageLookup = new HashMap<>(); + private static Set toFind = new HashSet<>(); private static boolean isLibGDXPort = false; @@ -109,7 +111,8 @@ public final class ImageKeys { filename = key; dir = CACHE_CARD_PICS_DIR; } - + if (toFind.contains(filename)) + return null; if (missingCards.contains(filename)) return null; @@ -171,10 +174,20 @@ public final class ImageKeys { return file; } //setlookup - file = setLookUpFile(filename, fullborderFile); - if (file != null) { - cachedCards.put(filename, file); - return file; + if (hasSetLookup(filename)) { + toFind.add(filename); + try { + ThreadUtil.getServicePool().submit(() -> { + File f = setLookUpFile(filename, fullborderFile); + if (f != null) + cachedCards.put(filename, f); + else //is null + missingCards.add(filename); + toFind.remove(filename); + }); + } catch (Exception e) { + toFind.remove(filename); + } } } //if an image, like phenomenon or planes is missing .full in their filenames but you have an existing images that have .full/.fullborder @@ -250,7 +263,7 @@ public final class ImageKeys { // System.out.println("File not found, no image created: " + key); //add missing cards - disable for desktop version for compatibility reasons with autodownloader - if (isLibGDXPort) + if (isLibGDXPort && !hasSetLookup(filename)) //missing cards with setlookup is handled differently missingCards.add(filename); return null; } @@ -260,34 +273,44 @@ public final class ImageKeys { ? StaticData.instance().getEditions().getCode2ByCode(edition) // by default 2-letter codes from MWS are used : CACHE_CARD_PICS_SUBDIR.get(edition); // may use custom paths though } - private static File setLookUpFile(String filename, String fullborderFile) { + public static boolean hasSetLookup(String filename) { + if (!StaticData.instance().getSetLookup().isEmpty()) { + return StaticData.instance().getSetLookup().keySet().stream().anyMatch(setKey -> filename.startsWith(setKey)); + } + + return false; + } + public static File setLookUpFile(String filename, String fullborderFile) { if (!StaticData.instance().getSetLookup().isEmpty()) { for (String setKey : StaticData.instance().getSetLookup().keySet()) { if (filename.startsWith(setKey)) { for (String setLookup : StaticData.instance().getSetLookup().get(setKey)) { String lookupDirectory = CACHE_CARD_PICS_DIR + setLookup; File f = new File(lookupDirectory); - String[] cardNames = f.list(); - if (cardNames != null) { - Set cardList = new HashSet<>(Arrays.asList(cardNames)); + if (f.exists() && f.isDirectory()) { for (String ext : FILE_EXTENSIONS) { if (ext.equals("")) continue; + File placeholder; String fb1 = fullborderFile.replace(setKey+"/","")+ext; - if (cardList.contains(fb1)) { - return new File(lookupDirectory+"/"+fb1); + placeholder = new File(lookupDirectory+"/"+fb1); + if (placeholder.exists()) { + return placeholder; } String fb2 = fullborderFile.replace(setKey+"/","").replaceAll("[0-9]*.fullborder", "1.fullborder")+ext; - if (cardList.contains(fb2)) { - return new File(lookupDirectory+"/"+fb2); + placeholder = new File(lookupDirectory+"/"+fb2); + if (placeholder.exists()) { + return placeholder; } String f1 = filename.replace(setKey+"/","")+ext; - if (cardList.contains(f1)) { - return new File(lookupDirectory+"/"+f1); + placeholder = new File(lookupDirectory+"/"+f1); + if (placeholder.exists()) { + return placeholder; } String f2 = filename.replace(setKey+"/","").replaceAll("[0-9]*.full", "1.full")+ext; - if (cardList.contains(f2)) { - return new File(lookupDirectory+"/"+f2); + placeholder = new File(lookupDirectory+"/"+f2); + if (placeholder.exists()) { + return placeholder; } } } diff --git a/forge-core/src/main/java/forge/StaticData.java b/forge-core/src/main/java/forge/StaticData.java index e2d20940067..5570e7ea63a 100644 --- a/forge-core/src/main/java/forge/StaticData.java +++ b/forge-core/src/main/java/forge/StaticData.java @@ -8,9 +8,11 @@ import forge.card.PrintSheet; import forge.item.*; import forge.token.TokenDb; import forge.util.FileUtil; +import forge.util.ImageUtil; import forge.util.TextUtil; import forge.util.storage.IStorage; import forge.util.storage.StorageBase; +import org.apache.commons.lang3.tuple.Pair; import java.io.File; import java.util.*; @@ -45,6 +47,7 @@ public class StaticData { private boolean allowCustomCardsInDecksConformance; private boolean enableSmartCardArtSelection; + private boolean loadNonLegalCards; // Loaded lazily: private IStorage boosters; @@ -74,6 +77,7 @@ public class StaticData { this.customCardReader = customCardReader; this.allowCustomCardsInDecksConformance = allowCustomCardsInDecksConformance; this.enableSmartCardArtSelection = enableSmartCardArtSelection; + this.loadNonLegalCards = loadNonLegalCards; lastInstance = this; List funnyCards = new ArrayList<>(); List filtered = new ArrayList<>(); @@ -752,6 +756,129 @@ public class StaticData { preferences_avails[i] = prettifyCardArtPreferenceName(preferences[i]); return preferences_avails; } + public Pair audit(StringBuffer noImageFound, StringBuffer cardNotImplemented) { + int missingCount = 0; + int notImplementedCount = 0; + for (CardEdition e : editions) { + if (CardEdition.Type.FUNNY.equals(e.getType())) + continue; + boolean nifHeader = false; + boolean cniHeader = false; + boolean tokenHeader = false; + + String imagePath; + int artIndex = 1; + + HashMap> cardCount = new HashMap<>(); + for (CardEdition.CardInSet c : e.getAllCardsInSet()) { + if (cardCount.containsKey(c.name)) { + cardCount.put(c.name, Pair.of(c.collectorNumber.startsWith("F"), cardCount.get(c.name).getRight() + 1)); + } else { + cardCount.put(c.name, Pair.of(c.collectorNumber.startsWith("F"), 1)); + } + } + + // loop through the cards in this edition, considering art variations... + for (Map.Entry> entry : cardCount.entrySet()) { + String c = entry.getKey(); + artIndex = entry.getValue().getRight(); + + PaperCard cp = getCommonCards().getCard(c, e.getCode(), artIndex); + if (cp == null) { + cp = getVariantCards().getCard(c, e.getCode(), artIndex); + } + + if (cp == null) { + if (entry.getValue().getLeft()) //skip funny cards + continue; + if (!loadNonLegalCards && CardEdition.Type.FUNNY.equals(e.getType())) + continue; + if (!cniHeader) { + cardNotImplemented.append("Edition: ").append(e.getName()).append(" ").append("(").append(e.getCode()).append("/").append(e.getCode2()).append(")\n"); + cniHeader = true; + } + cardNotImplemented.append(" ").append(c).append("\n"); + notImplementedCount++; + continue; + } + + // check the front image + imagePath = ImageUtil.getImageRelativePath(cp, false, true, false); + if (imagePath != null) { + File file = ImageKeys.getImageFile(imagePath); + if (file == null && ImageKeys.hasSetLookup(imagePath)) + file = ImageKeys.setLookUpFile(imagePath, imagePath+"border"); + if (file == null) { + if (!nifHeader) { + noImageFound.append("Edition: ").append(e.getName()).append(" ").append("(").append(e.getCode()).append("/").append(e.getCode2()).append(")\n"); + nifHeader = true; + } + noImageFound.append(" ").append(imagePath).append("\n"); + missingCount++; + } + } + + // check the back face + if (cp.hasBackFace()) { + imagePath = ImageUtil.getImageRelativePath(cp, true, true, false); + if (imagePath != null) { + File file = ImageKeys.getImageFile(imagePath); + if (file == null && ImageKeys.hasSetLookup(imagePath)) + file = ImageKeys.setLookUpFile(imagePath, imagePath+"border"); + if (file == null) { + if (!nifHeader) { + noImageFound.append("Edition: ").append(e.getName()).append(" ").append("(").append(e.getCode()).append("/").append(e.getCode2()).append(")\n"); + nifHeader = true; + } + noImageFound.append(" ").append(imagePath).append("\n"); + missingCount++; + } + } + } + } + + // TODO: Audit token images here... + for(Map.Entry tokenEntry : e.getTokens().entrySet()) { + String name = tokenEntry.getKey(); + artIndex = tokenEntry.getValue(); + try { + PaperToken token = getAllTokens().getToken(name, e.getCode()); + if (token == null) { + continue; + } + + for(int i = 0; i < artIndex; i++) { + String imgKey = token.getImageKey(i); + File file = ImageKeys.getImageFile(imgKey); + if (file == null) { + if (!nifHeader) { + noImageFound.append("Edition: ").append(e.getName()).append(" ").append("(").append(e.getCode()).append("/").append(e.getCode2()).append(")\n"); + nifHeader = true; + } + if (!tokenHeader) { + noImageFound.append("\nTOKENS\n"); + tokenHeader = true; + } + noImageFound.append(" ").append(token.getImageFilename(i + 1)).append("\n"); + missingCount++; + } + } + } catch(Exception ex) { + System.out.println("No Token found: " + name + " in " + e.getName()); + } + } + if (nifHeader) + noImageFound.append("\n"); + } + + String totalStats = "Missing images: " + missingCount + "\nUnimplemented cards: " + notImplementedCount + "\n"; + cardNotImplemented.append("\n-----------\n"); + cardNotImplemented.append(totalStats); + cardNotImplemented.append("-----------\n\n"); + + noImageFound.append(cardNotImplemented); // combine things together... + return Pair.of(missingCount, notImplementedCount); + } private String prettifyCardArtPreferenceName(CardDb.CardArtPreference preference) { StringBuilder label = new StringBuilder(); diff --git a/forge-core/src/main/java/forge/util/ThreadUtil.java b/forge-core/src/main/java/forge/util/ThreadUtil.java index 87e54edbc4c..c481816ff95 100644 --- a/forge-core/src/main/java/forge/util/ThreadUtil.java +++ b/forge-core/src/main/java/forge/util/ThreadUtil.java @@ -53,6 +53,27 @@ public class ThreadUtil { return Thread.currentThread().getName().startsWith("Game"); } + private static ExecutorService service = Executors.newWorkStealingPool(); + public static ExecutorService getServicePool() { + return service; + } + public static void refreshServicePool() { + service = Executors.newWorkStealingPool(); + } + public static T limit(Callable task, long millis){ + Future future = null; + T result; + try { + future = service.submit(task); + result = future.get(millis, TimeUnit.MILLISECONDS); + } catch (Exception e) { + result = null; + } finally { + if (future != null) + future.cancel(true); + } + return result; + } public static T executeWithTimeout(Callable task, int milliseconds) { ExecutorService executor = Executors.newCachedThreadPool(); Future future = executor.submit(task); diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index a8fda242d85..764793295b7 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -53,6 +53,7 @@ import forge.game.card.CardLists; import forge.game.card.CardPredicates; import forge.game.card.CardUtil; import forge.game.card.CardView; +import forge.game.card.CardZoneTable; import forge.game.card.CounterType; import forge.game.combat.Combat; import forge.game.event.Event; @@ -120,6 +121,8 @@ public class Game { private CardCollection lastStateBattlefield = new CardCollection(); private CardCollection lastStateGraveyard = new CardCollection(); + private CardZoneTable untilHostLeavesPlayTriggerList = new CardZoneTable(); + private Table>> countersAddedThisTurn = HashBasedTable.create(); private FCollection globalDamageHistory = new FCollection<>(); @@ -187,6 +190,10 @@ public class Game { initiative = p; } + public CardZoneTable getUntilHostLeavesPlayTriggerList() { + return untilHostLeavesPlayTriggerList; + } + public CardCollectionView getLastStateBattlefield() { return lastStateBattlefield; } diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 03552568b9c..a3f9d0598ec 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -162,14 +162,32 @@ public class GameAction { // need to check before it enters if (c.isAura() && !c.isAttachedToEntity() && toBattlefield && (zoneFrom == null || !zoneFrom.is(ZoneType.Stack))) { boolean found = false; - if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c, null))) { - found = true; + try { + if (Iterables.any(game.getPlayers(), PlayerPredicates.canBeAttached(c, null))) { + found = true; + } + } catch (Exception e1) { + found = false; } - else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c, null))) { - found = true; + + if (!found) { + try { + if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateBattlefield), CardPredicates.canBeAttached(c, null))) { + found = true; + } + } catch (Exception e2) { + found = false; + } } - else if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c, null))) { - found = true; + + if (!found) { + try { + if (Iterables.any((CardCollectionView) params.get(AbilityKey.LastStateGraveyard), CardPredicates.canBeAttached(c, null))) { + found = true; + } + } catch (Exception e3) { + found = false; + } } if (!found) { c.clearControllers(); @@ -341,8 +359,10 @@ public class GameAction { if (commanderEffect != null) break; } // Disable the commander replacement effect - for (final ReplacementEffect re : commanderEffect.getReplacementEffects()) { - re.setSuppressed(true); + if (commanderEffect != null) { + for (final ReplacementEffect re : commanderEffect.getReplacementEffects()) { + re.setSuppressed(true); + } } } @@ -2315,14 +2335,16 @@ public class GameAction { final Player p = e.getKey(); final CardCollection toTop = e.getValue().getLeft(); final CardCollection toBottom = e.getValue().getRight(); - final int numLookedAt = toTop.size() + toBottom.size(); + int numLookedAt = 0; if (toTop != null) { + numLookedAt += toTop.size(); Collections.reverse(toTop); // reverse to get the correct order for (Card c : toTop) { moveToLibrary(c, cause, null); } } if (toBottom != null) { + numLookedAt += toBottom.size(); for (Card c : toBottom) { moveToBottomOfLibrary(c, cause, null); } @@ -2390,6 +2412,9 @@ public class GameAction { } } + // for Zangief do this before runWaitingTriggers DamageDone + damageMap.triggerExcessDamage(isCombat, lethalDamage, game); + // lose life simultaneously if (isCombat) { for (Player p : game.getPlayers()) { @@ -2418,7 +2443,6 @@ public class GameAction { preventMap.clear(); damageMap.triggerDamageDoneOnce(isCombat, game); - damageMap.triggerExcessDamage(isCombat, lethalDamage, game); damageMap.clear(); counterTable.replaceCounterEffect(game, cause, !isCombat); diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index f7574571163..22f49d4fa62 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -34,6 +34,7 @@ import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.CardPlayOption.PayManaCost; import forge.game.cost.Cost; +import forge.game.cost.CostPayment; import forge.game.keyword.Keyword; import forge.game.keyword.KeywordInterface; import forge.game.player.Player; @@ -842,4 +843,46 @@ public final class GameActionUtil { c.getGame().getTriggerHandler().resetActiveTriggers(); } + public static void rollbackAbility(SpellAbility ability, final Zone fromZone, final int zonePosition, CostPayment payment, Card oldCard) { + // cancel ability during target choosing + final Game game = ability.getActivatingPlayer().getGame(); + + if (fromZone != null) { // and not a copy + oldCard.setCastSA(null); + oldCard.setCastFrom(null); + // add back to where it came from, hopefully old state + // skip GameAction + oldCard.getZone().remove(oldCard); + fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); + ability.setHostCard(oldCard); + ability.setXManaCostPaid(null); + ability.setSpendPhyrexianMana(false); + if (ability.hasParam("Announce")) { + for (final String aVar : ability.getParam("Announce").split(",")) { + final String varName = aVar.trim(); + if (!varName.equals("X")) { + ability.setSVar(varName, "0"); + } + } + } + // better safe than sorry approach in case rolled back ability was copy (from addExtraKeywordCost) + for (SpellAbility sa : oldCard.getSpells()) { + sa.setHostCard(oldCard); + } + //for Chorus of the Conclave + ability.rollback(); + + oldCard.setBackSide(false); + oldCard.setState(oldCard.getFaceupCardStateName(), true); + oldCard.unanimateBestow(); + } + + ability.clearTargets(); + + ability.resetOnceResolved(); + payment.refundPayment(); + game.getStack().clearFrozen(); + game.getTriggerHandler().clearWaitingTriggers(); + } + } 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 a34a1019d38..e8b400abf19 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityFactory.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityFactory.java @@ -277,7 +277,7 @@ public final class AbilityFactory { } } - if (api == ApiType.Charm || api == ApiType.GenericChoice || api == ApiType.AssignGroup) { + if (api == ApiType.Charm || api == ApiType.GenericChoice || api == ApiType.AssignGroup) { final String key = "Choices"; if (mapParams.containsKey(key)) { List names = Lists.newArrayList(mapParams.get(key).split(",")); @@ -426,7 +426,9 @@ public final class AbilityFactory { private static final void makeRestrictions(final SpellAbility sa) { // SpellAbilityRestrictions should be added in here final SpellAbilityRestriction restrict = sa.getRestrictions(); - restrict.setRestrictions(sa.getMapParams()); + if (restrict != null) { + restrict.setRestrictions(sa.getMapParams()); + } } /** @@ -438,7 +440,7 @@ public final class AbilityFactory { * a {@link forge.game.spellability.SpellAbility} object. */ private static final void makeConditions(final SpellAbility sa) { - // SpellAbilityRestrictions should be added in here + // SpellAbilityConditions should be added in here final SpellAbilityCondition condition = sa.getConditions(); condition.setConditions(sa.getMapParams()); } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityKey.java b/forge-game/src/main/java/forge/game/ability/AbilityKey.java index b59c96e35d3..588e5f37712 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityKey.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityKey.java @@ -29,7 +29,6 @@ public enum AbilityKey { Blockers("Blockers"), CanReveal("CanReveal"), CastSA("CastSA"), - CastSACMC("CastSACMC"), Card("Card"), Cards("Cards"), CardLKI("CardLKI"), 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 668277ea322..a8ff585f6ca 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -163,17 +163,15 @@ public class AbilityUtils { return cards; } } - else if (defined.equals("Targeted") && sa instanceof SpellAbility) { + else if ((defined.equals("Targeted") || defined.equals("TargetedCard")) && sa instanceof SpellAbility) { for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) { Iterables.addAll(cards, tc.getTargetCards()); } } else if (defined.equals("TargetedSource") && sa instanceof SpellAbility) { - for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) { - for (SpellAbility s : tc.getTargetSpells()) { + for (TargetChoices tc : ((SpellAbility)sa).getAllTargetChoices()) for (SpellAbility s : tc.getTargetSpells()) { cards.add(s.getHostCard()); } - } } else if (defined.equals("ThisTargetedCard") && sa instanceof SpellAbility) { // do not add parent targeted if (((SpellAbility)sa).getTargets() != null) { @@ -285,7 +283,7 @@ public class AbilityUtils { System.err.println("Warning: couldn't find trigger SA in the chain of SpellAbility " + sa); } } else if (defined.equals("FirstRemembered")) { - Object o = Iterables.getFirst(hostCard.getRemembered(), null); + Object o = hostCard.getFirstRemembered(); if (o != null && o instanceof Card) { cards.add(game.getCardState((Card) o)); } @@ -544,6 +542,9 @@ public class AbilityUtils { } } else if (calcX[0].equals("OriginalHost")) { val = xCount(ability.getOriginalHost(), calcX[1], ability); + } else if (calcX[0].equals("LastStateBattlefield") && ability instanceof SpellAbility) { + Card c = ((SpellAbility) ability).getLastStateBattlefield().get(card); + val = c == null ? 0 : xCount(c, calcX[1], ability); } else if (calcX[0].startsWith("Remembered")) { // Add whole Remembered list to handlePaid final CardCollection list = new CardCollection(); @@ -687,10 +688,9 @@ public class AbilityUtils { Object o = root.getTriggeringObject(AbilityKey.fromString(calcX[0].substring(9))); val = o instanceof Player ? playerXProperty((Player) o, calcX[1], card, ability) : 0; } - else if (calcX[0].equals("TriggeredSpellAbility")) { - final SpellAbility root = sa.getRootAbility(); - SpellAbility sat = (SpellAbility) root.getTriggeringObject(AbilityKey.SpellAbility); - val = calculateAmount(sat.getHostCard(), calcX[1], sat); + else if (calcX[0].equals("TriggeredSpellAbility") || calcX[0].equals("TriggeredStackInstance") || calcX[0].equals("SpellTargeted")) { + final SpellAbility sat = getDefinedSpellAbilities(card, calcX[0], sa).get(0); + val = xCount(sat.getHostCard(), calcX[1], sat); } else if (calcX[0].startsWith("TriggerCount")) { // TriggerCount is similar to a regular Count, but just @@ -730,7 +730,7 @@ public class AbilityUtils { else if (calcX[0].startsWith("Discarded")) { final SpellAbility root = sa.getRootAbility(); list = root.getPaidList("Discarded"); - if ((null == list) && root.isTrigger()) { + if (null == list && root.isTrigger()) { list = root.getHostCard().getSpellPermanent().getPaidList("Discarded"); } } @@ -973,7 +973,7 @@ public class AbilityUtils { final Player player = sa instanceof SpellAbility ? ((SpellAbility)sa).getActivatingPlayer() : card.getController(); - if (defined.equals("Self") || defined.equals("ThisTargetedCard") || defined.startsWith("Valid") || getPaidCards(sa, defined) != null) { + if (defined.equals("Self") || defined.equals("TargetedCard") || defined.equals("ThisTargetedCard") || defined.startsWith("Valid") || getPaidCards(sa, defined) != null) { // do nothing, Self is for Cards, not Players } else if (defined.equals("TargetedOrController")) { players.addAll(getDefinedPlayers(card, "Targeted", sa)); @@ -1849,7 +1849,7 @@ public class AbilityUtils { return doXMath(0, expr, c, ctb); } } - list = CardLists.getValidCards(list, k[1], sa.getActivatingPlayer(), c, sa); + list = CardLists.getValidCards(list, k[1], player, c, sa); return doXMath(list.size(), expr, c, ctb); } @@ -1873,7 +1873,7 @@ public class AbilityUtils { return doXMath(0, expr, c, ctb); } } - list = CardLists.getValidCards(list, k[1], sa.getActivatingPlayer(), c, sa); + list = CardLists.getValidCards(list, k[1], player, c, sa); return doXMath(list.size(), expr, c, ctb); } @@ -1901,14 +1901,14 @@ public class AbilityUtils { // fallback if ctb isn't a spellability if (sq[0].startsWith("LastStateBattlefield")) { final String[] k = l[0].split(" "); - CardCollection list = new CardCollection(game.getLastStateBattlefield()); + CardCollectionView list = game.getLastStateBattlefield(); list = CardLists.getValidCards(list, k[1], player, c, ctb); return doXMath(list.size(), expr, c, ctb); } if (sq[0].startsWith("LastStateGraveyard")) { final String[] k = l[0].split(" "); - CardCollection list = new CardCollection(game.getLastStateGraveyard()); + CardCollectionView list = game.getLastStateGraveyard(); list = CardLists.getValidCards(list, k[1], player, c, ctb); return doXMath(list.size(), expr, c, ctb); } @@ -2167,17 +2167,22 @@ public class AbilityUtils { // Count$CardManaCost if (sq[0].contains("CardManaCost")) { Card ce; - if (sq[0].contains("Equipped") && c.isEquipping()) { - ce = c.getEquipping(); - } - else if (sq[0].contains("Remembered")) { + if (sq[0].contains("Remembered")) { ce = (Card) c.getFirstRemembered(); } else { ce = c; } - return doXMath(ce == null ? 0 : ce.getCMC(), expr, c, ctb); + int cmc = ce == null ? 0 : ce.getCMC(); + + if (sq[0].contains("LKI") && ctb instanceof SpellAbility && ce != null && !ce.isInZone(ZoneType.Stack) && ce.getManaCost() != null) { + if (((SpellAbility) ctb).getXManaCostPaid() != null) { + cmc += ((SpellAbility) ctb).getXManaCostPaid() * ce.getManaCost().countX(); + } + } + + return doXMath(cmc, expr, c, ctb); } if (sq[0].startsWith("RememberedSize")) { @@ -3481,7 +3486,8 @@ public class AbilityUtils { } if (value.equals("OpponentsAttackedThisCombat")) { - return doXMath(game.getCombat().getAttackedOpponents(player).size(), m, source, ctb); + int amount = game.getCombat() == null ? 0 : game.getCombat().getAttackedOpponents(player).size(); + return doXMath(amount, m, source, ctb); } if (value.equals("DungeonsCompleted")) { 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 9cd9c20eade..c6fa06282c7 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -657,7 +657,6 @@ public abstract class SpellAbilityEffect { if (untilCards.isEmpty()) { return; } - CardZoneTable untilTable = new CardZoneTable(); Map moveParams = AbilityKey.newMap(); moveParams.put(AbilityKey.LastStateBattlefield, game.copyLastStateBattlefield()); moveParams.put(AbilityKey.LastStateGraveyard, game.copyLastStateGraveyard()); @@ -697,10 +696,9 @@ public abstract class SpellAbilityEffect { } // no cause there? Card movedCard = game.getAction().moveTo(cell.getRowKey(), newCard, 0, null, moveParams); - untilTable.put(cell.getColumnKey(), cell.getRowKey(), movedCard); + game.getUntilHostLeavesPlayTriggerList().put(cell.getColumnKey(), cell.getRowKey(), movedCard); } } - untilTable.triggerChangesZoneAll(game, null); } }; diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java index 5f066d9df5a..91bc885e70c 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateEffect.java @@ -154,8 +154,6 @@ public class AnimateEffect extends AnimateEffectBase { } } - - List tgts = getCardsfromTargets(sa); if (sa.hasParam("Optional")) { 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 072538ebf2e..7a7e633dc4a 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 @@ -1,41 +1,18 @@ package forge.game.ability.effects; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import forge.game.*; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; - import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; - -import forge.GameCommand; -import forge.card.CardStateName; import forge.card.CardType; +import forge.game.*; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; -import forge.game.card.Card; -import forge.game.card.CardCollection; -import forge.game.card.CardCollectionView; -import forge.game.card.CardLists; -import forge.game.card.CardPredicates; -import forge.game.card.CardState; -import forge.game.card.CardUtil; -import forge.game.card.CardView; -import forge.game.card.CardZoneTable; -import forge.game.card.CounterType; +import forge.game.card.*; import forge.game.event.GameEventCombatChanged; -import forge.game.player.DelayedReveal; -import forge.game.player.Player; -import forge.game.player.PlayerActionConfirmMode; -import forge.game.player.PlayerCollection; -import forge.game.player.PlayerView; +import forge.game.player.*; import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementType; import forge.game.spellability.SpellAbility; @@ -43,13 +20,13 @@ import forge.game.spellability.SpellAbilityStackInstance; import forge.game.trigger.TriggerType; import forge.game.zone.Zone; import forge.game.zone.ZoneType; -import forge.util.Aggregates; -import forge.util.CardTranslation; -import forge.util.Lang; -import forge.util.Localizer; -import forge.util.MessageUtil; -import forge.util.TextUtil; +import forge.util.*; import forge.util.collect.FCollectionView; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; public class ChangeZoneEffect extends SpellAbilityEffect { @@ -689,7 +666,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { // need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger if (sa.hasParam("FaceDown")) { gameCard.turnFaceDown(true); - setFaceDownState(gameCard, sa); + CardFactoryUtil.setFaceDownState(gameCard, sa); } movedCard = game.getAction().moveTo(gameCard.getController().getZone(destination), gameCard, sa, moveParams); @@ -1345,7 +1322,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { // need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger if (sa.hasParam("FaceDown")) { c.turnFaceDown(true); - setFaceDownState(c, sa); + CardFactoryUtil.setFaceDownState(c, sa); } movedCard = game.getAction().moveToPlay(c, c.getController(), sa, moveParams); @@ -1448,6 +1425,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } } } + if (ZoneType.Exile.equals(destination) && sa.hasParam("WithCountersType")) { + CounterType cType = CounterType.getType(sa.getParam("WithCountersType")); + int cAmount = AbilityUtils.calculateAmount(sa.getOriginalHost(), sa.getParamOrDefault("WithCountersAmount", "1"), sa); + GameEntityCounterTable table = new GameEntityCounterTable(); + movedCard.addCounter(cType, cAmount, player, table); + table.replaceCounterEffect(game, sa, true); + } } if (((!ZoneType.Battlefield.equals(destination) && changeType != null && !defined && !changeType.equals("Card")) @@ -1496,39 +1480,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect { && sa.getParam("WithTotalCMC") == null; } - private static void setFaceDownState(Card c, SpellAbility sa) { - final Card source = sa.getHostCard(); - CardState faceDown = c.getFaceDownState(); - - // set New Pt doesn't work because this values need to be copyable for clone effects - if (sa.hasParam("FaceDownPower")) { - faceDown.setBasePower(AbilityUtils.calculateAmount( - source, sa.getParam("FaceDownPower"), sa)); - } - if (sa.hasParam("FaceDownToughness")) { - faceDown.setBaseToughness(AbilityUtils.calculateAmount( - source, sa.getParam("FaceDownToughness"), sa)); - } - - if (sa.hasParam("FaceDownSetType")) { - faceDown.setType(new CardType(Arrays.asList(sa.getParam("FaceDownSetType").split(" & ")), false)); - } - - if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness") - || sa.hasParam("FaceDownSetType")) { - final GameCommand unanimate = new GameCommand() { - private static final long serialVersionUID = 8853789549297846163L; - - @Override - public void run() { - c.clearStates(CardStateName.FaceDown, true); - } - }; - - c.addFaceupCommand(unanimate); - } - } - /** *

* removeFromStack. diff --git a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java index 6e484d4a1a7..1f5902bc90a 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java @@ -156,6 +156,11 @@ public class CharmEffect extends SpellAbilityEffect { } public static boolean makeChoices(SpellAbility sa) { + // CR 700.2g + if (sa.isCopied()) { + return true; + } + //this resets all previous choices sa.setSubAbility(null); 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 d60a1bd5532..af29e46cf5e 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 @@ -1,9 +1,8 @@ package forge.game.ability.effects; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; +import com.google.common.collect.Sets; import forge.game.player.DelayedReveal; import forge.game.player.PlayerView; import forge.util.CardTranslation; @@ -119,6 +118,23 @@ public class ChooseCardEffect extends SpellAbilityEffect { } } } + } else if (sa.hasParam("ChooseParty")) { + Set partyTypes = Sets.newHashSet("Cleric", "Rogue", "Warrior", "Wizard"); + for (final String type : partyTypes) { + CardCollection valids = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), + CardPredicates.isType(type)); + for (Card alreadyChosen : chosen) { + valids.remove(alreadyChosen); + } + if (!valids.isEmpty()) { + final String prompt = Localizer.getInstance().getMessage("lblChoose") + " " + + Lang.nounWithNumeralExceptOne(1, type); + Card c = p.getController().chooseSingleEntityForEffect(valids, sa, prompt, true, null); + if (c != null) { + chosen.add(c); + } + } + } } else if (sa.hasParam("WithTotalPower")) { final int totP = AbilityUtils.calculateAmount(host, sa.getParam("WithTotalPower"), sa); CardCollection negativeCreats = CardLists.filterLEPower(p.getCreaturesInPlay(), -1); @@ -187,6 +203,18 @@ public class ChooseCardEffect extends SpellAbilityEffect { chosenPool.add(choice); } chosen.addAll(chosenPool); + } else if (sa.hasParam("ControlAndNot")) { + String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : Localizer.getInstance().getMessage("lblChooseCreature"); + // Targeted player (p) chooses N creatures that belongs to them + CardCollection tgtPlayerCtrl = CardLists.filterControlledBy(choices, p); + chosen.addAll(p.getController().chooseCardsForEffect(tgtPlayerCtrl, sa, title + " " + "you control", minAmount, validAmount, + !sa.hasParam("Mandatory"), null)); + // Targeted player (p) chooses N creatures that don't belong to them + CardCollection notTgtPlayerCtrl = new CardCollection(choices); + notTgtPlayerCtrl.removeAll(tgtPlayerCtrl); + chosen.addAll(p.getController().chooseCardsForEffect(notTgtPlayerCtrl, sa, title + " " + "you don't control", minAmount, validAmount, + !sa.hasParam("Mandatory"), null)); + } else if ((tgt == null) || p.canBeTargetedBy(sa)) { if (sa.hasParam("AtRandom") && !choices.isEmpty()) { Aggregates.random(choices, validAmount, chosen); @@ -217,8 +245,13 @@ public class ChooseCardEffect extends SpellAbilityEffect { } } } - if (sa.hasParam("Reveal")) { - game.getAction().reveal(chosen, p, true, Localizer.getInstance().getMessage("lblChosenCards") + " "); + if (sa.hasParam("Reveal") && !sa.hasParam("SecretlyChoose")) { + game.getAction().reveal(chosen, p, true, sa.hasParam("RevealTitle") ? sa.getParam("RevealTitle") : Localizer.getInstance().getMessage("lblChosenCards") + " "); + } + } + if(sa.hasParam("Reveal") && sa.hasParam("SecretlyChoose")) { + for (final Player p : tgtPlayers) { + game.getAction().reveal(chosen, p, true, sa.hasParam("RevealTitle") ? sa.getParam("RevealTitle") : Localizer.getInstance().getMessage("lblChosenCards") + " "); } } host.setChosenCards(chosen); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java index 5f95d0c4dd8..c1724471c82 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChooseGenericEffect.java @@ -50,7 +50,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect { List saToRemove = Lists.newArrayList(); for (SpellAbility saChoice : abilities) { - if (!saChoice.getRestrictions().checkOtherRestrictions(host, saChoice, sa.getActivatingPlayer()) ) { + if (saChoice.getRestrictions() != null && !saChoice.getRestrictions().checkOtherRestrictions(host, saChoice, sa.getActivatingPlayer())) { saToRemove.add(saChoice); } else if (saChoice.hasParam("UnlessCost")) { // generic check for if the cost can be paid diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java index a7133e10f6a..5a9da7560c9 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ControlGainEffect.java @@ -124,11 +124,12 @@ public class ControlGainEffect extends SpellAbilityEffect { sa.getParam("Chooser"), sa).get(0) : activator; CardCollectionView choices = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("Choices"), activator, source, sa); - if (!choices.isEmpty()) { - String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : - Localizer.getInstance().getMessage("lblChooseaCard") +" "; - tgtCards = chooser.getController().chooseCardsForEffect(choices, sa, title, 1, 1, false, null); + if (choices.isEmpty()) { + return; } + String title = sa.hasParam("ChoiceTitle") ? sa.getParam("ChoiceTitle") : + Localizer.getInstance().getMessage("lblChooseaCard") +" "; + tgtCards = chooser.getController().chooseCardsForEffect(choices, sa, title, 1, 1, false, null); } else { tgtCards = getDefinedCards(sa); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java index c0fe67d0440..0c1f4a9bef5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CounterEffect.java @@ -122,8 +122,9 @@ public class CounterEffect extends SpellAbilityEffect { CardZoneTable table = new CardZoneTable(); for (final SpellAbility tgtSA : sas) { final Card tgtSACard = tgtSA.getHostCard(); - // should remember even that spell cannot be countered, e.g. Dovescape - // TODO use LKI in case the spell gets countered before (else X amounts would be missing) + // should remember even that spell cannot be countered + // currently all effects using this are targeted in case the spell gets countered before + // so don't need to worry about LKI (else X amounts would be missing) if (sa.hasParam("RememberCounteredCMC")) { sa.getHostCard().addRemembered(Integer.valueOf(tgtSACard.getCMC())); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java index 74fe90383b4..3dec9d6ae4e 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java @@ -67,8 +67,9 @@ public class CountersPutEffect extends SpellAbilityEffect { stringBuilder.append(pronoun ? "they" : who).append(" "); + String desc = sa.getDescription(); + boolean forEach = desc.contains("for each"); if (sa.hasParam("CounterTypes")) { - String desc = sa.getDescription(); if (desc.contains("Put ") && desc.contains(" on ")) { desc = desc.substring(desc.indexOf("Put "), desc.indexOf(" on ") + 4) .replaceFirst("Put ", "puts "); @@ -84,8 +85,8 @@ public class CountersPutEffect extends SpellAbilityEffect { } } - final int amount = AbilityUtils.calculateAmount(card, - sa.getParamOrDefault("CounterNum", "1"), sa); + final String key = forEach ? "ForEachNum" : "CounterNum"; + final int amount = AbilityUtils.calculateAmount(card, sa.getParamOrDefault(key, "1"), sa); if (sa.hasParam("Bolster")) { stringBuilder.append("bolsters ").append(amount).append("."); @@ -155,7 +156,7 @@ public class CountersPutEffect extends SpellAbilityEffect { } } } - stringBuilder.append("."); + stringBuilder.append(forEach ? desc.substring(desc.indexOf(" for each")) : "."); return stringBuilder.toString(); } @@ -495,7 +496,7 @@ public class CountersPutEffect extends SpellAbilityEffect { // need to unfreeze tracker game.getTracker().unfreeze(); - // check if it can recive the Tribute + // check if it can receive the Tribute if (abort) { continue; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java index 07ca5f7cfe6..61aa696a247 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigUntilEffect.java @@ -67,7 +67,7 @@ public class DigUntilEffect extends SpellAbilityEffect { if (revealed.equals(ZoneType.Exile)) { sb.append("and exile all other cards revealed this way."); } - } else { + } else if (revealed != null) { if (revealed.equals(ZoneType.Hand)) { sb.append("all cards revealed this way into their hand"); } @@ -209,7 +209,9 @@ public class DigUntilEffect extends SpellAbilityEffect { Collections.shuffle(revealed, MyRandom.getRandom()); } - if (sa.hasParam("NoneFoundDestination") && found.size() < untilAmount) { + if (sa.hasParam("NoMoveRevealed")) { + //don't do anything + } else if (sa.hasParam("NoneFoundDestination") && found.size() < untilAmount) { // Allow ordering the revealed cards if (noneFoundDest.isKnown() && revealed.size() >= 2) { revealed = (CardCollection)p.getController().orderMoveToZoneList(revealed, noneFoundDest, sa); diff --git a/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java index fb0a99ace99..3a349fdd6ff 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RollDiceEffect.java @@ -1,6 +1,7 @@ package forge.game.ability.effects; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -144,6 +145,9 @@ public class RollDiceEffect extends SpellAbilityEffect { sa.setSVar(sa.getParam("OtherSVar"), Integer.toString(other)); } } + if (sa.hasParam("UseHighestRoll")) { + total = Collections.max(rolls); + } Map diceAbilities = sa.getAdditionalAbilities(); SpellAbility resultAbility = null; diff --git a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java index 39c3d3eb71a..820e1ae2a91 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java @@ -162,6 +162,10 @@ public class SetStateEffect extends SpellAbilityEffect { hasTransformed = gameCard.turnFaceUp(true, true, sa); } else { hasTransformed = gameCard.changeCardState(mode, sa.getParam("NewState"), sa); + if (gameCard.isFaceDown() && (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness") + || sa.hasParam("FaceDownSetType"))) { + CardFactoryUtil.setFaceDownState(gameCard, sa); + } } if (hasTransformed) { if (sa.isMorphUp()) { 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 ef63ba39d97..4c6e602f870 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -256,6 +256,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private Map damage = Maps.newHashMap(); private boolean hasBeenDealtDeathtouchDamage; + private boolean hasBeenDealtExcessDamageThisTurn; // regeneration private FCollection shields = new FCollection<>(); @@ -3016,7 +3017,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { public final FCollectionView getBasicSpells() { return getBasicSpells(currentState); } - public final FCollectionView getBasicSpells(CardState state) { final FCollection res = new FCollection<>(); for (final SpellAbility sa : state.getNonManaAbilities()) { @@ -5361,6 +5361,13 @@ public class Card extends GameEntity implements Comparable, IHasSVars { this.hasBeenDealtDeathtouchDamage = hasBeenDealtDeatchtouchDamage; } + public final boolean hasBeenDealtExcessDamageThisTurn() { + return hasBeenDealtExcessDamageThisTurn; + } + public final void setHasBeenDealtExcessDamageThisTurn(final boolean bool) { + this.hasBeenDealtExcessDamageThisTurn = bool; + } + public final Map getAssignedDamageMap() { return assignedDamageMap; } @@ -5515,7 +5522,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } // Defending player at the time the damage was dealt runParams.put(AbilityKey.DefendingPlayer, game.getCombat() != null ? game.getCombat().getDefendingPlayerRelatedTo(source) : null); - getGame().getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, false); + getGame().getTriggerHandler().runTrigger(TriggerType.DamageDone, runParams, true); DamageType damageType = DamageType.Normal; if (isPlaneswalker()) { // 120.3c @@ -6158,6 +6165,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { setDamage(0); } setHasBeenDealtDeathtouchDamage(false); + setHasBeenDealtExcessDamageThisTurn(false); setRegeneratedThisTurn(0); resetShield(); setBecameTargetThisTurn(false); diff --git a/forge-game/src/main/java/forge/game/card/CardDamageMap.java b/forge-game/src/main/java/forge/game/card/CardDamageMap.java index ad93f326739..37dd9c620b8 100644 --- a/forge-game/src/main/java/forge/game/card/CardDamageMap.java +++ b/forge-game/src/main/java/forge/game/card/CardDamageMap.java @@ -113,6 +113,7 @@ public class CardDamageMap extends ForwardingTable { int excess = sum - (damaged.getKey().hasBeenDealtDeathtouchDamage() ? 1 : damaged.getValue()); if (excess > 0) { + damaged.getKey().setHasBeenDealtExcessDamageThisTurn(true); // Run triggers final Map runParams = AbilityKey.newMap(); runParams.put(AbilityKey.DamageTarget, damaged.getKey()); 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 3f454c027cf..c99ec287ff7 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import forge.GameCommand; import forge.game.event.GameEventCardForetold; import forge.game.trigger.TriggerType; import org.apache.commons.lang3.StringUtils; @@ -1147,29 +1148,21 @@ public class CardFactoryUtil { + " | ValidCard$ Card.Self | Secondary$ True" + " | TriggerDescription$ Fabricate " + n + " (" + inst.getReminderText() + ")"; - final String choose = "DB$ GenericChoice | AILogic$ " + name; - final String counter = "DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ " + n + - " | IsPresent$ Card.StrictlySelf | SpellDescription$ Put " - + Lang.nounWithNumeral(n, "+1/+1 counter") + " on it."; - final String token = "DB$ Token | TokenAmount$ " + n + " | TokenScript$ c_1_1_a_servo | TokenOwner$ You " - + " | SpellDescription$ Create " + final String token = "DB$ Token | TokenAmount$ " + n + " | TokenScript$ c_1_1_a_servo" + + " | UnlessCost$ AddCounter<" + n + "/P1P1> | UnlessPayer$ You | UnlessAI$ " + name + + " | SpellDescription$ Fabricate - Create " + Lang.nounWithNumeral(n, "1/1 colorless Servo artifact creature token") + "."; final Trigger trigger = TriggerHandler.parseTrigger(trigStr, card, intrinsic); - SpellAbility saChoose = AbilityFactory.getAbility(choose, card); - - List list = Lists.newArrayList(); - list.add((AbilitySub)AbilityFactory.getAbility(counter, card)); - list.add((AbilitySub)AbilityFactory.getAbility(token, card)); - saChoose.setAdditionalAbilityList("Choices", list); + SpellAbility saChoose = AbilityFactory.getAbility(token, card); saChoose.setIntrinsic(intrinsic); trigger.setOverridingAbility(saChoose); inst.addTrigger(trigger); } else if (keyword.startsWith("Fading")) { - String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Secondary$ True " + + String upkeepTrig = "Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Secondary$ True" + " | TriggerDescription$ At the beginning of your upkeep, remove a fade counter from CARDNAME. If you can't, sacrifice CARDNAME."; final String removeCounterStr = "DB$ RemoveCounter | Defined$ Self | CounterType$ FADE | CounterNum$ 1 | RememberRemoved$ True"; @@ -3748,4 +3741,37 @@ public class CardFactoryUtil { re.setOverridingAbility(saExile); card.addReplacementEffect(re); } + + public static void setFaceDownState(Card c, SpellAbility sa) { + final Card source = sa.getHostCard(); + CardState faceDown = c.getFaceDownState(); + + // set New Pt doesn't work because this values need to be copyable for clone effects + if (sa.hasParam("FaceDownPower")) { + faceDown.setBasePower(AbilityUtils.calculateAmount( + source, sa.getParam("FaceDownPower"), sa)); + } + if (sa.hasParam("FaceDownToughness")) { + faceDown.setBaseToughness(AbilityUtils.calculateAmount( + source, sa.getParam("FaceDownToughness"), sa)); + } + + if (sa.hasParam("FaceDownSetType")) { + faceDown.setType(new CardType(Arrays.asList(sa.getParam("FaceDownSetType").split(" & ")), false)); + } + + if (sa.hasParam("FaceDownPower") || sa.hasParam("FaceDownToughness") + || sa.hasParam("FaceDownSetType")) { + final GameCommand unanimate = new GameCommand() { + private static final long serialVersionUID = 8853789549297846163L; + + @Override + public void run() { + c.clearStates(CardStateName.FaceDown, true); + } + }; + + c.addFaceupCommand(unanimate); + } + } } 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 cc63af4777c..0af9888951c 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -607,7 +607,7 @@ public class CardProperty { return false; } } else if (property.startsWith("Cloned")) { - if ((card.getCloneOrigin() == null) || !card.getCloneOrigin().equals(source)) { + if (card.getCloneOrigin() == null || !card.getCloneOrigin().equals(source)) { return false; } } else if (property.startsWith("SharesCMCWith")) { @@ -1180,6 +1180,10 @@ public class CardProperty { if (card.getAssignedDamage(false, null) == 0) { return false; } + } else if (property.startsWith("wasDealtExcessDamageThisTurn")) { + if (!card.hasBeenDealtExcessDamageThisTurn()) { + return false; + } } else if (property.startsWith("wasDealtDamageByThisGame")) { int idx = source.getDamageHistory().getThisGameDamaged().indexOf(card); if (idx == -1) { @@ -1475,6 +1479,14 @@ public class CardProperty { if (!card.getManaCost().getShortString().equals(property.substring(8))) { return false; } + } else if (property.equals("HasCounters")) { + if (!card.hasCounters()) { + return false; + } + } else if (property.equals("NoCounters")) { + if (card.hasCounters()) { + return false; + } } // syntax example: countersGE9 P1P1 or countersLT12TIME (greater number @@ -1691,7 +1703,7 @@ public class CardProperty { return false; } } else if (property.equals("hadToAttackThisCombat")) { - AttackRequirement e = game.getCombat().getAttackConstraints().getRequirements().get(card); + AttackRequirement e = combat.getAttackConstraints().getRequirements().get(card); if (e == null || !e.hasCreatureRequirement() || !e.getAttacker().equalsWithTimestamp(card)) { return false; } @@ -1807,14 +1819,6 @@ public class CardProperty { if (!card.hasNoAbilities()) { return false; } - } else if (property.equals("HasCounters")) { - if (!card.hasCounters()) { - return false; - } - } else if (property.equals("NoCounters")) { - if (card.hasCounters()) { - return false; - } } else if (property.equals("castKeyword")) { SpellAbility castSA = card.getCastSA(); if (castSA == null) { @@ -1937,8 +1941,16 @@ public class CardProperty { } else if (property.startsWith("Triggered")) { if (spellAbility instanceof SpellAbility) { final String key = property.substring(9); - CardCollection cc = (CardCollection) ((SpellAbility)spellAbility).getTriggeringObject(AbilityKey.fromString(key)); - if (cc == null || !cc.contains(card)) { + Object o = ((SpellAbility)spellAbility).getTriggeringObject(AbilityKey.fromString(key)); + boolean found = false; + if (o != null) { + if (o instanceof CardCollection) { + found = ((CardCollection) o).contains(card); + } else { + found = card.equals(o); + } + } + if (!found) { return false; } } else { diff --git a/forge-game/src/main/java/forge/game/card/CardZoneTable.java b/forge-game/src/main/java/forge/game/card/CardZoneTable.java index f0e9bb8d387..5658acdf97b 100644 --- a/forge-game/src/main/java/forge/game/card/CardZoneTable.java +++ b/forge-game/src/main/java/forge/game/card/CardZoneTable.java @@ -56,7 +56,12 @@ public class CardZoneTable extends ForwardingTable runParams = AbilityKey.newMap(); runParams.put(AbilityKey.Cards, new CardZoneTable(this)); runParams.put(AbilityKey.Cause, cause); - game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, AbilityKey.newMap(runParams), false); + game.getTriggerHandler().runTrigger(TriggerType.ChangesZoneAll, runParams, false); + } + final CardZoneTable untilTable = game.getUntilHostLeavesPlayTriggerList(); + if (this != untilTable) { + untilTable.triggerChangesZoneAll(game, null); + untilTable.clear(); } } diff --git a/forge-game/src/main/java/forge/game/cost/CostPutCounter.java b/forge-game/src/main/java/forge/game/cost/CostPutCounter.java index 8b8ed940eb5..ca4d4fd2b45 100644 --- a/forge-game/src/main/java/forge/game/cost/CostPutCounter.java +++ b/forge-game/src/main/java/forge/game/cost/CostPutCounter.java @@ -141,16 +141,16 @@ public class CostPutCounter extends CostPartWithList { public final boolean canPay(final SpellAbility ability, final Player payer, final boolean effect) { final Card source = ability.getHostCard(); if (this.payCostFromSource()) { - return source.canReceiveCounters(this.counter); - } else { - // 3 Cards have Put a -1/-1 Counter on a Creature you control. - List typeList = CardLists.getValidCards(source.getGame().getCardsIn(ZoneType.Battlefield), - this.getType().split(";"), payer, source, ability); - - typeList = CardLists.filter(typeList, CardPredicates.canReceiveCounters(this.counter)); - - return !typeList.isEmpty(); + return source.isInPlay() && source.canReceiveCounters(this.counter); } + + // 3 Cards have Put a -1/-1 Counter on a Creature you control. + List typeList = CardLists.getValidCards(source.getGame().getCardsIn(ZoneType.Battlefield), + this.getType().split(";"), payer, source, ability); + + typeList = CardLists.filter(typeList, CardPredicates.canReceiveCounters(this.counter)); + + return !typeList.isEmpty(); } /* 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 8ab507529ac..b267caa6292 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -836,7 +836,6 @@ public class PhaseHandler implements java.io.Serializable { game.getStack().onNextTurn(); game.getTriggerHandler().clearThisTurnDelayedTrigger(); - game.getTriggerHandler().resetTurnTriggerState(); Player next = getNextActivePlayer(); while (next.hasLost()) { diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 3e3a90e74f0..e6c7dc253a7 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1603,6 +1603,12 @@ public class Player extends GameEntity implements Comparable { } } + // MilledAll trigger + final Map runParams = AbilityKey.newMap(); + runParams.put(AbilityKey.Cards, milled); + runParams.put(AbilityKey.Player, this); + game.getTriggerHandler().runTrigger(TriggerType.MilledAll, runParams, false); + return milled; } 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 7ce788fd936..5a2631fdba9 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Lists; @@ -44,6 +45,7 @@ import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.card.Card; import forge.game.card.CardCollection; +import forge.game.card.CardCollectionView; import forge.game.card.CardDamageMap; import forge.game.card.CardState; import forge.game.card.CardTraitChanges; @@ -175,9 +177,17 @@ public class ReplacementHandler { final Card c = preList.get(crd); for (final ReplacementEffect replacementEffect : c.getReplacementEffects()) { - // Use "CheckLKIZone" parameter to test for effects that care abut where the card was last (e.g. Kalitas, Traitor of Ghet + Zone cardZone; + // Use "CheckLKIZone" parameter to test for effects that care about where the card was last (e.g. Kalitas, Traitor of Ghet // getting hit by mass removal should still produce tokens). - Zone cardZone = "True".equals(replacementEffect.getParam("CheckSelfLKIZone")) ? game.getChangeZoneLKIInfo(c).getLastKnownZone() : game.getZoneOf(c); + if ("True".equals(replacementEffect.getParam("CheckSelfLKIZone"))) { + cardZone = game.getChangeZoneLKIInfo(c).getLastKnownZone(); + if (cardZone.is(ZoneType.Battlefield) && runParams.containsKey(AbilityKey.LastStateBattlefield) && runParams.get(AbilityKey.LastStateBattlefield) != null && !((CardCollectionView) runParams.get(AbilityKey.LastStateBattlefield)).contains(crd)) { + continue; + } + } else { + cardZone = game.getZoneOf(c); + } // Replacement effects that are tied to keywords (e.g. damage prevention effects - if the keyword is removed, the replacement // effect should be inactive) @@ -351,8 +361,8 @@ public class ReplacementHandler { tailend = tailend.getSubAbility(); } while(tailend != null); - effectSA.setLastStateBattlefield(game.getLastStateBattlefield()); - effectSA.setLastStateGraveyard(game.getLastStateGraveyard()); + effectSA.setLastStateBattlefield((CardCollectionView) ObjectUtils.firstNonNull(runParams.get(AbilityKey.LastStateBattlefield), game.getLastStateBattlefield())); + effectSA.setLastStateGraveyard((CardCollectionView) ObjectUtils.firstNonNull(runParams.get(AbilityKey.LastStateGraveyard), game.getLastStateGraveyard())); if (replacementEffect.isIntrinsic()) { effectSA.setIntrinsic(true); effectSA.changeText(); 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 c1f5dc6e9d7..abb22e116ea 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -179,7 +179,7 @@ public class AbilityManaPart implements java.io.Serializable { if (source.isLand() && root.isManaAbility() && root.getPayCosts() != null && root.getPayCosts().hasTapCost()) { player.setTappedLandForManaThisTurn(true); } - } // end produceMana(String) + } /** *

@@ -348,7 +348,7 @@ public class AbilityManaPart implements java.io.Serializable { // "can't" zone restriction – shouldn't be mixed with other restrictions if (restriction.startsWith("CantCastSpellFrom")) { - if (!sa.isSpell()) { // + if (!sa.isSpell()) { return true; } final ZoneType badZone = ZoneType.smartValueOf(restriction.substring(17)); 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 ab9d7fc1426..e12a551dae3 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -138,7 +138,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit /** The pay costs. */ private Cost payCosts; - private SpellAbilityRestriction restrictions = new SpellAbilityRestriction(); + private SpellAbilityRestriction restrictions; private SpellAbilityCondition conditions = new SpellAbilityCondition(); private AbilitySub subAbility; @@ -179,8 +179,8 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit private StaticAbility mayPlay; - private CardCollection lastStateBattlefield = null; - private CardCollection lastStateGraveyard = null; + private CardCollection lastStateBattlefield; + private CardCollection lastStateGraveyard; private CardCollection rollbackEffects = new CardCollection(); @@ -214,6 +214,9 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit view0 = new SpellAbilityView(this); } view = view0; + if (!(this instanceof AbilitySub)) { + restrictions = new SpellAbilityRestriction(); + } } @Override 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 8f72ba69673..583830f2dc0 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java @@ -443,7 +443,7 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { if (getPresentDefined() != null) { list = AbilityUtils.getDefinedObjects(sa.getHostCard(), getPresentDefined(), sa); } else { - list = new FCollection(game.getCardsIn(getPresentZone())); + list = new FCollection<>(game.getCardsIn(getPresentZone())); } final int left = Iterables.size(Iterables.filter(list, GameObjectPredicates.restriction(getIsPresent().split(","), activator, c, sa))); diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java index a566c4afc31..c3920199e4c 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityStackInstance.java @@ -214,7 +214,6 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView { public final int getXManaPaid() { return xManaPaid; } - public final void setXManaPaid(int x) { xManaPaid = x; } @@ -341,7 +340,6 @@ public class SpellAbilityStackInstance implements IIdentifiable, IHasCardView { public Player getActivatingPlayer() { return activatingPlayer; } - public void setActivatingPlayer(Player activatingPlayer0) { if (activatingPlayer == activatingPlayer0) { return; } activatingPlayer = activatingPlayer0; 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 e0a45949555..58441905bce 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java @@ -45,6 +45,7 @@ import forge.game.zone.Zone; import forge.game.zone.ZoneType; import forge.util.CardTranslation; import forge.util.Expressions; +import forge.util.FileSection; import forge.util.Lang; import forge.util.TextUtil; @@ -89,41 +90,12 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone * @return a {@link java.util.HashMap} object. */ private static Map parseParams(final String abString, final Card hostCard) { - final Map mapParameters = Maps.newHashMap(); - if (!(abString.length() > 0)) { throw new RuntimeException("StaticEffectFactory : getAbility -- abString too short in " + hostCard.getName() + ": [" + abString + "]"); } - final String[] a = abString.split("\\|"); - - for (int aCnt = 0; aCnt < a.length; aCnt++) { - a[aCnt] = a[aCnt].trim(); - } - - if (!(a.length > 0)) { - throw new RuntimeException("StaticEffectFactory : getAbility -- a[] too short in " + hostCard.getName()); - } - - for (final String element : a) { - final String[] aa = element.split("\\$"); - - for (int aaCnt = 0; aaCnt < aa.length; aaCnt++) { - aa[aaCnt] = aa[aaCnt].trim(); - } - - if (aa.length != 2) { - final StringBuilder sb = new StringBuilder(); - sb.append("StaticEffectFactory Parsing Error: Split length of "); - sb.append(element).append(" in ").append(hostCard.getName()).append(" is not 2."); - throw new RuntimeException(sb.toString()); - } - - mapParameters.put(aa[0], aa[1]); - } - - return mapParameters; + return FileSection.parseToMap(abString, FileSection.DOLLAR_SIGN_KV_SEPARATOR); } /** 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 b9f957a509f..2fd7291fa80 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -111,12 +111,18 @@ public final class StaticAbilityContinuous { final List affectedPlayers = StaticAbilityContinuous.getAffectedPlayers(stAb); final Game game = hostCard.getGame(); - final StaticEffect se = game.getStaticEffects().getStaticEffect(stAb); + final StaticEffects effects = game.getStaticEffects(); + final StaticEffect se = effects.getStaticEffect(stAb); se.setAffectedCards(affectedCards); se.setAffectedPlayers(affectedPlayers); se.setParams(params); se.setTimestamp(hostCard.getTimestamp()); + // nothing more to do + if (stAb.hasParam("Affected") && affectedPlayers.isEmpty() && affectedCards.isEmpty()) { + return affectedCards; + } + String addP = ""; int powerBonus = 0; String addT = ""; @@ -161,7 +167,6 @@ public final class StaticAbilityContinuous { //Global rules changes if (layer == StaticAbilityLayer.RULES && params.containsKey("GlobalRule")) { - final StaticEffects effects = game.getStaticEffects(); effects.setGlobalRuleChange(GlobalRuleChange.fromString(params.get("GlobalRule"))); } @@ -197,8 +202,6 @@ public final class StaticAbilityContinuous { // update keywords with Chosen parts final String hostCardUID = Integer.toString(hostCard.getId()); // Protection with "doesn't remove" effect - final ColorSet colorsYouCtrl = CardUtil.getColorsYouCtrl(controller); - Iterables.removeIf(addKeywords, new Predicate() { @Override public boolean apply(String input) { @@ -246,6 +249,8 @@ public final class StaticAbilityContinuous { } // two variants for Red vs. red in keyword if (input.contains("ColorsYouCtrl") || input.contains("colorsYouCtrl")) { + final ColorSet colorsYouCtrl = CardUtil.getColorsYouCtrl(controller); + for (byte color : colorsYouCtrl) { final String colorWord = MagicColor.toLongString(color); String y = input.replaceAll("ColorsYouCtrl", StringUtils.capitalize(colorWord)); @@ -718,6 +723,7 @@ public final class StaticAbilityContinuous { // add P/T bonus if (layer == StaticAbilityLayer.MODIFYPT) { if (addP.contains("Affected")) { + // TODO don't calculate these above if this gets used instead powerBonus = AbilityUtils.calculateAmount(affectedCard, addP, stAb, true); } if (addT.contains("Affected")) { 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 5b26b7adcbe..ad862b5c5bf 100644 --- a/forge-game/src/main/java/forge/game/trigger/Trigger.java +++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java @@ -77,10 +77,6 @@ public abstract class Trigger extends TriggerReplacementBase { private List triggerRemembered = Lists.newArrayList(); - // number of times this trigger was activated this this turn - // used to handle once-per-turn triggers like Crawling Sensation - private int numberTurnActivations = 0; - private Set validPhases; private SpellAbility spawningAbility; @@ -388,7 +384,8 @@ public abstract class Trigger extends TriggerReplacementBase { for (Player opp : this.getHostCard().getController().getOpponents()) { if (opp.equals(attackedP)) { continue; - } else if (opp.getLife() > life) { + } + if (opp.getLife() > life) { found = true; break; } @@ -536,16 +533,13 @@ public abstract class Trigger extends TriggerReplacementBase { } public int getActivationsThisTurn() { - return this.numberTurnActivations; + return hostCard.getAbilityActivatedThisTurn(this.getOverridingAbility()); } public void triggerRun() { - this.numberTurnActivations++; - } - - // Resets the state stored each turn for per-turn and per-instance restriction - public void resetTurnState() { - this.numberTurnActivations = 0; + if (this.getOverridingAbility() != null) { + hostCard.addAbilityActivated(this.getOverridingAbility()); + } } /** {@inheritDoc} */ 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 47e199d060e..4ae8797484f 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -346,15 +346,6 @@ public class TriggerHandler { waitingTriggers.clear(); } - public void resetTurnTriggerState() { - for (final Trigger t : activeTriggers) { - t.resetTurnState(); - } - for (final Trigger t : delayedTriggers) { - t.resetTurnState(); - } - } - private boolean runNonStaticTriggersForPlayer(final Player player, final TriggerWaiting wt, final List delayedTriggersWorkingCopy) { final TriggerType mode = wt.getMode(); final Map runParams = wt.getParams(); @@ -568,24 +559,11 @@ public class TriggerHandler { sa.setActivatingPlayer(p); } - if (regtrig.hasParam("RememberController")) { - host.addRemembered(sa.getActivatingPlayer()); - } - if (regtrig.hasParam("RememberTriggeringCard")) { Card triggeredCard = ((Card) sa.getTriggeringObject(AbilityKey.Card)); host.addRemembered(triggeredCard); } - if (regtrig.hasParam("RememberKey")) { - host.addRemembered(runParams.get(AbilityKey.fromString(regtrig.getParam("RememberKey")))); - } - - if (regtrig.hasParam("RememberAmount")) { - Integer amount = (Integer) sa.getTriggeringObject(AbilityKey.fromString(regtrig.getParam("RememberAmount"))); - host.addRemembered(amount); - } - sa.setStackDescription(sa.toString()); Player decider = null; diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerMilledAll.java b/forge-game/src/main/java/forge/game/trigger/TriggerMilledAll.java new file mode 100644 index 00000000000..fa2fd2ea73c --- /dev/null +++ b/forge-game/src/main/java/forge/game/trigger/TriggerMilledAll.java @@ -0,0 +1,71 @@ +package forge.game.trigger; + +import forge.game.ability.AbilityKey; +import forge.game.card.Card; +import forge.game.card.CardCollection; +import forge.game.card.CardLists; +import forge.game.spellability.SpellAbility; +import forge.util.Localizer; + +import java.util.Map; + +/** + *

+ * TriggerMilledAll class. + *

+ */ +public class TriggerMilledAll extends Trigger { + + /** + *

+ * Constructor for TriggerMilledAll + *

+ * + * @param params + * a {@link java.util.HashMap} object. + * @param host + * a {@link forge.game.card.Card} object. + * @param intrinsic + * the intrinsic + */ + public TriggerMilledAll (final Map params, final Card host, final boolean intrinsic) { + super(params, host, intrinsic); + } + + /** {@inheritDoc} + * @param runParams*/ + @Override + public final boolean performTest(final Map runParams) { + + if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Player))) { + return false; + } + if (!matchesValidParam("ValidCard", runParams.get(AbilityKey.Cards))) { + return false; + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public final void setTriggeringObjects(final SpellAbility sa, Map runParams) { + CardCollection cards = (CardCollection) runParams.get(AbilityKey.Cards); + + if (hasParam("ValidCard")) { + cards = CardLists.getValidCards(cards, getParam("ValidCard"), getHostCard().getController(), + getHostCard(), this); + } + + sa.setTriggeringObject(AbilityKey.Cards, cards); + sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player); + } + + @Override + public String getImportantStackObjects(SpellAbility sa) { + StringBuilder sb = new StringBuilder(); + sb.append(Localizer.getInstance().getMessage("lblPlayer")).append(": "); + sb.append(sa.getTriggeringObject(AbilityKey.Player)); + return sb.toString(); + } +} diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerScry.java b/forge-game/src/main/java/forge/game/trigger/TriggerScry.java index 5512ddbc89a..05a90284011 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerScry.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerScry.java @@ -64,8 +64,7 @@ public class TriggerScry extends Trigger { /** {@inheritDoc} */ @Override public final void setTriggeringObjects(final SpellAbility sa, Map runParams) { - sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player); - sa.setTriggeringObjectsFrom(runParams, AbilityKey.ScryNum); + sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player, AbilityKey.ScryNum); } @Override diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCastOrCopy.java b/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCastOrCopy.java index b253d660fe3..7c2a7c85189 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCastOrCopy.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerSpellAbilityCastOrCopy.java @@ -313,8 +313,7 @@ public class TriggerSpellAbilityCastOrCopy extends Trigger { AbilityKey.Player, AbilityKey.Activator, AbilityKey.CurrentStormCount, - AbilityKey.CurrentCastSpells, - AbilityKey.CastSACMC + AbilityKey.CurrentCastSpells ); } 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 4f7c94c9ba8..2490cb3b9aa 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerType.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerType.java @@ -80,6 +80,7 @@ public enum TriggerType { LifeGained(TriggerLifeGained.class), LifeLost(TriggerLifeLost.class), LosesGame(TriggerLosesGame.class), + MilledAll(TriggerMilledAll.class), Mutates(TriggerMutates.class), NewGame(TriggerNewGame.class), PayCumulativeUpkeep(TriggerPayCumulativeUpkeep.class), diff --git a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java index 277fd56868c..012c3712426 100644 --- a/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java +++ b/forge-game/src/main/java/forge/game/trigger/WrappedAbility.java @@ -175,21 +175,6 @@ public class WrappedAbility extends Ability { return sa.copy(); } - @Override - public Player getActivatingPlayer() { - return sa.getActivatingPlayer(); - } - - @Override - public String getDescription() { - return sa.getDescription(); - } - - @Override - public ManaCost getMultiKickerManaCost() { - return sa.getMultiKickerManaCost(); - } - @Override public SpellAbilityRestriction getRestrictions() { return sa.getRestrictions(); @@ -249,14 +234,18 @@ public class WrappedAbility extends Ability { } @Override - public AbilitySub getSubAbility() { - return sa.getSubAbility(); + public void setStackDescription(final String s) { + sa.setStackDescription(s); } @Override public TargetRestrictions getTargetRestrictions() { return sa.getTargetRestrictions(); } + @Override + public void setTargetRestrictions(final TargetRestrictions tgt) { + sa.setTargetRestrictions(tgt); + } @Override public Card getTargetCard() { @@ -267,6 +256,10 @@ public class WrappedAbility extends Ability { public TargetChoices getTargets() { return sa.getTargets(); } + @Override + public void setTargets(TargetChoices targets) { + sa.setTargets(targets); + } @Override public boolean isAbility() { @@ -327,16 +320,28 @@ public class WrappedAbility extends Ability { // sa.resetOnceResolved(); } + @Override + public Player getActivatingPlayer() { + return sa.getActivatingPlayer(); + } @Override public void setActivatingPlayer(final Player player) { sa.setActivatingPlayer(player); } + @Override + public String getDescription() { + return sa.getDescription(); + } @Override public void setDescription(final String s) { sa.setDescription(s); } + @Override + public ManaCost getMultiKickerManaCost() { + return sa.getMultiKickerManaCost(); + } @Override public void setMultiKickerManaCost(final ManaCost cost) { sa.setMultiKickerManaCost(cost); @@ -358,25 +363,14 @@ public class WrappedAbility extends Ability { } @Override - public void setStackDescription(final String s) { - sa.setStackDescription(s); + public AbilitySub getSubAbility() { + return sa.getSubAbility(); } - @Override public void setSubAbility(final AbilitySub subAbility) { sa.setSubAbility(subAbility); } - @Override - public void setTargetRestrictions(final TargetRestrictions tgt) { - sa.setTargetRestrictions(tgt); - } - - @Override - public void setTargets(TargetChoices targets) { - sa.setTargets(targets); - } - @Override public void setTargetCard(final Card card) { sa.setTargetCard(card); 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 48efbd76acd..7d500de6c50 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -41,7 +41,6 @@ import forge.game.GameObject; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; -import forge.game.ability.effects.CharmEffect; import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardUtil; @@ -72,6 +71,7 @@ import forge.util.TextUtil; */ public class MagicStack /* extends MyObservable */ implements Iterable { private final List simultaneousStackEntryList = Lists.newArrayList(); + private final List activePlayerSAs = Lists.newArrayList(); // They don't provide a LIFO queue, so had to use a deque private final Deque stack = new LinkedBlockingDeque<>(); @@ -308,7 +308,6 @@ public class MagicStack /* extends MyObservable */ implements Iterable activePlayerSAs = Lists.newArrayList(); - final List failedSAs = Lists.newArrayList(); + activePlayerSAs.clear(); for (int i = 0; i < simultaneousStackEntryList.size(); i++) { SpellAbility sa = simultaneousStackEntryList.get(i); Player activator = sa.getActivatingPlayer(); - if (sa.getApi() == ApiType.Charm) { - if (!CharmEffect.makeChoices(sa)) { - // 603.3c If no mode is chosen, the ability is removed from the stack. - failedSAs.add(sa); - continue; - } - } - if (activator == null) { - if (sa.getHostCard().getController().equals(activePlayer)) { - activePlayerSAs.add(sa); - } - } else { - if (activator.equals(activePlayer)) { - activePlayerSAs.add(sa); - } + activator = sa.getHostCard().getController(); + } + if (activator.equals(activePlayer)) { + activePlayerSAs.add(sa); } } simultaneousStackEntryList.removeAll(activePlayerSAs); - simultaneousStackEntryList.removeAll(failedSAs); if (activePlayerSAs.isEmpty()) { return false; } activePlayer.getController().orderAndPlaySimultaneousSa(activePlayerSAs); + activePlayerSAs.clear(); return true; } @@ -878,6 +865,12 @@ public class MagicStack /* extends MyObservable */ implements Iterable { * @param scr */ public void auditUpdate(FTextArea tar, FScrollPane scr) { - // Get top-level Forge objects - CardDb cardDb = StaticData.instance().getCommonCards(); - CardDb variantDb = StaticData.instance().getVariantCards(); - TokenDb tokenDb = StaticData.instance().getAllTokens(); - CardEdition.Collection editions = StaticData.instance().getEditions(); - - int missingCount = 0; - int notImplementedCount = 0; - - final StringBuffer nifSB = new StringBuffer(); // NO IMAGE FOUND BUFFER - final StringBuffer cniSB = new StringBuffer(); // CARD NOT IMPLEMENTED BUFFER + StringBuffer nifSB = new StringBuffer(); // NO IMAGE FOUND BUFFER + StringBuffer cniSB = new StringBuffer(); // CARD NOT IMPLEMENTED BUFFER nifSB.append("\n\n-------------------\n"); nifSB.append("NO IMAGE FOUND LIST\n"); @@ -252,118 +232,7 @@ public enum VSubmenuDownloaders implements IVSubmenu { cniSB.append("UNIMPLEMENTED CARD LIST\n"); cniSB.append("-------------------\n\n"); - for (CardEdition e : editions) { - if (CardEdition.Type.FUNNY.equals(e.getType())) - continue; - boolean nifHeader = false; - boolean cniHeader = false; - boolean tokenHeader = false; - - String imagePath; - int artIndex = 1; - - HashMap> cardCount = new HashMap<>(); - for (CardInSet c : e.getAllCardsInSet()) { - if (cardCount.containsKey(c.name)) { - cardCount.put(c.name, Pair.of(c.collectorNumber.startsWith("F"), cardCount.get(c.name).getRight() + 1)); - } else { - cardCount.put(c.name, Pair.of(c.collectorNumber.startsWith("F"), 1)); - } - } - - // loop through the cards in this edition, considering art variations... - for (Entry> entry : cardCount.entrySet()) { - String c = entry.getKey(); - artIndex = entry.getValue().getRight(); - - PaperCard cp = cardDb.getCard(c, e.getCode(), artIndex); - if (cp == null) { - cp = variantDb.getCard(c, e.getCode(), artIndex); - } - - if (cp == null) { - if (entry.getValue().getLeft()) //skip funny cards - continue; - if (!cniHeader) { - cniSB.append("Edition: ").append(e.getName()).append(" ").append("(").append(e.getCode()).append("/").append(e.getCode2()).append(")\n"); - cniHeader = true; - } - cniSB.append(" ").append(c).append("\n"); - notImplementedCount++; - continue; - } - - // check the front image - imagePath = ImageUtil.getImageRelativePath(cp, false, true, false); - if (imagePath != null) { - File file = ImageKeys.getImageFile(imagePath); - if (file == null) { - if (!nifHeader) { - nifSB.append("Edition: ").append(e.getName()).append(" ").append("(").append(e.getCode()).append("/").append(e.getCode2()).append(")\n"); - nifHeader = true; - } - nifSB.append(" ").append(imagePath).append("\n"); - missingCount++; - } - } - - // check the back face - if (cp.hasBackFace()) { - imagePath = ImageUtil.getImageRelativePath(cp, true, true, false); - if (imagePath != null) { - File file = ImageKeys.getImageFile(imagePath); - if (file == null) { - if (!nifHeader) { - nifSB.append("Edition: ").append(e.getName()).append(" ").append("(").append(e.getCode()).append("/").append(e.getCode2()).append(")\n"); - nifHeader = true; - } - nifSB.append(" ").append(imagePath).append("\n"); - missingCount++; - } - } - } - } - - // TODO: Audit token images here... - for(Entry tokenEntry : e.getTokens().entrySet()) { - String name = tokenEntry.getKey(); - artIndex = tokenEntry.getValue(); - try { - PaperToken token = tokenDb.getToken(name, e.getCode()); - if (token == null) { - continue; - } - - for(int i = 0; i < artIndex; i++) { - String imgKey = token.getImageKey(i); - File file = ImageKeys.getImageFile(imgKey); - if (file == null) { - if (!nifHeader) { - nifSB.append("Edition: ").append(e.getName()).append(" ").append("(").append(e.getCode()).append("/").append(e.getCode2()).append(")\n"); - nifHeader = true; - } - if (!tokenHeader) { - nifSB.append("\nTOKENS\n"); - tokenHeader = true; - } - nifSB.append(" ").append(token.getImageFilename(i + 1)).append("\n"); - missingCount++; - } - } - } catch(Exception ex) { - System.out.println("No Token found: " + name + " in " + e.getName()); - } - } - if (nifHeader) - nifSB.append("\n"); - } - - String totalStats = "Missing images: " + missingCount + "\nUnimplemented cards: " + notImplementedCount + "\n"; - cniSB.append("\n-----------\n"); - cniSB.append(totalStats); - cniSB.append("-----------\n\n"); - - nifSB.append(cniSB); // combine things together... + Pair totalAudit = StaticData.instance().audit(nifSB, cniSB); tar.setText(nifSB.toString()); tar.setCaretPosition(0); // this will move scroll view to the top... @@ -378,7 +247,7 @@ public enum VSubmenuDownloaders implements IVSubmenu { }); scr.getParent().add(btnClipboardCopy, "w 200!, h pref+12!, center, gaptop 10"); - String labelText = "Missing images: " + missingCount + "
Unimplemented cards: " + notImplementedCount + "
"; + String labelText = "Missing images: " + totalAudit.getLeft() + "
Unimplemented cards: " + totalAudit.getRight() + "
"; final FLabel statsLabel = new FLabel.Builder().text(labelText).fontSize(15).build(); scr.getParent().add(statsLabel); diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 7267170a25b..7da28d0c317 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -55,9 +55,7 @@ import java.io.File; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; -import java.util.HashMap; import java.util.List; -import java.util.Map; public class Forge implements ApplicationListener { public static final String CURRENT_VERSION = "1.6.53.001"; @@ -81,6 +79,7 @@ public class Forge implements ApplicationListener { protected static TransitionScreen transitionScreen; public static KeyInputAdapter keyInputAdapter; private static boolean exited; + public boolean needsUpdate = false; public static boolean safeToClose = true; public static boolean magnify = false; public static boolean magnifyToggle = true; @@ -104,15 +103,14 @@ public class Forge implements ApplicationListener { public static boolean enablePreloadExtendedArt = false; public static boolean isTabletDevice = false; public static String locale = "en-US"; - public Assets cardAssets; - public Assets otherAssets; + public Assets assets; public static boolean hdbuttons = false; public static boolean hdstart = false; public static boolean isPortraitMode = false; public static boolean gameInProgress = false; public static boolean disposeTextures = false; public static boolean isMobileAdventureMode = false; - public static int cacheSize = 400; + public static int cacheSize = 300; public static int totalDeviceRAM = 0; public static int androidVersion = 0; public static boolean autoCache = false; @@ -126,7 +124,6 @@ public class Forge implements ApplicationListener { public static boolean forcedEnglishonCJKMissing = false; public static boolean adventureLoaded = false; private static Localizer localizer; - static Map misc = new HashMap<>(); public static ApplicationListener getApp(Clipboard clipboard0, IDeviceAdapter deviceAdapter0, String assetDir0, boolean value, boolean androidOrientation, int totalRAM, boolean isTablet, int AndroidAPI, String AndroidRelease, String deviceName) { app = new Forge(); @@ -166,8 +163,7 @@ public class Forge implements ApplicationListener { // don't allow to read and process ForgeConstants.SPRITE_CARDBG_FILE = ""; } - cardAssets = new Assets(); - otherAssets = new Assets(); + assets = new Assets(); graphics = new Graphics(); splashScreen = new SplashScreen(); frameRate = new FrameRate(); @@ -212,9 +208,9 @@ public class Forge implements ApplicationListener { CJK_Font = prefs.getPref(FPref.UI_CJK_FONT); if (autoCache) { - //increase cacheSize for devices with RAM more than 5GB, default is 400. Some phones have more than 10GB RAM (Mi 10, OnePlus 8, S20, etc..) - if (totalDeviceRAM > 5000) //devices with more than 10GB RAM will have 800 Cache size, 600 Cache size for morethan 5GB RAM - cacheSize = totalDeviceRAM > 10000 ? 800 : 600; + //increase cacheSize for devices with RAM more than 5GB, default is 300. Some phones have more than 10GB RAM (Mi 10, OnePlus 8, S20, etc..) + if (totalDeviceRAM > 5000) //devices with more than 10GB RAM will have 600 Cache size, 400 Cache size for morethan 5GB RAM + cacheSize = totalDeviceRAM > 10000 ? 600 : 400; } //init cache ImageCache.initCache(cacheSize); @@ -345,22 +341,6 @@ public class Forge implements ApplicationListener { e.printStackTrace(); } } - public static Texture getTitleBG() { - if (misc.get(0) == null) { - misc.put(0, new Texture(GuiBase.isAndroid() - ? Gdx.files.internal("fallback_skin").child("title_bg_lq.png") - : Gdx.files.classpath("fallback_skin").child("title_bg_lq.png"))); - } - return misc.get(0); - } - public static Texture getTransitionBG() { - if (misc.get(1) == null) { - misc.put(1, new Texture(GuiBase.isAndroid() - ? Gdx.files.internal("fallback_skin").child("transition.png") - : Gdx.files.classpath("fallback_skin").child("transition.png"))); - } - return misc.get(1); - } protected void afterDbLoaded() { destroyThis = false; //Allow back() Gdx.input.setCatchKey(Keys.MENU, true); @@ -801,7 +781,7 @@ public class Forge implements ApplicationListener { @Override public void render() { if (showFPS) - frameRate.update(); + frameRate.update(ImageCache.counter, Forge.getAssets().manager().getMemoryInMegabytes()); try { ImageCache.allowSingleLoad(); @@ -837,8 +817,8 @@ public class Forge implements ApplicationListener { animationBatch.setColor(1, 1, 1, 1); animationBatch.draw(lastScreenTexture, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); animationBatch.setColor(1, 1, 1, 1 - (1 / transitionTime) * animationTimeout); - animationBatch.draw(getTransitionBG(), 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); - animationBatch.draw(getTransitionBG(), 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + animationBatch.draw(getAssets().fallback_skins().get(1), 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + animationBatch.draw(getAssets().fallback_skins().get(1), 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); animationBatch.end(); if (animationTimeout < 0) { currentScene.render(); @@ -857,8 +837,8 @@ public class Forge implements ApplicationListener { animationBatch.setColor(1, 1, 1, 1); animationBatch.draw(lastScreenTexture, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); animationBatch.setColor(1, 1, 1, (1 / transitionTime) * (animationTimeout + transitionTime)); - animationBatch.draw(getTransitionBG(), 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); - animationBatch.draw(getTransitionBG(), 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + animationBatch.draw(getAssets().fallback_skins().get(1), 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + animationBatch.draw(getAssets().fallback_skins().get(1), 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); animationBatch.end(); return; } @@ -896,6 +876,11 @@ public class Forge implements ApplicationListener { } } } + //update here + if (needsUpdate) { + if (getAssets().manager().update()) + needsUpdate = false; + } graphics.end(); } catch (Exception ex) { graphics.end(); @@ -942,6 +927,15 @@ public class Forge implements ApplicationListener { @Override public void resume() { + try { + Texture.setAssetManager(getAssets().manager()); + needsUpdate = true; + } catch (Exception e) { + //the application context must have been recreated from its last state. + //it could be triggered by the low memory on heap on android. + needsUpdate = false; + e.printStackTrace(); + } if (MatchController.getHostedMatch() != null) { MatchController.getHostedMatch().resume(); } @@ -954,8 +948,7 @@ public class Forge implements ApplicationListener { currentScreen.onClose(null); currentScreen = null; } - cardAssets.dispose(); - otherAssets.dispose(); + assets.dispose(); Dscreens.clear(); graphics.dispose(); SoundSystem.instance.dispose(); @@ -967,11 +960,8 @@ public class Forge implements ApplicationListener { /** Retrieve assets. * @param other if set to true returns otherAssets otherwise returns cardAssets */ - public static Assets getAssets(boolean other) { - if (other) - return ((Forge)Gdx.app.getApplicationListener()).otherAssets; - else - return ((Forge)Gdx.app.getApplicationListener()).cardAssets; + public static Assets getAssets() { + return ((Forge)Gdx.app.getApplicationListener()).assets; } public static boolean switchScene(Scene newScene) { if (currentScene != null) { diff --git a/forge-gui-mobile/src/forge/FrameRate.java b/forge-gui-mobile/src/forge/FrameRate.java index 3c11b521d0b..64dc61943b5 100644 --- a/forge-gui-mobile/src/forge/FrameRate.java +++ b/forge-gui-mobile/src/forge/FrameRate.java @@ -16,6 +16,8 @@ import com.badlogic.gdx.utils.TimeUtils; public class FrameRate implements Disposable{ long lastTimeCounted; + int cardsLoaded = 0; + int allocT = 0; private float sinceChange; private float frameRate; private BitmapFont font; @@ -38,7 +40,10 @@ public class FrameRate implements Disposable{ batch.setProjectionMatrix(cam.combined); } - public void update() { + public void update(int loadedCardSize, float toAlloc) { + if (toAlloc > 300f) + allocT = (int) toAlloc; + cardsLoaded = loadedCardSize; long delta = TimeUtils.timeSinceMillis(lastTimeCounted); lastTimeCounted = TimeUtils.millis(); sinceChange += delta; @@ -50,7 +55,7 @@ public class FrameRate implements Disposable{ public void render() { batch.begin(); - font.draw(batch, (int)frameRate + " fps", 3, Gdx.graphics.getHeight() - 3); + font.draw(batch, (int)frameRate + " FPS | " + cardsLoaded + " cards re/loaded - " + allocT + " vMem", 3, Gdx.graphics.getHeight() - 3); batch.end(); } diff --git a/forge-gui-mobile/src/forge/Graphics.java b/forge-gui-mobile/src/forge/Graphics.java index 48cba88cbcd..da4feba9f20 100644 --- a/forge-gui-mobile/src/forge/Graphics.java +++ b/forge-gui-mobile/src/forge/Graphics.java @@ -661,8 +661,10 @@ public class Graphics { drawRoundRect(2f, borderLining(borderColor.toString()), x, y, w, h, (h-w)/12); fillRoundRect(tintColor, x, y, w, h, (h-w)/12); } else { - image.draw(this, x, y, w, h); - fillRoundRect(borderColor, x, y, w, h, (h-w)/10);//show corners edges + if (image != null) { + image.draw(this, x, y, w, h); + fillRoundRect(borderColor, x, y, w, h, (h - w) / 10);//show corners edges + } } setAlphaComposite(oldalpha); } @@ -672,10 +674,14 @@ public class Graphics { setAlphaComposite(oldalpha); } public void drawImage(FImage image, Color borderColor, float x, float y, float w, float h) { + if (image == null) + return; image.draw(this, x, y, w, h); fillRoundRect(borderColor, x+1, y+1, w-1.5f, h-1.5f, (h-w)/10);//used by zoom let some edges show... } public void drawAvatarImage(FImage image, float x, float y, float w, float h, boolean drawGrayscale) { + if (image == null) + return; if (!drawGrayscale) { image.draw(this, x, y, w, h); } else { @@ -693,6 +699,8 @@ public class Graphics { } } public void drawCardImage(FImage image, TextureRegion damage_overlay, float x, float y, float w, float h, boolean drawGrayscale, boolean damaged) { + if (image == null) + return; if (!drawGrayscale) { image.draw(this, x, y, w, h); if (damage_overlay != null && damaged) @@ -752,6 +760,8 @@ public class Graphics { } } public void drawGrayTransitionImage(FImage image, float x, float y, float w, float h, boolean withDarkOverlay, float percentage) { + if (image == null) + return; batch.end(); shaderGrayscale.bind(); shaderGrayscale.setUniformf("u_grayness", percentage); @@ -839,6 +849,8 @@ public class Graphics { batch.begin(); } public void drawWarpImage(FImage image, float x, float y, float w, float h, float time) { + if (image == null) + return; batch.end(); shaderWarp.bind(); shaderWarp.setUniformf("u_amount", 0.2f); @@ -854,6 +866,8 @@ public class Graphics { batch.begin(); } public void drawUnderWaterImage(FImage image, float x, float y, float w, float h, float time, boolean withDarkOverlay) { + if (image == null) + return; batch.end(); shaderUnderwater.bind(); shaderUnderwater.setUniformf("u_amount", 10f*time); @@ -893,6 +907,8 @@ public class Graphics { drawImage(image, x, y, w, h, false); } public void drawImage(FImage image, float x, float y, float w, float h, boolean withDarkOverlay) { + if (image == null) + return; image.draw(this, x, y, w, h); if(withDarkOverlay){ float oldalpha = alphaComposite; diff --git a/forge-gui-mobile/src/forge/adventure/character/EnemySprite.java b/forge-gui-mobile/src/forge/adventure/character/EnemySprite.java index 67e70e448c4..48792671778 100644 --- a/forge-gui-mobile/src/forge/adventure/character/EnemySprite.java +++ b/forge-gui-mobile/src/forge/adventure/character/EnemySprite.java @@ -8,7 +8,6 @@ import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.utils.Array; -import com.google.common.base.Predicates; import forge.Forge; import forge.adventure.data.DialogData; import forge.adventure.data.EffectData; @@ -19,8 +18,6 @@ import forge.adventure.util.Current; import forge.adventure.util.MapDialog; import forge.adventure.util.Reward; import forge.card.CardRarity; -import forge.card.CardRulesPredicates; -import forge.deck.CardPool; import forge.deck.Deck; import forge.item.PaperCard; import forge.util.Aggregates; @@ -106,10 +103,11 @@ public class EnemySprite extends CharacterSprite { ret.add(new Reward(Reward.Type.Life, 1)); } else { if(data.rewards != null) { //Collect standard rewards. - Deck enemyDeck = Current.latestDeck(); // By popular demand, remove basic lands from the reward pool. - CardPool deckNoBasicLands = enemyDeck.getMain().getFilteredPool(Predicates.compose(Predicates.not(CardRulesPredicates.Presets.IS_BASIC_LAND), PaperCard.FN_GET_RULES)); + Deck enemyDeck = Current.latestDeck(); + /*// By popular demand, remove basic lands from the reward pool. + CardPool deckNoBasicLands = enemyDeck.getMain().getFilteredPool(Predicates.compose(Predicates.not(CardRulesPredicates.Presets.IS_BASIC_LAND), PaperCard.FN_GET_RULES));*/ for (RewardData rdata : data.rewards) { - ret.addAll(rdata.generate(false, deckNoBasicLands.toFlatList() )); + ret.addAll(rdata.generate(false, enemyDeck == null ? null : enemyDeck.getMain().toFlatList() )); } } if(rewards != null) { //Collect additional rewards. diff --git a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java index 42e5f08ce5f..b3658b2dcf2 100644 --- a/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java +++ b/forge-gui-mobile/src/forge/adventure/player/AdventurePlayer.java @@ -457,7 +457,8 @@ public class AdventurePlayer implements Serializable, SaveFileContent { onLifeTotalChangeList.emit(); } public void defeated() { - gold=gold/2; + int percentLoss = 10; + gold=gold-(gold*percentLoss/100); life=Math.max(1,(int)(life-(maxLife*0.2f))); onLifeTotalChangeList.emit(); onGoldChangeList.emit(); diff --git a/forge-gui-mobile/src/forge/adventure/scene/SaveLoadScene.java b/forge-gui-mobile/src/forge/adventure/scene/SaveLoadScene.java index dc95ad33b85..c75708ccb40 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/SaveLoadScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/SaveLoadScene.java @@ -177,7 +177,17 @@ public class SaveLoadScene extends UIScene { updateFiles(); //ensure the dialog is hidden before switching dialog.getColor().a = 0f; - Forge.switchScene(SceneType.GameScene.instance); + + Scene restoreScene = Forge.switchToLast(); + if (restoreScene != null) { + restoreScene = Forge.switchToLast(); + } + + if (restoreScene == null) { + restoreScene = SceneType.GameScene.instance; + } + + Forge.switchScene(restoreScene); } } diff --git a/forge-gui-mobile/src/forge/adventure/scene/SettingsScene.java b/forge-gui-mobile/src/forge/adventure/scene/SettingsScene.java index 0d6e55b270a..4f7dae601c9 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/SettingsScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/SettingsScene.java @@ -133,7 +133,8 @@ public class SettingsScene extends UIScene { Label label = Controls.newLabel(name); label.setWrap(true); settingGroup.row().space(5); - settingGroup.add(label).align(Align.left).pad(2, 2, 2, 5).width(100).expand(); + int w = Forge.isLandscapeMode() ? 160 : 80; + settingGroup.add(label).align(Align.left).pad(2, 2, 2, 5).width(w).expand(); } @Override diff --git a/forge-gui-mobile/src/forge/adventure/scene/StartScene.java b/forge-gui-mobile/src/forge/adventure/scene/StartScene.java index 38f4dc0b770..7157a93e541 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/StartScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/StartScene.java @@ -83,8 +83,10 @@ public class StartScene extends UIScene { @Override public void enter() { boolean hasSaveButton = WorldSave.getCurrentSave().getWorld().getData() != null; - if (hasSaveButton) - hasSaveButton = !((TileMapScene) SceneType.TileMapScene.instance).currentMap().isInMap(); + if (hasSaveButton) { + TileMapScene scene = (TileMapScene) SceneType.TileMapScene.instance; + hasSaveButton = !scene.currentMap().isInMap() || scene.inTown(); + } saveButton.setVisible(hasSaveButton); boolean hasResumeButton = WorldSave.getCurrentSave().getWorld().getData() != null; diff --git a/forge-gui-mobile/src/forge/adventure/scene/TileMapScene.java b/forge-gui-mobile/src/forge/adventure/scene/TileMapScene.java index a556839e71d..9f10556f089 100644 --- a/forge-gui-mobile/src/forge/adventure/scene/TileMapScene.java +++ b/forge-gui-mobile/src/forge/adventure/scene/TileMapScene.java @@ -92,6 +92,10 @@ public class TileMapScene extends HudScene { tiledMapRenderer.loadMap(map, ""); } + public boolean inTown() { + return "town".equalsIgnoreCase(rootPoint.getData().type); + } + PointOfInterest rootPoint; String oldMap; diff --git a/forge-gui-mobile/src/forge/adventure/stage/GameHUD.java b/forge-gui-mobile/src/forge/adventure/stage/GameHUD.java index 08457be0005..4d6209d0150 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/GameHUD.java +++ b/forge-gui-mobile/src/forge/adventure/stage/GameHUD.java @@ -5,6 +5,7 @@ import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.actions.Actions; import com.badlogic.gdx.scenes.scene2d.ui.Image; @@ -192,7 +193,8 @@ public class GameHUD extends Stage { } //auto follow touchpad if (GuiBase.isAndroid() && !MapStage.getInstance().getDialogOnlyInput() && !console.isVisible()) { - if (!(Controls.actorContainsVector(miniMap,touch)) // not inside map bounds + if (!(Controls.actorContainsVector(avatar,touch)) // not inside avatar bounds + && !(Controls.actorContainsVector(miniMap,touch)) // not inside map bounds && !(Controls.actorContainsVector(gamehud,touch)) //not inside gamehud bounds && !(Controls.actorContainsVector(menuActor,touch)) //not inside menu button && !(Controls.actorContainsVector(deckActor,touch)) //not inside deck button @@ -251,23 +253,25 @@ public class GameHUD extends Stage { mapborder.setVisible(visible); miniMapPlayer.setVisible(visible); gamehud.setVisible(visible); - avatarborder.setVisible(visible); - avatar.setVisible(visible); lifePoints.setVisible(visible); money.setVisible(visible); blank.setVisible(visible); if (visible) { + avatarborder.getColor().a = 1f; + avatar.getColor().a = 1f; deckActor.getColor().a = 1f; menuActor.getColor().a = 1f; statsActor.getColor().a = 1f; inventoryActor.getColor().a = 1f; opacity = 1f; } else { - deckActor.getColor().a = 0.5f; - menuActor.getColor().a = 0.5f; - statsActor.getColor().a = 0.5f; - inventoryActor.getColor().a = 0.5f; - opacity = 0.5f; + avatarborder.getColor().a = 0.4f; + avatar.getColor().a = 0.4f; + deckActor.getColor().a = 0.4f; + menuActor.getColor().a = 0.4f; + statsActor.getColor().a = 0.4f; + inventoryActor.getColor().a = 0.4f; + opacity = 0.4f; } } @@ -317,7 +321,7 @@ public class GameHUD extends Stage { } class ConsoleToggleListener extends ActorGestureListener { public ConsoleToggleListener() { - getGestureDetector().setLongPressSeconds(0.5f); + getGestureDetector().setLongPressSeconds(0.6f); } @Override public boolean longPress(Actor actor, float x, float y) { @@ -325,5 +329,12 @@ public class GameHUD extends Stage { console.toggle(); return super.longPress(actor, x, y); } + @Override + public void tap(InputEvent event, float x, float y, int count, int button) { + super.tap(event, x, y, count, button); + //show menu buttons if double tapping the avatar, for android devices without visible navigation buttons + if (count > 1) + showButtons(); + } } } diff --git a/forge-gui-mobile/src/forge/adventure/stage/MapStage.java b/forge-gui-mobile/src/forge/adventure/stage/MapStage.java index df973dd5781..1dc759ef348 100644 --- a/forge-gui-mobile/src/forge/adventure/stage/MapStage.java +++ b/forge-gui-mobile/src/forge/adventure/stage/MapStage.java @@ -23,7 +23,6 @@ import com.badlogic.gdx.scenes.scene2d.ui.Image; import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Array; -import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.Scaling; import forge.Forge; import forge.adventure.character.*; @@ -43,6 +42,7 @@ import forge.sound.SoundEffectType; import forge.sound.SoundSystem; +import java.util.HashMap; import java.util.Map; import static forge.adventure.util.Paths.MANA_ATLAS; @@ -69,7 +69,7 @@ public class MapStage extends GameStage { private final Vector2 oldPosition3 = new Vector2(); private final Vector2 oldPosition4 = new Vector2(); private boolean isLoadingMatch = false; - private ObjectMap mapFlags = new ObjectMap<>(); //Stores local map flags. These aren't available outside this map. + private HashMap mapFlags = new HashMap<>(); //Stores local map flags. These aren't available outside this map. private Dialog dialog; private Stage dialogStage; diff --git a/forge-gui-mobile/src/forge/adventure/util/Config.java b/forge-gui-mobile/src/forge/adventure/util/Config.java index 8813e54725c..9a2b7aa702d 100644 --- a/forge-gui-mobile/src/forge/adventure/util/Config.java +++ b/forge-gui-mobile/src/forge/adventure/util/Config.java @@ -120,11 +120,11 @@ public class Config { public TextureAtlas getAtlas(String spriteAtlas) { String fileName = getFile(spriteAtlas).path(); - if (!Forge.getAssets(true).manager.contains(fileName, TextureAtlas.class)) { - Forge.getAssets(true).manager.load(fileName, TextureAtlas.class); - Forge.getAssets(true).manager.finishLoadingAsset(fileName); + if (!Forge.getAssets().manager().contains(fileName, TextureAtlas.class)) { + Forge.getAssets().manager().load(fileName, TextureAtlas.class); + Forge.getAssets().manager().finishLoadingAsset(fileName); } - return Forge.getAssets(true).manager.get(fileName); + return Forge.getAssets().manager().get(fileName); } public SettingData getSettingData() { diff --git a/forge-gui-mobile/src/forge/adventure/util/Controls.java b/forge-gui-mobile/src/forge/adventure/util/Controls.java index bc667451338..073c4e9ea3c 100644 --- a/forge-gui-mobile/src/forge/adventure/util/Controls.java +++ b/forge-gui-mobile/src/forge/adventure/util/Controls.java @@ -11,6 +11,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.*; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.utils.Align; +import forge.Forge; import java.util.function.Function; @@ -18,15 +19,10 @@ import java.util.function.Function; * Class to create ui elements in the correct style */ public class Controls { - private static Skin SelectedSkin = null; - private static BitmapFont defaultfont, bigfont; - static public TextButton newTextButton(String text) { - return new TextButton(text, GetSkin()); } static public Rectangle getBoundingRect(Actor actor) { - return new Rectangle(actor.getX(),actor.getY(),actor.getWidth(),actor.getHeight()); } static public boolean actorContainsVector (Actor actor, Vector2 point) { @@ -56,8 +52,6 @@ public class Controls { return ret; } - - static public TextField newTextField(String text) { return new TextField(text, GetSkin()); } @@ -108,32 +102,28 @@ public class Controls { switch (fontName) { case "blackbig": case "big": - return bigfont; + return Forge.getAssets().advBigFont; default: - return defaultfont; + return Forge.getAssets().advDefaultFont; } } - static public Skin GetSkin() { - - if (SelectedSkin == null) { - SelectedSkin = new Skin(); - + if (Forge.getAssets().skin == null) { + Forge.getAssets().skin = new Skin(); FileHandle skinFile = Config.instance().getFile(Paths.SKIN); FileHandle atlasFile = skinFile.sibling(skinFile.nameWithoutExtension() + ".atlas"); TextureAtlas atlas = new TextureAtlas(atlasFile); //font - defaultfont = new BitmapFont(Config.instance().getFile(Paths.SKIN).sibling("LanaPixel.fnt")); - bigfont = new BitmapFont(Config.instance().getFile(Paths.SKIN).sibling("LanaPixel.fnt")); - bigfont.getData().setScale(2, 2); - SelectedSkin.add("default", defaultfont); - SelectedSkin.add("big", bigfont); - SelectedSkin.addRegions(atlas); - SelectedSkin.load(skinFile); - + Forge.getAssets().advDefaultFont = new BitmapFont(Config.instance().getFile(Paths.SKIN).sibling("LanaPixel.fnt")); + Forge.getAssets().advBigFont = new BitmapFont(Config.instance().getFile(Paths.SKIN).sibling("LanaPixel.fnt")); + Forge.getAssets().advBigFont.getData().setScale(2, 2); + Forge.getAssets().skin.add("default", Forge.getAssets().advDefaultFont); + Forge.getAssets().skin.add("big", Forge.getAssets().advBigFont); + Forge.getAssets().skin.addRegions(atlas); + Forge.getAssets().skin.load(skinFile); } - return SelectedSkin; + return Forge.getAssets().skin; } public static Label newLabel(String name) { diff --git a/forge-gui-mobile/src/forge/adventure/util/TemplateTmxMapLoader.java b/forge-gui-mobile/src/forge/adventure/util/TemplateTmxMapLoader.java index 0306838517a..e98c42eb5cb 100644 --- a/forge-gui-mobile/src/forge/adventure/util/TemplateTmxMapLoader.java +++ b/forge-gui-mobile/src/forge/adventure/util/TemplateTmxMapLoader.java @@ -15,6 +15,7 @@ import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.SerializationException; import com.badlogic.gdx.utils.XmlReader; +import forge.Forge; import java.io.File; @@ -34,17 +35,15 @@ public class TemplateTmxMapLoader extends TmxMapLoader { this.root = xml.parse(tmxFile); - ObjectMap textures = new ObjectMap(); - final Array textureFiles = getDependencyFileHandles(tmxFile); for (FileHandle textureFile : textureFiles) { Texture texture = new Texture(textureFile, parameter.generateMipMaps); texture.setFilter(parameter.textureMinFilter, parameter.textureMagFilter); - textures.put(textureFile.path(), texture); + Forge.getAssets().tmxMap().put(textureFile.path(), texture); } - TiledMap map = loadTiledMap(tmxFile, parameter, new ImageResolver.DirectImageResolver(textures)); - map.setOwnedResources(textures.values().toArray()); + TiledMap map = loadTiledMap(tmxFile, parameter, new ImageResolver.DirectImageResolver(Forge.getAssets().tmxMap())); + map.setOwnedResources(Forge.getAssets().tmxMap().values().toArray()); return map; } diff --git a/forge-gui-mobile/src/forge/assets/Assets.java b/forge-gui-mobile/src/forge/assets/Assets.java index 0b88a5af937..8e1fb4c7528 100644 --- a/forge-gui-mobile/src/forge/assets/Assets.java +++ b/forge-gui-mobile/src/forge/assets/Assets.java @@ -1,13 +1,234 @@ package forge.assets; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.assets.AssetLoaderParameters; import com.badlogic.gdx.assets.AssetManager; +import com.badlogic.gdx.assets.loaders.FileHandleResolver; +import com.badlogic.gdx.assets.loaders.TextureLoader; import com.badlogic.gdx.assets.loaders.resolvers.AbsoluteFileHandleResolver; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.TextureData; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.scenes.scene2d.ui.Skin; import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.utils.ObjectMap; +import forge.Forge; +import forge.gui.GuiBase; +import forge.localinstance.skin.FSkinProp; + +import java.util.HashMap; +import java.util.Map; public class Assets implements Disposable { - public AssetManager manager = new AssetManager(new AbsoluteFileHandleResolver()); + private MemoryTrackingAssetManager manager = new MemoryTrackingAssetManager(new AbsoluteFileHandleResolver()); + private HashMap fonts = new HashMap<>(); + private HashMap cardArtCache = new HashMap<>(1024); + private HashMap avatarImages = new HashMap<>(); + private HashMap manaImages = new HashMap<>(128); + private HashMap symbolLookup = new HashMap<>(64); + private HashMap images = new HashMap<>(512); + private HashMap avatars = new HashMap<>(150); + private HashMap sleeves = new HashMap<>(64); + private HashMap cracks = new HashMap<>(16); + private HashMap borders = new HashMap<>(); + private HashMap deckbox = new HashMap<>(); + private HashMap cursor = new HashMap<>(); + private ObjectMap counterFonts = new ObjectMap<>(); + private ObjectMap generatedCards = new ObjectMap<>(512); + private ObjectMap fallback_skins = new ObjectMap<>(); + private ObjectMap tmxMap = new ObjectMap<>(); + public Skin skin; + public BitmapFont advDefaultFont, advBigFont; + public Assets() { + //init titlebg fallback + fallback_skins.put(0, new Texture(GuiBase.isAndroid() + ? Gdx.files.internal("fallback_skin").child("title_bg_lq.png") + : Gdx.files.classpath("fallback_skin").child("title_bg_lq.png"))); + //init transition fallback + fallback_skins.put(1, new Texture(GuiBase.isAndroid() + ? Gdx.files.internal("fallback_skin").child("transition.png") + : Gdx.files.classpath("fallback_skin").child("transition.png"))); + } @Override public void dispose() { manager.dispose(); + for (BitmapFont bitmapFont : counterFonts.values()) + bitmapFont.dispose(); + for (Texture texture : generatedCards.values()) + texture.dispose(); + for (FSkinFont fSkinFont : fonts.values()) + fSkinFont.font.dispose(); + for (Texture texture : fallback_skins.values()) + texture.dispose(); + for (Texture texture : tmxMap.values()) + texture.dispose(); + if (advDefaultFont != null) + advDefaultFont.dispose(); + if (advBigFont != null) + advBigFont.dispose(); + if (skin != null) + skin.dispose(); + } + public MemoryTrackingAssetManager manager() { + if (manager == null) + manager = new MemoryTrackingAssetManager(new AbsoluteFileHandleResolver()); + return manager; + } + public HashMap fonts() { + if (fonts == null) + fonts = new HashMap<>(); + return fonts; + } + public HashMap cardArtCache() { + if (cardArtCache == null) + cardArtCache = new HashMap<>(1024); + return cardArtCache; + } + public HashMap avatarImages() { + if (avatarImages == null) + avatarImages = new HashMap<>(); + return avatarImages; + } + public HashMap manaImages() { + if (manaImages == null) + manaImages = new HashMap<>(128); + return manaImages; + } + public HashMap symbolLookup() { + if (symbolLookup == null) + symbolLookup = new HashMap<>(64); + return symbolLookup; + } + public HashMap images() { + if (images == null) + images = new HashMap<>(512); + return images; + } + public HashMap avatars() { + if (avatars == null) + avatars = new HashMap<>(150); + return avatars; + } + public HashMap sleeves() { + if (sleeves == null) + sleeves = new HashMap<>(64); + return sleeves; + } + public HashMap cracks() { + if (cracks == null) + cracks = new HashMap<>(16); + return cracks; + } + public HashMap borders() { + if (borders == null) + borders = new HashMap<>(); + return borders; + } + public HashMap deckbox() { + if (deckbox == null) + deckbox = new HashMap<>(); + return deckbox; + } + public HashMap cursor() { + if (cursor == null) + cursor = new HashMap<>(); + return cursor; + } + public ObjectMap counterFonts() { + if (counterFonts == null) + counterFonts = new ObjectMap<>(); + return counterFonts; + } + public ObjectMap generatedCards() { + if (generatedCards == null) + generatedCards = new ObjectMap<>(512); + return generatedCards; + } + public ObjectMap fallback_skins() { + if (fallback_skins == null) + fallback_skins = new ObjectMap<>(); + return fallback_skins; + } + public ObjectMap tmxMap() { + if (tmxMap == null) + tmxMap = new ObjectMap<>(); + return tmxMap; + } + public class MemoryTrackingAssetManager extends AssetManager { + private int currentMemory; + private Map memoryPerFile; + + public MemoryTrackingAssetManager(FileHandleResolver resolver) { + super(resolver); + + currentMemory = 0; + memoryPerFile = new HashMap(); + } + + @SuppressWarnings("unchecked") + private int calculateTextureSize(AssetManager assetManager, String fileName, Class type) { + if (memoryPerFile.containsKey(fileName)) { + return memoryPerFile.get(fileName); + } + + Texture texture = (Texture) assetManager.get(fileName, type); + TextureData textureData = texture.getTextureData(); + int textureSize = textureData.getWidth() * textureData.getHeight(); + if (Forge.isTextureFilteringEnabled()) + textureSize = textureSize + (textureSize/3); + switch (textureData.getFormat()) { + case RGB565: + textureSize *= 2; + break; + case RGB888: + textureSize *= 3; + break; + case RGBA4444: + textureSize *= 2; + break; + case RGBA8888: + textureSize *= 4; + break; + } + + memoryPerFile.put(fileName, textureSize); + + return textureSize; + } + + @SuppressWarnings("unchecked") + @Override + public synchronized void load(String fileName, Class type, AssetLoaderParameters parameter) { + if (type.equals(Texture.class)) { + if (parameter == null) { + parameter = (AssetLoaderParameters) new TextureLoader.TextureParameter(); + } + + final AssetLoaderParameters.LoadedCallback prevCallback = parameter.loadedCallback; + parameter.loadedCallback = (assetManager, fileName1, type1) -> { + if (prevCallback != null) { + prevCallback.finishedLoading(assetManager, fileName1, type1); + } + + currentMemory += calculateTextureSize(assetManager, fileName1, type1); + }; + + } + + super.load(fileName, type, parameter); + } + + @Override + public synchronized void unload(String fileName) { + super.unload(fileName); + if (memoryPerFile.containsKey(fileName)) { + currentMemory -= memoryPerFile.get(fileName); + } + } + + public float getMemoryInMegabytes() { + return (float) currentMemory / 1024f / 1024f; + } } } diff --git a/forge-gui-mobile/src/forge/assets/FSkin.java b/forge-gui-mobile/src/forge/assets/FSkin.java index 6c33a438979..e5e2cf06010 100644 --- a/forge-gui-mobile/src/forge/assets/FSkin.java +++ b/forge-gui-mobile/src/forge/assets/FSkin.java @@ -25,18 +25,9 @@ import forge.screens.TransitionScreen; import forge.toolbox.FProgressBar; import forge.util.WordUtil; -import java.util.HashMap; import java.util.Map; public class FSkin { - private static final Map images = new HashMap<>(512); - private static final Map avatars = new HashMap<>(150); - private static final Map sleeves = new HashMap<>(64); - private static final Map cracks = new HashMap<>(16); - private static final Map borders = new HashMap<>(); - private static final Map deckbox = new HashMap<>(); - private static final Map cursor = new HashMap<>(); - private static Array allSkins; private static FileHandle preferredDir; private static String preferredName; @@ -53,27 +44,19 @@ public class FSkin { prefs.setPref(FPref.UI_SKIN, skinName); prefs.save(); - Forge.setTransitionScreen(new TransitionScreen(new Runnable() { - @Override - public void run() { - FThreads.invokeInBackgroundThread(() -> FThreads.invokeInEdtLater(() -> { - final LoadingOverlay loader = new LoadingOverlay(Forge.getLocalizer().getMessageorUseDefault("lblRestartInFewSeconds", "Forge will restart after a few seconds..."), true); - loader.show(); - FThreads.invokeInBackgroundThread(() -> { - FSkinFont.deleteCachedFiles(); //delete cached font files so font can be update for new skin - FThreads.delayInEDT(2000, new Runnable() { - @Override - public void run() { - Forge.clearTransitionScreen(); - FThreads.invokeInEdtLater(() -> { - Forge.restart(true); - }); - } - }); + Forge.setTransitionScreen(new TransitionScreen(() -> FThreads.invokeInBackgroundThread(() -> FThreads.invokeInEdtLater(() -> { + final LoadingOverlay loader = new LoadingOverlay(Forge.getLocalizer().getMessageorUseDefault("lblRestartInFewSeconds", "Forge will restart after a few seconds..."), true); + loader.show(); + FThreads.invokeInBackgroundThread(() -> { + FSkinFont.deleteCachedFiles(); //delete cached font files so font can be update for new skin + FThreads.delayInEDT(2000, () -> { + Forge.clearTransitionScreen(); + FThreads.invokeInEdtLater(() -> { + Forge.restart(true); }); - })); - } - }, null, false, true)); + }); + }); + })), null, false, true)); } public static void loadLight(String skinName, final SplashScreen splashScreen,FileHandle prefDir) { preferredDir = prefDir; @@ -88,7 +71,7 @@ public class FSkin { * the skin name */ public static void loadLight(String skinName, final SplashScreen splashScreen) { - AssetManager manager = Forge.getAssets(true).manager; + AssetManager manager = Forge.getAssets().manager(); preferredName = skinName.toLowerCase().replace(' ', '_'); //reset hd buttons/icons @@ -165,17 +148,26 @@ public class FSkin { } try { - manager.load(f.path(), Texture.class); - manager.finishLoadingAsset(f.path()); - final int w = manager.get(f.path(), Texture.class).getWidth(); - final int h = manager.get(f.path(), Texture.class).getHeight(); - - if (f2.exists()) { - manager.load(f2.path(), Texture.class, new TextureLoader.TextureParameter(){{genMipMaps = true; minFilter = Texture.TextureFilter.MipMapLinearLinear; magFilter = Texture.TextureFilter.Linear;}}); - manager.finishLoadingAsset(f2.path()); - splashScreen.setBackground(new TextureRegion(manager.get(f2.path(), Texture.class))); + int w, h; + if (f.path().contains("fallback_skin")) { + //the file is not accesible by the assetmanager using absolute fileresolver since it resides on internal path or classpath + Texture txSplash = new Texture(f); + w = txSplash.getWidth(); + h = txSplash.getHeight(); + splashScreen.setBackground(new TextureRegion(txSplash, 0, 0, w, h - 100)); } else { - splashScreen.setBackground(new TextureRegion(manager.get(f.path(), Texture.class), 0, 0, w, h - 100)); + manager.load(f.path(), Texture.class); + manager.finishLoadingAsset(f.path()); + w = manager.get(f.path(), Texture.class).getWidth(); + h = manager.get(f.path(), Texture.class).getHeight(); + + if (f2.exists()) { + manager.load(f2.path(), Texture.class, new TextureLoader.TextureParameter(){{genMipMaps = true; minFilter = Texture.TextureFilter.MipMapLinearLinear; magFilter = Texture.TextureFilter.Linear;}}); + manager.finishLoadingAsset(f2.path()); + splashScreen.setBackground(new TextureRegion(manager.get(f2.path(), Texture.class))); + } else { + splashScreen.setBackground(new TextureRegion(manager.get(f.path(), Texture.class), 0, 0, w, h - 100)); + } } Pixmap pxSplash = new Pixmap(f); @@ -217,8 +209,8 @@ public class FSkin { if (FSkin.preferredName.isEmpty()) { FSkin.loadLight("default", splashScreen); } } - avatars.clear(); - sleeves.clear(); + Forge.getAssets().avatars().clear(); + Forge.getAssets().sleeves().clear(); TextureLoader.TextureParameter parameter = new TextureLoader.TextureParameter(); if (Forge.isTextureFilteringEnabled()) { @@ -227,7 +219,7 @@ public class FSkin { parameter.magFilter = Texture.TextureFilter.Linear; } - AssetManager manager = Forge.getAssets(true).manager; + AssetManager manager = Forge.getAssets().manager(); // Grab and test various sprite files. final FileHandle f1 = getDefaultSkinFile(SourceFile.ICONS.getFilename()); @@ -246,6 +238,8 @@ public class FSkin { final FileHandle f17 = getDefaultSkinFile(ForgeConstants.SPRITE_CRACKS_FILE); final FileHandle f18 = getDefaultSkinFile(ForgeConstants.SPRITE_PHYREXIAN_FILE); final FileHandle f19 = getDefaultSkinFile(ForgeConstants.SPRITE_CURSOR_FILE); + final FileHandle f20 = getSkinFile(ForgeConstants.SPRITE_SLEEVES_FILE); + final FileHandle f21 = getSkinFile(ForgeConstants.SPRITE_SLEEVES2_FILE); /*TODO Themeable final FileHandle f14 = getDefaultSkinFile(ForgeConstants.SPRITE_SETLOGO_FILE); @@ -321,7 +315,7 @@ public class FSkin { int counter = 0; int scount = 0; Color pxTest; - Pixmap pxDefaultAvatars, pxPreferredAvatars, pxDefaultSleeves; + Pixmap pxDefaultAvatars, pxPreferredAvatars, pxDefaultSleeves, pxPreferredSleeves; pxDefaultAvatars = new Pixmap(f4); pxDefaultSleeves = new Pixmap(f8); @@ -345,7 +339,7 @@ public class FSkin { if (i == 0 && j == 0) { continue; } pxTest = new Color(pxPreferredAvatars.getPixel(i + 50, j + 50)); if (pxTest.a == 0) { continue; } - FSkin.avatars.put(counter++, new TextureRegion(manager.get(f5.path(), Texture.class), i, j, 100, 100)); + Forge.getAssets().avatars().put(counter++, new TextureRegion(manager.get(f5.path(), Texture.class), i, j, 100, 100)); } } pxPreferredAvatars.dispose(); @@ -360,37 +354,72 @@ public class FSkin { if (i == 0 && j == 0) { continue; } pxTest = new Color(pxDefaultAvatars.getPixel(i + 50, j + 50)); if (pxTest.a == 0) { continue; } - FSkin.avatars.put(counter++, new TextureRegion(manager.get(f4.path(), Texture.class), i, j, 100, 100)); + Forge.getAssets().avatars().put(counter++, new TextureRegion(manager.get(f4.path(), Texture.class), i, j, 100, 100)); + } + } + } + if (f20.exists()) { + pxPreferredSleeves = new Pixmap(f20); + manager.load(f20.path(), Texture.class, parameter); + manager.finishLoadingAsset(f20.path()); + + final int sw = pxPreferredSleeves.getWidth(); + final int sh = pxPreferredSleeves.getHeight(); + + for (int j = 0; j < sh; j += 500) { + for (int i = 0; i < sw; i += 360) { + pxTest = new Color(pxPreferredSleeves.getPixel(i + 180, j + 250)); + if (pxTest.a == 0) { continue; } + Forge.getAssets().sleeves().put(scount++, new TextureRegion(manager.get(f20.path(), Texture.class), i, j, 360, 500)); + } + } + pxPreferredSleeves.dispose(); + } else { + final int sw = pxDefaultSleeves.getWidth(); + final int sh = pxDefaultSleeves.getHeight(); + + for (int j = 0; j < sh; j += 500) { + for (int i = 0; i < sw; i += 360) { + pxTest = new Color(pxDefaultSleeves.getPixel(i + 180, j + 250)); + if (pxTest.a == 0) { continue; } + Forge.getAssets().sleeves().put(scount++, new TextureRegion(manager.get(f8.path(), Texture.class), i, j, 360, 500)); + } + } + } + if (f21.exists()) { + pxPreferredSleeves = new Pixmap(f21); + manager.load(f21.path(), Texture.class, parameter); + manager.finishLoadingAsset(f21.path()); + + final int sw = pxPreferredSleeves.getWidth(); + final int sh = pxPreferredSleeves.getHeight(); + + for (int j = 0; j < sh; j += 500) { + for (int i = 0; i < sw; i += 360) { + pxTest = new Color(pxPreferredSleeves.getPixel(i + 180, j + 250)); + if (pxTest.a == 0) { continue; } + Forge.getAssets().sleeves().put(scount++, new TextureRegion(manager.get(f21.path(), Texture.class), i, j, 360, 500)); + } + } + pxPreferredSleeves.dispose(); + } else { + //re init second set of sleeves + pxDefaultSleeves = new Pixmap(f9); + manager.load(f9.path(), Texture.class, parameter); + manager.finishLoadingAsset(f9.path()); + + final int sw2 = pxDefaultSleeves.getWidth(); + final int sh2 = pxDefaultSleeves.getHeight(); + + for (int j = 0; j < sh2; j += 500) { + for (int i = 0; i < sw2; i += 360) { + pxTest = new Color(pxDefaultSleeves.getPixel(i + 180, j + 250)); + if (pxTest.a == 0) { continue; } + Forge.getAssets().sleeves().put(scount++, new TextureRegion(manager.get(f9.path(), Texture.class), i, j, 360, 500)); } } } - final int sw = pxDefaultSleeves.getWidth(); - final int sh = pxDefaultSleeves.getHeight(); - - for (int j = 0; j < sh; j += 500) { - for (int i = 0; i < sw; i += 360) { - pxTest = new Color(pxDefaultSleeves.getPixel(i + 180, j + 250)); - if (pxTest.a == 0) { continue; } - FSkin.sleeves.put(scount++, new TextureRegion(manager.get(f8.path(), Texture.class), i, j, 360, 500)); - } - } - - //re init second set of sleeves - pxDefaultSleeves = new Pixmap(f9); - manager.load(f9.path(), Texture.class, parameter); - manager.finishLoadingAsset(f9.path()); - - final int sw2 = pxDefaultSleeves.getWidth(); - final int sh2 = pxDefaultSleeves.getHeight(); - - for (int j = 0; j < sh2; j += 500) { - for (int i = 0; i < sw2; i += 360) { - pxTest = new Color(pxDefaultSleeves.getPixel(i + 180, j + 250)); - if (pxTest.a == 0) { continue; } - FSkin.sleeves.put(scount++, new TextureRegion(manager.get(f9.path(), Texture.class), i, j, 360, 500)); - } - } //cracks manager.load(f17.path(), Texture.class, parameter); manager.finishLoadingAsset(f17.path()); @@ -399,32 +428,32 @@ public class FSkin { int x = j * 200; for(int i = 0; i < 4; i++) { int y = i * 279; - FSkin.cracks.put(crackCount++, new TextureRegion(manager.get(f17.path(), Texture.class), x, y, 200, 279)); + Forge.getAssets().cracks().put(crackCount++, new TextureRegion(manager.get(f17.path(), Texture.class), x, y, 200, 279)); } } //borders manager.load(f10.path(), Texture.class); manager.finishLoadingAsset(f10.path()); - FSkin.borders.put(0, new TextureRegion(manager.get(f10.path(), Texture.class), 2, 2, 672, 936)); - FSkin.borders.put(1, new TextureRegion(manager.get(f10.path(), Texture.class), 676, 2, 672, 936)); + Forge.getAssets().borders().put(0, new TextureRegion(manager.get(f10.path(), Texture.class), 2, 2, 672, 936)); + Forge.getAssets().borders().put(1, new TextureRegion(manager.get(f10.path(), Texture.class), 676, 2, 672, 936)); //deckboxes manager.load(f13.path(), Texture.class, parameter); manager.finishLoadingAsset(f13.path()); //gold bg - FSkin.deckbox.put(0, new TextureRegion(manager.get(f13.path(), Texture.class), 2, 2, 488, 680)); + Forge.getAssets().deckbox().put(0, new TextureRegion(manager.get(f13.path(), Texture.class), 2, 2, 488, 680)); //deck box for card art - FSkin.deckbox.put(1, new TextureRegion(manager.get(f13.path(), Texture.class), 492, 2, 488, 680)); + Forge.getAssets().deckbox().put(1, new TextureRegion(manager.get(f13.path(), Texture.class), 492, 2, 488, 680)); //generic deck box - FSkin.deckbox.put(2, new TextureRegion(manager.get(f13.path(), Texture.class), 982, 2, 488, 680)); + Forge.getAssets().deckbox().put(2, new TextureRegion(manager.get(f13.path(), Texture.class), 982, 2, 488, 680)); //cursor manager.load(f19.path(), Texture.class); manager.finishLoadingAsset(f19.path()); - FSkin.cursor.put(0, new TextureRegion(manager.get(f19.path(), Texture.class), 0, 0, 32, 32)); //default - FSkin.cursor.put(1, new TextureRegion(manager.get(f19.path(), Texture.class), 32, 0, 32, 32)); //magnify on - FSkin.cursor.put(2, new TextureRegion(manager.get(f19.path(), Texture.class), 64, 0, 32, 32)); // magnify off + Forge.getAssets().cursor().put(0, new TextureRegion(manager.get(f19.path(), Texture.class), 0, 0, 32, 32)); //default + Forge.getAssets().cursor().put(1, new TextureRegion(manager.get(f19.path(), Texture.class), 32, 0, 32, 32)); //magnify on + Forge.getAssets().cursor().put(2, new TextureRegion(manager.get(f19.path(), Texture.class), 64, 0, 32, 32)); // magnify off - Forge.setCursor(cursor.get(0), "0"); + Forge.setCursor(Forge.getAssets().cursor().get(0), "0"); preferredIcons.dispose(); pxDefaultAvatars.dispose(); @@ -514,31 +543,31 @@ public class FSkin { } public static Map getImages() { - return images; + return Forge.getAssets().images(); } public static Map getAvatars() { - return avatars; + return Forge.getAssets().avatars(); } public static Map getSleeves() { - return sleeves; + return Forge.getAssets().sleeves(); } public static Map getCracks() { - return cracks; + return Forge.getAssets().cracks(); } public static Map getBorders() { - return borders; + return Forge.getAssets().borders(); } public static Map getDeckbox() { - return deckbox; + return Forge.getAssets().deckbox(); } public static Map getCursor() { - return cursor; + return Forge.getAssets().cursor(); } public static boolean isLoaded() { return loaded; } diff --git a/forge-gui-mobile/src/forge/assets/FSkinFont.java b/forge-gui-mobile/src/forge/assets/FSkinFont.java index 8367c054642..b74e6383b68 100644 --- a/forge-gui-mobile/src/forge/assets/FSkinFont.java +++ b/forge-gui-mobile/src/forge/assets/FSkinFont.java @@ -22,7 +22,6 @@ import com.badlogic.gdx.graphics.glutils.PixmapTextureData; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.IntSet; -import com.badlogic.gdx.utils.ObjectMap; import forge.Forge; import forge.gui.FThreads; import forge.localinstance.properties.ForgeConstants; @@ -39,29 +38,19 @@ public class FSkinFont { private static final int MAX_FONT_SIZE_MANY_GLYPHS = 36; private static final String TTF_FILE = "font1.ttf"; - private static final HashMap fonts = new HashMap<>(); - - private static final String commonCharacterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm" - + "nopqrstuvwxyz1234567890\"!?'.,;:()[]{}<>|/@\\^$-%+=#_&*\u2014" - + "\u2022ÁÉÍÓÚáéíóúÀÈÌÒÙàèìòùÑñÄËÏÖÜäëïöüẞß¿¡"; - private static ObjectMap langUniqueCharacterSet = new ObjectMap<>(); + private static HashMap langUniqueCharacterSet = new HashMap<>(); static { FileUtil.ensureDirectoryExists(ForgeConstants.FONTS_DIR); } - public static void clear() { - fonts.clear(); - //reset maxFontSize and Preload - preloadAll(""); - } public static FSkinFont get(final int unscaledSize) { return _get((int)Utils.scale(unscaledSize)); } public static FSkinFont _get(final int scaledSize) { - FSkinFont skinFont = fonts.get(scaledSize); + FSkinFont skinFont = Forge.getAssets().fonts().get(scaledSize); if (skinFont == null) { skinFont = new FSkinFont(scaledSize); - fonts.put(scaledSize, skinFont); + Forge.getAssets().fonts().put(scaledSize, skinFont); } return skinFont; } @@ -69,7 +58,8 @@ public class FSkinFont { public static FSkinFont forHeight(final float height) { int size = MIN_FONT_SIZE + 1; while (true) { - if (_get(size).getLineHeight() > height) { + FSkinFont f = _get(size); + if (f != null && f.getLineHeight() > height) { return _get(size - 1); } size++; @@ -97,14 +87,14 @@ public class FSkinFont { } public static void updateAll() { - for (FSkinFont skinFont : fonts.values()) { + for (FSkinFont skinFont : Forge.getAssets().fonts().values()) { skinFont.updateFont(); } } private final int fontSize; private final float scale; - private BitmapFont font; + BitmapFont font; private FSkinFont(int fontSize0) { if (fontSize0 > MAX_FONT_SIZE) { @@ -127,6 +117,8 @@ public class FSkinFont { } public int computeVisibleGlyphs (CharSequence str, int start, int end, float availableWidth) { + if (font == null) + return 0; BitmapFontData data = font.getData(); int index = start; float width = 0; @@ -180,6 +172,9 @@ public class FSkinFont { return getBounds(str, 0, str.length()); } public TextBounds getBounds(CharSequence str, int start, int end) { + if (font == null) { + return new TextBounds(0f, 0f); + } BitmapFontData data = font.getData(); //int start = 0; //int end = str.length(); @@ -228,6 +223,9 @@ public class FSkinFont { } public TextBounds getMultiLineBounds(CharSequence str) { updateScale(); + if (font == null) { + return new TextBounds(0f, 0f); + } BitmapFontData data = font.getData(); int start = 0; float maxWidth = 0; @@ -247,6 +245,9 @@ public class FSkinFont { } public TextBounds getWrappedBounds(CharSequence str, float wrapWidth) { updateScale(); + if (font == null) { + return new TextBounds(0f, 0f); + } BitmapFontData data = font.getData(); if (wrapWidth <= 0) wrapWidth = Integer.MAX_VALUE; int start = 0; @@ -303,14 +304,20 @@ public class FSkinFont { return new TextBounds(maxWidth, data.capHeight + (numLines - 1) * data.lineHeight); } public float getAscent() { + if (font == null) + return 0f; updateScale(); return font.getAscent(); } public float getCapHeight() { + if (font == null) + return 0f; updateScale(); return font.getCapHeight(); } public float getLineHeight() { + if (font == null) + return 0f; updateScale(); return font.getLineHeight(); } @@ -323,8 +330,12 @@ public class FSkinFont { //update scale of font if needed private void updateScale() { - if (font.getScaleX() != scale) { - font.getData().setScale(scale); + try { + if (font.getScaleX() != scale) { + font.getData().setScale(scale); + } + } catch (Exception e) { + e.printStackTrace(); } } @@ -348,10 +359,10 @@ public class FSkinFont { if (langUniqueCharacterSet.containsKey(langCode)) { return langUniqueCharacterSet.get(langCode); } - StringBuilder characters = new StringBuilder(commonCharacterSet); + StringBuilder characters = new StringBuilder(FreeTypeFontGenerator.DEFAULT_CHARS); IntSet characterSet = new IntSet(); - for (int offset = 0; offset < commonCharacterSet.length();) { - final int codePoint = commonCharacterSet.codePointAt(offset); + for (int offset = 0; offset < FreeTypeFontGenerator.DEFAULT_CHARS.length();) { + final int codePoint = FreeTypeFontGenerator.DEFAULT_CHARS.codePointAt(offset); characterSet.add(codePoint); offset += Character.charCount(codePoint); } @@ -400,16 +411,13 @@ public class FSkinFont { if (fontFile != null && fontFile.exists()) { final BitmapFontData data = new BitmapFontData(fontFile, false); String finalFontName = fontName; - FThreads.invokeInEdtNowOrLater(new Runnable() { - @Override - public void run() { //font must be initialized on UI thread - try { - font = new BitmapFont(data, (TextureRegion) null, true); - found[0] = true; - } catch (Exception e) { - e.printStackTrace(); - found[0] = false; - } + FThreads.invokeInEdtNowOrLater(() -> { //font must be initialized on UI thread + try { + font = new BitmapFont(data, (TextureRegion) null, true); + found[0] = true; + } catch (Exception e) { + e.printStackTrace(); + found[0] = false; } }); } diff --git a/forge-gui-mobile/src/forge/assets/FSkinTexture.java b/forge-gui-mobile/src/forge/assets/FSkinTexture.java index 26463c2850c..be4da23b628 100644 --- a/forge-gui-mobile/src/forge/assets/FSkinTexture.java +++ b/forge-gui-mobile/src/forge/assets/FSkinTexture.java @@ -128,9 +128,13 @@ public enum FSkinTexture implements FImage { FileHandle preferredFile = isPlane ? FSkin.getCachePlanechaseFile(filename) : FSkin.getSkinFile(filename); if (preferredFile.exists()) { try { - Forge.getAssets(true).manager.load(preferredFile.path(), Texture.class); - Forge.getAssets(true).manager.finishLoadingAsset(preferredFile.path()); - texture = Forge.getAssets(true).manager.get(preferredFile.path(), Texture.class); + if (preferredFile.path().contains("fallback_skin")) { + texture = new Texture(preferredFile); + } else { + Forge.getAssets().manager().load(preferredFile.path(), Texture.class); + Forge.getAssets().manager().finishLoadingAsset(preferredFile.path()); + texture = Forge.getAssets().manager().get(preferredFile.path(), Texture.class); + } } catch (final Exception e) { System.err.println("Failed to load skin file: " + preferredFile); @@ -148,9 +152,13 @@ public enum FSkinTexture implements FImage { if (defaultFile.exists()) { try { - Forge.getAssets(true).manager.load(defaultFile.path(), Texture.class); - Forge.getAssets(true).manager.finishLoadingAsset(defaultFile.path()); - texture = Forge.getAssets(true).manager.get(defaultFile.path(), Texture.class); + if (defaultFile.path().contains("fallback_skin")) { + texture = new Texture(defaultFile); + } else { + Forge.getAssets().manager().load(defaultFile.path(), Texture.class); + Forge.getAssets().manager().finishLoadingAsset(defaultFile.path()); + texture = Forge.getAssets().manager().get(defaultFile.path(), Texture.class); + } } catch (final Exception e) { System.err.println("Failed to load skin file: " + defaultFile); diff --git a/forge-gui-mobile/src/forge/assets/ImageCache.java b/forge-gui-mobile/src/forge/assets/ImageCache.java index 66e6f6da2de..ae54183c34f 100644 --- a/forge-gui-mobile/src/forge/assets/ImageCache.java +++ b/forge-gui-mobile/src/forge/assets/ImageCache.java @@ -18,22 +18,22 @@ package forge.assets; import java.io.File; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Queue; import java.util.Set; -import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.assets.loaders.TextureLoader.TextureParameter; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.TextureData; import com.badlogic.gdx.graphics.glutils.PixmapTextureData; -import com.badlogic.gdx.utils.ObjectMap; -import com.badlogic.gdx.utils.ObjectSet; import com.google.common.collect.EvictingQueue; import com.google.common.collect.Queues; import com.google.common.collect.Sets; import forge.gui.FThreads; +import forge.gui.GuiBase; import forge.util.FileUtil; import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; @@ -74,10 +74,12 @@ import forge.util.ImageUtil; * @version $Id: ImageCache.java 24769 2014-02-09 13:56:04Z Hellfish $ */ public class ImageCache { - private static final ObjectSet missingIconKeys = new ObjectSet<>(); + private static final HashSet missingIconKeys = new HashSet<>(); private static List borderlessCardlistKey = FileUtil.readFile(ForgeConstants.BORDERLESS_CARD_LIST_FILE); - static int maxCardCapacity = 400; //default card capacity + public static int counter = 0; + static int maxCardCapacity = 300; //default card capacity static EvictingQueue q; + static Set cardsLoaded; static Queue syncQ; static TextureParameter defaultParameter = new TextureParameter(); static TextureParameter filtered = new TextureParameter(); @@ -92,12 +94,14 @@ public class ImageCache { q = EvictingQueue.create(capacity); //init syncQ for threadsafe use syncQ = Queues.synchronizedQueue(q); + //cap + int cl = GuiBase.isAndroid() ? maxCardCapacity+(capacity/3) : 400; + cardsLoaded = new HashSet<>(cl); } public static final Texture defaultImage; public static FImage BlackBorder = FSkinImage.IMG_BORDER_BLACK; public static FImage WhiteBorder = FSkinImage.IMG_BORDER_WHITE; - private static final ObjectMap> imageBorder = new ObjectMap<>(1024); - private static final ObjectMap generatedCards = new ObjectMap<>(512); + private static final HashMap> imageBorder = new HashMap<>(1024); private static boolean imageLoaded, delayLoadRequested; public static void allowSingleLoad() { @@ -112,7 +116,7 @@ public class ImageCache { } catch (Exception ex) { System.err.println("could not load default card image"); } finally { - defaultImage = (null == defImage) ? new Texture(10, 10, Format.RGBA8888) : defImage; + defaultImage = (null == defImage) ? new Texture(10, 10, Format.RGBA4444) : defImage; } } @@ -121,11 +125,18 @@ public class ImageCache { ImageKeys.clearMissingCards(); } public static void clearGeneratedCards() { - generatedCards.clear(); + Forge.getAssets().generatedCards().clear(); } public static void disposeTextures(){ CardRenderer.clearcardArtCache(); - Forge.getAssets(false).manager.clear(); + //unload all cardsLoaded + for (String fileName : cardsLoaded) { + if (Forge.getAssets().manager().contains(fileName)) { + Forge.getAssets().manager().unload(fileName); + } + } + cardsLoaded.clear(); + ((Forge)Gdx.app.getApplicationListener()).needsUpdate = true; } public static Texture getImage(InventoryItem ii) { @@ -201,7 +212,7 @@ public class ImageCache { public static Texture getImage(String imageKey, boolean useDefaultIfNotFound) { return getImage(imageKey, useDefaultIfNotFound, false); } - public static Texture getImage(String imageKey, boolean useDefaultIfNotFound, boolean useOtherCache) { + public static Texture getImage(String imageKey, boolean useDefaultIfNotFound, boolean others) { if (FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_CARD_IMAGES)) return null; @@ -226,7 +237,7 @@ public class ImageCache { File imageFile = ImageKeys.getImageFile(imageKey); if (useDefaultIfNotFound) { // Load from file and add to cache if not found in cache initially. - image = getAsset(imageKey, imageFile, useOtherCache); + image = getAsset(imageKey, imageFile, others); if (image != null) { return image; } @@ -242,7 +253,7 @@ public class ImageCache { } try { - image = loadAsset(imageKey, imageFile, useOtherCache); + image = loadAsset(imageKey, imageFile, others); } catch (final Exception ex) { image = null; } @@ -261,55 +272,67 @@ public class ImageCache { } return image; } - static Texture getAsset(String imageKey, File file, boolean otherCache) { + static Texture getAsset(String imageKey, File file, boolean others) { if (file == null) return null; - if (!otherCache && Forge.enableUIMask.equals("Full") && isBorderless(imageKey)) - return generatedCards.get(imageKey); - return Forge.getAssets(otherCache).manager.get(file.getPath(), Texture.class, false); + if (!others && Forge.enableUIMask.equals("Full") && isBorderless(imageKey)) + return Forge.getAssets().generatedCards().get(imageKey); + return Forge.getAssets().manager().get(file.getPath(), Texture.class, false); } - static Texture loadAsset(String imageKey, File file, boolean otherCache) { + static Texture loadAsset(String imageKey, File file, boolean others) { if (file == null) return null; - syncQ.add(file.getPath()); - if (!otherCache && Forge.getAssets(false).manager.getLoadedAssets() > maxCardCapacity) { - unloadCardTextures(Forge.getAssets(false).manager); + Texture check = getAsset(imageKey, file, others); + if (check != null) + return check; + if (!others) { + syncQ.add(file.getPath()); + cardsLoaded.add(file.getPath()); + } + if (!others && cardsLoaded.size() > maxCardCapacity) { + unloadCardTextures(Forge.getAssets().manager()); return null; } String fileName = file.getPath(); //load to assetmanager - Forge.getAssets(otherCache).manager.load(fileName, Texture.class, Forge.isTextureFilteringEnabled() ? filtered : defaultParameter); - Forge.getAssets(otherCache).manager.finishLoadingAsset(fileName); + if (!Forge.getAssets().manager().contains(fileName, Texture.class)) { + Forge.getAssets().manager().load(fileName, Texture.class, Forge.isTextureFilteringEnabled() ? filtered : defaultParameter); + Forge.getAssets().manager().finishLoadingAsset(fileName); + counter+=1; + } + //return loaded assets - if (otherCache) { - return Forge.getAssets(true).manager.get(fileName, Texture.class, false); + if (others) { + return Forge.getAssets().manager().get(fileName, Texture.class, false); } else { - Texture t = Forge.getAssets(false).manager.get(fileName, Texture.class, false); + Texture cardTexture = Forge.getAssets().manager().get(fileName, Texture.class, false); //if full bordermasking is enabled, update the border color if (Forge.enableUIMask.equals("Full")) { boolean borderless = isBorderless(imageKey); - updateBorders(t.toString(), borderless ? Pair.of(Color.valueOf("#171717").toString(), false): isCloserToWhite(getpixelColor(t))); + updateBorders(cardTexture.toString(), borderless ? Pair.of(Color.valueOf("#171717").toString(), false): isCloserToWhite(getpixelColor(cardTexture))); //if borderless, generate new texture from the asset and store if (borderless) { - generatedCards.put(imageKey, generateTexture(new FileHandle(file), t, Forge.isTextureFilteringEnabled())); + Forge.getAssets().generatedCards().put(imageKey, generateTexture(new FileHandle(file), cardTexture, Forge.isTextureFilteringEnabled())); } } - return t; + return cardTexture; } } - static void unloadCardTextures(AssetManager manager) { + static void unloadCardTextures(Assets.MemoryTrackingAssetManager manager) { //get latest images from syncQ Set newQ = Sets.newHashSet(syncQ); - //get loaded images from assetmanager - Set old = Sets.newHashSet(manager.getAssetNames()); - //get all images not in newQ (old images to unload) - Set toUnload = Sets.difference(old, newQ); + //get all images not in newQ (cardLists to unload) + Set toUnload = Sets.difference(cardsLoaded, newQ); //unload from assetmanager to save RAM for (String asset : toUnload) { - manager.unload(asset); + if(manager.contains(asset)) { + manager.unload(asset); + } + cardsLoaded.remove(asset); } //clear cachedArt since this is dependant to the loaded texture CardRenderer.clearcardArtCache(); + ((Forge)Gdx.app.getApplicationListener()).needsUpdate = true; } public static void preloadCache(Iterable keys) { if (FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_DISABLE_CARD_IMAGES)) @@ -409,10 +432,10 @@ public class ImageCache { return borderColor(t); } - public static Texture generateTexture(FileHandle fh, Texture t, boolean textureFilter) { - if (t == null || fh == null) - return t; - final Texture[] n = new Texture[1]; + public static Texture generateTexture(FileHandle fh, Texture cardTexture, boolean textureFilter) { + if (cardTexture == null || fh == null) + return cardTexture; + final Texture[] placeholder = new Texture[1]; FThreads.invokeInEdtNowOrLater(() -> { Pixmap pImage = new Pixmap(fh); int w = pImage.getWidth(); @@ -422,20 +445,20 @@ public class ImageCache { drawPixelstoMask(pImage, pMask); TextureData textureData = new PixmapTextureData( pMask, //pixmap to use - Format.RGBA8888, + Format.RGBA4444, textureFilter, //use mipmaps false, true); - n[0] = new Texture(textureData); + placeholder[0] = new Texture(textureData); if (textureFilter) - n[0].setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear); + placeholder[0].setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear); pImage.dispose(); pMask.dispose(); }); - return n[0]; + return placeholder[0]; } public static Pixmap createRoundedRectangle(int width, int height, int cornerRadius, Color color) { - Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888); - Pixmap ret = new Pixmap(width, height, Pixmap.Format.RGBA8888); + Pixmap pixmap = new Pixmap(width, height, Format.RGBA4444); + Pixmap ret = new Pixmap(width, height, Format.RGBA4444); pixmap.setColor(color); //round corners pixmap.fillCircle(cornerRadius, cornerRadius, cornerRadius); @@ -486,11 +509,7 @@ public class ImageCache { //generated texture/pixmap? if (t.toString().contains("com.badlogic.gdx.graphics.Texture@")) return true; - for (String key : borderlessCardlistKey) { - if (t.toString().contains(key)) - return true; - } - return false; + return borderlessCardlistKey.stream().anyMatch(key -> t.toString().contains(key)); } public static String getpixelColor(Texture i) { diff --git a/forge-gui-mobile/src/forge/assets/TextRenderer.java b/forge-gui-mobile/src/forge/assets/TextRenderer.java index 231e16cfc66..9f6c4ee4fcd 100644 --- a/forge-gui-mobile/src/forge/assets/TextRenderer.java +++ b/forge-gui-mobile/src/forge/assets/TextRenderer.java @@ -2,10 +2,8 @@ package forge.assets; import java.text.BreakIterator; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.utils.Align; @@ -20,64 +18,63 @@ import forge.util.TextBounds; //Encodes text for drawing with symbols and reminder text public class TextRenderer { - private static final Map symbolLookup = new HashMap<>(64); static { - symbolLookup.put("C", FSkinImage.MANA_COLORLESS); - symbolLookup.put("W", FSkinImage.MANA_W); - symbolLookup.put("U", FSkinImage.MANA_U); - symbolLookup.put("B", FSkinImage.MANA_B); - symbolLookup.put("R", FSkinImage.MANA_R); - symbolLookup.put("G", FSkinImage.MANA_G); - symbolLookup.put("W/U", FSkinImage.MANA_HYBRID_WU); - symbolLookup.put("U/B", FSkinImage.MANA_HYBRID_UB); - symbolLookup.put("B/R", FSkinImage.MANA_HYBRID_BR); - symbolLookup.put("R/G", FSkinImage.MANA_HYBRID_RG); - symbolLookup.put("G/W", FSkinImage.MANA_HYBRID_GW); - symbolLookup.put("W/B", FSkinImage.MANA_HYBRID_WB); - symbolLookup.put("U/R", FSkinImage.MANA_HYBRID_UR); - symbolLookup.put("B/G", FSkinImage.MANA_HYBRID_BG); - symbolLookup.put("R/W", FSkinImage.MANA_HYBRID_RW); - symbolLookup.put("G/U", FSkinImage.MANA_HYBRID_GU); - symbolLookup.put("2/W", FSkinImage.MANA_2W); - symbolLookup.put("2/U", FSkinImage.MANA_2U); - symbolLookup.put("2/B", FSkinImage.MANA_2B); - symbolLookup.put("2/R", FSkinImage.MANA_2R); - symbolLookup.put("2/G", FSkinImage.MANA_2G); - symbolLookup.put("P", FSkinImage.MANA_PHRYX); - symbolLookup.put("P/W", FSkinImage.MANA_PHRYX_W); - symbolLookup.put("P/U", FSkinImage.MANA_PHRYX_U); - symbolLookup.put("P/B", FSkinImage.MANA_PHRYX_B); - symbolLookup.put("P/R", FSkinImage.MANA_PHRYX_R); - symbolLookup.put("P/G", FSkinImage.MANA_PHRYX_G); - symbolLookup.put("W/P", FSkinImage.MANA_PHRYX_W); - symbolLookup.put("U/P", FSkinImage.MANA_PHRYX_U); - symbolLookup.put("B/P", FSkinImage.MANA_PHRYX_B); - symbolLookup.put("R/P", FSkinImage.MANA_PHRYX_R); - symbolLookup.put("G/P", FSkinImage.MANA_PHRYX_G); - symbolLookup.put("P/B/G", FSkinImage.MANA_PHRYX_BG); - symbolLookup.put("P/B/R", FSkinImage.MANA_PHRYX_BR); - symbolLookup.put("P/G/U", FSkinImage.MANA_PHRYX_GU); - symbolLookup.put("P/G/W", FSkinImage.MANA_PHRYX_GW); - symbolLookup.put("P/R/G", FSkinImage.MANA_PHRYX_RG); - symbolLookup.put("P/R/W", FSkinImage.MANA_PHRYX_RW); - symbolLookup.put("P/U/B", FSkinImage.MANA_PHRYX_UB); - symbolLookup.put("P/U/R", FSkinImage.MANA_PHRYX_UR); - symbolLookup.put("P/W/B", FSkinImage.MANA_PHRYX_WB); - symbolLookup.put("P/W/U", FSkinImage.MANA_PHRYX_WU); + Forge.getAssets().symbolLookup().put("C", FSkinImage.MANA_COLORLESS); + Forge.getAssets().symbolLookup().put("W", FSkinImage.MANA_W); + Forge.getAssets().symbolLookup().put("U", FSkinImage.MANA_U); + Forge.getAssets().symbolLookup().put("B", FSkinImage.MANA_B); + Forge.getAssets().symbolLookup().put("R", FSkinImage.MANA_R); + Forge.getAssets().symbolLookup().put("G", FSkinImage.MANA_G); + Forge.getAssets().symbolLookup().put("W/U", FSkinImage.MANA_HYBRID_WU); + Forge.getAssets().symbolLookup().put("U/B", FSkinImage.MANA_HYBRID_UB); + Forge.getAssets().symbolLookup().put("B/R", FSkinImage.MANA_HYBRID_BR); + Forge.getAssets().symbolLookup().put("R/G", FSkinImage.MANA_HYBRID_RG); + Forge.getAssets().symbolLookup().put("G/W", FSkinImage.MANA_HYBRID_GW); + Forge.getAssets().symbolLookup().put("W/B", FSkinImage.MANA_HYBRID_WB); + Forge.getAssets().symbolLookup().put("U/R", FSkinImage.MANA_HYBRID_UR); + Forge.getAssets().symbolLookup().put("B/G", FSkinImage.MANA_HYBRID_BG); + Forge.getAssets().symbolLookup().put("R/W", FSkinImage.MANA_HYBRID_RW); + Forge.getAssets().symbolLookup().put("G/U", FSkinImage.MANA_HYBRID_GU); + Forge.getAssets().symbolLookup().put("2/W", FSkinImage.MANA_2W); + Forge.getAssets().symbolLookup().put("2/U", FSkinImage.MANA_2U); + Forge.getAssets().symbolLookup().put("2/B", FSkinImage.MANA_2B); + Forge.getAssets().symbolLookup().put("2/R", FSkinImage.MANA_2R); + Forge.getAssets().symbolLookup().put("2/G", FSkinImage.MANA_2G); + Forge.getAssets().symbolLookup().put("P", FSkinImage.MANA_PHRYX); + Forge.getAssets().symbolLookup().put("P/W", FSkinImage.MANA_PHRYX_W); + Forge.getAssets().symbolLookup().put("P/U", FSkinImage.MANA_PHRYX_U); + Forge.getAssets().symbolLookup().put("P/B", FSkinImage.MANA_PHRYX_B); + Forge.getAssets().symbolLookup().put("P/R", FSkinImage.MANA_PHRYX_R); + Forge.getAssets().symbolLookup().put("P/G", FSkinImage.MANA_PHRYX_G); + Forge.getAssets().symbolLookup().put("W/P", FSkinImage.MANA_PHRYX_W); + Forge.getAssets().symbolLookup().put("U/P", FSkinImage.MANA_PHRYX_U); + Forge.getAssets().symbolLookup().put("B/P", FSkinImage.MANA_PHRYX_B); + Forge.getAssets().symbolLookup().put("R/P", FSkinImage.MANA_PHRYX_R); + Forge.getAssets().symbolLookup().put("G/P", FSkinImage.MANA_PHRYX_G); + Forge.getAssets().symbolLookup().put("P/B/G", FSkinImage.MANA_PHRYX_BG); + Forge.getAssets().symbolLookup().put("P/B/R", FSkinImage.MANA_PHRYX_BR); + Forge.getAssets().symbolLookup().put("P/G/U", FSkinImage.MANA_PHRYX_GU); + Forge.getAssets().symbolLookup().put("P/G/W", FSkinImage.MANA_PHRYX_GW); + Forge.getAssets().symbolLookup().put("P/R/G", FSkinImage.MANA_PHRYX_RG); + Forge.getAssets().symbolLookup().put("P/R/W", FSkinImage.MANA_PHRYX_RW); + Forge.getAssets().symbolLookup().put("P/U/B", FSkinImage.MANA_PHRYX_UB); + Forge.getAssets().symbolLookup().put("P/U/R", FSkinImage.MANA_PHRYX_UR); + Forge.getAssets().symbolLookup().put("P/W/B", FSkinImage.MANA_PHRYX_WB); + Forge.getAssets().symbolLookup().put("P/W/U", FSkinImage.MANA_PHRYX_WU); for (int i = 0; i <= 20; i++) { - symbolLookup.put(String.valueOf(i), FSkinImage.valueOf("MANA_" + i)); + Forge.getAssets().symbolLookup().put(String.valueOf(i), FSkinImage.valueOf("MANA_" + i)); } - symbolLookup.put("X", FSkinImage.MANA_X); - symbolLookup.put("Y", FSkinImage.MANA_Y); - symbolLookup.put("Z", FSkinImage.MANA_Z); - symbolLookup.put("CHAOS", FSkinImage.CHAOS); - symbolLookup.put("Q", FSkinImage.UNTAP); - symbolLookup.put("S", FSkinImage.MANA_SNOW); - symbolLookup.put("T", FSkinImage.TAP); - symbolLookup.put("E", FSkinImage.ENERGY); - symbolLookup.put("AE", FSkinImage.AETHER_SHARD); - symbolLookup.put("PW", FSkinImage.PW_BADGE_COMMON); - symbolLookup.put("CR", FSkinImage.QUEST_COINSTACK); + Forge.getAssets().symbolLookup().put("X", FSkinImage.MANA_X); + Forge.getAssets().symbolLookup().put("Y", FSkinImage.MANA_Y); + Forge.getAssets().symbolLookup().put("Z", FSkinImage.MANA_Z); + Forge.getAssets().symbolLookup().put("CHAOS", FSkinImage.CHAOS); + Forge.getAssets().symbolLookup().put("Q", FSkinImage.UNTAP); + Forge.getAssets().symbolLookup().put("S", FSkinImage.MANA_SNOW); + Forge.getAssets().symbolLookup().put("T", FSkinImage.TAP); + Forge.getAssets().symbolLookup().put("E", FSkinImage.ENERGY); + Forge.getAssets().symbolLookup().put("AE", FSkinImage.AETHER_SHARD); + Forge.getAssets().symbolLookup().put("PW", FSkinImage.PW_BADGE_COMMON); + Forge.getAssets().symbolLookup().put("CR", FSkinImage.QUEST_COINSTACK); } public static String startColor(Color color) { @@ -192,7 +189,7 @@ public class TextRenderer { if (inSymbolCount > 0) { inSymbolCount--; if (text.length() > 0) { - FSkinImage symbol = symbolLookup.get(text.toString()); + FSkinImage symbol = Forge.getAssets().symbolLookup().get(text.toString()); if (symbol != null) { pieceWidth = lineHeight * CardFaceSymbols.FONT_SIZE_FACTOR; if (x + pieceWidth > width) { diff --git a/forge-gui-mobile/src/forge/card/CardFaceSymbols.java b/forge-gui-mobile/src/forge/card/CardFaceSymbols.java index 4c73a945404..cb8cc135829 100644 --- a/forge-gui-mobile/src/forge/card/CardFaceSymbols.java +++ b/forge-gui-mobile/src/forge/card/CardFaceSymbols.java @@ -17,10 +17,9 @@ */ package forge.card; -import java.util.HashMap; -import java.util.Map; import java.util.StringTokenizer; +import forge.Forge; import forge.Graphics; import forge.assets.FSkinImage; import forge.card.mana.ManaCost; @@ -30,141 +29,140 @@ import forge.gui.error.BugReporter; public class CardFaceSymbols { public static final float FONT_SIZE_FACTOR = 0.85f; - private static final Map MANA_IMAGES = new HashMap<>(128); public static void loadImages() { for (int i = 0; i <= 20; i++) { - MANA_IMAGES.put(String.valueOf(i), FSkinImage.valueOf("MANA_" + i)); + Forge.getAssets().manaImages().put(String.valueOf(i), FSkinImage.valueOf("MANA_" + i)); } - MANA_IMAGES.put("X", FSkinImage.MANA_X); - MANA_IMAGES.put("Y", FSkinImage.MANA_Y); - MANA_IMAGES.put("Z", FSkinImage.MANA_Z); + Forge.getAssets().manaImages().put("X", FSkinImage.MANA_X); + Forge.getAssets().manaImages().put("Y", FSkinImage.MANA_Y); + Forge.getAssets().manaImages().put("Z", FSkinImage.MANA_Z); - MANA_IMAGES.put("C", FSkinImage.MANA_COLORLESS); - MANA_IMAGES.put("B", FSkinImage.MANA_B); - MANA_IMAGES.put("BG", FSkinImage.MANA_HYBRID_BG); - MANA_IMAGES.put("BR", FSkinImage.MANA_HYBRID_BR); - MANA_IMAGES.put("G", FSkinImage.MANA_G); - MANA_IMAGES.put("GU", FSkinImage.MANA_HYBRID_GU); - MANA_IMAGES.put("GW", FSkinImage.MANA_HYBRID_GW); - MANA_IMAGES.put("R", FSkinImage.MANA_R); - MANA_IMAGES.put("RG", FSkinImage.MANA_HYBRID_RG); - MANA_IMAGES.put("RW", FSkinImage.MANA_HYBRID_RW); - MANA_IMAGES.put("U", FSkinImage.MANA_U); - MANA_IMAGES.put("UB", FSkinImage.MANA_HYBRID_UB); - MANA_IMAGES.put("UR", FSkinImage.MANA_HYBRID_UR); - MANA_IMAGES.put("W", FSkinImage.MANA_W); - MANA_IMAGES.put("WB", FSkinImage.MANA_HYBRID_WB); - MANA_IMAGES.put("WU", FSkinImage.MANA_HYBRID_WU); - MANA_IMAGES.put("P", FSkinImage.MANA_PHRYX); - MANA_IMAGES.put("PW", FSkinImage.MANA_PHRYX_W); - MANA_IMAGES.put("PR", FSkinImage.MANA_PHRYX_R); - MANA_IMAGES.put("PU", FSkinImage.MANA_PHRYX_U); - MANA_IMAGES.put("PB", FSkinImage.MANA_PHRYX_B); - MANA_IMAGES.put("PG", FSkinImage.MANA_PHRYX_G); - MANA_IMAGES.put("PBG", FSkinImage.MANA_PHRYX_BG); - MANA_IMAGES.put("PBR", FSkinImage.MANA_PHRYX_BR); - MANA_IMAGES.put("PGU", FSkinImage.MANA_PHRYX_GU); - MANA_IMAGES.put("PGW", FSkinImage.MANA_PHRYX_GW); - MANA_IMAGES.put("PRG", FSkinImage.MANA_PHRYX_RG); - MANA_IMAGES.put("PRW", FSkinImage.MANA_PHRYX_RW); - MANA_IMAGES.put("PUB", FSkinImage.MANA_PHRYX_UB); - MANA_IMAGES.put("PUR", FSkinImage.MANA_PHRYX_UR); - MANA_IMAGES.put("PWB", FSkinImage.MANA_PHRYX_WB); - MANA_IMAGES.put("PWU", FSkinImage.MANA_PHRYX_WU); - MANA_IMAGES.put("2W", FSkinImage.MANA_2W); - MANA_IMAGES.put("2U", FSkinImage.MANA_2U); - MANA_IMAGES.put("2R", FSkinImage.MANA_2R); - MANA_IMAGES.put("2G", FSkinImage.MANA_2G); - MANA_IMAGES.put("2B", FSkinImage.MANA_2B); + Forge.getAssets().manaImages().put("C", FSkinImage.MANA_COLORLESS); + Forge.getAssets().manaImages().put("B", FSkinImage.MANA_B); + Forge.getAssets().manaImages().put("BG", FSkinImage.MANA_HYBRID_BG); + Forge.getAssets().manaImages().put("BR", FSkinImage.MANA_HYBRID_BR); + Forge.getAssets().manaImages().put("G", FSkinImage.MANA_G); + Forge.getAssets().manaImages().put("GU", FSkinImage.MANA_HYBRID_GU); + Forge.getAssets().manaImages().put("GW", FSkinImage.MANA_HYBRID_GW); + Forge.getAssets().manaImages().put("R", FSkinImage.MANA_R); + Forge.getAssets().manaImages().put("RG", FSkinImage.MANA_HYBRID_RG); + Forge.getAssets().manaImages().put("RW", FSkinImage.MANA_HYBRID_RW); + Forge.getAssets().manaImages().put("U", FSkinImage.MANA_U); + Forge.getAssets().manaImages().put("UB", FSkinImage.MANA_HYBRID_UB); + Forge.getAssets().manaImages().put("UR", FSkinImage.MANA_HYBRID_UR); + Forge.getAssets().manaImages().put("W", FSkinImage.MANA_W); + Forge.getAssets().manaImages().put("WB", FSkinImage.MANA_HYBRID_WB); + Forge.getAssets().manaImages().put("WU", FSkinImage.MANA_HYBRID_WU); + Forge.getAssets().manaImages().put("P", FSkinImage.MANA_PHRYX); + Forge.getAssets().manaImages().put("PW", FSkinImage.MANA_PHRYX_W); + Forge.getAssets().manaImages().put("PR", FSkinImage.MANA_PHRYX_R); + Forge.getAssets().manaImages().put("PU", FSkinImage.MANA_PHRYX_U); + Forge.getAssets().manaImages().put("PB", FSkinImage.MANA_PHRYX_B); + Forge.getAssets().manaImages().put("PG", FSkinImage.MANA_PHRYX_G); + Forge.getAssets().manaImages().put("PBG", FSkinImage.MANA_PHRYX_BG); + Forge.getAssets().manaImages().put("PBR", FSkinImage.MANA_PHRYX_BR); + Forge.getAssets().manaImages().put("PGU", FSkinImage.MANA_PHRYX_GU); + Forge.getAssets().manaImages().put("PGW", FSkinImage.MANA_PHRYX_GW); + Forge.getAssets().manaImages().put("PRG", FSkinImage.MANA_PHRYX_RG); + Forge.getAssets().manaImages().put("PRW", FSkinImage.MANA_PHRYX_RW); + Forge.getAssets().manaImages().put("PUB", FSkinImage.MANA_PHRYX_UB); + Forge.getAssets().manaImages().put("PUR", FSkinImage.MANA_PHRYX_UR); + Forge.getAssets().manaImages().put("PWB", FSkinImage.MANA_PHRYX_WB); + Forge.getAssets().manaImages().put("PWU", FSkinImage.MANA_PHRYX_WU); + Forge.getAssets().manaImages().put("2W", FSkinImage.MANA_2W); + Forge.getAssets().manaImages().put("2U", FSkinImage.MANA_2U); + Forge.getAssets().manaImages().put("2R", FSkinImage.MANA_2R); + Forge.getAssets().manaImages().put("2G", FSkinImage.MANA_2G); + Forge.getAssets().manaImages().put("2B", FSkinImage.MANA_2B); - MANA_IMAGES.put("S", FSkinImage.MANA_SNOW); - MANA_IMAGES.put("T", FSkinImage.TAP); - MANA_IMAGES.put("E", FSkinImage.ENERGY); - MANA_IMAGES.put("slash", FSkinImage.SLASH); - MANA_IMAGES.put("attack", FSkinImage.ATTACK); - MANA_IMAGES.put("defend", FSkinImage.DEFEND); - MANA_IMAGES.put("summonsick", FSkinImage.SUMMONSICK); - MANA_IMAGES.put("phasing", FSkinImage.PHASING); - MANA_IMAGES.put("sacrifice", FSkinImage.COSTRESERVED); - MANA_IMAGES.put("counters1", FSkinImage.COUNTERS1); - MANA_IMAGES.put("counters2", FSkinImage.COUNTERS2); - MANA_IMAGES.put("counters3", FSkinImage.COUNTERS3); - MANA_IMAGES.put("countersMulti", FSkinImage.COUNTERS_MULTI); + Forge.getAssets().manaImages().put("S", FSkinImage.MANA_SNOW); + Forge.getAssets().manaImages().put("T", FSkinImage.TAP); + Forge.getAssets().manaImages().put("E", FSkinImage.ENERGY); + Forge.getAssets().manaImages().put("slash", FSkinImage.SLASH); + Forge.getAssets().manaImages().put("attack", FSkinImage.ATTACK); + Forge.getAssets().manaImages().put("defend", FSkinImage.DEFEND); + Forge.getAssets().manaImages().put("summonsick", FSkinImage.SUMMONSICK); + Forge.getAssets().manaImages().put("phasing", FSkinImage.PHASING); + Forge.getAssets().manaImages().put("sacrifice", FSkinImage.COSTRESERVED); + Forge.getAssets().manaImages().put("counters1", FSkinImage.COUNTERS1); + Forge.getAssets().manaImages().put("counters2", FSkinImage.COUNTERS2); + Forge.getAssets().manaImages().put("counters3", FSkinImage.COUNTERS3); + Forge.getAssets().manaImages().put("countersMulti", FSkinImage.COUNTERS_MULTI); - MANA_IMAGES.put("foil01", FSkinImage.FOIL_01); - MANA_IMAGES.put("foil02", FSkinImage.FOIL_02); - MANA_IMAGES.put("foil03", FSkinImage.FOIL_03); - MANA_IMAGES.put("foil04", FSkinImage.FOIL_04); - MANA_IMAGES.put("foil05", FSkinImage.FOIL_05); - MANA_IMAGES.put("foil06", FSkinImage.FOIL_06); - MANA_IMAGES.put("foil07", FSkinImage.FOIL_07); - MANA_IMAGES.put("foil08", FSkinImage.FOIL_08); - MANA_IMAGES.put("foil09", FSkinImage.FOIL_09); - MANA_IMAGES.put("foil10", FSkinImage.FOIL_10); + Forge.getAssets().manaImages().put("foil01", FSkinImage.FOIL_01); + Forge.getAssets().manaImages().put("foil02", FSkinImage.FOIL_02); + Forge.getAssets().manaImages().put("foil03", FSkinImage.FOIL_03); + Forge.getAssets().manaImages().put("foil04", FSkinImage.FOIL_04); + Forge.getAssets().manaImages().put("foil05", FSkinImage.FOIL_05); + Forge.getAssets().manaImages().put("foil06", FSkinImage.FOIL_06); + Forge.getAssets().manaImages().put("foil07", FSkinImage.FOIL_07); + Forge.getAssets().manaImages().put("foil08", FSkinImage.FOIL_08); + Forge.getAssets().manaImages().put("foil09", FSkinImage.FOIL_09); + Forge.getAssets().manaImages().put("foil10", FSkinImage.FOIL_10); - MANA_IMAGES.put("foil11", FSkinImage.FOIL_11); - MANA_IMAGES.put("foil12", FSkinImage.FOIL_12); - MANA_IMAGES.put("foil13", FSkinImage.FOIL_13); - MANA_IMAGES.put("foil14", FSkinImage.FOIL_14); - MANA_IMAGES.put("foil15", FSkinImage.FOIL_15); - MANA_IMAGES.put("foil16", FSkinImage.FOIL_16); - MANA_IMAGES.put("foil17", FSkinImage.FOIL_17); - MANA_IMAGES.put("foil18", FSkinImage.FOIL_18); - MANA_IMAGES.put("foil19", FSkinImage.FOIL_19); - MANA_IMAGES.put("foil20", FSkinImage.FOIL_20); + Forge.getAssets().manaImages().put("foil11", FSkinImage.FOIL_11); + Forge.getAssets().manaImages().put("foil12", FSkinImage.FOIL_12); + Forge.getAssets().manaImages().put("foil13", FSkinImage.FOIL_13); + Forge.getAssets().manaImages().put("foil14", FSkinImage.FOIL_14); + Forge.getAssets().manaImages().put("foil15", FSkinImage.FOIL_15); + Forge.getAssets().manaImages().put("foil16", FSkinImage.FOIL_16); + Forge.getAssets().manaImages().put("foil17", FSkinImage.FOIL_17); + Forge.getAssets().manaImages().put("foil18", FSkinImage.FOIL_18); + Forge.getAssets().manaImages().put("foil19", FSkinImage.FOIL_19); + Forge.getAssets().manaImages().put("foil20", FSkinImage.FOIL_20); - MANA_IMAGES.put("commander", FSkinImage.IMG_ABILITY_COMMANDER); + Forge.getAssets().manaImages().put("commander", FSkinImage.IMG_ABILITY_COMMANDER); - MANA_IMAGES.put("deathtouch", FSkinImage.IMG_ABILITY_DEATHTOUCH); - MANA_IMAGES.put("defender", FSkinImage.IMG_ABILITY_DEFENDER); - MANA_IMAGES.put("doublestrike", FSkinImage.IMG_ABILITY_DOUBLE_STRIKE); - MANA_IMAGES.put("firststrike", FSkinImage.IMG_ABILITY_FIRST_STRIKE); - MANA_IMAGES.put("fear", FSkinImage.IMG_ABILITY_FEAR); - MANA_IMAGES.put("flash", FSkinImage.IMG_ABILITY_FLASH); - MANA_IMAGES.put("flying", FSkinImage.IMG_ABILITY_FLYING); - MANA_IMAGES.put("haste", FSkinImage.IMG_ABILITY_HASTE); - MANA_IMAGES.put("hexproof", FSkinImage.IMG_ABILITY_HEXPROOF); - MANA_IMAGES.put("horsemanship", FSkinImage.IMG_ABILITY_HORSEMANSHIP); - MANA_IMAGES.put("indestructible", FSkinImage.IMG_ABILITY_INDESTRUCTIBLE); - MANA_IMAGES.put("intimidate", FSkinImage.IMG_ABILITY_INTIMIDATE); - MANA_IMAGES.put("landwalk", FSkinImage.IMG_ABILITY_LANDWALK); - MANA_IMAGES.put("lifelink", FSkinImage.IMG_ABILITY_LIFELINK); - MANA_IMAGES.put("menace", FSkinImage.IMG_ABILITY_MENACE); - MANA_IMAGES.put("reach", FSkinImage.IMG_ABILITY_REACH); - MANA_IMAGES.put("shadow", FSkinImage.IMG_ABILITY_SHADOW); - MANA_IMAGES.put("shroud", FSkinImage.IMG_ABILITY_SHROUD); - MANA_IMAGES.put("trample", FSkinImage.IMG_ABILITY_TRAMPLE); - MANA_IMAGES.put("vigilance", FSkinImage.IMG_ABILITY_VIGILANCE); + Forge.getAssets().manaImages().put("deathtouch", FSkinImage.IMG_ABILITY_DEATHTOUCH); + Forge.getAssets().manaImages().put("defender", FSkinImage.IMG_ABILITY_DEFENDER); + Forge.getAssets().manaImages().put("doublestrike", FSkinImage.IMG_ABILITY_DOUBLE_STRIKE); + Forge.getAssets().manaImages().put("firststrike", FSkinImage.IMG_ABILITY_FIRST_STRIKE); + Forge.getAssets().manaImages().put("fear", FSkinImage.IMG_ABILITY_FEAR); + Forge.getAssets().manaImages().put("flash", FSkinImage.IMG_ABILITY_FLASH); + Forge.getAssets().manaImages().put("flying", FSkinImage.IMG_ABILITY_FLYING); + Forge.getAssets().manaImages().put("haste", FSkinImage.IMG_ABILITY_HASTE); + Forge.getAssets().manaImages().put("hexproof", FSkinImage.IMG_ABILITY_HEXPROOF); + Forge.getAssets().manaImages().put("horsemanship", FSkinImage.IMG_ABILITY_HORSEMANSHIP); + Forge.getAssets().manaImages().put("indestructible", FSkinImage.IMG_ABILITY_INDESTRUCTIBLE); + Forge.getAssets().manaImages().put("intimidate", FSkinImage.IMG_ABILITY_INTIMIDATE); + Forge.getAssets().manaImages().put("landwalk", FSkinImage.IMG_ABILITY_LANDWALK); + Forge.getAssets().manaImages().put("lifelink", FSkinImage.IMG_ABILITY_LIFELINK); + Forge.getAssets().manaImages().put("menace", FSkinImage.IMG_ABILITY_MENACE); + Forge.getAssets().manaImages().put("reach", FSkinImage.IMG_ABILITY_REACH); + Forge.getAssets().manaImages().put("shadow", FSkinImage.IMG_ABILITY_SHADOW); + Forge.getAssets().manaImages().put("shroud", FSkinImage.IMG_ABILITY_SHROUD); + Forge.getAssets().manaImages().put("trample", FSkinImage.IMG_ABILITY_TRAMPLE); + Forge.getAssets().manaImages().put("vigilance", FSkinImage.IMG_ABILITY_VIGILANCE); //hexproof from - MANA_IMAGES.put("hexproofR", FSkinImage.IMG_ABILITY_HEXPROOF_R); - MANA_IMAGES.put("hexproofG", FSkinImage.IMG_ABILITY_HEXPROOF_G); - MANA_IMAGES.put("hexproofB", FSkinImage.IMG_ABILITY_HEXPROOF_B); - MANA_IMAGES.put("hexproofU", FSkinImage.IMG_ABILITY_HEXPROOF_U); - MANA_IMAGES.put("hexproofW", FSkinImage.IMG_ABILITY_HEXPROOF_W); - MANA_IMAGES.put("hexproofC", FSkinImage.IMG_ABILITY_HEXPROOF_C); - MANA_IMAGES.put("hexproofUB", FSkinImage.IMG_ABILITY_HEXPROOF_UB); + Forge.getAssets().manaImages().put("hexproofR", FSkinImage.IMG_ABILITY_HEXPROOF_R); + Forge.getAssets().manaImages().put("hexproofG", FSkinImage.IMG_ABILITY_HEXPROOF_G); + Forge.getAssets().manaImages().put("hexproofB", FSkinImage.IMG_ABILITY_HEXPROOF_B); + Forge.getAssets().manaImages().put("hexproofU", FSkinImage.IMG_ABILITY_HEXPROOF_U); + Forge.getAssets().manaImages().put("hexproofW", FSkinImage.IMG_ABILITY_HEXPROOF_W); + Forge.getAssets().manaImages().put("hexproofC", FSkinImage.IMG_ABILITY_HEXPROOF_C); + Forge.getAssets().manaImages().put("hexproofUB", FSkinImage.IMG_ABILITY_HEXPROOF_UB); //token icon - MANA_IMAGES.put("token", FSkinImage.IMG_ABILITY_TOKEN); + Forge.getAssets().manaImages().put("token", FSkinImage.IMG_ABILITY_TOKEN); //protection from - MANA_IMAGES.put("protectAll", FSkinImage.IMG_ABILITY_PROTECT_ALL); - MANA_IMAGES.put("protectB", FSkinImage.IMG_ABILITY_PROTECT_B); - MANA_IMAGES.put("protectBU", FSkinImage.IMG_ABILITY_PROTECT_BU); - MANA_IMAGES.put("protectBW", FSkinImage.IMG_ABILITY_PROTECT_BW); - MANA_IMAGES.put("protectColoredSpells", FSkinImage.IMG_ABILITY_PROTECT_COLOREDSPELLS); - MANA_IMAGES.put("protectG", FSkinImage.IMG_ABILITY_PROTECT_G); - MANA_IMAGES.put("protectGB", FSkinImage.IMG_ABILITY_PROTECT_GB); - MANA_IMAGES.put("protectGU", FSkinImage.IMG_ABILITY_PROTECT_GU); - MANA_IMAGES.put("protectGW", FSkinImage.IMG_ABILITY_PROTECT_GW); - MANA_IMAGES.put("protectGeneric", FSkinImage.IMG_ABILITY_PROTECT_GENERIC); - MANA_IMAGES.put("protectR", FSkinImage.IMG_ABILITY_PROTECT_R); - MANA_IMAGES.put("protectRB", FSkinImage.IMG_ABILITY_PROTECT_RB); - MANA_IMAGES.put("protectRG", FSkinImage.IMG_ABILITY_PROTECT_RG); - MANA_IMAGES.put("protectRU", FSkinImage.IMG_ABILITY_PROTECT_RU); - MANA_IMAGES.put("protectRW", FSkinImage.IMG_ABILITY_PROTECT_RW); - MANA_IMAGES.put("protectU", FSkinImage.IMG_ABILITY_PROTECT_U); - MANA_IMAGES.put("protectUW", FSkinImage.IMG_ABILITY_PROTECT_UW); - MANA_IMAGES.put("protectW", FSkinImage.IMG_ABILITY_PROTECT_W); + Forge.getAssets().manaImages().put("protectAll", FSkinImage.IMG_ABILITY_PROTECT_ALL); + Forge.getAssets().manaImages().put("protectB", FSkinImage.IMG_ABILITY_PROTECT_B); + Forge.getAssets().manaImages().put("protectBU", FSkinImage.IMG_ABILITY_PROTECT_BU); + Forge.getAssets().manaImages().put("protectBW", FSkinImage.IMG_ABILITY_PROTECT_BW); + Forge.getAssets().manaImages().put("protectColoredSpells", FSkinImage.IMG_ABILITY_PROTECT_COLOREDSPELLS); + Forge.getAssets().manaImages().put("protectG", FSkinImage.IMG_ABILITY_PROTECT_G); + Forge.getAssets().manaImages().put("protectGB", FSkinImage.IMG_ABILITY_PROTECT_GB); + Forge.getAssets().manaImages().put("protectGU", FSkinImage.IMG_ABILITY_PROTECT_GU); + Forge.getAssets().manaImages().put("protectGW", FSkinImage.IMG_ABILITY_PROTECT_GW); + Forge.getAssets().manaImages().put("protectGeneric", FSkinImage.IMG_ABILITY_PROTECT_GENERIC); + Forge.getAssets().manaImages().put("protectR", FSkinImage.IMG_ABILITY_PROTECT_R); + Forge.getAssets().manaImages().put("protectRB", FSkinImage.IMG_ABILITY_PROTECT_RB); + Forge.getAssets().manaImages().put("protectRG", FSkinImage.IMG_ABILITY_PROTECT_RG); + Forge.getAssets().manaImages().put("protectRU", FSkinImage.IMG_ABILITY_PROTECT_RU); + Forge.getAssets().manaImages().put("protectRW", FSkinImage.IMG_ABILITY_PROTECT_RW); + Forge.getAssets().manaImages().put("protectU", FSkinImage.IMG_ABILITY_PROTECT_U); + Forge.getAssets().manaImages().put("protectUW", FSkinImage.IMG_ABILITY_PROTECT_UW); + Forge.getAssets().manaImages().put("protectW", FSkinImage.IMG_ABILITY_PROTECT_W); } public static void drawManaCost(Graphics g, ManaCost manaCost, float x, float y, final float imageSize) { @@ -228,7 +226,7 @@ public class CardFaceSymbols { StringTokenizer tok = new StringTokenizer(s, " "); while (tok.hasMoreTokens()) { String symbol = tok.nextToken(); - FSkinImage image = MANA_IMAGES.get(symbol); + FSkinImage image = Forge.getAssets().manaImages().get(symbol); if (image == null) { BugReporter.reportBug("Symbol not recognized \"" + symbol + "\" in string: " + s); continue; @@ -245,7 +243,7 @@ public class CardFaceSymbols { } public static void drawSymbol(final String imageName, final Graphics g, final float x, final float y, final float w, final float h) { - g.drawImage(MANA_IMAGES.get(imageName), x, y, w, h); + g.drawImage(Forge.getAssets().manaImages().get(imageName), x, y, w, h); } public static float getWidth(final ManaCost manaCost, float imageSize) { diff --git a/forge-gui-mobile/src/forge/card/CardImageRenderer.java b/forge-gui-mobile/src/forge/card/CardImageRenderer.java index efdeab7f17c..499b049549c 100644 --- a/forge-gui-mobile/src/forge/card/CardImageRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardImageRenderer.java @@ -52,6 +52,7 @@ public class CardImageRenderer { prevImageWidth = 0; prevImageHeight = 0; forgeArt.clear(); + stretchedArt.clear(); } private static void updateStaticFields(float w, float h) { @@ -267,6 +268,7 @@ public class CardImageRenderer { } public static final FBufferedImage forgeArt; + private static final FBufferedImage stretchedArt; static { final float logoWidth = FSkinImage.LOGO.getWidth(); final float logoHeight = FSkinImage.LOGO.getHeight(); @@ -280,9 +282,25 @@ public class CardImageRenderer { g.drawImage(FSkinImage.LOGO, (w - logoWidth) / 2, (h - logoHeight) / 2, logoWidth, logoHeight); } }; + stretchedArt = new FBufferedImage(w, h) { + @Override + protected void draw(Graphics g, float w, float h) { + g.drawImage(FSkinTexture.BG_TEXTURE, 0, 0, w, h); + g.fillRect(FScreen.TEXTURE_OVERLAY_COLOR, 0, 0, w, h); + g.drawImage(FSkinImage.LOGO, (w - logoWidth) / 2, ((h - logoHeight) / 2)+h/3.5f, logoWidth, logoHeight/3); + } + }; } private static void drawArt(CardView cv, Graphics g, float x, float y, float w, float h, boolean altState, boolean isFaceDown) { + boolean isSaga = cv.getCurrentState().getType().hasSubtype("Saga"); + boolean isClass = cv.getCurrentState().getType().hasSubtype("Class"); + boolean isDungeon = cv.getCurrentState().getType().isDungeon(); + if (altState && cv.hasAlternateState()) { + isSaga = cv.getAlternateState().getType().hasSubtype("Saga"); + isClass = cv.getAlternateState().getType().hasSubtype("Class"); + isDungeon = cv.getAlternateState().getType().isDungeon(); + } if (cv == null) { if (isFaceDown) { Texture cardBack = ImageCache.getImage(ImageKeys.getTokenKey(ImageKeys.HIDDEN_CARD), false); @@ -292,7 +310,11 @@ public class CardImageRenderer { } } //fallback - g.drawImage(forgeArt, x, y, w, h); + if (isSaga || isClass || isDungeon) { + g.drawImage(stretchedArt, x, y, w, h); + } else { + g.drawImage(forgeArt, x, y, w, h); + } g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h); return; } @@ -303,7 +325,11 @@ public class CardImageRenderer { || cv.getCurrentState().getImageKey().equals(ImageKeys.getTokenKey(ImageKeys.FORETELL_IMAGE))); if (cardArt != null) { if (isHidden && !altState) { + if (isSaga || isClass || isDungeon) { + g.drawImage(stretchedArt, x, y, w, h); + } else { g.drawImage(forgeArt, x, y, w, h); + } } else if (cv.getCurrentState().getImageKey().equals(ImageKeys.getTokenKey(ImageKeys.MANIFEST_IMAGE)) && !altState) { altArt = CardRenderer.getAlternateCardArt(ImageKeys.getTokenKey(ImageKeys.MANIFEST_IMAGE), false); g.drawImage(altArt, x, y, w, h); @@ -330,10 +356,18 @@ public class CardImageRenderer { } } } else { - g.drawImage(forgeArt, x, y, w, h); + if (isSaga || isClass || isDungeon) { + g.drawImage(stretchedArt, x, y, w, h); + } else { + g.drawImage(forgeArt, x, y, w, h); + } } } else { - g.drawImage(forgeArt, x, y, w, h); + if (isSaga || isClass || isDungeon) { + g.drawImage(stretchedArt, x, y, w, h); + } else { + g.drawImage(forgeArt, x, y, w, h); + } } g.drawRect(BORDER_THICKNESS, Color.BLACK, x, y, w, h); } diff --git a/forge-gui-mobile/src/forge/card/CardRenderer.java b/forge-gui-mobile/src/forge/card/CardRenderer.java index a9d53f9f142..56de9c455fd 100644 --- a/forge-gui-mobile/src/forge/card/CardRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardRenderer.java @@ -67,28 +67,28 @@ public class CardRenderer { // class that simplifies the callback logic of CachedCardImage static class RendererCachedCardImage extends CachedCardImage { - boolean clearCardArtCache = false; + boolean clearcardArtCache = false; public RendererCachedCardImage(CardView card, boolean clearArtCache) { super(card); - this.clearCardArtCache = clearArtCache; + this.clearcardArtCache = clearArtCache; } public RendererCachedCardImage(InventoryItem ii, boolean clearArtCache) { super(ii); - this.clearCardArtCache = clearArtCache; + this.clearcardArtCache = clearArtCache; } public RendererCachedCardImage(String key, boolean clearArtCache) { super(key); - this.clearCardArtCache = clearArtCache; + this.clearcardArtCache = clearArtCache; } @Override public void onImageFetched() { ImageCache.clear(); - if (clearCardArtCache) { - cardArtCache.remove(key); + if (clearcardArtCache) { + Forge.getAssets().cardArtCache().remove(key); } } } @@ -104,8 +104,6 @@ public class CardRenderer { private static final float BORDER_THICKNESS = Utils.scale(1); public static final float PADDING_MULTIPLIER = 0.021f; public static final float CROP_MULTIPLIER = 0.96f; - - private static Map counterFonts = new HashMap<>(); private static final Color counterBackgroundColor = new Color(0f, 0f, 0f, 0.9f); private static final Map counterColorCache = new HashMap<>(); private static final GlyphLayout layout = new GlyphLayout(); @@ -191,13 +189,12 @@ public class CardRenderer { return Math.round(MANA_SYMBOL_SIZE + FSkinFont.get(12).getLineHeight() + 3 * FList.PADDING + 1); } - private static final Map cardArtCache = new HashMap<>(1024); public static final float CARD_ART_RATIO = 1.302f; public static final float CARD_ART_HEIGHT_PERCENTAGE = 0.43f; private static List classicModuleCardtoCrop = FileUtil.readFile(ForgeConstants.CLASSIC_MODULE_CARD_TO_CROP_FILE); public static void clearcardArtCache(){ - cardArtCache.clear(); + Forge.getAssets().cardArtCache().clear(); } //extract card art from the given card @@ -221,7 +218,7 @@ public class CardRenderer { } public static FImageComplex getCardArt(String imageKey, boolean isSplitCard, boolean isHorizontalCard, boolean isAftermathCard, boolean isSaga, boolean isClass, boolean isDungeon, boolean isFlipCard, boolean isPlanesWalker, boolean isModernFrame) { - FImageComplex cardArt = cardArtCache.get(imageKey); + FImageComplex cardArt = Forge.getAssets().cardArtCache().get(imageKey); boolean isClassicModule = imageKey != null && imageKey.length() > 2 && classicModuleCardtoCrop.contains(imageKey.substring(ImageKeys.CARD_PREFIX.length()).replace(".jpg", "").replace(".png", "")); if (cardArt == null) { Texture image = new RendererCachedCardImage(imageKey, true).getImage(); @@ -282,7 +279,7 @@ public class CardRenderer { w *= artH / srcH; h *= artW / srcW; cardArt = new FRotatedImage(image, Math.round(x), Math.round(y), Math.round(w), Math.round(h), true); - cardArtCache.put(imageKey, cardArt); + Forge.getAssets().cardArtCache().put(imageKey, cardArt); return cardArt; } } else { @@ -305,7 +302,7 @@ public class CardRenderer { cardArt = new FTextureRegionImage(new TextureRegion(image, Math.round(x), Math.round(y), Math.round(w), Math.round(h))); } if (!CardImageRenderer.forgeArt.equals(cardArt)) - cardArtCache.put(imageKey, cardArt); + Forge.getAssets().cardArtCache().put(imageKey, cardArt); } } //fix display for effect @@ -315,13 +312,13 @@ public class CardRenderer { } public static FImageComplex getAftermathSecondCardArt(final String imageKey) { - FImageComplex cardArt = cardArtCache.get("Aftermath_second_"+imageKey); + FImageComplex cardArt = Forge.getAssets().cardArtCache().get("Aftermath_second_"+imageKey); if (cardArt == null) { Texture image = new CachedCardImage(imageKey) { @Override public void onImageFetched() { ImageCache.clear(); - cardArtCache.remove("Aftermath_second_" + imageKey); + Forge.getAssets().cardArtCache().remove("Aftermath_second_" + imageKey); } }.getImage(); if (image != null) { @@ -341,20 +338,20 @@ public class CardRenderer { } if (!CardImageRenderer.forgeArt.equals(cardArt)) - cardArtCache.put("Aftermath_second_"+imageKey, cardArt); + Forge.getAssets().cardArtCache().put("Aftermath_second_"+imageKey, cardArt); } } return cardArt; } public static FImageComplex getAlternateCardArt(final String imageKey, boolean isPlanesWalker) { - FImageComplex cardArt = cardArtCache.get("Alternate_"+imageKey); + FImageComplex cardArt = Forge.getAssets().cardArtCache().get("Alternate_"+imageKey); if (cardArt == null) { Texture image = new CachedCardImage(imageKey) { @Override public void onImageFetched() { ImageCache.clear(); - cardArtCache.remove("Alternate_" + imageKey); + Forge.getAssets().cardArtCache().remove("Alternate_" + imageKey); } }.getImage(); if (image != null) { @@ -388,7 +385,7 @@ public class CardRenderer { cardArt = new FTextureRegionImage(new TextureRegion(image, Math.round(x), Math.round(y), Math.round(w), Math.round(h))); } if (!CardImageRenderer.forgeArt.equals(cardArt)) - cardArtCache.put("Alternate_"+imageKey, cardArt); + Forge.getAssets().cardArtCache().put("Alternate_"+imageKey, cardArt); } } return cardArt; @@ -397,9 +394,9 @@ public class CardRenderer { public static FImageComplex getMeldCardParts(final String imageKey, boolean bottom) { FImageComplex cardArt; if (!bottom) { - cardArt = cardArtCache.get("Meld_primary_"+imageKey); + cardArt = Forge.getAssets().cardArtCache().get("Meld_primary_"+imageKey); } else { - cardArt = cardArtCache.get("Meld_secondary_"+imageKey); + cardArt = Forge.getAssets().cardArtCache().get("Meld_secondary_"+imageKey); } if (cardArt == null) { @@ -407,8 +404,8 @@ public class CardRenderer { @Override public void onImageFetched() { ImageCache.clear(); - cardArtCache.remove("Meld_primary_" + imageKey); - cardArtCache.remove("Meld_secondary_" + imageKey); + Forge.getAssets().cardArtCache().remove("Meld_primary_" + imageKey); + Forge.getAssets().cardArtCache().remove("Meld_secondary_" + imageKey); } }.getImage(); if (image != null) { @@ -423,9 +420,9 @@ public class CardRenderer { } if (!bottom && !CardImageRenderer.forgeArt.equals(cardArt)) - cardArtCache.put("Meld_primary_"+imageKey, cardArt); + Forge.getAssets().cardArtCache().put("Meld_primary_"+imageKey, cardArt); else if (!CardImageRenderer.forgeArt.equals(cardArt)) - cardArtCache.put("Meld_secondary_"+imageKey, cardArt); + Forge.getAssets().cardArtCache().put("Meld_secondary_"+imageKey, cardArt); } } return cardArt; @@ -1101,7 +1098,7 @@ public class CardRenderer { private static void drawCounterTabs(final CardView card, final Graphics g, final float x, final float y, final float w, final float h) { int fontSize = Math.max(11, Math.min(22, (int) (h * 0.08))); - BitmapFont font = counterFonts.get(fontSize); + BitmapFont font = Forge.getAssets().counterFonts().get(fontSize); final float additionalXOffset = 3f * ((fontSize - 11) / 11f); final float variableWidth = ((fontSize - 11) / 11f) * 44f; @@ -1218,7 +1215,7 @@ public class CardRenderer { private static void drawMarkersTabs(final List markers, final Graphics g, final float x, final float y, final float w, final float h, boolean larger) { int fontSize = larger ? Math.max(9, Math.min(22, (int) (h * 0.08))) : Math.max(8, Math.min(22, (int) (h * 0.05))); - BitmapFont font = counterFonts.get(fontSize); + BitmapFont font = Forge.getAssets().counterFonts().get(fontSize); final float additionalXOffset = 3f * ((fontSize - 8) / 8f); @@ -1410,7 +1407,7 @@ public class CardRenderer { textureRegions.add(new TextureRegion(texture)); } - counterFonts.put(fontSize, new BitmapFont(fontData, textureRegions, true)); + Forge.getAssets().counterFonts().put(fontSize, new BitmapFont(fontData, textureRegions, true)); generator.dispose(); packer.dispose(); diff --git a/forge-gui-mobile/src/forge/deck/FDeckChooser.java b/forge-gui-mobile/src/forge/deck/FDeckChooser.java index 90a7b41ba43..dea7c235863 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckChooser.java +++ b/forge-gui-mobile/src/forge/deck/FDeckChooser.java @@ -131,75 +131,59 @@ public class FDeckChooser extends FScreen { lstDecks = new DeckManager(gameType0); isAi = isAi0; - lstDecks.setItemActivateHandler(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - if (lstDecks.getGameType() == GameType.DeckManager) { - //for Deck Editor, edit deck instead of accepting - editSelectedDeck(); - return; - } - accept(); - } - }); - btnNewDeck.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - createNewDeck(); - } - }); - btnEditDeck.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { + lstDecks.setItemActivateHandler(event -> { + if (lstDecks.getGameType() == GameType.DeckManager) { + //for Deck Editor, edit deck instead of accepting editSelectedDeck(); + return; + } + accept(); + }); + btnNewDeck.setCommand(event -> createNewDeck()); + btnEditDeck.setCommand(event -> editSelectedDeck()); + btnViewDeck.setCommand(event -> { + if (selectedDeckType != DeckType.STANDARD_COLOR_DECK && selectedDeckType != DeckType.STANDARD_CARDGEN_DECK + && selectedDeckType != DeckType.PIONEER_CARDGEN_DECK && selectedDeckType != DeckType.HISTORIC_CARDGEN_DECK + && selectedDeckType != DeckType.MODERN_CARDGEN_DECK && selectedDeckType != DeckType.LEGACY_CARDGEN_DECK + && selectedDeckType != DeckType.VINTAGE_CARDGEN_DECK && selectedDeckType != DeckType.MODERN_COLOR_DECK && + selectedDeckType != DeckType.COLOR_DECK && selectedDeckType != DeckType.THEME_DECK + && selectedDeckType != DeckType.RANDOM_COMMANDER_DECK && selectedDeckType != DeckType.RANDOM_CARDGEN_COMMANDER_DECK) { + FDeckViewer.show(getDeck(), false, DeckType.DRAFT_DECK.equals(selectedDeckType)); } }); - btnViewDeck.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - if (selectedDeckType != DeckType.STANDARD_COLOR_DECK && selectedDeckType != DeckType.STANDARD_CARDGEN_DECK - && selectedDeckType != DeckType.PIONEER_CARDGEN_DECK && selectedDeckType != DeckType.HISTORIC_CARDGEN_DECK - && selectedDeckType != DeckType.MODERN_CARDGEN_DECK && selectedDeckType != DeckType.LEGACY_CARDGEN_DECK - && selectedDeckType != DeckType.VINTAGE_CARDGEN_DECK && selectedDeckType != DeckType.MODERN_COLOR_DECK && - selectedDeckType != DeckType.COLOR_DECK && selectedDeckType != DeckType.THEME_DECK - && selectedDeckType != DeckType.RANDOM_COMMANDER_DECK && selectedDeckType != DeckType.RANDOM_CARDGEN_COMMANDER_DECK) { - FDeckViewer.show(getDeck(), false, DeckType.DRAFT_DECK.equals(selectedDeckType)); - } + btnRandom.setCommand(event -> { + if (lstDecks.getGameType() == GameType.DeckManager) { + //for Deck Editor, test deck instead of randomly selecting deck + testSelectedDeck(); + return; } - }); - btnRandom.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - if (lstDecks.getGameType() == GameType.DeckManager) { - //for Deck Editor, test deck instead of randomly selecting deck - testSelectedDeck(); - return; - } - if (selectedDeckType == DeckType.COLOR_DECK || selectedDeckType == DeckType.STANDARD_COLOR_DECK - || selectedDeckType == DeckType.MODERN_COLOR_DECK) { - DeckgenUtil.randomSelectColors(lstDecks); - } - else if (selectedDeckType == DeckType.STANDARD_CARDGEN_DECK){ - DeckgenUtil.randomSelect(lstDecks); - } - else if (selectedDeckType == DeckType.PIONEER_CARDGEN_DECK){ - DeckgenUtil.randomSelect(lstDecks); - } - else if (selectedDeckType == DeckType.HISTORIC_CARDGEN_DECK){ - DeckgenUtil.randomSelect(lstDecks); - } - else if (selectedDeckType == DeckType.MODERN_CARDGEN_DECK){ - DeckgenUtil.randomSelect(lstDecks); - } - else if (selectedDeckType == DeckType.LEGACY_CARDGEN_DECK){ - DeckgenUtil.randomSelect(lstDecks); - } - else if (selectedDeckType == DeckType.VINTAGE_CARDGEN_DECK){ - DeckgenUtil.randomSelect(lstDecks); - } - else { - int size = 0; + if (selectedDeckType == DeckType.COLOR_DECK || selectedDeckType == DeckType.STANDARD_COLOR_DECK + || selectedDeckType == DeckType.MODERN_COLOR_DECK) { + DeckgenUtil.randomSelectColors(lstDecks); + } + else if (selectedDeckType == DeckType.STANDARD_CARDGEN_DECK){ + DeckgenUtil.randomSelect(lstDecks); + } + else if (selectedDeckType == DeckType.PIONEER_CARDGEN_DECK){ + DeckgenUtil.randomSelect(lstDecks); + } + else if (selectedDeckType == DeckType.HISTORIC_CARDGEN_DECK){ + DeckgenUtil.randomSelect(lstDecks); + } + else if (selectedDeckType == DeckType.MODERN_CARDGEN_DECK){ + DeckgenUtil.randomSelect(lstDecks); + } + else if (selectedDeckType == DeckType.LEGACY_CARDGEN_DECK){ + DeckgenUtil.randomSelect(lstDecks); + } + else if (selectedDeckType == DeckType.VINTAGE_CARDGEN_DECK){ + DeckgenUtil.randomSelect(lstDecks); + } + else { + int size = 0; + try { if (isAi && !isGeneratedDeck(selectedDeckType) && Forge.autoAIDeckSelection) { + btnRandom.setEnabled(false); AIDecks = lstDecks.getPool().toFlatList().parallelStream().filter(deckProxy -> deckProxy.getAI().inMainDeck == 0).collect(Collectors.toList()); size = AIDecks.size(); } @@ -207,9 +191,12 @@ public class FDeckChooser extends FScreen { lstDecks.setSelectedItem(AIDecks.get(MyRandom.getRandom().nextInt(size))); else DeckgenUtil.randomSelect(lstDecks); + } catch (Exception ee) { + DeckgenUtil.randomSelect(lstDecks); } - accept(); } + btnRandom.setEnabled(true); + accept(); }); switch (lstDecks.getGameType()) { case Constructed: @@ -371,32 +358,29 @@ public class FDeckChooser extends FScreen { } else { editor = new FDeckEditor(getEditorType(), "", false); } - editor.setSaveHandler(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - //ensure user returns to proper deck type and that list is refreshed if new deck is saved - if (!needRefreshOnActivate) { - needRefreshOnActivate = true; - if (lstDecks.getGameType() == GameType.DeckManager) { - switch (selectedDeckType) { - case COMMANDER_DECK: - case OATHBREAKER_DECK: - case TINY_LEADERS_DECK: - case BRAWL_DECK: - case SCHEME_DECK: - case PLANAR_DECK: - case DRAFT_DECK: - case SEALED_DECK: - break; - default: - setSelectedDeckType(DeckType.CONSTRUCTED_DECK); - break; - } - } - else { - setSelectedDeckType(DeckType.CUSTOM_DECK); + editor.setSaveHandler(event -> { + //ensure user returns to proper deck type and that list is refreshed if new deck is saved + if (!needRefreshOnActivate) { + needRefreshOnActivate = true; + if (lstDecks.getGameType() == GameType.DeckManager) { + switch (selectedDeckType) { + case COMMANDER_DECK: + case OATHBREAKER_DECK: + case TINY_LEADERS_DECK: + case BRAWL_DECK: + case SCHEME_DECK: + case PLANAR_DECK: + case DRAFT_DECK: + case SEALED_DECK: + break; + default: + setSelectedDeckType(DeckType.CONSTRUCTED_DECK); + break; } } + else { + setSelectedDeckType(DeckType.CUSTOM_DECK); + } } }); Forge.openScreen(editor); @@ -624,249 +608,205 @@ public class FDeckChooser extends FScreen { cmbDeckTypes.setAlignment(Align.center); restoreSavedState(); - cmbDeckTypes.setChangedHandler(new FEventHandler() { - @Override + cmbDeckTypes.setChangedHandler(event -> { + final DeckType deckType = cmbDeckTypes.getSelectedItem(); - public void handleEvent(final FEvent e) { - final DeckType deckType = cmbDeckTypes.getSelectedItem(); + if (!refreshingDeckType&&(deckType == DeckType.NET_DECK || deckType == DeckType.NET_COMMANDER_DECK)) { + //needed for loading net decks + FThreads.invokeInBackgroundThread(() -> { + GameType gameType = lstDecks.getGameType(); + if (gameType == GameType.DeckManager) { + gameType = deckType == DeckType.NET_COMMANDER_DECK ? GameType.Commander : GameType.Constructed; + } + final NetDeckCategory category = NetDeckCategory.selectAndLoad(gameType); - if (!refreshingDeckType&&(deckType == DeckType.NET_DECK || deckType == DeckType.NET_COMMANDER_DECK)) { - FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks - @Override - public void run() { - GameType gameType = lstDecks.getGameType(); - if (gameType == GameType.DeckManager) { - gameType = deckType == DeckType.NET_COMMANDER_DECK ? GameType.Commander : GameType.Constructed; + FThreads.invokeInEdtLater(() -> { + if (category == null) { + cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels + if (selectedDeckType == deckType && netDeckCategory != null) { + cmbDeckTypes.setText(netDeckCategory.getDeckType()); } - final NetDeckCategory category = NetDeckCategory.selectAndLoad(gameType); - - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (category == null) { - cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels - if (selectedDeckType == deckType && netDeckCategory != null) { - cmbDeckTypes.setText(netDeckCategory.getDeckType()); - } - return; - } - - netDeckCategory = category; - refreshDecksList(deckType, true, e); - } - }); + return; } + + netDeckCategory = category; + refreshDecksList(deckType, true, event); }); - return; - } - if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_STANDARD_DECK)) { - FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks - @Override - public void run() { - GameType gameType = lstDecks.getGameType(); - if (gameType == GameType.DeckManager) { - gameType = GameType.Constructed; - } - final NetDeckArchiveStandard category = NetDeckArchiveStandard.selectAndLoad(gameType); - - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (category == null) { - cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels - if (selectedDeckType == deckType && NetDeckArchiveStandard != null) { - cmbDeckTypes.setText(NetDeckArchiveStandard.getDeckType()); - } - return; - } - - NetDeckArchiveStandard = category; - refreshDecksList(deckType, true, e); - } - }); - } - }); - return; - } - if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_PIONEER_DECK)) { - FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks - @Override - public void run() { - GameType gameType = lstDecks.getGameType(); - if (gameType == GameType.DeckManager) { - gameType = GameType.Constructed; - } - final NetDeckArchivePioneer category = NetDeckArchivePioneer.selectAndLoad(gameType); - - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (category == null) { - cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels - if (selectedDeckType == deckType && NetDeckArchivePioneer != null) { - cmbDeckTypes.setText(NetDeckArchivePioneer.getDeckType()); - } - return; - } - - NetDeckArchivePioneer = category; - refreshDecksList(deckType, true, e); - } - }); - } - }); - return; - } - if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_MODERN_DECK)) { - FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks - @Override - public void run() { - GameType gameType = lstDecks.getGameType(); - if (gameType == GameType.DeckManager) { - gameType = GameType.Constructed; - } - final NetDeckArchiveModern category = NetDeckArchiveModern.selectAndLoad(gameType); - - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (category == null) { - cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels - if (selectedDeckType == deckType && NetDeckArchiveModern != null) { - cmbDeckTypes.setText(NetDeckArchiveModern.getDeckType()); - } - return; - } - - NetDeckArchiveModern = category; - refreshDecksList(deckType, true, e); - } - }); - } - }); - return; - } - if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_PAUPER_DECK)) { - FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks - @Override - public void run() { - GameType gameType = lstDecks.getGameType(); - if (gameType == GameType.DeckManager) { - gameType = GameType.Constructed; - } - final NetDeckArchivePauper category = NetDeckArchivePauper.selectAndLoad(gameType); - - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (category == null) { - cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels - if (selectedDeckType == deckType && NetDeckArchivePauper != null) { - cmbDeckTypes.setText(NetDeckArchivePauper.getDeckType()); - } - return; - } - - NetDeckArchivePauper = category; - refreshDecksList(deckType, true, e); - } - }); - } - }); - return; - } - if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_LEGACY_DECK)) { - FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks - @Override - public void run() { - GameType gameType = lstDecks.getGameType(); - if (gameType == GameType.DeckManager) { - gameType = GameType.Constructed; - } - final NetDeckArchiveLegacy category = NetDeckArchiveLegacy.selectAndLoad(gameType); - - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (category == null) { - cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels - if (selectedDeckType == deckType && NetDeckArchiveLegacy != null) { - cmbDeckTypes.setText(NetDeckArchiveLegacy.getDeckType()); - } - return; - } - - NetDeckArchiveLegacy = category; - refreshDecksList(deckType, true, e); - } - }); - } - }); - return; - } - if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_VINTAGE_DECK)) { - FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks - @Override - public void run() { - GameType gameType = lstDecks.getGameType(); - if (gameType == GameType.DeckManager) { - gameType = GameType.Constructed; - } - final NetDeckArchiveVintage category = NetDeckArchiveVintage.selectAndLoad(gameType); - - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (category == null) { - cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels - if (selectedDeckType == deckType && NetDeckArchiveVintage != null) { - cmbDeckTypes.setText(NetDeckArchiveVintage.getDeckType()); - } - return; - } - - NetDeckArchiveVintage = category; - refreshDecksList(deckType, true, e); - } - }); - } - }); - return; - } - if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_BLOCK_DECK)) { - FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks - @Override - public void run() { - GameType gameType = lstDecks.getGameType(); - if (gameType == GameType.DeckManager) { - gameType = GameType.Constructed; - } - final NetDeckArchiveBlock category = NetDeckArchiveBlock.selectAndLoad(gameType); - - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (category == null) { - cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels - if (selectedDeckType == deckType && NetDeckArchiveBlock != null) { - cmbDeckTypes.setText(NetDeckArchiveBlock.getDeckType()); - } - return; - } - - NetDeckArchiveBlock = category; - refreshDecksList(deckType, true, e); - } - }); - } - }); - return; - } - - - - refreshDecksList(deckType, false, e); + }); + return; } + if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_STANDARD_DECK)) { + //needed for loading net decks + FThreads.invokeInBackgroundThread(() -> { + GameType gameType = lstDecks.getGameType(); + if (gameType == GameType.DeckManager) { + gameType = GameType.Constructed; + } + final NetDeckArchiveStandard category = NetDeckArchiveStandard.selectAndLoad(gameType); + + FThreads.invokeInEdtLater(() -> { + if (category == null) { + cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels + if (selectedDeckType == deckType && NetDeckArchiveStandard != null) { + cmbDeckTypes.setText(NetDeckArchiveStandard.getDeckType()); + } + return; + } + + NetDeckArchiveStandard = category; + refreshDecksList(deckType, true, event); + }); + }); + return; + } + if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_PIONEER_DECK)) { + //needed for loading net decks + FThreads.invokeInBackgroundThread(() -> { + GameType gameType = lstDecks.getGameType(); + if (gameType == GameType.DeckManager) { + gameType = GameType.Constructed; + } + final NetDeckArchivePioneer category = NetDeckArchivePioneer.selectAndLoad(gameType); + + FThreads.invokeInEdtLater(() -> { + if (category == null) { + cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels + if (selectedDeckType == deckType && NetDeckArchivePioneer != null) { + cmbDeckTypes.setText(NetDeckArchivePioneer.getDeckType()); + } + return; + } + + NetDeckArchivePioneer = category; + refreshDecksList(deckType, true, event); + }); + }); + return; + } + if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_MODERN_DECK)) { + //needed for loading net decks + FThreads.invokeInBackgroundThread(() -> { + GameType gameType = lstDecks.getGameType(); + if (gameType == GameType.DeckManager) { + gameType = GameType.Constructed; + } + final NetDeckArchiveModern category = NetDeckArchiveModern.selectAndLoad(gameType); + + FThreads.invokeInEdtLater(() -> { + if (category == null) { + cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels + if (selectedDeckType == deckType && NetDeckArchiveModern != null) { + cmbDeckTypes.setText(NetDeckArchiveModern.getDeckType()); + } + return; + } + + NetDeckArchiveModern = category; + refreshDecksList(deckType, true, event); + }); + }); + return; + } + if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_PAUPER_DECK)) { + //needed for loading net decks + FThreads.invokeInBackgroundThread(() -> { + GameType gameType = lstDecks.getGameType(); + if (gameType == GameType.DeckManager) { + gameType = GameType.Constructed; + } + final NetDeckArchivePauper category = NetDeckArchivePauper.selectAndLoad(gameType); + + FThreads.invokeInEdtLater(() -> { + if (category == null) { + cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels + if (selectedDeckType == deckType && NetDeckArchivePauper != null) { + cmbDeckTypes.setText(NetDeckArchivePauper.getDeckType()); + } + return; + } + + NetDeckArchivePauper = category; + refreshDecksList(deckType, true, event); + }); + }); + return; + } + if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_LEGACY_DECK)) { + //needed for loading net decks + FThreads.invokeInBackgroundThread(() -> { + GameType gameType = lstDecks.getGameType(); + if (gameType == GameType.DeckManager) { + gameType = GameType.Constructed; + } + final NetDeckArchiveLegacy category = NetDeckArchiveLegacy.selectAndLoad(gameType); + + FThreads.invokeInEdtLater(() -> { + if (category == null) { + cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels + if (selectedDeckType == deckType && NetDeckArchiveLegacy != null) { + cmbDeckTypes.setText(NetDeckArchiveLegacy.getDeckType()); + } + return; + } + + NetDeckArchiveLegacy = category; + refreshDecksList(deckType, true, event); + }); + }); + return; + } + if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_VINTAGE_DECK)) { + //needed for loading net decks + FThreads.invokeInBackgroundThread(() -> { + GameType gameType = lstDecks.getGameType(); + if (gameType == GameType.DeckManager) { + gameType = GameType.Constructed; + } + final NetDeckArchiveVintage category = NetDeckArchiveVintage.selectAndLoad(gameType); + + FThreads.invokeInEdtLater(() -> { + if (category == null) { + cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels + if (selectedDeckType == deckType && NetDeckArchiveVintage != null) { + cmbDeckTypes.setText(NetDeckArchiveVintage.getDeckType()); + } + return; + } + + NetDeckArchiveVintage = category; + refreshDecksList(deckType, true, event); + }); + }); + return; + } + if (!refreshingDeckType&&(deckType == DeckType.NET_ARCHIVE_BLOCK_DECK)) { + //needed for loading net decks + FThreads.invokeInBackgroundThread(() -> { + GameType gameType = lstDecks.getGameType(); + if (gameType == GameType.DeckManager) { + gameType = GameType.Constructed; + } + final NetDeckArchiveBlock category = NetDeckArchiveBlock.selectAndLoad(gameType); + + FThreads.invokeInEdtLater(() -> { + if (category == null) { + cmbDeckTypes.setSelectedItem(selectedDeckType); //restore old selection if user cancels + if (selectedDeckType == deckType && NetDeckArchiveBlock != null) { + cmbDeckTypes.setText(NetDeckArchiveBlock.getDeckType()); + } + return; + } + + NetDeckArchiveBlock = category; + refreshDecksList(deckType, true, event); + }); + }); + return; + } + + + + refreshDecksList(deckType, false, event); }); add(cmbDeckTypes); add(lstDecks); @@ -888,11 +828,11 @@ public class FDeckChooser extends FScreen { saveState(); } - private void refreshDecksList(DeckType deckType, boolean forceRefresh, FEvent e) { + private void refreshDecksList(DeckType deckType, boolean forceRefresh, FEvent event) { if (selectedDeckType == deckType && !forceRefresh) { return; } selectedDeckType = deckType; - if (e == null) { + if (event == null) { refreshingDeckType = true; cmbDeckTypes.setSelectedItem(deckType); refreshingDeckType = false; @@ -1186,7 +1126,7 @@ public class FDeckChooser extends FScreen { btnRandom.setLeft(getWidth() - PADDING - btnRandom.getWidth()); - if (e != null) { //set default list selection if from combo box change event + if (event != null) { //set default list selection if from combo box change event if (deckType == DeckType.COLOR_DECK) { // default selection = basic two color deck lstDecks.setSelectedIndices(new Integer[]{0, 1}); @@ -1510,36 +1450,26 @@ public class FDeckChooser extends FScreen { return; } - FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks - @Override - public void run() { - final NetDeckCategory netCat; - if (allowedDeckTypes.contains(DeckType.NET_DECK)) { - netCat = NetDeckCategory.selectAndLoad(GameType.Constructed); - } else { - netCat = null; - } - - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), new Runnable() { - @Override - public void run() { - GauntletData gauntlet = GauntletUtil.createQuickGauntlet(userDeck, numOpponents, allowedDeckTypes, netCat); - FModel.setGauntletData(gauntlet); - - List players = new ArrayList<>(); - RegisteredPlayer humanPlayer = new RegisteredPlayer(userDeck).setPlayer(GamePlayerUtil.getGuiPlayer()); - players.add(humanPlayer); - players.add(new RegisteredPlayer(gauntlet.getDecks().get(gauntlet.getCompleted())).setPlayer(GamePlayerUtil.createAiPlayer())); - - gauntlet.startRound(players, humanPlayer); - } - }); - } - }); + //needed for loading net decks + FThreads.invokeInBackgroundThread(() -> { + final NetDeckCategory netCat; + if (allowedDeckTypes.contains(DeckType.NET_DECK)) { + netCat = NetDeckCategory.selectAndLoad(GameType.Constructed); + } else { + netCat = null; } + + FThreads.invokeInEdtLater(() -> LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), true, () -> { + GauntletData gauntlet = GauntletUtil.createQuickGauntlet(userDeck, numOpponents, allowedDeckTypes, netCat); + FModel.setGauntletData(gauntlet); + + List players = new ArrayList<>(); + RegisteredPlayer humanPlayer = new RegisteredPlayer(userDeck).setPlayer(GamePlayerUtil.getGuiPlayer()); + players.add(humanPlayer); + players.add(new RegisteredPlayer(gauntlet.getDecks().get(gauntlet.getCompleted())).setPlayer(GamePlayerUtil.createAiPlayer())); + + gauntlet.startRound(players, humanPlayer); + })); }); } }); @@ -1554,26 +1484,23 @@ public class FDeckChooser extends FScreen { public void run(final Deck aiDeck) { if (aiDeck == null) { return; } - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), new Runnable() { - @Override - public void run() { - Set appliedVariants = new HashSet<>(); - appliedVariants.add(variant); + LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), true, () -> { + Set appliedVariants = new HashSet<>(); + appliedVariants.add(variant); - List players = new ArrayList<>(); - RegisteredPlayer humanPlayer = RegisteredPlayer.forVariants(2, appliedVariants, userDeck, null, false, null, null); - humanPlayer.setPlayer(GamePlayerUtil.getGuiPlayer()); - RegisteredPlayer aiPlayer = RegisteredPlayer.forVariants(2, appliedVariants, aiDeck, null, false, null, null); - aiPlayer.setPlayer(GamePlayerUtil.createAiPlayer()); - players.add(humanPlayer); - players.add(aiPlayer); + List players = new ArrayList<>(); + RegisteredPlayer humanPlayer = RegisteredPlayer.forVariants(2, appliedVariants, userDeck, null, false, null, null); + humanPlayer.setPlayer(GamePlayerUtil.getGuiPlayer()); + RegisteredPlayer aiPlayer = RegisteredPlayer.forVariants(2, appliedVariants, aiDeck, null, false, null, null); + aiPlayer.setPlayer(GamePlayerUtil.createAiPlayer()); + players.add(humanPlayer); + players.add(aiPlayer); - final Map guiMap = new HashMap<>(); - guiMap.put(humanPlayer, MatchController.instance); + final Map guiMap = new HashMap<>(); + guiMap.put(humanPlayer, MatchController.instance); - final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch(); - hostedMatch.startMatch(GameType.Constructed, appliedVariants, players, guiMap); - } + final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch(); + hostedMatch.startMatch(GameType.Constructed, appliedVariants, players, guiMap); }); } }); diff --git a/forge-gui-mobile/src/forge/screens/LoadingOverlay.java b/forge-gui-mobile/src/forge/screens/LoadingOverlay.java index 4fd299a2c4d..2ccda45df86 100644 --- a/forge-gui-mobile/src/forge/screens/LoadingOverlay.java +++ b/forge-gui-mobile/src/forge/screens/LoadingOverlay.java @@ -22,39 +22,25 @@ public class LoadingOverlay extends FOverlay { private static final FSkinFont FONT = FSkinFont.get(22); private static final FSkinColor BACK_COLOR = FSkinColor.get(Colors.CLR_ACTIVE).alphaColor(0.75f); private static final FSkinColor FORE_COLOR = FSkinColor.get(Colors.CLR_TEXT); - public static void show(String caption0, final Runnable runnable) { - final LoadingOverlay loader = new LoadingOverlay(caption0); + show(caption0, false, runnable); + } + public static void show(String caption0, boolean textMode, final Runnable runnable) { + final LoadingOverlay loader = new LoadingOverlay(caption0, textMode); loader.show(); //show loading overlay then delay running remaining logic so UI can respond - ThreadUtil.invokeInGameThread(new Runnable() { - @Override - public void run() { - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - runnable.run(); - loader.hide(); - loader.finishedloading(); //setLoadingaMatch to false - } - }); - } - }); + ThreadUtil.invokeInGameThread(() -> FThreads.invokeInEdtLater(() -> { + runnable.run(); + loader.hide(); + loader.finishedloading(); //setLoadingaMatch to false + })); } public static void runBackgroundTask(String caption0, final Runnable task) { - final LoadingOverlay loader = new LoadingOverlay(caption0); + final LoadingOverlay loader = new LoadingOverlay(caption0, true); loader.show(); - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { - task.run(); - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - loader.hide(); - } - }); - } + FThreads.invokeInBackgroundThread(() -> { + task.run(); + FThreads.invokeInEdtLater(() -> loader.hide()); }); } @@ -63,6 +49,7 @@ public class LoadingOverlay extends FOverlay { public LoadingOverlay(String caption0) { caption = caption0; + textMode = false; } public LoadingOverlay(String caption0, boolean textOnly) { diff --git a/forge-gui-mobile/src/forge/screens/SplashScreen.java b/forge-gui-mobile/src/forge/screens/SplashScreen.java index ed80eceb8b6..504530999b8 100644 --- a/forge-gui-mobile/src/forge/screens/SplashScreen.java +++ b/forge-gui-mobile/src/forge/screens/SplashScreen.java @@ -169,7 +169,7 @@ public class SplashScreen extends FContainer { } void drawTransition(Graphics g, boolean openAdventure, float percentage) { - TextureRegion tr = new TextureRegion(Forge.getTitleBG()); + TextureRegion tr = new TextureRegion(Forge.getAssets().fallback_skins().get(0)); if (!Forge.isLandscapeMode() && tr != null) { float ar = 1.78f; int w = (int) (tr.getRegionHeight() / ar); @@ -307,7 +307,7 @@ public class SplashScreen extends FContainer { + "Forge is open source software, released under the GNU General Public License."; if (Forge.forcedEnglishonCJKMissing && !clear) { clear = true; - FSkinFont.clear(); + FSkinFont.preloadAll(""); disclaimerFont = FSkinFont.get(9); } g.drawText(disclaimer, disclaimerFont, FProgressBar.SEL_FORE_COLOR, diff --git a/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java b/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java index 098ef9f2b04..216b4c46463 100644 --- a/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java +++ b/forge-gui-mobile/src/forge/screens/constructed/LobbyScreen.java @@ -44,8 +44,6 @@ import forge.screens.LoadingOverlay; import forge.screens.settings.SettingsScreen; import forge.toolbox.FCheckBox; import forge.toolbox.FComboBox; -import forge.toolbox.FEvent; -import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FLabel; import forge.toolbox.FList; import forge.toolbox.FOptionPane; @@ -111,23 +109,20 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView { cbPlayerCount.addItem(i); } cbPlayerCount.setSelectedItem(2); - cbPlayerCount.setChangedHandler(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - int numPlayers = getNumPlayers(); - while(lobby.getNumberOfSlots() < getNumPlayers()){ - lobby.addSlot(); - } - while(lobby.getNumberOfSlots() > getNumPlayers()){ - lobby.removeSlot(lobby.getNumberOfSlots()-1); - } - for (int i = 0; i < MAX_PLAYERS; i++) { - if(i { + int numPlayers = getNumPlayers(); + while(lobby.getNumberOfSlots() < getNumPlayers()){ + lobby.addSlot(); } + while(lobby.getNumberOfSlots() > getNumPlayers()){ + lobby.removeSlot(lobby.getNumberOfSlots()-1); + } + for (int i = 0; i < MAX_PLAYERS; i++) { + if(i FModel.getPreferences().setPref(FPref.UI_MATCHES_PER_GAME, cbGamesInMatch.getSelectedItem())); add(lblVariants); add(cbVariants); @@ -161,31 +151,28 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView { cbVariants.addItem(GameType.Archenemy); cbVariants.addItem(GameType.ArchenemyRumble); cbVariants.addItem(Forge.getLocalizer().getMessage("lblMore")); - cbVariants.setChangedHandler(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - if (cbVariants.getSelectedIndex() <= 0) { - lobby.clearVariants(); - updateLayoutForVariants(); - Set gameTypes = new HashSet<>(); - FModel.getPreferences().setGameType(FPref.UI_APPLIED_VARIANTS, gameTypes); - FModel.getPreferences().save(); - } - else if (cbVariants.getSelectedIndex() == cbVariants.getItemCount() - 1) { - Forge.openScreen(new MultiVariantSelect()); - updateVariantSelection(); - } - else { - lobby.clearVariants(); - lobby.applyVariant((GameType)cbVariants.getSelectedItem()); - updateLayoutForVariants(); - Set gameTypes = new HashSet<>(); - for (GameType variant: lobby.getAppliedVariants()) { - gameTypes.add(variant); - } - FModel.getPreferences().setGameType(FPref.UI_APPLIED_VARIANTS, gameTypes); - FModel.getPreferences().save(); + cbVariants.setChangedHandler(event -> { + if (cbVariants.getSelectedIndex() <= 0) { + lobby.clearVariants(); + updateLayoutForVariants(); + Set gameTypes = new HashSet<>(); + FModel.getPreferences().setGameType(FPref.UI_APPLIED_VARIANTS, gameTypes); + FModel.getPreferences().save(); + } + else if (cbVariants.getSelectedIndex() == cbVariants.getItemCount() - 1) { + Forge.openScreen(new MultiVariantSelect()); + updateVariantSelection(); + } + else { + lobby.clearVariants(); + lobby.applyVariant((GameType)cbVariants.getSelectedItem()); + updateLayoutForVariants(); + Set gameTypes = new HashSet<>(); + for (GameType variant: lobby.getAppliedVariants()) { + gameTypes.add(variant); } + FModel.getPreferences().setGameType(FPref.UI_APPLIED_VARIANTS, gameTypes); + FModel.getPreferences().save(); } }); @@ -195,40 +182,34 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView { updatePlayersFromPrefs(); - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { - playerPanels.get(0).initialize(FPref.CONSTRUCTED_P1_DECK_STATE, FPref.COMMANDER_P1_DECK_STATE, FPref.OATHBREAKER_P1_DECK_STATE, FPref.TINY_LEADER_P1_DECK_STATE, FPref.BRAWL_P1_DECK_STATE, DeckType.PRECONSTRUCTED_DECK); - playerPanels.get(1).initialize(FPref.CONSTRUCTED_P2_DECK_STATE, FPref.COMMANDER_P2_DECK_STATE, FPref.OATHBREAKER_P2_DECK_STATE, FPref.TINY_LEADER_P2_DECK_STATE, FPref.BRAWL_P2_DECK_STATE, DeckType.COLOR_DECK); - try { - if (getNumPlayers() > 2) { - playerPanels.get(2).initialize(FPref.CONSTRUCTED_P3_DECK_STATE, FPref.COMMANDER_P3_DECK_STATE, FPref.OATHBREAKER_P3_DECK_STATE, FPref.TINY_LEADER_P3_DECK_STATE, FPref.BRAWL_P3_DECK_STATE, DeckType.COLOR_DECK); - } - if (getNumPlayers() > 3) { - playerPanels.get(3).initialize(FPref.CONSTRUCTED_P4_DECK_STATE, FPref.COMMANDER_P4_DECK_STATE, FPref.OATHBREAKER_P3_DECK_STATE, FPref.TINY_LEADER_P4_DECK_STATE, FPref.BRAWL_P4_DECK_STATE, DeckType.COLOR_DECK); - } - } catch (Exception e) {} - /*playerPanels.get(4).initialize(FPref.CONSTRUCTED_P5_DECK_STATE, DeckType.COLOR_DECK); - playerPanels.get(5).initialize(FPref.CONSTRUCTED_P6_DECK_STATE, DeckType.COLOR_DECK); - playerPanels.get(6).initialize(FPref.CONSTRUCTED_P7_DECK_STATE, DeckType.COLOR_DECK); - playerPanels.get(7).initialize(FPref.CONSTRUCTED_P8_DECK_STATE, DeckType.COLOR_DECK);*/ //TODO: Improve performance of loading this screen by using background thread + FThreads.invokeInBackgroundThread(() -> { + playerPanels.get(0).initialize(FPref.CONSTRUCTED_P1_DECK_STATE, FPref.COMMANDER_P1_DECK_STATE, FPref.OATHBREAKER_P1_DECK_STATE, FPref.TINY_LEADER_P1_DECK_STATE, FPref.BRAWL_P1_DECK_STATE, DeckType.PRECONSTRUCTED_DECK); + playerPanels.get(1).initialize(FPref.CONSTRUCTED_P2_DECK_STATE, FPref.COMMANDER_P2_DECK_STATE, FPref.OATHBREAKER_P2_DECK_STATE, FPref.TINY_LEADER_P2_DECK_STATE, FPref.BRAWL_P2_DECK_STATE, DeckType.COLOR_DECK); + try { + if (getNumPlayers() > 2) { + playerPanels.get(2).initialize(FPref.CONSTRUCTED_P3_DECK_STATE, FPref.COMMANDER_P3_DECK_STATE, FPref.OATHBREAKER_P3_DECK_STATE, FPref.TINY_LEADER_P3_DECK_STATE, FPref.BRAWL_P3_DECK_STATE, DeckType.COLOR_DECK); + } + if (getNumPlayers() > 3) { + playerPanels.get(3).initialize(FPref.CONSTRUCTED_P4_DECK_STATE, FPref.COMMANDER_P4_DECK_STATE, FPref.OATHBREAKER_P3_DECK_STATE, FPref.TINY_LEADER_P4_DECK_STATE, FPref.BRAWL_P4_DECK_STATE, DeckType.COLOR_DECK); + } + } catch (Exception e) {} + /*playerPanels.get(4).initialize(FPref.CONSTRUCTED_P5_DECK_STATE, DeckType.COLOR_DECK); + playerPanels.get(5).initialize(FPref.CONSTRUCTED_P6_DECK_STATE, DeckType.COLOR_DECK); + playerPanels.get(6).initialize(FPref.CONSTRUCTED_P7_DECK_STATE, DeckType.COLOR_DECK); + playerPanels.get(7).initialize(FPref.CONSTRUCTED_P8_DECK_STATE, DeckType.COLOR_DECK);*/ //TODO: Improve performance of loading this screen by using background thread - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - btnStart.setEnabled(lobby.hasControl()); + FThreads.invokeInEdtLater(() -> { + btnStart.setEnabled(lobby.hasControl()); - Set gameTypes = FModel.getPreferences().getGameType(FPref.UI_APPLIED_VARIANTS); - if (!gameTypes.isEmpty()) { - for (GameType gameType : gameTypes) { - lobby.applyVariant(gameType); - } - updateVariantSelection(); - updateLayoutForVariants(); - } + Set gameTypes = FModel.getPreferences().getGameType(FPref.UI_APPLIED_VARIANTS); + if (!gameTypes.isEmpty()) { + for (GameType gameType : gameTypes) { + lobby.applyVariant(gameType); } - }); - } + updateVariantSelection(); + updateLayoutForVariants(); + } + }); }); lblPlayers.setEnabled(true); @@ -351,20 +332,13 @@ public abstract class LobbyScreen extends LaunchScreen implements ILobbyView { updateName(0, GamePlayerUtil.getGuiPlayer().getName()); } } - FThreads.invokeInBackgroundThread(new Runnable() { //must call startGame in background thread in case there are alerts - @Override - public void run() { - final Runnable startGame = lobby.startGame(); - if (startGame != null) { - //set this so we cant get any multi/rapid tap on start button - Forge.setLoadingaMatch(true); - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), startGame); - } - }); - } + //must call startGame in background thread in case there are alerts + FThreads.invokeInBackgroundThread(() -> { + final Runnable startGame = lobby.startGame(); + if (startGame != null) { + //set this so we cant get any multi/rapid tap on start button + Forge.setLoadingaMatch(true); + FThreads.invokeInEdtLater(() -> LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), true, startGame)); } }); } diff --git a/forge-gui-mobile/src/forge/screens/gauntlet/LoadGauntletScreen.java b/forge-gui-mobile/src/forge/screens/gauntlet/LoadGauntletScreen.java index 07c08c7bb34..e8dc7a078ca 100644 --- a/forge-gui-mobile/src/forge/screens/gauntlet/LoadGauntletScreen.java +++ b/forge-gui-mobile/src/forge/screens/gauntlet/LoadGauntletScreen.java @@ -4,7 +4,6 @@ import java.io.File; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; import com.badlogic.gdx.utils.Align; @@ -31,8 +30,6 @@ import forge.screens.home.LoadGameMenu; import forge.screens.home.NewGameMenu.NewGameScreen; import forge.screens.settings.SettingsScreen; import forge.toolbox.FButton; -import forge.toolbox.FEvent; -import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FList; import forge.toolbox.FOptionPane; import forge.util.Callback; @@ -53,26 +50,11 @@ public class LoadGauntletScreen extends LaunchScreen { super(null, LoadGameMenu.getMenu()); btnNewGauntlet.setFont(FSkinFont.get(16)); - btnNewGauntlet.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - NewGameScreen.Gauntlet.open(); - } - }); + btnNewGauntlet.setCommand(event -> NewGameScreen.Gauntlet.open()); btnRenameGauntlet.setFont(btnNewGauntlet.getFont()); - btnRenameGauntlet.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - renameGauntlet(lstGauntlets.getSelectedGauntlet()); - } - }); + btnRenameGauntlet.setCommand(event -> renameGauntlet(lstGauntlets.getSelectedGauntlet())); btnDeleteGauntlet.setFont(btnNewGauntlet.getFont()); - btnDeleteGauntlet.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - deleteGauntlet(lstGauntlets.getSelectedGauntlet()); - } - }); + btnDeleteGauntlet.setCommand(event -> deleteGauntlet(lstGauntlets.getSelectedGauntlet())); } public void onActivate() { @@ -136,81 +118,69 @@ public class LoadGauntletScreen extends LaunchScreen { return; } - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), new Runnable() { - @Override - public void run() { - final GauntletData gauntlet = FModel.getGauntletData(); - List players = new ArrayList<>(); - RegisteredPlayer humanPlayer = new RegisteredPlayer(gauntlet.getUserDeck()).setPlayer(GamePlayerUtil.getGuiPlayer()); - players.add(humanPlayer); - players.add(new RegisteredPlayer(gauntlet.getDecks().get(gauntlet.getCompleted())).setPlayer(GamePlayerUtil.createAiPlayer())); - gauntlet.startRound(players, humanPlayer); - } + LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), true, () -> { + final GauntletData gauntlet1 = FModel.getGauntletData(); + List players = new ArrayList<>(); + RegisteredPlayer humanPlayer = new RegisteredPlayer(gauntlet1.getUserDeck()).setPlayer(GamePlayerUtil.getGuiPlayer()); + players.add(humanPlayer); + players.add(new RegisteredPlayer(gauntlet1.getDecks().get(gauntlet1.getCompleted())).setPlayer(GamePlayerUtil.createAiPlayer())); + gauntlet1.startRound(players, humanPlayer); }); } private void renameGauntlet(final GauntletData gauntlet) { if (gauntlet == null) { return; } - ThreadUtil.invokeInGameThread(new Runnable() { - @Override - public void run() { - String gauntletName; - String oldGauntletName = gauntlet.getName(); - while (true) { - gauntletName = SOptionPane.showInputDialog(Forge.getLocalizer().getMessage("lblEnterNewGauntletGameName"), Forge.getLocalizer().getMessage("lblRenameGauntlet"), null, oldGauntletName); - if (gauntletName == null) { return; } + ThreadUtil.invokeInGameThread(() -> { + String gauntletName; + String oldGauntletName = gauntlet.getName(); + while (true) { + gauntletName = SOptionPane.showInputDialog(Forge.getLocalizer().getMessage("lblEnterNewGauntletGameName"), Forge.getLocalizer().getMessage("lblRenameGauntlet"), null, oldGauntletName); + if (gauntletName == null) { return; } - gauntletName = QuestUtil.cleanString(gauntletName); - if (gauntletName.equals(oldGauntletName)) { return; } //quit if chose same name + gauntletName = QuestUtil.cleanString(gauntletName); + if (gauntletName.equals(oldGauntletName)) { return; } //quit if chose same name - if (gauntletName.isEmpty()) { - SOptionPane.showMessageDialog(Forge.getLocalizer().getMessage("lblPleaseSpecifyGauntletName")); - continue; - } - - boolean exists = false; - for (GauntletData gauntletData : lstGauntlets) { - if (gauntletData.getName().equalsIgnoreCase(gauntletName)) { - exists = true; - break; - } - } - if (exists) { - SOptionPane.showMessageDialog(Forge.getLocalizer().getMessage("lblGauntletNameExistsPleasePickAnotherName")); - continue; - } - break; + if (gauntletName.isEmpty()) { + SOptionPane.showMessageDialog(Forge.getLocalizer().getMessage("lblPleaseSpecifyGauntletName")); + continue; } - final String newGauntletName = gauntletName; - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - gauntlet.rename(newGauntletName); - lstGauntlets.refresh(); - lstGauntlets.setSelectedGauntlet(gauntlet); + + boolean exists = false; + for (GauntletData gauntletData : lstGauntlets) { + if (gauntletData.getName().equalsIgnoreCase(gauntletName)) { + exists = true; + break; } - }); + } + if (exists) { + SOptionPane.showMessageDialog(Forge.getLocalizer().getMessage("lblGauntletNameExistsPleasePickAnotherName")); + continue; + } + break; } + final String newGauntletName = gauntletName; + FThreads.invokeInEdtLater(() -> { + gauntlet.rename(newGauntletName); + lstGauntlets.refresh(); + lstGauntlets.setSelectedGauntlet(gauntlet); + }); }); } private void deleteGauntlet(final GauntletData gauntlet) { if (gauntlet == null) { return; } - ThreadUtil.invokeInGameThread(new Runnable() { - @Override - public void run() { - if (!SOptionPane.showConfirmDialog( - Forge.getLocalizer().getMessage("lblAreYouSuerDeleteGauntlet", gauntlet.getName()), - Forge.getLocalizer().getMessage("lblDeleteGauntlet"), Forge.getLocalizer().getMessage("lblDelete"), Forge.getLocalizer().getMessage("lblCancel"))) { - return; - } - - GauntletIO.getGauntletFile(gauntlet).delete(); - - lstGauntlets.removeGauntlet(gauntlet); + ThreadUtil.invokeInGameThread(() -> { + if (!SOptionPane.showConfirmDialog( + Forge.getLocalizer().getMessage("lblAreYouSuerDeleteGauntlet", gauntlet.getName()), + Forge.getLocalizer().getMessage("lblDeleteGauntlet"), Forge.getLocalizer().getMessage("lblDelete"), Forge.getLocalizer().getMessage("lblCancel"))) { + return; } + + GauntletIO.getGauntletFile(gauntlet).delete(); + + lstGauntlets.removeGauntlet(gauntlet); }); } @@ -299,12 +269,7 @@ public class LoadGauntletScreen extends LaunchScreen { public void refresh() { List sorted = new ArrayList<>(); sorted.addAll(gauntlets); - Collections.sort(sorted, new Comparator() { - @Override - public int compare(final GauntletData x, final GauntletData y) { - return x.getName().toLowerCase().compareTo(y.getName().toLowerCase()); - } - }); + Collections.sort(sorted, (x, y) -> x.getName().toLowerCase().compareTo(y.getName().toLowerCase())); setListData(sorted); } diff --git a/forge-gui-mobile/src/forge/screens/home/puzzle/PuzzleScreen.java b/forge-gui-mobile/src/forge/screens/home/puzzle/PuzzleScreen.java index 751d20d5bfe..2393dd7ca24 100644 --- a/forge-gui-mobile/src/forge/screens/home/puzzle/PuzzleScreen.java +++ b/forge-gui-mobile/src/forge/screens/home/puzzle/PuzzleScreen.java @@ -60,39 +60,26 @@ public class PuzzleScreen extends LaunchScreen { @Override public void run(final Puzzle chosen) { if (chosen != null) { - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingThePuzzle"), new Runnable() { - @Override - public void run() { - // Load selected puzzle - final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch(); - hostedMatch.setStartGameHook(new Runnable() { - @Override - public final void run() { - chosen.applyToGame(hostedMatch.getGame()); - } - }); + LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingThePuzzle"), true, () -> { + // Load selected puzzle + final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch(); + hostedMatch.setStartGameHook(() -> chosen.applyToGame(hostedMatch.getGame())); - hostedMatch.setEndGameHook((new Runnable() { - @Override - public void run() { - chosen.savePuzzleSolve(hostedMatch.getGame().getOutcome().isWinner(GamePlayerUtil.getGuiPlayer())); - } - })); + hostedMatch.setEndGameHook((() -> chosen.savePuzzleSolve(hostedMatch.getGame().getOutcome().isWinner(GamePlayerUtil.getGuiPlayer())))); - final List players = new ArrayList<>(); - final RegisteredPlayer human = new RegisteredPlayer(new Deck()).setPlayer(GamePlayerUtil.getGuiPlayer()); - human.setStartingHand(0); - players.add(human); + final List players = new ArrayList<>(); + final RegisteredPlayer human = new RegisteredPlayer(new Deck()).setPlayer(GamePlayerUtil.getGuiPlayer()); + human.setStartingHand(0); + players.add(human); - final RegisteredPlayer ai = new RegisteredPlayer(new Deck()).setPlayer(GamePlayerUtil.createAiPlayer()); - ai.setStartingHand(0); - players.add(ai); + final RegisteredPlayer ai = new RegisteredPlayer(new Deck()).setPlayer(GamePlayerUtil.createAiPlayer()); + ai.setStartingHand(0); + players.add(ai); - GameRules rules = new GameRules(GameType.Puzzle); - rules.setGamesPerMatch(1); - hostedMatch.startMatch(rules, null, players, human, GuiBase.getInterface().getNewGuiGame()); - FOptionPane.showMessageDialog(chosen.getGoalDescription(), chosen.getName()); - } + GameRules rules = new GameRules(GameType.Puzzle); + rules.setGamesPerMatch(1); + hostedMatch.startMatch(rules, null, players, human, GuiBase.getInterface().getNewGuiGame()); + FOptionPane.showMessageDialog(chosen.getGoalDescription(), chosen.getName()); }); } } diff --git a/forge-gui-mobile/src/forge/screens/limited/LoadDraftScreen.java b/forge-gui-mobile/src/forge/screens/limited/LoadDraftScreen.java index 6f4139da61d..1331e0824a8 100644 --- a/forge-gui-mobile/src/forge/screens/limited/LoadDraftScreen.java +++ b/forge-gui-mobile/src/forge/screens/limited/LoadDraftScreen.java @@ -30,8 +30,6 @@ import forge.screens.LaunchScreen; import forge.screens.LoadingOverlay; import forge.screens.home.LoadGameMenu; import forge.toolbox.FComboBox; -import forge.toolbox.FEvent; -import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FLabel; import forge.toolbox.FOptionPane; @@ -54,12 +52,7 @@ public class LoadDraftScreen extends LaunchScreen { cbMode.addItem(Forge.getLocalizer().getMessage("lblSingleMatch")); lstDecks.setup(ItemManagerConfig.DRAFT_DECKS); - lstDecks.setItemActivateHandler(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - editSelectedDeck(); - } - }); + lstDecks.setItemActivateHandler(event -> editSelectedDeck()); } @Override @@ -96,80 +89,63 @@ public class LoadDraftScreen extends LaunchScreen { @Override protected void startMatch() { - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { - final DeckProxy humanDeck = lstDecks.getSelectedItem(); - if (humanDeck == null) { - FOptionPane.showErrorDialog(Forge.getLocalizer().getMessage("lblYouMustSelectExistingDeck"), Forge.getLocalizer().getMessage("lblNoDeck")); + FThreads.invokeInBackgroundThread(() -> { + final DeckProxy humanDeck = lstDecks.getSelectedItem(); + if (humanDeck == null) { + FOptionPane.showErrorDialog(Forge.getLocalizer().getMessage("lblYouMustSelectExistingDeck"), Forge.getLocalizer().getMessage("lblNoDeck")); + return; + } + + // TODO: if booster draft tournaments are supported in the future, add the possibility to choose them here + final boolean gauntlet = cbMode.getSelectedItem().equals(Forge.getLocalizer().getMessage("lblGauntlet")); + + if (gauntlet) { + final Integer rounds = SGuiChoose.getInteger(Forge.getLocalizer().getMessage("lblHowManyOpponents"), + 1, FModel.getDecks().getDraft().get(humanDeck.getName()).getAiDecks().size()); + if (rounds == null) { return; } - // TODO: if booster draft tournaments are supported in the future, add the possibility to choose them here - final boolean gauntlet = cbMode.getSelectedItem().equals(Forge.getLocalizer().getMessage("lblGauntlet")); - - if (gauntlet) { - final Integer rounds = SGuiChoose.getInteger(Forge.getLocalizer().getMessage("lblHowManyOpponents"), - 1, FModel.getDecks().getDraft().get(humanDeck.getName()).getAiDecks().size()); - if (rounds == null) { + FThreads.invokeInEdtLater(() -> { + if (!checkDeckLegality(humanDeck)) { return; } - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (!checkDeckLegality(humanDeck)) { - return; - } - - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), new Runnable() { - @Override - public void run() { - FModel.getGauntletMini().resetGauntletDraft(); - FModel.getGauntletMini().launch(rounds, humanDeck.getDeck(), GameType.Draft); - } - }); - } - }); - } else { - final Integer aiIndex = SGuiChoose.getInteger(Forge.getLocalizer().getMessage("lblWhichOpponentWouldYouLikeToFace"), - 1, FModel.getDecks().getDraft().get(humanDeck.getName()).getAiDecks().size()); - if (aiIndex == null) { - return; // Cancel was pressed - } - - final DeckGroup opponentDecks = FModel.getDecks().getDraft().get(humanDeck.getName()); - final Deck aiDeck = opponentDecks.getAiDecks().get(aiIndex - 1); - if (aiDeck == null) { - throw new IllegalStateException("Draft: Computer deck is null!"); - } - - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), new Runnable() { - @Override - public void run() { - if (!checkDeckLegality(humanDeck)) { - return; - } - - final List starter = new ArrayList<>(); - final RegisteredPlayer human = new RegisteredPlayer(humanDeck.getDeck()).setPlayer(GamePlayerUtil.getGuiPlayer()); - starter.add(human); - starter.add(new RegisteredPlayer(aiDeck).setPlayer(GamePlayerUtil.createAiPlayer())); - for (final RegisteredPlayer pl : starter) { - pl.assignConspiracies(); - } - - FModel.getGauntletMini().resetGauntletDraft(); - final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch(); - hostedMatch.startMatch(GameType.Draft, null, starter, human, GuiBase.getInterface().getNewGuiGame()); - } - }); - } + LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), true, () -> { + FModel.getGauntletMini().resetGauntletDraft(); + FModel.getGauntletMini().launch(rounds, humanDeck.getDeck(), GameType.Draft); }); + }); + } else { + final Integer aiIndex = SGuiChoose.getInteger(Forge.getLocalizer().getMessage("lblWhichOpponentWouldYouLikeToFace"), + 1, FModel.getDecks().getDraft().get(humanDeck.getName()).getAiDecks().size()); + if (aiIndex == null) { + return; // Cancel was pressed } + + final DeckGroup opponentDecks = FModel.getDecks().getDraft().get(humanDeck.getName()); + final Deck aiDeck = opponentDecks.getAiDecks().get(aiIndex - 1); + if (aiDeck == null) { + throw new IllegalStateException("Draft: Computer deck is null!"); + } + + FThreads.invokeInEdtLater(() -> LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), true, () -> { + if (!checkDeckLegality(humanDeck)) { + return; + } + + final List starter = new ArrayList<>(); + final RegisteredPlayer human = new RegisteredPlayer(humanDeck.getDeck()).setPlayer(GamePlayerUtil.getGuiPlayer()); + starter.add(human); + starter.add(new RegisteredPlayer(aiDeck).setPlayer(GamePlayerUtil.createAiPlayer())); + for (final RegisteredPlayer pl : starter) { + pl.assignConspiracies(); + } + + FModel.getGauntletMini().resetGauntletDraft(); + final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch(); + hostedMatch.startMatch(GameType.Draft, null, starter, human, GuiBase.getInterface().getNewGuiGame()); + })); } }); } diff --git a/forge-gui-mobile/src/forge/screens/limited/LoadSealedScreen.java b/forge-gui-mobile/src/forge/screens/limited/LoadSealedScreen.java index b73491db893..27f7c82e56b 100644 --- a/forge-gui-mobile/src/forge/screens/limited/LoadSealedScreen.java +++ b/forge-gui-mobile/src/forge/screens/limited/LoadSealedScreen.java @@ -30,8 +30,6 @@ import forge.screens.LaunchScreen; import forge.screens.LoadingOverlay; import forge.screens.home.LoadGameMenu; import forge.toolbox.FComboBox; -import forge.toolbox.FEvent; -import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FLabel; import forge.toolbox.FOptionPane; @@ -54,12 +52,7 @@ public class LoadSealedScreen extends LaunchScreen { cbMode.addItem(Forge.getLocalizer().getMessage("lblSingleMatch")); lstDecks.setup(ItemManagerConfig.SEALED_DECKS); - lstDecks.setItemActivateHandler(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - editSelectedDeck(); - } - }); + lstDecks.setItemActivateHandler(event -> editSelectedDeck()); } @Override @@ -95,74 +88,59 @@ public class LoadSealedScreen extends LaunchScreen { @Override protected void startMatch() { - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { - final DeckProxy humanDeck = lstDecks.getSelectedItem(); - if (humanDeck == null) { - FOptionPane.showErrorDialog(Forge.getLocalizer().getMessage("lblYouMustSelectExistingSealedPool"), Forge.getLocalizer().getMessage("lblNoDeck")); - return; - } + FThreads.invokeInBackgroundThread(() -> { + final DeckProxy humanDeck = lstDecks.getSelectedItem(); + if (humanDeck == null) { + FOptionPane.showErrorDialog(Forge.getLocalizer().getMessage("lblYouMustSelectExistingSealedPool"), Forge.getLocalizer().getMessage("lblNoDeck")); + return; + } - final boolean gauntlet = cbMode.getSelectedItem().equals(Forge.getLocalizer().getMessage("lblGauntlet")); + final boolean gauntlet = cbMode.getSelectedItem().equals(Forge.getLocalizer().getMessage("lblGauntlet")); - if (gauntlet) { - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (!checkDeckLegality(humanDeck)) { - return; - } - - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), new Runnable() { - @Override - public void run() { - final int matches = FModel.getDecks().getSealed().get(humanDeck.getName()).getAiDecks().size(); - FModel.getGauntletMini().launch(matches, humanDeck.getDeck(), GameType.Sealed); - } - }); - } - }); - } else { - - final Integer aiIndex = SGuiChoose.getInteger(Forge.getLocalizer().getMessage("lblWhichOpponentWouldYouLikeToFace"), - 1, FModel.getDecks().getSealed().get(humanDeck.getName()).getAiDecks().size()); - if (aiIndex == null) { - return; // Cancel was pressed + if (gauntlet) { + FThreads.invokeInEdtLater(() -> { + if (!checkDeckLegality(humanDeck)) { + return; } - final DeckGroup opponentDecks = FModel.getDecks().getSealed().get(humanDeck.getName()); - final Deck aiDeck = opponentDecks.getAiDecks().get(aiIndex - 1); - if (aiDeck == null) { - throw new IllegalStateException("Draft: Computer deck is null!"); + LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), true, () -> { + final int matches = FModel.getDecks().getSealed().get(humanDeck.getName()).getAiDecks().size(); + FModel.getGauntletMini().launch(matches, humanDeck.getDeck(), GameType.Sealed); + }); + }); + } else { + + final Integer aiIndex = SGuiChoose.getInteger(Forge.getLocalizer().getMessage("lblWhichOpponentWouldYouLikeToFace"), + 1, FModel.getDecks().getSealed().get(humanDeck.getName()).getAiDecks().size()); + if (aiIndex == null) { + return; // Cancel was pressed + } + + final DeckGroup opponentDecks = FModel.getDecks().getSealed().get(humanDeck.getName()); + final Deck aiDeck = opponentDecks.getAiDecks().get(aiIndex - 1); + if (aiDeck == null) { + throw new IllegalStateException("Draft: Computer deck is null!"); + } + + FThreads.invokeInEdtLater(() -> { + if (!checkDeckLegality(humanDeck)) { + return; } - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (!checkDeckLegality(humanDeck)) { - return; - } - - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), new Runnable() { - @Override - public void run() { - final List starter = new ArrayList<>(); - final RegisteredPlayer human = new RegisteredPlayer(humanDeck.getDeck()).setPlayer(GamePlayerUtil.getGuiPlayer()); - starter.add(human); - starter.add(new RegisteredPlayer(aiDeck).setPlayer(GamePlayerUtil.createAiPlayer())); - for (final RegisteredPlayer pl : starter) { - pl.assignConspiracies(); - } - - FModel.getGauntletMini().resetGauntletDraft(); - final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch(); - hostedMatch.startMatch(GameType.Sealed, null, starter, human, GuiBase.getInterface().getNewGuiGame()); - } - }); + LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), true, () -> { + final List starter = new ArrayList<>(); + final RegisteredPlayer human = new RegisteredPlayer(humanDeck.getDeck()).setPlayer(GamePlayerUtil.getGuiPlayer()); + starter.add(human); + starter.add(new RegisteredPlayer(aiDeck).setPlayer(GamePlayerUtil.createAiPlayer())); + for (final RegisteredPlayer pl : starter) { + pl.assignConspiracies(); } + + FModel.getGauntletMini().resetGauntletDraft(); + final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch(); + hostedMatch.startMatch(GameType.Sealed, null, starter, human, GuiBase.getInterface().getNewGuiGame()); }); - } + }); } }); } diff --git a/forge-gui-mobile/src/forge/screens/limited/NewDraftScreen.java b/forge-gui-mobile/src/forge/screens/limited/NewDraftScreen.java index 2431e17df83..60da366d034 100644 --- a/forge-gui-mobile/src/forge/screens/limited/NewDraftScreen.java +++ b/forge-gui-mobile/src/forge/screens/limited/NewDraftScreen.java @@ -41,27 +41,15 @@ public class NewDraftScreen extends LaunchScreen { @Override protected void startMatch() { - ThreadUtil.invokeInGameThread(new Runnable() { //must run in game thread to prevent blocking UI thread - @Override - public void run() { - final LimitedPoolType poolType = SGuiChoose.oneOrNone(Forge.getLocalizer().getMessage("lblChooseDraftFormat"), LimitedPoolType.values()); - if (poolType == null) { return; } + //must run in game thread to prevent blocking UI thread + ThreadUtil.invokeInGameThread(() -> { + final LimitedPoolType poolType = SGuiChoose.oneOrNone(Forge.getLocalizer().getMessage("lblChooseDraftFormat"), LimitedPoolType.values()); + if (poolType == null) { return; } - final BoosterDraft draft = BoosterDraft.createDraft(poolType); - if (draft == null) { return; } + final BoosterDraft draft = BoosterDraft.createDraft(poolType); + if (draft == null) { return; } - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewDraft"), new Runnable() { - @Override - public void run() { - Forge.openScreen(new DraftingProcessScreen(draft, EditorType.Draft, null)); - } - }); - } - }); - } + FThreads.invokeInEdtLater(() -> LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewDraft"), true, () -> Forge.openScreen(new DraftingProcessScreen(draft, EditorType.Draft, null)))); }); } } diff --git a/forge-gui-mobile/src/forge/screens/match/MatchController.java b/forge-gui-mobile/src/forge/screens/match/MatchController.java index ff8120b4520..50a3cc66b33 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchController.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchController.java @@ -2,7 +2,6 @@ package forge.screens.match; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -74,8 +73,6 @@ public class MatchController extends AbstractGuiGame { private MatchController() { } public static final MatchController instance = new MatchController(); - private static final Map avatarImages = new HashMap<>(); - private static HostedMatch hostedMatch; private static MatchScreen view; private static GameState phaseGameState; @@ -114,7 +111,7 @@ public class MatchController extends AbstractGuiGame { public static FImage getPlayerAvatar(final PlayerView p) { final String lp = p.getLobbyPlayerName(); - FImage avatar = avatarImages.get(lp); + FImage avatar = Forge.getAssets().avatarImages().get(lp); if (avatar == null) { if (StringUtils.isEmpty(p.getAvatarCardImageKey())) { avatar = new FTextureRegionImage(FSkin.getAvatars().get(p.getAvatarIndex())); @@ -488,15 +485,13 @@ public class MatchController extends AbstractGuiGame { public void setSelectables(final Iterable cards) { super.setSelectables(cards); // update zones on tabletop and floating zones - non-selectable cards may be rendered differently - FThreads.invokeInEdtNowOrLater(new Runnable() { - @Override public final void run() { - for (final PlayerView p : getGameView().getPlayers()) { - if ( p.getCards(ZoneType.Battlefield) != null ) { - updateCards(p.getCards(ZoneType.Battlefield)); - } - if ( p.getCards(ZoneType.Hand) != null ) { - updateCards(p.getCards(ZoneType.Hand)); - } + FThreads.invokeInEdtNowOrLater(() -> { + for (final PlayerView p : getGameView().getPlayers()) { + if ( p.getCards(ZoneType.Battlefield) != null ) { + updateCards(p.getCards(ZoneType.Battlefield)); + } + if ( p.getCards(ZoneType.Hand) != null ) { + updateCards(p.getCards(ZoneType.Hand)); } } }); @@ -506,15 +501,13 @@ public class MatchController extends AbstractGuiGame { public void clearSelectables() { super.clearSelectables(); // update zones on tabletop and floating zones - non-selectable cards may be rendered differently - FThreads.invokeInEdtNowOrLater(new Runnable() { - @Override public final void run() { - for (final PlayerView p : getGameView().getPlayers()) { - if ( p.getCards(ZoneType.Battlefield) != null ) { - updateCards(p.getCards(ZoneType.Battlefield)); - } - if ( p.getCards(ZoneType.Hand) != null ) { - updateCards(p.getCards(ZoneType.Hand)); - } + FThreads.invokeInEdtNowOrLater(() -> { + for (final PlayerView p : getGameView().getPlayers()) { + if ( p.getCards(ZoneType.Battlefield) != null ) { + updateCards(p.getCards(ZoneType.Battlefield)); + } + if ( p.getCards(ZoneType.Hand) != null ) { + updateCards(p.getCards(ZoneType.Hand)); } } }); @@ -703,7 +696,7 @@ public class MatchController extends AbstractGuiGame { @Override public void setPlayerAvatar(final LobbyPlayer player, final IHasIcon ihi) { - avatarImages.put(player.getName(), ImageCache.getIcon(ihi)); + Forge.getAssets().avatarImages().put(player.getName(), ImageCache.getIcon(ihi)); } @Override diff --git a/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java b/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java index 48753d91323..5eeb5f5169d 100644 --- a/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java +++ b/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java @@ -56,18 +56,15 @@ public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby { clearGameLobby(); Forge.back(); if (msg.length() > 0) { - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { - final boolean callBackAlwaysTrue = SOptionPane.showOptionDialog(msg, Forge.getLocalizer().getMessage("lblError"), FSkinProp.ICO_WARNING, ImmutableList.of(Forge.getLocalizer().getMessage("lblOk")), 1) == 0; - if (callBackAlwaysTrue) { //to activate online menu popup when player press play online - GuiBase.setInterrupted(false); + FThreads.invokeInBackgroundThread(() -> { + final boolean callBackAlwaysTrue = SOptionPane.showOptionDialog(msg, Forge.getLocalizer().getMessage("lblError"), FSkinProp.ICO_WARNING, ImmutableList.of(Forge.getLocalizer().getMessage("lblOk")), 1) == 0; + if (callBackAlwaysTrue) { //to activate online menu popup when player press play online + GuiBase.setInterrupted(false); - if(FServerManager.getInstance() != null) - FServerManager.getInstance().stopServer(); - if(getfGameClient() != null) - closeClient(); - } + if(FServerManager.getInstance() != null) + FServerManager.getInstance().stopServer(); + if(getfGameClient() != null) + closeClient(); } }); } @@ -93,51 +90,37 @@ public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby { if (getGameLobby() == null) { setGameLobby(getLobby()); //prompt to connect to server when offline lobby activated - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { - final String url = NetConnectUtil.getServerUrl(); - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - if (url == null) { - closeConn(""); //go back to previous screen if user cancels connection + FThreads.invokeInBackgroundThread(() -> { + final String url = NetConnectUtil.getServerUrl(); + FThreads.invokeInEdtLater(() -> { + if (url == null) { + closeConn(""); //go back to previous screen if user cancels connection + return; + } + + final boolean joinServer = url.length() > 0; + final String caption = joinServer ? Forge.getLocalizer().getMessage("lblConnectingToServer") : Forge.getLocalizer().getMessage("lblStartingServer"); + LoadingOverlay.show(caption, true, () -> { + final ChatMessage result; + final IOnlineChatInterface chatInterface = (IOnlineChatInterface)OnlineScreen.Chat.getScreen(); + if (joinServer) { + result = NetConnectUtil.join(url, OnlineLobbyScreen.this, chatInterface); + if (result.getMessage() == ForgeConstants.CLOSE_CONN_COMMAND) { //this message is returned via netconnectutil on exception + closeConn(Forge.getLocalizer().getMessage("lblDetectedInvalidHostAddress", url)); return; } - - final boolean joinServer = url.length() > 0; - final String caption = joinServer ? Forge.getLocalizer().getMessage("lblConnectingToServer") : Forge.getLocalizer().getMessage("lblStartingServer"); - LoadingOverlay.show(caption, new Runnable() { - @Override - public void run() { - final ChatMessage result; - final IOnlineChatInterface chatInterface = (IOnlineChatInterface)OnlineScreen.Chat.getScreen(); - if (joinServer) { - result = NetConnectUtil.join(url, OnlineLobbyScreen.this, chatInterface); - if (result.getMessage() == ForgeConstants.CLOSE_CONN_COMMAND) { //this message is returned via netconnectutil on exception - closeConn(Forge.getLocalizer().getMessage("lblDetectedInvalidHostAddress", url)); - return; - } - } - else { - result = NetConnectUtil.host(OnlineLobbyScreen.this, chatInterface); - } - chatInterface.addMessage(result); - if (!joinServer) { - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { - NetConnectUtil.copyHostedServerUrl(); - } - }); - } - //update menu buttons - OnlineScreen.Lobby.update(); - } - }); } + else { + result = NetConnectUtil.host(OnlineLobbyScreen.this, chatInterface); + } + chatInterface.addMessage(result); + if (!joinServer) { + FThreads.invokeInBackgroundThread(() -> NetConnectUtil.copyHostedServerUrl()); + } + //update menu buttons + OnlineScreen.Lobby.update(); }); - } + }); }); } } diff --git a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMenu.java b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMenu.java index 4416c32bd23..2fc572cf44b 100644 --- a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMenu.java +++ b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMenu.java @@ -12,8 +12,6 @@ import forge.screens.FScreen; import forge.screens.LoadingOverlay; import forge.screens.home.HomeScreen; import forge.screens.home.LoadGameMenu.LoadGameScreen; -import forge.toolbox.FEvent; -import forge.toolbox.FEvent.FEventHandler; public class ConquestMenu extends FPopupMenu { private static final ConquestMenu conquestMenu = new ConquestMenu(); @@ -26,54 +24,14 @@ public class ConquestMenu extends FPopupMenu { private static final ConquestStatsScreen statsScreen = new ConquestStatsScreen(); private static final ConquestPrefsScreen prefsScreen = new ConquestPrefsScreen(); - private static final FMenuItem multiverseItem = new FMenuItem(Forge.getLocalizer().getMessage("lblTheMultiverse"), FSkinImage.MULTIVERSE, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(multiverseScreen); - } - }); - private static final FMenuItem aetherItem = new FMenuItem(Forge.getLocalizer().getMessage("lblTheAether"), FSkinImage.AETHER_SHARD, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(aetherScreen); - } - }); - private static final FMenuItem commandersItem = new FMenuItem(Forge.getLocalizer().getMessage("lblCommanders"), FSkinImage.COMMANDER, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(commandersScreen); - } - }); - private static final FMenuItem planeswalkersItem = new FMenuItem(Forge.getLocalizer().getMessage("lblPlaneswalkers"), FSkinImage.PLANESWALKER, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(planeswalkersScreen); - } - }); - private static final FMenuItem collectionItem = new FMenuItem(Forge.getLocalizer().getMessage("lblCollection"), FSkinImage.SPELLBOOK, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(collectionScreen); - } - }); - private static final FMenuItem statsItem = new FMenuItem(Forge.getLocalizer().getMessage("lblStatistics"), FSkinImage.MENU_STATS, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(statsScreen); - } - }); - private static final FMenuItem planeswalkItem = new FMenuItem(Forge.getLocalizer().getMessage("lblPlaneswalk"), FSkinImage.PW_BADGE_COMMON, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(planeswalkScreen); - } - }); - private static final FMenuItem prefsItem = new FMenuItem(Forge.getLocalizer().getMessage("Preferences"), Forge.hdbuttons ? FSkinImage.HDPREFERENCE : FSkinImage.SETTINGS, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(prefsScreen); - } - }); + private static final FMenuItem multiverseItem = new FMenuItem(Forge.getLocalizer().getMessage("lblTheMultiverse"), FSkinImage.MULTIVERSE, event -> setCurrentScreen(multiverseScreen)); + private static final FMenuItem aetherItem = new FMenuItem(Forge.getLocalizer().getMessage("lblTheAether"), FSkinImage.AETHER_SHARD, event -> setCurrentScreen(aetherScreen)); + private static final FMenuItem commandersItem = new FMenuItem(Forge.getLocalizer().getMessage("lblCommanders"), FSkinImage.COMMANDER, event -> setCurrentScreen(commandersScreen)); + private static final FMenuItem planeswalkersItem = new FMenuItem(Forge.getLocalizer().getMessage("lblPlaneswalkers"), FSkinImage.PLANESWALKER, event -> setCurrentScreen(planeswalkersScreen)); + private static final FMenuItem collectionItem = new FMenuItem(Forge.getLocalizer().getMessage("lblCollection"), FSkinImage.SPELLBOOK, event -> setCurrentScreen(collectionScreen)); + private static final FMenuItem statsItem = new FMenuItem(Forge.getLocalizer().getMessage("lblStatistics"), FSkinImage.MENU_STATS, event -> setCurrentScreen(statsScreen)); + private static final FMenuItem planeswalkItem = new FMenuItem(Forge.getLocalizer().getMessage("lblPlaneswalk"), FSkinImage.PW_BADGE_COMMON, event -> setCurrentScreen(planeswalkScreen)); + private static final FMenuItem prefsItem = new FMenuItem(Forge.getLocalizer().getMessage("Preferences"), Forge.hdbuttons ? FSkinImage.HDPREFERENCE : FSkinImage.SETTINGS, event -> setCurrentScreen(prefsScreen)); private static void setCurrentScreen(FScreen screen0) { //make it so pressing Back from any screen besides Multiverse screen always goes to Multiverse screen @@ -84,12 +42,7 @@ public class ConquestMenu extends FPopupMenu { static { //the first time planarconquest mode is launched, add button for it if in Landscape mode if (Forge.isLandscapeMode()) { - HomeScreen.instance.addButtonForMode("-"+Forge.getLocalizer().getMessage("lblPlanarConquest"), new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - launchPlanarConquest(LaunchReason.StartPlanarConquest); - } - }); + HomeScreen.instance.addButtonForMode("-"+Forge.getLocalizer().getMessage("lblPlanarConquest"), event -> launchPlanarConquest(LaunchReason.StartPlanarConquest)); } } @@ -108,20 +61,16 @@ public class ConquestMenu extends FPopupMenu { public static void launchPlanarConquest(final LaunchReason reason) { Forge.lastButtonIndex = 7; - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingCurrentConquest"), new Runnable() { - @Override - @SuppressWarnings("unchecked") - public void run() { - ((DeckController)EditorType.PlanarConquest.getController()).setRootFolder(FModel.getConquest().getDecks()); - if (reason == LaunchReason.StartPlanarConquest) { - Forge.openScreen(multiverseScreen); - } - else { - multiverseScreen.update(); - Forge.openScreen(multiverseScreen); - if (reason == LaunchReason.NewConquest) { - LoadGameScreen.PlanarConquest.setAsBackScreen(true); - } + LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingCurrentConquest"), true, () -> { + ((DeckController)EditorType.PlanarConquest.getController()).setRootFolder(FModel.getConquest().getDecks()); + if (reason == LaunchReason.StartPlanarConquest) { + Forge.openScreen(multiverseScreen); + } + else { + multiverseScreen.update(); + Forge.openScreen(multiverseScreen); + if (reason == LaunchReason.NewConquest) { + LoadGameScreen.PlanarConquest.setAsBackScreen(true); } } }); diff --git a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMultiverseScreen.java b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMultiverseScreen.java index 5cbc1308f48..fbe7327925c 100644 --- a/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMultiverseScreen.java +++ b/forge-gui-mobile/src/forge/screens/planarconquest/ConquestMultiverseScreen.java @@ -47,8 +47,6 @@ import forge.screens.LoadingOverlay; import forge.toolbox.FButton; import forge.toolbox.FContainer; import forge.toolbox.FDisplayObject; -import forge.toolbox.FEvent; -import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FList; import forge.toolbox.FOptionPane; import forge.toolbox.FScrollPane; @@ -260,29 +258,18 @@ public class ConquestMultiverseScreen extends FScreen { } private void launchEvent() { - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblStartingBattle"), new Runnable() { - @Override - public void run() { - ConquestLocation loc = model.getCurrentLocation(); - activeBattle = loc.getEvent().createBattle(loc, 0); - FModel.getConquest().startBattle(activeBattle); - } + LoadingOverlay.show(Forge.getLocalizer().getMessage("lblStartingBattle"), true, () -> { + ConquestLocation loc = model.getCurrentLocation(); + activeBattle = loc.getEvent().createBattle(loc, 0); + FModel.getConquest().startBattle(activeBattle); }); } private void launchChaosBattle() { - FThreads.invokeInEdtNowOrLater(new Runnable() { - @Override - public void run() { - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblChaosApproaching"), new Runnable() { - @Override - public void run() { - activeBattle = new ConquestChaosBattle(); - FModel.getConquest().startBattle(activeBattle); - } - }); - } - }); + FThreads.invokeInEdtNowOrLater(() -> LoadingOverlay.show(Forge.getLocalizer().getMessage("lblChaosApproaching"), true, () -> { + activeBattle = new ConquestChaosBattle(); + FModel.getConquest().startBattle(activeBattle); + })); } @Override @@ -678,12 +665,7 @@ public class ConquestMultiverseScreen extends FScreen { private BattleBar() { playerAvatar = add(new AvatarDisplay(false)); opponentAvatar = add(new AvatarDisplay(true)); - btnBattle = add(new FButton(Forge.getLocalizer().getMessage("lblBattle"), new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - launchEvent(); - } - })); + btnBattle = add(new FButton(Forge.getLocalizer().getMessage("lblBattle"), event -> launchEvent())); btnBattle.setFont(FSkinFont.get(20)); } diff --git a/forge-gui-mobile/src/forge/screens/planarconquest/NewConquestScreen.java b/forge-gui-mobile/src/forge/screens/planarconquest/NewConquestScreen.java index 085ab992f89..5cfbaa7fb21 100644 --- a/forge-gui-mobile/src/forge/screens/planarconquest/NewConquestScreen.java +++ b/forge-gui-mobile/src/forge/screens/planarconquest/NewConquestScreen.java @@ -37,12 +37,7 @@ public class NewConquestScreen extends MultiStepWizardScreen newConquest()); } private void newConquest() { @@ -52,26 +47,18 @@ public class NewConquestScreen extends MultiStepWizardScreen LoadingOverlay.show(Forge.getLocalizer().getMessage("lblStartingNewConquest"), true, () -> { + ConquestController qc = FModel.getConquest(); + qc.setModel(new ConquestData(conquestName, model.startingPlane, model.startingPlaneswalker, model.startingCommander)); + qc.getDecks().add(Iterables.getFirst(qc.getModel().getCommanders(), null).getDeck()); //ensure starting deck is saved + qc.getModel().saveData(); - // Save in preferences. - FModel.getConquestPreferences().setPref(CQPref.CURRENT_CONQUEST, conquestName); - FModel.getConquestPreferences().save(); + // Save in preferences. + FModel.getConquestPreferences().setPref(CQPref.CURRENT_CONQUEST, conquestName); + FModel.getConquestPreferences().save(); - ConquestMenu.launchPlanarConquest(LaunchReason.NewConquest); - } - }); - } - }); + ConquestMenu.launchPlanarConquest(LaunchReason.NewConquest); + })); } private static class SelectStartingPlaneStep extends WizardStep { diff --git a/forge-gui-mobile/src/forge/screens/quest/NewQuestScreen.java b/forge-gui-mobile/src/forge/screens/quest/NewQuestScreen.java index 56eeeff4e54..51f14a71410 100644 --- a/forge-gui-mobile/src/forge/screens/quest/NewQuestScreen.java +++ b/forge-gui-mobile/src/forge/screens/quest/NewQuestScreen.java @@ -42,8 +42,6 @@ import forge.screens.quest.QuestMenu.LaunchReason; import forge.toolbox.FCheckBox; import forge.toolbox.FComboBox; import forge.toolbox.FDisplayObject; -import forge.toolbox.FEvent; -import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FLabel; import forge.toolbox.FNumericTextField; import forge.toolbox.FOptionPane; @@ -186,17 +184,9 @@ public class NewQuestScreen extends FScreen { private final FCheckBox cbCommander = scroller.add(new FCheckBox(Forge.getLocalizer().getMessage("rbCommanderSubformat"))); private final FLabel btnEmbark = add(new FLabel.ButtonBuilder() - .font(FSkinFont.get(22)).text(Forge.getLocalizer().getMessage("lblEmbark")).icon(FSkinImage.QUEST_ZEP).command(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - //create new quest in game thread so option panes can wait for input - ThreadUtil.invokeInGameThread(new Runnable() { - @Override - public void run() { - newQuest(); - } - }); - } + .font(FSkinFont.get(22)).text(Forge.getLocalizer().getMessage("lblEmbark")).icon(FSkinImage.QUEST_ZEP).command(event -> { + //create new quest in game thread so option panes can wait for input + ThreadUtil.invokeInGameThread(() -> newQuest()); }).build()); public NewQuestScreen() { @@ -209,24 +199,18 @@ public class NewQuestScreen extends FScreen { cbxStartingPool.addItem(StartingPoolType.DraftDeck); cbxStartingPool.addItem(StartingPoolType.SealedDeck); cbxStartingPool.addItem(StartingPoolType.Cube); - cbxStartingPool.setChangedHandler(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - updateStartingPoolOptions(); - scroller.revalidate(); - } + cbxStartingPool.setChangedHandler(event -> { + updateStartingPoolOptions(); + scroller.revalidate(); }); cbxPrizedCards.addItem(Forge.getLocalizer().getMessage("lblSameAsStartingPool")); cbxPrizedCards.addItem(StartingPoolType.Complete); cbxPrizedCards.addItem(StartingPoolType.Sanctioned); cbxPrizedCards.addItem(StartingPoolType.Casual); - cbxPrizedCards.setChangedHandler(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - updatePrizeOptions(); - scroller.revalidate(); - } + cbxPrizedCards.setChangedHandler(event -> { + updatePrizeOptions(); + scroller.revalidate(); }); for (GameFormat gf : FModel.getFormats().getSanctionedList()) { @@ -243,18 +227,15 @@ public class NewQuestScreen extends FScreen { numberOfBoostersField.setEnabled(false); @SuppressWarnings("serial") - UiCommand colorBoxEnabler = new UiCommand() { - @Override - public void run() { - cbBlack.setEnabled(radBalanced.isSelected()); - cbBlue.setEnabled(radBalanced.isSelected()); - cbGreen.setEnabled(radBalanced.isSelected()); - cbRed.setEnabled(radBalanced.isSelected()); - cbWhite.setEnabled(radBalanced.isSelected()); - cbColorless.setEnabled(radBalanced.isSelected()); - cbIncludeArtifacts.setEnabled(!radSurpriseMe.isSelected()); - numberOfBoostersField.setEnabled(radBoosters.isSelected()); - } + UiCommand colorBoxEnabler = () -> { + cbBlack.setEnabled(radBalanced.isSelected()); + cbBlue.setEnabled(radBalanced.isSelected()); + cbGreen.setEnabled(radBalanced.isSelected()); + cbRed.setEnabled(radBalanced.isSelected()); + cbWhite.setEnabled(radBalanced.isSelected()); + cbColorless.setEnabled(radBalanced.isSelected()); + cbIncludeArtifacts.setEnabled(!radSurpriseMe.isSelected()); + numberOfBoostersField.setEnabled(radBoosters.isSelected()); }; radBalanced.setCommand(colorBoxEnabler); @@ -268,12 +249,7 @@ public class NewQuestScreen extends FScreen { // Default to 'Main world' cbxStartingWorld.setSelectedItem(FModel.getWorlds().get("Main world")); - cbxStartingWorld.setChangedHandler(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - updateEnabledFormats(); - } - }); + cbxStartingWorld.setChangedHandler(event -> updateEnabledFormats()); updateStartingPoolOptions(); updatePrizeOptions(); @@ -298,59 +274,44 @@ public class NewQuestScreen extends FScreen { unselectableSets.add("ARC"); unselectableSets.add("PC2"); - btnSelectFormat.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - ArchivedFormatSelect archivedFormatSelect = new ArchivedFormatSelect(); - archivedFormatSelect.setOnCloseCallBack(new Runnable() { - @Override - public void run() { - customFormatCodes.clear(); - btnSelectFormat.setText(archivedFormatSelect.getSelectedFormat().getName()); - List setsToAdd = archivedFormatSelect.getSelectedFormat().getAllowedSetCodes(); - for (String setName:setsToAdd){ - if(!unselectableSets.contains(setName)){ - customFormatCodes.add(setName); - } - } + btnSelectFormat.setCommand(event -> { + ArchivedFormatSelect archivedFormatSelect = new ArchivedFormatSelect(); + archivedFormatSelect.setOnCloseCallBack(() -> { + customFormatCodes.clear(); + btnSelectFormat.setText(archivedFormatSelect.getSelectedFormat().getName()); + List setsToAdd = archivedFormatSelect.getSelectedFormat().getAllowedSetCodes(); + for (String setName:setsToAdd){ + if(!unselectableSets.contains(setName)){ + customFormatCodes.add(setName); } - }); - Forge.openScreen(archivedFormatSelect); - } + } + }); + Forge.openScreen(archivedFormatSelect); }); - btnPrizeSelectFormat.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - ArchivedFormatSelect archivedFormatSelect = new ArchivedFormatSelect(); - archivedFormatSelect.setOnCloseCallBack(new Runnable() { - @Override - public void run() { - customPrizeFormatCodes.clear(); - btnPrizeSelectFormat.setText(archivedFormatSelect.getSelectedFormat().getName()); - List setsToAdd = archivedFormatSelect.getSelectedFormat().getAllowedSetCodes(); - for (String setName:setsToAdd){ - if(!unselectableSets.contains(setName)){ - customPrizeFormatCodes.add(setName); - } - } + btnPrizeSelectFormat.setCommand(event -> { + ArchivedFormatSelect archivedFormatSelect = new ArchivedFormatSelect(); + archivedFormatSelect.setOnCloseCallBack(() -> { + customPrizeFormatCodes.clear(); + btnPrizeSelectFormat.setText(archivedFormatSelect.getSelectedFormat().getName()); + List setsToAdd = archivedFormatSelect.getSelectedFormat().getAllowedSetCodes(); + for (String setName:setsToAdd){ + if(!unselectableSets.contains(setName)){ + customPrizeFormatCodes.add(setName); } - }); - Forge.openScreen(archivedFormatSelect); - } + } + }); + Forge.openScreen(archivedFormatSelect); }); // Fantasy box enabled by Default cbFantasy.setSelected(true); cbFantasy.setEnabled(true); cbCommander.setSelected(false); - cbCommander.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - if (!isCommander()) - return; - cbxStartingWorld.setSelectedItem(FModel.getWorlds().get("Random Commander")); - } + cbCommander.setCommand(event -> { + if (!isCommander()) + return; + cbxStartingWorld.setSelectedItem(FModel.getWorlds().get("Random Commander")); }); } @@ -644,30 +605,22 @@ public class NewQuestScreen extends FScreen { } private void startNewQuest(final String questName, final GameFormat fmtPrizes, final Deck dckStartPool, final GameFormat fmtStartPool) { - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblCreatingNewQuest"), new Runnable() { - @Override - public void run() { - final QuestMode mode = isFantasy() ? QuestMode.Fantasy : QuestMode.Classic; - final StartingPoolPreferences userPrefs = - new StartingPoolPreferences(getPoolType(), getPreferredColors(), cbIncludeArtifacts.isSelected(), startWithCompleteSet(), allowDuplicateCards(), numberOfBoostersField.getValue()); - QuestController qc = FModel.getQuest(); + FThreads.invokeInEdtLater(() -> LoadingOverlay.show(Forge.getLocalizer().getMessage("lblCreatingNewQuest"), true, () -> { + final QuestMode mode = isFantasy() ? QuestMode.Fantasy : QuestMode.Classic; + final StartingPoolPreferences userPrefs = + new StartingPoolPreferences(getPoolType(), getPreferredColors(), cbIncludeArtifacts.isSelected(), startWithCompleteSet(), allowDuplicateCards(), numberOfBoostersField.getValue()); + QuestController qc = FModel.getQuest(); - DeckConstructionRules dcr = isCommander() ? DeckConstructionRules.Commander: DeckConstructionRules.Default; + DeckConstructionRules dcr = isCommander() ? DeckConstructionRules.Commander: DeckConstructionRules.Default; - qc.newGame(questName, getSelectedDifficulty(), mode, fmtPrizes, isUnlockSetsAllowed(), dckStartPool, fmtStartPool, getStartingWorldName(), userPrefs, dcr); - qc.save(); + qc.newGame(questName, getSelectedDifficulty(), mode, fmtPrizes, isUnlockSetsAllowed(), dckStartPool, fmtStartPool, getStartingWorldName(), userPrefs, dcr); + qc.save(); - // Save in preferences. - FModel.getQuestPreferences().setPref(QPref.CURRENT_QUEST, questName + ".dat"); - FModel.getQuestPreferences().save(); + // Save in preferences. + FModel.getQuestPreferences().setPref(QPref.CURRENT_QUEST, questName + ".dat"); + FModel.getQuestPreferences().save(); - QuestMenu.launchQuestMode(LaunchReason.NewQuest, isCommander()); //launch quest mode for new quest - } - }); - } - }); + QuestMenu.launchQuestMode(LaunchReason.NewQuest, isCommander()); //launch quest mode for new quest + })); } } diff --git a/forge-gui-mobile/src/forge/screens/quest/QuestDuelsScreen.java b/forge-gui-mobile/src/forge/screens/quest/QuestDuelsScreen.java index 52a6757d73a..8560f42ec61 100644 --- a/forge-gui-mobile/src/forge/screens/quest/QuestDuelsScreen.java +++ b/forge-gui-mobile/src/forge/screens/quest/QuestDuelsScreen.java @@ -12,8 +12,6 @@ import forge.gui.FThreads; import forge.gui.interfaces.IButton; import forge.model.FModel; import forge.screens.LoadingOverlay; -import forge.toolbox.FEvent; -import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FLabel; public class QuestDuelsScreen extends QuestLaunchScreen { @@ -33,12 +31,7 @@ public class QuestDuelsScreen extends QuestLaunchScreen { public QuestDuelsScreen() { super(); - pnlDuels.setActivateHandler(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - startMatch(); - } - }); + pnlDuels.setActivateHandler(event -> startMatch()); } @Override @@ -74,23 +67,15 @@ public class QuestDuelsScreen extends QuestLaunchScreen { } private void generateDuels() { - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingCurrentQuest"), new Runnable() { - @Override - public void run() { - pnlDuels.clear(); - List duels = FModel.getQuest().getDuelsManager().generateDuels(); - if (duels != null) { - for (QuestEventDuel duel : duels) { - pnlDuels.add(new QuestEventPanel(duel, pnlDuels)); - } - } - pnlDuels.revalidate(); - } - }); + FThreads.invokeInEdtLater(() -> LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingCurrentQuest"), true, () -> { + pnlDuels.clear(); + List duels = FModel.getQuest().getDuelsManager().generateDuels(); + if (duels != null) { + for (QuestEventDuel duel : duels) { + pnlDuels.add(new QuestEventPanel(duel, pnlDuels)); + } } - }); + pnlDuels.revalidate(); + })); } } diff --git a/forge-gui-mobile/src/forge/screens/quest/QuestLaunchScreen.java b/forge-gui-mobile/src/forge/screens/quest/QuestLaunchScreen.java index bc1f77da627..ea2169a9f83 100644 --- a/forge-gui-mobile/src/forge/screens/quest/QuestLaunchScreen.java +++ b/forge-gui-mobile/src/forge/screens/quest/QuestLaunchScreen.java @@ -22,23 +22,10 @@ public abstract class QuestLaunchScreen extends LaunchScreen { @Override protected void startMatch() { - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { - if (QuestUtil.canStartGame()) { - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), new Runnable() { - @Override - public void run() { - QuestUtil.finishStartingGame(); - } - }); - } - }); - return; - } + FThreads.invokeInBackgroundThread(() -> { + if (QuestUtil.canStartGame()) { + FThreads.invokeInEdtLater(() -> LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingNewGame"), true, () -> QuestUtil.finishStartingGame())); + return; } }); } diff --git a/forge-gui-mobile/src/forge/screens/quest/QuestMenu.java b/forge-gui-mobile/src/forge/screens/quest/QuestMenu.java index e2d6c1c7b9a..67ca2c6eba6 100644 --- a/forge-gui-mobile/src/forge/screens/quest/QuestMenu.java +++ b/forge-gui-mobile/src/forge/screens/quest/QuestMenu.java @@ -26,8 +26,6 @@ import forge.screens.LoadingOverlay; import forge.screens.home.HomeScreen; import forge.screens.home.LoadGameMenu.LoadGameScreen; import forge.screens.home.NewGameMenu.NewGameScreen; -import forge.toolbox.FEvent; -import forge.toolbox.FEvent.FEventHandler; import forge.util.ThreadUtil; public class QuestMenu extends FPopupMenu implements IVQuestStats { @@ -42,88 +40,28 @@ public class QuestMenu extends FPopupMenu implements IVQuestStats { private static final QuestStatsScreen statsScreen = new QuestStatsScreen(); private static final QuestTournamentsScreen tournamentsScreen = new QuestTournamentsScreen(); - private static final FMenuItem duelsItem = new FMenuItem(Forge.getLocalizer().getMessage("lblDuels"), FSkinImage.QUEST_BIG_SWORD, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(duelsScreen); - } + private static final FMenuItem duelsItem = new FMenuItem(Forge.getLocalizer().getMessage("lblDuels"), FSkinImage.QUEST_BIG_SWORD, event -> setCurrentScreen(duelsScreen)); + private static final FMenuItem challengesItem = new FMenuItem(Forge.getLocalizer().getMessage("lblChallenges"), FSkinImage.QUEST_HEART, event -> setCurrentScreen(challengesScreen)); + private static final FMenuItem tournamentsItem = new FMenuItem(Forge.getLocalizer().getMessage("lblTournaments"), FSkinImage.QUEST_BIG_SHIELD, event -> setCurrentScreen(tournamentsScreen)); + private static final FMenuItem decksItem = new FMenuItem(Forge.getLocalizer().getMessage("lblQuestDecks"), FSkinImage.QUEST_BIG_BAG, event -> setCurrentScreen(decksScreen)); + private static final FMenuItem spellShopItem = new FMenuItem(Forge.getLocalizer().getMessage("lblSpellShop"), FSkinImage.QUEST_BOOK, event -> setCurrentScreen(spellShopScreen)); + private static final FMenuItem bazaarItem = new FMenuItem(Forge.getLocalizer().getMessage("lblBazaar"), FSkinImage.QUEST_BOTTLES, event -> setCurrentScreen(bazaarScreen)); + private static final FMenuItem statsItem = new FMenuItem(Forge.getLocalizer().getMessage("lblStatistics"), FSkinImage.MENU_STATS, event -> setCurrentScreen(statsScreen)); + private static final FMenuItem unlockSetsItem = new FMenuItem(Forge.getLocalizer().getMessage("btnUnlockSets"), FSkinImage.QUEST_MAP, event -> { + //invoke in background thread so prompts can work + ThreadUtil.invokeInGameThread(() -> { + QuestUtil.chooseAndUnlockEdition(); + FThreads.invokeInEdtLater(() -> updateCurrentQuestScreen()); + }); }); - private static final FMenuItem challengesItem = new FMenuItem(Forge.getLocalizer().getMessage("lblChallenges"), FSkinImage.QUEST_HEART, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(challengesScreen); - } - }); - private static final FMenuItem tournamentsItem = new FMenuItem(Forge.getLocalizer().getMessage("lblTournaments"), FSkinImage.QUEST_BIG_SHIELD, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(tournamentsScreen); - } - }); - private static final FMenuItem decksItem = new FMenuItem(Forge.getLocalizer().getMessage("lblQuestDecks"), FSkinImage.QUEST_BIG_BAG, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(decksScreen); - } - }); - private static final FMenuItem spellShopItem = new FMenuItem(Forge.getLocalizer().getMessage("lblSpellShop"), FSkinImage.QUEST_BOOK, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(spellShopScreen); - } - }); - private static final FMenuItem bazaarItem = new FMenuItem(Forge.getLocalizer().getMessage("lblBazaar"), FSkinImage.QUEST_BOTTLES, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(bazaarScreen); - } - }); - private static final FMenuItem statsItem = new FMenuItem(Forge.getLocalizer().getMessage("lblStatistics"), FSkinImage.MENU_STATS, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(statsScreen); - } - }); - private static final FMenuItem unlockSetsItem = new FMenuItem(Forge.getLocalizer().getMessage("btnUnlockSets"), FSkinImage.QUEST_MAP, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - ThreadUtil.invokeInGameThread(new Runnable() { //invoke in background thread so prompts can work - @Override - public void run() { - QuestUtil.chooseAndUnlockEdition(); - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - updateCurrentQuestScreen(); - } - }); - } - }); - } - }); - private static final FMenuItem travelItem = new FMenuItem(Forge.getLocalizer().getMessage("btnTravel"), FSkinImage.QUEST_MAP, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - ThreadUtil.invokeInGameThread(new Runnable() { //invoke in background thread so prompts can work - @Override - public void run() { - QuestUtil.travelWorld(); - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - updateCurrentQuestScreen(); - } - }); - } - }); - } - }); - private static final FMenuItem prefsItem = new FMenuItem(Forge.getLocalizer().getMessage("Preferences"), Forge.hdbuttons ? FSkinImage.HDPREFERENCE : FSkinImage.SETTINGS, new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - setCurrentScreen(prefsScreen); - } + private static final FMenuItem travelItem = new FMenuItem(Forge.getLocalizer().getMessage("btnTravel"), FSkinImage.QUEST_MAP, event -> { + //invoke in background thread so prompts can work + ThreadUtil.invokeInGameThread(() -> { + QuestUtil.travelWorld(); + FThreads.invokeInEdtLater(() -> updateCurrentQuestScreen()); + }); }); + private static final FMenuItem prefsItem = new FMenuItem(Forge.getLocalizer().getMessage("Preferences"), Forge.hdbuttons ? FSkinImage.HDPREFERENCE : FSkinImage.SETTINGS, event -> setCurrentScreen(prefsScreen)); static { statsScreen.addTournamentResultsLabels(tournamentsScreen); @@ -159,12 +97,7 @@ public class QuestMenu extends FPopupMenu implements IVQuestStats { static { //the first time quest mode is launched, add button for it if in Landscape mode if (Forge.isLandscapeMode()) { - HomeScreen.instance.addButtonForMode("-"+Forge.getLocalizer().getMessage("lblQuestMode"), new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - launchQuestMode(LaunchReason.StartQuestMode, HomeScreen.instance.getQuestCommanderMode()); - } - }); + HomeScreen.instance.addButtonForMode("-"+Forge.getLocalizer().getMessage("lblQuestMode"), event -> launchQuestMode(LaunchReason.StartQuestMode, HomeScreen.instance.getQuestCommanderMode())); } } @@ -190,40 +123,36 @@ public class QuestMenu extends FPopupMenu implements IVQuestStats { final String questname = FModel.getQuestPreferences().getPref(QPref.CURRENT_QUEST); final File data = new File(dirQuests.getPath(), questname); if (data.exists()) { - LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingCurrentQuest"), new Runnable() { - @Override - @SuppressWarnings("unchecked") - public void run() { - try { - FModel.getQuest().load(QuestDataIO.loadData(data)); - } catch (IOException e) { - System.err.println(String.format("Failed to load quest '%s'", questname)); - // Failed to load last quest, don't continue with quest loading stuff - return; - } + LoadingOverlay.show(Forge.getLocalizer().getMessage("lblLoadingCurrentQuest"), true, () -> { + try { + FModel.getQuest().load(QuestDataIO.loadData(data)); + } catch (IOException e) { + System.err.println(String.format("Failed to load quest '%s'", questname)); + // Failed to load last quest, don't continue with quest loading stuff + return; + } - ((DeckController)EditorType.Quest.getController()).setRootFolder(FModel.getQuest().getMyDecks()); - ((DeckController)EditorType.QuestDraft.getController()).setRootFolder(FModel.getQuest().getDraftDecks()); - if (reason == LaunchReason.StartQuestMode) { - if (QuestUtil.getCurrentDeck() == null) { - Forge.openScreen(decksScreen); //if quest doesn't have a deck specified, open decks screen by default - } - else { - Forge.openScreen(duelsScreen); //TODO: Consider opening most recent quest view - } + ((DeckController)EditorType.Quest.getController()).setRootFolder(FModel.getQuest().getMyDecks()); + ((DeckController)EditorType.QuestDraft.getController()).setRootFolder(FModel.getQuest().getDraftDecks()); + if (reason == LaunchReason.StartQuestMode) { + if (QuestUtil.getCurrentDeck() == null) { + Forge.openScreen(decksScreen); //if quest doesn't have a deck specified, open decks screen by default } else { - duelsScreen.update(); - challengesScreen.update(); - tournamentsScreen.update(); - decksScreen.refreshDecks(); - Forge.openScreen(duelsScreen); - if (reason == LaunchReason.NewQuest) { - LoadGameScreen.QuestMode.setAsBackScreen(true); - } + Forge.openScreen(duelsScreen); //TODO: Consider opening most recent quest view } - HomeScreen.instance.updateQuestWorld(FModel.getQuest().getWorld() == null ? "" : FModel.getQuest().getWorld().toString()); } + else { + duelsScreen.update(); + challengesScreen.update(); + tournamentsScreen.update(); + decksScreen.refreshDecks(); + Forge.openScreen(duelsScreen); + if (reason == LaunchReason.NewQuest) { + LoadGameScreen.QuestMode.setAsBackScreen(true); + } + } + HomeScreen.instance.updateQuestWorld(FModel.getQuest().getWorld() == null ? "" : FModel.getQuest().getWorld().toString()); }); return; } diff --git a/forge-gui-mobile/src/forge/screens/quest/QuestSpellShopScreen.java b/forge-gui-mobile/src/forge/screens/quest/QuestSpellShopScreen.java index 50fab6e379d..4cf53a0909f 100644 --- a/forge-gui-mobile/src/forge/screens/quest/QuestSpellShopScreen.java +++ b/forge-gui-mobile/src/forge/screens/quest/QuestSpellShopScreen.java @@ -29,8 +29,6 @@ import forge.model.FModel; import forge.screens.LoadingOverlay; import forge.screens.TabPageScreen; import forge.toolbox.FDisplayObject; -import forge.toolbox.FEvent; -import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FLabel; import forge.toolbox.FTextArea; import forge.toolbox.GuiChoose; @@ -49,36 +47,25 @@ public class QuestSpellShopScreen extends TabPageScreen { inventoryPage = ((InventoryPage)tabPages[1]); btnBuySellMultiple.setVisible(false); //hide unless in multi-select mode - btnBuySellMultiple.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - final SpellShopManager itemManager = ((SpellShopBasePage)getSelectedPage()).itemManager; - final ItemPool items = itemManager.getSelectedItemPool(); + btnBuySellMultiple.setCommand(event -> { + final SpellShopManager itemManager = ((SpellShopBasePage)getSelectedPage()).itemManager; + final ItemPool items = itemManager.getSelectedItemPool(); - if (items.isEmpty()) { - //toggle off multi-select mode if no items selected - itemManager.toggleMultiSelectMode(-1); - return; - } - - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { - if (getSelectedPage() == spellShopPage) { - spellShopPage.activateItems(items); - } - else { - inventoryPage.activateItems(items); - } - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - updateCreditsLabel(); - } - }); - } - }); + if (items.isEmpty()) { + //toggle off multi-select mode if no items selected + itemManager.toggleMultiSelectMode(-1); + return; } + + FThreads.invokeInBackgroundThread(() -> { + if (getSelectedPage() == spellShopPage) { + spellShopPage.activateItems(items); + } + else { + inventoryPage.activateItems(items); + } + FThreads.invokeInEdtLater(() -> updateCreditsLabel()); + }); }); } @@ -190,28 +177,17 @@ public class QuestSpellShopScreen extends TabPageScreen { parentScreen.tabHeader.setVisible(!multiSelectMode); } }); - itemManager.setItemActivateHandler(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - } + itemManager.setItemActivateHandler(event -> { }); itemManager.setContextMenuBuilder(new ContextMenuBuilder() { @Override public void buildMenu(final FDropDownMenu menu, final InventoryItem item) { - menu.addItem(new FMenuItem(getVerb(), getVerbIcon(), new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - activateSelectedItem(); - } - })); + menu.addItem(new FMenuItem(getVerb(), getVerbIcon(), event -> activateSelectedItem())); } }); - itemManager.setSelectionChangedHandler(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - if (itemManager.getMultiSelectMode()) { - parentScreen.updateBuySellButtonCaption(); - } + itemManager.setSelectionChangedHandler(event -> { + if (itemManager.getMultiSelectMode()) { + parentScreen.updateBuySellButtonCaption(); } }); add(lblCredits); @@ -235,19 +211,11 @@ public class QuestSpellShopScreen extends TabPageScreen { if (result == null || result <= 0) { return; } //invoke in background thread so other dialogs can be shown properly - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { - ItemPool items = new ItemPool<>(InventoryItem.class); - items.add(item, result); - activateItems(items); - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - parentScreen.updateCreditsLabel(); - } - }); - } + FThreads.invokeInBackgroundThread(() -> { + ItemPool items = new ItemPool<>(InventoryItem.class); + items.add(item, result); + activateItems(items); + FThreads.invokeInEdtLater(() -> parentScreen.updateCreditsLabel()); }); } }; @@ -290,22 +258,14 @@ public class QuestSpellShopScreen extends TabPageScreen { @Override protected void refresh() { - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - LoadingOverlay.show(Forge.getLocalizer().getInstance().getMessage("lblLoading"), new Runnable() { - @Override - public void run() { - Map colOverrides = new HashMap<>(); - ItemColumn.addColOverride(ItemManagerConfig.SPELL_SHOP, colOverrides, ColumnDef.PRICE, QuestSpellShop.fnPriceCompare, QuestSpellShop.fnPriceGet); - ItemColumn.addColOverride(ItemManagerConfig.SPELL_SHOP, colOverrides, ColumnDef.OWNED, FModel.getQuest().getCards().getFnOwnedCompare(), FModel.getQuest().getCards().getFnOwnedGet()); - itemManager.setup(ItemManagerConfig.SPELL_SHOP, colOverrides); + FThreads.invokeInEdtLater(() -> LoadingOverlay.show(Forge.getLocalizer().getInstance().getMessage("lblLoading"), true, () -> { + Map colOverrides = new HashMap<>(); + ItemColumn.addColOverride(ItemManagerConfig.SPELL_SHOP, colOverrides, ColumnDef.PRICE, QuestSpellShop.fnPriceCompare, QuestSpellShop.fnPriceGet); + ItemColumn.addColOverride(ItemManagerConfig.SPELL_SHOP, colOverrides, ColumnDef.OWNED, FModel.getQuest().getCards().getFnOwnedCompare(), FModel.getQuest().getCards().getFnOwnedGet()); + itemManager.setup(ItemManagerConfig.SPELL_SHOP, colOverrides); - itemManager.setPool(FModel.getQuest().getCards().getShopList()); - } - }); - } - }); + itemManager.setPool(FModel.getQuest().getCards().getShopList()); + })); } @Override @@ -337,47 +297,25 @@ public class QuestSpellShopScreen extends TabPageScreen { private static class InventoryPage extends SpellShopBasePage { protected FLabel lblSellExtras = add(new FLabel.Builder().text(Forge.getLocalizer().getMessage("lblSellAllExtras")) .icon(Forge.hdbuttons ? FSkinImage.HDMINUS : FSkinImage.MINUS).iconScaleFactor(1f).align(Align.right).font(FSkinFont.get(16)) - .command(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - //invoke in background thread so other dialogs can be shown properly - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { + .command(event -> { + //invoke in background thread so other dialogs can be shown properly + FThreads.invokeInBackgroundThread(() -> { QuestSpellShop.sellExtras(parentScreen.spellShopPage.itemManager, itemManager); - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - parentScreen.updateCreditsLabel(); - } - }); - } - }); - } - }).build()); + FThreads.invokeInEdtLater(() -> parentScreen.updateCreditsLabel()); + }); + }).build()); protected FLabel lblSelectAll = add(new FLabel.Builder().text(Forge.getLocalizer().getMessage("lblSelectAllCards")) .icon(Forge.hdbuttons ? FSkinImage.HDSTAR_FILLED : FSkinImage.STAR_FILLED).iconScaleFactor(1f).align(Align.right).font(FSkinFont.get(16)) - .command(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - //invoke in background thread so other dialogs can be shown properly - FThreads.invokeInBackgroundThread(new Runnable() { - @Override - public void run() { - if (!itemManager.getMultiSelectMode()) { - itemManager.toggleMultiSelectMode(0); - } - itemManager.selectAll(); - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - parentScreen.updateCreditsLabel(); - } - }); - } - }); - } + .command(event -> { + //invoke in background thread so other dialogs can be shown properly + FThreads.invokeInBackgroundThread(() -> { + if (!itemManager.getMultiSelectMode()) { + itemManager.toggleMultiSelectMode(0); + } + itemManager.selectAll(); + FThreads.invokeInEdtLater(() -> parentScreen.updateCreditsLabel()); + }); }).build()); private InventoryPage() { diff --git a/forge-gui-mobile/src/forge/screens/quest/QuestTournamentsScreen.java b/forge-gui-mobile/src/forge/screens/quest/QuestTournamentsScreen.java index 710393ab227..98f4a5ac791 100644 --- a/forge-gui-mobile/src/forge/screens/quest/QuestTournamentsScreen.java +++ b/forge-gui-mobile/src/forge/screens/quest/QuestTournamentsScreen.java @@ -34,8 +34,6 @@ import forge.screens.LoadingOverlay; import forge.screens.limited.DraftingProcessScreen; import forge.toolbox.FButton; import forge.toolbox.FContainer; -import forge.toolbox.FEvent; -import forge.toolbox.FEvent.FEventHandler; import forge.toolbox.FLabel; import forge.toolbox.FTextField; import forge.util.Utils; @@ -84,52 +82,21 @@ public class QuestTournamentsScreen extends QuestLaunchScreen implements IQuestT public QuestTournamentsScreen() { super(); controller = new QuestTournamentController(this); - btnSpendToken.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - FThreads.invokeInBackgroundThread(new Runnable() { //must run in background thread to handle alerts - @Override - public void run() { - controller.spendToken(); - } - }); - } + btnSpendToken.setCommand(event -> { + //must run in background thread to handle alerts + FThreads.invokeInBackgroundThread(() -> controller.spendToken()); }); - btnEditDeck.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - editDeck(true); - } - }); - btnLeaveTournament.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - FThreads.invokeInBackgroundThread(new Runnable() { //must run in background thread to handle alerts - @Override - public void run() { - controller.endTournamentAndAwardPrizes(); - } - }); - } + btnEditDeck.setCommand(event -> editDeck(true)); + btnLeaveTournament.setCommand(event -> { + //must run in background thread to handle alerts + FThreads.invokeInBackgroundThread(() -> controller.endTournamentAndAwardPrizes()); }); // TODO: is it possible to somehow reuse the original btnEditDeck/btnLeaveTournament - btnEditDeckInTourn.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - editDeck(true); - } - }); - btnLeaveTournamentInTourn.setCommand(new FEventHandler() { - @Override - public void handleEvent(FEvent e) { - FThreads.invokeInBackgroundThread(new Runnable() { //must run in background thread to handle alerts - @Override - public void run() { - controller.endTournamentAndAwardPrizes(); - } - }); - } + btnEditDeckInTourn.setCommand(event -> editDeck(true)); + btnLeaveTournamentInTourn.setCommand(event -> { + //must run in background thread to handle alerts + FThreads.invokeInBackgroundThread(() -> controller.endTournamentAndAwardPrizes()); }); pnlPrepareDeck.add(btnEditDeck); @@ -173,7 +140,8 @@ public class QuestTournamentsScreen extends QuestLaunchScreen implements IQuestT @Override protected void updateHeaderCaption() { if (mode == Mode.PREPARE_DECK) { - setHeaderCaption(FModel.getQuest().getName() + " - " + getGameType() + "\n" + Forge.getLocalizer().getMessage("lblDraft") + " - " + FModel.getQuest().getAchievements().getCurrentDraft().getTitle()); + String title = FModel.getQuest().getAchievements().getCurrentDraft() == null ? "" : FModel.getQuest().getAchievements().getCurrentDraft().getTitle(); + setHeaderCaption(FModel.getQuest().getName() + " - " + getGameType() + "\n" + Forge.getLocalizer().getMessage("lblDraft") + " - " + title); } else { super.updateHeaderCaption(); @@ -230,17 +198,7 @@ public class QuestTournamentsScreen extends QuestLaunchScreen implements IQuestT @Override public void startDraft(BoosterDraft draft) { - FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - LoadingOverlay.show("Loading Quest Tournament", new Runnable() { - @Override - public void run() { - Forge.openScreen(new DraftingProcessScreen(draft, EditorType.QuestDraft, controller)); - } - }); - } - }); + FThreads.invokeInEdtLater(() -> LoadingOverlay.show("Loading Quest Tournament", true, () -> Forge.openScreen(new DraftingProcessScreen(draft, EditorType.QuestDraft, controller)))); } private Deck getDeck() { @@ -275,22 +233,20 @@ public class QuestTournamentsScreen extends QuestLaunchScreen implements IQuestT return; } - FThreads.invokeInBackgroundThread(new Runnable() { //must run in background thread to handle alerts - @Override - public void run() { - switch (mode) { - case SELECT_TOURNAMENT: - controller.startDraft(); - break; - case PREPARE_DECK: - controller.startTournament(); - break; - case TOURNAMENT_ACTIVE: - controller.startNextMatch(); - break; - default: - break; - } + //must run in background thread to handle alerts + FThreads.invokeInBackgroundThread(() -> { + switch (mode) { + case SELECT_TOURNAMENT: + controller.startDraft(); + break; + case PREPARE_DECK: + controller.startTournament(); + break; + case TOURNAMENT_ACTIVE: + controller.startNextMatch(); + break; + default: + break; } }); } diff --git a/forge-gui-mobile/src/forge/screens/settings/FilesPage.java b/forge-gui-mobile/src/forge/screens/settings/FilesPage.java index 93476fb842d..cbd354505bb 100644 --- a/forge-gui-mobile/src/forge/screens/settings/FilesPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/FilesPage.java @@ -5,7 +5,11 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; +import com.google.common.collect.ImmutableList; +import forge.StaticData; +import forge.gui.FThreads; import forge.gui.GuiBase; +import forge.screens.LoadingOverlay; import org.apache.commons.lang3.StringUtils; import com.badlogic.gdx.utils.Align; @@ -34,6 +38,7 @@ import forge.toolbox.FOptionPane; import forge.toolbox.GuiChoose; import forge.util.Callback; import forge.util.FileUtil; +import org.apache.commons.lang3.tuple.Pair; public class FilesPage extends TabPage { private final FGroupList lstItems = add(new FGroupList<>()); @@ -43,10 +48,45 @@ public class FilesPage extends TabPage { lstItems.setListItemRenderer(new FilesItemRenderer()); + lstItems.addGroup(Forge.getLocalizer().getMessage("lblCardAudit")); lstItems.addGroup(Forge.getLocalizer().getMessage("ContentDownloaders")); lstItems.addGroup(Forge.getLocalizer().getMessage("lblStorageLocations")); //lstItems.addGroup("Data Import"); + //Auditer + lstItems.addItem(new Extra(Forge.getLocalizer().getMessage("btnListImageData"), Forge.getLocalizer().getMessage("lblListImageData")) { + @Override + public void select() { + FThreads.invokeInEdtLater(() -> LoadingOverlay.show(Forge.getLocalizer().getMessage("lblProcessingCards"), true, () -> { + StringBuffer nifSB = new StringBuffer(); // NO IMAGE FOUND BUFFER + StringBuffer cniSB = new StringBuffer(); // CARD NOT IMPLEMENTED BUFFER + + nifSB.append("\n\n-------------------\n"); + nifSB.append("NO IMAGE FOUND LIST\n"); + nifSB.append("-------------------\n\n"); + + cniSB.append("\n\n-------------------\n"); + cniSB.append("UNIMPLEMENTED CARD LIST\n"); + cniSB.append("-------------------\n\n"); + + Pair totalAudit = StaticData.instance().audit(nifSB, cniSB); + String msg = nifSB.toString(); + String title = "Missing images: " + totalAudit.getLeft() + "\nUnimplemented cards: " + totalAudit.getRight(); + FOptionPane.showOptionDialog(msg, title, FOptionPane.INFORMATION_ICON, ImmutableList.of(Forge.getLocalizer().getMessage("lblCopy"), Forge.getLocalizer().getMessage("lblClose")), -1, new Callback() { + @Override + public void run(Integer result) { + switch (result) { + case 0: + Forge.getClipboard().setContents(msg); + break; + default: + break; + } + } + }); + })); + } + }, 0); //content downloaders lstItems.addItem(new ContentDownloader(Forge.getLocalizer().getMessage("btnDownloadPics"), Forge.getLocalizer().getMessage("lblDownloadPics")) { @@ -54,35 +94,35 @@ public class FilesPage extends TabPage { protected GuiDownloadService createService() { return new GuiDownloadPicturesLQ(); } - }, 0); + }, 1); lstItems.addItem(new ContentDownloader(Forge.getLocalizer().getMessage("btnDownloadSetPics"), Forge.getLocalizer().getMessage("lblDownloadSetPics")) { @Override protected GuiDownloadService createService() { return new GuiDownloadSetPicturesLQ(); } - }, 0); + }, 1); lstItems.addItem(new ContentDownloader(Forge.getLocalizer().getMessage("btnDownloadQuestImages"), Forge.getLocalizer().getMessage("lblDownloadQuestImages")) { @Override protected GuiDownloadService createService() { return new GuiDownloadQuestImages(); } - }, 0); + }, 1); lstItems.addItem(new ContentDownloader(Forge.getLocalizer().getMessage("btnDownloadAchievementImages"), Forge.getLocalizer().getMessage("lblDownloadAchievementImages")) { @Override protected GuiDownloadService createService() { return new GuiDownloadAchievementImages(); } - }, 0); + }, 1); lstItems.addItem(new ContentDownloader(Forge.getLocalizer().getMessage("btnDownloadPrices"), Forge.getLocalizer().getMessage("lblDownloadPrices")) { @Override protected GuiDownloadService createService() { return new GuiDownloadPrices(); } - }, 0); + }, 1); lstItems.addItem(new ContentDownloader(Forge.getLocalizer().getMessage("btnDownloadSkins"), Forge.getLocalizer().getMessage("lblDownloadSkins")) { @Override @@ -93,7 +133,7 @@ public class FilesPage extends TabPage { protected void finishCallback() { SettingsScreen.getSettingsScreen().getSettingsPage().refreshSkinsList(); } - }, 0); + }, 1); lstItems.addItem(new OptionContentDownloader(Forge.getLocalizer().getMessage("btnDownloadCJKFonts"), Forge.getLocalizer().getMessage("lblDownloadCJKFonts"), Forge.getLocalizer().getMessage("lblDownloadCJKFontPrompt")) { @@ -118,7 +158,7 @@ public class FilesPage extends TabPage { protected void finishCallback() { SettingsScreen.getSettingsScreen().getSettingsPage().refreshCJKFontsList(); } - }, 0); + }, 1); //storage locations final StorageOption cardPicsOption = new StorageOption(Forge.getLocalizer().getMessage("lblCardPicsLocation"), ForgeProfileProperties.getCardPicsDir()) { @Override @@ -141,7 +181,7 @@ public class FilesPage extends TabPage { //ensure decks option is updated if needed decksOption.updateDir(ForgeProfileProperties.getDecksDir()); } - }, 1); + }, 2); lstItems.addItem(new StorageOption(Forge.getLocalizer().getMessage("lblImageCacheLocation"), ForgeProfileProperties.getCacheDir()) { @Override protected void onDirectoryChanged(String newDir) { @@ -150,9 +190,9 @@ public class FilesPage extends TabPage { //ensure card pics option is updated if needed cardPicsOption.updateDir(ForgeProfileProperties.getCardPicsDir()); } - }, 1); - lstItems.addItem(cardPicsOption, 1); - lstItems.addItem(decksOption, 1); + }, 2); + lstItems.addItem(cardPicsOption, 2); + lstItems.addItem(decksOption, 2); } } @@ -201,7 +241,19 @@ public class FilesPage extends TabPage { g.drawText(value.description, SettingsScreen.DESC_FONT, SettingsScreen.DESC_COLOR, x, y + h, w, totalHeight - h + SettingsScreen.getInsets(w), true, Align.left, false); } } + private abstract class Extra extends FilesItem { + Extra(String label0, String description0) { + super(label0, description0); + } + @Override + public void select() { + + } + + protected void finishCallback() { + } + } private abstract class ContentDownloader extends FilesItem { ContentDownloader(String label0, String description0) { super(label0, description0); @@ -239,7 +291,7 @@ public class FilesPage extends TabPage { @Override public void run(String result) { final String url = categories.get(result); - final String name = url.substring(url.lastIndexOf("/") + 1); + final String name = url.substring(url.lastIndexOf("/") + 2); new GuiDownloader(new GuiDownloadZipService(name, name, url, ForgeConstants.FONTS_DIR, null, null), new Callback() { @Override public void run(Boolean finished) { diff --git a/forge-gui/release-files/CONTRIBUTORS.txt b/forge-gui/release-files/CONTRIBUTORS.txt index 8c948dd7771..3f2b373d0d7 100644 --- a/forge-gui/release-files/CONTRIBUTORS.txt +++ b/forge-gui/release-files/CONTRIBUTORS.txt @@ -61,5 +61,6 @@ twosat Xyx Zimtente Zuchinni +XavierMD (If you think your name should be on this list, add it with your next contribution) diff --git a/forge-gui/res/cardsfolder/a/aerial_predation.txt b/forge-gui/res/cardsfolder/a/aerial_predation.txt index 229268eb642..742936cffc4 100644 --- a/forge-gui/res/cardsfolder/a/aerial_predation.txt +++ b/forge-gui/res/cardsfolder/a/aerial_predation.txt @@ -1,6 +1,7 @@ Name:Aerial Predation ManaCost:2 G Types:Instant -A:SP$ Destroy | Cost$ 2 G | ValidTgts$ Creature.withFlying | TgtPrompt$ Select target creature with flying | SubAbility$ NaturalLife | SpellDescription$ Destroy target creature with flying. You gain 2 life -SVar:NaturalLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 +A:SP$ Destroy | ValidTgts$ Creature.withFlying | TgtPrompt$ Select target creature with flying | SubAbility$ DBGainLife | SpellDescription$ Destroy target creature with flying. You gain 2 life. +SVar:DBGainLife:DB$ GainLife | LifeAmount$ 2 +DeckHas:Ability$LifeGain Oracle:Destroy target creature with flying. You gain 2 life. diff --git a/forge-gui/res/cardsfolder/a/audacious_swap.txt b/forge-gui/res/cardsfolder/a/audacious_swap.txt index 1a6033ff4e8..c71b1d856b8 100644 --- a/forge-gui/res/cardsfolder/a/audacious_swap.txt +++ b/forge-gui/res/cardsfolder/a/audacious_swap.txt @@ -5,7 +5,7 @@ K:Casualty:2 A:SP$ ChangeZone | ValidTgts$ Permanent.nonEnchantment | TgtPrompt$ Select target nonenchantment permanent | Origin$ Battlefield | Destination$ Library | Shuffle$ True | SubAbility$ DBExile | SpellDescription$ The owner of target nonenchantment permanent shuffles it into their library, SVar:DBExile:DB$ Dig | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | Defined$ TargetedOwner | RememberChanged$ True | SubAbility$ DBPutLand | SpellDescription$ then exiles the top card of their library. SVar:DBPutLand:DB$ ChangeZone | ConditionDefined$ Remembered | ConditionPresent$ Land | Defined$ Remembered | DefinedDesc$ it | Origin$ Exile | Destination$ Battlefield | ForgetChanged$ True | SubAbility$ DBCast | SpellDescription$ If it's a land card, they put it onto the battlefield. -SVar:DBCast:DB$ Play | Defined$ Remembered | DefinedDesc$ it | ValidSA$ Spell | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup | SpellDescription$ Otherwise, they may cast it without paying its mana cost. +SVar:DBCast:DB$ Play | Defined$ Remembered | DefinedDesc$ it | ValidSA$ Spell | Controller$ RememberedOwner | WithoutManaCost$ True | Optional$ True | SubAbility$ DBCleanup | SpellDescription$ Otherwise, they may cast it without paying its mana cost. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Ability$Sacrifice Oracle:Casualty 2 (As you cast this spell, you may sacrifice a creature with power 2 or greater. When you do, copy this spell and you may choose a new target for the copy.)\nThe owner of target nonenchantment permanent shuffles it into their library, then exiles the top card of their library. If it's a land card, they put it onto the battlefield. Otherwise, they may cast it without paying its mana cost. diff --git a/forge-gui/res/cardsfolder/b/boreal_outrider.txt b/forge-gui/res/cardsfolder/b/boreal_outrider.txt index 21b4c26cc2f..8e5f58d2fb3 100644 --- a/forge-gui/res/cardsfolder/b/boreal_outrider.txt +++ b/forge-gui/res/cardsfolder/b/boreal_outrider.txt @@ -3,7 +3,7 @@ ManaCost:2 G Types:Snow Creature Elf Warrior PT:3/2 T:Mode$ SpellCast | ValidCard$ Creature | ValidActivatingPlayer$ You | Execute$ TrigEffect | TriggerZones$ Battlefield | SnowSpentForCardsColor$ True | TriggerDescription$ Whenever you cast a creature spell, if {S} of any of that spell's colors was spent to cast it, that creature enters the battlefield with an additional +1/+1 counter on it. ({S} is mana from a snow source.) -SVar:TrigEffect:DB$ Effect | RememberObjects$ TriggeredCard | ReplacementEffects$ ETBCreat +SVar:TrigEffect:DB$ Effect | RememberObjects$ TriggeredCard | ReplacementEffects$ ETBCreat | ExileOnMoved$ Stack SVar:ETBCreat:Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | ReplaceWith$ DBPutP1P1 | ReplacementResult$ Updated | Description$ That creature enters the battlefield with an additional +1/+1 counter on it. SVar:DBPutP1P1:DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | ETB$ True | CounterNum$ 1 | SubAbility$ DBExile SVar:DBExile:DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile diff --git a/forge-gui/res/cardsfolder/b/bounteous_kirin.txt b/forge-gui/res/cardsfolder/b/bounteous_kirin.txt index 0a4a8f13df6..52bb1fe44d7 100644 --- a/forge-gui/res/cardsfolder/b/bounteous_kirin.txt +++ b/forge-gui/res/cardsfolder/b/bounteous_kirin.txt @@ -5,5 +5,5 @@ PT:4/4 K:Flying T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigGainLife | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, you may gain life equal to that spell's mana value. SVar:TrigGainLife:DB$ GainLife | Defined$ You | LifeAmount$ X -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI Oracle:Flying\nWhenever you cast a Spirit or Arcane spell, you may gain life equal to that spell's mana value. diff --git a/forge-gui/res/cardsfolder/c/cathedral_sanctifier.txt b/forge-gui/res/cardsfolder/c/cathedral_sanctifier.txt index ee333ab61c3..30ab9315729 100644 --- a/forge-gui/res/cardsfolder/c/cathedral_sanctifier.txt +++ b/forge-gui/res/cardsfolder/c/cathedral_sanctifier.txt @@ -4,4 +4,5 @@ Types:Creature Human Cleric PT:1/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigGainLife | TriggerDescription$ When CARDNAME enters the battlefield, you gain 3 life. SVar:TrigGainLife:DB$ GainLife | LifeAmount$ 3 +DeckHas:Ability$LifeGain Oracle:When Cathedral Sanctifier enters the battlefield, you gain 3 life. diff --git a/forge-gui/res/cardsfolder/c/celestial_kirin.txt b/forge-gui/res/cardsfolder/c/celestial_kirin.txt index 9e5f488521c..0887b8c9fa4 100644 --- a/forge-gui/res/cardsfolder/c/celestial_kirin.txt +++ b/forge-gui/res/cardsfolder/c/celestial_kirin.txt @@ -5,7 +5,7 @@ PT:3/3 K:Flying T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDestroyAll | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, destroy all permanents with that spell's mana value. SVar:TrigDestroyAll:DB$ DestroyAll | ValidCards$ Permanent.cmcEQX -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI AI:RemoveDeck:All DeckHints:Type$Spirit|Arcane AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/c/cloudhoof_kirin.txt b/forge-gui/res/cardsfolder/c/cloudhoof_kirin.txt index 7679ab31ae6..f47f1aba9bc 100644 --- a/forge-gui/res/cardsfolder/c/cloudhoof_kirin.txt +++ b/forge-gui/res/cardsfolder/c/cloudhoof_kirin.txt @@ -5,5 +5,5 @@ PT:4/4 K:Flying T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigMill | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, you may have target player mill X cards, where X is that spell's mana value. SVar:TrigMill:DB$ Mill | ValidTgts$ Player | TgtPrompt$ Select target player | NumCards$ X -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI Oracle:Flying\nWhenever you cast a Spirit or Arcane spell, you may have target player mill X cards, where X is that spell's mana value. 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 23a359c0211..9f187d653db 100644 --- a/forge-gui/res/cardsfolder/c/colfenor_the_last_yew.txt +++ b/forge-gui/res/cardsfolder/c/colfenor_the_last_yew.txt @@ -4,10 +4,9 @@ Types:Legendary Creature Treefolk Shaman PT:3/7 K:Vigilance K:Reach -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.Other+YouCtrl | RememberTriggeringCard$ True | 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. +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ 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. T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChange | Secondary$ True | 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+IsNotRemembered+toughnessLTX+YouOwn | TgtPrompt$ Select up to one other target creature card with lesser toughness from your graveyard to return to your hand | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +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: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/d/deekah_fractal_theorist.txt b/forge-gui/res/cardsfolder/d/deekah_fractal_theorist.txt index 604f7fcb87f..a73a125f01e 100644 --- a/forge-gui/res/cardsfolder/d/deekah_fractal_theorist.txt +++ b/forge-gui/res/cardsfolder/d/deekah_fractal_theorist.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Human Wizard PT:3/3 T:Mode$ SpellCastOrCopy | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Magecraft — Whenever you cast or copy an instant or sorcery spell, create a 0/0 green and blue Fractal creature token. Put X +1/+1 counters on it, where X is that spell's mana value. SVar:TrigToken:DB$ Token | TokenScript$ gu_0_0_fractal | RememberTokens$ True | SubAbility$ DBPutCounter -SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterType$ P1P1 | CounterNum$ TriggerCount$CastSACMC | SubAbility$ DBCleanup +SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterType$ P1P1 | CounterNum$ TriggeredStackInstance$CardManaCostLKI | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True A:AB$ Pump | Cost$ 3 U | KW$ HIDDEN Unblockable | TgtPrompt$ Select target creature token | ValidTgts$ Creature.token | SpellDescription$ Target creature token can't be blocked this turn. DeckHas:Ability$Token|Counters diff --git a/forge-gui/res/cardsfolder/d/djerus_resolve.txt b/forge-gui/res/cardsfolder/d/djerus_resolve.txt index 441cab095b4..c940cf2a52e 100644 --- a/forge-gui/res/cardsfolder/d/djerus_resolve.txt +++ b/forge-gui/res/cardsfolder/d/djerus_resolve.txt @@ -2,6 +2,7 @@ Name:Djeru's Resolve ManaCost:W Types:Instant A:SP$ Untap | Cost$ W | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBPump | SpellDescription$ Untap target creature. Prevent all damage that would be dealt to it this turn. -SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Prevent all damage that would be dealt to CARDNAME. +SVar:DBPump:DB$ Effect | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield +SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn. K:Cycling:2 Oracle:Untap target creature. Prevent all damage that would be dealt to it this turn.\nCycling {2} ({2}, Discard this card: Draw a card.) diff --git a/forge-gui/res/cardsfolder/d/dovescape.txt b/forge-gui/res/cardsfolder/d/dovescape.txt index d7e66acb7a7..2d6e568e040 100644 --- a/forge-gui/res/cardsfolder/d/dovescape.txt +++ b/forge-gui/res/cardsfolder/d/dovescape.txt @@ -2,8 +2,7 @@ Name:Dovescape ManaCost:3 WU WU WU Types:Enchantment T:Mode$ SpellCast | ValidCard$ Card.nonCreature | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ Whenever a player casts a noncreature spell, counter that spell. That player creates X 1/1 white and blue Bird creature tokens with flying, where X is the spell's mana value. -SVar:TrigCounter:DB$ Counter | Defined$ TriggeredSpellAbility | RememberCounteredCMC$ True | SubAbility$ DBToken -SVar:DBToken:DB$ Token | TokenAmount$ X | TokenScript$ wu_1_1_bird_flying | TokenOwner$ TriggeredActivator | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$RememberedNumber +SVar:TrigCounter:DB$ Counter | Defined$ TriggeredSpellAbility | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenAmount$ X | TokenScript$ wu_1_1_bird_flying | TokenOwner$ TriggeredActivator +SVar:X:TriggeredStackInstance$CardManaCostLKI Oracle:({W/U} can be paid with either {W} or {U}.)\nWhenever a player casts a noncreature spell, counter that spell. That player creates X 1/1 white and blue Bird creature tokens with flying, where X is the spell's mana value. diff --git a/forge-gui/res/cardsfolder/d/dueling_coach.txt b/forge-gui/res/cardsfolder/d/dueling_coach.txt index c7a27b75524..3545bde30ea 100644 --- a/forge-gui/res/cardsfolder/d/dueling_coach.txt +++ b/forge-gui/res/cardsfolder/d/dueling_coach.txt @@ -4,6 +4,6 @@ Types:Creature Human Monk PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, 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 -A:AB$ PutCounterAll | Cost$ 4 W T | ValidCards$ Creature.YouCtrl+counters_GE1_P1P1 | CounterType$ P1P1 | CounterNum$ 1 +A:AB$ PutCounterAll | Cost$ 4 W T | ValidCards$ Creature.YouCtrl+counters_GE1_P1P1 | CounterType$ P1P1 | CounterNum$ 1 | ValidCardsDesc$ creature you control with a +1/+1 counter on it. | SpellDescription$ Put a +1/+1 counter on each creature you control with a +1/+1 counter on it. DeckHas:Ability$Counters Oracle:When Dueling Coach enters the battlefield, put a +1/+1 counter on target creature.\n{4}{W}, {T}: Put a +1/+1 counter on each creature you control with a +1/+1 counter on it. diff --git a/forge-gui/res/cardsfolder/e/endrek_sahr_master_breeder.txt b/forge-gui/res/cardsfolder/e/endrek_sahr_master_breeder.txt index effe498e614..c9016d87b4e 100644 --- a/forge-gui/res/cardsfolder/e/endrek_sahr_master_breeder.txt +++ b/forge-gui/res/cardsfolder/e/endrek_sahr_master_breeder.txt @@ -5,6 +5,6 @@ PT:2/2 T:Mode$ SpellCast | ValidCard$ Creature | ValidActivatingPlayer$ You | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a creature spell, create X 1/1 black Thrull creature tokens, where X is that spell's mana value. T:Mode$ Always | IsPresent$ Card.Thrull+YouCtrl | PresentCompare$ GE7 | Execute$ TrigSac | TriggerZones$ Battlefield | TriggerDescription$ When you control seven or more Thrulls, sacrifice CARDNAME. SVar:TrigToken:DB$ Token | TokenOwner$ You | TokenAmount$ X | TokenScript$ b_1_1_thrull -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI SVar:TrigSac:DB$ Sacrifice | Defined$ Self Oracle:Whenever you cast a creature spell, create X 1/1 black Thrull creature tokens, where X is that spell's mana value.\nWhen you control seven or more Thrulls, sacrifice Endrek Sahr, Master Breeder. diff --git a/forge-gui/res/cardsfolder/e/ephemerate.txt b/forge-gui/res/cardsfolder/e/ephemerate.txt index ebd5abecdb3..9825f714363 100644 --- a/forge-gui/res/cardsfolder/e/ephemerate.txt +++ b/forge-gui/res/cardsfolder/e/ephemerate.txt @@ -2,7 +2,7 @@ Name:Ephemerate ManaCost:W Types:Instant K:Rebound -A:SP$ ChangeZone | Cost$ W | ValidTgts$ Creature.YouCtrl | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Select target creature you control | RememberTargets$ True | SubAbility$ DBReturn | StackDescription$ Exile {c:Targeted}, then return it the battlefield under its owner's control. | SpellDescription$ Exile target creature you control, then return that card to the battlefield under its owner's control. -SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup +A:SP$ ChangeZone | ValidTgts$ Creature.YouCtrl | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Select target creature you control | RememberTargets$ True | SubAbility$ DBReturn | StackDescription$ Exile {c:Targeted}, | SpellDescription$ Exile target creature you control, then return that card to the battlefield under its owner's control. +SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup | StackDescription$ then return it to the battlefield under its owner's control. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Exile target creature you control, then return it to the battlefield under its owner's control.\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/e/erratic_cyclops.txt b/forge-gui/res/cardsfolder/e/erratic_cyclops.txt index 5a0a4018d7d..a750980d8d8 100644 --- a/forge-gui/res/cardsfolder/e/erratic_cyclops.txt +++ b/forge-gui/res/cardsfolder/e/erratic_cyclops.txt @@ -5,7 +5,7 @@ PT:0/8 K:Trample T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever you cast an instant or sorcery spell, CARDNAME gets +X/+0 until end of turn, where X is that spell's mana value. SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ +X -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI SVar:BuffedBy:Instant,Sorcery DeckHints:Type$Instant|Sorcery Oracle:Trample\nWhenever you cast an instant or sorcery spell, Erratic Cyclops gets +X/+0 until end of turn, where X is that spell's mana value. diff --git a/forge-gui/res/cardsfolder/f/final_parting.txt b/forge-gui/res/cardsfolder/f/final_parting.txt index 7738200ee91..45b80065299 100644 --- a/forge-gui/res/cardsfolder/f/final_parting.txt +++ b/forge-gui/res/cardsfolder/f/final_parting.txt @@ -2,7 +2,7 @@ Name:Final Parting ManaCost:3 B B Types:Sorcery A:SP$ ChangeZone | Cost$ 3 B B | Origin$ Library | Destination$ Library | ChangeType$ Card | ChangeNum$ 2 | Mandatory$ True | RememberChanged$ True | Shuffle$ False | StackDescription$ SpellDescription | SubAbility$ DBChangeZone1 | SpellDescription$ Search your library for two cards. Put one into your hand and the other into your graveyard. Then shuffle. -SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.IsRemembered | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to go to your hand | Shuffle$ False | SubAbility$ DBChangeZone2 | StackDescription$ None +SVar:DBChangeZone1:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card.IsRemembered | ChangeNum$ 1 | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to go to your hand | Shuffle$ False | NoReveal$ True | SubAbility$ DBChangeZone2 | StackDescription$ None SVar:DBChangeZone2:DB$ ChangeZone | Origin$ Library | Destination$ Graveyard | ChangeType$ Card.IsRemembered | Mandatory$ True | NoLooking$ True | SelectPrompt$ Select a card to go to your graveyard | StackDescription$ None | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Search your library for two cards. Put one into your hand and the other into your graveyard. Then shuffle. diff --git a/forge-gui/res/cardsfolder/g/gales_redirection.txt b/forge-gui/res/cardsfolder/g/gales_redirection.txt index bd90f32dd46..2d85425ef4d 100644 --- a/forge-gui/res/cardsfolder/g/gales_redirection.txt +++ b/forge-gui/res/cardsfolder/g/gales_redirection.txt @@ -7,7 +7,7 @@ SVar:DBMayPlay:DB$ Effect | StaticAbilities$ STPlay | RememberObjects$ Remembere SVar:DBMayPlayWithoutCost:DB$ Effect | StaticAbilities$ STPlayWithoutCost | RememberObjects$ Remembered | Duration$ Permanent | ExileOnMoved$ Exile | SubAbility$ DBCleanup | SpellDescription$ 15+ VERT You may cast that card without paying its mana cost for as long as it remains exiled. SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreColor$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand | AffectedZone$ Exile | Description$ You may cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast it. SVar:STPlayWithoutCost:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand | AffectedZone$ Exile | Description$ You may cast that card without paying its mana cost for as long as it remains exiled. -SVar:Y:RememberedLKI$CardManaCost +SVar:Y:SpellTargeted$CardManaCostLKI SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:All Oracle:Exile target spell, then roll a d20 and add that spell's mana value.\n1-14 | You may cast the exiled card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast that spell.\n15+ | You may cast the exiled card without paying its mana cost for as long as it remains exiled. diff --git a/forge-gui/res/cardsfolder/g/gelatinous_cube.txt b/forge-gui/res/cardsfolder/g/gelatinous_cube.txt index 70bdf80565a..2e0493ae7d7 100644 --- a/forge-gui/res/cardsfolder/g/gelatinous_cube.txt +++ b/forge-gui/res/cardsfolder/g/gelatinous_cube.txt @@ -3,11 +3,9 @@ ManaCost:2 B B Types:Creature Ooze PT:4/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ Engulf — When CARDNAME enters the battlefield, exile target non-Ooze creature an opponent controls until CARDNAME leaves the battlefield. -SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Creature.nonOoze+OppCtrl | TgtPrompt$ Select target non-Ooze creature an opponent controls | Duration$ UntilHostLeavesPlay | RememberChanged$ True | SubAbility$ DBEffect -SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | Duration$ Permanent | ForgetOnMoved$ Exile +SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Creature.nonOoze+OppCtrl | TgtPrompt$ Select target non-Ooze creature an opponent controls | Duration$ UntilHostLeavesPlay | RememberChanged$ True A:AB$ ChangeZone | Cost$ X B | Origin$ Exile | Destination$ Graveyard | TgtPrompt$ Select target creature card exiled with Gelatinous Cube with mana value X | ValidTgts$ Creature.IsRemembered+ExiledWithSource+cmcEQX | SpellDescription$ Dissolve — Put target creature card with mana value X exiled with CARDNAME into its owner's graveyard. SVar:X:Count$xPaid -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:PlayMain1:TRUE SVar:NeedsToPlay:Creature.nonOoze+OppCtrl Oracle:Engulf — When Gelatinous Cube enters the battlefield, exile target non-Ooze creature an opponent controls until Gelatinous Cube leaves the battlefield.\nDissolve — {X}{B}: Put target creature card with mana value X exiled with Gelatinous Cube into its owner's graveyard. diff --git a/forge-gui/res/cardsfolder/g/geometric_nexus.txt b/forge-gui/res/cardsfolder/g/geometric_nexus.txt index 85092e70882..d4dc23625a0 100644 --- a/forge-gui/res/cardsfolder/g/geometric_nexus.txt +++ b/forge-gui/res/cardsfolder/g/geometric_nexus.txt @@ -3,7 +3,7 @@ ManaCost:2 Types:Artifact T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever a player casts an instant or sorcery spell, put a number of charge counters on CARDNAME equal to that spell's mana value. SVar:TrigPutCounter:DB$ PutCounter | CounterType$ CHARGE | CounterNum$ Y -SVar:Y:TriggerCount$CastSACMC +SVar:Y:TriggeredStackInstance$CardManaCostLKI A:AB$ Token | Cost$ 6 T SubCounter | CostDesc$ {6}, {T}, Remove all charge counters from CARDNAME: | TokenScript$ gu_0_0_fractal | RememberTokens$ True | SubAbility$ DBPutCounter | SpellDescription$ Create a 0/0 green and blue Fractal creature token. SVar:DBPutCounter:DB$ PutCounter | Defined$ Remembered | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBCleanup | StackDescription$ SpellDescription | SpellDescription$ Put X +1/+1 counters on it, where X is the number of charge counters removed this way. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True diff --git a/forge-gui/res/cardsfolder/g/godtoucher.txt b/forge-gui/res/cardsfolder/g/godtoucher.txt index ffa765c3cde..1052967e2fe 100644 --- a/forge-gui/res/cardsfolder/g/godtoucher.txt +++ b/forge-gui/res/cardsfolder/g/godtoucher.txt @@ -2,7 +2,8 @@ Name:Godtoucher ManaCost:3 G Types:Creature Elf Cleric PT:2/2 -A:AB$ Pump | Cost$ 1 W T | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature.powerGE5 | TgtPrompt$ Select target creature with power 5 or greater | SpellDescription$ Prevent all damage that would be dealt to target creature with power 5 or greater this turn. +A:AB$ Effect | Cost$ 1 W T | ValidTgts$ Creature.powerGE5 | TgtPrompt$ Select target creature with power 5 or greater | ReplacementEffects$ RPrevent| RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target creature with power 5 or greater this turn. +SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn. AI:RemoveDeck:All AI:RemoveDeck:Random Oracle:{1}{W}, {T}: Prevent all damage that would be dealt to target creature with power 5 or greater this turn. diff --git a/forge-gui/res/cardsfolder/h/harvestguard_alseids.txt b/forge-gui/res/cardsfolder/h/harvestguard_alseids.txt index 50e85f86af2..efb0d150036 100644 --- a/forge-gui/res/cardsfolder/h/harvestguard_alseids.txt +++ b/forge-gui/res/cardsfolder/h/harvestguard_alseids.txt @@ -3,7 +3,8 @@ ManaCost:2 W Types:Enchantment Creature Nymph PT:2/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self,Enchantment.Other+YouCtrl | Execute$ TrigPump | TriggerDescription$ Constellation — Whenever CARDNAME or another enchantment enters the battlefield under your control, prevent all damage that would be dealt to target creature this turn. -SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ Prevent all damage that would be dealt to CARDNAME. +SVar:TrigPump:DB$ Effect | Cost$ W | ValidTgts$ Creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield +SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn. SVar:PlayMain1:TRUE SVar:BuffedBy:Enchantment Oracle:Constellation — Whenever Harvestguard Alseids or another enchantment enters the battlefield under your control, prevent all damage that would be dealt to target creature this turn. diff --git a/forge-gui/res/cardsfolder/h/hedron_matrix.txt b/forge-gui/res/cardsfolder/h/hedron_matrix.txt index 1349f6cba19..61cae2f3ee7 100644 --- a/forge-gui/res/cardsfolder/h/hedron_matrix.txt +++ b/forge-gui/res/cardsfolder/h/hedron_matrix.txt @@ -3,5 +3,5 @@ ManaCost:4 Types:Artifact Equipment K:Equip:4 S:Mode$ Continuous | Affected$ Card.EquippedBy | AddPower$ X | AddToughness$ X | Description$ Equipped creature gets +X/+X, where X is its mana value. -SVar:X:Count$EquippedCardManaCost +SVar:X:Equipped$CardManaCost Oracle:Equipped creature gets +X/+X, where X is its mana value.\nEquip {4} diff --git a/forge-gui/res/cardsfolder/i/idol_of_endurance.txt b/forge-gui/res/cardsfolder/i/idol_of_endurance.txt index 22847f0143a..e5d70dc8bc9 100644 --- a/forge-gui/res/cardsfolder/i/idol_of_endurance.txt +++ b/forge-gui/res/cardsfolder/i/idol_of_endurance.txt @@ -2,13 +2,10 @@ Name:Idol of Endurance ManaCost:2 W Types:Artifact T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChange | TriggerDescription$ When CARDNAME enters the battlefield, exile all creature cards with mana value 3 or less from your graveyard until CARDNAME leaves the battlefield. -SVar:TrigChange:DB$ ChangeZoneAll | ChangeType$ Creature.cmcLE3+YouOwn | Origin$ Graveyard | Destination$ Exile | RememberChanged$ True | SubAbility$ DBEffect -SVar:DBEffect:DB$ Effect | Name$ Exile Effect | Triggers$ ComeBack | RememberObjects$ Remembered | ImprintCards$ Self | ConditionPresent$ Card.Self | Duration$ Permanent | ForgetOnMoved$ Exile -SVar:ComeBack:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.IsImprinted | Execute$ TrigReturn | TriggerZones$ Command | TriggerController$ TriggeredCardController | Static$ True | TriggerDescription$ Those creatures are exiled until EFFECTSOURCE leaves the battlefield. -SVar:TrigReturn:DB$ ChangeZoneAll | Origin$ Exile | Destination$ Graveyard | ChangeType$ Card.IsRemembered | SubAbility$ ExileSelf -SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self +SVar:TrigChange:DB$ ChangeZoneAll | ChangeType$ Creature.cmcLE3+YouOwn | Origin$ Graveyard | Destination$ Exile | RememberChanged$ True | Duration$ UntilHostLeavesPlay A:AB$ Effect | Cost$ 1 W T | RememberObjects$ Remembered | StaticAbilities$ MayPlay | Triggers$ TrigSpellCast | SpellDescription$ Until end of turn, you may cast a creature spell from among cards exiled with CARDNAME without paying its mana cost. SVar:MayPlay:Mode$ Continuous | EffectZone$ Command | Affected$ Card.IsRemembered+Creature | AffectedZone$ Exile | MayPlay$ True | MayPlayWithoutManaCost$ True | Description$ Until end of turn, you may cast a creature spell from among cards exiled with CARDNAME without paying its mana cost. SVar:TrigSpellCast:Mode$ SpellCast | ValidCard$ Card.IsRemembered | ValidActivatingPlayer$ You | TriggerZones$ Command | Execute$ ExileSelf | Static$ True +SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self AI:RemoveDeck:All Oracle:When Idol of Endurance enters the battlefield, exile all creature cards with mana value 3 or less from your graveyard until Idol of Endurance leaves the battlefield.\n{1}{W}, {T}: Until end of turn, you may cast a creature spell from among cards exiled with Idol of Endurance without paying its mana cost. diff --git a/forge-gui/res/cardsfolder/i/imminent_doom.txt b/forge-gui/res/cardsfolder/i/imminent_doom.txt index 66b1d069fe2..d3c90e3ce5b 100644 --- a/forge-gui/res/cardsfolder/i/imminent_doom.txt +++ b/forge-gui/res/cardsfolder/i/imminent_doom.txt @@ -6,5 +6,5 @@ T:Mode$ SpellCast | ValidCard$ Card.cmcEQX | ValidActivatingPlayer$ You | Trigge SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ Y | SubAbility$ DBPutCounter SVar:DBPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ DOOM | CounterNum$ 1 SVar:X:Count$CardCounters.DOOM -SVar:Y:TriggerCount$CastSACMC +SVar:Y:LastStateBattlefield$CardCounters.DOOM Oracle:Imminent Doom enters the battlefield with a doom counter on it.\nWhenever you cast a spell with mana value equal to the number of doom counters on Imminent Doom, Imminent Doom deals that much damage to any target. Then put a doom counter on Imminent Doom. diff --git a/forge-gui/res/cardsfolder/i/indestructible_aura.txt b/forge-gui/res/cardsfolder/i/indestructible_aura.txt index da743170761..fab165f32b9 100644 --- a/forge-gui/res/cardsfolder/i/indestructible_aura.txt +++ b/forge-gui/res/cardsfolder/i/indestructible_aura.txt @@ -1,6 +1,7 @@ Name:Indestructible Aura ManaCost:W Types:Instant -A:SP$ Pump | Cost$ W | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Prevent all damage that would be dealt to target creature this turn. +A:SP$ Effect | ValidTgts$ Creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target creature this turn. +SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn. AI:RemoveDeck:All Oracle:Prevent all damage that would be dealt to target creature this turn. diff --git a/forge-gui/res/cardsfolder/i/infernal_kirin.txt b/forge-gui/res/cardsfolder/i/infernal_kirin.txt index 8cbcc73b7ed..81c05b6c7c6 100644 --- a/forge-gui/res/cardsfolder/i/infernal_kirin.txt +++ b/forge-gui/res/cardsfolder/i/infernal_kirin.txt @@ -5,5 +5,5 @@ PT:3/3 K:Flying T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDiscard | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, target player reveals their hand and discards all cards with that spell's mana value. SVar:TrigDiscard:DB$ Discard | ValidTgts$ Player | Mode$ RevealDiscardAll | DiscardValid$ Card.cmcEQX -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI Oracle:Flying\nWhenever you cast a Spirit or Arcane spell, target player reveals their hand and discards all cards with that spell's mana value. diff --git a/forge-gui/res/cardsfolder/k/kaervek_the_merciless.txt b/forge-gui/res/cardsfolder/k/kaervek_the_merciless.txt index 141a23661b6..2a31a6c99a0 100644 --- a/forge-gui/res/cardsfolder/k/kaervek_the_merciless.txt +++ b/forge-gui/res/cardsfolder/k/kaervek_the_merciless.txt @@ -4,5 +4,5 @@ Types:Legendary Creature Human Shaman PT:5/4 T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Opponent | TriggerZones$ Battlefield | Execute$ TrigDealDamage | TriggerDescription$ Whenever an opponent casts a spell, CARDNAME deals damage equal to that spell's mana value to any target. SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI Oracle:Whenever an opponent casts a spell, Kaervek the Merciless deals damage equal to that spell's mana value to any target. diff --git a/forge-gui/res/cardsfolder/k/killer_cosplay.txt b/forge-gui/res/cardsfolder/k/killer_cosplay.txt index 429957c4526..eb5fb35e8f6 100644 --- a/forge-gui/res/cardsfolder/k/killer_cosplay.txt +++ b/forge-gui/res/cardsfolder/k/killer_cosplay.txt @@ -5,6 +5,5 @@ T:Mode$ Attached | ValidSource$ Card.Self | ValidTarget$ Creature | TriggerZones SVar:TrigChoose:DB$ NameCard | Defined$ You | ValidCards$ Creature.ManaCost=Equipped | SubAbility$ DBCopy SVar:DBCopy:DB$ Clone | CopyFromChosenName$ True | CloneTarget$ TriggeredTarget | Duration$ UntilUnattached | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearNamedCard$ True -SVar:X:Count$EquippedCardManaCost K:Equip:3 Oracle:Whenever Killer Cosplay becomes attached to a creature, choose a creature card name with an identical mana cost. That creature becomes a copy of the card with the chosen name until Killer Cosplay becomes unattached from it.\nEquip {3} diff --git a/forge-gui/res/cardsfolder/l/living_lore.txt b/forge-gui/res/cardsfolder/l/living_lore.txt index 94bc6cbd2fe..7e7c174ec2e 100644 --- a/forge-gui/res/cardsfolder/l/living_lore.txt +++ b/forge-gui/res/cardsfolder/l/living_lore.txt @@ -9,6 +9,7 @@ SVar:X:Count$RememberedCardManaCost T:Mode$ DamageDealtOnce | CombatDamage$ True | ValidSource$ Card.Self | Execute$ TrigSacLore | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage, you may sacrifice it. If you do, you may cast the exiled card without paying its mana cost. SVar:TrigSacLore:AB$ Play | Cost$ Sac<1/CARDNAME> | Defined$ Remembered | Amount$ All | Controller$ You | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | ForgetRemembered$ True T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | Static$ True | ValidCard$ Card.Self | Execute$ DBCleanup +T:Mode$ ChangesZone | Origin$ Exile | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered | Execute$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:Random Oracle:As Living Lore enters the battlefield, exile an instant or sorcery card from your graveyard.\nLiving Lore's power and toughness are each equal to the exiled card's mana value.\nWhenever Living Lore deals combat damage, you may sacrifice it. If you do, you may cast the exiled card without paying its mana cost. diff --git a/forge-gui/res/cardsfolder/l/lozhan_dragons_legacy.txt b/forge-gui/res/cardsfolder/l/lozhan_dragons_legacy.txt index e09f6793a65..5d9fce4e9d8 100644 --- a/forge-gui/res/cardsfolder/l/lozhan_dragons_legacy.txt +++ b/forge-gui/res/cardsfolder/l/lozhan_dragons_legacy.txt @@ -5,7 +5,7 @@ PT:4/2 K:Flying T:Mode$ SpellCast | ValidCard$ Card.Adventure,Dragon | ValidActivatingPlayer$ You | Execute$ TrigDamage | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast an Adventure spell or Dragon spell, CARDNAME deals damage equal to that spell's mana value to any target that isn't a commander. SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature.IsNotCommander,Player,Planeswalker.IsNotCommander | TgtPrompt$ Select any target that isn't a commander | NumDmg$ X -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI SVar:BuffedBy:Creature.AdventureCard,Dragon DeckHints:Type$Adventure|Dragon Oracle:Flying\nWhenever you cast an Adventure spell or Dragon spell, Lozhan, Dragons' Legacy deals damage equal to that spell's mana value to any target that isn't a commander. diff --git a/forge-gui/res/cardsfolder/m/manaplasm.txt b/forge-gui/res/cardsfolder/m/manaplasm.txt index 613ccc5d276..44dda08c17f 100644 --- a/forge-gui/res/cardsfolder/m/manaplasm.txt +++ b/forge-gui/res/cardsfolder/m/manaplasm.txt @@ -4,6 +4,6 @@ Types:Creature Ooze PT:1/1 T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever you cast a spell, CARDNAME gets +X/+X until end of turn, where X is that spell's mana value. SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ +X | NumDef$ +X -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI SVar:BuffedBy:Card Oracle:Whenever you cast a spell, Manaplasm gets +X/+X until end of turn, where X is that spell's mana value. diff --git a/forge-gui/res/cardsfolder/m/mausoleum_turnkey.txt b/forge-gui/res/cardsfolder/m/mausoleum_turnkey.txt index f24a6631db6..337bcf834fa 100644 --- a/forge-gui/res/cardsfolder/m/mausoleum_turnkey.txt +++ b/forge-gui/res/cardsfolder/m/mausoleum_turnkey.txt @@ -2,7 +2,6 @@ Name:Mausoleum Turnkey ManaCost:3 B Types:Creature Ogre Rogue PT:3/2 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | RememberController$ True | TriggerDescription$ When CARDNAME enters the battlefield, return target creature card of an opponent's choice from your graveyard to your hand. -SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Creature.RememberedPlayerCtrl | TargetingPlayer$ Opponent | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ When CARDNAME enters the battlefield, return target creature card of an opponent's choice from your graveyard to your hand. +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Creature.YouOwn | TargetingPlayer$ Opponent Oracle:When Mausoleum Turnkey enters the battlefield, return target creature card of an opponent's choice from your graveyard to your hand. diff --git a/forge-gui/res/cardsfolder/m/maze_of_ith.txt b/forge-gui/res/cardsfolder/m/maze_of_ith.txt index cc8481dfa25..c766ff9b417 100644 --- a/forge-gui/res/cardsfolder/m/maze_of_ith.txt +++ b/forge-gui/res/cardsfolder/m/maze_of_ith.txt @@ -5,4 +5,5 @@ A:AB$ Untap | Cost$ T | ValidTgts$ Creature.attacking | TgtPrompt$ Select target SVar:DBPump:DB$ Effect | ReplacementEffects$ RPrevent1,RPrevent2 | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all combat damage that would be dealt to and dealt by that creature this turn. SVar:RPrevent1:Event$ DamageDone | Prevent$ True | IsCombat$ True | ValidSource$ Card.IsRemembered | Description$ Prevent all combat damage that would be dealt to and dealt by that creature this turn. SVar:RPrevent2:Event$ DamageDone | Prevent$ True | IsCombat$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all combat damage that would be dealt to and dealt by that creature this turn. | Secondary$ True +AI:RemoveDeck:All Oracle:{T}: Untap target attacking creature. Prevent all combat damage that would be dealt to and dealt by that creature this turn. diff --git a/forge-gui/res/cardsfolder/m/metallurgic_summonings.txt b/forge-gui/res/cardsfolder/m/metallurgic_summonings.txt index 09758a25c08..59d53b878a1 100644 --- a/forge-gui/res/cardsfolder/m/metallurgic_summonings.txt +++ b/forge-gui/res/cardsfolder/m/metallurgic_summonings.txt @@ -4,7 +4,7 @@ Types:Enchantment T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you cast an instant or sorcery spell, create an X/X colorless Construct artifact creature token, where X is that spell's mana value. SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_x_x_a_construct | TokenPower$ X | TokenToughness$ X | TokenOwner$ You A:AB$ ChangeZoneAll | Cost$ 3 U U Exile<1/CARDNAME> | ChangeType$ Instant.YouOwn,Sorcery.YouOwn | IsPresent$ Card.Artifact+YouCtrl | PresentCompare$ GE6 | Origin$ Graveyard | Destination$ Hand | SpellDescription$ Return all instant and sorcery cards from your graveyard to your hand. Activate only if you control six or more artifacts. -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI SVar:BuffedBy:Instant,Sorcery DeckHints:Type$Instant|Sorcery Oracle:Whenever you cast an instant or sorcery spell, create an X/X colorless Construct artifact creature token, where X is that spell's mana value.\n{3}{U}{U}, Exile Metallurgic Summonings: Return all instant and sorcery cards from your graveyard to your hand. Activate only if you control six or more artifacts. diff --git a/forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt b/forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt index 92af7a3a0a8..d3d21e4db38 100644 --- a/forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt +++ b/forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt @@ -5,7 +5,7 @@ PT:3/4 K:Flying K:Haste T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigChange | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, return target creature card from your graveyard to the battlefield tapped and attacking. It gains "When you don't control a legendary Vampire, exile this creature." -SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouOwn | Tapped$ True | Attacking$ True | RememberChanged$ True | AnimateSubAbility$ DBAnimate +SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouOwn | Tapped$ True | Attacking$ True | AnimateSubAbility$ DBAnimate SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Duration$ Permanent | Triggers$ TrigOlivia SVar:TrigOlivia:Mode$ Always | TriggerZones$ Battlefield | IsPresent$ Vampire.YouCtrl+Legendary | PresentCompare$ EQ0 | Execute$ TrigExile | TriggerDescription$ When you don't control a legendary Vampire, exile this creature. SVar:TrigExile:DB$ ChangeZone | Defined$ Self | Origin$ Battlefield | Destination$ Exile diff --git a/forge-gui/res/cardsfolder/p/painful_bond.txt b/forge-gui/res/cardsfolder/p/painful_bond.txt index c5be6a83c99..a95083b04ac 100644 --- a/forge-gui/res/cardsfolder/p/painful_bond.txt +++ b/forge-gui/res/cardsfolder/p/painful_bond.txt @@ -1,11 +1,11 @@ Name:Painful Bond ManaCost:1 B Types:Instant -A:SP$ Draw | Cost$ 1 B | NumCards$ 2 | SubAbility$ DBEffect | SpellDescription$ Draw two cards, then cards in your hand with mana value 3 or greater perpetually gain "When you cast this spell, you lose 1 life." -SVar:DBEffect:DB$ Effect | RememberObjects$ ValidHand Card.cmcLE3+YouOwn | StaticAbilities$ PerpetualAbility | Duration$ Permanent | Triggers$ Update | Name$ Painful Bond's Perpetual Effect | StackDescription$ Then cards in {p:You}'s hand with mana value 3 or less perpetually gain "When you cast this spell, you lose 1 life." -SVar:PerpetualAbility:Mode$ Continuous | Affected$ Card.IsRemembered | AddTrigger$ CastSpellLoseLife | EffectZone$ Command | AffectedZone$ Battlefield,Hand,Graveyard,Exile,Stack,Library,Command | Description$ Cards in your hand with mana value 3 or less perpetually gain "When you cast this spell, you lose 1 life." +A:SP$ Draw | Cost$ 1 B | NumCards$ 2 | SubAbility$ DBEffect | SpellDescription$ Draw two cards, then nonland cards in your hand with mana value 3 or less perpetually gain "When you cast this spell, you lose 1 life." +SVar:DBEffect:DB$ Effect | RememberObjects$ ValidHand Card.nonLand+cmcLE3+YouOwn | StaticAbilities$ PerpetualAbility | Duration$ Permanent | Triggers$ Update | Name$ Painful Bond's Perpetual Effect | StackDescription$ Then nonland cards in {p:You}'s hand with mana value 3 or less perpetually gain "When you cast this spell, you lose 1 life." +SVar:PerpetualAbility:Mode$ Continuous | Affected$ Card.IsRemembered | AddTrigger$ CastSpellLoseLife | EffectZone$ Command | AffectedZone$ Battlefield,Hand,Graveyard,Exile,Stack,Library,Command | Description$ Nonland cards in your hand with mana value 3 or less perpetually gain "When you cast this spell, you lose 1 life." SVar:CastSpellLoseLife:Mode$ SpellCast | ValidCard$ Card.Self | Execute$ TrigDrain | TriggerDescription$ When you cast this spell, you lose 1 life. SVar:TrigDrain:DB$ LoseLife | LifeAmount$ 1 SVar:Update:Mode$ ChangesZone | Origin$ Any | Destination$ Any | Static$ True | ValidCard$ Card.IsRemembered | Execute$ DBUpdate SVar:DBUpdate:DB$ UpdateRemember -Oracle:Draw two cards, then cards in your hand with mana value 3 or less perpetually gain "When you cast this spell, you lose 1 life." +Oracle:Draw two cards, then nonland cards in your hand with mana value 3 or less perpetually gain "When you cast this spell, you lose 1 life." diff --git a/forge-gui/res/cardsfolder/p/piston_fist_cyclops.txt b/forge-gui/res/cardsfolder/p/piston_fist_cyclops.txt index 25f2a682d01..37fb8914942 100644 --- a/forge-gui/res/cardsfolder/p/piston_fist_cyclops.txt +++ b/forge-gui/res/cardsfolder/p/piston_fist_cyclops.txt @@ -4,7 +4,7 @@ Types:Creature Cyclops PT:4/3 K:Defender S:Mode$ CanAttackDefender | ValidCard$ Card.Self | CheckSVar$ X | Description$ As long as you've cast an instant or sorcery spell this turn, CARDNAME can attack as though it didn't have defender. -SVar:X:Count$ThisTurnCast_Instant.YouOwn,Sorcery.YouOwn +SVar:X:Count$ThisTurnCast_Instant.YouCtrl,Sorcery.YouCtrl SVar:BuffedBy:Instant,Sorcery DeckHints:Type$Instant|Sorcery Oracle:Defender\nAs long as you've cast an instant or sorcery spell this turn, Piston-Fist Cyclops can attack as though it didn't have defender. diff --git a/forge-gui/res/cardsfolder/p/prismari_apprentice.txt b/forge-gui/res/cardsfolder/p/prismari_apprentice.txt index c82c2e70410..4601e8e7946 100644 --- a/forge-gui/res/cardsfolder/p/prismari_apprentice.txt +++ b/forge-gui/res/cardsfolder/p/prismari_apprentice.txt @@ -4,7 +4,7 @@ Types:Creature Human Shaman PT:2/2 T:Mode$ SpellCastOrCopy | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Magecraft — Whenever you cast or copy an instant or sorcery spell, CARDNAME can't be blocked this turn. If that spell has mana value 5 or greater, put a +1/+1 counter on CARDNAME. SVar:TrigPump:DB$ Pump | Defined$ Self | KW$ HIDDEN Unblockable | SubAbility$ DBPutCounter -SVar:DBPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | ConditionCheckSVar$ TriggerCount$CastSACMC | ConditionSVarCompare$ GE5 +SVar:DBPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | ConditionCheckSVar$ TriggeredStackInstance$CardManaCostLKI | ConditionSVarCompare$ GE5 DeckNeeds:Type$Instant|Sorcery DeckHas:Ability$Counters Oracle:Magecraft — Whenever you cast or copy an instant or sorcery spell, Prismari Apprentice can't be blocked this turn. If that spell has mana value 5 or greater, put a +1/+1 counter on Prismari Apprentice. diff --git a/forge-gui/res/cardsfolder/r/rage_extractor.txt b/forge-gui/res/cardsfolder/r/rage_extractor.txt index ff82a5032eb..ce5286c30cc 100644 --- a/forge-gui/res/cardsfolder/r/rage_extractor.txt +++ b/forge-gui/res/cardsfolder/r/rage_extractor.txt @@ -3,7 +3,7 @@ ManaCost:4 PR Types:Artifact T:Mode$ SpellCast | ValidCard$ Card.CostsPhyrexianMana | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDealDamage | TriggerDescription$ Whenever you cast a spell with {P} in its mana cost, CARDNAME deals damage equal to that spell's mana value to any target. SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI SVar:BuffedBy:Card.CostsPhyrexianMana AI:RemoveDeck:Random Oracle:({R/P} can be paid with either {R} or 2 life.)\nWhenever you cast a spell with {P} in its mana cost, Rage Extractor deals damage equal to that spell's mana value to any target. diff --git a/forge-gui/res/cardsfolder/r/rumbling_aftershocks.txt b/forge-gui/res/cardsfolder/r/rumbling_aftershocks.txt index 7fb8034adfe..93b8c550cf6 100644 --- a/forge-gui/res/cardsfolder/r/rumbling_aftershocks.txt +++ b/forge-gui/res/cardsfolder/r/rumbling_aftershocks.txt @@ -3,6 +3,6 @@ ManaCost:4 R Types:Enchantment T:Mode$ SpellCast | ValidCard$ Card.YouCtrl+kicked | TriggerZones$ Battlefield | Execute$ DamageSomeone | OptionalDecider$ You | TriggerDescription$ Whenever you cast a kicked spell, you may have CARDNAME deal damage to any target equal to the number of times that spell was kicked. SVar:DamageSomeone:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | NumDmg$ X | TgtPrompt$ Select any target -SVar:X:TriggeredSpellAbility$Count$TimesKicked +SVar:X:TriggeredSpellAbility$TimesKicked AI:RemoveDeck:Random Oracle:Whenever you cast a kicked spell, you may have Rumbling Aftershocks deal damage to any target equal to the number of times that spell was kicked. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-ardent_dustspeaker.txt b/forge-gui/res/cardsfolder/rebalanced/a-ardent_dustspeaker.txt new file mode 100644 index 00000000000..a8bcc9d44db --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-ardent_dustspeaker.txt @@ -0,0 +1,11 @@ +Name:A-Ardent Dustspeaker +ManaCost:2 R +Types:Creature Minotaur Shaman +PT:3/2 +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ ABImpulse | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, you may put an instant or sorcery card from your graveyard on the bottom of your library. If you do, exile the top two cards of your library. You may play those cards this turn. +SVar:ABImpulse:AB$ Dig | Cost$ PutCardToLibFromGrave<1/-1/Sorcery;Instant> | Defined$ You | DigNum$ 2 | ChangeNum$ All | DestinationZone$ Exile | SubAbility$ DBEffect | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Exile the top two cards of your library. You may play this cards this turn. +SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ Play | SubAbility$ DBCleanup | ForgetOnMoved$ Exile +SVar:Play:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play remembered card. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:HasAttackEffect:TRUE +Oracle:Whenever Ardent Dustspeaker attacks, you may put an instant or sorcery card from your graveyard on the bottom of your library. If you do, exile the top two cards of your library. You may play those cards this turn. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-bretagard_stronghold.txt b/forge-gui/res/cardsfolder/rebalanced/a-bretagard_stronghold.txt new file mode 100644 index 00000000000..ad8a29a999c --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-bretagard_stronghold.txt @@ -0,0 +1,9 @@ +Name:A-Bretagard Stronghold +ManaCost:no cost +Types:Land +K:CARDNAME enters the battlefield tapped. +A:AB$ Mana | Cost$ T | Produced$ G | SpellDescription$ Add {G}. +A:AB$ PutCounter | Cost$ G W W T Sac<1/CARDNAME> | CounterNum$ 2 | CounterType$ P1P1 | TargetMin$ 1 | TargetMax$ 2 | DividedAsYouChoose$ 2 | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select one or two target creatures you control | SorcerySpeed$ True | SubAbility$ DBPump | SpellDescription$ Distribute two +1/+1 counters among one or two target creatures you control. They gain vigilance and lifelink until end of turn. Activate only as a sorcery. +SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ Vigilance & Lifelink | StackDescription$ {c:Targeted} gain vigilance and lifelink until end of turn. +DeckHas:Ability$Sacrifice|Counters|LifeGain +Oracle:Bretagard Stronghold enters the battlefield tapped.\n{T}: Add {G}.\n{G}{W}{W}, {T}, Sacrifice Bretagard Stronghold: Distribute two +1/+1 counters among one or two target creatures you control. They gain vigilance and lifelink until end of turn. Activate only as a sorcery. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-cauldron_familiar.txt b/forge-gui/res/cardsfolder/rebalanced/a-cauldron_familiar.txt new file mode 100644 index 00000000000..dd7e57a5991 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-cauldron_familiar.txt @@ -0,0 +1,15 @@ +Name:A-Cauldron Familiar +ManaCost:B +Types:Creature Cat +PT:1/1 +K:CARDNAME can't block. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDrain | TriggerDescription$ When CARDNAME enters the battlefield, each opponent loses 1 life and you gain 1 life. +SVar:TrigDrain:DB$ LoseLife | Defined$ Player.Opponent | LifeAmount$ 1 | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 +DeckHas:Ability$LifeGain +A:AB$ ChangeZone | Cost$ Sac<1/Food> | Origin$ Graveyard | Destination$ Battlefield | ActivationZone$ Graveyard | SpellDescription$ Return CARDNAME from your graveyard to the battlefield. +SVar:DiscardMe:2 +SVar:SacMe:1 +SVar:AIPreference:SacCost$Card.Food +DeckHints:Ability$Food +Oracle:Cauldron Familiar can't block.\nWhen Cauldron Familiar enters the battlefield, each opponent loses 1 life and you gain 1 life.\nSacrifice a Food: Return Cauldron Familiar from your graveyard to the battlefield. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-dragons_rage_channeler.txt b/forge-gui/res/cardsfolder/rebalanced/a-dragons_rage_channeler.txt new file mode 100644 index 00000000000..4c1ec431ed0 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-dragons_rage_channeler.txt @@ -0,0 +1,10 @@ +Name:A-Dragon's Rage Channeler +ManaCost:R +Types:Creature Human Shaman +PT:1/1 +T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ DBSurveil | TriggerDescription$ Whenever you cast a noncreature spell, surveil 1. +SVar:DBSurveil:DB$ Surveil | Defined$ You | Amount$ 1 +S:Mode$ Continuous | Affected$ Card.Self | AddPower$ 2 | AddKeyword$ Flying | Condition$ Delirium | Description$ Delirium — As long as there are four or more card types in your graveyard, CARDNAME gets +2/+0, has flying, and attacks each combat if able. +S:Mode$ MustAttack | ValidCreature$ Card.Self | Condition$ Delirium | Secondary$ True +DeckHas:Ability$Delirium|Surveil +Oracle:Whenever you cast a noncreature spell, surveil 1.\nDelirium — As long as there are four or more card types among cards in your graveyard, Dragon's Rage Channeler gets +2/+0, has flying, and attacks each combat if able. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-dueling_coach.txt b/forge-gui/res/cardsfolder/rebalanced/a-dueling_coach.txt new file mode 100644 index 00000000000..eb9531afdb4 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-dueling_coach.txt @@ -0,0 +1,9 @@ +Name:A-Dueling Coach +ManaCost:3 W +Types:Creature Human Monk +PT:2/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, distribute two +1/+1 counters among one or two target creatures you control. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select one or two target creatures you control | CounterType$ P1P1 | CounterNum$ 2 | TargetMin$ 1 | TargetMax$ 2 | DividedAsYouChoose$ 2 +A:AB$ PutCounterAll | Cost$ W T | ValidCards$ Creature.YouCtrl+counters_GE1_P1P1 | CounterType$ P1P1 | CounterNum$ 1 | ValidCardsDesc$ creature you control with a +1/+1 counter on it. | SpellDescription$ Put a +1/+1 counter on each creature you control with a +1/+1 counter on it. +DeckHas:Ability$Counters +Oracle:When Dueling Coach enters the battlefield, distribute two +1/+1 counters among one or two target creatures you control.\n{W}, {T}: Put a +1/+1 counter on each creature you control with a +1/+1 counter on it. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-fall_of_the_impostor.txt b/forge-gui/res/cardsfolder/rebalanced/a-fall_of_the_impostor.txt new file mode 100644 index 00000000000..52513da069c --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-fall_of_the_impostor.txt @@ -0,0 +1,11 @@ +Name:A-Fall of the Impostor +ManaCost:G W +Types:Enchantment Saga +K:Saga:3:DBPutCounter,DBPutCounter,DBExileGreatest +SVar:DBPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ P1P1 | CounterNum$ 1 | TargetMin$ 0 | TargetMax$ 1 | SpellDescription$ Put a +1/+1 counter on up to one target creature. +SVar:DBExileGreatest:DB$ Pump | ValidTgts$ Player.Opponent | RememberTargets$ True | SubAbility$ DBChooseExiled | IsCurse$ True | SpellDescription$ Exile a creature with the greatest power among creatures target opponent controls. +SVar:DBChooseExiled:DB$ ChooseCard | Choices$ Creature.greatestPowerControlledByRemembered | MinAmount$ 1 | Amount$ 1 | Mandatory$ True | ChoiceZone$ Battlefield | SubAbility$ DBChangeZone +SVar:DBChangeZone:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | Defined$ ChosenCard | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHas:Ability$Counters +Oracle:(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)\nI, II — Put a +1/+1 counter on up to one target creature.\nIII — Exile a creature with the greatest power among creatures target opponent controls. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-gnarlid_colony.txt b/forge-gui/res/cardsfolder/rebalanced/a-gnarlid_colony.txt new file mode 100644 index 00000000000..94712c713bf --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-gnarlid_colony.txt @@ -0,0 +1,11 @@ +Name:A-Gnarlid Colony +ManaCost:1 G +Types:Creature Beast +PT:2/2 +K:Kicker:2 G +K:etbCounter:P1P1:4:CheckSVar$ WasKicked:If CARDNAME was kicked, it enters the battlefield with four +1/+1 counters on it. +SVar:WasKicked:Count$Kicked.1.0 +S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddKeyword$ Trample | Description$ Each creature you control with a +1/+1 counter on it has trample. +DeckHints:Ability$Counters +DeckHas:Ability$Counters +Oracle:Kicker {2}{G}\nIf Gnarlid Colony was kicked, it enters the battlefield with four +1/+1 counters on it.\nEach creature you control with a +1/+1 counter on it has trample. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-hagra_constrictor.txt b/forge-gui/res/cardsfolder/rebalanced/a-hagra_constrictor.txt new file mode 100644 index 00000000000..ee28cfb23e6 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-hagra_constrictor.txt @@ -0,0 +1,8 @@ +Name:A-Hagra Constrictor +ManaCost:1 B +Types:Creature Snake +PT:0/0 +K:etbCounter:P1P1:2 +S:Mode$ Continuous | Affected$ Creature.YouCtrl+counters_GE1_P1P1 | AddKeyword$ Menace | Description$ Each creature you control with a +1/+1 counter on it has menace. +DeckHas:Ability$Counters +Oracle:Hagra Constrictor enters the battlefield with two +1/+1 counters on it.\nEach creature you control with a +1/+1 counter on it has menace. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-iridescent_hornbeetle.txt b/forge-gui/res/cardsfolder/rebalanced/a-iridescent_hornbeetle.txt new file mode 100644 index 00000000000..3a71972483a --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-iridescent_hornbeetle.txt @@ -0,0 +1,10 @@ +Name:A-Iridescent Hornbeetle +ManaCost:3 G +Types:Creature Insect +PT:3/3 +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of your end step, create a 1/1 green Insect creature token for each +1/+1 counter you've put on creatures under your control this turn. +SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenOwner$ You | TokenScript$ g_1_1_insect +SVar:X:Count$CountersAddedThisTurn P1P1 You Creature.YouCtrl +DeckNeeds:Ability$Counters +DeckHas:Ability$Counters +Oracle:At the beginning of your end step, create a 1/1 green Insect creature token for each +1/+1 counter you've put on creatures under your control this turn. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-maelstrom_muse.txt b/forge-gui/res/cardsfolder/rebalanced/a-maelstrom_muse.txt new file mode 100644 index 00000000000..94d546011b9 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-maelstrom_muse.txt @@ -0,0 +1,14 @@ +Name:A-Maelstrom Muse +ManaCost:U U/R R +Types:Creature Djinn Wizard +PT:2/4 +K:Flying +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ CheckPower | TriggerDescription$ Whenever CARDNAME attacks, the next instant or sorcery spell you cast this turn costs {X} less to cast, where X is CARDNAME's power as this ability resolves. +SVar:CheckPower:DB$ StoreSVar | SVar$ CurrPower | Type$ CountSVar | Expression$ X | SubAbility$ DBEffect | SpellDescription$ The next instant or sorcery spell you cast this turn costs {X} less to cast, where X is CARDNAME's power as this ability resolves. +SVar:DBEffect:DB$ Effect | StaticAbilities$ ReduceCost | Triggers$ TrigCastSpell +SVar:ReduceCost:Mode$ ReduceCost | EffectZone$ Command | Type$ Spell | ValidCard$ Instant,Sorcery | Activator$ You | Amount$ CurrPower | Description$ The next instant or sorcery spell you cast this turn costs {X} less to cast, where X is CARDNAME's power at the time EFFECTSOURCE's ability resolved. +SVar:TrigCastSpell:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Command | Execute$ RemoveEffect | Static$ True +SVar:RemoveEffect:DB$ ChangeZone | Origin$ Command | Destination$ Exile +SVar:CurrPower:Number$0 +SVar:X:TriggeredCard$CardPower +Oracle:Flying\nWhenever Maelstrom Muse attacks, the next instant or sorcery spell you cast this turn costs {X} less to cast, where X is Maelstrom Muse's power as this ability resolves. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-master_of_winds.txt b/forge-gui/res/cardsfolder/rebalanced/a-master_of_winds.txt new file mode 100644 index 00000000000..3152a38d04b --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-master_of_winds.txt @@ -0,0 +1,15 @@ +Name:A-Master of Winds +ManaCost:2 U U +Types:Creature Sphinx Wizard +PT:1/5 +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw two cards, then discard a card. +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 2 | SubAbility$ DBDiscard +SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose +T:Mode$ SpellCast | ValidCard$ Instant,Sorcery,Wizard | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigChoose | TriggerDescription$ Whenever you cast an instant, sorcery, or Wizard spell, you may have CARDNAME's base power and toughness become 5/1 or 1/5 until end of turn. +SVar:TrigChoose:DB$ GenericChoice | Defined$ You | Choices$ Animate51,Animate15 +SVar:Animate51:DB$ Animate | Defined$ Self | Power$ 5 | Toughness$ 1 | SpellDescription$ CARDNAME's base power and toughness become 5/1 until end of turn. +SVar:Animate15:DB$ Animate | Defined$ Self | Power$ 1 | Toughness$ 5 | SpellDescription$ CARDNAME's base power and toughness become 1/5 until end of turn. +SVar:BuffedBy:Instant,Sorcery,Wizard +DeckNeeds:Type$Instant|Sorcery|Wizard +Oracle:Flying\nWhen Master of Winds enters the battlefield, draw two cards, then discard a card.\nWhenever you cast an instant, sorcery, or Wizard spell, you may have Master of Winds's base power and toughness become 5/1 or 1/5 until end of turn. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-mentors_guidance.txt b/forge-gui/res/cardsfolder/rebalanced/a-mentors_guidance.txt new file mode 100644 index 00000000000..dd5e5851bb2 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-mentors_guidance.txt @@ -0,0 +1,9 @@ +Name:A-Mentor's Guidance +ManaCost:1 U +Types:Sorcery +T:Mode$ SpellCast | ValidCard$ Card.Self | Execute$ TrigCopy | TriggerDescription$ When you cast this spell, copy it if you control a planeswalker, Cleric, Druid, Shaman, Warlock, or Wizard. +SVar:TrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | ConditionPresent$ Planeswalker.YouCtrl,Cleric.YouCtrl,Druid.YouCtrl,Shaman.YouCtrl,Warlock.YouCtrl,Wizard.YouCtrl | ConditionCompare$ GE1 +A:SP$ Scry | ScryNum$ 1 | SubAbility$ DBDraw | SpellDescription$ Scry 1, +SVar:DBDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SpellDescription$ then draw a card. +DeckHints:Type$Planeswalker|Cleric|Druid|Shaman|Warlock|Wizard +Oracle:When you cast this spell, copy it if you control a planeswalker, Cleric, Druid, Shaman, Warlock, or Wizard.\nScry 1, then draw a card. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-moss_pit_skeleton.txt b/forge-gui/res/cardsfolder/rebalanced/a-moss_pit_skeleton.txt new file mode 100644 index 00000000000..642ea4e05ce --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-moss_pit_skeleton.txt @@ -0,0 +1,12 @@ +Name:A-Moss-Pit Skeleton +ManaCost:B G +Types:Creature Plant Skeleton +PT:2/2 +K:Kicker:3 +K:etbCounter:P1P1:3:CheckSVar$ WasKicked:If CARDNAME was kicked, it enters the battlefield with three +1/+1 counters on it. +SVar:WasKicked:Count$Kicked.1.0 +T:Mode$ CounterAddedOnce | ValidCard$ Creature.YouCtrl | CounterType$ P1P1 | TriggerZones$ Graveyard | IsPresent$ Card.StrictlySelf | PresentZone$ Graveyard | Execute$ TrigDelayedTrigger | TriggerDescription$ Whenever one or more +1/+1 counters are put on a creature you control, return CARDNAME from your graveyard to your hand at the beginning of the next end step. +SVar:TrigDelayedTrigger:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigChange | TriggerDescription$ Return CARDNAME from your graveyard to your hand at the beginning of the next end step. +SVar:TrigChange:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Hand +DeckHints:Ability$Counters|Graveyard +Oracle:Kicker {3}\nIf Moss-Pit Skeleton was kicked, it enters the battlefield with three +1/+1 counters on it.\nWhenever one or more +1/+1 counters are put on a creature you control, return Moss-Pit Skeleton from your graveyard to your hand at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-ochre_jelly.txt b/forge-gui/res/cardsfolder/rebalanced/a-ochre_jelly.txt new file mode 100644 index 00000000000..717e43c381d --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-ochre_jelly.txt @@ -0,0 +1,14 @@ +Name:A-Ochre Jelly +ManaCost:X G +Types:Creature Ooze +PT:0/0 +K:Trample +K:Ward:2 +K:etbCounter:P1P1:X +SVar:X:Count$xPaid +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self+counters_GE2_P1P1 | TriggerZones$ Battlefield | Execute$ TrigDelayTrigger | TriggerDescription$ Split — When CARDNAME dies, if it had two or more +1/+1 counters on it, create a token that's a copy of it at the beginning of the next end step. The token enters the battlefield with half that many +1/+1 counters on it, rounded down. +SVar:TrigDelayTrigger:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | ValidPlayer$ Player | RememberObjects$ TriggeredCardLKICopy | CopyTriggeringObjects$ True | Execute$ TrigCopy | TriggerDescription$ Create a token that's a copy of it at the beginning of the next end step. That token enters the battlefield with half that many +1/+1 counters on it, rounded down. +SVar:TrigCopy:DB$ CopyPermanent | Defined$ DelayTriggerRememberedLKI | WithCountersType$ P1P1 | WithCountersAmount$ Y +SVar:Y:TriggeredCard$CardCounters.P1P1/HalfDown +DeckHas:Ability$Counters|Token +Oracle:Trample\nWard {2}\nOchre Jelly enters the battlefield with X +1/+1 counters on it.\nSplit — When Ochre Jelly dies, if it had two or more +1/+1 counters on it, create a token that's a copy of it at the beginning of the next end step. The token enters the battlefield with half that many +1/+1 counters on it, rounded down. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-oran_rief_ooze.txt b/forge-gui/res/cardsfolder/rebalanced/a-oran_rief_ooze.txt new file mode 100644 index 00000000000..4f66135fa49 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-oran_rief_ooze.txt @@ -0,0 +1,11 @@ +Name:A-Oran-Rief Ooze +ManaCost:2 G +Types:Creature Ooze +PT:2/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, 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 +T:Mode$ Attacks | ValidCard$ Creature.Self | Execute$ TrigPutCounterAll | TriggerDescription$ Whenever CARDNAME attacks, put a +1/+1 counter on each creature you control with a +1/+1 counter on it. +SVar:TrigPutCounterAll:DB$ PutCounterAll | ValidCards$ Creature.YouCtrl+counters_GE1_P1P1 | CounterType$ P1P1 | CounterNum$ 1 +SVar:HasAttackEffect:TRUE +DeckHas:Ability$Counters +Oracle:When Oran-Rief Ooze enters the battlefield, put a +1/+1 counter on target creature you control.\nWhenever Oran-Rief Ooze attacks, put a +1/+1 counter on each creature you control with a +1/+1 counter on it. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-rockslide_sorcerer.txt b/forge-gui/res/cardsfolder/rebalanced/a-rockslide_sorcerer.txt new file mode 100644 index 00000000000..b8e1aa4dace --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-rockslide_sorcerer.txt @@ -0,0 +1,9 @@ +Name:A-Rockslide Sorcerer +ManaCost:2 R +Types:Creature Human Wizard +PT:2/2 +T:Mode$ SpellCast | ValidCard$ Instant,Sorcery,Wizard | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDealDamage | TriggerDescription$ Whenever you cast an instant, sorcery, or Wizard spell, CARDNAME deals 1 damage to any target. +SVar:TrigDealDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 +SVar:BuffedBy:Instant,Sorcery,Wizard +DeckNeeds:Type$Instant|Sorcery|Wizard +Oracle:Whenever you cast an instant, sorcery, or Wizard spell, Rockslide Sorcerer deals 1 damage to any target. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-rowan_scholar_of_sparks_will_scholar_of_frost.txt b/forge-gui/res/cardsfolder/rebalanced/a-rowan_scholar_of_sparks_will_scholar_of_frost.txt new file mode 100644 index 00000000000..90095d53a16 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-rowan_scholar_of_sparks_will_scholar_of_frost.txt @@ -0,0 +1,30 @@ +Name:A-Rowan, Scholar of Sparks +ManaCost:2 R +Types:Legendary Planeswalker Rowan +Loyalty:3 +S:Mode$ ReduceCost | ValidCard$ Instant,Sorcery | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Instant and sorcery spells you cast cost {1} less to cast. +A:AB$ DealDamage | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | Defined$ Player.Opponent | NumDmg$ X | SpellDescription$ CARDNAME deals 1 damage to each opponent. If you've drawn three or more cards this turn, she deals 3 damage to each opponent instead. +SVar:X:Count$Compare Y GE3.3.1 +SVar:Y:Count$YouDrewThisTurn +A:AB$ Effect | Cost$ SubCounter<5/LOYALTY> | Planeswalker$ True | Ultimate$ True | Name$ Emblem - Rowan, Scholar of Sparks | Triggers$ TRCast | Duration$ Permanent | SpellDescription$ You get an emblem with "Whenever you cast an instant or sorcery spell, you may pay {2}. If you do, copy that spell. You may choose new targets for the copy." +SVar:TRCast:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Command | Execute$ TrigCopy | TriggerDescription$ Whenever you cast an instant or sorcery spell, you may pay {2}. If you do, copy that spell. You may choose new targets for the copy. +SVar:TrigCopy:AB$ CopySpellAbility | Cost$ 2 | Defined$ TriggeredSpellAbility | AILogic$ AlwaysIfViable | MayChooseTarget$ True +AlternateMode:Modal +DeckHints:Type$Instant|Sorcery +Oracle:Instant and sorcery spells you cast cost {1} less to cast.\n[+1]: Rowan, Scholar of Sparks deals 1 damage to each opponent. If you've drawn three or more cards this turn, she deals 3 damage to each opponent instead.\n[-5]: You get an emblem with "Whenever you cast an instant or sorcery spell, you may pay {2}. If you do, copy that spell. You may choose new targets for the copy." + +ALTERNATE + +Name:A-Will, Scholar of Frost +ManaCost:4 U +Types:Legendary Planeswalker Will +Loyalty:5 +S:Mode$ ReduceCost | ValidCard$ Instant,Sorcery | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Instant and sorcery spells you cast cost {1} less to cast. +A:AB$ Animate | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature | TgtPrompt$ Select up to one target creature | TargetMin$ 0 | TargetMax$ 1 | Power$ 0 | Toughness$ 2 | Duration$ UntilYourNextTurn | SpellDescription$ Up to one target creature has base power and toughness 0/2 until your next turn. +A:AB$ Draw | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | NumCards$ 2 | SpellDescription$ Draw two cards. +A:AB$ ChangeZone | Cost$ SubCounter<8/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidTgts$ Permanent | TgtPrompt$ Select up to five target permanents | TargetMin$ 0 | TargetMax$ 5 | Origin$ Battlefield | Destination$ Exile | RememberLKI$ True | SubAbility$ DBRepeat | SpellDescription$ Exile up to five target permanents. For each permanent exiled this way, its controller creates a 4/4 blue and red Elemental creature token. +SVar:DBRepeat:DB$ RepeatEach | DefinedCards$ DirectRemembered | UseImprinted$ True | RepeatSubAbility$ DBToken | SubAbility$ DBCleanup | ChangeZoneTables$ True +SVar:DBToken:DB$ Token | TokenScript$ ur_4_4_elemental | TokenOwner$ ImprintedController +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHas:Ability$Token +Oracle:Instant and sorcery spells you cast cost {1} less to cast.\n[+1]: Up to one target creature has base power and toughness 0/2 until your next turn.\n[-3]: Draw two cards.\n[-8]: Exile up to five target permanents. For each permanent exiled this way, its controller creates a 4/4 blue and red Elemental creature token. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-sigardian_paladin.txt b/forge-gui/res/cardsfolder/rebalanced/a-sigardian_paladin.txt new file mode 100644 index 00000000000..9ecc4847fe3 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-sigardian_paladin.txt @@ -0,0 +1,10 @@ +Name:A-Sigardian Paladin +ManaCost:1 G W +Types:Creature Human Knight +PT:4/3 +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Trample & Lifelink | CheckSVar$ X | Description$ As long as you've put one or more +1/+1 counters on a creature this turn, CARDNAME has trample and lifelink. +SVar:X:Count$CountersAddedThisTurn P1P1 You Creature +A:AB$ Pump | Cost$ G W | ValidTgts$ Creature.YouCtrl+counters_GE1_P1P1 | TgtPrompt$ Select target creature with a +1/+1 counter on it | KW$ Trample & Lifelink | SpellDescription$ Target creature you control with a +1/+1 counter on it gains trample and lifelink until end of turn. +DeckNeeds:Ability$Counters +DeckHas:Ability$LifeGain +Oracle:As long as you've put one or more +1/+1 counters on a creature this turn, Sigardian Paladin has trample and lifelink.\n{G}{W}: Target creature you control with a +1/+1 counter on it gains trample and lifelink until end of turn. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-skyclave_shadowcat.txt b/forge-gui/res/cardsfolder/rebalanced/a-skyclave_shadowcat.txt new file mode 100644 index 00000000000..c510e3832ea --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-skyclave_shadowcat.txt @@ -0,0 +1,10 @@ +Name:A-Skyclave Shadowcat +ManaCost:2 B +Types:Creature Cat Horror +PT:3/3 +A:AB$ PutCounter | Cost$ B Sac<1/Creature.Other/another creature> | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Put a +1/+1 counter on CARDNAME. +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouCtrl+counters_GE1_P1P1 | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever a creature you control with a +1/+1 counter on it dies, draw a card. +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 +SVar:PlayMain1:TRUE +DeckHas:Ability$Sacrifice|Counters +Oracle:{B}, Sacrifice another creature: Put a +1/+1 counter on Skyclave Shadowcat.\nWhenever a creature you control with a +1/+1 counter on it dies, draw a card. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-sorcerer_class.txt b/forge-gui/res/cardsfolder/rebalanced/a-sorcerer_class.txt new file mode 100644 index 00000000000..57f2d403001 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-sorcerer_class.txt @@ -0,0 +1,14 @@ +Name:A-Sorcerer Class +ManaCost:U R +Types:Enchantment Class +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw two cards, then discard two cards. +SVar:TrigDraw:DB$ Draw | NumCards$ 2 | SubAbility$ DBDiscard +SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 2 | Mode$ TgtChoose +K:Class:2:U R:AddStaticAbility$ STapMana +SVar:STapMana:Mode$ Continuous | EffectZone$ Battlefield | Affected$ Creature.YouCtrl | AddAbility$ AddMana | Secondary$ True | Description$ Creatures you control have "{T}: Add {U} or {R}. Spend this mana only to cast an instant or sorcery spell or to gain a Class level." +SVar:AddMana:AB$ Mana | Cost$ T | Produced$ Combo U R | Amount$ 1 | RestrictValid$ Spell.Instant,Spell.Sorcery,Activated.ClassLevelUp | SpellDescription$ Add {U} or {R}. Spend this mana only to cast an instant or sorcery spell or to gain a Class level. +K:Class:3:1 U R:AddTrigger$ TriggerSpellCast +SVar:TriggerSpellCast:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigDealDamage | Secondary$ True | TriggerDescription$ Whenever you cast an instant or sorcery spell, that spell deals damage to each opponent equal to the number of instant and sorcery spells you've cast this turn. +SVar:TrigDealDamage:DB$ DamageAll | ValidPlayers$ Player.Opponent | NumDmg$ X | DamageSource$ TriggeredCard +SVar:X:Count$ThisTurnCast_Instant.YouCtrl,Sorcery.YouCtrl +Oracle:(Gain the next level as a sorcery to add its ability.)\nWhen Sorcerer Class enters the battlefield, draw two cards, then discard two cards.\n{U}{R}: Level 2\nCreatures you control have "{T}: Add {U} or {R}. Spend this mana only to cast an instant or sorcery spell or to gain a Class level."\n{1}{U}{R}: Level 3\nWhenever you cast an instant or sorcery spell, that spell deals damage to each opponent equal to the number of instant and sorcery spells you've cast this turn. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-tanazir_quandrix.txt b/forge-gui/res/cardsfolder/rebalanced/a-tanazir_quandrix.txt new file mode 100644 index 00000000000..c13be2ee7fb --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-tanazir_quandrix.txt @@ -0,0 +1,15 @@ +Name:A-Tanazir Quandrix +ManaCost:3 G U +Types:Legendary Creature Elder Dragon +PT:5/5 +K:Flying +K:Trample +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDouble | TriggerDescription$ When CARDNAME enters the battlefield, double the number of +1/+1 counters on target creature you control. +SVar:TrigDouble:DB$ MultiplyCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | CounterType$ P1P1 +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigAnimateAll | OptionalDecider$ You | TriggerDescription$ Whenever CARDNAME attacks, you may have the base power and toughness of other creatures you control become equal to CARDNAME's power and toughness until end of turn. +SVar:TrigAnimateAll:DB$ AnimateAll | Power$ X | Toughness$ Y | ValidCards$ Creature.YouCtrl+Other +SVar:X:Count$CardPower +SVar:Y:Count$CardToughness +SVar:HasAttackEffect:TRUE +DeckHas:Ability$Counters +Oracle:Flying, trample\nWhen Tanazir Quandrix enters the battlefield, double the number of +1/+1 counters on target creature you control.\nWhenever Tanazir Quandrix attacks, you may have the base power and toughness of other creatures you control become equal to Tanazir Quandrix's power and toughness until end of turn. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-tenured_inkcaster.txt b/forge-gui/res/cardsfolder/rebalanced/a-tenured_inkcaster.txt new file mode 100644 index 00000000000..5150ad8f6bf --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-tenured_inkcaster.txt @@ -0,0 +1,11 @@ +Name:A-Tenured Inkcaster +ManaCost:3 B +Types:Creature Vampire Warlock +PT:3/3 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPutCounter | TriggerDescription$ When CARDNAME enters the battlefield, put a +1/+1 counter on target creature. +SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature you control | CounterType$ P1P1 | CounterNum$ 1 +T:Mode$ Attacks | ValidCard$ Creature.YouCtrl+counters_GE1_P1P1 | TriggerZones$ Battlefield | Execute$ TrigDrain | TriggerDescription$ Whenever a creature you control with a +1/+1 counter on it attacks, each opponent loses 1 life and you gain 1 life. +SVar:TrigDrain:DB$ LoseLife | Defined$ Player.Opponent | LifeAmount$ 1 | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 1 +DeckHas:Ability$Counters|LifeGain +Oracle:When Tenured Inkcaster enters the battlefield, put a +1/+1 counter on target creature.\nWhenever a creature you control with a +1/+1 counter on it attacks, each opponent loses 1 life and you gain 1 life. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-the_meathook_massacre.txt b/forge-gui/res/cardsfolder/rebalanced/a-the_meathook_massacre.txt new file mode 100644 index 00000000000..2fb4b9dae26 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-the_meathook_massacre.txt @@ -0,0 +1,10 @@ +Name:A-The Meathook Massacre +ManaCost:X B B +Types:Legendary Enchantment +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPumpAll | TriggerDescription$ When CARDNAME enters the battlefield, each creature gets -X/-X until end of turn. +SVar:TrigPumpAll:DB$ PumpAll | ValidCards$ Creature | NumAtt$ -X | NumDef$ -X | IsCurse$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigLoseLife | TriggerDescription$ Whenever a creature you control dies, each opponent loses 1 life. +SVar:TrigLoseLife:DB$ LoseLife | Defined$ Opponent | LifeAmount$ 1 +SVar:X:Count$xPaid +DeckHints:Ability$Graveyard|Sacrifice +Oracle:When The Meathook Massacre enters the battlefield, each creature gets -X/-X until end of turn.\nWhenever a creature you control dies, each opponent loses 1 life. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-tome_shredder.txt b/forge-gui/res/cardsfolder/rebalanced/a-tome_shredder.txt new file mode 100644 index 00000000000..22102dc727e --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-tome_shredder.txt @@ -0,0 +1,8 @@ +Name:A-Tome Shredder +ManaCost:1 R +Types:Creature Wolf +PT:2/1 +K:Haste +A:AB$ PutCounter | Cost$ T ExileFromGrave<1/Instant;Sorcery> | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | SpellDescription$ Exile an instant or sorcery card from your graveyard: Put a +1/+1 counter on CARDNAME. +DeckHas:Ability$Counters +Oracle:Haste\n{T}, Exile an instant or sorcery card from your graveyard: Put a +1/+1 counter on Tome Shredder. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-umara_mystic.txt b/forge-gui/res/cardsfolder/rebalanced/a-umara_mystic.txt new file mode 100644 index 00000000000..ad36b9a7a0a --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-umara_mystic.txt @@ -0,0 +1,11 @@ +Name:A-Umara Mystic +ManaCost:1 U R +Types:Creature Merfolk Wizard +PT:1/3 +K:Flying +K:Haste +T:Mode$ SpellCast | ValidCard$ Instant,Sorcery,Wizard | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever you cast a instant, sorcery, or Wizard spell, CARDNAME gets +2/+0 until end of turn. +SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ 2 +SVar:BuffedBy:Instant,Sorcery,Wizard +DeckHints:Type$Instant|Sorcery|Wizard +Oracle:Flying, haste\nWhenever you cast an instant, sorcery, or Wizard spell, Umara Mystic gets +2/+0 until end of turn. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-unholy_heat.txt b/forge-gui/res/cardsfolder/rebalanced/a-unholy_heat.txt new file mode 100644 index 00000000000..e677adfc180 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-unholy_heat.txt @@ -0,0 +1,7 @@ +Name:A-Unholy Heat +ManaCost:R +Types:Instant +A:SP$ DealDamage | Cost$ R | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker | NumDmg$ X | SpellDescription$ CARDNAME deals 2 damage to target creature or planeswalker. Delirium — CARDNAME deals 4 damage instead if there are four or more card types among cards in your graveyard. +SVar:X:Count$Compare Y GE4.4.2 +SVar:Y:Count$CardControllerTypes.Graveyard +Oracle:Unholy Heat deals 2 damage to target creature or planeswalker.\nDelirium — Unholy Heat deals 4 damage instead if there are four or more card types among cards in your graveyard. diff --git a/forge-gui/res/cardsfolder/rebalanced/a-winota_joiner_of_forces.txt b/forge-gui/res/cardsfolder/rebalanced/a-winota_joiner_of_forces.txt new file mode 100644 index 00000000000..92def8178d9 --- /dev/null +++ b/forge-gui/res/cardsfolder/rebalanced/a-winota_joiner_of_forces.txt @@ -0,0 +1,10 @@ +Name:A-Winota, Joiner of Forces +ManaCost:2 R W +Types:Legendary Creature Human Warrior +PT:4/4 +T:Mode$ AttackersDeclared | ValidAttackers$ Creature.nonHuman+YouCtrl | Execute$ TrigDig | TriggerZones$ Battlefield | TriggerDescription$ Whenever one or more non-Human creatures you control attack, look at the top six cards of your library. You may put a Human creature card from among them onto the battlefield tapped and attacking. It gains indestructible until end of turn. Put the rest of the cards on the bottom of your library in a random order. +SVar:TrigDig:DB$ Dig | DigNum$ 6 | ChangeNum$ 1 | Optional$ True | Reveal$ False | ChangeValid$ Creature.Human | DestinationZone$ Battlefield | DestinationZone2$ Library | LibraryPosition$ -1 | RestRandomOrder$ True | Tapped$ True | Attacking$ True | RememberChanged$ True | SubAbility$ DBPump +SVar:DBPump:DB$ Pump | Defined$ Remembered | KW$ Indestructible | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHints:Type$Human +Oracle:Whenever one or more non-Human creatures you control attack, look at the top six cards of your library. You may put a Human creature card from among them onto the battlefield tapped and attacking. It gains indestructible until end of turn. Put the rest of the cards on the bottom of your library in a random order. diff --git a/forge-gui/res/cardsfolder/s/shielded_passage.txt b/forge-gui/res/cardsfolder/s/shielded_passage.txt index 596241bbb38..5ab6ad87861 100644 --- a/forge-gui/res/cardsfolder/s/shielded_passage.txt +++ b/forge-gui/res/cardsfolder/s/shielded_passage.txt @@ -1,6 +1,7 @@ Name:Shielded Passage ManaCost:W Types:Instant -A:SP$ Pump | Cost$ W | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Prevent all damage that would be dealt to target creature this turn. +A:SP$ Effect | ValidTgts$ Creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target creature this turn. +SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn. AI:RemoveDeck:All Oracle:Prevent all damage that would be dealt to target creature this turn. diff --git a/forge-gui/res/cardsfolder/s/sigardian_paladin.txt b/forge-gui/res/cardsfolder/s/sigardian_paladin.txt index a31376d48aa..36a0b3f8a9a 100644 --- a/forge-gui/res/cardsfolder/s/sigardian_paladin.txt +++ b/forge-gui/res/cardsfolder/s/sigardian_paladin.txt @@ -2,7 +2,7 @@ Name:Sigardian Paladin ManaCost:2 G W Types:Creature Human Knight PT:4/4 -S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Trample & Lifelink | CheckSVar$ X | Description$ As long as you've put one or more +1/+1 counters on a creature this turn, Sigardian Paladin has trample and lifelink. +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Trample & Lifelink | CheckSVar$ X | Description$ As long as you've put one or more +1/+1 counters on a creature this turn, CARDNAME has trample and lifelink. SVar:X:Count$CountersAddedThisTurn P1P1 You Creature A:AB$ Pump | Cost$ 1 G W | ValidTgts$ Creature.YouCtrl+counters_GE1_P1P1 | TgtPrompt$ Select target creature with a +1/+1 counter on it | KW$ Trample & Lifelink | SpellDescription$ Target creature you control with a +1/+1 counter on it gains trample and lifelink until end of turn. DeckNeeds:Ability$Counters diff --git a/forge-gui/res/cardsfolder/s/skyfire_kirin.txt b/forge-gui/res/cardsfolder/s/skyfire_kirin.txt index 59045bc1a12..b4e7267b073 100644 --- a/forge-gui/res/cardsfolder/s/skyfire_kirin.txt +++ b/forge-gui/res/cardsfolder/s/skyfire_kirin.txt @@ -5,6 +5,6 @@ PT:3/3 K:Flying T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigChange | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, you may gain control of target creature with that spell's mana value until end of turn. SVar:TrigChange:DB$ GainControl | ValidTgts$ Creature.cmcEQX | TgtPrompt$ Select target creature | LoseControl$ EOT | NewController$ You -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI DeckHints:Type$Spirit|Arcane Oracle:Flying\nWhenever you cast a Spirit or Arcane spell, you may gain control of target creature with that spell's mana value until end of turn. diff --git a/forge-gui/res/cardsfolder/s/sunbirds_invocation.txt b/forge-gui/res/cardsfolder/s/sunbirds_invocation.txt index 384206a983b..691bbc6dcdd 100644 --- a/forge-gui/res/cardsfolder/s/sunbirds_invocation.txt +++ b/forge-gui/res/cardsfolder/s/sunbirds_invocation.txt @@ -6,5 +6,5 @@ SVar:TrigDig:DB$ PeekAndReveal | Defined$ You | PeekAmount$ X | RememberRevealed SVar:DBPlay:DB$ Play | ValidZone$ Library | Valid$ Card.nonLand+IsRemembered | ValidSA$ Spell.cmcLEX | WithoutManaCost$ True | Optional$ True | Amount$ 1 | ShowCards$ Card.IsRemembered | ForgetPlayed$ True | SubAbility$ DBRestRandomOrder SVar:DBRestRandomOrder:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI Oracle:Whenever you cast a spell from your hand, reveal the top X cards of your library, where X is that spell's mana value. You may cast a spell with mana value X or less from among cards revealed this way without paying its mana cost. Put the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/cardsfolder/t/timothar_baron_of_bats.txt b/forge-gui/res/cardsfolder/t/timothar_baron_of_bats.txt index ef1782bc389..01b4860e327 100644 --- a/forge-gui/res/cardsfolder/t/timothar_baron_of_bats.txt +++ b/forge-gui/res/cardsfolder/t/timothar_baron_of_bats.txt @@ -3,10 +3,9 @@ ManaCost:4 B B Types:Legendary Creature Vampire Noble PT:4/4 K:Ward:Discard<1/Card> -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Vampire.Other+nonToken+YouCtrl | RememberTriggeringCard$ True | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever another nontoken Vampire you control dies, you may pay {1} and exile it. If you do, create a 1/1 black Bat creature token with flying. It gains "When this creature deals combat damage to a player, sacrifice it and return the exiled card to the battlefield tapped." -SVar:TrigToken:AB$ Token | Cost$ 1 ExileFromGrave<1/Card.IsRemembered/the Vampire card> | TokenRemembered$ ExiledCards | TokenScript$ b_1_1_bat_flying | ImprintTokens$ True | SubAbility$ DBAnimate -SVar:DBAnimate:DB$ Animate | Defined$ Imprinted | Duration$ Permanent | Triggers$ CDTrigger | Duration$ Permanent | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True | ClearRemembered$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Vampire.Other+nonToken+YouCtrl | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever another nontoken Vampire you control dies, you may pay {1} and exile it. If you do, create a 1/1 black Bat creature token with flying. It gains "When this creature deals combat damage to a player, sacrifice it and return the exiled card to the battlefield tapped." +SVar:TrigToken:AB$ Token | Cost$ 1 ExileFromGrave<1/Card.TriggeredNewCard/the Vampire card> | TokenRemembered$ ExiledCards | TokenScript$ b_1_1_bat_flying | ImprintTokens$ True | SubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | Defined$ Imprinted | Duration$ Permanent | Triggers$ CDTrigger SVar:CDTrigger:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigSac | TriggerZones$ Battlefield | TriggerDescription$ When this creature deals combat damage to a player, sacrifice it and return the exiled card to the battlefield tapped. SVar:TrigSac:DB$ Sacrifice | Defined$ Self | SubAbility$ DBReturn SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | Tapped$ True diff --git a/forge-gui/res/cardsfolder/upcoming/alaundo_the_seer.txt b/forge-gui/res/cardsfolder/upcoming/alaundo_the_seer.txt new file mode 100644 index 00000000000..8d018b7090f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/alaundo_the_seer.txt @@ -0,0 +1,15 @@ +Name:Alaundo the Seer +ManaCost:2 G U +Types:Legendary Creature Human Shaman +PT:3/5 +A:AB$ Draw | Cost$ T | SubAbility$ DBExile | StackDescription$ SpellDescription | SpellDescription$ Draw a card, then exile a card from your hand and put a number of time counters on it equal to its mana value. It gains "When the last time counter is removed from this card, if it's exiled, you may cast it without paying its mana cost. If you cast a creature spell this way, it gains haste until end of turn." Then remove a time counter from each other card you own in exile. +SVar:DBExile:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | TgtPrompt$ Select a card to exile with a number of time counters equal to its mana value | WithCountersType$ TIME | WithCountersAmount$ X | SubAbility$ DBAddTrigger | RememberChanged$ True | Mandatory$ True +SVar:X:Remembered$CardManaCost +SVar:DBAddTrigger:DB$ Animate | Defined$ Remembered | Triggers$ TWhenLastTimeCounterRemoved | Duration$ Permanent | SubAbility$ DBRemoveTimeCounterOtherCardsInExile +SVar:TWhenLastTimeCounterRemoved:Mode$ CounterRemoved | ValidCard$ Card.Self+counters_EQ0_TIME | TriggerZones$ Exile | CounterType$ TIME | Execute$ DBCastWithoutPayingManaCost | TriggerDescription$ When the last time counter is removed from this card, if it's exiled, you may cast it without paying its mana cost. If you cast a creature spell this way, it gains haste until end of turn. +SVar:DBCastWithoutPayingManaCost:DB$ Play | Valid$ Card.Self | ValidSA$ Spell | ValidZone$ Exile | Destination$ Battlefield | WithoutManaCost$ True | Optional$ True | SubAbility$ DBGiveHaste +SVar:DBGiveHaste:DB$ Pump | Defined$ Self | KW$ Haste | ConditionDefined$ Self | ConditionPresent$ Card.Creature | PumpZone$ Stack +SVar:DBRemoveTimeCounterOtherCardsInExile:DB$ RemoveCounterAll | ValidCards$ Card.YouOwn+IsNotRemembered | ValidZone$ Exile | CounterType$ TIME | CounterNum$ 1 | SubAbility$ DBCleanUp +SVar:DBCleanUp:DB$ Cleanup | ClearRemembered$ True +DeckHints:Keyword$Suspend +Oracle:{T}:Draw a card, then exile a card from your hand and put a number of time counters on it equal to its mana value. It gains "When the last time counter is removed from this card, if it's exiled, you may cast it without paying its mana cost. If you cast a creature spell this way, it gains haste until end of turn." Then remove a time counter from each other card you own in exile. diff --git a/forge-gui/res/cardsfolder/upcoming/astral_confrontation.txt b/forge-gui/res/cardsfolder/upcoming/astral_confrontation.txt new file mode 100644 index 00000000000..b5325fe01ae --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/astral_confrontation.txt @@ -0,0 +1,7 @@ +Name:Astral Confrontation +ManaCost:4 W +Types:Instant +S:Mode$ ReduceCost | ValidCard$ Card.Self | Type$ Spell | Amount$ X | EffectZone$ All | Description$ This spell costs {1} less to cast for each opponent you're attacking. +SVar:X:PlayerCountPropertyYou$OpponentsAttackedThisCombat +A:SP$ ChangeZone | Cost$ 4 W | ValidTgts$ Creature | TgtPrompt$ Select target creature | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target creature. +Oracle:This spell costs 1 less to cast for each opponent you're attacking.\nExile target creature. diff --git a/forge-gui/res/cardsfolder/upcoming/astral_dragon.txt b/forge-gui/res/cardsfolder/upcoming/astral_dragon.txt new file mode 100644 index 00000000000..76ef3414450 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/astral_dragon.txt @@ -0,0 +1,9 @@ +Name:Astral Dragon +ManaCost:6 U U +Types:Creature Dragon +PT:4/4 +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCopy | TriggerDescription$ Project Image — When CARDNAME enters the battlefield, create two tokens that are copies of target noncreature permanent, except they're 3/3 Dragon creatures in addition to their other types, and they have flying. +SVar:TrigCopy:DB$ CopyPermanent | ValidTgts$ Permanent.nonCreature | NumCopies$ 2 | SetPower$ 3 | SetToughness$ 3 | AddTypes$ Dragon & Creature | AddKeywords$ Flying | TgtPrompt$ Select target noncreature permanent +DeckHas:Ability$Token +Oracle:Flying\nProject Image — When Astral Dragon enters the battlefield, create two tokens that are copies of target noncreature permanent, except they're 3/3 Dragon creatures in addition to their other types, and they have flying. diff --git a/forge-gui/res/cardsfolder/upcoming/battle_angels_of_tyr.txt b/forge-gui/res/cardsfolder/upcoming/battle_angels_of_tyr.txt new file mode 100644 index 00000000000..36d511876be --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/battle_angels_of_tyr.txt @@ -0,0 +1,18 @@ +Name:Battle Angels of Tyr +ManaCost:2 W W +Types:Creature Angel Knight +PT:4/4 +K:Flying +K:Myriad +T:Mode$ DamageDone | ValidSource$ Card.Self | Execute$ TrigDraw | CombatDamage$ True | ValidTarget$ Player | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, draw a card if that player has more cards in hand than each other player. Then you create a Treasure token if that player controls more lands than each other player. Then you gain 3 life if that player has more life than each other player. +SVar:TrigDraw:DB$ Draw | ConditionCheckSVar$ NumInHand | ConditionSVarCompare$ GTMostInHand | SubAbility$ DBTreasure +SVar:DBTreasure:DB$ Token | ConditionCheckSVar$ NumLands | ConditionSVarCompare$ GTMostLands | TokenScript$ c_a_treasure_sac | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | ConditionCheckSVar$ NumLife | ConditionSVarCompare$ GTMostLife | LifeAmount$ 3 +SVar:NumInHand:TriggeredTarget$CardsInHand +SVar:MostInHand:PlayerCountDefinedNonTriggeredTarget$HighestCardsInHand +SVar:NumLands:TriggeredTarget$Valid Land.YouCtrl +SVar:MostLands:PlayerCountDefinedNonTriggeredTarget$HighestValid Land.YouCtrl +SVar:NumLife:TriggeredTarget$LifeTotal +SVar:MostLife:PlayerCountDefinedNonTriggeredTarget$HighestLifeTotal +DeckHas:Ability$Token|Sacrifice|LifeGain & Type$Artifact|Treasure +Oracle:Flying, myriad\nWhenever Battle Angels of Tyr deals combat damage to a player, draw a card if that player has more cards in hand than each other player. Then you create a Treasure token if that player controls more lands than each other player. Then you gain 3 life if that player has more life than each other player. diff --git a/forge-gui/res/cardsfolder/upcoming/blur.txt b/forge-gui/res/cardsfolder/upcoming/blur.txt new file mode 100644 index 00000000000..5a87dc42d3f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/blur.txt @@ -0,0 +1,8 @@ +Name:Blur +ManaCost:2 U +Types:Instant +A:SP$ ChangeZone | ValidTgts$ Creature.YouCtrl | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Select target creature you control | RememberTargets$ True | SubAbility$ DBReturn | SpellDescription$ Exile target creature you control, then return that card to the battlefield under their owner's control. +SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ All | Destination$ Battlefield | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw | NumCards$ 1 | SpellDescription$ Draw a card. +Oracle:Exile target creature you control, then return that card to the battlefield under their owner's control.\nDraw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/bronze_walrus.txt b/forge-gui/res/cardsfolder/upcoming/bronze_walrus.txt new file mode 100644 index 00000000000..8cd0fe6cb95 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bronze_walrus.txt @@ -0,0 +1,8 @@ +Name:Bronze Walrus +ManaCost:3 +Types:Artifact Creature Walrus +PT:2/2 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigScry | TriggerDescription$ When CARDNAME enters the battlefield, scry 2. (Look at the top two cards of your library, then put any number of them on the bottom of your library and the rest on top in any order.) +SVar:TrigScry:DB$ Scry | ScryNum$ 2 +A:AB$ Mana | Cost$ T | Produced$ Any | SpellDescription$ Add one mana of any color. +Oracle:When Bronze Walrus enters the battlefield, scry 2. (Look at the top two cards of your library, then put any number of them on the bottom of your library and the rest on top in any order.)\n{T}: Add one mana of any color. diff --git a/forge-gui/res/cardsfolder/upcoming/call_to_the_void.txt b/forge-gui/res/cardsfolder/upcoming/call_to_the_void.txt new file mode 100644 index 00000000000..1d3cdcd9124 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/call_to_the_void.txt @@ -0,0 +1,6 @@ +Name:Call to the Void +ManaCost:4 B +Types:Sorcery +A:SP$ ChooseCard | Defined$ Player | Choices$ Creature | SecretlyChoose$ True | Amount$ 1 | ControlAndNot$ True | ChoiceTitle$ Secretly choose a creature | Reveal$ True | RevealTitle$ OVERRIDE Chosen creatures. They will be destroyed. | SubAbility$ DBDestroyChosen | Mandatory$ True +SVar:DBDestroyChosen:DB$ DestroyAll | ValidCards$ Creature.ChosenCard +Oracle:Each player secretly chooses a creature they control and a creature they don't control. Then those choices are revealed. Destroy each creature chosen this way. diff --git a/forge-gui/res/cardsfolder/upcoming/circle_of_the_land_druid.txt b/forge-gui/res/cardsfolder/upcoming/circle_of_the_land_druid.txt new file mode 100644 index 00000000000..7eab57122a1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/circle_of_the_land_druid.txt @@ -0,0 +1,10 @@ +Name:Circle of the Land Druid +ManaCost:1 G +Types:Creature Gnome Druid +PT:1/1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMill | TriggerDescription$ When CARDNAME enters the battlefield, you may mill four cards. (You may put the top four cards of your library into your graveyard.) +SVar:TrigMill:DB$ Mill | NumCards$ 4 | Defined$ You | Optional$ True +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChangeZone | TriggerDescription$ Natural Recovery — When CARDNAME dies, return target land card from your graveyard to your hand. +SVar:TrigChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand | ValidTgts$ Land.YouCtrl +DeckHas:Ability$Mill|Graveyard +Oracle:When Circle of the Land Druid enters the battlefield, you may mill four cards. (You may put the top four cards of your library into your graveyard.)\nNatural Recovery — When Circle of the Land Druid dies, return target land card from your graveyard to your hand. diff --git a/forge-gui/res/cardsfolder/upcoming/clockwork_fox.txt b/forge-gui/res/cardsfolder/upcoming/clockwork_fox.txt new file mode 100644 index 00000000000..c60ee91e115 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/clockwork_fox.txt @@ -0,0 +1,8 @@ +Name:Clockwork Fox +ManaCost:3 +Types:Artifact Creature Fox +PT:3/2 +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME leaves the battlefield, you draw two cards and each opponent draws a card. +SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 2 | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw | Defined$ Player.Opponent | NumCards$ 1 +Oracle:When Clockwork Fox leaves the battlefield, you draw two cards and each opponent draws a card. diff --git a/forge-gui/res/cardsfolder/upcoming/colossal_badger_dig_deep.txt b/forge-gui/res/cardsfolder/upcoming/colossal_badger_dig_deep.txt new file mode 100644 index 00000000000..9f0221451d5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/colossal_badger_dig_deep.txt @@ -0,0 +1,21 @@ +Name:Colossal Badger +ManaCost:5 G +Types:Creature Badger +PT:6/5 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigGainLife | TriggerDescription$ When CARDNAME enters the battlefield, you gain 3 life. +SVar:TrigGainLife:DB$ GainLife | LifeAmount$ 3 +DeckHas:Ability$LifeGain +AlternateMode:Adventure +Oracle:When Colossal Badger enters the battlefield, you gain 3 life. + +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, +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 +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/upcoming/earth_tremor.txt b/forge-gui/res/cardsfolder/upcoming/earth_tremor.txt new file mode 100644 index 00000000000..66e50f7e772 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/earth_tremor.txt @@ -0,0 +1,6 @@ +Name:Earth Tremor +ManaCost:3 R +Types:Instant +A:SP$ DealDamage | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker | NumDmg$ X | SpellDescription$ CARDNAME deals damage to target creature or planeswalker equal to the number of lands you control. +SVar:X:Count$Valid Land.YouCtrl +Oracle:Earth Tremor deals damage to target creature or planeswalker equal to the number of lands you control. diff --git a/forge-gui/res/cardsfolder/upcoming/elminster.txt b/forge-gui/res/cardsfolder/upcoming/elminster.txt index b3d5afaa01c..dbc18744723 100644 --- a/forge-gui/res/cardsfolder/upcoming/elminster.txt +++ b/forge-gui/res/cardsfolder/upcoming/elminster.txt @@ -2,19 +2,19 @@ Name:Elminster ManaCost:3 W U Types:Legendary Planeswalker Elminster Loyalty:5 -T:Mode$ Scry | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigStore | RememberAmount$ ScryNum | TriggerDescription$ Whenever you scry, the next instant or sorcery spell you cast this turn costs {X} less to cast, where X is the number of cards looked at while scrying this way. -SVar:TrigStore:DB$ StoreSVar | SVar$ X | Type$ CountSVar | Expression$ Y | SubAbility$ DBEffect -SVar:DBEffect:DB$ Effect | StaticAbilities$ ReduceCost | Triggers$ TrigCastSpell | SubAbility$ DBCleanup +T:Mode$ Scry | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigStore | TriggerDescription$ Whenever you scry, the next instant or sorcery spell you cast this turn costs {X} less to cast, where X is the number of cards looked at while scrying this way. +SVar:TrigStore:DB$ StoreSVar | SVar$ X | Type$ Calculate | Expression$ Y | SubAbility$ DBEffect +SVar:DBEffect:DB$ Effect | StaticAbilities$ ReduceCost | Triggers$ TrigCastSpell SVar:ReduceCost:Mode$ ReduceCost | EffectZone$ Command | Type$ Spell | ValidCard$ Instant,Sorcery | Activator$ You | Amount$ X | Insert$ X | Description$ The next instant or sorcery spell you cast this turn costs {X} less to cast, where X is the number of cards looked at while scrying. (INSERT) SVar:TrigCastSpell:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Command | Execute$ RemoveEffect | Static$ True SVar:RemoveEffect:DB$ ChangeZone | Origin$ Command | Destination$ Exile -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:Number$0 -SVar:Y:Count$RememberedNumber +SVar:Y:TriggerCount$ScryNum A:AB$ Draw | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | SubAbility$ DBScry | SpellDescription$ Draw a card, then scry 2. SVar:DBScry:DB$ Scry | ScryNum$ 2 A:AB$ Dig | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | RememberChanged$ True | Reveal$ True | SubAbility$ DBToken | SpellDescription$ Exile the top card of your library. -SVar:DBToken:DB$ Token | TokenAmount$ Z | TokenScript$ u_1_1_faerie_dragon_flying | SpellDescription$ Create a number of 1/1 blue Faerie Dragon creature tokens with flying equal to that card's mana value. +SVar:DBToken:DB$ Token | TokenAmount$ Z | TokenScript$ u_1_1_faerie_dragon_flying | SubAbility$ DBCleanup | SpellDescription$ Create a number of 1/1 blue Faerie Dragon creature tokens with flying equal to that card's mana value. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:Z:Count$RememberedCardManaCost Text:CARDNAME can be your commander. DeckHas:Ability$Token & Type$Faerie|Dragon diff --git a/forge-gui/res/cardsfolder/upcoming/ettercap_web_shot.txt b/forge-gui/res/cardsfolder/upcoming/ettercap_web_shot.txt new file mode 100644 index 00000000000..d52520ce57c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ettercap_web_shot.txt @@ -0,0 +1,15 @@ +Name:Ettercap +ManaCost:4 G +Types:Creature Spider Beast +PT:2/5 +K:Reach +AlternateMode:Adventure +Oracle:Reach + +ALTERNATE + +Name:Web Shot +ManaCost:2 G +Types:Instant Adventure +A:SP$ Destroy | ValidTgts$ Creature.withFlying | TgtPrompt$ Select target creature with flying | SpellDescription$ Destroy target creature with flying. +Oracle:Destroy target creature with flying. (Then exile this card. You may cast the creature later from exile.) diff --git a/forge-gui/res/cardsfolder/upcoming/gluntch_the_bestower.txt b/forge-gui/res/cardsfolder/upcoming/gluntch_the_bestower.txt index dc9a26e6d7d..f3f1f538384 100644 --- a/forge-gui/res/cardsfolder/upcoming/gluntch_the_bestower.txt +++ b/forge-gui/res/cardsfolder/upcoming/gluntch_the_bestower.txt @@ -1,6 +1,6 @@ Name:Gluntch, the Bestower ManaCost:1 G W -Types:Creature Jellyfish +Types:Legendary Creature Jellyfish PT:0/5 K:Flying T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigChoosePlayer1 | TriggerDescription$ At the beginning of your end step, choose a player. They put two +1/+1 counters on a creature they control. Choose a second player to draw a card. Then choose a third player to create two Treasure tokens. @@ -13,4 +13,4 @@ SVar:Tokens:DB$ Token | ConditionCheckSVar$ X | ConditionSVarCompare$ GE3 | Defi SVar:X:PlayerCountPlayers$Amount SVar:CleanupFinal:DB$ Cleanup | ClearRemembered$ True DeckHas:Ability$Counters|Token|Sacrifice & Type$Treasure|Artifact -Oracle:Flying\nAt the beginning of your end step, choose a player. They put two +1/+1 counters on a creature they control. Choose a second player to draw a card. Then choose a third player to create two Treasure tokens. +Oracle:Flying\nAt the beginning of your end step, choose a player. They put two +1/+1 counters on a creature they control. Choose a second player to draw a card. Then choose a third player to create two Treasure tokens. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/gray_slaad_entropic_decay.txt b/forge-gui/res/cardsfolder/upcoming/gray_slaad_entropic_decay.txt new file mode 100644 index 00000000000..ca85b4dc024 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/gray_slaad_entropic_decay.txt @@ -0,0 +1,18 @@ +Name:Gray Slaad +ManaCost:2 B +Types:Creature Frog Horror +PT:4/1 +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Menace & Deathtouch | IsPresent$ Creature.YouOwn | PresentCompare$ GE4 | PresentZone$ Graveyard | Description$ As long as there are four or more creature cards in your graveyard, CARDNAME has menace and deathtouch. +DeckHints:Ability$Sacrifice|Discard|Mill +DeckHas:Ability$Graveyard +AlternateMode:Adventure +Oracle:As long as there are four or more creature cards in your graveyard, Gray Slaad has menace and deathtouch. + +ALTERNATE + +Name:Entropic Decay +ManaCost:1 B +Types:Sorcery Adventure +A:SP$ Mill | NumCards$ 4 | SpellDescription$ Mill four cards. (Then exile this card. You may cast the creature later from exile.) +DeckHas:Ability$Mill +Oracle:Mill four cards. (Then exile this card. You may cast the creature later from exile.) diff --git a/forge-gui/res/cardsfolder/upcoming/guardian_naga_banishing_coils.txt b/forge-gui/res/cardsfolder/upcoming/guardian_naga_banishing_coils.txt new file mode 100644 index 00000000000..c51f4eea210 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/guardian_naga_banishing_coils.txt @@ -0,0 +1,16 @@ +Name:Guardian Naga +ManaCost:5 W W +Types:Creature Naga +PT:5/6 +K:Vigilance +R:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.Self | PlayerTurn$ True | Description$ As long as it's your turn, prevent all damage that would be dealt to CARDNAME. +AlternateMode:Adventure +Oracle:Vigilance\nAs long as it's your turn, prevent all damage that would be dealt to Guardian Naga. + +ALTERNATE + +Name:Banishing Coils +ManaCost:2 W +Types:Instant Adventure +A:SP$ ChangeZone | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Choose target artifact or enchantment | ValidTgts$ Artifact,Enchantment | SpellDescription$ Exile target artifact or enchantment. +Oracle:Exile target artifact or enchantment. (Then exile this card. You may cast the creature later from exile.) diff --git a/forge-gui/res/cardsfolder/upcoming/haunted_one.txt b/forge-gui/res/cardsfolder/upcoming/haunted_one.txt new file mode 100644 index 00000000000..d06a97698ff --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/haunted_one.txt @@ -0,0 +1,9 @@ +Name:Haunted One +ManaCost:2 B +Types:Legendary Enchantment Background +S:Mode$ Continuous | Affected$ Creature.IsCommander+YouOwn | AddTrigger$ BecomesTapped | Description$ Commander creatures you own have "Whenever this creature becomes tapped, it and other creatures you control that share a creature type with it each get +2/+0 and gain undying until end of turn." +SVar:BecomesTapped:Mode$ Taps | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ Whenever this creature becomes tapped, it and other creatures you control that share a creature type with it each get +2/+0 and gain undying until end of turn. +SVar:TrigPump:DB$ PumpAll | ValidCards$ Card.sharesCreatureTypeWith+YouCtrl | KW$ Undying | NumAtt$ 2 +DeckHas:Ability$Counters & Keyword$Undying +AI:RemoveDeck:NonCommander +Oracle:Commander creatures you own have "Whenever this creature becomes tapped, it and other creatures you control that share a creature type with it each get +2/+0 and gain undying until end of turn." (When a creature with undying 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.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/hezrou_demonic_stench.txt b/forge-gui/res/cardsfolder/upcoming/hezrou_demonic_stench.txt new file mode 100644 index 00000000000..61fcaaf8484 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/hezrou_demonic_stench.txt @@ -0,0 +1,16 @@ +Name:Hezrou +ManaCost:5 B B +Types:Creature Frog Demon +PT:6/6 +T:Mode$ AttackerBlockedOnce | ValidCard$ Creature.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever one or more creatures you control become blocked, each blocking creature gets -1/-1 until end of turn. +SVar:TrigPump:DB$ PumpAll | NumAtt$ -1 | NumDef$ -1 | ValidCards$ Creature.blocking +AlternateMode:Adventure +Oracle:Whenever one or more creatures you control become blocked, each blocking creature gets -1/-1 until end of turn. + +ALTERNATE + +Name:Demonic Stench +ManaCost:B +Types:Instant Adventure +A:SP$ PumpAll | NumAtt$ -1 | NumDef$ -1 | ValidCards$ Creature.blockedThisTurn | SpellDescription$ Each creature that blocked this turn gets -1/-1 until end of turn. +Oracle:Each creature that blocked this turn gets -1/-1 until end of turn. (Then exile this card. You may cast the creature later from exile.) diff --git a/forge-gui/res/cardsfolder/upcoming/illithid_harvester_plant_tadpoles.txt b/forge-gui/res/cardsfolder/upcoming/illithid_harvester_plant_tadpoles.txt new file mode 100644 index 00000000000..e350f2df221 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/illithid_harvester_plant_tadpoles.txt @@ -0,0 +1,19 @@ +Name:Illithid Harvester +ManaCost:4 U +Types:Creature Horror +PT:4/4 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTurnFaceDown | TriggerDescription$ Ceremorphosis — When CARDNAME enters the battlefield, turn any number of target tapped nontoken creatures face down. They're 2/2 Horror creatures. +SVar:TrigTurnFaceDown:DB$ SetState | ValidTgts$ Creature.tapped+nonToken | TgtPrompt$ Select any number of target tapped nontoken creatures | TargetMin$ 0 | TargetMax$ X | Mode$ TurnFace | FaceDownPower$ 2 | FaceDownToughness$ 2 | FaceDownSetType$ Horror & Creature +SVar:X:Count$Valid Creature.tapped+nonToken +AlternateMode:Adventure +Oracle:Ceremorphosis — When Illithid Harvester enters the battlefield, turn any number of target tapped nontoken creatures face down. They're 2/2 Horror creatures. + +ALTERNATE + +Name:Plant Tadpoles +ManaCost:X U U +Types:Sorcery Adventure +A:SP$ Tap | ValidTgts$ Creature | TgtPrompt$ Select X target creatures | TargetMin$ X | TargetMax$ X | AlwaysRemember$ True | SubAbility$ DBPump | SpellDescription$ Tap X target creatures. +SVar:DBPump:DB$ Pump | Defined$ Targeted | KW$ HIDDEN This card doesn't untap during your next untap step. | Duration$ Permanent | StackDescription$ SpellDescription | SpellDescription$ They don't untap during their controllers' next untap steps. +SVar:X:Count$xPaid +Oracle:Tap X target creatures. They don't untap during their controllers' next untap steps. (Then exile this card. You may cast the creature later from exile.) diff --git a/forge-gui/res/cardsfolder/upcoming/ingenious_artillerist.txt b/forge-gui/res/cardsfolder/upcoming/ingenious_artillerist.txt new file mode 100644 index 00000000000..8f1e1ef5c33 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/ingenious_artillerist.txt @@ -0,0 +1,9 @@ +Name:Ingenious Artillerist +ManaCost:2 R +Types:Creature Human Artificer +PT:3/1 +T:Mode$ ChangesZoneAll | ValidCards$ Artifact.YouCtrl | Origin$ Any | Destination$ Battlefield | TriggerZones$ Battlefield | Execute$ TrigDealDamage | TriggerDescription$ Whenever one or more artifacts enter the battlefield under your control, CARDNAME deals that much damage to each opponent. +SVar:TrigDealDamage:DB$ DealDamage | Defined$ Opponent | NumDmg$ X +SVar:X:TriggerCount$Amount +DeckNeeds:Type$Artifact +Oracle:Whenever one or more artifacts enter the battlefield under your control, Ingenious Artillerist deals that much damage to each opponent. diff --git a/forge-gui/res/cardsfolder/upcoming/iron_mastiff.txt b/forge-gui/res/cardsfolder/upcoming/iron_mastiff.txt new file mode 100644 index 00000000000..83f805f815c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/iron_mastiff.txt @@ -0,0 +1,12 @@ +Name:Iron Mastiff +ManaCost:4 +Types:Artifact Creature Dog +PT:4/4 +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ DBTrigRollDice | TriggerDescription$ Whenever CARDNAME attacks, roll a d20 for each player being attacked and ignore all but the highest roll. +SVar:DBTrigRollDice:DB$ RollDice | Sides$ 20 | Amount$ NbAttackedPlayers | UseHighestRoll$ True | ResultSubAbilities$ 1-9:DBDamageToController,10-19:DBDamageToDefending,20:DBDamageToOpponents +SVar:NbAttackedPlayers:PlayerCountOpponents$HasPropertyDefending +SVar:DBDamageToController:DB$ DealDamage | Defined$ You | NumDmg$ X | SpellDescription$ 1—9 VERT CARDNAME deals damage equal to its power to you. +SVar:DBDamageToDefending:DB$ DealDamage | Defined$ Player.attackedBySourceThisCombat | NumDmg$ X | SpellDescription$ 10—19 VERT CARDNAME deals damage equal to its power to defending player. +SVar:DBDamageToOpponents:DB$ DealDamage | Defined$ Player.Opponent | NumDmg$ X | SpellDescription$ 20 VERT CARDNAME deals damage equal to its power to each opponent. +SVar:X:Count$CardPower +Oracle:Whenever Iron Mastiff attacks, roll a d20 for each player being attacked and ignore all but the highest roll.\n1—9 VERT Iron Mastiff deals damage equal to its power to you.\n10—19 VERT Iron Mastiff deals damage equal to its power to defending player.\n20 VERT Iron Mastiff deals damage equal to its power to each opponent. diff --git a/forge-gui/res/cardsfolder/upcoming/jade_orb_of_dragonkind.txt b/forge-gui/res/cardsfolder/upcoming/jade_orb_of_dragonkind.txt new file mode 100644 index 00000000000..51f0beaf807 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/jade_orb_of_dragonkind.txt @@ -0,0 +1,13 @@ +Name:Jade Orb of Dragonkind +ManaCost:2 G +Types:Artifact +A:AB$ Mana | Cost$ T | Produced$ G | TriggersWhenSpent$ TrigSpent | SpellDescription$ Add {G}. When you spend this mana to cast a Dragon creature spell, it enters the battlefield with an additional +1/+1 counter on it and gains hexproof until your next turn. (It can't be the target of spells or abilities your opponents control.) +SVar:TrigSpent:Mode$ SpellCast | ValidCard$ Creature.Dragon | ValidActivatingPlayer$ You | Execute$ TrigEffect | TriggerDescription$ When you spend this mana to cast a Dragon creature spell, it enters the battlefield with an additional +1/+1 counter on it and gains hexproof until your next turn. (It can't be the target of spells or abilities your opponents control.) +SVar:TrigEffect:DB$ Effect | RememberObjects$ TriggeredCard | ForgetOnMoved$ Stack | ReplacementEffects$ ETBCreat +SVar:ETBCreat:Event$ Moved | ValidCard$ Card.IsRemembered | Destination$ Battlefield | ReplaceWith$ DBPutP1P1 | ReplacementResult$ Updated | Description$ That creature enters the battlefield with an additional +1/+1 counter on it and gains hexproof until your next turn. +SVar:DBPutP1P1:DB$ PutCounter | Defined$ ReplacedCard | CounterType$ P1P1 | ETB$ True | CounterNum$ 1 | SubAbility$ DBPump +SVar:DBPump:DB$ Pump | Defined$ ReplacedCard | Duration$ UntilYourNextTurn | KW$ Hexproof | PumpZone$ Stack +DeckHas:Ability$Counters +DeckHints:Type$Dragon +Oracle:{T}: Add {G}. When you spend this mana to cast a Dragon creature spell, it enters the battlefield with an additional +1/+1 counter on it and gains hexproof until your next turn. (It can't be the target of spells or abilities your opponents control.) + diff --git a/forge-gui/res/cardsfolder/upcoming/jon_irenicus_shattered_one.txt b/forge-gui/res/cardsfolder/upcoming/jon_irenicus_shattered_one.txt new file mode 100644 index 00000000000..49397f88be3 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/jon_irenicus_shattered_one.txt @@ -0,0 +1,15 @@ +Name:Jon Irenicus, Shattered One +ManaCost:2 U B +Types:Legendary Creature Elf Wizard +PT:3/3 +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ DBSelectRecipient | TriggerDescription$ At the beginning of your end step, target opponent gains control of up to one target creature you control. +SVar:DBSelectRecipient:DB$ Pump | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | SubAbility$ DBDonate +SVar:DBDonate:DB$ GainControl | ValidTgts$ Creature.YouCtrl | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target creature you control | NewController$ ParentTarget | SubAbility$ DBPutCounters +SVar:DBPutCounters:DB$ PutCounter | Defined$ TargetedCard | CounterType$ P1P1 | CounterNum$ 2 | SubAbility$ DBTap +SVar:DBTap:DB$ Tap | Defined$ Targeted | SubAbility$ DBGoad +SVar:DBGoad:DB$ Goad | Defined$ Targeted | Duration$ Permanent | SubAbility$ DBDisableSacing +SVar:DBDisableSacing:DB$ Animate | Defined$ Targeted | staticAbilities$ SCantSac | Duration$ Permanent +SVar:SCantSac:Mode$ CantSacrifice | ValidCard$ Card.Self | Description$ This creature cannot be sacrificed. +T:Mode$ Attacks | ValidCard$ Creature.YouDontCtrl+YouOwn | TriggerZones$ Battlefield | Execute$ DrawACard | TriggerDescription$ Whenever a creature you own but don't control attacks, you draw a card. +SVar:DrawACard:DB$ Draw | Defined$ You | NumCards$ 1 +Oracle:At the beginning of your end step, target opponent gains control of up to one target creature you control. Put two +1/+1 counters on it and tap it. It's goaded for the rest of the game and it gains "This creature can't be sacrificed." (It attacks each combat if able and attacks a player other than you if able.)\nWhenever a creature you own but don't control attacks, you draw a card. diff --git a/forge-gui/res/cardsfolder/upcoming/lantern_of_revealing.txt b/forge-gui/res/cardsfolder/upcoming/lantern_of_revealing.txt new file mode 100644 index 00000000000..709f63c0384 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/lantern_of_revealing.txt @@ -0,0 +1,8 @@ +Name:Lantern of Revealing +ManaCost:3 +Types:Artifact +A:AB$ Mana | Cost$ T | Produced$ Any | SpellDescription$ Add one mana of any color. +A:AB$ Dig | Cost$ 4 T | DigNum$ 1 | ChangeNum$ All | ForceRevealToController$ True | Optional$ True | PromptToSkipOptionalAbility$ True | OptionalAbilityPrompt$ Would you like to put the land onto the battlefield tapped? | ChangeValid$ Land | DestinationZone$ Battlefield | Tapped$ True | DestinationZone2$ Library | LibraryPosition2$ 0 | RememberChanged$ True | SubAbility$ DBMoveToBottom | SpellDescription$ Look at the top card of your library. If it's a land card, you may put it onto the battlefield tapped. If you don't put the card onto the battlefield, you may put it on the bottom of your library. +SVar:DBMoveToBottom:DB$ Dig | DigNum$ 1 | DestinationZone$ Library | Optional$ True | LibraryPosition$ -1 | LibraryPosition2$ 0 | ConditionPresent$ Card | ConditionDefined$ Remembered | ConditionCompare$ EQ0 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:{T}: Add one mana of any color.\n{4}, {T}: Look at the top card of your library. If it's a land card, you may put it onto the battlefield tapped. If you don't put the card onto the battlefield, you may put it on the bottom of your library. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/livaan_cultist_of_tiamat.txt b/forge-gui/res/cardsfolder/upcoming/livaan_cultist_of_tiamat.txt index 13894961fb5..6b90040aa9e 100644 --- a/forge-gui/res/cardsfolder/upcoming/livaan_cultist_of_tiamat.txt +++ b/forge-gui/res/cardsfolder/upcoming/livaan_cultist_of_tiamat.txt @@ -4,6 +4,6 @@ Types:Legendary Creature Dragon Shaman PT:1/3 T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | Execute$ TrigPump | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a noncreature spell, target creature gets +X/+0 until end of turn, where X is that spell's mana value. SVar:TrigPump:DB$ Pump | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control | NumAtt$ +X -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI K:Choose a Background Oracle:Whenever you cast a noncreature spell, target creature gets +X/+0 until end of turn, where X is that spell's mana value.\nChoose a Background (You can have a Background as a second commander.) diff --git a/forge-gui/res/cardsfolder/upcoming/mahadi_emporium_master.txt b/forge-gui/res/cardsfolder/upcoming/mahadi_emporium_master.txt new file mode 100644 index 00000000000..e486e74a7c9 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/mahadi_emporium_master.txt @@ -0,0 +1,10 @@ +Name:Mahadi, Emporium Master +ManaCost:1 B R +Types:Legendary Creature Cat Devil +PT:3/3 +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of your end step, create a Treasure token for each creature that died this turn. +SVar:TrigToken:DB$ Token | TokenAmount$ X | TokenScript$ c_a_treasure_sac | TokenOwner$ You +SVar:X:Count$ThisTurnEntered_Graveyard_from_Battlefield_Creature +DeckHints:Ability$Token +DeckHas:Ability$Sacrifice|Token & Type$Treasure|Artifact +Oracle:At the beginning of your end step, create a Treasure token for each creature that died this turn. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.") diff --git a/forge-gui/res/cardsfolder/upcoming/marching_duodrone.txt b/forge-gui/res/cardsfolder/upcoming/marching_duodrone.txt new file mode 100644 index 00000000000..60fe45d69c2 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/marching_duodrone.txt @@ -0,0 +1,8 @@ +Name:Marching Duodrone +ManaCost:2 +Types:Artifact Creature Construct +PT:2/2 +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ DBTreasure | TriggerDescription$ Whenever CARDNAME attacks, each player creates a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.") +SVar:DBTreasure:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | TokenOwner$ Player +DeckHas:Ability$Token|Sacrifice & Type$Artifact|Treasure +Oracle:Whenever Marching Duodrone attacks, each player creates a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.") \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/marut.txt b/forge-gui/res/cardsfolder/upcoming/marut.txt new file mode 100644 index 00000000000..c6d2f46a256 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/marut.txt @@ -0,0 +1,11 @@ +Name:Marut +ManaCost:8 +Types:Artifact Creature Construct +PT:7/7 +K:Trample +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | CheckSVar$ X | Execute$ TrigTreasure | TriggerDescription$ When CARDNAME enters the battlefield, if mana from a Treasure was spent to cast it, create a Treasure token for each mana from a Treasure spent to cast it. +SVar:TrigTreasure:DB$ Token | TokenAmount$ X | TokenScript$ c_a_treasure_sac +SVar:X:Count$CastTotalManaSpent Treasure +SVar:AIPreference:ManaFrom$Treasure +DeckHas:Ability$Sacrifice|Token & Type$Treasure|Artifact +Oracle:Trample\nWhen Marut enters the battlefield, if mana from a Treasure was spent to cast it, create a Treasure token for each mana from a Treasure spent to cast it. diff --git a/forge-gui/res/cardsfolder/upcoming/minthara_merciless_soul.txt b/forge-gui/res/cardsfolder/upcoming/minthara_merciless_soul.txt new file mode 100644 index 00000000000..71ef84a6d07 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/minthara_merciless_soul.txt @@ -0,0 +1,10 @@ +Name:Minthara, Merciless Soul +ManaCost:2 W B +Types:Legendary Creature Elf Cleric +PT:2/2 +K:Ward:X +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | TriggerZones$ Battlefield | Revolt$ True | Execute$ TrigExperience | TriggerDescription$ At the beginning of your end step, if a permanent you controlled left the battlefield this turn, you get an experience counter. +SVar:TrigExperience:DB$ PutCounter | Defined$ You | CounterType$ Experience | CounterNum$ 1 +S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ X | Description$ Creatures you control get +1/+0 for each experience counter you have. +SVar:X:Count$YourCountersExperience +Oracle:Ward {X}, where X is the number of experience counters you have.\nAt the beginning of your end step, if a permanent you controlled left the battlefield this turn, you get an experience counter.\nCreatures you control get +1/+0 for each experience counter you have. diff --git a/forge-gui/res/cardsfolder/upcoming/moonshae_pixie.txt b/forge-gui/res/cardsfolder/upcoming/moonshae_pixie.txt index 7e1365cf04c..f2f45c2d8ca 100644 --- a/forge-gui/res/cardsfolder/upcoming/moonshae_pixie.txt +++ b/forge-gui/res/cardsfolder/upcoming/moonshae_pixie.txt @@ -4,7 +4,7 @@ Types:Creature Faerie PT:2/2 K:Flying T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw cards equal to the number of opponents who were dealt combat damage this turn. -SVar:TrigDraw:AB$ Draw | NumCards$ X +SVar:TrigDraw:DB$ Draw | NumCards$ X SVar:X:PlayerCountRegisteredOpponents$HasPropertywasDealtCombatDamageThisTurn AlternateMode:Adventure Oracle:Flying\nWhen Moonshae Pixie enters the battlefield, draw cards equal to the number of opponents who were dealt combat damage this turn. diff --git a/forge-gui/res/cardsfolder/upcoming/myrkuls_edict.txt b/forge-gui/res/cardsfolder/upcoming/myrkuls_edict.txt index 510104f75bf..74211a09160 100644 --- a/forge-gui/res/cardsfolder/upcoming/myrkuls_edict.txt +++ b/forge-gui/res/cardsfolder/upcoming/myrkuls_edict.txt @@ -2,7 +2,7 @@ Name:Myrkul's Edict ManaCost:1 B Types:Sorcery A:SP$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-9:OneOppSac,10-19:EachOppSac,20:SacTopPower | SpellDescription$ Roll a d20. -SVar:OneOppSac:ChoosePlayer | Defined$ You | Choices$ Player.Opponent | SubAbility$ DBSac | StackDescription$ SpellDescription | SpellDescription$ 1—9 VERT Choose an opponent. That player sacrifices a creature. +SVar:OneOppSac:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | SubAbility$ DBSac | StackDescription$ SpellDescription | SpellDescription$ 1—9 VERT Choose an opponent. That player sacrifices a creature. SVar:DBSac:DB$ Sacrifice | Defined$ Chosen | SacValid$ Creature | SubAbility$ DBCleanupChosen SVar:EachOppSac:DB$ Sacrifice | Defined$ Player.Opponent | SacValid$ Creature | StackDescription$ SpellDescription | SpellDescription$ 10—19 VERT Each opponent sacrifices a creature. SVar:SacTopPower:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ DBChooseCard | SubAbility$ DBSacAll | StackDescription$ SpellDescription | SpellDescription$ 20 VERT Each opponent sacrifices a creature with the greatest power among creatures that player controls. diff --git a/forge-gui/res/cardsfolder/upcoming/neera_wild_mage.txt b/forge-gui/res/cardsfolder/upcoming/neera_wild_mage.txt new file mode 100644 index 00000000000..d7023157b93 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/neera_wild_mage.txt @@ -0,0 +1,11 @@ +Name:Neera, Wild Mage +ManaCost:4 U R +Types:Legendary Creature Human Elf Shaman +PT:2/7 +T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ You | Execute$ TrigPutBottom | TriggerZones$ Battlefield | ActivationLimit$ 1 | OptionalDecider$ You | TriggerDescription$ Whenever you cast a spell, you may put it on the bottom of its owner's library. If you do, reveal cards from the top of your library until you reveal a nonland card. You may cast that card without paying its mana cost. Then put all revealed cards not cast this way on the bottom of your library in a random order. This ability triggers only once each turn. +SVar:TrigPutBottom:DB$ ChangeZone | Origin$ Stack | Destination$ Library | LibraryPosition$ -1 | Defined$ TriggeredCard | Fizzle$ True | RememberChanged$ True | SubAbility$ DBDig +SVar:DBDig:DB$ DigUntil | Valid$ Card.nonLand | ForgetOtherRemembered$ True | ImprintFound$ True | RememberFound$ True | RememberRevealed$ True | NoMoveFound$ True | NoMoveRevealed$ True | ConditionDefined$ Remembered | ConditionPresent$ Card | SubAbility$ DBPlay +SVar:DBPlay:DB$ Play | Defined$ Imprinted | ValidZone$ Library | ValidSA$ Spell | WithoutManaCost$ True | Optional$ True | ForgetPlayed$ True | SubAbility$ DBBottom +SVar:DBBottom:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True +Oracle:Whenever you cast a spell, you may put it on the bottom of its owner's library. If you do, reveal cards from the top of your library until you reveal a nonland card. You may cast that card without paying its mana cost. Then put all revealed cards not cast this way on the bottom of your library in a random order. This ability triggers only once each turn. diff --git a/forge-gui/res/cardsfolder/upcoming/patron_of_the_arts.txt b/forge-gui/res/cardsfolder/upcoming/patron_of_the_arts.txt new file mode 100644 index 00000000000..63efea66796 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/patron_of_the_arts.txt @@ -0,0 +1,9 @@ +Name:Patron of the Arts +ManaCost:2 R +Types:Creature Dragon Noble +PT:3/1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield or dies, create a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.") +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigToken | Secondary$ True | TriggerDescription$ When CARDNAME enters the battlefield or dies, create a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.") +SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ c_a_treasure_sac | TokenOwner$ TriggeredCardController +DeckHas:Ability$Sacrifice|Token & Type$Treasure|Artifact +Oracle:When Patron of the Arts enters the battlefield or dies, create a Treasure token. (It's an artifact with "{T}, Sacrifice this artifact: Add one mana of any color.") diff --git a/forge-gui/res/cardsfolder/upcoming/pegasus_guardian_rescue_the_foal.txt b/forge-gui/res/cardsfolder/upcoming/pegasus_guardian_rescue_the_foal.txt new file mode 100644 index 00000000000..27edc434df3 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/pegasus_guardian_rescue_the_foal.txt @@ -0,0 +1,20 @@ +Name:Pegasus Guardian +ManaCost:5 W +Types:Creature Pegasus +PT:3/3 +K:Flying +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Revolt$ True | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ At the beginning of your end step, if a permanent you controlled left the battlefield this turn, create a 1/1 white Pegasus creature token with flying. +SVar:TrigToken:DB$ Token | TokenScript$ w_1_1_pegasus_flying +AlternateMode:Adventure +DeckHas:Ability$Token +Oracle:Flying\nAt the beginning of your end step, if a permanent you controlled left the battlefield this turn, create a 1/1 white Pegasus creature token with flying. + +ALTERNATE + +Name:Rescue the Foal +ManaCost:1 W +Types:Instant Adventure +A:SP$ ChangeZone | ValidTgts$ Creature.YouCtrl | Origin$ Battlefield | Destination$ Exile | TgtPrompt$ Select target creature you control | RememberTargets$ True | SubAbility$ DBReturn | StackDescription$ Exile {c:Targeted}, | SpellDescription$ Exile target creature you control, then return that card to the battlefield under its owner's control. +SVar:DBReturn:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup | StackDescription$ then return it to the battlefield under its owner's control. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:Exile target creature you control, then return that card to the battlefield under its owner's control. (Then exile this card. You may cast the creature later from exile.) diff --git a/forge-gui/res/cardsfolder/upcoming/stick_together.txt b/forge-gui/res/cardsfolder/upcoming/stick_together.txt new file mode 100644 index 00000000000..b97333a2366 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/stick_together.txt @@ -0,0 +1,9 @@ +Name:Stick Together +ManaCost:3 W W +Types:Sorcery +A:SP$ ChooseCard | Defined$ Player | ChooseParty$ True | SubAbility$ SacAllOthers | StackDescription$ SpellDescription | SpellDescription$ Each player chooses a party from among creatures they control, +SVar:SacAllOthers:DB$ SacrificeAll | ValidCards$ Creature.nonChosenCard | StackDescription$ then sacrifices the rest. | SpellDescription$ then sacrifices the rest. (To choose a party, choose up to one each of Cleric, Rogue, Warrior, and Wizard.) +DeckHints:Ability$Party +DeckNeeds:Type$Cleric|Rogue|Warrior|Wizard +AI:RemoveDeck:Random +Oracle:Each player chooses a party from among creatures they control, then sacrifices the rest. (To choose a party, choose up to one each of Cleric, Rogue, Warrior, and Wizard.) diff --git a/forge-gui/res/cardsfolder/upcoming/storm_kings_thunder.txt b/forge-gui/res/cardsfolder/upcoming/storm_kings_thunder.txt new file mode 100644 index 00000000000..7f696a217a1 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/storm_kings_thunder.txt @@ -0,0 +1,9 @@ +Name:Storm King's Thunder +ManaCost:X R R R +Types:Instant +A:SP$ DelayedTrigger | Cost$ X R R R | Execute$ EffTrigCopy | ThisTurn$ True | Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | SpellDescription$ When you cast your next instant or sorcery spell this turn, copy that spell X times. You may choose new targets for the copies. +SVar:EffTrigCopy:DB$ CopySpellAbility | Defined$ TriggeredSpellAbility | Amount$ X | MayChooseTarget$ True +SVar:X:Count$xPaid +DeckNeeds:Type$Instant|Sorcery +AI:RemoveDeck:All +Oracle:When you cast your next instant or sorcery spell this turn, copy that spell X times. You may choose new targets for the copies. diff --git a/forge-gui/res/cardsfolder/upcoming/street_urchin.txt b/forge-gui/res/cardsfolder/upcoming/street_urchin.txt new file mode 100644 index 00000000000..df29c421ed8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/street_urchin.txt @@ -0,0 +1,10 @@ +Name:Street Urchin +ManaCost:1 R +Types:Legendary Enchantment Background +S:Mode$ Continuous | Affected$ Creature.IsCommander+YouOwn | AddAbility$ DealDamage | Description$ Commander creatures you own have "{1}, Sacrifice another creature or an artifact: This creature deals 1 damage to any target." +SVar:DealDamage:AB$ DealDamage | Cost$ 1 Sac<1/Creature.Other;Artifact/another creature or an artifact> | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ This creature deals 1 damage to any target. +SVar:AIPreference:SacCost$Creature.token,Artifact.token,Creature.cmcLE1,Artifact.cmcEQ1 +AI:RemoveDeck:NonCommander +DeckHas:Ability$Sacrifice +DeckHints:Type$Artifact +Oracle:Commander creatures you own have "{1}, Sacrifice another creature or an artifact: This creature deals 1 damage to any target." diff --git a/forge-gui/res/cardsfolder/upcoming/sword_coast_serpent_capsizing_wave.txt b/forge-gui/res/cardsfolder/upcoming/sword_coast_serpent_capsizing_wave.txt new file mode 100644 index 00000000000..4ad81ae7abd --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sword_coast_serpent_capsizing_wave.txt @@ -0,0 +1,17 @@ +Name:Sword Coast Serpent +ManaCost:5 U U +Types:Creature Serpent Dragon +PT:6/6 +S:Mode$ Continuous | Affected$ Card.Self | AddHiddenKeyword$ Unblockable | CheckSVar$ X | SVarCompare$ GE1 | Description$ CARDNAME can't be blocked as long as you've cast a noncreature spell this turn. +SVar:X:Count$ThisTurnCast_Card.nonCreature+YouCtrl +AlternateMode:Adventure +SVar:BuffedBy:Card.nonCreature+nonLand +Oracle:Sword Coast Serpent can't be blocked as long as you've cast a noncreature spell this turn. + +ALTERNATE + +Name:Capsizing Wave +ManaCost:1 U +Types:Instant Adventure +A:SP$ ChangeZone | ValidTgts$ Creature | Origin$ Battlefield | Destination$ Hand | SpellDescription$ Return target creature to its owner's hand. +Oracle:Return target creature to its owner's hand. (Then exile this card. You may cast the creature later from exile.) diff --git a/forge-gui/res/cardsfolder/upcoming/zangief_the_red_cyclone.txt b/forge-gui/res/cardsfolder/upcoming/zangief_the_red_cyclone.txt new file mode 100644 index 00000000000..70157645115 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/zangief_the_red_cyclone.txt @@ -0,0 +1,9 @@ +Name:Zangief, the Red Cyclone +ManaCost:2 B R G +Types:Legendary Creature Human Warrior +PT:7/4 +K:CARDNAME must be blocked if able. +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Indestructible | Condition$ PlayerTurn | Description$ Iron Muscle — As long as it's your turn, NICKNAME has indestructible. +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Creature.wasDealtExcessDamageThisTurn | Execute$ TrigSac | TriggerDescription$ Spinning Piledriver – Whenever NICKNAME deals damage to a creature, if that creature was dealt excess damage this turn, that creature's controller sacrifices a noncreature, nonland permanent. +SVar:TrigSac:DB$ Sacrifice | SacValid$ Permanent.nonCreature+nonLand | SacMessage$ noncreature, nonland permanent | Defined$ TriggeredTargetController +Oracle:Zangief, the Red Cyclone must be blocked if able.\nIron Muscle — As long as it's your turn, Zangief has indestructible.\nSpinning Piledriver — Whenever Zangief deals damage to a creature, if that creature was dealt excess damage this turn, that creature's controller sacrifices a noncreature, nonland permanent. diff --git a/forge-gui/res/cardsfolder/v/valki_god_of_lies_tibalt_cosmic_impostor.txt b/forge-gui/res/cardsfolder/v/valki_god_of_lies_tibalt_cosmic_impostor.txt index 36bdf2e9f70..8c861bbdd74 100644 --- a/forge-gui/res/cardsfolder/v/valki_god_of_lies_tibalt_cosmic_impostor.txt +++ b/forge-gui/res/cardsfolder/v/valki_god_of_lies_tibalt_cosmic_impostor.txt @@ -4,13 +4,9 @@ Types:Legendary Creature God PT:2/1 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReveal | TriggerDescription$ When NICKNAME enters the battlefield, each opponent reveals their hand. For each opponent, exile a creature card they revealed this way until NICKNAME leaves the battlefield. SVar:TrigReveal:DB$ RevealHand | Defined$ Player.Opponent | ImprintRevealed$ True | SubAbility$ DBRepeatEach -SVar:DBRepeatEach:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ DBExile | SubAbility$ DBEffect -SVar:DBExile:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | DefinedPlayer$ Remembered | ChangeType$ Creature.IsImprinted | ChangeNum$ 1 | AlreadyRevealed$ True | Chooser$ You | RememberChanged$ True -SVar:DBEffect:DB$ Effect | Triggers$ ComeBack | RememberObjects$ Remembered | ImprintCards$ Self | ConditionPresent$ Card.Self | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup -SVar:ComeBack:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.IsImprinted | Execute$ TrigReturn | TriggerZones$ Command | Static$ True | TriggerDescription$ These creature cards are exiled until EFFECTSOURCE leaves the battlefield. -SVar:TrigReturn:DB$ ChangeZoneAll | Origin$ Exile | Destination$ Hand | ChangeType$ Card.IsRemembered | SubAbility$ ExileSelf -SVar:ExileSelf:DB$ ChangeZone | Origin$ Command | Destination$ Exile | Defined$ Self -SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True | ClearRemembered$ True +SVar:DBRepeatEach:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ DBExile | SubAbility$ DBCleanup +SVar:DBExile:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | DefinedPlayer$ Remembered | ChangeType$ Creature.IsImprinted | ChangeNum$ 1 | AlreadyRevealed$ True | Chooser$ You | Duration$ UntilHostLeavesPlay +SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True A:AB$ ChooseCard | Cost$ X | ChoiceZone$ Exile | Choices$ Card.Creature+ExiledWithSource+cmcEQX | Amount$ 1 | ChoiceTitle$ Choose a card exiled with Valki with mana value X | SubAbility$ DBClone | AILogic$ Never | StackDescription$ {p:You} chooses a card exiled with NICKNAME with mana value X. NICKNAME becomes a copy of that card. | SpellDescription$ Choose a card exiled with NICKNAME with mana value X. NICKNAME becomes a copy of that card. SVar:DBClone:DB$ Clone | Defined$ ChosenCard | SubAbility$ DBClearChosen SVar:DBClearChosen:DB$ Cleanup | ClearChosenCard$ True diff --git a/forge-gui/res/cardsfolder/v/veiled_sentry.txt b/forge-gui/res/cardsfolder/v/veiled_sentry.txt index a6b720cc84f..8512496cc9c 100644 --- a/forge-gui/res/cardsfolder/v/veiled_sentry.txt +++ b/forge-gui/res/cardsfolder/v/veiled_sentry.txt @@ -3,5 +3,5 @@ ManaCost:U Types:Enchantment T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Opponent | Execute$ TrigAnimate | TriggerZones$ Battlefield | IsPresent$ Card.Self+Enchantment | TriggerDescription$ When an opponent casts a spell, if CARDNAME is an enchantment, CARDNAME becomes an Illusion creature with power and toughness each equal to that spell's mana value. SVar:TrigAnimate:DB$ Animate | Defined$ Self | Power$ X | Toughness$ X | Types$ Creature,Illusion | RemoveCardTypes$ True | Duration$ Permanent -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI Oracle:When an opponent casts a spell, if Veiled Sentry is an enchantment, Veiled Sentry becomes an Illusion creature with power and toughness each equal to that spell's mana value. diff --git a/forge-gui/res/cardsfolder/v/vial_smasher_the_fierce.txt b/forge-gui/res/cardsfolder/v/vial_smasher_the_fierce.txt index 7fb1453f0e4..ef3e3462414 100644 --- a/forge-gui/res/cardsfolder/v/vial_smasher_the_fierce.txt +++ b/forge-gui/res/cardsfolder/v/vial_smasher_the_fierce.txt @@ -6,6 +6,6 @@ T:Mode$ SpellCast | ValidActivatingPlayer$ You | ActivatorThisTurnCast$ EQ1 | No SVar:TrigChoose:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | Random$ True | SubAbility$ DBDealDamage SVar:DBDealDamage:DB$ DealDamage | Defined$ ChosenPlayer | NumDmg$ X | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearChosenPlayer$ True -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI K:Partner Oracle:Whenever you cast your first spell each turn, choose an opponent at random. Vial Smasher the Fierce deals damage equal to that spell's mana value to that player or a planeswalker that player controls.\nPartner (You can have two commanders if both have partner.) diff --git a/forge-gui/res/cardsfolder/v/vivien_monsters_advocate.txt b/forge-gui/res/cardsfolder/v/vivien_monsters_advocate.txt index 745eec7f07a..ff0fe4043f1 100644 --- a/forge-gui/res/cardsfolder/v/vivien_monsters_advocate.txt +++ b/forge-gui/res/cardsfolder/v/vivien_monsters_advocate.txt @@ -11,6 +11,6 @@ SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True A:AB$ Effect | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | Ultimate$ True | Triggers$ TrigSearch | AILogic$ WillCastCreature | SpellDescription$ When you cast your next creature spell this turn, search your library for a creature card with lesser mana value, put it onto the battlefield, then shuffle. SVar:TrigSearch:Mode$ SpellCast | ValidCard$ Creature | ValidActivatingPlayer$ You | OneOff$ True | TriggerZones$ Command | Execute$ DBSearch | TriggerDescription$ When you cast your next creature spell this turn, search your library for a creature card with lesser mana value, put it onto the battlefield, then shuffle. SVar:DBSearch:DB$ ChangeZone | Origin$ Library | Destination$ Battlefield | ChangeType$ Creature.cmcLTX | ChangeNum$ 1 -SVar:X:TriggerCount$CastSACMC +SVar:X:TriggeredStackInstance$CardManaCostLKI DeckHas:Ability$Token|Counters Oracle:You may look at the top card of your library any time.\nYou may cast creature spells from the top of your library.\n[+1]: Create a 3/3 green Beast creature token. Put your choice of a vigilance counter, a reach counter, or a trample counter on it.\n[-2]: When you cast your next creature spell this turn, search your library for a creature card with lesser mana value, put it onto the battlefield, then shuffle. diff --git a/forge-gui/res/cardsfolder/w/wellgabber_apothecary.txt b/forge-gui/res/cardsfolder/w/wellgabber_apothecary.txt index 239f9a3e2b4..8dd9b05c732 100644 --- a/forge-gui/res/cardsfolder/w/wellgabber_apothecary.txt +++ b/forge-gui/res/cardsfolder/w/wellgabber_apothecary.txt @@ -2,5 +2,6 @@ Name:Wellgabber Apothecary ManaCost:4 W Types:Creature Merfolk Cleric PT:2/3 -A:AB$ Pump | Cost$ 1 W | KW$ Prevent all damage that would be dealt to CARDNAME. | ValidTgts$ Creature.Merfolk+tapped,Creature.Kithkin+tapped | TgtPrompt$ Select tapped Merfolk or Kithkin creature | SpellDescription$ Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn. +A:SP$ Effect | Cost$ 1 W | ValidTgts$ Creature.Merfolk+tapped,Creature.Kithkin+tapped | TgtPrompt$ Select tapped Merfolk or Kithkin creature | ReplacementEffects$ RPrevent | RememberObjects$ Targeted | ExileOnMoved$ Battlefield | SpellDescription$ Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn. +SVar:RPrevent:Event$ DamageDone | Prevent$ True | ValidTarget$ Card.IsRemembered | Description$ Prevent all damage that would be dealt to that creature this turn. Oracle:{1}{W}: Prevent all damage that would be dealt to target tapped Merfolk or Kithkin creature this turn. diff --git a/forge-gui/res/cardsfolder/z/zaffai_thunder_conductor.txt b/forge-gui/res/cardsfolder/z/zaffai_thunder_conductor.txt index d478c4f47e1..07ce4a04ad2 100644 --- a/forge-gui/res/cardsfolder/z/zaffai_thunder_conductor.txt +++ b/forge-gui/res/cardsfolder/z/zaffai_thunder_conductor.txt @@ -4,9 +4,9 @@ Types:Legendary Creature Human Shaman PT:1/4 T:Mode$ SpellCastOrCopy | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ DBScry | TriggerDescription$ Magecraft — Whenever you cast or copy an instant or sorcery spell, scry 1. If that spell's mana value is 5 or greater, create a 4/4 blue and red Elemental creature token. If that spell's mana value is 10 or greater, CARDNAME deals 10 damage to an opponent chosen at random. SVar:DBScry:DB$ Scry | ScryNum$ 1 | SubAbility$ DBToken -SVar:DBToken:DB$ Token | TokenScript$ ur_4_4_elemental | TokenOwner$ You | ConditionCheckSVar$ TriggerCount$CastSACMC | ConditionSVarCompare$ GE5 | SubAbility$ DBChoose +SVar:DBToken:DB$ Token | TokenScript$ ur_4_4_elemental | TokenOwner$ You | ConditionCheckSVar$ TriggeredStackInstance$CardManaCostLKI | ConditionSVarCompare$ GE5 | SubAbility$ DBChoose SVar:DBChoose:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | Random$ True | SubAbility$ DBDamage -SVar:DBDamage:DB$ DealDamage | NumDmg$ 10 | Defined$ ChosenPlayer | ConditionCheckSVar$ TriggerCount$CastSACMC | ConditionSVarCompare$ GE10 +SVar:DBDamage:DB$ DealDamage | NumDmg$ 10 | Defined$ ChosenPlayer | ConditionCheckSVar$ TriggeredStackInstance$CardManaCostLKI | ConditionSVarCompare$ GE10 DeckHas:Ability$Token SVar:BuffedBy:Instant,Sorcery DeckHints:Type$Instant|Sorcery diff --git a/forge-gui/res/editions/Alchemy Horizons Baldur's Gate.txt b/forge-gui/res/editions/Alchemy Horizons Baldur's Gate.txt new file mode 100644 index 00000000000..e89f6086041 --- /dev/null +++ b/forge-gui/res/editions/Alchemy Horizons Baldur's Gate.txt @@ -0,0 +1,405 @@ +[metadata] +Code=HBG +Date=2022-07-07 +Name=Alchemy Horizons: Baldur's Gate +Type=Online +ScryfallCode=HBG + +[cards] +1 R Klement, Novice Acolyte @Maria Poliakova +1b R Klement, Death Acolyte @Diego Gisbert +1g R Klement, Nature Acolyte @Raluca Marinescu +1r R Klement, Tempest Acolyte @Zezhou Chen +1u R Klement, Knowledge Acolyte @Jodie Muir +1w R Klement, Life Acolyte @Winona Nelson +2 R Lae'zel, Githyanki Warrior @John Stanko +2b R Lae'zel, Callous Warrior @John Stanko +2g R Lae'zel, Primal Warrior @John Stanko +2r R Lae'zel, Wrathful Warrior @John Stanko +2u R Lae'zel, Illithid Thrall @Igor Grechanyi +2w R Lae'zel, Blessed Warrior @John Stanko +3 U Lulu, Forgetful Hollyphant @Jakob Eirich +3b U Lulu, Vengeful Hollyphant @Jakob Eirich +3g U Lulu, Wild Hollyphant @Jakob Eirich +3r U Lulu, Inspiring Hollyphant @Jakob Eirich +3u U Lulu, Curious Hollyphant @Jakob Eirich +3w U Lulu, Helpful Hollyphant @Jakob Eirich +4 U Rasaad, Monk of Selûne @Dan Scott +4b U Rasaad, Shadow Monk @Dan Scott +4g U Rasaad, Sylvan Monk @Dan Scott +4r U Rasaad, Warrior Monk @Dan Scott +4u U Rasaad, Dragon Monk @Dan Scott +4w U Rasaad, Radiant Monk @Dan Scott +5 U Alora, Rogue Companion @Aaron Miller +5b U Alora, Cheerful Assassin @Aaron Miller +5g U Alora, Cheerful Scout @Aaron Miller +5r U Alora, Cheerful Swashbuckler @Aaron Miller +5u U Alora, Cheerful Thief @Aaron Miller +5w U Alora, Cheerful Mastermind @Aaron Miller +6 M Gale, Conduit of the Arcane @Cristi Balanescu +6b M Gale, Abyssal Conduit @Cristi Balanescu +6g M Gale, Primeval Conduit @Cristi Balanescu +6r M Gale, Storm Conduit @Cristi Balanescu +6u M Gale, Temporal Conduit @Cristi Balanescu +6w M Gale, Holy Conduit @Cristi Balanescu +7 R Imoen, Trickster Friend @Alix Branwyn +7b R Imoen, Occult Trickster @Alix Branwyn +7g R Imoen, Wise Trickster @Alix Branwyn +7r R Imoen, Chaotic Trickster @Alix Branwyn +7u R Imoen, Wily Trickster @Alix Branwyn +7w R Imoen, Honorable Trickster @Alix Branwyn +8 U Vhal, Eager Scholar @David Gaillet +8b U Vhal, Scholar of Mortality @David Gaillet +8g U Vhal, Scholar of Creation @David Gaillet +8r U Vhal, Scholar of Elements @David Gaillet +8u U Vhal, Scholar of Prophecy @David Gaillet +8w U Vhal, Scholar of Tactics @David Gaillet +9 U Sarevok the Usurper @Ben Hill +9b U Sarevok, Deadly Usurper @Ben Hill +9g U Sarevok, Mighty Usurper @Ben Hill +9r U Sarevok, Ferocious Usurper @Ben Hill +9u U Sarevok, Deceitful Usurper @Ben Hill +9w U Sarevok, Divine Usurper @Ben Hill +10 R Shadowheart, Sharran Cleric @Cristi Balanescu +10b R Shadowheart, Cleric of Graves @Cristi Balanescu +10g R Shadowheart, Cleric of Twilight @Cristi Balanescu +10r R Shadowheart, Cleric of War @Cristi Balanescu +10u R Shadowheart, Cleric of Trickery @Cristi Balanescu +10w R Shadowheart, Cleric of Order @Cristi Balanescu +11 U Viconia, Nightsinger's Disciple @Daarken +11b U Viconia, Disciple of Blood @Daarken +11g U Viconia, Disciple of Strength @Daarken +11r U Viconia, Disciple of Violence @Daarken +11u U Viconia, Disciple of Arcana @Daarken +11w U Viconia, Disciple of Rebirth @Daarken +12 U Ambergris, Citadel Agent @Darek Zabrocki +12b U Ambergris, Agent of Tyranny @Darek Zabrocki +12g U Ambergris, Agent of Balance @Darek Zabrocki +12r U Ambergris, Agent of Destruction @Darek Zabrocki +12u U Ambergris, Agent of Progress @Darek Zabrocki +12w U Ambergris, Agent of Law @Darek Zabrocki +13 U Gut, Fanatical Priestess @Wayne Reynolds +13b U Gut, Brutal Fanatic @Wayne Reynolds +13g U Gut, Bestial Fanatic @Wayne Reynolds +13r U Gut, Furious Fanatic @Wayne Reynolds +13u U Gut, Devious Fanatic @Wayne Reynolds +13w U Gut, Zealous Fanatic @Wayne Reynolds +14 R Karlach, Raging Tiefling @Billy Christian +14b R Karlach, Tiefling Punisher @Billy Christian +14g R Karlach, Tiefling Guardian @Billy Christian +14r R Karlach, Tiefling Berserker @Billy Christian +14u R Karlach, Tiefling Spellrager @Billy Christian +14w R Karlach, Tiefling Zealot @Billy Christian +15 M Wyll, Pact-Bound Duelist @Mads Ahm +15b M Wyll of the Fiend Pact @Mads Ahm +15g M Wyll of the Fey Pact @Mads Ahm +15r M Wyll of the Blade Pact @Mads Ahm +15u M Wyll of the Elder Pact @Mads Ahm +15w M Wyll of the Celestial Pact @Mads Ahm +16 U Jaheira, Harper Emissary @Mila Pesic +16b U Jaheira, Ruthless Harper @Mila Pesic +16g U Jaheira, Merciful Harper @Mila Pesic +16r U Jaheira, Stirring Harper @Mila Pesic +16u U Jaheira, Insightful Harper @Mila Pesic +16w U Jaheira, Heroic Harper @Mila Pesic +17 M Lukamina, Moon Druid @Zara Alfonso +17b M Lukamina, Scorpion Form @Jason Kang +17g M Lukamina, Bear Form @Wisnu Tan +17r M Lukamina, Wolf Form @Maria Zolotukhina +17u M Lukamina, Crocodile Form @Simon Dominic +17w M Lukamina, Hawk Form @Julie Dillon +18 U Skanos, Dragon Vassal @Daarken +18b U Skanos, Black Dragon Vassal @Daarken +18g U Skanos, Green Dragon Vassal @Daarken +18r U Skanos, Red Dragon Vassal @Daarken +18u U Skanos, Blue Dragon Vassal @Daarken +18w U Skanos, White Dragon Vassal @Daarken +19 R Wilson, Bear Comrade @Ilse Gort +19b R Wilson, Fearsome Bear @Ilse Gort +19g R Wilson, Majestic Bear @Ilse Gort +19r R Wilson, Ardent Bear @Ilse Gort +19u R Wilson, Subtle Bear @Ilse Gort +19w R Wilson, Urbane Bear @Ilse Gort +20 U Boareskyr Tollkeeper @Andrey Kuzinskiy +21 M Champions of Tyr @Fajareka Setiawan +22 C Flaming Fist Duskguard @Matt Forsyth +23 C Mace of Disruption @Craig J Spearing +24 U Moradin's Disciples @Justine Cruz +25 C Patriar's Humiliation @Dave Greco +26 C Ranger Squadron @Filipe Pagliuso +27 U Seatower Imprisonment @Jokubas Uogintas +28 C Soldiers of the Watch @Edgar Sánchez Hidalgo +29 R Sune's Intervention @Anastasia Ovchinnikova +30 M Sworn to the Legion @Aaron J. Riley +31 C Valiant Farewell @Konstantin Porubov +32 R Vladimir and Godfrey @Josu Hernaiz +33 R Calim, Djinn Emperor @Zoltan Boros +34 C Hypnotic Pattern @Olena Richards +35 C Lizardfolk Librarians @Brian Valeza +36 U Seek New Knowledge @Dave Greco +37 R Signature Spells @Reiko Murakami +38 M Snowborn Simulacra @Irina Nordsol +39 R Thayan Evokers @Josh Hass +40 C Undersimplify @Brent Hollowell +41 C Water Weird @Brent Hollowell +42 C Wizened Githzerai @Jodie Muir +43 U Grave Choice @Will Gist +44 C Hook Horror @Olivier Bernard +45 R The Hourglass Coven @Konstantin Porubov +45a R Hag of Ceaseless Torment @Konstantin Porubov +45b R Hag of Dark Duress @Konstantin Porubov +45c R Hag of Death's Legion @Konstantin Porubov +45d R Hag of Inner Weakness @Konstantin Porubov +45e R Hag of Mage's Doom @Konstantin Porubov +45f R Hag of Noxious Nightmares @Konstantin Porubov +45g R Hag of Scoured Thoughts @Konstantin Porubov +45h R Hag of Syphoned Breath @Konstantin Porubov +45i R Hag of Twisted Visions @Konstantin Porubov +46 U Mind Spike @Matt Forsyth +47 C Sewer Plague @Piotr Foksowicz +48 R Stroke of Luck @Forrest Imel +49 M Chaos Balor @Uriah Voth +50 U Craving of Yeenoghu @Yeong-Hao Han +51 U Dragonborn Immolator @Artur Nakhodkin +52 R Flames of Moradin @Forrest Imel +53 C Genasi Rabble-Rouser @Joshua Raphael +54 C Giant Fire Beetles @Vincent Christiaens +55 U Gnoll Hunting Party @Ben Wootten +56 U Goblin Trapfinder @Brian Valeza +57 C Incessant Provocation @Brian Valeza +58 R Kardum, Patron of Flames @Artur Nakhodkin +59 C Kobold Warcaller @Igor Grechanyi +60 U Mephit's Enthusiasm @Kim Sokol +61 R Tiefling Outcasts @Zoltan Boros +62 C Unexpected Allies @Milivoj Ćeran +63 R Uthgardt Fury @Yeong-Hao Han +64 C Warriors of Tiamat @David Auden Nash +65 C Arcane Archery @Julian Kok Joon Wen +66 R Favored Enemy @Aaron J. Riley +67 C Follow the Tracks @Julian Kok Joon Wen +68 R Oyaminartok, Polar Werebear @Borja Pindado +69 M Verdant Rejuvenation @Alayna Danner +70 C You Line Up the Shot @Joe Slucher +71 R Yuan-Ti Scaleshield @Ben Hill +72 R Jon Irenicus, the Exile @Igor Grechanyi +73 U Liara of the Flaming Fist @David Rapoza +74 U Minthara of the Absolute @Evyn Fong +75 M Tasha, Unholy Archmage @Martina Fackova +76 R Ulder Ravengard, Marshal @Eric Deschamps +77 U Gate of the Black Dragon @Sergey Glushakov +78 U Gate to Manorborn @Andreas Rocha +79 U Gate to Seatower @Kamila Szutenberg +80 U Gate to the Citadel @Andreas Rocha +81 U Gate to Tumbledown @Emmanuel Shiu +82 M Ancient Gold Dragon @Simon Dominic +83 R Archivist of Oghma @Stella Spente +84 R Ascend from Avernus @Bruce Brenneise +85 C Blessed Hippogriff @Leanna Crossan +86 C Celestial Unicorn @Johannes Voss +87 C Dawnbringer Cleric @Lie Setiawan +88 C Devoted Paladin @Chris Rallis +89 C Flaming Fist Officer @Darek Zabrocki +90 C Guardian Naga @Tom Babbey +91 U Guiding Bolt @Halil Ural +92 R Horn of Valhalla @John Severin Brassell +93 C Icewind Stalwart @Marcela Medeiros +94 R Lae'zel's Acrobatics @Tatiana Kirgetova +95 C Minimus Containment @Steve Prescott +96 U Monk of the Open Hand @Bryan Sola +97 U Pegasus Guardian @Leanna Crossan +98 U Portable Hole @John Stanko +99 C Priest of Ancient Lore @Jarel Threat +100 U Rally Maneuver @Vincent Proce +101 U Rescuer Chwinga @Nils Hamm +102 U Scouting Hawk @Ilse Gort +103 C Steadfast Paladin @Chris Rallis +104 C Steadfast Unicorn @John Thacker +105 R Stick Together @Dave Greco +106 C You Hear Something on Watch @Zezhou Chen +107 C You're Ambushed on the Road @Eric Deschamps +108 U You're Confronted by Robbers @Durion +109 C Air-Cult Elemental @Kari Christensen +110 M Ancient Silver Dragon @Raoul Vitale +111 C Blur @Dave Greco +112 C Charmed Sleep @Zoltan Boros +113 C Clever Conjurer @Zoltan Boros +114 C Contact Other Plane @Alix Branwyn +115 R Displacer Kitten @Campbell White +116 U Draconic Lore @Tom Babbey +117 C Dragonborn Looter @Julio Reyna +118 U Dream Fracture @Liiga Smilshkalne +119 R Gale's Redirection @Yeong-Hao Han +120 U Goggles of Night @Forrest Imel +121 U Guild Thief @Mike Jordana +122 R Illithid Harvester @David Astruga +123 U Irenicus's Vile Duplication @Oleksandr Kozachenko +124 U Juvenile Mist Dragon @Leanna Crossan +125 U Kenku Artificer @Dave Greco +126 R Kindred Discovery @Vincent Christiaens +127 U Lapis Orb of Dragonkind @Olena Richards +128 C Pseudodragon Familiar @Campbell White +129 U Ray of Frost @Kim Sokol +130 C Rimeshield Frost Giant @Matt Stewart +131 R Robe of the Archmagi @Dallas Williams +132 C Shocking Grasp @Jason Felix +133 C Soulknife Spy @Miguel Mercado +134 U Sword Coast Serpent @Caio Monteiro +135 C Tymora's Invoker @Leonardo Santanna +136 C You Come to a River @Viko Menezes +137 C You Find the Villains' Lair @Gabor Szikszai +138 C Young Blue Dragon @Tuan Duong Chu +139 R Altar of Bhaal @Jonas De Ro +140 U Ambition's Cost @Zezhou Chen +141 M Ancient Brass Dragon @Johan Grenier +142 C Armor of Shadows @Craig J Spearing +143 C Baleful Beholder @Lars Grant-West +144 U Black Dragon @Mark Zug +145 R Black Market Connections @Evyn Fong +146 M Blood Money @Inka Schulz +147 U Bonecaller Cleric @Jason A. Engle +148 U Cast Down @Tyler Walpole +149 C Chain Devil @Bartek Fedyczak +150 C Deadly Dispute @Irina Nordsol +151 C Demogorgon's Clutches @Alexander Mokhov +152 U Drider @Jodie Muir +153 R Eldritch Pact @Peter Polach +154 C Eyes of the Beholder @Kari Christensen +155 U Ghost Lantern @Julian Kok Joon Wen +156 C Gray Slaad @Piotr Foksowicz +157 C Grim Bounty @Justine Cruz +158 R Grim Hireling @Tomas Duchek +159 U Grim Wanderer @Jason A. Engle +160 C Guildsworn Prowler @Fariba Khamseh +161 C Hoard Robber @Anna Pavleeva +162 R Intellect Devourer @Brian Valeza +163 C Manticore @Billy Christian +164 C Nefarious Imp @Konstantin Porubov +165 M Pact Weapon @Volkan Baǵa +166 C Sepulcher Ghoul @Jason A. Engle +167 C Shambling Ghast @Dave Kendall +168 U Sigil of Myrkul @David Astruga +169 U Skullport Merchant @Tomek Larek +170 C Summon Undead @Dallas Williams +171 C Thieves' Tools @Deruchenko Alexander +172 C Vampire Spawn @Alex Brock +173 R Wand of Orcus @Andrew Mar +174 M Ancient Copper Dragon @Antonio José Manzanedo +175 U Battle Cry Goblin @April Prime +176 U Breath Weapon @Adam Vehige +177 U Carnelian Orb of Dragonkind @Olena Richards +178 C Dragon's Fire @Campbell White +179 C Dueling Rapier @Anna Podedworna +180 C Earth-Cult Elemental @Aaron Miller +181 C Farideh's Fireball @Josu Hernaiz +182 R Fiendlash @Antonio José Manzanedo +183 C Hobgoblin Captain @Karl Kopinski +184 C Improvised Weaponry @Alix Branwyn +185 C Jaded Sell-Sword @Randy Vargas +186 R Nalfeshnee @Sam White +187 C Reckless Barbarian @Oleksandr Kozachenko +188 U Red Dragon @Andrey Kuzinskiy +189 M Storm King's Thunder @Alexander Mokhov +190 U Swashbuckler Extraordinaire @Durion +191 U Two-Handed Axe @Milivoj Ćeran +192 C Unexpected Windfall @Alayna Danner +193 C Valor Singer @Justyna Gil +194 R Wrathful Red Dragon @Dan Scott +195 C You Come to the Gnoll Camp @Billy Christian +196 C You Find Some Prisoners @Lie Setiawan +197 C Young Red Dragon @Adam Vehige +198 C Ambitious Dragonborn @Gaboleps +199 M Ancient Bronze Dragon @Johan Grenier +200 C Band Together @Brian Valeza +201 R Belt of Giant Strength @Viko Menezes +202 U Choose Your Weapon @Olivier Bernard +203 C Circle of the Land Druid @Alexandre Honoré +204 C Circle of the Moon Druid @Nicholas Elias +205 U Draconic Muralists @Tom Babbey +206 C Dread Linnorm @Caio Monteiro +207 U Druid of the Emerald Grove @Edgar Sánchez Hidalgo +208 C Druidic Ritual @Vincent Christiaens +209 R Earthquake Dragon @Johan Grenier +210 U Emerald Dragon @Diego Gisbert +211 C Ettercap @Olivier Bernard +212 C Gnoll Hunter @Jesper Ejsing +213 C Hill Giant Herdgorger @Chris Rahn +214 C Inspiring Bard @Eelis Kyttanen +215 U Jade Orb of Dragonkind @Olena Richards +216 U Lurking Roper @Andrew Mar +217 R Monster Manual @David Gaillet +218 C Owlbear @Ilse Gort +219 U Owlbear Shepherd @Scott Murphy +220 C Poison the Blade @Matt Forsyth +221 U Prosperous Innkeeper @Eric Deschamps +222 C Scaled Nurturer @David Gaillet +223 U Split the Spoils @Edgar Sánchez Hidalgo +224 C Sylvan Shepherd @Darrell Riche +225 R Traverse the Outlands @Chuck Lukacs +226 C Undercellar Myconid @David Szabo +227 C Underdark Basilisk @Brent Hollowell +228 U Wild Shape @Sam Guay +229 U You Meet in a Tavern @Zoltan Boros +230 R Alaundo the Seer @Aurore Folny +231 R Astarion, the Decadent @Ben Hill +232 R Baba Lysaga, Night Witch @Slawomir Maniak +233 R Catti-brie of Mithral Hall @Lius Lasahido +234 R Gorion, Wise Mentor @Jason Kang +235 R Jan Jansen, Chaos Crafter @Vladimir Krisetskiy +236 U Kagha, Shadow Archdruid @Alexander Mokhov +237 U Kalain, Reclusive Painter @Justine Cruz +238 U Korlessa, Scale Singer @Jesper Ejsing +239 U Krydle of Baldur's Gate @Bryan Sola +240 U Lozhan, Dragons' Legacy @Rudy Siswanto +241 R Mazzy, Truesword Paladin @Justyna Gil +242 R Miirym, Sentinel Wyrm @Kekai Kotaki +243 M Minsc & Boo, Timeless Heroes @Andreas Zafiratos +244 M Nalia de'Arnise @John Stanko +245 R Neera, Wild Mage @Pauline Voss +246 U Oji, the Exquisite Blade @Andreas Zafiratos +247 M Prosper, Tome-Bound @Yongjae Choi +248 R Raggadragga, Goreguts Boss @Xavier Ribeiro +249 R Raphael, Fiendish Savior @Livia Prima +250 U Thrakkus the Butcher @Nestor Ossandon Leal +251 U Trelasarra, Moon Dancer @Kieran Yanner +252 U Bag of Holding @Evyn Fong +253 R Basilisk Collar @Craig J Spearing +254 U Bronze Walrus @James Paick +255 U Chardalyn Dragon @Sergey Glushakov +256 C Cloak of the Bat @Dominik Mayer +257 R Fraying Line @Campbell White +258 C Iron Golem @Nicholas Gregory +259 C Lantern of Revealing @Eytan Zana +260 U Meteor Golem @James Paick +261 R Mirror of Life Trapping @Dallas Williams +262 U Navigation Orb @Robin Olausson +263 C Pilgrim's Eye @Sean Murray +264 C Prophetic Prism @Diego Gisbert +265 C Spiked Pit Trap @Deruchenko Alexander +266 R Baldur's Gate @Titus Lunter +289 L Plains @Bruce Brenneise +290 L Plains @Leanna Crossan +291 L Plains @Titus Lunter +292 L Plains @Emmanuel Shiu +293 L Island @Bruce Brenneise +294 L Island @Piotr Dura +295 L Island @James Paick +296 L Island @Sam White +297 L Swamp @Piotr Dura +298 L Swamp @Logan Feliciano +299 L Swamp @Grady Frederick +300 L Swamp @Sam White +301 L Mountain @Matt Gaser +302 L Mountain @Lucas Graciano +303 L Mountain @Muhammad Firdaus +304 L Mountain @Sam White +305 L Forest @Bruce Brenneise +306 L Forest @Muhammad Firdaus +307 L Forest @Lucas Graciano +308 L Forest @Julian Kok Joon Wen + +[rebalanced] +A166 C A-Sepulcher Ghoul @Jason A. Engle +A209 R A-Earthquake Dragon @Johan Grenier +A217 R A-Monster Manual @David Gaillet +A232 R A-Baba Lysaga, Night Witch @Slawomir Maniak +A243 M A-Minsc & Boo, Timeless Heroes @Andreas Zafiratos diff --git a/forge-gui/res/editions/Dungeons & Dragons Adventures in the Forgotten Realms.txt b/forge-gui/res/editions/Dungeons & Dragons Adventures in the Forgotten Realms.txt index 9a0a23ab885..f0bc4795efd 100644 --- a/forge-gui/res/editions/Dungeons & Dragons Adventures in the Forgotten Realms.txt +++ b/forge-gui/res/editions/Dungeons & Dragons Adventures in the Forgotten Realms.txt @@ -440,8 +440,10 @@ A130 C A-Armory Veteran @Caio Monteiro A180 U A-Druid Class @Svetlin Velinov A181 M A-Ellywick Tumblestrum @Anna Steinbauer A183 C A-Find the Path @Lindsey Look +A196 R A-Ochre Jelly @Daarken A219 U A-Bruenor Battlehammer @Wayne Reynolds A231 U A-Shessra, Death's Whisper @Marie Magny +A233 R A-Sorcerer Class @Alexander Mokhov A237 R A-Triumphant Adventurer @Alexander Mokhov A255 R A-Dungeon Descent @Kasia 'Kafis' Zielińska diff --git a/forge-gui/res/editions/Ikoria Lair of Behemoths.txt b/forge-gui/res/editions/Ikoria Lair of Behemoths.txt index 50c747ab1f9..f38d8dd711c 100644 --- a/forge-gui/res/editions/Ikoria Lair of Behemoths.txt +++ b/forge-gui/res/editions/Ikoria Lair of Behemoths.txt @@ -411,6 +411,9 @@ ScryfallCode=IKO 386 R Dirge Bat @羽山晃平 387 R Crystalline Giant @Kotakan +[rebalanced] +A216 M A-Winota, Joiner of Forces @Magali Villeneuve + [Lands] 10 Bloodfell Caves|IKO 10 Blossoming Sands|IKO diff --git a/forge-gui/res/editions/Innistrad Crimson Vow.txt b/forge-gui/res/editions/Innistrad Crimson Vow.txt index 314154c7a43..a49d054ff55 100644 --- a/forge-gui/res/editions/Innistrad Crimson Vow.txt +++ b/forge-gui/res/editions/Innistrad Crimson Vow.txt @@ -437,6 +437,7 @@ Prerelease=6 Boosters, 1 RareMythic+ A52 U A-Cobbled Lancer @Igor Kieryluk A63 R A-Hullbreaker Horror @Svetlin Velinov A81 C A-Stitched Assistant @Andrey Kuzinskiy +A247 U A-Sigardian Paladin @Slawomir Maniak A248 U A-Skull Skaab @Nicholas Gregory [tokens] diff --git a/forge-gui/res/editions/Innistrad Midnight Hunt.txt b/forge-gui/res/editions/Innistrad Midnight Hunt.txt index ec059f71f76..6ed816bf8e7 100644 --- a/forge-gui/res/editions/Innistrad Midnight Hunt.txt +++ b/forge-gui/res/editions/Innistrad Midnight Hunt.txt @@ -417,6 +417,7 @@ Prerelease=6 Boosters, 1 RareMythic+ A52 C A-Falcon Abomination @Brent Hollowell A59 M A-Lier, Disciple of the Drowned @Ekaterina Burmak A106 C A-Hobbling Zombie @Josh Hass +A112 M A-The Meathook Massacre @Chris Seaman [tokens] b_1_1_bat_flying diff --git a/forge-gui/res/editions/Judge Gift Cards 2022.txt b/forge-gui/res/editions/Judge Gift Cards 2022.txt index 94879906576..f0b922dde4c 100644 --- a/forge-gui/res/editions/Judge Gift Cards 2022.txt +++ b/forge-gui/res/editions/Judge Gift Cards 2022.txt @@ -10,4 +10,7 @@ ScryfallCode=P22 2 R Omniscience @Alayna Danner 3 R Parallel Lives @Greg Staples 4 R Stranglehold @Ralph Horsley +5 R Smothering Tithe @Aurore Folny +6 R Training Grounds @Caroline Gariba 9 R No Mercy @John Stanko +10 R R Growing Rites of Itlimoc @Dmitry Burmak diff --git a/forge-gui/res/editions/Jumpstart Historic Horizons.txt b/forge-gui/res/editions/Jumpstart Historic Horizons.txt index 9adc946bf70..a4fc22b816b 100644 --- a/forge-gui/res/editions/Jumpstart Historic Horizons.txt +++ b/forge-gui/res/editions/Jumpstart Historic Horizons.txt @@ -794,5 +794,9 @@ ScryfallCode=J21 786 L Mountain 787 L Forest +[rebalanced] +A438 U A-Dragon's Rage Channeler @Martina Fackova +A529 C A-Unholy Heat @Kari Christensen + [tokens] u_8_8_kraken diff --git a/forge-gui/res/editions/Kaldheim.txt b/forge-gui/res/editions/Kaldheim.txt index a1db1e459a9..88e57b6d4cf 100644 --- a/forge-gui/res/editions/Kaldheim.txt +++ b/forge-gui/res/editions/Kaldheim.txt @@ -442,11 +442,13 @@ A165 C A-Elderleaf Mentor @Zoltan Boros A166 U A-Elven Bow @Dallas Williams A169 R A-Esika's Chariot @Raoul Vitale A198 M A-Tyvar Kell @Chris Rallis +A208 U A-Fall of the Impostor @Eric Deschamps A224 U A-Narfi, Betrayer King @Daarken A212 U A-Harald, King of Skemfar @Grzegorz Rutkowski A213 R A-Harald Unites the Elves @Ryan Pancoast A233 U A-Vega, the Watcher @Paul Scott Canavan A237 R A-Cosmos Elixir @Volkan Baǵa +A253 U A-Bretagard Stronghold @Jung Park A255 R A-Faceless Haven @Titus Lunter A268 U A-Skemfar Elderhall @Johannes Voss A378 R A-Canopy Tactician @Ekaterina Burmak diff --git a/forge-gui/res/editions/Strixhaven School of Mages.txt b/forge-gui/res/editions/Strixhaven School of Mages.txt index f4a3c6c0291..4f3c9f9419a 100644 --- a/forge-gui/res/editions/Strixhaven School of Mages.txt +++ b/forge-gui/res/editions/Strixhaven School of Mages.txt @@ -409,8 +409,16 @@ ScryfallCode=STX 382 U Decisive Denial @Lorenzo Mastroianni [rebalanced] +A15 U A-Dueling Coach @Caio Monteiro A41 U A-Divide by Zero @Liiga Smilshkalne +A46 U A-Mentor's Guidance @Brian Valeza A56 U A-Symmetry Sage @Jehan Choo +A88 U A-Tenured Inkcaster @Jake Murray +A92 U A-Ardent Dustspeaker @Mads Ahm +A117 C A-Tome Shredder @Sam Rowan +A156 M A-Rowan, Scholar of Sparks @Magali Villeneuve +A202 U A-Maelstrom Muse @Michele Parisi +A240 M A-Tanazir Quandrix @Raymond Swanland A258 U A-Spell Satchel @YW Tang [lesson] diff --git a/forge-gui/res/editions/Throne of Eldraine.txt b/forge-gui/res/editions/Throne of Eldraine.txt index 5174383e2ac..d53f359cd39 100644 --- a/forge-gui/res/editions/Throne of Eldraine.txt +++ b/forge-gui/res/editions/Throne of Eldraine.txt @@ -425,6 +425,7 @@ ScryfallCode=ELD 397 U Inspiring Veteran @Scott Murphy [rebalanced] +A81 U A-Cauldron Familiar @Milivoj Ćeran A125 R A-Fires of Invention @Stanton Feng [tokens] diff --git a/forge-gui/res/editions/Zendikar Rising.txt b/forge-gui/res/editions/Zendikar Rising.txt index 1fd2b6e4fb5..882f1bc3819 100644 --- a/forge-gui/res/editions/Zendikar Rising.txt +++ b/forge-gui/res/editions/Zendikar Rising.txt @@ -419,12 +419,21 @@ ScryfallCode=ZNR [rebalanced] A24 R A-Luminarch Aspirant @Mads Ahm +A68 R A-Master of Winds @Yigit Koroglu +A105 C A-Hagra Constrictor @Simon Dominic +A126 U A-Skyclave Shadowcat @Simon Dominic A141 U A-Goma Fada Vanguard @Sean Sevestre A145 R A-Kargan Intimidator @Kieran Yanner +A154 U A-Rockslide Sorcerer @Daarken +A185 C A-Gnarlid Colony @Izzy +A187 U A-Iridescent Hornbeetle @Simon Dominic +A198 R A-Oran-Rief Ooze @Daarken A224 U A-Kargan Warleader @Colin Boyer +A228 U A-Moss-Pit Skeleton @Bryan Sola A230 M A-Nahiri, Heir of the Ancients @Anna Steinbauer A232 M A-Omnath, Locus of Creation @Chris Rahn A234 R A-Phylath, World Sculptor @Victor Adame Minguez +A238 U A-Umara Mystic @Bryan Sola A257 U A-Base Camp @Jokubas Uogintas [ModalDoubleFaceCards] diff --git a/forge-gui/res/formats/Archived/Alchemy/2022-07-07.txt b/forge-gui/res/formats/Archived/Alchemy/2022-07-07.txt new file mode 100644 index 00000000000..f020b77db24 --- /dev/null +++ b/forge-gui/res/formats/Archived/Alchemy/2022-07-07.txt @@ -0,0 +1,7 @@ +[format] +Name:Alchemy (HBG) +Type:Archived +Subtype:Arena +Effective:2022-07-07 +Sets:ANA, ANB, ZNR, KHM, STX, AFR, MID, VOW, YMID, NEO, YNEO, SNC, YSNC, HBG +Banned:Grinning Ignus diff --git a/forge-gui/res/formats/Archived/Historic/2022-07-07.txt b/forge-gui/res/formats/Archived/Historic/2022-07-07.txt new file mode 100644 index 00000000000..f42567978ad --- /dev/null +++ b/forge-gui/res/formats/Archived/Historic/2022-07-07.txt @@ -0,0 +1,7 @@ +[format] +Name:Historic (HBG) +Type:Archived +Subtype:Arena +Effective:2022-07-07 +Sets: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 +Banned:Agent of Treachery; Brainstorm; Channel; Counterspell; Dark Ritual; Demonic Tutor; Field of the Dead; Lightning Bolt; Memory Lapse; Natural Order; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Swords to Plowshares; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Wilderness Reclamation diff --git a/forge-gui/res/formats/Sanctioned/Historic.txt b/forge-gui/res/formats/Sanctioned/Historic.txt index f5f2b80afe0..0fd4d467bae 100644 --- a/forge-gui/res/formats/Sanctioned/Historic.txt +++ b/forge-gui/res/formats/Sanctioned/Historic.txt @@ -4,5 +4,5 @@ Type:Digital Subtype:Arena Effective:2019-11-21 Order:142 -Sets: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 -Banned:Agent of Treachery; Brainstorm; Channel; Counterspell; Dark Ritual; Demonic Tutor; Field of the Dead; Lightning Bolt; Memory Lapse; Natural Order; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Swords to Plowshares; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Wilderness Reclamation; Winota, Joiner of Forces +Sets: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 +Banned:Agent of Treachery; Brainstorm; Channel; Counterspell; Dark Ritual; Demonic Tutor; Field of the Dead; Lightning Bolt; Memory Lapse; Natural Order; Nexus of Fate; Oko, Thief of Crowns; Once Upon a Time; Swords to Plowshares; Thassa's Oracle; Tibalt's Trickery; Time Warp; Uro, Titan of Nature's Wrath; Veil of Summer; Wilderness Reclamation diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 9edcb5f56ae..d9d41a58429 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -229,6 +229,8 @@ btnDownloadPics=Bilder(LQ) Karten herunterladen btnDownloadQuestImages=Bilder für Quests herunterladen btnDownloadAchievementImages=Bilder für Erfolge herunterladen btnReportBug=Einen Fehler melden +lblProcessingCards=Karten verarbeiten ... +lblCardAudit=Kartenprüfung btnListImageData=Prüfe Karten- und Bilddaten lblListImageData=Prüfe auf von Forge nicht unterstütze Karten und fehlende Kartenbilder btnImportPictures=Daten importieren @@ -1502,6 +1504,8 @@ lblAttachee=Anhang #TriggerAttackerBlocked.java lblNumberBlockers=Anzahl Blocker lblBlocker=Blocker +#TriggerAttackerBlockedOnce.java +lblAttackers=Angreifer #TriggerAttackersDeclared.java lblNumberAttackers=Anzahl Angreifer #TriggerAttackerUnblockedOnce.java diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index cd3d95cc229..fb0bb721444 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -230,6 +230,8 @@ btnDownloadPics=Download LQ Card Pictures btnDownloadQuestImages=Download Quest Images btnDownloadAchievementImages=Download Achievement Images btnReportBug=Report a Bug +lblProcessingCards=Processing Cards... +lblCardAudit=Card Audit btnListImageData=Audit Card and Image Data lblListImageData=Audit cards not implemented by Forge and missing card images btnImportPictures=Import Data @@ -1504,6 +1506,8 @@ lblAttachee=Attachee #TriggerAttackerBlocked.java lblNumberBlockers=Number Blockers lblBlocker=Blocker +#TriggerAttackerBlockedOnce.java +lblAttackers=Attackers #TriggerAttackersDeclared.java lblNumberAttackers=Number Attackers #TriggerAttackerUnblockedOnce.java diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 6d9c08b0e81..a958ef3753e 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -230,6 +230,8 @@ btnDownloadPics=Descargar cartas únicas btnDownloadQuestImages=Descargar imágenes del modo aventura btnDownloadAchievementImages=Descargar imágenes de los trofeos btnReportBug=Reportar un error +lblProcessingCards=Tarjetas de procesamiento ... +lblCardAudit=Auditoría btnListImageData=Auditar cartas y datos de imagen lblListImageData=Audita cartas no implementadas por Forge e imágenes de cartas faltantes btnImportPictures=Importar datos @@ -1502,6 +1504,8 @@ lblAttachee=Acoplado #TriggerAttackerBlocked.java lblNumberBlockers=Número de bloqueadores lblBlocker=Bloqueador +#TriggerAttackerBlockedOnce.java +lblAttackers=Atacantes #TriggerAttackersDeclared.java lblNumberAttackers=Atacantes #TriggerAttackerUnblockedOnce.java diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index c4e9cadf3b4..a05c4995887 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -229,6 +229,8 @@ btnDownloadPics=Scarica immagini delle carte a bassa qualità btnDownloadQuestImages=Scarica immagini Avventura btnDownloadAchievementImages=Scarica immagini dei trofei btnReportBug=Segnalare un bug +lblProcessingCards=Carte di elaborazione ... +lblCardAudit=Audit della scheda btnListImageData=Verifica i dati e le immagini delle carte lblListImageData=Verifica le carte non implementate in Forge e le immagini mancanti btnImportPictures=Importa dati @@ -1502,6 +1504,8 @@ lblAttachee=Assegnata #TriggerAttackerBlocked.java lblNumberBlockers=Numero di bloccanti lblBlocker=Creatura bloccante +#TriggerAttackerBlockedOnce.java +lblAttackers=Attackers #TriggerAttackersDeclared.java lblNumberAttackers=Numero di attaccanti #TriggerAttackerUnblockedOnce.java diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index 406f7c2f50a..b0dcf152e0f 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -230,6 +230,8 @@ btnDownloadPics=LQ カード画像のダウンロード btnDownloadQuestImages=クエスト画像をダウンロード btnDownloadAchievementImages=実績画像のダウンロード btnReportBug=バグを報告する +lblProcessingCards=処理カード... +lblCardAudit=カード監査 btnListImageData=カードと画像データの監査 lblListImageData=Forge で実装されていないカードと欠落しているカード画像の監査 btnImportPictures=画像データのインポート @@ -1503,6 +1505,8 @@ lblAttachee=つけ先 #TriggerAttackerBlocked.java lblNumberBlockers=ブロッカー数 lblBlocker=ブロッカー +#TriggerAttackerBlockedOnce.java +lblAttackers=Attackers #TriggerAttackersDeclared.java lblNumberAttackers=アタッカー数 #TriggerAttackerUnblockedOnce.java diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties index 6b46663718e..fe47d779f1a 100644 --- a/forge-gui/res/languages/pt-BR.properties +++ b/forge-gui/res/languages/pt-BR.properties @@ -231,6 +231,8 @@ btnDownloadPics=Baixar Imagens das Cartas em Baixa Qualidade btnDownloadQuestImages=Baixar Imagens das Missões btnDownloadAchievementImages=Baixar Imagens das Conquistas btnReportBug=Relatar um Bug +lblProcessingCards=Cartões de processamento ... +lblCardAudit=Auditoria do cartão btnListImageData=Auditar Cartas e Imagens lblListImageData=Auditar cartas não implementadas no Forge e imagens ausentes de cartas btnImportPictures=Importar Dados @@ -1538,6 +1540,8 @@ lblAttachee=Anexar #TriggerAttackerBlocked.java lblNumberBlockers=Numerar Bloqueadores lblBlocker=Bloqueador +#TriggerAttackerBlockedOnce.java +lblAttackers=Attackers #TriggerAttackersDeclared.java lblNumberAttackers=Numerar Atacantes #TriggerAttackerUnblockedOnce.java diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 1f1b3423eb4..98aeec6bf1e 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -183,7 +183,7 @@ nlWorkshopSyntax=在作坊中启用卡牌脚本检查。注意:该功能任在 nlGameLogEntryType=更改游戏中日志显示的信息量。排序为最少到最详细。 nlCloseAction=更改单击右上角X按钮时的动作 nlLoadCardsLazily=如果打开该选项Forge将在使用到卡牌脚本时才加载(警告:实验状态)。 -nlLoadArchivedFormats=If turned on, Forge will load all archived format definitions, this may take slightly longer to load at startup. +nlLoadArchivedFormats=如果启用该选项,Forge将会在启动时加载所有历史赛制,这可能会导致Forge的启动时间明显变长。 GraphicOptions=图形选项 nlDefaultFontSize=UI中字体的默认大小。所有字体元素都相对于此缩放。(需要重启) nlCardArtFormat=牌图的格式。(Full表示使用完整的牌张图片,Crop表示只使用牌图的插画部分) @@ -230,6 +230,8 @@ btnDownloadPics=下载低清卡图 btnDownloadQuestImages=下载冒险图片 btnDownloadAchievementImages=下载成就图片 btnReportBug=报告错误 +lblProcessingCards=处理卡... +lblCardAudit=卡审核 btnListImageData=统计牌和图片数据 lblListImageData=统计Forge实现且缺少的图片的牌 btnImportPictures=导入数据 @@ -552,7 +554,7 @@ cbWantReprints=允许来自其他系列的重印牌 lblChooseFormats=选择赛制 lblSanctioned=常规 lblOther=其他 -lblArchived=Archived +lblArchived=存档 lblCancel=取消 #DialogChoosePoolDistribution.java lblBlack=黑 @@ -1504,6 +1506,8 @@ lblAttachee=结附 #TriggerAttackerBlocked.java lblNumberBlockers=阻挡者数 lblBlocker=阻挡者 +#TriggerAttackerBlockedOnce.java +lblAttackers=进攻者 #TriggerAttackersDeclared.java lblNumberAttackers=攻击者数 #TriggerAttackerUnblockedOnce.java @@ -2673,7 +2677,7 @@ lblRenameQuestTo=重命名冒险之旅为 lblQuestRename=冒险之旅重命名 #StartingPoolType.java lblUnrestricted=无限制 -lblCasualOrArchivedFormat=Casual/Archived format +lblCasualOrArchivedFormat=休闲/存档赛制 lblCustomFormat=自定义赛制 lblEventOrStartDeck=活动或初始套牌 lblMySealedDeck=我的现开套牌 diff --git a/forge-gui/res/lists/borderlessCardList.txt b/forge-gui/res/lists/borderlessCardList.txt index 61c6dbad236..2e512c10f20 100644 --- a/forge-gui/res/lists/borderlessCardList.txt +++ b/forge-gui/res/lists/borderlessCardList.txt @@ -1,3 +1,88 @@ +2X2/Aether Vial2.fullborder +2X2/Allosaurus Shepherd2.fullborder +2X2/Anger of the Gods2.fullborder +2X2/Assassin's Trophy2.fullborder +2X2/Azorius Chancery2.fullborder +2X2/Blood Artist2.fullborder +2X2/Bloodbraid Elf2.fullborder +2X2/Bloodforged Battle-Axe2.fullborder +2X2/Bloom Tender2.fullborder +2X2/Boros Garrison2.fullborder +2X2/Burning-Tree Emissary2.fullborder +2X2/Cavern of Souls2.fullborder +2X2/Chaos Warp2.fullborder +2X2/City of Brass2.fullborder +2X2/Coiling Oracle2.fullborder +2X2/Concordant Crossroads2.fullborder +2X2/Consecrated Sphinx2.fullborder +2X2/Crucible of Worlds2.fullborder +2X2/Damnation2.fullborder +2X2/Dimir Aqueduct2.fullborder +2X2/Dockside Extortionist2.fullborder +2X2/Dragonlord Dromoka2.fullborder +2X2/Elenda, the Dusk Rose2.fullborder +2X2/Emiel the Blessed2.fullborder +2X2/Emrakul, the Aeons Torn2.fullborder +2X2/Emrakul, the Aeons Torn4.fullborder +2X2/Eternal Witness2.fullborder +2X2/Flickerwisp2.fullborder +2X2/Forbidden Orchard2.fullborder +2X2/Force of Negation2.fullborder +2X2/Gifts Ungiven2.fullborder +2X2/Glimpse the Unthinkable2.fullborder +2X2/Golgari Rot Farm2.fullborder +2X2/Grand Arbiter Augustin IV2.fullborder +2X2/Grim Flayer2.fullborder +2X2/Gruul Turf2.fullborder +2X2/Hardened Scales2.fullborder +2X2/Imperial Seal2.fullborder +2X2/Inquisition of Kozilek2.fullborder +2X2/Izzet Boilerworks2.fullborder +2X2/Kolaghan's Command2.fullborder +2X2/Kozilek, Butcher of Truth2.fullborder +2X2/Kozilek, Butcher of Truth4.fullborder +2X2/Lightning Bolt2.fullborder +2X2/Liliana, the Last Hope2.fullborder +2X2/Liliana, the Last Hope4.fullborder +2X2/Mana Drain2.fullborder +2X2/Mana Vault2.fullborder +2X2/Marchesa, the Black Rose2.fullborder +2X2/Mentor of the Meek2.fullborder +2X2/Monastery Swiftspear2.fullborder +2X2/Muldrotha, the Gravetide2.fullborder +2X2/Mulldrifter2.fullborder +2X2/Oracle of Mul Daya2.fullborder +2X2/Orzhov Basilica2.fullborder +2X2/Panharmonicon2.fullborder +2X2/Phyrexian Altar2.fullborder +2X2/Pithing Needle2.fullborder +2X2/Privileged Position2.fullborder +2X2/Qasali Pridemage2.fullborder +2X2/Rakdos Carnarium2.fullborder +2X2/Rampant Growth2.fullborder +2X2/Seasoned Pyromancer2.fullborder +2X2/Sedris, the Traitor King2.fullborder +2X2/Seeker of the Way2.fullborder +2X2/Selesnya Sanctuary2.fullborder +2X2/Sensei's Divining Top2.fullborder +2X2/Simic Growth Chamber2.fullborder +2X2/Smothering Tithe2.fullborder +2X2/Spell Pierce2.fullborder +2X2/Supreme Verdict2.fullborder +2X2/Surgical Extraction2.fullborder +2X2/Teferi's Protection2.fullborder +2X2/Terminate2.fullborder +2X2/The Mimeoplasm2.fullborder +2X2/Thought Scour2.fullborder +2X2/Thousand-Year Storm2.fullborder +2X2/Ulamog, the Infinite Gyre2.fullborder +2X2/Ulamog, the Infinite Gyre4.fullborder +2X2/Unearth2.fullborder +2X2/Vedalken Orrery2.fullborder +2X2/Wall of Omens2.fullborder +2X2/Wrenn and Six2.fullborder +2X2/Wrenn and Six4.fullborder +2X2/Young Pyromancer2.fullborder 2XM/Academy Ruins2.fullborder 2XM/Atraxa, Praetors' Voice2.fullborder 2XM/Avacyn, Angel of Hope2.fullborder @@ -38,6 +123,36 @@ 2XM/Urza's Power Plant2.fullborder 2XM/Urza's Tower2.fullborder 2XM/Wurmcoil Engine2.fullborder +AFR/Adult Gold Dragon2.fullborder +AFR/Black Dragon2.fullborder +AFR/Blue Dragon2.fullborder +AFR/Ebondeath, Dracolich2.fullborder +AFR/Ellywick Tumblestrum2.fullborder +AFR/Grand Master of Flowers2.fullborder +AFR/Green Dragon2.fullborder +AFR/Icingdeath, Frost Tyrant2.fullborder +AFR/Inferno of the Star Mounts2.fullborder +AFR/Iymrith, Desert Doom2.fullborder +AFR/Lolth, Spider Queen2.fullborder +AFR/Mordenkainen2.fullborder +AFR/Old Gnawbone2.fullborder +AFR/Red Dragon2.fullborder +AFR/Tiamat2.fullborder +AFR/White Dragon2.fullborder +AFR/Zariel, Archduke of Avernus2.fullborder +CLB/Ancient Brass Dragon2.fullborder +CLB/Ancient Bronze Dragon2.fullborder +CLB/Ancient Copper Dragon2.fullborder +CLB/Ancient Gold Dragon2.fullborder +CLB/Ancient Silver Dragon2.fullborder +CLB/Battle Angels of Tyr2.fullborder +CLB/Bramble Sovereign2.fullborder +CLB/Elminster2.fullborder +CLB/Legion Loyalty2.fullborder +CLB/Minsc & Boo, Timeless Heroes2.fullborder +CLB/Nautiloid Ship2.fullborder +CLB/Tasha, the Witch Queen2.fullborder +CLB/Vexing Puzzlebox2.fullborder CMR/Jeska, Thrice Reborn2.fullborder CMR/Tevesh Szat, Doom of Fools2.fullborder ELD/Garruk, Cursed Huntsman2.fullborder @@ -112,6 +227,53 @@ M21/Solemn Simulacrum2.fullborder M21/Teferi, Master of Time2.fullborder M21/Ugin, the Spirit Dragon2.fullborder M21/Ugin, the Spirit Dragon3.fullborder +MH2/Cabal Coffers2.fullborder +MH2/Chatterfang, Squirrel General3.fullborder +MH2/Dakkon, Shadow Slayer3.fullborder +MH2/Endurance2.fullborder +MH2/Fury2.fullborder +MH2/Geyadrone Dihada3.fullborder +MH2/Grief2.fullborder +MH2/Grist, the Hunger Tide3.fullborder +MH2/Imperial Recruiter2.fullborder +MH2/Mirari's Wake2.fullborder +MH2/Mishra's Factory2.fullborder +MH2/Ragavan, Nimble Pilferer2.fullborder +MH2/Scion of Draco3.fullborder +MH2/Shardless Agent3.fullborder +MH2/Solitude2.fullborder +MH2/Subtlety2.fullborder +MH2/Svyelun of Sea and Sky3.fullborder +MH2/Sword of Hearth and Home3.fullborder +MH2/Thrasta, Tempest's Roar2.fullborder +MH2/Titania, Protector of Argoth2.fullborder +MH2/Tourach, Dread Cantor2.fullborder +MH2/Vindicate2.fullborder +MID/Arlinn, the Moon's Fury2.fullborder +MID/Arlinn, the Pack's Hope2.fullborder +MID/Deserted Beach2.fullborder +MID/Haunted Ridge2.fullborder +MID/Overgrown Farmland2.fullborder +MID/Rockfall Vale2.fullborder +MID/Shipwreck Marsh2.fullborder +MID/Teferi, Who Slows the Sunset2.fullborder +MID/Wrenn and Seven2.fullborder +NEO/Ao, the Dawn Sky2.fullborder +NEO/Atsushi, the Blazing Sky2.fullborder +NEO/Boseiju, Who Endures2.fullborder +NEO/Eiganjo, Seat of the Empire3.fullborder +NEO/Junji, the Midnight Sky2.fullborder +NEO/Kairi, the Swirling Sky2.fullborder +NEO/Kaito Shizuki2.fullborder +NEO/Kura, the Boundless Sky2.fullborder +NEO/Kyodai, Soul of Kamigawa2.fullborder +NEO/Otawara, Soaring City2.fullborder +NEO/Sokenzan, Crucible of Defiance2.fullborder +NEO/Takenuma, Abandoned Mire2.fullborder +NEO/Tamiyo, Compleated Sage2.fullborder +NEO/Tezzeret, Betrayer of Flesh2.fullborder +NEO/The Wandering Emperor2.fullborder +PLG20/Hangarback Walker.fullborder PLGS/Hangarback Walker.fullborder PSLD/Tibalt, the Fiend-Blooded.fullborder SLD/Acidic Slime.fullborder @@ -124,6 +286,8 @@ SLD/Saskia the Unyielding.fullborder SLD/Scavenging Ooze.fullborder SLD/The Mimeoplasm.fullborder SLD/Voidslime.fullborder +SLU/Boulderloft Pathway.fullborder +SLU/Branchloft Pathway.fullborder SLU/Brightclimb Pathway.fullborder SLU/Clearwater Pathway.fullborder SLU/Cragcrown Pathway.fullborder @@ -134,8 +298,32 @@ SLU/Needleverge Pathway.fullborder SLU/Pillarverge Pathway.fullborder SLU/Riverglide Pathway.fullborder SLU/Timbercrown Pathway.fullborder -SLU/Boulderloft Pathway.fullborder -SLU/Branchloft Pathway.fullborder +SNC/All-Seeing Arbiter2.fullborder +SNC/Bootleggers' Stash2.fullborder +SNC/Elspeth Resplendent2.fullborder +SNC/Gala Greeters10.fullborder +SNC/Gala Greeters11.fullborder +SNC/Gala Greeters12.fullborder +SNC/Gala Greeters13.fullborder +SNC/Gala Greeters3.fullborder +SNC/Gala Greeters4.fullborder +SNC/Gala Greeters5.fullborder +SNC/Gala Greeters6.fullborder +SNC/Gala Greeters7.fullborder +SNC/Gala Greeters8.fullborder +SNC/Gala Greeters9.fullborder +SNC/Halo Fountain2.fullborder +SNC/Jetmir's Garden2.fullborder +SNC/Ob Nixilis, the Adversary2.fullborder +SNC/Raffine's Tower2.fullborder +SNC/Shadow of Mortality2.fullborder +SNC/Spara's Headquarters2.fullborder +SNC/Titan of Industry2.fullborder +SNC/Topiary Stomper2.fullborder +SNC/Vivien on the Hunt3.fullborder +SNC/Xander's Lounge2.fullborder +SNC/Ziatora's Proving Ground2.fullborder +STA/Abundant Harvest1.fullborder STA/Abundant Harvest2.fullborder STA/Adventurous Impulse1.fullborder STA/Adventurous Impulse2.fullborder @@ -261,7 +449,6 @@ STA/Weather the Storm1.fullborder STA/Weather the Storm2.fullborder STA/Whirlwind Denial1.fullborder STA/Whirlwind Denial2.fullborder -STA/Abundant Harvest1.fullborder STX/Beledros Witherbloom2.fullborder STX/Galazeth Prismari2.fullborder STX/Kasmina, Enigma Sage2.fullborder @@ -276,11 +463,62 @@ STX/Will, Scholar of Frost2.fullborder THB/Ashiok, Nightmare Muse2.fullborder THB/Calix, Destiny's Hand2.fullborder THB/Elspeth, Sun's Nemesis2.fullborder +UNF/Blood Crypt.fullborder +UNF/Breeding Pool.fullborder +UNF/Forest1.fullborder +UNF/Forest2.fullborder +UNF/Godless Shrine.fullborder +UNF/Hallowed Fountain.fullborder +UNF/Island1.fullborder +UNF/Island2.fullborder +UNF/Mountain1.fullborder +UNF/Mountain2.fullborder +UNF/Overgrown Tomb.fullborder +UNF/Plains1.fullborder +UNF/Plains2.fullborder +UNF/Sacred Foundry.fullborder +UNF/Steam Vents.fullborder +UNF/Stomping Ground.fullborder +UNF/Swamp1.fullborder +UNF/Swamp2.fullborder +UNF/Temple Garden.fullborder +UNF/Watery Grave.fullborder UST/Forest.fullborder UST/Island.fullborder UST/Mountain.fullborder UST/Plains.fullborder UST/Swamp.fullborder +VOW/Bloodbat Summoner3.fullborder +VOW/Chandra, Dressed to Kill2.fullborder +VOW/Circle of Confinement2.fullborder +VOW/Deathcap Glade2.fullborder +VOW/Dreamroot Cascade2.fullborder +VOW/Edgar Markov's Coffin3.fullborder +VOW/Edgar, Charmed Groom3.fullborder +VOW/Eruth, Tormented Prophet3.fullborder +VOW/Falkenrath Forebear3.fullborder +VOW/Hauken's Insight3.fullborder +VOW/Henrika Domnathi3.fullborder +VOW/Henrika, Infernal Seer3.fullborder +VOW/Innocent Traveler3.fullborder +VOW/Investigator's Journal2.fullborder +VOW/Jacob Hauken, Inspector3.fullborder +VOW/Kaya, Geist Hunter2.fullborder +VOW/Malicious Invader3.fullborder +VOW/Olivia, Crimson Bride3.fullborder +VOW/Reclusive Taxidermist2.fullborder +VOW/Savior of Ollenbock2.fullborder +VOW/Shattered Sanctum2.fullborder +VOW/Sorin the Mirthless2.fullborder +VOW/Sorin the Mirthless4.fullborder +VOW/Stormcarved Coast2.fullborder +VOW/Sundown Pass2.fullborder +VOW/Thalia, Guardian of Thraben3.fullborder +VOW/Thirst for Discovery2.fullborder +VOW/Torens, Fist of the Angels3.fullborder +VOW/Vampires' Vengeance2.fullborder +VOW/Voldaren Bloodcaster3.fullborder +VOW/Voldaren Estate3.fullborder ZNR/Boulderloft Pathway2.fullborder ZNR/Branchloft Pathway2.fullborder ZNR/Brightclimb Pathway2.fullborder diff --git a/forge-gui/res/lists/net-decks-archive-block.txt b/forge-gui/res/lists/net-decks-archive-block.txt index 52fdad3d70c..54c87de90bd 100644 --- a/forge-gui/res/lists/net-decks-archive-block.txt +++ b/forge-gui/res/lists/net-decks-archive-block.txt @@ -994,3 +994,4 @@ 2022-04-01 Sealed Neo Block Super Qualifier (8 decks) | https://downloads.cardforge.org/decks/archive/block/2022-04-01-sealed-neo-block-super-qualifier.zip 2022-04-24 Sealed Neo Block Mocs (8 decks) | https://downloads.cardforge.org/decks/archive/block/2022-04-24-sealed-neo-block-mocs.zip 2022-04-25 Sealed Neo Block Mocs (8 decks) | https://downloads.cardforge.org/decks/archive/block/2022-04-25-sealed-neo-block-mocs.zip +2022-07-11 Sealed Snc Block Super Qualifier (8 decks) | https://downloads.cardforge.org/decks/archive/block/2022-07-11-sealed-snc-block-super-qualifier.zip diff --git a/forge-gui/res/lists/net-decks-archive-legacy.txt b/forge-gui/res/lists/net-decks-archive-legacy.txt index 50da6c75e44..41180c70a54 100644 --- a/forge-gui/res/lists/net-decks-archive-legacy.txt +++ b/forge-gui/res/lists/net-decks-archive-legacy.txt @@ -2332,3 +2332,19 @@ 2022-06-12 Legacy Challenge (31 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-06-12-legacy-challenge.zip 2022-06-12 Legacy Preliminary (42 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-06-12-legacy-preliminary.zip 2022-06-13 Legacy Challenge (31 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-06-13-legacy-challenge.zip +2022-06-18 Legacy League (62 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-06-18-legacy-league.zip +2022-06-19 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-06-19-legacy-challenge.zip +2022-06-20 Legacy Challenge (31 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-06-20-legacy-challenge.zip +2022-06-21 Legacy Preliminary (3 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-06-21-legacy-preliminary.zip +2022-06-25 Legacy League (53 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-06-25-legacy-league.zip +2022-06-26 Legacy Challenge (31 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-06-26-legacy-challenge.zip +2022-06-27 Legacy Showcase Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-06-27-legacy-showcase-challenge.zip +2022-06-28 Legacy Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-06-28-legacy-preliminary.zip +2022-07-02 Legacy League (55 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-07-02-legacy-league.zip +2022-07-03 Legacy Challenge (31 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-07-03-legacy-challenge.zip +2022-07-04 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-07-04-legacy-challenge.zip +2022-07-05 Legacy Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-07-05-legacy-preliminary.zip +2022-07-09 Legacy League (45 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-07-09-legacy-league.zip +2022-07-10 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-07-10-legacy-challenge.zip +2022-07-11 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-07-11-legacy-challenge.zip +2022-07-13 Legacy Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/legacy/2022-07-13-legacy-preliminary.zip diff --git a/forge-gui/res/lists/net-decks-archive-modern.txt b/forge-gui/res/lists/net-decks-archive-modern.txt index 93e532c8c1d..6301917f9ab 100644 --- a/forge-gui/res/lists/net-decks-archive-modern.txt +++ b/forge-gui/res/lists/net-decks-archive-modern.txt @@ -3106,3 +3106,39 @@ 2022-06-14 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-14-modern-preliminary.zip 2022-06-17 Modern League (51 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-17-modern-league.zip 2022-06-17 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-17-modern-preliminary.zip +2022-06-18 Modern Preliminary (41 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-18-modern-preliminary.zip +2022-06-19 Modern Preliminary (42 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-19-modern-preliminary.zip +2022-06-20 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-20-modern-challenge.zip +2022-06-20 Modern Preliminary (50 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-20-modern-preliminary.zip +2022-06-21 Modern League (69 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-21-modern-league.zip +2022-06-21 Modern Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-21-modern-preliminary.zip +2022-06-22 Modern Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-22-modern-preliminary.zip +2022-06-23 Modern Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-23-modern-preliminary.zip +2022-06-24 Modern League (66 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-24-modern-league.zip +2022-06-24 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-24-modern-preliminary.zip +2022-06-25 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-25-modern-preliminary.zip +2022-06-26 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-26-modern-challenge.zip +2022-06-27 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-27-modern-challenge.zip +2022-06-28 Modern League (67 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-28-modern-league.zip +2022-06-28 Modern Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-28-modern-preliminary.zip +2022-06-29 Modern Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-29-modern-preliminary.zip +2022-06-30 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-06-30-modern-preliminary.zip +2022-07-01 Modern League (61 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-01-modern-league.zip +2022-07-01 Modern Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-01-modern-preliminary.zip +2022-07-02 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-02-modern-preliminary.zip +2022-07-03 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-03-modern-challenge.zip +2022-07-04 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-04-modern-challenge.zip +2022-07-04 Modern Super Qualifier (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-04-modern-super-qualifier.zip +2022-07-05 Modern League (70 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-05-modern-league.zip +2022-07-05 Modern Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-05-modern-preliminary.zip +2022-07-06 Modern Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-06-modern-preliminary.zip +2022-07-07 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-07-modern-preliminary.zip +2022-07-08 Modern League (54 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-08-modern-league.zip +2022-07-08 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-08-modern-preliminary.zip +2022-07-09 Modern Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-09-modern-preliminary.zip +2022-07-10 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-10-modern-challenge.zip +2022-07-11 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-11-modern-challenge.zip +2022-07-12 Modern League (71 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-12-modern-league.zip +2022-07-12 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-12-modern-preliminary.zip +2022-07-13 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-13-modern-preliminary.zip +2022-07-14 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2022-07-14-modern-preliminary.zip diff --git a/forge-gui/res/lists/net-decks-archive-pauper.txt b/forge-gui/res/lists/net-decks-archive-pauper.txt index 3c8118486ab..923cfe0134b 100644 --- a/forge-gui/res/lists/net-decks-archive-pauper.txt +++ b/forge-gui/res/lists/net-decks-archive-pauper.txt @@ -1933,3 +1933,16 @@ 2022-06-12 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-06-12-pauper-challenge.zip 2022-06-13 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-06-13-pauper-challenge.zip 2022-06-15 Pauper League (38 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-06-15-pauper-league.zip +2022-06-19 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-06-19-pauper-challenge.zip +2022-06-19 Pauper Showcase Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-06-19-pauper-showcase-challenge.zip +2022-06-20 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-06-20-pauper-challenge.zip +2022-06-22 Pauper League (37 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-06-22-pauper-league.zip +2022-06-26 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-06-26-pauper-challenge.zip +2022-06-27 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-06-27-pauper-challenge.zip +2022-06-29 Pauper League (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-06-29-pauper-league.zip +2022-07-03 Pauper Challenge (31 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-07-03-pauper-challenge.zip +2022-07-04 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-07-04-pauper-challenge.zip +2022-07-06 Pauper League (34 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-07-06-pauper-league.zip +2022-07-10 Pauper Challenge (31 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-07-10-pauper-challenge.zip +2022-07-11 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-07-11-pauper-challenge.zip +2022-07-13 Pauper League (36 decks) | https://downloads.cardforge.org/decks/archive/pauper/2022-07-13-pauper-league.zip \ No newline at end of file diff --git a/forge-gui/res/lists/net-decks-archive-pioneer.txt b/forge-gui/res/lists/net-decks-archive-pioneer.txt index d0737532343..e317cca34ce 100644 --- a/forge-gui/res/lists/net-decks-archive-pioneer.txt +++ b/forge-gui/res/lists/net-decks-archive-pioneer.txt @@ -1023,3 +1023,34 @@ 2022-06-13 Pioneer Showcase Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-13-pioneer-showcase-challenge.zip 2022-06-15 Pioneer Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-15-pioneer-preliminary.zip 2022-06-16 Pioneer League (48 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-16-pioneer-league.zip +2022-06-19 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-19-pioneer-challenge.zip +2022-06-20 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-20-pioneer-challenge.zip +2022-06-20 Pioneer League (50 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-20-pioneer-league.zip +2022-06-22 Pioneer Preliminary (50 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-22-pioneer-preliminary.zip +2022-06-23 Pioneer League (46 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-23-pioneer-league.zip +2022-06-23 Pioneer Preliminary (39 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-23-pioneer-preliminary.zip +2022-06-24 Pioneer Preliminary (54 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-24-pioneer-preliminary.zip +2022-06-26 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-26-pioneer-challenge.zip +2022-06-27 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-27-pioneer-challenge.zip +2022-06-27 Pioneer League (57 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-27-pioneer-league.zip +2022-06-28 Pioneer Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-28-pioneer-preliminary.zip +2022-06-29 Pioneer Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-29-pioneer-preliminary.zip +2022-06-30 Pioneer League (45 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-06-30-pioneer-league.zip +2022-07-01 Pioneer Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-01-pioneer-preliminary.zip +2022-07-03 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-03-pioneer-challenge.zip +2022-07-03 Pioneer Super Qualifier (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-03-pioneer-super-qualifier.zip +2022-07-04 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-04-pioneer-challenge.zip +2022-07-04 Pioneer League (41 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-04-pioneer-league.zip +2022-07-05 Pioneer Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-05-pioneer-preliminary.zip +2022-07-06 Pioneer Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-06-pioneer-preliminary.zip +2022-07-07 Pioneer League (37 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-07-pioneer-league.zip +2022-07-07 Pioneer Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-07-pioneer-preliminary.zip +2022-07-08 Pioneer Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-08-pioneer-preliminary.zip +2022-07-09 Pioneer Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-09-pioneer-preliminary.zip +2022-07-10 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-10-pioneer-challenge.zip +2022-07-11 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-11-pioneer-challenge.zip +2022-07-11 Pioneer League (41 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-11-pioneer-league.zip +2022-07-12 Pioneer Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-12-pioneer-preliminary.zip +2022-07-13 Pioneer Preliminary (8 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-13-pioneer-preliminary.zip +2022-07-14 Pioneer League (18 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-14-pioneer-league.zip +2022-07-14 Pioneer Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2022-07-14-pioneer-preliminary.zip diff --git a/forge-gui/res/lists/net-decks-archive-standard.txt b/forge-gui/res/lists/net-decks-archive-standard.txt index 5cc2ee11237..454df870504 100644 --- a/forge-gui/res/lists/net-decks-archive-standard.txt +++ b/forge-gui/res/lists/net-decks-archive-standard.txt @@ -2658,3 +2658,19 @@ 2022-06-13 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-06-13-standard-challenge.zip 2022-06-13 Standard League (16 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-06-13-standard-league.zip 2022-06-16 Standard League (15 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-06-16-standard-league.zip +2022-06-19 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-06-19-standard-challenge.zip +2022-06-20 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-06-20-standard-challenge.zip +2022-06-20 Standard League (20 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-06-20-standard-league.zip +2022-06-23 Standard League (14 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-06-23-standard-league.zip +2022-06-26 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-06-26-standard-challenge.zip +2022-06-27 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-06-27-standard-challenge.zip +2022-06-27 Standard League (19 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-06-27-standard-league.zip +2022-06-30 Standard League (15 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-06-30-standard-league.zip +2022-07-03 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-07-03-standard-challenge.zip +2022-07-04 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-07-04-standard-challenge.zip +2022-07-04 Standard League (11 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-07-04-standard-league.zip +2022-07-07 Standard League (11 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-07-07-standard-league.zip +2022-07-10 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-07-10-standard-challenge.zip +2022-07-11 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-07-11-standard-challenge.zip +2022-07-11 Standard League (7 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-07-11-standard-league.zip +2022-07-14 Standard League (10 decks) | https://downloads.cardforge.org/decks/archive/standard/2022-07-14-standard-league.zip diff --git a/forge-gui/res/lists/net-decks-archive-vintage.txt b/forge-gui/res/lists/net-decks-archive-vintage.txt index 6ae331b4b74..45d9a411391 100644 --- a/forge-gui/res/lists/net-decks-archive-vintage.txt +++ b/forge-gui/res/lists/net-decks-archive-vintage.txt @@ -1680,3 +1680,15 @@ 2022-06-14 Vintage Preliminary (21 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-06-14-vintage-preliminary.zip 2022-06-15 Vintage Preliminary (21 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-06-15-vintage-preliminary.zip 2022-06-16 Vintage Preliminary (21 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-06-16-vintage-preliminary.zip +2022-06-19 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-06-19-vintage-challenge.zip +2022-06-19 Vintage League (19 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-06-19-vintage-league.zip +2022-06-20 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-06-20-vintage-challenge.zip +2022-06-26 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-06-26-vintage-challenge.zip +2022-06-26 Vintage League (15 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-06-26-vintage-league.zip +2022-06-27 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-06-27-vintage-challenge.zip +2022-07-03 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-07-03-vintage-challenge.zip +2022-07-03 Vintage League (8 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-07-03-vintage-league.zip +2022-07-04 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-07-04-vintage-challenge.zip +2022-07-10 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-07-10-vintage-challenge.zip +2022-07-10 Vintage League (15 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-07-10-vintage-league.zip +2022-07-11 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2022-07-11-vintage-challenge.zip diff --git a/forge-gui/res/puzzle/PS_SNC3.pzl b/forge-gui/res/puzzle/PS_SNC3.pzl new file mode 100644 index 00000000000..7e35c91537d --- /dev/null +++ b/forge-gui/res/puzzle/PS_SNC3.pzl @@ -0,0 +1,18 @@ +[metadata] +Name:Possibility Storm - Streets of New Capenna #03 +URL:https://i0.wp.com/www.possibilitystorm.com/wp-content/uploads/2022/05/200.-SNC3-scaled.jpg?ssl=1 +Goal:Win +Turns:1 +Difficulty:Mythic +Description:Win this turn. Can you deal 200 damage in this 200th Possibility Storm puzzle? Assume graveyards start empty. Remember that your solution must satisfy all blocking possibilities. Good luck! +[state] +humanlife=20 +ailife=200 +turn=1 +activeplayer=human +activephase=MAIN1 +humanhand=Exponential Growth;Wayward Guide-Beast;Ognis, the Dragon's Lash;Stimulus Package;Exhibition Magician;Prizefight +humanbattlefield=Ill-Tempered Loner|Transformed;Pyre-Sledge Arsonist;Jaxis, the Troublemaker;Xorn;Treasure Vault;Treasure Vault;Ziatora's Proving Ground;Ziatora's Proving Ground;Ziatora's Proving Ground;Rockfall Vale;Rockfall Vale;Rockfall Vale +aibattlefield=Wingshield Agent;Pugnacious Pugilist +aiprecast=Exhibition Magician:DBToken;Exhibition Magician:DBToken;Exhibition Magician:DBToken;Exhibition Magician:DBToken +removesummoningsickness=true diff --git a/forge-gui/res/puzzle/PS_SNC4.pzl b/forge-gui/res/puzzle/PS_SNC4.pzl new file mode 100644 index 00000000000..6e4974041c6 --- /dev/null +++ b/forge-gui/res/puzzle/PS_SNC4.pzl @@ -0,0 +1,19 @@ +[metadata] +Name:Possibility Storm - Streets of New Capenna #04 +URL:https://i1.wp.com/www.possibilitystorm.com/wp-content/uploads/2022/06/201.-SNC4NoOverkill-scaled.jpg?ssl=1 +Goal:Win +Turns:1 +Difficulty:Uncommon +Description:Win this turn. Your opponent has one card in hand, but assume they can't cast it. Remember that your solution must satisfy all blocking possibilities. Good luck! +[state] +humanlife=20 +ailife=9 +turn=1 +activeplayer=human +activephase=MAIN1 +humanhand=Bloodline Culling;Luxior, Giada's Gift;Ob Nixilis, the Adversary;Pugnacious Pugilist;Bloodchief's Thirst +humanbattlefield=Lolth, Spider Queen|Counters:LOYALTY=6;Sepulcher Ghoul;Sepulcher Ghoul;Swamp;Swamp;Swamp;Swamp;Mountain;Mountain +aihand=Opt +aibattlefield=Beledros Witherbloom +aiprecast=Beledros Witherbloom:TrigToken;Beledros Witherbloom:TrigToken +removesummoningsickness=true diff --git a/forge-gui/res/puzzle/PS_SNC5.pzl b/forge-gui/res/puzzle/PS_SNC5.pzl new file mode 100644 index 00000000000..0f8ea6bcdc9 --- /dev/null +++ b/forge-gui/res/puzzle/PS_SNC5.pzl @@ -0,0 +1,19 @@ +[metadata] +Name:Possibility Storm - Streets of New Capenna #05 +URL:https://i2.wp.com/www.possibilitystorm.com/wp-content/uploads/2022/07/latest-scaled.jpg?ssl=1 +Goal:Win +Turns:1 +Difficulty:Mythic +Description:Win this turn. In your library, assume you have additional copies of each card on your board and in your hand (and no others - and you don't know the order.) Remember that your solution must satisfy all blocking possibilities. Good luck! +[state] +humanlife=20 +ailife=14 +turn=1 +activeplayer=human +activephase=MAIN1 +humanhand=Iron Apprentice;Getaway Car;High-Speed Hoverbike;Vampire's Kiss;Murder +humanlibrary=Mechtitan Core;Teferi, Who Slows the Sunset;High-Speed Hoverbike;Swamp;Swamp;Swamp;Getaway Car;Iron Apprentice;Deserted Beach;Deserted Beach;Deserted Beach;Vampire's Kiss;Murder;Raffine, Scheming Seer;Oswald Fiddlebender;Surgehacker Mech +humanbattlefield=Teferi, Who Slows the Sunset|Counters:LOYALTY=1;Raffine, Scheming Seer|Counters:P1P1=1;Oswald Fiddlebender|Counters:P1P1=1;Mechtitan Core;Surgehacker Mech;Deserted Beach;Deserted Beach;Deserted Beach;Swamp;Swamp;Swamp +aibattlefield=Iymrith, Desert Doom;Master of Winds;Needlethorn Drake +# Shuffle the human library to randomize the order in which the cards are (since the player is assumed not to know or have any specific fixed order of cards in the library). +humanprecast=Ancestral Knowledge:TrigShuffle diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index cb06d43414d..1647515cac3 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -667,6 +667,10 @@ public class HumanCostDecision extends CostDecisionMakerBase { final int c = cost.getAbilityAmount(ability); if (cost.payCostFromSource()) { + // UnlessCost so player might not want to pay (Fabricate) + if (ability.hasParam("UnlessCost") && !controller.confirmPayment(cost, Localizer.getInstance().getMessage("lblPutNTypeCounterOnTarget", String.valueOf(c), cost.getCounter().getName(), ability.getHostCard().getName()), ability)) { + return null; + } cost.setLastPaidAmount(c); return PaymentDecision.number(c); } diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 14087cd9995..4a324214593 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -105,10 +105,9 @@ public class HumanPlay { source.forceTurnFaceUp(); } - if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { - if (!CharmEffect.makeChoices(sa)) { - return false; - } + if (sa.getApi() == ApiType.Charm && !CharmEffect.makeChoices(sa)) { + // 603.3c If no mode is chosen, the ability is removed from the stack. + return false; } sa = AbilityUtils.addSpliceEffects(sa); @@ -168,12 +167,12 @@ public class HumanPlay { source.setSplitStateToPlayAbility(sa); + if (sa.getApi() == ApiType.Charm && !CharmEffect.makeChoices(sa)) { + // 603.3c If no mode is chosen, the ability is removed from the stack. + return; + } + if (!sa.isCopied()) { - if (sa.getApi() == ApiType.Charm && !sa.isWrapper()) { - if (!CharmEffect.makeChoices(sa)) { - return; - } - } sa = AbilityUtils.addSpliceEffects(sa); } diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index 74fa086002a..9d9b4ce0ebb 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -164,7 +164,7 @@ public class HumanPlaySpellAbility { if (!prerequisitesMet) { if (!ability.isTrigger()) { - rollbackAbility(fromZone, zonePosition, payment, c); + GameActionUtil.rollbackAbility(ability, fromZone, zonePosition, payment, c); if (ability.getHostCard().isMadness()) { // if a player failed to play madness cost, move the card to graveyard Card newCard = game.getAction().moveToGraveyard(c, null); @@ -209,48 +209,6 @@ public class HumanPlaySpellAbility { return true; } - private void rollbackAbility(final Zone fromZone, final int zonePosition, CostPayment payment, Card oldCard) { - // cancel ability during target choosing - final Game game = ability.getActivatingPlayer().getGame(); - - if (fromZone != null) { // and not a copy - oldCard.setCastSA(null); - oldCard.setCastFrom(null); - // add back to where it came from, hopefully old state - // skip GameAction - oldCard.getZone().remove(oldCard); - fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null); - ability.setHostCard(oldCard); - ability.setXManaCostPaid(null); - ability.setSpendPhyrexianMana(false); - if (ability.hasParam("Announce")) { - for (final String aVar : ability.getParam("Announce").split(",")) { - final String varName = aVar.trim(); - if (!varName.equals("X")) { - ability.setSVar(varName, "0"); - } - } - } - // better safe than sorry approach in case rolled back ability was copy (from addExtraKeywordCost) - for (SpellAbility sa : oldCard.getSpells()) { - sa.setHostCard(oldCard); - } - //for Chorus of the Conclave - ability.rollback(); - - oldCard.setBackSide(false); - oldCard.setState(oldCard.getFaceupCardStateName(), true); - oldCard.unanimateBestow(); - } - - ability.clearTargets(); - - ability.resetOnceResolved(); - payment.refundPayment(); - game.getStack().clearFrozen(); - game.getTriggerHandler().clearWaitingTriggers(); - } - private boolean announceValuesLikeX() { if (ability.isCopied() || ability.isWrapper()) { return true; } //don't re-announce for spell copies diff --git a/forge-gui/src/main/java/forge/util/ImageFetcher.java b/forge-gui/src/main/java/forge/util/ImageFetcher.java index f0f513f201f..da702c2c5da 100644 --- a/forge-gui/src/main/java/forge/util/ImageFetcher.java +++ b/forge-gui/src/main/java/forge/util/ImageFetcher.java @@ -3,7 +3,7 @@ package forge.util; import java.io.File; import java.util.*; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; import forge.card.CardEdition; import forge.item.IPaperCard; @@ -18,7 +18,7 @@ import forge.localinstance.properties.ForgePreferences; import forge.model.FModel; public abstract class ImageFetcher { - private static final ExecutorService threadPool = Executors.newCachedThreadPool(); + private static final ExecutorService threadPool = ThreadUtil.getServicePool(); // see https://scryfall.com/docs/api/languages and // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes private static final HashMap langCodeMap = new HashMap<>(); @@ -167,7 +167,11 @@ public abstract class ImageFetcher { currentFetches.remove(destPath); } }; - threadPool.submit(getDownloadTask(downloadUrls.toArray(new String[0]), destPath, notifyObservers)); + try { + threadPool.submit(getDownloadTask(downloadUrls.toArray(new String[0]), destPath, notifyObservers)); + } catch (RejectedExecutionException re) { + re.printStackTrace(); + } } protected abstract Runnable getDownloadTask(String[] toArray, String destPath, Runnable notifyObservers);