diff options
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()); } } |
