Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions forge-gui/src/main/java/forge/gamemodes/match/AbstractGuiGame.java
Original file line number Diff line number Diff line change
Expand Up @@ -352,12 +352,16 @@ public boolean concede() {
}
if (hasLocalPlayers()) {
boolean concedeNeeded = false;
// check if anyone still needs to confirm
// check if anyone still needs to concede
for (final IGameController c : getOriginalGameControllers()) {
if (c instanceof PlayerControllerHuman) {
if (((PlayerControllerHuman) c).getPlayer().getOutcome() == null) {
if (c instanceof PlayerControllerHuman pch) {
if (pch.getPlayer().getOutcome() == null) {
concedeNeeded = true;
}
} else {
// Network client — no access to Player outcome, but game
// is still in progress (isGameOver checked above)
concedeNeeded = true;
}
}
if (concedeNeeded) {
Expand All @@ -376,6 +380,11 @@ public boolean concede() {
} else {
return !ignoreConcedeChain;
}
if (isNetGame()) {
// Network: concede was sent to server asynchronously.
// Let the server drive game-end flow — don't send nextGameDecision here.
return false;
}
if (gameView.isGameOver()) {
// Don't immediately close, wait for win/lose screen
return false;
Expand Down
7 changes: 7 additions & 0 deletions forge-gui/src/main/java/forge/gamemodes/match/GameLobby.java
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ else if (autoGenerateVariant != null) {
//if above checks succeed, return runnable that can be used to finish starting game
return () -> {
hostedMatch = GuiBase.getInterface().hostMatch();
hostedMatch.setOnMatchOver(this::onMatchOver);
hostedMatch.startMatch(GameType.Constructed, variantTypes, players, guis);

for (final Player p : hostedMatch.getGame().getPlayers()) {
Expand All @@ -542,6 +543,12 @@ else if (autoGenerateVariant != null) {
};
}

protected void onMatchOver() {
hostedMatch = null;
gameControllers.clear();
updateView(true);
}

public final static class GameLobbyData implements Serializable {
private static final long serialVersionUID = 9184758307999646864L;

Expand Down
22 changes: 22 additions & 0 deletions forge-gui/src/main/java/forge/gamemodes/match/HostedMatch.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public class HostedMatch {
public HashMap<LobbySlot, IGameController> gameControllers = null;
private Runnable startGameHook = null;
private Runnable endGameHook = null;
private Runnable onMatchOver = null;
private final List<PlayerControllerHuman> humanControllers = Lists.newArrayList();
private Map<RegisteredPlayer, IGuiGame> guis;
private int humanCount;
Expand All @@ -81,6 +82,7 @@ public void setStartGameHook(Runnable hook) {
startGameHook = hook;
}
public void setEndGameHook(Runnable hook) { endGameHook = hook; }
public void setOnMatchOver(Runnable callback) { onMatchOver = callback; }

private static GameRules getDefaultRules(final GameType gameType) {
final GameRules gameRules = new GameRules(gameType);
Expand Down Expand Up @@ -303,6 +305,17 @@ public void startGame() {
endGameHook.run();
}

// Flush any buffered game events to remote clients so they receive
// GameEventGameOutcome and GameEventGameFinished before we proceed.
for (PlayerControllerHuman hc : humanControllers) {
if (hc.getGui() instanceof forge.gamemodes.net.server.RemoteClientGuiGame ngg) {
forge.gui.control.GameEventForwarder fwd = ngg.getForwarder();
if (fwd != null) {
fwd.flush();
}
}
}

// After game is over...
isMatchOver = match.isMatchOver();
if (humanCount == 0) {
Expand Down Expand Up @@ -366,6 +379,12 @@ public void endCurrentGame() {

for (final PlayerControllerHuman humanController : humanControllers) {
if (humanController.getGui() instanceof forge.gamemodes.net.server.RemoteClientGuiGame ngg) {
forge.gui.control.GameEventForwarder fwd = ngg.getForwarder();
if (fwd != null) {
for (PlayerControllerHuman allHc : humanControllers) {
allHc.getInputQueue().deleteObserver(fwd);
}
}
ngg.shutdownForwarder();
Comment thread
tool4ever marked this conversation as resolved.
}
humanController.getGui().setGameSpeed(PlaybackSpeed.NORMAL);
Expand Down Expand Up @@ -520,6 +539,9 @@ private void addNextGameDecision(final PlayerControllerHuman controller, final N
FThreads.invokeInEdtNowOrLater(() -> {
endCurrentGame();
isMatchOver = true;
if (onMatchOver != null) {
onMatchOver.run();
}
});
return; // if any player chooses quit, quit the match
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ public final void channelRead(final ChannelHandlerContext ctx, final Object msg)
protocolMethod.checkArgs(args);

final Object toInvoke = getToInvoke(ctx);
if (toInvoke == null) {
netLog.info("Ignoring {} — controller no longer available (game ended)", methodName);
// For methods expecting a reply, send null so the client doesn't hang
final Class<?> earlyReturnType = protocolMethod.getReturnType();
if (!earlyReturnType.equals(Void.TYPE)) {
final IRemote remote = getRemote(ctx);
if (remote != null) {
remote.send(new ReplyEvent(event.getId(), null));
}
}
return;
}

// Pre-call actions (runs on IO thread — blocks all subsequent messages)
final long beforeCallStart = System.currentTimeMillis();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ protected IRemote getRemote(final ChannelHandlerContext ctx) {

@Override
protected IGameController getToInvoke(final ChannelHandlerContext ctx) {
return server.getController(getClient(ctx).getIndex());
final RemoteClient client = getClient(ctx);
return client != null ? server.getController(client.getIndex()) : null;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,17 @@ protected IGuiGame getGui(final int index) {
@Override
protected void onGameStarted() {
}

@Override
protected void onMatchOver() {
for (int i = 0; i < getNumberOfSlots(); i++) {
final LobbySlot slot = getSlot(i);
if (slot != null) {
slot.setIsReady(false);
}
}
super.onMatchOver();
FServerManager.getInstance().clearPlayerGuis();
FServerManager.getInstance().updateLobbyState();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3344,6 +3344,15 @@ public void concede() {
if (player != null) {
player.concede();
getGame().getAction().checkGameOverCondition();
if (getGame().isGameOver()) {
// Remote-client controllers on the server have no FControlGameEventHandler,
// so their input queues won't be released by the normal event path
for (Player p : getGame().getPlayers()) {
if (p.getController() instanceof PlayerControllerHuman pch) {
pch.getInputQueue().onGameOver(true);
Comment thread
tool4ever marked this conversation as resolved.
}
}
}
}
}

Expand Down
Loading