- 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

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,93 +20,43 @@ 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
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
protected void beforeCall(final ProtocolMethod protocolMethod, final Object[] args) {
switch (protocolMethod) {
case openView:
final TrackableCollection<PlayerView> myPlayers = (TrackableCollection<PlayerView>) args[0];
client.setGameControllers(myPlayers);
break;
default:
break;
}
}
@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])));
}
@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
switch (protocolMethod) {
case openView:
final TrackableCollection<PlayerView> myPlayers = (TrackableCollection<PlayerView>) args[0];
client.setGameControllers(myPlayers);
break;
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();
}
}

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 final void run() {
getGui().message("Cannot pass priority at this time.");
}
});
}
FThreads.invokeInEdtNowOrLater(new Runnable() {
@Override
public 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();