mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
Merge branch 'multiplayer' into 'master'
Multiplayer See merge request core-developers/forge!207
This commit is contained in:
@@ -373,7 +373,14 @@ public class PlayerView extends GameEntityView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getMana(final byte color) {
|
public int getMana(final byte color) {
|
||||||
Integer count = getMana().get(color);
|
Integer count = null;
|
||||||
|
try {
|
||||||
|
count = getMana().get(color);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
count = null;
|
||||||
|
}
|
||||||
return count != null ? count.intValue() : 0;
|
return count != null ? count.intValue() : 0;
|
||||||
}
|
}
|
||||||
private Map<Byte, Integer> getMana() {
|
private Map<Byte, Integer> getMana() {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public abstract class TrackableObject implements IIdentifiable, Serializable {
|
|||||||
private static final long serialVersionUID = 7386836745378571056L;
|
private static final long serialVersionUID = 7386836745378571056L;
|
||||||
|
|
||||||
private final int id;
|
private final int id;
|
||||||
protected final transient Tracker tracker;
|
protected transient Tracker tracker;
|
||||||
private final Map<TrackableProperty, Object> props;
|
private final Map<TrackableProperty, Object> props;
|
||||||
private final Set<TrackableProperty> changedProps;
|
private final Set<TrackableProperty> changedProps;
|
||||||
private boolean copyingProps;
|
private boolean copyingProps;
|
||||||
@@ -30,6 +30,11 @@ public abstract class TrackableObject implements IIdentifiable, Serializable {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// needed for multiplayer support
|
||||||
|
public void setTracker(Tracker tracker) {
|
||||||
|
this.tracker = tracker;
|
||||||
|
}
|
||||||
|
|
||||||
public final Tracker getTracker() {
|
public final Tracker getTracker() {
|
||||||
return tracker;
|
return tracker;
|
||||||
}
|
}
|
||||||
@@ -45,6 +50,11 @@ public abstract class TrackableObject implements IIdentifiable, Serializable {
|
|||||||
return o.hashCode() == id && o.getClass().equals(getClass());
|
return o.hashCode() == id && o.getClass().equals(getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// don't know if this is really needed, but don't know a better way
|
||||||
|
public <T> T getProps() {
|
||||||
|
return (T)props;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected final <T> T get(final TrackableProperty key) {
|
protected final <T> T get(final TrackableProperty key) {
|
||||||
T value = (T)props.get(key);
|
T value = (T)props.get(key);
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ public class PlayerPanel extends FPanel {
|
|||||||
txtPlayerName.setEnabled(mayEdit);
|
txtPlayerName.setEnabled(mayEdit);
|
||||||
txtPlayerName.setText(type == LobbySlotType.OPEN ? StringUtils.EMPTY : playerName);
|
txtPlayerName.setText(type == LobbySlotType.OPEN ? StringUtils.EMPTY : playerName);
|
||||||
nameRandomiser.setEnabled(mayEdit);
|
nameRandomiser.setEnabled(mayEdit);
|
||||||
|
teamComboBox.setEnabled(mayEdit);
|
||||||
deckLabel.setVisible(mayEdit);
|
deckLabel.setVisible(mayEdit);
|
||||||
deckBtn.setVisible(mayEdit);
|
deckBtn.setVisible(mayEdit);
|
||||||
chkReady.setVisible(type == LobbySlotType.LOCAL || type == LobbySlotType.REMOTE);
|
chkReady.setVisible(type == LobbySlotType.LOCAL || type == LobbySlotType.REMOTE);
|
||||||
|
|||||||
@@ -86,9 +86,11 @@ public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
SOverlayUtils.hideOverlay();
|
SOverlayUtils.hideOverlay();
|
||||||
FNetOverlay.SINGLETON_INSTANCE.show(result);
|
if (result instanceof ChatMessage) {
|
||||||
if (CHomeUI.SINGLETON_INSTANCE.getCurrentDocID() == EDocID.HOME_NETWORK) {
|
FNetOverlay.SINGLETON_INSTANCE.show(result);
|
||||||
VSubmenuOnlineLobby.SINGLETON_INSTANCE.populate();
|
if (CHomeUI.SINGLETON_INSTANCE.getCurrentDocID() == EDocID.HOME_NETWORK) {
|
||||||
|
VSubmenuOnlineLobby.SINGLETON_INSTANCE.populate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import forge.util.ITriggerEvent;
|
|||||||
|
|
||||||
public interface IGuiGame {
|
public interface IGuiGame {
|
||||||
void setGameView(GameView gameView);
|
void setGameView(GameView gameView);
|
||||||
|
GameView getGameView();
|
||||||
void setOriginalGameController(PlayerView view, IGameController gameController);
|
void setOriginalGameController(PlayerView view, IGameController gameController);
|
||||||
void setGameController(PlayerView player, IGameController gameController);
|
void setGameController(PlayerView player, IGameController gameController);
|
||||||
void setSpectator(IGameController spectator);
|
void setSpectator(IGameController spectator);
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package forge.interfaces;
|
package forge.interfaces;
|
||||||
|
|
||||||
import forge.match.GameLobby.GameLobbyData;
|
import forge.match.GameLobby.GameLobbyData;
|
||||||
|
import forge.net.client.ClientGameLobby;
|
||||||
|
|
||||||
public interface ILobbyListener {
|
public interface ILobbyListener {
|
||||||
void message(String source, String message);
|
void message(String source, String message);
|
||||||
void update(GameLobbyData state, int slot);
|
void update(GameLobbyData state, int slot);
|
||||||
void close();
|
void close();
|
||||||
|
ClientGameLobby getLobby();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package forge.net;
|
package forge.net;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import forge.GuiBase;
|
import forge.GuiBase;
|
||||||
@@ -75,6 +76,10 @@ public class NetConnectUtil {
|
|||||||
public final void close() {
|
public final void close() {
|
||||||
// NO-OP, server can't receive close message
|
// NO-OP, server can't receive close message
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public ClientGameLobby getLobby() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
chatInterface.setGameClient(new IRemote() {
|
chatInterface.setGameClient(new IRemote() {
|
||||||
@Override
|
@Override
|
||||||
@@ -124,9 +129,13 @@ public class NetConnectUtil {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public final void close() {
|
public final void close() {
|
||||||
SOptionPane.showMessageDialog("Connection to the host was interrupted.", "Error", FSkinProp.ICO_WARNING);
|
SOptionPane.showMessageDialog("Your connection to the host (" + url + ") was interrupted.", "Error", FSkinProp.ICO_WARNING);
|
||||||
onlineLobby.setClient(null);
|
onlineLobby.setClient(null);
|
||||||
}
|
}
|
||||||
|
@Override
|
||||||
|
public ClientGameLobby getLobby() {
|
||||||
|
return lobby;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
view.setPlayerChangeListener(new IPlayerChangeListener() {
|
view.setPlayerChangeListener(new IPlayerChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -149,7 +158,12 @@ public class NetConnectUtil {
|
|||||||
catch (Exception ex) {}
|
catch (Exception ex) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
client.connect(hostname, port);
|
try {
|
||||||
|
client.connect(hostname, port);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return new ChatMessage(null, String.format("Connected to %s:%d", hostname, port));
|
return new ChatMessage(null, String.format("Connected to %s:%d", hostname, port));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ public class FGameClient implements IToServer {
|
|||||||
return replies.get(event.getId());
|
return replies.get(event.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<ILobbyListener> getLobbyListeners() {
|
||||||
|
return lobbyListeners;
|
||||||
|
}
|
||||||
|
|
||||||
public void addLobbyListener(final ILobbyListener listener) {
|
public void addLobbyListener(final ILobbyListener listener) {
|
||||||
lobbyListeners.add(listener);
|
lobbyListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
package forge.net.client;
|
package forge.net.client;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import forge.LobbyPlayer;
|
||||||
|
import forge.game.*;
|
||||||
|
import forge.game.player.RegisteredPlayer;
|
||||||
|
import forge.interfaces.ILobbyListener;
|
||||||
|
import forge.match.LobbySlot;
|
||||||
|
import forge.player.LobbyPlayerHuman;
|
||||||
|
import forge.trackable.TrackableObject;
|
||||||
|
import forge.trackable.Tracker;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import forge.game.player.PlayerView;
|
import forge.game.player.PlayerView;
|
||||||
import forge.interfaces.IGuiGame;
|
import forge.interfaces.IGuiGame;
|
||||||
@@ -12,9 +21,12 @@ import forge.net.event.LoginEvent;
|
|||||||
import forge.properties.ForgePreferences.FPref;
|
import forge.properties.ForgePreferences.FPref;
|
||||||
import forge.trackable.TrackableCollection;
|
import forge.trackable.TrackableCollection;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
final class GameClientHandler extends GameProtocolHandler<IGuiGame> {
|
final class GameClientHandler extends GameProtocolHandler<IGuiGame> {
|
||||||
private final FGameClient client;
|
private final FGameClient client;
|
||||||
private final IGuiGame gui;
|
private final IGuiGame gui;
|
||||||
|
private Tracker tracker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a client-side game handler.
|
* Creates a client-side game handler.
|
||||||
@@ -23,6 +35,7 @@ final class GameClientHandler extends GameProtocolHandler<IGuiGame> {
|
|||||||
super(true);
|
super(true);
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.gui = client.getGui();
|
this.gui = client.getGui();
|
||||||
|
this.tracker = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -44,12 +57,169 @@ final class GameClientHandler extends GameProtocolHandler<IGuiGame> {
|
|||||||
@Override
|
@Override
|
||||||
protected void beforeCall(final ProtocolMethod protocolMethod, final Object[] args) {
|
protected void beforeCall(final ProtocolMethod protocolMethod, final Object[] args) {
|
||||||
switch (protocolMethod) {
|
switch (protocolMethod) {
|
||||||
case openView:
|
case openView:
|
||||||
final TrackableCollection<PlayerView> myPlayers = (TrackableCollection<PlayerView>) args[0];
|
if (this.tracker == null) {
|
||||||
client.setGameControllers(myPlayers);
|
int maxAttempts = 5;
|
||||||
break;
|
for (int numAttempts = 0; numAttempts < maxAttempts; numAttempts++) {
|
||||||
default:
|
try {
|
||||||
break;
|
|
||||||
|
this.tracker = createTracker();
|
||||||
|
|
||||||
|
for (PlayerView myPlayer : (TrackableCollection<PlayerView>) args[0]) {
|
||||||
|
if (myPlayer.getTracker() == null) {
|
||||||
|
myPlayer.setTracker(this.tracker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final TrackableCollection<PlayerView> myPlayers = (TrackableCollection<PlayerView>) args[0];
|
||||||
|
client.setGameControllers(myPlayers);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Failed: attempt number: " + numAttempts + " - " + e.toString());
|
||||||
|
try {
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException e1) {
|
||||||
|
e1.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!(this.tracker == null)) {
|
||||||
|
updateTrackers(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method attempts to recreate a <b>GameRules</b> object from existing state.
|
||||||
|
*
|
||||||
|
* @return GameRules
|
||||||
|
*/
|
||||||
|
private GameRules createGameRules(GameType gameType, GameView gameView) {
|
||||||
|
// FIXME: how do we know the rules are the same on each side???
|
||||||
|
GameRules gameRules = new GameRules(gameType);
|
||||||
|
gameRules.setGamesPerMatch(gameView.getNumGamesInMatch());
|
||||||
|
gameRules.setPoisonCountersToLose(gameView.getPoisonCountersToLose());
|
||||||
|
|
||||||
|
return gameRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method creates the necessary objects and state to retrieve a <b>Tracker</b> object.
|
||||||
|
*
|
||||||
|
* Near as I can tell, that means that we need to create a <b>Match</b>.
|
||||||
|
*
|
||||||
|
* Creating a <b>Match</b> requires that we have:
|
||||||
|
* * <b>GameRules</b>
|
||||||
|
* * <b>RegisteredPlayers</b>
|
||||||
|
* * Title
|
||||||
|
*
|
||||||
|
* @return Tracker
|
||||||
|
*/
|
||||||
|
private Tracker createTracker() {
|
||||||
|
|
||||||
|
// retrieve what we can from the existing (but incomplete) state
|
||||||
|
final IGuiGame gui = client.getGui();
|
||||||
|
GameView gameView = gui.getGameView();
|
||||||
|
|
||||||
|
final GameType gameType = gameView.getGameType();
|
||||||
|
final GameRules gameRules = createGameRules(gameType, gameView);
|
||||||
|
final List<RegisteredPlayer> registeredPlayers = createRegisteredPlayers(gameType);
|
||||||
|
|
||||||
|
// pull the title from the existing (incomplete) GameView
|
||||||
|
final String title = gameView.getTitle();
|
||||||
|
|
||||||
|
// create a valid match object and game
|
||||||
|
Match match = new Match(gameRules, registeredPlayers, title);
|
||||||
|
Game game = match.createGame();
|
||||||
|
|
||||||
|
// replace the existing incomplete GameView with the newly created one
|
||||||
|
gui.setGameView(null);
|
||||||
|
gui.setGameView(game.getView());
|
||||||
|
|
||||||
|
return gui.getGameView().getTracker();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method retrieves existing information about the players to
|
||||||
|
* build a list of <b>RegisteredPlayers</b>.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param gameType
|
||||||
|
* @return List<RegisteredPlayer>
|
||||||
|
*/
|
||||||
|
private List<RegisteredPlayer> createRegisteredPlayers(GameType gameType) {
|
||||||
|
// get all lobby players
|
||||||
|
List<ILobbyListener> lobbyListeners = client.getLobbyListeners();
|
||||||
|
ILobbyListener lobbyListener = lobbyListeners.get(0);
|
||||||
|
ClientGameLobby myLobby = lobbyListener.getLobby();
|
||||||
|
|
||||||
|
List<RegisteredPlayer> players = Lists.newArrayList();
|
||||||
|
int playerCount = myLobby.getNumberOfSlots();
|
||||||
|
for (int i = 0; i < playerCount; i++) {
|
||||||
|
LobbySlot playerSlot = myLobby.getSlot(i);
|
||||||
|
RegisteredPlayer player = RegisteredPlayer.forVariants(
|
||||||
|
playerCount,
|
||||||
|
Collections.singleton(gameType),
|
||||||
|
playerSlot.getDeck(),
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
LobbyPlayer lobbyPlayer = new LobbyPlayerHuman(
|
||||||
|
playerSlot.getName(),
|
||||||
|
playerSlot.getAvatarIndex()
|
||||||
|
);
|
||||||
|
player.setPlayer(lobbyPlayer);
|
||||||
|
player.setTeamNumber(playerSlot.getTeam());
|
||||||
|
players.add(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<RegisteredPlayer> sortedPlayers = Lists.newArrayList(players);
|
||||||
|
Collections.sort(sortedPlayers, new Comparator<RegisteredPlayer>() {
|
||||||
|
@Override
|
||||||
|
public final int compare(final RegisteredPlayer p1, final RegisteredPlayer p2) {
|
||||||
|
final int v1 = p1.getPlayer() instanceof LobbyPlayerHuman ? 0 : 1;
|
||||||
|
final int v2 = p2.getPlayer() instanceof LobbyPlayerHuman ? 0 : 1;
|
||||||
|
return Integer.compare(v1, v2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sortedPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is used to recursively update the <b>tracker</b>
|
||||||
|
* references on all objects and their props.
|
||||||
|
*
|
||||||
|
* @param objs
|
||||||
|
*/
|
||||||
|
private void updateTrackers(final Object[] objs) {
|
||||||
|
for (Object obj: objs) {
|
||||||
|
if (obj instanceof TrackableObject) {
|
||||||
|
TrackableObject trackableObject = ((TrackableObject) obj);
|
||||||
|
if (trackableObject.getTracker() == null) {
|
||||||
|
trackableObject.setTracker(this.tracker);
|
||||||
|
// walk the props
|
||||||
|
EnumMap props = (EnumMap) trackableObject.getProps();
|
||||||
|
if (!(props == null)) {
|
||||||
|
for (Object propObj : props.values()) {
|
||||||
|
updateTrackers(new Object[]{propObj});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (obj instanceof TrackableCollection) {
|
||||||
|
TrackableCollection collection = ((TrackableCollection) obj);
|
||||||
|
Iterator itrCollection = collection.iterator();
|
||||||
|
while (itrCollection.hasNext()) {
|
||||||
|
Object objCollection = itrCollection.next();
|
||||||
|
updateTrackers(new Object[]{objCollection});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,9 +30,16 @@ import io.netty.handler.codec.serialization.ObjectEncoder;
|
|||||||
import io.netty.handler.logging.LogLevel;
|
import io.netty.handler.logging.LogLevel;
|
||||||
import io.netty.handler.logging.LoggingHandler;
|
import io.netty.handler.logging.LoggingHandler;
|
||||||
|
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.Inet4Address;
|
||||||
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.log4j.PropertyConfigurator;
|
import org.apache.log4j.PropertyConfigurator;
|
||||||
@@ -198,9 +205,35 @@ public final class FServerManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inspired by:
|
||||||
|
// https://stackoverflow.com/a/34873630
|
||||||
|
// https://stackoverflow.com/a/901943
|
||||||
|
private String getRoutableAddress(boolean preferIpv4, boolean preferIPv6) throws SocketException, UnknownHostException {
|
||||||
|
DatagramSocket s = new DatagramSocket();
|
||||||
|
s.connect(InetAddress.getByAddress(new byte[]{8,8,8,8}), 0);
|
||||||
|
NetworkInterface n = NetworkInterface.getByInetAddress(s.getLocalAddress());
|
||||||
|
Enumeration en = n.getInetAddresses();
|
||||||
|
while (en.hasMoreElements()) {
|
||||||
|
InetAddress addr = (InetAddress) en.nextElement();
|
||||||
|
if (addr instanceof Inet4Address) {
|
||||||
|
if (preferIPv6) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return addr.getHostAddress();
|
||||||
|
}
|
||||||
|
if (addr instanceof Inet6Address) {
|
||||||
|
if (preferIpv4) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return addr.getHostAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public String getLocalAddress() {
|
public String getLocalAddress() {
|
||||||
try {
|
try {
|
||||||
return InetAddress.getLocalHost().getHostAddress();
|
return getRoutableAddress(true, false);
|
||||||
}
|
}
|
||||||
catch (final Exception e) {
|
catch (final Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
Reference in New Issue
Block a user