feat: Multiplayer (#2)

Make this game multiplayer.

Setup:
- `common` for common classes used both by the server and the client
- `game` the actual client
- `server` the server

Reviewed-on: https://gitea.local.jzitnik.dev/jzitnik/game/pulls/2
Co-authored-by: jzitnik-dev <email@jzitnik.dev>
Co-committed-by: jzitnik-dev <email@jzitnik.dev>
This commit was merged in pull request #2.
This commit is contained in:
2026-02-04 10:33:24 +00:00
committed by Jakub Žitník
parent b72ac87098
commit aec0e8e978
254 changed files with 3776 additions and 1197 deletions

39
server/.gitignore vendored Normal file
View File

@@ -0,0 +1,39 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
.kotlin
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

3
server/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

7
server/.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

14
server/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

6
server/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

110
server/pom.xml Normal file
View File

@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cz.jzitnik</groupId>
<artifactId>game-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>server</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</path>
</annotationProcessorPaths>
<source>25</source>
<target>25</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>cz.jzitnik.server.Main</mainClass>
<classpathScope>compile</classpathScope>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>cz.jzitnik</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-api</artifactId>
<version>2.3.0-M2</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-server</artifactId>
<version>2.2.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-container-grizzly-server</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.17</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.25</version>
</dependency>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,28 @@
package cz.jzitnik.server;
import cz.jzitnik.server.context.AppContext;
import cz.jzitnik.server.context.GlobalContext;
import cz.jzitnik.server.events.EventManager;
import cz.jzitnik.server.socket.WebSocket;
import jakarta.websocket.DeploymentException;
import org.glassfish.tyrus.server.Server;
import org.reflections.Reflections;
import java.util.*;
public class Main {
public static void main(String[] args) throws DeploymentException {
GlobalContext globalContext = new GlobalContext();
AppContext.set(globalContext);
EventManager eventManager = new EventManager(new Reflections("cz.jzitnik.server"), globalContext);
globalContext.setEventManager(eventManager);
eventManager.start();
Map<String, Object> properties = new HashMap<>();
Server server = new Server("localhost", 8025, "/", properties, WebSocket.class);
server.start();
new Scanner(System.in).nextLine();
}
}

View File

@@ -0,0 +1,14 @@
package cz.jzitnik.server.annotations;
import cz.jzitnik.common.socket.SocketMessage;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EventHandler {
Class<? extends SocketMessage> value();
}

View File

@@ -0,0 +1,13 @@
package cz.jzitnik.server.context;
public class AppContext {
private static GlobalContext globalContext;
public static void set(GlobalContext context) {
globalContext = context;
}
public static GlobalContext get() {
return globalContext;
}
}

View File

@@ -0,0 +1,4 @@
package cz.jzitnik.server.context;
public class GameManager {
}

View File

@@ -0,0 +1,54 @@
package cz.jzitnik.server.context;
import cz.jzitnik.server.game.Client;
import cz.jzitnik.server.events.EventManager;
import cz.jzitnik.server.game.Game;
import jakarta.websocket.Session;
import lombok.Getter;
import lombok.Setter;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
public class GlobalContext {
@Getter
private final HashMap<Session, Client> sessions = new HashMap<>();
@Getter
private final Set<Game> games = new HashSet<>();
@Getter
@Setter
private EventManager eventManager;
@Getter
private final Properties properties;
public void registerClient(Client client) {
sessions.put(client.getSession().getSession(), client);
}
public Optional<Game> getGame(String pass) {
return games.stream().filter(game -> game.getPassword().equals(pass)).findFirst();
}
public GlobalContext() {
Properties props = new Properties();
try (InputStream input = GlobalContext.class
.getClassLoader()
.getResourceAsStream("config.properties")) {
if (input == null) {
throw new RuntimeException("config.properties not found");
}
props.load(input);
this.properties = props;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,12 @@
package cz.jzitnik.server.events;
import cz.jzitnik.common.socket.SocketMessage;
import cz.jzitnik.server.context.GlobalContext;
import cz.jzitnik.server.game.Client;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public abstract class AbstractEventHandler<T extends SocketMessage> {
protected final GlobalContext globalContext;
public abstract void handle(T event, Client client);
}

View File

@@ -0,0 +1,77 @@
package cz.jzitnik.server.events;
import cz.jzitnik.common.socket.SocketMessage;
import cz.jzitnik.server.game.Client;
import cz.jzitnik.server.annotations.EventHandler;
import cz.jzitnik.server.context.GlobalContext;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
@Slf4j
public class EventManager extends Thread {
private ExecutorService eventExecutor;
private final HashMap<Class<? extends SocketMessage>, AbstractEventHandler<?>> handlers = new HashMap<>();
private final BlockingQueue<Registry> eventQueue = new LinkedBlockingQueue<>();
private record Registry(SocketMessage event, Client client) {}
public void emitEvent(SocketMessage event, Client client) {
eventQueue.add(new Registry(event, client));
}
public EventManager(Reflections reflections, GlobalContext globalContext) {
setDaemon(true);
var classes = reflections.getTypesAnnotatedWith(EventHandler.class);
for (var clazz : classes) {
EventHandler eventHandler = clazz.getAnnotation(EventHandler.class);
try {
var instance = (AbstractEventHandler<?>) clazz.getDeclaredConstructor(GlobalContext.class).newInstance(globalContext);
handlers.put(eventHandler.value(), instance);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {
log.error("Failed to instantiate socket event handler: {}", clazz.getName(), e);
}
}
}
@Override
public void run() {
eventExecutor = Executors.newFixedThreadPool(6);
while (true) {
try {
Registry registry = eventQueue.take();
handleEvent(registry.event, registry.client);
} catch (InterruptedException e) {
eventExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
}
//eventExecutor.shutdown();
}
@SuppressWarnings("unchecked")
private <T extends SocketMessage> AbstractEventHandler<T> getHandler(Class<T> type) {
return (AbstractEventHandler<T>) handlers.get(type);
}
@SuppressWarnings("unchecked")
private void handleEvent(SocketMessage event, Client client) {
eventExecutor.submit(() -> {
try {
AbstractEventHandler<SocketMessage> handler = getHandler((Class<SocketMessage>) event.getClass());
handler.handle(event, client);
} catch (Exception e) {
log.error("Error", e);
}
});
}
}

View File

@@ -0,0 +1,57 @@
package cz.jzitnik.server.events.handlers;
import cz.jzitnik.common.models.player.PlayerCreation;
import cz.jzitnik.common.socket.messages.game.connection.ConnectToAGame;
import cz.jzitnik.common.socket.messages.game.connection.ConnectToAGameResponse;
import cz.jzitnik.common.socket.messages.player.PlayerArrivalChange;
import cz.jzitnik.common.socket.messages.player.PlayerJoined;
import cz.jzitnik.common.socket.messages.player.PlayerRotation;
import cz.jzitnik.server.annotations.EventHandler;
import cz.jzitnik.server.context.GlobalContext;
import cz.jzitnik.server.events.AbstractEventHandler;
import cz.jzitnik.server.game.Client;
import cz.jzitnik.server.game.Player;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ObjectReader;
import tools.jackson.dataformat.yaml.YAMLFactory;
@EventHandler(ConnectToAGame.class)
public class ConnectToAGameHandler extends AbstractEventHandler<ConnectToAGame> {
public ConnectToAGameHandler(GlobalContext globalContext) {
super(globalContext);
}
@Override
public void handle(ConnectToAGame event, Client client) {
var gameOptional = globalContext.getGame(event.gamePass().toUpperCase());
if (gameOptional.isEmpty()) {
client.getSession().sendMessage(new ConnectToAGameResponse());
return;
}
var game = gameOptional.get();
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
ObjectReader playerReader = objectMapper.readerFor(PlayerCreation.class);
PlayerCreation player = playerReader.readValue(getClass().getClassLoader().getResourceAsStream("setup/player.yaml"));
player.setId(game.getPlayers().size());
client.setPlayer(new Player(player));
client.setGame(game);
String defaultRoomId = globalContext.getProperties().getProperty("rooms.default");
client.getPlayer().setCurrentRoom(defaultRoomId);
client.getSession().sendMessage(new ConnectToAGameResponse(player, game.getPlayers().stream().map(client1 -> client1.getPlayer().toPlayerCreation()).toList()));
for (Client cl : game.getPlayers()) {
cl.getSession().sendMessage(new PlayerJoined(player));
if (cl.getPlayer().getCurrentRoom().equals(defaultRoomId)) {
cl.getSession().sendMessage(new PlayerArrivalChange(client.getPlayer().getId(), player.getPlayerCords(), PlayerRotation.FRONT, true, true));
client.getSession().sendMessage(new PlayerArrivalChange(cl.getPlayer().getId(), cl.getPlayer().getCords(), PlayerRotation.FRONT, true, false));
}
}
game.getPlayers().add(client);
}
}

View File

@@ -0,0 +1,51 @@
package cz.jzitnik.server.events.handlers;
import cz.jzitnik.common.Config;
import cz.jzitnik.common.models.player.PlayerCreation;
import cz.jzitnik.common.socket.messages.game.creation.CreateGame;
import cz.jzitnik.common.socket.messages.game.creation.CreateGameResponse;
import cz.jzitnik.server.annotations.EventHandler;
import cz.jzitnik.server.context.GlobalContext;
import cz.jzitnik.server.events.AbstractEventHandler;
import cz.jzitnik.server.game.Client;
import cz.jzitnik.server.game.Game;
import cz.jzitnik.server.game.Player;
import cz.jzitnik.server.utils.PasswordGenerator;
import lombok.RequiredArgsConstructor;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ObjectReader;
import tools.jackson.dataformat.yaml.YAMLFactory;
import java.util.ArrayList;
import java.util.List;
@EventHandler(CreateGame.class)
public class CreateGameHandler extends AbstractEventHandler<CreateGame> {
public CreateGameHandler(GlobalContext globalContext) {
super(globalContext);
}
@Override
public void handle(CreateGame event, Client client) {
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
String pass = PasswordGenerator.generatePassword(Config.WORLD_PASSWORD_LENGTH);
int id = 0; // Owners id is always 0
ObjectReader playerReader = objectMapper.readerFor(PlayerCreation.class);
PlayerCreation player = playerReader.readValue(getClass().getClassLoader().getResourceAsStream("setup/player.yaml"));
player.setId(id);
client.setPlayer(new Player(player));
CreateGameResponse gameResponse = new CreateGameResponse(pass, player);
Game game = new Game(
pass,
new ArrayList<>(List.of(client))
);
client.setGame(game);
client.getPlayer().setCurrentRoom(globalContext.getProperties().getProperty("rooms.default"));
globalContext.getGames().add(game);
client.getSession().sendMessage(gameResponse);
}
}

View File

@@ -0,0 +1,23 @@
package cz.jzitnik.server.events.handlers;
import cz.jzitnik.common.socket.messages.items.ItemTookFromChest;
import cz.jzitnik.server.annotations.EventHandler;
import cz.jzitnik.server.context.GlobalContext;
import cz.jzitnik.server.events.AbstractEventHandler;
import cz.jzitnik.server.game.Client;
@EventHandler(ItemTookFromChest.class)
public class ItemTookFromChestHandler extends AbstractEventHandler<ItemTookFromChest> {
public ItemTookFromChestHandler(GlobalContext globalContext) {
super(globalContext);
}
@Override
public void handle(ItemTookFromChest event, Client client) {
client.getGame().getItemModifiers().add(event);
for (Client client1 : client.getGame().getPlayers()) {
client1.getSession().sendMessage(event);
}
}
}

View File

@@ -0,0 +1,50 @@
package cz.jzitnik.server.events.handlers;
import cz.jzitnik.common.socket.messages.player.PlayerArrivalChange;
import cz.jzitnik.common.socket.messages.room.MovePlayerRoom;
import cz.jzitnik.common.socket.messages.room.MovePlayerRoomResponse;
import cz.jzitnik.server.annotations.EventHandler;
import cz.jzitnik.server.context.GlobalContext;
import cz.jzitnik.server.events.AbstractEventHandler;
import cz.jzitnik.server.game.Client;
import lombok.extern.slf4j.Slf4j;
import java.util.stream.Collectors;
@Slf4j
@EventHandler(MovePlayerRoom.class)
public class MovePlayerRoomHandler extends AbstractEventHandler<MovePlayerRoom> {
public MovePlayerRoomHandler(GlobalContext globalContext) {
super(globalContext);
}
@Override
public void handle(MovePlayerRoom event, Client client) {
String oldRoomId = client.getPlayer().getCurrentRoom();
client.getSession().sendMessage(new MovePlayerRoomResponse(
client.getGame().getPlayers().stream().filter(player ->
player.getPlayer().getCurrentRoom().equals(event.newRoomId())
).map(client1 ->
new MovePlayerRoomResponse.Registry(
client1.getPlayer().getId(),
client1.getPlayer().getCords(),
client1.getPlayer().getPlayerRotation()
)
).collect(Collectors.toSet())
));
client.getPlayer().setCurrentRoom(event.newRoomId());
for (Client player : client.getGame().getPlayers()) {
if (player.getPlayer().getId() != client.getPlayer().getId()) {
if (player.getPlayer().getCurrentRoom().equals(oldRoomId)) {
log.debug("{}", event.oldCords());
player.getSession().sendMessage(new PlayerArrivalChange(client.getPlayer().getId(), event.oldCords(), client.getPlayer().getPlayerRotation(), false, true));
} else if (player.getPlayer().getCurrentRoom().equals(event.newRoomId())) {
player.getSession().sendMessage(new PlayerArrivalChange(client.getPlayer().getId(), event.newCords(), client.getPlayer().getPlayerRotation(), true, true));
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
package cz.jzitnik.server.events.handlers;
import cz.jzitnik.common.socket.messages.player.PlayerMove;
import cz.jzitnik.common.socket.messages.player.PlayerMovedInUrRoom;
import cz.jzitnik.server.annotations.EventHandler;
import cz.jzitnik.server.context.GlobalContext;
import cz.jzitnik.server.events.AbstractEventHandler;
import cz.jzitnik.server.game.Client;
@EventHandler(PlayerMove.class)
public class PlayerMoveHandler extends AbstractEventHandler<PlayerMove> {
public PlayerMoveHandler(GlobalContext globalContext) {
super(globalContext);
}
@Override
public void handle(PlayerMove event, Client client) {
client.getPlayer().getCords().updateCords(event.newCords());
client.getPlayer().setPlayerRotation(event.playerRotation());
for (Client player : client.getGame().getPlayers()) {
if (player.getPlayer().getCurrentRoom().equals(client.getPlayer().getCurrentRoom()) && player.getPlayer().getId() != client.getPlayer().getId()) {
player.getSession().sendMessage(new PlayerMovedInUrRoom(client.getPlayer().getId(), event.newCords(), event.playerRotation()));
}
}
}
}

View File

@@ -0,0 +1,25 @@
package cz.jzitnik.server.game;
import cz.jzitnik.server.socket.SocketSession;
import lombok.Getter;
import lombok.Setter;
@Getter
public final class Client {
private final SocketSession session;
@Setter
private Player player;
@Setter
private Game game;
public Client(SocketSession session, Player player, Game game) {
this.session = session;
this.player = player;
this.game = game;
}
public Client(SocketSession session, Player player) {
this(session, player, null);
}
}

View File

@@ -0,0 +1,16 @@
package cz.jzitnik.server.game;
import cz.jzitnik.common.socket.messages.items.ItemTookFromChest;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
@Getter
@AllArgsConstructor
public class Game {
private String password;
private List<Client> players;
private final List<ItemTookFromChest> itemModifiers = new ArrayList<>();
}

View File

@@ -0,0 +1,27 @@
package cz.jzitnik.server.game;
import cz.jzitnik.common.models.coordinates.RoomCords;
import cz.jzitnik.common.models.player.PlayerCreation;
import cz.jzitnik.common.socket.messages.player.PlayerRotation;
import lombok.Getter;
import lombok.Setter;
@Getter
public class Player {
private final int id;
private final RoomCords cords;
@Setter
private PlayerRotation playerRotation;
@Setter
private String currentRoom;
public Player(PlayerCreation creation) {
id = creation.getId();
cords = creation.getPlayerCords();
playerRotation = PlayerRotation.FRONT;
}
public PlayerCreation toPlayerCreation() {
return new PlayerCreation(cords, null);
}
}

View File

@@ -0,0 +1,14 @@
package cz.jzitnik.server.socket;
import cz.jzitnik.server.context.AppContext;
import jakarta.websocket.HandshakeResponse;
import jakarta.websocket.server.HandshakeRequest;
import jakarta.websocket.server.ServerEndpointConfig;
public class GlobalContextConfigurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
sec.getUserProperties().put("globalContext", AppContext.get());
}
}

View File

@@ -0,0 +1,28 @@
package cz.jzitnik.server.socket;
import cz.jzitnik.common.socket.SocketMessage;
import jakarta.websocket.Session;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
@RequiredArgsConstructor
@Getter
public class SocketSession {
private final Session session;
public void sendMessage(SocketMessage message) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(message);
oos.flush();
session.getBasicRemote().sendBinary(java.nio.ByteBuffer.wrap(baos.toByteArray()));
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,66 @@
package cz.jzitnik.server.socket;
import cz.jzitnik.common.socket.SocketMessage;
import cz.jzitnik.common.socket.messages.player.PlayerArrivalChange;
import cz.jzitnik.common.socket.messages.player.PlayerDisconnected;
import cz.jzitnik.server.game.Client;
import cz.jzitnik.server.context.GlobalContext;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
@Slf4j
@ServerEndpoint(value = "/ws", configurator = GlobalContextConfigurator.class)
public class WebSocket {
private GlobalContext globalContext;
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
this.globalContext = (GlobalContext) config.getUserProperties().get("globalContext");
globalContext.registerClient(new Client(new SocketSession(session), null));
log.debug("Client connected: {}", session.getId());
}
@OnMessage
public void onMessage(byte[] bytes, Session session) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais)) {
SocketMessage socketMessage = (SocketMessage) ois.readObject();
Client client = globalContext.getSessions().get(session);
globalContext.getEventManager().emitEvent(socketMessage, client);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
@OnClose
public void onClose(Session session, CloseReason reason) {
Client client = globalContext.getSessions().get(session);
client.getGame().getPlayers().remove(client);
for (Client otherClient : client.getGame().getPlayers()) {
if (otherClient.getPlayer().getCurrentRoom().equals(client.getPlayer().getCurrentRoom())) {
otherClient.getSession().sendMessage(new PlayerArrivalChange(
client.getPlayer().getId(),
client.getPlayer().getCords(),
client.getPlayer().getPlayerRotation(),
false,
true
));
}
otherClient.getSession().sendMessage(new PlayerDisconnected(client.getPlayer().getId()));
}
log.debug("Connection closed: {}", reason);
}
@OnError
public void onError(Session session, Throwable throwable) {
throwable.printStackTrace();
}
}

View File

@@ -0,0 +1,23 @@
package cz.jzitnik.server.utils;
import java.security.SecureRandom;
public class PasswordGenerator {
private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
private static final SecureRandom RANDOM = new SecureRandom();
public static String generatePassword(int length) {
if (length <= 0) {
throw new IllegalArgumentException("Password length must be greater than 0");
}
StringBuilder password = new StringBuilder(length);
for (int i = 0; i < length; i++) {
int index = RANDOM.nextInt(CHARACTERS.length());
password.append(CHARACTERS.charAt(index));
}
return password.toString();
}
}

View File

@@ -0,0 +1 @@
rooms.default=spawn

View File

@@ -0,0 +1,11 @@
playerCords:
x: 90
y: 100
collider:
start:
x: 0
y: 52
end:
x: 44
y: 78