From 8cb78656e20125389033481d472752553bca8e4c Mon Sep 17 00:00:00 2001 From: JohnWilliams77 <103562494+JohnWilliams77@users.noreply.github.com> Date: Sun, 18 Dec 2022 12:12:42 +0000 Subject: [PATCH 01/76] Create aisha_of_sparks_and_smoke.txt --- .../upcoming/aisha_of_sparks_and_smoke.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/aisha_of_sparks_and_smoke.txt diff --git a/forge-gui/res/cardsfolder/upcoming/aisha_of_sparks_and_smoke.txt b/forge-gui/res/cardsfolder/upcoming/aisha_of_sparks_and_smoke.txt new file mode 100644 index 00000000000..3fb3564aef7 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/aisha_of_sparks_and_smoke.txt @@ -0,0 +1,11 @@ +Name:Aisha of Sparks and Smoke +ManaCost:1 R R +Types:Legendary Creature Human Warrior +PT:4/2 +K:Prowess +A:AB$ Pump | Cost$ RW | Defined$ Self | KW$ First Strike | SpellDescription$ NICKNAME gains first strike until end of turn. +T:Mode$ DamageDealtOnce | CombatDamage$ True | ValidSource$ Card.Self | Execute$ TrigCast | OptionalDecider$ You | TriggerDescription$ When NICKNAME deals combat damage, you may cast a sorcery spell from your hand with mana value less than or equal to that damage without paying its mana cost. +SVar:TrigCast:DB$ Play | Valid$ Sorcery.YouOwn+cmcLEX | ValidSA$ Spell | ValidZone$ Hand | WithoutManaCost$ True | Amount$ 1 | Optional$ True +SVar:X:TriggerCount$DamageAmount +DeckHints:Type$Sorcery +Oracle:Prowess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.)\n{R/W}: Aisha gains first strike until end of turn.\nWhenever Aisha deals combat damage, you may cast a sorcery spell from your hand with mana value less than or equal to that damage without paying its mana cost. From 4c8e7f2bc10768a3cafa0125a5a72cd13c10b686 Mon Sep 17 00:00:00 2001 From: JohnWilliams77 <103562494+JohnWilliams77@users.noreply.github.com> Date: Sun, 18 Dec 2022 12:13:57 +0000 Subject: [PATCH 02/76] Create baldin_century_herdmaster.txt --- .../cardsfolder/upcoming/baldin_century_herdmaster.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/baldin_century_herdmaster.txt diff --git a/forge-gui/res/cardsfolder/upcoming/baldin_century_herdmaster.txt b/forge-gui/res/cardsfolder/upcoming/baldin_century_herdmaster.txt new file mode 100644 index 00000000000..824a84426b6 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/baldin_century_herdmaster.txt @@ -0,0 +1,10 @@ +Name:Baldin, Century Herdmaster +ManaCost:4 W W +Types:Legendary Creature Human Warrior +PT:0/7 +S:Mode$ CombatDamageToughness | Condition$ PlayerTurn | ValidCard$ Creature | Description$ As long as it's your turn, each creature assigns combat damage equal to its toughness rather than its power. +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPump | TriggerDescription$ Whenever CARDNAME attacks, up to one hundred target creatures each get +0/+X until end of turn, where X is the number of cards in your hand. +SVar:TrigPump:DB$ Pump | ValidTgts$ Creature | TgtPrompt$ Select target creature | TargetMin$ 0 | TargetMax$ 100 | NumDef$ +X | AILogic$ Pump +SVar:HasAttackEffect:TRUE +SVar:X:Count$CardsInYourHand +Oracle:As long as it's your turn, each creature assigns combat damage equal to its toughness rather than its power.\nWhenever Baldin, Century Herdmaster attacks, up to one hundred target creatures each get +0/+X until end of turn, where X is the number of cards in your hand. From c5a21c8d258a5f30eea33f89701ac9ca7598fd65 Mon Sep 17 00:00:00 2001 From: JohnWilliams77 <103562494+JohnWilliams77@users.noreply.github.com> Date: Sun, 18 Dec 2022 12:14:28 +0000 Subject: [PATCH 03/76] Create immard_the_stormcleaver.txt --- .../upcoming/immard_the_stormcleaver.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/immard_the_stormcleaver.txt diff --git a/forge-gui/res/cardsfolder/upcoming/immard_the_stormcleaver.txt b/forge-gui/res/cardsfolder/upcoming/immard_the_stormcleaver.txt new file mode 100644 index 00000000000..84ad5401275 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/immard_the_stormcleaver.txt @@ -0,0 +1,14 @@ +Name:Immard, the Stormcleaver +ManaCost:1 U R W +Types:Legendary Creature Human Soldier +PT:4/4 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigCharge | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, put a charge counter on him or remove one from him. When you remove a counter this way, ABILITY +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigCharge | Secondary$ True | TriggerDescription$ Whenever CARDNAME enters the battlefield or attacks, put a charge counter on him or remove one from him. When you remove a counter this way, ABILITY +SVar:TrigCharge:DB$ AddOrRemoveCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ 1 | RememberRemovedCards$ True | SubAbility$ DBImmediateTrigger +SVar:DBImmediateTrigger:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card.Self | ConditionCompare$ GE1 | Execute$ TrigCharm | SubAbility$ DBCleanup | TriggerDescription$ When you remove a counter this way, ABILITY +SVar:TrigCharm:DB$ Charm | Choices$ DBDamage,DBPump +SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 4 | SpellDescription$ CARDNAME deals 4 damage to any target. +SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Lifelink & Indestructible | SpellDescription$ CARDNAME gains lifelink and indestructible until end of turn. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHas:Ability$Counters|LifeGain +Oracle:Whenever Immard, the Stormcleaver enters the battlefield or attacks, put a charge counter on him or remove one from him. When you remove a counter this way, choose one—\n• Immard, the Stormcleaver deals 4 damage to any target.\n• Immard, the Stormcleaver gains lifelink and indestructible until end of turn. From 5143c3ea4afbcdcf746d994145c5d49e281c61af Mon Sep 17 00:00:00 2001 From: JohnWilliams77 <103562494+JohnWilliams77@users.noreply.github.com> Date: Sun, 18 Dec 2022 12:15:08 +0000 Subject: [PATCH 04/76] Add files via upload --- .../upcoming/maarika_brutal_gladiator.txt | 9 +++++++++ .../upcoming/tadeas_juniper_ascendant.txt | 15 +++++++++++++++ .../upcoming/the_howling_abomination.txt | 11 +++++++++++ .../upcoming/vikya_scorching_stalwart.txt | 10 ++++++++++ .../upcoming/zethi_arcane_blademaster.txt | 13 +++++++++++++ 5 files changed, 58 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/maarika_brutal_gladiator.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/tadeas_juniper_ascendant.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/the_howling_abomination.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/vikya_scorching_stalwart.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/zethi_arcane_blademaster.txt diff --git a/forge-gui/res/cardsfolder/upcoming/maarika_brutal_gladiator.txt b/forge-gui/res/cardsfolder/upcoming/maarika_brutal_gladiator.txt new file mode 100644 index 00000000000..c858ccfc00e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/maarika_brutal_gladiator.txt @@ -0,0 +1,9 @@ +Name:Maarika, Brutal Gladiator +ManaCost:2 B R G +Types:Legendary Creature Human Warrior +PT:7/4 +K:CARDNAME must be blocked if able. +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Indestructible | Condition$ PlayerTurn | Description$ As long as it's your turn, NICKNAME has indestructible. +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Creature.wasDealtExcessDamageThisTurn | Execute$ TrigSac | TriggerDescription$ Whenever NICKNAME deals damage to a creature, if that creature was dealt excess damage this turn, that creature's controller sacrifices a noncreature, nonland permanent. +SVar:TrigSac:DB$ Sacrifice | SacValid$ Permanent.nonCreature+nonLand | SacMessage$ noncreature, nonland permanent | Defined$ TriggeredTargetController +Oracle:Maarika, Brutal Gladiator must be blocked if able.\nAs long as it's your turn, Maarika has indestructible.\nWhenever Maarika deals damage to a creature, if that creature was dealt excess damage this turn, that creature's controller sacrifices a noncreature, nonland permanent. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/tadeas_juniper_ascendant.txt b/forge-gui/res/cardsfolder/upcoming/tadeas_juniper_ascendant.txt new file mode 100644 index 00000000000..f64cb7651ec --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/tadeas_juniper_ascendant.txt @@ -0,0 +1,15 @@ +Name:Tadeas, Juniper Ascendant +ManaCost:2 G W +Types:Legendary Creature Human Monk +PT:1/3 +K:Reach +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Hexproof | IsPresent$ Card.Self+notattacking | Description$ Teleport — CARDNAME has hexproof unless he's attacking. +T:Mode$ Attacks | ValidCard$ Creature.YouCtrl+withReach | TriggerZones$ Battlefield | Execute$ TrigUntap | TriggerDescription$ Whenever a creature you control with reach attacks, untap it and it can't be blocked by creatures with greater power this combat. +SVar:TrigUntap:DB$ Untap | Defined$ TriggeredAttackerLKICopy | SubAbility$ DBEffect +SVar:DBEffect:DB$ Effect | RememberObjects$ TriggeredAttacker | StaticAbilities$ CantBeBlockedPow | ForgetOnMoved$ Battlefield | Duration$ UntilEndOfCombat +SVar:CantBeBlockedPow:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | ValidBlocker$ Creature.powerGTX | Description$ CARDNAME can't be blocked by creatures with greater power this combat. +SVar:X:Remembered$CardPower +T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Creature.YouCtrl | TriggerZones$ Battlefield | ValidTarget$ Player | Execute$ TrigDraw | TriggerDescription$ Whenever one or more creatures you control deal combat damage to a player, draw a card. +SVar:TrigDraw:DB$ Draw +DeckHints:Keyword$Reach +Oracle:Reach\nTadeas, Juniper Ascendant has hexproof unless he's attacking.\nWhenever a creature you control with reach attacks, untap it and it can't be blocked by creatures with greater power this combat.\nWhenever one or more creatures you control deal combat damage to a player, draw a card. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/the_howling_abomination.txt b/forge-gui/res/cardsfolder/upcoming/the_howling_abomination.txt new file mode 100644 index 00000000000..4c6510b0124 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/the_howling_abomination.txt @@ -0,0 +1,11 @@ +Name:The Howling Abomination +ManaCost:3 R G +Types:Legendary Creature Human Beast Warrior +PT:5/5 +K:Haste +S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Trample | CheckSVar$ X | SVarCompare$ GE3 | Description$ CARDNAME has trample as long as you've cast three or more spells this turn. +T:Mode$ BecomesTarget | ValidTarget$ Card.Self | ValidSource$ Spell | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever NICKNAME becomes the target of a spell, he gets +2/+2 until end of turn and deals 2 damage to each opponent. +SVar:TrigPump:DB$ Pump | Defined$ Self | NumAtt$ +2 | NumDef$ +2 | SubAbility$ DBDmg +SVar:DBDmg:DB$ DealDamage | Defined$ Opponent | NumDmg$ 2 +SVar:X:Count$ThisTurnCast_Card.YouCtrl +Oracle:Haste\nThe Howling Abomination has trample as long as you've cast three or more spells this turn.\nWhenever The Howling Abomination becomes the target of a spell, he gets +2/+2 until end of turn and deals 2 damage to each opponent. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/vikya_scorching_stalwart.txt b/forge-gui/res/cardsfolder/upcoming/vikya_scorching_stalwart.txt new file mode 100644 index 00000000000..ff8e82c921e --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/vikya_scorching_stalwart.txt @@ -0,0 +1,10 @@ +Name:Vikya, Scorching Stalwart +ManaCost:2 W +Types:Legendary Creature Human Warrior +PT:2/4 +K:Training +A:AB$ DealDamage | Cost$ 4 R Untap Discard<1/Card> | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ X | AILogic$ PowerDmg | ExcessSVar$ Excess | SubAbility$ DBDraw | SpellDescription$ CARDNAME deals damage equal to his power to any target. +SVar:DBDraw:DB$ Draw | ConditionCheckSVar$ Excess | ConditionSVarCompare$ GE1 | ConditionDefined$ Targeted | ConditionPresent$ Creature | IfDesc$ True | SpellDescription$ If excess damage was dealt to a creature this way, draw a card. ({Q} is the untap symbol.) +SVar:X:Count$CardPower +DeckHas:Ability$Counters|Discard +Oracle:Training (Whenever this creature attacks with another creature with greater power, put a +1/+1 counter on this creature.)\n{4}{R}, {Q}, Discard a card: Vikya, Scorching Stalwart deals damage equal to his power to any target. If excess damage was dealt to a creature this way, draw a card. ({Q} is the untap symbol.) \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/zethi_arcane_blademaster.txt b/forge-gui/res/cardsfolder/upcoming/zethi_arcane_blademaster.txt new file mode 100644 index 00000000000..b62184e1ec8 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/zethi_arcane_blademaster.txt @@ -0,0 +1,13 @@ +Name:Zethi, Arcane Blademaster +ManaCost:1 W U +Types:Legendary Creature Human Soldier +PT:3/3 +K:Multikicker:WU +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When NICKNAME enters the battlefield, exile up to X target instant cards from your graveyard, where X is the number of times NICKNAME was kicked. Put a kick counter on each of them. +SVar:TrigExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | WithCountersType$ KICK | ValidTgts$ Instant.YouOwn | TargetMin$ 0 | TargetMax$ XKicked | TgtPrompt$ Select up to X target instant cards +T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigPlay | TriggerZones$ Battlefield | TriggerDescription$ Whenever NICKNAME attacks, copy each exiled card you own with a kick counter on it. You may cast the copies. +SVar:TrigPlay:DB$ Play | Valid$ Card.counters_GE1_KICK | ValidSA$ Spell | ValidZone$ Exile | Amount$ All | CopyCard$ True | Optional$ True +SVar:XKicked:Count$TimesKicked +DeckHints:Type$Instant & Ability$Discard +DeckHas:Ability$Graveyard +Oracle:Multikicker {W/U}\nWhen Zethi enters the battlefield, exile up to X target instant cards from your graveyard, where X is the number of times Zethi was kicked. Put a kick counter on each of them.\nWhenever Zethi attacks, copy each exiled card you own with a kick counter on it. You may cast the copies. \ No newline at end of file From dcc90fb0d1dd70087ac84ef9a7b94ab4ea9eaf7e Mon Sep 17 00:00:00 2001 From: asvitkine Date: Sun, 18 Dec 2022 23:15:27 -0700 Subject: [PATCH 05/76] Clean up lobby code and fix a deck deselect bug. --- .../main/java/forge/screens/home/CLobby.java | 95 +---- .../java/forge/screens/home/PlayerPanel.java | 32 +- .../main/java/forge/screens/home/VLobby.java | 331 ++++++------------ 3 files changed, 144 insertions(+), 314 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java index 7c9cd1a8fa1..efc7d8793b6 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java @@ -1,7 +1,5 @@ package forge.screens.home; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.util.Arrays; import java.util.Vector; @@ -10,8 +8,6 @@ import javax.swing.SwingUtilities; import com.google.common.collect.Iterables; import forge.deck.DeckProxy; -import forge.deck.DeckType; -import forge.deckchooser.FDeckChooser; import forge.localinstance.properties.ForgePreferences; import forge.localinstance.properties.ForgePreferences.FPref; import forge.model.FModel; @@ -22,12 +18,10 @@ public class CLobby { private final VLobby view; public CLobby(final VLobby view) { this.view = view; - this.view.setForCommander(true); } private void addDecks(final Iterable commanderDecks, FList deckList, String... initialItems) { - Vector listData = new Vector<>(); - listData.addAll(Arrays.asList(initialItems)); + Vector listData = new Vector<>(Arrays.asList(initialItems)); listData.add("Generate"); if (!Iterables.isEmpty(commanderDecks)) { listData.add("Random"); @@ -46,89 +40,38 @@ public class CLobby { } public void update() { - SwingUtilities.invokeLater(new Runnable() { - @Override public final void run() { - final Iterable schemeDecks = DeckProxy.getAllSchemeDecks(); - final Iterable planarDecks = DeckProxy.getAllPlanarDecks(); + SwingUtilities.invokeLater(() -> { + final Iterable schemeDecks = DeckProxy.getAllSchemeDecks(); + final Iterable planarDecks = DeckProxy.getAllPlanarDecks(); - for (int i = 0; i < VLobby.MAX_PLAYERS; i++) { - addDecks(schemeDecks, view.getSchemeDeckLists().get(i), - "Use deck's scheme section (random if unavailable)"); - addDecks(planarDecks, view.getPlanarDeckLists().get(i), - "Use deck's planes section (random if unavailable)"); - view.updateVanguardList(i); - } - - // General updates when switching back to this view - view.getBtnStart().requestFocusInWindow(); + for (int i = 0; i < VLobby.MAX_PLAYERS; i++) { + addDecks(schemeDecks, view.getSchemeDeckLists().get(i), + "Use deck's scheme section (random if unavailable)"); + addDecks(planarDecks, view.getPlanarDeckLists().get(i), + "Use deck's planes section (random if unavailable)"); + view.updateVanguardList(i); } + + // General updates when switching back to this view + view.getBtnStart().requestFocusInWindow(); }); } public void initialize() { - for (int iSlot = 0; iSlot < VLobby.MAX_PLAYERS; iSlot++) { - final FDeckChooser fdc = view.getDeckChooser(iSlot); - fdc.initialize(FPref.CONSTRUCTED_DECK_STATES[iSlot], defaultDeckTypeForSlot(iSlot)); - fdc.populate(); - /*fdc.getDecksComboBox().addListener(new IDecksComboBoxListener() { - @Override public final void deckTypeSelected(final DecksComboBoxEvent ev) { - view.focusOnAvatar(); - } - });*/ - final FDeckChooser fdccom = view.getCommanderDeckChooser(iSlot); - fdccom.initialize(FPref.COMMANDER_DECK_STATES[iSlot], defaultDeckTypeForCommanderSlot(iSlot)); - fdccom.populate(); - final FDeckChooser fdobcom = view.getOathbreakerDeckChooser(iSlot); - fdobcom.initialize(FPref.OATHBREAKER_DECK_STATES[iSlot], defaultDeckTypeForOathbreakerSlot(iSlot)); - fdobcom.populate(); - final FDeckChooser fdtlcom = view.getTinyLeaderDeckChooser(iSlot); - fdtlcom.initialize(FPref.TINY_LEADER_DECK_STATES[iSlot], defaultDeckTypeForTinyLeaderSlot(iSlot)); - fdtlcom.populate(); - final FDeckChooser fdbcom = view.getBrawlDeckChooser(iSlot); - fdbcom.initialize(FPref.BRAWL_DECK_STATES[iSlot], defaultDeckTypeForBrawlSlot(iSlot)); - fdbcom.populate(); - } - final ForgePreferences prefs = FModel.getPreferences(); // Checkbox event handling - view.getCbSingletons().addActionListener(new ActionListener() { - @Override - public void actionPerformed(final ActionEvent arg0) { - prefs.setPref(FPref.DECKGEN_SINGLETONS, String.valueOf(view.getCbSingletons().isSelected())); - prefs.save(); - } + view.getCbSingletons().addActionListener(arg0 -> { + prefs.setPref(FPref.DECKGEN_SINGLETONS, String.valueOf(view.getCbSingletons().isSelected())); + prefs.save(); }); - view.getCbArtifacts().addActionListener(new ActionListener() { - @Override - public void actionPerformed(final ActionEvent arg0) { - prefs.setPref(FPref.DECKGEN_ARTIFACTS, String.valueOf(view.getCbArtifacts().isSelected())); - prefs.save(); - } + view.getCbArtifacts().addActionListener(arg0 -> { + prefs.setPref(FPref.DECKGEN_ARTIFACTS, String.valueOf(view.getCbArtifacts().isSelected())); + prefs.save(); }); // Pre-select checkboxes view.getCbSingletons().setSelected(prefs.getPrefBoolean(FPref.DECKGEN_SINGLETONS)); view.getCbArtifacts().setSelected(prefs.getPrefBoolean(FPref.DECKGEN_ARTIFACTS)); } - - private static DeckType defaultDeckTypeForSlot(final int iSlot) { - return iSlot == 0 ? DeckType.PRECONSTRUCTED_DECK : DeckType.COLOR_DECK; - } - - private static DeckType defaultDeckTypeForCommanderSlot(final int iSlot) { - return iSlot == 0 ? DeckType.COMMANDER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; - } - - private static DeckType defaultDeckTypeForOathbreakerSlot(final int iSlot) { - return iSlot == 0 ? DeckType.OATHBREAKER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; - } - - private static DeckType defaultDeckTypeForTinyLeaderSlot(final int iSlot) { - return iSlot == 0 ? DeckType.TINY_LEADERS_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; - } - - private static DeckType defaultDeckTypeForBrawlSlot(final int iSlot) { - return iSlot == 0 ? DeckType.BRAWL_DECK : DeckType.CUSTOM_DECK; - } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java b/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java index 9a1e6d69f4e..4073dc543c2 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java @@ -1,5 +1,6 @@ package forge.screens.home; +import forge.deckchooser.FDeckChooser; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -103,6 +104,8 @@ public class PlayerPanel extends FPanel { private boolean allowNetworking; + private FDeckChooser deckChooser; + private final VLobby lobby; public PlayerPanel(final VLobby lobby, final boolean allowNetworking, final int index, final LobbySlot slot, final boolean mayEdit, final boolean mayControl) { super(); @@ -463,10 +466,6 @@ public class PlayerPanel extends FPanel { radioAiUseSimulation.setSelected(useSimulation); } - public boolean isLocal() { - return type == LobbySlotType.LOCAL; - } - public boolean isArchenemy() { return aeTeamComboBox.getSelectedIndex() == 0; } @@ -540,9 +539,6 @@ public class PlayerPanel extends FPanel { } }; - /** - * @param index - */ private void addHandlersToVariantsControls() { // Archenemy buttons scmDeckSelectorBtn.setCommand(new Runnable() { @@ -621,9 +617,6 @@ public class PlayerPanel extends FPanel { }); } - /** - * @param index - */ private void createPlayerTypeOptions() { radioHuman = new FRadioButton(localizer.getMessage("lblHuman")); radioAi = new FRadioButton(localizer.getMessage("lblAI")); @@ -674,9 +667,6 @@ public class PlayerPanel extends FPanel { }); } - /** - * @param index - */ private void addHandlersDeckSelector() { deckBtn.setCommand(new Runnable() { @Override @@ -688,10 +678,6 @@ public class PlayerPanel extends FPanel { }); } - /** - * @param index - * @return - */ private FLabel createNameRandomizer() { final FLabel newNameBtn = new FLabel.Builder().tooltip(localizer.getMessage("lblGetaNewRandomName")).iconInBackground(false) .icon(FSkin.getIcon(FSkinProp.ICO_EDIT)).hoverable(true).opaque(false) @@ -717,10 +703,6 @@ public class PlayerPanel extends FPanel { return newNameBtn; } - /** - * @param index - * @return - */ private void createNameEditor() { String name; if (index == 0) { @@ -898,4 +880,12 @@ public class PlayerPanel extends FPanel { public void setMayRemove(final boolean mayRemove) { this.mayRemove = mayRemove; } + + FDeckChooser getDeckChooser() { + return deckChooser; + } + + void setDeckChooser(final FDeckChooser deckChooser) { + this.deckChooser = deckChooser; + } } \ No newline at end of file diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java index 0aaa4ba989c..dd08d22aa17 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java @@ -4,10 +4,8 @@ import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Vector; @@ -36,7 +34,6 @@ import forge.gamemodes.match.LobbySlotType; import forge.gamemodes.net.event.UpdateLobbyPlayerEvent; import forge.gui.CardDetailPanel; import forge.gui.GuiBase; -import forge.gui.UiCommand; import forge.gui.interfaces.ILobbyView; import forge.gui.util.SOptionPane; import forge.interfaces.IPlayerChangeListener; @@ -110,17 +107,11 @@ public class VLobby implements ILobbyView { // Deck frame elements private final JPanel decksFrame = new JPanel(new MigLayout("insets 0, gap 0, wrap, hidemode 3")); - private final List deckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS); private final FCheckBox cbSingletons = new FCheckBox(localizer.getMessage("cbSingletons")); private final FCheckBox cbArtifacts = new FCheckBox(localizer.getMessage("cbRemoveArtifacts")); private final Deck[] decks = new Deck[MAX_PLAYERS]; // Variants - private final List commanderDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS); - private final List oathbreakerDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS); - private final List tinyLeadersDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS); - private final List brawlDeckChoosers = Lists.newArrayListWithCapacity(MAX_PLAYERS); - private final List> schemeDeckLists = new ArrayList<>(); private final List schemeDeckPanels = new ArrayList<>(MAX_PLAYERS); @@ -131,22 +122,11 @@ public class VLobby implements ILobbyView { private final List vgdPanels = new ArrayList<>(MAX_PLAYERS); private final List vgdAvatarDetails = new ArrayList<>(); private final List vgdAllAvatars = new ArrayList<>(); - private final List vgdAllAiAvatars = new ArrayList<>(); private final List nonRandomHumanAvatars = new ArrayList<>(); private final List nonRandomAiAvatars = new ArrayList<>(); private final Vector humanListData = new Vector<>(); private final Vector aiListData = new Vector<>(); - public boolean isForCommander() { - return isForCommander; - } - - public void setForCommander(boolean forCommander) { - isForCommander = forCommander; - } - - private boolean isForCommander = false; - // CTR public VLobby(final GameLobby lobby) { this.lobby = lobby; @@ -178,12 +158,7 @@ public class VLobby implements ILobbyView { if (lobby.hasControl()) { addPlayerBtn.setFocusable(true); - addPlayerBtn.setCommand(new Runnable() { - @Override - public final void run() { - lobby.addSlot(); - } - }); + addPlayerBtn.setCommand(lobby::addSlot); playersFrame.add(addPlayerBtn, "height 30px!, growx, pushx"); } @@ -207,7 +182,7 @@ public class VLobby implements ILobbyView { // Start button event handling btnStart.addActionListener(new ActionListener() { @Override - public final void actionPerformed(final ActionEvent arg0) { + public void actionPerformed(final ActionEvent arg0) { Runnable startGame = lobby.startGame(); if (startGame != null) { if (!gamesInMatch.getSelectedItem().equals(ForgePreferences.FPref.UI_MATCHES_PER_GAME)) { @@ -226,17 +201,8 @@ public class VLobby implements ILobbyView { } public void updateDeckPanel() { - for (int iPlayer = 0; iPlayer < activePlayersNum; iPlayer++) { - final FDeckChooser fdc = getDeckChooser(iPlayer); - fdc.restoreSavedState(); - final FDeckChooser fdcom = getCommanderDeckChooser(iPlayer); - fdcom.restoreSavedState(); - final FDeckChooser fdob = getOathbreakerDeckChooser(iPlayer); - fdob.restoreSavedState(); - final FDeckChooser fdtl = getTinyLeaderDeckChooser(iPlayer); - fdtl.restoreSavedState(); - final FDeckChooser fdbr = getBrawlDeckChooser(iPlayer); - fdbr.restoreSavedState(); + for (final PlayerPanel playerPanel : playerPanels) { + playerPanel.getDeckChooser().restoreSavedState(); } } @@ -244,10 +210,16 @@ public class VLobby implements ILobbyView { getPlayerPanelWithFocus().focusOnAvatar(); } + private PlayerPanel getPlayerPanel(int slot) { + return playerPanels.get(slot); + } + @Override - public void update(final int slot, final LobbySlotType type){ - final FDeckChooser deckChooser = getDeckChooser(slot); + public void update(final int slot, final LobbySlotType type) { + System.err.println("Update:"+slot+" / "+type); + final FDeckChooser deckChooser = getPlayerPanel(slot).getDeckChooser(); deckChooser.setIsAi(type==LobbySlotType.AI); + DeckType selectedDeckType = deckChooser.getSelectedDeckType(); switch (selectedDeckType){ case STANDARD_CARDGEN_DECK: @@ -259,32 +231,19 @@ public class VLobby implements ILobbyView { case COLOR_DECK: case STANDARD_COLOR_DECK: case MODERN_COLOR_DECK: + case RANDOM_CARDGEN_COMMANDER_DECK: + case RANDOM_COMMANDER_DECK: deckChooser.refreshDeckListForAI(); break; default: break; } - updateCommanderStyleDeckChooser(getCommanderDeckChooser(slot), type); - updateCommanderStyleDeckChooser(getOathbreakerDeckChooser(slot), type); - updateCommanderStyleDeckChooser(getTinyLeaderDeckChooser(slot), type); - updateCommanderStyleDeckChooser(getBrawlDeckChooser(slot), type); - } - - private void updateCommanderStyleDeckChooser(final FDeckChooser deckChooser, final LobbySlotType type) { - deckChooser.setIsAi(type==LobbySlotType.AI); - DeckType selectedDeckType = deckChooser.getSelectedDeckType(); - switch (selectedDeckType){ - case RANDOM_CARDGEN_COMMANDER_DECK: - case RANDOM_COMMANDER_DECK: - deckChooser.refreshDeckListForAI(); - break; - default: - break; - } } @Override public void update(final boolean fullUpdate) { + System.err.println("Update:"+fullUpdate); + activePlayersNum = lobby.getNumberOfSlots(); addPlayerBtn.setEnabled(activePlayersNum < MAX_PLAYERS); @@ -308,11 +267,6 @@ public class VLobby implements ILobbyView { if (i < activePlayersNum) { // visible panels final LobbySlot slot = lobby.getSlot(i); - final FDeckChooser deckChooser = getDeckChooser(i); - final FDeckChooser commanderDeckChooser = getCommanderDeckChooser(i); - final FDeckChooser oathbreakerDeckChooser = getOathbreakerDeckChooser(i); - final FDeckChooser tinyLeaderDeckChooser = getTinyLeaderDeckChooser(i); - final FDeckChooser brawlDeckChooser = getBrawlDeckChooser(i); final PlayerPanel panel; final boolean isNewPanel; if (hasPanel) { @@ -326,15 +280,6 @@ public class VLobby implements ILobbyView { constraints += ", gaptop 5px"; } playersScroll.add(panel, constraints); - deckChooser.restoreSavedState(); - commanderDeckChooser.restoreSavedState(); - oathbreakerDeckChooser.restoreSavedState(); - tinyLeaderDeckChooser.restoreSavedState(); - brawlDeckChooser.restoreSavedState(); - if (i == 0) { - slot.setIsDevMode(prefs.getPrefBoolean(FPref.DEV_MODE_ENABLED)); - changePlayerFocus(0); - } isNewPanel = true; } @@ -353,14 +298,23 @@ public class VLobby implements ILobbyView { panel.update(); final boolean isSlotAI = slot.getType() == LobbySlotType.AI; - - deckChooser.setIsAi(isSlotAI); - commanderDeckChooser.setIsAi(isSlotAI); - oathbreakerDeckChooser.setIsAi(isSlotAI); - tinyLeaderDeckChooser.setIsAi(isSlotAI); - brawlDeckChooser.setIsAi(isSlotAI); + if (isNewPanel || fullUpdate) { + final FDeckChooser deckChooser = createDeckChooser(lobby.getGameType(), i, isSlotAI); + deckChooser.populate(); + panel.setDeckChooser(deckChooser); + if (i == 0) { + // TODO: This seems like the wrong place to do this: + slot.setIsDevMode(prefs.getPrefBoolean(FPref.DEV_MODE_ENABLED)); + changePlayerFocus(0); + } + } else { + panel.getDeckChooser().setIsAi(isSlotAI); + } if (fullUpdate && (type == LobbySlotType.LOCAL || isSlotAI)) { - selectDeck(i); + // Deck section selection + selectSchemeDeck(i); + selectPlanarDeck(i); + selectVanguardAvatar(i); } if (isNewPanel) { panel.setVisible(true); @@ -373,7 +327,7 @@ public class VLobby implements ILobbyView { if (playerWithFocus >= activePlayersNum) { changePlayerFocus(activePlayersNum - 1); } else { - populateDeckPanel(getCurrentGameMode()); + populateDeckPanel(lobby.getGameType()); } refreshPanels(true, true); } @@ -395,8 +349,7 @@ public class VLobby implements ILobbyView { void setDevMode(final int index) { // clear ready for everyone for (int i = 0; i < activePlayersNum; i++) { - final PlayerPanel panel = playerPanels.get(i); - panel.setIsReady(false); + getPlayerPanel(i).setIsReady(false); firePlayerChangeListener(i); } changePlayerFocus(index); @@ -430,7 +383,7 @@ public class VLobby implements ILobbyView { } private UpdateLobbyPlayerEvent getSlot(final int index) { - final PlayerPanel panel = playerPanels.get(index); + final PlayerPanel panel = getPlayerPanel(index); return UpdateLobbyPlayerEvent.create(panel.getType(), panel.getPlayerName(), panel.getAvatarIndex(), -1/*TODO panel.getSleeveIndex()*/, panel.getTeam(), panel.isArchenemy(), panel.isReady(), panel.isDevMode(), panel.getAiOptions()); } @@ -438,16 +391,6 @@ public class VLobby implements ILobbyView { * These are added to a list which can be referenced to populate the deck panel appropriately. */ @SuppressWarnings("serial") private void buildDeckPanels(final int playerIndex) { - // Main deck - final FDeckChooser mainChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.Constructed, false); - mainChooser.getLstDecks().setSelectCommand(new UiCommand() { - @Override public final void run() { - selectMainDeck(playerIndex); - } - }); - mainChooser.initialize(); - deckChoosers.add(mainChooser); - // Scheme deck list buildDeckPanel(localizer.getMessage("lblSchemeDeck"), playerIndex, schemeDeckLists, schemeDeckPanels, new ListSelectionListener() { @Override public final void valueChanged(final ListSelectionEvent e) { @@ -455,42 +398,6 @@ public class VLobby implements ILobbyView { } }); - final FDeckChooser commanderChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.Commander, true); - commanderChooser.getLstDecks().setSelectCommand(new UiCommand() { - @Override public final void run() { - selectCommanderDeck(playerIndex); - } - }); - commanderChooser.initialize(); - commanderDeckChoosers.add(commanderChooser); - - final FDeckChooser oathbreakerChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.Oathbreaker, true); - oathbreakerChooser.getLstDecks().setSelectCommand(new UiCommand() { - @Override public final void run() { - selectOathbreakerDeck(playerIndex); - } - }); - oathbreakerChooser.initialize(); - oathbreakerDeckChoosers.add(oathbreakerChooser); - - final FDeckChooser tinyLeaderChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.TinyLeaders, true); - tinyLeaderChooser.getLstDecks().setSelectCommand(new UiCommand() { - @Override public final void run() { - selectTinyLeadersDeck(playerIndex); - } - }); - tinyLeaderChooser.initialize(); - tinyLeadersDeckChoosers.add(tinyLeaderChooser); - - final FDeckChooser brawlChooser = new FDeckChooser(null, isPlayerAI(playerIndex), GameType.Brawl, true); - brawlChooser.getLstDecks().setSelectCommand(new UiCommand() { - @Override public final void run() { - selectBrawlDeck(playerIndex); - } - }); - brawlChooser.initialize(); - brawlDeckChoosers.add(brawlChooser); - // Planar deck list buildDeckPanel(localizer.getMessage("lblPlanarDeck"), playerIndex, planarDeckLists, planarDeckPanels, new ListSelectionListener() { @Override public final void valueChanged(final ListSelectionEvent e) { @@ -532,26 +439,11 @@ public class VLobby implements ILobbyView { deckPanels.add(deckPanel); } - private void selectDeck(final int playerIndex) { - // Full deck selection - selectMainDeck(playerIndex); - selectCommanderDeck(playerIndex); - selectOathbreakerDeck(playerIndex); - selectTinyLeadersDeck(playerIndex); - selectBrawlDeck(playerIndex); - - // Deck section selection - selectSchemeDeck(playerIndex); - selectPlanarDeck(playerIndex); - selectVanguardAvatar(playerIndex); - } - - private void selectMainDeck(final int playerIndex) { + private void selectMainDeck(final FDeckChooser mainChooser, final int playerIndex) { if (hasVariant(GameType.Commander) || hasVariant(GameType.Oathbreaker) || hasVariant(GameType.TinyLeaders) || hasVariant(GameType.Brawl)) { // These game types use specific deck panel return; } - final FDeckChooser mainChooser = getDeckChooser(playerIndex); final DeckType type = mainChooser.getSelectedDeckType(); final Deck deck = mainChooser.getDeck(); // something went wrong, clear selection to prevent error loop @@ -561,75 +453,75 @@ public class VLobby implements ILobbyView { final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - playerPanels.get(playerIndex).setDeckSelectorButtonText(text); + getPlayerPanel(playerIndex).setDeckSelectorButtonText(text); fireDeckChangeListener(playerIndex, deck); } mainChooser.saveState(); } - private void selectCommanderDeck(final int playerIndex) { + private FDeckChooser getDeckChooser(final int iSlot) { + return getPlayerPanel(iSlot).getDeckChooser(); + } + + private void selectCommanderDeck(final FDeckChooser mainChooser, final int playerIndex) { if (!hasVariant(GameType.Commander) && !hasVariant(GameType.Oathbreaker) && !hasVariant(GameType.TinyLeaders) && !hasVariant(GameType.Brawl)) { // Only these game types use this specific deck panel return; } - final FDeckChooser mainChooser = getCommanderDeckChooser(playerIndex); final DeckType type = mainChooser.getSelectedDeckType(); final Deck deck = mainChooser.getDeck(); final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text); + getPlayerPanel(playerIndex).setCommanderDeckSelectorButtonText(text); fireDeckChangeListener(playerIndex, deck); } mainChooser.saveState(); } - private void selectOathbreakerDeck(final int playerIndex) { + private void selectOathbreakerDeck(final FDeckChooser mainChooser, final int playerIndex) { if (!hasVariant(GameType.Oathbreaker)) { // Only these game types use this specific deck panel return; } - final FDeckChooser mainChooser = getOathbreakerDeckChooser(playerIndex); final DeckType type = mainChooser.getSelectedDeckType(); final Deck deck = mainChooser.getDeck(); final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text); + getPlayerPanel(playerIndex).setCommanderDeckSelectorButtonText(text); fireDeckChangeListener(playerIndex, deck); } mainChooser.saveState(); } - private void selectTinyLeadersDeck(final int playerIndex) { + private void selectTinyLeadersDeck(final FDeckChooser mainChooser, final int playerIndex) { if (!hasVariant(GameType.TinyLeaders)) { // Only these game types use this specific deck panel return; } - final FDeckChooser mainChooser = getTinyLeaderDeckChooser(playerIndex); final DeckType type = mainChooser.getSelectedDeckType(); final Deck deck = mainChooser.getDeck(); final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text); + getPlayerPanel(playerIndex).setCommanderDeckSelectorButtonText(text); fireDeckChangeListener(playerIndex, deck); } mainChooser.saveState(); } - private void selectBrawlDeck(final int playerIndex) { + private void selectBrawlDeck(final FDeckChooser mainChooser,final int playerIndex) { if (!hasVariant(GameType.Brawl)) { // Only these game types use this specific deck panel return; } - final FDeckChooser mainChooser = getBrawlDeckChooser(playerIndex); final DeckType type = mainChooser.getSelectedDeckType(); final Deck deck = mainChooser.getDeck(); final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - playerPanels.get(playerIndex).setCommanderDeckSelectorButtonText(text); + getPlayerPanel(playerIndex).setCommanderDeckSelectorButtonText(text); fireDeckChangeListener(playerIndex, deck); } mainChooser.saveState(); @@ -653,7 +545,7 @@ public class VLobby implements ILobbyView { } } if (sel.equals("Random")) { - final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, playerPanels.get(playerIndex).isAi()); + final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, isPlayerAI(playerIndex)); schemePool = randomDeck.get(DeckSection.Schemes); } } else if (selected instanceof Deck) { @@ -684,7 +576,7 @@ public class VLobby implements ILobbyView { } } if (sel.equals("Random")) { - final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, playerPanels.get(playerIndex).isAi()); + final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, getPlayerPanel(playerIndex).isAi()); planePool = randomDeck.get(DeckSection.Planes); } } else if (selected instanceof Deck) { @@ -703,7 +595,7 @@ public class VLobby implements ILobbyView { } final Object selected = vgdAvatarLists.get(playerIndex).getSelectedValue(); - final PlayerPanel pp = playerPanels.get(playerIndex); + final PlayerPanel pp = getPlayerPanel(playerIndex); final CardDetailPanel cdp = vgdAvatarDetails.get(playerIndex); PaperCard vanguardAvatar = null; @@ -726,7 +618,7 @@ public class VLobby implements ILobbyView { if (sel.contains("Use deck's default avatar") && deck != null && deck.has(DeckSection.Avatar)) { vanguardAvatar = deck.get(DeckSection.Avatar).get(0); } else { //Only other string is "Random" - if (playerPanels.get(playerIndex).isAi()) { //AI + if (getPlayerPanel(playerIndex).isAi()) { //AI vanguardAvatar = Aggregates.random(getNonRandomAiAvatars()); } else { //Human vanguardAvatar = Aggregates.random(getNonRandomHumanAvatars()); @@ -750,8 +642,8 @@ public class VLobby implements ILobbyView { switch (forGameType) { case Constructed: - decksFrame.add(deckChoosers.get(playerWithFocus), "grow, push"); - if (deckChoosers.get(playerWithFocus).getSelectedDeckType().toString().contains(localizer.getMessage("lblRandom"))) { + decksFrame.add(getDeckChooser(playerWithFocus), "grow, push"); + if (getDeckChooser(playerWithFocus).getSelectedDeckType().toString().contains(localizer.getMessage("lblRandom"))) { final String strCheckboxConstraints = "h 30px!, gap 0 20px 0 0"; decksFrame.add(cbSingletons, strCheckboxConstraints); decksFrame.add(cbArtifacts, strCheckboxConstraints); @@ -766,16 +658,10 @@ public class VLobby implements ILobbyView { } break; case Commander: - decksFrame.add(commanderDeckChoosers.get(playerWithFocus), "grow, push"); - break; case Oathbreaker: - decksFrame.add(oathbreakerDeckChoosers.get(playerWithFocus), "grow, push"); - break; case TinyLeaders: - decksFrame.add(tinyLeadersDeckChoosers.get(playerWithFocus), "grow, push"); - break; case Brawl: - decksFrame.add(brawlDeckChoosers.get(playerWithFocus), "grow, push"); + decksFrame.add(getDeckChooser(playerWithFocus), "grow, push"); break; case Planechase: decksFrame.add(planarDeckPanels.get(playerWithFocus), "grow, push"); @@ -798,7 +684,13 @@ public class VLobby implements ILobbyView { public LblHeader getLblTitle() { return lblTitle; } public JPanel getConstructedFrame() { return constructedFrame; } public JPanel getPanelStart() { return pnlStart; } - public List getDeckChoosers() { return Collections.unmodifiableList(deckChoosers); } + public List getDeckChoosers() { + List choosers = new ArrayList<>(playerPanels.size()); + for (PlayerPanel playerPanel : playerPanels) { + choosers.add(playerPanel.getDeckChooser()); + } + return choosers; + } /** Gets the random deck checkbox for Singletons. */ FCheckBox getCbSingletons() { return cbSingletons; } @@ -816,29 +708,6 @@ public class VLobby implements ILobbyView { return iPlayer == playerWithFocus; } - public final FDeckChooser getDeckChooser(final int playernum) { - return deckChoosers.get(playernum); - } - - public final FDeckChooser getCommanderDeckChooser(final int playernum) { - return commanderDeckChoosers.get(playernum); - } - - public final FDeckChooser getOathbreakerDeckChooser(final int playernum) { - return oathbreakerDeckChoosers.get(playernum); - } - - public final FDeckChooser getTinyLeaderDeckChooser(final int playernum) { - return tinyLeadersDeckChoosers.get(playernum); - } - - public final FDeckChooser getBrawlDeckChooser(final int playernum) { - return brawlDeckChoosers.get(playernum); - } - - GameType getCurrentGameMode() { - return lobby.getGameType(); - } void setCurrentGameMode(final GameType mode) { lobby.setGameType(mode); update(true); @@ -851,10 +720,6 @@ public class VLobby implements ILobbyView { return true; } - public int getNumPlayers() { - return activePlayersNum; - } - /** Revalidates the player and deck sections. Necessary after adding or hiding any panels. */ private void refreshPanels(final boolean refreshPlayerFrame, final boolean refreshDeckFrame) { if (refreshPlayerFrame) { @@ -888,8 +753,8 @@ public class VLobby implements ILobbyView { /** Saves avatar prefs for players one and two. */ void updateAvatarPrefs() { - final int pOneIndex = playerPanels.get(0).getAvatarIndex(); - final int pTwoIndex = playerPanels.get(1).getAvatarIndex(); + final int pOneIndex = getPlayerPanel(0).getAvatarIndex(); + final int pTwoIndex = getPlayerPanel(1).getAvatarIndex(); prefs.setPref(FPref.UI_AVATARS, pOneIndex + "," + pTwoIndex); prefs.save(); @@ -897,8 +762,8 @@ public class VLobby implements ILobbyView { /** Saves sleeve prefs for players one and two. */ void updateSleevePrefs() { - final int pOneIndex = playerPanels.get(0).getSleeveIndex(); - final int pTwoIndex = playerPanels.get(1).getSleeveIndex(); + final int pOneIndex = getPlayerPanel(0).getSleeveIndex(); + final int pTwoIndex = getPlayerPanel(1).getSleeveIndex(); prefs.setPref(FPref.UI_SLEEVES, pOneIndex + "," + pTwoIndex); prefs.save(); @@ -925,7 +790,6 @@ public class VLobby implements ILobbyView { return usedSleeves; } - private static final ImmutableList genderOptions = ImmutableList.of("Male", "Female", "Any"), typeOptions = ImmutableList.of("Fantasy", "Generic", "Any"); final String getNewName() { @@ -973,18 +837,57 @@ public class VLobby implements ILobbyView { this.variant = variantType; setToolTipText(variantType.getDescription()); - addItemListener(new ItemListener() { - @Override public final void itemStateChanged(final ItemEvent e) { - if (e.getStateChange() == ItemEvent.SELECTED) { - lobby.applyVariant(variantType); - } else { - lobby.removeVariant(variantType); - } + addItemListener(e -> { + if (e.getStateChange() == ItemEvent.SELECTED) { + lobby.applyVariant(variantType); + } else { + lobby.removeVariant(variantType); } + VLobby.this.update(false); }); } } + private FDeckChooser createDeckChooser(final GameType gameType, final int iSlot, final boolean ai) { + switch (gameType) { + case Commander: { + final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.Commander, true); + final DeckType type = iSlot == 0 ? DeckType.COMMANDER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; + fdc.initialize(FPref.COMMANDER_DECK_STATES[iSlot], type); + fdc.getLstDecks().setSelectCommand(() -> selectCommanderDeck(fdc, iSlot)); + return fdc; + } + case TinyLeaders: { + final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.TinyLeaders, true); + final DeckType type = iSlot == 0 ? DeckType.TINY_LEADERS_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; + fdc.initialize(FPref.TINY_LEADER_DECK_STATES[iSlot], type); + fdc.getLstDecks().setSelectCommand(() -> selectTinyLeadersDeck(fdc, iSlot)); + return fdc; + } + case Oathbreaker: { + final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.Oathbreaker, true); + final DeckType type = iSlot == 0 ? DeckType.OATHBREAKER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; + fdc.initialize(FPref.OATHBREAKER_DECK_STATES[iSlot], type); + fdc.getLstDecks().setSelectCommand(() -> selectOathbreakerDeck(fdc, iSlot)); + return fdc; + } + case Brawl: { + final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.Brawl, true); + final DeckType type = iSlot == 0 ? DeckType.BRAWL_DECK : DeckType.CUSTOM_DECK; + fdc.initialize(FPref.BRAWL_DECK_STATES[iSlot], type); + fdc.getLstDecks().setSelectCommand(() -> selectBrawlDeck(fdc, iSlot)); + return fdc; + } + default: { + final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.Constructed, false); + final DeckType type = iSlot == 0 ? DeckType.PRECONSTRUCTED_DECK : DeckType.COLOR_DECK; + fdc.initialize(FPref.CONSTRUCTED_DECK_STATES[iSlot], type); + fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot)); + return fdc; + } + } + } + final ActionListener nameListener = new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { @@ -1007,7 +910,7 @@ public class VLobby implements ILobbyView { } public boolean isPlayerArchenemy(final int playernum) { - return playerPanels.get(playernum).isArchenemy(); + return getPlayerPanel(playernum).isArchenemy(); } /** Gets the list of Vanguard avatar lists. */ @@ -1027,11 +930,6 @@ public class VLobby implements ILobbyView { return vgdAllAvatars; } - /** Return the Vanguard avatars not flagged RemoveDeck:All. */ - public List getAllAiAvatars() { - return vgdAllAiAvatars; - } - /** Return the Vanguard avatars not flagged RemoveDeck:Random. */ public List getNonRandomHumanAvatars() { return nonRandomHumanAvatars; @@ -1055,7 +953,6 @@ public class VLobby implements ILobbyView { } if (!cp.getRules().getAiHints().getRemAIDecks()) { aiListData.add(cp); - vgdAllAiAvatars.add(cp); if (!cp.getRules().getAiHints().getRemRandomDecks()) { nonRandomAiAvatars.add(cp); } From 2abafbc2ee94d74bd781170fd6b99347df7bbc99 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Sun, 18 Dec 2022 23:21:54 -0700 Subject: [PATCH 06/76] Remove print statements. --- forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java index dd08d22aa17..8f0a41248ce 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java @@ -216,7 +216,6 @@ public class VLobby implements ILobbyView { @Override public void update(final int slot, final LobbySlotType type) { - System.err.println("Update:"+slot+" / "+type); final FDeckChooser deckChooser = getPlayerPanel(slot).getDeckChooser(); deckChooser.setIsAi(type==LobbySlotType.AI); @@ -242,8 +241,6 @@ public class VLobby implements ILobbyView { @Override public void update(final boolean fullUpdate) { - System.err.println("Update:"+fullUpdate); - activePlayersNum = lobby.getNumberOfSlots(); addPlayerBtn.setEnabled(activePlayersNum < MAX_PLAYERS); From 53e3899932eaf0dfa8df03347259886d272bea8e Mon Sep 17 00:00:00 2001 From: asvitkine Date: Sun, 18 Dec 2022 23:26:01 -0700 Subject: [PATCH 07/76] Small clean ups. --- .../src/main/java/forge/screens/home/VLobby.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java index 8f0a41248ce..bf831adfb42 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java @@ -216,9 +216,8 @@ public class VLobby implements ILobbyView { @Override public void update(final int slot, final LobbySlotType type) { - final FDeckChooser deckChooser = getPlayerPanel(slot).getDeckChooser(); + final FDeckChooser deckChooser = getDeckChooser(slot); deckChooser.setIsAi(type==LobbySlotType.AI); - DeckType selectedDeckType = deckChooser.getSelectedDeckType(); switch (selectedDeckType){ case STANDARD_CARDGEN_DECK: @@ -573,7 +572,7 @@ public class VLobby implements ILobbyView { } } if (sel.equals("Random")) { - final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, getPlayerPanel(playerIndex).isAi()); + final Deck randomDeck = RandomDeckGenerator.getRandomUserDeck(lobby, isPlayerAI(playerIndex)); planePool = randomDeck.get(DeckSection.Planes); } } else if (selected instanceof Deck) { @@ -615,7 +614,7 @@ public class VLobby implements ILobbyView { if (sel.contains("Use deck's default avatar") && deck != null && deck.has(DeckSection.Avatar)) { vanguardAvatar = deck.get(DeckSection.Avatar).get(0); } else { //Only other string is "Random" - if (getPlayerPanel(playerIndex).isAi()) { //AI + if (isPlayerAI(playerIndex)) { //AI vanguardAvatar = Aggregates.random(getNonRandomAiAvatars()); } else { //Human vanguardAvatar = Aggregates.random(getNonRandomHumanAvatars()); @@ -683,7 +682,7 @@ public class VLobby implements ILobbyView { public JPanel getPanelStart() { return pnlStart; } public List getDeckChoosers() { List choosers = new ArrayList<>(playerPanels.size()); - for (PlayerPanel playerPanel : playerPanels) { + for (final PlayerPanel playerPanel : playerPanels) { choosers.add(playerPanel.getDeckChooser()); } return choosers; From ca54e87eb0cc1474bd95e91f0f701050c9c07a87 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Mon, 19 Dec 2022 07:20:22 -0700 Subject: [PATCH 08/76] Remove more code duplication. --- .../main/java/forge/screens/home/VLobby.java | 94 +++---------------- 1 file changed, 15 insertions(+), 79 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java index bf831adfb42..bda5e1ac450 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java @@ -435,11 +435,11 @@ public class VLobby implements ILobbyView { deckPanels.add(deckPanel); } - private void selectMainDeck(final FDeckChooser mainChooser, final int playerIndex) { - if (hasVariant(GameType.Commander) || hasVariant(GameType.Oathbreaker) || hasVariant(GameType.TinyLeaders) || hasVariant(GameType.Brawl)) { - // These game types use specific deck panel - return; - } + private FDeckChooser getDeckChooser(final int iSlot) { + return getPlayerPanel(iSlot).getDeckChooser(); + } + + private void selectMainDeck(final FDeckChooser mainChooser, final int playerIndex, final boolean isCommanderDeck) { final DeckType type = mainChooser.getSelectedDeckType(); final Deck deck = mainChooser.getDeck(); // something went wrong, clear selection to prevent error loop @@ -449,75 +449,11 @@ public class VLobby implements ILobbyView { final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - getPlayerPanel(playerIndex).setDeckSelectorButtonText(text); - fireDeckChangeListener(playerIndex, deck); - } - mainChooser.saveState(); - } - - private FDeckChooser getDeckChooser(final int iSlot) { - return getPlayerPanel(iSlot).getDeckChooser(); - } - - private void selectCommanderDeck(final FDeckChooser mainChooser, final int playerIndex) { - if (!hasVariant(GameType.Commander) && !hasVariant(GameType.Oathbreaker) && !hasVariant(GameType.TinyLeaders) && !hasVariant(GameType.Brawl)) { - // Only these game types use this specific deck panel - return; - } - final DeckType type = mainChooser.getSelectedDeckType(); - final Deck deck = mainChooser.getDeck(); - final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); - if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { - final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - getPlayerPanel(playerIndex).setCommanderDeckSelectorButtonText(text); - fireDeckChangeListener(playerIndex, deck); - } - mainChooser.saveState(); - } - - private void selectOathbreakerDeck(final FDeckChooser mainChooser, final int playerIndex) { - if (!hasVariant(GameType.Oathbreaker)) { - // Only these game types use this specific deck panel - return; - } - final DeckType type = mainChooser.getSelectedDeckType(); - final Deck deck = mainChooser.getDeck(); - final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); - if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { - final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - getPlayerPanel(playerIndex).setCommanderDeckSelectorButtonText(text); - fireDeckChangeListener(playerIndex, deck); - } - mainChooser.saveState(); - } - - private void selectTinyLeadersDeck(final FDeckChooser mainChooser, final int playerIndex) { - if (!hasVariant(GameType.TinyLeaders)) { - // Only these game types use this specific deck panel - return; - } - final DeckType type = mainChooser.getSelectedDeckType(); - final Deck deck = mainChooser.getDeck(); - final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); - if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { - final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - getPlayerPanel(playerIndex).setCommanderDeckSelectorButtonText(text); - fireDeckChangeListener(playerIndex, deck); - } - mainChooser.saveState(); - } - - private void selectBrawlDeck(final FDeckChooser mainChooser,final int playerIndex) { - if (!hasVariant(GameType.Brawl)) { - // Only these game types use this specific deck panel - return; - } - final DeckType type = mainChooser.getSelectedDeckType(); - final Deck deck = mainChooser.getDeck(); - final Collection selectedDecks = mainChooser.getLstDecks().getSelectedItems(); - if (playerIndex < activePlayersNum && lobby.mayEdit(playerIndex)) { - final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - getPlayerPanel(playerIndex).setCommanderDeckSelectorButtonText(text); + if (isCommanderDeck) { + getPlayerPanel(playerIndex).setCommanderDeckSelectorButtonText(text); + } else { + getPlayerPanel(playerIndex).setDeckSelectorButtonText(text); + } fireDeckChangeListener(playerIndex, deck); } mainChooser.saveState(); @@ -850,35 +786,35 @@ public class VLobby implements ILobbyView { final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.Commander, true); final DeckType type = iSlot == 0 ? DeckType.COMMANDER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; fdc.initialize(FPref.COMMANDER_DECK_STATES[iSlot], type); - fdc.getLstDecks().setSelectCommand(() -> selectCommanderDeck(fdc, iSlot)); + fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, true)); return fdc; } case TinyLeaders: { final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.TinyLeaders, true); final DeckType type = iSlot == 0 ? DeckType.TINY_LEADERS_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; fdc.initialize(FPref.TINY_LEADER_DECK_STATES[iSlot], type); - fdc.getLstDecks().setSelectCommand(() -> selectTinyLeadersDeck(fdc, iSlot)); + fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, true)); return fdc; } case Oathbreaker: { final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.Oathbreaker, true); final DeckType type = iSlot == 0 ? DeckType.OATHBREAKER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; fdc.initialize(FPref.OATHBREAKER_DECK_STATES[iSlot], type); - fdc.getLstDecks().setSelectCommand(() -> selectOathbreakerDeck(fdc, iSlot)); + fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, true)); return fdc; } case Brawl: { final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.Brawl, true); final DeckType type = iSlot == 0 ? DeckType.BRAWL_DECK : DeckType.CUSTOM_DECK; fdc.initialize(FPref.BRAWL_DECK_STATES[iSlot], type); - fdc.getLstDecks().setSelectCommand(() -> selectBrawlDeck(fdc, iSlot)); + fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, true)); return fdc; } default: { final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.Constructed, false); final DeckType type = iSlot == 0 ? DeckType.PRECONSTRUCTED_DECK : DeckType.COLOR_DECK; fdc.initialize(FPref.CONSTRUCTED_DECK_STATES[iSlot], type); - fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot)); + fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, false)); return fdc; } } From 962244de1bca157fb19c872890acf21cb9b47a7d Mon Sep 17 00:00:00 2001 From: JohnWilliams77 <103562494+JohnWilliams77@users.noreply.github.com> Date: Mon, 19 Dec 2022 14:53:54 +0000 Subject: [PATCH 09/76] Update aisha_of_sparks_and_smoke.txt --- .../res/cardsfolder/upcoming/aisha_of_sparks_and_smoke.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/upcoming/aisha_of_sparks_and_smoke.txt b/forge-gui/res/cardsfolder/upcoming/aisha_of_sparks_and_smoke.txt index 3fb3564aef7..f5003cac6fa 100644 --- a/forge-gui/res/cardsfolder/upcoming/aisha_of_sparks_and_smoke.txt +++ b/forge-gui/res/cardsfolder/upcoming/aisha_of_sparks_and_smoke.txt @@ -4,7 +4,7 @@ Types:Legendary Creature Human Warrior PT:4/2 K:Prowess A:AB$ Pump | Cost$ RW | Defined$ Self | KW$ First Strike | SpellDescription$ NICKNAME gains first strike until end of turn. -T:Mode$ DamageDealtOnce | CombatDamage$ True | ValidSource$ Card.Self | Execute$ TrigCast | OptionalDecider$ You | TriggerDescription$ When NICKNAME deals combat damage, you may cast a sorcery spell from your hand with mana value less than or equal to that damage without paying its mana cost. +T:Mode$ DamageDealtOnce | CombatDamage$ True | ValidSource$ Card.Self | Execute$ TrigCast | OptionalDecider$ You | TriggerDescription$ Whenever NICKNAME deals combat damage, you may cast a sorcery spell from your hand with mana value less than or equal to that damage without paying its mana cost. SVar:TrigCast:DB$ Play | Valid$ Sorcery.YouOwn+cmcLEX | ValidSA$ Spell | ValidZone$ Hand | WithoutManaCost$ True | Amount$ 1 | Optional$ True SVar:X:TriggerCount$DamageAmount DeckHints:Type$Sorcery From 439b1a83c68d6c1b59438211db763277add1fc04 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Mon, 19 Dec 2022 10:18:55 -0700 Subject: [PATCH 10/76] Cache deckchoosers to avoid re-randomizing decks. --- .../main/java/forge/screens/home/VLobby.java | 78 ++++++++++--------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java index bda5e1ac450..5c53a6a858a 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java @@ -6,7 +6,9 @@ import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Vector; import javax.swing.*; @@ -102,6 +104,8 @@ public class VLobby implements ILobbyView { private final JPanel playersFrame = new JPanel(new MigLayout("insets 0, gap 0 5, wrap, hidemode 3")); private final FScrollPanel playersScroll = new FScrollPanel(new MigLayout("insets 0, gap 0, wrap, hidemode 3"), true); private final List playerPanels = new ArrayList<>(MAX_PLAYERS); + // Cache deck choosers so switching settings doesn't re-generate random decks. + private final Map cachedDeckChoosers = new HashMap<>(); private final FLabel addPlayerBtn = new FLabel.ButtonBuilder().fontSize(14).text(localizer.getMessage("lblAddAPlayer")).build(); @@ -780,44 +784,44 @@ public class VLobby implements ILobbyView { } } - private FDeckChooser createDeckChooser(final GameType gameType, final int iSlot, final boolean ai) { - switch (gameType) { - case Commander: { - final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.Commander, true); - final DeckType type = iSlot == 0 ? DeckType.COMMANDER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; - fdc.initialize(FPref.COMMANDER_DECK_STATES[iSlot], type); - fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, true)); - return fdc; - } - case TinyLeaders: { - final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.TinyLeaders, true); - final DeckType type = iSlot == 0 ? DeckType.TINY_LEADERS_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; - fdc.initialize(FPref.TINY_LEADER_DECK_STATES[iSlot], type); - fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, true)); - return fdc; - } - case Oathbreaker: { - final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.Oathbreaker, true); - final DeckType type = iSlot == 0 ? DeckType.OATHBREAKER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; - fdc.initialize(FPref.OATHBREAKER_DECK_STATES[iSlot], type); - fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, true)); - return fdc; - } - case Brawl: { - final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.Brawl, true); - final DeckType type = iSlot == 0 ? DeckType.BRAWL_DECK : DeckType.CUSTOM_DECK; - fdc.initialize(FPref.BRAWL_DECK_STATES[iSlot], type); - fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, true)); - return fdc; - } - default: { - final FDeckChooser fdc = new FDeckChooser(null, ai, GameType.Constructed, false); - final DeckType type = iSlot == 0 ? DeckType.PRECONSTRUCTED_DECK : DeckType.COLOR_DECK; - fdc.initialize(FPref.CONSTRUCTED_DECK_STATES[iSlot], type); - fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, false)); - return fdc; - } + private FDeckChooser createDeckChooser(final GameType type, final int iSlot, final boolean ai) { + boolean forCommander; + DeckType deckType; + FPref prefKey; + switch (type) { + case Commander: + forCommander = true; + deckType = iSlot == 0 ? DeckType.COMMANDER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; + prefKey = FPref.COMMANDER_DECK_STATES[iSlot]; + break; + case TinyLeaders: + forCommander = true; + deckType = iSlot == 0 ? DeckType.TINY_LEADERS_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; + prefKey = FPref.TINY_LEADER_DECK_STATES[iSlot]; + break; + case Oathbreaker: + forCommander = true; + deckType = iSlot == 0 ? DeckType.OATHBREAKER_DECK : DeckType.RANDOM_CARDGEN_COMMANDER_DECK; + prefKey = FPref.OATHBREAKER_DECK_STATES[iSlot]; + break; + case Brawl: + forCommander = true; + deckType = iSlot == 0 ? DeckType.BRAWL_DECK : DeckType.CUSTOM_DECK; + prefKey = FPref.BRAWL_DECK_STATES[iSlot]; + break; + default: + forCommander = false; + deckType = iSlot == 0 ? DeckType.PRECONSTRUCTED_DECK : DeckType.COLOR_DECK; + prefKey = FPref.CONSTRUCTED_DECK_STATES[iSlot]; + break; } + return cachedDeckChoosers.computeIfAbsent(prefKey, (key) -> { + final GameType gameType = forCommander ? type : GameType.Constructed; + final FDeckChooser fdc = new FDeckChooser(null, ai, gameType, forCommander); + fdc.initialize(prefKey, deckType); + fdc.getLstDecks().setSelectCommand(() -> selectMainDeck(fdc, iSlot, forCommander)); + return fdc; + }); } final ActionListener nameListener = new ActionListener() { From 56b22ee73a73c7f1cd09f63e8e94cb944a28e338 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Thu, 22 Dec 2022 21:40:52 -0700 Subject: [PATCH 11/76] Simulated AI: Fix land pruning logic and skip invalid targets. This change improves Simulated AI logic by eliminating many unnecessary simulations, particularly due to: - Invalid targets involving counterspells countering themselves (including modal spells). - Pruning identical land drop decisions. Tests are added to cover these cases. Some core logic is changed, in particular, SpellAbility.canTarget() was not rejecting self-targeting for counterspells. This was likely being done at a higher level somewhere (e.g. in UI code for choosing targets for the human player or AI-specific code for non-simulated AI). Additionally, a convenience SpellAbility.hasLegalTargets() method is added, from the logic that was previously in AIController.java, so that it can be re-used by the simulation AI code. A few small style clean ups are included in the code being changed. --- .../src/main/java/forge/ai/AiController.java | 9 +- .../forge/ai/simulation/GameSimulator.java | 14 ++-- .../SpellAbilityChoicesIterator.java | 44 ++++++---- .../ai/simulation/SpellAbilityPicker.java | 38 +++++---- .../forge/game/spellability/SpellAbility.java | 17 +++- .../SpellAbilityPickerSimulationTest.java | 83 +++++++++++++++++++ 6 files changed, 159 insertions(+), 46 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 9c1cc7cd8aa..4b7f89f3e88 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -903,13 +903,8 @@ public class AiController { if (sa instanceof SpellPermanent) { return canPlayFromEffectAI((SpellPermanent)sa, false, true); } - if (sa.usesTargeting()) { - if (!sa.isTargetNumberValid() && sa.getTargetRestrictions().getNumCandidates(sa, true) == 0) { - return AiPlayDecision.TargetingFailed; - } - if (!StaticAbilityMustTarget.meetsMustTargetRestriction(sa)) { - return AiPlayDecision.TargetingFailed; - } + if (sa.usesTargeting() && !sa.hasLegalTargets()) { + return AiPlayDecision.TargetingFailed; } if (sa instanceof Spell) { if (!player.cantLoseForZeroOrLessLife() && player.canLoseLife() && diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java index 0cbd88374b5..847a69067f7 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java @@ -1,6 +1,9 @@ package forge.ai.simulation; +import forge.game.ability.ApiType; +import forge.game.ability.effects.CharmEffect; import forge.game.spellability.LandAbility; +import forge.game.zone.ZoneType; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -198,13 +201,10 @@ public class GameSimulator { final SpellAbility playingSa = sa; simGame.copyLastState(); - boolean success = ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, new Runnable() { - @Override - public void run() { - if (interceptor != null) { - interceptor.announceX(playingSa); - interceptor.chooseTargets(playingSa, GameSimulator.this); - } + boolean success = ComputerUtil.handlePlayingSpellAbility(aiPlayer, sa, simGame, () -> { + if (interceptor != null) { + interceptor.announceX(playingSa); + interceptor.chooseTargets(playingSa, GameSimulator.this); } }); if (!success) { diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java index b2294c0cf39..c1972564bf1 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java @@ -16,7 +16,7 @@ import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; public class SpellAbilityChoicesIterator { - private SimulationController controller; + private final SimulationController controller; private Iterator modeIterator; private int[] selectedModes; @@ -34,11 +34,13 @@ public class SpellAbilityChoicesIterator { Card selectedChoice; Score bestScoreForChoice = new Score(Integer.MIN_VALUE); } - private ArrayList choicePoints = new ArrayList<>(); + private final ArrayList choicePoints = new ArrayList<>(); private int incrementedCpIndex = 0; private int cpIndex = -1; private int evalDepth; + // Maps from filtered mode indexes to original ones. + private List modesMap; public SpellAbilityChoicesIterator(SimulationController controller) { this.controller = controller; @@ -46,17 +48,26 @@ public class SpellAbilityChoicesIterator { public List chooseModesForAbility(List choices, int min, int num, boolean allowRepeat) { if (modeIterator == null) { - // TODO: Need to skip modes that are invalid (e.g. targets don't exist)! + // Skip modes that don't have legal targets. + modesMap = new ArrayList<>(); + int origIndex = -1; + for (AbilitySub sub : choices) { + origIndex++; + if (sub.usesTargeting() && !sub.hasLegalTargets()) { + continue; + } + modesMap.add(origIndex); + } // TODO: Do we need to do something special to support cards that have extra costs // when choosing more modes, like Blessed Alliance? if (!allowRepeat) { - modeIterator = CombinatoricsUtils.combinationsIterator(choices.size(), num); + modeIterator = CombinatoricsUtils.combinationsIterator(modesMap.size(), num); } else { // Note: When allowRepeat is true, it does result in many possibilities being tried. // We should ideally prune some of those at a higher level. - modeIterator = new AllowRepeatModesIterator(choices.size(), min, num); + modeIterator = new AllowRepeatModesIterator(modesMap.size(), min, num); } - selectedModes = modeIterator.next(); + selectedModes = remapModes(modeIterator.next()); advancedToNextMode = true; } // Note: If modeIterator already existed, selectedModes would have been updated in advance(). @@ -78,6 +89,13 @@ public class SpellAbilityChoicesIterator { return result; } + private int[] remapModes(int[] modes) { + for (int i = 0; i < modes.length; i++) { + modes[i] = modesMap.get(modes[i]); + } + return modes; + } + public Card chooseCard(CardCollection fetchList) { cpIndex++; if (cpIndex >= choicePoints.size()) { @@ -86,8 +104,7 @@ public class SpellAbilityChoicesIterator { ChoicePoint cp = choicePoints.get(cpIndex); // Prune duplicates. HashSet uniqueCards = new HashSet<>(); - for (int i = 0; i < fetchList.size(); i++) { - Card card = fetchList.get(i); + for (Card card : fetchList) { if (uniqueCards.add(card.getName()) && uniqueCards.size() == cp.nextChoice + 1) { cp.selectedChoice = card; } @@ -137,14 +154,9 @@ public class SpellAbilityChoicesIterator { evalDepth++; pushTarget = false; } - return; } } - public int[] getSelectModes() { - return selectedModes; - } - public boolean advance(Score lastScore) { cpIndex = -1; for (ChoicePoint cp : choicePoints) { @@ -195,7 +207,7 @@ public class SpellAbilityChoicesIterator { doneEvaluating(bestScoreForMode); bestScoreForMode = new Score(Integer.MIN_VALUE); if (modeIterator.hasNext()) { - selectedModes = modeIterator.next(); + selectedModes = remapModes(modeIterator.next()); advancedToNextMode = true; return true; } @@ -232,8 +244,8 @@ public class SpellAbilityChoicesIterator { } private static class AllowRepeatModesIterator implements Iterator { - private int numChoices; - private int max; + private final int numChoices; + private final int max; private int[] indexes; public AllowRepeatModesIterator(int numChoices, int min, int max) { diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java index 03885c8fd4d..11ba01dc2c6 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java @@ -39,6 +39,7 @@ public class SpellAbilityPicker { private SpellAbilityChoicesIterator interceptor; private Plan plan; + private int numSimulations; public SpellAbilityPicker(Game game, Player player) { this.game = game; @@ -66,19 +67,7 @@ public class SpellAbilityPicker { private List getCandidateSpellsAndAbilities() { CardCollection cards = ComputerUtilAbility.getAvailableCards(game, player); List all = ComputerUtilAbility.getSpellAbilities(cards, player); - CardCollection landsToPlay = ComputerUtilAbility.getAvailableLandsToPlay(game, player); - if (landsToPlay != null) { - HashMap landsDeDupe = new HashMap<>(); - for (Card land : landsToPlay) { - Card previousLand = landsDeDupe.get(land.getName()); - // Skip identical lands. - if (previousLand != null && previousLand.getZone() == land.getZone() && previousLand.getOwner() == land.getOwner()) { - continue; - } - landsDeDupe.put(land.getName(), land); - all.add(new LandAbility(land)); - } - } + HashMap landsDeDupe = new HashMap<>(); List candidateSAs = ComputerUtilAbility.getOriginalAndAltCostAbilities(all, player); int writeIndex = 0; for (int i = 0; i < candidateSAs.size(); i++) { @@ -86,6 +75,16 @@ public class SpellAbilityPicker { if (sa.isManaAbility()) { continue; } + // Skip identical lands. + if (sa instanceof LandAbility) { + Card land = sa.getHostCard(); + Card previousLand = landsDeDupe.get(sa.getHostCard().getName()); + if (previousLand != null && previousLand.getZone() == land.getZone() && + previousLand.getOwner() == land.getOwner()) { + continue; + } + landsDeDupe.put(land.getName(), land); + } sa.setActivatingPlayer(player, true); AiPlayDecision opinion = canPlayAndPayForSim(sa); @@ -95,7 +94,7 @@ public class SpellAbilityPicker { if (opinion != AiPlayDecision.WillPlay) continue; - candidateSAs.set(writeIndex, sa); + candidateSAs.set(writeIndex, sa); writeIndex++; } candidateSAs.subList(writeIndex, candidateSAs.size()).clear(); @@ -353,7 +352,9 @@ public class SpellAbilityPicker { if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) { return AiPlayDecision.CantAfford; } - + if (sa.usesTargeting() && !sa.hasLegalTargets()) { + return AiPlayDecision.TargetingFailed; + } if (shouldWaitForLater(sa)) { return AiPlayDecision.AnotherTime; } @@ -375,10 +376,13 @@ public class SpellAbilityPicker { final SpellAbilityChoicesIterator choicesIterator = new SpellAbilityChoicesIterator(controller); Score lastScore; do { + // TODO: MyRandom should be an instance on the game object, so that we could do + // simulations in parallel without messing up global state. MyRandom.setRandom(new Random(randomSeedToUse)); GameSimulator simulator = new GameSimulator(controller, game, player, phase); simulator.setInterceptor(choicesIterator); lastScore = simulator.simulateSpellAbility(sa); + numSimulations++; if (lastScore.value > bestScore.value) { bestScore = lastScore; } @@ -462,4 +466,8 @@ public class SpellAbilityPicker { } return ComputerUtil.chooseSacrificeType(player, type, ability, ability.getTargetCard(), effect, amount, exclude); } + + public int getNumSimulations() { + return numSimulations; + } } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index d5d4b01d726..d71bf584e7d 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1254,10 +1254,15 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return false; } - final TargetRestrictions tr = getTargetRestrictions(); + final SpellAbility rootAbility = this.getRootAbility(); + // 115.5. A spell or ability on the stack is an illegal target for itself. + if (rootAbility.isSpell() && rootAbility.getHostCard() == entity) { + return false; + } // Restriction related to this ability if (usesTargeting()) { + final TargetRestrictions tr = getTargetRestrictions(); if (tr.isUniqueTargets() && getUniqueTargets().contains(entity)) return false; @@ -1649,6 +1654,16 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return targetRestrictions != null; } + public boolean hasLegalTargets() { + if (!isTargetNumberValid() && getTargetRestrictions().getNumCandidates(this, true) == 0) { + return false; + } + if (!StaticAbilityMustTarget.meetsMustTargetRestriction(this)) { + return false; + } + return true; + } + public TargetRestrictions getTargetRestrictions() { return targetRestrictions; } diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java index 8aa56bc5053..29b8a3d3b01 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java @@ -483,4 +483,87 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest { AssertJUnit.assertEquals("Chaos Warp", sa.getHostCard().getName()); AssertJUnit.assertEquals(expectedTarget, sa.getTargetCard()); } + + @Test + public void testNoSimulationsWhenNoTargets() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(1); + + addCard("Island", p); + addCard("Island", p); + addCardToZone("Counterspell", p, ZoneType.Hand); + addCardToZone("Unsummon", p, ZoneType.Hand); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + game.getAction().checkStateEffects(true); + + SpellAbilityPicker picker = new SpellAbilityPicker(game, p); + SpellAbility sa = picker.chooseSpellAbilityToPlay(null); + AssertJUnit.assertNull(sa); + AssertJUnit.assertEquals(0, picker.getNumSimulations()); + } + + @Test + public void testLandDropPruning() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(1); + + addCardToZone("Island", p, ZoneType.Hand); + addCardToZone("Island", p, ZoneType.Hand); + addCardToZone("Island", p, ZoneType.Hand); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + game.getAction().checkStateEffects(true); + + SpellAbilityPicker picker = new SpellAbilityPicker(game, p); + SpellAbility sa = picker.chooseSpellAbilityToPlay(null); + AssertJUnit.assertNotNull(sa); + // Only one land drop should be simulated, since the cards are identical. + AssertJUnit.assertEquals(1, picker.getNumSimulations()); + } + + @Test + public void testSpellCantTargetSelf() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(1); + Player opponent = game.getPlayers().get(0); + + addCardToZone("Unsubstantiate", p, ZoneType.Hand); + addCard("Forest", p); + addCard("Island", p); + Card expectedTarget = addCard("Flying Men", opponent); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + game.getAction().checkStateEffects(true); + + SpellAbilityPicker picker = new SpellAbilityPicker(game, p); + SpellAbility sa = picker.chooseSpellAbilityToPlay(null); + AssertJUnit.assertNotNull(sa); + AssertJUnit.assertEquals(expectedTarget, sa.getTargetCard()); + // Only a single simulation expected (no target self). + AssertJUnit.assertEquals(1, picker.getNumSimulations()); + } + + @Test + public void testModalSpellCantTargetSelf() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(1); + Player opponent = game.getPlayers().get(0); + + addCardToZone("Decisive Denial", p, ZoneType.Hand); + addCard("Forest", p); + addCard("Island", p); + addCard("Runeclaw Bear", p); + addCard("Flying Men", opponent); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + game.getAction().checkStateEffects(true); + + SpellAbilityPicker picker = new SpellAbilityPicker(game, p); + SpellAbility sa = picker.chooseSpellAbilityToPlay(null); + AssertJUnit.assertNotNull(sa); + // Expected: Runeclaw Bear fights Flying Men + // Only a single simulation expected (no target self). + AssertJUnit.assertEquals(1, picker.getNumSimulations()); + } } From e797c358bf3174093aa81fadcb82ee206d656b74 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Thu, 22 Dec 2022 21:44:27 -0700 Subject: [PATCH 12/76] Add a comment. --- .../src/main/java/forge/game/spellability/SpellAbility.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index d71bf584e7d..3b398c05f53 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1256,6 +1256,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit final SpellAbility rootAbility = this.getRootAbility(); // 115.5. A spell or ability on the stack is an illegal target for itself. + // (This covers the spell case.) if (rootAbility.isSpell() && rootAbility.getHostCard() == entity) { return false; } From 24c56f4731068925cf2920d6e29060ca5f27cedb Mon Sep 17 00:00:00 2001 From: asvitkine Date: Thu, 22 Dec 2022 21:58:40 -0700 Subject: [PATCH 13/76] Use empty iterator if all modes filtered out. --- .../forge/ai/simulation/SpellAbilityChoicesIterator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java index c1972564bf1..0650c9c98bb 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java @@ -1,6 +1,7 @@ package forge.ai.simulation; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -60,7 +61,9 @@ public class SpellAbilityChoicesIterator { } // TODO: Do we need to do something special to support cards that have extra costs // when choosing more modes, like Blessed Alliance? - if (!allowRepeat) { + if (modesMap.size() == 0) { + modeIterator = Collections.emptyIterator(); + } else if (!allowRepeat) { modeIterator = CombinatoricsUtils.combinationsIterator(modesMap.size(), num); } else { // Note: When allowRepeat is true, it does result in many possibilities being tried. From 4a74c936c2134eb4242b8dd708de9d83ab2536a9 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Thu, 22 Dec 2022 22:04:30 -0700 Subject: [PATCH 14/76] Return null modes when all invalid. --- .../java/forge/ai/simulation/SpellAbilityChoicesIterator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java index 0650c9c98bb..46ef273c6b9 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java @@ -61,8 +61,8 @@ public class SpellAbilityChoicesIterator { } // TODO: Do we need to do something special to support cards that have extra costs // when choosing more modes, like Blessed Alliance? - if (modesMap.size() == 0) { - modeIterator = Collections.emptyIterator(); + if (modesMap.isEmpty()) { + return null; } else if (!allowRepeat) { modeIterator = CombinatoricsUtils.combinationsIterator(modesMap.size(), num); } else { From 7da360584945db830b1178f064433ba0dce7a5c6 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Thu, 22 Dec 2022 22:04:39 -0700 Subject: [PATCH 15/76] . --- .../java/forge/ai/simulation/SpellAbilityChoicesIterator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java index 46ef273c6b9..bbc2cead20f 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java @@ -1,7 +1,6 @@ package forge.ai.simulation; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; From 74a37c95c46a21f23222839fec0371d1625172f7 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Fri, 23 Dec 2022 20:00:56 -0700 Subject: [PATCH 16/76] Remove unused imports. --- forge-ai/src/main/java/forge/ai/AiController.java | 1 - forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java | 3 --- 2 files changed, 4 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 4b7f89f3e88..79a02420d0b 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -54,7 +54,6 @@ import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementType; import forge.game.spellability.*; import forge.game.staticability.StaticAbility; -import forge.game.staticability.StaticAbilityMustTarget; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerType; import forge.game.trigger.WrappedAbility; diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java index 847a69067f7..2b6b0f120b9 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameSimulator.java @@ -1,9 +1,6 @@ package forge.ai.simulation; -import forge.game.ability.ApiType; -import forge.game.ability.effects.CharmEffect; import forge.game.spellability.LandAbility; -import forge.game.zone.ZoneType; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; From eb0f426adcd14d1265771f7f68607b7967b170cc Mon Sep 17 00:00:00 2001 From: asvitkine Date: Fri, 23 Dec 2022 20:28:05 -0700 Subject: [PATCH 17/76] Use ComputerUtilAbility.isFullyTargetable() and add more tests. --- .../src/main/java/forge/ai/AiController.java | 10 ++++- .../SpellAbilityChoicesIterator.java | 3 +- .../ai/simulation/SpellAbilityPicker.java | 2 +- .../forge/game/spellability/SpellAbility.java | 10 ----- .../SpellAbilityPickerSimulationTest.java | 41 +++++++++++++++++++ 5 files changed, 52 insertions(+), 14 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 79a02420d0b..9c1cc7cd8aa 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -54,6 +54,7 @@ import forge.game.replacement.ReplacementLayer; import forge.game.replacement.ReplacementType; import forge.game.spellability.*; import forge.game.staticability.StaticAbility; +import forge.game.staticability.StaticAbilityMustTarget; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerType; import forge.game.trigger.WrappedAbility; @@ -902,8 +903,13 @@ public class AiController { if (sa instanceof SpellPermanent) { return canPlayFromEffectAI((SpellPermanent)sa, false, true); } - if (sa.usesTargeting() && !sa.hasLegalTargets()) { - return AiPlayDecision.TargetingFailed; + if (sa.usesTargeting()) { + if (!sa.isTargetNumberValid() && sa.getTargetRestrictions().getNumCandidates(sa, true) == 0) { + return AiPlayDecision.TargetingFailed; + } + if (!StaticAbilityMustTarget.meetsMustTargetRestriction(sa)) { + return AiPlayDecision.TargetingFailed; + } } if (sa instanceof Spell) { if (!player.cantLoseForZeroOrLessLife() && player.canLoseLife() && diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java index bbc2cead20f..fbf17632d44 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityChoicesIterator.java @@ -1,5 +1,6 @@ package forge.ai.simulation; +import forge.ai.ComputerUtilAbility; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -53,7 +54,7 @@ public class SpellAbilityChoicesIterator { int origIndex = -1; for (AbilitySub sub : choices) { origIndex++; - if (sub.usesTargeting() && !sub.hasLegalTargets()) { + if (!ComputerUtilAbility.isFullyTargetable(sub)) { continue; } modesMap.add(origIndex); diff --git a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java index 11ba01dc2c6..e02e90c6be6 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java +++ b/forge-ai/src/main/java/forge/ai/simulation/SpellAbilityPicker.java @@ -352,7 +352,7 @@ public class SpellAbilityPicker { if (!ComputerUtilCost.canPayCost(sa, player, sa.isTrigger())) { return AiPlayDecision.CantAfford; } - if (sa.usesTargeting() && !sa.hasLegalTargets()) { + if (!ComputerUtilAbility.isFullyTargetable(sa)) { return AiPlayDecision.TargetingFailed; } if (shouldWaitForLater(sa)) { diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 3b398c05f53..a86cdcf21e5 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -1655,16 +1655,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit return targetRestrictions != null; } - public boolean hasLegalTargets() { - if (!isTargetNumberValid() && getTargetRestrictions().getNumCandidates(this, true) == 0) { - return false; - } - if (!StaticAbilityMustTarget.meetsMustTargetRestriction(this)) { - return false; - } - return true; - } - public TargetRestrictions getTargetRestrictions() { return targetRestrictions; } diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java index 29b8a3d3b01..80dbba2f80b 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java @@ -566,4 +566,45 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest { // Only a single simulation expected (no target self). AssertJUnit.assertEquals(1, picker.getNumSimulations()); } + + @Test + public void testModalSpellNoTargetsForModeWithSubAbility() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(1); + Player opponent = game.getPlayers().get(0); + + addCardToZone("Temur Charm", p, ZoneType.Hand); + addCard("Forest", p); + addCard("Island", p); + addCard("Mountain", p); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + game.getAction().checkStateEffects(true); + + SpellAbilityPicker picker = new SpellAbilityPicker(game, p); + SpellAbility sa = picker.chooseSpellAbilityToPlay(null); + // Only mode "Creatures with power 3 or less can’t block this turn" should be simulated. + AssertJUnit.assertEquals(1, picker.getNumSimulations()); + } + + @Test + public void testModalSpellNoTargetsForAnyModes() { + Game game = initAndCreateGame(); + Player p = game.getPlayers().get(1); + Player opponent = game.getPlayers().get(0); + + addCardToZone("Drown in the Loch", p, ZoneType.Hand); + addCard("Swamp", p); + addCard("Island", p); + + game.getPhaseHandler().devModeSet(PhaseType.MAIN2, p); + game.getAction().checkStateEffects(true); + + SpellAbilityPicker picker = new SpellAbilityPicker(game, p); + SpellAbility sa = picker.chooseSpellAbilityToPlay(null); + // TODO: Ideally, this would be 0 simulations, but we currently only determine there are no + // valid in SpellAbilityChoicesIterator, which runs once we're already simulating the spell. + // Still, this test case exercises the code path and ensures we don't crash in this case. + AssertJUnit.assertEquals(1, picker.getNumSimulations()); + } } From e788d3683ae1544528c7cf3905ce389f55f8a44a Mon Sep 17 00:00:00 2001 From: asvitkine Date: Fri, 23 Dec 2022 20:31:41 -0700 Subject: [PATCH 18/76] Improve comment. --- .../forge/ai/simulation/SpellAbilityPickerSimulationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java index 80dbba2f80b..d6bf58ceba4 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java @@ -603,7 +603,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest { SpellAbilityPicker picker = new SpellAbilityPicker(game, p); SpellAbility sa = picker.chooseSpellAbilityToPlay(null); // TODO: Ideally, this would be 0 simulations, but we currently only determine there are no - // valid in SpellAbilityChoicesIterator, which runs once we're already simulating the spell. + // valid modes in SpellAbilityChoicesIterator, which runs already when we're simulating. // Still, this test case exercises the code path and ensures we don't crash in this case. AssertJUnit.assertEquals(1, picker.getNumSimulations()); } From 4c568f941f246013fcbabbf5463c73175e62c41d Mon Sep 17 00:00:00 2001 From: asvitkine Date: Fri, 23 Dec 2022 20:33:01 -0700 Subject: [PATCH 19/76] Clean up unused vars. --- .../ai/simulation/SpellAbilityPickerSimulationTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java index d6bf58ceba4..67780d024a3 100644 --- a/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java +++ b/forge-gui-desktop/src/test/java/forge/ai/simulation/SpellAbilityPickerSimulationTest.java @@ -571,7 +571,6 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest { public void testModalSpellNoTargetsForModeWithSubAbility() { Game game = initAndCreateGame(); Player p = game.getPlayers().get(1); - Player opponent = game.getPlayers().get(0); addCardToZone("Temur Charm", p, ZoneType.Hand); addCard("Forest", p); @@ -582,7 +581,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest { game.getAction().checkStateEffects(true); SpellAbilityPicker picker = new SpellAbilityPicker(game, p); - SpellAbility sa = picker.chooseSpellAbilityToPlay(null); + picker.chooseSpellAbilityToPlay(null); // Only mode "Creatures with power 3 or less can’t block this turn" should be simulated. AssertJUnit.assertEquals(1, picker.getNumSimulations()); } @@ -591,7 +590,6 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest { public void testModalSpellNoTargetsForAnyModes() { Game game = initAndCreateGame(); Player p = game.getPlayers().get(1); - Player opponent = game.getPlayers().get(0); addCardToZone("Drown in the Loch", p, ZoneType.Hand); addCard("Swamp", p); @@ -601,7 +599,7 @@ public class SpellAbilityPickerSimulationTest extends SimulationTest { game.getAction().checkStateEffects(true); SpellAbilityPicker picker = new SpellAbilityPicker(game, p); - SpellAbility sa = picker.chooseSpellAbilityToPlay(null); + picker.chooseSpellAbilityToPlay(null); // TODO: Ideally, this would be 0 simulations, but we currently only determine there are no // valid modes in SpellAbilityChoicesIterator, which runs already when we're simulating. // Still, this test case exercises the code path and ensures we don't crash in this case. From d77b575ecd384b3df9307a4cbf02ec6d27813743 Mon Sep 17 00:00:00 2001 From: JohnWilliams77 <103562494+JohnWilliams77@users.noreply.github.com> Date: Sat, 24 Dec 2022 09:03:47 +0000 Subject: [PATCH 20/76] Update Secret Lair Drop Series.txt --- forge-gui/res/editions/Secret Lair Drop Series.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 2498c683a38..99653653ec8 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -612,6 +612,7 @@ ScryfallCode=SLD 648 R Gemhide Sliver @John Matson 649 R Horned Sliver @Allen Williams 650 R Manaweft Sliver @Trevor Claxton +651 R Megantic Sliver @Ryan Barger 652 R Might Sliver @Jeff Miracola 653 R Muscle Sliver @Richard Kane Ferguson 654 R Predatory Sliver @Mathias Kollros From cd2582408b0f0a96b05b8b3a2ede3483aca05cfb Mon Sep 17 00:00:00 2001 From: George Date: Sat, 24 Dec 2022 13:09:39 +0200 Subject: [PATCH 21/76] Correct the update of POIChanges for bought cards from shops --- .../forge/adventure/pointofintrest/PointOfInterestChanges.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java b/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java index ec5a953008f..22595e81e7d 100644 --- a/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java +++ b/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java @@ -41,7 +41,7 @@ public class PointOfInterestChanges implements SaveFileContent { } data.storeObject("keys",keys.toArray(new String[0])); for(int i=0;i Date: Sun, 25 Dec 2022 09:11:06 +0000 Subject: [PATCH 22/76] Update Secret Lair Drop Series.txt --- forge-gui/res/editions/Secret Lair Drop Series.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 99653653ec8..543cf057bc0 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -663,6 +663,7 @@ ScryfallCode=SLD 711 R Tireless Tracker @Nils Hamm 718 R Maro @Jesper Ejsing 719 R Maro @Mark Rosewater +720 R Thought-Knot Seer @Aleksi Briclot 721 R Diabolic Tutor @Brain Dead 726 R Zur the Enchanter @Chase Stone 727 R Fabled Passage @Warren Mahy From 00c67716082cc23425b718fb50f9bdd24e952be3 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Sun, 25 Dec 2022 04:11:52 -0500 Subject: [PATCH 23/76] Fix ArrayIndexOutOfBounds exception printed to stderr. (#2141) --- .../src/main/java/forge/deckchooser/FDeckChooser.java | 4 ++-- forge-gui-mobile/src/forge/deck/FDeckChooser.java | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) 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 11d5b1709bf..8529ff0ef2a 100644 --- a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java +++ b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckChooser.java @@ -840,9 +840,9 @@ public class FDeckChooser extends JPanel implements IDecksComboBoxListener { try { if (StringUtils.isBlank(savedState)) { return new ArrayList<>(); - } else { - return Arrays.asList(savedState.split(";")[1].split(SELECTED_DECK_DELIMITER)); } + final String[] parts = savedState.split(";", -1); + return Arrays.asList(parts[1].split(SELECTED_DECK_DELIMITER)); } catch (final Exception ex) { System.err.println(ex + " [savedState=" + savedState + "]"); return new ArrayList<>(); diff --git a/forge-gui-mobile/src/forge/deck/FDeckChooser.java b/forge-gui-mobile/src/forge/deck/FDeckChooser.java index dea7c235863..20f25a6c97b 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckChooser.java +++ b/forge-gui-mobile/src/forge/deck/FDeckChooser.java @@ -1360,9 +1360,8 @@ public class FDeckChooser extends FScreen { if (StringUtils.isBlank(savedState)) { return new ArrayList<>(); } - else { - return Arrays.asList(savedState.split(";")[1].split(SELECTED_DECK_DELIMITER)); - } + final String[] parts = savedState.split(";", -1); + return Arrays.asList(parts[1].split(SELECTED_DECK_DELIMITER)); } catch (Exception ex) { System.err.println(ex + " [savedState=" + savedState + "]"); From c22eb54dff0b46181d7d4494f9d535134742acd6 Mon Sep 17 00:00:00 2001 From: JohnWilliams77 <103562494+JohnWilliams77@users.noreply.github.com> Date: Sun, 25 Dec 2022 09:48:12 +0000 Subject: [PATCH 24/76] Update Secret Lair Drop Series.txt --- forge-gui/res/editions/Secret Lair Drop Series.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 543cf057bc0..1353c4f98c5 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -642,6 +642,7 @@ ScryfallCode=SLD 682 R Shadowborn Apostle @Diego Andrade 683 R Shadowborn Apostle @Jakub Rebelka 684 R Shadowborn Apostle @Laynes +687 R Shadowborn Apostle @Roman Klonek 690 R Forest @Magali Villeneuve 691 R Hedron Archive @Cosmin Podar 692 R Pilgrim's Eye @Cosmin Podar From 9097acdca58f16bbd173c18f8485c63e0d4ba8fa Mon Sep 17 00:00:00 2001 From: JohnWilliams77 <103562494+JohnWilliams77@users.noreply.github.com> Date: Sun, 25 Dec 2022 15:21:04 +0000 Subject: [PATCH 25/76] Update Secret Lair Drop Series.txt --- forge-gui/res/editions/Secret Lair Drop Series.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 1353c4f98c5..b559ea441b0 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -666,6 +666,7 @@ ScryfallCode=SLD 719 R Maro @Mark Rosewater 720 R Thought-Knot Seer @Aleksi Briclot 721 R Diabolic Tutor @Brain Dead +724 R Lightning Strike @Frank Frazetta 726 R Zur the Enchanter @Chase Stone 727 R Fabled Passage @Warren Mahy 900 R The Scarab God @Barely Human From 01780ce43370580f481ab7372875246f9eb802cd Mon Sep 17 00:00:00 2001 From: paulsnoops Date: Sun, 25 Dec 2022 18:42:58 +0000 Subject: [PATCH 26/76] SLD Update --- forge-gui/res/editions/Secret Lair Drop Series.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index b559ea441b0..6db321c22da 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -587,6 +587,7 @@ ScryfallCode=SLD 610 R Bonescythe Sliver @Trevor Claxton 612 R Essence Sliver @Glen Angus 613 R Pulmonic Sliver @Jeff Easley +615 R Sidewinder Sliver @Ron Spencer 617 R Ward Sliver @Pete Venters 618 R Diffusion Sliver @Trevor Claxton 619 R Galerider Sliver @James Zapata From 037f1d0b7b11fed8c51a07b026f43f36bfc23f59 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Mon, 26 Dec 2022 10:42:03 +0100 Subject: [PATCH 27/76] Some fixes (#2170) --- forge-ai/src/main/java/forge/ai/ability/PumpAi.java | 2 +- .../src/main/java/forge/ai/simulation/GameCopier.java | 2 ++ forge-game/src/main/java/forge/game/GameEntity.java | 7 +++++++ .../src/main/java/forge/game/ability/AbilityUtils.java | 8 ++------ forge-game/src/main/java/forge/game/card/Card.java | 7 ------- forge-gui/res/cardsfolder/e/eradicate.txt | 4 ++-- forge-gui/res/cardsfolder/s/sowing_salt.txt | 4 ++-- forge-gui/res/cardsfolder/s/splinter.txt | 4 ++-- forge-gui/res/cardsfolder/t/the_ruinous_powers.txt | 4 ++-- forge-gui/res/cardsfolder/v/volcanic_offering.txt | 2 +- forge-gui/res/cardsfolder/w/wand_of_denial.txt | 2 +- forge-gui/res/cardsfolder/z/zurs_weirding.txt | 2 +- 12 files changed, 23 insertions(+), 25 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java index e9223b1b708..61ebcd32291 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PumpAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PumpAi.java @@ -423,7 +423,7 @@ public class PumpAi extends PumpAiBase { } if (sa.hasParam("TargetingPlayer") && sa.getActivatingPlayer().equals(ai) && !sa.isTrigger()) { - if (ComputerUtilAbility.isFullyTargetable(sa)) { // Volcanic Offering: only prompt if second part can happen too + if (!ComputerUtilAbility.isFullyTargetable(sa)) { // Volcanic Offering: only prompt if second part can happen too return false; } Player targetingPlayer = AbilityUtils.getDefinedPlayers(source, sa.getParam("TargetingPlayer"), sa).get(0); diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java index 6376ecbf37b..6c6ebc1b605 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java @@ -83,6 +83,7 @@ public class GameCopier { Player origPlayer = origGame.getPlayers().get(i); Player newPlayer = newGame.getPlayers().get(i); newPlayer.setLife(origPlayer.getLife(), null); + newPlayer.setDamageReceivedThisTurn(origPlayer.getDamageReceivedThisTurn()); newPlayer.setActivateLoyaltyAbilityThisTurn(origPlayer.getActivateLoyaltyAbilityThisTurn()); for (int j = 0; j < origPlayer.getSpellsCastThisTurn(); j++) newPlayer.addSpellCastThisTurn(); @@ -350,6 +351,7 @@ public class GameCopier { newCard.setPTBoost(c.getPTBoostTable()); // TODO copy by map newCard.setDamage(c.getDamage()); + newCard.setDamageReceivedThisTurn(c.getDamageReceivedThisTurn()); newCard.setChangedCardColors(c.getChangedCardColorsTable()); newCard.setChangedCardColorsCharacterDefining(c.getChangedCardColorsCharacterDefiningTable()); diff --git a/forge-game/src/main/java/forge/game/GameEntity.java b/forge-game/src/main/java/forge/game/GameEntity.java index 13f1787de40..8396405f6ed 100644 --- a/forge-game/src/main/java/forge/game/GameEntity.java +++ b/forge-game/src/main/java/forge/game/GameEntity.java @@ -340,6 +340,13 @@ public abstract class GameEntity extends GameObject implements IIdentifiable { addCounterInternal(CounterType.get(counterType), n, source, fireEvents, table, params); } + public List> getDamageReceivedThisTurn() { + return damageReceivedThisTurn; + } + public void setDamageReceivedThisTurn(List> dmg) { + damageReceivedThisTurn.addAll(dmg); + } + public void receiveDamage(Pair dmg) { damageReceivedThisTurn.add(dmg); } diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index 4a176304034..a3e122b4559 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -511,12 +511,8 @@ public class AbilityUtils { } else if (hType.equals("Other")) { players.addAll(player.getAllOtherPlayers()); val = playerXCount(players, calcX[1], card, ability); - } else if (hType.equals("Remembered")) { - for (final Object o : card.getRemembered()) { - if (o instanceof Player) { - players.add((Player) o); - } - } + } else if (hType.startsWith("Remembered")) { + addPlayer(card.getRemembered(), hType, players); val = playerXCount(players, calcX[1], card, ability); } else if (hType.equals("NonActive")) { players.addAll(game.getPlayers()); diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 6e318fc2c0c..b7f1ea95ced 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -5458,13 +5458,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { damageHistory = history; } - public List> getDamageReceivedThisTurn() { - return damageReceivedThisTurn; - } - public void setDamageReceivedThisTurn(List> dmg) { - damageReceivedThisTurn.addAll(dmg); - } - public final boolean hasDealtDamageToOpponentThisTurn() { return getDamageHistory().getDamageDoneThisTurn(null, true, null, "Player.Opponent", this, getController(), null) > 0; } diff --git a/forge-gui/res/cardsfolder/e/eradicate.txt b/forge-gui/res/cardsfolder/e/eradicate.txt index 63d7ab16dbe..7ca2fd2c5fc 100644 --- a/forge-gui/res/cardsfolder/e/eradicate.txt +++ b/forge-gui/res/cardsfolder/e/eradicate.txt @@ -5,7 +5,7 @@ A:SP$ ChangeZone | Cost$ 2 B B | Origin$ Battlefield | Destination$ Exile | Vali SVar:ExileYard:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Exile | Defined$ RememberedController | ChangeType$ Remembered.sameName | SubAbility$ ExileHand | StackDescription$ None SVar:ExileHand:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInHand | Chooser$ You | SubAbility$ ExileLib | StackDescription$ None SVar:ExileLib:DB$ ChangeZone | Origin$ Library | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInLib | Chooser$ You | Search$ True | Shuffle$ True | SubAbility$ DBCleanup | StackDescription$ None -SVar:NumInHand:RememberedController$CardsInHand -SVar:NumInLib:RememberedController$CardsInLibrary +SVar:NumInHand:PlayerCountRememberedController$CardsInHand +SVar:NumInLib:PlayerCountRememberedController$CardsInLibrary SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Exile target nonblack creature. Search its controller's graveyard, hand, and library for all cards with the same name as that creature and exile them. Then that player shuffles. diff --git a/forge-gui/res/cardsfolder/s/sowing_salt.txt b/forge-gui/res/cardsfolder/s/sowing_salt.txt index ab4db027d46..2219d459e84 100644 --- a/forge-gui/res/cardsfolder/s/sowing_salt.txt +++ b/forge-gui/res/cardsfolder/s/sowing_salt.txt @@ -5,7 +5,7 @@ A:SP$ ChangeZone | Cost$ 2 R R | Origin$ Battlefield | Destination$ Exile | Vali SVar:ExileYard:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Exile | Defined$ RememberedController | ChangeType$ Remembered.sameName | SubAbility$ ExileHand | StackDescription$ None SVar:ExileHand:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInHand | Chooser$ You | SubAbility$ ExileLib | StackDescription$ None SVar:ExileLib:DB$ ChangeZone | Origin$ Library | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInLib | Chooser$ You | Search$ True | Shuffle$ True | SubAbility$ DBCleanup | StackDescription$ None -SVar:NumInHand:RememberedController$CardsInHand -SVar:NumInLib:RememberedController$CardsInLibrary +SVar:NumInHand:PlayerCountRememberedController$CardsInHand +SVar:NumInLib:PlayerCountRememberedController$CardsInLibrary SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Exile target nonbasic land. Search its controller's graveyard, hand, and library for all cards with the same name as that land and exile them. Then that player shuffles. diff --git a/forge-gui/res/cardsfolder/s/splinter.txt b/forge-gui/res/cardsfolder/s/splinter.txt index 29474054ed5..728c70a2b04 100644 --- a/forge-gui/res/cardsfolder/s/splinter.txt +++ b/forge-gui/res/cardsfolder/s/splinter.txt @@ -5,7 +5,7 @@ A:SP$ ChangeZone | Cost$ 2 G G | Origin$ Battlefield | Destination$ Exile | Vali SVar:ExileYard:DB$ ChangeZoneAll | Origin$ Graveyard | Destination$ Exile | Defined$ RememberedController | ChangeType$ Remembered.sameName | SubAbility$ ExileHand | StackDescription$ None SVar:ExileHand:DB$ ChangeZone | Origin$ Hand | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInHand | Chooser$ You | SubAbility$ ExileLib | StackDescription$ None SVar:ExileLib:DB$ ChangeZone | Origin$ Library | Destination$ Exile | DefinedPlayer$ RememberedController | ChangeType$ Remembered.sameName | ChangeNum$ NumInLib | Chooser$ You | Search$ True | Shuffle$ True | SubAbility$ DBCleanup | StackDescription$ None -SVar:NumInHand:RememberedController$CardsInHand -SVar:NumInLib:RememberedController$CardsInLibrary +SVar:NumInHand:PlayerCountRememberedController$CardsInHand +SVar:NumInLib:PlayerCountRememberedController$CardsInLibrary SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Exile target artifact. Search its controller's graveyard, hand, and library for all cards with the same name as that artifact and exile them. Then that player shuffles. diff --git a/forge-gui/res/cardsfolder/t/the_ruinous_powers.txt b/forge-gui/res/cardsfolder/t/the_ruinous_powers.txt index 7f5bd2d4a95..28dbba4cc74 100644 --- a/forge-gui/res/cardsfolder/t/the_ruinous_powers.txt +++ b/forge-gui/res/cardsfolder/t/the_ruinous_powers.txt @@ -5,8 +5,8 @@ T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | E SVar:TrigChoose:DB$ ChoosePlayer | Defined$ You | Choices$ Opponent | Random$ True | SubAbility$ DBExile SVar:DBExile:DB$ Dig | Defined$ ChosenPlayer | DigNum$ 1 | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBEffect SVar:DBEffect:DB$ Effect | StaticAbilities$ STPlay | Triggers$ TriggerCastDoM | ExileOnMoved$ Exile | RememberObjects$ Remembered -SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreColor$ True | EffectZone$ Command | Affected$ Card.IsRemembered+nonLand | AffectedZone$ Exile | Description$ Until end of turn, you may cast that card and you may spend mana as though it were mana of any color to cast it. -SVar:TriggerCastDoM:Mode$ SpellCast | ValidCard$ Card.IsRemembered | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigLoseLife | Secondary$ True | TriggerDescription$ At the beginning of your upkeep, choose an opponent at random. Exile the top card of that player's library. Until end of turn, you may play that card and you may spend mana as though it were mana of any color to cast it. When you cast a spell this way, its owner loses life equal to its mana value. +SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreColor$ True | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ Until end of turn, you may play that card and you may spend mana as though it were mana of any color to cast it. +SVar:TriggerCastDoM:Mode$ SpellCast | ValidCard$ Card.IsRemembered | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigLoseLife | Secondary$ True | TriggerDescription$ When you cast a spell this way, its owner loses life equal to its mana value. SVar:TrigLoseLife:DB$ LoseLife | Defined$ TriggeredCardOwner | LifeAmount$ X SVar:X:TriggeredStackInstance$CardManaCostLKI Oracle:At the beginning of your upkeep, choose an opponent at random. Exile the top card of that player's library. Until end of turn, you may play that card and you may spend mana as though it were mana of any color to cast it. When you cast a spell this way, its owner loses life equal to its mana value. diff --git a/forge-gui/res/cardsfolder/v/volcanic_offering.txt b/forge-gui/res/cardsfolder/v/volcanic_offering.txt index 7e172d0382c..ca101db90fa 100644 --- a/forge-gui/res/cardsfolder/v/volcanic_offering.txt +++ b/forge-gui/res/cardsfolder/v/volcanic_offering.txt @@ -3,7 +3,7 @@ ManaCost:4 R Types:Instant A:SP$ Pump | Cost$ 4 R | ValidTgts$ Land.nonBasic+YouDontCtrl | TgtPrompt$ Select target nonbasic land you don't control | AILogic$ Destroy | IsCurse$ True | RememberTargets$ True | SubAbility$ DBDestroyLand | SpellDescription$ Destroy target nonbasic land you don't control and target nonbasic land of an opponent's choice you don't control. SVar:DBDestroyLand:DB$ Pump | TargetingPlayer$ Player.Opponent | ValidTgts$ Land.nonBasic+YouDontCtrl | TgtPrompt$ Select target nonbasic land the caster of this spell don't control | AILogic$ Destroy | IsCurse$ True | RememberTargets$ True | SubAbility$ DBDestroy -SVar:DBDestroy:DB$ Destroy | Defined$ Remembered | SubAbility$ DBDamage +SVar:DBDestroy:DB$ Destroy | Defined$ Remembered | AILogic$ Always | SubAbility$ DBDamage SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control | NumDmg$ 7 | DamageMap$ True | SubAbility$ DBDamage2 | SpellDescription$ CARDNAME deals 7 damage to target creature you don't control and 7 damage to target creature of an opponent's choice you don't control. SVar:DBDamage2:DB$ DealDamage | TargetingPlayer$ Player.Opponent | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature the caster of this spell don't control | NumDmg$ 7 | SubAbility$ DBDamageResolve SVar:DBDamageResolve:DB$ DamageResolve | SubAbility$ DBCleanup diff --git a/forge-gui/res/cardsfolder/w/wand_of_denial.txt b/forge-gui/res/cardsfolder/w/wand_of_denial.txt index c8eedd4405f..fddb44c5951 100644 --- a/forge-gui/res/cardsfolder/w/wand_of_denial.txt +++ b/forge-gui/res/cardsfolder/w/wand_of_denial.txt @@ -2,7 +2,7 @@ Name:Wand of Denial ManaCost:2 Types:Artifact A:AB$ PeekAndReveal | Cost$ T | ValidTgts$ Player | NoReveal$ True | RememberPeeked$ True | SubAbility$ DBChangeZone | StackDescription$ SpellDescription | SpellDescription$ Look at the top card of target player's library. If it's a nonland card, you may pay 2 life. If you do, put it into that player's graveyard. -SVar:DBChangeZone:DB$ Dig | Defined$ Targeted | DestinationZone$ Graveyard | ConditionDefined$ Remembered | ConditionPresent$ Card.nonLand | ConditionCompare$ GE1 | UnlessPayer$ You | UnlessCost$ PayLife<2> | UnlessSwitched$ True | StackDescription$ None | SubAbility$ DBCleanup +SVar:DBChangeZone:DB$ Dig | Defined$ Targeted | DestinationZone$ Graveyard | DigNum$ 1 | ChangeNum$ All | ConditionDefined$ Remembered | ConditionPresent$ Card.nonLand | ConditionCompare$ GE1 | UnlessPayer$ You | UnlessCost$ PayLife<2> | UnlessSwitched$ True | StackDescription$ None | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:All Oracle:{T}: Look at the top card of target player's library. If it's a nonland card, you may pay 2 life. If you do, put it into that player's graveyard. diff --git a/forge-gui/res/cardsfolder/z/zurs_weirding.txt b/forge-gui/res/cardsfolder/z/zurs_weirding.txt index 14018603d6d..9b13cca8e4f 100644 --- a/forge-gui/res/cardsfolder/z/zurs_weirding.txt +++ b/forge-gui/res/cardsfolder/z/zurs_weirding.txt @@ -4,7 +4,7 @@ Types:Enchantment S:Mode$ Continuous | AffectedZone$ Hand | MayLookAt$ Player | Description$ Players play with their hands revealed. R:Event$ Draw | ActiveZones$ Battlefield | ValidPlayer$ Player | ReplaceWith$ RevealTop | Description$ If a player would draw a card, they reveal it instead. Then any other player may pay 2 life. If a player does, put that card into its owner's graveyard. Otherwise, that player draws a card. SVar:RevealTop:DB$ PeekAndReveal | Defined$ ReplacedPlayer | NoPeek$ True | SubAbility$ DBMill -SVar:DBMill:DB$ Dig | Defined$ ReplacedPlayer | DestinationZone$ Graveyard | SubAbility$ DBDraw | UnlessPayer$ NonReplacedPlayer | UnlessCost$ PayLife<2> | UnlessSwitched$ True | UnlessResolveSubs$ WhenNotPaid | StackDescription$ None +SVar:DBMill:DB$ Dig | Defined$ ReplacedPlayer | DestinationZone$ Graveyard | DigNum$ 1 | ChangeNum$ All | SubAbility$ DBDraw | UnlessPayer$ NonReplacedPlayer | UnlessCost$ PayLife<2> | UnlessSwitched$ True | UnlessResolveSubs$ WhenNotPaid | StackDescription$ None SVar:DBDraw:DB$ Draw | Defined$ ReplacedPlayer | NumCards$ 1 | StackDescription$ that player draws a card SVar:NonStackingEffect:True AI:RemoveDeck:All From f61fec3d02e27b8cca74e17dcabacbad7d5785cc Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Tue, 27 Dec 2022 00:54:36 +0800 Subject: [PATCH 28/76] Update livio changezoneall --- forge-gui/res/cardsfolder/l/livio_oathsworn_sentinel.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/res/cardsfolder/l/livio_oathsworn_sentinel.txt b/forge-gui/res/cardsfolder/l/livio_oathsworn_sentinel.txt index 81b4729d172..40b0d7f42a1 100644 --- a/forge-gui/res/cardsfolder/l/livio_oathsworn_sentinel.txt +++ b/forge-gui/res/cardsfolder/l/livio_oathsworn_sentinel.txt @@ -3,7 +3,7 @@ ManaCost:1 W Types:Legendary Creature Human Knight PT:2/2 A:AB$ ChangeZone | Cost$ 1 W | ValidTgts$ Creature.Other | TgtPrompt$ Select another target creature | Optional$ True | DefinedPlayer$ TargetedController | Chooser$ TargetedController | Origin$ Battlefield | Destination$ Exile | WithCountersType$ AEGIS | StackDescription$ {p:You} chooses {c:Targeted}. {p:TargetedController} may exile it with an aegis counter on it. | SpellDescription$ Choose another target creature. Its controller may exile it with an aegis counter on it. -A:AB$ ChangeZoneAll | Cost$ 2 W T | ChangeType$ Creature.counters_GE1_AEGIS | Origin$ Exile | Destination$ Battlefield | SpellDescription$ Return all exiled cards with aegis counters on them to the battlefield under their owners' control. +A:AB$ ChangeZoneAll | Cost$ 2 W T | ChangeType$ Card.counters_GE1_AEGIS | Origin$ Exile | Destination$ Battlefield | SpellDescription$ Return all exiled cards with aegis counters on them to the battlefield under their owners' control. K:Partner DeckHas:Ability$Counters Oracle:{1}{W}: Choose another target creature. Its controller may exile it with an aegis counter on it.\n{2}{W}, {T}: Return all exiled cards with aegis counters on them to the battlefield under their owners' control.\nPartner (You can have two commanders if both have partner.) From abe5828195ac5457f6e4f226d985b8715d260a26 Mon Sep 17 00:00:00 2001 From: Northmoc <103371817+Northmoc@users.noreply.github.com> Date: Mon, 26 Dec 2022 13:25:15 -0500 Subject: [PATCH 29/76] YBRO: By Elspeth's Command and Yotian Courier + support (#2114) * by_elspeths_command.txt with support v1 * yotian_courier.txt --- .../game/ability/effects/CharmEffect.java | 12 ++-- .../src/main/java/forge/game/card/Card.java | 72 +++++++++++++------ .../java/forge/game/phase/PhaseHandler.java | 3 + .../main/java/forge/game/zone/MagicStack.java | 2 +- .../upcoming/by_elspeths_command.txt | 15 ++++ .../cardsfolder/upcoming/yotian_courier.txt | 14 ++++ 6 files changed, 87 insertions(+), 31 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/by_elspeths_command.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/yotian_courier.txt diff --git a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java index bd5b134ccda..2e02ff333bc 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CharmEffect.java @@ -25,19 +25,15 @@ public class CharmEffect extends SpellAbilityEffect { List restriction = null; if (sa.hasParam("ChoiceRestriction")) { - String rest = sa.getParam("ChoiceRestriction"); - if (rest.equals("ThisGame")) { - restriction = source.getChosenModesGame(sa); - } else if (rest.equals("ThisTurn")) { - restriction = source.getChosenModesTurn(sa); - } + restriction = source.getChosenModes(sa, sa.getParam("ChoiceRestriction")); } List choices = Lists.newArrayList(sa.getAdditionalAbilityList("Choices")); List toRemove = Lists.newArrayList(); for (AbilitySub ch : choices) { // 603.3c If one of the modes would be illegal, that mode can't be chosen. - if ((ch.usesTargeting() && ch.isTrigger() && ch.getTargetRestrictions().getNumCandidates(ch, true) == 0) || + if ((ch.usesTargeting() && ch.isTrigger() && ch.getMinTargets() > 0 && + ch.getTargetRestrictions().getNumCandidates(ch, true) == 0) || (restriction != null && restriction.contains(ch.getDescription()))) { toRemove.add(ch); } @@ -97,6 +93,8 @@ public class CharmEffect extends SpellAbilityEffect { sb.append(" that hasn't been chosen"); } else if (rest.equals("ThisTurn")) { sb.append(" that hasn't been chosen this turn"); + } else if (rest.equals("YourLastCombat")) { + sb.append(" that wasn't chosen during your last combat"); } } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index b7f1ea95ced..770aa52158e 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -331,9 +331,13 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private final Map> chosenModesTurn = Maps.newHashMap(); private final Map> chosenModesGame = Maps.newHashMap(); + private final Map> chosenModesYourCombat = Maps.newHashMap(); + private final Map> chosenModesYourLastCombat = Maps.newHashMap(); private final Table> chosenModesTurnStatic = HashBasedTable.create(); private final Table> chosenModesGameStatic = HashBasedTable.create(); + private final Table> chosenModesYourCombatStatic = HashBasedTable.create(); + private final Table> chosenModesYourLastCombatStatic = HashBasedTable.create(); private CombatLki combatLKI; @@ -6338,6 +6342,18 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return isInZone(ZoneType.Battlefield); } + public void onEndOfCombat(final Player active) { + if (this.getController().equals(active)) { + chosenModesYourLastCombat.clear(); + chosenModesYourLastCombatStatic.clear(); + chosenModesYourLastCombat.putAll(chosenModesYourCombat); + chosenModesYourLastCombatStatic.putAll(chosenModesYourCombatStatic); + chosenModesYourCombat.clear(); + chosenModesYourCombatStatic.clear(); + updateAbilityTextForView(); + } + } + public void onCleanupPhase(final Player turn) { if (!this.hasKeyword("Damage isn't removed from CARDNAME during cleanup steps.")) { setDamage(0); @@ -7030,7 +7046,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { numberAbilityResolved.clear(); } - public List getChosenModesTurn(SpellAbility ability) { + public List getChosenModes(SpellAbility ability, String type) { SpellAbility original = null; SpellAbility root = ability.getRootAbility(); @@ -7044,32 +7060,26 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } } - if (ability.getGrantorStatic() != null) { - return chosenModesTurnStatic.get(original, ability.getGrantorStatic()); - } - return chosenModesTurn.get(original); - } - public List getChosenModesGame(SpellAbility ability) { - SpellAbility original = null; - SpellAbility root = ability.getRootAbility(); - - // because trigger spell abilities are copied, try to get original one - if (root.isTrigger()) { - original = root.getTrigger().getOverridingAbility(); - } else { - original = ability.getOriginalAbility(); - if (original == null) { - original = ability; + if (type.equals("ThisTurn")) { + if (ability.getGrantorStatic() != null) { + return chosenModesTurnStatic.get(original, ability.getGrantorStatic()); } + return chosenModesTurn.get(original); + } else if (type.equals("ThisGame")) { + if (ability.getGrantorStatic() != null) { + return chosenModesGameStatic.get(original, ability.getGrantorStatic()); + } + return chosenModesGame.get(original); + } else if (type.equals("YourLastCombat")) { + if (ability.getGrantorStatic() != null) { + return chosenModesYourLastCombatStatic.get(original, ability.getGrantorStatic()); + } + return chosenModesYourLastCombat.get(original); } - - if (ability.getGrantorStatic() != null) { - return chosenModesGameStatic.get(original, ability.getGrantorStatic()); - } - return chosenModesGame.get(original); + return null; } - public void addChosenModes(SpellAbility ability, String mode) { + public void addChosenModes(SpellAbility ability, String mode, boolean yourCombat) { SpellAbility original = null; SpellAbility root = ability.getRootAbility(); @@ -7096,6 +7106,13 @@ public class Card extends GameEntity implements Comparable, IHasSVars { chosenModesGameStatic.put(original, ability.getGrantorStatic(), result); } result.add(mode); + if (yourCombat) { + result = chosenModesYourCombatStatic.get(original, ability.getGrantorStatic()); + if (result == null) { + result = Lists.newArrayList(); + chosenModesYourCombatStatic.put(original, ability.getGrantorStatic(), result); + } + } } else { List result = chosenModesTurn.get(original); if (result == null) { @@ -7110,6 +7127,15 @@ public class Card extends GameEntity implements Comparable, IHasSVars { chosenModesGame.put(original, result); } result.add(mode); + + if (yourCombat) { + result = chosenModesYourCombat.get(original); + if (result == null) { + result = Lists.newArrayList(); + chosenModesYourCombat.put(original, result); + } + result.add(mode); + } } } diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java index ea61da97ce0..ae9ce2d17e4 100644 --- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java +++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java @@ -357,6 +357,9 @@ public class PhaseHandler implements java.io.Serializable { case COMBAT_END: // End Combat always happens + for (final Card c : game.getCardsIn(ZoneType.Battlefield)) { + c.onEndOfCombat(playerTurn); + } game.getEndOfCombat().executeAt(); //SDisplayUtil.showTab(EDocID.REPORT_STACK.getDoc()); diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index 479e7439b03..857b7d5b0d4 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -288,7 +288,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable Date: Mon, 26 Dec 2022 13:40:45 -0500 Subject: [PATCH 30/76] tighten up Adversary cycle from MID (#2162) --- .../res/cardsfolder/b/bloodthirsty_adversary.txt | 11 +++++------ forge-gui/res/cardsfolder/i/intrepid_adversary.txt | 6 +++--- forge-gui/res/cardsfolder/p/primal_adversary.txt | 8 ++++---- forge-gui/res/cardsfolder/s/spectral_adversary.txt | 8 ++++---- forge-gui/res/cardsfolder/t/tainted_adversary.txt | 9 ++++----- 5 files changed, 20 insertions(+), 22 deletions(-) diff --git a/forge-gui/res/cardsfolder/b/bloodthirsty_adversary.txt b/forge-gui/res/cardsfolder/b/bloodthirsty_adversary.txt index 8b3c61b2a2e..f1111b4fffb 100644 --- a/forge-gui/res/cardsfolder/b/bloodthirsty_adversary.txt +++ b/forge-gui/res/cardsfolder/b/bloodthirsty_adversary.txt @@ -4,12 +4,11 @@ Types:Creature Vampire PT:2/2 K:Haste T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {2}{R} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. -SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<2 R\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. -SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBExile -SVar:DBExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ NumTimes | ValidTgts$ Instant.cmcLE3+YouOwn,Sorcery.cmcLE3+YouOwn | TgtPrompt$ Select up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard | RememberChanged$ True | SubAbility$ DBCopyCast -SVar:DBCopyCast:DB$ Play | Valid$ Card.IsRemembered | ValidZone$ Exile | Controller$ You | CopyCard$ True | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | Amount$ All | SubAbility$ ExileMe -SVar:ExileMe:DB$ ChangeZoneAll | Origin$ Stack | Destination$ Exile | ChangeType$ Card.Self | SubAbility$ DBCleanup +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<2 R\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | RememberSVarAmount$ NumTimes | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBExile +SVar:DBExile:DB$ ChangeZone | Origin$ Graveyard | Destination$ Exile | TargetMin$ 0 | TargetMax$ X | ValidTgts$ Instant.cmcLE3+YouOwn,Sorcery.cmcLE3+YouOwn | TgtPrompt$ Select up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard | RememberChanged$ True | SubAbility$ DBCopyCast +SVar:DBCopyCast:DB$ Play | Valid$ Card.IsRemembered | ValidZone$ Exile | Controller$ You | CopyCard$ True | WithoutManaCost$ True | ValidSA$ Spell | Optional$ True | Amount$ All | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:NumTimes:Number$0 +SVar:X:Count$TriggerRememberAmount DeckHas:Ability$Counters Oracle:Haste\nWhen Bloodthirsty Adversary enters the battlefield, you may pay {2}{R} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Bloodthirsty Adversary, then exile up to that many target instant and/or sorcery cards with mana value 3 or less from your graveyard and copy them. You may cast any number of the copies without paying their mana costs. diff --git a/forge-gui/res/cardsfolder/i/intrepid_adversary.txt b/forge-gui/res/cardsfolder/i/intrepid_adversary.txt index f403d206104..efb36af41ee 100644 --- a/forge-gui/res/cardsfolder/i/intrepid_adversary.txt +++ b/forge-gui/res/cardsfolder/i/intrepid_adversary.txt @@ -4,9 +4,9 @@ Types:Creature Human Scout PT:3/1 K:Lifelink T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {1}{W} any number of times. When you pay this cost one or more times, put that many valor counters on CARDNAME. -SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 W\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many valor counters on CARDNAME. -SVar:TrigPutCounter:DB$ PutCounter | CounterType$ VALOR | CounterNum$ NumTimes -SVar:NumTimes:Number$0 +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 W\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | RememberSVarAmount$ NumTimes | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many valor counters on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ VALOR | CounterNum$ X +SVar:X:Count$TriggerRememberAmount S:Mode$ Continuous | Affected$ Creature.YouCtrl | AddPower$ Z | AddToughness$ Z | Description$ Creatures you control get +1/+1 for each valor counter on CARDNAME. SVar:Z:Count$CardCounters.VALOR DeckHas:Ability$LifeGain|Counters diff --git a/forge-gui/res/cardsfolder/p/primal_adversary.txt b/forge-gui/res/cardsfolder/p/primal_adversary.txt index 99d1c966c3c..4e11f174f9d 100644 --- a/forge-gui/res/cardsfolder/p/primal_adversary.txt +++ b/forge-gui/res/cardsfolder/p/primal_adversary.txt @@ -4,9 +4,9 @@ Types:Creature Wolf PT:4/3 K:Trample T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {1}{G} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many target lands you control become 3/3 Wolf creatures with haste that are still lands. -SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 G\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many target lands you control become 3/3 Wolf creatures with haste that are still lands. -SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBAnimate -SVar:DBAnimate:DB$ Animate | TargetMin$ 0 | TargetMax$ NumTimes | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select up to that many target lands you control | Power$ 3 | Toughness$ 3 | Types$ Wolf,Creature | Keywords$ Haste | Duration$ Permanent -SVar:NumTimes:Number$0 +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 G\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | RememberSVarAmount$ NumTimes | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many target lands you control become 3/3 Wolf creatures with haste that are still lands. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | TargetMin$ 0 | TargetMax$ X | ValidTgts$ Land.YouCtrl | TgtPrompt$ Select up to that many target lands you control | Power$ 3 | Toughness$ 3 | Types$ Wolf,Creature | Keywords$ Haste | Duration$ Permanent +SVar:X:Count$TriggerRememberAmount DeckHas:Ability$Counters Oracle:Trample\nWhen Primal Adversary enters the battlefield, you may pay {1}{G} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Primal Adversary, then up to that many target lands you control become 3/3 Wolf creatures with haste that are still lands. diff --git a/forge-gui/res/cardsfolder/s/spectral_adversary.txt b/forge-gui/res/cardsfolder/s/spectral_adversary.txt index 063ddcf9bd9..ea66850993a 100644 --- a/forge-gui/res/cardsfolder/s/spectral_adversary.txt +++ b/forge-gui/res/cardsfolder/s/spectral_adversary.txt @@ -5,9 +5,9 @@ PT:2/1 K:Flash K:Flying T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {1}{U} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many other target artifacts, creatures, and/or enchantments phase out. -SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 U\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many other target artifacts, creatures, and/or enchantments phase out. -SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBPhases -SVar:DBPhases:DB$ Phases | ValidTgts$ Creature.Other,Artifact.Other,Enchantment.Other | TgtPrompt$ Select up to that many other target artifacts, creatures, and/or enchantments | TargetMin$ 0 | TargetMax$ NumTimes -SVar:NumTimes:Number$0 +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<1 U\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | RememberSVarAmount$ NumTimes | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then up to that many other target artifacts, creatures, and/or enchantments phase out. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBPhases +SVar:DBPhases:DB$ Phases | ValidTgts$ Creature.Other,Artifact.Other,Enchantment.Other | TgtPrompt$ Select up to that many other target artifacts, creatures, and/or enchantments | TargetMin$ 0 | TargetMax$ X +SVar:X:Count$TriggerRememberAmount DeckHas:Ability$Counters Oracle:Flash\nFlying\nWhen Spectral Adversary enters the battlefield, you may pay {1}{U} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Spectral Adversary, then up to that many other target artifacts, creatures, and/or enchantments phase out. diff --git a/forge-gui/res/cardsfolder/t/tainted_adversary.txt b/forge-gui/res/cardsfolder/t/tainted_adversary.txt index f330d222aa4..3fc6893f907 100644 --- a/forge-gui/res/cardsfolder/t/tainted_adversary.txt +++ b/forge-gui/res/cardsfolder/t/tainted_adversary.txt @@ -4,10 +4,9 @@ Types:Creature Zombie PT:2/3 K:Deathtouch T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigPay | TriggerDescription$ When CARDNAME enters the battlefield, you may pay {2}{B} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then create twice that many black 2/2 Zombie creature tokens with decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.) -SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<2 B\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then create twice that many black 2/2 Zombie creature tokens with decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.) -SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ NumTimes | SubAbility$ DBToken -SVar:DBToken:DB$ Token | TokenScript$ b_2_2_zombie_decayed | TokenAmount$ TwiceNumTimes -SVar:NumTimes:Number$0 -SVar:TwiceNumTimes:SVar$NumTimes/Twice +SVar:TrigPay:AB$ ImmediateTrigger | Cost$ Mana<2 B\NumTimes> | Announce$ NumTimes | ConditionCheckSVar$ NumTimes | ConditionSVarCompare$ GE1 | RememberSVarAmount$ NumTimes | Execute$ TrigPutCounter | TriggerDescription$ When you pay this cost one or more times, put that many +1/+1 counters on CARDNAME, then create twice that many black 2/2 Zombie creature tokens with decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.) +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ P1P1 | CounterNum$ X | SubAbility$ DBToken +SVar:DBToken:DB$ Token | TokenScript$ b_2_2_zombie_decayed | TokenAmount$ SVar$X/Twice +SVar:X:Count$TriggerRememberAmount DeckHas:Ability$Token|Counters Oracle:Deathtouch\nWhen Tainted Adversary enters the battlefield, you may pay {2}{B} any number of times. When you pay this cost one or more times, put that many +1/+1 counters on Tainted Adversary, then create twice that many black 2/2 Zombie creature tokens with decayed. (A creature with decayed can't block. When it attacks, sacrifice it at end of combat.) From ef44210a5670c8eea083d27fac394525ff2679ab Mon Sep 17 00:00:00 2001 From: Northmoc <103371817+Northmoc@users.noreply.github.com> Date: Mon, 26 Dec 2022 14:14:15 -0500 Subject: [PATCH 31/76] =?UTF-8?q?ONE=20=E2=80=93=2021=20Dec=20(leaked=20ca?= =?UTF-8?q?rd=20scripts)=20(#2151)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bonepicker_skirge.txt * sinew_dancer.txt * quicksilver_fisher.txt * sawblade_scamp.txt * furnace_punisher.txt --- .../res/cardsfolder/upcoming/bonepicker_skirge.txt | 11 +++++++++++ .../res/cardsfolder/upcoming/furnace_punisher.txt | 11 +++++++++++ .../res/cardsfolder/upcoming/quicksilver_fisher.txt | 10 ++++++++++ forge-gui/res/cardsfolder/upcoming/sawblade_scamp.txt | 11 +++++++++++ forge-gui/res/cardsfolder/upcoming/sinew_dancer.txt | 9 +++++++++ 5 files changed, 52 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/bonepicker_skirge.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/furnace_punisher.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/quicksilver_fisher.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sawblade_scamp.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/sinew_dancer.txt diff --git a/forge-gui/res/cardsfolder/upcoming/bonepicker_skirge.txt b/forge-gui/res/cardsfolder/upcoming/bonepicker_skirge.txt new file mode 100644 index 00000000000..b211059af4c --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/bonepicker_skirge.txt @@ -0,0 +1,11 @@ +Name:Bonepicker Skirge +ManaCost:2 B +Types:Creature Phyrexian Imp +PT:2/2 +K:Flying +S:Mode$ Continuous | Affected$ Card.Self | CheckSVar$ X | SVarCompare$ GE3 | AddKeyword$ Deathtouch & Lifelink | Description$ Corrupted – As long as an opponent has three or more poison counters, CARDNAME has deathtouch and lifelink. +SVar:X:PlayerCountOpponents$HighestPoisonCounters +DeckHas:Ability$LifeGain +AI:RemoveDeck:Random +DeckNeeds:Ability$Counters +Oracle:Flying\nCorrupted – As long as an opponent has three or more poison counters, Bonepicker Skirge has deathtouch and lifelink. diff --git a/forge-gui/res/cardsfolder/upcoming/furnace_punisher.txt b/forge-gui/res/cardsfolder/upcoming/furnace_punisher.txt new file mode 100644 index 00000000000..36f453c2ed5 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/furnace_punisher.txt @@ -0,0 +1,11 @@ +Name:Furnace Punisher +ManaCost:2 R +Types:Creature Phyrexian Warrior +PT:3/3 +K:Menace +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ Player | Execute$ TrigDealDamage | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of each player's upkeep, CARDNAME deals 2 damage to that player unless they control two or more basic lands. +SVar:TrigDealDamage:DB$ DealDamage | Defined$ TriggeredPlayer | NumDmg$ 2 | ConditionPresent$ Land.Basic+ControlledBy TriggeredPlayer | ConditionCompare$ LT2 +AI:RemoveDeck:Random +SVar:X:Count$Valid Land.Basic+YouCtrl +SVar:NeedsToPlayVar:X GE2 +Oracle:Menace\nAt the beginning of each player's upkeep, Furnace Punisher deals 2 damage to that player unless they control two or more basic lands. diff --git a/forge-gui/res/cardsfolder/upcoming/quicksilver_fisher.txt b/forge-gui/res/cardsfolder/upcoming/quicksilver_fisher.txt new file mode 100644 index 00000000000..16b59503015 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/quicksilver_fisher.txt @@ -0,0 +1,10 @@ +Name:Quicksilver Fisher +ManaCost:3 U U +Types:Creature Phyrexian Drake +PT:4/3 +K:Flying +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw a card, then discard a card. +SVar:TrigDraw:DB$ Draw | SubAbility$ DBDiscard +SVar:DBDiscard:DB$ Discard | Mode$ TgtChoose +DeckHas:Ability$Discard +Oracle:Flying\nWhen Quicksilver Fisher enters the battlefield, draw a card, then discard a card. diff --git a/forge-gui/res/cardsfolder/upcoming/sawblade_scamp.txt b/forge-gui/res/cardsfolder/upcoming/sawblade_scamp.txt new file mode 100644 index 00000000000..2155df75bac --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sawblade_scamp.txt @@ -0,0 +1,11 @@ +Name:Sawblade Scamp +ManaCost:R +Types:Creature Phyrexian Beast +PT:1/1 +K:Haste +T:Mode$ SpellCast | ValidCard$ Card.nonCreature | ValidActivatingPlayer$ You | Execute$ TrigPutCounter | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast a noncreature spell, put an oil counter on CARDNAME. +SVar:TrigPutCounter:DB$ PutCounter | CounterType$ OIL +A:AB$ DealDamage | Cost$ T SubCounter<1/OIL> | Defined$ Opponent | NumDmg$ 1 | SpellDescription$ It deals 1 damage to each opponent. +SVar:BuffedBy:Card.nonCreature +DeckHas:Ability$Counters +Oracle:Haste\nWhenever you cast a noncreature spell, put an oil counter on Sawblade Scamp.\n{T}, Remove an oil counter from Sawblade Scamp: It deals 1 damage to each opponent. diff --git a/forge-gui/res/cardsfolder/upcoming/sinew_dancer.txt b/forge-gui/res/cardsfolder/upcoming/sinew_dancer.txt new file mode 100644 index 00000000000..c266a042198 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/sinew_dancer.txt @@ -0,0 +1,9 @@ +Name:Sinew Dancer +ManaCost:W +Types:Creature Phyrexian Soldier +PT:1/1 +A:AB$ Tap | Cost$ 3 W T | ValidTgts$ Creature | SpellDescription$ Tap target creature. +A:AB$ Tap | PrecostDesc$ Corrupted — | Cost$ W T | ValidTgts$ Creature | CheckSVar$ X | SVarCompare$ GE3 | SpellDescription$ Tap target creature. Activate only if an opponent has three or more poison counters. +SVar:X:PlayerCountOpponents$HighestPoisonCounters +DeckHints:Ability$Counters +Oracle:{3}{W}, {T}: Tap target creature.\nCorrupted - {W}, {T}: Tap target creature. Activate only if an opponent has three or more poison counters. From 0645b23405655e9ae6a66696529d6f01d70be5db Mon Sep 17 00:00:00 2001 From: DorkmasterFlek Date: Mon, 26 Dec 2022 15:44:42 -0500 Subject: [PATCH 32/76] Fix crash when filtering card sets in workshop Null pointer exception caused a crash when trying to filter by set in the debug workshop. Null value was in the limitedSets parameter being passed in. --- .../java/forge/itemmanager/filters/CardSetFilter.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/itemmanager/filters/CardSetFilter.java b/forge-gui-desktop/src/main/java/forge/itemmanager/filters/CardSetFilter.java index 22b4e39f60d..6b2c98f1c04 100644 --- a/forge-gui-desktop/src/main/java/forge/itemmanager/filters/CardSetFilter.java +++ b/forge-gui-desktop/src/main/java/forge/itemmanager/filters/CardSetFilter.java @@ -9,7 +9,7 @@ import forge.item.PaperCard; import forge.itemmanager.ItemManager; import forge.screens.home.quest.DialogChooseSets; -/** +/** * TODO: Write javadoc for this type. * */ @@ -26,7 +26,9 @@ public class CardSetFilter extends CardFormatFilter { public CardSetFilter(ItemManager itemManager0, Collection sets0, Collection limitedSets0, boolean allowReprints0){ this(itemManager0, sets0, allowReprints0); - this.limitedSets.addAll(limitedSets0); + if (limitedSets0 != null) { + this.limitedSets.addAll(limitedSets0); + } } @Override @@ -60,7 +62,7 @@ public class CardSetFilter extends CardFormatFilter { final DialogChooseSets dialog = new DialogChooseSets(this.sets, null, this.limitedSets, true, this.allowReprints); final CardSetFilter itemFilter = this; - + dialog.setOkCallback(new Runnable() { @Override public void run() { From a00dc8fc4c2bade6d26297a3b80b272b906a15ab Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Tue, 27 Dec 2022 10:55:59 +0800 Subject: [PATCH 33/76] Update wicked_guardian.txt - closes #2172 --- forge-gui/res/cardsfolder/w/wicked_guardian.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/cardsfolder/w/wicked_guardian.txt b/forge-gui/res/cardsfolder/w/wicked_guardian.txt index e8655d8b8d2..b6323ddab43 100644 --- a/forge-gui/res/cardsfolder/w/wicked_guardian.txt +++ b/forge-gui/res/cardsfolder/w/wicked_guardian.txt @@ -2,7 +2,10 @@ Name:Wicked Guardian ManaCost:3 B Types:Creature Human Noble PT:4/2 -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDamage | OptionalDecider$ You | TriggerDescription$ When CARDNAME enters the battlefield, you may have it deal 2 damage to another creature you control. If you do, draw a card. -SVar:TrigDamage:DB$ DealDamage | ValidTgts$ Creature.Other+YouCtrl | TgtPrompt$ Select another creature you control | NumDmg$ 2 | SubAbility$ DBDraw -SVar:DBDraw:DB$ Draw | NumCards$ 1 +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChoose | TriggerDescription$ When CARDNAME enters the battlefield, you may have it deal 2 damage to another creature you control. If you do, draw a card. +SVar:TrigChoose:DB$ ChooseCard | Choices$ Creature.YouCtrl+Other | ChoiceZone$ Battlefield | ChoiceTitle$ Select another creature you control | SubAbility$ DBDamageChosen +SVar:DBDamageChosen:DB$ DealDamage | Defined$ ChosenCard | NumDmg$ 2 | SubAbility$ DBImmediateTrigger +SVar:DBImmediateTrigger:DB$ ImmediateTrigger | RememberObjects$ ChosenCard | ConditionDefined$ ChosenCard | ConditionPresent$ Creature.YouCtrl+Other | ConditionCompare$ GE1 | Execute$ DBDraw | TriggerDescription$ If you do, draw a card. +SVar:DBDraw:DB$ Draw | NumCards$ 1 | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True Oracle:When Wicked Guardian enters the battlefield, you may have it deal 2 damage to another creature you control. If you do, draw a card. From a8863054104edaa431601583401418d49a851e45 Mon Sep 17 00:00:00 2001 From: Mike S <81990938+MikeS-NZ@users.noreply.github.com> Date: Tue, 27 Dec 2022 07:50:38 -0600 Subject: [PATCH 34/76] ONE: Kaito, Dancing Shadow + Token script (#2163) * ONE: Kaito, Dancing Shadow + Token script ONE: Added script for Kaito, Dancing Shadow, plus his token script. - Kaito, Dancing Shadow - 2/2 colorless Drone artifact creature token with deathtouch and "When this creature leaves the battlefield, each opponent loses 2 life and you gain 2 life." * Update c_2_2_a_drone_deathtouch_leavedrain.txt * Engine change to DamageDoneOnce Engine change to accommodate proposed change to Kaito, Dancing Shadow's script * Update TriggerDamageDoneOnce.java * Update kaito_dancing_shadow.txt * Update kaito_dancing_shadow.txt * Update TriggerDamageDoneOnce.java fix indention * Update kaito_dancing_shadow.txt Co-authored-by: Hans Mackowiak --- .../forge/game/trigger/TriggerDamageDoneOnce.java | 10 ++++------ .../cardsfolder/upcoming/kaito_dancing_shadow.txt | 12 ++++++++++++ .../c_2_2_a_drone_deathtouch_leavedrain.txt | 9 +++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/kaito_dancing_shadow.txt create mode 100644 forge-gui/res/tokenscripts/c_2_2_a_drone_deathtouch_leavedrain.txt diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDamageDoneOnce.java b/forge-game/src/main/java/forge/game/trigger/TriggerDamageDoneOnce.java index bfdb696212a..b401e8e5959 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerDamageDoneOnce.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerDamageDoneOnce.java @@ -1,12 +1,10 @@ package forge.game.trigger; import java.util.Map; -import java.util.Set; - -import com.google.common.collect.Sets; import forge.game.ability.AbilityKey; import forge.game.card.Card; +import forge.game.card.CardCollection; import forge.game.card.CardUtil; import forge.game.spellability.SpellAbility; import forge.util.Localizer; @@ -79,11 +77,11 @@ public class TriggerDamageDoneOnce extends Trigger { return result; } - public Set getDamageSources(Map damageMap) { + public CardCollection getDamageSources(Map damageMap) { if (!hasParam("ValidSource")) { - return Sets.newHashSet(damageMap.keySet()); + return new CardCollection(damageMap.keySet()); } - Set result = Sets.newHashSet(); + CardCollection result = new CardCollection(); for (Card c : damageMap.keySet()) { if (matchesValid(c, getParam("ValidSource").split(","))) { result.add(c); diff --git a/forge-gui/res/cardsfolder/upcoming/kaito_dancing_shadow.txt b/forge-gui/res/cardsfolder/upcoming/kaito_dancing_shadow.txt new file mode 100644 index 00000000000..d2be974c5e0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kaito_dancing_shadow.txt @@ -0,0 +1,12 @@ +Name:Kaito, Dancing Shadow +ManaCost:2 U B +Types:Legendary Planeswalker Kaito +Loyalty:3 +T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Creature.YouCtrl | TriggerZones$ Battlefield | ValidTarget$ Player | Execute$ TrigLoyalty | TriggerDescription$ Whenever one or more creatures you control deal combat damage to a player, you may return one of them to its owner's hand. If you do, you may activate loyalty abilities of NICKNAME twice this turn rather than only once. +SVar:TrigLoyalty:AB$ Effect | Cost$ Return<1/Card.TriggeredSources> | StaticAbilities$ PWTwice +SVar:PWTwice:Mode$ NumLoyaltyAct | ValidCard$ Card.EffectSource | Twice$ True | Description$ You may activate the loyalty abilities of NICKNAME twice this turn rather than only once. +A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select up to one target creature | KW$ HIDDEN CARDNAME can't attack or block. | IsCurse$ True | Duration$ UntilYourNextTurn | AILogic$ DetainNonLand | StackDescription$ {c:Targeted} can't attack or block until your next turn. | SpellDescription$ Up to one target creature can't attack or block until your next turn. +A:AB$ Draw | Cost$ AddCounter<0/LOYALTY> | NumCards$ 1 | Planeswalker$ True | SpellDescription$ Draw a card. +A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenScript$ c_2_2_a_drone_deathtouch_leavedrain | SpellDescription$ Create a 2/2 colorless Drone artifact creature token with deathtouch and "When this creature leaves the battlefield, each opponent loses 2 life and you gain 2 life." +DeckHas:Ability$Token|LifeGain & Type$Artifact|Drone +Oracle:Whenever one or more creatures you control deal combat damage to a player, you may return one of them to its owner's hand. If you do, you may activate loyalty abilities of Kaito twice this turn rather than only once.\n[+1]: Up to one target creature can't attack or block until your next turn.\n[0]: Draw a card.\n[-2]: Create a 2/2 colorless Drone artifact creature token with deathtouch and "When this creature leaves the battlefield, each opponent loses 2 life and you gain 2 life." diff --git a/forge-gui/res/tokenscripts/c_2_2_a_drone_deathtouch_leavedrain.txt b/forge-gui/res/tokenscripts/c_2_2_a_drone_deathtouch_leavedrain.txt new file mode 100644 index 00000000000..8aa7f698360 --- /dev/null +++ b/forge-gui/res/tokenscripts/c_2_2_a_drone_deathtouch_leavedrain.txt @@ -0,0 +1,9 @@ +Name:Drone Token +ManaCost:no cost +Types:Artifact Creature Drone +PT:2/2 +K:Deathtouch +T:Mode$ ChangesZone | ValidCard$ Card.Self | Origin$ Battlefield | Execute$ TrigDrain | TriggerDescription$ When this creature leaves the battlefield, each opponent loses 2 life and you gain 2 life. +SVar:TrigDrain:DB$ LoseLife | Defined$ Opponent | LifeAmount$ 2 | SubAbility$ DBGainLife +SVar:DBGainLife:DB$ GainLife | Defined$ You | LifeAmount$ 2 +Oracle:Deathtouch\nWhen this creature leaves the battlefield, each opponent loses 2 life and you gain 2 life. From 7a3c19992aec55ac91581f4fe20238815e507f45 Mon Sep 17 00:00:00 2001 From: HassanSky81 <65109338+HassanSky81@users.noreply.github.com> Date: Tue, 27 Dec 2022 22:34:31 +0200 Subject: [PATCH 35/76] Update PointOfInterestChanges.java --- .../forge/adventure/pointofintrest/PointOfInterestChanges.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java b/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java index 22595e81e7d..4e1478f690e 100644 --- a/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java +++ b/forge-gui-mobile/src/forge/adventure/pointofintrest/PointOfInterestChanges.java @@ -41,7 +41,7 @@ public class PointOfInterestChanges implements SaveFileContent { } data.storeObject("keys",keys.toArray(new String[0])); for(int i=0;i Date: Wed, 28 Dec 2022 08:15:17 +0100 Subject: [PATCH 36/76] Clean up (#2182) --- .../main/java/forge/ai/simulation/GameCopier.java | 13 +++++++------ .../game/ability/effects/BecomesBlockedEffect.java | 7 ++----- .../ability/effects/ChangeCombatantsEffect.java | 7 ++----- .../game/ability/effects/ChangeZoneAllEffect.java | 1 - .../game/ability/effects/ControlPlayerEffect.java | 2 +- .../ability/effects/CountersPutOrRemoveEffect.java | 5 +++-- .../game/ability/effects/CountersRemoveEffect.java | 9 +++------ .../game/ability/effects/DamageEachEffect.java | 5 ++--- .../ability/effects/RemoveFromCombatEffect.java | 9 ++------- .../game/staticability/StaticAbilityContinuous.java | 12 ++++++------ forge-gui/res/cardsfolder/c/celestial_mantle.txt | 6 ++---- forge-gui/res/cardsfolder/d/deputy_of_detention.txt | 4 ++-- .../res/cardsfolder/g/gruesome_realization.txt | 2 +- forge-gui/res/cardsfolder/l/legions_to_ashes.txt | 3 ++- 14 files changed, 35 insertions(+), 50 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java index 6c6ebc1b605..1485e3ed1c9 100644 --- a/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java +++ b/forge-ai/src/main/java/forge/ai/simulation/GameCopier.java @@ -76,22 +76,23 @@ public class GameCopier { for (RegisteredPlayer p : origPlayers) { newPlayers.add(clonePlayer(p)); } + GameRules currentRules = origGame.getRules(); Match newMatch = new Match(currentRules, newPlayers, origGame.getView().getTitle()); Game newGame = new Game(newPlayers, currentRules, newMatch); + for (int i = 0; i < origGame.getPlayers().size(); i++) { Player origPlayer = origGame.getPlayers().get(i); Player newPlayer = newGame.getPlayers().get(i); newPlayer.setLife(origPlayer.getLife(), null); - newPlayer.setDamageReceivedThisTurn(origPlayer.getDamageReceivedThisTurn()); - newPlayer.setActivateLoyaltyAbilityThisTurn(origPlayer.getActivateLoyaltyAbilityThisTurn()); - for (int j = 0; j < origPlayer.getSpellsCastThisTurn(); j++) - newPlayer.addSpellCastThisTurn(); - newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn()); - newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters())); newPlayer.setLifeLostLastTurn(origPlayer.getLifeLostLastTurn()); newPlayer.setLifeLostThisTurn(origPlayer.getLifeLostThisTurn()); newPlayer.setLifeGainedThisTurn(origPlayer.getLifeGainedThisTurn()); + newPlayer.setLifeStartedThisTurnWith(origPlayer.getLifeStartedThisTurnWith()); + newPlayer.setDamageReceivedThisTurn(origPlayer.getDamageReceivedThisTurn()); + newPlayer.setActivateLoyaltyAbilityThisTurn(origPlayer.getActivateLoyaltyAbilityThisTurn()); + newPlayer.setLandsPlayedThisTurn(origPlayer.getLandsPlayedThisTurn()); + newPlayer.setCounters(Maps.newHashMap(origPlayer.getCounters())); newPlayer.setBlessing(origPlayer.hasBlessing()); newPlayer.setRevolt(origPlayer.hasRevolt()); newPlayer.setLibrarySearched(origPlayer.getLibrarySearched()); diff --git a/forge-game/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java b/forge-game/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java index 8547d6bd190..226daa867c3 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/BecomesBlockedEffect.java @@ -3,8 +3,6 @@ package forge.game.ability.effects; import java.util.List; import java.util.Map; -import org.apache.commons.lang3.StringUtils; - import com.google.common.collect.Lists; import forge.game.Game; @@ -14,6 +12,7 @@ import forge.game.card.Card; import forge.game.event.GameEventCombatChanged; import forge.game.spellability.SpellAbility; import forge.game.trigger.TriggerType; +import forge.util.Lang; public class BecomesBlockedEffect extends SpellAbilityEffect { @@ -21,9 +20,7 @@ public class BecomesBlockedEffect extends SpellAbilityEffect { protected String getStackDescription(SpellAbility sa) { final StringBuilder sb = new StringBuilder(); - final List tgtCards = getTargetCards(sa); - - sb.append(StringUtils.join(tgtCards, ", ")); + sb.append(Lang.joinHomogenous(getTargetCards(sa))); sb.append(" becomes blocked."); return sb.toString(); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeCombatantsEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeCombatantsEffect.java index 4d138cad810..128ce54a8b0 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeCombatantsEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeCombatantsEffect.java @@ -1,10 +1,7 @@ package forge.game.ability.effects; -import java.util.List; import java.util.Map; -import org.apache.commons.lang3.StringUtils; - import com.google.common.collect.Maps; import forge.game.Game; @@ -19,6 +16,7 @@ import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; import forge.util.CardTranslation; +import forge.util.Lang; import forge.util.Localizer; import forge.util.collect.FCollection; @@ -28,10 +26,9 @@ public class ChangeCombatantsEffect extends SpellAbilityEffect { protected String getStackDescription(SpellAbility sa) { final StringBuilder sb = new StringBuilder(); - final List tgtCards = getTargetCards(sa); // should update when adding effects for defined blocker sb.append("Reselect the defender of "); - sb.append(StringUtils.join(tgtCards, ", ")); + sb.append(Lang.joinHomogenous(getTargetCards(sa))); return sb.toString(); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java index a5c036f608a..528ac2bbbc2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java @@ -68,7 +68,6 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { if (origin.contains(ZoneType.Library) && sa.hasParam("Search") && !sa.getActivatingPlayer().canSearchLibraryWith(sa, p)) { cards.removeAll(p.getCardsIn(ZoneType.Library)); } - } if (origin.contains(ZoneType.Library) && sa.hasParam("Search")) { // Search library using changezoneall effect need a param "Search" diff --git a/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java index 2490857f88f..29110762def 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ControlPlayerEffect.java @@ -20,7 +20,7 @@ public class ControlPlayerEffect extends SpellAbilityEffect { @Override protected String getStackDescription(SpellAbility sa) { List tgtPlayers = getTargetPlayers(sa); - return TextUtil.concatWithSpace(sa.getActivatingPlayer().toString(),"controls", Lang.joinHomogenous(tgtPlayers),"during their next turn"); + return TextUtil.concatWithSpace(sa.getActivatingPlayer().toString(), "controls", Lang.joinHomogenous(tgtPlayers), "during their next turn"); } @SuppressWarnings("serial") diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java index c36f92001f4..bb41d885831 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutOrRemoveEffect.java @@ -28,8 +28,9 @@ public class CountersPutOrRemoveEffect extends SpellAbilityEffect { @Override protected String getStackDescription(SpellAbility sa) { final StringBuilder sb = new StringBuilder(); - final Player pl = !sa.hasParam("DefinedPlayer") ? sa.getActivatingPlayer() : - AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("DefinedPlayer"), sa).getFirst(); + final Player pl = sa.hasParam("DefinedPlayer") ? + AbilityUtils.getDefinedPlayers(sa.getHostCard(), sa.getParam("DefinedPlayer"), sa).getFirst() + : sa.getActivatingPlayer(); sb.append(pl.getName()); if (sa.hasParam("CounterType")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java index e17cd12cc04..2b17995ad58 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersRemoveEffect.java @@ -18,6 +18,7 @@ import forge.game.player.PlayerController; import forge.game.spellability.SpellAbility; import forge.game.zone.Zone; import forge.game.zone.ZoneType; +import forge.util.Lang; import forge.util.Localizer; public class CountersRemoveEffect extends SpellAbilityEffect { @@ -53,13 +54,9 @@ public class CountersRemoveEffect extends SpellAbilityEffect { } sb.append(" from"); - for (final Card c : getTargetCards(sa)) { - sb.append(" ").append(c); - } + sb.append(Lang.joinHomogenous(getTargetCards(sa))); - for (final Player tgtPlayer : getTargetPlayers(sa)) { - sb.append(" ").append(tgtPlayer); - } + sb.append(Lang.joinHomogenous(getTargetPlayers(sa))); sb.append("."); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java index 26020eb59eb..93d39d5951d 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DamageEachEffect.java @@ -9,6 +9,7 @@ import forge.game.card.CardLists; import forge.game.player.Player; import forge.game.spellability.SpellAbility; import forge.game.zone.ZoneType; +import forge.util.Lang; import forge.util.collect.FCollectionView; public class DamageEachEffect extends DamageBaseEffect { @@ -38,9 +39,7 @@ public class DamageEachEffect extends DamageBaseEffect { sb.append(sa.getParam("StackDescription")); } else { sb.append("Each ").append(desc).append(" deals ").append(dmg).append(" to "); - for (final Player p : getTargetPlayers(sa)) { - sb.append(p); - } + Lang.joinHomogenous(getTargetPlayers(sa)); if (sa.hasParam("DefinedCards")) { if (sa.getParam("DefinedCards").equals("Self")) { sb.append(" itself"); diff --git a/forge-game/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java index dba17adcc12..77906ed0664 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/RemoveFromCombatEffect.java @@ -1,9 +1,5 @@ package forge.game.ability.effects; -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - import forge.game.Game; import forge.game.ability.AbilityUtils; import forge.game.ability.SpellAbilityEffect; @@ -12,6 +8,7 @@ import forge.game.card.CardCollection; import forge.game.combat.Combat; import forge.game.player.Player; import forge.game.spellability.SpellAbility; +import forge.util.Lang; public class RemoveFromCombatEffect extends SpellAbilityEffect { @@ -19,10 +16,8 @@ public class RemoveFromCombatEffect extends SpellAbilityEffect { protected String getStackDescription(SpellAbility sa) { final StringBuilder sb = new StringBuilder(); - final List tgtCards = getTargetCards(sa); - sb.append("Remove "); - sb.append(StringUtils.join(tgtCards, ", ")); + sb.append(Lang.joinHomogenous(getTargetCards(sa))); sb.append(" from combat."); return sb.toString(); diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java index 651c0421140..3e531be8fde 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -563,6 +563,11 @@ public final class StaticAbilityContinuous { p.setMaxHandSize(max); } } + if (params.containsKey("RaiseMaxHandSize")) { + String rmhs = params.get("RaiseMaxHandSize"); + int rmax = AbilityUtils.calculateAmount(hostCard, rmhs, stAb); + p.setMaxHandSize(p.getMaxHandSize() + rmax); + } if (params.containsKey("AdjustLandPlays")) { String mhs = params.get("AdjustLandPlays"); @@ -573,6 +578,7 @@ public final class StaticAbilityContinuous { p.addMaxLandPlays(se.getTimestamp(), add); } } + if (params.containsKey("ControlOpponentsSearchingLibrary")) { Player cntl = Iterables.getFirst(AbilityUtils.getDefinedPlayers(hostCard, params.get("ControlOpponentsSearchingLibrary"), stAb), null); p.addControlledWhileSearching(se.getTimestamp(), cntl); @@ -592,12 +598,6 @@ public final class StaticAbilityContinuous { p.addAdditionalOptionalVote(se.getTimestamp(), add); } - if (params.containsKey("RaiseMaxHandSize")) { - String rmhs = params.get("RaiseMaxHandSize"); - int rmax = AbilityUtils.calculateAmount(hostCard, rmhs, stAb); - p.setMaxHandSize(p.getMaxHandSize() + rmax); - } - if (params.containsKey("ManaConversion")) { AbilityUtils.applyManaColorConversion(p.getManaPool(), params); } diff --git a/forge-gui/res/cardsfolder/c/celestial_mantle.txt b/forge-gui/res/cardsfolder/c/celestial_mantle.txt index 35c6d468427..82dcdca1e1e 100644 --- a/forge-gui/res/cardsfolder/c/celestial_mantle.txt +++ b/forge-gui/res/cardsfolder/c/celestial_mantle.txt @@ -5,8 +5,6 @@ K:Enchant creature A:SP$ Attach | Cost$ 3 W W W | ValidTgts$ Creature | AILogic$ Pump S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 3 | AddToughness$ 3 | Description$ Enchanted creature gets +3/+3. T:Mode$ DamageDone | ValidSource$ Card.AttachedBy | ValidTarget$ Player | Execute$ TrigSetLife | CombatDamage$ True | TriggerDescription$ Whenever enchanted creature deals combat damage to a player, double its controller's life total. -SVar:TrigSetLife:DB$ Pump | RememberObjects$ TriggeredSourceController | SubAbility$ DBSet -SVar:DBSet:DB$ SetLife | Defined$ Remembered | LifeAmount$ X | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:PlayerCountRemembered$LifeTotal/Twice +SVar:TrigSetLife:DB$ SetLife | Defined$ TriggeredSourceController | LifeAmount$ X +SVar:X:PlayerCountDefinedTriggeredSourceController$LifeTotal/Twice Oracle:Enchant creature\nEnchanted creature gets +3/+3.\nWhenever enchanted creature deals combat damage to a player, double its controller's life total. diff --git a/forge-gui/res/cardsfolder/d/deputy_of_detention.txt b/forge-gui/res/cardsfolder/d/deputy_of_detention.txt index 336dde3ebcb..bcd0b368c77 100644 --- a/forge-gui/res/cardsfolder/d/deputy_of_detention.txt +++ b/forge-gui/res/cardsfolder/d/deputy_of_detention.txt @@ -3,6 +3,6 @@ ManaCost:1 W U Types:Creature Vedalken Wizard PT:1/3 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, exile target nonland permanent an opponent controls and all other nonland permanents that player controls with the same name as that permanent until CARDNAME leaves the battlefield. -SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Permanent.nonLand+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls | SubAbility$ DBChangeZoneAll | Duration$ UntilHostLeavesPlay -SVar:DBChangeZoneAll:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ Permanent.nonLand+NotDefinedTargeted+sharesNameWith Targeted+ControlledBy TargetedOrController | Duration$ UntilHostLeavesPlay +SVar:TrigExile:DB$ Pump | ValidTgts$ Permanent.nonLand+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls | SubAbility$ DBChangeZoneAll +SVar:DBChangeZoneAll:DB$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ TargetedCard.Self,Permanent.nonLand+NotDefinedTargeted+sharesNameWith Targeted+ControlledBy TargetedController | Duration$ UntilHostLeavesPlay Oracle:When Deputy of Detention enters the battlefield, exile target nonland permanent an opponent controls and all other nonland permanents that player controls with the same name as that permanent until Deputy of Detention leaves the battlefield. diff --git a/forge-gui/res/cardsfolder/g/gruesome_realization.txt b/forge-gui/res/cardsfolder/g/gruesome_realization.txt index 452d4dc887d..6d5fe617b66 100644 --- a/forge-gui/res/cardsfolder/g/gruesome_realization.txt +++ b/forge-gui/res/cardsfolder/g/gruesome_realization.txt @@ -3,6 +3,6 @@ ManaCost:1 B B Types:Sorcery A:SP$ Charm | Choices$ DBDraw,DBDebuff SVar:DBDraw:DB$ Draw | NumCards$ 2 | SubAbility$ DBLoseLife | SpellDescription$ You draw two cards and you lose 2 life. -SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1 +SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 2 SVar:DBDebuff:DB$ PumpAll | ValidCards$ Creature.OppCtrl | NumAtt$ -1 | NumDef$ -1 | IsCurse$ True | SpellDescription$ Creatures your opponents control get -1/-1 until end of turn. Oracle:Choose one —\n• You draw two cards and you lose 2 life.\n• Creatures your opponents control get -1/-1 until end of turn. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/l/legions_to_ashes.txt b/forge-gui/res/cardsfolder/l/legions_to_ashes.txt index a9f6c9ad77f..a5984327b70 100644 --- a/forge-gui/res/cardsfolder/l/legions_to_ashes.txt +++ b/forge-gui/res/cardsfolder/l/legions_to_ashes.txt @@ -1,6 +1,7 @@ Name:Legions to Ashes ManaCost:1 W B Types:Sorcery -A:SP$ ChangeZoneAll | TgtPrompt$ Select target nonland permanent an opponent controls | ValidTgts$ Permanent.nonLand+OppCtrl | ChangeType$ TargetedCard.Self,Creature.NotDefinedTargeted+token+sharesNameWith Targeted | Origin$ Battlefield | Destination$ Exile | SpellDescription$ Exile target nonland permanent an opponent controls and all tokens that player controls with the same name as that permanent. +A:SP$ Pump | ValidTgts$ Permanent.nonland | TgtPrompt$ Select target nonland permanent an opponent controls | SubAbility$ ExileAll | SpellDescription$ Exile target nonland permanent an opponent controls and all tokens that player controls with the same name as that permanent. +SVar:ExileAll:DB$ ChangeZoneAll | ChangeType$ TargetedCard.Self,Card.NotDefinedTargeted+token+sharesNameWith Targeted+ControlledBy TargetedController | Origin$ Battlefield | Destination$ Exile AI:RemoveDeck:Random Oracle:Exile target nonland permanent an opponent controls and all tokens that player controls with the same name as that permanent. \ No newline at end of file From b0d5d952e873c7294fd6c5633ddea196ca733cba Mon Sep 17 00:00:00 2001 From: asvitkine Date: Wed, 28 Dec 2022 05:02:47 -0500 Subject: [PATCH 37/76] Add "ask simulated ai" dev option. (#2180) * Add "ask simulated ai" dev option. Also moves the setup/dump game state options at the end of the list, as they're different from the other ones and shouldn't just be in the middle. Also makes the code a bit more concise for the different mouse listeners. * Fix comment location. --- .../forge/screens/match/controllers/CDev.java | 236 +++--------------- .../java/forge/screens/match/views/VDev.java | 22 +- forge-gui/res/languages/en-US.properties | 1 + .../java/forge/interfaces/IDevModeCheats.java | 4 +- .../forge/player/PlayerControllerHuman.java | 3 +- 5 files changed, 57 insertions(+), 209 deletions(-) diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDev.java b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDev.java index be6b3b8540c..896dfa0dc47 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDev.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDev.java @@ -28,31 +28,32 @@ public final class CDev implements ICDoc { this.view = new VDev(this); addListener(view); - view.getLblUnlimitedLands().addMouseListener(madUnlimited); - view.getLblViewAll().addMouseListener(madViewAll); - view.getLblGenerateMana().addMouseListener(madMana); - view.getLblSetupGame().addMouseListener(madSetup); - view.getLblDumpGame().addMouseListener(madDump); - view.getLblTutor().addMouseListener(madTutor); - view.getLblCardToHand().addMouseListener(madCardToHand); - view.getLblExileFromHand().addMouseListener(madExileFromHand); - view.getLblCardToBattlefield().addMouseListener(madCardToBattlefield); - view.getLblCardToLibrary().addMouseListener(madCardToLibrary); - view.getLblCardToGraveyard().addMouseListener(madCardToGraveyard); - view.getLblCardToExile().addMouseListener(madCardToExile); - view.getLblCastSpell().addMouseListener(madCastASpell); - view.getLblRepeatAddCard().addMouseListener(madRepeatAddCard); - view.getLblAddCounterPermanent().addMouseListener(madAddCounter); - view.getLblSubCounterPermanent().addMouseListener(madSubCounter); - view.getLblTapPermanent().addMouseListener(madTap); - view.getLblUntapPermanent().addMouseListener(madUntap); - view.getLblSetLife().addMouseListener(madLife); - view.getLblWinGame().addMouseListener(madWinGame); - view.getLblExileFromPlay().addMouseListener(madExileFromPlay); - view.getLblRemoveFromGame().addMouseListener(madRemoveFromGame); - view.getLblRiggedRoll().addMouseListener(madRiggedRoll); - view.getLblWalkTo().addMouseListener(madWalkToPlane); - view.getLblAskAI().addMouseListener(madAskAI); + view.getLblUnlimitedLands().addMouseListener(onClick(this::togglePlayManyLandsPerTurn)); + view.getLblViewAll().addMouseListener(onClick(this::toggleViewAllCards)); + view.getLblGenerateMana().addMouseListener(onClick(this::generateMana)); + view.getLblSetupGame().addMouseListener(onClick(this::setupGameState)); + view.getLblDumpGame().addMouseListener(onClick(this::dumpGameState)); + view.getLblTutor().addMouseListener(onClick(this::tutorForCard)); + view.getLblCardToHand().addMouseListener(onClick(this::addCardToHand)); + view.getLblExileFromHand().addMouseListener(onClick(this::exileCardsFromHand)); + view.getLblCardToBattlefield().addMouseListener(onClick(this::addCardToBattlefield)); + view.getLblCardToLibrary().addMouseListener(onClick(this::addCardToLibrary)); + view.getLblCardToGraveyard().addMouseListener(onClick(this::addCardToGraveyard)); + view.getLblCardToExile().addMouseListener(onClick(this::addCardToExile)); + view.getLblCastSpell().addMouseListener(onClick(this::castASpell)); + view.getLblRepeatAddCard().addMouseListener(onClick(this::repeatAddCard)); + view.getLblAddCounterPermanent().addMouseListener(onClick(this::addCounterToPermanent)); + view.getLblSubCounterPermanent().addMouseListener(onClick(this::removeCountersFromPermanent)); + view.getLblTapPermanent().addMouseListener(onClick(this::tapPermanent)); + view.getLblUntapPermanent().addMouseListener(onClick(this::untapPermanent)); + view.getLblSetLife().addMouseListener(onClick(this::setPlayerLife)); + view.getLblWinGame().addMouseListener(onClick(this::winGame)); + view.getLblExileFromPlay().addMouseListener(onClick(this::exileCardsFromPlay)); + view.getLblRemoveFromGame().addMouseListener(onClick(this::removeCardsFromGame)); + view.getLblRiggedRoll().addMouseListener(onClick(this::riggedPlanerRoll)); + view.getLblWalkTo().addMouseListener(onClick(this::planeswalkTo)); + view.getLblAskAI().addMouseListener(onClick(this::askAI)); + view.getLblAskSimulationAI().addMouseListener(onClick(this::askSimulationAI)); } public IGameController getController() { return matchUI.getGameController(); @@ -66,258 +67,99 @@ public final class CDev implements ICDoc { listeners.add(listener); } - private final MouseListener madUnlimited = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - togglePlayManyLandsPerTurn(); - } - }; + private MouseListener onClick(Runnable r) { + return new MouseAdapter() { + @Override + public void mousePressed(final MouseEvent e) { + r.run(); + } + }; + } + public void togglePlayManyLandsPerTurn() { final boolean newValue = !view.getLblUnlimitedLands().getToggled(); getController().cheat().setCanPlayUnlimitedLands(newValue); update(); } - private final MouseListener madViewAll = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - toggleViewAllCards(); - } - }; public void toggleViewAllCards() { final boolean newValue = !view.getLblViewAll().getToggled(); getController().cheat().setViewAllCards(newValue); update(); } - private final MouseListener madMana = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - generateMana(); - } - }; public void generateMana() { getController().cheat().generateMana(); } - - private final MouseListener madSetup = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - setupGameState(); - } - }; public void setupGameState() { getController().cheat().setupGameState(); } - - private final MouseListener madDump = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - dumpGameState(); - } - }; public void dumpGameState() { getController().cheat().dumpGameState(); } - - private final MouseListener madTutor = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - tutorForCard(); - } - }; public void tutorForCard() { getController().cheat().tutorForCard(); } - - private final MouseListener madCardToHand = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - addCardToHand(); - } - }; public void addCardToHand() { getController().cheat().addCardToHand(); } - - private final MouseListener madCardToLibrary = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - addCardToLibrary(); - } - }; public void addCardToLibrary() { getController().cheat().addCardToLibrary(); } - - private final MouseListener madCardToGraveyard = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - addCardToGraveyard(); - } - }; public void addCardToGraveyard() { getController().cheat().addCardToGraveyard(); } - - private final MouseListener madCardToExile = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - addCardToExile(); - } - }; public void addCardToExile() { getController().cheat().addCardToExile(); } - private final MouseListener madCastASpell = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - castASpell(); - } - }; public void castASpell() { getController().cheat().castASpell(); } - - private final MouseListener madRepeatAddCard = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - repeatAddCard(); - } - }; public void repeatAddCard() { getController().cheat().repeatLastAddition(); } - - private final MouseListener madExileFromHand = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - exileCardsFromHand(); - } - }; public void exileCardsFromHand() { getController().cheat().exileCardsFromHand(); } - - private final MouseListener madAddCounter = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - addCounterToPermanent(); - } - }; public void addCounterToPermanent() { getController().cheat().addCountersToPermanent(); } - - private final MouseListener madSubCounter = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - removeCountersFromPermanent(); - } - }; public void removeCountersFromPermanent() { getController().cheat().removeCountersFromPermanent(); } - - private final MouseListener madTap = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - tapPermanent(); - } - }; public void tapPermanent() { getController().cheat().tapPermanents(); } - - private final MouseListener madUntap = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - untapPermanent(); - } - }; public void untapPermanent() { getController().cheat().untapPermanents(); } - - private final MouseListener madLife = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - setPlayerLife(); - } - }; public void setPlayerLife() { getController().cheat().setPlayerLife(); } - - private final MouseListener madWinGame = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - winGame(); - } - }; public void winGame() { getController().cheat().winGame(); } - - private final MouseListener madCardToBattlefield = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - addCardToBattlefield(); - } - }; public void addCardToBattlefield() { getController().cheat().addCardToBattlefield(); } - - private final MouseListener madExileFromPlay = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - exileCardsFromPlay(); - } - }; public void exileCardsFromPlay() { getController().cheat().exileCardsFromBattlefield(); } - - private final MouseListener madRemoveFromGame = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - removeCardsFromGame(); - } - }; public void removeCardsFromGame() { getController().cheat().removeCardsFromGame(); } - - private final MouseListener madRiggedRoll = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - riggedPlanerRoll(); - } - }; public void riggedPlanerRoll() { getController().cheat().riggedPlanarRoll(); } - - private final MouseListener madWalkToPlane = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - planeswalkTo(); - } - }; public void planeswalkTo() { getController().cheat().planeswalkTo(); } - - private final MouseListener madAskAI = new MouseAdapter() { - @Override - public void mousePressed(final MouseEvent e) { - askAI(); - } - }; public void askAI() { - getController().cheat().askAI(); + getController().cheat().askAI(false); + } + public void askSimulationAI() { + getController().cheat().askAI(true); } //========== End mouse listener inits diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/views/VDev.java b/forge-gui-desktop/src/main/java/forge/screens/match/views/VDev.java index e1deb6fe01c..6de6eb49e30 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/views/VDev.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/views/VDev.java @@ -82,6 +82,7 @@ public class VDev implements IVDoc, IDevListener { private final DevLabel lblWalkTo = new DevLabel(Localizer.getInstance().getMessage("lblWalkTo")); private final DevLabel lblAskAI = new DevLabel(Localizer.getInstance().getMessage("lblAskAI")); + private final DevLabel lblAskSimulationAI = new DevLabel(Localizer.getInstance().getMessage("lblAskSimulationAI")); private final CDev controller; @@ -91,7 +92,6 @@ public class VDev implements IVDoc, IDevListener { public VDev(final CDev controller) { this.controller = controller; - final String constraints = "w 95%!, gap 0 0 4px 0"; final String halfConstraints = "w 47%!, gap 0 0 4px 0"; final String halfConstraintsLeft = halfConstraints + ", split 2"; viewport.setOpaque(false); @@ -113,13 +113,14 @@ public class VDev implements IVDoc, IDevListener { viewport.add(this.lblWinGame, halfConstraints); viewport.add(this.lblAddCounterPermanent, halfConstraintsLeft); viewport.add(this.lblSubCounterPermanent, halfConstraints); - viewport.add(this.lblSetupGame, halfConstraintsLeft); - viewport.add(this.lblDumpGame, halfConstraints); viewport.add(this.lblTapPermanent, halfConstraintsLeft); viewport.add(this.lblUntapPermanent, halfConstraints); viewport.add(this.lblRiggedRoll, halfConstraintsLeft); viewport.add(this.lblWalkTo, halfConstraints); viewport.add(this.lblAskAI, halfConstraintsLeft); + viewport.add(this.lblAskSimulationAI, halfConstraintsLeft); + viewport.add(this.lblSetupGame, halfConstraintsLeft); + viewport.add(this.lblDumpGame, halfConstraints); } //========= Overridden methods @@ -302,20 +303,23 @@ public class VDev implements IVDoc, IDevListener { return this.lblAskAI; } + public DevLabel getLblAskSimulationAI() { + return this.lblAskSimulationAI; + } + /** * Labels that act as buttons which control dev mode functions. Labels are * used to support multiline text. */ - public class DevLabel extends SkinnedLabel { + public static class DevLabel extends SkinnedLabel { private static final long serialVersionUID = 7917311680519060700L; private FSkin.SkinColor defaultBG; private final FSkin.SkinColor hoverBG = FSkin.getColor(FSkin.Colors.CLR_HOVER); private final FSkin.SkinColor pressedBG = FSkin.getColor(FSkin.Colors.CLR_INACTIVE); private boolean toggled; - private int w, h; // Width, height, radius, insets (for paintComponent) - private final int r, i; + private final int r, i; // Radius, insets (for paintComponent) public DevLabel(final String text0) { super(); @@ -380,10 +384,10 @@ public class VDev implements IVDoc, IDevListener { */ @Override protected void paintComponent(final Graphics g) { - this.w = this.getWidth(); - this.h = this.getHeight(); + int w = this.getWidth(); + int h = this.getHeight(); g.setColor(this.getBackground()); - g.fillRoundRect(this.i, this.i, this.w - (2 * this.i), this.h - this.i, this.r, this.r); + g.fillRoundRect(this.i, this.i, w - (2 * this.i), h - this.i, this.r, this.r); super.paintComponent(g); } } diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 68fe3dff823..38317691163 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -2100,6 +2100,7 @@ lblRemoveFromGame=Remove Card from Game lblRiggedRoll=Rigged Planar Roll lblWalkTo=Planeswalk to lblAskAI=Ask AI for suggestion +lblAskSimulationAI=Ask Simulation AI for suggestion #PhaseType.java lblUntapStep=Untap step lblUpkeepStep=Upkeep step diff --git a/forge-gui/src/main/java/forge/interfaces/IDevModeCheats.java b/forge-gui/src/main/java/forge/interfaces/IDevModeCheats.java index 4e3bfbae68e..886261b5a7f 100644 --- a/forge-gui/src/main/java/forge/interfaces/IDevModeCheats.java +++ b/forge-gui/src/main/java/forge/interfaces/IDevModeCheats.java @@ -59,7 +59,7 @@ public interface IDevModeCheats { void planeswalkTo(); - void askAI(); + void askAI(boolean useSimulation); /** * Implementation of {@link IDevModeCheats} that disallows cheating by @@ -144,7 +144,7 @@ public interface IDevModeCheats { public void removeCardsFromGame() { } @Override - public void askAI() { + public void askAI(boolean useSimulation) { } }; diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 4150cb01d27..98724c11758 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -3042,8 +3042,9 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont }); } - public void askAI() { + public void askAI(boolean useSimulation) { PlayerControllerAi ai = new PlayerControllerAi(player.getGame(), player, player.getOriginalLobbyPlayer()); + ai.setUseSimulation(useSimulation); player.runWithController(() -> { List sas = ai.chooseSpellAbilityToPlay(); SpellAbility chosen = sas == null ? null : sas.get(0); From 88308ecd5c97049a08f8a7bdc1579224dc12380c Mon Sep 17 00:00:00 2001 From: asvitkine Date: Wed, 28 Dec 2022 09:56:16 -0500 Subject: [PATCH 38/76] Support export/import states with arbitrary players. (#2168) * Support export/import states with arbitrary players. Previously, the dump/setup game state dev options were only able to handle a 2-player human vs. AI game. This PR expands the functionality to support any number of players and ignores the type of player (naming them p0,p1,...,pN). The previous names of ai and human are supported for backwards compatibility. --- .../src/main/java/forge/ai/GameState.java | 471 +++++++----------- 1 file changed, 179 insertions(+), 292 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index d677c7fc8f0..49d9697ce9f 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -7,6 +7,7 @@ import java.util.*; import java.util.Map.Entry; import com.google.common.collect.*; +import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import forge.StaticData; @@ -52,27 +53,24 @@ public abstract class GameState { ZONES.put(ZoneType.Sideboard, "sideboard"); } - private int humanLife = -1; - private int computerLife = -1; - private String humanCounters = ""; - private String computerCounters = ""; - private String humanManaPool = ""; - private String computerManaPool = ""; - private String humanPersistentMana = ""; - private String computerPersistentMana = ""; - private int humanLandsPlayed = 0; - private int computerLandsPlayed = 0; - private int humanLandsPlayedLastTurn = 0; - private int computerLandsPlayedLastTurn = 0; + static class PlayerState { + private int life = -1; + private String counters = ""; + private String manaPool = ""; + private String persistentMana = ""; + private int landsPlayed = 0; + private int landsPlayedLastTurn = 0; + private String precast = null; + private String putOnStack = null; + private final Map cardTexts = new EnumMap<>(ZoneType.class); + } + private final List playerStates = new ArrayList<>(); private boolean puzzleCreatorState = false; - private final Map humanCardTexts = new EnumMap<>(ZoneType.class); - private final Map aiCardTexts = new EnumMap<>(ZoneType.class); - private final Map idToCard = new HashMap<>(); private final Map cardToAttachId = new HashMap<>(); - private final Map cardToEnchantPlayerId = new HashMap<>(); + private final Map cardToEnchantPlayerId = new HashMap<>(); private final Map markedDamage = new HashMap<>(); private final Map> cardToChosenClrs = new HashMap<>(); private final Map cardToChosenCards = new HashMap<>(); @@ -98,12 +96,6 @@ public abstract class GameState { private String tAdvancePhase = "NONE"; - private String precastHuman = null; - private String precastAI = null; - - private String putOnStackHuman = null; - private String putOnStackAI = null; - private int turn = 1; private boolean removeSummoningSickness = false; @@ -134,32 +126,27 @@ public abstract class GameState { sb.append("[state]\n"); } - sb.append(TextUtil.concatNoSpace("humanlife=", String.valueOf(humanLife), "\n")); - sb.append(TextUtil.concatNoSpace("ailife=", String.valueOf(computerLife), "\n")); - sb.append(TextUtil.concatNoSpace("humanlandsplayed=", String.valueOf(humanLandsPlayed), "\n")); - sb.append(TextUtil.concatNoSpace("ailandsplayed=", String.valueOf(computerLandsPlayed), "\n")); - sb.append(TextUtil.concatNoSpace("humanlandsplayedlastturn=", String.valueOf(humanLandsPlayedLastTurn), "\n")); - sb.append(TextUtil.concatNoSpace("ailandsplayedlastturn=", String.valueOf(computerLandsPlayedLastTurn), "\n")); sb.append(TextUtil.concatNoSpace("turn=", String.valueOf(turn), "\n")); - - if (!humanCounters.isEmpty()) { - sb.append(TextUtil.concatNoSpace("humancounters=", humanCounters, "\n")); - } - if (!computerCounters.isEmpty()) { - sb.append(TextUtil.concatNoSpace("aicounters=", computerCounters, "\n")); - } - - if (!humanManaPool.isEmpty()) { - sb.append(TextUtil.concatNoSpace("humanmanapool=", humanManaPool, "\n")); - } - if (!computerManaPool.isEmpty()) { - sb.append(TextUtil.concatNoSpace("aimanapool=", computerManaPool, "\n")); - } - sb.append(TextUtil.concatNoSpace("activeplayer=", tChangePlayer, "\n")); sb.append(TextUtil.concatNoSpace("activephase=", tChangePhase, "\n")); - appendCards(humanCardTexts, "human", sb); - appendCards(aiCardTexts, "ai", sb); + + int playerIndex = 0; + for (PlayerState p : playerStates) { + String prefix = "p" + playerIndex++; + sb.append(TextUtil.concatNoSpace(prefix + "life=", String.valueOf(p.life), "\n")); + sb.append(TextUtil.concatNoSpace(prefix + "landsplayed=", String.valueOf(p.landsPlayed), "\n")); + sb.append(TextUtil.concatNoSpace(prefix + "landsplayedlastturn=", String.valueOf(p.landsPlayedLastTurn), "\n")); + if (!p.counters.isEmpty()) { + sb.append(TextUtil.concatNoSpace(prefix + "counters=", p.counters, "\n")); + } + if (!p.manaPool.isEmpty()) { + sb.append(TextUtil.concatNoSpace(prefix + "manapool=", p.manaPool, "\n")); + } + if (!p.persistentMana.isEmpty()) { + sb.append(TextUtil.concatNoSpace(prefix + "persistentmana=", p.persistentMana, "\n")); + } + appendCards(p.cardTexts, prefix, sb); + } return sb.toString(); } @@ -169,33 +156,21 @@ public abstract class GameState { } } - public void initFromGame(Game game) throws Exception { - FCollectionView players = game.getPlayers(); - // Can only serialize a two player game with one AI and one human. - if (players.size() != 2) { - throw new Exception("Game not supported"); + public void initFromGame(Game game) { + playerStates.clear(); + for (Player player : game.getPlayers()) { + PlayerState p = new PlayerState(); + p.life = player.getLife(); + p.landsPlayed = player.getLandsPlayedThisTurn(); + p.landsPlayedLastTurn = player.getLandsPlayedLastTurn(); + p.counters = countersToString(player.getCounters()); + p.manaPool = processManaPool(player.getManaPool()); + playerStates.add(p); } - final Player human = game.getPlayers().get(0); - final Player ai = game.getPlayers().get(1); - if (!human.getController().isGuiPlayer() || !ai.getController().isAI()) { - throw new Exception("Game not supported"); - } - humanLife = human.getLife(); - computerLife = ai.getLife(); - humanLandsPlayed = human.getLandsPlayedThisTurn(); - computerLandsPlayed = ai.getLandsPlayedThisTurn(); - humanLandsPlayedLastTurn = human.getLandsPlayedLastTurn(); - computerLandsPlayedLastTurn = ai.getLandsPlayedLastTurn(); - humanCounters = countersToString(human.getCounters()); - computerCounters = countersToString(ai.getCounters()); - humanManaPool = processManaPool(human.getManaPool()); - computerManaPool = processManaPool(ai.getManaPool()); - tChangePlayer = game.getPhaseHandler().getPlayerTurn() == ai ? "ai" : "human"; + tChangePlayer = "p" + game.getPlayers().indexOf(game.getPhaseHandler().getPlayerTurn()); tChangePhase = game.getPhaseHandler().getPhase().toString(); turn = game.getPhaseHandler().getTurn(); - aiCardTexts.clear(); - humanCardTexts.clear(); // Mark the cards that need their ID remembered for various reasons cardsReferencedByID.clear(); @@ -238,8 +213,9 @@ public abstract class GameState { for (ZoneType zone : ZONES.keySet()) { // Init texts to empty, so that restoring will clear the state // if the zone had no cards in it (e.g. empty hand). - aiCardTexts.put(zone, ""); - humanCardTexts.put(zone, ""); + for (PlayerState p : playerStates) { + p.cardTexts.put(zone, ""); + } for (Card card : game.getCardsIncludePhasingIn(zone)) { if (card.getName().equals("Puzzle Goal") && card.getOracleText().contains("New Puzzle")) { puzzleCreatorState = true; @@ -247,18 +223,35 @@ public abstract class GameState { if (card instanceof DetachedCardEffect) { continue; } - addCard(zone, card.getController() == ai ? aiCardTexts : humanCardTexts, card); + int playerIndex = game.getPlayers().indexOf(card.getController()); + addCard(zone, playerStates.get(playerIndex).cardTexts, card); } } } + private String getPlayerString(Player p) { + return "P" + p.getGame().getPlayers().indexOf(p); + } + + private Player parsePlayerString(Game game, String str) { + if (str.equalsIgnoreCase("HUMAN")) { + return game.getPlayers().get(0); + } else if (str.equalsIgnoreCase("AI")) { + return game.getPlayers().get(1); + } else if (str.startsWith("P") && Character.isDigit(str.charAt(1))) { + return game.getPlayers().get(Integer.parseInt(String.valueOf(str.charAt(1)))); + } else { + return game.getPlayers().get(0); + } + } + private void addCard(ZoneType zoneType, Map cardTexts, Card c) { StringBuilder newText = new StringBuilder(cardTexts.get(zoneType)); if (newText.length() > 0) { newText.append(";"); } if (c.isToken()) { - newText.append("t:").append(new TokenInfo(c).toString()); + newText.append("t:").append(new TokenInfo(c)); } else { if (c.getPaperCard() == null) { return; @@ -281,8 +274,7 @@ public abstract class GameState { if (zoneType == ZoneType.Battlefield) { if (c.getOwner() != c.getController()) { - // TODO: Handle more than 2-player games. - newText.append("|Owner:" + (c.getOwner().isAI() ? "AI" : "Human")); + newText.append("|Owner:").append(getPlayerString(c.getOwner())); } if (c.isTapped()) { newText.append("|Tapped"); @@ -298,7 +290,7 @@ public abstract class GameState { } if (c.isPhasedOut()) { newText.append("|PhasedOut:"); - newText.append(c.getPhasedOut().isAI() ? "AI" : "HUMAN"); + newText.append(getPlayerString(c.getPhasedOut())); } if (c.isFaceDown()) { newText.append("|FaceDown"); @@ -319,10 +311,8 @@ public abstract class GameState { } if (c.getPlayerAttachedTo() != null) { - // TODO: improve this for game states with more than two players newText.append("|EnchantingPlayer:"); - Player p = c.getPlayerAttachedTo(); - newText.append(p.getController().isAI() ? "AI" : "HUMAN"); + newText.append(getPlayerString(c.getPlayerAttachedTo())); } else if (c.isAttachedToEntity()) { newText.append("|AttachedTo:").append(c.getEntityAttachedTo().getId()); } @@ -348,11 +338,8 @@ public abstract class GameState { } List chosenCardIds = Lists.newArrayList(); - for (Object obj : c.getChosenCards()) { - if (obj instanceof Card) { - int id = ((Card)obj).getId(); - chosenCardIds.add(String.valueOf(id)); - } + for (Card obj : c.getChosenCards()) { + chosenCardIds.add(String.valueOf(obj.getId())); } if (!chosenCardIds.isEmpty()) { newText.append("|ChosenCards:").append(TextUtil.join(chosenCardIds, ",")); @@ -465,16 +452,36 @@ public abstract class GameState { public void parse(InputStream in) throws Exception { final BufferedReader br = new BufferedReader(new InputStreamReader(in)); - - String line; - while ((line = br.readLine()) != null) { - parseLine(line); - } + parse(br.lines()); } public void parse(List lines) { - for (String line : lines) { - parseLine(line); + parse(lines.stream()); + } + + public void parse(Stream lines) { + playerStates.clear(); + lines.forEach(this::parseLine); + } + + + private PlayerState getPlayerState(int index) { + while (index >= playerStates.size()) { + playerStates.add(new PlayerState()); + } + return playerStates.get(index); + } + + private PlayerState getPlayerState(String key) { + if (key.startsWith("human")) { + return getPlayerState(0); + } else if (key.startsWith("ai")) { + return getPlayerState(1); + } else if (key.startsWith("p") && Character.isDigit(key.charAt(1))) { + return getPlayerState(Integer.parseInt(String.valueOf(key.charAt(1)))); + } else { + System.err.println("Unknown player state key: " + key); + return new PlayerState(); } } @@ -495,142 +502,56 @@ public abstract class GameState { return; } - boolean isHuman = categoryName.startsWith("human"); - if (categoryName.equals("turn")) { turn = Integer.parseInt(categoryValue); - } - - else if (categoryName.equals("removesummoningsickness")) { + } else if (categoryName.equals("removesummoningsickness")) { removeSummoningSickness = categoryValue.equalsIgnoreCase("true"); - } - - else if (categoryName.endsWith("life")) { - if (isHuman) - humanLife = Integer.parseInt(categoryValue); - else - computerLife = Integer.parseInt(categoryValue); - } - - else if (categoryName.endsWith("counters")) { - if (isHuman) - humanCounters = categoryValue; - else - computerCounters = categoryValue; - } - - else if (categoryName.endsWith("landsplayed")) { - if (isHuman) - humanLandsPlayed = Integer.parseInt(categoryValue); - else - computerLandsPlayed = Integer.parseInt(categoryValue); - } - - else if (categoryName.endsWith("landsplayedlastturn")) { - if (isHuman) - humanLandsPlayedLastTurn = Integer.parseInt(categoryValue); - else - computerLandsPlayedLastTurn = Integer.parseInt(categoryValue); - } - - else if (categoryName.endsWith("play") || categoryName.endsWith("battlefield")) { - if (isHuman) - humanCardTexts.put(ZoneType.Battlefield, categoryValue); - else - aiCardTexts.put(ZoneType.Battlefield, categoryValue); - } - - else if (categoryName.endsWith("hand")) { - if (isHuman) - humanCardTexts.put(ZoneType.Hand, categoryValue); - else - aiCardTexts.put(ZoneType.Hand, categoryValue); - } - - else if (categoryName.endsWith("graveyard")) { - if (isHuman) - humanCardTexts.put(ZoneType.Graveyard, categoryValue); - else - aiCardTexts.put(ZoneType.Graveyard, categoryValue); - } - - else if (categoryName.endsWith("library")) { - if (isHuman) - humanCardTexts.put(ZoneType.Library, categoryValue); - else - aiCardTexts.put(ZoneType.Library, categoryValue); - } - - else if (categoryName.endsWith("exile")) { - if (isHuman) - humanCardTexts.put(ZoneType.Exile, categoryValue); - else - aiCardTexts.put(ZoneType.Exile, categoryValue); - } - - else if (categoryName.endsWith("command")) { - if (isHuman) - humanCardTexts.put(ZoneType.Command, categoryValue); - else - aiCardTexts.put(ZoneType.Command, categoryValue); - } - - else if (categoryName.endsWith("sideboard")) { - if (isHuman) - humanCardTexts.put(ZoneType.Sideboard, categoryValue); - else - aiCardTexts.put(ZoneType.Sideboard, categoryValue); - } - - else if (categoryName.startsWith("ability")) { + } else if (categoryName.endsWith("life")) { + getPlayerState(categoryName).life = Integer.parseInt(categoryValue); + } else if (categoryName.endsWith("counters")) { + getPlayerState(categoryName).counters = categoryValue; + } else if (categoryName.endsWith("landsplayed")) { + getPlayerState(categoryName).landsPlayed = Integer.parseInt(categoryValue); + } else if (categoryName.endsWith("landsplayedlastturn")) { + getPlayerState(categoryName).landsPlayedLastTurn = Integer.parseInt(categoryValue); + } else if (categoryName.endsWith("play") || categoryName.endsWith("battlefield")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Battlefield, categoryValue); + } else if (categoryName.endsWith("hand")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Hand, categoryValue); + } else if (categoryName.endsWith("graveyard")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Graveyard, categoryValue); + } else if (categoryName.endsWith("library")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Library, categoryValue); + } else if (categoryName.endsWith("exile")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Exile, categoryValue); + } else if (categoryName.endsWith("command")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Command, categoryValue); + } else if (categoryName.endsWith("sideboard")) { + getPlayerState(categoryName).cardTexts.put(ZoneType.Sideboard, categoryValue); + } else if (categoryName.startsWith("ability")) { abilityString.put(categoryName.substring("ability".length()), categoryValue); - } - - else if (categoryName.endsWith("precast")) { - if (isHuman) - precastHuman = categoryValue; - else - precastAI = categoryValue; - } - - else if (categoryName.endsWith("putonstack")) { - if (isHuman) - putOnStackHuman = categoryValue; - else - putOnStackAI = categoryValue; - } - - else if (categoryName.endsWith("manapool")) { - if (isHuman) - humanManaPool = categoryValue; - else - computerManaPool = categoryValue; - } - - else if (categoryName.endsWith("persistentmana")) { - if (isHuman) - humanPersistentMana = categoryValue; - else - computerPersistentMana = categoryValue; - } - - else { - System.out.println("Unknown key: " + categoryName); + } else if (categoryName.endsWith("precast")) { + getPlayerState(categoryName).precast = categoryValue; + } else if (categoryName.endsWith("putonstack")) { + getPlayerState(categoryName).putOnStack = categoryValue; + } else if (categoryName.endsWith("manapool")) { + getPlayerState(categoryName).manaPool = categoryValue; + } else if (categoryName.endsWith("persistentmana")) { + getPlayerState(categoryName).persistentMana = categoryValue; + } else { + System.err.println("Unknown key: " + categoryName); } } public void applyToGame(final Game game) { - game.getAction().invoke(new Runnable() { - @Override - public void run() { - applyGameOnThread(game); - } - }); + game.getAction().invoke(() -> applyGameOnThread(game)); } protected void applyGameOnThread(final Game game) { - final Player human = game.getPlayers().get(0); - final Player ai = game.getPlayers().get(1); + if (game.getPlayers().size() != playerStates.size()) { + throw new RuntimeException("Non-matching number of players, (" + + game.getPlayers().size() + " vs. " + playerStates.size() + ")"); + } idToCard.clear(); cardToAttachId.clear(); @@ -647,32 +568,21 @@ public abstract class GameState { cardToScript.clear(); cardAttackMap.clear(); - Player newPlayerTurn = tChangePlayer.equalsIgnoreCase("human") ? human : tChangePlayer.equalsIgnoreCase("ai") ? ai : null; + int playerTurn = playerStates.indexOf(getPlayerState(tChangePlayer)); + Player newPlayerTurn = game.getPlayers().get(playerTurn); PhaseType newPhase = tChangePhase.equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tChangePhase); PhaseType advPhase = tAdvancePhase.equalsIgnoreCase("none") ? null : PhaseType.smartValueOf(tAdvancePhase); // Set stack to resolving so things won't trigger/effects be checked right away game.getStack().setResolving(true); - updateManaPool(human, humanManaPool, true, false); - updateManaPool(ai, computerManaPool, true, false); - updateManaPool(human, humanPersistentMana, false, true); - updateManaPool(ai, computerPersistentMana, false, true); - - if (!humanCounters.isEmpty()) { - applyCountersToGameEntity(human, humanCounters); - } - if (!computerCounters.isEmpty()) { - applyCountersToGameEntity(ai, computerCounters); - } - game.getPhaseHandler().devModeSet(newPhase, newPlayerTurn, turn); game.getTriggerHandler().setSuppressAllTriggers(true); - setupPlayerState(humanLife, humanCardTexts, human, humanLandsPlayed, humanLandsPlayedLastTurn); - setupPlayerState(computerLife, aiCardTexts, ai, computerLandsPlayed, computerLandsPlayedLastTurn); - + for (int i = 0; i < playerStates.size(); i++) { + setupPlayerState(game.getPlayers().get(i), playerStates.get(i)); + } handleCardAttachments(); handleChosenEntities(); handleRememberedEntities(); @@ -712,10 +622,11 @@ public abstract class GameState { // Set negative or zero life after state effects if need be, important for some puzzles that rely on // pre-setting negative life (e.g. PS_NEO4). - if (humanLife <= 0) { - human.setLife(humanLife, null); - } else if (computerLife <= 0) { - ai.setLife(computerLife, null); + for (int i = 0; i < playerStates.size(); i++) { + int life = playerStates.get(i).life; + if (life <= 0) { + game.getPlayers().get(i).setLife(life, null); + } } } @@ -746,12 +657,7 @@ public abstract class GameState { produced.put("PersistentMana", "True"); } final AbilityManaPart abMana = new AbilityManaPart(dummy, produced); - game.getAction().invoke(new Runnable() { - @Override - public void run() { - abMana.produceMana(null); - } - }); + game.getAction().invoke(() -> abMana.produceMana(null)); } } @@ -832,8 +738,7 @@ public abstract class GameState { } private int parseTargetInScript(final String tgtDef) { - int tgtID = TARGET_NONE; - + int tgtID; if (tgtDef.equalsIgnoreCase("human")) { tgtID = TARGET_HUMAN; } else if (tgtDef.equalsIgnoreCase("ai")) { @@ -972,37 +877,23 @@ public abstract class GameState { } private void handlePrecastSpells(final Game game) { - Player human = game.getPlayers().get(0); - Player ai = game.getPlayers().get(1); - - if (precastHuman != null) { - String[] spellList = TextUtil.split(precastHuman, ';'); - for (String spell : spellList) { - precastSpellFromCard(spell, human, game); - } - } - if (precastAI != null) { - String[] spellList = TextUtil.split(precastAI, ';'); - for (String spell : spellList) { - precastSpellFromCard(spell, ai, game); + for (int i = 0; i < playerStates.size(); i++) { + if (playerStates.get(i).precast != null) { + String[] spellList = TextUtil.split(playerStates.get(i).precast, ';'); + for (String spell : spellList) { + precastSpellFromCard(spell, game.getPlayers().get(i), game); + } } } } private void handleAddSAsToStack(final Game game) { - Player human = game.getPlayers().get(0); - Player ai = game.getPlayers().get(1); - - if (putOnStackHuman != null) { - String[] spellList = TextUtil.split(putOnStackHuman, ';'); - for (String spell : spellList) { - precastSpellFromCard(spell, human, game, true); - } - } - if (putOnStackAI != null) { - String[] spellList = TextUtil.split(putOnStackAI, ';'); - for (String spell : spellList) { - precastSpellFromCard(spell, ai, game, true); + for (int i = 0; i < playerStates.size(); i++) { + if (playerStates.get(i).putOnStack != null) { + String[] spellList = TextUtil.split(playerStates.get(i).putOnStack, ';'); + for (String spell : spellList) { + precastSpellFromCard(spell, game.getPlayers().get(i), game, true); + } } } } @@ -1138,14 +1029,9 @@ public abstract class GameState { } } - // Enchant players by ID - for (Entry entry : cardToEnchantPlayerId.entrySet()) { - // TODO: improve this for game states with more than two players - Card attacher = entry.getKey(); - Game game = attacher.getGame(); - Player attachedTo = entry.getValue() == TARGET_AI ? game.getPlayers().get(1) : game.getPlayers().get(0); - - attacher.attachToEntity(attachedTo, null); + // Enchant players + for (Entry entry : cardToEnchantPlayerId.entrySet()) { + entry.getKey().attachToEntity(entry.getValue(), null); } } @@ -1184,7 +1070,7 @@ public abstract class GameState { top.removeCloneState(top.getMutatedTimestamp()); } - final Long ts = game.getNextTimestamp(); + final long ts = game.getNextTimestamp(); top.setMutatedTimestamp(ts); if (top.getCurrentStateName() != CardStateName.FaceDown) { final CardCloneStates mutatedStates = CardFactory.getMutatedCloneStates(top, null/*FIXME*/); @@ -1207,7 +1093,7 @@ public abstract class GameState { } } - private void setupPlayerState(int life, Map cardTexts, final Player p, final int landsPlayed, final int landsPlayedLastTurn) { + private void setupPlayerState(final Player p, final PlayerState state) { // Lock check static as we setup player state // Clear all zones first, this ensures that any lingering cards and effects (e.g. in command zone) get cleared up @@ -1219,14 +1105,14 @@ public abstract class GameState { p.setCommanders(Lists.newArrayList()); Map playerCards = new EnumMap<>(ZoneType.class); - for (Entry kv : cardTexts.entrySet()) { + for (Entry kv : state.cardTexts.entrySet()) { String value = kv.getValue(); playerCards.put(kv.getKey(), processCardsForZone(value.isEmpty() ? new String[0] : value.split(";"), p)); } - if (life >= 0) p.setLife(life, null); - p.setLandsPlayedThisTurn(landsPlayed); - p.setLandsPlayedLastTurn(landsPlayedLastTurn); + if (state.life >= 0) p.setLife(state.life, null); + p.setLandsPlayedThisTurn(state.landsPlayed); + p.setLandsPlayedLastTurn(state.landsPlayedLastTurn); p.clearPaidForSA(); @@ -1275,6 +1161,13 @@ public abstract class GameState { for (Card cmd : p.getCommanders()) { p.getZone(ZoneType.Command).add(Player.createCommanderEffect(p.getGame(), cmd)); } + + updateManaPool(p, state.manaPool, true, false); + updateManaPool(p, state.persistentMana, false, true); + + if (!state.counters.isEmpty()) { + applyCountersToGameEntity(p, state.counters); + } } /** @@ -1330,9 +1223,7 @@ public abstract class GameState { c.setMonstrous(true); } else if (info.startsWith("PhasedOut")) { String tgt = info.substring(info.indexOf(':') + 1); - Player human = player.getGame().getPlayers().get(0); - Player ai = player.getGame().getPlayers().get(1); - c.setPhasedOut(tgt.equalsIgnoreCase("AI") ? ai : human); + c.setPhasedOut(parsePlayerString(player.getGame(), tgt)); } else if (info.startsWith("Counters:")) { applyCountersToGameEntity(c, info.substring(info.indexOf(':') + 1)); } else if (info.startsWith("SummonSick")) { @@ -1377,16 +1268,12 @@ public abstract class GameState { int id = Integer.parseInt(info.substring(info.indexOf(':') + 1)); cardToAttachId.put(c, id); } else if (info.startsWith("EnchantingPlayer:")) { - // TODO: improve this for game states with more than two players String tgt = info.substring(info.indexOf(':') + 1); - cardToEnchantPlayerId.put(c, tgt.equalsIgnoreCase("AI") ? TARGET_AI : TARGET_HUMAN); + cardToEnchantPlayerId.put(c, parsePlayerString(player.getGame(), tgt)); } else if (info.startsWith("Owner:")) { - // TODO: improve this for game states with more than two players - Player human = player.getGame().getPlayers().get(0); - Player ai = player.getGame().getPlayers().get(1); String owner = info.substring(info.indexOf(':') + 1); Player controller = c.getController(); - c.setOwner(owner.equalsIgnoreCase("AI") ? ai : human); + c.setOwner(parsePlayerString(player.getGame(), owner)); c.setController(controller, c.getGame().getNextTimestamp()); } else if (info.startsWith("Ability:")) { String abString = info.substring(info.indexOf(':') + 1).toLowerCase(); From 64a980b1564b5c4cb487e20a1897a1cf7ab71a0c Mon Sep 17 00:00:00 2001 From: Paco Date: Wed, 28 Dec 2022 20:59:53 +0000 Subject: [PATCH 39/76] Fix loading bundle string --- forge-core/src/main/java/forge/util/Localizer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-core/src/main/java/forge/util/Localizer.java b/forge-core/src/main/java/forge/util/Localizer.java index 173e936ce49..5f244afab4c 100644 --- a/forge-core/src/main/java/forge/util/Localizer.java +++ b/forge-core/src/main/java/forge/util/Localizer.java @@ -159,7 +159,7 @@ public class Localizer { e.printStackTrace(); } - System.out.println("Language '" + resourceBundle.toString() + "' loaded successfully."); + System.out.println("Language '" + resourceBundle.getBaseBundleName() + "' loaded successfully."); notifyObservers(); From d6af6aa68f479280a9c7c99a5ec01ea17c8b4384 Mon Sep 17 00:00:00 2001 From: asvitkine Date: Wed, 28 Dec 2022 14:04:15 -0700 Subject: [PATCH 40/76] Fix deck re-randomization on new game. --- forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java index 5c53a6a858a..ec39ffbcacb 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java @@ -312,6 +312,7 @@ public class VLobby implements ILobbyView { } if (fullUpdate && (type == LobbySlotType.LOCAL || isSlotAI)) { // Deck section selection + panel.getDeckChooser().getLstDecks().getSelectCommand().run(); selectSchemeDeck(i); selectPlanarDeck(i); selectVanguardAvatar(i); From 24e32e7af6dc91a6c3a2800479a36d732a62d7cc Mon Sep 17 00:00:00 2001 From: tool4ever Date: Wed, 28 Dec 2022 22:45:53 +0100 Subject: [PATCH 41/76] Clean up (#2185) --- .../game/staticability/StaticAbilityPanharmonicon.java | 4 ++-- forge-gui/res/cardsfolder/a/acidic_sliver.txt | 2 +- forge-gui/res/cardsfolder/a/anavolver.txt | 2 +- forge-gui/res/cardsfolder/a/animal_boneyard.txt | 2 +- forge-gui/res/cardsfolder/a/arcane_teachings.txt | 2 +- forge-gui/res/cardsfolder/b/barbed_field.txt | 2 +- forge-gui/res/cardsfolder/b/basal_sliver.txt | 2 +- forge-gui/res/cardsfolder/c/cephalid_shrine.txt | 8 +++----- forge-gui/res/cardsfolder/e/emberwilde_captain.txt | 8 +++----- forge-gui/res/cardsfolder/e/eye_of_singularity.txt | 10 ++++------ forge-gui/res/cardsfolder/i/illuna_apex_of_wishes.txt | 6 ++---- forge-gui/res/cardsfolder/m/mentor_of_the_meek.txt | 2 +- forge-gui/res/cardsfolder/n/natures_will.txt | 8 +++----- forge-gui/res/cardsfolder/p/personal_incarnation.txt | 6 ++---- .../s/sasaya_orochi_ascendant_sasayas_essence.txt | 6 ++---- forge-gui/res/cardsfolder/s/sigil_of_valor.txt | 6 ++---- 16 files changed, 30 insertions(+), 46 deletions(-) diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java index e71134638b5..00ee0ccc414 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityPanharmonicon.java @@ -100,8 +100,8 @@ public class StaticAbilityPanharmonicon { } } else if (trigMode.equals(TriggerType.ChangesZoneAll)) { // Check if the cards have a trigger at all - final String origin = stAb.getParamOrDefault("Origin", null); - final String destination = stAb.getParamOrDefault("Destination", null); + final String origin = stAb.getParam("Origin"); + final String destination = stAb.getParam("Destination"); final CardZoneTable table = (CardZoneTable) runParams.get(AbilityKey.Cards); if (table.filterCards(origin == null ? null : ImmutableList.of(ZoneType.smartValueOf(origin)), ZoneType.smartValueOf(destination), stAb.getParam("ValidCause"), card, stAb).isEmpty()) { diff --git a/forge-gui/res/cardsfolder/a/acidic_sliver.txt b/forge-gui/res/cardsfolder/a/acidic_sliver.txt index c6f3144c5da..ee78d610d50 100644 --- a/forge-gui/res/cardsfolder/a/acidic_sliver.txt +++ b/forge-gui/res/cardsfolder/a/acidic_sliver.txt @@ -3,6 +3,6 @@ ManaCost:B R Types:Creature Sliver PT:2/2 S:Mode$ Continuous | Affected$ Sliver | AddAbility$ Damage | Description$ All Slivers have "{2}, Sacrifice this permanent: This permanent deals 2 damage to any target." -SVar:Damage:AB$DealDamage | Cost$ 2 Sac<1/CARDNAME> | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 2 | SpellDescription$ CARDNAME deals 2 damage to any target. +SVar:Damage:AB$ DealDamage | Cost$ 2 Sac<1/CARDNAME> | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 2 | SpellDescription$ CARDNAME deals 2 damage to any target. SVar:BuffedBy:Sliver Oracle:All Slivers have "{2}, Sacrifice this permanent: This permanent deals 2 damage to any target." diff --git a/forge-gui/res/cardsfolder/a/anavolver.txt b/forge-gui/res/cardsfolder/a/anavolver.txt index c049d5713e1..d67913f4d16 100644 --- a/forge-gui/res/cardsfolder/a/anavolver.txt +++ b/forge-gui/res/cardsfolder/a/anavolver.txt @@ -9,7 +9,7 @@ SVar:VolverStrength:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | Counter SVar:VolverLaunch:DB$ Animate | Defined$ Self | Keywords$ Flying | Duration$ Permanent SVar:VolverPumped:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 | ETB$ True | SubAbility$ VolverResilience | SpellDescription$ If CARDNAME was kicked with its {B} kicker, it enters the battlefield with a +1/+1 counter on it and with "Pay 3 life: Regenerate CARDNAME." SVar:VolverResilience:DB$ Animate | Defined$ Self | Abilities$ ABRegen | Duration$ Permanent -SVar:ABRegen:AB$Regenerate | Cost$ PayLife<3> | SpellDescription$ Regenerate CARDNAME. +SVar:ABRegen:AB$ Regenerate | Cost$ PayLife<3> | SpellDescription$ Regenerate CARDNAME. AI:RemoveDeck:Random DeckNeeds:Color$Blue|Black DeckHas:Ability$Counters diff --git a/forge-gui/res/cardsfolder/a/animal_boneyard.txt b/forge-gui/res/cardsfolder/a/animal_boneyard.txt index d979cc8c48f..f4181417a46 100644 --- a/forge-gui/res/cardsfolder/a/animal_boneyard.txt +++ b/forge-gui/res/cardsfolder/a/animal_boneyard.txt @@ -4,7 +4,7 @@ Types:Enchantment Aura K:Enchant land A:SP$ Attach | Cost$ 2 W | ValidTgts$ Land | AILogic$ Pump S:Mode$ Continuous | Affected$ Land.AttachedBy | AddAbility$ GainLife | AddSVar$ AnimalBoneyardX | Description$ Enchanted land has "{T}, Sacrifice a creature: You gain life equal to the sacrificed creature's toughness." -SVar:GainLife:AB$GainLife | Cost$ T Sac<1/Creature> | LifeAmount$ AnimalBoneyardX | SpellDescription$ You gain life equal to the sacrificed creature's toughness. +SVar:GainLife:AB$ GainLife | Cost$ T Sac<1/Creature> | LifeAmount$ AnimalBoneyardX | SpellDescription$ You gain life equal to the sacrificed creature's toughness. SVar:AnimalBoneyardX:Sacrificed$CardToughness AI:RemoveDeck:All SVar:NonStackingAttachEffect:True diff --git a/forge-gui/res/cardsfolder/a/arcane_teachings.txt b/forge-gui/res/cardsfolder/a/arcane_teachings.txt index b214189fad8..54385c23da4 100644 --- a/forge-gui/res/cardsfolder/a/arcane_teachings.txt +++ b/forge-gui/res/cardsfolder/a/arcane_teachings.txt @@ -4,5 +4,5 @@ Types:Enchantment Aura K:Enchant creature A:SP$ Attach | Cost$ 2 R | ValidTgts$ Creature | AILogic$ Pump S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | AddAbility$ Damage | Description$ Enchanted creature gets +2/+2 and has "{T}: This creature deals 1 damage to any target." -SVar:Damage:AB$DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target. +SVar:Damage:AB$ DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target. Oracle:Enchant creature (Target a creature as you cast this. This card enters the battlefield attached to that creature.)\nEnchanted creature gets +2/+2 and has "{T}: This creature deals 1 damage to any target." diff --git a/forge-gui/res/cardsfolder/b/barbed_field.txt b/forge-gui/res/cardsfolder/b/barbed_field.txt index 0efbeeee681..e57ce0fbd47 100644 --- a/forge-gui/res/cardsfolder/b/barbed_field.txt +++ b/forge-gui/res/cardsfolder/b/barbed_field.txt @@ -4,6 +4,6 @@ Types:Enchantment Aura K:Enchant land A:SP$ Attach | Cost$ 2 R R | ValidTgts$ Land | AILogic$ Pump S:Mode$ Continuous | Affected$ Land.EnchantedBy | AddAbility$ Damage | Description$ Enchanted land has "{T}: This land deals 1 damage to any target." -SVar:Damage:AB$DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target. +SVar:Damage:AB$ DealDamage | Cost$ T | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to any target. SVar:NonStackingAttachEffect:True Oracle:Enchant land\nEnchanted land has "{T}: This land deals 1 damage to any target." diff --git a/forge-gui/res/cardsfolder/b/basal_sliver.txt b/forge-gui/res/cardsfolder/b/basal_sliver.txt index 5fe4e91b7df..c971ebafc71 100644 --- a/forge-gui/res/cardsfolder/b/basal_sliver.txt +++ b/forge-gui/res/cardsfolder/b/basal_sliver.txt @@ -3,6 +3,6 @@ ManaCost:2 B Types:Creature Sliver PT:2/2 S:Mode$ Continuous | Affected$ Sliver | AddAbility$ Mana | Description$ All Slivers have "Sacrifice this permanent: Add {B}{B}." -SVar:Mana:AB$Mana | Cost$ Sac<1/CARDNAME> | Produced$ B | Amount$ 2 | SpellDescription$ Add {B}{B}. +SVar:Mana:AB$ Mana | Cost$ Sac<1/CARDNAME> | Produced$ B | Amount$ 2 | SpellDescription$ Add {B}{B}. AI:RemoveDeck:All Oracle:All Slivers have "Sacrifice this permanent: Add {B}{B}." diff --git a/forge-gui/res/cardsfolder/c/cephalid_shrine.txt b/forge-gui/res/cardsfolder/c/cephalid_shrine.txt index 3037a0bf0bb..a77fc4f2c45 100644 --- a/forge-gui/res/cardsfolder/c/cephalid_shrine.txt +++ b/forge-gui/res/cardsfolder/c/cephalid_shrine.txt @@ -1,10 +1,8 @@ Name:Cephalid Shrine ManaCost:1 U U Types:Enchantment -T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigCounterRem | TriggerDescription$ Whenever a player casts a spell, counter that spell unless that player pays {X}, where X is the number of cards in all graveyards with the same name as the spell. -SVar:TrigCounterRem:DB$ Pump | RememberObjects$ TriggeredCard | SubAbility$ DBCounter -SVar:DBCounter:DB$ Counter | Defined$ TriggeredSpellAbility | UnlessCost$ X | UnlessPayer$ TriggeredActivator | SubAbility$ DBCleanup -SVar:X:Count$ValidGraveyard Card.sharesNameWith Remembered -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +T:Mode$ SpellCast | ValidCard$ Card | ValidActivatingPlayer$ Player | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ Whenever a player casts a spell, counter that spell unless that player pays {X}, where X is the number of cards in all graveyards with the same name as the spell. +SVar:TrigCounter:DB$ Counter | Defined$ TriggeredSpellAbility | UnlessCost$ X | UnlessPayer$ TriggeredActivator +SVar:X:Count$ValidGraveyard Card.sharesNameWith TriggeredCard AI:RemoveDeck:Random Oracle:Whenever a player casts a spell, counter that spell unless that player pays {X}, where X is the number of cards in all graveyards with the same name as the spell. diff --git a/forge-gui/res/cardsfolder/e/emberwilde_captain.txt b/forge-gui/res/cardsfolder/e/emberwilde_captain.txt index 5ad9b59621d..4da6db5ac1e 100644 --- a/forge-gui/res/cardsfolder/e/emberwilde_captain.txt +++ b/forge-gui/res/cardsfolder/e/emberwilde_captain.txt @@ -4,9 +4,7 @@ Types:Creature Djinn Pirate PT:4/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigMonarch | TriggerDescription$ When CARDNAME enters the battlefield, you become the monarch. SVar:TrigMonarch:DB$ BecomeMonarch | Defined$ You -T:Mode$ AttackersDeclared | AttackingPlayer$ Player.Opponent | AttackedTarget$ You | NoResolvingCheck$ True | CheckDefinedPlayer$ You.isMonarch | TriggerZones$ Battlefield | Execute$ TrigPump | TriggerDescription$ Whenever an opponent attacks you while you're the monarch, CARDNAME deals damage to that player equal to the number of cards in their hand. -SVar:TrigPump:DB$ Pump | RememberObjects$ TriggeredAttackingPlayer | SubAbility$ DBDmg -SVar:DBDmg:DB$ DealDamage | Defined$ Remembered | NumDmg$ X | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$ValidHand Card.RememberedPlayerCtrl +T:Mode$ AttackersDeclared | AttackingPlayer$ Player.Opponent | AttackedTarget$ You | NoResolvingCheck$ True | CheckDefinedPlayer$ You.isMonarch | TriggerZones$ Battlefield | Execute$ TrigDmg | TriggerDescription$ Whenever an opponent attacks you while you're the monarch, CARDNAME deals damage to that player equal to the number of cards in their hand. +SVar:TrigDmg:DB$ DealDamage | Defined$ TriggeredAttackingPlayer | NumDmg$ X +SVar:X:Count$ValidHand Card.OwnedBy TriggeredAttackingPlayer Oracle:When Emberwilde Captain enters the battlefield, you become the monarch.\nWhenever an opponent attacks you while you're the monarch, Emberwilde Captain deals damage to that player equal to the number of cards in their hand. diff --git a/forge-gui/res/cardsfolder/e/eye_of_singularity.txt b/forge-gui/res/cardsfolder/e/eye_of_singularity.txt index 59c2fb204f6..f261f334185 100644 --- a/forge-gui/res/cardsfolder/e/eye_of_singularity.txt +++ b/forge-gui/res/cardsfolder/e/eye_of_singularity.txt @@ -2,11 +2,9 @@ Name:Eye of Singularity ManaCost:3 W Types:World Enchantment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigRepeat | TriggerDescription$ When CARDNAME enters the battlefield, destroy each permanent with the same name as another permanent, except for basic lands. They can't be regenerated. -SVar:TrigRepeat:DB$ RepeatEach | RepeatCards$ Permanent.nonBasic | RepeatSubAbility$ DBDestroy | UseImprinted$ True | SubAbility$ DBCleanup -SVar:DBDestroy:DB$ Destroy | Defined$ Valid Permanent.sharesNameWith Imprinted+IsNotImprinted | NoRegen$ True -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Permanent.nonBasic | TriggerZones$ Battlefield | Execute$ TrigDestroyRem | TriggerDescription$ Whenever a permanent other than a basic land enters the battlefield, destroy all other permanents with that name. They can't be regenerated. -SVar:TrigDestroyRem:DB$ Pump | RememberObjects$ TriggeredCard | SubAbility$ DBDestroyAll -SVar:DBDestroyAll:DB$ DestroyAll | ValidCards$ Permanent.IsNotRemembered+sharesNameWith Remembered | NoRegen$ True | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:TrigRepeat:DB$ RepeatEach | RepeatCards$ Permanent.nonBasic | RepeatSubAbility$ DBDestroy +SVar:DBDestroy:DB$ Destroy | Defined$ Valid Permanent.sharesNameWith Remembered+IsNotRemembered | NoRegen$ True +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Permanent.nonBasic | TriggerZones$ Battlefield | Execute$ TrigDestroy | TriggerDescription$ Whenever a permanent other than a basic land enters the battlefield, destroy all other permanents with that name. They can't be regenerated. +SVar:TrigDestroy:DB$ DestroyAll | ValidCards$ Permanent.NotTriggeredCard+sharesNameWith TriggeredCard | NoRegen$ True AI:RemoveDeck:All Oracle:When Eye of Singularity enters the battlefield, destroy each permanent with the same name as another permanent, except for basic lands. They can't be regenerated.\nWhenever a permanent other than a basic land enters the battlefield, destroy all other permanents with that name. They can't be regenerated. diff --git a/forge-gui/res/cardsfolder/i/illuna_apex_of_wishes.txt b/forge-gui/res/cardsfolder/i/illuna_apex_of_wishes.txt index 8006aae5385..c51ca78b14d 100644 --- a/forge-gui/res/cardsfolder/i/illuna_apex_of_wishes.txt +++ b/forge-gui/res/cardsfolder/i/illuna_apex_of_wishes.txt @@ -6,9 +6,7 @@ K:Mutate:3 RG U U K:Flying K:Trample T:Mode$ Mutates | ValidCard$ Card.Self | Execute$ TrigDigUntil | TriggerDescription$ Whenever this creature mutates, exile cards from the top of your library until you exile a nonland permanent card. Put that card onto the battlefield or into your hand. -SVar:TrigDigUntil:DB$ DigUntil | Valid$ Permanent.nonLand | ValidDescription$ nonland permanent | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | SubAbility$ DBChoose -SVar:DBChoose:DB$ GenericChoice | Choices$ Battlefield,Hand | Defined$ You -SVar:Battlefield:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | SubAbility$ DBCleanup | SpellDescription$ Put the nonland permanent onto the battlefield -SVar:Hand:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Hand | SubAbility$ DBCleanup | SpellDescription$ Put the nonland permanent into your hand +SVar:TrigDigUntil:DB$ DigUntil | Valid$ Permanent.nonLand | ValidDescription$ nonland permanent | FoundDestination$ Exile | RevealedDestination$ Exile | RememberFound$ True | SubAbility$ DBChange +SVar:DBChange:DB$ ChangeZone | Defined$ Remembered | Origin$ Exile | Destination$ Battlefield | DestinationAlternative$ Hand | AlternativeDestinationMessage$ Put that card onto the battlefield instead of putting it into your hand? | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Mutate {3}{R/G}{U}{U} (If you cast this spell for its mutate cost, put it over or under target non-Human creature you own. They mutate into the creature on top plus all abilities from under it.)\nFlying, trample\nWhenever this creature mutates, exile cards from the top of your library until you exile a nonland permanent card. Put that card onto the battlefield or into your hand. diff --git a/forge-gui/res/cardsfolder/m/mentor_of_the_meek.txt b/forge-gui/res/cardsfolder/m/mentor_of_the_meek.txt index f6bbf8e3f60..18f36c1f600 100644 --- a/forge-gui/res/cardsfolder/m/mentor_of_the_meek.txt +++ b/forge-gui/res/cardsfolder/m/mentor_of_the_meek.txt @@ -3,6 +3,6 @@ ManaCost:2 W Types:Creature Human Soldier PT:2/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Creature.powerLE2+YouCtrl+Other | TriggerZones$ Battlefield | Execute$ TrigDraw | Optional$ True | TriggerDescription$ Whenever another creature with power 2 or less enters the battlefield under your control, you may pay {1}. If you do, draw a card. -SVar:TrigDraw:AB$Draw | Cost$ 1 | NumCards$ 1 | SpellDescription$ Draw a card. +SVar:TrigDraw:AB$ Draw | Cost$ 1 | NumCards$ 1 | SpellDescription$ Draw a card. SVar:PlayMain1:TRUE Oracle:Whenever another creature with power 2 or less enters the battlefield under your control, you may pay {1}. If you do, draw a card. diff --git a/forge-gui/res/cardsfolder/n/natures_will.txt b/forge-gui/res/cardsfolder/n/natures_will.txt index f3988cc3a83..ea16cf4cace 100644 --- a/forge-gui/res/cardsfolder/n/natures_will.txt +++ b/forge-gui/res/cardsfolder/n/natures_will.txt @@ -1,10 +1,8 @@ Name:Nature's Will ManaCost:2 G G Types:Enchantment -T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Creature.YouCtrl | TriggerZones$ Battlefield | ValidTarget$ Player | Execute$ TrigRememberTarget | TriggerDescription$ Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control. -SVar:TrigRememberTarget:DB$ Pump | RememberObjects$ TriggeredTarget | SubAbility$ DBTapAll -SVar:DBTapAll:DB$ TapAll | ValidCards$ Land.RememberedPlayerCtrl | SubAbility$ DBUntapAll -SVar:DBUntapAll:DB$ UntapAll | ValidCards$ Land.YouCtrl | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +T:Mode$ DamageDoneOnce | CombatDamage$ True | ValidSource$ Creature.YouCtrl | TriggerZones$ Battlefield | ValidTarget$ Player | Execute$ DBTapAll | TriggerDescription$ Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control. +SVar:DBTapAll:DB$ TapAll | ValidCards$ Land.ControlledBy TriggeredTarget | SubAbility$ DBUntapAll +SVar:DBUntapAll:DB$ UntapAll | ValidCards$ Land.YouCtrl SVar:PlayMain1:TRUE Oracle:Whenever one or more creatures you control deal combat damage to a player, tap all lands that player controls and untap all lands you control. diff --git a/forge-gui/res/cardsfolder/p/personal_incarnation.txt b/forge-gui/res/cardsfolder/p/personal_incarnation.txt index a5609389dd0..401055f6c66 100644 --- a/forge-gui/res/cardsfolder/p/personal_incarnation.txt +++ b/forge-gui/res/cardsfolder/p/personal_incarnation.txt @@ -6,9 +6,7 @@ A:AB$ Effect | Cost$ 0 | Activator$ Player.Owner | Name$ Personal Incarnation Re SVar:RedirectDamage:Event$ DamageDone | ValidTarget$ Creature.EffectSource | ReplaceWith$ RedirectDmg | DamageTarget$ You | Description$ The next 1 damage that would be dealt to EFFECTSOURCE this turn is dealt to its owner instead. Only EFFECTSOURCE's owner may activate this ability. SVar:RedirectDmg:DB$ ReplaceSplitDamage | DamageTarget$ You T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigLoseLife | TriggerDescription$ When CARDNAME dies, its owner loses half their life, rounded up. -SVar:TrigLoseLife:DB$ Pump | RememberObjects$ TriggeredCardOwner | SubAbility$ DBLoseLife -SVar:DBLoseLife:DB$ LoseLife | Defined$ Remembered | LifeAmount$ HavocX | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:HavocX:PlayerCountRemembered$LifeTotal/HalfUp +SVar:TrigLoseLife:DB$ LoseLife | Defined$ TriggeredCardOwner | LifeAmount$ HavocX +SVar:HavocX:PlayerCountDefinedTriggeredCardOwner$LifeTotal/HalfUp AI:RemoveDeck:All Oracle:{0}: The next 1 damage that would be dealt to Personal Incarnation this turn is dealt to its owner instead. Only Personal Incarnation's owner may activate this ability.\nWhen Personal Incarnation dies, its owner loses half their life, rounded up. diff --git a/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt b/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt index 88cf440bd17..fe1bafcc93f 100644 --- a/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt +++ b/forge-gui/res/cardsfolder/s/sasaya_orochi_ascendant_sasayas_essence.txt @@ -13,9 +13,7 @@ Name:Sasaya's Essence ManaCost:1 G G Colors:green Types:Legendary Enchantment -T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ TrigMana | Static$ True | TriggerDescription$ Whenever a land you control is tapped for mana, add an additional one mana of any type that land produced for each other land you control with the same name as it. -SVar:TrigMana:DB$ Pump | RememberObjects$ TriggeredCard | SubAbility$ DBRepeat -SVar:DBRepeat:DB$ RepeatEach | UseImprinted$ True | RepeatCards$ Land.YouCtrl+IsNotRemembered+sharesNameWith Remembered | RepeatSubAbility$ DBManaReflect | SubAbility$ DBCleanup +T:Mode$ TapsForMana | ValidCard$ Land.YouCtrl | TriggerZones$ Battlefield | Execute$ DBRepeat | Static$ True | TriggerDescription$ Whenever a land you control is tapped for mana, add an additional one mana of any type that land produced for each other land you control with the same name as it. +SVar:DBRepeat:DB$ RepeatEach | RepeatCards$ Land.YouCtrl+NotTriggeredCard+sharesNameWith TriggeredCard | RepeatSubAbility$ DBManaReflect SVar:DBManaReflect:DB$ ManaReflected | ColorOrType$ Type | ReflectProperty$ Produced | Defined$ You -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Whenever a land you control is tapped for mana, add an additional one mana of any type that land produced for each other land you control with the same name as it. diff --git a/forge-gui/res/cardsfolder/s/sigil_of_valor.txt b/forge-gui/res/cardsfolder/s/sigil_of_valor.txt index b13987ae686..de392054a90 100644 --- a/forge-gui/res/cardsfolder/s/sigil_of_valor.txt +++ b/forge-gui/res/cardsfolder/s/sigil_of_valor.txt @@ -3,8 +3,6 @@ ManaCost:2 Types:Artifact Equipment K:Equip:1 T:Mode$ Attacks | ValidCard$ Card.EquippedBy | Alone$ True | Execute$ TrigPump | TriggerDescription$ Whenever equipped creature attacks alone, it gets +1/+1 until end of turn for each other creature you control. -SVar:TrigPump:DB$ Pump | RememberObjects$ TriggeredAttacker | SubAbility$ TrigPump2 -SVar:TrigPump2:DB$ Pump | Defined$ Remembered | NumAtt$ X | NumDef$ X | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:Count$Valid Creature.YouCtrl+IsNotRemembered +SVar:TrigPump:DB$ Pump | Defined$ TriggeredAttackerLKICopy | NumAtt$ X | NumDef$ X +SVar:X:Count$Valid Creature.YouCtrl+NotTriggeredAttacker Oracle:Whenever equipped creature attacks alone, it gets +1/+1 until end of turn for each other creature you control.\nEquip {1} ({1}: Attach to target creature you control. Equip only as a sorcery.) From 21f92239818e7238547111bca1e990e0c628fe7c Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Thu, 29 Dec 2022 09:34:05 +0800 Subject: [PATCH 42/76] fix Adventure cards in exile lose "may be cast" when rewinding casting - closes #2176 --- .../java/forge/game/ability/SpellAbilityEffect.java | 9 +++++++++ .../forge/game/ability/effects/EffectEffect.java | 2 ++ .../main/java/forge/game/card/CardFactoryUtil.java | 2 +- .../src/main/java/forge/game/zone/PlayerZone.java | 12 +++++++++++- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index 6ec34b5119b..e978eef4454 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -400,6 +400,15 @@ public abstract class SpellAbilityEffect { addedTrigger.setIntrinsic(true); } + protected static void addExileOnCastTrigger(final Card card) { + String trig = "Mode$ SpellCast | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True"; + String effect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"; + final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true); + parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card)); + final Trigger addedTrigger = card.addTrigger(parsedTrigger); + addedTrigger.setIntrinsic(true); + } + protected static void addExileOnCounteredTrigger(final Card card) { String trig = "Mode$ Countered | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True"; String effect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"; diff --git a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java index 51da7b83dc2..444b16d26b9 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java @@ -227,6 +227,8 @@ public class EffectEffect extends SpellAbilityEffect { addForgetOnCastTrigger(eff); } else if (sa.hasParam("ExileOnMoved")) { addExileOnMovedTrigger(eff, sa.getParam("ExileOnMoved")); + } else if (sa.hasParam("ExileOnCast")) { + addExileOnCastTrigger(eff); } if (sa.hasParam("ForgetOnPhasedIn")) { addForgetOnPhasedInTrigger(eff); diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index 51917427525..8abadb26bfd 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3855,7 +3855,7 @@ public class CardFactoryUtil { SpellAbility saExile = AbilityFactory.getAbility(abExile, card); - String abEffect = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnMoved$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell"; + String abEffect = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnCast$ True | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell"; AbilitySub saEffect = (AbilitySub)AbilityFactory.getAbility(abEffect, card); StringBuilder sbPlay = new StringBuilder(); diff --git a/forge-game/src/main/java/forge/game/zone/PlayerZone.java b/forge-game/src/main/java/forge/game/zone/PlayerZone.java index ab52d23fee0..53770ab025d 100644 --- a/forge-game/src/main/java/forge/game/zone/PlayerZone.java +++ b/forge-game/src/main/java/forge/game/zone/PlayerZone.java @@ -20,6 +20,7 @@ package forge.game.zone; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; +import forge.card.CardStateName; import forge.game.card.Card; import forge.game.card.CardCollectionView; import forge.game.card.CardLists; @@ -63,7 +64,7 @@ public class PlayerZone extends Zone { boolean graveyardCastable = c.hasKeyword(Keyword.FLASHBACK) || c.hasKeyword(Keyword.RETRACE) || c.hasKeyword(Keyword.JUMP_START) || c.hasKeyword(Keyword.ESCAPE) || c.hasKeyword(Keyword.DISTURB); - boolean exileCastable = (c.isAdventureCard() || c.isForetold()) && c.isInZone(ZoneType.Exile); + boolean exileCastable = c.isForetold() || isOnAdventure(c); for (final SpellAbility sa : c.getSpellAbilities()) { final ZoneType restrictZone = sa.getRestrictions().getZone(); @@ -92,6 +93,15 @@ public class PlayerZone extends Zone { return false; } } + private boolean isOnAdventure(Card c) { + if (!c.isAdventureCard()) + return false; + if (c.getExiledWith() == null) + return false; + if (!CardStateName.Adventure.equals(c.getExiledWith().getCurrentStateName())) + return false; + return true; + } private final Player player; From 4f365848e1a0b65ef18f4f1838d6e65fc3361809 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Thu, 29 Dec 2022 10:11:48 +0800 Subject: [PATCH 43/76] update --- .../java/forge/game/ability/SpellAbilityEffect.java | 10 +++++++++- .../java/forge/game/ability/effects/EffectEffect.java | 2 +- .../src/main/java/forge/game/card/CardFactoryUtil.java | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index e978eef4454..a935835b7da 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -400,13 +400,21 @@ public abstract class SpellAbilityEffect { addedTrigger.setIntrinsic(true); } - protected static void addExileOnCastTrigger(final Card card) { + protected static void addExileOnCastOrMoveTrigger(final Card card, final String zone) { String trig = "Mode$ SpellCast | ValidCard$ Card.IsRemembered | TriggerZones$ Command | Static$ True"; String effect = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"; final Trigger parsedTrigger = TriggerHandler.parseTrigger(trig, card, true); parsedTrigger.setOverridingAbility(AbilityFactory.getAbility(effect, card)); final Trigger addedTrigger = card.addTrigger(parsedTrigger); addedTrigger.setIntrinsic(true); + //Any on Destination will cause the effect to remove itself when cancelling to play the card + String trig2 = "Mode$ ChangesZone | ValidCard$ Card.IsRemembered | Origin$ " + zone + " | Destination$ Hand,Library,Graveyard,Battlefield,Command,Sideboard | TriggerZones$ Command | Static$ True"; + String effect2 = "DB$ ChangeZone | Defined$ Self | Origin$ Command | Destination$ Exile"; + final Trigger parsedTrigger2 = TriggerHandler.parseTrigger(trig2, card, true); + parsedTrigger2.setOverridingAbility(AbilityFactory.getAbility(effect2, card)); + final Trigger addedTrigger2 = card.addTrigger(parsedTrigger2); + addedTrigger2.setIntrinsic(true); + } protected static void addExileOnCounteredTrigger(final Card card) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java index 444b16d26b9..90f473d08aa 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/EffectEffect.java @@ -228,7 +228,7 @@ public class EffectEffect extends SpellAbilityEffect { } else if (sa.hasParam("ExileOnMoved")) { addExileOnMovedTrigger(eff, sa.getParam("ExileOnMoved")); } else if (sa.hasParam("ExileOnCast")) { - addExileOnCastTrigger(eff); + addExileOnCastOrMoveTrigger(eff, sa.getParam("ExileOnCast")); } if (sa.hasParam("ForgetOnPhasedIn")) { addForgetOnPhasedInTrigger(eff); 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 8abadb26bfd..ba826116d40 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -3855,7 +3855,7 @@ public class CardFactoryUtil { SpellAbility saExile = AbilityFactory.getAbility(abExile, card); - String abEffect = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnCast$ True | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell"; + String abEffect = "DB$ Effect | RememberObjects$ Self | StaticAbilities$ Play | ExileOnCast$ Exile | Duration$ Permanent | ConditionDefined$ Self | ConditionPresent$ Card.nonCopiedSpell"; AbilitySub saEffect = (AbilitySub)AbilityFactory.getAbility(abEffect, card); StringBuilder sbPlay = new StringBuilder(); From 6bc950c508ee69fea6227d0500272b0d368e0bf2 Mon Sep 17 00:00:00 2001 From: JohnWilliams77 <103562494+JohnWilliams77@users.noreply.github.com> Date: Fri, 30 Dec 2022 09:15:46 +0000 Subject: [PATCH 44/76] Update Secret Lair Drop Series.txt --- forge-gui/res/editions/Secret Lair Drop Series.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 6db321c22da..2b1b110be82 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -592,6 +592,7 @@ ScryfallCode=SLD 618 R Diffusion Sliver @Trevor Claxton 619 R Galerider Sliver @James Zapata 620 R Mesmeric Sliver @Michael Bruinsma +623 R Scuttling Sliver @Mike Bierek 624 R Shadow Sliver @Warren Mahy 626 R Synapse Sliver @Thomas M. Baxa 627 R Telekinetic Sliver @Randy Elliott @@ -618,6 +619,7 @@ ScryfallCode=SLD 653 R Muscle Sliver @Richard Kane Ferguson 654 R Predatory Sliver @Mathias Kollros 655 R Quick Sliver @John Avon +656 R Root Sliver @Matt Thompson 657 R Tempered Sliver @Mitchell Malloy 659 R Virulent Sliver @Franz Vohwinkel 660 R Cloudshredder Sliver @Filip Burburan From c3dcd364582acb4deb74710ef747636e3d0a3480 Mon Sep 17 00:00:00 2001 From: JohnWilliams77 <103562494+JohnWilliams77@users.noreply.github.com> Date: Fri, 30 Dec 2022 12:44:01 +0000 Subject: [PATCH 45/76] Update Secret Lair Drop Series.txt --- forge-gui/res/editions/Secret Lair Drop Series.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index 2b1b110be82..e9987660339 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -585,6 +585,7 @@ ScryfallCode=SLD 608 R Darksteel Citadel @DXTR 609 R Hawkins National Laboratory @Ravenna Tran 610 R Bonescythe Sliver @Trevor Claxton +611 R Constricting Sliver @Karl Kopinski 612 R Essence Sliver @Glen Angus 613 R Pulmonic Sliver @Jeff Easley 615 R Sidewinder Sliver @Ron Spencer From a2d93861cfb05ec35ac4f8f7feb99195899279b3 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Fri, 30 Dec 2022 18:47:56 +0100 Subject: [PATCH 46/76] Script fixes (#2189) * Update captain_rex_nebula.txt * Update pirated_copy.txt * Update face_of_divinity.txt * Fix Primal Amulet transforming if counter gets replaced --- .../res/cardsfolder/c/captain_rex_nebula.txt | 2 +- .../res/cardsfolder/c/chromium_the_mutable.txt | 5 ++++- forge-gui/res/cardsfolder/c/creeping_tar_pit.txt | 5 ++++- forge-gui/res/cardsfolder/d/dimir_keyrune.txt | 5 ++++- .../res/cardsfolder/e/eye_of_singularity.txt | 6 ++++-- forge-gui/res/cardsfolder/f/face_of_divinity.txt | 2 +- .../f/farid_enterprising_salvager.txt | 8 ++++---- .../res/cardsfolder/g/gurgling_anointer.txt | 2 +- forge-gui/res/cardsfolder/p/pirated_copy.txt | 2 +- .../p/primal_amulet_primal_wellspring.txt | 16 +++++++--------- forge-gui/res/cardsfolder/r/riverfall_mimic.txt | 4 +++- 11 files changed, 34 insertions(+), 23 deletions(-) diff --git a/forge-gui/res/cardsfolder/c/captain_rex_nebula.txt b/forge-gui/res/cardsfolder/c/captain_rex_nebula.txt index 1711c671f50..5e534afe75f 100644 --- a/forge-gui/res/cardsfolder/c/captain_rex_nebula.txt +++ b/forge-gui/res/cardsfolder/c/captain_rex_nebula.txt @@ -7,7 +7,7 @@ SVar:TrigAnimate:DB$ Animate | ValidTgts$ Permanent.nonLand+YouCtrl | TgtPrompt$ SVar:CrashLand:Mode$ DamageDealtOnce | ValidSource$ Card.Self | ValidTarget$ Player,Permanent | Execute$ RollCounters | TriggerZones$ Battlefield | TriggerDescription$ Crash Land — Whenever this Vehicle deals damage, roll a six-sided die. If the result is equal to this Vehicle's mana value, sacrifice this Vehicle, then it deals that much damage to any target. SVar:RollCounters:DB$ RollDice | ResultSVar$ Result | SubAbility$ Crash SVar:Crash:DB$ Sacrifice | ConditionCheckSVar$ Result | ConditionSVarCompare$ EQY | SubAbility$ CrashDamage -SVar:CrashDamage:DB$ DealDamage | ValidTgt$ Planeswalker,Player,Permanent | TgtPromp$ Choose any target | NumDmg$ Y | ConditionCheckSVar$ Result | ConditionSVarCompare$ EQY +SVar:CrashDamage:DB$ DealDamage | ValidTgts$ Planeswalker,Player,Creature | TgtPrompt$ Choose any target | NumDmg$ Y | ConditionCheckSVar$ Result | ConditionSVarCompare$ EQY SVar:X:Targeted$CardManaCost SVar:Y:Count$CardManaCost DeckHints:Type$Vehicle diff --git a/forge-gui/res/cardsfolder/c/chromium_the_mutable.txt b/forge-gui/res/cardsfolder/c/chromium_the_mutable.txt index 037322e3eac..84abd7f7912 100644 --- a/forge-gui/res/cardsfolder/c/chromium_the_mutable.txt +++ b/forge-gui/res/cardsfolder/c/chromium_the_mutable.txt @@ -5,5 +5,8 @@ PT:7/7 K:Flash K:Flying K:This spell can't be countered. -A:AB$ Animate | Cost$ Discard<1/Card> | Types$ Human | Power$ 1 | Toughness$ 1 | Keywords$ Hexproof | HiddenKeywords$ Unblockable | RemoveAllAbilities$ True | RemoveCreatureTypes$ True | SpellDescription$ Until end of turn, CARDNAME becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn. +A:AB$ Animate | Cost$ Discard<1/Card> | Types$ Human | Power$ 1 | Toughness$ 1 | Keywords$ Hexproof | RemoveAllAbilities$ True | RemoveCreatureTypes$ True | SubAbility$ DBUnblockable | SpellDescription$ Until end of turn, CARDNAME becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn. +SVar:DBUnblockable:DB$ Effect | ExileOnMoved$ Battlefield | RememberObjects$ Self | StaticAbilities$ Unblockable +SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. +DeckHas:Ability$Discard Oracle:Flash\nThis spell can't be countered.\nFlying\nDiscard a card: Until end of turn, Chromium, the Mutable becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn. diff --git a/forge-gui/res/cardsfolder/c/creeping_tar_pit.txt b/forge-gui/res/cardsfolder/c/creeping_tar_pit.txt index b96eeed71f2..28729462f4a 100644 --- a/forge-gui/res/cardsfolder/c/creeping_tar_pit.txt +++ b/forge-gui/res/cardsfolder/c/creeping_tar_pit.txt @@ -3,5 +3,8 @@ ManaCost:no cost Types:Land K:CARDNAME enters the battlefield tapped. A:AB$ Mana | Cost$ T | Produced$ Combo U B | SpellDescription$ Add {U} or {B}. -A:AB$ Animate | Cost$ 1 U B | Defined$ Self | Power$ 3 | Toughness$ 2 | Types$ Creature,Elemental | Colors$ Blue,Black | HiddenKeywords$ Unblockable | SpellDescription$ CARDNAME becomes a 3/2 blue and black Elemental creature until end of turn and can't be blocked this turn. It's still a land. +A:AB$ Animate | Cost$ 1 U B | Defined$ Self | Power$ 3 | Toughness$ 2 | Types$ Creature,Elemental | Colors$ Blue,Black | SubAbility$ DBUnblockable | SpellDescription$ CARDNAME becomes a 3/2 blue and black Elemental creature until end of turn and can't be blocked this turn. It's still a land. +SVar:DBUnblockable:DB$ Effect | ExileOnMoved$ Battlefield | RememberObjects$ Self | StaticAbilities$ Unblockable +SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. +DeckHas:Type$Elemental Oracle:Creeping Tar Pit enters the battlefield tapped.\n{T}: Add {U} or {B}.\n{1}{U}{B}: Creeping Tar Pit becomes a 3/2 blue and black Elemental creature until end of turn and can't be blocked this turn. It's still a land. diff --git a/forge-gui/res/cardsfolder/d/dimir_keyrune.txt b/forge-gui/res/cardsfolder/d/dimir_keyrune.txt index 71526fdfed7..9db5a4b6657 100644 --- a/forge-gui/res/cardsfolder/d/dimir_keyrune.txt +++ b/forge-gui/res/cardsfolder/d/dimir_keyrune.txt @@ -2,7 +2,10 @@ Name:Dimir Keyrune ManaCost:3 Types:Artifact A:AB$ Mana | Cost$ T | Produced$ Combo U B | SpellDescription$ Add {U} or {B}. -A:AB$ Animate | Cost$ U B | Defined$ Self | Power$ 2 | Toughness$ 2 | Types$ Artifact,Creature,Horror | Colors$ Blue,Black | HiddenKeywords$ Unblockable | SpellDescription$ CARDNAME becomes a 2/2 blue and black Horror artifact creature until end of turn and can't be blocked this turn. +A:AB$ Animate | Cost$ U B | Defined$ Self | Power$ 2 | Toughness$ 2 | Types$ Artifact,Creature,Horror | Colors$ Blue,Black | SubAbility$ DBUnblockable | SpellDescription$ CARDNAME becomes a 2/2 blue and black Horror artifact creature until end of turn and can't be blocked this turn. +SVar:DBUnblockable:DB$ Effect | ExileOnMoved$ Battlefield | RememberObjects$ Self | StaticAbilities$ Unblockable +SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. AI:RemoveDeck:Random +DeckHas:Type$Horror DeckNeeds:Color$Blue|Black Oracle:{T}: Add {U} or {B}.\n{U}{B}: Dimir Keyrune becomes a 2/2 blue and black Horror artifact creature until end of turn and can't be blocked this turn. diff --git a/forge-gui/res/cardsfolder/e/eye_of_singularity.txt b/forge-gui/res/cardsfolder/e/eye_of_singularity.txt index f261f334185..9c739500b4c 100644 --- a/forge-gui/res/cardsfolder/e/eye_of_singularity.txt +++ b/forge-gui/res/cardsfolder/e/eye_of_singularity.txt @@ -2,8 +2,10 @@ Name:Eye of Singularity ManaCost:3 W Types:World Enchantment T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigRepeat | TriggerDescription$ When CARDNAME enters the battlefield, destroy each permanent with the same name as another permanent, except for basic lands. They can't be regenerated. -SVar:TrigRepeat:DB$ RepeatEach | RepeatCards$ Permanent.nonBasic | RepeatSubAbility$ DBDestroy -SVar:DBDestroy:DB$ Destroy | Defined$ Valid Permanent.sharesNameWith Remembered+IsNotRemembered | NoRegen$ True +SVar:TrigRepeat:DB$ RepeatEach | RepeatCards$ Permanent.nonBasic | RepeatSubAbility$ DBRem | SubAbility$ DBDestroy +SVar:DBRem:DB$ Pump | ImprintCards$ Valid Permanent.sharesNameWith Remembered+IsNotRemembered +SVar:DBDestroy:DB$ DestroyAll | ValidCards$ Card.IsImprinted | NoRegen$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearImprinted$ True T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Permanent.nonBasic | TriggerZones$ Battlefield | Execute$ TrigDestroy | TriggerDescription$ Whenever a permanent other than a basic land enters the battlefield, destroy all other permanents with that name. They can't be regenerated. SVar:TrigDestroy:DB$ DestroyAll | ValidCards$ Permanent.NotTriggeredCard+sharesNameWith TriggeredCard | NoRegen$ True AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/f/face_of_divinity.txt b/forge-gui/res/cardsfolder/f/face_of_divinity.txt index 5ea6cc3bed6..f5c1bb0f6d9 100644 --- a/forge-gui/res/cardsfolder/f/face_of_divinity.txt +++ b/forge-gui/res/cardsfolder/f/face_of_divinity.txt @@ -4,6 +4,6 @@ Types:Enchantment Aura K:Enchant creature A:SP$ Attach | Cost$ 2 W | ValidTgts$ Creature | AILogic$ Pump S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 2 | AddToughness$ 2 | Description$ Enchanted creature gets +2/+2. -S:Mode$ Continuous | Affected$ Creature.EnchantedBy Aura.Other | AddKeyword$ First Strike & Lifelink | Description$ As long as another Aura is attached to enchanted creature, it has first strike and lifelink. +S:Mode$ Continuous | Affected$ Creature.EnchantedBy+EnchantedBy Aura.Other | AddKeyword$ First Strike & Lifelink | Description$ As long as another Aura is attached to enchanted creature, it has first strike and lifelink. SVar:EnchantMe:Multiple Oracle:Enchant creature\nEnchanted creature gets +2/+2.\nAs long as another Aura is attached to enchanted creature, it has first strike and lifelink. diff --git a/forge-gui/res/cardsfolder/f/farid_enterprising_salvager.txt b/forge-gui/res/cardsfolder/f/farid_enterprising_salvager.txt index 7eb259f1feb..2e99c7a10dc 100644 --- a/forge-gui/res/cardsfolder/f/farid_enterprising_salvager.txt +++ b/forge-gui/res/cardsfolder/f/farid_enterprising_salvager.txt @@ -5,12 +5,12 @@ PT:3/3 T:Mode$ ChangesZone | ValidCard$ Artifact.nonToken+YouCtrl | Origin$ Battlefield | Destination$ Graveyard | Execute$ TrigToken | TriggerZones$ Battlefield | TriggerDescription$ Whenever a nontoken artifact you control is put into a graveyard from the battlefield, create a colorless artifact token named Scrap. SVar:TrigToken:DB$ Token | TokenScript$ scrap A:AB$ Charm | Cost$ 1 R Sac<1/Artifact> | Choices$ DBCounter,DBGoad,DBLoot | CharmNum$ 1 -SVar:DBCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | SubAbility$ DBPump | SpellDescription$ Put a +1/+1 counter on NICKNAME. It gains haste until end of turn. -SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Haste +SVar:DBCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | SubAbility$ DBPump | SpellDescription$ Put a +1/+1 counter on NICKNAME. It gains menace until end of turn. +SVar:DBPump:DB$ Pump | Defined$ Self | KW$ Menace SVar:DBGoad:DB$ Goad | ValidTgts$ Creature | SpellDescription$ Goad target creature. SVar:DBLoot:DB$ Discard | Mode$ TgtChoose | SubAbility$ DBDraw | SpellDescription$ Discard a card, then draw a card. SVar:DBDraw:DB$ Draw SVar:AIPreference:SacCost$Artifact.token DeckHints:Type$Artifact -DeckHas:Ability$Discard|Token|Counters & Type$Artifact & Keyword$Haste -Oracle:Whenever a nontoken artifact you control is put into a graveyard from the battlefield, create a colorless artifact token named Scrap.\n{1}{R}, Sacrifice an artifact: Choose one —\n• Put a +1/+1 counter on Farid. It gains haste until end of turn.\n• Goad target creature.\n• Discard a card, then draw a card. \ No newline at end of file +DeckHas:Ability$Discard|Token|Counters & Type$Artifact & Keyword$Menace +Oracle:Whenever a nontoken artifact you control is put into a graveyard from the battlefield, create a colorless artifact token named Scrap.\n{1}{R}, Sacrifice an artifact: Choose one —\n• Put a +1/+1 counter on Farid. It gains menace until end of turn.\n• Goad target creature.\n• Discard a card, then draw a card. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/g/gurgling_anointer.txt b/forge-gui/res/cardsfolder/g/gurgling_anointer.txt index 39f11fd0228..140f551a77b 100644 --- a/forge-gui/res/cardsfolder/g/gurgling_anointer.txt +++ b/forge-gui/res/cardsfolder/g/gurgling_anointer.txt @@ -7,7 +7,7 @@ SVar:SacMe:3 T:Mode$ Drawn | ValidCard$ Card.YouCtrl | Number$ 2 | TriggerZones$ Battlefield | Execute$ TrigPutCounter | TriggerDescription$ Whenever you draw your second card each turn, put a +1/+1 counter on CARDNAME. SVar:TrigPutCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigReturn | TriggerDescription$ When CARDNAME dies, return another target creature card with mana value less than or equal to CARDNAME's power from your graveyard to the battlefield. -SVar:TrigReturn:DB$ ChangeZone | ValidTgt$ Creature.YouOwn+cmcLEX+Other | TgtPrompt$ Choose another target creature card with mana value less than or equal to CARDNAME's power | Origin$ Graveyard | Destination$ Battlefield +SVar:TrigReturn:DB$ ChangeZone | ValidTgts$ Creature.YouOwn+cmcLEX+Other | TgtPrompt$ Choose another target creature card with mana value less than or equal to CARDNAME's power | Origin$ Graveyard | Destination$ Battlefield SVar:X:TriggeredCard$CardPower DeckHas:Ability$Counters|Graveyard Oracle:Flying\nWhenever you draw your second card each turn, put a +1/+1 counter on Gurgling Anointer.\nWhen Gurgling Anointer dies, return another target creature card with mana value less than or equal to Gurgling Anointer's power from your graveyard to the battlefield. diff --git a/forge-gui/res/cardsfolder/p/pirated_copy.txt b/forge-gui/res/cardsfolder/p/pirated_copy.txt index ca1a3b435ee..64a2bb6b071 100644 --- a/forge-gui/res/cardsfolder/p/pirated_copy.txt +++ b/forge-gui/res/cardsfolder/p/pirated_copy.txt @@ -4,6 +4,6 @@ Types:Creature Shapeshifter Pirate PT:0/0 K:ETBReplacement:Copy:DBCopy:Optional SVar:DBCopy:DB$ Clone | Choices$ Creature.Other | AddTypes$ Pirate | AddTriggers$ DrawTrig | AddSVars$ TrigDraw | SpellDescription$ You may have CARDNAME enter the battlefield as a copy of any creature on the battlefield, except it's a Pirate in addition to its other types and it has "Whenever this creature or another creature with the same name deals combat damage to a player, you draw a card." -SVar:DrawTrig:Mode$ DamageDone | ValidSource$ Card.Self,Creature.sameName | TriggerZones$ Battlefield | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever this creature or another creature with the same name deals combat damage to a player, you draw a card. +SVar:DrawTrig:Mode$ DamageDone | ValidSource$ Card.Self,Creature.Other+sameName | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDraw | TriggerZones$ Battlefield | TriggerDescription$ Whenever this creature or another creature with the same name deals combat damage to a player, you draw a card. SVar:TrigDraw:DB$ Draw Oracle:You may have Pirated Copy enter the battlefield as a copy of any creature on the battlefield, except it's a Pirate in addition to its other types and it has "Whenever this creature or another creature with the same name deals combat damage to a player, you draw a card." diff --git a/forge-gui/res/cardsfolder/p/primal_amulet_primal_wellspring.txt b/forge-gui/res/cardsfolder/p/primal_amulet_primal_wellspring.txt index c18c54ae554..91e49776fd1 100644 --- a/forge-gui/res/cardsfolder/p/primal_amulet_primal_wellspring.txt +++ b/forge-gui/res/cardsfolder/p/primal_amulet_primal_wellspring.txt @@ -3,15 +3,13 @@ ManaCost:4 Types:Artifact S:Mode$ ReduceCost | ValidCard$ Instant,Sorcery | Type$ Spell | Activator$ You | Amount$ 1 | Description$ Instant and sorcery spells you cast cost {1} less to cast. T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCharge | TriggerDescription$ Whenever you cast an instant or sorcery spell, put a charge counter on CARDNAME. Then if there are four or more charge counters on it, you may remove those counters and transform it. -SVar:TrigCharge:DB$ PutCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ 1 | SubAbility$ DBStoreSVar -SVar:DBStoreSVar:DB$ StoreSVar | SVar$ FullyCharged | Type$ Number | Expression$ 1 | ConditionCheckSVar$ ChargeCounter | ConditionSVarCompare$ GE1 | SubAbility$ DBRemoveCtrs -SVar:DBRemoveCtrs:DB$ RemoveCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ All | Optional$ True | ConditionCheckSVar$ FullyCharged | ConditionSVarCompare$ GE1 | SubAbility$ DBTransform -SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform | ConditionCheckSVar$ Discharged | ConditionSVarCompare$ EQ1 -SVar:ChargeCounter:Count$Valid Card.Self+counters_GE4_CHARGE -SVar:Discharged:Count$Valid Card.Self+counters_EQ0_CHARGE -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | Execute$ DBInitSVar | Static$ True -SVar:DBInitSVar:DB$ StoreSVar | SVar$ FullyCharged | Type$ Number | Expression$ 0 -SVar:FullyCharged:Number$0 +SVar:TrigCharge:DB$ PutCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ 1 | SubAbility$ DBBranch +SVar:DBBranch:DB$ Branch | BranchConditionSVar$ FullyCharged | TrueSubAbility$ DBRemoveCtrs +SVar:DBRemoveCtrs:DB$ RemoveCounter | Defined$ Self | CounterType$ CHARGE | CounterNum$ All | Optional$ True | RememberAmount$ True | SubAbility$ DBTransform +SVar:DBTransform:DB$ SetState | Defined$ Self | Mode$ Transform | ConditionCheckSVar$ Discharged | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:FullyCharged:Count$Valid Card.Self+counters_GE4_CHARGE +SVar:Discharged:Count$RememberedSize AlternateMode:DoubleFaced Oracle:Instant and sorcery spells you cast cost {1} less to cast.\nWhenever you cast an instant or sorcery spell, put a charge counter on Primal Amulet. Then if there are four or more charge counters on it, you may remove those counters and transform it. diff --git a/forge-gui/res/cardsfolder/r/riverfall_mimic.txt b/forge-gui/res/cardsfolder/r/riverfall_mimic.txt index d1e68c0a925..842b1a872cf 100644 --- a/forge-gui/res/cardsfolder/r/riverfall_mimic.txt +++ b/forge-gui/res/cardsfolder/r/riverfall_mimic.txt @@ -3,6 +3,8 @@ ManaCost:1 UR Types:Creature Shapeshifter PT:2/1 T:Mode$ SpellCast | ValidCard$ Card.Blue+Red | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigAnimate | TriggerDescription$ Whenever you cast a spell that's both blue and red, CARDNAME has base power and toughness 3/3 until end of turn and can't be blocked this turn. -SVar:TrigAnimate:DB$ Animate | Defined$ Self | Power$ 3 | Toughness$ 3 | HiddenKeywords$ Unblockable +SVar:TrigAnimate:DB$ Animate | Defined$ Self | Power$ 3 | Toughness$ 3 | SubAbility$ DBUnblockable +SVar:DBUnblockable:DB$ Effect | ExileOnMoved$ Battlefield | RememberObjects$ Self | StaticAbilities$ Unblockable +SVar:Unblockable:Mode$ CantBlockBy | ValidAttacker$ Card.IsRemembered | Description$ This creature can't be blocked this turn. SVar:BuffedBy:Card.Blue+Red Oracle:Whenever you cast a spell that's both blue and red, Riverfall Mimic has base power and toughness 3/3 until end of turn and can't be blocked this turn. From f67f5636e0677e0d1e85a30edf02d40842322bda Mon Sep 17 00:00:00 2001 From: Northmoc <103371817+Northmoc@users.noreply.github.com> Date: Fri, 30 Dec 2022 12:48:09 -0500 Subject: [PATCH 47/76] ONE: Green Sun's Twilight and support (#2175) --- .../ability/effects/ChangeZoneEffect.java | 8 +- .../ability/effects/DigMultipleEffect.java | 109 ++++++++++-------- .../src/main/java/forge/util/MessageUtil.java | 1 + .../upcoming/green_suns_twilight.txt | 11 ++ forge-gui/res/languages/de-DE.properties | 2 + forge-gui/res/languages/en-US.properties | 2 + forge-gui/res/languages/es-ES.properties | 2 + forge-gui/res/languages/fr-FR.properties | 2 + forge-gui/res/languages/it-IT.properties | 2 + forge-gui/res/languages/ja-JP.properties | 2 + forge-gui/res/languages/pt-BR.properties | 2 + forge-gui/res/languages/zh-CN.properties | 2 + .../forge/player/PlayerControllerHuman.java | 2 +- 13 files changed, 97 insertions(+), 50 deletions(-) create mode 100644 forge-gui/res/cardsfolder/upcoming/green_suns_twilight.txt diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 9a8ca6c6a6b..c1bcc34c7c7 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -133,10 +133,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (sa.hasParam("ExileFaceDown")) { sb.append(" face down"); } + sb.append("."); } else if (destination.equals("Ante")) { - sb.append("Add the top card of your library to the ante"); + sb.append("Add the top card of your library to the ante."); } - sb.append("."); } else if (origin.equals("Library")) { final boolean originAlt = sa.hasParam("OriginAlternative"); sb.append(chooserNames).append(" search").append(choosers.size() > 1 ? " " : "es "); @@ -1109,7 +1109,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { fetchList = (CardCollection)AbilityUtils.filterListByType(fetchList, sa.getParam("ChangeType"), sa); } - if (sa.hasParam("NoShuffle")) { + if (sa.hasParam("NoShuffle") || "False".equals(sa.getParam("Shuffle"))) { shuffleMandatory = false; } @@ -1249,7 +1249,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { CardLists.shuffle(chosenCards); } // do not shuffle the library once we have placed a fetched card on top. - if (origin.contains(ZoneType.Library) && (destination == ZoneType.Library) && !"False".equals(sa.getParam("Shuffle"))) { + if (origin.contains(ZoneType.Library) && (destination == ZoneType.Library) && shuffleMandatory) { player.shuffle(sa); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java index e815b89f8a9..2f1b7a3534f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigMultipleEffect.java @@ -77,10 +77,20 @@ public class DigMultipleEffect extends SpellAbilityEffect { if (validMap.isEmpty()) { chooser.getController().notifyOfValue(sa, null, Localizer.getInstance().getMessage("lblNoValidCards")); } else { - CardCollection chosen = chooser.getController().chooseCardsForEffectMultiple(validMap, sa, Localizer.getInstance().getMessage("lblChooseCards"), chooseOptional); + CardCollection chosen; + //ensure choosing something when possible and not optional + while (true) { + chosen = chooser.getController().chooseCardsForEffectMultiple(validMap, sa, + Localizer.getInstance().getMessage("lblChooseCards"), chooseOptional); - if (!chosen.isEmpty()) { - game.getAction().reveal(chosen, chooser, true, Localizer.getInstance().getMessage("lblPlayerPickedCardFrom", chooser.getName())); + if (!chosen.isEmpty()) { + game.getAction().reveal(chosen, chooser, true, + Localizer.getInstance().getMessage("lblPlayerPickedCardFrom", chooser.getName())); + break; + } + if (chooseOptional) break; + chooser.getController().notifyOfValue(sa, null, + Localizer.getInstance().getMessage("lblMustChoose")); } if (sa.hasParam("ChooseAmount") || sa.hasParam("ChosenZone")) { @@ -108,21 +118,23 @@ public class DigMultipleEffect extends SpellAbilityEffect { final ZoneType origin = c.getZone().getZoneType(); final PlayerZone zone = c.getOwner().getZone(destZone1); - if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) { - if (libraryPosition == -1 || libraryPosition > zone.size()) { - libraryPosition = zone.size(); - } - c = game.getAction().moveTo(zone, c, libraryPosition, sa); - } else { - if (destZone1.equals(ZoneType.Battlefield)) { - if (sa.hasParam("Tapped")) { - c.setTapped(true); + if (!sa.hasParam("ChangeLater")) { + if (zone.is(ZoneType.Library) || zone.is(ZoneType.PlanarDeck) || zone.is(ZoneType.SchemeDeck)) { + if (libraryPosition == -1 || libraryPosition > zone.size()) { + libraryPosition = zone.size(); } + c = game.getAction().moveTo(zone, c, libraryPosition, sa); + } else { + if (destZone1.equals(ZoneType.Battlefield)) { + if (sa.hasParam("Tapped")) { + c.setTapped(true); + } + } + c = game.getAction().moveTo(zone, c, sa); + } + if (!origin.equals(c.getZone().getZoneType())) { + table.put(origin, c.getZone().getZoneType(), c); } - c = game.getAction().moveTo(zone, c, sa); - } - if (!origin.equals(c.getZone().getZoneType())) { - table.put(origin, c.getZone().getZoneType(), c); } if (sa.hasParam("ExileFaceDown")) { @@ -142,38 +154,45 @@ public class DigMultipleEffect extends SpellAbilityEffect { } // now, move the rest to destZone2 - if (destZone2 == ZoneType.Library || destZone2 == ZoneType.PlanarDeck || destZone2 == ZoneType.SchemeDeck - || destZone2 == ZoneType.Graveyard) { - CardCollection afterOrder = rest; - if (sa.hasParam("RestRandomOrder")) { - CardLists.shuffle(afterOrder); - } - if (libraryPosition2 != -1) { - // Closest to top - Collections.reverse(afterOrder); - } - for (final Card c : afterOrder) { - final ZoneType origin = c.getZone().getZoneType(); - Card m; - if (destZone2 == ZoneType.Library) { - m = game.getAction().moveToLibrary(c, libraryPosition2, sa); - } else { - m = game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2, sa); + if (!sa.hasParam("ChangeLater")) { + if (destZone2 == ZoneType.Library || destZone2 == ZoneType.PlanarDeck + || destZone2 == ZoneType.SchemeDeck || destZone2 == ZoneType.Graveyard) { + CardCollection afterOrder = rest; + if (sa.hasParam("RestRandomOrder")) { + CardLists.shuffle(afterOrder); } - if (m != null && !origin.equals(m.getZone().getZoneType())) { - table.put(origin, m.getZone().getZoneType(), m); + if (libraryPosition2 != -1) { + // Closest to top + Collections.reverse(afterOrder); + } + for (final Card c : afterOrder) { + final ZoneType origin = c.getZone().getZoneType(); + Card m; + if (destZone2 == ZoneType.Library) { + m = game.getAction().moveToLibrary(c, libraryPosition2, sa); + } else { + m = game.getAction().moveToVariantDeck(c, destZone2, libraryPosition2, sa); + } + if (m != null && !origin.equals(m.getZone().getZoneType())) { + table.put(origin, m.getZone().getZoneType(), m); + } + } + } else { + // just move them randomly + for (int i = 0; i < rest.size(); i++) { + Card c = rest.get(i); + final ZoneType origin = c.getZone().getZoneType(); + final PlayerZone toZone = c.getOwner().getZone(destZone2); + c = game.getAction().moveTo(toZone, c, sa); + if (!origin.equals(c.getZone().getZoneType())) { + table.put(origin, c.getZone().getZoneType(), c); + } } } - } else { - // just move them randomly - for (int i = 0; i < rest.size(); i++) { - Card c = rest.get(i); - final ZoneType origin = c.getZone().getZoneType(); - final PlayerZone toZone = c.getOwner().getZone(destZone2); - c = game.getAction().moveTo(toZone, c, sa); - if (!origin.equals(c.getZone().getZoneType())) { - table.put(origin, c.getZone().getZoneType(), c); - } + } + if (sa.hasParam("ImprintRest")) { + for (Card c : rest) { + host.addImprintedCard(c); } } } diff --git a/forge-game/src/main/java/forge/util/MessageUtil.java b/forge-game/src/main/java/forge/util/MessageUtil.java index 243cc38fc38..96cc62d8147 100644 --- a/forge-game/src/main/java/forge/util/MessageUtil.java +++ b/forge-game/src/main/java/forge/util/MessageUtil.java @@ -35,6 +35,7 @@ public class MessageUtil { String choser = StringUtils.capitalize(mayBeYou(player, target)); switch(sa.getApi()) { case ChooseDirection: + case DigMultiple: return value; case ChooseColor: return sa.hasParam("Random") diff --git a/forge-gui/res/cardsfolder/upcoming/green_suns_twilight.txt b/forge-gui/res/cardsfolder/upcoming/green_suns_twilight.txt new file mode 100644 index 00000000000..0e7245ae2cd --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/green_suns_twilight.txt @@ -0,0 +1,11 @@ +Name:Green Sun's Twilight +ManaCost:X G +Types:Sorcery +A:SP$ DigMultiple | DigNum$ SVar$X/Plus.1 | Reveal$ True | ChangeValid$ Creature,Land | ChangeLater$ True | RememberChanged$ True | ImprintRest$ True | SubAbility$ DBBranch | SpellDescription$ Reveal the top X plus one cards of your library. Choose a creature card and/or a land card from among them. Put those cards into your hand and the rest on the bottom of your library in a random order. If X is 5 or more, instead put the chosen cards onto the battlefield or into your hand and the rest on the bottom of your library in a random order. +SVar:DBBranch:DB$ Branch | BranchConditionSVar$ X | BranchConditionSVarCompare$ GE5 | TrueSubAbility$ DBChangeZone | FalseSubAbility$ Hand | SubAbility$ RestBottom +SVar:DBChangeZone:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Battlefield | DestinationAlternative$ Hand | AlternativeDestinationMessage$ Put the chosen card(s) onto the battlefield instead of into your hand? +SVar:Hand:DB$ ChangeZone | Defined$ Remembered | Origin$ Library | Destination$ Hand | NoShuffle$ True +SVar:RestBottom:DB$ ChangeZone | Defined$ Imprinted | Origin$ Library | Destination$ Library | LibraryPosition$ -1 | RandomOrder$ True | NoShuffle$ True | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True +SVar:X:Count$xPaid +Oracle:Reveal the top X plus one cards of your library. Choose a creature card and/or a land card from among them. Put those cards into your hand and the rest on the bottom of your library in a random order. If X is 5 or more, instead put the chosen cards onto the battlefield or into your hand and the rest on the bottom of your library in a random order. diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 9593303d302..6564af2414e 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -1967,6 +1967,8 @@ lblNoValidCards=Keine gültigen Karten #DigUntilEffect.java lblDoYouWantDigYourLibrary=Möchtest dein Bibliothek durchsuchen? lblDoYouWantPutCardToZone=Möchtest du diese Karte nach {0} legen? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=Möchtest du {0} zufällige Karte(n) abwerfen? lblPlayerHasChosenCardsFrom={0} hat Karte(n) gewählt von diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 38317691163..038d5fd82fa 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -1968,6 +1968,8 @@ lblNoValidCards=No valid cards #DigUntilEffect.java lblDoYouWantDigYourLibrary=Do you want to dig your library? lblDoYouWantPutCardToZone=Do you want to put that card to {0}? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=Would you like to discard {0} random card(s)? lblPlayerHasChosenCardsFrom={0} has chosen card(s) from diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index f5f1b1b8c2b..e04061ee006 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -1966,6 +1966,8 @@ lblNoValidCards=No hay cartas válidas #DigUntilEffect.java lblDoYouWantDigYourLibrary=¿Quieres escarbar en tu biblioteca? lblDoYouWantPutCardToZone=¿Quieres poner esa carta a {0}? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=¿Te gustaría descartar {0} carta(s) aleatoria(s)? lblPlayerHasChosenCardsFrom={0} ha elegido una o varias cartas de diff --git a/forge-gui/res/languages/fr-FR.properties b/forge-gui/res/languages/fr-FR.properties index f83b7978584..f2ebf18b233 100644 --- a/forge-gui/res/languages/fr-FR.properties +++ b/forge-gui/res/languages/fr-FR.properties @@ -1967,6 +1967,8 @@ lblNoValidCards=Aucune carte valide #DigUntilEffect.java lblDoYouWantDigYourLibrary=Voulez-vous fouiller votre bibliothèque ? lblDoYouWantPutCardToZone=Voulez-vous mettre cette carte dans {0} ? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=Voulez-vous défausser {0} carte(s) aléatoire(s) ? lblPlayerHasChosenCardsFrom={0} a choisi une ou plusieurs cartes parmi diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 236f329bf35..0567a17ede2 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -1965,6 +1965,8 @@ lblNoValidCards=Nessuna carta valida #DigUntilEffect.java lblDoYouWantDigYourLibrary=Vuoi passare in rassegna il tuo grimorio? lblDoYouWantPutCardToZone=Vuoi mettere la carta in {0}? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=Vuoi scartare {0} carta/e a caso? lblPlayerHasChosenCardsFrom={0}ha scelto delle carte da diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index 6ee053f47f3..54b4c977740 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -1965,6 +1965,8 @@ lblNoValidCards=符合するカードがありません #DigUntilEffect.java lblDoYouWantDigYourLibrary=ライブラリーを探しますか? lblDoYouWantPutCardToZone=そのカードを {0}に置きますか? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=不作為に {0}枚のカードを捨てますか? lblPlayerHasChosenCardsFrom={0}がカードを選択した: diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties index 0e5a8a40762..ff09833a288 100644 --- a/forge-gui/res/languages/pt-BR.properties +++ b/forge-gui/res/languages/pt-BR.properties @@ -2027,6 +2027,8 @@ lblNoValidCards=Sem cartas válidas #DigUntilEffect.java lblDoYouWantDigYourLibrary=Deseja vasculhar seu grimório? lblDoYouWantPutCardToZone=Deseja colocar essa carta em {0}? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=Descartar {0} carta(s) aleatoriamente? lblPlayerHasChosenCardsFrom={0} escolheu carta(s) de diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 3e9d94b932b..67be8ed3fb4 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -1969,6 +1969,8 @@ lblNoValidCards=没有有效的牌 #DigUntilEffect.java lblDoYouWantDigYourLibrary=你想要挖掘你的牌库吗? lblDoYouWantPutCardToZone=你想把这张牌放到{0}吗? +#DigMultipleEffect.java +lblMustChoose=You must choose at least one card. #DiscardEffect.java lblWouldYouLikeRandomDiscardTargetCard=你想随机弃掉%d张牌吗? lblPlayerHasChosenCardsFrom={0}选择了牌自 diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 98724c11758..8918a30f1af 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -3335,7 +3335,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont public CardCollection chooseCardsForEffectMultiple(Map validMap, SpellAbility sa, String title, boolean isOptional) { CardCollection result = new CardCollection(); for (Map.Entry e : validMap.entrySet()) { - result.addAll(chooseCardsForEffect(e.getValue(), sa, title + " " + e.getKey(), 0, 1, isOptional, null)); + result.addAll(chooseCardsForEffect(e.getValue(), sa, title + " (" + e.getKey() + ")", 0, 1, isOptional, null)); } return result; } From e63ba2263043b6b2a8a9eadf0c8c82ce8ed1315a Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Fri, 30 Dec 2022 20:24:37 +0100 Subject: [PATCH 48/76] Fix NumLoyaltyAct counting limit and additional wrong together --- forge-game/src/main/java/forge/game/card/Card.java | 11 ++++++++++- .../game/spellability/SpellAbilityRestriction.java | 13 ++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 770aa52158e..e3b5fc0f092 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -324,6 +324,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private SpellAbility[] basicLandAbilities = new SpellAbility[MagicColor.WUBRG.length]; private int planeswalkerAbilityActivated; + private boolean planeswalkerActivationLimitUsed; private final ActivationTable numberTurnActivations = new ActivationTable(); private final ActivationTable numberGameActivations = new ActivationTable(); @@ -7149,11 +7150,19 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } public void addPlaneswalkerAbilityActivated() { - planeswalkerAbilityActivated++; + // track if increased limit was used for activation because if there are also additional ones they can count on top + if (++planeswalkerAbilityActivated == 2 && StaticAbilityNumLoyaltyAct.limitIncrease(this)) { + planeswalkerActivationLimitUsed = true; + } + } + + public boolean planeswalkerActivationLimitUsed() { + return planeswalkerActivationLimitUsed; } public void resetActivationsPerTurn() { planeswalkerAbilityActivated = 0; + planeswalkerActivationLimitUsed = false; numberTurnActivations.clear(); } diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java index b22803c1936..536c140727e 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityRestriction.java @@ -475,12 +475,15 @@ public class SpellAbilityRestriction extends SpellAbilityVariables { } if (sa.isPwAbility()) { - final int initialLimit = StaticAbilityNumLoyaltyAct.limitIncrease(c) ? 1 : 0; - final int limit = StaticAbilityNumLoyaltyAct.additionalActivations(c, sa) + initialLimit; - int numActivates = c.getPlaneswalkerAbilityActivated(); - if (numActivates > limit) { - return false; + int limit = StaticAbilityNumLoyaltyAct.limitIncrease(c) ? 2 : 1; + + if (numActivates >= limit) { + // increased limit only counts if it's been used already + limit += StaticAbilityNumLoyaltyAct.additionalActivations(c, sa) - (limit == 1 || c.planeswalkerActivationLimitUsed() ? 0 : 1); + if (numActivates >= limit) { + return false; + } } } From 776c7873af5b8e2ec116e0985f35b4696f9e9c49 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sun, 1 Jan 2023 21:18:34 +0800 Subject: [PATCH 49/76] Update CardDetailUtil.java fix facedown typeline status --- forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java index 9aade51b490..effcbc211a9 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java @@ -171,8 +171,9 @@ public class CardDetailUtil { } public static String formatCardType(final CardStateView card, final boolean canShow) { + boolean isInPlay = card.getCard() != null && ZoneType.Battlefield.equals(card.getCard().getZone()); String translatedtype = CardTranslation.getTranslatedType(card.getName(), card.getType().toString()); - return canShow ? translatedtype : (card.getState() == CardStateName.FaceDown ? "Creature" : "---"); + return canShow ? translatedtype : (card.getState() == CardStateName.FaceDown && isInPlay ? "Creature" : ""); } public static String formatPowerToughness(final CardStateView card, final boolean canShow) { From 944933fd12c5af919c7b68e4688b03ffae4ca0aa Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Mon, 2 Jan 2023 15:55:31 +0800 Subject: [PATCH 50/76] fix copying deck to clipboard --- forge-core/src/main/java/forge/item/PaperCard.java | 3 +++ .../src/main/java/forge/deckchooser/FDeckViewer.java | 2 +- forge-gui-mobile/src/forge/deck/FDeckViewer.java | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index 32e9550b253..b835109f866 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -240,6 +240,9 @@ public class PaperCard implements Comparable, InventoryItemFromSet, // cannot still decide, if this "name|set" format is needed anymore // return String.format("%s|%s", name, cardSet); } + public String getCardName() { + return name; + } /* * This (utility) method transform a collectorNumber String into a key string for sorting. diff --git a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckViewer.java b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckViewer.java index 5f889736ddf..60cb60b8a06 100644 --- a/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckViewer.java +++ b/forge-gui-desktop/src/main/java/forge/deckchooser/FDeckViewer.java @@ -189,7 +189,7 @@ public class FDeckViewer extends FDialog { sectionCards = new TreeMap<>(); deckList.append(nl); for (final Entry ev : cp) { - cardName = ev.getKey().toString(); + cardName = ev.getKey().getCardName(); if (sectionCards.containsKey(cardName)) { sectionCards.put(cardName, (int)sectionCards.get(cardName) + ev.getValue()); } diff --git a/forge-gui-mobile/src/forge/deck/FDeckViewer.java b/forge-gui-mobile/src/forge/deck/FDeckViewer.java index 16b359a8766..ddb0894a722 100644 --- a/forge-gui-mobile/src/forge/deck/FDeckViewer.java +++ b/forge-gui-mobile/src/forge/deck/FDeckViewer.java @@ -99,7 +99,7 @@ public class FDeckViewer extends FScreen { deckList.append(s.toString()).append(": "); deckList.append(nl); for (final Entry ev : cp) { - deckList.append(ev.getValue()).append(" ").append(ev.getKey()).append(nl); + deckList.append(ev.getValue()).append(" ").append(ev.getKey().getCardName()).append(nl); } deckList.append(nl); } From 6dadf396c1f5a656ea36bc69f4e89028240545bc Mon Sep 17 00:00:00 2001 From: JohnWilliams77 <103562494+JohnWilliams77@users.noreply.github.com> Date: Mon, 2 Jan 2023 09:52:10 +0000 Subject: [PATCH 51/76] Update Secret Lair Drop Series.txt --- forge-gui/res/editions/Secret Lair Drop Series.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt index e9987660339..2df3a6a9062 100644 --- a/forge-gui/res/editions/Secret Lair Drop Series.txt +++ b/forge-gui/res/editions/Secret Lair Drop Series.txt @@ -605,6 +605,8 @@ ScryfallCode=SLD 634 R Syphon Sliver @Tyler Jacobson 635 R Toxin Sliver @Lars Grant-West 636 R Belligerent Sliver @Raymond Swanland +637 R Blur Sliver @Daarken +638 R Fury Sliver @Paolo Parente 640 R Homing Sliver @Trevor Hairsine 641 R Magma Sliver @Wayne England 643 R Spiteful Sliver @Johann Bodin @@ -665,6 +667,7 @@ ScryfallCode=SLD 707 R Knight Exemplar @Victor Adame Minguez 708 R Fellwar Stone @Dan Frazier 709 R Dragon's Hoard @Pedro Potier +710 R Command Tower @Evan Shipard 711 R Tireless Tracker @Nils Hamm 718 R Maro @Jesper Ejsing 719 R Maro @Mark Rosewater From 359a97462ee0ad72f7276290b201e02564c056f6 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Wed, 4 Jan 2023 06:04:17 +0100 Subject: [PATCH 52/76] Few fixes (#2198) Co-authored-by: tool4EvEr --- forge-ai/src/main/java/forge/ai/ability/EffectAi.java | 3 +-- forge-game/src/main/java/forge/game/GameActionUtil.java | 2 +- .../src/main/java/forge/game/ability/SpellAbilityEffect.java | 2 +- .../main/java/forge/game/ability/effects/BalanceEffect.java | 2 +- forge-game/src/main/java/forge/game/card/Card.java | 2 -- forge-gui/res/cardsfolder/c/conspiracy_theorist.txt | 2 +- forge-gui/res/cardsfolder/g/gorilla_tactics.txt | 2 +- forge-gui/res/cardsfolder/l/lorcan_warlock_collector.txt | 2 +- forge-gui/res/cardsfolder/m/myrkuls_edict.txt | 2 +- forge-gui/res/cardsfolder/o/omen_machine.txt | 2 +- forge-gui/res/cardsfolder/p/pure_intentions.txt | 3 ++- forge-gui/res/cardsfolder/s/sand_golem.txt | 5 ++--- forge-gui/res/cardsfolder/s/solitude.txt | 3 ++- forge-gui/res/cardsfolder/w/wicked_guardian.txt | 5 ++--- 14 files changed, 17 insertions(+), 20 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java index 0d6452f4975..69264b310c1 100644 --- a/forge-ai/src/main/java/forge/ai/ability/EffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/EffectAi.java @@ -115,8 +115,7 @@ public class EffectAi extends SpellAbilityAi { } randomReturn = true; } else if (logic.equals("ChainVeil")) { - if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2) - || CardLists.getType(ai.getCardsIn(ZoneType.Battlefield), "Planeswalker").isEmpty()) { + if (!phase.isPlayerTurn(ai) || !phase.getPhase().equals(PhaseType.MAIN2) || ai.getPlaneswalkersInPlay().isEmpty()) { return false; } randomReturn = true; diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java index 6830d9b4fc9..b8c7e2a1e9f 100644 --- a/forge-game/src/main/java/forge/game/GameActionUtil.java +++ b/forge-game/src/main/java/forge/game/GameActionUtil.java @@ -895,7 +895,7 @@ public final class GameActionUtil { } CardCollection completeList = new CardCollection(); PlayerCollection players = new PlayerCollection(game.getPlayers()); - // CR 613.7k use APNAP + // CR 613.7m use APNAP int indexAP = players.indexOf(game.getPhaseHandler().getPlayerTurn()); if (indexAP != -1) { Collections.rotate(players, - indexAP); diff --git a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java index a935835b7da..0bf5d8306fc 100644 --- a/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java +++ b/forge-game/src/main/java/forge/game/ability/SpellAbilityEffect.java @@ -451,7 +451,7 @@ public abstract class SpellAbilityEffect { protected static void addLeaveBattlefieldReplacement(final Card card, final SpellAbility sa, final String zone) { final Card host = sa.getHostCard(); final Game game = card.getGame(); - final Card eff = createEffect(sa, sa.getActivatingPlayer(), host.getName() + "'s Effect", host.getImageKey()); + final Card eff = createEffect(sa, sa.getActivatingPlayer(), host + "'s Effect", host.getImageKey()); addLeaveBattlefieldReplacement(eff, zone); diff --git a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java b/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java index 727c782a689..233151770b5 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/BalanceEffect.java @@ -62,7 +62,7 @@ public class BalanceEffect extends SpellAbilityEffect { discardedMap.put(p, p.getController().chooseCardsToDiscardFrom(p, sa, validCards.get(i), numToBalance, numToBalance)); } else { // Battlefield for (Card card : p.getController().choosePermanentsToSacrifice(sa, numToBalance, numToBalance, validCards.get(i), valid)) { - if ( null == card ) continue; + if (null == card) continue; game.getAction().sacrifice(card, sa, true, table, params); } } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index e3b5fc0f092..696cea94a1a 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -3268,7 +3268,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { public final boolean isFlipped() { return flipped; } - public final void setFlipped(boolean value) { flipped = value; } @@ -3276,7 +3275,6 @@ public class Card extends GameEntity implements Comparable, IHasSVars { public final void setCanCounter(final boolean b) { canCounter = b; } - public final boolean getCanCounter() { return canCounter; } diff --git a/forge-gui/res/cardsfolder/c/conspiracy_theorist.txt b/forge-gui/res/cardsfolder/c/conspiracy_theorist.txt index c1434492381..c833d71b8f5 100644 --- a/forge-gui/res/cardsfolder/c/conspiracy_theorist.txt +++ b/forge-gui/res/cardsfolder/c/conspiracy_theorist.txt @@ -6,7 +6,7 @@ T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ SVar:TrigDraw:AB$ Draw | Cost$ 1 Discard<1/Card> T:Mode$ DiscardedAll | ValidPlayer$ You | ValidCard$ Card.nonLand | TriggerZones$ Battlefield | Execute$ TrigEffect | TriggerDescription$ Whenever you discard one or more nonland cards, you may exile one of them from your graveyard. If you do, you may cast it this turn. SVar:TrigEffect:AB$ Effect | Cost$ ExileFromGrave<1/Card.TriggeredCards> | RememberObjects$ ExiledCards | StaticAbilities$ MayCast | ExileOnMoved$ Stack -SVar:MayCast:Mode$ Continuous | Affected$ Card.IsRemembered | MayPlay$ True | EffectZone$ Command | AffectedZone$ Exile | Description$ You may cast this spell this turn. +SVar:MayCast:Mode$ Continuous | Affected$ Card.IsRemembered+nonLand | MayPlay$ True | EffectZone$ Command | AffectedZone$ Exile | Description$ You may cast this spell this turn. SVar:HasAttackEffect:TRUE DeckHas:Ability$Discard Oracle:Whenever Conspiracy Theorist attacks, you may pay {1} and discard a card. If you do, draw a card.\nWhenever you discard one or more nonland cards, you may exile one of them from your graveyard. If you do, you may cast it this turn. diff --git a/forge-gui/res/cardsfolder/g/gorilla_tactics.txt b/forge-gui/res/cardsfolder/g/gorilla_tactics.txt index 8f556c5c3b8..4338219b7ca 100644 --- a/forge-gui/res/cardsfolder/g/gorilla_tactics.txt +++ b/forge-gui/res/cardsfolder/g/gorilla_tactics.txt @@ -2,7 +2,7 @@ Name:Gorilla Tactics ManaCost:1 G Types:Instant A:SP$ Token | Cost$ 1 G | TokenScript$ g_2_2_gorilla | SpellDescription$ Create a 2/2 green Gorilla creature token. -T:Mode$ Discarded | ValidCard$ Card.Self | ValidCause$ Card.OppCtrl | Execute$ TrigDouble | TriggerDescription$ When a spell or ability an opponent controls causes you to discard Gorilla Tactics, create two 2/2 green Gorilla creature tokens. +T:Mode$ Discarded | ValidCard$ Card.Self | ValidCause$ Card.OppCtrl | Execute$ TrigDouble | TriggerDescription$ When a spell or ability an opponent controls causes you to discard CARDNAME, create two 2/2 green Gorilla creature tokens. SVar:TrigDouble:DB$ Token | TokenScript$ g_2_2_gorilla | TokenAmount$ 2 DeckHas:Ability$Token Oracle:Create a 2/2 green Gorilla creature token.\nWhen a spell or ability an opponent controls causes you to discard Gorilla Tactics, create two 2/2 green Gorilla creature tokens. diff --git a/forge-gui/res/cardsfolder/l/lorcan_warlock_collector.txt b/forge-gui/res/cardsfolder/l/lorcan_warlock_collector.txt index 43c7f96894e..e15def1a18d 100644 --- a/forge-gui/res/cardsfolder/l/lorcan_warlock_collector.txt +++ b/forge-gui/res/cardsfolder/l/lorcan_warlock_collector.txt @@ -7,6 +7,6 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Graveyard | ValidCard$ Creature SVar:TrigReanimate:AB$ ChangeZone | Cost$ PayLife | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Battlefield | GainControl$ True | ChangeNum$ 1 | AnimateSubAbility$ Animate SVar:Animate:DB$ Animate | Defined$ TriggeredCard | Types$ Warlock | Duration$ Permanent SVar:X:TriggeredCard$CardManaCost -R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidLKI$ Creature.Warlock+YouCtrl | ReplaceWith$ Exile | CheckSelfLKIZone$ True | Description$ If a Warlock you control would die, exile it instead. +R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Battlefield | Destination$ Graveyard | ValidLKI$ Warlock.YouCtrl | ReplaceWith$ Exile | CheckSelfLKIZone$ True | Description$ If a Warlock you control would die, exile it instead. SVar:Exile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | Defined$ ReplacedCard Oracle:Flying\nWhenever a creature card is put into an opponent's graveyard from anywhere, you may pay life equal to its mana value. If you do, put it onto the battlefield under your control. It's a Warlock in addition to its other types.\nIf a Warlock you control would die, exile it instead. diff --git a/forge-gui/res/cardsfolder/m/myrkuls_edict.txt b/forge-gui/res/cardsfolder/m/myrkuls_edict.txt index 81fab7c208a..62037404491 100644 --- a/forge-gui/res/cardsfolder/m/myrkuls_edict.txt +++ b/forge-gui/res/cardsfolder/m/myrkuls_edict.txt @@ -3,7 +3,7 @@ ManaCost:1 B Types:Sorcery A:SP$ RollDice | Sides$ 20 | ResultSubAbilities$ 1-9:OneOppSac,10-19:EachOppSac,20:SacTopPower | SpellDescription$ Roll a d20. SVar:OneOppSac:DB$ ChoosePlayer | Defined$ You | Choices$ Player.Opponent | SubAbility$ DBSac | StackDescription$ SpellDescription | SpellDescription$ 1—9 VERT Choose an opponent. That player sacrifices a creature. -SVar:DBSac:DB$ Sacrifice | Defined$ Chosen | SacValid$ Creature | SubAbility$ DBCleanupChosen +SVar:DBSac:DB$ Sacrifice | Defined$ ChosenPlayer | SacValid$ Creature | SubAbility$ DBCleanupChosen SVar:EachOppSac:DB$ Sacrifice | Defined$ Player.Opponent | SacValid$ Creature | StackDescription$ SpellDescription | SpellDescription$ 10—19 VERT Each opponent sacrifices a creature. SVar:SacTopPower:DB$ RepeatEach | RepeatPlayers$ Player.Opponent | RepeatSubAbility$ DBChooseCard | SubAbility$ DBSacAll | StackDescription$ SpellDescription | SpellDescription$ 20 VERT Each opponent sacrifices a creature with the greatest power among creatures that player controls. SVar:DBChooseCard:DB$ ChooseCard | Defined$ Player.IsRemembered | Choices$ Creature.greatestPowerControlledByRemembered | ChoiceTitle$ Choose a creature you control with the greatest power | Mandatory$ True | RememberChosen$ True diff --git a/forge-gui/res/cardsfolder/o/omen_machine.txt b/forge-gui/res/cardsfolder/o/omen_machine.txt index 08071df7c0b..51301808a75 100644 --- a/forge-gui/res/cardsfolder/o/omen_machine.txt +++ b/forge-gui/res/cardsfolder/o/omen_machine.txt @@ -5,7 +5,7 @@ S:Mode$ CantDraw | ValidPlayer$ Player | Description$ Players can't draw cards. T:Mode$ Phase | Phase$ Draw | ValidPlayer$ Player | Execute$ TrigOmenExileCard | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of each player's draw step, that player exiles the top card of their library. If it's a land card, the player puts it onto the battlefield. Otherwise, the player casts it without paying its mana cost if able. SVar:TrigOmenExileCard:DB$ Dig | DigNum$ 1 | ChangeNum$ All | Defined$ TriggeredPlayer | DestinationZone$ Exile | RememberChanged$ True | SubAbility$ DBOmenLand SVar:DBOmenLand:DB$ ChangeZone | Origin$ Exile | Destination$ Battlefield | ChangeType$ Land.IsRemembered+ActivePlayerCtrl | ChangeNum$ 1 | DefinedPlayer$ TriggeredPlayer | Chooser$ TriggeredPlayer | Hidden$ True | Mandatory$ True | SubAbility$ DBOmenPlay -SVar:DBOmenPlay:DB$ Play | ValidZone$ Exile | Controller$ TriggeredPlayer | Valid$ Card.IsRemembered | WithoutManaCost$ True | SubAbility$ DBOmenCleanup +SVar:DBOmenPlay:DB$ Play | ValidZone$ Exile | Controller$ TriggeredPlayer | Valid$ Card.IsRemembered | ValidSA$ Spell | WithoutManaCost$ True | SubAbility$ DBOmenCleanup SVar:DBOmenCleanup:DB$ Cleanup | ClearRemembered$ True AI:RemoveDeck:Random Oracle:Players can't draw cards.\nAt the beginning of each player's draw step, that player exiles the top card of their library. If it's a land card, the player puts it onto the battlefield. Otherwise, the player casts it without paying its mana cost if able. diff --git a/forge-gui/res/cardsfolder/p/pure_intentions.txt b/forge-gui/res/cardsfolder/p/pure_intentions.txt index 12731beaa57..2033b1c98aa 100644 --- a/forge-gui/res/cardsfolder/p/pure_intentions.txt +++ b/forge-gui/res/cardsfolder/p/pure_intentions.txt @@ -4,7 +4,8 @@ Types:Instant Arcane A:SP$ Effect | Cost$ W | Triggers$ PureDiscarded | SpellDescription$ Whenever a spell or ability an opponent controls causes you to discard cards this turn, return those cards from your graveyard to your hand. SVar:PureDiscarded:Mode$ Discarded | ValidCard$ Card.YouCtrl | ValidCause$ Card.OppCtrl | TriggerZones$ Command | Execute$ TrigPureChange | TriggerDescription$ Whenever a spell or ability an opponent controls causes you to discard cards this turn, return those cards from your graveyard to your hand. SVar:TrigPureChange:DB$ ChangeZone | Defined$ TriggeredCardLKICopy | Origin$ Graveyard | Destination$ Hand -T:Mode$ Discarded | ValidCard$ Card.Self | ValidCause$ Card.OppCtrl | Execute$ TrigPureReturn | TriggerDescription$ When a spell or ability an opponent controls causes you to discard CARDNAME, return it to your hand. +T:Mode$ Discarded | ValidCard$ Card.Self | ValidCause$ Card.OppCtrl | Execute$ TrigDelay | TriggerDescription$ When a spell or ability an opponent controls causes you to discard CARDNAME, return CARDNAME from your graveyard to your hand at the beginning of the next end step. +SVar:TrigDelay:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigPureReturn | SpellDescription$ Return CARDNAME from your graveyard to your hand at the beginning of the next end step. SVar:TrigPureReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Hand SVar:DiscardMeByOpp:1 AI:RemoveDeck:All diff --git a/forge-gui/res/cardsfolder/s/sand_golem.txt b/forge-gui/res/cardsfolder/s/sand_golem.txt index 41905d9cee1..afe52163795 100644 --- a/forge-gui/res/cardsfolder/s/sand_golem.txt +++ b/forge-gui/res/cardsfolder/s/sand_golem.txt @@ -3,8 +3,7 @@ ManaCost:5 Types:Artifact Creature Golem PT:3/3 T:Mode$ Discarded | ValidCard$ Card.Self | ValidCause$ Card.OppCtrl | Execute$ DelTrig | TriggerDescription$ When a spell or ability an opponent controls causes you to discard CARDNAME, return CARDNAME from your graveyard to the battlefield with a +1/+1 counter on it at the beginning of the next end step. -SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigReturn | TriggerDescription$ return CARDNAME from your graveyard to the battlefield with a +1/+1 counter on it at the beginning of the next end step. -SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield | SubAbility$ AddCounter -SVar:AddCounter:DB$ PutCounter | Defined$ Self | CounterType$ P1P1 | CounterNum$ 1 +SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigReturn | TriggerDescription$ Return CARDNAME from your graveyard to the battlefield with a +1/+1 counter on it at the beginning of the next end step. +SVar:TrigReturn:DB$ ChangeZone | Defined$ Self | Origin$ Graveyard | Destination$ Battlefield | WithCountersType$ P1P1 SVar:DiscardMeByOpp:3 Oracle:When a spell or ability an opponent controls causes you to discard Sand Golem, return Sand Golem from your graveyard to the battlefield with a +1/+1 counter on it at the beginning of the next end step. diff --git a/forge-gui/res/cardsfolder/s/solitude.txt b/forge-gui/res/cardsfolder/s/solitude.txt index 3a44daf7058..a6250d46f39 100644 --- a/forge-gui/res/cardsfolder/s/solitude.txt +++ b/forge-gui/res/cardsfolder/s/solitude.txt @@ -7,7 +7,8 @@ K:Lifelink K:Evoke:ExileFromHand<1/Card.White+Other/white card> T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigExile | TriggerDescription$ When CARDNAME enters the battlefield, exile up to one other target creature. That creature's controller gains life equal to its power. SVar:TrigExile:DB$ ChangeZone | Origin$ Battlefield | Destination$ Exile | ValidTgts$ Creature.Other | RememberLKI$ True | TgtPrompt$ Select up to one other target creature | TargetMin$ 0 | TargetMax$ 1 | SubAbility$ DBGainLife -SVar:DBGainLife:DB$ GainLife | Defined$ RememberedController | LifeAmount$ X +SVar:DBGainLife:DB$ GainLife | Defined$ RememberedController | LifeAmount$ X | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:RememberedLKI$CardPower DeckHas:Ability$LifeGain Oracle:Flash\nLifelink\nWhen Solitude enters the battlefield, exile up to one other target creature. That creature's controller gains life equal to its power.\nEvoke—Exile a white card from your hand. diff --git a/forge-gui/res/cardsfolder/w/wicked_guardian.txt b/forge-gui/res/cardsfolder/w/wicked_guardian.txt index b6323ddab43..eb81b02d470 100644 --- a/forge-gui/res/cardsfolder/w/wicked_guardian.txt +++ b/forge-gui/res/cardsfolder/w/wicked_guardian.txt @@ -4,8 +4,7 @@ Types:Creature Human Noble PT:4/2 T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigChoose | TriggerDescription$ When CARDNAME enters the battlefield, you may have it deal 2 damage to another creature you control. If you do, draw a card. SVar:TrigChoose:DB$ ChooseCard | Choices$ Creature.YouCtrl+Other | ChoiceZone$ Battlefield | ChoiceTitle$ Select another creature you control | SubAbility$ DBDamageChosen -SVar:DBDamageChosen:DB$ DealDamage | Defined$ ChosenCard | NumDmg$ 2 | SubAbility$ DBImmediateTrigger -SVar:DBImmediateTrigger:DB$ ImmediateTrigger | RememberObjects$ ChosenCard | ConditionDefined$ ChosenCard | ConditionPresent$ Creature.YouCtrl+Other | ConditionCompare$ GE1 | Execute$ DBDraw | TriggerDescription$ If you do, draw a card. -SVar:DBDraw:DB$ Draw | NumCards$ 1 | SubAbility$ DBCleanup +SVar:DBDamageChosen:DB$ DealDamage | Defined$ ChosenCard | NumDmg$ 2 | SubAbility$ DBDraw +SVar:DBDraw:DB$ Draw | NumCards$ 1 | ConditionDefined$ ChosenCard | ConditionPresent$ Card | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearChosenCard$ True Oracle:When Wicked Guardian enters the battlefield, you may have it deal 2 damage to another creature you control. If you do, draw a card. From c7feb44214d73b73dbfb8a1a29e3de91ecbc4fe3 Mon Sep 17 00:00:00 2001 From: TRT <> Date: Wed, 4 Jan 2023 16:13:26 +0100 Subject: [PATCH 53/76] Fix "can't happen" applied with other replacements --- .../src/main/java/forge/game/replacement/ReplacementLayer.java | 1 + forge-gui/res/cardsfolder/g/grafdiggers_cage.txt | 2 +- forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt | 2 +- forge-gui/res/cardsfolder/w/weathered_runestone.txt | 2 +- forge-gui/res/cardsfolder/w/worms_of_the_earth.txt | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementLayer.java b/forge-game/src/main/java/forge/game/replacement/ReplacementLayer.java index ca71573f4b8..38ab284bcd9 100644 --- a/forge-game/src/main/java/forge/game/replacement/ReplacementLayer.java +++ b/forge-game/src/main/java/forge/game/replacement/ReplacementLayer.java @@ -6,6 +6,7 @@ package forge.game.replacement; * */ public enum ReplacementLayer { + CantHappen, // 614.17 Control, // 616.1b Copy, // 616.1c Transform, // 616.1d diff --git a/forge-gui/res/cardsfolder/g/grafdiggers_cage.txt b/forge-gui/res/cardsfolder/g/grafdiggers_cage.txt index f77d045954e..72f0491a6f8 100644 --- a/forge-gui/res/cardsfolder/g/grafdiggers_cage.txt +++ b/forge-gui/res/cardsfolder/g/grafdiggers_cage.txt @@ -1,7 +1,7 @@ Name:Grafdigger's Cage ManaCost:1 Types:Artifact -R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Description$ Creature cards in graveyards and libraries can't enter the battlefield. +R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Layer$ CantHappen | Description$ Creature cards in graveyards and libraries can't enter the battlefield. S:Mode$ CantBeCast | Origin$ Graveyard,Library | Description$ Players can't cast spells from graveyards or libraries. SVar:NonStackingEffect:True AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt b/forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt index d13c61e88b4..4924dd4516d 100644 --- a/forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt +++ b/forge-gui/res/cardsfolder/k/kunoros_hound_of_athreos.txt @@ -5,7 +5,7 @@ PT:3/3 K:Vigilance K:Menace K:Lifelink -R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Description$ Creature cards in graveyards can't enter the battlefield. +R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Layer$ CantHappen | Description$ Creature cards in graveyards can't enter the battlefield. S:Mode$ CantBeCast | Origin$ Graveyard | Description$ Players can't cast spells from graveyards. SVar:NonStackingEffect:True Oracle:Vigilance, menace, lifelink\nCreature cards in graveyards can't enter the battlefield.\nPlayers can't cast spells from graveyards. diff --git a/forge-gui/res/cardsfolder/w/weathered_runestone.txt b/forge-gui/res/cardsfolder/w/weathered_runestone.txt index 824a1b4f92e..68f39c73052 100644 --- a/forge-gui/res/cardsfolder/w/weathered_runestone.txt +++ b/forge-gui/res/cardsfolder/w/weathered_runestone.txt @@ -1,7 +1,7 @@ Name:Weathered Runestone ManaCost:2 Types:Artifact -R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Permanent.nonland | Prevent$ True | Description$ Nonland permanent cards in graveyards and libraries can't enter the battlefield. +R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Permanent.nonland | Prevent$ True | Layer$ CantHappen | Description$ Nonland permanent cards in graveyards and libraries can't enter the battlefield. S:Mode$ CantBeCast | Origin$ Graveyard,Library | Description$ Players can't cast spells from graveyards or libraries. SVar:NonStackingEffect:True AI:RemoveDeck:Random diff --git a/forge-gui/res/cardsfolder/w/worms_of_the_earth.txt b/forge-gui/res/cardsfolder/w/worms_of_the_earth.txt index b758fbfa3d0..b1432871eaa 100644 --- a/forge-gui/res/cardsfolder/w/worms_of_the_earth.txt +++ b/forge-gui/res/cardsfolder/w/worms_of_the_earth.txt @@ -2,7 +2,7 @@ Name:Worms of the Earth ManaCost:2 B B B Types:Enchantment S:Mode$ CantPlayLand | Description$ Players can't play lands. -R:Event$ Moved | ActiveZones$ Battlefield | Destination$ Battlefield | ValidCard$ Land | Prevent$ True | Description$ Lands can't enter the battlefield. +R:Event$ Moved | ActiveZones$ Battlefield | Destination$ Battlefield | ValidCard$ Land | Prevent$ True | Layer$ CantHappen | Description$ Lands can't enter the battlefield. T:Mode$ Phase | Phase$ Upkeep | TriggerZones$ Battlefield | Execute$ RepeatAbility | TriggerDescription$ At the beginning of each upkeep, any player may sacrifice two lands or have CARDNAME deal 5 damage to that player. If a player does either, destroy CARDNAME. SVar:RepeatAbility:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBChoose SVar:DBChoose:DB$ GenericChoice | Defined$ Player.IsRemembered | Choices$ SacTwoLands,DealDmg | AILogic$ PayUnlessCost From 0250c9ca0b1178783375d4666cd3f4684802243b Mon Sep 17 00:00:00 2001 From: TRT <> Date: Wed, 4 Jan 2023 16:14:34 +0100 Subject: [PATCH 54/76] Fix missing LKI for Startled Awake --- .../ability/effects/ChangeZoneEffect.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 9a8ca6c6a6b..2deac9438ad 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -575,6 +575,17 @@ public class ChangeZoneEffect extends SpellAbilityEffect { movedCard = game.getAction().moveToLibrary(gameCard, libraryPosition, sa); } else { if (destination.equals(ZoneType.Battlefield)) { + Map moveParams = AbilityKey.newMap(); + moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield); + moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard); + if (sa.isReplacementAbility()) { + ReplacementEffect re = sa.getReplacementEffect(); + moveParams.put(AbilityKey.ReplacementEffect, re); + if (ReplacementType.Moved.equals(re.getMode()) && sa.getReplacingObject(AbilityKey.CardLKI) != null) { + moveParams.put(AbilityKey.CardLKI, sa.getReplacingObject(AbilityKey.CardLKI)); + } + } + if (sa.hasParam("Tapped") || sa.isNinjutsu()) { gameCard.setTapped(true); } @@ -583,6 +594,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } if (sa.hasParam("Transformed")) { if (gameCard.isDoubleFaced()) { + // need LKI before Animate does apply + if (!moveParams.containsKey(AbilityKey.CardLKI)) { + moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(gameCard)); + } gameCard.changeCardState("Transform", null, sa); } else { // If it can't Transform, don't change zones. @@ -650,17 +665,6 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } } - Map moveParams = AbilityKey.newMap(); - moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield); - moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard); - if (sa.isReplacementAbility()) { - ReplacementEffect re = sa.getReplacementEffect(); - moveParams.put(AbilityKey.ReplacementEffect, re); - if (ReplacementType.Moved.equals(re.getMode()) && sa.getReplacingObject(AbilityKey.CardLKI) != null) { - moveParams.put(AbilityKey.CardLKI, sa.getReplacingObject(AbilityKey.CardLKI)); - } - } - if (sa.hasAdditionalAbility("AnimateSubAbility")) { // need LKI before Animate does apply if (!moveParams.containsKey(AbilityKey.CardLKI)) { From 414fb3fcef252704f245d1c2782b70b91400d0db Mon Sep 17 00:00:00 2001 From: TRT <> Date: Wed, 4 Jan 2023 16:15:16 +0100 Subject: [PATCH 55/76] Improve cleanup when card can't ETB --- .../src/main/java/forge/game/GameAction.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 91c4f25e68c..b671c2a823d 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -365,7 +365,22 @@ public class GameAction { copied.getOwner().removeInboundToken(copied); if (repres == ReplacementResult.Prevented) { - if (game.getStack().isResolving(c) && !zoneTo.is(ZoneType.Graveyard)) { + c.clearEtbCounters(); + if (cause != null) { + if (cause.hasParam("AnimateSubAbility")) { + c.removeChangedCardKeywords(game.getTimestamp(), 0); + c.removeChangedCardTraits(game.getTimestamp(), 0); + c.removeChangedCardTypes(game.getTimestamp(), 0); + c.removeChangedName(game.getTimestamp(), 0); + } + if (cause.hasParam("Transformed") || cause.hasParam("FaceDown")) { + c.setBackSide(false); + c.changeToState(CardStateName.Original); + } + unattachCardLeavingBattlefield(c); + } + + if (c.isInZone(ZoneType.Stack) && !zoneTo.is(ZoneType.Graveyard)) { return moveToGraveyard(c, cause, params); } @@ -373,10 +388,8 @@ public class GameAction { copied.clearDelved(); copied.clearConvoked(); copied.clearExploited(); - } - - // was replaced with another Zone Change - if (toBattlefield && !c.isInPlay()) { + } else if (toBattlefield && !c.isInPlay()) { + // was replaced with another Zone Change if (c.removeChangedState()) { c.updateStateForView(); } From 96037f9388242200d42767c1e708591215e7f648 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Wed, 4 Jan 2023 20:29:55 +0100 Subject: [PATCH 56/76] Fix cards --- forge-gui/res/cardsfolder/g/greasefang_okiba_boss.txt | 5 +++-- forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/cardsfolder/g/greasefang_okiba_boss.txt b/forge-gui/res/cardsfolder/g/greasefang_okiba_boss.txt index a5204791aeb..2a083be643c 100644 --- a/forge-gui/res/cardsfolder/g/greasefang_okiba_boss.txt +++ b/forge-gui/res/cardsfolder/g/greasefang_okiba_boss.txt @@ -3,8 +3,9 @@ ManaCost:1 W B Types:Legendary Creature Rat Pilot PT:4/3 T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigReturn | TriggerDescription$ At the beginning of combat on your turn, return target Vehicle card from your graveyard to the battlefield. It gains haste. Return it to its owner's hand at the beginning of your next end step. -SVar:TrigReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Select target Vehicle card in your graveyard | ValidTgts$ Vehicle.YouOwn | AnimateSubAbility$ Animate -SVar:Animate:DB$ Animate | Keywords$ Haste | Defined$ Remembered | Duration$ Permanent | AtEOT$ Hand +SVar:TrigReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Select target Vehicle card in your graveyard | ValidTgts$ Vehicle.YouOwn | SubAbility$ Animate | RememberChanged$ True | AtEOT$ Hand +SVar:Animate:DB$ Animate | Keywords$ Haste | Defined$ Remembered | Duration$ Permanent | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True DeckHas:Ability$Graveyard DeckNeeds:Type$Vehicle Oracle:At the beginning of combat on your turn, return target Vehicle card from your graveyard to the battlefield. It gains haste. Return it to its owner's hand at the beginning of your next end step. diff --git a/forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt b/forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt index d3d21e4db38..6badaf7a978 100644 --- a/forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt +++ b/forge-gui/res/cardsfolder/o/olivia_crimson_bride.txt @@ -5,8 +5,9 @@ PT:3/4 K:Flying K:Haste T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigChange | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, return target creature card from your graveyard to the battlefield tapped and attacking. It gains "When you don't control a legendary Vampire, exile this creature." -SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouOwn | Tapped$ True | Attacking$ True | AnimateSubAbility$ DBAnimate -SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Duration$ Permanent | Triggers$ TrigOlivia +SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouOwn | Tapped$ True | Attacking$ True | RememberChanged$ True | SubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Duration$ Permanent | Triggers$ TrigOlivia | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:TrigOlivia:Mode$ Always | TriggerZones$ Battlefield | IsPresent$ Vampire.YouCtrl+Legendary | PresentCompare$ EQ0 | Execute$ TrigExile | TriggerDescription$ When you don't control a legendary Vampire, exile this creature. SVar:TrigExile:DB$ ChangeZone | Defined$ Self | Origin$ Battlefield | Destination$ Exile SVar:HasAttackEffect:TRUE From 58a917e667cca2fc05a1fd5067011e04cde4866e Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Wed, 4 Jan 2023 20:33:35 +0100 Subject: [PATCH 57/76] Store timestamp to be safe --- .../src/main/java/forge/game/GameAction.java | 12 +++++--- .../ability/effects/AnimateAllEffect.java | 28 +++++++++---------- .../ability/effects/ChangeZoneAllEffect.java | 4 ++- .../ability/effects/ChangeZoneEffect.java | 12 ++++++-- .../forge/game/ability/effects/DigEffect.java | 8 +++--- 5 files changed, 39 insertions(+), 25 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index b671c2a823d..678fa0f9f20 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -368,10 +368,14 @@ public class GameAction { c.clearEtbCounters(); if (cause != null) { if (cause.hasParam("AnimateSubAbility")) { - c.removeChangedCardKeywords(game.getTimestamp(), 0); - c.removeChangedCardTraits(game.getTimestamp(), 0); - c.removeChangedCardTypes(game.getTimestamp(), 0); - c.removeChangedName(game.getTimestamp(), 0); + long unanimateTimestamp = Long.valueOf(cause.getAdditionalAbility("AnimateSubAbility").getSVar("unanimateTimestamp")); + c.removeChangedCardKeywords(unanimateTimestamp, 0); + c.removeChangedCardTypes(unanimateTimestamp, 0); + c.removeChangedName(unanimateTimestamp, 0); + c.removeNewPT(unanimateTimestamp, 0); + if (c.removeChangedCardTraits(unanimateTimestamp, 0)) { + c.updateStateForView(); + } } if (cause.hasParam("Transformed") || cause.hasParam("FaceDown")) { c.setBackSide(false); diff --git a/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java index 0276fb32bc1..357331bd1d0 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/AnimateAllEffect.java @@ -136,10 +136,10 @@ public class AnimateAllEffect extends AnimateEffectBase { CardCollectionView list; - if (!sa.usesTargeting() && !sa.hasParam("Defined")) { - list = game.getCardsIn(ZoneType.Battlefield); - } else { + if (sa.usesTargeting() || sa.hasParam("Defined")) { list = getTargetPlayers(sa).getCardsIn(ZoneType.Battlefield); + } else { + list = game.getCardsIn(ZoneType.Battlefield); } list = CardLists.getValidCards(list, valid, sa.getActivatingPlayer(), host, sa); @@ -155,18 +155,18 @@ public class AnimateAllEffect extends AnimateEffectBase { game.fireEvent(new GameEventCardStatsChanged(c)); - final GameCommand unanimate = new GameCommand() { - private static final long serialVersionUID = -5861759814760561373L; - - @Override - public void run() { - doUnanimate(c, timestamp); - - game.fireEvent(new GameEventCardStatsChanged(c)); - } - }; - if (!permanent) { + final GameCommand unanimate = new GameCommand() { + private static final long serialVersionUID = -5861759814760561373L; + + @Override + public void run() { + doUnanimate(c, timestamp); + + game.fireEvent(new GameEventCardStatsChanged(c)); + } + }; + addUntilCommand(sa, unanimate); } } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java index 528ac2bbbc2..4436dfafd7f 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java @@ -171,9 +171,11 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { // need LKI before Animate does apply moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); + final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility"); source.addRemembered(c); - AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); + AbilityUtils.resolve(animate); source.removeRemembered(c); + animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp())); } if (sa.hasParam("Tapped")) { c.setTapped(true); diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 2deac9438ad..e03ef947792 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -671,9 +671,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect { moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(gameCard)); } + final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility"); hostCard.addRemembered(gameCard); - AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); + AbilityUtils.resolve(animate); hostCard.removeRemembered(gameCard); + animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp())); } // need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger @@ -1314,9 +1316,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect { // need LKI before Animate does apply moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); + final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility"); source.addRemembered(c); - AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); + AbilityUtils.resolve(animate); source.removeRemembered(c); + animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp())); } if (sa.hasParam("GainControl")) { final String g = sa.getParam("GainControl"); @@ -1335,6 +1339,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } if (sa.hasParam("Transformed")) { if (c.isDoubleFaced()) { + // need LKI before Animate does apply + if (!moveParams.containsKey(AbilityKey.CardLKI)) { + moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); + } c.changeCardState("Transform", null, sa); } else { // If it can't Transform, don't change zones. diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index 415ea98a006..e6a92d4cb74 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -417,13 +417,13 @@ public class DigEffect extends SpellAbilityEffect { } if (sa.hasAdditionalAbility("AnimateSubAbility")) { // need LKI before Animate does apply - if (!moveParams.containsKey(AbilityKey.CardLKI)) { - moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); - } + moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c)); + final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility"); host.addRemembered(c); - AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility")); + AbilityUtils.resolve(animate); host.removeRemembered(c); + animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp())); } c = game.getAction().moveTo(zone, c, sa, moveParams); if (destZone1.equals(ZoneType.Battlefield)) { From a51582237a187dc1ba92e6cb19ef296b5d96a064 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Wed, 4 Jan 2023 20:49:06 +0100 Subject: [PATCH 58/76] Stonehewer Giant fix when it can't ETB --- .../main/java/forge/game/ability/effects/ChangeZoneEffect.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index e03ef947792..6679367396b 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -1402,7 +1402,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { movedCard.setTimestamp(ts); - if (sa.hasParam("AttachAfter") && movedCard.isAttachment()) { + if (sa.hasParam("AttachAfter") && movedCard.isAttachment() && movedCard.isInPlay()) { CardCollection list = AbilityUtils.getDefinedCards(source, sa.getParam("AttachAfter"), sa); if (list.isEmpty()) { list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AttachAfter"), c.getController(), c, sa); From e8704e3c5e3b049ab252363622d034e83201f378 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Wed, 4 Jan 2023 22:16:02 +0100 Subject: [PATCH 59/76] Fix auras losing all previous traits when they can't ETB --- .../src/main/java/forge/game/GameAction.java | 28 +++++++++++-------- .../cardsfolder/s/swords_to_plowshares.txt | 3 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 678fa0f9f20..098e5ced755 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -164,8 +164,8 @@ public class GameAction { } if (!found) { c.clearControllers(); - if (c.removeChangedState()) { - c.updateStateForView(); + if (cause != null) { + unanimateOnAbortedChange(cause, c); } return c; } @@ -367,16 +367,7 @@ public class GameAction { if (repres == ReplacementResult.Prevented) { c.clearEtbCounters(); if (cause != null) { - if (cause.hasParam("AnimateSubAbility")) { - long unanimateTimestamp = Long.valueOf(cause.getAdditionalAbility("AnimateSubAbility").getSVar("unanimateTimestamp")); - c.removeChangedCardKeywords(unanimateTimestamp, 0); - c.removeChangedCardTypes(unanimateTimestamp, 0); - c.removeChangedName(unanimateTimestamp, 0); - c.removeNewPT(unanimateTimestamp, 0); - if (c.removeChangedCardTraits(unanimateTimestamp, 0)) { - c.updateStateForView(); - } - } + unanimateOnAbortedChange(cause, c); if (cause.hasParam("Transformed") || cause.hasParam("FaceDown")) { c.setBackSide(false); c.changeToState(CardStateName.Original); @@ -2577,4 +2568,17 @@ public class GameAction { } return false; } + + private static void unanimateOnAbortedChange(final SpellAbility cause, final Card c) { + if (cause.hasParam("AnimateSubAbility")) { + long unanimateTimestamp = Long.valueOf(cause.getAdditionalAbility("AnimateSubAbility").getSVar("unanimateTimestamp")); + c.removeChangedCardKeywords(unanimateTimestamp, 0); + c.removeChangedCardTypes(unanimateTimestamp, 0); + c.removeChangedName(unanimateTimestamp, 0); + c.removeNewPT(unanimateTimestamp, 0); + if (c.removeChangedCardTraits(unanimateTimestamp, 0)) { + c.updateStateForView(); + } + } + } } diff --git a/forge-gui/res/cardsfolder/s/swords_to_plowshares.txt b/forge-gui/res/cardsfolder/s/swords_to_plowshares.txt index e75a18cde30..7ffd4a071eb 100644 --- a/forge-gui/res/cardsfolder/s/swords_to_plowshares.txt +++ b/forge-gui/res/cardsfolder/s/swords_to_plowshares.txt @@ -2,6 +2,7 @@ Name:Swords to Plowshares ManaCost:W Types:Instant A:SP$ ChangeZone | ValidTgts$ Creature | Origin$ Battlefield | Destination$ Exile | RememberLKI$ True | SubAbility$ DBGainLife | SpellDescription$ Exile target creature. -SVar:DBGainLife:DB$ GainLife | Defined$ RememberedController | LifeAmount$ X | SpellDescription$ Its controller gains life equal to its power. +SVar:DBGainLife:DB$ GainLife | Defined$ RememberedController | LifeAmount$ X | SubAbility$ DBCleanup | SpellDescription$ Its controller gains life equal to its power. +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:RememberedLKI$CardPower Oracle:Exile target creature. Its controller gains life equal to its power. From 8986a025e7f3fba43908d3c6d0b15c4f6c66ea82 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Wed, 4 Jan 2023 22:24:33 +0100 Subject: [PATCH 60/76] Also remove controller --- forge-game/src/main/java/forge/game/GameAction.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 098e5ced755..df67f6bf4c6 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -366,6 +366,7 @@ public class GameAction { if (repres == ReplacementResult.Prevented) { c.clearEtbCounters(); + c.clearControllers(); if (cause != null) { unanimateOnAbortedChange(cause, c); if (cause.hasParam("Transformed") || cause.hasParam("FaceDown")) { From 838fa0cc1f2f82436ad144277b7ae7cc3866dbc5 Mon Sep 17 00:00:00 2001 From: invalidCards <842080+invalidCards@users.noreply.github.com> Date: Wed, 4 Jan 2023 22:34:45 +0100 Subject: [PATCH 61/76] Add SCD precons --- .../Chaos Incarnate [SCD] [2022].dck | 78 +++++++++++++++++++ .../Draconic Destruction [SCD] [2022].dck | 77 ++++++++++++++++++ .../First Flight [SCD] [2022].dck | 77 ++++++++++++++++++ .../Grave Danger [SCD] [2022].dck | 76 ++++++++++++++++++ .../Token Triumph [SCD] [2022].dck | 78 +++++++++++++++++++ 5 files changed, 386 insertions(+) create mode 100644 forge-gui/res/quest/commanderprecons/Chaos Incarnate [SCD] [2022].dck create mode 100644 forge-gui/res/quest/commanderprecons/Draconic Destruction [SCD] [2022].dck create mode 100644 forge-gui/res/quest/commanderprecons/First Flight [SCD] [2022].dck create mode 100644 forge-gui/res/quest/commanderprecons/Grave Danger [SCD] [2022].dck create mode 100644 forge-gui/res/quest/commanderprecons/Token Triumph [SCD] [2022].dck diff --git a/forge-gui/res/quest/commanderprecons/Chaos Incarnate [SCD] [2022].dck b/forge-gui/res/quest/commanderprecons/Chaos Incarnate [SCD] [2022].dck new file mode 100644 index 00000000000..002e211ad06 --- /dev/null +++ b/forge-gui/res/quest/commanderprecons/Chaos Incarnate [SCD] [2022].dck @@ -0,0 +1,78 @@ +[metadata] +name=Chaos Incarnate [SCD] [2022] +[Commander] +1 Kardur, Doomscourge|SCD|1 +[Main] +1 Abrade|SCD|1 +1 Akoum Refuge|SCD|1 +1 Ambition's Cost|SCD|1 +1 Arcane Signet|SCD|1 +1 Archfiend of Depravity|SCD|1 +1 Blasphemous Act|SCD|1 +1 Bloodfell Caves|SCD|1 +1 Bloodgift Demon|SCD|1 +1 Brash Taunter|SCD|1 +1 Breath of Malfegor|SCD|1 +1 Burnished Hart|SCD|1 +1 Chaos Warp|SCD|1 +1 Cinder Barrens|SCD|1 +1 Combustible Gearhulk|SCD|1 +1 Command Tower|SCD|1 +1 Commander's Sphere|SCD|1 +1 Coveted Jewel|SCD|1 +1 Deadly Tempest|SCD|1 +1 Dictate of the Twin Gods|SCD|1 +1 Dredge the Mire|SCD|1 +1 Explosion of Riches|SCD|1 +1 Feed the Swarm|SCD|1 +1 Fiery Confluence|SCD|1 +1 Foreboding Ruins|SCD|1 +1 Geode Rager|SCD|1 +1 Guttersnipe|SCD|1 +1 Hate Mirage|SCD|1 +1 Indulgent Tormentor|SCD|1 +1 Kaervek the Merciless|SCD|1 +1 Kazuul, Tyrant of the Cliffs|SCD|1 +1 Lightning Greaves|SCD|1 +1 Magmatic Force|SCD|1 +1 Mana Geyser|SCD|1 +1 Molten Slagheap|SCD|1 +14 Mountain|SCD|1 +1 Myriad Landscape|SCD|1 +1 Nihil Spellbomb|SCD|1 +1 Ob Nixilis Reignited|SCD|1 +1 Profane Command|SCD|1 +1 Rakdos Charm|SCD|1 +1 Rakdos Signet|SCD|1 +1 Rakshasa Debaser|SCD|1 +1 Read the Bones|SCD|1 +1 Reign of the Pit|SCD|1 +1 Sangromancer|SCD|1 +1 Scythe Specter|SCD|1 +1 Sepulchral Primordial|SCD|1 +1 Sign in Blood|SCD|1 +1 Smoldering Marsh|SCD|1 +1 Sol Ring|SCD|1 +1 Solemn Simulacrum|SCD|1 +1 Soul Shatter|SCD|1 +1 Spiteful Visions|SCD|1 +1 Stensia Bloodhall|SCD|1 +1 Stormfist Crusader|SCD|1 +1 Sunbird's Invocation|SCD|1 +15 Swamp|SCD|1 +1 Syphon Mind|SCD|1 +1 Talisman of Indulgence|SCD|1 +1 Tectonic Giant|SCD|1 +1 Temple of Malice|SCD|1 +1 Terminate|SCD|1 +1 Theater of Horrors|SCD|1 +1 Thermo-Alchemist|SCD|1 +1 Titan Hunter|SCD|1 +1 Unlicensed Disintegration|SCD|1 +1 Urborg Volcano|SCD|1 +1 Vampire Nighthawk|SCD|1 +1 Wayfarer's Bauble|SCD|1 +1 Wild Ricochet|SCD|1 +1 Wildfire Devils|SCD|1 +1 Worn Powerstone|SCD|1 +[Sideboard] diff --git a/forge-gui/res/quest/commanderprecons/Draconic Destruction [SCD] [2022].dck b/forge-gui/res/quest/commanderprecons/Draconic Destruction [SCD] [2022].dck new file mode 100644 index 00000000000..b74f90379c1 --- /dev/null +++ b/forge-gui/res/quest/commanderprecons/Draconic Destruction [SCD] [2022].dck @@ -0,0 +1,77 @@ +[metadata] +name=Draconic Destruction [SCD] [2022] +[Commander] +1 Atarka, World Render|SCD|1 +[Main] +1 Akoum Hellkite|SCD|1 +1 Arcane Signet|SCD|1 +1 Atarka Monument|SCD|1 +1 Beast Within|SCD|1 +1 Blossoming Defense|SCD|1 +1 Chain Reaction|SCD|1 +1 Cinder Glade|SCD|1 +1 Clan Defiance|SCD|1 +1 Command Tower|SCD|1 +1 Commander's Sphere|SCD|1 +1 Crucible of Fire|SCD|1 +1 Cultivate|SCD|1 +1 Demanding Dragon|SCD|1 +1 Draconic Disciple|SCD|1 +1 Dragon Mage|SCD|1 +1 Dragon Tempest|SCD|1 +1 Dragon's Hoard|SCD|1 +1 Dragonkin Berserker|SCD|1 +1 Dragonlord's Servant|SCD|1 +1 Dragonmaster Outcast|SCD|1 +1 Dragonspeaker Shaman|SCD|1 +1 Drakuseth, Maw of Flames|SCD|1 +1 Dream Pillager|SCD|1 +1 Drumhunter|SCD|1 +1 Elemental Bond|SCD|1 +1 Fires of Yavimaya|SCD|1 +1 Flameblast Dragon|SCD|1 +1 Foe-Razer Regent|SCD|1 +12 Forest|SCD|1 +1 Frontier Siege|SCD|1 +1 Furnace Whelp|SCD|1 +1 Game Trail|SCD|1 +1 Garruk's Uprising|SCD|1 +1 Harbinger of the Hunt|SCD|1 +1 Harmonize|SCD|1 +1 Haven of the Spirit Dragon|SCD|1 +1 Hoard-Smelter Dragon|SCD|1 +1 Hunter's Insight|SCD|1 +1 Hunter's Prowess|SCD|1 +1 Kazandu Refuge|SCD|1 +1 Loaming Shaman|SCD|1 +1 Magmaquake|SCD|1 +1 Mordant Dragon|SCD|1 +18 Mountain|SCD|1 +1 Path of Ancestry|SCD|1 +1 Primal Might|SCD|1 +1 Provoke the Trolls|SCD|1 +1 Rapacious Dragon|SCD|1 +1 Return to Nature|SCD|1 +1 Rugged Highlands|SCD|1 +1 Runehorn Hellkite|SCD|1 +1 Sakura-Tribe Elder|SCD|1 +1 Sarkhan, the Dragonspeaker|SCD|1 +1 Savage Ventmaw|SCD|1 +1 Scourge of Valkas|SCD|1 +1 Shamanic Revelation|SCD|1 +1 Shivan Oasis|SCD|1 +1 Sol Ring|SCD|1 +1 Spit Flame|SCD|1 +1 Steel Hellkite|SCD|1 +1 Sweltering Suns|SCD|1 +1 Swiftfoot Boots|SCD|1 +1 Talisman of Impulse|SCD|1 +1 Temple of Abandon|SCD|1 +1 Thunderbreak Regent|SCD|1 +1 Thundermaw Hellkite|SCD|1 +1 Timber Gorge|SCD|1 +1 Tyrant's Familiar|SCD|1 +1 Unleash Fury|SCD|1 +1 Vandalblast|SCD|1 +1 Verix Bladewing|SCD|1 +[Sideboard] diff --git a/forge-gui/res/quest/commanderprecons/First Flight [SCD] [2022].dck b/forge-gui/res/quest/commanderprecons/First Flight [SCD] [2022].dck new file mode 100644 index 00000000000..04aae9fa3b6 --- /dev/null +++ b/forge-gui/res/quest/commanderprecons/First Flight [SCD] [2022].dck @@ -0,0 +1,77 @@ +[metadata] +name=First Flight [SCD] [2022] +[Commander] +1 Isperia, Supreme Judge|SCD|1 +[Main] +1 Absorb|SCD|1 +1 Aetherize|SCD|1 +1 Angler Turtle|SCD|1 +1 Arcane Signet|SCD|1 +1 Archon of Redemption|SCD|1 +1 Aven Gagglemaster|SCD|1 +1 Azorius Signet|SCD|1 +1 Banishing Light|SCD|1 +1 Bident of Thassa|SCD|1 +1 Cartographer's Hawk|SCD|1 +1 Cleansing Nova|SCD|1 +1 Cloudblazer|SCD|1 +1 Coastal Tower|SCD|1 +1 Command Tower|SCD|1 +1 Commander's Sphere|SCD|1 +1 Condemn|SCD|1 +1 Counterspell|SCD|1 +1 Crush Contraband|SCD|1 +1 Diluvian Primordial|SCD|1 +1 Disenchant|SCD|1 +1 Emeria Angel|SCD|1 +1 Empyrean Eagle|SCD|1 +1 Ever-Watching Threshold|SCD|1 +1 Faerie Formation|SCD|1 +1 Favorable Winds|SCD|1 +1 Generous Gift|SCD|1 +1 Gideon Jura|SCD|1 +1 Gravitational Shift|SCD|1 +1 Hanged Executioner|SCD|1 +1 Hedron Archive|SCD|1 +1 Inspired Sphinx|SCD|1 +15 Island|SCD|1 +1 Jubilant Skybonder|SCD|1 +1 Kangee's Lieutenant|SCD|1 +1 Kangee, Sky Warden|SCD|1 +1 Meandering River|SCD|1 +1 Migratory Route|SCD|1 +1 Moorland Haunt|SCD|1 +1 Negate|SCD|1 +1 Pilgrim's Eye|SCD|1 +15 Plains|SCD|1 +1 Port Town|SCD|1 +1 Prairie Stream|SCD|1 +1 Rally of Wings|SCD|1 +1 Remorseful Cleric|SCD|1 +1 Sejiri Refuge|SCD|1 +1 Sephara, Sky's Blade|SCD|1 +1 Sharding Sphinx|SCD|1 +1 Sky Diamond|SCD|1 +1 Skycat Sovereign|SCD|1 +1 Skyscanner|SCD|1 +1 Sol Ring|SCD|1 +1 Soul Snare|SCD|1 +1 Sphinx of Enlightenment|SCD|1 +1 Sphinx's Revelation|SCD|1 +1 Staggering Insight|SCD|1 +1 Steel-Plume Marshal|SCD|1 +1 Storm Herd|SCD|1 +1 Swords to Plowshares|SCD|1 +1 Talisman of Progress|SCD|1 +1 Temple of Enlightenment|SCD|1 +1 Thought Vessel +1 Thunderclap Wyvern|SCD|1 +1 Tide Skimmer|SCD|1 +1 Time Wipe|SCD|1 +1 Tranquil Cove|SCD|1 +1 True Conviction|SCD|1 +1 Vow of Duty|SCD|1 +1 Warden of Evos Isle|SCD|1 +1 Windreader Sphinx|SCD|1 +1 Winged Words|SCD|1 +[Sideboard] diff --git a/forge-gui/res/quest/commanderprecons/Grave Danger [SCD] [2022].dck b/forge-gui/res/quest/commanderprecons/Grave Danger [SCD] [2022].dck new file mode 100644 index 00000000000..2651ec5b0d4 --- /dev/null +++ b/forge-gui/res/quest/commanderprecons/Grave Danger [SCD] [2022].dck @@ -0,0 +1,76 @@ +[metadata] +name=Grave Danger [SCD] [2022] +[Commander] +1 Gisa and Geralf|SCD|1 +[Main] +1 Arcane Signet|SCD|1 +1 Army of the Damned|SCD|1 +1 Cemetery Reaper|SCD|1 +1 Champion of the Perished|SCD|1 +1 Choked Estuary|SCD|1 +1 Command Tower|SCD|1 +1 Commander's Sphere|SCD|1 +1 Crippling Fear|SCD|1 +1 Cruel Revival|SCD|1 +1 Curse of Disturbance|SCD|1 +1 Deep Analysis|SCD|1 +1 Dimir Signet|SCD|1 +1 Diregraf Captain|SCD|1 +1 Dismal Backwater|SCD|1 +1 Distant Melody|SCD|1 +1 Enter the God-Eternals|SCD|1 +1 Eternal Skylord|SCD|1 +1 Feed the Swarm|SCD|1 +1 Fleshbag Marauder|SCD|1 +1 Geralf's Mindcrusher|SCD|1 +1 Gleaming Overseer|SCD|1 +1 Gravespawn Sovereign|SCD|1 +1 Gray Merchant of Asphodel|SCD|1 +1 Grimoire of the Dead|SCD|1 +1 Havengul Lich|SCD|1 +1 Heraldic Banner|SCD|1 +13 Island|SCD|1 +1 Josu Vess, Lich Knight|SCD|1 +1 Jwar Isle Refuge|SCD|1 +1 Laboratory Drudge|SCD|1 +1 Lazotep Plating|SCD|1 +1 Lazotep Reaver|SCD|1 +1 Liliana's Devotee|SCD|1 +1 Liliana's Mastery|SCD|1 +1 Liliana's Standard Bearer|SCD|1 +1 Liliana, Untouched by Death|SCD|1 +1 Lord of the Accursed|SCD|1 +1 Lotleth Giant|SCD|1 +1 Loyal Subordinate|SCD|1 +1 Midnight Reaper|SCD|1 +1 Mire Triton|SCD|1 +1 Murder|SCD|1 +1 Necromantic Selection|SCD|1 +1 Necrotic Hex|SCD|1 +1 Open the Graves|SCD|1 +1 Overseer of the Damned|SCD|1 +1 Pilfered Plans|SCD|1 +1 Salt Marsh|SCD|1 +1 Scourge of Nel Toth|SCD|1 +1 Sinister Sabotage|SCD|1 +1 Sol Ring|SCD|1 +1 Spark Reaper|SCD|1 +1 Submerged Boneyard|SCD|1 +1 Sunken Hollow|SCD|1 +18 Swamp|SCD|1 +1 Syphon Flesh|SCD|1 +1 Talisman of Dominance|SCD|1 +1 Temple of Deceit|SCD|1 +1 Unbreathing Horde|SCD|1 +1 Undead Augur|SCD|1 +1 Undermine|SCD|1 +1 Unstable Obelisk|SCD|1 +1 Vampiric Rites|SCD|1 +1 Vela the Night-Clad|SCD|1 +1 Vengeful Dead|SCD|1 +1 Victimize|SCD|1 +1 Vizier of the Scorpion|SCD|1 +1 Wayfarer's Bauble|SCD|1 +1 Withered Wretch|SCD|1 +1 Zombie Apocalypse|SCD|1 +[Sideboard] diff --git a/forge-gui/res/quest/commanderprecons/Token Triumph [SCD] [2022].dck b/forge-gui/res/quest/commanderprecons/Token Triumph [SCD] [2022].dck new file mode 100644 index 00000000000..660ec3d15ca --- /dev/null +++ b/forge-gui/res/quest/commanderprecons/Token Triumph [SCD] [2022].dck @@ -0,0 +1,78 @@ +[metadata] +name=Token Triumph [SCD] [2022] +[Commander] +1 Emmara, Soul of the Accord|SCD|1 +[Main] +1 Ajani, Caller of the Pride|SCD|1 +1 Arcane Signet|SCD|1 +1 Aura Mutation|SCD|1 +1 Avacyn's Pilgrim|SCD|1 +1 Blossoming Sands|SCD|1 +1 Camaraderie|SCD|1 +1 Canopy Vista|SCD|1 +1 Champion of Lambholt|SCD|1 +1 Citanul Hierophants|SCD|1 +1 Citywide Bust|SCD|1 +1 Collective Blessing|SCD|1 +1 Collective Unconscious|SCD|1 +1 Command Tower|SCD|1 +1 Commander's Insignia|SCD|1 +1 Commander's Sphere|SCD|1 +1 Conclave Tribunal|SCD|1 +1 Curse of Bounty|SCD|1 +1 Dauntless Escort|SCD|1 +1 Dawn of Hope|SCD|1 +1 Devouring Light|SCD|1 +1 Dictate of Heliod|SCD|1 +1 Elfhame Palace|SCD|1 +1 Eternal Witness|SCD|1 +1 Farhaven Elf|SCD|1 +1 Felidar Retreat|SCD|1 +15 Forest|SCD|1 +1 Fortified Village|SCD|1 +1 Graypelt Refuge|SCD|1 +1 Great Oak Guardian|SCD|1 +1 Harmonize|SCD|1 +1 Harvest Season|SCD|1 +1 Holdout Settlement|SCD|1 +1 Hornet Nest|SCD|1 +1 Hornet Queen|SCD|1 +1 Hour of Reckoning|SCD|1 +1 Idol of Oblivion|SCD|1 +1 Jade Mage|SCD|1 +1 Jaspera Sentinel|SCD|1 +1 Karametra's Favor|SCD|1 +1 Leafkin Druid|SCD|1 +1 Loyal Guardian|SCD|1 +1 Maja, Bretagard Protector|SCD|1 +1 March of the Multitudes|SCD|1 +1 Mentor of the Meek|SCD|1 +1 Nissa's Expedition|SCD|1 +1 Nullmage Shepherd|SCD|1 +1 Overrun|SCD|1 +1 Overwhelming Instinct|SCD|1 +1 Path to Exile|SCD|1 +14 Plains|SCD|1 +1 Presence of Gond|SCD|1 +1 Reclamation Sage|SCD|1 +1 Rishkar, Peema Renegade|SCD|1 +1 Rootborn Defenses|SCD|1 +1 Scatter the Seeds|SCD|1 +1 Scavenging Ooze|SCD|1 +1 Selesnya Evangel|SCD|1 +1 Selesnya Guildmage|SCD|1 +1 Slate of Ancestry|SCD|1 +1 Sol Ring|SCD|1 +1 Sporemound|SCD|1 +1 Sylvan Reclamation|SCD|1 +1 Talisman of Unity|SCD|1 +1 Temple of Plenty|SCD|1 +1 Thunderfoot Baloth|SCD|1 +1 Tranquil Expanse|SCD|1 +1 Trostani Discordant|SCD|1 +1 Valor in Akros|SCD|1 +1 Verdant Force|SCD|1 +1 Vitu-Ghazi, the City-Tree|SCD|1 +1 Voice of Many|SCD|1 +1 White Sun's Zenith|SCD|1 +[Sideboard] From 50966d3f7a1ecfc20322e5c212dd9b18b0eb8952 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Wed, 4 Jan 2023 22:43:17 +0100 Subject: [PATCH 62/76] Morph fix --- forge-gui/src/main/java/forge/player/HumanPlay.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index 9b5cc2c5dc0..07cd9c0b126 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -114,9 +114,11 @@ public class HumanPlay { final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa); if (!req.playAbility(true, false, false)) { - if (flippedToCast && !castFaceDown) { + Card rollback = p.getGame().getCardState(sa.getHostCard()); + if (castFaceDown) { + rollback.setFaceDown(false); + } else if (flippedToCast) { // need to get the changed card if able - Card rollback = p.getGame().getCardState(sa.getHostCard()); rollback.turnFaceDown(true); //need to set correct imagekey when forcing facedown rollback.setImageKey(ImageKeys.getTokenKey(isforetold ? ImageKeys.FORETELL_IMAGE : ImageKeys.HIDDEN_CARD)); From 58eb11ec56d724282e3ef3893e2bce094b1bb690 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Thu, 5 Jan 2023 12:25:39 +0800 Subject: [PATCH 63/76] ValidCard$ Self to ValidCard$ Card.Self fixes Moonshae Pixie --- forge-gui/res/cardsfolder/b/bow_to_my_command.txt | 2 +- forge-gui/res/cardsfolder/m/moonshae_pixie.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui/res/cardsfolder/b/bow_to_my_command.txt b/forge-gui/res/cardsfolder/b/bow_to_my_command.txt index d6eba81a67d..11145dd97f4 100644 --- a/forge-gui/res/cardsfolder/b/bow_to_my_command.txt +++ b/forge-gui/res/cardsfolder/b/bow_to_my_command.txt @@ -16,7 +16,7 @@ SVar:RepeatOpp:DB$ RepeatEach | RepeatSubAbility$ ChooseCardsToTap | RepeatPlaye SVar:ChooseCardsToTap:DB$ ChooseCard | Defined$ Opponent | MinAmount$ 0 | Amount$ NumCreatures | Choices$ Creature.untapped+RememberedPlayerCtrl | ChoiceTitle$ Choose any number of untapped creatures you control | ChoiceZone$ Battlefield | RememberChosen$ True | AILogic$ BowToMyCommand | SubAbility$ TapChosenCards SVar:TapChosenCards:DB$ Tap | Defined$ Remembered | SubAbility$ AbandonSelf | ConditionCheckSVar$ TappedCreaturePower | ConditionSVarCompare$ GE8 SVar:AbandonSelf:DB$ Abandon | SubAbility$ DBCleanup | ConditionCheckSVar$ TappedCreaturePower | ConditionSVarCompare$ GE8 -T:Mode$ Abandoned | ValidCard$ Self | Execute$ DBCleanup +T:Mode$ Abandoned | ValidCard$ Card.Self | Execute$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearChosenCard$ True SVar:NumCreatures:Count$Valid Creature.RememberedPlayerCtrl SVar:TappedCreaturePower:Count$SumPower_Card.IsRemembered diff --git a/forge-gui/res/cardsfolder/m/moonshae_pixie.txt b/forge-gui/res/cardsfolder/m/moonshae_pixie.txt index f2f45c2d8ca..48bccc005b6 100644 --- a/forge-gui/res/cardsfolder/m/moonshae_pixie.txt +++ b/forge-gui/res/cardsfolder/m/moonshae_pixie.txt @@ -3,7 +3,7 @@ ManaCost:3 U Types:Creature Faerie PT:2/2 K:Flying -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw cards equal to the number of opponents who were dealt combat damage this turn. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw cards equal to the number of opponents who were dealt combat damage this turn. SVar:TrigDraw:DB$ Draw | NumCards$ X SVar:X:PlayerCountRegisteredOpponents$HasPropertywasDealtCombatDamageThisTurn AlternateMode:Adventure From b1df2d10081d4939d538a48d3e8c05b8adba9937 Mon Sep 17 00:00:00 2001 From: TRT <> Date: Thu, 5 Jan 2023 16:10:50 +0100 Subject: [PATCH 64/76] Fix DiscardedThisTurn --- forge-ai/src/main/java/forge/ai/ComputerUtil.java | 9 +++++---- forge-ai/src/main/java/forge/ai/ComputerUtilMana.java | 9 +++++---- .../src/main/java/forge/ai/ability/ManaEffectAi.java | 1 + forge-ai/src/main/java/forge/ai/ability/ManifestAi.java | 2 +- .../forge/game/ability/effects/ChangeZoneAllEffect.java | 3 +-- .../forge/game/ability/effects/ChangeZoneEffect.java | 2 +- forge-game/src/main/java/forge/game/card/Card.java | 6 +++--- .../src/main/java/forge/game/card/CardProperty.java | 3 +-- forge-game/src/main/java/forge/game/player/Player.java | 3 +++ .../java/forge/game/spellability/AbilityManaPart.java | 4 ++-- .../main/java/forge/player/HumanPlaySpellAbility.java | 2 +- 11 files changed, 24 insertions(+), 20 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index bd12ad6c7e0..8ab5228fc26 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1063,6 +1063,10 @@ public class ComputerUtil { return true; } + if (cardState.hasKeyword(Keyword.EXALTED) || cardState.hasKeyword(Keyword.EXTORT)) { + return true; + } + if (cardState.hasKeyword(Keyword.RIOT) && ChooseGenericEffectAi.preferHasteForRiot(sa, ai)) { // Planning to choose Haste for Riot, so do this in Main 1 return true; @@ -1070,6 +1074,7 @@ public class ComputerUtil { // if we have non-persistent mana in our pool, would be good to try to use it and not waste it if (ai.getManaPool().willManaBeLostAtEndOfPhase()) { + // TODO should check if some will be kept and skip those boolean canUseToPayCost = false; for (byte color : ManaAtom.MANATYPES) { // tries to reuse any amount of colorless if cost only has generic @@ -1089,10 +1094,6 @@ public class ComputerUtil { return true; } - if (cardState.hasKeyword(Keyword.EXALTED) || cardState.hasKeyword(Keyword.EXTORT)) { - return true; - } - //cast equipments in Main1 when there are creatures to equip and no other unequipped equipment if (card.isEquipment()) { boolean playNow = false; diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java index f08f5a69332..87cc9c397b7 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java @@ -93,6 +93,7 @@ public class ComputerUtilMana { ability.setActivatingPlayer(card.getController(), true); if (ability.isManaAbility()) { score += ability.calculateScoreForManaAbility(); + // TODO check TriggersWhenSpent } else if (!ability.isTrigger() && ability.isPossible()) { score += 13; //add 13 for any non-mana activated abilities @@ -393,9 +394,9 @@ public class ComputerUtilMana { String manaProduced = toPay.isSnow() && hostCard.isSnow() ? "S" : GameActionUtil.generatedTotalMana(saPayment); //String originalProduced = manaProduced; - final Map repParams = AbilityKey.mapFromPlayer(ai); + final Map repParams = AbilityKey.mapFromAffected(hostCard); repParams.put(AbilityKey.Mana, manaProduced); - repParams.put(AbilityKey.Affected, hostCard); + repParams.put(AbilityKey.Activator, ai); repParams.put(AbilityKey.AbilityMana, saPayment); // RootAbility // TODO Damping Sphere might replace later? @@ -1614,9 +1615,9 @@ public class ComputerUtilMana { // setup produce mana replacement effects String origin = mp.getOrigProduced(); - final Map repParams = AbilityKey.mapFromPlayer(ai); + final Map repParams = AbilityKey.mapFromAffected(sourceCard); repParams.put(AbilityKey.Mana, origin); - repParams.put(AbilityKey.Affected, sourceCard); + repParams.put(AbilityKey.Activator, ai); repParams.put(AbilityKey.AbilityMana, m); // RootAbility List reList = game.getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other); diff --git a/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java b/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java index 22c5a093338..eb8104e47db 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ManaEffectAi.java @@ -266,6 +266,7 @@ public class ManaEffectAi extends SpellAbilityAi { ManaPool mp = ai.getManaPool(); Mana test = null; if (mp.isEmpty()) { + // TODO use color from ability test = new Mana((byte) ManaAtom.COLORLESS, source, null); mp.addMana(test, false); } diff --git a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java index b807069b859..891fabb44ba 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ManifestAi.java @@ -101,7 +101,7 @@ public class ManifestAi extends SpellAbilityAi { repParams.put(AbilityKey.Origin, card.getZone().getZoneType()); repParams.put(AbilityKey.Destination, ZoneType.Battlefield); repParams.put(AbilityKey.Source, sa.getHostCard()); - List list = game.getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.Other); + List list = game.getReplacementHandler().getReplacementList(ReplacementType.Moved, repParams, ReplacementLayer.CantHappen); if (!list.isEmpty()) { return false; } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java index 4436dfafd7f..9468d9a3cd4 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java @@ -94,8 +94,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { if (!libCards.isEmpty()) { sa.getActivatingPlayer().getController().reveal(libCards, ZoneType.Library, libCards.get(0).getOwner()); } - final Map runParams = AbilityKey.newMap(); - runParams.put(AbilityKey.Player, sa.getActivatingPlayer()); + final Map runParams = AbilityKey.mapFromPlayer(sa.getActivatingPlayer()); runParams.put(AbilityKey.Target, tgtPlayers); game.getTriggerHandler().runTrigger(TriggerType.SearchedLibrary, runParams, false); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 89e84303122..68d6f40bac1 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -782,7 +782,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } if (sa.hasParam("TrackDiscarded")) { - movedCard.setMadnessWithoutCast(true); + movedCard.setDiscarded(true); } } } diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 696cea94a1a..ad4f65c5634 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -226,7 +226,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private boolean tributed = false; private boolean embalmed = false; private boolean eternalized = false; - private boolean madnessWithoutCast = false; + private boolean discarded = false; private boolean flipped = false; private boolean facedown = false; @@ -5801,8 +5801,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } return getCastSA().isMadness(); } - public boolean getMadnessWithoutCast() { return madnessWithoutCast; } - public void setMadnessWithoutCast(boolean state) { madnessWithoutCast = state; } + public boolean wasDiscarded() { return discarded; } + public void setDiscarded(boolean state) { discarded = state; } public final boolean isMonstrous() { return monstrous; diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java index 1cd1130174a..2d13ad41bb2 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -984,8 +984,7 @@ public class CardProperty { return false; } - List cards = CardUtil.getThisTurnEntered(ZoneType.Graveyard, ZoneType.Hand, "Card", source, spellAbility); - if (!cards.contains(card) && !card.getMadnessWithoutCast()) { + if (!card.wasDiscarded()) { return false; } } else if (property.startsWith("ControlledByPlayerInTheDirection")) { diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index 50191ded7fd..747fb5dfc9b 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -1444,6 +1444,9 @@ public class Player extends GameEntity implements Comparable { newCard = game.getAction().moveToGraveyard(c, sa, params); // Play the Discard sound } + + newCard.setDiscarded(true); + if (table != null) { table.put(origin, newCard.getZone().getZoneType(), newCard); } diff --git a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java index b95f6119b3a..c5966bf587c 100644 --- a/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java +++ b/forge-game/src/main/java/forge/game/spellability/AbilityManaPart.java @@ -664,9 +664,9 @@ public class AbilityManaPart implements java.io.Serializable { // check for produce mana replacement effects - they mess this up, so just use the mana ability final Card source = am.getHostCard(); final Player activator = am.getActivatingPlayer(); - final Map repParams = AbilityKey.mapFromPlayer(activator); + final Map repParams = AbilityKey.mapFromAffected(source); repParams.put(AbilityKey.Mana, getOrigProduced()); - repParams.put(AbilityKey.Affected, source); + repParams.put(AbilityKey.Activator, activator); repParams.put(AbilityKey.AbilityMana, am.getRootAbility()); if (!source.getGame().getReplacementHandler().getReplacementList(ReplacementType.ProduceMana, repParams, ReplacementLayer.Other).isEmpty()) { diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java index 43994257c45..f5010b2f039 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java +++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java @@ -173,7 +173,7 @@ public class HumanPlaySpellAbility { if (ability.getHostCard().isMadness()) { // if a player failed to play madness cost, move the card to graveyard Card newCard = game.getAction().moveToGraveyard(c, null); - newCard.setMadnessWithoutCast(true); + newCard.setDiscarded(true); } } else { payment.refundPayment(); From ef14fb934a2c0ac8a9091d2b3fbed5ea08eb0518 Mon Sep 17 00:00:00 2001 From: sky81 Date: Fri, 6 Jan 2023 09:07:04 +0200 Subject: [PATCH 65/76] Do not clear POI changes after reload --- forge-gui-mobile/src/forge/adventure/world/WorldSave.java | 1 - 1 file changed, 1 deletion(-) diff --git a/forge-gui-mobile/src/forge/adventure/world/WorldSave.java b/forge-gui-mobile/src/forge/adventure/world/WorldSave.java index aa787f54634..a8957a458ab 100644 --- a/forge-gui-mobile/src/forge/adventure/world/WorldSave.java +++ b/forge-gui-mobile/src/forge/adventure/world/WorldSave.java @@ -79,7 +79,6 @@ public class WorldSave { System.err.println("Generating New World"); currentSave.world.generateNew(0); } - currentSave.pointOfInterestChanges.clear(); currentSave.onLoadList.emit(); From 0bc50c313c6e84398d32532cb1abb290cb85ed7c Mon Sep 17 00:00:00 2001 From: paulsnoops Date: Fri, 6 Jan 2023 08:49:04 +0000 Subject: [PATCH 66/76] Formats update: Add DMR --- forge-gui/res/formats/Archived/Legacy/2023-01-13.txt | 8 ++++++++ forge-gui/res/formats/Archived/Vintage/2023-01-13.txt | 9 +++++++++ forge-gui/res/formats/Sanctioned/Legacy.txt | 2 +- forge-gui/res/formats/Sanctioned/Vintage.txt | 2 +- 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 forge-gui/res/formats/Archived/Legacy/2023-01-13.txt create mode 100644 forge-gui/res/formats/Archived/Vintage/2023-01-13.txt diff --git a/forge-gui/res/formats/Archived/Legacy/2023-01-13.txt b/forge-gui/res/formats/Archived/Legacy/2023-01-13.txt new file mode 100644 index 00000000000..333b95ab4fc --- /dev/null +++ b/forge-gui/res/formats/Archived/Legacy/2023-01-13.txt @@ -0,0 +1,8 @@ +[format] +Name:Legacy (DMR) +Type:Archived +Subtype:Legacy +Effective:2023-01-13 +Sets:LEA, LEB, 2ED, ARN, ATQ, 3ED, LEG, DRC94, DRK, PHPR, FEM, 4ED, ICE, CHR, HML, ALL, MIR, VIS, 5ED, POR, WTH, TMP, STH, EXO, PO2, USG, ATH, ULG, 6ED, UDS, S99, PTK, MMQ, BRB, NMS, S00, PCY, BTD, INV, PLS, 7ED, APC, ODY, DKM, TOR, JUD, ONS, LGN, SCG, 8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, MED, LRW, DD1, MOR, SHM, EVE, DRB, ME2, ALA, DD2, CFX, DDC, ARB, M10, TD0, FVE, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, M13, V12, DDJ, RTR, CM1, GTC, DDK, DGM, MMA, M14, V13, DDL, THS, C13, BNG, DDM, JOU, MD1, CNS, VMA, M15, V14, DDN, KTK, C14, DVD, EVG, GVL, JVC, FRF, UGF, DDO, DTK, TPR, MM2, ORI, V15, DDP, BFZ, EXP, C15, PZ1, OGW, DDQ, SOI, W16, EMA, EMN, V16, CN2, DDR, KLD, MPS_KLD, C16, PZ2, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, HOU, C17, XLN, DDT, IMA, V17, RIX, A25, DDU, DOM, CM2, BBD, SS1, GS1, M19, C18, GRN, MPS_GRN, GK1, G18, GNT, UMA, RNA, MPS_RNA, GK2, WAR, MPS_WAR, MH1, SS2, M20, C19, ELD, MB1, GN2, THB, IKO, C20, SS3, M21, JMP, 2XM, ZNR, ZNE, ZNC, CMR, CC1, KHM, KHC, TSR, STX, STA, C21, MH2, H1R, AFR, AFC, MID, MIC, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR +Banned:Advantageous Proclamation; Adriana's Valor; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Backup Plan; Balance; Bazaar of Baghdad; Black Lotus; Brago's Favor; Bronze Tablet; Channel; Chaos Orb; Cleanse; Contract from Below; Crusade; Darkpact; Deathrite Shaman; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Double Stroke; Dreadhorde Arcanist; Earthcraft; Echoing Boon; Emissary's Ploy; Falling Star; Fastbond; Flash; Frantic Search; Gitaxian Probe; Goblin Recruiter; Gush; Hermit Druid; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imperial Seal; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Library of Alexandria; Lurrus of the Dream-Den; Mana Crypt; Mana Drain; Mana Vault; Memory Jar; Mental Misstep; Mind Twist; Mind's Desire; Mishra's Workshop; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Mystical Tutor; Natural Unity; Necropotence; Oath of Druids; Oko, Thief of Crowns; Power Play; Pradesh Gypsies; Ragavan, Nimble Pilferer; Rebirth; Secret Summoning; Secrets of Paradise; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Sovereign's Realm; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Tempest Efreet; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Underworld Breach; Unexpected Potential; Vampiric Tutor; Weight Advantage; Wheel of Fortune; Windfall; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker +Additional:Arvinox, the Mind Flail; Blanka, Ferocious Friend; Bjorna, Nightfall Alchemist; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Dhalsim, Pliable Pacifist; Dustin, Gadget Genius; E. Honda, Sumo Champion; Eleven, the Mage; Elmar, Ulvenwald Informant; Glenn, the Voice of Calm; Guile, Sonic Soldier; Hargilde, Kindly Runechanter; Havengul Laboratory; Ken, Burning Brawler; Lucas, the Sharpshooter; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rick, Steadfast Leader; Ryu, World Warrior; Sophina, Spearsage Deserter; Wernog, Rider's Chaplain; Will the Wise; Zangief, the Red Cyclone diff --git a/forge-gui/res/formats/Archived/Vintage/2023-01-13.txt b/forge-gui/res/formats/Archived/Vintage/2023-01-13.txt new file mode 100644 index 00000000000..343a0caeff6 --- /dev/null +++ b/forge-gui/res/formats/Archived/Vintage/2023-01-13.txt @@ -0,0 +1,9 @@ +[format] +Name:Vintage (DMR) +Type:Archived +Subtype:Vintage +Effective:2023-01-13 +Sets:LEA, LEB, 2ED, ARN, ATQ, 3ED, LEG, DRC94, DRK, PHPR, FEM, 4ED, ICE, CHR, HML, ALL, MIR, VIS, 5ED, POR, WTH, TMP, STH, EXO, PO2, USG, ATH, ULG, 6ED, UDS, S99, PTK, MMQ, BRB, NMS, S00, PCY, BTD, INV, PLS, 7ED, APC, ODY, DKM, TOR, JUD, ONS, LGN, SCG, 8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, MED, LRW, DD1, MOR, SHM, EVE, DRB, ME2, ALA, DD2, CFX, DDC, ARB, M10, TD0, FVE, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, M13, V12, DDJ, RTR, CM1, GTC, DDK, DGM, MMA, M14, V13, DDL, THS, C13, BNG, DDM, JOU, MD1, CNS, VMA, M15, V14, DDN, KTK, C14, DVD, EVG, GVL, JVC, FRF, UGF, DDO, DTK, TPR, MM2, ORI, V15, DDP, BFZ, EXP, C15, PZ1, OGW, DDQ, SOI, W16, EMA, EMN, V16, CN2, DDR, KLD, MPS_KLD, C16, PZ2, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, HOU, C17, XLN, DDT, IMA, V17, RIX, A25, DDU, DOM, CM2, BBD, SS1, GS1, M19, C18, GRN, MPS_GRN, GK1, G18, GNT, UMA, RNA, MPS_RNA, GK2, WAR, MPS_WAR, MH1, SS2, M20, C19, ELD, MB1, GN2, THB, IKO, C20, SS3, M21, JMP, 2XM, ZNR, ZNE, ZNC, CMR, CC1, KHM, KHC, TSR, STX, STA, C21, MH2, H1R, AFR, AFC, MID, MIC, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR +Restricted:Ancestral Recall; Balance; Black Lotus; Brainstorm; Chalice of the Void; Channel; Demonic Consultation; Demonic Tutor; Dig Through Time; Flash; Gitaxian Probe; Golgari Grave-Troll; Gush; Imperial Seal; Karn, the Great Creator; Library of Alexandria; Lion's Eye Diamond; Lodestone Golem; Lotus Petal; Mana Crypt; Mana Vault; Memory Jar; Mental Misstep; Merchant Scroll; Mind's Desire; Monastery Mentor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Mystic Forge; Mystical Tutor; Narset, Parter of Veils; Necropotence; Ponder; Sol Ring; Strip Mine; Thorn of Amethyst; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Trinisphere; Vampiric Tutor; Wheel of Fortune; Windfall; Yawgmoth's Will +Banned:Adriana's Valor; Advantageous Proclamation; Amulet of Quoz; Assemble the Rank and Vile; Backup Plan; Brago's Favor; Bronze Tablet; Chaos Orb; Cleanse; Contract from Below; Crusade; Darkpact; Demonic Attorney; Double Stroke; Echoing Boon; Emissary's Ploy; Falling Star; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Muzzio's Preparations; Natural Unity; Power Play; Pradesh Gypsies; Rebirth; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Shahrazad; Sovereign's Realm; Stone-Throwing Devils; Summoner's Bond; Tempest Efreet; Timmerian Fiends; Unexpected Potential; Weight Advantage; Worldknit +Additional:Arvinox, the Mind Flail; Blanka, Ferocious Friend; Bjorna, Nightfall Alchemist; Cecily, Haunted Mage; Chief Jim Hopper; Chun-Li, Countless Kicks; Daryl, Hunter of Walkers; Dhalsim, Pliable Pacifist; Dustin, Gadget Genius; E. Honda, Sumo Champion; Eleven, the Mage; Elmar, Ulvenwald Informant; Glenn, the Voice of Calm; Guile, Sonic Soldier; Hargilde, Kindly Runechanter; Havengul Laboratory; Ken, Burning Brawler; Lucas, the Sharpshooter; Max, the Daredevil; Michonne, Ruthless Survivor; Mike, the Dungeon Master; Mind Flayer, the Shadow; Negan, the Cold-Blooded; Othelm, Sigardian Outcast; Rick, Steadfast Leader; Ryu, World Warrior; Sophina, Spearsage Deserter; Wernog, Rider's Chaplain; Will the Wise; Zangief, the Red Cyclone diff --git a/forge-gui/res/formats/Sanctioned/Legacy.txt b/forge-gui/res/formats/Sanctioned/Legacy.txt index d4417bd86d0..d4bb3870224 100644 --- a/forge-gui/res/formats/Sanctioned/Legacy.txt +++ b/forge-gui/res/formats/Sanctioned/Legacy.txt @@ -3,5 +3,5 @@ Name:Legacy Order:105 Subtype:Legacy Type:Sanctioned -Sets:LEA, LEB, 2ED, ARN, ATQ, 3ED, LEG, DRC94, DRK, PHPR, FEM, 4ED, ICE, CHR, HML, ALL, MIR, VIS, 5ED, POR, WTH, TMP, STH, EXO, PO2, USG, ATH, ULG, 6ED, UDS, S99, PTK, MMQ, BRB, NMS, S00, PCY, BTD, INV, PLS, 7ED, APC, ODY, DKM, TOR, JUD, ONS, LGN, SCG, 8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, MED, LRW, DD1, MOR, SHM, EVE, DRB, ME2, ALA, DD2, CFX, DDC, ARB, M10, TD0, FVE, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, M13, V12, DDJ, RTR, CM1, GTC, DDK, DGM, MMA, M14, V13, DDL, THS, C13, BNG, DDM, JOU, MD1, CNS, VMA, M15, V14, DDN, KTK, C14, DVD, EVG, GVL, JVC, FRF, UGF, DDO, DTK, TPR, MM2, ORI, V15, DDP, BFZ, EXP, C15, PZ1, OGW, DDQ, SOI, W16, EMA, EMN, V16, CN2, DDR, KLD, MPS_KLD, C16, PZ2, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, HOU, C17, XLN, DDT, IMA, V17, RIX, A25, DDU, DOM, CM2, BBD, SS1, GS1, M19, C18, GRN, MPS_GRN, GK1, G18, GNT, UMA, RNA, MPS_RNA, GK2, WAR, MPS_WAR, MH1, SS2, M20, C19, ELD, MB1, GN2, SLD, THB, IKO, C20, SS3, M21, JMP, 2XM, ZNR, ZNE, ZNC, CMR, CC1, KHM, KHC, TSR, STX, STA, C21, MH2, H1R, AFR, AFC, MID, MIC, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, SLX, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD +Sets:LEA, LEB, 2ED, ARN, ATQ, 3ED, LEG, DRC94, DRK, PHPR, FEM, 4ED, ICE, CHR, HML, ALL, MIR, VIS, 5ED, POR, WTH, TMP, STH, EXO, PO2, USG, ATH, ULG, 6ED, UDS, S99, PTK, MMQ, BRB, NMS, S00, PCY, BTD, INV, PLS, 7ED, APC, ODY, DKM, TOR, JUD, ONS, LGN, SCG, 8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, MED, LRW, DD1, MOR, SHM, EVE, DRB, ME2, ALA, DD2, CFX, DDC, ARB, M10, TD0, FVE, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, M13, V12, DDJ, RTR, CM1, GTC, DDK, DGM, MMA, M14, V13, DDL, THS, C13, BNG, DDM, JOU, MD1, CNS, VMA, M15, V14, DDN, KTK, C14, DVD, EVG, GVL, JVC, FRF, UGF, DDO, DTK, TPR, MM2, ORI, V15, DDP, BFZ, EXP, C15, PZ1, OGW, DDQ, SOI, W16, EMA, EMN, V16, CN2, DDR, KLD, MPS_KLD, C16, PZ2, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, HOU, C17, XLN, DDT, IMA, V17, RIX, A25, DDU, DOM, CM2, BBD, SS1, GS1, M19, C18, GRN, MPS_GRN, GK1, G18, GNT, UMA, RNA, MPS_RNA, GK2, WAR, MPS_WAR, MH1, SS2, M20, C19, ELD, MB1, GN2, SLD, THB, IKO, C20, SS3, M21, JMP, 2XM, ZNR, ZNE, ZNC, CMR, CC1, KHM, KHC, TSR, STX, STA, C21, MH2, H1R, AFR, AFC, MID, MIC, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, SLX, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR Banned:Advantageous Proclamation; Adriana's Valor; Amulet of Quoz; Ancestral Recall; Arcum's Astrolabe; Assemble the Rank and Vile; Backup Plan; Balance; Bazaar of Baghdad; Black Lotus; Brago's Favor; Bronze Tablet; Channel; Chaos Orb; Cleanse; Contract from Below; Crusade; Darkpact; Deathrite Shaman; Demonic Attorney; Demonic Consultation; Demonic Tutor; Dig Through Time; Double Stroke; Dreadhorde Arcanist; Earthcraft; Echoing Boon; Emissary's Ploy; Falling Star; Fastbond; Flash; Frantic Search; Gitaxian Probe; Goblin Recruiter; Gush; Hermit Druid; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imperial Seal; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Library of Alexandria; Lurrus of the Dream-Den; Mana Crypt; Mana Drain; Mana Vault; Memory Jar; Mental Misstep; Mind Twist; Mind's Desire; Mishra's Workshop; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Muzzio's Preparations; Mystical Tutor; Natural Unity; Necropotence; Oath of Druids; Oko, Thief of Crowns; Power Play; Pradesh Gypsies; Ragavan, Nimble Pilferer; Rebirth; Secret Summoning; Secrets of Paradise; Sensei's Divining Top; Sentinel Dispatch; Shahrazad; Skullclamp; Sol Ring; Sovereign's Realm; Stone-Throwing Devils; Strip Mine; Summoner's Bond; Survival of the Fittest; Tempest Efreet; Time Vault; Time Walk; Timetwister; Timmerian Fiends; Tinker; Tolarian Academy; Treasure Cruise; Underworld Breach; Unexpected Potential; Vampiric Tutor; Weight Advantage; Wheel of Fortune; Windfall; Worldknit; Wrenn and Six; Yawgmoth's Bargain; Yawgmoth's Will; Zirda, the Dawnwaker diff --git a/forge-gui/res/formats/Sanctioned/Vintage.txt b/forge-gui/res/formats/Sanctioned/Vintage.txt index 2c9ffdb5309..718514b02d3 100644 --- a/forge-gui/res/formats/Sanctioned/Vintage.txt +++ b/forge-gui/res/formats/Sanctioned/Vintage.txt @@ -3,6 +3,6 @@ Name:Vintage Order:104 Subtype:Vintage Type:Sanctioned -Sets:LEA, LEB, 2ED, ARN, ATQ, 3ED, LEG, DRC94, DRK, PHPR, FEM, 4ED, ICE, CHR, HML, ALL, MIR, VIS, 5ED, POR, WTH, TMP, STH, EXO, PO2, USG, ATH, ULG, 6ED, UDS, S99, PTK, MMQ, BRB, NMS, S00, PCY, BTD, INV, PLS, 7ED, APC, ODY, DKM, TOR, JUD, ONS, LGN, SCG, 8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, MED, LRW, DD1, MOR, SHM, EVE, DRB, ME2, ALA, DD2, CFX, DDC, ARB, M10, TD0, FVE, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, M13, V12, DDJ, RTR, CM1, GTC, DDK, DGM, MMA, M14, V13, DDL, THS, C13, BNG, DDM, JOU, MD1, CNS, VMA, M15, V14, DDN, KTK, C14, DVD, EVG, GVL, JVC, FRF, UGF, DDO, DTK, TPR, MM2, ORI, V15, DDP, BFZ, EXP, C15, PZ1, OGW, DDQ, SOI, W16, EMA, EMN, V16, CN2, DDR, KLD, MPS_KLD, C16, PZ2, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, HOU, C17, XLN, DDT, IMA, V17, RIX, A25, DDU, DOM, CM2, BBD, SS1, GS1, M19, C18, GRN, MPS_GRN, GK1, G18, GNT, UMA, RNA, MPS_RNA, GK2, WAR, MPS_WAR, MH1, SS2, M20, C19, ELD, MB1, GN2, SLD, THB, IKO, C20, SS3, M21, JMP, 2XM, ZNR, ZNE, ZNC, CMR, CC1, KHM, KHC, TSR, STX, STA, C21, MH2, H1R, AFR, AFC, MID, MIC, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, SLX, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD +Sets:LEA, LEB, 2ED, ARN, ATQ, 3ED, LEG, DRC94, DRK, PHPR, FEM, 4ED, ICE, CHR, HML, ALL, MIR, VIS, 5ED, POR, WTH, TMP, STH, EXO, PO2, USG, ATH, ULG, 6ED, UDS, S99, PTK, MMQ, BRB, NMS, S00, PCY, BTD, INV, PLS, 7ED, APC, ODY, DKM, TOR, JUD, ONS, LGN, SCG, 8ED, MRD, DST, 5DN, CHK, BOK, SOK, 9ED, RAV, GPT, DIS, CSP, TSP, TSB, PLC, FUT, 10E, MED, LRW, DD1, MOR, SHM, EVE, DRB, ME2, ALA, DD2, CFX, DDC, ARB, M10, TD0, FVE, ME3, ZEN, DDD, PDS, WWK, DDE, ROE, DPA, M11, FVR, DDF, SOM, TD1, PD2, ME4, MBS, DDG, NPH, TD2, COM, M12, FVL, DDH, ISD, PD3, DKA, DDI, AVR, M13, V12, DDJ, RTR, CM1, GTC, DDK, DGM, MMA, M14, V13, DDL, THS, C13, BNG, DDM, JOU, MD1, CNS, VMA, M15, V14, DDN, KTK, C14, DVD, EVG, GVL, JVC, FRF, UGF, DDO, DTK, TPR, MM2, ORI, V15, DDP, BFZ, EXP, C15, PZ1, OGW, DDQ, SOI, W16, EMA, EMN, V16, CN2, DDR, KLD, MPS_KLD, C16, PZ2, AER, MM3, DDS, AKH, MPS_AKH, W17, CMA, HOU, C17, XLN, DDT, IMA, V17, RIX, A25, DDU, DOM, CM2, BBD, SS1, GS1, M19, C18, GRN, MPS_GRN, GK1, G18, GNT, UMA, RNA, MPS_RNA, GK2, WAR, MPS_WAR, MH1, SS2, M20, C19, ELD, MB1, GN2, SLD, THB, IKO, C20, SS3, M21, JMP, 2XM, ZNR, ZNE, ZNC, CMR, CC1, KHM, KHC, TSR, STX, STA, C21, MH2, H1R, AFR, AFC, MID, MIC, VOW, VOC, DBL, CC2, NEO, NEC, SNC, NCC, SLX, CLB, 2X2, DMU, DMC, 40K, UNF, GN3, BRO, BRC, BRR, BOT, J22, SCD, DMR Restricted:Ancestral Recall; Balance; Black Lotus; Brainstorm; Chalice of the Void; Channel; Demonic Consultation; Demonic Tutor; Dig Through Time; Flash; Gitaxian Probe; Golgari Grave-Troll; Gush; Imperial Seal; Karn, the Great Creator; Library of Alexandria; Lion's Eye Diamond; Lodestone Golem; Lotus Petal; Mana Crypt; Mana Vault; Memory Jar; Mental Misstep; Merchant Scroll; Mind's Desire; Monastery Mentor; Mox Emerald; Mox Jet; Mox Pearl; Mox Ruby; Mox Sapphire; Mystic Forge; Mystical Tutor; Narset, Parter of Veils; Necropotence; Ponder; Sol Ring; Strip Mine; Thorn of Amethyst; Time Vault; Time Walk; Timetwister; Tinker; Tolarian Academy; Treasure Cruise; Trinisphere; Vampiric Tutor; Wheel of Fortune; Windfall; Yawgmoth's Will Banned:Adriana's Valor; Advantageous Proclamation; Amulet of Quoz; Assemble the Rank and Vile; Backup Plan; Brago's Favor; Bronze Tablet; Chaos Orb; Cleanse; Contract from Below; Crusade; Darkpact; Demonic Attorney; Double Stroke; Echoing Boon; Emissary's Ploy; Falling Star; Hired Heist; Hold the Perimeter; Hymn of the Wilds; Immediate Action; Imprison; Incendiary Dissent; Invoke Prejudice; Iterative Analysis; Jeweled Bird; Jihad; Muzzio's Preparations; Natural Unity; Power Play; Pradesh Gypsies; Rebirth; Secret Summoning; Secrets of Paradise; Sentinel Dispatch; Shahrazad; Sovereign's Realm; Stone-Throwing Devils; Summoner's Bond; Tempest Efreet; Timmerian Fiends; Unexpected Potential; Weight Advantage; Worldknit From 70a062afd3c5735761f0f7769c7cee83ab841141 Mon Sep 17 00:00:00 2001 From: paulsnoops Date: Fri, 6 Jan 2023 09:08:00 +0000 Subject: [PATCH 67/76] Edition update: PH21 --- forge-gui/res/editions/2021 Heroes of the Realm.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-gui/res/editions/2021 Heroes of the Realm.txt b/forge-gui/res/editions/2021 Heroes of the Realm.txt index 95f6cfe360d..a3ed44fe505 100644 --- a/forge-gui/res/editions/2021 Heroes of the Realm.txt +++ b/forge-gui/res/editions/2021 Heroes of the Realm.txt @@ -7,5 +7,6 @@ ScryfallCode=PH21 [cards] 1 M Andrios, Roaming Explorer @Borja Pindado +2 M Arteeoh, Dread Scavenger @Zoltan Boros 3 M Byode, Inverse Sun @Dominik Mayer 4 M Ersta, Friend to All @Winona Nelson From 794fc11ab16763203672da0812f24eab5e33e4c3 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Fri, 6 Jan 2023 22:17:38 +0100 Subject: [PATCH 68/76] Rework UntilHostLeavesPlay timing --- .../src/main/java/forge/game/GameAction.java | 27 ++++++++++--------- .../game/zone/PlayerZoneBattlefield.java | 10 ------- forge-gui/res/cardsfolder/a/animate_dead.txt | 2 +- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index df67f6bf4c6..a51816f5ff4 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -579,6 +579,12 @@ public class GameAction { c.setZone(zoneTo); } + if (fromBattlefield) { + // order here is important so it doesn't unattach cards that might have returned from UntilHostLeavesPlay + unattachCardLeavingBattlefield(copied); + c.runLeavesPlayCommands(); + } + // do ETB counters after zone add if (!suppress && toBattlefield && !copied.getEtbCounters().isEmpty()) { game.getTriggerHandler().registerActiveTrigger(copied, false); @@ -706,7 +712,6 @@ public class GameAction { copied.setState(CardStateName.Original, true); } - unattachCardLeavingBattlefield(copied); } else if (toBattlefield) { for (Player p : game.getPlayers()) { copied.getDamageHistory().setNotAttackedSinceLastUpkeepOf(p); @@ -982,9 +987,15 @@ public class GameAction { if (c.isInZone(ZoneType.Stack)) { c.getGame().getStack().remove(c); } + + final Zone z = c.getZone(); // in some corner cases there's no zone yet (copied spell that failed targeting) - if (c.getZone() != null) { - c.getZone().remove(c); + if (z != null) { + z.remove(c); + if (z.is(ZoneType.Battlefield)) { + c.runLeavesPlayCommands(); + } + } // CR 603.6c other players LTB triggers should work @@ -1045,20 +1056,13 @@ public class GameAction { } game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); - for (Player p : game.getPlayers()) { - ((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(false); - } - - final int tiz = c.getTurnInZone(); oldBattlefield.remove(c); newBattlefield.add(c); - c.setSickness(true); if (game.getPhaseHandler().inCombat()) { game.getCombat().removeFromCombat(c); } - c.setTurnInZone(tiz); c.setCameUnderControlSinceLastUpkeep(true); final Map runParams = AbilityKey.mapFromCard(c); @@ -1066,9 +1070,6 @@ public class GameAction { game.getTriggerHandler().runTrigger(TriggerType.ChangesController, runParams, false); game.getTriggerHandler().clearSuppression(TriggerType.ChangesZone); - for (Player p : game.getPlayers()) { - ((PlayerZoneBattlefield) p.getZone(ZoneType.Battlefield)).setTriggers(true); - } c.runChangeControllerCommands(); } diff --git a/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java b/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java index 4bbddf21e57..8011a36b88a 100644 --- a/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java +++ b/forge-game/src/main/java/forge/game/zone/PlayerZoneBattlefield.java @@ -65,16 +65,6 @@ public class PlayerZoneBattlefield extends PlayerZone { } } - /** {@inheritDoc} */ - @Override - public final void remove(final Card c) { - super.remove(c); - - if (trigger) { - c.runLeavesPlayCommands(); - } - } - public final void setTriggers(final boolean b) { trigger = b; } diff --git a/forge-gui/res/cardsfolder/a/animate_dead.txt b/forge-gui/res/cardsfolder/a/animate_dead.txt index 378e22d60dc..c4d9b77523b 100644 --- a/forge-gui/res/cardsfolder/a/animate_dead.txt +++ b/forge-gui/res/cardsfolder/a/animate_dead.txt @@ -8,7 +8,7 @@ SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield SVar:DBAnimate:DB$ Animate | Defined$ Self | OverwriteSpells$ True | Abilities$ NewAttach | Keywords$ Enchant creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant creature card in a graveyard | Duration$ Permanent | SubAbility$ DBAttach SVar:DBAttach:DB$ Attach | Defined$ Remembered SVar:NewAttach:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump -T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigSacrifice | TriggerDescription$ When Animate Dead leaves the battlefield, that creature's controller sacrifices it. +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigSacrifice | TriggerDescription$ When CARDNAME leaves the battlefield, that creature's controller sacrifices it. SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ DirectRemembered | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ -1 | Description$ Enchanted creature gets -1/-0. From c23bf249f5f5e063b4b9410d62395f66ad2ac3f3 Mon Sep 17 00:00:00 2001 From: tool4EvEr Date: Fri, 6 Jan 2023 22:57:52 +0100 Subject: [PATCH 69/76] Trigger fix --- forge-game/src/main/java/forge/game/zone/MagicStack.java | 2 +- forge-gui/res/cardsfolder/a/animate_dead.txt | 9 +++++---- forge-gui/res/cardsfolder/d/dance_of_the_dead.txt | 9 +++++---- forge-gui/res/cardsfolder/n/necromancy.txt | 9 +++++---- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index 857b7d5b0d4..4f26ab23c1f 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -857,7 +857,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable Date: Sat, 7 Jan 2023 13:34:12 +0100 Subject: [PATCH 70/76] update --- .../res/cardsfolder/upcoming/argentum_masticore.txt | 13 +++++++++++++ .../upcoming/kethek_crucible_goliath.txt | 9 +++++++++ .../cardsfolder/upcoming/migloz_maze_crusher.txt | 10 ++++++++++ .../cardsfolder/upcoming/mindsplice_apparatus.txt | 11 +++++++++++ .../res/cardsfolder/upcoming/zenith_chronicler.txt | 7 +++++++ 5 files changed, 50 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/kethek_crucible_goliath.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/migloz_maze_crusher.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/mindsplice_apparatus.txt create mode 100644 forge-gui/res/cardsfolder/upcoming/zenith_chronicler.txt diff --git a/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt b/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt new file mode 100644 index 00000000000..b48cc1e5c6a --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt @@ -0,0 +1,13 @@ +Name:Argentum Masticore +ManaCost:5 +Types:Artifact Creature Phyrexian Masticore +PT:5/5 +K:First strike +K:Protection:Card.MultiColor:Protection from multicolored +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSacrifice | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you discard a card. When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. +SVar:TrigSacrifice:DB$ Sacrifice | UnlessCost$ Discard<1/Card> | UnlessPayer$ You | OrString$ Sacrifice {c:Self}. | SubAbility$ TrigImmediateTrig +SVar:TrigImmediateTrig:DB$ ImmediateTrigger | Execute$ TrigDestroy | TriggerDescription$ When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. +SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Permanent.nonLand+cmcLEX+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls with mana value less or equal to the discarded card +SVar:X:Discarded$CardManaCost +DeckHas:Ability$Discard|Sacrifice +Oracle:First strike, protection from multicolored\nAt the beginning of your upkeep, sacrifice Argentum Masticore unless you discard a card\nWhen you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/kethek_crucible_goliath.txt b/forge-gui/res/cardsfolder/upcoming/kethek_crucible_goliath.txt new file mode 100644 index 00000000000..ce0048c32a0 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/kethek_crucible_goliath.txt @@ -0,0 +1,9 @@ +Name:Kethek, Crucible Goliath +ManaCost:2 R B +Types:Legendary Creature Phyrexian Beast +PT:4/4 +T:Mode$ Phase | Phase$ End of Turn | ValidPlayer$ You | Execute$ TrigSac | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your end step, you may sacrifice another creature. If you do, reveal cards from the top of your library until you reveal a nonlegendary creature card with lesser mana value, put it onto the battlefield, then put the rest on the bottom of your library in a random order. +SVar:TrigSac:AB$ DigUntil | Cost$ Sac<1/Creature.Other/another creature> | Defined$ You | Valid$ Card.Creature+nonLegendary+cmcLTX | FoundDestination$ Battlefield | RevealedDestination$ Library | RestRandomOrder$ True +SVar:X:Sacrificed$CardManaCost +DeckHas:Ability$Sacrifice +Oracle:At the beginning of your end step, you may sacrifice another creature. If you do, reveal cards from the top of your library until you reveal a nonlegendary creature card with lesser mana value, put it onto the battlefield, then put the rest on the bottom of your library in a random order. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/migloz_maze_crusher.txt b/forge-gui/res/cardsfolder/upcoming/migloz_maze_crusher.txt new file mode 100644 index 00000000000..3564563ac96 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/migloz_maze_crusher.txt @@ -0,0 +1,10 @@ +Name:Migloz, Maze Crusher +ManaCost:1 R G +Types:Legendary Creature Phyrexian Beast +PT:4/4 +K:etbCounter:OIL:5 +A:AB$ Pump | Cost$ 1 SubCounter<1/OIL> | Defined$ Self | KW$ Vigilance & Menace | SpellDescription$ It gains vigilance and menace until end of turn. +A:AB$ Pump | Cost$ 2 SubCounter<2/OIL> | Defined$ Self | NumAtt$ 2 | NumDef$ 2 | SpellDescription$ It gets +2/+2 until end of turn. +A:AB$ Destroy | Cost$ 3 SubCounter<3/OIL> | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy target artifact or enchantment. +DeckHas:Ability$Counters +Oracle:Migloz, Maze Crusher enters the battlefield with five oil counters on it.\n{1}, Remove an oil counter from Migloz: It gains vigilance and menace until end of turn.\n{2}, Remove two oil counters from Migloz: It gets +2/+2 until end of turn.\n{3}, Remove three oil counters from Migloz: Destroy target artifact or enchantment. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/mindsplice_apparatus.txt b/forge-gui/res/cardsfolder/upcoming/mindsplice_apparatus.txt new file mode 100644 index 00000000000..aa42080bf7d --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/mindsplice_apparatus.txt @@ -0,0 +1,11 @@ +Name:Mindsplice Apparatus +ManaCost:3 U +Types:Artifact +K:Flash +T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigCounter | TriggerDescription$ At the beginning of your upkeep, put an oil counter on CARDNAME. +SVar:TrigCounter:DB$ PutCounter | Defined$ Self | CounterType$ OIL | CounterNum$ 1 +S:Mode$ ReduceCost | ValidCard$ Instant,Sorcery | Type$ Spell | Activator$ You | Amount$ X | Description$ Instant and sorcery spells you cast cost 1 less to cast for each oil counter on CARDNAME. +SVar:X:Count$CardCounters.OIL +DeckHas:Ability$Counters +DeckHints:Type$Instant|Sorcery +Oracle:At the beginning of your upkeep, put an oil counter on Mindsplice Apparatus.\nInstant and sorcery spells you cast cost 1 less to cast for each oil counter on Mindsplice Apparatus. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/zenith_chronicler.txt b/forge-gui/res/cardsfolder/upcoming/zenith_chronicler.txt new file mode 100644 index 00000000000..e3a9237a46f --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/zenith_chronicler.txt @@ -0,0 +1,7 @@ +Name:Zenith Chronicler +ManaCost:2 +Types:Artifact Creature Phyrexian Construct +PT:3/1 +T:Mode$ SpellCast | ValidCard$ Card.MultiColor | ValidActivatingPlayer$ Player | TriggerZones$ Battlefield | ActivatorThisTurnCast$ EQ1 | NoResolvingCheck$ True | Execute$ TrigDraw | TriggerDescription$ Whenever a player casts their first multicolored spell each turn, each other player draws a card. +SVar:TrigDraw:DB$ Draw | Defined$ TriggeredCardOpponent +Oracle:Whenever a player casts their first multicolored spell each turn, each other player draws a card. \ No newline at end of file From 960b2531a3130a400f88a23740214ea155197ec9 Mon Sep 17 00:00:00 2001 From: Simisays <67333662+Simisays@users.noreply.github.com> Date: Sat, 7 Jan 2023 14:17:25 +0100 Subject: [PATCH 71/76] update --- .../res/cardsfolder/upcoming/argentum_masticore.txt | 9 +++++---- .../res/cardsfolder/upcoming/mindsplice_apparatus.txt | 2 +- forge-gui/res/cardsfolder/upcoming/zenith_chronicler.txt | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt b/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt index b48cc1e5c6a..9586ddfb88d 100644 --- a/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt +++ b/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt @@ -5,9 +5,10 @@ PT:5/5 K:First strike K:Protection:Card.MultiColor:Protection from multicolored T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSacrifice | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you discard a card. When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. -SVar:TrigSacrifice:DB$ Sacrifice | UnlessCost$ Discard<1/Card> | UnlessPayer$ You | OrString$ Sacrifice {c:Self}. | SubAbility$ TrigImmediateTrig -SVar:TrigImmediateTrig:DB$ ImmediateTrigger | Execute$ TrigDestroy | TriggerDescription$ When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. -SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Permanent.nonLand+cmcLEX+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls with mana value less or equal to the discarded card -SVar:X:Discarded$CardManaCost +SVar:TrigSacrifice:DB$ Sacrifice | UnlessCost$ Discard<1/Card> | UnlessPayer$ You | OrString$ Sacrifice {c:Self}. | RememberObjects$ Discarded | SubAbility$ TrigImmediateTrig +SVar:TrigImmediateTrig:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | Execute$ TrigDestroy | TriggerDescription$ When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. +SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Permanent.nonLand+cmcLEX+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls with mana value less or equal to the discarded card | SubAbility$ DBCleanup +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +SVar:X:TriggeredRemembered$CardManaCost DeckHas:Ability$Discard|Sacrifice Oracle:First strike, protection from multicolored\nAt the beginning of your upkeep, sacrifice Argentum Masticore unless you discard a card\nWhen you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/mindsplice_apparatus.txt b/forge-gui/res/cardsfolder/upcoming/mindsplice_apparatus.txt index aa42080bf7d..a35114952b8 100644 --- a/forge-gui/res/cardsfolder/upcoming/mindsplice_apparatus.txt +++ b/forge-gui/res/cardsfolder/upcoming/mindsplice_apparatus.txt @@ -8,4 +8,4 @@ S:Mode$ ReduceCost | ValidCard$ Instant,Sorcery | Type$ Spell | Activator$ You | SVar:X:Count$CardCounters.OIL DeckHas:Ability$Counters DeckHints:Type$Instant|Sorcery -Oracle:At the beginning of your upkeep, put an oil counter on Mindsplice Apparatus.\nInstant and sorcery spells you cast cost 1 less to cast for each oil counter on Mindsplice Apparatus. \ No newline at end of file +Oracle:Flash\nAt the beginning of your upkeep, put an oil counter on Mindsplice Apparatus.\nInstant and sorcery spells you cast cost 1 less to cast for each oil counter on Mindsplice Apparatus. \ No newline at end of file diff --git a/forge-gui/res/cardsfolder/upcoming/zenith_chronicler.txt b/forge-gui/res/cardsfolder/upcoming/zenith_chronicler.txt index e3a9237a46f..1c71f96b130 100644 --- a/forge-gui/res/cardsfolder/upcoming/zenith_chronicler.txt +++ b/forge-gui/res/cardsfolder/upcoming/zenith_chronicler.txt @@ -3,5 +3,5 @@ ManaCost:2 Types:Artifact Creature Phyrexian Construct PT:3/1 T:Mode$ SpellCast | ValidCard$ Card.MultiColor | ValidActivatingPlayer$ Player | TriggerZones$ Battlefield | ActivatorThisTurnCast$ EQ1 | NoResolvingCheck$ True | Execute$ TrigDraw | TriggerDescription$ Whenever a player casts their first multicolored spell each turn, each other player draws a card. -SVar:TrigDraw:DB$ Draw | Defined$ TriggeredCardOpponent +SVar:TrigDraw:DB$ Draw | Defined$ NonTriggeredCardController Oracle:Whenever a player casts their first multicolored spell each turn, each other player draws a card. \ No newline at end of file From c88caf6dbf0aba25ef9bb66a6dbde50c4d0a5fec Mon Sep 17 00:00:00 2001 From: Simisays <67333662+Simisays@users.noreply.github.com> Date: Sat, 7 Jan 2023 14:21:29 +0100 Subject: [PATCH 72/76] Update argentum_masticore.txt --- forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt b/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt index 9586ddfb88d..837bce62865 100644 --- a/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt +++ b/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt @@ -5,8 +5,8 @@ PT:5/5 K:First strike K:Protection:Card.MultiColor:Protection from multicolored T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSacrifice | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you discard a card. When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. -SVar:TrigSacrifice:DB$ Sacrifice | UnlessCost$ Discard<1/Card> | UnlessPayer$ You | OrString$ Sacrifice {c:Self}. | RememberObjects$ Discarded | SubAbility$ TrigImmediateTrig -SVar:TrigImmediateTrig:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | Execute$ TrigDestroy | TriggerDescription$ When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. +SVar:TrigSacrifice:DB$ Sacrifice | UnlessCost$ Discard<1/Card> | UnlessPayer$ You | OrString$ Sacrifice {c:Self}. | SubAbility$ TrigImmediateTrig +SVar:TrigImmediateTrig:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | RememberObjects$ Discarded | Execute$ TrigDestroy | TriggerDescription$ When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Permanent.nonLand+cmcLEX+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls with mana value less or equal to the discarded card | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:X:TriggeredRemembered$CardManaCost From 2ee9d3f355d2d3bb38f53165b0e20fc9a8bc1100 Mon Sep 17 00:00:00 2001 From: Simisays <67333662+Simisays@users.noreply.github.com> Date: Sat, 7 Jan 2023 14:32:44 +0100 Subject: [PATCH 73/76] Update argentum_masticore.txt --- forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt b/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt index 837bce62865..0ec4b698fc3 100644 --- a/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt +++ b/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt @@ -5,10 +5,9 @@ PT:5/5 K:First strike K:Protection:Card.MultiColor:Protection from multicolored T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSacrifice | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you discard a card. When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. -SVar:TrigSacrifice:DB$ Sacrifice | UnlessCost$ Discard<1/Card> | UnlessPayer$ You | OrString$ Sacrifice {c:Self}. | SubAbility$ TrigImmediateTrig +SVar:TrigSacrifice:DB$ Sacrifice | UnlessCost$ Discard<1/Card> | UnlessPayer$ You | OrString$ Sacrifice {c:Self}. | RememberObjects$ Discarded | SubAbility$ TrigImmediateTrig SVar:TrigImmediateTrig:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | RememberObjects$ Discarded | Execute$ TrigDestroy | TriggerDescription$ When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. -SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Permanent.nonLand+cmcLEX+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls with mana value less or equal to the discarded card | SubAbility$ DBCleanup -SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True -SVar:X:TriggeredRemembered$CardManaCost +SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Permanent.nonLand+cmcLEX+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls with mana value less or equal to the discarded card +SVar:X:TriggerRemembered$CardManaCost DeckHas:Ability$Discard|Sacrifice Oracle:First strike, protection from multicolored\nAt the beginning of your upkeep, sacrifice Argentum Masticore unless you discard a card\nWhen you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. \ No newline at end of file From faf0f3f1fd5de4d81dc4eee635ffebccd84af89b Mon Sep 17 00:00:00 2001 From: Simisays <67333662+Simisays@users.noreply.github.com> Date: Sat, 7 Jan 2023 15:31:30 +0100 Subject: [PATCH 74/76] PH21 Arteeoh, Dread Scaveneger (#2211) * Create arteeoh_dread_scavenger.txt * Update arteeoh_dread_scavenger.txt * Update arteeoh_dread_scavenger.txt * Update arteeoh_dread_scavenger.txt * Update arteeoh_dread_scavenger.txt * Update arteeoh_dread_scavenger.txt * Update arteeoh_dread_scavenger.txt --- .../upcoming/arteeoh_dread_scavenger.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 forge-gui/res/cardsfolder/upcoming/arteeoh_dread_scavenger.txt diff --git a/forge-gui/res/cardsfolder/upcoming/arteeoh_dread_scavenger.txt b/forge-gui/res/cardsfolder/upcoming/arteeoh_dread_scavenger.txt new file mode 100644 index 00000000000..3171f634429 --- /dev/null +++ b/forge-gui/res/cardsfolder/upcoming/arteeoh_dread_scavenger.txt @@ -0,0 +1,14 @@ +Name:Arteeoh, Dread Scavenger +ManaCost:1 B G U +Types:Legendary Artifact Creature Robot +PT:3/3 +K:Flying +K:Deathtouch +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigExchange | TriggerDescription$ Whenever NICKNAME deals combat damage to a player, you may exchange control of two other target artifacts. When you do, create a token that's a copy of target artifact you don't control, except it's a 1/1 green Squirrel creature token in addition to its other colors and types. +SVar:TrigExchange:DB$ ExchangeControl | RememberExchanged$ True | ValidTgts$ Artifact.Other | TargetMin$ 2 | TargetMax$ 2 | TgtPrompt$ Choose two other target artifacts | Optional$ True | AILogic$ TrigTwoTargets | SubAbility$ TrigImmediateTrig +SVar:TrigImmediateTrig:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE2 | SubAbility$ DBCleanup | Execute$ TrigToken | TriggerDescription$ When you do, create a token that's a copy of target artifact you don't control, except it's a 1/1 green Squirrel creature token in addition to its other colors and types. +SVar:TrigToken:DB$ CopyPermanent | ValidTgts$ Artifact.YouDontCtrl | TgtPrompt$ Select target artifact you don't control | SetPower$ 1 | SetToughness$ 1 | AddColors$ Green | AddTypes$ Creature & Squirrel +SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +DeckHas:Ability$Token & Type$Squirrel +AI:RemoveDeck:Random +Oracle:Flying, deathtouch\nWhenever Arteeoh deals combat damage to a player, you may exchange control of two other target artifacts. When you do, create a token that's a copy of target artifact you don't control, except it's a 1/1 green Squirrel creature token in addition to its other colors and types. From 4432de4df0754177b37a19549d5051250deede88 Mon Sep 17 00:00:00 2001 From: Simisays <67333662+Simisays@users.noreply.github.com> Date: Sat, 7 Jan 2023 15:41:34 +0100 Subject: [PATCH 75/76] Update migloz_maze_crusher.txt --- .../res/cardsfolder/upcoming/migloz_maze_crusher.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/migloz_maze_crusher.txt b/forge-gui/res/cardsfolder/upcoming/migloz_maze_crusher.txt index 3564563ac96..c0547163bfc 100644 --- a/forge-gui/res/cardsfolder/upcoming/migloz_maze_crusher.txt +++ b/forge-gui/res/cardsfolder/upcoming/migloz_maze_crusher.txt @@ -3,8 +3,8 @@ ManaCost:1 R G Types:Legendary Creature Phyrexian Beast PT:4/4 K:etbCounter:OIL:5 -A:AB$ Pump | Cost$ 1 SubCounter<1/OIL> | Defined$ Self | KW$ Vigilance & Menace | SpellDescription$ It gains vigilance and menace until end of turn. -A:AB$ Pump | Cost$ 2 SubCounter<2/OIL> | Defined$ Self | NumAtt$ 2 | NumDef$ 2 | SpellDescription$ It gets +2/+2 until end of turn. -A:AB$ Destroy | Cost$ 3 SubCounter<3/OIL> | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy target artifact or enchantment. +A:AB$ Pump | Cost$ 1 SubCounter<1/OIL/NICKNAME> | Defined$ Self | KW$ Vigilance & Menace | SpellDescription$ It gains vigilance and menace until end of turn. +A:AB$ Pump | Cost$ 2 SubCounter<2/OIL/NICKNAME> | Defined$ Self | NumAtt$ 2 | NumDef$ 2 | SpellDescription$ It gets +2/+2 until end of turn. +A:AB$ Destroy | Cost$ 3 SubCounter<3/OIL/NICKNAME> | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy target artifact or enchantment. DeckHas:Ability$Counters -Oracle:Migloz, Maze Crusher enters the battlefield with five oil counters on it.\n{1}, Remove an oil counter from Migloz: It gains vigilance and menace until end of turn.\n{2}, Remove two oil counters from Migloz: It gets +2/+2 until end of turn.\n{3}, Remove three oil counters from Migloz: Destroy target artifact or enchantment. \ No newline at end of file +Oracle:Migloz, Maze Crusher enters the battlefield with five oil counters on it.\n{1}, Remove an oil counter from Migloz: It gains vigilance and menace until end of turn.\n{2}, Remove two oil counters from Migloz: It gets +2/+2 until end of turn.\n{3}, Remove three oil counters from Migloz: Destroy target artifact or enchantment. From 87c79300d9c4fb095f3a4fcb9170d2d2854066fc Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sat, 7 Jan 2023 16:41:11 +0000 Subject: [PATCH 76/76] Update argentum_masticore.txt --- forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt b/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt index 0ec4b698fc3..d74028da427 100644 --- a/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt +++ b/forge-gui/res/cardsfolder/upcoming/argentum_masticore.txt @@ -5,9 +5,9 @@ PT:5/5 K:First strike K:Protection:Card.MultiColor:Protection from multicolored T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigSacrifice | TriggerDescription$ At the beginning of your upkeep, sacrifice CARDNAME unless you discard a card. When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. -SVar:TrigSacrifice:DB$ Sacrifice | UnlessCost$ Discard<1/Card> | UnlessPayer$ You | OrString$ Sacrifice {c:Self}. | RememberObjects$ Discarded | SubAbility$ TrigImmediateTrig -SVar:TrigImmediateTrig:DB$ ImmediateTrigger | ConditionDefined$ Remembered | ConditionPresent$ Card | ConditionCompare$ GE1 | RememberObjects$ Discarded | Execute$ TrigDestroy | TriggerDescription$ When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. +SVar:TrigSacrifice:DB$ Sacrifice | UnlessCost$ Discard<1/Card> | UnlessPayer$ You | OrString$ Sacrifice it. | SubAbility$ TrigImmediateTrig +SVar:TrigImmediateTrig:DB$ ImmediateTrigger | ConditionDefined$ Discarded | ConditionPresent$ Card | ConditionCompare$ GE1 | RememberObjects$ Discarded | Execute$ TrigDestroy | TriggerDescription$ When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. SVar:TrigDestroy:DB$ Destroy | ValidTgts$ Permanent.nonLand+cmcLEX+OppCtrl | TgtPrompt$ Select target nonland permanent an opponent controls with mana value less or equal to the discarded card SVar:X:TriggerRemembered$CardManaCost DeckHas:Ability$Discard|Sacrifice -Oracle:First strike, protection from multicolored\nAt the beginning of your upkeep, sacrifice Argentum Masticore unless you discard a card\nWhen you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card. \ No newline at end of file +Oracle:First strike, protection from multicolored\nAt the beginning of your upkeep, sacrifice Argentum Masticore unless you discard a card. When you discard a card this way, destroy target nonland permanent an opponent controls with mana value less than or equal to the mana value of the discarded card.