- Huge update to network play

- Updated protocol: now used everywhere for type checking
  - No more locks between server and client: client no longer expects any replies
  - More code reuse
  - Fixed issue with location of multiple abilities selection popup menu
  - Improved stability; actual games should now be possible
- Other updates
  - Mobile version player panel now uses same code as desktop
  - Remove automatic "view all cards" cheat in hotseat (no longer necessary)
  - Code cleanup
This commit is contained in:
elcnesh
2015-04-26 10:54:55 +00:00
parent 5c89e1a9ce
commit addcece582
48 changed files with 694 additions and 856 deletions

3
.gitattributes vendored
View File

@@ -17715,6 +17715,8 @@ forge-gui/src/main/java/forge/model/MultipleForgeJarsFoundError.java -text
forge-gui/src/main/java/forge/model/UnOpenedMeta.java -text
forge-gui/src/main/java/forge/model/package-info.java svneol=native#text/plain
forge-gui/src/main/java/forge/net/GameProtocol.java -text
forge-gui/src/main/java/forge/net/GameProtocolHandler.java -text
forge-gui/src/main/java/forge/net/GameProtocolSender.java -text
forge-gui/src/main/java/forge/net/IRemote.java -text
forge-gui/src/main/java/forge/net/ReplyPool.java -text
forge-gui/src/main/java/forge/net/client/ClientGameLobby.java -text
@@ -17735,6 +17737,7 @@ forge-gui/src/main/java/forge/net/event/UpdateLobbyPlayerEvent.java -text
forge-gui/src/main/java/forge/net/event/package-info.java -text
forge-gui/src/main/java/forge/net/package-info.java -text
forge-gui/src/main/java/forge/net/server/FServerManager.java -text
forge-gui/src/main/java/forge/net/server/GameServerHandler.java -text
forge-gui/src/main/java/forge/net/server/IToClient.java -text
forge-gui/src/main/java/forge/net/server/NetGuiGame.java -text
forge-gui/src/main/java/forge/net/server/RemoteClient.java -text

View File

@@ -79,7 +79,7 @@ public class PlayerControllerAi extends PlayerController {
brains.setUseSimulation(value);
}
public SpellAbility getAbilityToPlay(List<SpellAbility> abilities, ITriggerEvent triggerEvent) {
public SpellAbility getAbilityToPlay(Card hostCard, List<SpellAbility> abilities, ITriggerEvent triggerEvent) {
if (abilities.size() == 0) {
return null;
}

View File

@@ -2,4 +2,6 @@ package forge.util;
public interface ITriggerEvent {
int getButton();
int getX();
int getY();
}

View File

@@ -1,8 +1,11 @@
package forge.util;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import org.apache.commons.lang3.reflect.TypeUtils;
/**
* Static utilities related to reflection.
*
@@ -62,4 +65,10 @@ public final class ReflectionUtil {
return null;
}
public static boolean isInstance(final Object obj, final Class<?> type) {
if (Array.class.equals(type)) {
return obj.getClass().isArray();
}
return TypeUtils.isInstance(obj, type);
}
}

View File

@@ -26,10 +26,6 @@ import forge.util.FCollectionView;
public class GameView extends TrackableObject {
private static final long serialVersionUID = 8522884512960961528L;
/*private final TrackableIndex<CardView> cards = new TrackableIndex<CardView>();
private final TrackableIndex<PlayerView> players = new TrackableIndex<PlayerView>();
private final TrackableIndex<SpellAbilityView> spellAbilities = new TrackableIndex<SpellAbilityView>();
private final TrackableIndex<StackItemView> stackItems = new TrackableIndex<StackItemView>();*/
private final TrackableCollection<PlayerView> players;
private CombatView combatView;
private final transient Game game; //TODO: Remove this when possible before network support added

View File

@@ -720,7 +720,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
if (sas.isEmpty()) {
continue;
}
SpellAbility tgtSA = decider.getController().getAbilityToPlay(sas);
SpellAbility tgtSA = decider.getController().getAbilityToPlay(tgtCard, sas);
if (!decider.getController().confirmAction(tgtSA, null, "Do you want to play " + tgtCard + "?")) {
continue;
}

View File

@@ -1,13 +1,18 @@
package forge.game.ability.effects;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.StaticData;
import forge.card.CardStateName;
import forge.card.CardRulesPredicates;
import forge.card.CardStateName;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.ability.SpellAbilityEffect;
@@ -23,11 +28,6 @@ import forge.item.PaperCard;
import forge.util.Aggregates;
import forge.util.Lang;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class PlayEffect extends SpellAbilityEffect {
@Override
protected String getStackDescription(SpellAbility sa) {
@@ -160,7 +160,7 @@ public class PlayEffect extends SpellAbilityEffect {
}
Card original = tgtCard;
if (sa.hasParam("CopyCard")) {
Zone zone = tgtCard.getZone();
final Zone zone = tgtCard.getZone();
tgtCard = Card.fromPaperCard(tgtCard.getPaperCard(), sa.getActivatingPlayer());
tgtCard.setToken(true);
@@ -197,7 +197,7 @@ public class PlayEffect extends SpellAbilityEffect {
}
// only one mode can be used
SpellAbility tgtSA = sa.getActivatingPlayer().getController().getAbilityToPlay(sas);
SpellAbility tgtSA = sa.getActivatingPlayer().getController().getAbilityToPlay(tgtCard, sas);
boolean noManaCost = sa.hasParam("WithoutManaCost");
if (noManaCost) {
tgtSA = tgtSA.copyWithNoManaCost();

View File

@@ -1,5 +1,6 @@
package forge.game.player;
import java.io.Serializable;
import java.util.Collection;
import forge.game.card.Card;
@@ -10,7 +11,9 @@ import forge.game.zone.ZoneType;
* Stores information to reveal cards after a delay unless those cards can be
* revealed in the same dialog as cards being selected
*/
public class DelayedReveal {
public class DelayedReveal implements Serializable {
private static final long serialVersionUID = 5516713460440436615L;
private final Collection<CardView> cards;
private final ZoneType zone;
private final PlayerView owner;

View File

@@ -90,8 +90,8 @@ public abstract class PlayerController {
public Player getPlayer() { return player; }
public LobbyPlayer getLobbyPlayer() { return lobbyPlayer; }
public final SpellAbility getAbilityToPlay(List<SpellAbility> abilities) { return getAbilityToPlay(abilities, null); }
public abstract SpellAbility getAbilityToPlay(List<SpellAbility> abilities, ITriggerEvent triggerEvent);
public final SpellAbility getAbilityToPlay(final Card hostCard, final List<SpellAbility> abilities) { return getAbilityToPlay(hostCard, abilities, null); }
public abstract SpellAbility getAbilityToPlay(Card hostCard, List<SpellAbility> abilities, ITriggerEvent triggerEvent);
//public abstract void playFromSuspend(Card c);
public abstract void playSpellAbilityForFree(SpellAbility copySA, boolean mayChoseNewTargets);

View File

@@ -375,6 +375,8 @@ public class PlayerView extends GameEntityView {
}
public String getDetails() {
final StringBuilder builder = new StringBuilder();
builder.append(getName());
builder.append('\n');
for (final String detailsPart : getDetailsList()) {
builder.append(detailsPart);
builder.append('\n');

View File

@@ -17,8 +17,8 @@
*/
package forge.screens.match;
import java.awt.Component;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -32,11 +32,10 @@ import java.util.concurrent.atomic.AtomicReference;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.MenuElement;
import javax.swing.MenuSelectionManager;
import javax.swing.SwingUtilities;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import forge.FThreads;
@@ -56,7 +55,6 @@ import forge.game.combat.CombatView;
import forge.game.phase.PhaseType;
import forge.game.player.DelayedReveal;
import forge.game.player.IHasIcon;
import forge.game.player.Player;
import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbilityView;
import forge.game.zone.ZoneType;
@@ -72,11 +70,9 @@ import forge.gui.framework.IVDoc;
import forge.gui.framework.SDisplayUtil;
import forge.gui.framework.SLayoutIO;
import forge.gui.framework.VEmptyDoc;
import forge.interfaces.IButton;
import forge.item.InventoryItem;
import forge.item.PaperCard;
import forge.match.AbstractGuiGame;
import forge.match.MatchButtonType;
import forge.menus.IMenuProvider;
import forge.model.FModel;
import forge.player.PlayerZoneUpdate;
@@ -97,7 +93,6 @@ import forge.toolbox.FButton;
import forge.toolbox.FOptionPane;
import forge.toolbox.FSkin;
import forge.toolbox.FSkin.SkinImage;
import forge.toolbox.MouseTriggerEvent;
import forge.toolbox.special.PhaseIndicator;
import forge.toolbox.special.PhaseLabel;
import forge.trackable.TrackableCollection;
@@ -149,13 +144,13 @@ public final class CMatchUI
Singletons.getView().getLpnDocument().add(targetingOverlay.getPanel(), FView.TARGETING_LAYER);
targetingOverlay.getPanel().setSize(Singletons.getControl().getDisplaySize());
this.myDocs = new EnumMap<EDocID, IVDoc<? extends ICDoc>>(EDocID.class);
this.myDocs.put(EDocID.CARD_PICTURE, getCDetailPicture().getCPicture().getView());
this.myDocs.put(EDocID.CARD_DETAIL, getCDetailPicture().getCDetail().getView());
this.myDocs.put(EDocID.CARD_ANTES, getCAntes().getView());
this.myDocs.put(EDocID.CARD_PICTURE, cDetailPicture.getCPicture().getView());
this.myDocs.put(EDocID.CARD_DETAIL, cDetailPicture.getCDetail().getView());
this.myDocs.put(EDocID.CARD_ANTES, cAntes.getView());
this.myDocs.put(EDocID.REPORT_MESSAGE, getCPrompt().getView());
this.myDocs.put(EDocID.REPORT_STACK, getCStack().getView());
this.myDocs.put(EDocID.REPORT_COMBAT, getCCombat().getView());
this.myDocs.put(EDocID.REPORT_LOG, getCLog().getView());
this.myDocs.put(EDocID.REPORT_COMBAT, cCombat.getView());
this.myDocs.put(EDocID.REPORT_LOG, cLog.getView());
this.myDocs.put(EDocID.DEV_MODE, getCDev().getView());
this.myDocs.put(EDocID.BUTTON_DOCK, getCDock().getView());;
}
@@ -212,25 +207,13 @@ public final class CMatchUI
getCDev().update();
}
public CAntes getCAntes() {
return cAntes;
}
public CCombat getCCombat() {
return cCombat;
}
public CDetailPicture getCDetailPicture() {
return cDetailPicture;
}
public CDev getCDev() {
return cDev;
}
public CDock getCDock() {
return cDock;
}
public CLog getCLog() {
return cLog;
}
public CPrompt getCPrompt() {
CPrompt getCPrompt() {
return cPrompt;
}
public CStack getCStack() {
@@ -338,24 +321,6 @@ public final class CMatchUI
return idx < 0 || idx >= allHands.size() ? null : allHands.get(idx);
}
/**
* Checks if game control should stop at a phase, for either a forced
* programmatic stop, or a user-induced phase toggle.
*
* @param turn
* the {@link Player} at whose phase might be stopped.
* @param phase
* the {@link PhaseType} at which might be stopped.
* @return boolean whether the current GUI calls for a stop at the specified
* phase of the specified player.
*/
@Override
public final boolean stopAtPhase(final PlayerView turn, final PhaseType phase) {
final VField vf = getFieldViewFor(turn);
final PhaseLabel label = vf.getPhaseIndicator().getLabelFor(phase);
return label == null || label.getEnabled();
}
@Override
public void setCard(final CardView c) {
this.setCard(c, false);
@@ -395,8 +360,8 @@ public final class CMatchUI
SDisplayUtil.showTab(selectedDocBeforeCombat);
selectedDocBeforeCombat = null;
}
getCCombat().setModel(combat);
getCCombat().update();
cCombat.setModel(combat);
cCombat.update();
} // showCombat(CombatView)
@Override
@@ -548,26 +513,60 @@ public final class CMatchUI
return panels;
}
@Override
public IButton getBtnOK(final PlayerView playerView) {
return view.getBtnOK();
/**
* Find the card panel belonging to a card, bringing up the corresponding
* window or tab if necessary.
*
* @param card
* the {@link CardView} to find a panel for.
* @return a {@link CardPanel}, or {@code null} if no corresponding panel is
* found.
*/
private CardPanel findCardPanel(final CardView card) {
final int id = card.getId();
switch (card.getZone()) {
case Battlefield:
for (final VField f : view.getFieldViews()) {
final CardPanel panel = f.getTabletop().getCardPanel(id);
if (panel != null) {
SDisplayUtil.showTab(f);
return panel;
}
}
break;
case Hand:
for (final VHand h : view.getHands()) {
final CardPanel panel = h.getHandArea().getCardPanel(id);
if (panel != null) {
SDisplayUtil.showTab(h);
return panel;
}
}
break;
case Command:
case Exile:
case Graveyard:
case Library:
return FloatingCardArea.getCardPanel(this, card);
default:
break;
}
return null;
}
@Override
public IButton getBtnCancel(final PlayerView playerView) {
return view.getBtnCancel();
}
public void updateButtons(final PlayerView owner, final String label1, final String label2, final boolean enable1, final boolean enable2, final boolean focus1) {
final FButton btn1 = view.getBtnOK(), btn2 = view.getBtnCancel();
btn1.setText(label1);
btn2.setText(label2);
btn1.setEnabled(enable1);
btn2.setEnabled(enable2);
@Override
public void focusButton(final MatchButtonType button) {
final FButton toFocus = enable1 && focus1 ? btn1 : (enable2 ? btn2 : null);
// ensure we don't steal focus from an overlay
if (!SOverlayUtils.overlayHasFocus()) {
if (toFocus != null && !SOverlayUtils.overlayHasFocus()) {
FThreads.invokeInEdtLater(new Runnable() {
@Override public final void run() {
final FButton btn = button == MatchButtonType.OK
? getCPrompt().getView().getBtnOK()
: getCPrompt().getView().getBtnCancel();
btn.requestFocusInWindow();
toFocus.requestFocusInWindow();
}
});
}
@@ -671,7 +670,7 @@ public final class CMatchUI
}
@Override
public SpellAbilityView getAbilityToPlay(final List<SpellAbilityView> abilities, final ITriggerEvent triggerEvent) {
public SpellAbilityView getAbilityToPlay(final CardView hostCard, final List<SpellAbilityView> abilities, final ITriggerEvent triggerEvent) {
if (triggerEvent == null) {
if (abilities.isEmpty()) {
return null;
@@ -696,19 +695,20 @@ public final class CMatchUI
final JPopupMenu menu = new JPopupMenu("Abilities");
boolean enabled;
boolean hasEnabled = false;
int firstEnabled = -1;
int shortcut = KeyEvent.VK_1; //use number keys as shortcuts for abilities 1-9
int index = 0;
for (final SpellAbilityView ab : abilities) {
enabled = ab.canPlay();
if (enabled) {
hasEnabled = true;
if (enabled && firstEnabled < 0) {
firstEnabled = index;
}
GuiUtils.addMenuItem(menu, FSkin.encodeSymbols(ab.toString(), true),
shortcut > 0 ? KeyStroke.getKeyStroke(shortcut, 0) : null,
new Runnable() {
@Override
public void run() {
cPrompt.selectAbility(ab);
getGameController().selectAbility(ab);
}
}, enabled);
if (shortcut > 0) {
@@ -717,15 +717,38 @@ public final class CMatchUI
shortcut = 0; //stop adding shortcuts after 9
}
}
index++;
}
if (firstEnabled >= 0) { //only show menu if at least one ability can be played
final CardPanel panel = findCardPanel(hostCard);
final Component menuParent;
final int x, y;
if (panel == null) {
// Fall back to showing in VPrompt if no panel can be found
menuParent = getCPrompt().getView().getTarMessage();
x = 0;
y = 0;
SDisplayUtil.showTab(getCPrompt().getView());
} else {
final ZoneType zone = hostCard.getZone();
if (ImmutableList.of(ZoneType.Command, ZoneType.Exile, ZoneType.Graveyard, ZoneType.Library).contains(zone)) {
FloatingCardArea.show(this, hostCard.getController(), zone);
}
menuParent = panel.getParent();
x = triggerEvent.getX();
y = triggerEvent.getY();
}
menu.show(menuParent, x, y);
final int _firstEnabled = firstEnabled;
SwingUtilities.invokeLater(new Runnable() { //use invoke later to ensure first enabled ability selected by default
@Override public final void run() {
for (int i = 0; i <= _firstEnabled; i++) {
menu.dispatchEvent(new KeyEvent(menu, KeyEvent.KEY_PRESSED, 0, 0, KeyEvent.VK_DOWN, KeyEvent.CHAR_UNDEFINED));
}
if (hasEnabled) { //only show menu if at least one ability can be played
SwingUtilities.invokeLater(new Runnable() { //use invoke later to ensure first ability selected by default
public void run() {
MenuSelectionManager.defaultManager().setSelectedPath(new MenuElement[]{menu, menu.getSubElements()[0]});
}
});
MouseEvent mouseEvent = ((MouseTriggerEvent)triggerEvent).getMouseEvent();
menu.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
}
return null; //delay ability until choice made
@@ -772,7 +795,7 @@ public final class CMatchUI
@Override
public void openView(final TrackableCollection<PlayerView> myPlayers) {
final GameView gameView = getGameView();
gameView.getGameLog().addObserver(getCLog());
gameView.getGameLog().addObserver(cLog);
// Sort players
FCollectionView<PlayerView> players = gameView.getPlayers();

View File

@@ -32,6 +32,6 @@ public class ZoneAction extends ForgeAction {
@Override
public void actionPerformed(final ActionEvent e) {
FloatingCardArea.show(matchUI, player, zone);
FloatingCardArea.showOrHide(matchUI, player, zone);
}
}

View File

@@ -61,7 +61,7 @@ public class CDock implements ICDoc {
* End turn.
*/
public void endTurn() {
matchUI.getCPrompt().passPriorityUntilEndOfTurn();
matchUI.getGameController().passPriorityUntilEndOfTurn();
}
/**

View File

@@ -47,7 +47,7 @@ public class CField implements ICDoc {
private final MouseListener madAvatar = new MouseAdapter() {
@Override
public void mousePressed(final MouseEvent e) {
matchUI.getCPrompt().selectPlayer(player, new MouseTriggerEvent(e));
matchUI.getGameController().selectPlayer(player, new MouseTriggerEvent(e));
}
};
@@ -71,9 +71,11 @@ public class CField implements ICDoc {
final ZoneAction commandAction = new ZoneAction(matchUI, player, ZoneType.Command, MatchConstants.HUMANCOMMAND);
final Function<Byte, Boolean> manaAction = new Function<Byte, Boolean>() {
public Boolean apply(Byte colorCode) {
public Boolean apply(final Byte colorCode) {
if (CField.this.player.isLobbyPlayer(Singletons.getControl().getGuiPlayer())) {
return matchUI.getGameController().useMana(colorCode.byteValue());
final int oldMana = player.getMana(colorCode);
matchUI.getGameController().useMana(colorCode.byteValue());
return oldMana != player.getMana(colorCode);
}
return false;
}

View File

@@ -23,22 +23,17 @@ import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.List;
import javax.swing.JButton;
import forge.FThreads;
import forge.UiCommand;
import forge.game.GameView;
import forge.game.card.CardView;
import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbilityView;
import forge.gui.framework.ICDoc;
import forge.gui.framework.SDisplayUtil;
import forge.screens.match.CMatchUI;
import forge.screens.match.views.VPrompt;
import forge.toolbox.FSkin;
import forge.util.ITriggerEvent;
/**
* Controls the prompt panel in the match UI.
@@ -96,34 +91,14 @@ public class CPrompt implements ICDoc {
_initButton(view.getBtnOK(), actOK);
}
public void selectButtonOk() {
private void selectButtonOk() {
matchUI.getGameController().selectButtonOk();
}
public void selectButtonCancel() {
private void selectButtonCancel() {
matchUI.getGameController().selectButtonCancel();
}
public boolean passPriority() {
return matchUI.getGameController().passPriority();
}
public boolean passPriorityUntilEndOfTurn() {
return matchUI.getGameController().passPriorityUntilEndOfTurn();
}
public void selectPlayer(final PlayerView playerView, final ITriggerEvent triggerEvent) {
matchUI.getGameController().selectPlayer(playerView, triggerEvent);
}
public boolean selectCard(final CardView cardView, final List<CardView> otherCardViewsToSelect, final ITriggerEvent triggerEvent) {
return matchUI.getGameController().selectCard(cardView, otherCardViewsToSelect, triggerEvent);
}
public void selectAbility(final SpellAbilityView sa) {
matchUI.getGameController().selectAbility(sa);
}
public void setMessage(String s0) {
view.getTarMessage().setText(FSkin.encodeSymbols(s0, false));
}

View File

@@ -85,7 +85,7 @@ public final class GameMenu {
return new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
matchUI.getGameController().tryUndoLastAction();
matchUI.getGameController().undoLastAction();
}
};
}

View File

@@ -69,7 +69,7 @@ public class VStack implements IVDoc<CStack> {
// Other fields
private final AbilityMenu abilityMenu = new AbilityMenu();
private static StackInstanceTextArea hoveredItem;
private StackInstanceTextArea hoveredItem;
public StackInstanceTextArea getHoveredItem() {
return hoveredItem;
@@ -267,7 +267,7 @@ public class VStack implements IVDoc<CStack> {
controller.getMatchUI().setShouldAutoYield(key, !autoYield);
if (!autoYield && controller.getMatchUI().getGameView().peekStack() == item) {
//auto-pass priority if ability is on top of stack
controller.getMatchUI().getCPrompt().passPriority();
controller.getMatchUI().getGameController().passPriority();
}
}
});

View File

@@ -17,6 +17,24 @@
*/
package forge.toolbox;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import forge.UiCommand;
import forge.assets.FSkinProp;
import forge.gui.framework.ILocalRepaint;
@@ -25,11 +43,6 @@ import forge.toolbox.FSkin.Colors;
import forge.toolbox.FSkin.SkinImage;
import forge.toolbox.FSkin.SkinnedButton;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
/**
* The core JButton used throughout the Forge project. Follows skin font and
* theme button styling.

View File

@@ -7,20 +7,28 @@ import forge.util.ITriggerEvent;
//MouseEvent wrapper used for passing trigger to input classes
public class MouseTriggerEvent implements ITriggerEvent, Serializable {
private static final long serialVersionUID = -4746127117012991732L;
private static final long serialVersionUID = -5440485066050000298L;
private final MouseEvent event;
private final int button, x, y;
public MouseTriggerEvent(MouseEvent event0) {
event = event0;
public MouseTriggerEvent(final MouseEvent event) {
this.button = event.getButton();
this.x = event.getX();
this.y = event.getY();
}
@Override
public int getButton() {
return event.getButton();
return button;
}
public MouseEvent getMouseEvent() {
return event;
@Override
public int getX() {
return x;
}
@Override
public int getY() {
return y;
}
}

View File

@@ -26,6 +26,7 @@ import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.ScrollPaneConstants;
import javax.swing.Timer;
@@ -55,12 +56,20 @@ public class FloatingCardArea extends CardArea {
private static final String COORD_DELIM = ",";
private static final ForgePreferences prefs = FModel.getPreferences();
private static final HashMap<Integer, FloatingCardArea> floatingAreas = new HashMap<Integer, FloatingCardArea>();
private static final Map<Integer, FloatingCardArea> floatingAreas = new HashMap<Integer, 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 FloatingCardArea cardArea = _init(matchUI, player, zone);
cardArea.showOrHideWindow();
}
public static void show(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
final FloatingCardArea cardArea = _init(matchUI, player, zone);
cardArea.showWindow();
}
private static FloatingCardArea _init(final CMatchUI matchUI, final PlayerView player, final ZoneType zone) {
final int key = getKey(player, zone);
FloatingCardArea cardArea = floatingAreas.get(key);
if (cardArea == null || cardArea.getMatchUI() != matchUI) {
@@ -69,7 +78,11 @@ public class FloatingCardArea extends CardArea {
} else {
cardArea.setPlayer(player); //ensure player is updated if needed
}
cardArea.showWindow();
return cardArea;
}
public static CardPanel getCardPanel(final CMatchUI matchUI, final CardView card) {
final FloatingCardArea window = _init(matchUI, card.getController(), card.getZone());
return window.getCardPanel(card.getId());
}
public static void refresh(final PlayerView player, final ZoneType zone) {
FloatingCardArea cardArea = floatingAreas.get(getKey(player, zone));
@@ -188,16 +201,22 @@ public class FloatingCardArea extends CardArea {
}
private void showWindow() {
onShow();
window.setVisible(true);
}
private void showOrHideWindow() {
onShow();
window.setVisible(!window.isVisible());
}
private void onShow() {
if (!hasBeenShown) {
loadLocation();
window.getTitleBar().addMouseListener(new FMouseAdapter() {
@Override
public void onLeftDoubleClick(MouseEvent e) {
@Override public final void onLeftDoubleClick(final MouseEvent e) {
window.setVisible(false); //hide window if titlebar double-clicked
}
});
}
window.setVisible(!window.isVisible());
}
private void loadLocation() {
@@ -251,7 +270,7 @@ public class FloatingCardArea extends CardArea {
window.setSize(mainFrame.getWidth() / 5, mainFrame.getHeight() / 2);
}
public void refresh() {
private void refresh() {
if (!window.isVisible()) { return; } //don't refresh while window hidden
List<CardPanel> cardPanels = new ArrayList<CardPanel>();
@@ -292,7 +311,7 @@ public class FloatingCardArea extends CardArea {
}
}
final Timer layoutTimer = new Timer(250, new ActionListener() {
private final Timer layoutTimer = new Timer(250, new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
layoutTimer.stop();
@@ -311,12 +330,12 @@ public class FloatingCardArea extends CardArea {
}
@Override
public final void mouseLeftClicked(final CardPanel panel, final MouseEvent evt) {
getMatchUI().getCPrompt().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt));
getMatchUI().getGameController().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt));
super.mouseLeftClicked(panel, evt);
}
@Override
public final void mouseRightClicked(final CardPanel panel, final MouseEvent evt) {
getMatchUI().getCPrompt().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt));
getMatchUI().getGameController().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt));
super.mouseRightClicked(panel, evt);
}
}

View File

@@ -60,14 +60,14 @@ public class HandArea extends CardArea {
/** {@inheritDoc} */
@Override
public final void mouseLeftClicked(final CardPanel panel, final MouseEvent evt) {
getMatchUI().getCPrompt().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt));
getMatchUI().getGameController().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt));
super.mouseLeftClicked(panel, evt);
}
/** {@inheritDoc} */
@Override
public final void mouseRightClicked(final CardPanel panel, final MouseEvent evt) {
getMatchUI().getCPrompt().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt));
getMatchUI().getGameController().selectCard(panel.getCard(), null, new MouseTriggerEvent(evt));
super.mouseRightClicked(panel, evt);
}
}

View File

@@ -543,7 +543,7 @@ public class PlayArea extends CardPanelContainer implements CardPanelMouseListen
}
}
}
if (getMatchUI().getCPrompt().selectCard(panel.getCard(), otherCardViewsToSelect, triggerEvent)) {
if (getMatchUI().getGameController().selectCard(panel.getCard(), otherCardViewsToSelect, triggerEvent)) {
return true;
}
//if panel can't do anything with card selection, try selecting previous panel in stack

View File

@@ -473,8 +473,9 @@ public class PlayerControllerForTests extends PlayerController {
}
@Override
public SpellAbility getAbilityToPlay(List<SpellAbility> abilities, ITriggerEvent triggerEvent) {
return getAbilityToPlay(abilities);
public SpellAbility getAbilityToPlay(Card hostCard, List<SpellAbility> abilities, ITriggerEvent triggerEvent) {
// Isn't this a method invocation loop? --elcnesh
return getAbilityToPlay(hostCard, abilities);
}
@Override

View File

@@ -34,11 +34,9 @@ import forge.game.player.IHasIcon;
import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbilityView;
import forge.game.zone.ZoneType;
import forge.interfaces.IButton;
import forge.item.PaperCard;
import forge.match.AbstractGuiGame;
import forge.match.HostedMatch;
import forge.match.MatchButtonType;
import forge.model.FModel;
import forge.player.PlayerZoneUpdate;
import forge.properties.ForgePreferences;
@@ -50,6 +48,7 @@ import forge.screens.match.views.VPlayerPanel;
import forge.screens.match.views.VPlayerPanel.InfoTab;
import forge.screens.match.views.VPrompt;
import forge.screens.match.winlose.ViewWinLose;
import forge.toolbox.FButton;
import forge.toolbox.FDisplayObject;
import forge.toolbox.FOptionPane;
import forge.trackable.TrackableCollection;
@@ -142,24 +141,18 @@ public class MatchController extends AbstractGuiGame {
Forge.openScreen(view);
}
@Override
public IButton getBtnOK(PlayerView player) {
return view.getPrompt(player).getBtnOk();
}
@Override
public IButton getBtnCancel(PlayerView player) {
return view.getPrompt(player).getBtnCancel();
}
@Override
public void showPromptMessage(final PlayerView player, final String message) {
view.getPrompt(player).setMessage(message);
}
@Override
public void focusButton(final MatchButtonType button) {
//not needed for mobile game
public void updateButtons(final PlayerView owner, final String label1, final String label2, final boolean enable1, final boolean enable2, final boolean focus1) {
final VPrompt prompt = view.getPrompt(owner);
final FButton btn1 = prompt.getBtnOk(), btn2 = prompt.getBtnCancel();
btn1.setText(label1);
btn2.setText(label2);
btn1.setEnabled(enable1);
btn2.setEnabled(enable2);
}
@Override
@@ -224,7 +217,7 @@ public class MatchController extends AbstractGuiGame {
}
@Override
public SpellAbilityView getAbilityToPlay(List<SpellAbilityView> abilities, ITriggerEvent triggerEvent) {
public SpellAbilityView getAbilityToPlay(final CardView hostCard, final List<SpellAbilityView> abilities, final ITriggerEvent triggerEvent) {
if (abilities.isEmpty()) {
return null;
}
@@ -320,11 +313,6 @@ public class MatchController extends AbstractGuiGame {
}
}
@Override
public boolean stopAtPhase(PlayerView turn, PhaseType phase) {
return view.stopAtPhase(turn, phase);
}
@Override
public void updateZones(Iterable<PlayerZoneUpdate> zonesToUpdate) {
view.updateZones(zonesToUpdate);

View File

@@ -384,7 +384,7 @@ public class MatchScreen extends FScreen {
break;
case Keys.Z: //undo on Ctrl+Z
if (KeyInputAdapter.isCtrlKeyDown()) {
getGameController().tryUndoLastAction();
getGameController().undoLastAction();
return true;
}
break;

View File

@@ -91,7 +91,13 @@ public class VManaPool extends VDisplayArea {
public boolean flick(float x, float y) {
if (player.isLobbyPlayer(GamePlayerUtil.getGuiPlayer())) {
//on two finger tap, keep using mana until it runs out or no longer can be put towards the cost
while (MatchController.instance.getGameController().useMana(colorCode)) {}
int oldMana, newMana = player.getMana(colorCode);
do {
oldMana = newMana;
MatchController.instance.getGameController().useMana(colorCode);
newMana = player.getMana(colorCode);
}
while (oldMana != newMana);
}
return true;
}

View File

@@ -5,11 +5,8 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont.HAlignment;
import forge.Graphics;
import forge.assets.FImage;
import forge.assets.FSkinFont;
import forge.game.card.CardView;
import forge.game.player.PlayerView;
import forge.menu.FDropDown;
import forge.model.FModel;
import forge.properties.ForgePreferences.FPref;
import forge.screens.match.MatchController;
import forge.toolbox.FContainer;
import forge.toolbox.FDisplayObject;
@@ -65,33 +62,7 @@ public class VPlayers extends FDropDown {
g.drawImage(avatarImage, x, y, h, h);
x += h + PADDING;
StringBuilder builder = new StringBuilder();
builder.append(player.getName());
builder.append("\nLife: " + String.valueOf(player.getLife()));
builder.append(" | Poison counters: " + String.valueOf(player.getPoisonCounters()));
builder.append(" | Maximum hand size: " + String.valueOf(player.getMaxHandSize()));
builder.append(" | Cards drawn this turn: " + String.valueOf(player.getNumDrawnThisTurn()));
builder.append(" | Damage Prevention: " + String.valueOf(player.getPreventNextDamage()));
if (!player.getKeywords().isEmpty()) {
builder.append(" | " + player.getKeywords().toString());
}
if (FModel.getPreferences().getPrefBoolean(FPref.UI_ANTE)) {
Iterable<CardView> list = player.getAnte();
builder.append(" | Ante'd: ");
boolean needDelim = false;
for (CardView cv : list) {
if (needDelim) {
builder.append(", ");
}
else { needDelim = true; }
builder.append(cv);
}
}
if (MatchController.instance.getGameView().isCommander()) {
builder.append(" | " + player.getCommanderInfo());
}
g.drawText(builder.toString(), FONT, FList.FORE_COLOR, x, y, getWidth() - PADDING - x, h, true, HAlignment.LEFT, true);
g.drawText(player.getDetails(), FONT, FList.FORE_COLOR, x, y, getWidth() - PADDING - x, h, true, HAlignment.LEFT, true);
}
@Override

View File

@@ -126,7 +126,8 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
}
synchronized (zonesUpdate) {
if (!zonesUpdate.isEmpty()) {
matchController.updateZones(zonesUpdate);
// Copy to prevent concurrency issues
matchController.updateZones(new PlayerZoneUpdates(zonesUpdate));
zonesUpdate.clear();
}
}

View File

@@ -32,8 +32,7 @@ public class WatchLocalGame extends PlayerControllerHuman {
}
@Override
public boolean tryUndoLastAction() {
return false;
public void undoLastAction() {
}
@Override
@@ -63,18 +62,15 @@ public class WatchLocalGame extends PlayerControllerHuman {
}
@Override
public boolean passPriority() {
return false;
public void passPriority() {
}
@Override
public boolean passPriorityUntilEndOfTurn() {
return false;
public void passPriorityUntilEndOfTurn() {
}
@Override
public boolean useMana(final byte mana) {
return false;
public void useMana(final byte mana) {
}
@Override

View File

@@ -18,15 +18,15 @@ public interface IGameController {
void alphaStrike();
boolean useMana(byte color);
void useMana(byte color);
void selectButtonOk();
void selectButtonCancel();
boolean passPriority();
void passPriority();
boolean passPriorityUntilEndOfTurn();
void passPriorityUntilEndOfTurn();
void selectPlayer(PlayerView playerView, ITriggerEvent triggerEvent);
@@ -35,7 +35,7 @@ public interface IGameController {
void selectAbility(SpellAbilityView sa);
boolean tryUndoLastAction();
void undoLastAction();
IDevModeCheats cheat();

View File

@@ -19,7 +19,6 @@ import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbilityView;
import forge.game.zone.ZoneType;
import forge.item.PaperCard;
import forge.match.MatchButtonType;
import forge.player.PlayerZoneUpdate;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
@@ -31,10 +30,8 @@ public interface IGuiGame {
void afterGameEnd();
void showCombat();
void showPromptMessage(PlayerView playerView, String message);
boolean stopAtPhase(PlayerView playerTurn, PhaseType phase);
IButton getBtnOK(PlayerView playerView);
IButton getBtnCancel(PlayerView playerView);
void focusButton(MatchButtonType button);
void updateButtons(PlayerView owner, boolean okEnabled, boolean cancelEnabled, boolean focusOk);
void updateButtons(PlayerView owner, String label1, String label2, boolean enable1, boolean enable2, boolean focus1);
void flashIncorrectAction();
void updatePhase();
void updateTurn(PlayerView player);
@@ -52,11 +49,8 @@ public interface IGuiGame {
void updateManaPool(Iterable<PlayerView> manaPoolUpdate);
void updateLives(Iterable<PlayerView> livesUpdate);
void setPanelSelection(CardView hostCard);
SpellAbilityView getAbilityToPlay(List<SpellAbilityView> abilities,
ITriggerEvent triggerEvent);
Map<CardView, Integer> assignDamage(CardView attacker,
List<CardView> blockers, int damage, GameEntityView defender,
boolean overrideOrder);
SpellAbilityView getAbilityToPlay(CardView hostCard, List<SpellAbilityView> abilities, ITriggerEvent triggerEvent);
Map<CardView, Integer> assignDamage(CardView attacker, List<CardView> blockers, int damage, GameEntityView defender, boolean overrideOrder);
void message(String message);
void message(String message, String title);
@@ -163,10 +157,6 @@ public interface IGuiGame {
void setPlayerAvatar(LobbyPlayer player, IHasIcon ihi);
boolean openZones(Collection<ZoneType> zones, Map<PlayerView, Object> players);
void restoreOldZones(Map<PlayerView, Object> playersToRestoreZonesFor);
void updateButtons(PlayerView owner, boolean okEnabled,
boolean cancelEnabled, boolean focusOk);
void updateButtons(PlayerView owner, String okLabel, String cancelLabel,
boolean okEnabled, boolean cancelEnabled, boolean focusOk);
void setHighlighted(PlayerView pv, boolean b);
void setUsedToPay(CardView card, boolean value);

View File

@@ -24,7 +24,6 @@ import forge.game.GameView;
import forge.game.card.CardView;
import forge.game.card.CardView.CardStateView;
import forge.game.player.PlayerView;
import forge.interfaces.IButton;
import forge.interfaces.IGameController;
import forge.interfaces.IGuiGame;
import forge.interfaces.IMayViewCards;
@@ -195,20 +194,7 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards {
}
@Override
public void updateButtons(final PlayerView owner, final String okLabel, final String cancelLabel, final boolean okEnabled, final boolean cancelEnabled, final boolean focusOk) {
final IButton btnOk = getBtnOK(owner);
final IButton btnCancel = getBtnCancel(owner);
btnOk.setText(okLabel);
btnCancel.setText(cancelLabel);
btnOk.setEnabled(okEnabled);
btnCancel.setEnabled(cancelEnabled);
if (okEnabled && focusOk) {
focusButton(MatchButtonType.OK);
} else if (cancelEnabled) {
focusButton(MatchButtonType.CANCEL);
}
}
public abstract void updateButtons(PlayerView owner, String label1, String label2, boolean enable1, boolean enable2, boolean focus1);
// Auto-yield and other input-related code

View File

@@ -193,11 +193,6 @@ public class HostedMatch {
gui.setGameController(null, humanController);
gui.openView(null);
} else if (humanCount == players.size()) {
//if there are no AI's, allow all players to see all cards (hotseat mode).
for (final PlayerControllerHuman humanController : humanControllers) {
humanController.setMayLookAtAllCards(true);
}
}
//prompt user for player one name if needed

View File

@@ -129,7 +129,7 @@ public class InputPassPriority extends InputSyncronizedBase {
return false;
}
final SpellAbility ability = getController().getAbilityToPlay(abilities, triggerEvent);
final SpellAbility ability = getController().getAbilityToPlay(card, abilities, triggerEvent);
if (ability != null) {
chosenSa = new ArrayList<SpellAbility>();
chosenSa.add(ability);

View File

@@ -153,14 +153,12 @@ public abstract class InputPayMana extends InputSyncronizedBase {
return abilities;
}
public boolean useManaFromPool(byte colorCode) {
public void useManaFromPool(byte colorCode) {
// find the matching mana in pool.
if (player.getManaPool().tryPayCostWithColor(colorCode, saPaidFor, manaCost)) {
onManaAbilityPaid();
showMessage();
return true;
}
return false;
}
protected boolean activateManaAbility(final Card card, ManaCostBeingPaid manaCost) {

View File

@@ -8,7 +8,6 @@ import java.util.Map;
import com.google.common.base.Function;
import forge.UiCommand;
import forge.assets.FSkinProp;
import forge.deck.CardPool;
import forge.game.GameEntityView;
@@ -18,11 +17,12 @@ import forge.game.phase.PhaseType;
import forge.game.player.DelayedReveal;
import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbilityView;
import forge.interfaces.IButton;
import forge.interfaces.IGameController;
import forge.interfaces.IGuiGame;
import forge.match.MatchButtonType;
import forge.match.NextGameDecision;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
import forge.util.ReflectionUtil;
public final class GameProtocol {
@@ -36,101 +36,146 @@ public final class GameProtocol {
return ProtocolMethod.valueOf(name);
}
private enum Mode {
SERVER(IGuiGame.class),
CLIENT(IGameController.class);
private final Class<?> toInvoke;
private Mode(final Class<?> toInvoke) {
this.toInvoke = toInvoke;
}
};
/**
* The methods that can be sent through this protocol.
*/
public enum ProtocolMethod {
setGameView(Void.TYPE, GameView.class),
openView(Void.TYPE, TrackableCollection.class),
afterGameEnd(),
showCombat(),
showPromptMessage(Void.TYPE, PlayerView.class, String.class),
stopAtPhase(Boolean.TYPE, PlayerView.class, PhaseType.class),
focusButton(Void.TYPE, MatchButtonType.class),
flashIncorrectAction(),
updatePhase(),
updateTurn(Void.TYPE, PlayerView.class),
updatePlayerControl(),
enableOverlay(),
disableOverlay(),
finishGame(),
showManaPool(Void.TYPE, PlayerView.class),
hideManaPool(Void.TYPE, PlayerView.class),
updateStack(),
updateZones(Void.TYPE, Iterable.class),
updateCards(Void.TYPE, Iterable.class),
updateManaPool(Void.TYPE, Iterable.class),
updateLives(Void.TYPE, Iterable.class),
setPanelSelection(Void.TYPE, CardView.class),
getAbilityToPlay(SpellAbilityView.class, List.class, ITriggerEvent.class),
assignDamage(Map.class, CardView.class, List.class, Integer.TYPE, GameEntityView.class, Boolean.TYPE),
message(Void.TYPE, String.class, String.class),
showErrorDialog(Void.TYPE, String.class, String.class),
showConfirmDialog(Boolean.TYPE, String.class, String.class, String.class, String.class, Boolean.TYPE),
showOptionDialog(Integer.TYPE, String.class, String.class, FSkinProp.class, Array.class, Integer.TYPE),
showCardOptionDialog(Integer.TYPE, CardView.class, String.class, String.class, FSkinProp.class, String.class, Array.class),
showInputDialog(String.class, String.class, String.class, FSkinProp.class, String.class, Array.class),
confirm(Boolean.TYPE, CardView.class, String.class, Boolean.TYPE, Array.class),
getChoices(List.class, String.class, Integer.TYPE, Integer.TYPE, Collection.class, Object.class, Function.class),
order(List.class, String.class, String.class, Integer.TYPE, Integer.TYPE, List.class, List.class, CardView.class, Boolean.TYPE),
sideboard(List.class, CardPool.class, CardPool.class),
chooseSingleEntityForEffect(GameEntityView.class, String.class, TrackableCollection.class, DelayedReveal.class, Boolean.TYPE),
setCard(Void.TYPE, CardView.class),
// Server -> Client
setGameView (Mode.SERVER, Void.TYPE, GameView.class),
openView (Mode.SERVER, Void.TYPE, TrackableCollection/*PlayerView*/.class),
afterGameEnd (Mode.SERVER),
showCombat (Mode.SERVER),
showPromptMessage (Mode.SERVER, Void.TYPE, PlayerView.class, String.class),
updateButtons (Mode.SERVER, Void.TYPE, PlayerView.class, String.class, String.class, Boolean.TYPE, Boolean.TYPE, Boolean.TYPE),
flashIncorrectAction(Mode.SERVER),
updatePhase (Mode.SERVER),
updateTurn (Mode.SERVER, Void.TYPE, PlayerView.class),
updatePlayerControl (Mode.SERVER),
enableOverlay (Mode.SERVER),
disableOverlay (Mode.SERVER),
finishGame (Mode.SERVER),
showManaPool (Mode.SERVER, Object.class, PlayerView.class),
hideManaPool (Mode.SERVER, Void.TYPE, PlayerView.class),
updateStack (Mode.SERVER),
updateZones (Mode.SERVER, Void.TYPE, Iterable/*PlayerZoneUpdate*/.class),
updateCards (Mode.SERVER, Void.TYPE, Iterable/*CardView*/.class),
updateManaPool (Mode.SERVER, Void.TYPE, Iterable/*PlayerView*/.class),
updateLives (Mode.SERVER, Void.TYPE, Iterable/*PlayerView*/.class),
setPanelSelection (Mode.SERVER, Void.TYPE, CardView.class),
getAbilityToPlay (Mode.SERVER, SpellAbilityView.class, CardView.class, List/*SpellAbilityView*/.class, ITriggerEvent.class),
assignDamage (Mode.SERVER, Map.class, CardView.class, List/*CardView*/.class, Integer.TYPE, GameEntityView.class, Boolean.TYPE),
message (Mode.SERVER, Void.TYPE, String.class, String.class),
showErrorDialog (Mode.SERVER, Void.TYPE, String.class, String.class),
showConfirmDialog (Mode.SERVER, Boolean.TYPE, String.class, String.class, String.class, String.class, Boolean.TYPE),
showOptionDialog (Mode.SERVER, Integer.TYPE, String.class, String.class, FSkinProp.class, Array/*String*/.class, Integer.TYPE),
showCardOptionDialog(Mode.SERVER, Integer.TYPE, CardView.class, String.class, String.class, FSkinProp.class, String.class, Array/*String*/.class),
showInputDialog (Mode.SERVER, String.class, String.class, String.class, FSkinProp.class, String.class, Array/*String*/.class),
confirm (Mode.SERVER, Boolean.TYPE, CardView.class, String.class, Boolean.TYPE, Array/*String*/.class),
getChoices (Mode.SERVER, List.class, String.class, Integer.TYPE, Integer.TYPE, Collection.class, Object.class, Function.class),
order (Mode.SERVER, List.class, String.class, String.class, Integer.TYPE, Integer.TYPE, List.class, List.class, CardView.class, Boolean.TYPE),
sideboard (Mode.SERVER, List.class, CardPool.class, CardPool.class),
chooseSingleEntityForEffect(Mode.SERVER, GameEntityView.class, String.class, TrackableCollection.class, DelayedReveal.class, Boolean.TYPE),
setCard (Mode.SERVER, Void.TYPE, CardView.class),
// TODO case "setPlayerAvatar":
openZones(Boolean.TYPE, Collection.class, Map.class),
restoreOldZones(Void.TYPE, Map.class),
isUiSetToSkipPhase(Boolean.TYPE, PlayerView.class, PhaseType.class),
// BUTTONS
btn_setEnabled(Void.TYPE, Boolean.TYPE),
btn_setVisible(Void.TYPE, Boolean.TYPE),
btn_setText(Void.TYPE, String.class),
btn_isSelected(Boolean.TYPE),
btn_setSelected(Void.TYPE, Boolean.TYPE),
btn_requestFocusInWindows(Boolean.TYPE),
btn_setCommand(Void.TYPE, UiCommand.class),
btn_setImage(Void.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE),
btn_setTextImage(Void.TYPE, FSkinProp.class);
openZones (Mode.SERVER, Boolean.TYPE, Collection/*ZoneType*/.class, Map/*PlayerView,Object*/.class),
restoreOldZones (Mode.SERVER, Void.TYPE, Map/*PlayerView,Object*/.class),
isUiSetToSkipPhase (Mode.SERVER, Boolean.TYPE, PlayerView.class, PhaseType.class),
// Client -> Server
// Note: these should all return void, to avoid awkward situations in
// which client and server wait for one another's response and block
// the threads that're supposed to give that response
useMana (Mode.CLIENT, Void.TYPE, Byte.TYPE),
undoLastAction (Mode.CLIENT, Void.TYPE, Boolean.TYPE),
selectPlayer (Mode.CLIENT, Void.TYPE, PlayerView.class, ITriggerEvent.class),
selectCard (Mode.CLIENT, Void.TYPE, CardView.class, List.class, ITriggerEvent.class),
selectButtonOk (Mode.CLIENT),
selectButtonCancel (Mode.CLIENT),
selectAbility (Mode.CLIENT, Void.TYPE, SpellAbilityView.class),
passPriorityUntilEndOfTurn(Mode.CLIENT),
passPriority (Mode.CLIENT),
nextGameDecision (Mode.CLIENT, Void.TYPE, NextGameDecision.class),
getActivateDescription (Mode.CLIENT, Void.TYPE, String.class, CardView.class),
concede (Mode.CLIENT),
alphaStrike (Mode.CLIENT),
reorderHand (Mode.CLIENT, Void.TYPE, CardView.class, Integer.TYPE);
private final Mode mode;
private final Class<?> returnType;
private final Class<?>[] args;
private ProtocolMethod() {
this(Void.TYPE);
private ProtocolMethod(final Mode mode) {
this(mode, Void.TYPE);
}
private ProtocolMethod(final Class<?> returnType) {
this(returnType, (Class<?>[]) null);
private ProtocolMethod(final Mode mode, final Class<?> returnType) {
this(mode, returnType, (Class<?>[]) null);
}
@SafeVarargs
private ProtocolMethod(final Class<?> returnType, final Class<?> ... args) {
private ProtocolMethod(final Mode mode, final Class<?> returnType, final Class<?> ... args) {
this.mode = mode;
this.returnType = returnType;
this.args = args == null ? new Class<?>[] {} : args;
}
public Method getMethod() {
try {
final String name;
final Class<?> toCall;
if (name().startsWith("btn_")) {
name = name().substring("btn_".length());
toCall = IButton.class;
} else {
name = name();
toCall = IGuiGame.class;
}
final Method candidate = toCall.getMethod(name, args);
if (!candidate.getReturnType().equals(returnType)) {
final Class<?> toCall = mode.toInvoke;
final Method candidate = toCall.getMethod(name(), args);
// Don't check Client return values for now as some use void
// and a default return value, to improve performance
if (mode == Mode.SERVER && !candidate.getReturnType().equals(returnType)) {
throw new NoSuchMethodException(String.format("Wrong return type for method %s", name()));
}
return candidate;
} catch (final NoSuchMethodException | SecurityException e) {
System.err.println(String.format("Warning: class contains no method named %s", name()));
return null;
System.err.println(String.format("Warning: class contains no accessible method named %s", name()));
return getMethodNoArgs();
}
}
public boolean invokeOnButton() {
return name().startsWith("btn_");
private Method getMethodNoArgs() {
try {
return mode.toInvoke.getMethod(name(), (Class<?>[]) null);
} catch (final NoSuchMethodException | SecurityException e) {
System.err.println(String.format("Warning: class contains no accessible arg-less method named %s", name()));
return null;
}
}
public Class<?> getReturnType() {
return returnType;
}
public Class<?>[] getArgTypes() {
return args;
}
public void checkArgs(final Object[] args) {
for (int iArg = 0; iArg < args.length; iArg++) {
final Object arg = args[iArg];
final Class<?> type = this.args[iArg];
if (!ReflectionUtil.isInstance(arg, type)) {
throw new InternalError(String.format("Protocol method %s: illegal argument (%d) of type %s, %s expected", name(), iArg, arg.getClass().getName(), type.getName()));
}
}
}
public void checkReturnValue(final Object value) {
if (returnType.equals(Void.TYPE)) {
// If void is expected, any return value is fine
return;
}
if (!ReflectionUtil.isInstance(value, returnType)) {
throw new IllegalStateException(String.format("Protocol method %s: illegal return object type %s returned by client, expected %s", name(), value.getClass().getName(), getReturnType().getName()));
}
}
}
}

View File

@@ -0,0 +1,96 @@
package forge.net;
import forge.FThreads;
import forge.net.GameProtocol.ProtocolMethod;
import forge.net.event.GuiGameEvent;
import forge.net.event.ReplyEvent;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public abstract class GameProtocolHandler<T> extends ChannelInboundHandlerAdapter {
private final boolean runInEdt;
protected GameProtocolHandler(final boolean runInEdt) {
this.runInEdt = runInEdt;
}
protected abstract ReplyPool getReplyPool(ChannelHandlerContext ctx);
protected abstract IRemote getRemote(ChannelHandlerContext ctx);
protected abstract T getToInvoke(ChannelHandlerContext ctx);
protected abstract void beforeCall(ProtocolMethod protocolMethod, Object[] args);
@Override
public final void channelRead(final ChannelHandlerContext ctx, final Object msg) {
System.out.println("Received: " + msg);
if (msg instanceof ReplyEvent) {
final ReplyEvent event = (ReplyEvent) msg;
getReplyPool(ctx).complete(event.getIndex(), event.getReply());
} else if (msg instanceof GuiGameEvent) {
final GuiGameEvent event = (GuiGameEvent) msg;
final ProtocolMethod protocolMethod = event.getMethod();
final String methodName = protocolMethod.name();
final Method method = protocolMethod.getMethod();
if (method == null) {
throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name()));
}
final Object[] args = event.getObjects();
protocolMethod.checkArgs(args);
final Object toInvoke = getToInvoke(ctx);
// Pre-call actions
beforeCall(protocolMethod, args);
final Class<?> returnType = protocolMethod.getReturnType();
final Runnable toRun = new Runnable() {
@Override public final void run() {
if (returnType.equals(Void.TYPE)) {
try {
method.invoke(toInvoke, args);
} catch (final IllegalAccessException | IllegalArgumentException e) {
System.err.println(String.format("Unknown protocol method %s with %d args", methodName, args == null ? 0 : args.length));
} catch (final InvocationTargetException e) {
throw new RuntimeException(e);
}
} else {
Serializable reply = null;
try {
final Object theReply = method.invoke(toInvoke, args);
if (theReply instanceof Serializable) {
protocolMethod.checkReturnValue(theReply);
reply = (Serializable) theReply;
} else if (theReply != null) {
System.err.println(String.format("Non-serializable return type %s for method %s, returning null", returnType.getName(), methodName));
}
} catch (final IllegalAccessException | IllegalArgumentException e) {
System.err.println(String.format("Unknown protocol method %s with %d args, replying with null", methodName, args == null ? 0 : args.length));
} catch (final InvocationTargetException e) {
throw new RuntimeException(e);
}
getRemote(ctx).send(new ReplyEvent(event.getId(), reply));
}
}
};
if (runInEdt) {
FThreads.invokeInEdtNowOrLater(toRun);
} else {
FThreads.invokeInBackgroundThread(toRun);
}
}
}
@Override
public final void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}

View File

@@ -0,0 +1,32 @@
package forge.net;
import java.util.concurrent.TimeoutException;
import forge.net.GameProtocol.ProtocolMethod;
import forge.net.event.GuiGameEvent;
public final class GameProtocolSender {
private final IRemote remote;
public GameProtocolSender(final IRemote remote) {
this.remote = remote;
}
public void send(final ProtocolMethod method, final Object... args) {
method.checkArgs(args);
remote.send(new GuiGameEvent(method, args));
}
@SuppressWarnings("unchecked")
public <T> T sendAndWait(final ProtocolMethod method, final Object... args) {
method.checkArgs(args);
try {
final Object returned = remote.sendAndWait(new GuiGameEvent(method, args));
method.checkReturnValue(returned);
return (T) returned;
} catch (final TimeoutException e) {
e.printStackTrace();
}
return null;
}
}

View File

@@ -1,26 +1,18 @@
package forge.net.client;
import forge.FThreads;
import io.netty.channel.ChannelHandlerContext;
import forge.game.player.PlayerView;
import forge.interfaces.IGuiGame;
import forge.match.MatchButtonType;
import forge.model.FModel;
import forge.net.GameProtocol;
import forge.net.GameProtocol.ProtocolMethod;
import forge.net.event.GuiGameEvent;
import forge.net.GameProtocolHandler;
import forge.net.IRemote;
import forge.net.ReplyPool;
import forge.net.event.LoginEvent;
import forge.net.event.ReplyEvent;
import forge.properties.ForgePreferences.FPref;
import forge.trackable.TrackableCollection;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
class GameClientHandler extends ChannelInboundHandlerAdapter {
final class GameClientHandler extends GameProtocolHandler<IGuiGame> {
private final FGameClient client;
private final IGuiGame gui;
@@ -28,49 +20,29 @@ class GameClientHandler extends ChannelInboundHandlerAdapter {
* Creates a client-side game handler.
*/
public GameClientHandler(final FGameClient client) {
super(true);
this.client = client;
this.gui = client.getGui();
}
@Override
public void channelActive(final ChannelHandlerContext ctx) {
// Don't use send() here, as this.channel is not yet set!
ctx.channel().writeAndFlush(new LoginEvent(FModel.getPreferences().getPref(FPref.PLAYER_NAME), Integer.parseInt(FModel.getPreferences().getPref(FPref.UI_AVATARS).split(",")[0])));
protected ReplyPool getReplyPool(final ChannelHandlerContext ctx) {
return client.getReplyPool();
}
@Override
protected IRemote getRemote(final ChannelHandlerContext ctx) {
return client;
}
@Override
protected IGuiGame getToInvoke(final ChannelHandlerContext ctx) {
return gui;
}
@SuppressWarnings("unchecked")
@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
System.out.println("Client received: " + msg);
if (msg instanceof ReplyEvent) {
final ReplyEvent event = (ReplyEvent) msg;
client.getReplyPool().complete(event.getIndex(), event.getReply());
} else if (msg instanceof GuiGameEvent) {
final GuiGameEvent event = (GuiGameEvent) msg;
final String methodName = event.getMethod();
final Object[] originalArgs = event.getObjects();
final ProtocolMethod protocolMethod = GameProtocol.getProtocolMethod(methodName);
if (protocolMethod == null) {
throw new IllegalStateException(String.format("Protocol method %s unknown", methodName));
}
final Method method = protocolMethod.getMethod();
if (method == null) {
throw new IllegalStateException(String.format("Method %s not found", protocolMethod.name()));
}
final Object toInvoke;
final Object[] args;
if (protocolMethod.invokeOnButton()) {
toInvoke = originalArgs[1] == MatchButtonType.OK ? gui.getBtnOK((PlayerView) originalArgs[0]) : gui.getBtnCancel((PlayerView) originalArgs[0]);
// Remove the player and button type from the args passed to the method
args = Arrays.copyOfRange(originalArgs, 2, originalArgs.length);
} else {
toInvoke = gui;
args = originalArgs;
}
// Pre-call actions
protected void beforeCall(final ProtocolMethod protocolMethod, final Object[] args) {
switch (protocolMethod) {
case openView:
final TrackableCollection<PlayerView> myPlayers = (TrackableCollection<PlayerView>) args[0];
@@ -79,42 +51,12 @@ class GameClientHandler extends ChannelInboundHandlerAdapter {
default:
break;
}
final Class<?> returnType = method.getReturnType();
if (returnType.equals(Void.TYPE)) {
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override public final void run() {
try {
method.invoke(toInvoke, args);
} catch (final IllegalAccessException | IllegalArgumentException e) {
System.err.println(String.format("Unknown protocol method %s with %d args", methodName, args == null ? 0 : args.length));
} catch (final InvocationTargetException e) {
throw new RuntimeException(e);
}
}
});
} else {
Serializable reply = null;
try {
final Object theReply = method.invoke(toInvoke, args);
if (theReply instanceof Serializable) {
reply = (Serializable) method.invoke(toInvoke, args);
} else {
System.err.println(String.format("Non-serializable return type %s for method %s", returnType.getName(), methodName));
}
} catch (final IllegalAccessException | IllegalArgumentException e) {
System.err.println(String.format("Unknown protocol method %s with %d args, replying with null", methodName, args == null ? 0 : args.length));
} catch (final InvocationTargetException e) {
throw new RuntimeException(e);
}
client.send(new ReplyEvent(event.getId(), reply));
}
}
}
@Override
public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {
cause.printStackTrace();
ctx.close();
public void channelActive(final ChannelHandlerContext ctx) {
// Don't use send() here, as this.channel is not yet set!
ctx.channel().writeAndFlush(new LoginEvent(FModel.getPreferences().getPref(FPref.PLAYER_NAME), Integer.parseInt(FModel.getPreferences().getPref(FPref.UI_AVATARS).split(",")[0])));
}
}

View File

@@ -1,7 +1,6 @@
package forge.net.client;
import java.util.List;
import java.util.concurrent.TimeoutException;
import forge.game.card.CardView;
import forge.game.player.PlayerView;
@@ -9,90 +8,77 @@ import forge.game.spellability.SpellAbilityView;
import forge.interfaces.IDevModeCheats;
import forge.interfaces.IGameController;
import forge.match.NextGameDecision;
import forge.net.event.GuiGameEvent;
import forge.net.GameProtocol.ProtocolMethod;
import forge.net.GameProtocolSender;
import forge.util.ITriggerEvent;
public class NetGameController implements IGameController {
private final IToServer server;
private final GameProtocolSender sender;
public NetGameController(final IToServer server) {
this.server = server;
this.sender = new GameProtocolSender(server);
}
private String methodName() {
boolean passedFirst = false;
for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) {
if (ste.getClassName() == getClass().getName()) {
if (passedFirst) {
return ste.getMethodName();
}
passedFirst = true;
}
}
return null;
private void send(final ProtocolMethod method, final Object... args) {
sender.send(method, args);
}
private void send(final String method, final Object... args) {
server.send(new GuiGameEvent(method, args));
}
@SuppressWarnings("unchecked")
private <T> T sendAndWait(final String method, final Object... args) {
try {
return (T) server.sendAndWait(new GuiGameEvent(method, args));
} catch (final TimeoutException e) {
e.printStackTrace();
}
return null;
private <T> T sendAndWait(final ProtocolMethod method, final Object... args) {
return sender.sendAndWait(method, args);
}
@Override
public boolean useMana(final byte color) {
return sendAndWait(methodName(), color);
public void useMana(final byte color) {
send(ProtocolMethod.useMana, Byte.valueOf(color));
}
@Override
public boolean tryUndoLastAction() {
return sendAndWait(methodName());
public void undoLastAction() {
send(ProtocolMethod.undoLastAction);
}
@Override
public void selectPlayer(final PlayerView playerView, final ITriggerEvent triggerEvent) {
send(methodName(), playerView, triggerEvent);
send(ProtocolMethod.selectPlayer, playerView, triggerEvent);
}
@Override
public boolean selectCard(final CardView cardView, final List<CardView> otherCardViewsToSelect, final ITriggerEvent triggerEvent) {
return sendAndWait(methodName(), cardView, otherCardViewsToSelect, triggerEvent);
send(ProtocolMethod.selectCard, cardView, otherCardViewsToSelect, triggerEvent);
// Difference from local games! Always consider a card as successfully selected,
// to avoid blocks where server and client wait for each other to respond.
// Some cost in functionality but a huge gain in stability & speed.
return true;
}
@Override
public void selectButtonOk() {
send(methodName());
send(ProtocolMethod.selectButtonOk);
}
@Override
public void selectButtonCancel() {
send(methodName());
send(ProtocolMethod.selectButtonCancel);
}
@Override
public void selectAbility(final SpellAbilityView sa) {
send(methodName(), sa);
send(ProtocolMethod.selectAbility, sa);
}
@Override
public boolean passPriorityUntilEndOfTurn() {
return sendAndWait(methodName());
public void passPriorityUntilEndOfTurn() {
send(ProtocolMethod.passPriorityUntilEndOfTurn);
}
@Override
public boolean passPriority() {
return sendAndWait(methodName());
public void passPriority() {
send(ProtocolMethod.passPriority);
}
@Override
public void nextGameDecision(final NextGameDecision decision) {
send(methodName(), decision);
send(ProtocolMethod.nextGameDecision, decision);
}
@Override
@@ -102,31 +88,33 @@ public class NetGameController implements IGameController {
}
public String getActivateDescription(final CardView card) {
return sendAndWait(methodName(), card);
return sendAndWait(ProtocolMethod.getActivateDescription, card);
}
@Override
public void concede() {
send(methodName());
send(ProtocolMethod.concede);
}
@Override
public IDevModeCheats cheat() {
// No cheating in network games!
return IDevModeCheats.NO_CHEAT;
}
@Override
public boolean canPlayUnlimitedLands() {
// Don't do this over network
return false;
}
@Override
public void alphaStrike() {
send(methodName());
send(ProtocolMethod.alphaStrike);
}
@Override
public void reorderHand(CardView card, int index) {
send(methodName(), card, index);
send(ProtocolMethod.reorderHand, card, Integer.valueOf(index));
}
}

View File

@@ -1,5 +1,6 @@
package forge.net.event;
import forge.net.GameProtocol.ProtocolMethod;
import forge.net.server.RemoteClient;
public final class GuiGameEvent implements IdentifiableNetEvent {
@@ -7,10 +8,10 @@ public final class GuiGameEvent implements IdentifiableNetEvent {
private static int staticId = 0;
private final int id;
private final String method;
private final ProtocolMethod method;
private final Object[] objects;
public GuiGameEvent(final String method, final Object ... objects) {
public GuiGameEvent(final ProtocolMethod method, final Object ... objects) {
this.id = staticId++;
this.method = method;
this.objects = objects == null ? new Object[0] : objects;
@@ -30,7 +31,7 @@ public final class GuiGameEvent implements IdentifiableNetEvent {
return id;
}
public String getMethod() {
public ProtocolMethod getMethod() {
return method;
}

View File

@@ -1,28 +1,19 @@
package forge.net.server;
import forge.FThreads;
import forge.GuiBase;
import forge.LobbyPlayer;
import forge.game.GameView;
import forge.game.card.CardView;
import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbilityView;
import forge.interfaces.IGameController;
import forge.interfaces.IGuiGame;
import forge.interfaces.ILobbyListener;
import forge.match.LobbySlot;
import forge.match.LobbySlotType;
import forge.match.NextGameDecision;
import forge.net.event.GuiGameEvent;
import forge.net.event.LobbyUpdateEvent;
import forge.net.event.LoginEvent;
import forge.net.event.LogoutEvent;
import forge.net.event.MessageEvent;
import forge.net.event.NetEvent;
import forge.net.event.ReplyEvent;
import forge.net.event.UpdateLobbyPlayerEvent;
import forge.properties.ForgeConstants;
import forge.util.ITriggerEvent;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
@@ -40,12 +31,10 @@ import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.log4j.PropertyConfigurator;
@@ -79,6 +68,16 @@ public final class FServerManager {
private FServerManager() {
}
RemoteClient getClient(final Channel ch) {
return clients.get(ch);
}
GameView getGameView() {
return localLobby.getGameView();
}
IGameController getController(final int index) {
return localLobby.getController(index);
}
/**
* Get the singleton instance of {@link FServerManager}.
*
@@ -126,7 +125,7 @@ public final class FServerManager {
}
}).start();
//mapNatPort(port);
mapNatPort(port);
Runtime.getRuntime().addShutdownHook(shutdownHook);
isHosting = true;
} catch (final InterruptedException e) {
@@ -229,197 +228,6 @@ public final class FServerManager {
}
}
private class GameServerHandler extends ChannelInboundHandlerAdapter {
@SuppressWarnings("unchecked")
@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
System.out.println("Server received: " + msg);
final RemoteClient client = clients.get(ctx.channel());
if (msg instanceof ReplyEvent) {
client.setReply(((ReplyEvent) msg).getIndex(), ((ReplyEvent) msg).getReply());
} else if (msg instanceof GuiGameEvent) {
final GuiGameEvent event = (GuiGameEvent) msg;
final GameView gameView = localLobby.getGameView();
final IGameController controller = localLobby.getController(client.getIndex());
final Object[] args = event.getObjects();
FThreads.invokeInBackgroundThread(new Runnable() {
@Override public final void run() {
Serializable reply = null;
boolean doReply = false;
switch (event.getMethod()) {
// From GameController
case "useMana":
controller.useMana((byte) args[0]);
break;
case "tryUndoLastAction":
reply = controller.tryUndoLastAction();
doReply = true;
break;
case "selectPlayer":
controller.selectPlayer((PlayerView) args[0], (ITriggerEvent) args[1]);
break;
case "selectCard":
reply = controller.selectCard((CardView) args[0], (List<CardView>) args[1], (ITriggerEvent) args[2]);
doReply = true;
break;
case "selectButtonOk":
controller.selectButtonOk();
break;
case "selectButtonCancel":
controller.selectButtonCancel();
break;
case "selectAbility":
controller.selectAbility((SpellAbilityView) args[0]);
break;
case "passPriorityUntilEndOfTurn":
reply = controller.passPriorityUntilEndOfTurn();
doReply = true;
break;
case "passPriority":
reply = controller.passPriority();
doReply = true;
break;
case "nextGameDecision":
controller.nextGameDecision((NextGameDecision) args[0]);
break;
case "mayLookAtAllCards":
reply = controller.mayLookAtAllCards();
doReply = true;
break;
case "getActivateDescription":
reply = controller.getActivateDescription((CardView) args[0]);
doReply = true;
break;
case "concede":
controller.concede();
break;
case "alphaStrike":
controller.alphaStrike();
break;
// From GameView
case "getPlayers":
reply = (Serializable) gameView.getPlayers();
doReply = true;
break;
case "getTitle":
reply = gameView.getTitle();
doReply = true;
break;
case "isCommander":
reply = gameView.isCommander();
doReply = true;
break;
case "getGameType":
reply = gameView.getGameType();
doReply = true;
break;
case "getPoisonCountersToLose":
reply = gameView.getPoisonCountersToLose();
doReply = true;
break;
case "getNumGamesInMatch":
reply = gameView.getNumGamesInMatch();
doReply = true;
break;
case "getTurn":
reply = gameView.getTurn();
doReply = true;
break;
case "getPhase":
reply = gameView.getPhase();
doReply = true;
break;
case "getPlayerTurn":
reply = gameView.getPlayerTurn();
doReply = true;
break;
case "getStack":
reply = (Serializable) gameView.getStack();
doReply = true;
break;
case "peekStack":
reply = gameView.peekStack();
doReply = true;
break;
case "getStormCount":
reply = gameView.getStormCount();
doReply = true;
break;
case "isFirstGameInMatch":
reply = gameView.isFirstGameInMatch();
doReply = true;
break;
case "getNumPlayedGamesInMatch":
reply = gameView.getNumPlayedGamesInMatch();
doReply = true;
break;
case "isGameOver":
reply = gameView.isGameOver();
doReply = true;
break;
case "isMatchOver":
reply = gameView.isMatchOver();
doReply = true;
break;
case "getWinningTeam":
reply = gameView.getWinningTeam();
doReply = true;
break;
case "getGameLog":
reply = gameView.getGameLog();
doReply = true;
break;
case "getCombat":
reply = gameView.getCombat();
doReply = true;
break;
case "isMatchWonBy":
reply = gameView.isMatchWonBy((LobbyPlayer) args[0]);
doReply = true;
break;
case "getOutcomesOfMatch":
reply = (Serializable) gameView.getOutcomesOfMatch();
doReply = true;
break;
// TODO case "getWinningPlayer":
case "isWinner":
reply = gameView.isWinner((LobbyPlayer) args[0]);
doReply = true;
break;
case "getGamesWonBy":
reply = gameView.getGamesWonBy((LobbyPlayer) args[0]);
doReply = true;
break;
case "getDeck":
reply = gameView.getDeck((String) args[0]);
doReply = true;
break;
case "getAnteResult":
reply = gameView.getAnteResult((PlayerView) args[0]);
doReply = true;
break;
default:
System.err.println(String.format("Unknown incoming client command %s", event.getMethod()));
break;
}
if (doReply) {
client.send(new ReplyEvent(event.getId(), reply));
}
}
});
}
}
@Override
public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
private class RegisterClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {

View File

@@ -0,0 +1,41 @@
package forge.net.server;
import io.netty.channel.ChannelHandlerContext;
import forge.interfaces.IGameController;
import forge.net.GameProtocol.ProtocolMethod;
import forge.net.GameProtocolHandler;
import forge.net.IRemote;
import forge.net.ReplyPool;
final class GameServerHandler extends GameProtocolHandler<IGameController> {
private final FServerManager server = FServerManager.getInstance();
GameServerHandler() {
super(false);
}
private RemoteClient getClient(final ChannelHandlerContext ctx) {
return server.getClient(ctx.channel());
}
@Override
protected ReplyPool getReplyPool(final ChannelHandlerContext ctx) {
return getClient(ctx).getReplyPool();
}
@Override
protected IRemote getRemote(final ChannelHandlerContext ctx) {
return getClient(ctx);
}
@Override
protected IGameController getToInvoke(final ChannelHandlerContext ctx) {
return server.getController(getClient(ctx).getIndex());
}
@Override
protected void beforeCall(final ProtocolMethod protocolMethod, final Object[] args) {
// Nothing needs to be done here
}
}

View File

@@ -1,16 +1,12 @@
package forge.net.server;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import com.google.common.base.Function;
import com.google.common.collect.Maps;
import forge.LobbyPlayer;
import forge.UiCommand;
import forge.assets.FSkinProp;
import forge.deck.CardPool;
import forge.game.GameEntityView;
@@ -22,54 +18,31 @@ import forge.game.player.IHasIcon;
import forge.game.player.PlayerView;
import forge.game.spellability.SpellAbilityView;
import forge.game.zone.ZoneType;
import forge.interfaces.IButton;
import forge.item.PaperCard;
import forge.match.AbstractGuiGame;
import forge.match.MatchButtonType;
import forge.net.event.GuiGameEvent;
import forge.net.GameProtocol.ProtocolMethod;
import forge.net.GameProtocolSender;
import forge.player.PlayerZoneUpdate;
import forge.trackable.TrackableCollection;
import forge.util.ITriggerEvent;
public class NetGuiGame extends AbstractGuiGame {
private final IToClient client;
private final Map<MatchButtonType, Map<PlayerView, NetButton>> btns = new EnumMap<MatchButtonType, Map<PlayerView,NetButton>>(MatchButtonType.class);
private final GameProtocolSender sender;
public NetGuiGame(final IToClient client) {
this.client = client;
for (final MatchButtonType type : MatchButtonType.values()) {
btns.put(type, Maps.<PlayerView, NetButton>newHashMap());
}
this.sender = new GameProtocolSender(client);
}
private String methodName() {
boolean passedFirst = false;
for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) {
if (ste.getClassName() == getClass().getName()) {
if (passedFirst) {
return ste.getMethodName();
}
passedFirst = true;
}
}
return null;
private void send(final ProtocolMethod method, final Object... args) {
sender.send(method, args);
}
private void send(final String method, final Object... args) {
client.send(new GuiGameEvent(method, args));
}
@SuppressWarnings("unchecked")
private <T> T sendAndWait(final String method, final Object... args) {
try {
return (T) client.sendAndWait(new GuiGameEvent(method, args));
} catch (final TimeoutException e) {
e.printStackTrace();
}
return null;
private <T> T sendAndWait(final ProtocolMethod method, final Object... args) {
return sender.sendAndWait(method, args);
}
private void updateGameView() {
send("setGameView", getGameView());
send(ProtocolMethod.setGameView, getGameView());
}
@Override
@@ -80,227 +53,206 @@ public class NetGuiGame extends AbstractGuiGame {
@Override
public void openView(final TrackableCollection<PlayerView> myPlayers) {
for (final MatchButtonType type : MatchButtonType.values()) {
btns.get(type).clear();
for (final PlayerView player : myPlayers) {
btns.get(type).put(player, new NetButton(player, type));
}
}
send(methodName(), myPlayers);
send(ProtocolMethod.openView, myPlayers);
updateGameView();
}
@Override
public void afterGameEnd() {
send(methodName());
send(ProtocolMethod.afterGameEnd);
}
@Override
public void showCombat() {
send(methodName());
send(ProtocolMethod.showCombat);
}
@Override
public void showPromptMessage(final PlayerView playerView, final String message) {
updateGameView();
send(methodName(), playerView, message);
send(ProtocolMethod.showPromptMessage, playerView, message);
}
@Override
public boolean stopAtPhase(final PlayerView playerTurn, final PhaseType phase) {
return sendAndWait(methodName(), playerTurn, phase);
}
@Override
public IButton getBtnOK(final PlayerView playerView) {
return btns.get(MatchButtonType.OK).get(playerView);
}
@Override
public IButton getBtnCancel(final PlayerView playerView) {
return btns.get(MatchButtonType.CANCEL).get(playerView);
}
@Override
public void focusButton(final MatchButtonType button) {
send(methodName(), button);
public void updateButtons(final PlayerView owner, final String label1, final String label2, final boolean enable1, final boolean enable2, final boolean focus1) {
send(ProtocolMethod.updateButtons, owner, label1, label2, enable1, enable2, focus1);
}
@Override
public void flashIncorrectAction() {
send(methodName());
send(ProtocolMethod.flashIncorrectAction);
}
@Override
public void updatePhase() {
updateGameView();
send(methodName());
send(ProtocolMethod.updatePhase);
}
@Override
public void updateTurn(final PlayerView player) {
updateGameView();
send(methodName(), player);
send(ProtocolMethod.updateTurn, player);
}
@Override
public void updatePlayerControl() {
updateGameView();
send(methodName());
send(ProtocolMethod.updatePlayerControl);
}
@Override
public void enableOverlay() {
send(methodName());
send(ProtocolMethod.enableOverlay);
}
@Override
public void disableOverlay() {
send(methodName());
send(ProtocolMethod.disableOverlay);
}
@Override
public void finishGame() {
send(methodName());
send(ProtocolMethod.finishGame);
}
@Override
public Object showManaPool(final PlayerView player) {
send(methodName(), player);
send(ProtocolMethod.showManaPool, player);
return null;
}
@Override
public void hideManaPool(final PlayerView player, final Object zoneToRestore) {
send(methodName(), player, zoneToRestore);
send(ProtocolMethod.hideManaPool, player, zoneToRestore);
}
@Override
public void updateStack() {
updateGameView();
send(methodName());
send(ProtocolMethod.updateStack);
}
@Override
public void updateZones(final Iterable<PlayerZoneUpdate> zonesToUpdate) {
updateGameView();
send(methodName(), zonesToUpdate);
send(ProtocolMethod.updateZones, zonesToUpdate);
}
@Override
public void updateCards(final Iterable<CardView> cards) {
updateGameView();
send(methodName(), cards);
send(ProtocolMethod.updateCards, cards);
}
@Override
public void updateManaPool(final Iterable<PlayerView> manaPoolUpdate) {
updateGameView();
send(methodName(), manaPoolUpdate);
send(ProtocolMethod.updateManaPool, manaPoolUpdate);
}
@Override
public void updateLives(final Iterable<PlayerView> livesUpdate) {
updateGameView();
send(methodName(), livesUpdate);
send(ProtocolMethod.updateLives, livesUpdate);
}
@Override
public void setPanelSelection(final CardView hostCard) {
updateGameView();
send(methodName(), hostCard);
send(ProtocolMethod.setPanelSelection, hostCard);
}
@Override
public SpellAbilityView getAbilityToPlay(final List<SpellAbilityView> abilities, final ITriggerEvent triggerEvent) {
return sendAndWait(methodName(), abilities, triggerEvent);
public SpellAbilityView getAbilityToPlay(final CardView hostCard, final List<SpellAbilityView> abilities, final ITriggerEvent triggerEvent) {
return sendAndWait(ProtocolMethod.getAbilityToPlay, hostCard, abilities, triggerEvent);
}
@Override
public Map<CardView, Integer> assignDamage(final CardView attacker, final List<CardView> blockers, final int damage, final GameEntityView defender, final boolean overrideOrder) {
return sendAndWait(methodName(), attacker, blockers, damage, defender, overrideOrder);
return sendAndWait(ProtocolMethod.assignDamage, attacker, blockers, damage, defender, overrideOrder);
}
@Override
public void message(final String message, final String title) {
send(methodName(), message, title);
send(ProtocolMethod.message, message, title);
}
@Override
public void showErrorDialog(final String message, final String title) {
send(methodName(), message, title);
send(ProtocolMethod.showErrorDialog, message, title);
}
@Override
public boolean showConfirmDialog(final String message, final String title, final String yesButtonText, final String noButtonText, final boolean defaultYes) {
return sendAndWait(methodName(), message, title, yesButtonText, noButtonText, defaultYes);
return sendAndWait(ProtocolMethod.showConfirmDialog, message, title, yesButtonText, noButtonText, defaultYes);
}
@Override
public int showOptionDialog(final String message, final String title, final FSkinProp icon, final String[] options, final int defaultOption) {
return sendAndWait(methodName(), message, title, icon, options, defaultOption);
return sendAndWait(ProtocolMethod.showOptionDialog, message, title, icon, options, defaultOption);
}
@Override
public int showCardOptionDialog(final CardView card, final String message, final String title, final FSkinProp icon, final String[] options, final int defaultOption) {
return sendAndWait(methodName(), card, message, title, icon, options, defaultOption);
return sendAndWait(ProtocolMethod.showCardOptionDialog, card, message, title, icon, options, defaultOption);
}
@Override
public String showInputDialog(final String message, final String title, final FSkinProp icon, final String initialInput, final String[] inputOptions) {
return sendAndWait(methodName(), message, title, icon, initialInput, inputOptions);
return sendAndWait(ProtocolMethod.showInputDialog, message, title, icon, initialInput, inputOptions);
}
@Override
public boolean confirm(final CardView c, final String question, final boolean defaultIsYes, final String[] options) {
return sendAndWait(methodName(), c, question, defaultIsYes, options);
return sendAndWait(ProtocolMethod.confirm, c, question, defaultIsYes, options);
}
@Override
public <T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display) {
return sendAndWait(methodName(), message, min, max, choices, selected, display);
return sendAndWait(ProtocolMethod.getChoices, message, min, max, choices, selected, display);
}
@Override
public <T> List<T> order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax, final List<T> sourceChoices, final List<T> destChoices, final CardView referenceCard, final boolean sideboardingMode) {
return sendAndWait(methodName(), title, top, remainingObjectsMin, remainingObjectsMax, sourceChoices, destChoices, referenceCard, sideboardingMode);
return sendAndWait(ProtocolMethod.order, title, top, remainingObjectsMin, remainingObjectsMax, sourceChoices, destChoices, referenceCard, sideboardingMode);
}
@Override
public List<PaperCard> sideboard(final CardPool sideboard, final CardPool main) {
return sendAndWait(methodName(), sideboard, main);
return sendAndWait(ProtocolMethod.sideboard, sideboard, main);
}
@Override
public GameEntityView chooseSingleEntityForEffect(final String title, final Collection<? extends GameEntityView> optionList, final DelayedReveal delayedReveal, final boolean isOptional) {
return sendAndWait(methodName(), title, optionList, delayedReveal, isOptional);
return sendAndWait(ProtocolMethod.chooseSingleEntityForEffect, title, optionList, delayedReveal, isOptional);
}
@Override
public void setCard(final CardView card) {
updateGameView();
send(methodName(), card);
send(ProtocolMethod.setCard, card);
}
@Override
public void setPlayerAvatar(LobbyPlayer player, IHasIcon ihi) {
public void setPlayerAvatar(final LobbyPlayer player, final IHasIcon ihi) {
// TODO Auto-generated method stub
}
@Override
public boolean openZones(final Collection<ZoneType> zones, final Map<PlayerView, Object> players) {
updateGameView();
return sendAndWait(methodName(), zones, players);
return sendAndWait(ProtocolMethod.openZones, zones, players);
}
@Override
public void restoreOldZones(final Map<PlayerView, Object> playersToRestoreZonesFor) {
send(methodName(), playersToRestoreZonesFor);
send(ProtocolMethod.restoreOldZones, playersToRestoreZonesFor);
}
@Override
public boolean isUiSetToSkipPhase(final PlayerView playerTurn, final PhaseType phase) {
return sendAndWait(methodName(), playerTurn, phase);
return sendAndWait(ProtocolMethod.isUiSetToSkipPhase, playerTurn, phase);
}
@Override
@@ -308,71 +260,4 @@ public class NetGuiGame extends AbstractGuiGame {
// TODO Auto-generated method stub
}
private final class NetButton implements IButton {
private String methodName() {
boolean passedFirst = false;
for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) {
if (ste.getClassName() == getClass().getName()) {
if (passedFirst) {
return ste.getMethodName();
}
passedFirst = true;
}
}
return null;
}
private final PlayerView playerView;
private final MatchButtonType type;
private NetButton(final PlayerView playerView, final MatchButtonType type) {
this.playerView = playerView;
this.type = type;
}
@Override
public void setEnabled(final boolean b0) {
send("btn_" + methodName(), playerView, type, b0);
}
@Override
public void setVisible(final boolean b0) {
send("btn_" + methodName(), playerView, type, b0);
}
@Override
public void setText(final String text0) {
send("btn_" + methodName(), playerView, type, text0);
}
@Override
public boolean isSelected() {
return sendAndWait("btn_" + methodName(), playerView, type);
}
@Override
public void setSelected(final boolean b0) {
send("btn_" + methodName(), playerView, type, b0);
}
@Override
public boolean requestFocusInWindow() {
return sendAndWait("btn_" + methodName(), playerView, type);
}
@Override
public void setCommand(final UiCommand command0) {
send("btn_" + methodName(), playerView, type, command0);
}
@Override
public void setImage(final FSkinProp color) {
send("btn_" + methodName(), playerView, type, color);
}
@Override
public void setTextColor(final int r, final int g, final int b) {
send("btn_" + methodName(), playerView, type, r, g, b);
}
}
}

View File

@@ -46,7 +46,7 @@ public final class RemoteClient implements IToClient {
this.index = index;
}
public void setReply(final int id, final Object reply) {
replies.complete(id, reply);
ReplyPool getReplyPool() {
return replies;
}
}

View File

@@ -144,7 +144,7 @@ public class HumanPlay {
return original;
}
final List<SpellAbility> abilities = GameActionUtil.getOptionalCosts(original);
return p.getController().getAbilityToPlay(abilities);
return p.getController().getAbilityToPlay(original.getHostCard(), abilities);
}
private static boolean payManaCostIfNeeded(final PlayerControllerHuman controller, final Player p, final SpellAbility sa) {

View File

@@ -219,8 +219,8 @@ public class PlayerControllerHuman
/**
* Uses GUI to learn which spell the player (human in our case) would like to play
*/
public SpellAbility getAbilityToPlay(final List<SpellAbility> abilities, final ITriggerEvent triggerEvent) {
final SpellAbilityView resultView = getGui().getAbilityToPlay(SpellAbilityView.getCollection(abilities), triggerEvent);
public SpellAbility getAbilityToPlay(final Card hostCard, final List<SpellAbility> abilities, final ITriggerEvent triggerEvent) {
final SpellAbilityView resultView = getGui().getAbilityToPlay(CardView.get(hostCard), SpellAbilityView.getCollection(abilities), triggerEvent);
return getGame().getSpellAbility(resultView);
}
@@ -1304,6 +1304,12 @@ public class PlayerControllerHuman
return true;
}
@Override
public void undoLastAction() {
tryUndoLastAction();
}
public boolean tryUndoLastAction() {
if (!canUndoLastAction()) {
return false;
@@ -1333,37 +1339,33 @@ public class PlayerControllerHuman
}
}
public boolean passPriority() {
return passPriority(false);
public void passPriority() {
passPriority(false);
}
public boolean passPriorityUntilEndOfTurn() {
return passPriority(true);
public void passPriorityUntilEndOfTurn() {
passPriority(true);
}
private boolean passPriority(final boolean passUntilEndOfTurn) {
private void passPriority(final boolean passUntilEndOfTurn) {
final Input inp = inputProxy.getInput();
if (inp instanceof InputPassPriority) {
if (passUntilEndOfTurn) {
autoPassUntilEndOfTurn();
}
inp.selectButtonOK();
return true;
}
} else {
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public void run() {
@Override public final void run() {
getGui().message("Cannot pass priority at this time.");
}
});
return false;
}
}
public boolean useMana(final byte mana) {
public void useMana(final byte mana) {
final Input input = inputQueue.getInput();
if (input instanceof InputPayMana) {
return ((InputPayMana) input).useManaFromPool(mana);
((InputPayMana) input).useManaFromPool(mana);
}
return false;
}
public void selectPlayer(final PlayerView playerView, final ITriggerEvent triggerEvent) {

View File

@@ -1,6 +1,7 @@
package forge.player;
import java.io.Serializable;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
@@ -11,11 +12,21 @@ import forge.game.player.PlayerView;
public class PlayerZoneUpdates implements Iterable<PlayerZoneUpdate>, Serializable {
private static final long serialVersionUID = 7023549243041119023L;
private final Map<PlayerView, PlayerZoneUpdate> updates = Maps.newHashMap();
private final Map<PlayerView, PlayerZoneUpdate> updates = Collections.synchronizedMap(Maps.<PlayerView, PlayerZoneUpdate>newHashMap());
public PlayerZoneUpdates() {
}
/**
* Copy constructor.
*
* @param other
* the {@link PlayerZoneUpdates} to copy.
*/
public PlayerZoneUpdates(final PlayerZoneUpdates other) {
this.updates.putAll(other.updates);
}
@Override
public Iterator<PlayerZoneUpdate> iterator() {
return updates.values().iterator();