diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java
index e63ae76c366..1df09691ee1 100644
--- a/forge-game/src/main/java/forge/game/CardTraitBase.java
+++ b/forge-game/src/main/java/forge/game/CardTraitBase.java
@@ -561,4 +561,12 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
// this does overwrite the original MapParams
this.originalMapParams = Maps.newHashMap(this.mapParams);
}
+
+ protected void copyHelper(CardTraitBase copy, Card host) {
+ copy.originalMapParams = Maps.newHashMap(originalMapParams);
+ copy.mapParams = Maps.newHashMap(originalMapParams);
+ copy.sVars = Maps.newHashMap(sVars);
+ // dont use setHostCard to not trigger the not copied parts yet
+ copy.hostCard = host;
+ }
}
diff --git a/forge-game/src/main/java/forge/game/GameObjectPredicates.java b/forge-game/src/main/java/forge/game/GameObjectPredicates.java
new file mode 100644
index 00000000000..9331bcb6a89
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/GameObjectPredicates.java
@@ -0,0 +1,44 @@
+/*
+ * Forge: Play Magic: the Gathering.
+ * Copyright (C) 2011 Forge Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package forge.game;
+
+import com.google.common.base.Predicate;
+
+import forge.game.card.Card;
+import forge.game.player.Player;
+import forge.game.spellability.SpellAbility;
+
+
+/**
+ *
+ * Predicate interface.
+ *
+ *
+ * @author Forge
+ */
+public final class GameObjectPredicates {
+
+ public static final Predicate restriction(final String[] restrictions, final Player sourceController, final Card source, final SpellAbility spellAbility) {
+ return new Predicate() {
+ @Override
+ public boolean apply(final GameObject c) {
+ return (c != null) && c.isValid(restrictions, sourceController, source, spellAbility);
+ }
+ };
+ }
+}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java
index c68659e0709..a4e8e6c1217 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DamageAllEffect.java
@@ -122,7 +122,7 @@ public class DamageAllEffect extends DamageBaseEffect {
if (!usedDamageMap) {
preventMap.triggerPreventDamage(false);
- damageMap.triggerDamageDoneOnce(false, sa);
+ damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear();
damageMap.clear();
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java
index 5ba5f4d8203..6092fde9af8 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DamageDealEffect.java
@@ -166,7 +166,7 @@ public class DamageDealEffect extends DamageBaseEffect {
if (!usedDamageMap) {
preventMap.triggerPreventDamage(false);
// non combat damage cause lifegain there
- damageMap.triggerDamageDoneOnce(false, sa);
+ damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear();
damageMap.clear();
@@ -215,7 +215,7 @@ public class DamageDealEffect extends DamageBaseEffect {
if (!usedDamageMap) {
preventMap.triggerPreventDamage(false);
// non combat damage cause lifegain there
- damageMap.triggerDamageDoneOnce(false, sa);
+ damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear();
damageMap.clear();
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 8887b60bfb6..913b6e979a2 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
@@ -136,7 +136,7 @@ public class DamageEachEffect extends DamageBaseEffect {
if (!usedDamageMap) {
preventMap.triggerPreventDamage(false);
- damageMap.triggerDamageDoneOnce(false, sa);
+ damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear();
damageMap.clear();
diff --git a/forge-game/src/main/java/forge/game/ability/effects/DamageResolveEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DamageResolveEffect.java
index 6ed532889a6..a9d7c35a677 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/DamageResolveEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/DamageResolveEffect.java
@@ -25,7 +25,7 @@ public class DamageResolveEffect extends SpellAbilityEffect {
}
// non combat damage cause lifegain there
if (damageMap != null) {
- damageMap.triggerDamageDoneOnce(false, sa);
+ damageMap.triggerDamageDoneOnce(false, sa.getHostCard().getGame(), sa);
damageMap.clear();
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java b/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java
index 1596cbea793..7ae2fc18dc7 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/FightEffect.java
@@ -154,7 +154,7 @@ public class FightEffect extends DamageBaseEffect {
if (!usedDamageMap) {
preventMap.triggerPreventDamage(false);
- damageMap.triggerDamageDoneOnce(false, sa);
+ damageMap.triggerDamageDoneOnce(false, fighterA.getGame(), sa);
preventMap.clear();
damageMap.clear();
diff --git a/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java b/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java
index e7ed46e2b1e..54e32d8e6d6 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/RepeatEachEffect.java
@@ -236,7 +236,7 @@ public class RepeatEachEffect extends SpellAbilityEffect {
sa.getPreventMap().triggerPreventDamage(false);
sa.setPreventMap(null);
// non combat damage cause lifegain there
- sa.getDamageMap().triggerDamageDoneOnce(false, sa);
+ sa.getDamageMap().triggerDamageDoneOnce(false, game, sa);
sa.setDamageMap(null);
}
if (sa.hasParam("ChangeZoneTable")) {
diff --git a/forge-game/src/main/java/forge/game/card/CardDamageMap.java b/forge-game/src/main/java/forge/game/card/CardDamageMap.java
index 8a742dacf68..b5cf899f352 100644
--- a/forge-game/src/main/java/forge/game/card/CardDamageMap.java
+++ b/forge-game/src/main/java/forge/game/card/CardDamageMap.java
@@ -1,16 +1,20 @@
/**
- *
+ *
*/
package forge.game.card;
import java.util.Map;
+import java.util.Set;
import com.google.common.collect.ForwardingTable;
import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
+import forge.game.Game;
import forge.game.GameEntity;
+import forge.game.GameObjectPredicates;
import forge.game.ability.AbilityKey;
import forge.game.keyword.Keyword;
import forge.game.spellability.SpellAbility;
@@ -18,7 +22,7 @@ import forge.game.trigger.TriggerType;
public class CardDamageMap extends ForwardingTable {
private Table dataMap = HashBasedTable.create();
-
+
public CardDamageMap(Table damageMap) {
this.putAll(damageMap);
}
@@ -38,13 +42,13 @@ public class CardDamageMap extends ForwardingTable {
runParams.put(AbilityKey.DamageTarget, ge);
runParams.put(AbilityKey.DamageAmount, sum);
runParams.put(AbilityKey.IsCombatDamage, isCombat);
-
+
ge.getGame().getTriggerHandler().runTrigger(TriggerType.DamagePreventedOnce, runParams, false);
}
}
}
- public void triggerDamageDoneOnce(boolean isCombat, final SpellAbility sa) {
+ public void triggerDamageDoneOnce(boolean isCombat, final Game game, final SpellAbility sa) {
// Source -> Targets
for (Map.Entry> e : this.rowMap().entrySet()) {
final Card sourceLKI = e.getKey();
@@ -58,9 +62,9 @@ public class CardDamageMap extends ForwardingTable {
runParams.put(AbilityKey.DamageTargets, Sets.newHashSet(e.getValue().keySet()));
runParams.put(AbilityKey.DamageAmount, sum);
runParams.put(AbilityKey.IsCombatDamage, isCombat);
-
- sourceLKI.getGame().getTriggerHandler().runTrigger(TriggerType.DamageDealtOnce, runParams, false);
-
+
+ game.getTriggerHandler().runTrigger(TriggerType.DamageDealtOnce, runParams, false);
+
if (sourceLKI.hasKeyword(Keyword.LIFELINK)) {
sourceLKI.getController().gainLife(sum, sourceLKI, sa);
}
@@ -79,10 +83,15 @@ public class CardDamageMap extends ForwardingTable {
runParams.put(AbilityKey.DamageSources, Sets.newHashSet(e.getValue().keySet()));
runParams.put(AbilityKey.DamageAmount, sum);
runParams.put(AbilityKey.IsCombatDamage, isCombat);
-
- ge.getGame().getTriggerHandler().runTrigger(TriggerType.DamageDoneOnce, runParams, false);
+
+ game.getTriggerHandler().runTrigger(TriggerType.DamageDoneOnce, runParams, false);
}
}
+
+ final Map runParams = AbilityKey.newMap();
+ runParams.put(AbilityKey.DamageMap, new CardDamageMap(this));
+ runParams.put(AbilityKey.IsCombatDamage, isCombat);
+ game.getTriggerHandler().runTrigger(TriggerType.DamageAll, runParams, false);
}
/**
* special put logic, sum the values
@@ -98,4 +107,29 @@ public class CardDamageMap extends ForwardingTable {
return dataMap;
}
+ public int filteredAmount(String validSource, String validTarget, Card host, SpellAbility sa) {
+ int result = 0;
+
+ Set filteredSource = null;
+ Set filteredTarget = null;
+ if (validSource != null) {
+ filteredSource = Sets.newHashSet(Iterables.filter(rowKeySet(), GameObjectPredicates.restriction(validSource.split(","), host.getController(), host, sa)));
+ }
+ if (validTarget != null) {
+ filteredTarget = Sets.newHashSet(Iterables.filter(columnKeySet(), GameObjectPredicates.restriction(validTarget.split(","), host.getController(), host, sa)));
+ }
+
+ for (Table.Cell c : cellSet()) {
+ if (filteredSource != null && !filteredSource.contains(c.getRowKey())) {
+ continue;
+ }
+ if (filteredTarget != null && !filteredTarget.contains(c.getColumnKey())) {
+ continue;
+ }
+ result += c.getValue();
+ }
+
+ return result;
+ }
+
}
diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java
index 72099af6fb4..4fc83734d74 100644
--- a/forge-game/src/main/java/forge/game/combat/Combat.java
+++ b/forge-game/src/main/java/forge/game/combat/Combat.java
@@ -836,7 +836,7 @@ public class Combat {
// Run the trigger to deal combat damage once
// LifeLink for Combat Damage at this place
- dealtDamageTo.triggerDamageDoneOnce(true, null);
+ dealtDamageTo.triggerDamageDoneOnce(true, game, null);
dealtDamageTo.clear();
counterTable.triggerCountersPutAll(game);
diff --git a/forge-game/src/main/java/forge/game/cost/CostDamage.java b/forge-game/src/main/java/forge/game/cost/CostDamage.java
index cc33d7c6501..198ac700ea8 100644
--- a/forge-game/src/main/java/forge/game/cost/CostDamage.java
+++ b/forge-game/src/main/java/forge/game/cost/CostDamage.java
@@ -74,7 +74,7 @@ public class CostDamage extends CostPart {
payer.addDamage(decision.c, source, damageMap, preventMap, table, sa);
preventMap.triggerPreventDamage(false);
- damageMap.triggerDamageDoneOnce(false, sa);
+ damageMap.triggerDamageDoneOnce(false, source.getGame(), sa);
table.triggerCountersPutAll(payer.getGame());
preventMap.clear();
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 6bd8dbc3be1..af850385eee 100644
--- a/forge-game/src/main/java/forge/game/player/Player.java
+++ b/forge-game/src/main/java/forge/game/player/Player.java
@@ -1708,7 +1708,8 @@ public class Player extends GameEntity implements Comparable {
if (land.isFaceDown()) {
land.turnFaceUp();
}
- game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null);
+ final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null);
+ game.updateLastStateForCard(c);
// play a sound
game.fireEvent(new GameEventLandPlayed(this, land));
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java
index 34f48bcc28c..2ff6a07d9ec 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplacementEffect.java
@@ -164,9 +164,8 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
*/
public final ReplacementEffect copy(final Card host, final boolean lki) {
final ReplacementEffect res = (ReplacementEffect) clone();
- for (String key : getSVars()) {
- res.setSVar(key, getSVar(key));
- }
+
+ copyHelper(res, host);
final SpellAbility sa = this.getOverridingAbility();
if (sa != null) {
@@ -182,8 +181,6 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
res.setOtherChoices(null);
}
- res.setHostCard(host);
-
res.setActiveZone(validHostZones);
res.setLayer(getLayer());
return res;
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 d467b6c0ffe..75479eaadfc 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
@@ -870,13 +870,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
clone.view = new SpellAbilityView(clone);
// dont use setHostCard to not trigger the not copied parts yet
- clone.hostCard = host;
+
+ copyHelper(clone, host);
if (!lki && host != null && host.getGame() != null) {
host.getGame().addSpellAbility(clone);
}
- // need to clone the maps too so they can be changed
- clone.originalMapParams = Maps.newHashMap(this.originalMapParams);
- clone.mapParams = Maps.newHashMap(this.mapParams);
clone.triggeringObjects = AbilityKey.newMap(this.triggeringObjects);
@@ -902,7 +900,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
// clear maps for copy, the values will be added later
clone.additionalAbilities = Maps.newHashMap();
clone.additionalAbilityLists = Maps.newHashMap();
- clone.sVars = Maps.newHashMap();
// run special copy Ability to make a deep copy
CardFactory.copySpellAbility(this, clone, host, activ, lki);
} catch (final CloneNotSupportedException e) {
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java
index 3f3b33d26b9..f8442310ec0 100644
--- a/forge-game/src/main/java/forge/game/staticability/StaticAbility.java
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbility.java
@@ -819,13 +819,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
clone = (StaticAbility) clone();
clone.id = lki ? id : nextId();
- // dont use setHostCard to not trigger the not copied parts yet
- clone.hostCard = host;
- // need to clone the maps too so they can be changed
- clone.originalMapParams = Maps.newHashMap(this.originalMapParams);
- clone.mapParams = Maps.newHashMap(this.mapParams);
-
- clone.sVars = Maps.newHashMap(this.sVars);
+ copyHelper(clone, host);
clone.layers = this.generateLayer();
diff --git a/forge-game/src/main/java/forge/game/trigger/Trigger.java b/forge-game/src/main/java/forge/game/trigger/Trigger.java
index b9130a8cbd8..8388fa3ccdf 100644
--- a/forge-game/src/main/java/forge/game/trigger/Trigger.java
+++ b/forge-game/src/main/java/forge/game/trigger/Trigger.java
@@ -37,7 +37,6 @@ import forge.game.zone.ZoneType;
import java.util.*;
import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import forge.util.TextUtil;
@@ -523,9 +522,7 @@ public abstract class Trigger extends TriggerReplacementBase {
public final Trigger copy(Card newHost, boolean lki) {
final Trigger copy = (Trigger) clone();
- copy.originalMapParams = Maps.newHashMap(originalMapParams);
- copy.mapParams = Maps.newHashMap(originalMapParams);
- copy.setHostCard(newHost);
+ copyHelper(copy, newHost);
if (getOverridingAbility() != null) {
copy.setOverridingAbility(getOverridingAbility().copy(newHost, lki));
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerDamageAll.java b/forge-game/src/main/java/forge/game/trigger/TriggerDamageAll.java
new file mode 100644
index 00000000000..4df7cf871b8
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerDamageAll.java
@@ -0,0 +1,52 @@
+package forge.game.trigger;
+
+import java.util.Map;
+
+import forge.game.ability.AbilityKey;
+import forge.game.card.Card;
+import forge.game.card.CardDamageMap;
+import forge.game.spellability.SpellAbility;
+import forge.util.Localizer;
+
+public class TriggerDamageAll extends Trigger {
+
+ public TriggerDamageAll(Map params, Card host, boolean intrinsic) {
+ super(params, host, intrinsic);
+ }
+
+ @Override
+ public boolean performTest(Map runParams) {
+
+ if (hasParam("CombatDamage")) {
+ if (getParam("CombatDamage").equals("True")) {
+ if (!((Boolean) runParams.get(AbilityKey.IsCombatDamage))) {
+ return false;
+ }
+ } else if (getParam("CombatDamage").equals("False")) {
+ if (((Boolean) runParams.get(AbilityKey.IsCombatDamage))) {
+ return false;
+ }
+ }
+ }
+ final CardDamageMap table = (CardDamageMap) runParams.get(AbilityKey.DamageMap);
+ return filterTable(table) > 0;
+ }
+
+ @Override
+ public void setTriggeringObjects(SpellAbility sa, Map runParams) {
+ final CardDamageMap table = (CardDamageMap) runParams.get(AbilityKey.DamageMap);
+
+ sa.setTriggeringObject(AbilityKey.DamageAmount, filterTable(table));
+ }
+
+ @Override
+ public String getImportantStackObjects(SpellAbility sa) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Localizer.getInstance().getMessage("lblAmount")).append(": ").append(sa.getTriggeringObject(AbilityKey.DamageAmount));
+ return sb.toString();
+ }
+
+ private int filterTable(CardDamageMap table) {
+ return table.filteredAmount(getParam("ValidSource"), getParam("ValidTarget"), getHostCard(), null);
+ }
+}
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerType.java b/forge-game/src/main/java/forge/game/trigger/TriggerType.java
index 952a21bb6f9..b0f82c1c153 100644
--- a/forge-game/src/main/java/forge/game/trigger/TriggerType.java
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerType.java
@@ -43,6 +43,7 @@ public enum TriggerType {
CounterRemovedOnce(TriggerCounterRemovedOnce.class),
Crewed(TriggerCrewed.class),
Cycled(TriggerCycled.class),
+ DamageAll(TriggerDamageAll.class),
DamageDealtOnce(TriggerDamageDealtOnce.class),
DamageDone(TriggerDamageDone.class),
DamageDoneOnce(TriggerDamageDoneOnce.class),
diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java
index 20900adcb56..b16504526ba 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java
@@ -17,39 +17,11 @@
*/
package forge.screens.match;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.event.KeyEvent;
-import java.awt.image.BufferedImage;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.atomic.AtomicReference;
-
-import javax.swing.JMenu;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JPopupMenu;
-import javax.swing.KeyStroke;
-import javax.swing.SwingUtilities;
-import javax.swing.event.PopupMenuEvent;
-import javax.swing.event.PopupMenuListener;
-
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-
-import forge.FThreads;
-import forge.GuiBase;
-import forge.ImageCache;
-import forge.LobbyPlayer;
-import forge.Singletons;
-import forge.StaticData;
+import com.google.common.collect.Lists;
+import forge.*;
import forge.assets.FSkinProp;
import forge.card.CardStateName;
import forge.control.KeyboardShortcuts;
@@ -71,51 +43,26 @@ import forge.game.phase.PhaseType;
import forge.game.player.DelayedReveal;
import forge.game.player.IHasIcon;
import forge.game.player.PlayerView;
-import forge.game.spellability.SpellAbility;
-import forge.game.spellability.SpellAbilityStackInstance;
-import forge.game.spellability.SpellAbilityView;
-import forge.game.spellability.StackItemView;
-import forge.game.spellability.TargetChoices;
+import forge.game.spellability.*;
import forge.game.zone.ZoneType;
-import forge.gui.FNetOverlay;
-import forge.gui.GuiChoose;
-import forge.gui.GuiDialog;
-import forge.gui.GuiUtils;
-import forge.gui.SOverlayUtils;
-import forge.gui.framework.DragCell;
-import forge.gui.framework.EDocID;
-import forge.gui.framework.FScreen;
-import forge.gui.framework.ICDoc;
-import forge.gui.framework.IVDoc;
-import forge.gui.framework.SDisplayUtil;
-import forge.gui.framework.SLayoutIO;
-import forge.gui.framework.VEmptyDoc;
+import forge.gui.*;
+import forge.gui.framework.*;
import forge.item.InventoryItem;
import forge.item.PaperCard;
import forge.match.AbstractGuiGame;
import forge.menus.IMenuProvider;
import forge.model.FModel;
import forge.player.PlayerZoneUpdate;
+import forge.player.PlayerZoneUpdates;
import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences;
import forge.properties.ForgePreferences.FPref;
-import forge.screens.match.controllers.CAntes;
-import forge.screens.match.controllers.CCombat;
-import forge.screens.match.controllers.CDetailPicture;
-import forge.screens.match.controllers.CDev;
-import forge.screens.match.controllers.CDock;
-import forge.screens.match.controllers.CLog;
-import forge.screens.match.controllers.CPrompt;
-import forge.screens.match.controllers.CStack;
+import forge.screens.match.controllers.*;
import forge.screens.match.menus.CMatchUIMenus;
import forge.screens.match.views.VField;
import forge.screens.match.views.VHand;
-import forge.toolbox.FButton;
-import forge.toolbox.FLabel;
-import forge.toolbox.FOptionPane;
-import forge.toolbox.FSkin;
+import forge.toolbox.*;
import forge.toolbox.FSkin.SkinImage;
-import forge.toolbox.FTextArea;
import forge.toolbox.imaging.FImagePanel;
import forge.toolbox.imaging.FImagePanel.AutoSizeImageMode;
import forge.toolbox.imaging.FImageUtil;
@@ -123,15 +70,26 @@ import forge.toolbox.special.PhaseIndicator;
import forge.toolbox.special.PhaseLabel;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
+import forge.util.Localizer;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import forge.util.gui.SOptionPane;
-import forge.util.Localizer;
import forge.view.FView;
import forge.view.arcane.CardPanel;
import forge.view.arcane.FloatingZone;
import net.miginfocom.swing.MigLayout;
+import javax.swing.*;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+import java.awt.*;
+import java.awt.event.KeyEvent;
+import java.awt.image.BufferedImage;
+import java.util.List;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.concurrent.atomic.AtomicReference;
+
/**
* Constructs instance of match UI controller, used as a single point of
* top-level control for child UIs. Tasks targeting the view of individual
@@ -459,6 +417,8 @@ public final class CMatchUI
@Override
public Iterable tempShowZones(final PlayerView controller, final Iterable zonesToUpdate) {
+ List updatedPlayerZones = Lists.newArrayList();
+
for (final PlayerZoneUpdate update : zonesToUpdate) {
final PlayerView player = update.getPlayer();
for (final ZoneType zone : update.getZones()) {
@@ -467,7 +427,9 @@ public final class CMatchUI
break;
case Hand: // controller hand always shown
if (controller != player) {
- FloatingZone.show(this,player,zone);
+ if (FloatingZone.show(this,player,zone)) {
+ updatedPlayerZones.add(update);
+ }
}
break;
case Library:
@@ -475,14 +437,16 @@ public final class CMatchUI
case Exile:
case Flashback:
case Command:
- FloatingZone.show(this,player,zone);
+ if (FloatingZone.show(this,player,zone)) {
+ updatedPlayerZones.add(update);
+ }
break;
default:
break;
}
}
}
- return zonesToUpdate; //pfps should return only the newly shown zones
+ return updatedPlayerZones;
}
@Override
@@ -1086,21 +1050,30 @@ public final class CMatchUI
}
@Override
- public boolean openZones(final Collection zones, final Map players) {
- if (zones.size() == 1) {
- switch (zones.iterator().next()) {
- case Battlefield:
- case Hand:
- return true; //don't actually need to open anything, but indicate that zone can be opened
- default:
- return false;
+ public PlayerZoneUpdates openZones(PlayerView controller, final Collection zones, final Map playersWithTargetables) {
+ final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
+ for (final PlayerView view : playersWithTargetables.keySet()) {
+ for(final ZoneType zone : zones) {
+ if (zone.equals(ZoneType.Battlefield) || zone.equals(ZoneType.Hand)) {
+ continue;
+ }
+
+ if (zone.equals(ZoneType.Stack)) {
+ // TODO: Remove this if we have ever have a Stack zone that's displayable for Counters
+ continue;
+ }
+
+ zonesToUpdate.add(new PlayerZoneUpdate(view, zone));
}
}
- return false;
+
+ tempShowZones(controller, zonesToUpdate);
+ return zonesToUpdate;
}
@Override
- public void restoreOldZones(final Map playersToRestoreZonesFor) {
+ public void restoreOldZones(PlayerView playerView, PlayerZoneUpdates playerZoneUpdates) {
+ hideZones(playerView, playerZoneUpdates);
}
@Override
diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java b/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java
index a80b635e0fe..beaa4601708 100644
--- a/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java
+++ b/forge-gui-desktop/src/main/java/forge/toolbox/FSkin.java
@@ -61,6 +61,9 @@ import forge.util.Localizer;
*/
public class FSkin {
+ public static final int SYMBOL_WIDTH = 13;
+ public static final int SYMBOL_HEIGHT = 13;
+
/**
* Retrieves a color from this skin's color map.
*
@@ -77,7 +80,7 @@ public class FSkin {
*
* @param clr0 {@link java.awt.Color}
* @param step int
- * @return {@link java.awt.Color}
+ * @return {@link java.awt.CFaceolor}
*/
public static Color stepColor(final Color clr0, final int step) {
int r = clr0.getRed();
@@ -1006,7 +1009,7 @@ public class FSkin {
private static void addEncodingSymbol(final String key, final FSkinProp skinProp) {
final String path = ForgeConstants.CACHE_SYMBOLS_DIR + "/" + key.replace("/", "") + ".png";
- getImage(skinProp).save(path, 13, 13);
+ getImage(skinProp).save(path, SYMBOL_WIDTH, SYMBOL_HEIGHT);
}
public static String encodeSymbols(String str, final boolean formatReminderText) {
@@ -1023,7 +1026,7 @@ public class FSkin {
//format mana symbols to display as icons
pattern = "\\{([A-Z0-9]+)\\}|\\{([A-Z0-9]+)/([A-Z0-9]+)\\}"; //fancy pattern needed so "/" can be omitted from replacement
try {
- replacement = "
";
+ replacement = "
";
str = str.replaceAll(pattern, replacement);
} catch (final MalformedURLException e) {
e.printStackTrace();
diff --git a/forge-gui-desktop/src/main/java/forge/view/arcane/FloatingCardArea.java b/forge-gui-desktop/src/main/java/forge/view/arcane/FloatingCardArea.java
index d686c6bb341..c5282b053b5 100644
--- a/forge-gui-desktop/src/main/java/forge/view/arcane/FloatingCardArea.java
+++ b/forge-gui-desktop/src/main/java/forge/view/arcane/FloatingCardArea.java
@@ -17,18 +17,6 @@
*/
package forge.view.arcane;
-import java.awt.Point;
-import java.awt.Rectangle;
-import java.awt.Component;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.MouseEvent;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.ScrollPaneConstants;
-import javax.swing.Timer;
-
import forge.Singletons;
import forge.game.card.CardView;
import forge.gui.framework.SDisplayUtil;
@@ -39,10 +27,19 @@ import forge.screens.match.CMatchUI;
import forge.toolbox.FMouseAdapter;
import forge.toolbox.FScrollPane;
import forge.toolbox.MouseTriggerEvent;
-//import forge.util.collect.FCollectionView;
import forge.view.FDialog;
import forge.view.FFrame;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+//import forge.util.collect.FCollectionView;
+
// show some cards in a new window
public abstract class FloatingCardArea extends CardArea {
@@ -67,18 +64,20 @@ public abstract class FloatingCardArea extends CardArea {
getWindow().setFocusableWindowState(false); // should probably do this earlier
getWindow().setVisible(true);
}
+
protected void hideWindow() {
onShow();
getWindow().setFocusableWindowState(false); // should probably do this earlier
getWindow().setVisible(false);
- getWindow().dispose(); //pfps so that old content does not show up
+ getWindow().dispose(); //pfps so that old content does not show up
}
+
protected void showOrHideWindow() {
- if (getWindow().isVisible()) {
- hideWindow();
- } else {
- showWindow();
- }
+ if (getWindow().isVisible()) {
+ hideWindow();
+ } else {
+ showWindow();
+ }
}
protected void onShow() {
if (!hasBeenShown) {
@@ -98,24 +97,31 @@ public abstract class FloatingCardArea extends CardArea {
if (hasBeenShown || locLoaded) { return; }
super.setLocationRelativeTo(c);
}
+
@Override
public void setVisible(boolean b0) {
- if (isVisible() == b0) { return; }
+ if (isVisible() == b0) {
+ return;
+ }
if (!b0 && hasBeenShown && locPref != null) {
//update preference before hiding window, as otherwise its location will be 0,0
prefs.setPref(locPref,
getX() + COORD_DELIM + getY() + COORD_DELIM +
- getWidth() + COORD_DELIM + getHeight());
+ getWidth() + COORD_DELIM + getHeight());
//don't call prefs.save(), instead allowing them to be saved when match ends
}
if (b0) {
- doRefresh(); // force a refresh before showing to pick up any changes when hidden
+ doRefresh(); // force a refresh before showing to pick up any changes when hidden
hasBeenShown = true;
- }
+ }
super.setVisible(b0);
}
};
+ public boolean isVisible() {
+ return window.isVisible();
+ }
+
protected FDialog getWindow() {
return window;
}
diff --git a/forge-gui-desktop/src/main/java/forge/view/arcane/FloatingZone.java b/forge-gui-desktop/src/main/java/forge/view/arcane/FloatingZone.java
index 4dba33b85f2..8efeceef05f 100644
--- a/forge-gui-desktop/src/main/java/forge/view/arcane/FloatingZone.java
+++ b/forge-gui-desktop/src/main/java/forge/view/arcane/FloatingZone.java
@@ -6,38 +6,37 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package forge.view.arcane;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Collections;
-import java.util.Comparator;
-
-import java.awt.event.MouseEvent;
-import javax.swing.ScrollPaneConstants;
-import javax.swing.WindowConstants;
-
+import forge.FThreads;
import forge.assets.FSkinProp;
import forge.game.card.CardView;
import forge.game.player.PlayerView;
import forge.game.zone.ZoneType;
import forge.properties.ForgePreferences.FPref;
import forge.screens.match.CMatchUI;
-import forge.toolbox.FScrollPane;
import forge.toolbox.FMouseAdapter;
+import forge.toolbox.FScrollPane;
import forge.toolbox.FSkin;
import forge.util.Lang;
import forge.util.collect.FCollection;
+import javax.swing.*;
+import java.awt.event.MouseEvent;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
public class FloatingZone extends FloatingCardArea {
private static final long serialVersionUID = 1927906492186378596L;
@@ -46,18 +45,44 @@ public class FloatingZone extends FloatingCardArea {
private static int getKey(final PlayerView player, final ZoneType zone) {
return 40 * player.getId() + zone.hashCode();
}
+
public static void showOrHide(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
final FloatingZone cardArea = _init(matchUI, player, zone);
cardArea.showOrHideWindow();
}
- public static void show(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
+
+ public static boolean show(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
final FloatingZone cardArea = _init(matchUI, player, zone);
- cardArea.showWindow();
+
+ if (cardArea.isVisible()) {
+ return false;
+ }
+
+ FThreads.invokeInEdtNowOrLater(new Runnable() {
+ @Override public void run() {
+ cardArea.showWindow();
+ }
+ });
+
+ return true;
}
- public static void hide(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
+
+ public static boolean hide(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
final FloatingZone cardArea = _init(matchUI, player, zone);
- cardArea.hideWindow();
+
+ if (!cardArea.isVisible()) {
+ return false;
+ }
+
+ FThreads.invokeInEdtNowOrLater(new Runnable() {
+ @Override public void run() {
+ cardArea.hideWindow();
+ }
+ });
+
+ return true;
}
+
private static FloatingZone _init(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
final int key = getKey(player, zone);
FloatingZone cardArea = floatingAreas.get(key);
@@ -69,10 +94,12 @@ public class FloatingZone extends FloatingCardArea {
}
return cardArea;
}
+
public static CardPanel getCardPanel(final CMatchUI matchUI, final CardView card) {
final FloatingZone window = _init(matchUI, card.getController(), card.getZone());
return window.getCardPanel(card.getId());
}
+
public static void refresh(final PlayerView player, final ZoneType zone) {
FloatingZone cardArea = floatingAreas.get(getKey(player, zone));
if (cardArea != null) {
@@ -82,21 +109,23 @@ public class FloatingZone extends FloatingCardArea {
//refresh flashback zone when graveyard, library, or exile zones updated
switch (zone) {
- case Graveyard:
- case Library:
- case Exile:
- refresh(player, ZoneType.Flashback);
- break;
- default:
- break;
+ case Graveyard:
+ case Library:
+ case Exile:
+ refresh(player, ZoneType.Flashback);
+ break;
+ default:
+ break;
}
}
+
public static void closeAll() {
for (final FloatingZone cardArea : floatingAreas.values()) {
cardArea.window.setVisible(false);
}
floatingAreas.clear();
}
+
public static void refreshAll() {
for (final FloatingZone cardArea : floatingAreas.values()) {
cardArea.refresh();
@@ -110,23 +139,23 @@ public class FloatingZone extends FloatingCardArea {
protected FCollection cardList;
private final Comparator comp = new Comparator() {
- @Override
- public int compare(CardView lhs, CardView rhs) {
- if ( !getMatchUI().mayView(lhs) ) {
- return ( getMatchUI().mayView(rhs) ) ? 1 : 0 ;
- } else if ( !getMatchUI().mayView(rhs) ) {
- return -1;
- } else {
- return lhs.getName().compareTo(rhs.getName());
- }
- }
- };
+ @Override
+ public int compare(CardView lhs, CardView rhs) {
+ if (!getMatchUI().mayView(lhs)) {
+ return (getMatchUI().mayView(rhs)) ? 1 : 0;
+ } else if (!getMatchUI().mayView(rhs)) {
+ return -1;
+ } else {
+ return lhs.getName().compareTo(rhs.getName());
+ }
+ }
+ };
protected Iterable getCards() {
Iterable zoneCards = player.getCards(zone);
- if ( zoneCards != null ) {
+ if (zoneCards != null) {
cardList = new FCollection<>(zoneCards);
- if ( sortedByName ) {
+ if (sortedByName) {
Collections.sort(cardList, comp);
}
return cardList;
@@ -138,28 +167,28 @@ public class FloatingZone extends FloatingCardArea {
private FloatingZone(final CMatchUI matchUI, final PlayerView player0, final ZoneType zone0) {
super(matchUI, new FScrollPane(false, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER));
window.add(getScrollPane(), "grow, push");
- window.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); //pfps so that old content does not reappear?
+ window.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); //pfps so that old content does not reappear?
getScrollPane().setViewportView(this);
setOpaque(false);
switch (zone0) {
- case Exile:
- window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_EXILE));
- break;
- case Graveyard:
- window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_GRAVEYARD));
- break;
- case Hand:
- window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_HAND));
- break;
- case Library:
- window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_LIBRARY));
- break;
- case Flashback:
- window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_FLASHBACK));
- break;
- default:
- locPref = null;
- break;
+ case Exile:
+ window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_EXILE));
+ break;
+ case Graveyard:
+ window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_GRAVEYARD));
+ break;
+ case Hand:
+ window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_HAND));
+ break;
+ case Library:
+ window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_LIBRARY));
+ break;
+ case Flashback:
+ window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_FLASHBACK));
+ break;
+ default:
+ locPref = null;
+ break;
}
zone = zone0;
setPlayer(player0);
@@ -167,19 +196,20 @@ public class FloatingZone extends FloatingCardArea {
}
private void toggleSorted() {
- sortedByName = !sortedByName;
- setTitle();
- refresh();
- // revalidation does not appear to be necessary here
- getWindow().repaint();
+ sortedByName = !sortedByName;
+ setTitle();
+ refresh();
+ // revalidation does not appear to be necessary here
+ getWindow().repaint();
}
@Override
protected void onShow() {
- super.onShow();
+ super.onShow();
if (!hasBeenShown) {
getWindow().getTitleBar().addMouseListener(new FMouseAdapter() {
- @Override public final void onRightClick(final MouseEvent e) {
+ @Override
+ public final void onRightClick(final MouseEvent e) {
toggleSorted();
}
});
@@ -188,34 +218,36 @@ public class FloatingZone extends FloatingCardArea {
private void setTitle() {
title = Lang.getPossessedObject(player.getName(), zone.name()) + " (%d)" +
- ( sortedByName ? " - sorted by name (right click in title to not sort)" : " (right click in title to sort)" ) ;
- }
+ (sortedByName ? " - sorted by name (right click in title to not sort)" : " (right click in title to sort)");
+ }
private void setPlayer(PlayerView player0) {
- if (player == player0) { return; }
+ if (player == player0) {
+ return;
+ }
player = player0;
- setTitle();
+ setTitle();
boolean isAi = player0.isAI();
switch (zone) {
- case Exile:
- locPref = isAi ? FPref.ZONE_LOC_AI_EXILE : FPref.ZONE_LOC_HUMAN_EXILE;
- break;
- case Graveyard:
- locPref = isAi ? FPref.ZONE_LOC_AI_GRAVEYARD : FPref.ZONE_LOC_HUMAN_GRAVEYARD;
- break;
- case Hand:
- locPref = isAi ? FPref.ZONE_LOC_AI_HAND : FPref.ZONE_LOC_HUMAN_HAND;
- break;
- case Library:
- locPref = isAi ? FPref.ZONE_LOC_AI_LIBRARY : FPref.ZONE_LOC_HUMAN_LIBRARY;
- break;
- case Flashback:
- locPref = isAi ? FPref.ZONE_LOC_AI_FLASHBACK : FPref.ZONE_LOC_HUMAN_FLASHBACK;
- break;
- default:
- locPref = null;
- break;
+ case Exile:
+ locPref = isAi ? FPref.ZONE_LOC_AI_EXILE : FPref.ZONE_LOC_HUMAN_EXILE;
+ break;
+ case Graveyard:
+ locPref = isAi ? FPref.ZONE_LOC_AI_GRAVEYARD : FPref.ZONE_LOC_HUMAN_GRAVEYARD;
+ break;
+ case Hand:
+ locPref = isAi ? FPref.ZONE_LOC_AI_HAND : FPref.ZONE_LOC_HUMAN_HAND;
+ break;
+ case Library:
+ locPref = isAi ? FPref.ZONE_LOC_AI_LIBRARY : FPref.ZONE_LOC_HUMAN_LIBRARY;
+ break;
+ case Flashback:
+ locPref = isAi ? FPref.ZONE_LOC_AI_FLASHBACK : FPref.ZONE_LOC_HUMAN_FLASHBACK;
+ break;
+ default:
+ locPref = null;
+ break;
}
}
diff --git a/forge-gui-mobile/src/forge/screens/match/MatchController.java b/forge-gui-mobile/src/forge/screens/match/MatchController.java
index 67032a0563b..8cc153d9ac4 100644
--- a/forge-gui-mobile/src/forge/screens/match/MatchController.java
+++ b/forge-gui-mobile/src/forge/screens/match/MatchController.java
@@ -5,7 +5,6 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import forge.FThreads;
import forge.assets.FSkinImage;
@@ -44,6 +43,7 @@ import forge.match.AbstractGuiGame;
import forge.match.HostedMatch;
import forge.model.FModel;
import forge.player.PlayerZoneUpdate;
+import forge.player.PlayerZoneUpdates;
import forge.properties.ForgePreferences;
import forge.properties.ForgePreferences.FPref;
import forge.screens.match.views.VAssignDamage;
@@ -307,40 +307,51 @@ public class MatchController extends AbstractGuiGame {
}
@Override
- public boolean openZones(final Collection zones, final Map players) {
+ public PlayerZoneUpdates openZones(PlayerView controller, final Collection zones, final Map playersWithTargetables) {
+ PlayerZoneUpdates updates = new PlayerZoneUpdates();
if (zones.size() == 1) {
final ZoneType zoneType = zones.iterator().next();
switch (zoneType) {
case Battlefield:
case Command:
- players.clear(); //clear since no zones need to be restored
- return true; //Battlefield is always open
+ playersWithTargetables.clear(); //clear since no zones need to be restored
default:
//open zone tab for given zone if needed
boolean result = true;
- for (final PlayerView player : players.keySet()) {
+ for (final PlayerView player : playersWithTargetables.keySet()) {
final VPlayerPanel playerPanel = view.getPlayerPanel(player);
- players.put(player, playerPanel.getSelectedTab()); //backup selected tab before changing it
+ playersWithTargetables.put(player, playerPanel.getSelectedTab()); //backup selected tab before changing it
final InfoTab zoneTab = playerPanel.getZoneTab(zoneType);
- if (zoneTab == null) {
- result = false;
- } else {
+ ZoneType previousZone = playerPanel.getZoneByInfoTab(playerPanel.getSelectedTab());
+ updates.add(new PlayerZoneUpdate(player, previousZone));
+ if (zoneTab != null) {
playerPanel.setSelectedTab(zoneTab);
}
}
- return result;
}
}
- return false;
+ return updates;
}
@Override
- public void restoreOldZones(final Map playersToRestoreZonesFor) {
- for (final Entry player : playersToRestoreZonesFor.entrySet()) {
- final VPlayerPanel playerPanel = view.getPlayerPanel(player.getKey());
- if (player.getValue() == null || player.getValue() instanceof InfoTab) {
- playerPanel.setSelectedTab((InfoTab) player.getValue());
+ public void restoreOldZones(PlayerView playerView, PlayerZoneUpdates playerZoneUpdates) {
+ for(PlayerZoneUpdate update : playerZoneUpdates) {
+ PlayerView player = update.getPlayer();
+
+ ZoneType zone = null;
+ for(ZoneType type : update.getZones()) {
+ zone = type;
+ break;
}
+
+ final VPlayerPanel playerPanel = view.getPlayerPanel(player);
+ if (zone == null) {
+ playerPanel.hideSelectedTab();
+ continue;
+ }
+
+ final InfoTab zoneTab = playerPanel.getZoneTab(zone);
+ playerPanel.setSelectedTab(zoneTab);
}
}
@@ -381,7 +392,7 @@ public class MatchController extends AbstractGuiGame {
@Override
public void hideZones(final PlayerView controller, final Iterable zonesToUpdate) {
- view.hideZones(controller, zonesToUpdate);
+ view.hideZones(controller, zonesToUpdate);
}
@Override
diff --git a/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java b/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java
index ee7e4c40967..3d24cc4d8e8 100644
--- a/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java
+++ b/forge-gui-mobile/src/forge/screens/match/views/VPlayerPanel.java
@@ -102,18 +102,32 @@ public class VPlayerPanel extends FContainer {
return zoneTabs.get(zoneType);
}
+ public ZoneType getZoneByInfoTab(InfoTab tab) {
+ for(ZoneType zone : zoneTabs.keySet()) {
+ if (zoneTabs.get(zone).equals(tab)) {
+ return zone;
+ }
+ }
+
+ return null;
+ }
+
public void setSelectedZone(ZoneType zoneType) {
setSelectedTab(zoneTabs.get(zoneType));
}
+ public void hideSelectedTab() {
+ if (selectedTab != null) {
+ selectedTab.displayArea.setVisible(false);
+ }
+ }
+
public void setSelectedTab(InfoTab selectedTab0) {
if (selectedTab == selectedTab0) {
return;
}
- if (selectedTab != null) {
- selectedTab.displayArea.setVisible(false);
- }
+ hideSelectedTab();
selectedTab = selectedTab0;
diff --git a/forge-gui-mobile/src/forge/screens/match/views/VStack.java b/forge-gui-mobile/src/forge/screens/match/views/VStack.java
index f131139b806..7d9c5639694 100644
--- a/forge-gui-mobile/src/forge/screens/match/views/VStack.java
+++ b/forge-gui-mobile/src/forge/screens/match/views/VStack.java
@@ -31,6 +31,7 @@ import forge.menu.FDropDown;
import forge.menu.FMenuItem;
import forge.menu.FMenuTab;
import forge.menu.FPopupMenu;
+import forge.player.PlayerZoneUpdates;
import forge.screens.match.MatchController;
import forge.screens.match.TargetingOverlay;
import forge.toolbox.FCardPanel;
@@ -55,6 +56,7 @@ public class VStack extends FDropDown {
private StackInstanceDisplay activeItem;
private StackItemView activeStackInstance;
private Map playersWithValidTargets;
+ private PlayerZoneUpdates restorablePlayerZones = null;
private int stackSize;
@@ -70,6 +72,8 @@ public class VStack extends FDropDown {
private void revealTargetZones() {
if (activeStackInstance == null) { return; }
+ PlayerView player = MatchController.instance.getCurrentPlayer();
+
final Set zones = new HashSet<>();
playersWithValidTargets = new HashMap<>();
for (final CardView c : activeStackInstance.getTargetCards()) {
@@ -79,14 +83,15 @@ public class VStack extends FDropDown {
}
}
if (zones.isEmpty() || playersWithValidTargets.isEmpty()) { return; }
- MatchController.instance.openZones(zones, playersWithValidTargets);
+ restorablePlayerZones = MatchController.instance.openZones(player, zones, playersWithValidTargets);
}
//restore old zones when active stack instance changes
private void restoreOldZones() {
- if (playersWithValidTargets == null) { return; }
- MatchController.instance.restoreOldZones(playersWithValidTargets);
- playersWithValidTargets = null;
+ if (restorablePlayerZones == null) { return; }
+ PlayerView player = MatchController.instance.getCurrentPlayer();
+ MatchController.instance.restoreOldZones(player, restorablePlayerZones);
+ restorablePlayerZones = null;
}
@Override
diff --git a/forge-gui/release-files/ANNOUNCEMENTS.txt b/forge-gui/release-files/ANNOUNCEMENTS.txt
index 64bb4abaa7f..aa8d1a24122 100644
--- a/forge-gui/release-files/ANNOUNCEMENTS.txt
+++ b/forge-gui/release-files/ANNOUNCEMENTS.txt
@@ -1,9 +1,6 @@
#Add one announcement per line
-Theros Beyond Death Prerelease!
-"Prerelease" limited mode for latest sets
+We believe the issue with 1.8.0_211 or greater have been resolved. Let us know if you are still on the latest version and things are better now.
Bunches of bug fixes,
Continued work on Translations
[b]Forge now requires Java 8 (or newer). You will not be able to start the game if you are not yet running Java 8.[/b]
-For some reason Oracle hates Forge and version 1.8.0_211 does bad things with Forge for unknown reasons. Downgrade to 202 for a beter time.
-https://www.oracle.com/technetwork/java/javase/downloads/java-archive-javase8-2177648.html
We have a Discord server for hanging out with Forge devs and other Forge fans. Feel free to [url=https://discord.gg/3v9JCVr]jump on in and say hi[/url]!
\ No newline at end of file
diff --git a/forge-gui/release-files/CONTRIBUTORS.txt b/forge-gui/release-files/CONTRIBUTORS.txt
index ffae9fb7a4b..a10b0182abb 100644
--- a/forge-gui/release-files/CONTRIBUTORS.txt
+++ b/forge-gui/release-files/CONTRIBUTORS.txt
@@ -33,6 +33,7 @@ tjtillman
tojammot
torridus
Xyx
+Zimtente
Zuchinni
(If you think your name should be on this list, add it with your next contribution)
\ No newline at end of file
diff --git a/forge-gui/res/blockdata/printsheets.txt b/forge-gui/res/blockdata/printsheets.txt
index 07e25c8c98e..c98ef6015bd 100644
--- a/forge-gui/res/blockdata/printsheets.txt
+++ b/forge-gui/res/blockdata/printsheets.txt
@@ -1556,6 +1556,39 @@ Evolving Wilds
13 Swamp|M20
[ELD Secret Cards]
+Garruk, Cursed Huntsman|ELD|2
+Oko, Thief of Crowns|ELD|2
+The Royal Scions|ELD|2
+Ardenvale Tactician|ELD|2
+Faerie Guidemother|ELD|2
+Giant Killer|ELD|2
+Lonesome Unicorn|ELD|2
+Realm-Cloaked Giant|ELD|2
+Shepherd of the Flock|ELD|2
+Silverflame Squire|ELD|2
+Animating Faerie|ELD|2
+Brazen Borrower|ELD|2
+Fae of Wishes|ELD|2
+Hypnotic Sprite|ELD|2
+Merfolk Secretkeeper|ELD|2
+Queen of Ice|ELD|2
+Foulmire Knight|ELD|2
+Murderous Rider|ELD|2
+Order of Midnight|ELD|2
+Reaper of Night|ELD|2
+Smitten Swordmaster|ELD|2
+Bonecrusher Giant|ELD|2
+Embereth Shieldbreaker|ELD|2
+Merchant of the Vale|ELD|2
+Rimrock Knight|ELD|2
+Beanstalk Giant|ELD|2
+Curious Pair|ELD|2
+Flaxen Intruder|ELD|2
+Garenbrig Carver|ELD|2
+Lovestruck Beast|ELD|2
+Rosethorn Acolyte|ELD|2
+Tuinvale Treefolk|ELD|2
+Oakhame Ranger|ELD|2
Kenrith, the Returned King
Rowan, Fearless Sparkmage
Garrison Griffin
@@ -1587,6 +1620,70 @@ Syr Gwyn, Hero of Ashvale
Arcane Signet
Tome of Legends
Command Tower
+Acclaimed Contender|ELD|2
+Charming Prince|ELD|2
+The Circle of Loyalty|ELD|2
+Happily Ever After|ELD|2
+Harmonious Archon|ELD|2
+Hushbringer|ELD|2
+Linden, the Steadfast Queen|ELD|2
+Worthy Knight|ELD|2
+Emry, Lurker of the Loch|ELD|2
+Folio of Fancies|ELD|2
+Gadwick, the Wizened|ELD|2
+The Magic Mirror|ELD|2
+Midnight Clock|ELD|2
+Mirrormade|ELD|2
+Stolen by the Fae|ELD|2
+Vantress Gargoyle|ELD|2
+Ayara, First of Locthwain|ELD|2
+Blacklance Paragon|ELD|2
+The Cauldron of Eternity|ELD|2
+Clackbridge Troll|ELD|2
+Oathsworn Knight|ELD|2
+Piper of the Swarm|ELD|2
+Rankle, Master of Pranks|ELD|2
+Wishclaw Talisman|ELD|2
+Witch's Vengeance|ELD|2
+Embercleave|ELD|2
+Fervent Champion|ELD|2
+Fires of Invention|ELD|2
+Irencrag Feat|ELD|2
+Irencrag Pyromancer|ELD|2
+Opportunistic Dragon|ELD|2
+Robber of the Rich|ELD|2
+Sundering Stroke|ELD|2
+Torbran, Thane of Red Fell|ELD|2
+Feasting Troll King|ELD|2
+Gilded Goose|ELD|2
+The Great Henge|ELD|2
+Once Upon A Time|ELD|2
+Questing Beast|ELD|2
+Return of the Wildspeaker|ELD|2
+Wicked Wolf|ELD|2
+Wildborn Preserver|ELD|2
+Yorvo, Lord of Garenbrig|ELD|2
+Dance of the Manse|ELD|2
+Doom Foretold|ELD|2
+Escape to the Wilds|ELD|2
+Faeburrow Elder|ELD|2
+Lochmere Serpent|ELD|2
+Outlaws' Merriment|ELD|2
+Stormfist Crusader|ELD|2
+Sorcerous Spyglass|ELD|2
+Stonecoil Serpent|ELD|2
+Castle Ardenvale|ELD|2
+Castle Embereth|ELD|2
+Castle Garenbrig|ELD|2
+Castle Locthwain|ELD|2
+Castle Vantress|ELD|2
+Fabled Passage|ELD|2
+Piper of the Swarm|ELD|3
+Glass Casket|ELD|2
+Slaying Fire|ELD|2
+Kenrith's Transformation|ELD|2
+Improbable Alliance|ELD|2
+Inspiring Veteran|ELD|2
[THB Secret Cards]
Athreos, Shroud-Veiled
@@ -1627,6 +1724,7 @@ Eidolon of Obstruction|THB|2
Heliod's Intervention|THB|2
Idyllic Tutor|THB|2
Shatter the Sky|THB|2
+Taranika, Akroan Veteran|THB|2
Ashiok's Erasure|THB|2
Nadir Kraken|THB|2
Protean Thaumaturge|THB|2
@@ -1674,4 +1772,10 @@ Temple of Abandon|THB|2
Temple of Deceit|THB|2
Temple of Enlightenment|THB|2
Temple of Malice|THB|2
-Temple of Plenty|THB|2
\ No newline at end of file
+Temple of Plenty|THB|2
+Arasta of the Endless Web|THB|3
+Alseid of Life's Bounty|THB|2
+Thirst For Meaning|THB|2
+Gray Merchant of Asphodel|THB|2
+Thrill of Possibility|THB|2
+Wolfwillow Haven|THB|2
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/h/hapatra_vizier_of_poisons.txt b/forge-gui/res/cardsfolder/h/hapatra_vizier_of_poisons.txt
index 163ef02047c..bf96c3ec72a 100644
--- a/forge-gui/res/cardsfolder/h/hapatra_vizier_of_poisons.txt
+++ b/forge-gui/res/cardsfolder/h/hapatra_vizier_of_poisons.txt
@@ -2,7 +2,7 @@ Name:Hapatra, Vizier of Poisons
ManaCost:B G
Types:Legendary Creature Human Cleric
PT:2/2
-T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Opponent | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, you may put a -1/-1 counter on target creature.
+T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Opponent | CombatDamage$ True | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, you may put a -1/-1 counter on target creature.
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ M1M1 | CounterNum$ 1 | IsCurse$ True
T:Mode$ CounterAddedOnce | ValidCard$ Creature | ValidSource$ You | CounterType$ M1M1 | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you put one or more -1/-1 counters on a creature, create a 1/1 green Snake creature token with deathtouch.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_snake_deathtouch | TokenOwner$ You | LegacyImage$ g 1 1 snake deathtouch akh
diff --git a/forge-gui/res/cardsfolder/m/mindblade_render.txt b/forge-gui/res/cardsfolder/m/mindblade_render.txt
new file mode 100644
index 00000000000..0be61532af9
--- /dev/null
+++ b/forge-gui/res/cardsfolder/m/mindblade_render.txt
@@ -0,0 +1,8 @@
+Name:Mindblade Render
+ManaCost:1 B
+Types:Creature Azra Warrior
+PT:1/3
+T:Mode$ DamageAll | ValidSource$ Creature.Warrior | ValidTarget$ Player.Opponent | CombatDamage$ True | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever your opponents are dealt combat damage, if any of that damage was dealt by a Warrior, you draw a card and you lose 1 life.
+SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SubAbility$ DBLoseLife
+SVar:DBLoseLife:DB$LoseLife | Defined$ You | LifeAmount$ 1
+Oracle: Whenever your opponents are dealt combat damage, if any of that damage was dealt by a Warrior, you draw a card and you lose 1 life.
diff --git a/forge-gui/res/cardsfolder/m/mire_in_misery.txt b/forge-gui/res/cardsfolder/m/mire_in_misery.txt
index 49023107ee1..0e26e1e763a 100644
--- a/forge-gui/res/cardsfolder/m/mire_in_misery.txt
+++ b/forge-gui/res/cardsfolder/m/mire_in_misery.txt
@@ -1,5 +1,5 @@
Name:Mire in Misery
ManaCost:1 B
Types:Sorcery
-A:SP$ Sacrifice | Cost$ 1 B | Valid$ Creature,Enchantment | SacMessage$ Creature or Enchantment | Defined$ Player.Opponent | SpellDescription$ Each opponent sacrifices a creature or enchantment.
+A:SP$ Sacrifice | Cost$ 1 B | SacValid$ Creature,Enchantment | SacMessage$ Creature or Enchantment | Defined$ Player.Opponent | SpellDescription$ Each opponent sacrifices a creature or enchantment.
Oracle:Each opponent sacrifices a creature or enchantment.
diff --git a/forge-gui/res/editions/Secret Lair Drop Series.txt b/forge-gui/res/editions/Secret Lair Drop Series.txt
new file mode 100644
index 00000000000..a2eba070cd7
--- /dev/null
+++ b/forge-gui/res/editions/Secret Lair Drop Series.txt
@@ -0,0 +1,61 @@
+[metadata]
+Code=SLD
+Date=2019-12-02
+Name=Secret Lair Drop Series
+MciCode=sld
+Type=Other
+
+[cards]
+1 R Snow-Covered Plains
+2 R Snow-Covered Island
+3 R Snow-Covered Swamp
+4 R Snow-Covered Mountain
+5 R Snow-Covered Forest
+6 R Bloodghast
+7 R Golgari Thug
+8 R Life from the Loam
+9 M Reaper King
+10 M Sliver Overlord
+11 M The Ur-Dragon
+12 M Bitterblossom
+17 R Goblin Bushwhacker
+18 R Goblin Sharpshooter
+19 R Goblin King
+20 R Goblin Lackey
+21 R Goblin Piledriver
+22 R Leonin Warleader
+23 R Regal Caracal
+24 R Qasali Slingers
+25 M Arahbo, Roar of the World
+26 M Mirri, Weatherlight Duelist
+29 R Serum Visions
+30 R Serum Visions
+31 R Serum Visions
+32 R Serum Visions
+33 R Ink-Eyes, Servant of Oni
+34 R Marrow-Gnawer
+35 R Pack Rat
+36 R Rat Colony
+68 M Heliod, God of the Sun
+69 M Karametra, God of Harvests
+70 M Iroas, God of Victory
+71 M Thassa, God of the Sea
+72 M Ephara, God of the Polis
+73 M Kruphix, God of Horizons
+74 M Erebos, God of the Dead
+75 M Phenax, God of Deception
+76 M Athreos, God of Passage
+77 M Purphoros, God of the Forge
+78 M Mogis, God of Slaughter
+79 M Keranos, God of Storms
+80 M Nylea, God of the Hunt
+81 M Xenagos, God of Revels
+82 M Pharika, God of Affliction
+
+[tokens]
+b_1_1_faerie_rogue_flying
+b_1_1_faerie_rogue_flying
+b_1_1_faerie_rogue_flying
+b_1_1_faerie_rogue_flying
+w_1_1_cat_lifelink
+w_1_1_cat_lifelink
\ No newline at end of file
diff --git a/forge-gui/res/editions/Secret Lair Promos.txt b/forge-gui/res/editions/Secret Lair Promos.txt
new file mode 100644
index 00000000000..cd9f2fea783
--- /dev/null
+++ b/forge-gui/res/editions/Secret Lair Promos.txt
@@ -0,0 +1,25 @@
+[metadata]
+Code=PSLD
+Date=2019-12-02
+Name=Secret Lair Promos
+MciCode=sld
+Type=Other
+
+[cards]
+503 M Gideon Blackblade
+504 U Teyo, the Shieldmage
+505 U The Wanderer
+506 R Jace, Wielder of Mysteries
+514 R Sarkhan the Masterless
+516 U Arlinn, Voice of the Pack
+520 R Ajani, the Greathearted
+521 R Domri, Anarch of Bolas
+522 M Nicol Bolas, Dragon-God
+523 R Ral, Storm Conduit
+524 R Sorin, Vengeful Bloodlord
+525 R Tamiyo, Collector of Tales
+526 R Teferi, Time Raveler
+527 U Angrath, Captain of Chaos
+528 U Ashiok, Dream Render
+530 U Huatli, the Sun's Heart
+533 U Nahiri, Storm of Stone
diff --git a/forge-gui/res/editions/Theros Beyond Death.txt b/forge-gui/res/editions/Theros Beyond Death.txt
index edee4e218d8..af7be4b1772 100644
--- a/forge-gui/res/editions/Theros Beyond Death.txt
+++ b/forge-gui/res/editions/Theros Beyond Death.txt
@@ -263,21 +263,25 @@ Prerelease=6 Boosters, 1 RareMythic+
252 L Swamp
253 L Mountain
254 L Forest
-#255 M Elspeth, Sun's Nemesis
-#256 M Ashiok, Nightmare Muse
-#257 M Calix, Destiny's Hand
-#258 U Daxos, Blessed by the Sun
-#259 M Heliod, Sun-Crowned
-#260 U Callaphe, Beloved of the Sea
-#261 M Thassa, Deep-Dwelling
-#262 M Erebos, Bleak-Hearted
-#263 U Tymaret, Chosen from Death
-#264 U Anax, Hardened in the Forge
-#265 M Purphoros, Bronze-Blooded
-#266 M Nylea, Keen-Eyed
-#267 U Renata, Called to the Hunt
-#268 M Klothys, God of Destiny
+#Statue borderless planeswalkers
+255 M Elspeth, Sun's Nemesis
+256 M Ashiok, Nightmare Muse
+#Constellation Gods and Demigods
+257 M Calix, Destiny's Hand
+258 U Daxos, Blessed by the Sun
+259 M Heliod, Sun-Crowned
+260 U Callaphe, Beloved of the Sea
+261 M Thassa, Deep-Dwelling
+262 M Erebos, Bleak-Hearted
+263 U Tymaret, Chosen from Death
+264 U Anax, Hardened in the Forge
+265 M Purphoros, Bronze-Blooded
+266 M Nylea, Keen-Eyed
+267 U Renata, Called to the Hunt
+268 M Klothys, God of Destiny
+#Buy-A-Box Promo
269 M Athreos, Shroud-Veiled
+#Planeswalker Deck Cards
270 M Elspeth, Undaunted Hero
271 U Eidolon of Inspiration
272 R Elspeth's Devotee
@@ -296,6 +300,7 @@ Prerelease=6 Boosters, 1 RareMythic+
285 L Mountain
286 L Forest
287 L Forest
+#Theme Booster Exclusive Rares
288 R Grasping Giant
289 R Victory's Envoy
290 R Sphinx Mindbreaker
@@ -306,59 +311,69 @@ Prerelease=6 Boosters, 1 RareMythic+
295 R Terror of Mount Velus
296 R Ironscale Hydra
297 R Treeshaker Chimera
-#298 R Archon of Sun's Grace
-#299 R Eidolon of Obstruction
-#300 R Heliod's Intervention
-#301 R Idyllic Tutor
-#302 R Shatter the Sky
-#304 R Ashiok's Erasure
-#305 R Nadir Kraken
-#306 R Protean Thaumaturge
-#307 R Thassa's Intervention
-#308 R Thassa's Oracle
-#309 R Thryx, the Sudden Storm
-#310 R Wavebreak Hippocamp
-#311 R Aphemia, the Cacophony
-#312 R Eat to Extinction
-#313 R Erebos's Intervention
-#314 R Gravebreaker Lamia
-#315 R Nightmare Shepherd
-#316 R Treacherous Blessing
-#317 R Woe Strider
-#318 M Ox of Agonas
-#319 R Phoenix of Ash
-#320 R Purphoros's Intervention
-#321 R Storm Herald
-#322 R Storm's Wrath
-#323 R Tectonic Giant
-#324 R Underworld Breach
-#325 R Arasta of the Endless Web
-#326 R Dryad of the Ilysian Grove
-#327 R Mantle of the Wolf
-#328 R Nessian Boar
-#329 R Nylea's Intervention
-#330 M Nyxbloom Ancient
-#331 R Setessan Champion
-#332 R Allure of the Unknown
-#333 R Atris, Oracle of Half-Truths
-#334 R Bronzehide Lion
-#335 R Dalakos, Crafter of Wonders
-#336 R Dream Trawler
-#337 R Enigmatic Incarnation
-#338 R Gallia of the Endless Dance
-#339 R Haktos the Unscarred
-#340 M Kroxa, Titan of Death's Hunger
-#341 R Kunoros, Hound of Athreos
-#342 M Polukranos, Unchained
-#343 M Uro, Titan of Nature's Wrath
-#344 R Nyx Lotus
-#345 R Shadowspear
-#346 R Labyrinth of Skophos
-#347 R Temple of Abandon
-#348 R Temple of Deceit
-#349 R Temple of Enlightenment
-#350 R Temple of Malice
-#351 R Temple of Plenty
+#Borderless art rares and mythics
+298 R Archon of Sun's Grace
+299 R Eidolon of Obstruction
+300 R Heliod's Intervention
+301 R Idyllic Tutor
+302 R Shatter the Sky
+303 R Taranika, Akroan Veteran
+304 R Ashiok's Erasure
+305 R Nadir Kraken
+306 R Protean Thaumaturge
+307 R Thassa's Intervention
+308 R Thassa's Oracle
+309 R Thryx, the Sudden Storm
+310 R Wavebreak Hippocamp
+311 R Aphemia, the Cacophony
+312 R Eat to Extinction
+313 R Erebos's Intervention
+314 R Gravebreaker Lamia
+315 R Nightmare Shepherd
+316 R Treacherous Blessing
+317 R Woe Strider
+318 M Ox of Agonas
+319 R Phoenix of Ash
+320 R Purphoros's Intervention
+321 R Storm Herald
+322 R Storm's Wrath
+323 R Tectonic Giant
+324 R Underworld Breach
+325 R Arasta of the Endless Web
+326 R Dryad of the Ilysian Grove
+327 R Mantle of the Wolf
+328 R Nessian Boar
+329 R Nylea's Intervention
+330 M Nyxbloom Ancient
+331 R Setessan Champion
+332 R Allure of the Unknown
+333 R Atris, Oracle of Half-Truths
+334 R Bronzehide Lion
+335 R Dalakos, Crafter of Wonders
+336 R Dream Trawler
+337 R Enigmatic Incarnation
+338 R Gallia of the Endless Dance
+339 R Haktos the Unscarred
+340 M Kroxa, Titan of Death's Hunger
+341 R Kunoros, Hound of Athreos
+342 M Polukranos, Unchained
+343 M Uro, Titan of Nature's Wrath
+344 R Nyx Lotus
+345 R Shadowspear
+346 R Labyrinth of Skophos
+347 R Temple of Abandon
+348 R Temple of Deceit
+349 R Temple of Enlightenment
+350 R Temple of Malice
+351 R Temple of Plenty
+#Bundle promo
+R Arasta of the Endless Web
+#Promo Pack
+U Alseid of Life's Bounty
+C Thirst For Meaning
+U Gray Merchant of Asphodel
+C Thrill of Possibility
+U Wolfwillow Haven
[tokens]
b_2_2_zombie
diff --git a/forge-gui/res/editions/Throne of Eldraine.txt b/forge-gui/res/editions/Throne of Eldraine.txt
index d5cb63e5419..1c567deab3e 100644
--- a/forge-gui/res/editions/Throne of Eldraine.txt
+++ b/forge-gui/res/editions/Throne of Eldraine.txt
@@ -279,7 +279,44 @@ Prerelease=6 Boosters, 1 RareMythic+
267 L Forest
268 L Forest
269 L Forest
+#Borderless Planeswalkers
+270 M Garruk, Cursed Huntsman
+271 M Oko, Thief of Crowns
+272 M The Royal Scions
+#Storybook Frames
+C Ardenvale Tactician
+C Faerie Guidemother
+R Giant Killer
+C Lonesome Unicorn
+M Realm-Cloaked Giant
+U Shepherd of the Flock
+C Silverflame Squire
+U Animating Faerie
+M Brazen Borrower
+R Fae of Wishes
+U Hypnotic Sprite
+C Merfolk Secretkeeper
+C Queen of Ice
+U Foulmire Knight
+R Murderous Rider
+U Order of Midnight
+C Reaper of Night
+C Smitten Swordmaster
+R Bonecrusher Giant
+U Embereth Shieldbreaker
+C Merchant of the Vale
+C Rimrock Knight
+U Beanstalk Giant
+C Curious Pair
+U Flaxen Intruder
+C Garenbrig Carver
+R Lovestruck Beast
+C Rosethorn Acolyte
+C Tuinvale Treefolk
+U Oakhame Ranger
+#Buy-A-Box Promo
303 M Kenrith, the Returned King
+#Planeswalker Deck Cards
304 M Rowan, Fearless Sparkmage
305 C Garrison Griffin
306 U Rowan's Battleguard
@@ -310,6 +347,73 @@ Prerelease=6 Boosters, 1 RareMythic+
331 C Arcane Signet
332 R Tome of Legends
333 C Command Tower
+#Borderless art rares and mythics
+R Acclaimed Contender
+R Charming Prince
+M The Circle of Loyalty
+R Happily Ever After
+M Harmonious Archon
+R Hushbringer
+R Linden, the Steadfast Queen
+R Worthy Knight
+R Emry, Lurker of the Loch
+R Folio of Fancies
+R Gadwick, the Wizened
+M The Magic Mirror
+R Midnight Clock
+R Mirrormade
+R Stolen by the Fae
+R Vantress Gargoyle
+R Ayara, First of Locthwain
+R Blacklance Paragon
+M The Cauldron of Eternity
+R Clackbridge Troll
+R Oathsworn Knight
+R Piper of the Swarm
+M Rankle, Master of Pranks
+R Wishclaw Talisman
+R Witch's Vengeance
+M Embercleave
+R Fervent Champion
+R Fires of Invention
+R Irencrag Feat
+R Irencrag Pyromancer
+R Opportunistic Dragon
+M Robber of the Rich
+R Sundering Stroke
+R Torbran, Thane of Red Fell
+R Feasting Troll King
+R Gilded Goose
+M The Great Henge
+R Once Upon A Time
+M Questing Beast
+R Return of the Wildspeaker
+R Wicked Wolf
+R Wildborn Preserver
+R Yorvo, Lord of Garenbrig
+R Dance of the Manse
+R Doom Foretold
+R Escape to the Wilds
+R Faeburrow Elder
+R Lochmere Serpent
+M Outlaws' Merriment
+R Stormfist Crusader
+R Sorcerous Spyglass
+R Stonecoil Serpent
+R Castle Ardenvale
+R Castle Embereth
+R Castle Garenbrig
+R Castle Locthwain
+R Castle Vantress
+R Fabled Passage
+#Bundle promo
+R Piper of the Swarm
+#Promo Pack
+U Glass Casket
+U Slaying Fire
+U Kenrith's Transformation
+U Improbable Alliance
+U Inspiring Veteran
[tokens]
w_0_1_goat
diff --git a/forge-gui/res/editions/Ultimate Box Topper.txt b/forge-gui/res/editions/Ultimate Box Topper.txt
new file mode 100644
index 00000000000..65b99372a70
--- /dev/null
+++ b/forge-gui/res/editions/Ultimate Box Topper.txt
@@ -0,0 +1,48 @@
+[metadata]
+Code=PUMA
+Date=2018-12-07
+Name=Ultimate Box Topper - Ultimate Masters
+MciCode=uma
+Type=Other
+
+[cards]
+1 M Emrakul, the Aeons Torn
+2 M Karn Liberated
+3 M Kozilek, Butcher of Truth
+4 M Ulamog, the Infinite Gyre
+5 M Snapcaster Mage
+6 M Temporal Manipulation
+7 M Bitterblossom
+8 M Demonic Tutor
+9 M Goryo's Vengeance
+10 M Liliana of the Veil
+11 M Mikaeus, the Unhallowed
+12 M Reanimate
+13 M Tasigur, the Golden Fang
+14 M Balefire Dragon
+15 M Through the Breach
+16 M Eternal Witness
+17 M Life from the Loam
+18 M Noble Hierarch
+19 M Tarmogoyf
+20 M Vengevine
+21 M Gaddock Teeg
+22 M Leovold, Emissary of Trest
+23 M Lord of Extinction
+24 M Maelstrom Pulse
+25 M Sigarda, Host of Herons
+26 M Fulminator Mage
+27 M Kitchen Finks
+28 M Engineered Explosives
+29 M Mana Vault
+30 M Platinum Emperion
+31 M Ancient Tomb
+32 M Cavern of Souls
+33 M Celestial Colonnade
+34 M Creeping Tar Pit
+35 M Dark Depths
+36 M Karakas
+37 M Lavaclaw Reaches
+38 M Raging Ravine
+39 M Stirring Wildwood
+40 M Urborg, Tomb of Yawgmoth
\ No newline at end of file
diff --git a/forge-gui/res/formats/Casual/Brawl.txt b/forge-gui/res/formats/Casual/Brawl.txt
index 78fa086854a..804db4799d5 100644
--- a/forge-gui/res/formats/Casual/Brawl.txt
+++ b/forge-gui/res/formats/Casual/Brawl.txt
@@ -3,5 +3,5 @@ Name:Brawl
Order:101
Type:Casual
Subtype:Commander
-Sets:GRN, RNA, WAR, M20, ELD
-Banned:Oko, Thief of Crowns
+Sets:GRN, RNA, WAR, M20, ELD, THB
+Banned:Sorcerous Spyglass;Oko, Thief of Crowns
diff --git a/forge-gui/res/puzzle/PS_THB3.pzl b/forge-gui/res/puzzle/PS_THB3.pzl
new file mode 100644
index 00000000000..4cf23f2fd02
--- /dev/null
+++ b/forge-gui/res/puzzle/PS_THB3.pzl
@@ -0,0 +1,20 @@
+[metadata]
+Name:Possibility Storm - Theros Beyond Death #03
+URL:https://i0.wp.com/www.possibilitystorm.com/wp-content/uploads/2020/02/146.-THB3-1-scaled.jpg
+Goal:Win
+Turns:1
+Difficulty:Mythic
+Description:Win this turn. Assume any cards drawn are not relevant to solving the puzzle. Assume your opponent has no cards in hand.
+[state]
+humanlife=20
+ailife=17
+turn=1
+activeplayer=human
+activephase=MAIN1
+humanhand=Gingerbrute;Purphoros, Bronze-Blooded;Nyxborn Brute;Mire's Grasp;Omen of the Dead
+humanlibrary=Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt;Opt
+humangraveyard=Rotting Regisaur;Erebos, Bleak-Hearted;Kroxa, Titan of Death's Hunger
+humanbattlefield=Lazav, the Multifarious;Protean Thaumaturge;Bloodmist Infiltrator;The Royal Scions|Counters:LOYALTY=3;Blood Crypt|NoETBTrigs;Blood Crypt|NoETBTrigs;Blood Crypt|NoETBTrigs;Blood Crypt|NoETBTrigs;Watery Grave|NoETBTrigs;Watery Grave|NoETBTrigs;Watery Grave|NoETBTrigs;Watery Grave|NoETBTrigs
+aibattlefield=Cerulean Drake;Cerulean Drake;Archon of Sun's Grace
+aiprecast=Archon of Sun's Grace:TrigToken;Archon of Sun's Grace:TrigToken
+removesummoningsickness=true
diff --git a/forge-gui/res/quest/precons/Adaptive Enchantment.dck b/forge-gui/res/quest/precons/Adaptive Enchantment.dck
new file mode 100644
index 00000000000..ec7c27669fc
--- /dev/null
+++ b/forge-gui/res/quest/precons/Adaptive Enchantment.dck
@@ -0,0 +1,92 @@
+[metadata]
+Name=Adaptive Enchantment
+[shop]
+WinsToUnlock=0
+Credits=3600
+MinDifficulty=0
+MaxDifficulty=5
+[metadata]
+Description=Estrid wears many faces, only occasionally wearing her own. Her arsenal of enchanted masks lets her shift seamlessly between the powers and fighting styles of countless creatures to adapt to enemy tactics.
+Set=C18
+Image=adaptive_enchantment.jpg
+[Main]
+1 Ajani's Chosen|C18
+1 Archetype of Imagination|C18
+1 Arixmethes, Slumbering Isle|C18
+1 Aura Gnarlid|C18
+1 Azorius Chancery|C18
+1 Bant Charm|C18
+1 Bear Umbra|C18
+1 Blossoming Sands|C18
+1 Boon Satyr|C18
+1 Bruna, Light of Alabaster|C18
+1 Celestial Archon|C18
+1 Cold-Eyed Selkie|C18
+1 Command Tower|C18
+1 Creeping Renaissance|C18
+1 Dawn's Reflection|C18
+1 Daxos of Meletis|C18
+1 Dictate of Kruphix|C18
+1 Dismantling Blow|C18
+1 Eel Umbra|C18
+1 Eidolon of Blossoms|C18
+1 Elderwood Scion|C18
+1 Empyrial Storm|C18
+1 Enchantress's Presence|C18
+1 Epic Proportions|C18
+1 Estrid's Invocation|C18
+1 Estrid, the Masked+|C18
+1 Ever-Watching Threshold|C18
+1 Evolving Wilds|C18
+1 Fertile Ground|C18
+1 Finest Hour|C18
+8 Forest|C18
+1 Forge of Heroes|C18
+1 Genesis Storm|C18
+1 Ground Seal|C18
+1 Heavenly Blademaster|C18
+1 Herald of the Pantheon|C18
+1 Hydra Omnivore|C18
+6 Island|C18
+1 Kestia, the Cultivator|C18
+1 Krosan Verge|C18
+1 Kruphix's Insight|C18
+1 Loyal Drake|C18
+1 Loyal Guardian|C18
+1 Loyal Unicorn|C18
+1 Martial Coup|C18
+1 Meandering River|C18
+1 Mosswort Bridge|C18
+1 Myth Unbound|C18
+1 Nylea's Colossus|C18
+1 Octopus Umbra|C18
+1 Overgrowth|C18
+1 Phyrexian Rebirth|C18
+9 Plains|C18
+1 Ravenous Slime|C18
+1 Reclamation Sage|C18
+1 Righteous Authority|C18
+1 Sage's Reverie|C18
+1 Seaside Citadel|C18
+1 Selesnya Sanctuary|C18
+1 Sigil of the Empty Throne|C18
+1 Silent Sentinel|C18
+1 Simic Growth Chamber|C18
+1 Snake Umbra|C18
+1 Sol Ring|C18
+1 Soul Snare|C18
+1 Spawning Grounds|C18
+1 Terramorphic Expanse|C18
+1 Thornwood Falls|C18
+1 Tranquil Cove|C18
+1 Tranquil Expanse|C18
+1 Tuvasa the Sunlit|C18
+1 Unflinching Courage|C18
+1 Unquestioned Authority|C18
+1 Vow of Flight|C18
+1 Vow of Wildness|C18
+1 Whitewater Naiads|C18
+1 Wild Growth|C18
+1 Winds of Rath|C18
+1 Woodland Stream|C18
+1 Yavimaya Enchantress|C18
diff --git a/forge-gui/res/quest/precons/Ashiok, Sculptor of Fears.dck b/forge-gui/res/quest/precons/Ashiok, Sculptor of Fears.dck
index d0263cad431..d1c983bf8d4 100644
--- a/forge-gui/res/quest/precons/Ashiok, Sculptor of Fears.dck
+++ b/forge-gui/res/quest/precons/Ashiok, Sculptor of Fears.dck
@@ -6,7 +6,7 @@ Credits=1200
MinDifficulty=0
MaxDifficulty=5
[metadata]
-Description=Theros Beyond Death U/B Planeswalker Deck
+Description=Ashiok has power to torment foes by conjuring their darkest memories, fears, and regrets. With Ashiok’s deck, you’ll fill your graveyard along with your opponent’s for massive value as you slowly drive them insane.
Set=THB
Image=ashiok_sculptor_of_fears.jpg
[Main]
diff --git a/forge-gui/res/quest/precons/Elspeth, Undaunted Hero.dck b/forge-gui/res/quest/precons/Elspeth, Undaunted Hero.dck
index 0d3df54935e..68ee479c7cf 100644
--- a/forge-gui/res/quest/precons/Elspeth, Undaunted Hero.dck
+++ b/forge-gui/res/quest/precons/Elspeth, Undaunted Hero.dck
@@ -6,7 +6,7 @@ Credits=1200
MinDifficulty=0
MaxDifficulty=5
[metadata]
-Description=Theros Beyond Death W Planeswalker Deck
+Description=Elspeth is a renowned warrior, who has defeated everything from gods to death itself. With Elspeth’s deck, you’ll build up a battalion of devoted soldiers and lead them fearlessly to a glorious victory.
Set=THB
Image=elspeth_undaunted_hero.jpg
[Main]
diff --git a/forge-gui/res/quest/precons/Exquisite Invention.dck b/forge-gui/res/quest/precons/Exquisite Invention.dck
new file mode 100644
index 00000000000..51b9f2eb393
--- /dev/null
+++ b/forge-gui/res/quest/precons/Exquisite Invention.dck
@@ -0,0 +1,87 @@
+[metadata]
+Name=Exquisite Invention
+[shop]
+WinsToUnlock=0
+Credits=3600
+MinDifficulty=0
+MaxDifficulty=5
+[metadata]
+Description=Saheeli Rai is a brilliant inventor who specializes in creating lifelike animated constructs as beautiful as they are deadly. Her dazzling intellect and mastery over metal make her a formidable adversary.
+Set=C18
+Image=exquisite_invention.jpg
+[Main]
+1 Aether Gale|C18
+1 Ancient Stone Idol|C18
+1 Blasphemous Act|C18
+1 Blinkmoth Urn|C18
+1 Bosh, Iron Golem|C18
+1 Brudiclad, Telchor Engineer|C18
+1 Buried Ruin|C18
+1 Chaos Warp|C18
+1 Chief of the Foundry|C18
+1 Command Tower|C18
+1 Commander's Sphere|C18
+1 Coveted Jewel|C18
+1 Darksteel Citadel|C18
+1 Darksteel Juggernaut|C18
+1 Dreamstone Hedron|C18
+1 Duplicant|C18
+1 Echo Storm|C18
+1 Enchanter's Bane|C18
+1 Endless Atlas|C18
+1 Etherium Sculptor|C18
+1 Forge of Heroes|C18
+1 Foundry of the Consuls|C18
+1 Geode Golem|C18
+1 Great Furnace|C18
+1 Hedron Archive|C18
+1 Hellkite Igniter|C18
+1 Highland Lake|C18
+1 Inkwell Leviathan|C18
+1 Into the Roil|C18
+15 Island|C18
+1 Izzet Boilerworks|C18
+1 Izzet Guildgate|C18
+1 Izzet Signet|C18
+1 Loyal Apprentice|C18
+1 Loyal Drake|C18
+1 Magmaquake|C18
+1 Magnifying Glass|C18
+1 Maverick Thopterist|C18
+1 Mimic Vat|C18
+1 Mind Stone|C18
+1 Mirrorworks|C18
+12 Mountain|C18
+1 Myr Battlesphere|C18
+1 Pilgrim's Eye|C18
+1 Prismatic Lens|C18
+1 Prototype Portal|C18
+1 Psychosis Crawler|C18
+1 Retrofitter Foundry|C18
+1 Reverse Engineer|C18
+1 Saheeli's Artistry|C18
+1 Saheeli's Directive|C18
+1 Saheeli, the Gifted+|C18
+1 Scrabbling Claws|C18
+1 Scuttling Doom Engine|C18
+1 Seat of the Synod|C18
+1 Sharding Sphinx|C18
+1 Sol Ring|C18
+1 Soul of New Phyrexia|C18
+1 Steel Hellkite|C18
+1 Swiftfoot Boots|C18
+1 Swiftwater Cliffs|C18
+1 Tawnos, Urza's Apprentice|C18
+1 Thirst for Knowledge|C18
+1 Thopter Assembly|C18
+1 Thopter Engineer|C18
+1 Thopter Spy Network|C18
+1 Tidings|C18
+1 Treasure Nabber|C18
+1 Unstable Obelisk|C18
+1 Unwinding Clock|C18
+1 Varchild, Betrayer of Kjeldor|C18
+1 Vedalken Humiliator|C18
+1 Vessel of Endless Rest|C18
+1 Whirler Rogue|C18
+1 Worn Powerstone|C18
diff --git a/forge-gui/res/quest/precons/Nature's Vengeance.dck b/forge-gui/res/quest/precons/Nature's Vengeance.dck
new file mode 100644
index 00000000000..670b63c11a2
--- /dev/null
+++ b/forge-gui/res/quest/precons/Nature's Vengeance.dck
@@ -0,0 +1,97 @@
+[metadata]
+Name=Nature's Vengeance
+[shop]
+WinsToUnlock=0
+Credits=3600
+MinDifficulty=0
+MaxDifficulty=5
+[metadata]
+Description=Lord Windgrace saw his entire world devastated by the detonation of a magical device, instilling in him a hatred of artifacts. Now he commands nature to rise and join his furious war against synthetic abominations.
+Set=C18
+Image=natures_vengeance.jpg
+[Main]
+1 Acidic Slime|C18
+1 Akoum Refuge|C18
+1 Avenger of Zendikar|C18
+1 Baloth Woodcrasher|C18
+1 Barren Moor|C18
+1 Blighted Woodland|C18
+1 Bloodtracker|C18
+1 Bojuka Bog|C18
+1 Borderland Explorer|C18
+1 Budoka Gardener|C18
+1 Centaur Vinecrasher|C18
+1 Chain Reaction|C18
+1 Charnelhoard Wurm|C18
+1 Command Tower|C18
+1 Consign to Dust|C18
+1 Crash of Rhino Beetles|C18
+1 Cultivate|C18
+1 Deathreap Ritual|C18
+1 Decimate|C18
+1 Emissary of Grudges|C18
+1 Evolving Wilds|C18
+1 Explore|C18
+1 Explosive Vegetation|C18
+1 Far Wanderings|C18
+1 Farhaven Elf|C18
+1 Flameblast Dragon|C18
+7 Forest|C18
+1 Forge of Heroes|C18
+1 Forgotten Cave|C18
+1 Fury Storm|C18
+1 Gaze of Granite|C18
+1 Golgari Rot Farm|C18
+1 Grapple with the Past|C18
+1 Grim Backwoods|C18
+1 Grisly Salvage|C18
+1 Gruul Turf|C18
+1 Gyrus, Waker of Corpses|C18
+1 Harrow|C18
+1 Haunted Fengraf|C18
+1 Hunting Wilds|C18
+1 Jund Panorama|C18
+1 Jungle Hollow|C18
+1 Kazandu Refuge|C18
+1 Khalni Garden|C18
+1 Khalni Heart Expedition|C18
+1 Lavalanche|C18
+1 Lord Windgrace+|C18
+1 Loyal Apprentice|C18
+1 Loyal Guardian|C18
+1 Loyal Subordinate|C18
+1 Moldgraf Monstrosity|C18
+1 Moonlight Bargain|C18
+5 Mountain|C18
+1 Mountain Valley|C18
+1 Myriad Landscape|C18
+1 Nesting Dragon|C18
+1 Putrefy|C18
+1 Rakdos Carnarium|C18
+1 Rampaging Baloths|C18
+1 Reality Scramble|C18
+1 Retreat to Hagra|C18
+1 Rocky Tar Pit|C18
+1 Rubblehulk|C18
+1 Ruinous Path|C18
+1 Sakura-Tribe Elder|C18
+1 Savage Lands|C18
+1 Savage Twister|C18
+1 Scute Mob|C18
+1 Seer's Sundial|C18
+1 Sol Ring|C18
+1 Soul of Innistrad|C18
+1 Stitch Together|C18
+6 Swamp|C18
+1 Temple of the False God|C18
+1 Terramorphic Expanse|C18
+1 Thantis, the Warweaver|C18
+1 Tranquil Thicket|C18
+1 Turntimber Sower|C18
+1 Warped Landscape|C18
+1 Whiptongue Hydra|C18
+1 Windgrace's Judgment|C18
+1 Worm Harvest|C18
+1 Xantcha, Sleeper Agent|C18
+1 Yavimaya Elder|C18
+1 Zendikar Incarnate|C18
diff --git a/forge-gui/res/quest/precons/Subjective Reality.dck b/forge-gui/res/quest/precons/Subjective Reality.dck
new file mode 100644
index 00000000000..66f939efa23
--- /dev/null
+++ b/forge-gui/res/quest/precons/Subjective Reality.dck
@@ -0,0 +1,99 @@
+[metadata]
+Name=Subjective Reality
+[shop]
+WinsToUnlock=0
+Credits=3600
+MinDifficulty=0
+MaxDifficulty=5
+[metadata]
+Description=Aminatou may be a child, but she possesses the wisdom of many lifetimes and the power to manipulate reality itself. She can effortlessly alter a person's entire destiny – for better or for worse – and leave no trace of her interference.
+Set=C18
+Image=subjective_reality.jpg
+[Main]
+1 Adarkar Valkyrie|C18
+1 Aethermage's Touch|C18
+1 Akroma's Vengeance|C18
+1 Aminatou's Augury|C18
+1 Aminatou, the Fateshifter+|C18
+1 Arcane Sanctum|C18
+1 Army of the Damned|C18
+1 Azorius Chancery|C18
+1 Azorius Guildgate|C18
+1 Azorius Signet|C18
+1 Banishing Stroke|C18
+1 Barren Moor|C18
+1 Boreas Charger|C18
+1 Brainstorm|C18
+1 Cloudform|C18
+1 Command Tower|C18
+1 Commander's Sphere|C18
+1 Conundrum Sphinx|C18
+1 Crib Swap|C18
+1 Crystal Ball|C18
+1 Devastation Tide|C18
+1 Dimir Aqueduct|C18
+1 Dimir Guildgate|C18
+1 Dimir Signet|C18
+1 Dismal Backwater|C18
+1 Djinn of Wishes|C18
+1 Dream Cache|C18
+1 Duskmantle Seer|C18
+1 Enigma Sphinx|C18
+1 Entreat the Angels|C18
+1 Entreat the Dead|C18
+1 Esper Charm|C18
+1 Forge of Heroes|C18
+1 Forsaken Sanctuary|C18
+1 Geode Golem|C18
+1 Halimar Depths|C18
+1 High Priest of Penance|C18
+5 Island|C18
+1 Isolated Watchtower|C18
+1 Jeskai Infiltrator|C18
+1 Jwar Isle Refuge|C18
+1 Lightform|C18
+1 Lonely Sandbar|C18
+1 Loyal Subordinate|C18
+1 Loyal Unicorn|C18
+1 Magus of the Balance|C18
+1 Meandering River|C18
+1 Mind Stone|C18
+1 Mortify|C18
+1 Mortuary Mire|C18
+1 Mulldrifter|C18
+1 New Benalia|C18
+1 Night Incarnate|C18
+1 Ninja of the Deep Hours|C18
+1 Orzhov Basilica|C18
+1 Orzhov Guildgate|C18
+1 Orzhov Signet|C18
+1 Phyrexian Delver|C18
+1 Pilgrim's Eye|C18
+8 Plains|C18
+1 Ponder|C18
+1 Portent|C18
+1 Predict|C18
+1 Primordial Mist|C18
+1 Return to Dust|C18
+1 Scoured Barrens|C18
+1 Secluded Steppe|C18
+1 Seer's Lantern|C18
+1 Sejiri Refuge|C18
+1 Serra Avatar|C18
+1 Sigiled Starfish|C18
+1 Silent-Blade Oni|C18
+1 Skull Storm|C18
+1 Sol Ring|C18
+1 Sower of Discord|C18
+1 Sphinx of Jwar Isle|C18
+1 Sphinx of Uthuun|C18
+1 Submerged Boneyard|C18
+3 Swamp|C18
+1 Telling Time|C18
+1 Terminus|C18
+1 Tranquil Cove|C18
+1 Treasure Hunt|C18
+1 Utter End|C18
+1 Varina, Lich Queen|C18
+1 Yennett, Cryptic Sovereign|C18
+1 Yuriko, the Tiger's Shadow|C18
diff --git a/forge-gui/src/main/java/forge/interfaces/IGuiGame.java b/forge-gui/src/main/java/forge/interfaces/IGuiGame.java
index aac6d6aaf34..997c5e7b09a 100644
--- a/forge-gui/src/main/java/forge/interfaces/IGuiGame.java
+++ b/forge-gui/src/main/java/forge/interfaces/IGuiGame.java
@@ -1,11 +1,6 @@
package forge.interfaces;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
import com.google.common.base.Function;
-
import forge.LobbyPlayer;
import forge.assets.FSkinProp;
import forge.deck.CardPool;
@@ -22,9 +17,14 @@ import forge.game.spellability.SpellAbilityView;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.player.PlayerZoneUpdate;
+import forge.player.PlayerZoneUpdates;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
public interface IGuiGame {
void setGameView(GameView gameView);
GameView getGameView();
@@ -107,7 +107,6 @@ public interface IGuiGame {
* @return null if choices is missing, empty, or if the users' choices are
* empty; otherwise, returns the first item in the List returned by
* getChoices.
- * @see #getChoices(String, int, int, Object...)
*/
T oneOrNone(String message, List choices);
@@ -158,8 +157,8 @@ public interface IGuiGame {
void setCard(CardView card);
void setPlayerAvatar(LobbyPlayer player, IHasIcon ihi);
- boolean openZones(Collection zones, Map players);
- void restoreOldZones(Map playersToRestoreZonesFor);
+ PlayerZoneUpdates openZones(PlayerView controller, Collection zones, Map players);
+ void restoreOldZones(PlayerView playerView, PlayerZoneUpdates playerZoneUpdates);
void setHighlighted(PlayerView pv, boolean b);
void setUsedToPay(CardView card, boolean value);
void setSelectables(final Iterable cards);
diff --git a/forge-gui/src/main/java/forge/match/input/InputSelectEntitiesFromList.java b/forge-gui/src/main/java/forge/match/input/InputSelectEntitiesFromList.java
index fc8b962b19c..ab34c9f6abd 100644
--- a/forge-gui/src/main/java/forge/match/input/InputSelectEntitiesFromList.java
+++ b/forge-gui/src/main/java/forge/match/input/InputSelectEntitiesFromList.java
@@ -1,23 +1,22 @@
package forge.match.input;
-import java.util.Collection;
-import java.util.List;
-import java.util.ArrayList;
-
+import forge.FThreads;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CardView;
-
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
+import forge.game.zone.Zone;
import forge.player.PlayerControllerHuman;
-import forge.util.collect.FCollection;
-import forge.util.collect.FCollectionView;
-import forge.util.ITriggerEvent;
import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
-import forge.game.zone.Zone;
-import forge.FThreads;
+import forge.util.ITriggerEvent;
+import forge.util.collect.FCollection;
+import forge.util.collect.FCollectionView;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
public class InputSelectEntitiesFromList extends InputSelectManyBase {
private static final long serialVersionUID = -6609493252672573139L;
@@ -31,31 +30,32 @@ public class InputSelectEntitiesFromList extends InputSele
}
public InputSelectEntitiesFromList(final PlayerControllerHuman controller, final int min, final int max, final FCollectionView validChoices0, final SpellAbility sa0) {
- super(controller, Math.min(min, validChoices0.size()), Math.min(max, validChoices0.size()),sa0);
+ super(controller, Math.min(min, validChoices0.size()), Math.min(max, validChoices0.size()), sa0);
validChoices = validChoices0;
if (min > validChoices.size()) { // pfps does this really do anything useful??
System.out.println(String.format("Trying to choose at least %d things from a list with only %d things!", min, validChoices.size()));
}
- ArrayList vCards = new ArrayList<>();
- for ( T c : validChoices0 ) {
- if ( c instanceof Card ) {
- vCards.add(((Card)c).getView()) ;
- }
- }
- getController().getGui().setSelectables(vCards);
- final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
- for (final GameEntity c : validChoices) {
- final Zone cz = (c instanceof Card) ? ((Card) c).getZone() : null ;
- if ( cz != null ) {
- zonesToUpdate.add(new PlayerZoneUpdate(cz.getPlayer().getView(),cz.getZoneType()));
- }
- }
- FThreads.invokeInEdtNowOrLater(new Runnable() {
- @Override public void run() {
- getController().getGui().updateZones(zonesToUpdate);
- zonesShown = getController().getGui().tempShowZones(controller.getPlayer().getView(),zonesToUpdate);
+ ArrayList vCards = new ArrayList<>();
+ for (T c : validChoices0) {
+ if (c instanceof Card) {
+ vCards.add(((Card) c).getView());
}
- });
+ }
+ getController().getGui().setSelectables(vCards);
+ final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
+ for (final GameEntity c : validChoices) {
+ final Zone cz = (c instanceof Card) ? ((Card) c).getZone() : null;
+ if (cz != null) {
+ zonesToUpdate.add(new PlayerZoneUpdate(cz.getPlayer().getView(), cz.getZoneType()));
+ }
+ }
+ FThreads.invokeInEdtNowOrLater(new Runnable() {
+ @Override
+ public void run() {
+ getController().getGui().updateZones(zonesToUpdate);
+ zonesShown = getController().getGui().tempShowZones(controller.getPlayer().getView(), zonesToUpdate);
+ }
+ });
}
@Override
diff --git a/forge-gui/src/main/java/forge/match/input/InputSelectTargets.java b/forge-gui/src/main/java/forge/match/input/InputSelectTargets.java
index d67b78b4698..547b185b681 100644
--- a/forge-gui/src/main/java/forge/match/input/InputSelectTargets.java
+++ b/forge-gui/src/main/java/forge/match/input/InputSelectTargets.java
@@ -1,13 +1,7 @@
package forge.match.input;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
import com.google.common.collect.ImmutableList;
-
+import forge.FThreads;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.ability.ApiType;
@@ -18,13 +12,18 @@ import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.model.FModel;
import forge.player.PlayerControllerHuman;
+import forge.player.PlayerZoneUpdate;
+import forge.player.PlayerZoneUpdates;
import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences;
import forge.util.ITriggerEvent;
import forge.util.TextUtil;
-import forge.player.PlayerZoneUpdate;
-import forge.player.PlayerZoneUpdates;
-import forge.FThreads;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
public final class InputSelectTargets extends InputSyncronizedBase {
private final List choices;
@@ -47,16 +46,17 @@ public final class InputSelectTargets extends InputSyncronizedBase {
this.tgt = sa.getTargetRestrictions();
this.sa = sa;
this.mandatory = mandatory;
- controller.getGui().setSelectables(CardView.getCollection(choices));
- final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
- for (final Card c : choices) {
- zonesToUpdate.add(new PlayerZoneUpdate(c.getZone().getPlayer().getView(),c.getZone().getZoneType()));
- }
- FThreads.invokeInEdtNowOrLater(new Runnable() {
- @Override public void run() {
- controller.getGui().updateZones(zonesToUpdate);
+ controller.getGui().setSelectables(CardView.getCollection(choices));
+ final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
+ for (final Card c : choices) {
+ zonesToUpdate.add(new PlayerZoneUpdate(c.getZone().getPlayer().getView(), c.getZone().getZoneType()));
+ }
+ FThreads.invokeInEdtNowOrLater(new Runnable() {
+ @Override
+ public void run() {
+ controller.getGui().updateZones(zonesToUpdate);
}
- });
+ });
}
@Override
diff --git a/forge-gui/src/main/java/forge/net/ProtocolMethod.java b/forge-gui/src/main/java/forge/net/ProtocolMethod.java
index 254ac09a3c5..4497b5cf0f9 100644
--- a/forge-gui/src/main/java/forge/net/ProtocolMethod.java
+++ b/forge-gui/src/main/java/forge/net/ProtocolMethod.java
@@ -1,14 +1,6 @@
package forge.net;
-import java.io.Serializable;
-import java.lang.reflect.Method;
-import java.util.Collection;
-import java.util.ConcurrentModificationException;
-import java.util.List;
-import java.util.Map;
-
import com.google.common.base.Function;
-
import forge.GuiBase;
import forge.assets.FSkinProp;
import forge.deck.CardPool;
@@ -22,11 +14,19 @@ import forge.game.spellability.SpellAbilityView;
import forge.interfaces.IGameController;
import forge.interfaces.IGuiGame;
import forge.match.NextGameDecision;
+import forge.player.PlayerZoneUpdates;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
import forge.util.ReflectionUtil;
import org.apache.commons.lang3.SerializationUtils;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.List;
+import java.util.Map;
+
/**
* The methods that can be sent through this protocol.
*/
@@ -75,8 +75,8 @@ public enum ProtocolMethod {
clearSelectables (Mode.SERVER),
refreshField (Mode.SERVER),
// TODO case "setPlayerAvatar":
- openZones (Mode.SERVER, Boolean.TYPE, Collection/*ZoneType*/.class, Map/*PlayerView,Object*/.class),
- restoreOldZones (Mode.SERVER, Void.TYPE, Map/*PlayerView,Object*/.class),
+ openZones (Mode.SERVER, PlayerZoneUpdates.class, Collection/*ZoneType*/.class, Map/*PlayerView,Object*/.class),
+ restoreOldZones (Mode.SERVER, Void.TYPE, PlayerView.class, PlayerZoneUpdates.class),
isUiSetToSkipPhase (Mode.SERVER, Boolean.TYPE, PlayerView.class, PhaseType.class),
setRememberedActions(Mode.SERVER, Void.TYPE),
nextRememberedAction(Mode.SERVER, Void.TYPE),
diff --git a/forge-gui/src/main/java/forge/net/server/NetGuiGame.java b/forge-gui/src/main/java/forge/net/server/NetGuiGame.java
index 7601297f2e6..33e6f727b07 100644
--- a/forge-gui/src/main/java/forge/net/server/NetGuiGame.java
+++ b/forge-gui/src/main/java/forge/net/server/NetGuiGame.java
@@ -1,11 +1,6 @@
package forge.net.server;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
import com.google.common.base.Function;
-
import forge.LobbyPlayer;
import forge.assets.FSkinProp;
import forge.deck.CardPool;
@@ -23,9 +18,14 @@ import forge.match.AbstractGuiGame;
import forge.net.GameProtocolSender;
import forge.net.ProtocolMethod;
import forge.player.PlayerZoneUpdate;
+import forge.player.PlayerZoneUpdates;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
public class NetGuiGame extends AbstractGuiGame {
private final GameProtocolSender sender;
@@ -283,14 +283,14 @@ public class NetGuiGame extends AbstractGuiGame {
}
@Override
- public boolean openZones(final Collection zones, final Map players) {
+ public PlayerZoneUpdates openZones(PlayerView controller, final Collection zones, final Map players) {
updateGameView();
- return sendAndWait(ProtocolMethod.openZones, zones, players);
+ return sendAndWait(ProtocolMethod.openZones, controller, zones, players);
}
@Override
- public void restoreOldZones(final Map playersToRestoreZonesFor) {
- send(ProtocolMethod.restoreOldZones, playersToRestoreZonesFor);
+ public void restoreOldZones(PlayerView playerView, PlayerZoneUpdates playerZoneUpdates) {
+ send(ProtocolMethod.restoreOldZones, playerView, playerZoneUpdates);
}
@Override
diff --git a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java
index e3663f41ad3..a337db37099 100644
--- a/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java
+++ b/forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java
@@ -133,8 +133,8 @@ public class HumanPlaySpellAbility {
}
if (ability.isAbility() && ability instanceof AbilityActivated) {
- final Map params = Maps.newHashMap();
- params.put("ManaColorConversion", "Additive");
+ final Map params = Maps.newHashMap();
+ params.put("ManaColorConversion", "Additive");
for (KeywordInterface inst : c.getKeywords()) {
String keyword = inst.getOriginal();
@@ -249,6 +249,8 @@ public class HumanPlaySpellAbility {
final Game game = ability.getActivatingPlayer().getGame();
if (fromZone != null) { // and not a copy
+ ability.getHostCard().setCastSA(null);
+ ability.getHostCard().setCastFrom(null);
// add back to where it came from
game.getAction().moveTo(fromZone, ability.getHostCard(), zonePosition >= 0 ? Integer.valueOf(zonePosition) : null, null);
}
diff --git a/forge-gui/src/main/java/forge/player/PlayerZoneUpdate.java b/forge-gui/src/main/java/forge/player/PlayerZoneUpdate.java
index 5408941ffe5..7ed01f9f469 100644
--- a/forge-gui/src/main/java/forge/player/PlayerZoneUpdate.java
+++ b/forge-gui/src/main/java/forge/player/PlayerZoneUpdate.java
@@ -14,11 +14,15 @@ public class PlayerZoneUpdate implements Serializable {
private final Set zones;
public PlayerZoneUpdate(final PlayerView player, final ZoneType zone) {
- if (player == null || zone == null) {
+ if (player == null ) {
throw new NullPointerException();
}
this.player = player;
- this.zones = EnumSet.of(zone);
+ if (zone != null) {
+ this.zones = EnumSet.of(zone);
+ } else {
+ this.zones = EnumSet.noneOf(ZoneType.class);
+ }
}
public PlayerView getPlayer() {
@@ -30,13 +34,13 @@ public class PlayerZoneUpdate implements Serializable {
void addZone(final ZoneType zone) {
if (zone == null) {
- throw new NullPointerException();
+ return;
}
zones.add(zone);
}
void add(final PlayerZoneUpdate other) {
if (other == null) {
- throw new NullPointerException();
+ return;
}
zones.addAll(other.getZones());
}
diff --git a/forge-gui/src/main/java/forge/player/TargetSelection.java b/forge-gui/src/main/java/forge/player/TargetSelection.java
index deced7b9fb8..914f22e0916 100644
--- a/forge-gui/src/main/java/forge/player/TargetSelection.java
+++ b/forge-gui/src/main/java/forge/player/TargetSelection.java
@@ -100,7 +100,7 @@ public class TargetSelection {
return true;
}
- final List zone = tgt.getZone();
+ final List zones = tgt.getZone();
final boolean mandatory = tgt.getMandatory() && hasCandidates;
final boolean choiceResult;
@@ -110,7 +110,7 @@ public class TargetSelection {
final GameObject choice = Aggregates.random(candidates);
return ability.getTargets().add(choice);
}
- else if (zone.size() == 1 && zone.get(0) == ZoneType.Stack) {
+ else if (zones.size() == 1 && zones.get(0) == ZoneType.Stack) {
// If Zone is Stack, the choices are handled slightly differently.
// Handle everything inside function due to interaction with StackInstance
return this.chooseCardFromStack(mandatory);
@@ -152,12 +152,15 @@ public class TargetSelection {
for (Card card : validTargets) {
playersWithValidTargets.put(PlayerView.get(card.getController()), null);
}
- if (controller.getGui().openZones(zone, playersWithValidTargets)) {
+
+ PlayerView playerView = controller.getLocalPlayerView();
+ PlayerZoneUpdates playerZoneUpdates = controller.getGui().openZones(playerView, zones, playersWithValidTargets);
+ if (!zones.contains(ZoneType.Stack)) {
InputSelectTargets inp = new InputSelectTargets(controller, validTargets, ability, mandatory);
inp.showAndWait();
choiceResult = !inp.hasCancelled();
bTargetingDone = inp.hasPressedOk();
- controller.getGui().restoreOldZones(playersWithValidTargets);
+ controller.getGui().restoreOldZones(playerView, playerZoneUpdates);
}
else {
// for every other case an all-purpose GuiChoose