summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorilotterytea <iltsu@alright.party>2025-01-23 00:55:33 +0500
committerilotterytea <iltsu@alright.party>2025-01-23 00:55:33 +0500
commit6bf7ee0b5f0c15fefa2a6cde4a17d8f6ade3f39b (patch)
treed085466f3d453a73322adc881296e6c8b30ba26e
parent7757de487c909fe15e0ac0b5cbaa27f12109c66d (diff)
feat: chat !!!
-rw-r--r--core/src/main/java/kz/ilotterytea/frogartha/screens/GameScreen.java107
-rw-r--r--core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java2
-rw-r--r--core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java10
-rw-r--r--server/src/main/java/kz/ilotterytea/frogartha/server/FrogarthaServer.java7
-rw-r--r--server/src/main/java/kz/ilotterytea/frogartha/server/ServerHandlers.java24
-rw-r--r--shared/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java5
-rw-r--r--shared/src/main/java/kz/ilotterytea/frogartha/domain/PlayerState.java9
-rw-r--r--shared/src/main/java/kz/ilotterytea/frogartha/domain/actions/ChatMessageAction.java31
-rw-r--r--shared/src/main/java/kz/ilotterytea/frogartha/domain/events/ChatMessageEvent.java32
-rw-r--r--shared/src/main/java/kz/ilotterytea/frogartha/exceptions/PlayerKickException.java4
-rw-r--r--shared/src/main/java/kz/ilotterytea/frogartha/utils/SerializerUtils.java8
11 files changed, 227 insertions, 12 deletions
diff --git a/core/src/main/java/kz/ilotterytea/frogartha/screens/GameScreen.java b/core/src/main/java/kz/ilotterytea/frogartha/screens/GameScreen.java
index b6444bd..4f8b1c7 100644
--- a/core/src/main/java/kz/ilotterytea/frogartha/screens/GameScreen.java
+++ b/core/src/main/java/kz/ilotterytea/frogartha/screens/GameScreen.java
@@ -11,8 +11,17 @@ import com.badlogic.gdx.graphics.g3d.decals.CameraGroupStrategy;
import com.badlogic.gdx.graphics.g3d.decals.DecalBatch;
import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;
import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.scenes.scene2d.InputEvent;
+import com.badlogic.gdx.scenes.scene2d.Stage;
+import com.badlogic.gdx.scenes.scene2d.ui.*;
+import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
+import com.badlogic.gdx.utils.Align;
+import com.badlogic.gdx.utils.viewport.ScreenViewport;
+import kz.ilotterytea.frogartha.FrogarthaConstants;
import kz.ilotterytea.frogartha.FrogarthaGame;
+import kz.ilotterytea.frogartha.assets.Assets;
import kz.ilotterytea.frogartha.domain.PlayerData;
+import kz.ilotterytea.frogartha.domain.actions.ChatMessageAction;
import kz.ilotterytea.frogartha.entities.LocalPlayerEntity;
import kz.ilotterytea.frogartha.entities.PlayerEntity;
import net.mgsx.gltf.scene3d.attributes.PBRCubemapAttribute;
@@ -30,6 +39,11 @@ import java.util.Map;
public class GameScreen implements Screen {
private FrogarthaGame game;
+ private Stage stage;
+ private Skin skin;
+ private Table chatMessageTable;
+ private ScrollPane chatPane;
+
private PerspectiveCamera camera;
private SceneManager sceneManager;
private DecalBatch decalBatch;
@@ -43,6 +57,7 @@ public class GameScreen implements Screen {
game = FrogarthaGame.getInstance();
create3D();
+ createStage();
playerEntity = new LocalPlayerEntity(camera);
playerEntity.setPosition(0f, 1f, 0f);
@@ -53,6 +68,8 @@ public class GameScreen implements Screen {
entity.setDirection(entry.getValue().getState().getDirection());
playerEntityMap.put(entry.getKey(), entity);
}
+
+ Gdx.input.setInputProcessor(stage);
}
@Override
@@ -71,6 +88,9 @@ public class GameScreen implements Screen {
decalBatch.flush();
+ stage.act(delta);
+ stage.draw();
+
if (game.getSessionClient().hasThrown()) {
game.setScreen(new KickScreen());
}
@@ -78,6 +98,7 @@ public class GameScreen implements Screen {
@Override
public void resize(int width, int height) {
+ stage.getViewport().update(width, height, true);
sceneManager.updateViewport(width, height);
}
@@ -98,6 +119,7 @@ public class GameScreen implements Screen {
@Override
public void dispose() {
+ stage.dispose();
sceneManager.dispose();
decalBatch.dispose();
}
@@ -155,6 +177,91 @@ public class GameScreen implements Screen {
decalBatch = new DecalBatch(new CameraGroupStrategy(camera));
}
+ private void createStage() {
+ stage = new Stage(new ScreenViewport());
+ skin = game.getAssetManager().get(Assets.Skins.SKIN_UI);
+
+ Table mainTable = new Table();
+ mainTable.setFillParent(true);
+ stage.addActor(mainTable);
+
+ // --- Chat ---
+ Table chatBase = new Table();
+ chatBase.pad(8f);
+ chatBase.align(Align.bottomLeft);
+ mainTable.add(chatBase).grow();
+
+ Table chatWrapper = new Table(skin);
+ chatWrapper.setBackground("halftransparentblack");
+ chatWrapper.pad(4f);
+ chatBase.add(chatWrapper).width(384f).align(Align.bottomLeft);
+
+ chatMessageTable = new Table();
+ chatMessageTable.setFillParent(true);
+ chatMessageTable.align(Align.bottom);
+ chatPane = new ScrollPane(chatMessageTable, skin);
+ chatPane.setScrollbarsVisible(true);
+ chatPane.setScrollingDisabled(true, false);
+
+ chatWrapper.add(chatPane).growX().height(300f).row();
+
+ // Chat sender table
+ Table chatSenderTable = new Table();
+ chatWrapper.add(chatSenderTable).grow();
+
+ TextField chatField = new TextField("", skin);
+ chatField.setMessageText("...");
+ chatField.setMaxLength(FrogarthaConstants.Chat.MAX_MESSAGE_LENGTH);
+ chatSenderTable.add(chatField).grow().padRight(16f);
+
+ TextButton chatSendButton = new TextButton("Send", skin);
+ chatSenderTable.add(chatSendButton).grow();
+
+ chatSendButton.addListener(new ClickListener() {
+ @Override
+ public void clicked(InputEvent event, float x, float y) {
+ super.clicked(event, x, y);
+
+ if (chatField.getText().isEmpty()) return;
+
+ game.getSessionClient().send(new ChatMessageAction(chatField.getText()));
+ chatField.setText("");
+ }
+ });
+ }
+
+ public void addMessage(int playerId, String message) {
+ String name = null;
+
+ if (playerId == game.getSessionClient().getConnectionId()) {
+ name = game.getIdentityClient().getUsername();
+ } else {
+ for (Map.Entry<Integer, PlayerData> entry : game.getSessionClient().getPlayerDataMap().entrySet()) {
+ if (entry.getKey() == playerId) {
+ name = entry.getValue().getIdentity().getUsername();
+ break;
+ }
+ }
+ }
+
+ if (name == null) return;
+
+ Table table = new Table(skin);
+ if (chatMessageTable.getCells().size % 2 == 0) {
+ table.setBackground("halftransparentblack");
+ }
+
+ Label nameLabel = new Label(name, skin);
+ table.add(nameLabel).padBottom(4f).growX().row();
+
+ Label messageLabel = new Label(message, skin);
+ messageLabel.setWrap(true);
+ table.add(messageLabel).padLeft(4f).width(296f).row();
+
+ chatMessageTable.add(table).growX().row();
+ chatPane.scrollTo(0f, 0f, 0f, 0f);
+ }
+
public LocalPlayerEntity getPlayerEntity() {
return playerEntity;
}
diff --git a/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java b/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java
index 6132a8a..cde0b2e 100644
--- a/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java
+++ b/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionClient.java
@@ -81,6 +81,8 @@ public class SessionClient implements WebSocketListener {
SessionHandlers.handlePlayerJoinedRoomEvent((PlayerJoinedRoomEvent) obj);
} else if (obj instanceof PlayerLeftRoomEvent) {
SessionHandlers.handlePlayerLeftRoomEvent((PlayerLeftRoomEvent) obj);
+ } else if (obj instanceof ChatMessageEvent) {
+ SessionHandlers.handleChatMessageEvent((ChatMessageEvent) obj);
} else if (obj instanceof PlayerKickException) {
throw (PlayerKickException) obj;
}
diff --git a/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java b/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java
index 3b82305..06b7f8e 100644
--- a/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java
+++ b/core/src/main/java/kz/ilotterytea/frogartha/sessions/SessionHandlers.java
@@ -139,4 +139,14 @@ public class SessionHandlers {
GameScreen screen = (GameScreen) game.getScreen();
screen.getPlayerEntityMap().remove(event.getPlayerId());
}
+
+ public static void handleChatMessageEvent(ChatMessageEvent event) {
+ if (!game.getScreen().getClass().equals(GameScreen.class)) {
+ log.log("The screen is not GameScreen, but the session received ChatMessageEvent");
+ return;
+ }
+
+ GameScreen screen = (GameScreen) game.getScreen();
+ screen.addMessage(event.getPlayerId(), event.getMessage());
+ }
}
diff --git a/server/src/main/java/kz/ilotterytea/frogartha/server/FrogarthaServer.java b/server/src/main/java/kz/ilotterytea/frogartha/server/FrogarthaServer.java
index 99847df..6e8fbde 100644
--- a/server/src/main/java/kz/ilotterytea/frogartha/server/FrogarthaServer.java
+++ b/server/src/main/java/kz/ilotterytea/frogartha/server/FrogarthaServer.java
@@ -3,10 +3,7 @@ package kz.ilotterytea.frogartha.server;
import com.github.czyzby.websocket.serialization.SerializationException;
import com.github.czyzby.websocket.serialization.impl.ManualSerializer;
import kz.ilotterytea.frogartha.domain.Identity;
-import kz.ilotterytea.frogartha.domain.actions.ChangedDirectionAction;
-import kz.ilotterytea.frogartha.domain.actions.JoinRoomAction;
-import kz.ilotterytea.frogartha.domain.actions.LeaveRoomAction;
-import kz.ilotterytea.frogartha.domain.actions.PlayerJumpAction;
+import kz.ilotterytea.frogartha.domain.actions.*;
import kz.ilotterytea.frogartha.exceptions.PlayerKickException;
import kz.ilotterytea.frogartha.utils.Logger;
import kz.ilotterytea.frogartha.utils.SerializerUtils;
@@ -101,6 +98,8 @@ public class FrogarthaServer extends WebSocketServer {
ServerHandlers.handleJoinRoomAction(player, (JoinRoomAction) obj);
} else if (obj instanceof LeaveRoomAction) {
ServerHandlers.handleLeaveRoomAction(player, (LeaveRoomAction) obj);
+ } else if (obj instanceof ChatMessageAction) {
+ ServerHandlers.handleChatMessageAction(player, (ChatMessageAction) obj);
} else {
throw PlayerKickException.internalServerError();
}
diff --git a/server/src/main/java/kz/ilotterytea/frogartha/server/ServerHandlers.java b/server/src/main/java/kz/ilotterytea/frogartha/server/ServerHandlers.java
index 6f16281..6891fef 100644
--- a/server/src/main/java/kz/ilotterytea/frogartha/server/ServerHandlers.java
+++ b/server/src/main/java/kz/ilotterytea/frogartha/server/ServerHandlers.java
@@ -5,11 +5,9 @@ import com.badlogic.gdx.math.Vector3;
import kz.ilotterytea.frogartha.FrogarthaConstants;
import kz.ilotterytea.frogartha.domain.Identity;
import kz.ilotterytea.frogartha.domain.PlayerState;
-import kz.ilotterytea.frogartha.domain.actions.ChangedDirectionAction;
-import kz.ilotterytea.frogartha.domain.actions.JoinRoomAction;
-import kz.ilotterytea.frogartha.domain.actions.LeaveRoomAction;
-import kz.ilotterytea.frogartha.domain.actions.PlayerJumpAction;
+import kz.ilotterytea.frogartha.domain.actions.*;
import kz.ilotterytea.frogartha.domain.events.ChangedDirectionEvent;
+import kz.ilotterytea.frogartha.domain.events.ChatMessageEvent;
import kz.ilotterytea.frogartha.domain.events.IdentifiedEvent;
import kz.ilotterytea.frogartha.domain.events.PlayerJumpEvent;
import kz.ilotterytea.frogartha.exceptions.PlayerKickException;
@@ -127,4 +125,22 @@ public class ServerHandlers {
log.log("{} left {}", player, room);
}
+
+ public static void handleChatMessageAction(PlayerConnection player, ChatMessageAction action) {
+ if (player.getRoom() == null) {
+ throw PlayerKickException.notInRoom();
+ }
+
+ long nowTimestamp = System.currentTimeMillis();
+
+ if (nowTimestamp - player.getState().getLastMessageTimestamp() < FrogarthaConstants.Chat.MESSAGE_PER_MS) {
+ throw PlayerKickException.messageSpam();
+ }
+
+ player.getState().setLastMessageTimestamp(nowTimestamp);
+
+ Room room = player.getRoom();
+ room.broadcast(new ChatMessageEvent(player.getId(), action.getMessage()));
+ log.log("{} sent a message: {}", player, action.getMessage());
+ }
}
diff --git a/shared/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java b/shared/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java
index 84dc6b0..e205bf5 100644
--- a/shared/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java
+++ b/shared/src/main/java/kz/ilotterytea/frogartha/FrogarthaConstants.java
@@ -14,4 +14,9 @@ public class FrogarthaConstants {
public static class Room {
public static final int MAX_PLAYERS = 30;
}
+
+ public static class Chat {
+ public static final int MAX_MESSAGE_LENGTH = 100;
+ public static final int MESSAGE_PER_MS = 500;
+ }
}
diff --git a/shared/src/main/java/kz/ilotterytea/frogartha/domain/PlayerState.java b/shared/src/main/java/kz/ilotterytea/frogartha/domain/PlayerState.java
index 0aa89c5..c3db5a8 100644
--- a/shared/src/main/java/kz/ilotterytea/frogartha/domain/PlayerState.java
+++ b/shared/src/main/java/kz/ilotterytea/frogartha/domain/PlayerState.java
@@ -11,6 +11,7 @@ public class PlayerState implements Transferable<PlayerState> {
private final Vector3 position;
private final Vector2 direction;
private float nextJumpTimestamp;
+ private long lastMessageTimestamp;
public PlayerState() {
this(new Vector3(), new Vector2());
@@ -42,6 +43,14 @@ public class PlayerState implements Transferable<PlayerState> {
this.nextJumpTimestamp = nextJumpTimestamp;
}
+ public long getLastMessageTimestamp() {
+ return lastMessageTimestamp;
+ }
+
+ public void setLastMessageTimestamp(long lastMessageTimestamp) {
+ this.lastMessageTimestamp = lastMessageTimestamp;
+ }
+
public void setDirection(float x, float z) {
this.direction.set(x, z);
}
diff --git a/shared/src/main/java/kz/ilotterytea/frogartha/domain/actions/ChatMessageAction.java b/shared/src/main/java/kz/ilotterytea/frogartha/domain/actions/ChatMessageAction.java
new file mode 100644
index 0000000..465238a
--- /dev/null
+++ b/shared/src/main/java/kz/ilotterytea/frogartha/domain/actions/ChatMessageAction.java
@@ -0,0 +1,31 @@
+package kz.ilotterytea.frogartha.domain.actions;
+
+import com.github.czyzby.websocket.serialization.SerializationException;
+import com.github.czyzby.websocket.serialization.Transferable;
+import com.github.czyzby.websocket.serialization.impl.Deserializer;
+import com.github.czyzby.websocket.serialization.impl.Serializer;
+
+public class ChatMessageAction implements Transferable<ChatMessageAction> {
+ private String message;
+
+ public ChatMessageAction() {
+ }
+
+ public ChatMessageAction(String message) {
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public void serialize(Serializer serializer) throws SerializationException {
+ serializer.serializeString(message);
+ }
+
+ @Override
+ public ChatMessageAction deserialize(Deserializer deserializer) throws SerializationException {
+ return new ChatMessageAction(deserializer.deserializeString());
+ }
+}
diff --git a/shared/src/main/java/kz/ilotterytea/frogartha/domain/events/ChatMessageEvent.java b/shared/src/main/java/kz/ilotterytea/frogartha/domain/events/ChatMessageEvent.java
new file mode 100644
index 0000000..7bd395e
--- /dev/null
+++ b/shared/src/main/java/kz/ilotterytea/frogartha/domain/events/ChatMessageEvent.java
@@ -0,0 +1,32 @@
+package kz.ilotterytea.frogartha.domain.events;
+
+import com.github.czyzby.websocket.serialization.SerializationException;
+import com.github.czyzby.websocket.serialization.Transferable;
+import com.github.czyzby.websocket.serialization.impl.Deserializer;
+import com.github.czyzby.websocket.serialization.impl.Serializer;
+
+public class ChatMessageEvent extends Event implements Transferable<ChatMessageEvent> {
+ private String message;
+
+ public ChatMessageEvent() {
+ }
+
+ public ChatMessageEvent(int playerId, String message) {
+ super(playerId);
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public void serialize(Serializer serializer) throws SerializationException {
+ serializer.serializeInt(playerId).serializeString(message);
+ }
+
+ @Override
+ public ChatMessageEvent deserialize(Deserializer deserializer) throws SerializationException {
+ return new ChatMessageEvent(deserializer.deserializeInt(), deserializer.deserializeString());
+ }
+}
diff --git a/shared/src/main/java/kz/ilotterytea/frogartha/exceptions/PlayerKickException.java b/shared/src/main/java/kz/ilotterytea/frogartha/exceptions/PlayerKickException.java
index ad9c408..e24c633 100644
--- a/shared/src/main/java/kz/ilotterytea/frogartha/exceptions/PlayerKickException.java
+++ b/shared/src/main/java/kz/ilotterytea/frogartha/exceptions/PlayerKickException.java
@@ -33,6 +33,10 @@ public class PlayerKickException extends RuntimeException implements Transferabl
return new PlayerKickException("You are not in room");
}
+ public static PlayerKickException messageSpam() {
+ return new PlayerKickException("Kicked for spamming");
+ }
+
@Override
public void serialize(Serializer serializer) throws SerializationException {
serializer.serializeString(getMessage());
diff --git a/shared/src/main/java/kz/ilotterytea/frogartha/utils/SerializerUtils.java b/shared/src/main/java/kz/ilotterytea/frogartha/utils/SerializerUtils.java
index e005937..3529968 100644
--- a/shared/src/main/java/kz/ilotterytea/frogartha/utils/SerializerUtils.java
+++ b/shared/src/main/java/kz/ilotterytea/frogartha/utils/SerializerUtils.java
@@ -2,10 +2,7 @@ package kz.ilotterytea.frogartha.utils;
import com.github.czyzby.websocket.serialization.impl.ManualSerializer;
import kz.ilotterytea.frogartha.domain.Identity;
-import kz.ilotterytea.frogartha.domain.actions.ChangedDirectionAction;
-import kz.ilotterytea.frogartha.domain.actions.JoinRoomAction;
-import kz.ilotterytea.frogartha.domain.actions.LeaveRoomAction;
-import kz.ilotterytea.frogartha.domain.actions.PlayerJumpAction;
+import kz.ilotterytea.frogartha.domain.actions.*;
import kz.ilotterytea.frogartha.domain.events.*;
import kz.ilotterytea.frogartha.exceptions.PlayerKickException;
@@ -26,5 +23,8 @@ public class SerializerUtils {
serializer.register(new LeaveRoomAction());
serializer.register(new PlayerLeftRoomEvent());
serializer.register(new SenderLeftRoomEvent());
+
+ serializer.register(new ChatMessageAction());
+ serializer.register(new ChatMessageEvent());
}
}