feat: Multiplayer #3
3
.idea/compiler.xml
generated
3
.idea/compiler.xml
generated
@@ -14,7 +14,6 @@
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.42/lombok-1.18.42.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.42/lombok-1.18.42.jar" />
|
||||
</processorPath>
|
||||
<module name="common" />
|
||||
</profile>
|
||||
@@ -24,7 +23,6 @@
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.42/lombok-1.18.42.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.42/lombok-1.18.42.jar" />
|
||||
</processorPath>
|
||||
<module name="server" />
|
||||
</profile>
|
||||
@@ -34,7 +32,6 @@
|
||||
<outputRelativeToContentRoot value="true" />
|
||||
<processorPath useClasspath="false">
|
||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.38/lombok-1.18.38.jar" />
|
||||
<entry name="$MAVEN_REPOSITORY$/org/projectlombok/lombok/1.18.38/lombok-1.18.38.jar" />
|
||||
</processorPath>
|
||||
<module name="game (1)" />
|
||||
</profile>
|
||||
|
||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -8,5 +8,5 @@
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK" />
|
||||
</project>
|
||||
5
common/src/main/java/cz/jzitnik/common/Config.java
Normal file
5
common/src/main/java/cz/jzitnik/common/Config.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package cz.jzitnik.common;
|
||||
|
||||
public class Config {
|
||||
public static final int WORLD_PASSWORD_LENGTH = 5;
|
||||
}
|
||||
@@ -29,6 +29,10 @@ public class RoomCords implements Cloneable, Serializable {
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public void updateCords(RoomCords roomCords) {
|
||||
updateCords(roomCords.getX(), roomCords.getY());
|
||||
}
|
||||
|
||||
public void updateCordsWithColliders(List<RoomPart> colliders, int x, int y, RoomPart playerCollider) {
|
||||
var normalizedPlayerCollider = new RoomPart(
|
||||
new RoomCords(playerCollider.getStart().getX() + x, playerCollider.getStart().getY() + y),
|
||||
|
||||
@@ -5,11 +5,14 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.common.models.coordinates.RoomPart;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Getter
|
||||
public final class PlayerCreation implements Serializable {
|
||||
@Setter
|
||||
private int id;
|
||||
private final RoomCords playerCords;
|
||||
private final RoomPart collider;
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package cz.jzitnik.common.socket.messages.player;
|
||||
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.common.socket.SocketMessage;
|
||||
|
||||
public record PlayerMove(RoomCords newCords) implements SocketMessage {
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cz.jzitnik.common.socket.messages.player;
|
||||
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.common.socket.SocketMessage;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class PlayerMovedInUrRoom implements SocketMessage {
|
||||
private int playerId;
|
||||
private RoomCords cords;
|
||||
}
|
||||
@@ -8,10 +8,7 @@ import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.config.Debugging;
|
||||
import cz.jzitnik.client.config.PlayerConfig;
|
||||
import cz.jzitnik.client.events.MouseMoveEvent;
|
||||
import cz.jzitnik.client.events.PlayerMoveEvent;
|
||||
import cz.jzitnik.client.events.RerenderScreen;
|
||||
import cz.jzitnik.client.events.RoomChangeEvent;
|
||||
import cz.jzitnik.client.events.*;
|
||||
import cz.jzitnik.client.game.GameRoom;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.game.Player;
|
||||
@@ -26,6 +23,7 @@ import cz.jzitnik.client.utils.RerenderUtils;
|
||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||
import cz.jzitnik.client.utils.events.Event;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import cz.jzitnik.common.socket.messages.player.PlayerMove;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
@@ -146,6 +144,7 @@ public class PlayerMoveEventHandler extends AbstractEventHandler<PlayerMoveEvent
|
||||
RerenderUtils.rerenderPart(forStartX, forEndX, forStartY, forEndY, startX, startY, currentRoom, room, player, playerTexture, screenBuffer, resourceManager, debugging);
|
||||
|
||||
eventManager.emitEvent(new Event[]{
|
||||
new SendSocketMessageEvent(new PlayerMove(playerCords)),
|
||||
new MouseMoveEvent(null),
|
||||
new RerenderScreen(
|
||||
new RerenderScreen.ScreenPart[]{
|
||||
|
||||
@@ -8,6 +8,9 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@State
|
||||
public class GameState {
|
||||
@@ -21,6 +24,9 @@ public class GameState {
|
||||
@Setter
|
||||
private Player player;
|
||||
|
||||
@Getter
|
||||
private final List<OtherPlayer> otherPlayers = new ArrayList<>();
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private Interactable interacting;
|
||||
|
||||
20
game/src/main/java/cz/jzitnik/client/game/OtherPlayer.java
Normal file
20
game/src/main/java/cz/jzitnik/client/game/OtherPlayer.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package cz.jzitnik.client.game;
|
||||
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.common.models.player.PlayerCreation;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
public class OtherPlayer {
|
||||
private final int id;
|
||||
private boolean hitAnimationOn = false;
|
||||
private final RoomCords playerCords;
|
||||
@Setter
|
||||
private Player.PlayerRotation playerRotation = Player.PlayerRotation.FRONT;
|
||||
|
||||
public OtherPlayer(PlayerCreation playerCreation) {
|
||||
this.id = playerCreation.getId();
|
||||
this.playerCords = playerCreation.getPlayerCords();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package cz.jzitnik.client.game;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import cz.jzitnik.client.events.RerenderPart;
|
||||
import cz.jzitnik.client.game.items.GameItem;
|
||||
import cz.jzitnik.client.game.items.types.interfaces.WeaponInterface;
|
||||
@@ -26,6 +24,7 @@ import java.util.concurrent.TimeUnit;
|
||||
@Getter
|
||||
@Slf4j
|
||||
public class Player {
|
||||
private final int id;
|
||||
public static final int MAX_STAMINA = 20;
|
||||
public static final int MAX_HEALTH = 30;
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
@@ -43,18 +42,10 @@ public class Player {
|
||||
private boolean hitAnimationOn = false;
|
||||
private ScheduledFuture<?> currentTimeoutHitAnimation = null;
|
||||
|
||||
@JsonCreator
|
||||
public Player(
|
||||
@JsonProperty("playerCords") RoomCords playerCords,
|
||||
@JsonProperty("collider") RoomPart collider
|
||||
) {
|
||||
this.playerCords = playerCords;
|
||||
this.collider = collider;
|
||||
}
|
||||
|
||||
public Player(PlayerCreation playerCreation) {
|
||||
this.playerCords = playerCreation.getPlayerCords();
|
||||
this.collider = playerCreation.getCollider();
|
||||
this.id = playerCreation.getId();
|
||||
}
|
||||
|
||||
public void increaseStamina() {
|
||||
|
||||
@@ -2,6 +2,7 @@ package cz.jzitnik.client.game.setup.scenes.connect;
|
||||
|
||||
import com.googlecode.lanterna.TerminalSize;
|
||||
import com.googlecode.lanterna.TextColor;
|
||||
import com.googlecode.lanterna.graphics.TextGraphics;
|
||||
import com.googlecode.lanterna.input.KeyType;
|
||||
import com.googlecode.lanterna.screen.TerminalScreen;
|
||||
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||
@@ -9,11 +10,13 @@ import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||
import cz.jzitnik.client.events.KeyboardPressEvent;
|
||||
import cz.jzitnik.client.events.MouseAction;
|
||||
import cz.jzitnik.client.events.SendSocketMessageEvent;
|
||||
import cz.jzitnik.client.game.GameState;
|
||||
import cz.jzitnik.client.screens.Screen;
|
||||
import cz.jzitnik.client.screens.scenes.Scene;
|
||||
import cz.jzitnik.client.socket.Client;
|
||||
import cz.jzitnik.client.sound.SoundPlayer;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
import cz.jzitnik.client.ui.Inventory;
|
||||
import cz.jzitnik.client.ui.pixels.AlphaPixel;
|
||||
import cz.jzitnik.client.ui.pixels.Empty;
|
||||
import cz.jzitnik.client.ui.utils.Input;
|
||||
@@ -22,10 +25,13 @@ import cz.jzitnik.client.utils.TextRenderer;
|
||||
import cz.jzitnik.client.utils.events.EventManager;
|
||||
import cz.jzitnik.common.socket.messages.game.creation.CreateGame;
|
||||
import jakarta.websocket.DeploymentException;
|
||||
import cz.jzitnik.client.ui.utils.Button;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
public class ServerChoose extends Scene {
|
||||
public ServerChoose(DependencyManager dependencyManager) {
|
||||
GameMenuAudioScreen gameMenuScreen = new GameMenuAudioScreen();
|
||||
@@ -78,6 +84,12 @@ public class ServerChoose extends Scene {
|
||||
@InjectDependency
|
||||
private TextRenderer textRenderer;
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectDependency
|
||||
private DependencyManager dependencyManager;
|
||||
|
||||
private void renderInput(boolean refresh) {
|
||||
var tg = terminalState.getTextGraphics();
|
||||
TerminalScreen screen = terminalState.getTerminalScreen();
|
||||
@@ -136,32 +148,12 @@ public class ServerChoose extends Scene {
|
||||
|
||||
AlphaPixel[][] selectServer = textRenderer.renderText("Enter server IP", terminalSize.getColumns(), 20, Color.WHITE, 15f, true);
|
||||
|
||||
int padY = 10;
|
||||
|
||||
for (int y = 0; y < selectServer.length; y += 2) {
|
||||
for (int x = 0; x < selectServer[y].length; x++) {
|
||||
AlphaPixel topPixel = selectServer[y][x];
|
||||
|
||||
AlphaPixel bottomPixel;
|
||||
if (y + 1 < selectServer.length) {
|
||||
bottomPixel = selectServer[y + 1][x];
|
||||
} else {
|
||||
bottomPixel = new Empty();
|
||||
}
|
||||
|
||||
int termX = x;
|
||||
int termY = padY / 2 + y / 2;
|
||||
|
||||
tg.setBackgroundColor(topPixel instanceof Empty ? TextColor.ANSI.BLACK : topPixel.getColor());
|
||||
tg.setForegroundColor(bottomPixel instanceof Empty ? TextColor.ANSI.BLACK : bottomPixel.getColor());
|
||||
tg.setCharacter(termX, termY, '▄');
|
||||
}
|
||||
}
|
||||
render(selectServer, 0, 10, tg);
|
||||
|
||||
renderInput(false);
|
||||
|
||||
try {
|
||||
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.DELTA);
|
||||
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.COMPLETE);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -178,7 +170,10 @@ public class ServerChoose extends Scene {
|
||||
connecting = true;
|
||||
client.connect(ipBuffer.toString());
|
||||
|
||||
eventManager.emitEvent(new SendSocketMessageEvent(new CreateGame()));
|
||||
Screen screen = new ActionSelector();
|
||||
dependencyManager.inject(screen);
|
||||
gameState.setScreen(screen);
|
||||
screen.fullRender();
|
||||
} catch (DeploymentException | IOException e) {
|
||||
connecting = false;
|
||||
}
|
||||
@@ -195,7 +190,7 @@ public class ServerChoose extends Scene {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getKeyStroke().getKeyType() == KeyType.Character) {
|
||||
if (event.getKeyStroke().getKeyType() == KeyType.Character && !event.getKeyStroke().isCtrlDown()) {
|
||||
ipBuffer.append(event.getKeyStroke().getCharacter());
|
||||
renderInput(true);
|
||||
}
|
||||
@@ -205,4 +200,282 @@ public class ServerChoose extends Scene {
|
||||
public void handleMouseAction(MouseAction event) {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ActionSelector extends Screen {
|
||||
@InjectDependency
|
||||
private TextRenderer textRenderer;
|
||||
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
private int selectedIndex = -1;
|
||||
|
||||
@Override
|
||||
public void fullRender() {
|
||||
TerminalScreen screen = terminalState.getTerminalScreen();
|
||||
var tg = terminalState.getTextGraphics();
|
||||
screen.clear();
|
||||
TerminalSize terminalSize = screen.getTerminalSize();
|
||||
|
||||
for (int y = 0; y < terminalSize.getRows(); y += 1) {
|
||||
for (int x = 0; x < terminalSize.getColumns(); x++) {
|
||||
tg.setBackgroundColor(TextColor.ANSI.BLACK);
|
||||
tg.setForegroundColor(TextColor.ANSI.BLACK);
|
||||
tg.setCharacter(x, y, '▄');
|
||||
}
|
||||
}
|
||||
|
||||
AlphaPixel[][] selectAction = textRenderer.renderText("Select action", terminalSize.getColumns(), 20, Color.WHITE, 15f, true);
|
||||
|
||||
render(selectAction, 0, 10, tg);
|
||||
renderButtons();
|
||||
|
||||
try {
|
||||
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.COMPLETE);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int BUTTON_HEIGHT = 20;
|
||||
private static final int BUTTON_WIDTH = 200;
|
||||
private static final int BUTTON_GAP = 10;
|
||||
private static final int BUTTON_COUNT = 2;
|
||||
private static final int BUTTONS_HEIGHT = BUTTON_HEIGHT * BUTTON_COUNT + (BUTTON_COUNT - 1) * BUTTON_GAP;
|
||||
|
||||
private void renderButtons() {
|
||||
var tg = terminalState.getTextGraphics();
|
||||
TerminalSize terminalSize = terminalState.getTerminalScreen().getTerminalSize();
|
||||
|
||||
final int BUTTON_PAD_X = terminalSize.getColumns() / 2 - BUTTON_WIDTH / 2;
|
||||
final int BUTTON_PAD_Y = terminalSize.getRows() - BUTTONS_HEIGHT / 2;
|
||||
|
||||
Button button = new Button(
|
||||
Inventory.BORDER_COLOR,
|
||||
Inventory.BACKGROUND_COLOR,
|
||||
Inventory.BACKGROUND_COLOR_HOVERED,
|
||||
Color.WHITE,
|
||||
BUTTON_HEIGHT,
|
||||
BUTTON_WIDTH,
|
||||
1,
|
||||
15f,
|
||||
textRenderer
|
||||
);
|
||||
|
||||
render(button.render("Create a world", selectedIndex == 0), BUTTON_PAD_X, BUTTON_PAD_Y, tg);
|
||||
render(button.render("Connect to an existing world", selectedIndex == 1), BUTTON_PAD_X, BUTTON_PAD_Y + (BUTTON_HEIGHT + BUTTON_GAP), tg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMouseAction(MouseAction event) {
|
||||
TerminalSize terminalSize = terminalState.getTerminalScreen().getTerminalSize();
|
||||
|
||||
final int BUTTON_START_X = terminalSize.getColumns() / 2 - BUTTON_WIDTH / 2;
|
||||
final int BUTTON_START_Y = terminalSize.getRows() - BUTTONS_HEIGHT / 2;
|
||||
final int BUTTON_END_X = BUTTON_START_X + BUTTON_WIDTH;
|
||||
final int BUTTON_END_Y = BUTTON_START_Y + BUTTONS_HEIGHT;
|
||||
|
||||
final int TERMINAL_X = event.getPosition().getColumn();
|
||||
final int TERMINAL_Y = event.getPosition().getRow() * 2;
|
||||
|
||||
final int SINGLE_BUTTON_HEIGHT = BUTTON_HEIGHT + BUTTON_GAP;
|
||||
|
||||
int index = (TERMINAL_Y - BUTTON_START_Y) / SINGLE_BUTTON_HEIGHT;
|
||||
int rest = (TERMINAL_Y - BUTTON_START_Y) % SINGLE_BUTTON_HEIGHT;
|
||||
|
||||
if (!(TERMINAL_X >= BUTTON_START_X && TERMINAL_Y >= BUTTON_START_Y && TERMINAL_X < BUTTON_END_X && TERMINAL_Y < BUTTON_END_Y) || rest > BUTTON_HEIGHT) {
|
||||
if (selectedIndex != -1) {
|
||||
selectedIndex = -1;
|
||||
renderButtons();
|
||||
refresh();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.getActionType()) {
|
||||
case MOVE -> {
|
||||
selectedIndex = index;
|
||||
renderButtons();
|
||||
refresh();
|
||||
}
|
||||
case CLICK_RELEASE -> {
|
||||
switch (index) {
|
||||
case 0 -> eventManager.emitEvent(new SendSocketMessageEvent(new CreateGame()));
|
||||
case 1 -> {
|
||||
Screen screen = new ConnectWorld();
|
||||
gameState.setScreen(screen);
|
||||
screen.fullRender();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
try {
|
||||
terminalState.getTerminalScreen().refresh(com.googlecode.lanterna.screen.Screen.RefreshType.DELTA);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleKeyboardAction(KeyboardPressEvent event) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class ConnectWorld extends Screen {
|
||||
private final StringBuilder passBuffer = new StringBuilder();
|
||||
private boolean connecting = false;
|
||||
|
||||
@InjectDependency
|
||||
private EventManager eventManager;
|
||||
|
||||
@InjectDependency
|
||||
private Client client;
|
||||
|
||||
@InjectState
|
||||
private TerminalState terminalState;
|
||||
|
||||
@InjectDependency
|
||||
private TextRenderer textRenderer;
|
||||
|
||||
@InjectState
|
||||
private GameState gameState;
|
||||
|
||||
@InjectDependency
|
||||
private DependencyManager dependencyManager;
|
||||
|
||||
private void renderInput(boolean refresh) {
|
||||
var tg = terminalState.getTextGraphics();
|
||||
TerminalScreen screen = terminalState.getTerminalScreen();
|
||||
Input input = new Input(passBuffer.toString(), 18, 100);
|
||||
var inputBuffer = input.render(textRenderer);
|
||||
TerminalSize termSize = screen.getTerminalSize();
|
||||
int renderPixelWidth = inputBuffer[0].length;
|
||||
int renderPixelHeight = inputBuffer.length;
|
||||
int renderCharWidth = renderPixelWidth;
|
||||
int renderCharHeight = (renderPixelHeight + 1) / 2;
|
||||
int startX = (termSize.getColumns() - renderCharWidth) / 2;
|
||||
int startY = (termSize.getRows() - renderCharHeight) / 2;
|
||||
|
||||
for (int y = 0; y < inputBuffer.length; y += 2) {
|
||||
for (int x = 0; x < inputBuffer[y].length; x++) {
|
||||
AlphaPixel bottomPixel;
|
||||
AlphaPixel topPixel = inputBuffer[y][x];
|
||||
if (y + 1 < inputBuffer.length) {
|
||||
bottomPixel = inputBuffer[y + 1][x];
|
||||
} else {
|
||||
bottomPixel = new Empty();
|
||||
}
|
||||
|
||||
int termX = startX + x;
|
||||
int termY = startY + (y / 2);
|
||||
|
||||
tg.setBackgroundColor(topPixel instanceof Empty ? TextColor.ANSI.BLACK : topPixel.getColor());
|
||||
tg.setForegroundColor(bottomPixel instanceof Empty ? TextColor.ANSI.BLACK : bottomPixel.getColor());
|
||||
tg.setCharacter(termX, termY, '▄');
|
||||
}
|
||||
}
|
||||
|
||||
if (refresh) {
|
||||
try {
|
||||
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.DELTA);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullRender() {
|
||||
TerminalScreen screen = terminalState.getTerminalScreen();
|
||||
var tg = terminalState.getTextGraphics();
|
||||
screen.clear();
|
||||
TerminalSize terminalSize = screen.getTerminalSize();
|
||||
|
||||
for (int y = 0; y < terminalSize.getRows(); y += 1) {
|
||||
for (int x = 0; x < terminalSize.getColumns(); x++) {
|
||||
tg.setBackgroundColor(TextColor.ANSI.BLACK);
|
||||
tg.setForegroundColor(TextColor.ANSI.BLACK);
|
||||
tg.setCharacter(x, y, '▄');
|
||||
}
|
||||
}
|
||||
|
||||
AlphaPixel[][] selectServer = textRenderer.renderText("Enter world password", terminalSize.getColumns(), 20, Color.WHITE, 15f, true);
|
||||
|
||||
render(selectServer, 0, 10, tg);
|
||||
|
||||
renderInput(false);
|
||||
|
||||
try {
|
||||
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.COMPLETE);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleKeyboardAction(KeyboardPressEvent event) {
|
||||
if (connecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getKeyStroke().getKeyType() == KeyType.Enter) {
|
||||
connecting = true;
|
||||
String pass = passBuffer.toString();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getKeyStroke().getKeyType() == KeyType.Backspace) {
|
||||
if (passBuffer.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
passBuffer.deleteCharAt(passBuffer.length() - 1);
|
||||
renderInput(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getKeyStroke().getKeyType() == KeyType.Character && !event.getKeyStroke().isCtrlDown()) {
|
||||
passBuffer.append(event.getKeyStroke().getCharacter());
|
||||
renderInput(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMouseAction(MouseAction event) {
|
||||
}
|
||||
}
|
||||
|
||||
private static void render(AlphaPixel[][] buffer, int padX, int padY, TextGraphics tg) {
|
||||
for (int y = 0; y < buffer.length; y += 2) {
|
||||
for (int x = 0; x < buffer[y].length; x++) {
|
||||
AlphaPixel topPixel = buffer[y][x];
|
||||
|
||||
AlphaPixel bottomPixel;
|
||||
if (y + 1 < buffer.length) {
|
||||
bottomPixel = buffer[y + 1][x];
|
||||
} else {
|
||||
bottomPixel = new Empty();
|
||||
}
|
||||
|
||||
int termX = padX + x;
|
||||
int termY = padY / 2 + y / 2;
|
||||
|
||||
tg.setBackgroundColor(topPixel instanceof Empty ? TextColor.ANSI.BLACK : topPixel.getColor());
|
||||
tg.setForegroundColor(bottomPixel instanceof Empty ? TextColor.ANSI.BLACK : bottomPixel.getColor());
|
||||
tg.setCharacter(termX, termY, '▄');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import cz.jzitnik.client.game.ResourceManager;
|
||||
import cz.jzitnik.client.game.items.GameItem;
|
||||
import cz.jzitnik.client.game.items.types.InteractableItem;
|
||||
import cz.jzitnik.client.game.objects.DroppedItem;
|
||||
import cz.jzitnik.client.ui.pixels.AlphaPixel;
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import cz.jzitnik.client.states.ScreenBuffer;
|
||||
import cz.jzitnik.client.states.TerminalState;
|
||||
@@ -50,17 +51,17 @@ public class Inventory {
|
||||
private static final int INNER_BORDER_WIDTH = 1;
|
||||
private static final int ITEM_SIZE = 16; // Characters
|
||||
private static final int ITEM_PADDING = 2; // padding on each side
|
||||
public static final Pixel BORDER_COLOR =
|
||||
public static final AlphaPixel BORDER_COLOR =
|
||||
new ColoredPixel(new TextColor.RGB(41, 29, 19));
|
||||
public static final Pixel BACKGROUND_COLOR =
|
||||
public static final AlphaPixel BACKGROUND_COLOR =
|
||||
new ColoredPixel(new TextColor.RGB(61, 45, 29));
|
||||
private static final Pixel BACKGROUND_COLOR_HOVERED =
|
||||
public static final AlphaPixel BACKGROUND_COLOR_HOVERED =
|
||||
new ColoredPixel(new TextColor.RGB(77, 56, 36));
|
||||
private static final Pixel BACKGROUND_COLOR_SELECTED =
|
||||
private static final AlphaPixel BACKGROUND_COLOR_SELECTED =
|
||||
new ColoredPixel(new TextColor.RGB(95, 62, 32));
|
||||
private static final Pixel BACKGROUND_COLOR_EQUIPPED =
|
||||
private static final AlphaPixel BACKGROUND_COLOR_EQUIPPED =
|
||||
new ColoredPixel(new TextColor.RGB(50, 145, 150));
|
||||
private static final Pixel BACKGROUND_COLOR_EQUIPPED_SELECTED =
|
||||
private static final AlphaPixel BACKGROUND_COLOR_EQUIPPED_SELECTED =
|
||||
new ColoredPixel(new TextColor.RGB(58, 170, 176));
|
||||
private static final int ITEM_SLOT_SIZE = ITEM_SIZE + ITEM_PADDING * 2;
|
||||
public static final int INVENTORY_WIDTH =
|
||||
|
||||
@@ -10,4 +10,8 @@ public sealed abstract class AlphaPixel extends Pixel permits Empty, ColoredPixe
|
||||
super(color);
|
||||
this.alpha = alpha;
|
||||
}
|
||||
|
||||
public boolean isTransparent() {
|
||||
return alpha == 0f;
|
||||
}
|
||||
}
|
||||
|
||||
50
game/src/main/java/cz/jzitnik/client/ui/utils/Button.java
Normal file
50
game/src/main/java/cz/jzitnik/client/ui/utils/Button.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package cz.jzitnik.client.ui.utils;
|
||||
|
||||
import cz.jzitnik.client.ui.pixels.AlphaPixel;
|
||||
import cz.jzitnik.client.utils.TextRenderer;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public record Button(
|
||||
AlphaPixel borderColor,
|
||||
AlphaPixel backgroundColor,
|
||||
AlphaPixel backgroundSelectedColor,
|
||||
Color textColor,
|
||||
int height,
|
||||
int width,
|
||||
int borderWidth,
|
||||
float fontSize,
|
||||
TextRenderer textRenderer
|
||||
) {
|
||||
public AlphaPixel[][] render(String text, boolean selected) {
|
||||
AlphaPixel[][] buf = new AlphaPixel[height][width];
|
||||
AlphaPixel[][] textBuf = textRenderer.renderTextSingleLine(text, width - borderWidth * 2, height - borderWidth * 2, textColor, fontSize, true, true);
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (x < borderWidth || y < borderWidth || x >= width - borderWidth || y >= height - borderWidth) {
|
||||
buf[y][x] = borderColor;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (x == borderWidth || y == borderWidth || x - 1 == width - borderWidth || y == height - borderWidth) {
|
||||
buf[y][x] = selected ? backgroundSelectedColor : backgroundColor;
|
||||
continue;
|
||||
}
|
||||
|
||||
int textActualX = x - borderWidth - 1;
|
||||
int textActualY = y - borderWidth - 1;
|
||||
AlphaPixel textPixel = textBuf[textActualY][textActualX];
|
||||
|
||||
if (!textPixel.isTransparent()) {
|
||||
buf[y][x] = textPixel;
|
||||
continue;
|
||||
}
|
||||
|
||||
buf[y][x] = selected ? backgroundSelectedColor : backgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,52 @@ package cz.jzitnik.server.context;
|
||||
|
||||
import cz.jzitnik.server.game.Client;
|
||||
import cz.jzitnik.server.events.EventManager;
|
||||
import cz.jzitnik.server.socket.SocketSession;
|
||||
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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
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;
|
||||
|
||||
public void registerClient(Session session) {
|
||||
Client client = new Client(new SocketSession(session));
|
||||
sessions.put(session, client);
|
||||
@Getter
|
||||
private final Properties properties;
|
||||
|
||||
public void registerClient(Client client) {
|
||||
sessions.put(client.getSession().getSession(), client);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -7,11 +8,17 @@ 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;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@EventHandler(CreateGame.class)
|
||||
public class CreateGameHandler extends AbstractEventHandler<CreateGame> {
|
||||
@@ -20,13 +27,24 @@ public class CreateGameHandler extends AbstractEventHandler<CreateGame> {
|
||||
@Override
|
||||
public void handle(CreateGame event, Client client) {
|
||||
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
|
||||
String pass = "nevim"; // TODO: Generate
|
||||
String pass = PasswordGenerator.generatePassword(Config.WORLD_PASSWORD_LENGTH); // TODO: Generate
|
||||
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(id, player.getPlayerCords()));
|
||||
|
||||
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.session().sendMessage(gameResponse);
|
||||
client.getSession().sendMessage(gameResponse);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
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;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@EventHandler(PlayerMove.class)
|
||||
public class PlayerMoveHandler extends AbstractEventHandler<PlayerMove> {
|
||||
private final GlobalContext globalContext;
|
||||
|
||||
@Override
|
||||
public void handle(PlayerMove event, Client client) {
|
||||
client.getPlayer().getCords().updateCords(event.newCords());
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,25 @@
|
||||
package cz.jzitnik.server.game;
|
||||
|
||||
import cz.jzitnik.server.socket.SocketSession;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public record Client(SocketSession session) {}
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,13 @@
|
||||
package cz.jzitnik.server.game;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class Game {
|
||||
private String password;
|
||||
private List<Client> players;
|
||||
}
|
||||
|
||||
15
server/src/main/java/cz/jzitnik/server/game/Player.java
Normal file
15
server/src/main/java/cz/jzitnik/server/game/Player.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package cz.jzitnik.server.game;
|
||||
|
||||
import cz.jzitnik.common.models.coordinates.RoomCords;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class Player {
|
||||
private final int id;
|
||||
private final RoomCords cords;
|
||||
@Setter
|
||||
private String currentRoom;
|
||||
}
|
||||
@@ -18,7 +18,8 @@ public class WebSocket {
|
||||
@OnOpen
|
||||
public void onOpen(Session session, EndpointConfig config) {
|
||||
this.globalContext = (GlobalContext) config.getUserProperties().get("globalContext");
|
||||
globalContext.registerClient(session);
|
||||
|
||||
globalContext.registerClient(new Client(new SocketSession(session), null));
|
||||
log.debug("Client connected: {}", session.getId());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package cz.jzitnik.server.utils;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class PasswordGenerator {
|
||||
private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
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();
|
||||
}
|
||||
}
|
||||
1
server/src/main/resources/config.properties
Normal file
1
server/src/main/resources/config.properties
Normal file
@@ -0,0 +1 @@
|
||||
rooms.default=spawn
|
||||
Reference in New Issue
Block a user