Merge remote-tracking branch 'upstream/master'

This commit is contained in:
CCTV-1
2020-02-16 12:00:01 +08:00
52 changed files with 1348 additions and 413 deletions

View File

@@ -561,4 +561,12 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
// this does overwrite the original MapParams // this does overwrite the original MapParams
this.originalMapParams = Maps.newHashMap(this.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;
}
} }

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
package forge.game;
import com.google.common.base.Predicate;
import forge.game.card.Card;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
/**
* <p>
* Predicate<GameObject> interface.
* </p>
*
* @author Forge
*/
public final class GameObjectPredicates {
public static final Predicate<GameObject> restriction(final String[] restrictions, final Player sourceController, final Card source, final SpellAbility spellAbility) {
return new Predicate<GameObject>() {
@Override
public boolean apply(final GameObject c) {
return (c != null) && c.isValid(restrictions, sourceController, source, spellAbility);
}
};
}
}

View File

@@ -122,7 +122,7 @@ public class DamageAllEffect extends DamageBaseEffect {
if (!usedDamageMap) { if (!usedDamageMap) {
preventMap.triggerPreventDamage(false); preventMap.triggerPreventDamage(false);
damageMap.triggerDamageDoneOnce(false, sa); damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear(); preventMap.clear();
damageMap.clear(); damageMap.clear();

View File

@@ -166,7 +166,7 @@ public class DamageDealEffect extends DamageBaseEffect {
if (!usedDamageMap) { if (!usedDamageMap) {
preventMap.triggerPreventDamage(false); preventMap.triggerPreventDamage(false);
// non combat damage cause lifegain there // non combat damage cause lifegain there
damageMap.triggerDamageDoneOnce(false, sa); damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear(); preventMap.clear();
damageMap.clear(); damageMap.clear();
@@ -215,7 +215,7 @@ public class DamageDealEffect extends DamageBaseEffect {
if (!usedDamageMap) { if (!usedDamageMap) {
preventMap.triggerPreventDamage(false); preventMap.triggerPreventDamage(false);
// non combat damage cause lifegain there // non combat damage cause lifegain there
damageMap.triggerDamageDoneOnce(false, sa); damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear(); preventMap.clear();
damageMap.clear(); damageMap.clear();

View File

@@ -136,7 +136,7 @@ public class DamageEachEffect extends DamageBaseEffect {
if (!usedDamageMap) { if (!usedDamageMap) {
preventMap.triggerPreventDamage(false); preventMap.triggerPreventDamage(false);
damageMap.triggerDamageDoneOnce(false, sa); damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear(); preventMap.clear();
damageMap.clear(); damageMap.clear();

View File

@@ -25,7 +25,7 @@ public class DamageResolveEffect extends SpellAbilityEffect {
} }
// non combat damage cause lifegain there // non combat damage cause lifegain there
if (damageMap != null) { if (damageMap != null) {
damageMap.triggerDamageDoneOnce(false, sa); damageMap.triggerDamageDoneOnce(false, sa.getHostCard().getGame(), sa);
damageMap.clear(); damageMap.clear();
} }
} }

View File

@@ -154,7 +154,7 @@ public class FightEffect extends DamageBaseEffect {
if (!usedDamageMap) { if (!usedDamageMap) {
preventMap.triggerPreventDamage(false); preventMap.triggerPreventDamage(false);
damageMap.triggerDamageDoneOnce(false, sa); damageMap.triggerDamageDoneOnce(false, fighterA.getGame(), sa);
preventMap.clear(); preventMap.clear();
damageMap.clear(); damageMap.clear();

View File

@@ -236,7 +236,7 @@ public class RepeatEachEffect extends SpellAbilityEffect {
sa.getPreventMap().triggerPreventDamage(false); sa.getPreventMap().triggerPreventDamage(false);
sa.setPreventMap(null); sa.setPreventMap(null);
// non combat damage cause lifegain there // non combat damage cause lifegain there
sa.getDamageMap().triggerDamageDoneOnce(false, sa); sa.getDamageMap().triggerDamageDoneOnce(false, game, sa);
sa.setDamageMap(null); sa.setDamageMap(null);
} }
if (sa.hasParam("ChangeZoneTable")) { if (sa.hasParam("ChangeZoneTable")) {

View File

@@ -1,16 +1,20 @@
/** /**
* *
*/ */
package forge.game.card; package forge.game.card;
import java.util.Map; import java.util.Map;
import java.util.Set;
import com.google.common.collect.ForwardingTable; import com.google.common.collect.ForwardingTable;
import com.google.common.collect.HashBasedTable; import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.collect.Table; import com.google.common.collect.Table;
import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameObjectPredicates;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.keyword.Keyword; import forge.game.keyword.Keyword;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
@@ -18,7 +22,7 @@ import forge.game.trigger.TriggerType;
public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> { public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
private Table<Card, GameEntity, Integer> dataMap = HashBasedTable.create(); private Table<Card, GameEntity, Integer> dataMap = HashBasedTable.create();
public CardDamageMap(Table<Card, GameEntity, Integer> damageMap) { public CardDamageMap(Table<Card, GameEntity, Integer> damageMap) {
this.putAll(damageMap); this.putAll(damageMap);
} }
@@ -38,13 +42,13 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
runParams.put(AbilityKey.DamageTarget, ge); runParams.put(AbilityKey.DamageTarget, ge);
runParams.put(AbilityKey.DamageAmount, sum); runParams.put(AbilityKey.DamageAmount, sum);
runParams.put(AbilityKey.IsCombatDamage, isCombat); runParams.put(AbilityKey.IsCombatDamage, isCombat);
ge.getGame().getTriggerHandler().runTrigger(TriggerType.DamagePreventedOnce, runParams, false); 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 // Source -> Targets
for (Map.Entry<Card, Map<GameEntity, Integer>> e : this.rowMap().entrySet()) { for (Map.Entry<Card, Map<GameEntity, Integer>> e : this.rowMap().entrySet()) {
final Card sourceLKI = e.getKey(); final Card sourceLKI = e.getKey();
@@ -58,9 +62,9 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
runParams.put(AbilityKey.DamageTargets, Sets.newHashSet(e.getValue().keySet())); runParams.put(AbilityKey.DamageTargets, Sets.newHashSet(e.getValue().keySet()));
runParams.put(AbilityKey.DamageAmount, sum); runParams.put(AbilityKey.DamageAmount, sum);
runParams.put(AbilityKey.IsCombatDamage, isCombat); 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)) { if (sourceLKI.hasKeyword(Keyword.LIFELINK)) {
sourceLKI.getController().gainLife(sum, sourceLKI, sa); sourceLKI.getController().gainLife(sum, sourceLKI, sa);
} }
@@ -79,10 +83,15 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
runParams.put(AbilityKey.DamageSources, Sets.newHashSet(e.getValue().keySet())); runParams.put(AbilityKey.DamageSources, Sets.newHashSet(e.getValue().keySet()));
runParams.put(AbilityKey.DamageAmount, sum); runParams.put(AbilityKey.DamageAmount, sum);
runParams.put(AbilityKey.IsCombatDamage, isCombat); runParams.put(AbilityKey.IsCombatDamage, isCombat);
ge.getGame().getTriggerHandler().runTrigger(TriggerType.DamageDoneOnce, runParams, false); game.getTriggerHandler().runTrigger(TriggerType.DamageDoneOnce, runParams, false);
} }
} }
final Map<AbilityKey, Object> 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 * special put logic, sum the values
@@ -98,4 +107,29 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
return dataMap; return dataMap;
} }
public int filteredAmount(String validSource, String validTarget, Card host, SpellAbility sa) {
int result = 0;
Set<Card> filteredSource = null;
Set<GameEntity> 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<Card, GameEntity, Integer> c : cellSet()) {
if (filteredSource != null && !filteredSource.contains(c.getRowKey())) {
continue;
}
if (filteredTarget != null && !filteredTarget.contains(c.getColumnKey())) {
continue;
}
result += c.getValue();
}
return result;
}
} }

View File

@@ -836,7 +836,7 @@ public class Combat {
// Run the trigger to deal combat damage once // Run the trigger to deal combat damage once
// LifeLink for Combat Damage at this place // LifeLink for Combat Damage at this place
dealtDamageTo.triggerDamageDoneOnce(true, null); dealtDamageTo.triggerDamageDoneOnce(true, game, null);
dealtDamageTo.clear(); dealtDamageTo.clear();
counterTable.triggerCountersPutAll(game); counterTable.triggerCountersPutAll(game);

View File

@@ -74,7 +74,7 @@ public class CostDamage extends CostPart {
payer.addDamage(decision.c, source, damageMap, preventMap, table, sa); payer.addDamage(decision.c, source, damageMap, preventMap, table, sa);
preventMap.triggerPreventDamage(false); preventMap.triggerPreventDamage(false);
damageMap.triggerDamageDoneOnce(false, sa); damageMap.triggerDamageDoneOnce(false, source.getGame(), sa);
table.triggerCountersPutAll(payer.getGame()); table.triggerCountersPutAll(payer.getGame());
preventMap.clear(); preventMap.clear();

View File

@@ -1708,7 +1708,8 @@ public class Player extends GameEntity implements Comparable<Player> {
if (land.isFaceDown()) { if (land.isFaceDown()) {
land.turnFaceUp(); 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 // play a sound
game.fireEvent(new GameEventLandPlayed(this, land)); game.fireEvent(new GameEventLandPlayed(this, land));

View File

@@ -164,9 +164,8 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
*/ */
public final ReplacementEffect copy(final Card host, final boolean lki) { public final ReplacementEffect copy(final Card host, final boolean lki) {
final ReplacementEffect res = (ReplacementEffect) clone(); final ReplacementEffect res = (ReplacementEffect) clone();
for (String key : getSVars()) {
res.setSVar(key, getSVar(key)); copyHelper(res, host);
}
final SpellAbility sa = this.getOverridingAbility(); final SpellAbility sa = this.getOverridingAbility();
if (sa != null) { if (sa != null) {
@@ -182,8 +181,6 @@ public abstract class ReplacementEffect extends TriggerReplacementBase {
res.setOtherChoices(null); res.setOtherChoices(null);
} }
res.setHostCard(host);
res.setActiveZone(validHostZones); res.setActiveZone(validHostZones);
res.setLayer(getLayer()); res.setLayer(getLayer());
return res; return res;

View File

@@ -870,13 +870,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
clone.view = new SpellAbilityView(clone); clone.view = new SpellAbilityView(clone);
// dont use setHostCard to not trigger the not copied parts yet // dont use setHostCard to not trigger the not copied parts yet
clone.hostCard = host;
copyHelper(clone, host);
if (!lki && host != null && host.getGame() != null) { if (!lki && host != null && host.getGame() != null) {
host.getGame().addSpellAbility(clone); 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); 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 // clear maps for copy, the values will be added later
clone.additionalAbilities = Maps.newHashMap(); clone.additionalAbilities = Maps.newHashMap();
clone.additionalAbilityLists = Maps.newHashMap(); clone.additionalAbilityLists = Maps.newHashMap();
clone.sVars = Maps.newHashMap();
// run special copy Ability to make a deep copy // run special copy Ability to make a deep copy
CardFactory.copySpellAbility(this, clone, host, activ, lki); CardFactory.copySpellAbility(this, clone, host, activ, lki);
} catch (final CloneNotSupportedException e) { } catch (final CloneNotSupportedException e) {

View File

@@ -819,13 +819,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
clone = (StaticAbility) clone(); clone = (StaticAbility) clone();
clone.id = lki ? id : nextId(); clone.id = lki ? id : nextId();
// dont use setHostCard to not trigger the not copied parts yet copyHelper(clone, host);
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);
clone.layers = this.generateLayer(); clone.layers = this.generateLayer();

View File

@@ -37,7 +37,6 @@ import forge.game.zone.ZoneType;
import java.util.*; import java.util.*;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import forge.util.TextUtil; import forge.util.TextUtil;
@@ -523,9 +522,7 @@ public abstract class Trigger extends TriggerReplacementBase {
public final Trigger copy(Card newHost, boolean lki) { public final Trigger copy(Card newHost, boolean lki) {
final Trigger copy = (Trigger) clone(); final Trigger copy = (Trigger) clone();
copy.originalMapParams = Maps.newHashMap(originalMapParams); copyHelper(copy, newHost);
copy.mapParams = Maps.newHashMap(originalMapParams);
copy.setHostCard(newHost);
if (getOverridingAbility() != null) { if (getOverridingAbility() != null) {
copy.setOverridingAbility(getOverridingAbility().copy(newHost, lki)); copy.setOverridingAbility(getOverridingAbility().copy(newHost, lki));

View File

@@ -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<String, String> params, Card host, boolean intrinsic) {
super(params, host, intrinsic);
}
@Override
public boolean performTest(Map<AbilityKey, Object> 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<AbilityKey, Object> 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);
}
}

View File

@@ -43,6 +43,7 @@ public enum TriggerType {
CounterRemovedOnce(TriggerCounterRemovedOnce.class), CounterRemovedOnce(TriggerCounterRemovedOnce.class),
Crewed(TriggerCrewed.class), Crewed(TriggerCrewed.class),
Cycled(TriggerCycled.class), Cycled(TriggerCycled.class),
DamageAll(TriggerDamageAll.class),
DamageDealtOnce(TriggerDamageDealtOnce.class), DamageDealtOnce(TriggerDamageDealtOnce.class),
DamageDone(TriggerDamageDone.class), DamageDone(TriggerDamageDone.class),
DamageDoneOnce(TriggerDamageDoneOnce.class), DamageDoneOnce(TriggerDamageDoneOnce.class),

View File

@@ -17,39 +17,11 @@
*/ */
package forge.screens.match; 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.base.Function;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import forge.FThreads; import forge.*;
import forge.GuiBase;
import forge.ImageCache;
import forge.LobbyPlayer;
import forge.Singletons;
import forge.StaticData;
import forge.assets.FSkinProp; import forge.assets.FSkinProp;
import forge.card.CardStateName; import forge.card.CardStateName;
import forge.control.KeyboardShortcuts; import forge.control.KeyboardShortcuts;
@@ -71,51 +43,26 @@ import forge.game.phase.PhaseType;
import forge.game.player.DelayedReveal; import forge.game.player.DelayedReveal;
import forge.game.player.IHasIcon; import forge.game.player.IHasIcon;
import forge.game.player.PlayerView; import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbility; import forge.game.spellability.*;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.SpellAbilityView;
import forge.game.spellability.StackItemView;
import forge.game.spellability.TargetChoices;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.gui.FNetOverlay; import forge.gui.*;
import forge.gui.GuiChoose; import forge.gui.framework.*;
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.item.InventoryItem; import forge.item.InventoryItem;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.match.AbstractGuiGame; import forge.match.AbstractGuiGame;
import forge.menus.IMenuProvider; import forge.menus.IMenuProvider;
import forge.model.FModel; import forge.model.FModel;
import forge.player.PlayerZoneUpdate; import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.properties.ForgeConstants; import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences; import forge.properties.ForgePreferences;
import forge.properties.ForgePreferences.FPref; import forge.properties.ForgePreferences.FPref;
import forge.screens.match.controllers.CAntes; import forge.screens.match.controllers.*;
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.menus.CMatchUIMenus; import forge.screens.match.menus.CMatchUIMenus;
import forge.screens.match.views.VField; import forge.screens.match.views.VField;
import forge.screens.match.views.VHand; import forge.screens.match.views.VHand;
import forge.toolbox.FButton; import forge.toolbox.*;
import forge.toolbox.FLabel;
import forge.toolbox.FOptionPane;
import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinImage; import forge.toolbox.FSkin.SkinImage;
import forge.toolbox.FTextArea;
import forge.toolbox.imaging.FImagePanel; import forge.toolbox.imaging.FImagePanel;
import forge.toolbox.imaging.FImagePanel.AutoSizeImageMode; import forge.toolbox.imaging.FImagePanel.AutoSizeImageMode;
import forge.toolbox.imaging.FImageUtil; import forge.toolbox.imaging.FImageUtil;
@@ -123,15 +70,26 @@ import forge.toolbox.special.PhaseIndicator;
import forge.toolbox.special.PhaseLabel; import forge.toolbox.special.PhaseLabel;
import forge.trackable.TrackableCollection; import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent; import forge.util.ITriggerEvent;
import forge.util.Localizer;
import forge.util.collect.FCollection; import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView; import forge.util.collect.FCollectionView;
import forge.util.gui.SOptionPane; import forge.util.gui.SOptionPane;
import forge.util.Localizer;
import forge.view.FView; import forge.view.FView;
import forge.view.arcane.CardPanel; import forge.view.arcane.CardPanel;
import forge.view.arcane.FloatingZone; import forge.view.arcane.FloatingZone;
import net.miginfocom.swing.MigLayout; 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 * Constructs instance of match UI controller, used as a single point of
* top-level control for child UIs. Tasks targeting the view of individual * top-level control for child UIs. Tasks targeting the view of individual
@@ -459,6 +417,8 @@ public final class CMatchUI
@Override @Override
public Iterable<PlayerZoneUpdate> tempShowZones(final PlayerView controller, final Iterable<PlayerZoneUpdate> zonesToUpdate) { public Iterable<PlayerZoneUpdate> tempShowZones(final PlayerView controller, final Iterable<PlayerZoneUpdate> zonesToUpdate) {
List<PlayerZoneUpdate> updatedPlayerZones = Lists.newArrayList();
for (final PlayerZoneUpdate update : zonesToUpdate) { for (final PlayerZoneUpdate update : zonesToUpdate) {
final PlayerView player = update.getPlayer(); final PlayerView player = update.getPlayer();
for (final ZoneType zone : update.getZones()) { for (final ZoneType zone : update.getZones()) {
@@ -467,7 +427,9 @@ public final class CMatchUI
break; break;
case Hand: // controller hand always shown case Hand: // controller hand always shown
if (controller != player) { if (controller != player) {
FloatingZone.show(this,player,zone); if (FloatingZone.show(this,player,zone)) {
updatedPlayerZones.add(update);
}
} }
break; break;
case Library: case Library:
@@ -475,14 +437,16 @@ public final class CMatchUI
case Exile: case Exile:
case Flashback: case Flashback:
case Command: case Command:
FloatingZone.show(this,player,zone); if (FloatingZone.show(this,player,zone)) {
updatedPlayerZones.add(update);
}
break; break;
default: default:
break; break;
} }
} }
} }
return zonesToUpdate; //pfps should return only the newly shown zones return updatedPlayerZones;
} }
@Override @Override
@@ -1086,21 +1050,30 @@ public final class CMatchUI
} }
@Override @Override
public boolean openZones(final Collection<ZoneType> zones, final Map<PlayerView, Object> players) { public PlayerZoneUpdates openZones(PlayerView controller, final Collection<ZoneType> zones, final Map<PlayerView, Object> playersWithTargetables) {
if (zones.size() == 1) { final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
switch (zones.iterator().next()) { for (final PlayerView view : playersWithTargetables.keySet()) {
case Battlefield: for(final ZoneType zone : zones) {
case Hand: if (zone.equals(ZoneType.Battlefield) || zone.equals(ZoneType.Hand)) {
return true; //don't actually need to open anything, but indicate that zone can be opened continue;
default: }
return false;
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 @Override
public void restoreOldZones(final Map<PlayerView, Object> playersToRestoreZonesFor) { public void restoreOldZones(PlayerView playerView, PlayerZoneUpdates playerZoneUpdates) {
hideZones(playerView, playerZoneUpdates);
} }
@Override @Override

View File

@@ -61,6 +61,9 @@ import forge.util.Localizer;
*/ */
public class FSkin { 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. * Retrieves a color from this skin's color map.
* *
@@ -77,7 +80,7 @@ public class FSkin {
* *
* @param clr0 {@link java.awt.Color} * @param clr0 {@link java.awt.Color}
* @param step int * @param step int
* @return {@link java.awt.Color} * @return {@link java.awt.CFaceolor}
*/ */
public static Color stepColor(final Color clr0, final int step) { public static Color stepColor(final Color clr0, final int step) {
int r = clr0.getRed(); int r = clr0.getRed();
@@ -1006,7 +1009,7 @@ public class FSkin {
private static void addEncodingSymbol(final String key, final FSkinProp skinProp) { private static void addEncodingSymbol(final String key, final FSkinProp skinProp) {
final String path = ForgeConstants.CACHE_SYMBOLS_DIR + "/" + key.replace("/", "") + ".png"; 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) { public static String encodeSymbols(String str, final boolean formatReminderText) {
@@ -1023,7 +1026,7 @@ public class FSkin {
//format mana symbols to display as icons //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 pattern = "\\{([A-Z0-9]+)\\}|\\{([A-Z0-9]+)/([A-Z0-9]+)\\}"; //fancy pattern needed so "/" can be omitted from replacement
try { try {
replacement = "<img src=\"" + new File(ForgeConstants.CACHE_SYMBOLS_DIR + "/$1$2$3.png").toURI().toURL().toString() + "\">"; replacement = "<img src=\"" + new File(ForgeConstants.CACHE_SYMBOLS_DIR + "/$1$2$3.png").toURI().toURL().toString() + "\" width=" + SYMBOL_WIDTH + " height=" + SYMBOL_HEIGHT + ">";
str = str.replaceAll(pattern, replacement); str = str.replaceAll(pattern, replacement);
} catch (final MalformedURLException e) { } catch (final MalformedURLException e) {
e.printStackTrace(); e.printStackTrace();

View File

@@ -17,18 +17,6 @@
*/ */
package forge.view.arcane; 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.Singletons;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.gui.framework.SDisplayUtil; import forge.gui.framework.SDisplayUtil;
@@ -39,10 +27,19 @@ import forge.screens.match.CMatchUI;
import forge.toolbox.FMouseAdapter; import forge.toolbox.FMouseAdapter;
import forge.toolbox.FScrollPane; import forge.toolbox.FScrollPane;
import forge.toolbox.MouseTriggerEvent; import forge.toolbox.MouseTriggerEvent;
//import forge.util.collect.FCollectionView;
import forge.view.FDialog; import forge.view.FDialog;
import forge.view.FFrame; 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 // show some cards in a new window
public abstract class FloatingCardArea extends CardArea { 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().setFocusableWindowState(false); // should probably do this earlier
getWindow().setVisible(true); getWindow().setVisible(true);
} }
protected void hideWindow() { protected void hideWindow() {
onShow(); onShow();
getWindow().setFocusableWindowState(false); // should probably do this earlier getWindow().setFocusableWindowState(false); // should probably do this earlier
getWindow().setVisible(false); 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() { protected void showOrHideWindow() {
if (getWindow().isVisible()) { if (getWindow().isVisible()) {
hideWindow(); hideWindow();
} else { } else {
showWindow(); showWindow();
} }
} }
protected void onShow() { protected void onShow() {
if (!hasBeenShown) { if (!hasBeenShown) {
@@ -98,24 +97,31 @@ public abstract class FloatingCardArea extends CardArea {
if (hasBeenShown || locLoaded) { return; } if (hasBeenShown || locLoaded) { return; }
super.setLocationRelativeTo(c); super.setLocationRelativeTo(c);
} }
@Override @Override
public void setVisible(boolean b0) { public void setVisible(boolean b0) {
if (isVisible() == b0) { return; } if (isVisible() == b0) {
return;
}
if (!b0 && hasBeenShown && locPref != null) { if (!b0 && hasBeenShown && locPref != null) {
//update preference before hiding window, as otherwise its location will be 0,0 //update preference before hiding window, as otherwise its location will be 0,0
prefs.setPref(locPref, prefs.setPref(locPref,
getX() + COORD_DELIM + getY() + COORD_DELIM + 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 //don't call prefs.save(), instead allowing them to be saved when match ends
} }
if (b0) { 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; hasBeenShown = true;
} }
super.setVisible(b0); super.setVisible(b0);
} }
}; };
public boolean isVisible() {
return window.isVisible();
}
protected FDialog getWindow() { protected FDialog getWindow() {
return window; return window;
} }

View File

@@ -6,38 +6,37 @@
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package forge.view.arcane; package forge.view.arcane;
import java.util.HashMap; import forge.FThreads;
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.assets.FSkinProp; import forge.assets.FSkinProp;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.game.player.PlayerView; import forge.game.player.PlayerView;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.properties.ForgePreferences.FPref; import forge.properties.ForgePreferences.FPref;
import forge.screens.match.CMatchUI; import forge.screens.match.CMatchUI;
import forge.toolbox.FScrollPane;
import forge.toolbox.FMouseAdapter; import forge.toolbox.FMouseAdapter;
import forge.toolbox.FScrollPane;
import forge.toolbox.FSkin; import forge.toolbox.FSkin;
import forge.util.Lang; import forge.util.Lang;
import forge.util.collect.FCollection; 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 { public class FloatingZone extends FloatingCardArea {
private static final long serialVersionUID = 1927906492186378596L; 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) { private static int getKey(final PlayerView player, final ZoneType zone) {
return 40 * player.getId() + zone.hashCode(); return 40 * player.getId() + zone.hashCode();
} }
public static void showOrHide(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) { public static void showOrHide(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
final FloatingZone cardArea = _init(matchUI, player, zone); final FloatingZone cardArea = _init(matchUI, player, zone);
cardArea.showOrHideWindow(); 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); 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); 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) { private static FloatingZone _init(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
final int key = getKey(player, zone); final int key = getKey(player, zone);
FloatingZone cardArea = floatingAreas.get(key); FloatingZone cardArea = floatingAreas.get(key);
@@ -69,10 +94,12 @@ public class FloatingZone extends FloatingCardArea {
} }
return cardArea; return cardArea;
} }
public static CardPanel getCardPanel(final CMatchUI matchUI, final CardView card) { public static CardPanel getCardPanel(final CMatchUI matchUI, final CardView card) {
final FloatingZone window = _init(matchUI, card.getController(), card.getZone()); final FloatingZone window = _init(matchUI, card.getController(), card.getZone());
return window.getCardPanel(card.getId()); return window.getCardPanel(card.getId());
} }
public static void refresh(final PlayerView player, final ZoneType zone) { public static void refresh(final PlayerView player, final ZoneType zone) {
FloatingZone cardArea = floatingAreas.get(getKey(player, zone)); FloatingZone cardArea = floatingAreas.get(getKey(player, zone));
if (cardArea != null) { if (cardArea != null) {
@@ -82,21 +109,23 @@ public class FloatingZone extends FloatingCardArea {
//refresh flashback zone when graveyard, library, or exile zones updated //refresh flashback zone when graveyard, library, or exile zones updated
switch (zone) { switch (zone) {
case Graveyard: case Graveyard:
case Library: case Library:
case Exile: case Exile:
refresh(player, ZoneType.Flashback); refresh(player, ZoneType.Flashback);
break; break;
default: default:
break; break;
} }
} }
public static void closeAll() { public static void closeAll() {
for (final FloatingZone cardArea : floatingAreas.values()) { for (final FloatingZone cardArea : floatingAreas.values()) {
cardArea.window.setVisible(false); cardArea.window.setVisible(false);
} }
floatingAreas.clear(); floatingAreas.clear();
} }
public static void refreshAll() { public static void refreshAll() {
for (final FloatingZone cardArea : floatingAreas.values()) { for (final FloatingZone cardArea : floatingAreas.values()) {
cardArea.refresh(); cardArea.refresh();
@@ -110,23 +139,23 @@ public class FloatingZone extends FloatingCardArea {
protected FCollection<CardView> cardList; protected FCollection<CardView> cardList;
private final Comparator<CardView> comp = new Comparator<CardView>() { private final Comparator<CardView> comp = new Comparator<CardView>() {
@Override @Override
public int compare(CardView lhs, CardView rhs) { public int compare(CardView lhs, CardView rhs) {
if ( !getMatchUI().mayView(lhs) ) { if (!getMatchUI().mayView(lhs)) {
return ( getMatchUI().mayView(rhs) ) ? 1 : 0 ; return (getMatchUI().mayView(rhs)) ? 1 : 0;
} else if ( !getMatchUI().mayView(rhs) ) { } else if (!getMatchUI().mayView(rhs)) {
return -1; return -1;
} else { } else {
return lhs.getName().compareTo(rhs.getName()); return lhs.getName().compareTo(rhs.getName());
} }
} }
}; };
protected Iterable<CardView> getCards() { protected Iterable<CardView> getCards() {
Iterable<CardView> zoneCards = player.getCards(zone); Iterable<CardView> zoneCards = player.getCards(zone);
if ( zoneCards != null ) { if (zoneCards != null) {
cardList = new FCollection<>(zoneCards); cardList = new FCollection<>(zoneCards);
if ( sortedByName ) { if (sortedByName) {
Collections.sort(cardList, comp); Collections.sort(cardList, comp);
} }
return cardList; return cardList;
@@ -138,28 +167,28 @@ public class FloatingZone extends FloatingCardArea {
private FloatingZone(final CMatchUI matchUI, final PlayerView player0, final ZoneType zone0) { 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)); super(matchUI, new FScrollPane(false, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER));
window.add(getScrollPane(), "grow, push"); 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); getScrollPane().setViewportView(this);
setOpaque(false); setOpaque(false);
switch (zone0) { switch (zone0) {
case Exile: case Exile:
window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_EXILE)); window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_EXILE));
break; break;
case Graveyard: case Graveyard:
window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_GRAVEYARD)); window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_GRAVEYARD));
break; break;
case Hand: case Hand:
window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_HAND)); window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_HAND));
break; break;
case Library: case Library:
window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_LIBRARY)); window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_LIBRARY));
break; break;
case Flashback: case Flashback:
window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_FLASHBACK)); window.setIconImage(FSkin.getImage(FSkinProp.IMG_ZONE_FLASHBACK));
break; break;
default: default:
locPref = null; locPref = null;
break; break;
} }
zone = zone0; zone = zone0;
setPlayer(player0); setPlayer(player0);
@@ -167,19 +196,20 @@ public class FloatingZone extends FloatingCardArea {
} }
private void toggleSorted() { private void toggleSorted() {
sortedByName = !sortedByName; sortedByName = !sortedByName;
setTitle(); setTitle();
refresh(); refresh();
// revalidation does not appear to be necessary here // revalidation does not appear to be necessary here
getWindow().repaint(); getWindow().repaint();
} }
@Override @Override
protected void onShow() { protected void onShow() {
super.onShow(); super.onShow();
if (!hasBeenShown) { if (!hasBeenShown) {
getWindow().getTitleBar().addMouseListener(new FMouseAdapter() { getWindow().getTitleBar().addMouseListener(new FMouseAdapter() {
@Override public final void onRightClick(final MouseEvent e) { @Override
public final void onRightClick(final MouseEvent e) {
toggleSorted(); toggleSorted();
} }
}); });
@@ -188,34 +218,36 @@ public class FloatingZone extends FloatingCardArea {
private void setTitle() { private void setTitle() {
title = Lang.getPossessedObject(player.getName(), zone.name()) + " (%d)" + 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) { private void setPlayer(PlayerView player0) {
if (player == player0) { return; } if (player == player0) {
return;
}
player = player0; player = player0;
setTitle(); setTitle();
boolean isAi = player0.isAI(); boolean isAi = player0.isAI();
switch (zone) { switch (zone) {
case Exile: case Exile:
locPref = isAi ? FPref.ZONE_LOC_AI_EXILE : FPref.ZONE_LOC_HUMAN_EXILE; locPref = isAi ? FPref.ZONE_LOC_AI_EXILE : FPref.ZONE_LOC_HUMAN_EXILE;
break; break;
case Graveyard: case Graveyard:
locPref = isAi ? FPref.ZONE_LOC_AI_GRAVEYARD : FPref.ZONE_LOC_HUMAN_GRAVEYARD; locPref = isAi ? FPref.ZONE_LOC_AI_GRAVEYARD : FPref.ZONE_LOC_HUMAN_GRAVEYARD;
break; break;
case Hand: case Hand:
locPref = isAi ? FPref.ZONE_LOC_AI_HAND : FPref.ZONE_LOC_HUMAN_HAND; locPref = isAi ? FPref.ZONE_LOC_AI_HAND : FPref.ZONE_LOC_HUMAN_HAND;
break; break;
case Library: case Library:
locPref = isAi ? FPref.ZONE_LOC_AI_LIBRARY : FPref.ZONE_LOC_HUMAN_LIBRARY; locPref = isAi ? FPref.ZONE_LOC_AI_LIBRARY : FPref.ZONE_LOC_HUMAN_LIBRARY;
break; break;
case Flashback: case Flashback:
locPref = isAi ? FPref.ZONE_LOC_AI_FLASHBACK : FPref.ZONE_LOC_HUMAN_FLASHBACK; locPref = isAi ? FPref.ZONE_LOC_AI_FLASHBACK : FPref.ZONE_LOC_HUMAN_FLASHBACK;
break; break;
default: default:
locPref = null; locPref = null;
break; break;
} }
} }

View File

@@ -5,7 +5,6 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import forge.FThreads; import forge.FThreads;
import forge.assets.FSkinImage; import forge.assets.FSkinImage;
@@ -44,6 +43,7 @@ import forge.match.AbstractGuiGame;
import forge.match.HostedMatch; import forge.match.HostedMatch;
import forge.model.FModel; import forge.model.FModel;
import forge.player.PlayerZoneUpdate; import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.properties.ForgePreferences; import forge.properties.ForgePreferences;
import forge.properties.ForgePreferences.FPref; import forge.properties.ForgePreferences.FPref;
import forge.screens.match.views.VAssignDamage; import forge.screens.match.views.VAssignDamage;
@@ -307,40 +307,51 @@ public class MatchController extends AbstractGuiGame {
} }
@Override @Override
public boolean openZones(final Collection<ZoneType> zones, final Map<PlayerView, Object> players) { public PlayerZoneUpdates openZones(PlayerView controller, final Collection<ZoneType> zones, final Map<PlayerView, Object> playersWithTargetables) {
PlayerZoneUpdates updates = new PlayerZoneUpdates();
if (zones.size() == 1) { if (zones.size() == 1) {
final ZoneType zoneType = zones.iterator().next(); final ZoneType zoneType = zones.iterator().next();
switch (zoneType) { switch (zoneType) {
case Battlefield: case Battlefield:
case Command: case Command:
players.clear(); //clear since no zones need to be restored playersWithTargetables.clear(); //clear since no zones need to be restored
return true; //Battlefield is always open
default: default:
//open zone tab for given zone if needed //open zone tab for given zone if needed
boolean result = true; boolean result = true;
for (final PlayerView player : players.keySet()) { for (final PlayerView player : playersWithTargetables.keySet()) {
final VPlayerPanel playerPanel = view.getPlayerPanel(player); 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); final InfoTab zoneTab = playerPanel.getZoneTab(zoneType);
if (zoneTab == null) { ZoneType previousZone = playerPanel.getZoneByInfoTab(playerPanel.getSelectedTab());
result = false; updates.add(new PlayerZoneUpdate(player, previousZone));
} else { if (zoneTab != null) {
playerPanel.setSelectedTab(zoneTab); playerPanel.setSelectedTab(zoneTab);
} }
} }
return result;
} }
} }
return false; return updates;
} }
@Override @Override
public void restoreOldZones(final Map<PlayerView, Object> playersToRestoreZonesFor) { public void restoreOldZones(PlayerView playerView, PlayerZoneUpdates playerZoneUpdates) {
for (final Entry<PlayerView, Object> player : playersToRestoreZonesFor.entrySet()) { for(PlayerZoneUpdate update : playerZoneUpdates) {
final VPlayerPanel playerPanel = view.getPlayerPanel(player.getKey()); PlayerView player = update.getPlayer();
if (player.getValue() == null || player.getValue() instanceof InfoTab) {
playerPanel.setSelectedTab((InfoTab) player.getValue()); 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 @Override
public void hideZones(final PlayerView controller, final Iterable<PlayerZoneUpdate> zonesToUpdate) { public void hideZones(final PlayerView controller, final Iterable<PlayerZoneUpdate> zonesToUpdate) {
view.hideZones(controller, zonesToUpdate); view.hideZones(controller, zonesToUpdate);
} }
@Override @Override

View File

@@ -102,18 +102,32 @@ public class VPlayerPanel extends FContainer {
return zoneTabs.get(zoneType); 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) { public void setSelectedZone(ZoneType zoneType) {
setSelectedTab(zoneTabs.get(zoneType)); setSelectedTab(zoneTabs.get(zoneType));
} }
public void hideSelectedTab() {
if (selectedTab != null) {
selectedTab.displayArea.setVisible(false);
}
}
public void setSelectedTab(InfoTab selectedTab0) { public void setSelectedTab(InfoTab selectedTab0) {
if (selectedTab == selectedTab0) { if (selectedTab == selectedTab0) {
return; return;
} }
if (selectedTab != null) { hideSelectedTab();
selectedTab.displayArea.setVisible(false);
}
selectedTab = selectedTab0; selectedTab = selectedTab0;

View File

@@ -31,6 +31,7 @@ import forge.menu.FDropDown;
import forge.menu.FMenuItem; import forge.menu.FMenuItem;
import forge.menu.FMenuTab; import forge.menu.FMenuTab;
import forge.menu.FPopupMenu; import forge.menu.FPopupMenu;
import forge.player.PlayerZoneUpdates;
import forge.screens.match.MatchController; import forge.screens.match.MatchController;
import forge.screens.match.TargetingOverlay; import forge.screens.match.TargetingOverlay;
import forge.toolbox.FCardPanel; import forge.toolbox.FCardPanel;
@@ -55,6 +56,7 @@ public class VStack extends FDropDown {
private StackInstanceDisplay activeItem; private StackInstanceDisplay activeItem;
private StackItemView activeStackInstance; private StackItemView activeStackInstance;
private Map<PlayerView, Object> playersWithValidTargets; private Map<PlayerView, Object> playersWithValidTargets;
private PlayerZoneUpdates restorablePlayerZones = null;
private int stackSize; private int stackSize;
@@ -70,6 +72,8 @@ public class VStack extends FDropDown {
private void revealTargetZones() { private void revealTargetZones() {
if (activeStackInstance == null) { return; } if (activeStackInstance == null) { return; }
PlayerView player = MatchController.instance.getCurrentPlayer();
final Set<ZoneType> zones = new HashSet<>(); final Set<ZoneType> zones = new HashSet<>();
playersWithValidTargets = new HashMap<>(); playersWithValidTargets = new HashMap<>();
for (final CardView c : activeStackInstance.getTargetCards()) { for (final CardView c : activeStackInstance.getTargetCards()) {
@@ -79,14 +83,15 @@ public class VStack extends FDropDown {
} }
} }
if (zones.isEmpty() || playersWithValidTargets.isEmpty()) { return; } 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 //restore old zones when active stack instance changes
private void restoreOldZones() { private void restoreOldZones() {
if (playersWithValidTargets == null) { return; } if (restorablePlayerZones == null) { return; }
MatchController.instance.restoreOldZones(playersWithValidTargets); PlayerView player = MatchController.instance.getCurrentPlayer();
playersWithValidTargets = null; MatchController.instance.restoreOldZones(player, restorablePlayerZones);
restorablePlayerZones = null;
} }
@Override @Override

View File

@@ -1,9 +1,6 @@
#Add one announcement per line #Add one announcement per line
Theros Beyond Death Prerelease! 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.
"Prerelease" limited mode for latest sets
Bunches of bug fixes, Bunches of bug fixes,
Continued work on Translations 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] [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]! 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]!

View File

@@ -33,6 +33,7 @@ tjtillman
tojammot tojammot
torridus torridus
Xyx Xyx
Zimtente
Zuchinni Zuchinni
(If you think your name should be on this list, add it with your next contribution) (If you think your name should be on this list, add it with your next contribution)

View File

@@ -1556,6 +1556,39 @@ Evolving Wilds
13 Swamp|M20 13 Swamp|M20
[ELD Secret Cards] [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 Kenrith, the Returned King
Rowan, Fearless Sparkmage Rowan, Fearless Sparkmage
Garrison Griffin Garrison Griffin
@@ -1587,6 +1620,70 @@ Syr Gwyn, Hero of Ashvale
Arcane Signet Arcane Signet
Tome of Legends Tome of Legends
Command Tower 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] [THB Secret Cards]
Athreos, Shroud-Veiled Athreos, Shroud-Veiled
@@ -1627,6 +1724,7 @@ Eidolon of Obstruction|THB|2
Heliod's Intervention|THB|2 Heliod's Intervention|THB|2
Idyllic Tutor|THB|2 Idyllic Tutor|THB|2
Shatter the Sky|THB|2 Shatter the Sky|THB|2
Taranika, Akroan Veteran|THB|2
Ashiok's Erasure|THB|2 Ashiok's Erasure|THB|2
Nadir Kraken|THB|2 Nadir Kraken|THB|2
Protean Thaumaturge|THB|2 Protean Thaumaturge|THB|2
@@ -1674,4 +1772,10 @@ Temple of Abandon|THB|2
Temple of Deceit|THB|2 Temple of Deceit|THB|2
Temple of Enlightenment|THB|2 Temple of Enlightenment|THB|2
Temple of Malice|THB|2 Temple of Malice|THB|2
Temple of Plenty|THB|2 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

View File

@@ -2,7 +2,7 @@ Name:Hapatra, Vizier of Poisons
ManaCost:B G ManaCost:B G
Types:Legendary Creature Human Cleric Types:Legendary Creature Human Cleric
PT:2/2 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 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. 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 SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_snake_deathtouch | TokenOwner$ You | LegacyImage$ g 1 1 snake deathtouch akh

View File

@@ -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.

View File

@@ -1,5 +1,5 @@
Name:Mire in Misery Name:Mire in Misery
ManaCost:1 B ManaCost:1 B
Types:Sorcery 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. Oracle:Each opponent sacrifices a creature or enchantment.

View File

@@ -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

View File

@@ -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

View File

@@ -263,21 +263,25 @@ Prerelease=6 Boosters, 1 RareMythic+
252 L Swamp 252 L Swamp
253 L Mountain 253 L Mountain
254 L Forest 254 L Forest
#255 M Elspeth, Sun's Nemesis #Statue borderless planeswalkers
#256 M Ashiok, Nightmare Muse 255 M Elspeth, Sun's Nemesis
#257 M Calix, Destiny's Hand 256 M Ashiok, Nightmare Muse
#258 U Daxos, Blessed by the Sun #Constellation Gods and Demigods
#259 M Heliod, Sun-Crowned 257 M Calix, Destiny's Hand
#260 U Callaphe, Beloved of the Sea 258 U Daxos, Blessed by the Sun
#261 M Thassa, Deep-Dwelling 259 M Heliod, Sun-Crowned
#262 M Erebos, Bleak-Hearted 260 U Callaphe, Beloved of the Sea
#263 U Tymaret, Chosen from Death 261 M Thassa, Deep-Dwelling
#264 U Anax, Hardened in the Forge 262 M Erebos, Bleak-Hearted
#265 M Purphoros, Bronze-Blooded 263 U Tymaret, Chosen from Death
#266 M Nylea, Keen-Eyed 264 U Anax, Hardened in the Forge
#267 U Renata, Called to the Hunt 265 M Purphoros, Bronze-Blooded
#268 M Klothys, God of Destiny 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 269 M Athreos, Shroud-Veiled
#Planeswalker Deck Cards
270 M Elspeth, Undaunted Hero 270 M Elspeth, Undaunted Hero
271 U Eidolon of Inspiration 271 U Eidolon of Inspiration
272 R Elspeth's Devotee 272 R Elspeth's Devotee
@@ -296,6 +300,7 @@ Prerelease=6 Boosters, 1 RareMythic+
285 L Mountain 285 L Mountain
286 L Forest 286 L Forest
287 L Forest 287 L Forest
#Theme Booster Exclusive Rares
288 R Grasping Giant 288 R Grasping Giant
289 R Victory's Envoy 289 R Victory's Envoy
290 R Sphinx Mindbreaker 290 R Sphinx Mindbreaker
@@ -306,59 +311,69 @@ Prerelease=6 Boosters, 1 RareMythic+
295 R Terror of Mount Velus 295 R Terror of Mount Velus
296 R Ironscale Hydra 296 R Ironscale Hydra
297 R Treeshaker Chimera 297 R Treeshaker Chimera
#298 R Archon of Sun's Grace #Borderless art rares and mythics
#299 R Eidolon of Obstruction 298 R Archon of Sun's Grace
#300 R Heliod's Intervention 299 R Eidolon of Obstruction
#301 R Idyllic Tutor 300 R Heliod's Intervention
#302 R Shatter the Sky 301 R Idyllic Tutor
#304 R Ashiok's Erasure 302 R Shatter the Sky
#305 R Nadir Kraken 303 R Taranika, Akroan Veteran
#306 R Protean Thaumaturge 304 R Ashiok's Erasure
#307 R Thassa's Intervention 305 R Nadir Kraken
#308 R Thassa's Oracle 306 R Protean Thaumaturge
#309 R Thryx, the Sudden Storm 307 R Thassa's Intervention
#310 R Wavebreak Hippocamp 308 R Thassa's Oracle
#311 R Aphemia, the Cacophony 309 R Thryx, the Sudden Storm
#312 R Eat to Extinction 310 R Wavebreak Hippocamp
#313 R Erebos's Intervention 311 R Aphemia, the Cacophony
#314 R Gravebreaker Lamia 312 R Eat to Extinction
#315 R Nightmare Shepherd 313 R Erebos's Intervention
#316 R Treacherous Blessing 314 R Gravebreaker Lamia
#317 R Woe Strider 315 R Nightmare Shepherd
#318 M Ox of Agonas 316 R Treacherous Blessing
#319 R Phoenix of Ash 317 R Woe Strider
#320 R Purphoros's Intervention 318 M Ox of Agonas
#321 R Storm Herald 319 R Phoenix of Ash
#322 R Storm's Wrath 320 R Purphoros's Intervention
#323 R Tectonic Giant 321 R Storm Herald
#324 R Underworld Breach 322 R Storm's Wrath
#325 R Arasta of the Endless Web 323 R Tectonic Giant
#326 R Dryad of the Ilysian Grove 324 R Underworld Breach
#327 R Mantle of the Wolf 325 R Arasta of the Endless Web
#328 R Nessian Boar 326 R Dryad of the Ilysian Grove
#329 R Nylea's Intervention 327 R Mantle of the Wolf
#330 M Nyxbloom Ancient 328 R Nessian Boar
#331 R Setessan Champion 329 R Nylea's Intervention
#332 R Allure of the Unknown 330 M Nyxbloom Ancient
#333 R Atris, Oracle of Half-Truths 331 R Setessan Champion
#334 R Bronzehide Lion 332 R Allure of the Unknown
#335 R Dalakos, Crafter of Wonders 333 R Atris, Oracle of Half-Truths
#336 R Dream Trawler 334 R Bronzehide Lion
#337 R Enigmatic Incarnation 335 R Dalakos, Crafter of Wonders
#338 R Gallia of the Endless Dance 336 R Dream Trawler
#339 R Haktos the Unscarred 337 R Enigmatic Incarnation
#340 M Kroxa, Titan of Death's Hunger 338 R Gallia of the Endless Dance
#341 R Kunoros, Hound of Athreos 339 R Haktos the Unscarred
#342 M Polukranos, Unchained 340 M Kroxa, Titan of Death's Hunger
#343 M Uro, Titan of Nature's Wrath 341 R Kunoros, Hound of Athreos
#344 R Nyx Lotus 342 M Polukranos, Unchained
#345 R Shadowspear 343 M Uro, Titan of Nature's Wrath
#346 R Labyrinth of Skophos 344 R Nyx Lotus
#347 R Temple of Abandon 345 R Shadowspear
#348 R Temple of Deceit 346 R Labyrinth of Skophos
#349 R Temple of Enlightenment 347 R Temple of Abandon
#350 R Temple of Malice 348 R Temple of Deceit
#351 R Temple of Plenty 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] [tokens]
b_2_2_zombie b_2_2_zombie

View File

@@ -279,7 +279,44 @@ Prerelease=6 Boosters, 1 RareMythic+
267 L Forest 267 L Forest
268 L Forest 268 L Forest
269 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 303 M Kenrith, the Returned King
#Planeswalker Deck Cards
304 M Rowan, Fearless Sparkmage 304 M Rowan, Fearless Sparkmage
305 C Garrison Griffin 305 C Garrison Griffin
306 U Rowan's Battleguard 306 U Rowan's Battleguard
@@ -310,6 +347,73 @@ Prerelease=6 Boosters, 1 RareMythic+
331 C Arcane Signet 331 C Arcane Signet
332 R Tome of Legends 332 R Tome of Legends
333 C Command Tower 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] [tokens]
w_0_1_goat w_0_1_goat

View File

@@ -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

View File

@@ -3,5 +3,5 @@ Name:Brawl
Order:101 Order:101
Type:Casual Type:Casual
Subtype:Commander Subtype:Commander
Sets:GRN, RNA, WAR, M20, ELD Sets:GRN, RNA, WAR, M20, ELD, THB
Banned:Oko, Thief of Crowns Banned:Sorcerous Spyglass;Oko, Thief of Crowns

View File

@@ -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

View File

@@ -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

View File

@@ -6,7 +6,7 @@ Credits=1200
MinDifficulty=0 MinDifficulty=0
MaxDifficulty=5 MaxDifficulty=5
[metadata] [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 Ashioks deck, youll fill your graveyard along with your opponents for massive value as you slowly drive them insane.
Set=THB Set=THB
Image=ashiok_sculptor_of_fears.jpg Image=ashiok_sculptor_of_fears.jpg
[Main] [Main]

View File

@@ -6,7 +6,7 @@ Credits=1200
MinDifficulty=0 MinDifficulty=0
MaxDifficulty=5 MaxDifficulty=5
[metadata] [metadata]
Description=Theros Beyond Death W Planeswalker Deck Description=Elspeth is a renowned warrior, who has defeated everything from gods to death itself. With Elspeths deck, youll build up a battalion of devoted soldiers and lead them fearlessly to a glorious victory.
Set=THB Set=THB
Image=elspeth_undaunted_hero.jpg Image=elspeth_undaunted_hero.jpg
[Main] [Main]

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,11 +1,6 @@
package forge.interfaces; package forge.interfaces;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.google.common.base.Function; import com.google.common.base.Function;
import forge.LobbyPlayer; import forge.LobbyPlayer;
import forge.assets.FSkinProp; import forge.assets.FSkinProp;
import forge.deck.CardPool; import forge.deck.CardPool;
@@ -22,9 +17,14 @@ import forge.game.spellability.SpellAbilityView;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.item.PaperCard; import forge.item.PaperCard;
import forge.player.PlayerZoneUpdate; import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.trackable.TrackableCollection; import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent; import forge.util.ITriggerEvent;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public interface IGuiGame { public interface IGuiGame {
void setGameView(GameView gameView); void setGameView(GameView gameView);
GameView getGameView(); GameView getGameView();
@@ -107,7 +107,6 @@ public interface IGuiGame {
* @return null if choices is missing, empty, or if the users' choices are * @return null if choices is missing, empty, or if the users' choices are
* empty; otherwise, returns the first item in the List returned by * empty; otherwise, returns the first item in the List returned by
* getChoices. * getChoices.
* @see #getChoices(String, int, int, Object...)
*/ */
<T> T oneOrNone(String message, List<T> choices); <T> T oneOrNone(String message, List<T> choices);
@@ -158,8 +157,8 @@ public interface IGuiGame {
void setCard(CardView card); void setCard(CardView card);
void setPlayerAvatar(LobbyPlayer player, IHasIcon ihi); void setPlayerAvatar(LobbyPlayer player, IHasIcon ihi);
boolean openZones(Collection<ZoneType> zones, Map<PlayerView, Object> players); PlayerZoneUpdates openZones(PlayerView controller, Collection<ZoneType> zones, Map<PlayerView, Object> players);
void restoreOldZones(Map<PlayerView, Object> playersToRestoreZonesFor); void restoreOldZones(PlayerView playerView, PlayerZoneUpdates playerZoneUpdates);
void setHighlighted(PlayerView pv, boolean b); void setHighlighted(PlayerView pv, boolean b);
void setUsedToPay(CardView card, boolean value); void setUsedToPay(CardView card, boolean value);
void setSelectables(final Iterable<CardView> cards); void setSelectables(final Iterable<CardView> cards);

View File

@@ -1,23 +1,22 @@
package forge.match.input; package forge.match.input;
import java.util.Collection; import forge.FThreads;
import java.util.List;
import java.util.ArrayList;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.card.Card; import forge.game.card.Card;
import forge.game.card.CardView; import forge.game.card.CardView;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.Zone;
import forge.player.PlayerControllerHuman; 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.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates; import forge.player.PlayerZoneUpdates;
import forge.game.zone.Zone; import forge.util.ITriggerEvent;
import forge.FThreads; 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<T extends GameEntity> extends InputSelectManyBase<T> { public class InputSelectEntitiesFromList<T extends GameEntity> extends InputSelectManyBase<T> {
private static final long serialVersionUID = -6609493252672573139L; private static final long serialVersionUID = -6609493252672573139L;
@@ -31,31 +30,32 @@ public class InputSelectEntitiesFromList<T extends GameEntity> extends InputSele
} }
public InputSelectEntitiesFromList(final PlayerControllerHuman controller, final int min, final int max, final FCollectionView<T> validChoices0, final SpellAbility sa0) { public InputSelectEntitiesFromList(final PlayerControllerHuman controller, final int min, final int max, final FCollectionView<T> 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; validChoices = validChoices0;
if (min > validChoices.size()) { // pfps does this really do anything useful?? 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())); System.out.println(String.format("Trying to choose at least %d things from a list with only %d things!", min, validChoices.size()));
} }
ArrayList<CardView> vCards = new ArrayList<>(); ArrayList<CardView> vCards = new ArrayList<>();
for ( T c : validChoices0 ) { for (T c : validChoices0) {
if ( c instanceof Card ) { if (c instanceof Card) {
vCards.add(((Card)c).getView()) ; 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);
} }
}); }
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 @Override

View File

@@ -1,13 +1,7 @@
package forge.match.input; 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 com.google.common.collect.ImmutableList;
import forge.FThreads;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameObject; import forge.game.GameObject;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
@@ -18,13 +12,18 @@ import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions; import forge.game.spellability.TargetRestrictions;
import forge.model.FModel; import forge.model.FModel;
import forge.player.PlayerControllerHuman; import forge.player.PlayerControllerHuman;
import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.properties.ForgeConstants; import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences; import forge.properties.ForgePreferences;
import forge.util.ITriggerEvent; import forge.util.ITriggerEvent;
import forge.util.TextUtil; import forge.util.TextUtil;
import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates; import java.util.ArrayList;
import forge.FThreads; import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public final class InputSelectTargets extends InputSyncronizedBase { public final class InputSelectTargets extends InputSyncronizedBase {
private final List<Card> choices; private final List<Card> choices;
@@ -47,16 +46,17 @@ public final class InputSelectTargets extends InputSyncronizedBase {
this.tgt = sa.getTargetRestrictions(); this.tgt = sa.getTargetRestrictions();
this.sa = sa; this.sa = sa;
this.mandatory = mandatory; this.mandatory = mandatory;
controller.getGui().setSelectables(CardView.getCollection(choices)); controller.getGui().setSelectables(CardView.getCollection(choices));
final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates(); final PlayerZoneUpdates zonesToUpdate = new PlayerZoneUpdates();
for (final Card c : choices) { for (final Card c : choices) {
zonesToUpdate.add(new PlayerZoneUpdate(c.getZone().getPlayer().getView(),c.getZone().getZoneType())); zonesToUpdate.add(new PlayerZoneUpdate(c.getZone().getPlayer().getView(), c.getZone().getZoneType()));
} }
FThreads.invokeInEdtNowOrLater(new Runnable() { FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public void run() { @Override
controller.getGui().updateZones(zonesToUpdate); public void run() {
controller.getGui().updateZones(zonesToUpdate);
} }
}); });
} }
@Override @Override

View File

@@ -1,14 +1,6 @@
package forge.net; 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 com.google.common.base.Function;
import forge.GuiBase; import forge.GuiBase;
import forge.assets.FSkinProp; import forge.assets.FSkinProp;
import forge.deck.CardPool; import forge.deck.CardPool;
@@ -22,11 +14,19 @@ import forge.game.spellability.SpellAbilityView;
import forge.interfaces.IGameController; import forge.interfaces.IGameController;
import forge.interfaces.IGuiGame; import forge.interfaces.IGuiGame;
import forge.match.NextGameDecision; import forge.match.NextGameDecision;
import forge.player.PlayerZoneUpdates;
import forge.trackable.TrackableCollection; import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent; import forge.util.ITriggerEvent;
import forge.util.ReflectionUtil; import forge.util.ReflectionUtil;
import org.apache.commons.lang3.SerializationUtils; 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. * The methods that can be sent through this protocol.
*/ */
@@ -75,8 +75,8 @@ public enum ProtocolMethod {
clearSelectables (Mode.SERVER), clearSelectables (Mode.SERVER),
refreshField (Mode.SERVER), refreshField (Mode.SERVER),
// TODO case "setPlayerAvatar": // TODO case "setPlayerAvatar":
openZones (Mode.SERVER, Boolean.TYPE, Collection/*ZoneType*/.class, Map/*PlayerView,Object*/.class), openZones (Mode.SERVER, PlayerZoneUpdates.class, Collection/*ZoneType*/.class, Map/*PlayerView,Object*/.class),
restoreOldZones (Mode.SERVER, Void.TYPE, Map/*PlayerView,Object*/.class), restoreOldZones (Mode.SERVER, Void.TYPE, PlayerView.class, PlayerZoneUpdates.class),
isUiSetToSkipPhase (Mode.SERVER, Boolean.TYPE, PlayerView.class, PhaseType.class), isUiSetToSkipPhase (Mode.SERVER, Boolean.TYPE, PlayerView.class, PhaseType.class),
setRememberedActions(Mode.SERVER, Void.TYPE), setRememberedActions(Mode.SERVER, Void.TYPE),
nextRememberedAction(Mode.SERVER, Void.TYPE), nextRememberedAction(Mode.SERVER, Void.TYPE),

View File

@@ -1,11 +1,6 @@
package forge.net.server; package forge.net.server;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.google.common.base.Function; import com.google.common.base.Function;
import forge.LobbyPlayer; import forge.LobbyPlayer;
import forge.assets.FSkinProp; import forge.assets.FSkinProp;
import forge.deck.CardPool; import forge.deck.CardPool;
@@ -23,9 +18,14 @@ import forge.match.AbstractGuiGame;
import forge.net.GameProtocolSender; import forge.net.GameProtocolSender;
import forge.net.ProtocolMethod; import forge.net.ProtocolMethod;
import forge.player.PlayerZoneUpdate; import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.trackable.TrackableCollection; import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent; import forge.util.ITriggerEvent;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class NetGuiGame extends AbstractGuiGame { public class NetGuiGame extends AbstractGuiGame {
private final GameProtocolSender sender; private final GameProtocolSender sender;
@@ -283,14 +283,14 @@ public class NetGuiGame extends AbstractGuiGame {
} }
@Override @Override
public boolean openZones(final Collection<ZoneType> zones, final Map<PlayerView, Object> players) { public PlayerZoneUpdates openZones(PlayerView controller, final Collection<ZoneType> zones, final Map<PlayerView, Object> players) {
updateGameView(); updateGameView();
return sendAndWait(ProtocolMethod.openZones, zones, players); return sendAndWait(ProtocolMethod.openZones, controller, zones, players);
} }
@Override @Override
public void restoreOldZones(final Map<PlayerView, Object> playersToRestoreZonesFor) { public void restoreOldZones(PlayerView playerView, PlayerZoneUpdates playerZoneUpdates) {
send(ProtocolMethod.restoreOldZones, playersToRestoreZonesFor); send(ProtocolMethod.restoreOldZones, playerView, playerZoneUpdates);
} }
@Override @Override

View File

@@ -133,8 +133,8 @@ public class HumanPlaySpellAbility {
} }
if (ability.isAbility() && ability instanceof AbilityActivated) { if (ability.isAbility() && ability instanceof AbilityActivated) {
final Map<String, String> params = Maps.newHashMap(); final Map<String, String> params = Maps.newHashMap();
params.put("ManaColorConversion", "Additive"); params.put("ManaColorConversion", "Additive");
for (KeywordInterface inst : c.getKeywords()) { for (KeywordInterface inst : c.getKeywords()) {
String keyword = inst.getOriginal(); String keyword = inst.getOriginal();
@@ -249,6 +249,8 @@ public class HumanPlaySpellAbility {
final Game game = ability.getActivatingPlayer().getGame(); final Game game = ability.getActivatingPlayer().getGame();
if (fromZone != null) { // and not a copy if (fromZone != null) { // and not a copy
ability.getHostCard().setCastSA(null);
ability.getHostCard().setCastFrom(null);
// add back to where it came from // add back to where it came from
game.getAction().moveTo(fromZone, ability.getHostCard(), zonePosition >= 0 ? Integer.valueOf(zonePosition) : null, null); game.getAction().moveTo(fromZone, ability.getHostCard(), zonePosition >= 0 ? Integer.valueOf(zonePosition) : null, null);
} }

View File

@@ -14,11 +14,15 @@ public class PlayerZoneUpdate implements Serializable {
private final Set<ZoneType> zones; private final Set<ZoneType> zones;
public PlayerZoneUpdate(final PlayerView player, final ZoneType zone) { public PlayerZoneUpdate(final PlayerView player, final ZoneType zone) {
if (player == null || zone == null) { if (player == null ) {
throw new NullPointerException(); throw new NullPointerException();
} }
this.player = player; 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() { public PlayerView getPlayer() {
@@ -30,13 +34,13 @@ public class PlayerZoneUpdate implements Serializable {
void addZone(final ZoneType zone) { void addZone(final ZoneType zone) {
if (zone == null) { if (zone == null) {
throw new NullPointerException(); return;
} }
zones.add(zone); zones.add(zone);
} }
void add(final PlayerZoneUpdate other) { void add(final PlayerZoneUpdate other) {
if (other == null) { if (other == null) {
throw new NullPointerException(); return;
} }
zones.addAll(other.getZones()); zones.addAll(other.getZones());
} }

View File

@@ -100,7 +100,7 @@ public class TargetSelection {
return true; return true;
} }
final List<ZoneType> zone = tgt.getZone(); final List<ZoneType> zones = tgt.getZone();
final boolean mandatory = tgt.getMandatory() && hasCandidates; final boolean mandatory = tgt.getMandatory() && hasCandidates;
final boolean choiceResult; final boolean choiceResult;
@@ -110,7 +110,7 @@ public class TargetSelection {
final GameObject choice = Aggregates.random(candidates); final GameObject choice = Aggregates.random(candidates);
return ability.getTargets().add(choice); 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. // If Zone is Stack, the choices are handled slightly differently.
// Handle everything inside function due to interaction with StackInstance // Handle everything inside function due to interaction with StackInstance
return this.chooseCardFromStack(mandatory); return this.chooseCardFromStack(mandatory);
@@ -152,12 +152,15 @@ public class TargetSelection {
for (Card card : validTargets) { for (Card card : validTargets) {
playersWithValidTargets.put(PlayerView.get(card.getController()), null); 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); InputSelectTargets inp = new InputSelectTargets(controller, validTargets, ability, mandatory);
inp.showAndWait(); inp.showAndWait();
choiceResult = !inp.hasCancelled(); choiceResult = !inp.hasCancelled();
bTargetingDone = inp.hasPressedOk(); bTargetingDone = inp.hasPressedOk();
controller.getGui().restoreOldZones(playersWithValidTargets); controller.getGui().restoreOldZones(playerView, playerZoneUpdates);
} }
else { else {
// for every other case an all-purpose GuiChoose // for every other case an all-purpose GuiChoose