Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
13 changes: 13 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,18 @@ else if (autoGenerateVariant != null) {
};
}

protected void onMatchOver() {
hostedMatch = null;
gameControllers.clear();
for (int i = 0; i < getNumberOfSlots(); i++) {
final LobbySlot slot = getSlot(i);
if (slot != null) {
slot.setIsReady(false);
Comment thread
tool4ever marked this conversation as resolved.
Outdated
}
}
updateView(true);
}

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

Expand Down
28 changes: 28 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 @@ -364,6 +377,18 @@ public void endCurrentGame() {

game = null;

// Must precede shutdownForwarder — observer references become stale after null
for (PlayerControllerHuman hc : humanControllers) {
if (hc.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);
}
}
}
}

for (final PlayerControllerHuman humanController : humanControllers) {
if (humanController.getGui() instanceof forge.gamemodes.net.server.RemoteClientGuiGame ngg) {
ngg.shutdownForwarder();
Comment thread
tool4ever marked this conversation as resolved.
Expand Down Expand Up @@ -520,6 +545,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,11 @@ protected IGuiGame getGui(final int index) {
@Override
protected void onGameStarted() {
}

@Override
protected void onMatchOver() {
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()) {
// Release all input latches — the game thread may be blocked
// on a different player's input queue than the one who conceded
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