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.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) {
preventMap.triggerPreventDamage(false);
damageMap.triggerDamageDoneOnce(false, sa);
damageMap.triggerDamageDoneOnce(false, game, sa);
preventMap.clear();
damageMap.clear();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,13 +4,17 @@
package forge.game.card;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ForwardingTable;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameObjectPredicates;
import forge.game.ability.AbilityKey;
import forge.game.keyword.Keyword;
import forge.game.spellability.SpellAbility;
@@ -44,7 +48,7 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
}
}
public void triggerDamageDoneOnce(boolean isCombat, final SpellAbility sa) {
public void triggerDamageDoneOnce(boolean isCombat, final Game game, final SpellAbility sa) {
// Source -> Targets
for (Map.Entry<Card, Map<GameEntity, Integer>> e : this.rowMap().entrySet()) {
final Card sourceLKI = e.getKey();
@@ -59,7 +63,7 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
runParams.put(AbilityKey.DamageAmount, sum);
runParams.put(AbilityKey.IsCombatDamage, isCombat);
sourceLKI.getGame().getTriggerHandler().runTrigger(TriggerType.DamageDealtOnce, runParams, false);
game.getTriggerHandler().runTrigger(TriggerType.DamageDealtOnce, runParams, false);
if (sourceLKI.hasKeyword(Keyword.LIFELINK)) {
sourceLKI.getController().gainLife(sum, sourceLKI, sa);
@@ -80,9 +84,14 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
runParams.put(AbilityKey.DamageAmount, sum);
runParams.put(AbilityKey.IsCombatDamage, isCombat);
ge.getGame().getTriggerHandler().runTrigger(TriggerType.DamageDoneOnce, runParams, false);
game.getTriggerHandler().runTrigger(TriggerType.DamageDoneOnce, runParams, false);
}
}
final Map<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
@@ -98,4 +107,29 @@ public class CardDamageMap extends ForwardingTable<Card, GameEntity, Integer> {
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
// LifeLink for Combat Damage at this place
dealtDamageTo.triggerDamageDoneOnce(true, null);
dealtDamageTo.triggerDamageDoneOnce(true, game, null);
dealtDamageTo.clear();
counterTable.triggerCountersPutAll(game);

View File

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

View File

@@ -1708,7 +1708,8 @@ public class Player extends GameEntity implements Comparable<Player> {
if (land.isFaceDown()) {
land.turnFaceUp();
}
game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null);
final Card c = game.getAction().moveTo(getZone(ZoneType.Battlefield), land, null);
game.updateLastStateForCard(c);
// play a sound
game.fireEvent(new GameEventLandPlayed(this, land));

View File

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

View File

@@ -870,13 +870,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
clone.view = new SpellAbilityView(clone);
// dont use setHostCard to not trigger the not copied parts yet
clone.hostCard = host;
copyHelper(clone, host);
if (!lki && host != null && host.getGame() != null) {
host.getGame().addSpellAbility(clone);
}
// need to clone the maps too so they can be changed
clone.originalMapParams = Maps.newHashMap(this.originalMapParams);
clone.mapParams = Maps.newHashMap(this.mapParams);
clone.triggeringObjects = AbilityKey.newMap(this.triggeringObjects);
@@ -902,7 +900,6 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
// clear maps for copy, the values will be added later
clone.additionalAbilities = Maps.newHashMap();
clone.additionalAbilityLists = Maps.newHashMap();
clone.sVars = Maps.newHashMap();
// run special copy Ability to make a deep copy
CardFactory.copySpellAbility(this, clone, host, activ, lki);
} catch (final CloneNotSupportedException e) {

View File

@@ -819,13 +819,7 @@ public class StaticAbility extends CardTraitBase implements IIdentifiable, Clone
clone = (StaticAbility) clone();
clone.id = lki ? id : nextId();
// dont use setHostCard to not trigger the not copied parts yet
clone.hostCard = host;
// need to clone the maps too so they can be changed
clone.originalMapParams = Maps.newHashMap(this.originalMapParams);
clone.mapParams = Maps.newHashMap(this.mapParams);
clone.sVars = Maps.newHashMap(this.sVars);
copyHelper(clone, host);
clone.layers = this.generateLayer();

View File

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

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),
Crewed(TriggerCrewed.class),
Cycled(TriggerCycled.class),
DamageAll(TriggerDamageAll.class),
DamageDealtOnce(TriggerDamageDealtOnce.class),
DamageDone(TriggerDamageDone.class),
DamageDoneOnce(TriggerDamageDoneOnce.class),

View File

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

View File

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

View File

@@ -17,18 +17,6 @@
*/
package forge.view.arcane;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ScrollPaneConstants;
import javax.swing.Timer;
import forge.Singletons;
import forge.game.card.CardView;
import forge.gui.framework.SDisplayUtil;
@@ -39,10 +27,19 @@ import forge.screens.match.CMatchUI;
import forge.toolbox.FMouseAdapter;
import forge.toolbox.FScrollPane;
import forge.toolbox.MouseTriggerEvent;
//import forge.util.collect.FCollectionView;
import forge.view.FDialog;
import forge.view.FFrame;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
//import forge.util.collect.FCollectionView;
// show some cards in a new window
public abstract class FloatingCardArea extends CardArea {
@@ -67,12 +64,14 @@ public abstract class FloatingCardArea extends CardArea {
getWindow().setFocusableWindowState(false); // should probably do this earlier
getWindow().setVisible(true);
}
protected void hideWindow() {
onShow();
getWindow().setFocusableWindowState(false); // should probably do this earlier
getWindow().setVisible(false);
getWindow().dispose(); //pfps so that old content does not show up
}
protected void showOrHideWindow() {
if (getWindow().isVisible()) {
hideWindow();
@@ -98,9 +97,12 @@ public abstract class FloatingCardArea extends CardArea {
if (hasBeenShown || locLoaded) { return; }
super.setLocationRelativeTo(c);
}
@Override
public void setVisible(boolean b0) {
if (isVisible() == b0) { return; }
if (isVisible() == b0) {
return;
}
if (!b0 && hasBeenShown && locPref != null) {
//update preference before hiding window, as otherwise its location will be 0,0
prefs.setPref(locPref,
@@ -116,6 +118,10 @@ public abstract class FloatingCardArea extends CardArea {
}
};
public boolean isVisible() {
return window.isVisible();
}
protected FDialog getWindow() {
return window;
}

View File

@@ -17,27 +17,26 @@
*/
package forge.view.arcane;
import java.util.HashMap;
import java.util.Map;
import java.util.Collections;
import java.util.Comparator;
import java.awt.event.MouseEvent;
import javax.swing.ScrollPaneConstants;
import javax.swing.WindowConstants;
import forge.FThreads;
import forge.assets.FSkinProp;
import forge.game.card.CardView;
import forge.game.player.PlayerView;
import forge.game.zone.ZoneType;
import forge.properties.ForgePreferences.FPref;
import forge.screens.match.CMatchUI;
import forge.toolbox.FScrollPane;
import forge.toolbox.FMouseAdapter;
import forge.toolbox.FScrollPane;
import forge.toolbox.FSkin;
import forge.util.Lang;
import forge.util.collect.FCollection;
import javax.swing.*;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
public class FloatingZone extends FloatingCardArea {
private static final long serialVersionUID = 1927906492186378596L;
@@ -46,18 +45,44 @@ public class FloatingZone extends FloatingCardArea {
private static int getKey(final PlayerView player, final ZoneType zone) {
return 40 * player.getId() + zone.hashCode();
}
public static void showOrHide(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
final FloatingZone cardArea = _init(matchUI, player, zone);
cardArea.showOrHideWindow();
}
public static void show(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
public static boolean show(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
final FloatingZone cardArea = _init(matchUI, player, zone);
if (cardArea.isVisible()) {
return false;
}
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public void run() {
cardArea.showWindow();
}
public static void hide(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
});
return true;
}
public static boolean hide(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
final FloatingZone cardArea = _init(matchUI, player, zone);
if (!cardArea.isVisible()) {
return false;
}
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public void run() {
cardArea.hideWindow();
}
});
return true;
}
private static FloatingZone _init(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
final int key = getKey(player, zone);
FloatingZone cardArea = floatingAreas.get(key);
@@ -69,10 +94,12 @@ public class FloatingZone extends FloatingCardArea {
}
return cardArea;
}
public static CardPanel getCardPanel(final CMatchUI matchUI, final CardView card) {
final FloatingZone window = _init(matchUI, card.getController(), card.getZone());
return window.getCardPanel(card.getId());
}
public static void refresh(final PlayerView player, final ZoneType zone) {
FloatingZone cardArea = floatingAreas.get(getKey(player, zone));
if (cardArea != null) {
@@ -91,12 +118,14 @@ public class FloatingZone extends FloatingCardArea {
break;
}
}
public static void closeAll() {
for (final FloatingZone cardArea : floatingAreas.values()) {
cardArea.window.setVisible(false);
}
floatingAreas.clear();
}
public static void refreshAll() {
for (final FloatingZone cardArea : floatingAreas.values()) {
cardArea.refresh();
@@ -179,7 +208,8 @@ public class FloatingZone extends FloatingCardArea {
super.onShow();
if (!hasBeenShown) {
getWindow().getTitleBar().addMouseListener(new FMouseAdapter() {
@Override public final void onRightClick(final MouseEvent e) {
@Override
public final void onRightClick(final MouseEvent e) {
toggleSorted();
}
});
@@ -192,7 +222,9 @@ public class FloatingZone extends FloatingCardArea {
}
private void setPlayer(PlayerView player0) {
if (player == player0) { return; }
if (player == player0) {
return;
}
player = player0;
setTitle();

View File

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

View File

@@ -102,18 +102,32 @@ public class VPlayerPanel extends FContainer {
return zoneTabs.get(zoneType);
}
public ZoneType getZoneByInfoTab(InfoTab tab) {
for(ZoneType zone : zoneTabs.keySet()) {
if (zoneTabs.get(zone).equals(tab)) {
return zone;
}
}
return null;
}
public void setSelectedZone(ZoneType zoneType) {
setSelectedTab(zoneTabs.get(zoneType));
}
public void hideSelectedTab() {
if (selectedTab != null) {
selectedTab.displayArea.setVisible(false);
}
}
public void setSelectedTab(InfoTab selectedTab0) {
if (selectedTab == selectedTab0) {
return;
}
if (selectedTab != null) {
selectedTab.displayArea.setVisible(false);
}
hideSelectedTab();
selectedTab = selectedTab0;

View File

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

View File

@@ -1,9 +1,6 @@
#Add one announcement per line
Theros Beyond Death Prerelease!
"Prerelease" limited mode for latest sets
We believe the issue with 1.8.0_211 or greater have been resolved. Let us know if you are still on the latest version and things are better now.
Bunches of bug fixes,
Continued work on Translations
[b]Forge now requires Java 8 (or newer). You will not be able to start the game if you are not yet running Java 8.[/b]
For some reason Oracle hates Forge and version 1.8.0_211 does bad things with Forge for unknown reasons. Downgrade to 202 for a beter time.
https://www.oracle.com/technetwork/java/javase/downloads/java-archive-javase8-2177648.html
We have a Discord server for hanging out with Forge devs and other Forge fans. Feel free to [url=https://discord.gg/3v9JCVr]jump on in and say hi[/url]!

View File

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

View File

@@ -1556,6 +1556,39 @@ Evolving Wilds
13 Swamp|M20
[ELD Secret Cards]
Garruk, Cursed Huntsman|ELD|2
Oko, Thief of Crowns|ELD|2
The Royal Scions|ELD|2
Ardenvale Tactician|ELD|2
Faerie Guidemother|ELD|2
Giant Killer|ELD|2
Lonesome Unicorn|ELD|2
Realm-Cloaked Giant|ELD|2
Shepherd of the Flock|ELD|2
Silverflame Squire|ELD|2
Animating Faerie|ELD|2
Brazen Borrower|ELD|2
Fae of Wishes|ELD|2
Hypnotic Sprite|ELD|2
Merfolk Secretkeeper|ELD|2
Queen of Ice|ELD|2
Foulmire Knight|ELD|2
Murderous Rider|ELD|2
Order of Midnight|ELD|2
Reaper of Night|ELD|2
Smitten Swordmaster|ELD|2
Bonecrusher Giant|ELD|2
Embereth Shieldbreaker|ELD|2
Merchant of the Vale|ELD|2
Rimrock Knight|ELD|2
Beanstalk Giant|ELD|2
Curious Pair|ELD|2
Flaxen Intruder|ELD|2
Garenbrig Carver|ELD|2
Lovestruck Beast|ELD|2
Rosethorn Acolyte|ELD|2
Tuinvale Treefolk|ELD|2
Oakhame Ranger|ELD|2
Kenrith, the Returned King
Rowan, Fearless Sparkmage
Garrison Griffin
@@ -1587,6 +1620,70 @@ Syr Gwyn, Hero of Ashvale
Arcane Signet
Tome of Legends
Command Tower
Acclaimed Contender|ELD|2
Charming Prince|ELD|2
The Circle of Loyalty|ELD|2
Happily Ever After|ELD|2
Harmonious Archon|ELD|2
Hushbringer|ELD|2
Linden, the Steadfast Queen|ELD|2
Worthy Knight|ELD|2
Emry, Lurker of the Loch|ELD|2
Folio of Fancies|ELD|2
Gadwick, the Wizened|ELD|2
The Magic Mirror|ELD|2
Midnight Clock|ELD|2
Mirrormade|ELD|2
Stolen by the Fae|ELD|2
Vantress Gargoyle|ELD|2
Ayara, First of Locthwain|ELD|2
Blacklance Paragon|ELD|2
The Cauldron of Eternity|ELD|2
Clackbridge Troll|ELD|2
Oathsworn Knight|ELD|2
Piper of the Swarm|ELD|2
Rankle, Master of Pranks|ELD|2
Wishclaw Talisman|ELD|2
Witch's Vengeance|ELD|2
Embercleave|ELD|2
Fervent Champion|ELD|2
Fires of Invention|ELD|2
Irencrag Feat|ELD|2
Irencrag Pyromancer|ELD|2
Opportunistic Dragon|ELD|2
Robber of the Rich|ELD|2
Sundering Stroke|ELD|2
Torbran, Thane of Red Fell|ELD|2
Feasting Troll King|ELD|2
Gilded Goose|ELD|2
The Great Henge|ELD|2
Once Upon A Time|ELD|2
Questing Beast|ELD|2
Return of the Wildspeaker|ELD|2
Wicked Wolf|ELD|2
Wildborn Preserver|ELD|2
Yorvo, Lord of Garenbrig|ELD|2
Dance of the Manse|ELD|2
Doom Foretold|ELD|2
Escape to the Wilds|ELD|2
Faeburrow Elder|ELD|2
Lochmere Serpent|ELD|2
Outlaws' Merriment|ELD|2
Stormfist Crusader|ELD|2
Sorcerous Spyglass|ELD|2
Stonecoil Serpent|ELD|2
Castle Ardenvale|ELD|2
Castle Embereth|ELD|2
Castle Garenbrig|ELD|2
Castle Locthwain|ELD|2
Castle Vantress|ELD|2
Fabled Passage|ELD|2
Piper of the Swarm|ELD|3
Glass Casket|ELD|2
Slaying Fire|ELD|2
Kenrith's Transformation|ELD|2
Improbable Alliance|ELD|2
Inspiring Veteran|ELD|2
[THB Secret Cards]
Athreos, Shroud-Veiled
@@ -1627,6 +1724,7 @@ Eidolon of Obstruction|THB|2
Heliod's Intervention|THB|2
Idyllic Tutor|THB|2
Shatter the Sky|THB|2
Taranika, Akroan Veteran|THB|2
Ashiok's Erasure|THB|2
Nadir Kraken|THB|2
Protean Thaumaturge|THB|2
@@ -1675,3 +1773,9 @@ Temple of Deceit|THB|2
Temple of Enlightenment|THB|2
Temple of Malice|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
Types:Legendary Creature Human Cleric
PT:2/2
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Opponent | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, you may put a -1/-1 counter on target creature.
T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Opponent | CombatDamage$ True | TriggerZones$ Battlefield | OptionalDecider$ You | Execute$ TrigPutCounter | TriggerDescription$ Whenever CARDNAME deals combat damage to a player, you may put a -1/-1 counter on target creature.
SVar:TrigPutCounter:DB$ PutCounter | ValidTgts$ Creature | TgtPrompt$ Select target creature | CounterType$ M1M1 | CounterNum$ 1 | IsCurse$ True
T:Mode$ CounterAddedOnce | ValidCard$ Creature | ValidSource$ You | CounterType$ M1M1 | TriggerZones$ Battlefield | Execute$ TrigToken | TriggerDescription$ Whenever you put one or more -1/-1 counters on a creature, create a 1/1 green Snake creature token with deathtouch.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ g_1_1_snake_deathtouch | TokenOwner$ You | LegacyImage$ g 1 1 snake deathtouch akh

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
ManaCost:1 B
Types:Sorcery
A:SP$ Sacrifice | Cost$ 1 B | Valid$ Creature,Enchantment | SacMessage$ Creature or Enchantment | Defined$ Player.Opponent | SpellDescription$ Each opponent sacrifices a creature or enchantment.
A:SP$ Sacrifice | Cost$ 1 B | SacValid$ Creature,Enchantment | SacMessage$ Creature or Enchantment | Defined$ Player.Opponent | SpellDescription$ Each opponent sacrifices a creature or enchantment.
Oracle:Each opponent sacrifices a creature or enchantment.

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

View File

@@ -279,7 +279,44 @@ Prerelease=6 Boosters, 1 RareMythic+
267 L Forest
268 L Forest
269 L Forest
#Borderless Planeswalkers
270 M Garruk, Cursed Huntsman
271 M Oko, Thief of Crowns
272 M The Royal Scions
#Storybook Frames
C Ardenvale Tactician
C Faerie Guidemother
R Giant Killer
C Lonesome Unicorn
M Realm-Cloaked Giant
U Shepherd of the Flock
C Silverflame Squire
U Animating Faerie
M Brazen Borrower
R Fae of Wishes
U Hypnotic Sprite
C Merfolk Secretkeeper
C Queen of Ice
U Foulmire Knight
R Murderous Rider
U Order of Midnight
C Reaper of Night
C Smitten Swordmaster
R Bonecrusher Giant
U Embereth Shieldbreaker
C Merchant of the Vale
C Rimrock Knight
U Beanstalk Giant
C Curious Pair
U Flaxen Intruder
C Garenbrig Carver
R Lovestruck Beast
C Rosethorn Acolyte
C Tuinvale Treefolk
U Oakhame Ranger
#Buy-A-Box Promo
303 M Kenrith, the Returned King
#Planeswalker Deck Cards
304 M Rowan, Fearless Sparkmage
305 C Garrison Griffin
306 U Rowan's Battleguard
@@ -310,6 +347,73 @@ Prerelease=6 Boosters, 1 RareMythic+
331 C Arcane Signet
332 R Tome of Legends
333 C Command Tower
#Borderless art rares and mythics
R Acclaimed Contender
R Charming Prince
M The Circle of Loyalty
R Happily Ever After
M Harmonious Archon
R Hushbringer
R Linden, the Steadfast Queen
R Worthy Knight
R Emry, Lurker of the Loch
R Folio of Fancies
R Gadwick, the Wizened
M The Magic Mirror
R Midnight Clock
R Mirrormade
R Stolen by the Fae
R Vantress Gargoyle
R Ayara, First of Locthwain
R Blacklance Paragon
M The Cauldron of Eternity
R Clackbridge Troll
R Oathsworn Knight
R Piper of the Swarm
M Rankle, Master of Pranks
R Wishclaw Talisman
R Witch's Vengeance
M Embercleave
R Fervent Champion
R Fires of Invention
R Irencrag Feat
R Irencrag Pyromancer
R Opportunistic Dragon
M Robber of the Rich
R Sundering Stroke
R Torbran, Thane of Red Fell
R Feasting Troll King
R Gilded Goose
M The Great Henge
R Once Upon A Time
M Questing Beast
R Return of the Wildspeaker
R Wicked Wolf
R Wildborn Preserver
R Yorvo, Lord of Garenbrig
R Dance of the Manse
R Doom Foretold
R Escape to the Wilds
R Faeburrow Elder
R Lochmere Serpent
M Outlaws' Merriment
R Stormfist Crusader
R Sorcerous Spyglass
R Stonecoil Serpent
R Castle Ardenvale
R Castle Embereth
R Castle Garenbrig
R Castle Locthwain
R Castle Vantress
R Fabled Passage
#Bundle promo
R Piper of the Swarm
#Promo Pack
U Glass Casket
U Slaying Fire
U Kenrith's Transformation
U Improbable Alliance
U Inspiring Veteran
[tokens]
w_0_1_goat

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
Type:Casual
Subtype:Commander
Sets:GRN, RNA, WAR, M20, ELD
Banned:Oko, Thief of Crowns
Sets:GRN, RNA, WAR, M20, ELD, THB
Banned:Sorcerous Spyglass;Oko, Thief of Crowns

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
MaxDifficulty=5
[metadata]
Description=Theros Beyond Death U/B Planeswalker Deck
Description=Ashiok has power to torment foes by conjuring their darkest memories, fears, and regrets. With Ashioks deck, youll fill your graveyard along with your opponents for massive value as you slowly drive them insane.
Set=THB
Image=ashiok_sculptor_of_fears.jpg
[Main]

View File

@@ -6,7 +6,7 @@ Credits=1200
MinDifficulty=0
MaxDifficulty=5
[metadata]
Description=Theros Beyond Death W Planeswalker Deck
Description=Elspeth is a renowned warrior, who has defeated everything from gods to death itself. With Elspeths deck, youll build up a battalion of devoted soldiers and lead them fearlessly to a glorious victory.
Set=THB
Image=elspeth_undaunted_hero.jpg
[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;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.google.common.base.Function;
import forge.LobbyPlayer;
import forge.assets.FSkinProp;
import forge.deck.CardPool;
@@ -22,9 +17,14 @@ import forge.game.spellability.SpellAbilityView;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public interface IGuiGame {
void setGameView(GameView gameView);
GameView getGameView();
@@ -107,7 +107,6 @@ public interface IGuiGame {
* @return null if choices is missing, empty, or if the users' choices are
* empty; otherwise, returns the first item in the List returned by
* getChoices.
* @see #getChoices(String, int, int, Object...)
*/
<T> T oneOrNone(String message, List<T> choices);
@@ -158,8 +157,8 @@ public interface IGuiGame {
void setCard(CardView card);
void setPlayerAvatar(LobbyPlayer player, IHasIcon ihi);
boolean openZones(Collection<ZoneType> zones, Map<PlayerView, Object> players);
void restoreOldZones(Map<PlayerView, Object> playersToRestoreZonesFor);
PlayerZoneUpdates openZones(PlayerView controller, Collection<ZoneType> zones, Map<PlayerView, Object> players);
void restoreOldZones(PlayerView playerView, PlayerZoneUpdates playerZoneUpdates);
void setHighlighted(PlayerView pv, boolean b);
void setUsedToPay(CardView card, boolean value);
void setSelectables(final Iterable<CardView> cards);

View File

@@ -1,23 +1,22 @@
package forge.match.input;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import forge.FThreads;
import forge.game.GameEntity;
import forge.game.card.Card;
import forge.game.card.CardView;
import forge.game.player.Player;
import forge.game.spellability.SpellAbility;
import forge.game.zone.Zone;
import forge.player.PlayerControllerHuman;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import forge.util.ITriggerEvent;
import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.game.zone.Zone;
import forge.FThreads;
import forge.util.ITriggerEvent;
import forge.util.collect.FCollection;
import forge.util.collect.FCollectionView;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class InputSelectEntitiesFromList<T extends GameEntity> extends InputSelectManyBase<T> {
private static final long serialVersionUID = -6609493252672573139L;
@@ -51,7 +50,8 @@ public class InputSelectEntitiesFromList<T extends GameEntity> extends InputSele
}
}
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public void run() {
@Override
public void run() {
getController().getGui().updateZones(zonesToUpdate);
zonesShown = getController().getGui().tempShowZones(controller.getPlayer().getView(), zonesToUpdate);
}

View File

@@ -1,13 +1,7 @@
package forge.match.input;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.collect.ImmutableList;
import forge.FThreads;
import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.ability.ApiType;
@@ -18,13 +12,18 @@ import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.model.FModel;
import forge.player.PlayerControllerHuman;
import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.properties.ForgeConstants;
import forge.properties.ForgePreferences;
import forge.util.ITriggerEvent;
import forge.util.TextUtil;
import forge.player.PlayerZoneUpdate;
import forge.player.PlayerZoneUpdates;
import forge.FThreads;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public final class InputSelectTargets extends InputSyncronizedBase {
private final List<Card> choices;
@@ -53,7 +52,8 @@ public final class InputSelectTargets extends InputSyncronizedBase {
zonesToUpdate.add(new PlayerZoneUpdate(c.getZone().getPlayer().getView(), c.getZone().getZoneType()));
}
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public void run() {
@Override
public void run() {
controller.getGui().updateZones(zonesToUpdate);
}
});

View File

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

View File

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

View File

@@ -249,6 +249,8 @@ public class HumanPlaySpellAbility {
final Game game = ability.getActivatingPlayer().getGame();
if (fromZone != null) { // and not a copy
ability.getHostCard().setCastSA(null);
ability.getHostCard().setCastFrom(null);
// add back to where it came from
game.getAction().moveTo(fromZone, ability.getHostCard(), zonePosition >= 0 ? Integer.valueOf(zonePosition) : null, null);
}

View File

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

View File

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