From 6b94871b7823a549efed55647816a0af771cbffe Mon Sep 17 00:00:00 2001 From: Michael Kamensky Date: Sat, 16 Oct 2021 18:29:18 +0300 Subject: [PATCH 01/32] - A Linux script that tries to detect the Java version and run the game in a way specific to the given version. Seems to work with OpenJDK. --- forge-gui-desktop/src/main/config/forge.sh | 52 +++++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/forge-gui-desktop/src/main/config/forge.sh b/forge-gui-desktop/src/main/config/forge.sh index 1e3165f2ed0..ee90b3109a8 100644 --- a/forge-gui-desktop/src/main/config/forge.sh +++ b/forge-gui-desktop/src/main/config/forge.sh @@ -1,3 +1,51 @@ -#!/bin/sh +#!/bin/bash + +# returns the JDK version. +# 8 for 1.8.0_nn, 9 for 9-ea etc, and "no_java" for undetected +# Based on the code from this source: https://eed3si9n.com/detecting-java-version-bash +jdk_version() { + local result + local java_cmd + if [[ -n $(type -p java) ]] + then + java_cmd=java + elif [[ (-n "$JAVA_HOME") && (-x "$JAVA_HOME/bin/java") ]] + then + java_cmd="$JAVA_HOME/bin/java" + fi + local IFS=$'\n' + # remove \r for Cygwin + local lines=$("$java_cmd" -Xms32M -Xmx32M -version 2>&1 | tr '\r' '\n') + if [[ -z $java_cmd ]] + then + result=no_java + else + for line in $lines; do + if [[ (-z $result) && ($line = *"version \""*) ]] + then + local ver=$(echo $line | sed -e 's/.*version "\(.*\)"\(.*\)/\1/; 1q') + # on macOS, sed doesn't support '?' + if [[ $ver = "1."* ]] + then + result=$(echo $ver | sed -e 's/1\.\([0-9]*\)\(.*\)/\1/; 1q') + else + result=$(echo $ver | sed -e 's/\([0-9]*\)\(.*\)/\1/; 1q') + fi + fi + done + fi + echo "$result" +} +v="$(jdk_version)" + cd $(dirname "${0}") -java -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$ + +if [[ $v -ge 17 ]] +then + java --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$ +elif [[ $v -ge 11 ]] +then + java --illegal-access=permit -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$ +else + java -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$ +fi From 445d655dd356fc3fbe046a4e308fbbe5f15805c2 Mon Sep 17 00:00:00 2001 From: Michael Kamensky Date: Tue, 19 Oct 2021 19:35:35 +0300 Subject: [PATCH 02/32] - Consolidate shared parameters in a variable. --- forge-gui-desktop/src/main/config/forge.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/forge-gui-desktop/src/main/config/forge.sh b/forge-gui-desktop/src/main/config/forge.sh index ee90b3109a8..f196096178e 100644 --- a/forge-gui-desktop/src/main/config/forge.sh +++ b/forge-gui-desktop/src/main/config/forge.sh @@ -38,14 +38,15 @@ jdk_version() { } v="$(jdk_version)" +SHAREDPARAMS='-Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$' cd $(dirname "${0}") if [[ $v -ge 17 ]] then - java --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$ + java --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED $SHAREDPARAMS elif [[ $v -ge 11 ]] then - java --illegal-access=permit -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$ + java --illegal-access=permit -Xmx4096m -Dfile.encoding=UTF-8 -jar $SHAREDPARAMS else - java -Xmx4096m -Dfile.encoding=UTF-8 -jar $project.build.finalName$ + java -Xmx4096m -Dfile.encoding=UTF-8 -jar $SHAREDPARAMS fi From 48f894b1ae394afb1010bc16b366ec0a2eba81fc Mon Sep 17 00:00:00 2001 From: Michael Kamensky Date: Wed, 20 Oct 2021 07:09:00 +0300 Subject: [PATCH 03/32] - Fix shared params lines. --- forge-gui-desktop/src/main/config/forge.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui-desktop/src/main/config/forge.sh b/forge-gui-desktop/src/main/config/forge.sh index f196096178e..3edf69ceec2 100644 --- a/forge-gui-desktop/src/main/config/forge.sh +++ b/forge-gui-desktop/src/main/config/forge.sh @@ -46,7 +46,7 @@ then java --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED $SHAREDPARAMS elif [[ $v -ge 11 ]] then - java --illegal-access=permit -Xmx4096m -Dfile.encoding=UTF-8 -jar $SHAREDPARAMS + java --illegal-access=permit $SHAREDPARAMS else - java -Xmx4096m -Dfile.encoding=UTF-8 -jar $SHAREDPARAMS + java $SHAREDPARAMS fi From 47a1a93625d1b0819120dcc3e86e9ac8d0e0b521 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Sun, 24 Oct 2021 22:10:22 +0200 Subject: [PATCH 04/32] Fix SA not fizzling when targeting cards that left game --- forge-game/src/main/java/forge/game/Game.java | 4 ++-- .../src/main/java/forge/game/GameAction.java | 3 +++ .../game/ability/effects/DigUntilEffect.java | 8 ++++---- .../src/main/java/forge/game/card/Card.java | 4 ++++ .../deckeditor/controllers/ACEditorBase.java | 4 ++-- .../controllers/CEditorConstructed.java | 1 - .../home/settings/VSubmenuDownloaders.java | 18 +----------------- 7 files changed, 16 insertions(+), 26 deletions(-) diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 8015ed28220..4ef0be2278e 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -830,7 +830,7 @@ public class Game { c.removeTempController(p); // return stolen spells if (c.isInZone(ZoneType.Stack)) { - SpellAbilityStackInstance si = getStack().getInstanceFromSpellAbility(c.getCastSA()); + SpellAbilityStackInstance si = getStack().getInstanceMatchingSpellAbilityID(c.getCastSA()); si.setActivatingPlayer(c.getController()); } if (c.getController().equals(p)) { @@ -854,7 +854,7 @@ public class Game { if (p != null && p.isMonarch()) { // if the player who lost was the Monarch, someone else will be the monarch - if(p.equals(getPhaseHandler().getPlayerTurn())) { + if (p.equals(getPhaseHandler().getPlayerTurn())) { getAction().becomeMonarch(getNextPlayerAfter(p), null); } else { getAction().becomeMonarch(getPhaseHandler().getPlayerTurn(), null); diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 427e372557b..9d3a3454e2d 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -997,6 +997,9 @@ public class GameAction { } public void ceaseToExist(Card c, boolean skipTrig) { + if (c.isInZone(ZoneType.Stack)) { + c.getGame().getStack().remove(c); + } c.getZone().remove(c); // CR 603.6c other players LTB triggers should work 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 68d3fabfe48..81f41238da0 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 @@ -179,12 +179,12 @@ public class DigUntilEffect extends SpellAbilityEffect { Localizer.getInstance().getMessage("lblDoYouWantPutCardToZone", foundDest.getTranslatedName()))) { continue; } + Map moveParams = AbilityKey.newMap(); + moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield); + moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard); Card m = null; if (sa.hasParam("GainControl") && foundDest.equals(ZoneType.Battlefield)) { c.setController(sa.getActivatingPlayer(), game.getNextTimestamp()); - Map moveParams = AbilityKey.newMap(); - moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield); - moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard); m = game.getAction().moveTo(c.getController().getZone(foundDest), c, sa, moveParams); if (sa.hasParam("Tapped")) { c.setTapped(true); @@ -195,7 +195,7 @@ public class DigUntilEffect extends SpellAbilityEffect { } else if (sa.hasParam("NoMoveFound") && foundDest.equals(ZoneType.Library)) { //Don't do anything } else { - m = game.getAction().moveTo(foundDest, c, foundLibPos, sa); + m = game.getAction().moveTo(foundDest, c, foundLibPos, sa, moveParams); } revealed.remove(c); if (m != null && !origin.equals(m.getZone().getZoneType())) { 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 cd0dce80ae2..fbe145d1c4c 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -5891,6 +5891,10 @@ public class Card extends GameEntity implements Comparable, IHasSVars { @Override public final boolean canBeTargetedBy(final SpellAbility sa) { + if (getOwner().hasLost()) { + return false; + } + if (sa == null) { return true; } diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java index 09d36720e7b..e84ef4b64a5 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java @@ -545,9 +545,9 @@ public abstract class ACEditorBase { pnlContent.setOpaque(false); if (javaRecentEnough()) { - // With Blacksmith we would upload the releases and the /latest would redirect to the right URL // That currently doesn't happen so lets comment out this button for now // pnlContent.add(btnCheckForUpdates, constraintsBTN); @@ -122,9 +120,7 @@ public enum VSubmenuDownloaders implements IVSubmenu { pnlContent.add(btnDownloadSkins, constraintsBTN); pnlContent.add(_makeLabel(localizer.getMessage("lblDownloadSkins")), constraintsLBL); - } else { - String text = localizer.getMessage("lblYourVersionOfJavaIsTooOld"); FLabel label = new FLabel.Builder().fontAlign(SwingConstants.CENTER).text(text).fontStyle(Font.BOLD).fontSize(18).build(); pnlContent.add(label, "w 90%!, h 25px!, center, gap 0 0 30px 3px"); @@ -137,7 +133,6 @@ public enum VSubmenuDownloaders implements IVSubmenu { text = text + " . " + localizer.getMessage("lblYouNeedAtLeastJavaVersion") ; label = new FLabel.Builder().fontAlign(SwingConstants.CENTER).text(text).fontStyle(Font.BOLD).fontSize(18).build(); pnlContent.add(label, "w 90%!, h 25px!, center, gap 0 0 0 36px"); - } pnlContent.add(btnListImageData, constraintsBTN); @@ -154,15 +149,12 @@ public enum VSubmenuDownloaders implements IVSubmenu { pnlContent.add(btnLicensing, constraintsBTN); pnlContent.add(_makeLabel(localizer.getMessage("lblLicensing")), constraintsLBL); - } private boolean javaRecentEnough() { - RuntimeVersion javaVersion = RuntimeVersion.of(System.getProperty("java.version")); return javaVersion.getMajor() >= 9 || (javaVersion.getMajor() >= 1 && (javaVersion.getMinor() > 8 || (javaVersion.getMinor() == 8 && javaVersion.getUpdate() >= 101))); - } /* (non-Javadoc) @@ -199,7 +191,7 @@ public enum VSubmenuDownloaders implements IVSubmenu { public void setHowToPlayCommand(UiCommand command) { btnHowToPlay.setCommand(command); } public void setDownloadPricesCommand(UiCommand command) { btnDownloadPrices.setCommand(command); } public void setLicensingCommand(UiCommand command) { btnLicensing.setCommand(command); } - public void setDownloadSkinsCommand(UiCommand command) { btnDownloadSkins.setCommand(command); } + public void setDownloadSkinsCommand(UiCommand command) { btnDownloadSkins.setCommand(command); } public void focusTopButton() { btnDownloadPics.requestFocusInWindow(); @@ -269,7 +261,6 @@ public enum VSubmenuDownloaders implements IVSubmenu { String imagePath; int artIndex = 1; - ArrayList cis = new ArrayList<>(); HashMap> cardCount = new HashMap<>(); for (CardInSet c : e.getAllCardsInSet()) { @@ -302,10 +293,7 @@ public enum VSubmenuDownloaders implements IVSubmenu { continue; } - - // // check the front image - // imagePath = ImageUtil.getImageRelativePath(cp, false, true, false); if (imagePath != null) { File file = ImageKeys.getImageFile(imagePath); @@ -319,9 +307,7 @@ public enum VSubmenuDownloaders implements IVSubmenu { } } - // // check the back face - // if (cp.hasBackFace()) { imagePath = ImageUtil.getImageRelativePath(cp, true, true, false); if (imagePath != null) { @@ -372,7 +358,6 @@ public enum VSubmenuDownloaders implements IVSubmenu { nifSB.append("\n"); } - String totalStats = "Missing images: " + missingCount + "\nUnimplemented cards: " + notImplementedCount + "\n"; cniSB.append("\n-----------\n"); cniSB.append(totalStats); @@ -424,7 +409,6 @@ public enum VSubmenuDownloaders implements IVSubmenu { } }); } - public void showLicensing() { String license = "Forge License Information

" From c9b94710b25cb948bd8709a4b516ca39ba4df310 Mon Sep 17 00:00:00 2001 From: Michael Kamensky Date: Mon, 25 Oct 2021 05:08:14 +0000 Subject: [PATCH 05/32] - Initial implementation for simulated manual dexterity cards (Chaos Orb and Falling Star) with basic AI support. --- .../src/main/java/forge/ai/AiController.java | 81 +++++------ .../src/main/java/forge/ai/SpellApiToAi.java | 1 + .../main/java/forge/ai/ability/DestroyAi.java | 2 + .../ai/ability/FlipOntoBattlefieldAi.java | 48 +++++++ .../main/java/forge/game/ability/ApiType.java | 1 + .../effects/FlipOntoBattlefieldEffect.java | 136 ++++++++++++++++++ forge-gui/res/cardsfolder/c/chaos_orb.txt | 8 ++ forge-gui/res/cardsfolder/f/falling_star.txt | 7 + ...ool 93-94 Magic Full Set 878 Card Cube.dck | 4 +- forge-gui/res/languages/de-DE.properties | 7 + forge-gui/res/languages/en-US.properties | 7 + forge-gui/res/languages/es-ES.properties | 7 + forge-gui/res/languages/it-IT.properties | 7 + forge-gui/res/languages/ja-JP.properties | 7 + forge-gui/res/languages/zh-CN.properties | 7 + 15 files changed, 286 insertions(+), 44 deletions(-) create mode 100644 forge-ai/src/main/java/forge/ai/ability/FlipOntoBattlefieldAi.java create mode 100644 forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java create mode 100644 forge-gui/res/cardsfolder/c/chaos_orb.txt create mode 100644 forge-gui/res/cardsfolder/f/falling_star.txt diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 89fdf85963d..d8728803d39 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -17,19 +17,11 @@ */ package forge.ai; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - import com.esotericsoftware.minlog.Log; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; - import forge.ai.ability.ChangeZoneAi; import forge.ai.ability.ExploreAi; import forge.ai.ability.LearnAi; @@ -40,39 +32,17 @@ import forge.card.mana.ManaCost; import forge.deck.CardPool; import forge.deck.Deck; import forge.deck.DeckSection; -import forge.game.CardTraitBase; -import forge.game.CardTraitPredicates; -import forge.game.Direction; -import forge.game.Game; -import forge.game.GameActionUtil; -import forge.game.GameEntity; -import forge.game.GlobalRuleChange; +import forge.game.*; import forge.game.ability.AbilityKey; import forge.game.ability.AbilityUtils; import forge.game.ability.ApiType; import forge.game.ability.SpellApiBased; -import forge.game.card.Card; -import forge.game.card.CardCollection; -import forge.game.card.CardCollectionView; -import forge.game.card.CardFactoryUtil; -import forge.game.card.CardLists; -import forge.game.card.CardPlayOption; -import forge.game.card.CardPredicates; +import forge.game.card.*; import forge.game.card.CardPredicates.Accessors; 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.cost.CostAdjustment; -import forge.game.cost.CostDiscard; -import forge.game.cost.CostPart; -import forge.game.cost.CostPayEnergy; -import forge.game.cost.CostPayLife; -import forge.game.cost.CostPutCounter; -import forge.game.cost.CostRemoveCounter; -import forge.game.cost.CostSacrifice; +import forge.game.cost.*; import forge.game.keyword.Keyword; import forge.game.mana.ManaCostBeingPaid; import forge.game.phase.PhaseType; @@ -82,15 +52,7 @@ import forge.game.replacement.ReplaceMoved; import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementType; -import forge.game.spellability.AbilitySub; -import forge.game.spellability.LandAbility; -import forge.game.spellability.OptionalCost; -import forge.game.spellability.OptionalCostValue; -import forge.game.spellability.Spell; -import forge.game.spellability.SpellAbility; -import forge.game.spellability.SpellAbilityCondition; -import forge.game.spellability.SpellAbilityPredicates; -import forge.game.spellability.SpellPermanent; +import forge.game.spellability.*; import forge.game.staticability.StaticAbility; import forge.game.staticability.StaticAbilityMustTarget; import forge.game.trigger.Trigger; @@ -106,6 +68,9 @@ import forge.util.collect.FCollectionView; import io.sentry.Sentry; import io.sentry.event.BreadcrumbBuilder; +import java.util.*; +import java.util.Map.Entry; + /** *

* AiController class. @@ -1968,6 +1933,38 @@ public class AiController { // Whims of the Fates {all, 0, 0} result.addAll(pool); break; + case FlipOntoBattlefield: + if ("DamageCreatures".equals(sa.getParam("AILogic"))) { + int maxToughness = Integer.valueOf(sa.getSubAbility().getParam("NumDmg")); + CardCollectionView rightToughness = CardLists.filter(pool, new Predicate() { + @Override + public boolean apply(Card card) { + return card.getController().isOpponentOf(sa.getActivatingPlayer()) + && card.getNetToughness() <= maxToughness + && card.canBeDestroyed(); + } + }); + Card bestCreature = ComputerUtilCard.getBestCreatureAI(rightToughness.isEmpty() ? pool : rightToughness); + if (bestCreature != null) { + result.add(bestCreature); + } else { + result.add(Aggregates.random(pool)); // should ideally never get here + } + } else { + CardCollectionView viableOptions = CardLists.filter(pool, Predicates.and(CardPredicates.isControlledByAnyOf(sa.getActivatingPlayer().getOpponents())), + new Predicate() { + @Override + public boolean apply(Card card) { + return card.canBeDestroyed(); + } + }); + Card best = ComputerUtilCard.getBestAI(viableOptions); + if (best == null) { + best = Aggregates.random(pool); // should ideally never get here either + } + result.add(best); + } + break; default: CardCollection editablePool = new CardCollection(pool); for (int i = 0; i < max; i++) { diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index f0829816f72..b3dd9474918 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -87,6 +87,7 @@ public enum SpellApiToAi { .put(ApiType.Explore, ExploreAi.class) .put(ApiType.Fight, FightAi.class) .put(ApiType.FlipACoin, FlipACoinAi.class) + .put(ApiType.FlipOntoBattlefield, FlipOntoBattlefieldAi.class) .put(ApiType.Fog, FogAi.class) .put(ApiType.GainControl, ControlGainAi.class) .put(ApiType.GainControlVariant, ControlGainVariantAi.class) diff --git a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java index a07e8b2f9bd..f5b182846ce 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DestroyAi.java @@ -303,6 +303,8 @@ public class DestroyAi extends SpellAbilityAi { || ai.getLife() <= 5)) { // Basic ai logic for Lethal Vapors return false; + } else if ("Always".equals(logic)) { + return true; } if (list.isEmpty() diff --git a/forge-ai/src/main/java/forge/ai/ability/FlipOntoBattlefieldAi.java b/forge-ai/src/main/java/forge/ai/ability/FlipOntoBattlefieldAi.java new file mode 100644 index 00000000000..dfce114c0a0 --- /dev/null +++ b/forge-ai/src/main/java/forge/ai/ability/FlipOntoBattlefieldAi.java @@ -0,0 +1,48 @@ +package forge.ai.ability; + +import com.google.common.base.Predicate; +import forge.ai.SpellAbilityAi; +import forge.game.card.Card; +import forge.game.card.CardCollectionView; +import forge.game.card.CardLists; +import forge.game.phase.PhaseHandler; +import forge.game.phase.PhaseType; +import forge.game.player.Player; +import forge.game.player.PlayerActionConfirmMode; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; + +public class FlipOntoBattlefieldAi extends SpellAbilityAi { + @Override + protected boolean canPlayAI(Player aiPlayer, SpellAbility sa) { + PhaseHandler ph = sa.getHostCard().getGame().getPhaseHandler(); + String logic = sa.getParamOrDefault("AILogic", ""); + + if (!SpellAbilityAi.isSorcerySpeed(sa) && sa.getPayCosts().hasManaCost()) { + return ph.is(PhaseType.END_OF_TURN); + } + + if ("DamageCreatures".equals(logic)) { + int maxToughness = Integer.valueOf(sa.getSubAbility().getParam("NumDmg")); + CardCollectionView rightToughness = CardLists.filter(aiPlayer.getOpponents().getCreaturesInPlay(), new Predicate() { + @Override + public boolean apply(Card card) { + return card.getNetToughness() <= maxToughness && card.canBeDestroyed(); + } + }); + return !rightToughness.isEmpty(); + } + + return !aiPlayer.getOpponents().getCardsIn(ZoneType.Battlefield).isEmpty(); + } + + @Override + protected boolean doTriggerAINoCost(Player aiPlayer, SpellAbility sa, boolean mandatory) { + return canPlayAI(aiPlayer, sa) || mandatory; + } + + @Override + public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) { + return true; + } +} diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 3aa8a5cda27..a7cf52c8f5b 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -85,6 +85,7 @@ public enum ApiType { Explore (ExploreEffect.class), Fight (FightEffect.class), FlipACoin (FlipCoinEffect.class), + FlipOntoBattlefield (FlipOntoBattlefieldEffect.class), Fog (FogEffect.class), GainControl (ControlGainEffect.class), GainControlVariant (ControlGainVariantEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java new file mode 100644 index 00000000000..c53a8f30923 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java @@ -0,0 +1,136 @@ +package forge.game.ability.effects; + +import com.google.common.base.Predicate; +import com.google.common.collect.Lists; +import forge.game.Game; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.*; +import forge.game.player.Player; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; +import forge.util.Aggregates; +import forge.util.Localizer; +import forge.util.MyRandom; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +public class FlipOntoBattlefieldEffect extends SpellAbilityEffect { + @Override + public void resolve(SpellAbility sa) { + // Basic parameters defining the chances + final float chanceToFlip = 0.85f; + final int maxFlipTimes = 2; + final float chanceToHit = 0.70f; + final float chanceToHitTwoCards = 0.20f; + + final Card host = sa.getHostCard(); + final Player p = sa.getActivatingPlayer(); + final Game game = host.getGame(); + boolean flippedOnce = false; + + // TODO: allow to make a bounding box of sorts somehow, ideally - upgrade to a full system allowing to actually target by location + CardCollectionView tgtBox = p.getController().chooseCardsForEffect(game.getCardsIn(ZoneType.Battlefield), sa, Localizer.getInstance().getMessage("lblChooseDesiredLocation"), 1, 1, sa.hasParam("AllowRandom"), null); + + Card tgtLoc = tgtBox.getFirst(); + + Card lhsNeighbor = getNeighboringCard(tgtLoc, -1); + Card rhsNeighbor = getNeighboringCard(tgtLoc, 1); + + CardCollection randChoices = new CardCollection(); + randChoices.add(tgtLoc); + if (lhsNeighbor != null) { + randChoices.add(lhsNeighbor); + } else if (rhsNeighbor != null) { + randChoices.add(rhsNeighbor); + } + + // TODO: would be fun to add a small chance (e.g. 3-5%) to land unpredictably on some random target? + + flippedOnce = MyRandom.getRandom().nextFloat() <= chanceToFlip; // 20% chance that the card won't flip even once + if (!flippedOnce) { + sa.setSVar("TimesFlipped", "0"); + game.getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblDidNotFlipOver"), null); + return; + } else { + int flippedTimes = MyRandom.getRandom().nextInt(maxFlipTimes) + 1; + sa.setSVar("TimesFlipped", String.valueOf(flippedTimes)); // Currently the exact # of times is unused + game.getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblFlippedOver", flippedTimes), null); + } + + // Choose what was hit + CardCollection hit = new CardCollection(); + float outcome = MyRandom.getRandom().nextFloat(); + if (outcome <= chanceToHitTwoCards) { + hit.addAll(Aggregates.random(randChoices, randChoices.size() > 1 ? 2 : 1)); + if (hit.size() == 2) { + game.getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblLandedOnTwoCards", hit.getFirst(), hit.getLast()), null); + } else { + game.getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblLandedOnOneCard", hit.getFirst()), null); + } + } + else if (outcome <= chanceToHit) { + hit.add(Aggregates.random(randChoices)); + game.getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblLandedOnOneCard", hit.getFirst()), null); + } else { + game.getAction().notifyOfValue(sa, host, Localizer.getInstance().getMessage("lblDidNotLandOnCards"), null); + } + + // Remember whatever was hit + for (Card c : hit) { + host.addRemembered(c); + } + } + + @Override + protected String getStackDescription(SpellAbility sa) { + final StringBuilder sb = new StringBuilder(); + final Card host = sa.getHostCard(); + final Player p = sa.getActivatingPlayer(); + + sb.append("Flip "); + sb.append(host.toString()); + sb.append(" onto the battlefield from a height of at least one foot."); + + return sb.toString(); + } + + private Card getNeighboringCard(Card c, int direction) { + // Currently gets the nearest (by ID) card to the left or to the right of the designated one by type + Player controller = c.getController(); + ArrayList cardsOTB = Lists.newArrayList(CardLists.filter( + controller.getCardsIn(ZoneType.Battlefield), new Predicate() { + @Override + public boolean apply(Card card) { + if (c.isCreature()) { + return card.isCreature(); + } else if (c.isPlaneswalker() || c.isArtifact() || (c.isEnchantment() && !c.isAura())) { + return card.isPlaneswalker() || card.isArtifact() || (c.isEnchantment() && !c.isAura()); + } else if (c.isLand()) { + return card.isLand(); + } else if (c.isAttachedToEntity()) { + return card.isAttachedToEntity(c.getEntityAttachedTo()) || c.equals(card.getAttachedTo()); + } + return card.sharesCardTypeWith(c); + } + } + )); + + Collections.sort(cardsOTB, new Comparator() { + @Override + public int compare(Card c0, Card c1) { + return c0.getId() - c1.getId(); + } + }); + + int loc = cardsOTB.indexOf(c); + if (direction < 0 && loc > 0) { + return cardsOTB.get(loc - 1); + } else if (loc < cardsOTB.size() - 1) { + return cardsOTB.get(loc + 1); + } + + return c; + } +} diff --git a/forge-gui/res/cardsfolder/c/chaos_orb.txt b/forge-gui/res/cardsfolder/c/chaos_orb.txt new file mode 100644 index 00000000000..bb504ed4944 --- /dev/null +++ b/forge-gui/res/cardsfolder/c/chaos_orb.txt @@ -0,0 +1,8 @@ +Name:Chaos Orb +ManaCost:2 +Types:Artifact +A:AB$ FlipOntoBattlefield | Cost$ 1 T | SubAbility$ DBDestroyTouched | ActivationZone$ Battlefield | SpellDescription$ If CARDNAME is on the battlefield, flip CARDNAME onto the battlefield from a height of at least one foot. If CARDNAME turns over completely at least once during the flip, destroy all nontoken permanents it touches. Then destroy CARDNAME. | StackDescription$ SpellDescription +SVar:DBDestroyTouched:DB$ DestroyAll | ValidCards$ Card.IsRemembered+nonToken | SubAbility$ DBDestroyChaosOrb | AILogic$ Always | StackDescription$ None +SVar:DBDestroyChaosOrb:DB$ Destroy | Defined$ Self | SubAbility$ DBCleanup | AILogic$ Always | StackDescription$ None +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:{1}, {T}: If Chaos Orb is on the battlefield, flip Chaos Orb onto the battlefield from a height of at least one foot. If Chaos Orb turns over completely at least once during the flip, destroy all nontoken permanents it touches. Then destroy Chaos Orb. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/f/falling_star.txt b/forge-gui/res/cardsfolder/f/falling_star.txt new file mode 100644 index 00000000000..73af8782e11 --- /dev/null +++ b/forge-gui/res/cardsfolder/f/falling_star.txt @@ -0,0 +1,7 @@ +Name:Falling Star +ManaCost:2 R +Types:Sorcery +A:SP$ FlipOntoBattlefield | SubAbility$ DBDamageTouched | AILogic$ DamageCreatures | SpellDescription$ Flip CARDNAME onto the playing area from a height of at least one foot. CARDNAME deals 3 damage to each creature it lands on. Tap all creatures dealt damage by CARDNAME. If CARDNAME doesn't turn completely over at least once during the flip, it has no effect. | StackDescription$ SpellDescription +SVar:DBDamageTouched:DB$ DamageAll | ValidCards$ Creature.IsRemembered | ValidDescription$ each creature touched. | NumDmg$ 3 | SubAbility$ DBCleanup | StackDescription$ None +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +Oracle:Flip Falling Star onto the playing area from a height of at least one foot. Falling Star deals 3 damage to each creature it lands on. Tap all creatures dealt damage by Falling Star. If Falling Star doesn't turn completely over at least once during the flip, it has no effect. diff --git a/forge-gui/res/cube/Oldschool 93-94 Magic Full Set 878 Card Cube.dck b/forge-gui/res/cube/Oldschool 93-94 Magic Full Set 878 Card Cube.dck index b7537a1b872..70c1b20d229 100644 --- a/forge-gui/res/cube/Oldschool 93-94 Magic Full Set 878 Card Cube.dck +++ b/forge-gui/res/cube/Oldschool 93-94 Magic Full Set 878 Card Cube.dck @@ -244,7 +244,7 @@ Eternal Warrior|LEG Eureka|LEG Evil Eye of Orms-by-Gore|LEG Fallen Angel|LEG -#Falling Star|LEG +Falling Star|LEG Feint|LEG Field of Dreams|LEG Fire Sprites|LEG @@ -504,7 +504,7 @@ Camouflage|LEB Castle|LEB Celestial Prism|LEB Channel|LEB -#Chaos Orb|LEB +Chaos Orb|LEB Chaoslace|LEB Circle of Protection: Black|LEB Circle of Protection: Blue|LEB diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 62755f5d7f0..b6f80d5ba25 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1938,6 +1938,13 @@ lblTails=Zahl lblCallCoinFlip=Kopf oder Zahl lblWin=Gewonnen lblLose=Verloren +#FlipOntoBattlefieldEffect.java +lblChooseDesiredLocation=Choose a card to represent the center of the desired card landing location. +lblDidNotFlipOver=The card did not flip over. +lblFlippedOver=The card flipped over {0} time(s). +lblDidNotLandOnCards=The card did not land on any cards. +lblLandedOnOneCard=The card landed on {0}. +lblLandedOnTwoCards=The card landed on {0} and {1}. #InvestigateEffect.java lblWouldYouLikeInvestigate=Möchtest du Nachforschungen anstellen? #LifeSetEffect.java diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 53fddb7ae66..bcda90abb88 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1939,6 +1939,13 @@ lblTails=tails lblCallCoinFlip=Call coin flip lblWin=win lblLose=lose +#FlipOntoBattlefieldEffect.java +lblChooseDesiredLocation=Choose a card to represent the center of the desired card landing location. +lblDidNotFlipOver=The card did not flip over. +lblFlippedOver=The card flipped over {0} time(s). +lblDidNotLandOnCards=The card did not land on any cards. +lblLandedOnOneCard=The card landed on {0}. +lblLandedOnTwoCards=The card landed on {0} and {1}. #InvestigateEffect.java lblWouldYouLikeInvestigate=Do you want to investigate? #LifeSetEffect.java diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 33914a44b69..cb3047d8e3c 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -1937,6 +1937,13 @@ lblTails=cruz lblCallCoinFlip=Llamar al lanzamiento de la moneda lblWin=gana lblLose=pierde +#FlipOntoBattlefieldEffect.java +lblChooseDesiredLocation=Choose a card to represent the center of the desired card landing location. +lblDidNotFlipOver=The card did not flip over. +lblFlippedOver=The card flipped over {0} time(s). +lblDidNotLandOnCards=The card did not land on any cards. +lblLandedOnOneCard=The card landed on {0}. +lblLandedOnTwoCards=The card landed on {0} and {1}. #InvestigateEffect.java lblWouldYouLikeInvestigate=¿Quieres investigar? #LifeSetEffect.java diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 884e47b7c49..98de6dd4347 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -1936,6 +1936,13 @@ lblTails=croce lblCallCoinFlip=Scegli testa o croce lblWin=hai vinto lblLose=hai perso +#FlipOntoBattlefieldEffect.java +lblChooseDesiredLocation=Choose a card to represent the center of the desired card landing location. +lblDidNotFlipOver=The card did not flip over. +lblFlippedOver=The card flipped over {0} time(s). +lblDidNotLandOnCards=The card did not land on any cards. +lblLandedOnOneCard=The card landed on {0}. +lblLandedOnTwoCards=The card landed on {0} and {1}. #InvestigateEffect.java lblWouldYouLikeInvestigate=Do you want to investigate? #LifeSetEffect.java diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index 5dd856e3578..5949d490ed0 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -1936,6 +1936,13 @@ lblTails=裏 lblCallCoinFlip=コイン投げを予想 lblWin=勝ち lblLose=負け +#FlipOntoBattlefieldEffect.java +lblChooseDesiredLocation=Choose a card to represent the center of the desired card landing location. +lblDidNotFlipOver=The card did not flip over. +lblFlippedOver=The card flipped over {0} time(s). +lblDidNotLandOnCards=The card did not land on any cards. +lblLandedOnOneCard=The card landed on {0}. +lblLandedOnTwoCards=The card landed on {0} and {1}. #InvestigateEffect.java lblWouldYouLikeInvestigate=Do you want to investigate? #LifeSetEffect.java diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 0c6a32e70c9..c28b53f03e4 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -1940,6 +1940,13 @@ lblTails=背面 lblCallCoinFlip=掷骰子 lblWin=赢 lblLose=输 +#FlipOntoBattlefieldEffect.java +lblChooseDesiredLocation=Choose a card to represent the center of the desired card landing location. +lblDidNotFlipOver=The card did not flip over. +lblFlippedOver=The card flipped over {0} time(s). +lblDidNotLandOnCards=The card did not land on any cards. +lblLandedOnOneCard=The card landed on {0}. +lblLandedOnTwoCards=The card landed on {0} and {1}. #InvestigateEffect.java lblWouldYouLikeInvestigate=你想要探查吗? #LifeSetEffect.java From 470fe5fba45b580da8b308657503afbc4b13ca83 Mon Sep 17 00:00:00 2001 From: Michael Kamensky Date: Mon, 25 Oct 2021 09:11:22 +0300 Subject: [PATCH 06/32] - No need to sort cards by ID for FlipOntoBattlefieldEffect --- .../ability/effects/FlipOntoBattlefieldEffect.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java index c53a8f30923..98ced261189 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java @@ -4,7 +4,10 @@ import com.google.common.base.Predicate; import com.google.common.collect.Lists; import forge.game.Game; import forge.game.ability.SpellAbilityEffect; -import forge.game.card.*; +import forge.game.card.Card; +import forge.game.card.CardCollection; +import forge.game.card.CardCollectionView; +import forge.game.card.CardLists; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; @@ -13,8 +16,6 @@ import forge.util.Localizer; import forge.util.MyRandom; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; public class FlipOntoBattlefieldEffect extends SpellAbilityEffect { @Override @@ -117,13 +118,6 @@ public class FlipOntoBattlefieldEffect extends SpellAbilityEffect { } )); - Collections.sort(cardsOTB, new Comparator() { - @Override - public int compare(Card c0, Card c1) { - return c0.getId() - c1.getId(); - } - }); - int loc = cardsOTB.indexOf(c); if (direction < 0 && loc > 0) { return cardsOTB.get(loc - 1); From c70b6d13c1802b5f76bd332cc65cce3c1e06b469 Mon Sep 17 00:00:00 2001 From: Michael Kamensky Date: Mon, 25 Oct 2021 09:13:42 +0300 Subject: [PATCH 07/32] - Tweak a comment. --- .../forge/game/ability/effects/FlipOntoBattlefieldEffect.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java index 98ced261189..8f37045d119 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/FlipOntoBattlefieldEffect.java @@ -98,7 +98,7 @@ public class FlipOntoBattlefieldEffect extends SpellAbilityEffect { } private Card getNeighboringCard(Card c, int direction) { - // Currently gets the nearest (by ID) card to the left or to the right of the designated one by type + // Currently gets the nearest (in zone order) card to the left or to the right of the designated one by type Player controller = c.getController(); ArrayList cardsOTB = Lists.newArrayList(CardLists.filter( controller.getCardsIn(ZoneType.Battlefield), new Predicate() { From 668ed97ed18cfc1122c0698b6ec2fe8dc86680a7 Mon Sep 17 00:00:00 2001 From: Michael Kamensky Date: Mon, 25 Oct 2021 11:56:20 +0300 Subject: [PATCH 08/32] - Improve some AI hints for AFR. --- forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java | 5 +++-- forge-gui/res/cardsfolder/b/bag_of_holding.txt | 2 +- forge-gui/res/cardsfolder/g/green_dragon.txt | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java index 11e96df2fbd..f6680775cab 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java @@ -260,8 +260,9 @@ public class ChangeZoneAllAi extends SpellAbilityAi { // minimum card advantage unless the hand will be fully reloaded int minAdv = logic.contains(".minAdv") ? Integer.parseInt(logic.substring(logic.indexOf(".minAdv") + 7)) : 0; + boolean noDiscard = logic.contains(".noDiscard"); - if (numExiledWithSrc > curHandSize) { + if (numExiledWithSrc > curHandSize || (noDiscard && numExiledWithSrc > 0)) { if (ComputerUtil.predictThreatenedObjects(ai, sa, true).contains(source)) { // Try to gain some card advantage if the card will die anyway // TODO: ideally, should evaluate the hand value and not discard good hands to it @@ -269,7 +270,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi { } } - return (curHandSize + minAdv - 1 < numExiledWithSrc) || (numExiledWithSrc >= ai.getMaxHandSize()); + return (curHandSize + minAdv - 1 < numExiledWithSrc) || (!noDiscard && numExiledWithSrc >= ai.getMaxHandSize()); } } else if (origin.equals(ZoneType.Stack)) { // time stop can do something like this: diff --git a/forge-gui/res/cardsfolder/b/bag_of_holding.txt b/forge-gui/res/cardsfolder/b/bag_of_holding.txt index 501646deb76..1833b22d7f3 100644 --- a/forge-gui/res/cardsfolder/b/bag_of_holding.txt +++ b/forge-gui/res/cardsfolder/b/bag_of_holding.txt @@ -6,6 +6,6 @@ SVar:TrigExile:DB$ ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Des A:AB$ Draw | Cost$ 2 T | NumCards$ 1 | SpellDescription$ Draw a card, then discard a card. | SubAbility$ DBDiscard SVar:DBDiscard:DB$ Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose AI:RemoveDeck:All -A:AB$ ChangeZoneAll | Cost$ 4 T Sac<1/CARDNAME> | ChangeType$ Card.ExiledWithSource | Origin$ Exile | Destination$ Hand | AILogic$ DiscardAllAndRetExiled.minAdv2 | SpellDescription$ Return all cards exiled with CARDNAME to their owner's hand. +A:AB$ ChangeZoneAll | Cost$ 4 T Sac<1/CARDNAME> | ChangeType$ Card.ExiledWithSource | Origin$ Exile | Destination$ Hand | AILogic$ DiscardAllAndRetExiled.noDiscard.minAdv2 | SpellDescription$ Return all cards exiled with CARDNAME to their owner's hand. AI:RemoveDeck:Random Oracle:Whenever you discard a card, exile that card from your graveyard.\n{2}, {T}: Draw a card, then discard a card.\n{4}, {T}, Sacrifice Bag of Holding: Return all cards exiled with Bag of Holding to their owner's hand. diff --git a/forge-gui/res/cardsfolder/g/green_dragon.txt b/forge-gui/res/cardsfolder/g/green_dragon.txt index 03d417b5691..6416e776783 100644 --- a/forge-gui/res/cardsfolder/g/green_dragon.txt +++ b/forge-gui/res/cardsfolder/g/green_dragon.txt @@ -7,4 +7,5 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S SVar:TrigEffect:DB$ Effect | Triggers$ TrigDamageDone SVar:TrigDamageDone:Mode$ DamageDone | ValidTarget$ Creature.OppCtrl | Execute$ TrigDestroy | TriggerDescription$ Poison Breath — Until end of turn, whenever a creature an opponent controls is dealt damage, destroy it. SVar:TrigDestroy:DB$ Destroy | Defined$ TriggeredTarget +SVar:PlayMain1:TRUE Oracle:Flying\nPoison Breath — When Green Dragon enters the battlefield, until end of turn, whenever a creature an opponent controls is dealt damage, destroy it. From f0c852c54f0116c88fda45dc9005404ed7a4fead Mon Sep 17 00:00:00 2001 From: Tim Mocny Date: Mon, 25 Oct 2021 09:01:43 +0000 Subject: [PATCH 09/32] Blacker Lotus and Look at Me, I'm the DCI --- .../src/main/java/forge/ai/SpellApiToAi.java | 1 + .../src/main/java/forge/game/Match.java | 46 +++++++++++-------- .../main/java/forge/game/ability/ApiType.java | 1 + .../effects/RemoveFromMatchEffect.java | 37 +++++++++++++++ forge-gui/res/cardsfolder/b/blacker_lotus.txt | 6 +++ .../cardsfolder/l/look_at_me_im_the_dci.txt | 6 +++ 6 files changed, 79 insertions(+), 18 deletions(-) create mode 100644 forge-game/src/main/java/forge/game/ability/effects/RemoveFromMatchEffect.java create mode 100644 forge-gui/res/cardsfolder/b/blacker_lotus.txt create mode 100644 forge-gui/res/cardsfolder/l/look_at_me_im_the_dci.txt diff --git a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java index f0829816f72..e784c4d68a1 100644 --- a/forge-ai/src/main/java/forge/ai/SpellApiToAi.java +++ b/forge-ai/src/main/java/forge/ai/SpellApiToAi.java @@ -140,6 +140,7 @@ public enum SpellApiToAi { .put(ApiType.RemoveCounterAll, CannotPlayAi.class) .put(ApiType.RemoveFromCombat, RemoveFromCombatAi.class) .put(ApiType.RemoveFromGame, AlwaysPlayAi.class) + .put(ApiType.RemoveFromMatch, AlwaysPlayAi.class) .put(ApiType.ReorderZone, AlwaysPlayAi.class) .put(ApiType.Repeat, RepeatAi.class) .put(ApiType.RepeatEach, RepeatEachAi.class) diff --git a/forge-game/src/main/java/forge/game/Match.java b/forge-game/src/main/java/forge/game/Match.java index 6177720f34f..beac2accbb4 100644 --- a/forge-game/src/main/java/forge/game/Match.java +++ b/forge-game/src/main/java/forge/game/Match.java @@ -1,24 +1,7 @@ package forge.game; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.HashMultiset; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import com.google.common.collect.Multiset; +import com.google.common.collect.*; import com.google.common.eventbus.EventBus; - import forge.LobbyPlayer; import forge.deck.CardPool; import forge.deck.Deck; @@ -40,7 +23,11 @@ import forge.util.Localizer; import forge.util.MyRandom; import forge.util.collect.FCollectionView; +import java.util.*; +import java.util.Map.Entry; + public class Match { + private static List removedCards = Lists.newArrayList(); private final List players; private final GameRules rules; private final String title; @@ -198,6 +185,12 @@ public class Match { return myRemovedAnteCards; } + public static List getRemovedCards() { return removedCards; } + + public void removeCard(PaperCard c) { + removedCards.add(c); + } + private static void preparePlayerZone(Player player, final ZoneType zoneType, CardPool section, boolean canRandomFoil) { PlayerZone library = player.getZone(zoneType); List newLibrary = new ArrayList<>(); @@ -257,6 +250,23 @@ public class Match { } Deck toChange = psc.getDeck(); + if (!getRemovedCards().isEmpty()) { + CardPool main = new CardPool(); + main.addAll(toChange.get(DeckSection.Main)); + CardPool sideboard = new CardPool(); + sideboard.addAll(toChange.getOrCreate(DeckSection.Sideboard)); + for (PaperCard c : removedCards) { + if (main.contains(c)) { + main.remove(c, 1); + } else if (sideboard.contains(c)) { + sideboard.remove(c, 1); + } + } + toChange.getMain().clear(); + toChange.getMain().addAll(main); + toChange.get(DeckSection.Sideboard).clear(); + toChange.get(DeckSection.Sideboard).addAll(sideboard); + } List newMain = person.sideboard(toChange, rules.getGameType(), player.getName()); if (null != newMain) { CardPool allCards = new CardPool(); diff --git a/forge-game/src/main/java/forge/game/ability/ApiType.java b/forge-game/src/main/java/forge/game/ability/ApiType.java index 3aa8a5cda27..8808b208c2e 100644 --- a/forge-game/src/main/java/forge/game/ability/ApiType.java +++ b/forge-game/src/main/java/forge/game/ability/ApiType.java @@ -139,6 +139,7 @@ public enum ApiType { RemoveCounterAll (CountersRemoveAllEffect.class), RemoveFromCombat (RemoveFromCombatEffect.class), RemoveFromGame (RemoveFromGameEffect.class), + RemoveFromMatch (RemoveFromMatchEffect.class), ReorderZone (ReorderZoneEffect.class), Repeat (RepeatEffect.class), RepeatEach (RepeatEachEffect.class), diff --git a/forge-game/src/main/java/forge/game/ability/effects/RemoveFromMatchEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RemoveFromMatchEffect.java new file mode 100644 index 00000000000..f06bc534c39 --- /dev/null +++ b/forge-game/src/main/java/forge/game/ability/effects/RemoveFromMatchEffect.java @@ -0,0 +1,37 @@ +package forge.game.ability.effects; + +import forge.game.ability.AbilityUtils; +import forge.game.ability.SpellAbilityEffect; +import forge.game.card.Card; +import forge.game.card.CardCollection; +import forge.game.event.GameEventRandomLog; +import forge.game.spellability.SpellAbility; +import forge.game.zone.ZoneType; +import forge.item.PaperCard; + +public class RemoveFromMatchEffect extends SpellAbilityEffect { + @Override + public void resolve(SpellAbility sa) { + final Card host = sa.getHostCard(); + CardCollection toRemove; + + if (sa.hasParam("RemoveType")) { + CardCollection cards = (CardCollection) host.getOwner().getGame().getCardsInGame(); + if (sa.hasParam("IncludeSideboard")) { + CardCollection sideboard = (CardCollection) host.getGame().getCardsIn(ZoneType.Sideboard); + cards.addAll(sideboard); + } + toRemove = (CardCollection) AbilityUtils.filterListByType(cards, sa.getParam("RemoveType"), sa); + } else { + toRemove = getTargetCards(sa); + } + String logMessage = sa.getParamOrDefault("LogMessage", "Removed from match"); + String remove = toRemove.toString().replace("[","").replace("]",""); + host.getController().getGame().fireEvent(new GameEventRandomLog(logMessage + ": " + remove)); + for (final Card tgtC : toRemove) { + tgtC.getGame().getAction().ceaseToExist(tgtC, true); + PaperCard rem = (PaperCard) tgtC.getPaperCard(); + host.getGame().getMatch().removeCard(rem); + } + } +} \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/b/blacker_lotus.txt b/forge-gui/res/cardsfolder/b/blacker_lotus.txt new file mode 100644 index 00000000000..1dbe01751b9 --- /dev/null +++ b/forge-gui/res/cardsfolder/b/blacker_lotus.txt @@ -0,0 +1,6 @@ +Name:Blacker Lotus +ManaCost:0 +Types:Artifact +A:AB$ Mana | Cost$ T | Produced$ Any | Amount$ 4 | AILogic$ BlackLotus | SubAbility$ DBTearUpAndRemove | SpellDescription$ Tear CARDNAME into pieces. Add four mana of any one color. Remove the pieces from the game. +SVar:DBTearUpAndRemove:DB$ RemoveFromMatch | Defined$ Self | LogMessage$ Torn to pieces and removed +Oracle:{T}: Tear Blacker Lotus into pieces. Add four mana of any one color. Remove the pieces from the game. diff --git a/forge-gui/res/cardsfolder/l/look_at_me_im_the_dci.txt b/forge-gui/res/cardsfolder/l/look_at_me_im_the_dci.txt new file mode 100644 index 00000000000..0f526ba3bc0 --- /dev/null +++ b/forge-gui/res/cardsfolder/l/look_at_me_im_the_dci.txt @@ -0,0 +1,6 @@ +Name:Look at Me, I'm the DCI +ManaCost:5 W W +Types:Sorcery +A:SP$ NameCard | Defined$ You | ValidCards$ Card.nonBasic | SelectPrompt$ Choose a card other than a basic land card to ban | SubAbility$ DBRemove | AILogic$ MostProminentInHumanDeck | StackDescription$ None | SpellDescription$ Ban a card other than a basic land card for the rest of the match. (All cards with that name in any zone or sideboard are removed from the match.) +SVar:DBRemove:DB$ RemoveFromMatch | RemoveType$ Card.NamedCard | IncludeSideboard$ True | StackDescription$ Ban a card other than a basic land card for the rest of the match. (All cards with that name in any zone or sideboard are removed from the match.) +Oracle:Ban a card other than a basic land card for the rest of the match. (All cards with that name in any zone or sideboard are removed from the match.) From f0f6592a383495bb4e0b9f67723dc28790714031 Mon Sep 17 00:00:00 2001 From: TRT <> Date: Mon, 25 Oct 2021 13:56:35 +0200 Subject: [PATCH 10/32] Fix predictToughnessBonusOfAttacker logic --- forge-ai/src/main/java/forge/ai/AiAttackController.java | 2 +- forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 23f5b9e7b31..94e600d655e 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -208,7 +208,7 @@ public class AiAttackController { */ public final boolean isEffectiveAttacker(final Player ai, final Card attacker, final Combat combat, final GameEntity defender) { // if the attacker will die when attacking don't attack - if ((attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) { + if (attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true) <= 0) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java index a841b338cd3..ab6f76c8477 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCombat.java @@ -1478,7 +1478,7 @@ public class ComputerUtilCombat { // DealDamage triggers if (ApiType.DealDamage.equals(sa.getApi())) { - if ("TriggeredAttacker".equals(sa.getParam("Defined"))) { + if (!sa.hasParam("Defined") || !sa.getParam("Defined").startsWith("TriggeredAttacker")) { continue; } int damage = AbilityUtils.calculateAmount(source, sa.getParam("NumDmg"), sa); From bfb5b588cac55a2518d90fc9c8908ba6c91f475e Mon Sep 17 00:00:00 2001 From: Andreas Bendel Date: Mon, 25 Oct 2021 20:52:20 +0000 Subject: [PATCH 11/32] Update de-DE.properties translated #FlipOntoBattlefieldEffect --- forge-gui/res/languages/de-DE.properties | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index b6f80d5ba25..db2f2362579 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1939,12 +1939,12 @@ lblCallCoinFlip=Kopf oder Zahl lblWin=Gewonnen lblLose=Verloren #FlipOntoBattlefieldEffect.java -lblChooseDesiredLocation=Choose a card to represent the center of the desired card landing location. -lblDidNotFlipOver=The card did not flip over. -lblFlippedOver=The card flipped over {0} time(s). -lblDidNotLandOnCards=The card did not land on any cards. -lblLandedOnOneCard=The card landed on {0}. -lblLandedOnTwoCards=The card landed on {0} and {1}. +lblChooseDesiredLocation=Wähle eine Karte als Mittelpunkt der gewünschten Landezone. +lblDidNotFlipOver=Die Karte hat sich NICHT überschlagen. +lblFlippedOver=Die Karte überschlug sich {0} mal. +lblDidNotLandOnCards=Die Karte landete auf keiner Karte. +lblLandedOnOneCard=Die Karte landete auf {0}. +lblLandedOnTwoCards=Die Karte landete auf {0} und {1}. #InvestigateEffect.java lblWouldYouLikeInvestigate=Möchtest du Nachforschungen anstellen? #LifeSetEffect.java From fb88fcfe9c6c1ac5a95c365d7c26a1172125bd44 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Mon, 25 Oct 2021 23:54:08 +0200 Subject: [PATCH 12/32] Fix deck loading not restoring foiled status --- forge-core/src/main/java/forge/deck/CardPool.java | 2 +- forge-core/src/main/java/forge/deck/Deck.java | 2 +- forge-core/src/main/java/forge/item/PaperCard.java | 8 ++++++++ .../screens/deckeditor/controllers/ACEditorBase.java | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/forge-core/src/main/java/forge/deck/CardPool.java b/forge-core/src/main/java/forge/deck/CardPool.java index 92d6da1feab..2ece80834e4 100644 --- a/forge-core/src/main/java/forge/deck/CardPool.java +++ b/forge-core/src/main/java/forge/deck/CardPool.java @@ -53,7 +53,7 @@ public class CardPool extends ItemPool { public void add(final String cardRequest, final int amount) { CardDb.CardRequest request = CardDb.CardRequest.fromString(cardRequest); - this.add(request.cardName, request.edition, request.artIndex, amount); + this.add(CardDb.CardRequest.compose(request.cardName, request.isFoil), request.edition, request.artIndex, amount); } public void add(final String cardName, final String setCode) { diff --git a/forge-core/src/main/java/forge/deck/Deck.java b/forge-core/src/main/java/forge/deck/Deck.java index 02b6ee3bf76..0d684c0898c 100644 --- a/forge-core/src/main/java/forge/deck/Deck.java +++ b/forge-core/src/main/java/forge/deck/Deck.java @@ -334,7 +334,7 @@ public class Deck extends DeckBase implements Iterable entry) { PaperCard card = entry.getKey(); int amount = entry.getValue(); - String cardRequest = CardDb.CardRequest.compose(card.getName(), card.getEdition(), card.getArtIndex()); + String cardRequest = CardDb.CardRequest.compose(card.isFoil() ? CardDb.CardRequest.compose(card.getName(), true) : card.getName(), card.getEdition(), card.getArtIndex()); return String.format("%d %s", amount, cardRequest); } diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index 95d0b8a8f2e..99f6415751c 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -123,6 +123,14 @@ public final class PaperCard implements Comparable, InventoryItemFro } return this.foiledVersion; } + public PaperCard getUnFoiled() { + if (!this.foil) + return this; + + PaperCard unFoiledVersion = new PaperCard(this.rules, this.edition, this.rarity, + this.artIndex, false, String.valueOf(collectorNumber), this.artist); + return unFoiledVersion; + } // @Override // public String getImageKey() { diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java index e84ef4b64a5..551dac31727 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java @@ -538,7 +538,7 @@ public abstract class ACEditorBase Date: Tue, 26 Oct 2021 13:41:31 +0200 Subject: [PATCH 13/32] Clean up --- .../src/main/java/forge/deck/CardPool.java | 27 +++++++++---------- forge-core/src/main/java/forge/deck/Deck.java | 23 +++++++--------- .../src/main/java/forge/item/PaperCard.java | 1 - .../java/forge/deckchooser/FDeckChooser.java | 16 +++++------ .../deckeditor/controllers/ACEditorBase.java | 7 ++--- .../controllers/DeckController.java | 11 +++----- 6 files changed, 34 insertions(+), 51 deletions(-) diff --git a/forge-core/src/main/java/forge/deck/CardPool.java b/forge-core/src/main/java/forge/deck/CardPool.java index 2ece80834e4..cc05b8422db 100644 --- a/forge-core/src/main/java/forge/deck/CardPool.java +++ b/forge-core/src/main/java/forge/deck/CardPool.java @@ -100,7 +100,7 @@ public class CardPool extends ItemPool { paperCard = StaticData.instance().getCommonCards().getCard(cardName); selectedDbName = "Common"; } - if (paperCard == null){ + if (paperCard == null) { // after all still null System.err.println("An unsupported card was requested: \"" + cardName + "\" from \"" + setCode + "\". \n"); paperCard = StaticData.instance().getCommonCards().createUnsupportedCard(cardName); @@ -130,7 +130,6 @@ public class CardPool extends ItemPool { } } - /** * Add all from a List of CardPrinted. * @@ -222,10 +221,10 @@ public class CardPool extends ItemPool { * * @see CardPool#getCardEditionStatistics(boolean) */ - public Map getCardEditionTypeStatistics(boolean includeBasicLands){ + public Map getCardEditionTypeStatistics(boolean includeBasicLands) { Map editionTypeStats = new HashMap<>(); Map editionStatistics = this.getCardEditionStatistics(includeBasicLands); - for(Entry entry : editionStatistics.entrySet()) { + for (Entry entry : editionStatistics.entrySet()) { CardEdition edition = entry.getKey(); int count = entry.getValue(); CardEdition.Type key = edition.getType(); @@ -242,11 +241,11 @@ public class CardPool extends ItemPool { * * @return The most frequent CardEdition.Type in the pool, or null if the Pool is empty */ - public CardEdition.Type getTheMostFrequentEditionType(){ + public CardEdition.Type getTheMostFrequentEditionType() { Map editionTypeStats = this.getCardEditionTypeStatistics(false); Integer mostFrequentType = 0; List mostFrequentEditionTypes = new ArrayList<>(); - for (Map.Entry entry : editionTypeStats.entrySet()){ + for (Map.Entry entry : editionTypeStats.entrySet()) { if (entry.getValue() > mostFrequentType) { mostFrequentType = entry.getValue(); mostFrequentEditionTypes.add(entry.getKey()); @@ -271,11 +270,11 @@ public class CardPool extends ItemPool { * If the count of Modern and PreModern cards is tied, the return value is determined * by the preferred Card Art Preference settings, namely True if Latest Art, False otherwise. */ - public boolean isModern(){ + public boolean isModern() { int modernEditionsCount = 0; int preModernEditionsCount = 0; Map editionStats = this.getCardEditionStatistics(false); - for (Map.Entry entry: editionStats.entrySet()){ + for (Map.Entry entry: editionStats.entrySet()) { CardEdition edition = entry.getKey(); if (edition.isModern()) modernEditionsCount += entry.getValue(); @@ -364,7 +363,7 @@ public class CardPool extends ItemPool { private static int getMedianFrequency(List frequencyValues, float meanFrequency) { int medianFrequency = frequencyValues.get(0); float refDelta = Math.abs(meanFrequency - medianFrequency); - for (int i = 1; i < frequencyValues.size(); i++){ + for (int i = 1; i < frequencyValues.size(); i++) { int currentFrequency = frequencyValues.get(i); float delta = Math.abs(meanFrequency - currentFrequency); if (delta < refDelta) { @@ -411,7 +410,7 @@ public class CardPool extends ItemPool { return pool; } - public static List> processCardList(final Iterable lines){ + public static List> processCardList(final Iterable lines) { List> cardRequests = new ArrayList<>(); if (lines == null) return cardRequests; // empty list @@ -466,7 +465,7 @@ public class CardPool extends ItemPool { public CardPool getFilteredPool(Predicate predicate) { CardPool filteredPool = new CardPool(); Iterator cardsInPool = this.items.keySet().iterator(); - while (cardsInPool.hasNext()){ + while (cardsInPool.hasNext()) { PaperCard c = cardsInPool.next(); if (predicate.apply(c)) filteredPool.add(c, this.items.get(c)); @@ -479,12 +478,12 @@ public class CardPool extends ItemPool { * @param predicate the Predicate to apply to this CardPool * @return a new CardPool made from this CardPool with only the cards that agree with the provided Predicate */ - public CardPool getFilteredPoolWithCardsCount(Predicate predicate){ + public CardPool getFilteredPoolWithCardsCount(Predicate predicate) { CardPool filteredPool = new CardPool(); - for(Entry entry : this.items.entrySet()){ + for (Entry entry : this.items.entrySet()) { PaperCard pc = entry.getKey(); int count = entry.getValue(); - if(predicate.apply(pc)) + if (predicate.apply(pc)) filteredPool.add(pc, count); } return filteredPool; diff --git a/forge-core/src/main/java/forge/deck/Deck.java b/forge-core/src/main/java/forge/deck/Deck.java index 0d684c0898c..45037314276 100644 --- a/forge-core/src/main/java/forge/deck/Deck.java +++ b/forge-core/src/main/java/forge/deck/Deck.java @@ -216,7 +216,7 @@ public class Deck extends DeckBase implements Iterable(this.deferredSections); - } - else + } else referenceDeckLoadingMap = new HashMap<>(loadedSections); loadedSections = new HashMap<>(); @@ -255,7 +254,7 @@ public class Deck extends DeckBase implements Iterable cardsInSection = s.getValue(); ArrayList cardNamesWithNoEdition = getAllCardNamesWithNoSpecifiedEdition(cardsInSection); - if (cardNamesWithNoEdition.size() > 0){ + if (cardNamesWithNoEdition.size() > 0) { includeCardsFromUnspecifiedSet = true; if (smartCardArtSelection) cardsWithNoEdition.put(sec, cardNamesWithNoEdition); @@ -267,10 +266,9 @@ public class Deck extends DeckBase implements Iterable() { @Override public boolean apply(PaperCard input) { @@ -356,9 +353,9 @@ public class Deck extends DeckBase implements Iterable part : parts.entrySet()) { + for (Entry part : parts.entrySet()) { DeckSection deckSection = part.getKey(); - if(deckSection == DeckSection.Planes || deckSection == DeckSection.Schemes || deckSection == DeckSection.Avatar) + if (deckSection == DeckSection.Planes || deckSection == DeckSection.Schemes || deckSection == DeckSection.Avatar) continue; // == 0. First Off, check if there is anything at all to do for the current section @@ -432,7 +429,7 @@ public class Deck extends DeckBase implements Iterable FN_NAME_SELECTOR = new Function() { @Override public String apply(Deck arg1) { diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index 99f6415751c..6685bc7c153 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -268,7 +268,6 @@ public final class PaperCard implements Comparable, InventoryItemFro return sortableCNKey; } - @Override public int compareTo(final IPaperCard o) { final int nameCmp = name.compareToIgnoreCase(o.getName()); diff --git a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java index 67fde1fa346..51b720726d1 100644 --- a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java +++ b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java @@ -371,7 +371,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { @Override public void deckTypeSelected(final DecksComboBoxEvent ev) { if (ev.getDeckType() == DeckType.NET_ARCHIVE_STANDARD_DECK && !refreshingDeckType) { - if(lstDecks.getGameType() != GameType.Constructed) + if (lstDecks.getGameType() != GameType.Constructed) return; FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks @Override @@ -398,7 +398,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { return; } else if (ev.getDeckType() == DeckType.NET_ARCHIVE_PIONEER_DECK && !refreshingDeckType) { - if(lstDecks.getGameType() != GameType.Constructed) + if (lstDecks.getGameType() != GameType.Constructed) return; FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks @Override @@ -424,7 +424,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { return; } else if (ev.getDeckType() == DeckType.NET_ARCHIVE_MODERN_DECK && !refreshingDeckType) { - if(lstDecks.getGameType() != GameType.Constructed) + if (lstDecks.getGameType() != GameType.Constructed) return; FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks @Override @@ -450,7 +450,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { return; } else if (ev.getDeckType() == DeckType.NET_ARCHIVE_PAUPER_DECK && !refreshingDeckType) { - if(lstDecks.getGameType() != GameType.Constructed) + if (lstDecks.getGameType() != GameType.Constructed) return; FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks @Override @@ -476,7 +476,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { return; } else if (ev.getDeckType() == DeckType.NET_ARCHIVE_LEGACY_DECK && !refreshingDeckType) { - if(lstDecks.getGameType() != GameType.Constructed) + if (lstDecks.getGameType() != GameType.Constructed) return; FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks @Override @@ -502,7 +502,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { return; } else if (ev.getDeckType() == DeckType.NET_ARCHIVE_VINTAGE_DECK && !refreshingDeckType) { - if(lstDecks.getGameType() != GameType.Constructed) + if (lstDecks.getGameType() != GameType.Constructed) return; FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks @Override @@ -528,7 +528,7 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { return; } else if (ev.getDeckType() == DeckType.NET_ARCHIVE_BLOCK_DECK && !refreshingDeckType) { - if(lstDecks.getGameType() != GameType.Constructed) + if (lstDecks.getGameType() != GameType.Constructed) return; FThreads.invokeInBackgroundThread(new Runnable() { //needed for loading net decks @Override @@ -712,7 +712,6 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { prefs.save(); } - private String getState() { final StringBuilder state = new StringBuilder(); DeckType selectedDeckType = this.selectedDeckType; // decksComboBox.getDeckType() @@ -827,7 +826,6 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { } } - private List getSelectedDecksFromSavedState(final String savedState) { try { if (StringUtils.isBlank(savedState)) { diff --git a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java index 551dac31727..62ea4e41347 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java +++ b/forge-gui-desktop/src/main/java/forge/screens/deckeditor/controllers/ACEditorBase.java @@ -216,7 +216,6 @@ public abstract class ACEditorBase itemEntry : itemsToAdd) { - final TItem item = itemEntry.getKey(); final PaperCard card = item instanceof PaperCard ? (PaperCard)item : null; int qty = itemEntry.getValue(); @@ -224,8 +223,7 @@ public abstract class ACEditorBase { /** * Load deck from file or clipboard */ - public void loadDeck(Deck deck){ + public void loadDeck(Deck deck) { this.loadDeck(deck, true); } - public void loadDeck(Deck deck, boolean substituteCurrentDeck) { if (view.getCatalogManager().isInfinite()) { Deck currentDeck = view.getHumanDeck(); @@ -155,7 +154,6 @@ public class DeckController { } private void pickFromCatalog(Map countByName, CardPool catalog, CardPool targetSection) { - CardPool catalogClone = new CardPool(catalog); // clone to iterate modified collection for (Map.Entry entry : catalogClone) { PaperCard availableCard = entry.getKey(); @@ -214,7 +212,6 @@ public class DeckController { public void setModel(final T document) { setModel(document, false); } - private void setModel(final T document, final boolean isStored) { model = document; onModelChanged(isStored); @@ -230,8 +227,7 @@ public class DeckController { if (isStored) { if (isModelInSyncWithFolder()) { setSaved(true); - } - else { + } else { notifyModelChanged(); } } else { //TODO: Make this smarter @@ -313,8 +309,7 @@ public class DeckController { final T newModel = currentFolder.get(name); if (newModel != null) { setModel((T) newModel.copyTo(name), true); - } - else { + } else { setSaved(true); } } From 9bced994194dbe5f5c6e1d49364877bd63e7e2ce Mon Sep 17 00:00:00 2001 From: Churrufli Date: Tue, 26 Oct 2021 22:21:26 +0200 Subject: [PATCH 14/32] Net Decks Archive Updates --- forge-gui/res/lists/net-decks-archive-legacy.txt | 8 ++++++++ forge-gui/res/lists/net-decks-archive-modern.txt | 14 ++++++++++++++ forge-gui/res/lists/net-decks-archive-pauper.txt | 5 +++++ forge-gui/res/lists/net-decks-archive-pioneer.txt | 7 +++++++ forge-gui/res/lists/net-decks-archive-standard.txt | 7 +++++++ forge-gui/res/lists/net-decks-archive-vintage.txt | 5 +++++ 6 files changed, 46 insertions(+) diff --git a/forge-gui/res/lists/net-decks-archive-legacy.txt b/forge-gui/res/lists/net-decks-archive-legacy.txt index ad1e107d7c1..bceafd34daa 100644 --- a/forge-gui/res/lists/net-decks-archive-legacy.txt +++ b/forge-gui/res/lists/net-decks-archive-legacy.txt @@ -2130,3 +2130,11 @@ 2021-10-11 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-11-legacy-challenge.zip 2021-10-12 Legacy Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-12-legacy-preliminary.zip 2021-10-16 Legacy League (49 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-16-legacy-league.zip +2021-10-18 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-18-legacy-challenge.zip +2021-10-20 Legacy Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-20-legacy-preliminary.zip +2021-10-22 Legacy Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-22-legacy-preliminary.zip +2021-10-23 Legacy League (49 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-23-legacy-league.zip +2021-10-23 Legacy Preliminary (3 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-23-legacy-preliminary.zip +2021-10-24 Legacy Premier (16 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-24-legacy-premier.zip +2021-10-25 Legacy Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-25-legacy-challenge.zip +2021-10-26 Legacy Preliminary (3 decks) | https://downloads.cardforge.org/decks/archive/legacy/2021-10-26-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 c78ae61a63f..79266b9f498 100644 --- a/forge-gui/res/lists/net-decks-archive-modern.txt +++ b/forge-gui/res/lists/net-decks-archive-modern.txt @@ -2798,3 +2798,17 @@ 2021-10-16 Modern Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-16-modern-preliminary.zip 2021-10-16 Modern Premier (16 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-16-modern-premier.zip 2021-10-17 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-17-modern-challenge.zip +2021-10-18 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-18-modern-challenge.zip +2021-10-18 Modern Premier (16 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-18-modern-premier.zip +2021-10-19 Modern League (56 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-19-modern-league.zip +2021-10-19 Modern Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-19-modern-preliminary.zip +2021-10-20 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-20-modern-preliminary.zip +2021-10-21 Modern Preliminary (10 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-21-modern-preliminary.zip +2021-10-22 Modern League (56 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-22-modern-league.zip +2021-10-22 Modern Preliminary (5 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-22-modern-preliminary.zip +2021-10-23 Modern Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-23-modern-preliminary.zip +2021-10-24 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-24-modern-challenge.zip +2021-10-25 Modern Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-25-modern-challenge.zip +2021-10-25 Modern Premier (16 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-25-modern-premier.zip +2021-10-26 Modern League (65 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-26-modern-league.zip +2021-10-26 Modern Preliminary (7 decks) | https://downloads.cardforge.org/decks/archive/modern/2021-10-26-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 3af79ec6fef..3f55845b47c 100644 --- a/forge-gui/res/lists/net-decks-archive-pauper.txt +++ b/forge-gui/res/lists/net-decks-archive-pauper.txt @@ -1815,3 +1815,8 @@ 2021-10-11 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-11-pauper-challenge.zip 2021-10-13 Pauper League (30 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-13-pauper-league.zip 2021-10-17 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-17-pauper-challenge.zip +2021-10-18 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-18-pauper-challenge.zip +2021-10-20 Pauper League (25 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-20-pauper-league.zip +2021-10-24 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-24-pauper-challenge.zip +2021-10-25 Pauper Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pauper/2021-10-25-pauper-challenge.zip +Pauper League (20 decks) | https://downloads.cardforge.org/decks/archive/pauper/pauper-league.zip diff --git a/forge-gui/res/lists/net-decks-archive-pioneer.txt b/forge-gui/res/lists/net-decks-archive-pioneer.txt index 3660d22784f..915a340af8b 100644 --- a/forge-gui/res/lists/net-decks-archive-pioneer.txt +++ b/forge-gui/res/lists/net-decks-archive-pioneer.txt @@ -755,3 +755,10 @@ 2021-10-11 Pioneer League (18 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-11-pioneer-league.zip 2021-10-14 Pioneer League (15 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-14-pioneer-league.zip 2021-10-17 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-17-pioneer-challenge.zip +2021-10-18 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-18-pioneer-challenge.zip +2021-10-18 Pioneer League (14 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-18-pioneer-league.zip +2021-10-21 Pioneer League (17 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-21-pioneer-league.zip +2021-10-23 Pioneer Preliminary (6 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-23-pioneer-preliminary.zip +2021-10-24 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-24-pioneer-challenge.zip +2021-10-25 Pioneer Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-25-pioneer-challenge.zip +2021-10-25 Pioneer League (19 decks) | https://downloads.cardforge.org/decks/archive/pioneer/2021-10-25-pioneer-league.zip diff --git a/forge-gui/res/lists/net-decks-archive-standard.txt b/forge-gui/res/lists/net-decks-archive-standard.txt index b0275a8de88..02745e35a0c 100644 --- a/forge-gui/res/lists/net-decks-archive-standard.txt +++ b/forge-gui/res/lists/net-decks-archive-standard.txt @@ -2515,3 +2515,10 @@ 2021-10-14 Standard League (12 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-14-standard-league.zip 2021-10-15 Standard Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-15-standard-preliminary.zip 2021-10-17 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-17-standard-challenge.zip +2021-10-18 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-18-standard-challenge.zip +2021-10-18 Standard League (15 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-18-standard-league.zip +2021-10-20 Standard Preliminary (3 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-20-standard-preliminary.zip +2021-10-21 Standard League (5 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-21-standard-league.zip +2021-10-24 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-24-standard-challenge.zip +2021-10-25 Standard Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-25-standard-challenge.zip +2021-10-25 Standard League (8 decks) | https://downloads.cardforge.org/decks/archive/standard/2021-10-25-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 95a314c2d0c..70032f261ca 100644 --- a/forge-gui/res/lists/net-decks-archive-vintage.txt +++ b/forge-gui/res/lists/net-decks-archive-vintage.txt @@ -1535,3 +1535,8 @@ 2021-10-11 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-11-vintage-challenge.zip 2021-10-17 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-17-vintage-challenge.zip 2021-10-17 Vintage League (12 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-17-vintage-league.zip +2021-10-18 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-18-vintage-challenge.zip +2021-10-23 Vintage Preliminary (4 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-23-vintage-preliminary.zip +2021-10-24 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-24-vintage-challenge.zip +2021-10-24 Vintage League (9 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-24-vintage-league.zip +2021-10-25 Vintage Challenge (32 decks) | https://downloads.cardforge.org/decks/archive/vintage/2021-10-25-vintage-challenge.zip From cbbdd8584c537e18a8dd1f51a5b39ce96787bebf Mon Sep 17 00:00:00 2001 From: TRT <> Date: Wed, 27 Oct 2021 01:12:17 +0200 Subject: [PATCH 15/32] Fix NPE when trying to remove card --- .../src/forge/deck/FDeckEditor.java | 30 +++++++------------ .../src/forge/itemmanager/ItemManager.java | 3 ++ .../forge/itemmanager/ItemManagerModel.java | 3 +- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/forge-gui-mobile/src/forge/deck/FDeckEditor.java b/forge-gui-mobile/src/forge/deck/FDeckEditor.java index 1fbb5bb5a59..80fda456699 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckEditor.java +++ b/forge-gui-mobile/src/forge/deck/FDeckEditor.java @@ -339,17 +339,14 @@ public class FDeckEditor extends TabPageScreen { //hide deck header on while drafting setDeck(new Deck()); deckHeader.setVisible(false); - } - else { + } else { if (newDeck == null) { editorType.getController().newModel(); - } - else { + } else { editorType.getController().setDeck(newDeck); } } - } - else { + } else { if (editorType == EditorType.Draft || editorType == EditorType.QuestDraft) { tabPages[0].hideTab(); //hide Draft Pack page if editing existing draft deck } @@ -897,8 +894,7 @@ public class FDeckEditor extends TabPageScreen { public void handleEvent(FEvent e) { if (max == 1) { callback.run(max); - } - else { + } else { final Localizer localizer = Localizer.getInstance(); GuiChoose.getInteger(cardManager.getSelectedItem() + " - " + verb + " " + localizer.getMessage("lblHowMany"), 1, max, 20, callback); } @@ -1150,8 +1146,7 @@ public class FDeckEditor extends TabPageScreen { default: // Do nothing } - } - else { + } else { //if a commander has been set, only show cards that match its color identity switch (editorType) { case Commander: @@ -1176,8 +1171,7 @@ public class FDeckEditor extends TabPageScreen { default: if (cardManager.getWantUnique()) { cardManager.setPool(editorType.applyCardFilter(FModel.getUniqueCardsNoAlt(), additionalFilter), true); - } - else { + } else { cardManager.setPool(editorType.applyCardFilter(FModel.getAllCardsNoAlt(), additionalFilter), true); } break; @@ -1250,8 +1244,7 @@ public class FDeckEditor extends TabPageScreen { CardPreferences.save(); } })); - } - else { + } else { menu.addItem(new FMenuItem(localizer.getMessage("lblRemoveFavorites"), Forge.hdbuttons ? FSkinImage.HDSTAR_OUTLINE : FSkinImage.STAR_OUTLINE, new FEventHandler() { @Override public void handleEvent(FEvent e) { @@ -1376,8 +1369,7 @@ public class FDeckEditor extends TabPageScreen { protected void updateCaption() { if (deckSection == DeckSection.Commander) { caption = captionPrefix; //don't display count for commander section since it won't be more than 1 - } - else { + } else { caption = captionPrefix + " (" + parentScreen.getDeck().get(deckSection).countAll() + ")"; } } @@ -1767,8 +1759,7 @@ public class FDeckEditor extends TabPageScreen { if (isStored) { if (isModelInSyncWithFolder()) { setSaved(true); - } - else { + } else { notifyModelChanged(); } } @@ -1779,8 +1770,7 @@ public class FDeckEditor extends TabPageScreen { } if (model != null) { editor.setDeck(model.getHumanDeck()); - } - else { + } else { editor.setDeck(null); } diff --git a/forge-gui-mobile/src/forge/itemmanager/ItemManager.java b/forge-gui-mobile/src/forge/itemmanager/ItemManager.java index 0885dd95fd9..6a059d9581d 100644 --- a/forge-gui-mobile/src/forge/itemmanager/ItemManager.java +++ b/forge-gui-mobile/src/forge/itemmanager/ItemManager.java @@ -525,6 +525,9 @@ public abstract class ItemManager extends FContainer im } public void addItem(final T item, int qty) { + if (pool == null) { + return; + } pool.add(item, qty); if (isUnfiltered()) { model.addItem(item, qty); diff --git a/forge-gui/src/main/java/forge/itemmanager/ItemManagerModel.java b/forge-gui/src/main/java/forge/itemmanager/ItemManagerModel.java index 03180147d4d..5e026e3021a 100644 --- a/forge-gui/src/main/java/forge/itemmanager/ItemManagerModel.java +++ b/forge-gui/src/main/java/forge/itemmanager/ItemManagerModel.java @@ -80,8 +80,7 @@ public final class ItemManagerModel { if (data.count(item0) > 0) { if (isInfinite()) { data.removeAll(item0); - } - else { + } else { data.remove(item0, qty); } isListInSync = false; From 17f3b24fa44d8d66f600aecd8d2f4f8828a6dfa8 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Tue, 26 Oct 2021 20:33:39 -0400 Subject: [PATCH 16/32] sorin_lord_of_innistrad.txt typo --- forge-gui/res/cardsfolder/s/sorin_lord_of_innistrad.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui/res/cardsfolder/s/sorin_lord_of_innistrad.txt b/forge-gui/res/cardsfolder/s/sorin_lord_of_innistrad.txt index a225cfdd26c..f8658c62e44 100644 --- a/forge-gui/res/cardsfolder/s/sorin_lord_of_innistrad.txt +++ b/forge-gui/res/cardsfolder/s/sorin_lord_of_innistrad.txt @@ -2,10 +2,10 @@ Name:Sorin, Lord of Innistrad ManaCost:2 W B Types:Legendary Planeswalker Sorin Loyalty:3 -A:AB$ Token | Cost$ AddCounter<1/LOYALTY> | TokenAmount$ 1 | TokenScript$ b_1_1_vampire_lifelink | TokenOwner$ You | LegacyImage$ b 1 1 vampire lifelink dka | Planeswalker$ True | SpellDescription$ Create a 1/1 black Vampire creature token with lifelink. +A:AB$ Token | Cost$ AddCounter<1/LOYALTY> | TokenAmount$ 1 | TokenScript$ b_1_1_vampire_lifelink | Planeswalker$ True | SpellDescription$ Create a 1/1 black Vampire creature token with lifelink. A:AB$ Effect | Cost$ SubCounter<2/LOYALTY> | Name$ Emblem - Sorin, Lord of Innistrad | Image$ emblem_sorin_lord_of_innistrad | StaticAbilities$ STPump | Planeswalker$ True | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "Creatures you control get +1/+0." SVar:STPump:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.YouCtrl | AddPower$ 1 -A:AB$ Destroy | Cost$ SubCounter<6/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidTgts$ Creature,Planeswalker.Other | TargetMin$ 0 | TargetMax$ 3 | TgtPrompt$ Select target creatures or other planeswalkers | RememberTargets$ True | ForgetOtherTargets$ True | SubAbility$ DBChangeZoneAll | SpellDescription$ Destroy up to 3 target creatures and/or other planeswalkers. Return each card put into a graveyard this way to the battlefield under your control. +A:AB$ Destroy | Cost$ SubCounter<6/LOYALTY> | Planeswalker$ True | Ultimate$ True | ValidTgts$ Creature,Planeswalker.Other | TargetMin$ 0 | TargetMax$ 3 | TgtPrompt$ Select target creatures or other planeswalkers | RememberTargets$ True | ForgetOtherTargets$ True | SubAbility$ DBChangeZoneAll | SpellDescription$ Destroy up to three target creatures and/or other planeswalkers. Return each card put into a graveyard this way to the battlefield under your control. SVar:DBChangeZoneAll:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:Picture:http://www.wizards.com/global/images/magic/general/sorin_lord_of_innistrad.jpg From 63c7852dedd87efab2afb11211ac00efd181d532 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Thu, 29 Jul 2021 21:42:33 -0400 Subject: [PATCH 17/32] urza_academy_headmaster.txt (need to finish ultimates and change ChangeTo param) --- .../cardsfolder/u/urza_academy_headmaster.txt | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt diff --git a/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt b/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt new file mode 100644 index 00000000000..baf1bd0a6b1 --- /dev/null +++ b/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt @@ -0,0 +1,80 @@ +Name:Urza, Academy Headmaster +ManaCost:W U B R G +Types:Legendary Planeswalker Urza +Loyalty:4 +A:AB$ GenericChoice | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ChangeToAtRandom$ Urza | Choices$ Pump1,PutCounter2,Destroy3,Token4,Token5,Token6,Dig7,MustAttack8,PutCounter9,Effect10,Exile11,Dig12,Animate13,Draw14,Animate15,PumpAll16,Dig17,DealDamage18,Animate19,Mana20 | ShowChoice$ Description | StackDescription$ SpellDescription | SpellDescription$ Head to AskUrza.com and click +1. +SVar:Pump1:DB$ Pump | TargetMin$ 0 | TargetMax$ 1 | KW$ First Strike & Vigilance & Lifelink | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +1 | NumDef$ +1 | KW$ First Strike & Vigilance & Lifelink | SpellDescription$ Until end of turn, up to one target creature gets +1/+1 and gains first strike, vigilance, and lifelink. +SVar:PutCounter2:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control to distribute counters to | CounterType$ P1P1 | CounterNum$ 3 | TargetMin$ 1 | TargetMax$ 3 | DividedAsYouChoose$ 3 | SpellDescription$ Distribute three +1/+1 counters among one, two, or three target creatures you control. +SVar:Destroy3:DB$ Destroy | ValidTgts$ Permanent.nonCreature | TgtPrompt$ Select a noncreature permanent | SpellDescription$ Destroy target noncreature permanent. +SVar:Token4:DB$ Token | TokenAmount$ 2 | TokenScript$ r_3_1_elemental_haste | AtEOT$ Exile | SpellDescription$ Create two 3/1 red Elemental creature tokens with haste. Exile them at the beginning of the next end step. +SVar:Token5:DB$ Token | TokenAmount$ 3 | TokenScript$ w_1_1_soldier | SpellDescription$ Create three 1/1 white Soldier creature tokens. +SVar:Token6:DB$ Token | TokenScript$ b_3_3_beast_deathtouch | SpellDescription$ Create a 3/3 black Beast creature token with deathtouch. +SVar:Dig7:DB$ Dig | DigNum$ 5 | Reveal$ True | ChangeNum$ All | ChangeValid$ Creature | SpellDescription$ Reveal the top five cards of your library. Put all creature cards revealed this way into your hand and the rest on the bottom of your library in any order. +SVar:MustAttack8:DB$ MustAttack | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | Defender$ Self | SpellDescription$ During target opponent's next turn, creatures that player controls attack NICKNAME if able. +SVar:PutCounter9:DB$ PutCounter | Defined$ Self | CounterType$ LOYALTY | CounterNum$ X9 | ValidTgts$ Opponent | TgtPrompt$ Select target opponent | StackDescription$ SpellDescription | SpellDescription$ Put a loyalty counter on NICKNAME for each creature target opponent controls. +SVar:X9:TargetedPlayer$CreaturesInPlay +SVar:Effect10:DB$ Effect | Triggers$ TrigAttack10 | Duration$ UntilYourNextTurn | SpellDescription$ Until your next turn, whenever a creature an opponent controls attacks, it gets -1/-0 until end of turn. +SVar:TrigAttack10:Mode$ Attacks | ValidCard$ Creature.OppCtrl | TriggerZones$ Command | Execute$ DebuffAttacker10 | TriggerDescription$ Whenever a creature an opponent controls attacks, it gets -1/-0 until end of turn. +SVar:DebuffAttacker10:DB$ Pump | Defined$ TriggeredAttacker | NumAtt$ -1 +SVar:Exile11:DB$ ChangeZone | ValidTgts$ Player | TgtPrompt$ Select target player | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ChangeNum$ 1 | Chooser$ Targeted | Hidden$ True | IsCurse$ True | Mandatory$ True | SpellDescription$ Target player exiles a card from their hand. +SVar:Dig12:DB$ Dig | DigNum$ 1 | Reveal$ True | ChangeNum$ All | ChangeValid$ Land | DestinationZone$ Battlefield | DestinationZone2$ Hand | SpellDescription$ Reveal the top card of your library. If it's a land card, put it onto the battlefield. Otherwise, put it into your hand. +SVar:Animate13:DB$ Animate | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select target land you control | Power$ 4 | Toughness$ 4 | Types$ Creature,Elemental | Duration$ Permanent | Keywords$ Trample | SpellDescription$ Target land you control becomes a 4/4 Elemental creature with trample. It's still a land. +SVar:Draw14:DB$ Draw | Defined$ You | SubAbility$ DBMana14 | SpellDescription$ Draw a card, then add one mana of any color. +SVar:DBMana14:DB$ Mana | Produced$ Any | AILogic$ MostProminentInComputerHand +SVar:Animate15:DB$ Animate | Power$ 4 | Toughness$ 4 | Types$ Creature,Legendary,Dragon | Colors$ Red | OverwriteColors$ True | RemoveCardTypes$ True | Keywords$ Flying & Indestructible & Haste | SpellDescription$ Until end of turn, NICKNAME becomes a legendary 4/4 red Dragon creature with flying, indestructible, and haste. (He doesn't lose loyalty while he's not a planeswalker.) +SVar:PumpAll16:DB$ PumpAll | ValidCards$ Creature.YouCtrl | NumAtt$ +1 | KW$ Lifelink | Duration$ UntilYourNextTurn | SpellDescription$ Until your next turn, creatures you control get +1/+0 and gain lifelink. +SVar:Dig17:DB$ Dig | DigNum$ 5 | ChangeNum$ 1 | Optional$ True | ChangeValid$ Artifact | SpellDescription$ Look at the top five cards of your library. You may reveal an artifact card from among them and put it into your hand. Put the rest on the bottom of your library in any order. +SVar:DealDamage18:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 3 | SpellDescription$ NICKNAME deals 3 damage to any target. +SVar:Animate19:DB$ Animate | Defined$ Self | Triggers$ DamageTrig19 | Duration$ UntilYourNextTurn | SpellDescription$ Until your next turn, whenever a creature deals combat damage to NICKNAME, destroy that creature. | StackDescription$ SpellDescription +SVar:DamageTrig19:Mode$ DamageDone | ValidSource$ Creature | ValidTarget$ Card.Self | TriggerZones$ Battlefield | CombatDamage$ True | Execute$ TrigDestroy19 | TriggerDescription$ Whenever a creature deals combat damage to NICKNAME, destroy that creature. +SVar:TrigDestroy19:DB$ Destroy | Defined$ TriggeredSourceLKICopy +SVar:Mana20:DB$ Mana | Produced$ Combo Any | Amount$ X20 | SpellDescription$ Add X mana in any combination of colors, where X is the number of creatures you control. +SVar:X20:Count$Valid Creature.YouCtrl +A:AB$ GenericChoice | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | ChangeToAtRandom$ Urza | Choices$ DamageAll1M,GainControl2M,DealDamage3M,Destroy4M,Effect5M,ChangeZone6M,Draw7M,Mill8M,Dig9M,Exile10M,Reveal11M,Tutor12M,Sacrifice13M,Token14M,Token15M,SetLife16M,Destroy17M,Return18M,Token19M,Draw20M | ShowChoice$ Description | StackDescription$ SpellDescription | SpellDescription$ Head to AskUrza.com and click -1. +SVar:DamageAll1M:DB$ DamageAll | NumDmg$ 3 | ValidCards$ Creature | SpellDescription$ NICKNAME deals 3 damage to each creature. +SVar:GainControl2M:DB$ GainControl | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Gain control of target creature. +SVar:DealDamage3M:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 4 | SubAbility$ DBGainLife3M | SpellDescription$ NICKNAME deals 4 damage to any target and you gain 4 life. +SVar:DBGainLife3M:DB$ GainLife | Defined$ You | LifeAmount$ 4 +SVar:Destroy4M:DB$ Destroy | ValidTgts$ Creature | TgtPrompt$ Select target creature | SubAbility$ DBGainLife4M | SpellDescription$ Destroy target creature. You gain life equal to its toughness. +SVar:DBGainLife4M:DB$ GainLife | Defined$ You | LifeAmount$ X4M +SVar:X4M:TargetedLKI$CardToughness +SVar:Effect5M:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (5M) | StaticAbilities$ STPump5M | Duration$ Permanent | SpellDescription$ You get an emblem with "Creatures you control get +1/+1." +SVar:STPump5M:Mode$ Continuous | EffectZone$ Command | AffectedZone$ Battlefield | Affected$ Creature.YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Creatures you control get +1/+1. +SVar:ChangeZone6M:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Creature | ChangeNum$ 1 | SpellDescription$ You may put a creature card from your hand onto the battlefield. +SVar:Draw7M:DB$ Draw | NumCards$ 3 | SubAbility$ DBChangeZone7M | SpellDescription$ Draw three cards, then put a card from your hand on top of your library. +SVar:DBChangeZone7M:DB$ ChangeZone | Origin$ Hand | Destination$ Library | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | SelectPrompt$ Select a card from your hand to put on top of your library +SVar:Mill8M:DB$ Mill | ValidTgts$ Player | TgtPrompt$ Select target player | NumCards$ 10 | SpellDescription$ Target player puts the top ten cards of their library into their graveyard. +SVar:Dig9M:DB$ Dig | DigNum$ 5 | Reveal$ True | RememberRevealed$ True | NoMove$ True | SubAbility$ DBTwoPiles9M | SpellDescription$ Reveal the top five cards of your library. An opponent separates them into two piles. Put one pile into your hand and the other on the bottom of your library in any order. +SVar:DBTwoPiles9M:DB$ TwoPiles | Defined$ You | DefinedCards$ Remembered | Separator$ Opponent | ChosenPile$ DBHand9M | UnchosenPile$ DBLibraryBottom9M +SVar:DBHand9M:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Hand +SVar:DBLibraryBottom9M:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | SubAbility$ DBCleanup +SVar:Exile10M:DB$ ChangeZone | ValidTgts$ Permanent | TgtPrompt$ Select target permanent | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target permanent. +SVar:Reveal11M:DB$ Dig | DigNum$ 5 | Reveal$ True | RememberRevealed$ True | NoMove$ True | SubAbility$ DBChangeCreatures11M | SpellDescription$ Reveal the top five cards of your library. You may put all creature cards and/or land cards from among them into your hand. Put the rest into your graveyard. +SVar:DBChangeCreatures11M:DB$ ChangeZoneAll | ChangeType$ Card.Creature+IsRemembered | Origin$ Library | Destination$ Hand | Optional$ True | OptionQuestion$ Put all creature cards into your hand? | ForgetChanged$ True | SubAbility$ DBChangeLands11M +SVar:DBChangeLands11M:DB$ ChangeZoneAll | ChangeType$ Card.Land+IsRemembered | Origin$ Library | Destination$ Hand | Optional$ True | OptionQuestion$ Put all land cards into your hand? | ForgetChanged$ True | SubAbility$ DBChangeRest11M +SVar:DBChangeRest11M:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Library | Destination$ Graveyard | ForgetChanged$ True +SVar:Tutor12M:DB$ ChangeZone | Origin$ Library | Destination$ Hand | ChangeType$ Card | ChangeNum$ 1 | Mandatory$ True | SpellDescription$ Search your library for a card and put that card into your hand. Then shuffle your library. +SVar:Sacrifice13M:DB$ Sacrifice | ValidTgts$ Player | Amount$ 2 | SacValid$ Creature | SpellDescription$ Target player sacrifices two creatures. +SVar:Token14M:DB$ Token | TokenScript$ b_5_5_demon_flying | SubAbility$ DBLoselife14M | SpellDescription$ Create a 5/5 black Demon creature token with flying. You lose 2 life. +SVar:DBLoselife14M:DB$ LoseLife | LifeAmount$ 2 +SVar:Token15M:DB$ Token | TokenScript$ c_4_4_dragon_flying | SpellDescription$ Create a 4/4 gold Dragon creature token with flying. +SVar:SetLife16M:DB$ SetLife | ValidTgts$ Player | TgtPrompt$ Select target player | LifeAmount$ 10 | SpellDescription$ Target player's life total becomes 10. +SVar:Destroy17M:DB$ Destroy | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select a nonland permanent | SpellDescription$ Destroy target nonland permanent. +SVar:Return18M:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | TgtPrompt$ Select target permanent in a graveyard | ValidTgts$ Permanent | SpellDescription$ Return target permanent from a graveyard to the battlefield under your control. +SVar:Token19M:DB$ Token | TokenAmount$ 2 | TokenScript$ g_3_3_beast | SpellDescription$ Create two 3/3 green Beast creature tokens. +SVar:Draw20M:DB$ Draw | NumCards$ 4 | SubAbility$ DBDiscard20M | SpellDescription$ Draw four cards, then discard two cards. +SVar:DBDiscard20M:DB$ Discard | Defined$ You | NumCards$ 2 | Mode$ TgtChoose +A:AB$ GenericChoice | Cost$ SubCounter<6/LOYALTY> | ChangeToAtRandom$ Urza | Choices$ | Planeswalker$ True | Ultimate$ True | ShowChoice$ Description | StackDescription$ SpellDescription | SpellDescription$ Head to AskUrza.com and click -6. +SVar:DealDamage1U:DB$ DealDamage | ValidTgts$ Player,Planeswalker | SubAbility$ DBDiscard1U | SpellDescription$ NICKNAME deals 7 damage to target player or planeswalker. That player or that planeswalker's controller discards seven cards, then sacrifices seven permanents. +SVar:DBDiscard1U:DB$ Discard | Defined$ TargetedOrController | NumCards$ 7 | Mode$ TgtChoose | SubAbility$ DBSac1U +SVar:DBSac1U:DB$Sacrifice | Defined$ TargetedOrController | SacValid$ Permanent | Amount$ 7 +SVar:Effect2U:DB$ Effect | Name$ Emblem - Ajani Steadfast | ReplacementEffects$ RPreventDamage | Stackable$ False | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "If a source would deal damage to you or a planeswalker you control, prevent all but 1 of that damage." +SVar:RPreventDamage:Event$ DamageDone | ValidTarget$ You,Planeswalker.YouCtrl | ReplaceWith$ PreventDmg | PreventionEffect$ True | Description$ If a source would deal damage to you or a planeswalker you control, prevent all but 1 of that damage. +SVar:PreventDmg:DB$ ReplaceDamage | Amount$ ShieldAmount +SVar:ShieldAmount:ReplaceCount$DamageAmount/Minus.1 + + + +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True +DeckHas:Ability$Counters & Ability$Token & Ability$LifeGain & Ability$Discard +Oracle:[+1]: Head to AskUrza.com and click +1.\n[-1]: Head to AskUrza.com and click -1.\n[-6]: Head to AskUrza.com and click -6. From f3a2f7210642ab0dd6e767b4c49dec5e01bae3ef Mon Sep 17 00:00:00 2001 From: Northmoc Date: Thu, 29 Jul 2021 21:42:50 -0400 Subject: [PATCH 18/32] add Urza planeswalker type --- forge-gui/res/lists/TypeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/lists/TypeLists.txt b/forge-gui/res/lists/TypeLists.txt index 7c21a195edb..0f0c14cfd53 100644 --- a/forge-gui/res/lists/TypeLists.txt +++ b/forge-gui/res/lists/TypeLists.txt @@ -374,6 +374,7 @@ Tezzeret Tibalt Tyvar Ugin +Urza Venser Vivien Vraska From 89a91c6df88ce85a01ab643da88057f45222d64e Mon Sep 17 00:00:00 2001 From: Northmoc Date: Thu, 29 Jul 2021 21:46:02 -0400 Subject: [PATCH 19/32] placeholder for special param that picks new ability if no legal targets --- .../game/ability/effects/ChooseGenericEffect.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 b249d308327..3204f34fa40 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 @@ -8,6 +8,7 @@ import com.google.common.collect.Lists; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; +import forge.game.card.CardUtil; import forge.game.event.GameEventCardModeChosen; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -81,6 +82,20 @@ public class ChooseGenericEffect extends SpellAbilityEffect { chosenSAs = p.getController().chooseSpellAbilitiesForEffect(abilities, sa, prompt, amount, ImmutableMap.of()); } + // TODO: Move this up into the AtRandom area + for (SpellAbility chosenSA : chosenSAs) { + if (sa.getParam("ChangeToAtRandom").equals("Urza") && chosenSA.usesTargeting()) { + List validTargets = CardUtil.getValidCardsToTarget(chosenSA.getTargetRestrictions(), sa); + if (validTargets.isEmpty()) { + List newChosenSAs = Lists.newArrayList(); + Aggregates.random(abilities, amount, newChosenSAs); + chosenSAs = newChosenSAs; + } else { + p.getController().chooseTargetsFor(chosenSA); + } + } + } + if (!chosenSAs.isEmpty()) { for (SpellAbility chosenSA : chosenSAs) { String chosenValue = chosenSA.getDescription(); From cd6cf320deec36c2287401e7ff06dbc674cc89db Mon Sep 17 00:00:00 2001 From: Northmoc Date: Tue, 28 Sep 2021 19:52:58 -0400 Subject: [PATCH 20/32] urza tweak --- forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt b/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt index baf1bd0a6b1..d8f9fda6b89 100644 --- a/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt +++ b/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt @@ -68,7 +68,7 @@ A:AB$ GenericChoice | Cost$ SubCounter<6/LOYALTY> | ChangeToAtRandom$ Urza | Cho SVar:DealDamage1U:DB$ DealDamage | ValidTgts$ Player,Planeswalker | SubAbility$ DBDiscard1U | SpellDescription$ NICKNAME deals 7 damage to target player or planeswalker. That player or that planeswalker's controller discards seven cards, then sacrifices seven permanents. SVar:DBDiscard1U:DB$ Discard | Defined$ TargetedOrController | NumCards$ 7 | Mode$ TgtChoose | SubAbility$ DBSac1U SVar:DBSac1U:DB$Sacrifice | Defined$ TargetedOrController | SacValid$ Permanent | Amount$ 7 -SVar:Effect2U:DB$ Effect | Name$ Emblem - Ajani Steadfast | ReplacementEffects$ RPreventDamage | Stackable$ False | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "If a source would deal damage to you or a planeswalker you control, prevent all but 1 of that damage." +SVar:Effect2U:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (2U) | ReplacementEffects$ RPreventDamage | Stackable$ False | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "If a source would deal damage to you or a planeswalker you control, prevent all but 1 of that damage." SVar:RPreventDamage:Event$ DamageDone | ValidTarget$ You,Planeswalker.YouCtrl | ReplaceWith$ PreventDmg | PreventionEffect$ True | Description$ If a source would deal damage to you or a planeswalker you control, prevent all but 1 of that damage. SVar:PreventDmg:DB$ ReplaceDamage | Amount$ ShieldAmount SVar:ShieldAmount:ReplaceCount$DamageAmount/Minus.1 From 9d02c0c39f9286c025b230ecfb9c09501f86a973 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Tue, 26 Oct 2021 20:29:47 -0400 Subject: [PATCH 21/32] Match.java final Urza work --- .../ability/effects/ChooseGenericEffect.java | 3 +- .../cardsfolder/u/urza_academy_headmaster.txt | 45 ++++++++++++++++--- 2 files changed, 40 insertions(+), 8 deletions(-) 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 3204f34fa40..6853b901275 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 @@ -82,9 +82,8 @@ public class ChooseGenericEffect extends SpellAbilityEffect { chosenSAs = p.getController().chooseSpellAbilitiesForEffect(abilities, sa, prompt, amount, ImmutableMap.of()); } - // TODO: Move this up into the AtRandom area for (SpellAbility chosenSA : chosenSAs) { - if (sa.getParam("ChangeToAtRandom").equals("Urza") && chosenSA.usesTargeting()) { + if (sa.getParam("AtRandom").equals("Urza") && chosenSA.usesTargeting()) { List validTargets = CardUtil.getValidCardsToTarget(chosenSA.getTargetRestrictions(), sa); if (validTargets.isEmpty()) { List newChosenSAs = Lists.newArrayList(); diff --git a/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt b/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt index d8f9fda6b89..717b3f178f8 100644 --- a/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt +++ b/forge-gui/res/cardsfolder/u/urza_academy_headmaster.txt @@ -2,7 +2,7 @@ Name:Urza, Academy Headmaster ManaCost:W U B R G Types:Legendary Planeswalker Urza Loyalty:4 -A:AB$ GenericChoice | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ChangeToAtRandom$ Urza | Choices$ Pump1,PutCounter2,Destroy3,Token4,Token5,Token6,Dig7,MustAttack8,PutCounter9,Effect10,Exile11,Dig12,Animate13,Draw14,Animate15,PumpAll16,Dig17,DealDamage18,Animate19,Mana20 | ShowChoice$ Description | StackDescription$ SpellDescription | SpellDescription$ Head to AskUrza.com and click +1. +A:AB$ GenericChoice | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | AtRandom$ Urza | ShowChoice$ Description | Choices$ Pump1,PutCounter2,Destroy3,Token4,Token5,Token6,Dig7,MustAttack8,PutCounter9,Effect10,Exile11,Dig12,Animate13,Draw14,Animate15,PumpAll16,Dig17,DealDamage18,Animate19,Mana20 | ShowChoice$ Description | StackDescription$ SpellDescription | SpellDescription$ Head to AskUrza.com and click +1. SVar:Pump1:DB$ Pump | TargetMin$ 0 | TargetMax$ 1 | KW$ First Strike & Vigilance & Lifelink | ValidTgts$ Creature | TgtPrompt$ Select target creature | NumAtt$ +1 | NumDef$ +1 | KW$ First Strike & Vigilance & Lifelink | SpellDescription$ Until end of turn, up to one target creature gets +1/+1 and gains first strike, vigilance, and lifelink. SVar:PutCounter2:DB$ PutCounter | ValidTgts$ Creature.YouCtrl | TgtPrompt$ Select target creature you control to distribute counters to | CounterType$ P1P1 | CounterNum$ 3 | TargetMin$ 1 | TargetMax$ 3 | DividedAsYouChoose$ 3 | SpellDescription$ Distribute three +1/+1 counters among one, two, or three target creatures you control. SVar:Destroy3:DB$ Destroy | ValidTgts$ Permanent.nonCreature | TgtPrompt$ Select a noncreature permanent | SpellDescription$ Destroy target noncreature permanent. @@ -30,7 +30,7 @@ SVar:DamageTrig19:Mode$ DamageDone | ValidSource$ Creature | ValidTarget$ Card.S SVar:TrigDestroy19:DB$ Destroy | Defined$ TriggeredSourceLKICopy SVar:Mana20:DB$ Mana | Produced$ Combo Any | Amount$ X20 | SpellDescription$ Add X mana in any combination of colors, where X is the number of creatures you control. SVar:X20:Count$Valid Creature.YouCtrl -A:AB$ GenericChoice | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | ChangeToAtRandom$ Urza | Choices$ DamageAll1M,GainControl2M,DealDamage3M,Destroy4M,Effect5M,ChangeZone6M,Draw7M,Mill8M,Dig9M,Exile10M,Reveal11M,Tutor12M,Sacrifice13M,Token14M,Token15M,SetLife16M,Destroy17M,Return18M,Token19M,Draw20M | ShowChoice$ Description | StackDescription$ SpellDescription | SpellDescription$ Head to AskUrza.com and click -1. +A:AB$ GenericChoice | Cost$ SubCounter<1/LOYALTY> | Planeswalker$ True | AtRandom$ Urza | ShowChoice$ Description | Choices$ DamageAll1M,GainControl2M,DealDamage3M,Destroy4M,Effect5M,ChangeZone6M,Draw7M,Mill8M,Dig9M,Exile10M,Reveal11M,Tutor12M,Sacrifice13M,Token14M,Token15M,SetLife16M,Destroy17M,Return18M,Token19M,Draw20M | ShowChoice$ Description | StackDescription$ SpellDescription | SpellDescription$ Head to AskUrza.com and click -1. SVar:DamageAll1M:DB$ DamageAll | NumDmg$ 3 | ValidCards$ Creature | SpellDescription$ NICKNAME deals 3 damage to each creature. SVar:GainControl2M:DB$ GainControl | ValidTgts$ Creature | TgtPrompt$ Select target creature | SpellDescription$ Gain control of target creature. SVar:DealDamage3M:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 4 | SubAbility$ DBGainLife3M | SpellDescription$ NICKNAME deals 4 damage to any target and you gain 4 life. @@ -64,7 +64,7 @@ SVar:Return18M:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | G SVar:Token19M:DB$ Token | TokenAmount$ 2 | TokenScript$ g_3_3_beast | SpellDescription$ Create two 3/3 green Beast creature tokens. SVar:Draw20M:DB$ Draw | NumCards$ 4 | SubAbility$ DBDiscard20M | SpellDescription$ Draw four cards, then discard two cards. SVar:DBDiscard20M:DB$ Discard | Defined$ You | NumCards$ 2 | Mode$ TgtChoose -A:AB$ GenericChoice | Cost$ SubCounter<6/LOYALTY> | ChangeToAtRandom$ Urza | Choices$ | Planeswalker$ True | Ultimate$ True | ShowChoice$ Description | StackDescription$ SpellDescription | SpellDescription$ Head to AskUrza.com and click -6. +A:AB$ GenericChoice | Cost$ SubCounter<6/LOYALTY> | AtRandom$ Urza | ShowChoice$ Description | Choices$ DealDamage1U,Effect2U,DestroyAll3U,Token4U,LifeGain5U,Damage6U,Effect7U,Effect8U,Token9U,Shuffle10U,Destroy11U,Effect12U,Effect13U,ControlPlayer14U,ExileLibrary15U,Token16U,Raise17U,LifeDraw18U,FlipTurns19U | Planeswalker$ True | Ultimate$ True | ShowChoice$ Description | StackDescription$ SpellDescription | SpellDescription$ Head to AskUrza.com and click -6. SVar:DealDamage1U:DB$ DealDamage | ValidTgts$ Player,Planeswalker | SubAbility$ DBDiscard1U | SpellDescription$ NICKNAME deals 7 damage to target player or planeswalker. That player or that planeswalker's controller discards seven cards, then sacrifices seven permanents. SVar:DBDiscard1U:DB$ Discard | Defined$ TargetedOrController | NumCards$ 7 | Mode$ TgtChoose | SubAbility$ DBSac1U SVar:DBSac1U:DB$Sacrifice | Defined$ TargetedOrController | SacValid$ Permanent | Amount$ 7 @@ -72,9 +72,42 @@ SVar:Effect2U:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (2U) | Replac SVar:RPreventDamage:Event$ DamageDone | ValidTarget$ You,Planeswalker.YouCtrl | ReplaceWith$ PreventDmg | PreventionEffect$ True | Description$ If a source would deal damage to you or a planeswalker you control, prevent all but 1 of that damage. SVar:PreventDmg:DB$ ReplaceDamage | Amount$ ShieldAmount SVar:ShieldAmount:ReplaceCount$DamageAmount/Minus.1 - - - +SVar:DestroyAll3U:DB$ DestroyAll | ValidTgts$ Player | TgtPrompt$ Select target player | ValidCards$ Land | SpellDescription$ Destroy all lands target player controls. +SVar:Token4U:DB$ Token | TokenAmount$ X4U | TokenScript$ w_2_2_cat | SpellDescription$ Create X 2/2 white Cat creature tokens, where X is your life total. +SVar:X4U:Count$YourLifeTotal +SVar:LifeGain5U:DB$ GainLife | LifeAmount$ 100 | SpellDescription$ You gain 100 life. +SVar:Damage6U:DB$ DealDamage | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select a player or planeswalker | NumDmg$ 10 | SubAbility$ DmgAll6U | DamageMap$ True | SpellDescription$ NICKNAME deals 10 damage to target player or planeswalker and each creature that player or that planeswalker's controller controls. +SVar:DmgAll6U:DB$ DamageAll | NumDmg$ 10 | ValidCards$ Creature.ControlledBy TargetedOrController | SubAbility$ DBDamageResolve6U +SVar:DBDamageResolve6U:DB$ DamageResolve +SVar:Effect7U:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (7U) | StaticAbilities$ ST7U | Stackable$ False | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "Creatures you control have double strike, trample, hexproof and haste." +SVar:ST7U:Mode$ Continuous | EffectZone$ Command | Affected$ Creature.YouCtrl | AffectedZone$ Battlefield | AddKeyword$ Double Strike & Trample & Hexproof & Haste | Description$ Creatures you control have double strike, trample, hexproof and haste. +SVar:Effect8U:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (8U) | StaticAbilities$ STIndestructible8U | Stackable$ False | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "Artifacts, creatures, enchantments, and lands you control have indestructible." +SVar:STIndestructible8U:Mode$ Continuous | EffectZone$ Command | Affected$ Artifact.YouCtrl,Creature.YouCtrl,Enchantment.YouCtrl,Land.YouCtrl | AffectedZone$ Battlefield | AddKeyword$ Indestructible | Description$ Artifacts, creatures, enchantments, and lands you control are indestructible. +SVar:Token9U:DB$ Token | TokenAmount$ Lands | TokenScript$ g_6_6_wurm | SpellDescription$ Create a 6/6 green Wurm creature token for each land you control. +SVar:Lands:Count$Valid Land.YouCtrl +SVar:Shuffle10U:DB$ ChangeZoneAll | ChangeType$ Card | Origin$ Hand,Graveyard | Destination$ Library | Shuffle$ True | SubAbility$ DBDraw10U | UseAllOriginZones$ True | SpellDescription$ Each player shuffles their hand and graveyard into their library. You draw seven cards. +SVar:DBDraw10U:DB$ Draw | Defined$ You | NumCards$ 7 +SVar:Destroy11U:DB$ Destroy | ValidTgts$ Creature,Planeswalker.Other | TargetMin$ 0 | TargetMax$ 3 | TgtPrompt$ Select up to three target creatures and/or other planeswalkers | RememberTargets$ True | SubAbility$ DBChangeZoneAll11U | SpellDescription$ Destroy up to three target creatures and/or other planeswalkers. Return each card put into a graveyard this way to the battlefield under your control. +SVar:DBChangeZoneAll11U:DB$ ChangeZoneAll | ChangeType$ Card.IsRemembered | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | SubAbility$ DBCleanup +SVar:Effect12U:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (12U) | Triggers$ TrigSpellCast12U | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "Whenever you cast a spell, exile target permanent." +SVar:TrigSpellCast12U:Mode$ SpellCast | ValidActivatingPlayer$ You | Execute$ EffSpellCast12U | TriggerDescription$ Whenever you cast a spell, exile target permanent. +SVar:EffSpellCast12U:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Permanent +SVar:Effect13U:DB$ Effect | Name$ Emblem - Urza, Academy Headmaster (13U) | Triggers$ TrigFight13U | Duration$ Permanent | AILogic$ Always | SubAbility$ DBToken13U | SpellDescription$ You get an emblem with "Whenever a creature enters the battlefield under your control, you may have it fight target creature." Then create three 8/8 blue Octopus creature tokens. +SVar:TrigFight13U:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.YouCtrl | Execute$ DBFight13U | OptionalDecider$ You | TriggerZones$ Command | TriggerDescription$ Whenever a creature enters the battlefield under your control, you may have it fight target creature. +SVar:DBFight13U:DB$ Fight | Defined$ TriggeredCardLKICopy | ValidTgts$ Creature | TgtPrompt$ Choose target creature +SVar:DBToken13U:DB$ Token | TokenAmount$ 3 | TokenScript$ u_8_8_octopus +SVar:ControlPlayer14U:DB$ ControlPlayer | ValidTgts$ Player | TgtPrompt$ Select target player | SpellDescription$ You control target player during that player's next turn. +SVar:ExileLibrary15U:DB$ ChangeZoneAll | Origin$ Library | Destination$ Exile | ValidTgts$ Player | TgtPrompt$ Select target player | SubAbility$ DBChangeZone15U | Planeswalker$ True | Ultimate$ True | SpellDescription$ Exile all cards from target player's library, then that player shuffles their hand into their library. +SVar:DBChangeZone15U:DB$ ChangeZoneAll | Origin$ Hand | Destination$ Library | Defined$ Targeted | ChangeType$ Card | Shuffle$ True +SVar:Token16U:DB$ Token | TokenAmount$ 3 | TokenScript$ b_1_1_assassin_lose_con | SpellDescription$ Create three 1/1 black Assassin creature tokens with "Whenever this creature deals combat damage to a player, that player loses the game." +SVar:Raise17U:DB$ ChangeZoneAll | ChangeType$ Creature | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | SpellDescription$ Put all creature cards in all graveyards onto the battlefield under your control. +SVar:LifeDraw18U:DB$ GainLife | LifeAmount$ Lands | SubAbility$ DBDraw18U | SpellDescription$ You gain X life and draw X cards, where X is the number of lands you control. +SVar:DBDraw18U:DB$ Draw | NumCards$ Lands +SVar:FlipTurns19U:DB$ FlipACoin | Amount$ 5 | NoCall$ True | HeadsSubAbility$ DBAddTurn19U | StackDescription$ SpellDescription | SpellDescription$ Flip 5 coins. Take an extra turn after this one for each coin that comes up heads. +SVar:DBAddTurn19U:DB$ AddTurn | Defined$ You | NumTurns$ 1 +SVar:LifeDrawPut20U:DB$ GainLife | LifeAmount$ 7 | SubAbility$ DBDraw20U | SpellDescription$ You gain 7 life, draw seven cards, then put up to seven permanent cards from your hand onto the battlefield. +SVar:DBDraw20U:DB$ Draw | NumCards$ 7 | SubAbility$ DBChangeZone20U +SVar:DBChangeZone20U:DB$ ChangeZone | Origin$ Hand | Destination$ Battlefield | ChangeType$ Permanent | ChangeNum$ 7 SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True DeckHas:Ability$Counters & Ability$Token & Ability$LifeGain & Ability$Discard Oracle:[+1]: Head to AskUrza.com and click +1.\n[-1]: Head to AskUrza.com and click -1.\n[-6]: Head to AskUrza.com and click -6. From d921677e60016414ad15862caaa99bf6be45ba7f Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Wed, 27 Oct 2021 13:33:16 +0800 Subject: [PATCH 22/32] prevent NPE --- .../java/forge/game/ability/effects/ChooseGenericEffect.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6853b901275..990139f880d 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 @@ -83,7 +83,7 @@ public class ChooseGenericEffect extends SpellAbilityEffect { } for (SpellAbility chosenSA : chosenSAs) { - if (sa.getParam("AtRandom").equals("Urza") && chosenSA.usesTargeting()) { + if (sa.hasParam("AtRandom") && sa.getParam("AtRandom").equals("Urza") && chosenSA.usesTargeting()) { List validTargets = CardUtil.getValidCardsToTarget(chosenSA.getTargetRestrictions(), sa); if (validTargets.isEmpty()) { List newChosenSAs = Lists.newArrayList(); From 9353587b89f8664eb8a4e2dfd613e4714baea77d Mon Sep 17 00:00:00 2001 From: Northmoc Date: Wed, 27 Oct 2021 10:47:37 -0400 Subject: [PATCH 23/32] TwoPilesEffect fix --- .../main/java/forge/game/ability/effects/TwoPilesEffect.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java b/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java index 746d6190d3e..869198b340b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/TwoPilesEffect.java @@ -51,7 +51,7 @@ public class TwoPilesEffect extends SpellAbilityEffect { zone = ZoneType.smartValueOf(sa.getParam("Zone")); } - final String valid = sa.getParamOrDefault("ValidCards", ""); + final String valid = sa.getParamOrDefault("ValidCards", "Card"); final TargetRestrictions tgt = sa.getTargetRestrictions(); final List tgtPlayers = getTargetPlayers(sa); From 24be7eb80226f7a4a365caa34713cb3e295630f7 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Wed, 27 Oct 2021 10:50:41 -0400 Subject: [PATCH 24/32] fact_or_fiction.txt improve StackDesc --- forge-gui/res/cardsfolder/f/fact_or_fiction.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/cardsfolder/f/fact_or_fiction.txt b/forge-gui/res/cardsfolder/f/fact_or_fiction.txt index e0112217fb5..fbf2cd9d7a7 100644 --- a/forge-gui/res/cardsfolder/f/fact_or_fiction.txt +++ b/forge-gui/res/cardsfolder/f/fact_or_fiction.txt @@ -1,10 +1,9 @@ Name:Fact or Fiction ManaCost:3 U Types:Instant -A:SP$ Dig | Cost$ 3 U | DigNum$ 5 | Reveal$ True | RememberRevealed$ True | NoMove$ True | SubAbility$ DBTwoPiles | SpellDescription$ Reveal the top five cards of your library. An opponent separates those cards into two piles. Put one pile into your hand and the other into your graveyard. -SVar:DBTwoPiles:DB$ TwoPiles | Defined$ You | DefinedCards$ Remembered | Separator$ Opponent | ChosenPile$ DBHand | UnchosenPile$ DBGrave +A:SP$ Dig | DigNum$ 5 | Reveal$ True | RememberRevealed$ True | NoMove$ True | SubAbility$ DBTwoPiles | SpellDescription$ Reveal the top five cards of your library. An opponent separates those cards into two piles. Put one pile into your hand and the other into your graveyard. +SVar:DBTwoPiles:DB$ TwoPiles | Defined$ You | DefinedCards$ Remembered | Separator$ Opponent | ChosenPile$ DBHand | UnchosenPile$ DBGrave | StackDescription$ An opponent separates those cards into two piles. {p:You} puts one pile into their hand and the other into their graveyard. SVar:DBHand:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Hand | SubAbility$ DBCleanup SVar:DBGrave:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Graveyard | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:Picture:http://www.wizards.com/global/images/magic/general/fact_or_fiction.jpg Oracle:Reveal the top five cards of your library. An opponent separates those cards into two piles. Put one pile into your hand and the other into your graveyard. From 49b54e3d30b9d7fb1c5666e0c669eab71f33cc38 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Wed, 27 Oct 2021 10:51:02 -0400 Subject: [PATCH 25/32] DigEffect improve StackDesc --- .../src/main/java/forge/game/ability/effects/DigEffect.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index 4b1d514d412..9e4d5a07cba 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -42,8 +42,9 @@ public class DigEffect extends SpellAbilityEffect { final int numToDig = AbilityUtils.calculateAmount(host, sa.getParam("DigNum"), sa); final List tgtPlayers = getTargetPlayers(sa); - sb.append(host.getController()).append(" looks at the top "); - sb.append(Lang.nounWithAmount(numToDig, "card")).append(" of "); + sb.append(host.getController()).append(sa.hasParam("Reveal") && sa.getParam("Reveal").equals("True") + ? " reveals " : " looks at ").append("the top "); + sb.append(numToDig == 1 ? "card" : (Lang.getNumeral(numToDig) + " cards")).append(" of "); if (tgtPlayers.contains(host.getController())) { sb.append("their "); From 04c52e95e69bab64f0189c73d3e9254060bc9f47 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Wed, 27 Oct 2021 10:51:24 -0400 Subject: [PATCH 26/32] MillEffect improve StackDesc --- .../main/java/forge/game/ability/effects/MillEffect.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java index 3b0e4e33212..d56de13f3fb 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java @@ -99,12 +99,8 @@ public class MillEffect extends SpellAbilityEffect { } else if (dest.equals(ZoneType.Ante)) { sb.append("antes "); } - sb.append(numCards); - sb.append(" card"); - if (numCards != 1) { - sb.append("s"); - } - sb.append("."); + + sb.append(numCards == 1 ? "a card" : (Lang.getNumeral(numCards) + " cards")).append("."); return sb.toString(); } From 1d0739d7284c9335ade8a35e443fab66c38a01aa Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Wed, 27 Oct 2021 20:38:15 +0200 Subject: [PATCH 27/32] Fix Fury --- .../java/forge/ai/ability/DigUntilAi.java | 6 +- .../src/main/java/forge/StaticData.java | 67 +++++++++---------- forge-gui/res/cardsfolder/f/fury.txt | 2 +- 3 files changed, 35 insertions(+), 40 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java index 8faeefc8e7b..331acd2a61a 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DigUntilAi.java @@ -51,8 +51,8 @@ public class DigUntilAi extends SpellAbilityAi { // until opponent's end of turn phase! // But we still want more (and want to fill grave) if nothing better to do then // This is important for Replenish/Living Death type decks - if (!((ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN)) - && (!ai.getGame().getPhaseHandler().isPlayerTurn(ai)))) { + if (!ai.getGame().getPhaseHandler().is(PhaseType.END_OF_TURN) + && !ai.getGame().getPhaseHandler().isPlayerTurn(ai)) { return false; } } @@ -75,7 +75,7 @@ public class DigUntilAi extends SpellAbilityAi { } final String num = sa.getParam("Amount"); - if ((num != null) && num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) { + if (num != null && num.equals("X") && sa.getSVar(num).equals("Count$xPaid")) { // Set PayX here to maximum value. SpellAbility root = sa.getRootAbility(); if (root.getXManaCostPaid() == null) { diff --git a/forge-core/src/main/java/forge/StaticData.java b/forge-core/src/main/java/forge/StaticData.java index 35c38076fcc..1bd2a565e0d 100644 --- a/forge-core/src/main/java/forge/StaticData.java +++ b/forge-core/src/main/java/forge/StaticData.java @@ -165,11 +165,10 @@ public class StaticData { return this.editions; } - public final CardEdition.Collection getCustomEditions(){ + public final CardEdition.Collection getCustomEditions() { return this.customEditions; } - private List sortedEditions; public final List getSortedEditions() { if (sortedEditions == null) { @@ -189,13 +188,13 @@ public class StaticData { } private TreeMap> editionsTypeMap; - public final Map> getEditionsTypeMap(){ - if (editionsTypeMap == null){ + public final Map> getEditionsTypeMap() { + if (editionsTypeMap == null) { editionsTypeMap = new TreeMap<>(); - for (CardEdition.Type editionType : CardEdition.Type.values()){ + for (CardEdition.Type editionType : CardEdition.Type.values()) { editionsTypeMap.put(editionType, new ArrayList<>()); } - for (CardEdition edition : this.getSortedEditions()){ + for (CardEdition edition : this.getSortedEditions()) { CardEdition.Type key = edition.getType(); List editionsOfType = editionsTypeMap.get(key); editionsOfType.add(edition); @@ -204,9 +203,9 @@ public class StaticData { return editionsTypeMap; } - public CardEdition getCardEdition(String setCode){ + public CardEdition getCardEdition(String setCode) { CardEdition edition = this.editions.get(setCode); - if (edition == null) // try custom editions + if (edition == null) // try custom editions edition = this.customEditions.get(setCode); return edition; } @@ -231,8 +230,7 @@ public class StaticData { public void attemptToLoadCard(String cardName){ this.attemptToLoadCard(cardName, null); } - - public void attemptToLoadCard(String cardName, String setCode){ + public void attemptToLoadCard(String cardName, String setCode) { CardRules rules = cardReader.attemptToLoadCard(cardName); CardRules customRules = null; if (customCardReader != null) { @@ -257,7 +255,7 @@ public class StaticData { * @param collectorNumber Card's collector Number * @return PaperCard instance found in one of the available CardDb databases, or null if not found. */ - public PaperCard fetchCard(final String cardName, final String setCode, final String collectorNumber){ + public PaperCard fetchCard(final String cardName, final String setCode, final String collectorNumber) { PaperCard card = null; for (CardDb db : this.getAvailableDatabases().values()) { card = db.getCard(cardName, setCode, collectorNumber); @@ -345,7 +343,7 @@ public class StaticData { return result; } - private CardDb matchTargetCardDb(final String cardName){ + private CardDb matchTargetCardDb(final String cardName) { // NOTE: any foil request in cardName is NOT taken into account here. // It's a private method, so it's a fair assumption. for (CardDb targetDb : this.getAvailableDatabases().values()){ @@ -360,7 +358,7 @@ public class StaticData { * @param cardName Name of the Card to verify (CASE SENSITIVE) * @return True if a card with the given input string can be found. False otherwise. */ - public boolean isMTGCard(final String cardName){ + public boolean isMTGCard(final String cardName) { if (cardName == null || cardName.trim().length() == 0) return false; CardDb.CardRequest cr = CardDb.CardRequest.fromString(cardName); // accounts for any foil request ending with + @@ -450,10 +448,6 @@ public class StaticData { public Predicate getBrawlPredicate() { return brawlPredicate; } - public void setFilteredHandsEnabled(boolean filteredHandsEnabled){ - this.filteredHandsEnabled = filteredHandsEnabled; - } - /** * Get an alternative card print for the given card wrt. the input setReleaseDate. * The reference release date will be used to retrieve the alternative art, according @@ -501,7 +495,7 @@ public class StaticData { */ public PaperCard getAlternativeCardPrint(PaperCard card, Date setReleaseDate, boolean isCardArtPreferenceLatestArt, - boolean cardArtPreferenceHasFilter){ + boolean cardArtPreferenceHasFilter) { Date searchReferenceDate = getReferenceDate(setReleaseDate, isCardArtPreferenceLatestArt); CardDb.CardArtPreference searchCardArtStrategy = getSearchStrategyForAlternativeCardArt(isCardArtPreferenceLatestArt, cardArtPreferenceHasFilter); @@ -537,7 +531,6 @@ public class StaticData { public PaperCard getAlternativeCardPrint(PaperCard card, Date setReleaseDate, boolean isCardArtPreferenceLatestArt, boolean cardArtPreferenceHasFilter, boolean preferCandidatesFromExpansionSets, boolean preferModernFrame) { - PaperCard altCard = this.getAlternativeCardPrint(card, setReleaseDate, isCardArtPreferenceLatestArt, cardArtPreferenceHasFilter); if (altCard == null) @@ -618,7 +611,7 @@ public class StaticData { private PaperCard tryToGetCardPrintFromExpansionSet(PaperCard altCard, boolean isCardArtPreferenceLatestArt, - boolean preferModernFrame){ + boolean preferModernFrame) { CardEdition altCardEdition = editions.get(altCard.getEdition()); if (altCardEdition.getType() == CardEdition.Type.EXPANSION) return null; // Nothing to do here! @@ -628,7 +621,7 @@ public class StaticData { CardDb.CardArtPreference searchStrategy = getSearchStrategyForAlternativeCardArt(searchStrategyFlag, true); PaperCard altCandidate = altCard; - while (altCandidate != null){ + while (altCandidate != null) { Date referenceDate = editions.get(altCandidate.getEdition()).getDate(); altCandidate = this.searchAlternativeCardCandidate(altCandidate, preferModernFrame, referenceDate, searchStrategy); @@ -645,7 +638,7 @@ public class StaticData { private PaperCard tryToGetCardPrintWithMatchingFrame(PaperCard altCard, boolean isCardArtPreferenceLatestArt, boolean cardArtHasFilter, - boolean preferModernFrame){ + boolean preferModernFrame) { CardEdition altCardEdition = editions.get(altCard.getEdition()); boolean frameIsCompliantAlready = (altCardEdition.isModern() == preferModernFrame); if (frameIsCompliantAlready) @@ -654,7 +647,7 @@ public class StaticData { CardDb.CardArtPreference searchStrategy = getSearchStrategyForAlternativeCardArt(searchStrategyFlag, cardArtHasFilter); PaperCard altCandidate = altCard; - while (altCandidate != null){ + while (altCandidate != null) { Date referenceDate = editions.get(altCandidate.getEdition()).getDate(); altCandidate = this.searchAlternativeCardCandidate(altCandidate, preferModernFrame, referenceDate, searchStrategy); @@ -677,7 +670,7 @@ public class StaticData { * @param card Instance of target PaperCard * @return The number of available arts for the given card in the corresponding set, or 0 if not found. */ - public int getCardArtCount(PaperCard card){ + public int getCardArtCount(PaperCard card) { Collection databases = this.getAvailableDatabases().values(); for (CardDb db: databases){ int artCount = db.getArtCount(card.getName(), card.getEdition()); @@ -687,9 +680,12 @@ public class StaticData { return 0; } - public boolean getFilteredHandsEnabled(){ + public boolean getFilteredHandsEnabled() { return filteredHandsEnabled; } + public void setFilteredHandsEnabled(boolean filteredHandsEnabled) { + this.filteredHandsEnabled = filteredHandsEnabled; + } public void setMulliganRule(MulliganDefs.MulliganRule rule) { mulliganRule = rule; @@ -699,21 +695,21 @@ public class StaticData { return mulliganRule; } - public void setCardArtPreference(boolean latestArt, boolean coreExpansionOnly){ + public void setCardArtPreference(boolean latestArt, boolean coreExpansionOnly) { this.commonCards.setCardArtPreference(latestArt, coreExpansionOnly); this.variantCards.setCardArtPreference(latestArt, coreExpansionOnly); this.customCards.setCardArtPreference(latestArt, coreExpansionOnly); } - public String getCardArtPreferenceName(){ + public String getCardArtPreferenceName() { return this.commonCards.getCardArtPreference().toString(); } - public CardDb.CardArtPreference getCardArtPreference(){ + public CardDb.CardArtPreference getCardArtPreference() { return this.commonCards.getCardArtPreference(); } - public CardDb.CardArtPreference getCardArtPreference(boolean latestArt, boolean coreExpansionOnly){ + public CardDb.CardArtPreference getCardArtPreference(boolean latestArt, boolean coreExpansionOnly) { if (latestArt){ return coreExpansionOnly ? CardDb.CardArtPreference.LATEST_ART_CORE_EXPANSIONS_REPRINT_ONLY : CardDb.CardArtPreference.LATEST_ART_ALL_EDITIONS; } @@ -721,15 +717,15 @@ public class StaticData { } - public boolean isCoreExpansionOnlyFilterSet(){ return this.commonCards.getCardArtPreference().filterSets; } + public boolean isCoreExpansionOnlyFilterSet() { return this.commonCards.getCardArtPreference().filterSets; } - public boolean cardArtPreferenceIsLatest(){ + public boolean cardArtPreferenceIsLatest() { return this.commonCards.getCardArtPreference().latestFirst; } // === MOBILE APP Alternative Methods (using String Labels, not yet localised!!) === // Note: only used in mobile - public String[] getCardArtAvailablePreferences(){ + public String[] getCardArtAvailablePreferences() { CardDb.CardArtPreference[] preferences = CardDb.CardArtPreference.values(); String[] preferences_avails = new String[preferences.length]; for (int i = 0; i < preferences.length; i++) @@ -745,17 +741,16 @@ public class StaticData { return label.toString().trim(); } - public void setCardArtPreference(String artPreference){ + public void setCardArtPreference(String artPreference) { this.commonCards.setCardArtPreference(artPreference); this.variantCards.setCardArtPreference(artPreference); this.customCards.setCardArtPreference(artPreference); } - // - public boolean isEnabledCardArtSmartSelection(){ + public boolean isEnabledCardArtSmartSelection() { return this.enableSmartCardArtSelection; } - public void setEnableSmartCardArtSelection(boolean isEnabled){ + public void setEnableSmartCardArtSelection(boolean isEnabled) { this.enableSmartCardArtSelection = isEnabled; } diff --git a/forge-gui/res/cardsfolder/f/fury.txt b/forge-gui/res/cardsfolder/f/fury.txt index db186bdd38c..fa813ffe401 100644 --- a/forge-gui/res/cardsfolder/f/fury.txt +++ b/forge-gui/res/cardsfolder/f/fury.txt @@ -5,5 +5,5 @@ PT:3/3 K:Double Strike K:Evoke:ExileFromHand<1/Card.Red+Other/red card> T:Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDamage | TriggerDescription$ When CARDNAME enters the battlefield, it deals 4 damage divided as you choose among any number of target creatures and/or planeswalkers. -SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker to distribute damage to | NumDmg$ 4 | TargetMin$ 1 | TargetMax$ 4 | DividedAsYouChoose$ 4 +SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature,Planeswalker | TgtPrompt$ Select target creature or planeswalker to distribute damage to | NumDmg$ 4 | TargetMin$ 0 | TargetMax$ 4 | DividedAsYouChoose$ 4 Oracle:Double strike\nWhen Fury enters the battlefield, it deals 4 damage divided as you choose among any number of target creatures and/or planeswalkers.\nEvoke—Exile a red card from your hand. From 0892d6de4a2fc20a24b1c7525cd71661aa1c8ca7 Mon Sep 17 00:00:00 2001 From: Sol Date: Thu, 28 Oct 2021 01:24:11 +0000 Subject: [PATCH 28/32] Update kolvori_god_of_kinship_the_ringhart_crest.txt --- .../cardsfolder/k/kolvori_god_of_kinship_the_ringhart_crest.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/k/kolvori_god_of_kinship_the_ringhart_crest.txt b/forge-gui/res/cardsfolder/k/kolvori_god_of_kinship_the_ringhart_crest.txt index 9cb1efb0157..87ad40e4b44 100644 --- a/forge-gui/res/cardsfolder/k/kolvori_god_of_kinship_the_ringhart_crest.txt +++ b/forge-gui/res/cardsfolder/k/kolvori_god_of_kinship_the_ringhart_crest.txt @@ -14,5 +14,5 @@ ManaCost:1 G Types:Legendary Artifact K:ETBReplacement:Other:ChooseCT SVar:ChooseCT:DB$ ChooseType | Defined$ You | Type$ Creature | AILogic$ MostProminentInComputerDeck | SpellDescription$ As CARDNAME enters the battlefield, choose a creature type. -A:AB$ Mana | Cost$ T | Produced$ G | RestrictValid$ Creature.ChosenType,Creature.Legendary | SpellDescription$ {T}: Add {G}. Spend this mana only to cast a creature spell of the chosen type or a legendary creature spell. +A:AB$ Mana | Cost$ T | Produced$ G | RestrictValid$ Creature.ChosenType,Creature.Legendary | SpellDescription$ Add {G}. Spend this mana only to cast a creature spell of the chosen type or a legendary creature spell. Oracle:As The Ringhart Crest enters the battlefield, choose a creature type.\n{T}: Add {G}. Spend this mana only to cast a creature spell of the chosen type or a legendary creature spell. From 65fd0974ca0dd1ee1cceb698ea0ebd2adc473886 Mon Sep 17 00:00:00 2001 From: Bug Hunter Date: Thu, 28 Oct 2021 13:25:47 +0000 Subject: [PATCH 29/32] Update forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt --- forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt b/forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt index 2da49610f6d..420a6924e29 100644 --- a/forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt +++ b/forge-gui/res/cardsfolder/l/ludevic_necro_alchemist.txt @@ -3,6 +3,6 @@ ManaCost:1 U R Types:Legendary Creature Human Wizard PT:1/4 T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ Player | Execute$ DrawDamageOther | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of each player's end step, that player may draw a card if a player other than you lost life this turn. -SVar:DrawDamageOther:DB$ Draw | Defined$ TriggeredPlayer | NumCards$ 1 | ConditionPlayerDefined$ Player.Other | ConditionPlayerContains$ Player.wasDealtDamageThisTurn | OptionalDecider$ True +SVar:DrawDamageOther:DB$ Draw | Defined$ TriggeredPlayer | NumCards$ 1 | ConditionPlayerDefined$ Player.Other | ConditionPlayerContains$ Player.LostLifeThisTurn | OptionalDecider$ True K:Partner Oracle:At the beginning of each player's end step, that player may draw a card if a player other than you lost life this turn.\nPartner (You can have two commanders if both have partner.) From d1769c12d4d7670e492d39ad2fa3d371ee47cd86 Mon Sep 17 00:00:00 2001 From: paul_snoops Date: Thu, 28 Oct 2021 19:19:17 +0100 Subject: [PATCH 30/32] VOW and VOC initial edition files --- .../Innistrad Crimson Vow Commander.txt | 14 +++ .../res/editions/Innistrad Crimson Vow.txt | 90 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 forge-gui/res/editions/Innistrad Crimson Vow Commander.txt create mode 100644 forge-gui/res/editions/Innistrad Crimson Vow.txt diff --git a/forge-gui/res/editions/Innistrad Crimson Vow Commander.txt b/forge-gui/res/editions/Innistrad Crimson Vow Commander.txt new file mode 100644 index 00000000000..d4b8bb45002 --- /dev/null +++ b/forge-gui/res/editions/Innistrad Crimson Vow Commander.txt @@ -0,0 +1,14 @@ +[metadata] +Code=VOC +Date=2021-11-19 +Name=Innistrad: Crimson Vow Commander +Type=Commander +ScryfallCode=VOC + +[cards] +1 M Millicent, Restless Revenant @ +2 M Strefan, Maurer Progenitor @ +32 M Wedding Ring @ +39 M Millicent, Restless Revenant @ +40 M Strefan, Maurer Progenitor @ +70 M Wedding Ring @ diff --git a/forge-gui/res/editions/Innistrad Crimson Vow.txt b/forge-gui/res/editions/Innistrad Crimson Vow.txt new file mode 100644 index 00000000000..a46d8a6a4cc --- /dev/null +++ b/forge-gui/res/editions/Innistrad Crimson Vow.txt @@ -0,0 +1,90 @@ +[metadata] +Code=VOW +Date=2021-11-19 +Name=Innistrad: Crimson Vow +Code2=VOW +MciCode=vow +Type=Expansion +ScryfallCode=VOW + +[cards] +5 R By Invitation Only @ +10 C Drogskol Infantry @ +15 C Gryff Rider @ +34 M Savior of Ollenbock @ +36 R Sigarda's Summons @ +38 R Thalia, Guardian of Thraben @ +45 R Wedding Announcement @ +60 U Geistlight Snare @ +71 R Overcharged Amalgam @ +103 R Demonic Bargain @ +112 U Fell Stinger @ +114 C Gluttonous Guest @ +129 C Rot-Tide Gargantua @ +131 M Sorin the Mirthless @ +137 R Voldaren Bloodcaster @ +154 R Dominating Vampire @ +184 C Weary Prisoner @ +185 C Apprentice Sharpshooter @ +197 R Dig Up @ +208 C Massive Might @ +235 R Dorothea, Vengeful Victim @ +238 R Grolnok, the Omnivore @ +245 M Olivia, Crimson Bride @ +260 C Wedding Invitation @ +261 R Deathcap Glade @ +262 R Dreamroot Cascade @ +264 R Shattered Sanctum @ +265 R Stormcarved Coast @ +266 R Sundown Pass @ +267 R Voldaren Estate @ +268 L Plains @ +269 L Plains @ +270 L Island @ +271 L Island @ +272 L Swamp @ +273 L Swamp @ +274 L Mountain @ +275 L Mountain @ +276 L Forest @ +277 L Forest @ +278 M Sorin the Mirthless @ +281 R Deathcap Glade @ +282 R Dreamroot Cascade @ +283 R Shattered Sanctum @ +284 R Stormcarved Coast @ +285 R Sundown Pass @ +292 C Gluttonous Guest @ +297 M Sorin the Mirthless @ +298 R Voldaren Bloodcaster @ +305 R Dominating Vampire @ +315 M Olivia, Crimson Bride @ +318 R Thalia, Guardian of Thraben @ +322 R Dorothea, Vengeful Victim @ +324 R Grolnok, the Omnivore @ +330 M Savior of Ollenbock @ +331 R Thalia, Guardian of Thraben @ +337 M Sorin the Mirthless @ +338 R Voldaren Bloodcaster @ +343 M Olivia, Crimson Bride @ +346 R By Invitation Only @ +352 M Savior of Ollenbock @ +353 R Sigarda's Summons @ +355 R Wedding Announcement @ +363 R Overcharged Amalgam @ +368 R Demonic Bargain @ +387 R Dig Up @ +397 R Voldaren Estate @ +398 L Plains @ +399 L Island @ +400 L Swamp @ +401 L Mountain @ +402 L Forest @ +403 R Voldaren Estate @ +404 R Sigarda's Summons @ +405 U Geistlight Snare @ +406 U Fell Stinger @ +407 R Dominating Vampire @ + +[tokens] +c_a_blood_draw From 2de6fc476c2a4a0ed09786ae721c99b07bc5c11a Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Thu, 28 Oct 2021 21:20:45 +0200 Subject: [PATCH 31/32] Fix dropping foil from card when copying --- .../main/java/forge/ai/ComputerUtilCard.java | 3 +-- .../ai/ability/ChooseGenericEffectAi.java | 2 +- .../java/forge/game/ability/AbilityUtils.java | 10 +++++----- .../ability/effects/PlayLandVariantEffect.java | 3 +-- .../game/ability/effects/ProtectAllEffect.java | 3 +-- .../src/main/java/forge/game/card/Card.java | 18 +++++++----------- .../main/java/forge/game/card/CardFactory.java | 5 +++++ .../java/forge/game/card/CardFactoryUtil.java | 6 +++--- .../java/forge/game/card/CardPredicates.java | 6 +++--- .../java/forge/game/card/CardProperty.java | 4 ++-- .../main/java/forge/game/card/CardUtil.java | 7 +------ .../java/forge/game/card/token/TokenInfo.java | 2 +- .../spellability/SpellAbilityCondition.java | 5 ++--- .../util/IntegerConstraint.java | 2 +- .../InputSelectCardsForConvokeOrImprovise.java | 3 +-- .../src/main/java/forge/gui/FThreads.java | 3 +-- 16 files changed, 36 insertions(+), 46 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index af792a1b66b..538064d763f 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -40,7 +40,6 @@ import forge.game.card.CardFactory; import forge.game.card.CardFactoryUtil; import forge.game.card.CardLists; import forge.game.card.CardPredicates; -import forge.game.card.CardUtil; import forge.game.card.CounterEnumType; import forge.game.card.CounterType; import forge.game.combat.Combat; @@ -905,7 +904,7 @@ public class ComputerUtilCard { } for (final Card crd : list) { - ColorSet color = CardUtil.getColors(crd); + ColorSet color = crd.determineColor(); if (color.hasWhite()) map.get(0).setValue(Integer.valueOf(map.get(0).getValue()+1)); if (color.hasBlue()) map.get(1).setValue(Integer.valueOf(map.get(1).getValue()+1)); if (color.hasBlack()) map.get(2).setValue(Integer.valueOf(map.get(2).getValue()+1)); 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 e76505383bd..7bcc6b80dfb 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChooseGenericEffectAi.java @@ -244,7 +244,7 @@ public class ChooseGenericEffectAi extends SpellAbilityAi { //if Iona does prevent from casting, allow it to draw for (final Card io : player.getCardsIn(ZoneType.Battlefield, "Iona, Shield of Emeria")) { - if (CardUtil.getColors(imprinted).hasAnyColor(MagicColor.fromName(io.getChosenColor()))) { + if (imprinted.determineColor().hasAnyColor(MagicColor.fromName(io.getChosenColor()))) { return allow; } } 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 e0969ec98a9..dae0902bba0 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1789,7 +1789,7 @@ public class AbilityUtils { if (sq[0].contains("HasNumChosenColors")) { int sum = 0; for (Card card : getDefinedCards(c, sq[1], sa)) { - sum += CardUtil.getColors(card).getSharedColors(ColorSet.fromNames(c.getChosenColors())).countColors(); + sum += card.determineColor().getSharedColors(ColorSet.fromNames(c.getChosenColors())).countColors(); } return sum; } @@ -1990,7 +1990,7 @@ public class AbilityUtils { // Count$CardMulticolor.. if (sq[0].contains("CardMulticolor")) { - final boolean isMulti = CardUtil.getColors(c).isMulticolor(); + final boolean isMulti = c.determineColor().isMulticolor(); return doXMath(Integer.parseInt(sq[isMulti ? 1 : 2]), expr, c, ctb); } // Count$Madness.. @@ -2046,7 +2046,7 @@ public class AbilityUtils { } if (sq[0].contains("CardNumColors")) { - return doXMath(CardUtil.getColors(c).countColors(), expr, c, ctb); + return doXMath(c.determineColor().countColors(), expr, c, ctb); } if (sq[0].contains("CardNumAttacksThisTurn")) { return doXMath(c.getDamageHistory().getCreatureAttacksThisTurn(), expr, c, ctb); @@ -3766,7 +3766,7 @@ public class AbilityUtils { someCards = CardLists.filter(someCards, new Predicate() { @Override public boolean apply(final Card c) { - return CardUtil.getColors(c).isMulticolor(); + return c.determineColor().isMulticolor(); } }); } @@ -3775,7 +3775,7 @@ public class AbilityUtils { someCards = CardLists.filter(someCards, new Predicate() { @Override public boolean apply(final Card c) { - return CardUtil.getColors(c).isMonoColor(); + return c.determineColor().isMonoColor(); } }); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java index c82b353a5a0..5269c48def3 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PlayLandVariantEffect.java @@ -15,7 +15,6 @@ import forge.game.Game; import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardFactory; -import forge.game.card.CardUtil; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.item.PaperCard; @@ -35,7 +34,7 @@ public class PlayLandVariantEffect extends SpellAbilityEffect { cards = Lists.newArrayList(Iterables.filter(cards, cpp)); } // current color of source card - final ColorSet color = CardUtil.getColors(source); + final ColorSet color = source.determineColor(); if (color.isColorless()) { return; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java index f7e111cb6ef..475a3801502 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ProtectAllEffect.java @@ -15,7 +15,6 @@ import forge.game.ability.SpellAbilityEffect; import forge.game.card.Card; import forge.game.card.CardCollectionView; import forge.game.card.CardLists; -import forge.game.card.CardUtil; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; @@ -65,7 +64,7 @@ public class ProtectAllEffect extends SpellAbilityEffect { } } else if (sa.getParam("Gains").equals("TargetedCardColor")) { for (final Card c : sa.getSATargetingCard().getTargets().getTargetCards()) { - ColorSet cs = CardUtil.getColors(c); + ColorSet cs = c.determineColor(); for (byte col : MagicColor.WUBRG) { if (cs.hasAnyColor(col)) gains.add(MagicColor.toLongString(col).toLowerCase()); 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 fbe145d1c4c..634ab28de32 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -4937,17 +4937,13 @@ public class Card extends GameEntity implements Comparable, IHasSVars { view.updateEmblem(this); } - /* - * there are easy checkers for Color. The CardUtil functions should be made - * part of the Card class, so calling out is not necessary - */ - public final boolean isOfColor(final String col) { return CardUtil.getColors(this).hasAnyColor(MagicColor.fromName(col)); } - public final boolean isBlack() { return CardUtil.getColors(this).hasBlack(); } - public final boolean isBlue() { return CardUtil.getColors(this).hasBlue(); } - public final boolean isRed() { return CardUtil.getColors(this).hasRed(); } - public final boolean isGreen() { return CardUtil.getColors(this).hasGreen(); } - public final boolean isWhite() { return CardUtil.getColors(this).hasWhite(); } - public final boolean isColorless() { return CardUtil.getColors(this).isColorless(); } + public final boolean isOfColor(final String col) { return determineColor().hasAnyColor(MagicColor.fromName(col)); } + public final boolean isBlack() { return determineColor().hasBlack(); } + public final boolean isBlue() { return determineColor().hasBlue(); } + public final boolean isRed() { return determineColor().hasRed(); } + public final boolean isGreen() { return determineColor().hasGreen(); } + public final boolean isWhite() { return determineColor().hasWhite(); } + public final boolean isColorless() { return determineColor().isColorless(); } public final boolean sharesNameWith(final Card c1) { // in a corner case where c1 is null, there is no name to share with. diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java index 1ffae8fd159..1c619716ffa 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactory.java +++ b/forge-game/src/main/java/forge/game/card/CardFactory.java @@ -104,6 +104,11 @@ public class CardFactory { out.setCommander(in.isRealCommander()); //out.setFaceDown(in.isFaceDown()); + int foil = in.getCurrentState().getFoil(); + if (foil > 0) { + out.setFoil(foil); + } + return out; } 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 36e2479e4e2..11166d7fad0 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -338,7 +338,7 @@ public class CardFactoryUtil { } for (final Card crd : list) { - ColorSet color = CardUtil.getColors(crd); + ColorSet color = crd.determineColor(); for (int i = 0; i < cntColors; i++) { if (color.hasAnyColor(MagicColor.WUBRG[i])) map[i]++; @@ -376,7 +376,7 @@ public class CardFactoryUtil { } for (final Card crd : list) { - ColorSet color = CardUtil.getColors(crd); + ColorSet color = crd.determineColor(); for (int i = 0; i < cntColors; i++) { if (color.hasAnyColor(MagicColor.WUBRG[i])) map[i]++; @@ -407,7 +407,7 @@ public class CardFactoryUtil { } for (final Card crd : list) { - ColorSet color = CardUtil.getColors(crd); + ColorSet color = crd.determineColor(); for (int i = 0; i < cntColors; i++) { if (color.hasAnyColor(colorRestrictions.get(i))) { map[i]++; diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-game/src/main/java/forge/game/card/CardPredicates.java index d8712ecacab..55912ec3aa8 100644 --- a/forge-game/src/main/java/forge/game/card/CardPredicates.java +++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java @@ -264,7 +264,7 @@ public final class CardPredicates { return new Predicate() { @Override public boolean apply(final Card c) { - return CardUtil.getColors(c).hasAnyColor(color); + return c.determineColor().hasAnyColor(color); } }; } // getColor() @@ -273,7 +273,7 @@ public final class CardPredicates { return new Predicate() { @Override public boolean apply(final Card c) { - return CardUtil.getColors(c).hasExactlyColor(color); + return c.determineColor().hasExactlyColor(color); } }; } @@ -282,7 +282,7 @@ public final class CardPredicates { return new Predicate() { @Override public boolean apply(final Card c) { - return CardUtil.getColors(c).isColorless(); + return c.determineColor().isColorless(); } }; } 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 95bbfd2f933..2025be855c7 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -689,7 +689,7 @@ public class CardProperty { break; case "MostProminentColor": byte mask = CardFactoryUtil.getMostProminentColors(game.getCardsIn(ZoneType.Battlefield)); - if (!CardUtil.getColors(card).hasAnyColor(mask)) + if (!card.determineColor().hasAnyColor(mask)) return false; break; case "LastCastThisTurn": @@ -703,7 +703,7 @@ public class CardProperty { if (castSA == null) { return false; } - if (!CardUtil.getColors(card).hasAnyColor(castSA.getPayingColors().getColor())) { + if (!card.determineColor().hasAnyColor(castSA.getPayingColors().getColor())) { return false; } break; diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java index d5b2df735df..b833f3543fd 100644 --- a/forge-game/src/main/java/forge/game/card/CardUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardUtil.java @@ -85,10 +85,6 @@ public final class CardUtil { return false; } - public static ColorSet getColors(final Card c) { - return c.determineColor(); - } - public static boolean isStackingKeyword(final String keyword) { String kw = keyword; if (kw.startsWith("HIDDEN")) { @@ -158,7 +154,6 @@ public final class CardUtil { public static List getLastTurnCast(final String valid, final Card src, final CardTraitBase ctb) { return CardLists.getValidCardsAsList(src.getGame().getStack().getSpellsCastLastTurn(), valid, src.getController(), src, ctb); - } public static List getLKICopyList(final Iterable in, Map cachedMap) { @@ -343,7 +338,7 @@ public final class CardUtil { byte combinedColor = 0; for (Card tgt : tgts) { - ColorSet cs = CardUtil.getColors(tgt); + ColorSet cs = tgt.determineColor(); for (byte color : MagicColor.WUBRG) { if(!cs.hasAnyColor(color)) continue; diff --git a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java index cd2a209d345..03338e6ee8c 100644 --- a/forge-game/src/main/java/forge/game/card/token/TokenInfo.java +++ b/forge-game/src/main/java/forge/game/card/token/TokenInfo.java @@ -158,7 +158,7 @@ public class TokenInfo { if (!colorMap.isEmpty()) { if (!result.isColorless()) { // change Token Colors - byte color = CardUtil.getColors(result).getColor(); + byte color = result.determineColor().getColor(); for (final Map.Entry e : colorMap.entrySet()) { byte v = MagicColor.fromName(e.getValue()); diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java index 44f8535dcfe..1ba5c680b6b 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java @@ -34,7 +34,6 @@ import forge.game.GameObjectPredicates; import forge.game.GameType; import forge.game.ability.AbilityUtils; import forge.game.card.Card; -import forge.game.card.CardUtil; import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseType; import forge.game.player.Player; @@ -300,9 +299,9 @@ public class SpellAbilityCondition extends SpellAbilityVariables { if (first == null) { return false; } - byte firstColor = CardUtil.getColors(first).getColor(); + byte firstColor = first.determineColor().getColor(); for (Card c : tgts) { - if (CardUtil.getColors(c).getColor() != firstColor) { + if (c.determineColor().getColor() != firstColor) { return false; } } diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/IntegerConstraint.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/IntegerConstraint.java index 54c41a01543..b2d4ebd7252 100644 --- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/IntegerConstraint.java +++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/IntegerConstraint.java @@ -23,7 +23,7 @@ public class IntegerConstraint { @Override public String toString() { - if( min == max ) { + if (min == max) { return String.valueOf( min ); } return "between " + min + " and " + max; diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectCardsForConvokeOrImprovise.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectCardsForConvokeOrImprovise.java index 248f9d393e0..b7012d86fff 100644 --- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectCardsForConvokeOrImprovise.java +++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputSelectCardsForConvokeOrImprovise.java @@ -13,7 +13,6 @@ import forge.card.mana.ManaCost; import forge.card.mana.ManaCostShard; import forge.game.card.Card; import forge.game.card.CardCollectionView; -import forge.game.card.CardUtil; import forge.game.mana.ManaCostBeingPaid; import forge.game.player.Player; import forge.game.spellability.SpellAbility; @@ -74,7 +73,7 @@ public final class InputSelectCardsForConvokeOrImprovise extends InputSelectMany if (improvise) { chosenColor = ManaCostShard.COLORLESS.getColorMask(); } else { - ColorSet colors = CardUtil.getColors(card); + ColorSet colors = card.determineColor(); if (colors.isMulticolor()) { //if card is multicolor, strip out any colors which can't be paid towards remaining cost colors = ColorSet.fromMask(colors.getColor() & remainingCost.getUnpaidColors()); diff --git a/forge-gui/src/main/java/forge/gui/FThreads.java b/forge-gui/src/main/java/forge/gui/FThreads.java index f90221c0c0e..e463f3ead90 100644 --- a/forge-gui/src/main/java/forge/gui/FThreads.java +++ b/forge-gui/src/main/java/forge/gui/FThreads.java @@ -30,8 +30,7 @@ public class FThreads { public static void invokeInEdtNowOrLater(final Runnable proc) { if (isGuiThread()) { GuiBase.getInterface().invokeInEdtNow(proc); - } - else { + } else { GuiBase.getInterface().invokeInEdtLater(proc); } } From 40dac70ebf62acf710747c583c18681cedf071c8 Mon Sep 17 00:00:00 2001 From: leriomaggio Date: Thu, 28 Oct 2021 21:35:32 +0100 Subject: [PATCH 32/32] QuickFIX deckSection validation in Decks that inhibited the smart card art option to trigger This MR brings a quick FIX to `validateDeferredSections` method in Deck which erroneously considered entries in filtered Card Pool as for the valid deck entries, instead of the original one. Incidentally this affected only decks with no specified edition, imposing one given by card Db query using default Card Art Preference. In other word, no card with NO edition was found and smart card art was never triggered. This now won't be the case! Another quick fix in `getAllCardNamesWithNoSpecifiedEdition` to skip all sections that are not MAIN | SIDE | Commander for which card art harmonisation is necessary. --- forge-core/src/main/java/forge/deck/Deck.java | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/forge-core/src/main/java/forge/deck/Deck.java b/forge-core/src/main/java/forge/deck/Deck.java index 45037314276..a7818c1b3e2 100644 --- a/forge-core/src/main/java/forge/deck/Deck.java +++ b/forge-core/src/main/java/forge/deck/Deck.java @@ -25,6 +25,7 @@ import forge.card.CardDb; import forge.card.CardEdition; import forge.item.IPaperCard; import forge.item.PaperCard; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import java.util.*; @@ -283,6 +284,7 @@ public class Deck extends DeckBase implements Iterable cardsInSection = s.getValue(); + List> originalCardRequests = CardPool.processCardList(cardsInSection); CardPool pool = CardPool.fromCardList(cardsInSection); if (pool.countDistinct() == 0) continue; // pool empty, no card has been found! @@ -299,7 +301,7 @@ public class Deck extends DeckBase implements Iterable(); for (Entry entry : filteredPool) { - String poolRequest = getPoolRequest(entry); + String poolRequest = getPoolRequest(entry, originalCardRequests); whiteList.add(poolRequest); } validatedSections.put(s.getKey(), whiteList); @@ -314,7 +316,7 @@ public class Deck extends DeckBase implements Iterable entry : blackList) { DeckSection cardSection = DeckSection.matchingSection(entry.getKey()); - String poolRequest = getPoolRequest(entry); + String poolRequest = getPoolRequest(entry, originalCardRequests); List sectionCardList = validatedSections.getOrDefault(cardSection.name(), null); if (sectionCardList == null) sectionCardList = new ArrayList<>(); @@ -328,11 +330,26 @@ public class Deck extends DeckBase implements Iterable entry) { + private String getPoolRequest(Entry entry, List> originalCardRequests) { PaperCard card = entry.getKey(); int amount = entry.getValue(); - String cardRequest = CardDb.CardRequest.compose(card.isFoil() ? CardDb.CardRequest.compose(card.getName(), true) : card.getName(), card.getEdition(), card.getArtIndex()); - return String.format("%d %s", amount, cardRequest); + String poolCardRequest = CardDb.CardRequest.compose( + card.isFoil() ? CardDb.CardRequest.compose(card.getName(), true) : card.getName(), + card.getEdition(), card.getArtIndex()); + String originalRequestCandidate = null; + for (Pair originalRequest : originalCardRequests){ + String cardRequest = originalRequest.getLeft(); + if (!StringUtils.startsWithIgnoreCase(poolCardRequest, cardRequest)) + continue; + originalRequestCandidate = cardRequest; + int cardAmount = originalRequest.getRight(); + if (amount == cardAmount) + return String.format("%d %s", cardAmount, cardRequest); + } + // This is just in case, it should never happen as we're + if (originalRequestCandidate != null) + return String.format("%d %s", amount, originalRequestCandidate); + return String.format("%d %s", amount, poolCardRequest); } private ArrayList getAllCardNamesWithNoSpecifiedEdition(List cardsInSection) { @@ -355,7 +372,7 @@ public class Deck extends DeckBase implements Iterable part : parts.entrySet()) { DeckSection deckSection = part.getKey(); - if (deckSection == DeckSection.Planes || deckSection == DeckSection.Schemes || deckSection == DeckSection.Avatar) + if (deckSection != DeckSection.Main && deckSection != DeckSection.Sideboard && deckSection != DeckSection.Commander) continue; // == 0. First Off, check if there is anything at all to do for the current section