Skip to content

Commit 3ed08b9

Browse files
feat: rejoining
1 parent 548f95d commit 3ed08b9

11 files changed

Lines changed: 602 additions & 4 deletions

File tree

commons/src/main/java/net/swofty/commons/game/Game.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
@AllArgsConstructor
1717
public class Game {
1818
private List<UUID> involvedPlayers;
19+
private List<UUID> disconnectedPlayers;
1920
private UUID gameId;
2021
private ServerType type;
2122
private String map;
@@ -27,6 +28,10 @@ public JSONObject toJSON() {
2728
json.put("map", map);
2829
List<String> players = involvedPlayers.stream().map(UUID::toString).toList();
2930
json.put("players", players);
31+
List<String> disconnected = disconnectedPlayers != null
32+
? disconnectedPlayers.stream().map(UUID::toString).toList()
33+
: List.of();
34+
json.put("disconnectedPlayers", disconnected);
3035
return json;
3136
}
3237

@@ -36,6 +41,9 @@ public static Game fromJSON(JSONObject json) {
3641
game.type = ServerType.valueOf(json.getString("type"));
3742
game.map = json.getString("map");
3843
game.involvedPlayers = json.getJSONArray("players").toList().stream().map(obj -> UUID.fromString(obj.toString())).toList();
44+
game.disconnectedPlayers = json.has("disconnectedPlayers")
45+
? json.getJSONArray("disconnectedPlayers").toList().stream().map(obj -> UUID.fromString(obj.toString())).toList()
46+
: List.of();
3947
return game;
4048
}
4149
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package net.swofty.commons.protocol.objects.orchestrator;
2+
3+
import net.swofty.commons.UnderstandableProxyServer;
4+
import net.swofty.commons.protocol.ProtocolObject;
5+
import net.swofty.commons.protocol.Serializer;
6+
import org.jetbrains.annotations.Nullable;
7+
import org.json.JSONObject;
8+
9+
import java.util.UUID;
10+
11+
public class RejoinGameProtocolObject extends ProtocolObject<
12+
RejoinGameProtocolObject.RejoinGameRequest,
13+
RejoinGameProtocolObject.RejoinGameResponse> {
14+
15+
@Override
16+
public Serializer<RejoinGameRequest> getSerializer() {
17+
return new Serializer<>() {
18+
@Override
19+
public String serialize(RejoinGameRequest value) {
20+
JSONObject json = new JSONObject();
21+
json.put("playerUuid", value.playerUuid.toString());
22+
return json.toString();
23+
}
24+
25+
@Override
26+
public RejoinGameRequest deserialize(String json) {
27+
JSONObject obj = new JSONObject(json);
28+
return new RejoinGameRequest(UUID.fromString(obj.getString("playerUuid")));
29+
}
30+
31+
@Override
32+
public RejoinGameRequest clone(RejoinGameRequest value) {
33+
return new RejoinGameRequest(value.playerUuid);
34+
}
35+
};
36+
}
37+
38+
@Override
39+
public Serializer<RejoinGameResponse> getReturnSerializer() {
40+
return new Serializer<>() {
41+
@Override
42+
public String serialize(RejoinGameResponse value) {
43+
JSONObject json = new JSONObject();
44+
json.put("hasActiveGame", value.hasActiveGame);
45+
if (value.hasActiveGame && value.server != null) {
46+
json.put("server", value.server.toJSON());
47+
json.put("gameId", value.gameId);
48+
json.put("mapName", value.mapName);
49+
json.put("teamName", value.teamName != null ? value.teamName : JSONObject.NULL);
50+
json.put("willBeSpectator", value.willBeSpectator);
51+
} else {
52+
json.put("server", JSONObject.NULL);
53+
json.put("gameId", JSONObject.NULL);
54+
json.put("mapName", JSONObject.NULL);
55+
json.put("teamName", JSONObject.NULL);
56+
json.put("willBeSpectator", false);
57+
}
58+
return json.toString();
59+
}
60+
61+
@Override
62+
public RejoinGameResponse deserialize(String json) {
63+
JSONObject obj = new JSONObject(json);
64+
boolean hasActiveGame = obj.getBoolean("hasActiveGame");
65+
if (!hasActiveGame || obj.isNull("server")) {
66+
return new RejoinGameResponse(false, null, null, null, null, false);
67+
}
68+
return new RejoinGameResponse(
69+
true,
70+
UnderstandableProxyServer.singleFromJSON(obj.getJSONObject("server")),
71+
obj.getString("gameId"),
72+
obj.getString("mapName"),
73+
obj.isNull("teamName") ? null : obj.getString("teamName"),
74+
obj.getBoolean("willBeSpectator")
75+
);
76+
}
77+
78+
@Override
79+
public RejoinGameResponse clone(RejoinGameResponse value) {
80+
return new RejoinGameResponse(
81+
value.hasActiveGame,
82+
value.server,
83+
value.gameId,
84+
value.mapName,
85+
value.teamName,
86+
value.willBeSpectator
87+
);
88+
}
89+
};
90+
}
91+
92+
public record RejoinGameRequest(UUID playerUuid) {
93+
}
94+
95+
public record RejoinGameResponse(
96+
boolean hasActiveGame,
97+
@Nullable UnderstandableProxyServer server,
98+
@Nullable String gameId,
99+
@Nullable String mapName,
100+
@Nullable String teamName,
101+
boolean willBeSpectator
102+
) {
103+
}
104+
}

service.orchestrator/src/main/java/net/swofty/service/orchestrator/OrchestratorCache.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,27 @@ public static Set<String> getMaps(ServerType type, String gameTypeName) {
112112
return maps;
113113
}
114114

115+
/**
116+
* Find a game that a player is part of (either active or disconnected).
117+
* Used for the rejoin system.
118+
*/
119+
public static GameWithServer findPlayerGame(UUID playerUuid) {
120+
cleanup();
121+
122+
for (GameWithServer gameWithServer : gamesByGameId.values()) {
123+
Game game = gameWithServer.game();
124+
// Check active players
125+
if (game.getInvolvedPlayers().contains(playerUuid)) {
126+
return gameWithServer;
127+
}
128+
// Check disconnected players
129+
if (game.getDisconnectedPlayers() != null && game.getDisconnectedPlayers().contains(playerUuid)) {
130+
return gameWithServer;
131+
}
132+
}
133+
return null;
134+
}
135+
115136
private static boolean isGameJoinable(Game game, BedwarsGameType gameType) {
116137
int maxPlayersForType = gameType.maxPlayers();
117138
int currentPlayers = game.getInvolvedPlayers().size();
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package net.swofty.service.orchestrator.endpoints;
2+
3+
import net.swofty.commons.UnderstandableProxyServer;
4+
import net.swofty.commons.impl.ServiceProxyRequest;
5+
import net.swofty.commons.protocol.ProtocolObject;
6+
import net.swofty.commons.protocol.objects.orchestrator.RejoinGameProtocolObject;
7+
import net.swofty.service.generic.redis.ServiceEndpoint;
8+
import net.swofty.service.orchestrator.OrchestratorCache;
9+
10+
import java.util.ArrayList;
11+
12+
public class RejoinGameEndpoint implements ServiceEndpoint<
13+
RejoinGameProtocolObject.RejoinGameRequest,
14+
RejoinGameProtocolObject.RejoinGameResponse> {
15+
16+
@Override
17+
public ProtocolObject<RejoinGameProtocolObject.RejoinGameRequest, RejoinGameProtocolObject.RejoinGameResponse> associatedProtocolObject() {
18+
return new RejoinGameProtocolObject();
19+
}
20+
21+
@Override
22+
public RejoinGameProtocolObject.RejoinGameResponse onMessage(ServiceProxyRequest message,
23+
RejoinGameProtocolObject.RejoinGameRequest body) {
24+
try {
25+
// Find the game this player is part of (active or disconnected)
26+
OrchestratorCache.GameWithServer gameWithServer = OrchestratorCache.findPlayerGame(body.playerUuid());
27+
28+
if (gameWithServer == null) {
29+
return new RejoinGameProtocolObject.RejoinGameResponse(false, null, null, null, null, false);
30+
}
31+
32+
OrchestratorCache.GameServerState hostingServer = OrchestratorCache.getServerByUuid(gameWithServer.serverUuid());
33+
if (hostingServer == null) {
34+
return new RejoinGameProtocolObject.RejoinGameResponse(false, null, null, null, null, false);
35+
}
36+
37+
// Check if this player is in the disconnected list (meaning they should rejoin)
38+
// If they're in involvedPlayers, they're already in the game
39+
boolean isDisconnected = gameWithServer.game().getDisconnectedPlayers() != null
40+
&& gameWithServer.game().getDisconnectedPlayers().contains(body.playerUuid());
41+
42+
if (!isDisconnected) {
43+
// Player is already in an active game, not a rejoin scenario
44+
return new RejoinGameProtocolObject.RejoinGameResponse(false, null, null, null, null, false);
45+
}
46+
47+
UnderstandableProxyServer proxy = new UnderstandableProxyServer(
48+
hostingServer.shortName(),
49+
hostingServer.uuid(),
50+
hostingServer.type(),
51+
-1,
52+
new ArrayList<>(),
53+
hostingServer.maxPlayers(),
54+
hostingServer.shortName()
55+
);
56+
57+
return new RejoinGameProtocolObject.RejoinGameResponse(
58+
true,
59+
proxy,
60+
gameWithServer.game().getGameId().toString(),
61+
gameWithServer.game().getMap(),
62+
null, // Team name not available from commons Game, will be determined on game server
63+
false // Whether spectator determined on game server based on current bed status
64+
);
65+
} catch (Exception e) {
66+
System.err.println("Failed to check rejoin: " + e.getMessage());
67+
return new RejoinGameProtocolObject.RejoinGameResponse(false, null, null, null, null, false);
68+
}
69+
}
70+
}

type.bedwarsgame/src/main/java/net/swofty/type/bedwarsgame/TypeBedWarsGameLoader.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ public void afterInitialize(MinecraftServer server) {
244244
}
245245
commonsGame.setInvolvedPlayers(playerUuids);
246246

247+
// Add disconnected players for rejoin system
248+
commonsGame.setDisconnectedPlayers(internalGame.getDisconnectedPlayerUuids());
249+
247250
commonsGames.add(commonsGame);
248251
}
249252

type.bedwarsgame/src/main/java/net/swofty/type/bedwarsgame/events/ActionPlayerDisconnect.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import net.minestom.server.event.player.PlayerDisconnectEvent;
55
import net.swofty.type.bedwarsgame.TypeBedWarsGameLoader;
66
import net.swofty.type.bedwarsgame.game.Game;
7+
import net.swofty.type.bedwarsgame.game.GameStatus;
78
import net.swofty.type.bedwarsgame.user.BedWarsPlayer;
89
import net.swofty.type.generic.event.EventNodes;
910
import net.swofty.type.generic.event.HypixelEvent;
@@ -17,7 +18,13 @@ public void run(PlayerDisconnectEvent event) {
1718
final BedWarsPlayer player = (BedWarsPlayer) event.getPlayer();
1819
Game game = TypeBedWarsGameLoader.getPlayerGame(player);
1920
if (game != null) {
20-
game.leave(player);
21+
if (game.getGameStatus() == GameStatus.IN_PROGRESS) {
22+
// Use handleDisconnect for active games to enable rejoin
23+
game.handleDisconnect(player);
24+
} else {
25+
// Normal leave for waiting/ending games
26+
game.leave(player);
27+
}
2128
}
2229
}
2330
}

type.bedwarsgame/src/main/java/net/swofty/type/bedwarsgame/events/ActionPlayerJoin.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,14 @@ public void run(AsyncPlayerConfigurationEvent event) {
3838
Game preferred = TypeBedWarsGameLoader.getGameById(preferredGameId);
3939
if (preferred != null) {
4040
MathUtility.delay(
41-
() -> preferred.join(player),
41+
() -> {
42+
// Check if this is a rejoin (player was disconnected from this game)
43+
if (preferred.hasDisconnectedPlayer(player.getUuid())) {
44+
preferred.rejoin(player);
45+
} else {
46+
preferred.join(player);
47+
}
48+
},
4249
15
4350
);
4451
}

0 commit comments

Comments
 (0)