From 843ad028ada695fd2f922fd908b12f6ac57e2d4b Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Wed, 27 Jul 2022 16:10:22 +0200 Subject: [PATCH] Added basic chat functionality, for server-side messages. --- client/pom.xml | 2 +- .../java/nl/andrewl/aos2_client/Client.java | 12 + .../nl/andrewl/aos2_client/model/Chat.java | 27 +++ .../aos2_client/render/GameRenderer.java | 2 +- .../aos2_client/render/gui/GuiRenderer.java | 43 +++- .../gui/{GUITexture.java => GuiTexture.java} | 8 +- .../render/gui/font/Character.java | 104 --------- .../render/gui/font/FontRenderer.java | 20 -- .../aos2_client/render/gui/font/FontType.java | 52 ----- .../aos2_client/render/gui/font/GUIText.java | 193 ---------------- .../aos2_client/render/gui/font/Line.java | 77 ------- .../aos2_client/render/gui/font/MetaFile.java | 213 ------------------ .../aos2_client/render/gui/font/TextMesh.java | 50 ---- .../render/gui/font/TextMeshCreator.java | 133 ----------- .../render/gui/font/TextMeshData.java | 30 --- .../aos2_client/render/gui/font/Word.java | 48 ---- .../aos2_client/sound/SoundManager.java | 1 + client/src/main/resources/sound/chat.wav | Bin 0 -> 51244 bytes .../main/resources/text/jetbrains-mono.fnt | 102 --------- .../main/resources/text/jetbrains-mono.png | Bin 35287 -> 0 bytes core/pom.xml | 2 +- .../main/java/nl/andrewl/aos_core/Net.java | 2 + .../aos_core/net/client/ChatMessage.java | 21 ++ .../net/client/ChatWrittenMessage.java | 10 + pom.xml | 2 +- server/pom.xml | 2 +- .../nl/andrewl/aos2_server/PlayerManager.java | 19 +- 27 files changed, 136 insertions(+), 1039 deletions(-) create mode 100644 client/src/main/java/nl/andrewl/aos2_client/model/Chat.java rename client/src/main/java/nl/andrewl/aos2_client/render/gui/{GUITexture.java => GuiTexture.java} (90%) delete mode 100644 client/src/main/java/nl/andrewl/aos2_client/render/gui/font/Character.java delete mode 100644 client/src/main/java/nl/andrewl/aos2_client/render/gui/font/FontRenderer.java delete mode 100644 client/src/main/java/nl/andrewl/aos2_client/render/gui/font/FontType.java delete mode 100644 client/src/main/java/nl/andrewl/aos2_client/render/gui/font/GUIText.java delete mode 100644 client/src/main/java/nl/andrewl/aos2_client/render/gui/font/Line.java delete mode 100644 client/src/main/java/nl/andrewl/aos2_client/render/gui/font/MetaFile.java delete mode 100644 client/src/main/java/nl/andrewl/aos2_client/render/gui/font/TextMesh.java delete mode 100644 client/src/main/java/nl/andrewl/aos2_client/render/gui/font/TextMeshCreator.java delete mode 100644 client/src/main/java/nl/andrewl/aos2_client/render/gui/font/TextMeshData.java delete mode 100644 client/src/main/java/nl/andrewl/aos2_client/render/gui/font/Word.java create mode 100644 client/src/main/resources/sound/chat.wav delete mode 100644 client/src/main/resources/text/jetbrains-mono.fnt delete mode 100644 client/src/main/resources/text/jetbrains-mono.png create mode 100644 core/src/main/java/nl/andrewl/aos_core/net/client/ChatMessage.java create mode 100644 core/src/main/java/nl/andrewl/aos_core/net/client/ChatWrittenMessage.java diff --git a/client/pom.xml b/client/pom.xml index e146b39..0e209db 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -5,7 +5,7 @@ ace-of-shades-2 nl.andrewl - 1.0.0 + 1.1.0 4.0.0 diff --git a/client/src/main/java/nl/andrewl/aos2_client/Client.java b/client/src/main/java/nl/andrewl/aos2_client/Client.java index 855308c..7e5eeeb 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/Client.java +++ b/client/src/main/java/nl/andrewl/aos2_client/Client.java @@ -2,6 +2,7 @@ package nl.andrewl.aos2_client; import nl.andrewl.aos2_client.config.ClientConfig; import nl.andrewl.aos2_client.control.*; +import nl.andrewl.aos2_client.model.Chat; import nl.andrewl.aos2_client.model.ClientPlayer; import nl.andrewl.aos2_client.model.OtherPlayer; import nl.andrewl.aos2_client.render.GameRenderer; @@ -41,6 +42,7 @@ public class Client implements Runnable { private final Map players; private final Map projectiles; private final Map teams; + private final Chat chat; public Client(ClientConfig config) { this.config = config; @@ -49,6 +51,7 @@ public class Client implements Runnable { this.projectiles = new ConcurrentHashMap<>(); this.communicationHandler = new CommunicationHandler(this); this.inputHandler = new InputHandler(this, communicationHandler); + this.chat = new Chat(); } public ClientConfig getConfig() { @@ -182,6 +185,11 @@ public class Client implements Runnable { } } else if (msg instanceof ClientHealthMessage healthMessage) { myPlayer.setHealth(healthMessage.health()); + } else if (msg instanceof ChatMessage chatMessage) { + chat.chatReceived(chatMessage); + if (soundManager != null) { + soundManager.play("chat", 1, myPlayer.getPosition(), myPlayer.getVelocity()); + } } } @@ -205,6 +213,10 @@ public class Client implements Runnable { return projectiles; } + public Chat getChat() { + return chat; + } + public void interpolatePlayers(long now, float dt) { Vector3f movement = new Vector3f(); for (var player : players.values()) { diff --git a/client/src/main/java/nl/andrewl/aos2_client/model/Chat.java b/client/src/main/java/nl/andrewl/aos2_client/model/Chat.java new file mode 100644 index 0000000..89d439c --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/model/Chat.java @@ -0,0 +1,27 @@ +package nl.andrewl.aos2_client.model; + +import nl.andrewl.aos_core.net.client.ChatMessage; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedDeque; + +/** + * Represents the set of all chat messages that this client is aware of. + */ +public class Chat { + public static final int MAX_HISTORY = 20; + private final Deque messages = new ConcurrentLinkedDeque<>(); + + public void chatReceived(ChatMessage msg) { + messages.addFirst(msg); + while (messages.size() > MAX_HISTORY) { + messages.removeLast(); + } + } + + public List getMessages() { + return new ArrayList<>(messages); + } +} diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java b/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java index 019898f..40e26ab 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java @@ -271,7 +271,7 @@ public class GameRenderer { // GUI rendering guiRenderer.start(); guiRenderer.drawNameplates(myPlayer, camera.getViewTransformData(), perspectiveTransform.get(new float[16])); - guiRenderer.drawNvg(screenWidth, screenHeight, myPlayer); + guiRenderer.drawNvg(screenWidth, screenHeight, myPlayer, client.getChat()); guiRenderer.end(); glfwSwapBuffers(windowHandle); diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiRenderer.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiRenderer.java index 416372d..63ca69e 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiRenderer.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiRenderer.java @@ -1,6 +1,7 @@ package nl.andrewl.aos2_client.render.gui; import nl.andrewl.aos2_client.Camera; +import nl.andrewl.aos2_client.model.Chat; import nl.andrewl.aos2_client.model.ClientPlayer; import nl.andrewl.aos2_client.model.OtherPlayer; import nl.andrewl.aos2_client.render.ShaderProgram; @@ -59,7 +60,7 @@ public class GuiRenderer { private final int namePlatePerspectiveTransformUniform; private final int namePlateTextureSamplerUniform; private final Font namePlateFont; - private final Map playerNamePlates = new HashMap<>(); + private final Map playerNamePlates = new HashMap<>(); public GuiRenderer() throws IOException { vgId = nvgCreate(NVG_ANTIALIAS); @@ -127,7 +128,7 @@ public class GuiRenderer { glUniform1i(textureSamplerUniform, 0); } - public void draw(GUITexture texture, float scaleX, float scaleY, float x, float y) { + public void draw(GuiTexture texture, float scaleX, float scaleY, float x, float y) { glActiveTexture(0); transformMatrix.identity() .translate(x, y, 0) @@ -139,12 +140,12 @@ public class GuiRenderer { } private void addNamePlate(OtherPlayer player) { - GUITexture texture = new GUITexture(generateNameplateImage(player.getUsername())); + GuiTexture texture = new GuiTexture(generateNameplateImage(player.getUsername())); playerNamePlates.put(player, texture); } private void removeNamePlate(OtherPlayer player) { - GUITexture texture = playerNamePlates.remove(player); + GuiTexture texture = playerNamePlates.remove(player); texture.free(); } @@ -197,7 +198,7 @@ public class GuiRenderer { OtherPlayer player = entry.getKey(); // Skip rendering far-away nameplates. if (player.getPosition().distance(myPlayer.getPosition()) > 50) continue; - GUITexture texture = entry.getValue(); + GuiTexture texture = entry.getValue(); float aspectRatio = (float) texture.getHeight() / (float) texture.getWidth(); transformMatrix.identity() .translate(player.getPosition().x(), player.getPosition().y() + Player.HEIGHT + 1f, player.getPosition().z()) @@ -215,11 +216,12 @@ public class GuiRenderer { shaderProgram.use(); } - public void drawNvg(float width, float height, ClientPlayer player) { + public void drawNvg(float width, float height, ClientPlayer player, Chat chat) { nvgBeginFrame(vgId, width, height, width / height); nvgSave(vgId); drawCrosshair(width, height); + drawChat(width, height, chat); drawHealthBar(width, height, player); drawHeldItemStackInfo(width, height, player); @@ -315,4 +317,33 @@ public class GuiRenderer { nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); nvgText(vgId, w - 140, h - 30, String.format("%d / %d Blocks", stack.getAmount(), block.getMaxAmount())); } + + private void drawChat(float w, float h, Chat chat) { + float chatWidth = w / 3; + float chatHeight = h / 4; + + nvgFillColor(vgId, GuiUtils.rgba(0, 0, 0, 0.25f, colorA)); + nvgBeginPath(vgId); + nvgRect(vgId, 0, 0, chatWidth, chatHeight); + nvgFill(vgId); + + nvgFontSize(vgId, 12f); + nvgFontFaceId(vgId, jetbrainsMonoFont); + nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); + float y = chatHeight - 12; + for (var msg : chat.getMessages()) { + if (msg.author().equals("_ANNOUNCE")) { + nvgFillColor(vgId, GuiUtils.rgba(0.7f, 0, 0, 1, colorA)); + nvgText(vgId, 5, y, msg.message()); + } else if (msg.author().equals("_PRIVATE")) { + nvgFillColor(vgId, GuiUtils.rgba(0.3f, 0.3f, 0.3f, 1, colorA)); + nvgText(vgId, 5, y, msg.message()); + } else { + nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA)); + nvgText(vgId, 5, y, msg.author() + ": " + msg.message()); + } + + y -= 16; + } + } } diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GUITexture.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiTexture.java similarity index 90% rename from client/src/main/java/nl/andrewl/aos2_client/render/gui/GUITexture.java rename to client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiTexture.java index 91cf40b..5e0457e 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GUITexture.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GuiTexture.java @@ -2,9 +2,7 @@ package nl.andrewl.aos2_client.render.gui; import nl.andrewl.aos_core.FileUtils; import nl.andrewl.aos_core.ImageUtils; -import org.joml.Vector2f; -import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.IOException; @@ -13,7 +11,7 @@ import static org.lwjgl.opengl.GL46.*; /** * Represents a texture loaded onto the GPU. */ -public class GUITexture { +public class GuiTexture { private final int textureId; /** * The original image's width. @@ -24,11 +22,11 @@ public class GUITexture { */ private final int height; - public GUITexture(String location) throws IOException { + public GuiTexture(String location) throws IOException { this(FileUtils.readClasspathImage(location)); } - public GUITexture(BufferedImage img) { + public GuiTexture(BufferedImage img) { width = img.getWidth(); height = img.getHeight(); diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/Character.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/Character.java deleted file mode 100644 index 57e2f19..0000000 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/Character.java +++ /dev/null @@ -1,104 +0,0 @@ -package nl.andrewl.aos2_client.render.gui.font; - -/** - * Simple data structure class holding information about a certain glyph in the - * font texture atlas. All sizes are for a font-size of 1. - * - * @author Karl - * - */ -public class Character { - - private int id; - private double xTextureCoord; - private double yTextureCoord; - private double xMaxTextureCoord; - private double yMaxTextureCoord; - private double xOffset; - private double yOffset; - private double sizeX; - private double sizeY; - private double xAdvance; - - /** - * @param id - * - the ASCII value of the character. - * @param xTextureCoord - * - the x texture coordinate for the top left corner of the - * character in the texture atlas. - * @param yTextureCoord - * - the y texture coordinate for the top left corner of the - * character in the texture atlas. - * @param xTexSize - * - the width of the character in the texture atlas. - * @param yTexSize - * - the height of the character in the texture atlas. - * @param xOffset - * - the x distance from the curser to the left edge of the - * character's quad. - * @param yOffset - * - the y distance from the curser to the top edge of the - * character's quad. - * @param sizeX - * - the width of the character's quad in screen space. - * @param sizeY - * - the height of the character's quad in screen space. - * @param xAdvance - * - how far in pixels the cursor should advance after adding - * this character. - */ - protected Character(int id, double xTextureCoord, double yTextureCoord, double xTexSize, double yTexSize, - double xOffset, double yOffset, double sizeX, double sizeY, double xAdvance) { - this.id = id; - this.xTextureCoord = xTextureCoord; - this.yTextureCoord = yTextureCoord; - this.xOffset = xOffset; - this.yOffset = yOffset; - this.sizeX = sizeX; - this.sizeY = sizeY; - this.xMaxTextureCoord = xTexSize + xTextureCoord; - this.yMaxTextureCoord = yTexSize + yTextureCoord; - this.xAdvance = xAdvance; - } - - protected int getId() { - return id; - } - - protected double getxTextureCoord() { - return xTextureCoord; - } - - protected double getyTextureCoord() { - return yTextureCoord; - } - - protected double getXMaxTextureCoord() { - return xMaxTextureCoord; - } - - protected double getYMaxTextureCoord() { - return yMaxTextureCoord; - } - - protected double getxOffset() { - return xOffset; - } - - protected double getyOffset() { - return yOffset; - } - - protected double getSizeX() { - return sizeX; - } - - protected double getSizeY() { - return sizeY; - } - - protected double getxAdvance() { - return xAdvance; - } - -} diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/FontRenderer.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/FontRenderer.java deleted file mode 100644 index 4e28da0..0000000 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/FontRenderer.java +++ /dev/null @@ -1,20 +0,0 @@ -package nl.andrewl.aos2_client.render.gui.font; - -import nl.andrewl.aos2_client.render.ShaderProgram; - -import static org.lwjgl.opengl.GL46.*; - -public class FontRenderer { - private final ShaderProgram shaderProgram; - - public FontRenderer() { - shaderProgram = new ShaderProgram.Builder() - .withShader("/shader/text/vertex.glsl", GL_VERTEX_SHADER) - .withShader("/shader/text/fragment.glsl", GL_FRAGMENT_SHADER) - .build(); - } - - public void free() { - shaderProgram.free(); - } -} diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/FontType.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/FontType.java deleted file mode 100644 index e6ed200..0000000 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/FontType.java +++ /dev/null @@ -1,52 +0,0 @@ -package nl.andrewl.aos2_client.render.gui.font; - -import java.io.File; - -/** - * Represents a font. It holds the font's texture atlas as well as having the - * ability to create the quad vertices for any text using this font. - * - * @author Karl - * - */ -public class FontType { - - private final int textureAtlas; - private final TextMeshCreator loader; - - /** - * Creates a new font and loads up the data about each character from the - * font file. - * - * @param textureAtlas - * - the ID of the font atlas texture. - * @param fontFile - * - the font file containing information about each character in - * the texture atlas. - */ - public FontType(int textureAtlas, File fontFile, float aspectRatio) { - this.textureAtlas = textureAtlas; - this.loader = new TextMeshCreator(fontFile, aspectRatio); - } - - /** - * @return The font texture atlas. - */ - public int getTextureAtlas() { - return textureAtlas; - } - - /** - * Takes in an unloaded text and calculate all of the vertices for the quads - * on which this text will be rendered. The vertex positions and texture - * coords and calculated based on the information from the font file. - * - * @param text - * - the unloaded text. - * @return Information about the vertices of all the quads. - */ - public TextMeshData loadText(GUIText text) { - return loader.createTextMesh(text); - } - -} diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/GUIText.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/GUIText.java deleted file mode 100644 index 4ca0fee..0000000 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/GUIText.java +++ /dev/null @@ -1,193 +0,0 @@ -package nl.andrewl.aos2_client.render.gui.font; - -import org.joml.Vector2f; -import org.joml.Vector3f; - -import static org.lwjgl.opengl.GL46.*; - -/** - * Represents a piece of text in the game. - * - * @author Karl - * - */ -public class GUIText { - - private final String textString; - private final float fontSize; - - private int textMeshVao; - private int textMeshVbo; - private int vertexCount; - private final Vector3f colour = new Vector3f(0f, 0f, 0f); - - private final Vector2f position; - private final float lineMaxSize; - private int numberOfLines; - - private final FontType font; - - private boolean centerText; - - /** - * Creates a new text, loads the text's quads into a VAO, and adds the text - * to the screen. - * - * @param text - * - the text. - * @param fontSize - * - the font size of the text, where a font size of 1 is the - * default size. - * @param font - * - the font that this text should use. - * @param position - * - the position on the screen where the top left corner of the - * text should be rendered. The top left corner of the screen is - * (0, 0) and the bottom right is (1, 1). - * @param maxLineLength - * - basically the width of the virtual page in terms of screen - * width (1 is full screen width, 0.5 is half the width of the - * screen, etc.) Text cannot go off the edge of the page, so if - * the text is longer than this length it will go onto the next - * line. When text is centered it is centered into the middle of - * the line, based on this line length value. - * @param centered - * - whether the text should be centered or not. - */ - public GUIText(String text, float fontSize, FontType font, Vector2f position, float maxLineLength, - boolean centered) { - this.textString = text; - this.fontSize = fontSize; - this.font = font; - this.position = position; - this.lineMaxSize = maxLineLength; - this.centerText = centered; - // load text - TextMeshData meshData = font.loadText(this); - textMeshVao = glGenVertexArrays(); - textMeshVbo = glGenBuffers(); - glBindBuffer(GL_ARRAY_BUFFER, textMeshVbo); -// FloatBuffer buffer1 = BufferUtils.createFloatBuffer(meshData) - } - - /** - * Remove the text from the screen. - */ - public void remove() { - // remove text - } - - /** - * @return The font used by this text. - */ - public FontType getFont() { - return font; - } - - /** - * Set the colour of the text. - * - * @param r - * - red value, between 0 and 1. - * @param g - * - green value, between 0 and 1. - * @param b - * - blue value, between 0 and 1. - */ - public void setColour(float r, float g, float b) { - colour.set(r, g, b); - } - - /** - * @return the colour of the text. - */ - public Vector3f getColour() { - return colour; - } - - /** - * @return The number of lines of text. This is determined when the text is - * loaded, based on the length of the text and the max line length - * that is set. - */ - public int getNumberOfLines() { - return numberOfLines; - } - - /** - * @return The position of the top-left corner of the text in screen-space. - * (0, 0) is the top left corner of the screen, (1, 1) is the bottom - * right. - */ - public Vector2f getPosition() { - return position; - } - - /** - * @return the ID of the text's VAO, which contains all the vertex data for - * the quads on which the text will be rendered. - */ - public int getMesh() { - return textMeshVao; - } - - /** - * Set the VAO and vertex count for this text. - * - * @param vao - * - the VAO containing all the vertex data for the quads on - * which the text will be rendered. - * @param verticesCount - * - the total number of vertices in all of the quads. - */ - public void setMeshInfo(int vao, int verticesCount) { - this.textMeshVao = vao; - this.vertexCount = verticesCount; - } - - /** - * @return The total number of vertices of all the text's quads. - */ - public int getVertexCount() { - return this.vertexCount; - } - - /** - * @return the font size of the text (a font size of 1 is normal). - */ - protected float getFontSize() { - return fontSize; - } - - /** - * Sets the number of lines that this text covers (method used only in - * loading). - * - * @param number - */ - protected void setNumberOfLines(int number) { - this.numberOfLines = number; - } - - /** - * @return {@code true} if the text should be centered. - */ - protected boolean isCentered() { - return centerText; - } - - /** - * @return The maximum length of a line of this text. - */ - protected float getMaxLineSize() { - return lineMaxSize; - } - - /** - * @return The string of text. - */ - protected String getTextString() { - return textString; - } - -} diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/Line.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/Line.java deleted file mode 100644 index 5e3ae3e..0000000 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/Line.java +++ /dev/null @@ -1,77 +0,0 @@ -package nl.andrewl.aos2_client.render.gui.font; - -import java.util.ArrayList; -import java.util.List; - -/** - * Represents a line of text during the loading of a text. - * - * @author Karl - * - */ -public class Line { - - private final double maxLength; - private final double spaceSize; - - private final List words = new ArrayList(); - private double currentLineLength = 0; - - /** - * Creates an empty line. - * - * @param spaceWidth - * - the screen-space width of a space character. - * @param fontSize - * - the size of font being used. - * @param maxLength - * - the screen-space maximum length of a line. - */ - protected Line(double spaceWidth, double fontSize, double maxLength) { - this.spaceSize = spaceWidth * fontSize; - this.maxLength = maxLength; - } - - /** - * Attempt to add a word to the line. If the line can fit the word in - * without reaching the maximum line length then the word is added and the - * line length increased. - * - * @param word - * - the word to try to add. - * @return {@code true} if the word has successfully been added to the line. - */ - protected boolean attemptToAddWord(Word word) { - double additionalLength = word.getWordWidth(); - additionalLength += !words.isEmpty() ? spaceSize : 0; - if (currentLineLength + additionalLength <= maxLength) { - words.add(word); - currentLineLength += additionalLength; - return true; - } else { - return false; - } - } - - /** - * @return The max length of the line. - */ - protected double getMaxLength() { - return maxLength; - } - - /** - * @return The current screen-space length of the line. - */ - protected double getLineLength() { - return currentLineLength; - } - - /** - * @return The list of words in the line. - */ - protected List getWords() { - return words; - } - -} diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/MetaFile.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/MetaFile.java deleted file mode 100644 index 92d2725..0000000 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/MetaFile.java +++ /dev/null @@ -1,213 +0,0 @@ -package nl.andrewl.aos2_client.render.gui.font; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -/** - * Provides functionality for getting the values from a font file. - * - * @author Karl - * - */ -public class MetaFile { - - private static final int PAD_TOP = 0; - private static final int PAD_LEFT = 1; - private static final int PAD_BOTTOM = 2; - private static final int PAD_RIGHT = 3; - - private static final int DESIRED_PADDING = 3; - - private static final String SPLITTER = " "; - private static final String NUMBER_SEPARATOR = ","; - - private final double aspectRatio; - - private double verticalPerPixelSize; - private double horizontalPerPixelSize; - private double spaceWidth; - private int[] padding; - private int paddingWidth; - private int paddingHeight; - - private final Map metaData = new HashMap<>(); - - private BufferedReader reader; - private final Map values = new HashMap<>(); - - /** - * Opens a font file in preparation for reading. - * - * @param file - * - the font file. - */ - protected MetaFile(File file, float aspectRatio) { - this.aspectRatio = aspectRatio; - openFile(file); - loadPaddingData(); - loadLineSizes(); - int imageWidth = getValueOfVariable("scaleW"); - loadCharacterData(imageWidth); - close(); - } - - protected double getSpaceWidth() { - return spaceWidth; - } - - protected Character getCharacter(int ascii) { - return metaData.get(ascii); - } - - /** - * Read in the next line and store the variable values. - * - * @return {@code true} if the end of the file hasn't been reached. - */ - private boolean processNextLine() { - values.clear(); - String line = null; - try { - line = reader.readLine(); - } catch (IOException e1) { - } - if (line == null) { - return false; - } - for (String part : line.split(SPLITTER)) { - String[] valuePairs = part.split("="); - if (valuePairs.length == 2) { - values.put(valuePairs[0], valuePairs[1]); - } - } - return true; - } - - /** - * Gets the {@code int} value of the variable with a certain name on the - * current line. - * - * @param variable - * - the name of the variable. - * @return The value of the variable. - */ - private int getValueOfVariable(String variable) { - return Integer.parseInt(values.get(variable)); - } - - /** - * Gets the array of ints associated with a variable on the current line. - * - * @param variable - * - the name of the variable. - * @return The int array of values associated with the variable. - */ - private int[] getValuesOfVariable(String variable) { - String[] numbers = values.get(variable).split(NUMBER_SEPARATOR); - int[] actualValues = new int[numbers.length]; - for (int i = 0; i < actualValues.length; i++) { - actualValues[i] = Integer.parseInt(numbers[i]); - } - return actualValues; - } - - /** - * Closes the font file after finishing reading. - */ - private void close() { - try { - reader.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Opens the font file, ready for reading. - * - * @param file - * - the font file. - */ - private void openFile(File file) { - try { - reader = new BufferedReader(new FileReader(file)); - } catch (Exception e) { - e.printStackTrace(); - System.err.println("Couldn't read font meta file!"); - } - } - - /** - * Loads the data about how much padding is used around each character in - * the texture atlas. - */ - private void loadPaddingData() { - processNextLine(); - this.padding = getValuesOfVariable("padding"); - this.paddingWidth = padding[PAD_LEFT] + padding[PAD_RIGHT]; - this.paddingHeight = padding[PAD_TOP] + padding[PAD_BOTTOM]; - } - - /** - * Loads information about the line height for this font in pixels, and uses - * this as a way to find the conversion rate between pixels in the texture - * atlas and screen-space. - */ - private void loadLineSizes() { - processNextLine(); - int lineHeightPixels = getValueOfVariable("lineHeight") - paddingHeight; - verticalPerPixelSize = TextMeshCreator.LINE_HEIGHT / (double) lineHeightPixels; - horizontalPerPixelSize = verticalPerPixelSize / aspectRatio; - } - - /** - * Loads in data about each character and stores the data in the - * {@link Character} class. - * - * @param imageWidth - * - the width of the texture atlas in pixels. - */ - private void loadCharacterData(int imageWidth) { - processNextLine(); - processNextLine(); - while (processNextLine()) { - Character c = loadCharacter(imageWidth); - if (c != null) { - metaData.put(c.getId(), c); - } - } - } - - /** - * Loads all the data about one character in the texture atlas and converts - * it all from 'pixels' to 'screen-space' before storing. The effects of - * padding are also removed from the data. - * - * @param imageSize - * - the size of the texture atlas in pixels. - * @return The data about the character. - */ - private Character loadCharacter(int imageSize) { - int id = getValueOfVariable("id"); - if (id == TextMeshCreator.SPACE_ASCII) { - this.spaceWidth = (getValueOfVariable("xadvance") - paddingWidth) * horizontalPerPixelSize; - return null; - } - double xTex = ((double) getValueOfVariable("x") + (padding[PAD_LEFT] - DESIRED_PADDING)) / imageSize; - double yTex = ((double) getValueOfVariable("y") + (padding[PAD_TOP] - DESIRED_PADDING)) / imageSize; - int width = getValueOfVariable("width") - (paddingWidth - (2 * DESIRED_PADDING)); - int height = getValueOfVariable("height") - ((paddingHeight) - (2 * DESIRED_PADDING)); - double quadWidth = width * horizontalPerPixelSize; - double quadHeight = height * verticalPerPixelSize; - double xTexSize = (double) width / imageSize; - double yTexSize = (double) height / imageSize; - double xOff = (getValueOfVariable("xoffset") + padding[PAD_LEFT] - DESIRED_PADDING) * horizontalPerPixelSize; - double yOff = (getValueOfVariable("yoffset") + (padding[PAD_TOP] - DESIRED_PADDING)) * verticalPerPixelSize; - double xAdvance = (getValueOfVariable("xadvance") - paddingWidth) * horizontalPerPixelSize; - return new Character(id, xTex, yTex, xTexSize, yTexSize, xOff, yOff, quadWidth, quadHeight, xAdvance); - } -} diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/TextMesh.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/TextMesh.java deleted file mode 100644 index dac8061..0000000 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/TextMesh.java +++ /dev/null @@ -1,50 +0,0 @@ -package nl.andrewl.aos2_client.render.gui.font; - -import org.joml.Vector2f; -import org.lwjgl.BufferUtils; - -import java.awt.*; -import java.nio.ByteBuffer; - -import static org.lwjgl.opengl.GL46.*; -import static org.lwjgl.stb.STBEasyFont.stb_easy_font_print; - -public class TextMesh { - private final int vbo; - private final int vao; - private int quadCount; - - private String text; - private final Vector2f position; - - public TextMesh() { - this.vbo = glGenBuffers(); - this.vao = glGenVertexArrays(); - this.position = new Vector2f(); - this.text = "Hello world"; - - glBindVertexArray(vao); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, false, 7, 0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, false, 7, 3); - } - - public void updateMesh() { - ByteBuffer colorBuffer = ByteBuffer.allocate(4); - colorBuffer.putInt(Color.WHITE.getRGB()); - colorBuffer.flip(); - ByteBuffer vertexBuffer = BufferUtils.createByteBuffer(text.length() * 500); - quadCount = stb_easy_font_print(position.x, position.y, text, colorBuffer, vertexBuffer); - vertexBuffer.flip(); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, vertexBuffer, GL_STATIC_DRAW); - } - - public void draw() { - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBindVertexArray(vao); - - glDrawArrays(GL_QUADS, 0, quadCount); - } -} diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/TextMeshCreator.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/TextMeshCreator.java deleted file mode 100644 index 940dee7..0000000 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/TextMeshCreator.java +++ /dev/null @@ -1,133 +0,0 @@ -package nl.andrewl.aos2_client.render.gui.font; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -public class TextMeshCreator { - - protected static final double LINE_HEIGHT = 0.03f; - protected static final int SPACE_ASCII = 32; - - private final MetaFile metaData; - - protected TextMeshCreator(File metaFile, float aspectRatio) { - metaData = new MetaFile(metaFile, aspectRatio); - } - - protected TextMeshData createTextMesh(GUIText text) { - List lines = createStructure(text); - return createQuadVertices(text, lines); - } - - private List createStructure(GUIText text) { - char[] chars = text.getTextString().toCharArray(); - List lines = new ArrayList(); - Line currentLine = new Line(metaData.getSpaceWidth(), text.getFontSize(), text.getMaxLineSize()); - Word currentWord = new Word(text.getFontSize()); - for (char c : chars) { - if ((int) c == SPACE_ASCII) { - boolean added = currentLine.attemptToAddWord(currentWord); - if (!added) { - lines.add(currentLine); - currentLine = new Line(metaData.getSpaceWidth(), text.getFontSize(), text.getMaxLineSize()); - currentLine.attemptToAddWord(currentWord); - } - currentWord = new Word(text.getFontSize()); - continue; - } - Character character = metaData.getCharacter(c); - currentWord.addCharacter(character); - } - completeStructure(lines, currentLine, currentWord, text); - return lines; - } - - private void completeStructure(List lines, Line currentLine, Word currentWord, GUIText text) { - boolean added = currentLine.attemptToAddWord(currentWord); - if (!added) { - lines.add(currentLine); - currentLine = new Line(metaData.getSpaceWidth(), text.getFontSize(), text.getMaxLineSize()); - currentLine.attemptToAddWord(currentWord); - } - lines.add(currentLine); - } - - private TextMeshData createQuadVertices(GUIText text, List lines) { - text.setNumberOfLines(lines.size()); - double curserX = 0f; - double curserY = 0f; - List vertices = new ArrayList(); - List textureCoords = new ArrayList(); - for (Line line : lines) { - if (text.isCentered()) { - curserX = (line.getMaxLength() - line.getLineLength()) / 2; - } - for (Word word : line.getWords()) { - for (Character letter : word.getCharacters()) { - addVerticesForCharacter(curserX, curserY, letter, text.getFontSize(), vertices); - addTexCoords(textureCoords, letter.getxTextureCoord(), letter.getyTextureCoord(), - letter.getXMaxTextureCoord(), letter.getYMaxTextureCoord()); - curserX += letter.getxAdvance() * text.getFontSize(); - } - curserX += metaData.getSpaceWidth() * text.getFontSize(); - } - curserX = 0; - curserY += LINE_HEIGHT * text.getFontSize(); - } - return new TextMeshData(listToArray(vertices), listToArray(textureCoords)); - } - - private void addVerticesForCharacter(double curserX, double curserY, Character character, double fontSize, - List vertices) { - double x = curserX + (character.getxOffset() * fontSize); - double y = curserY + (character.getyOffset() * fontSize); - double maxX = x + (character.getSizeX() * fontSize); - double maxY = y + (character.getSizeY() * fontSize); - double properX = (2 * x) - 1; - double properY = (-2 * y) + 1; - double properMaxX = (2 * maxX) - 1; - double properMaxY = (-2 * maxY) + 1; - addVertices(vertices, properX, properY, properMaxX, properMaxY); - } - - private static void addVertices(List vertices, double x, double y, double maxX, double maxY) { - vertices.add((float) x); - vertices.add((float) y); - vertices.add((float) x); - vertices.add((float) maxY); - vertices.add((float) maxX); - vertices.add((float) maxY); - vertices.add((float) maxX); - vertices.add((float) maxY); - vertices.add((float) maxX); - vertices.add((float) y); - vertices.add((float) x); - vertices.add((float) y); - } - - private static void addTexCoords(List texCoords, double x, double y, double maxX, double maxY) { - texCoords.add((float) x); - texCoords.add((float) y); - texCoords.add((float) x); - texCoords.add((float) maxY); - texCoords.add((float) maxX); - texCoords.add((float) maxY); - texCoords.add((float) maxX); - texCoords.add((float) maxY); - texCoords.add((float) maxX); - texCoords.add((float) y); - texCoords.add((float) x); - texCoords.add((float) y); - } - - - private static float[] listToArray(List listOfFloats) { - float[] array = new float[listOfFloats.size()]; - for (int i = 0; i < array.length; i++) { - array[i] = listOfFloats.get(i); - } - return array; - } - -} diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/TextMeshData.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/TextMeshData.java deleted file mode 100644 index 54412b2..0000000 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/TextMeshData.java +++ /dev/null @@ -1,30 +0,0 @@ -package nl.andrewl.aos2_client.render.gui.font; - -/** - * Stores the vertex data for all the quads on which a text will be rendered. - * @author Karl - * - */ -public class TextMeshData { - - private final float[] vertexPositions; - private final float[] textureCoords; - - protected TextMeshData(float[] vertexPositions, float[] textureCoords){ - this.vertexPositions = vertexPositions; - this.textureCoords = textureCoords; - } - - public float[] getVertexPositions() { - return vertexPositions; - } - - public float[] getTextureCoords() { - return textureCoords; - } - - public int getVertexCount() { - return vertexPositions.length/2; - } - -} diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/Word.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/Word.java deleted file mode 100644 index 46d88b3..0000000 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/font/Word.java +++ /dev/null @@ -1,48 +0,0 @@ -package nl.andrewl.aos2_client.render.gui.font; - -import java.util.ArrayList; -import java.util.List; - -/** - * During the loading of a text this represents one word in the text. - * @author Karl - * - */ -public class Word { - - private final List characters = new ArrayList<>(); - private double width = 0; - private final double fontSize; - - /** - * Create a new empty word. - * @param fontSize - the font size of the text which this word is in. - */ - protected Word(double fontSize){ - this.fontSize = fontSize; - } - - /** - * Adds a character to the end of the current word and increases the screen-space width of the word. - * @param character - the character to be added. - */ - protected void addCharacter(Character character){ - characters.add(character); - width += character.getxAdvance() * fontSize; - } - - /** - * @return The list of characters in the word. - */ - protected List getCharacters(){ - return characters; - } - - /** - * @return The width of the word in terms of screen size. - */ - protected double getWordWidth(){ - return width; - } - -} diff --git a/client/src/main/java/nl/andrewl/aos2_client/sound/SoundManager.java b/client/src/main/java/nl/andrewl/aos2_client/sound/SoundManager.java index dea7f47..60a19e3 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/sound/SoundManager.java +++ b/client/src/main/java/nl/andrewl/aos2_client/sound/SoundManager.java @@ -79,6 +79,7 @@ public class SoundManager { load("hurt_3", "sound/m_hurt_3.wav"); load("block_break_1", "sound/m_block_break_1.wav"); load("block_place_1", "sound/m_block_place_1.wav"); + load("chat", "sound/chat.wav"); } public void load(String name, String resource) { diff --git a/client/src/main/resources/sound/chat.wav b/client/src/main/resources/sound/chat.wav new file mode 100644 index 0000000000000000000000000000000000000000..7b6c766a263b8ec851f2d6dad8b0e20e511370dd GIT binary patch literal 51244 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 SODoL&W`}cIO|(Bu`~L#0yYM3b literal 0 HcmV?d00001 diff --git a/client/src/main/resources/text/jetbrains-mono.fnt b/client/src/main/resources/text/jetbrains-mono.fnt deleted file mode 100644 index 0a7414b..0000000 --- a/client/src/main/resources/text/jetbrains-mono.fnt +++ /dev/null @@ -1,102 +0,0 @@ -info face="JetBrains Mono Regular" size=74 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=3,3,3,3 spacing=0,0 -common lineHeight=105 base=76 scaleW=512 scaleH=512 pages=1 packed=0 -page id=0 file="jetbrains-mono.png" -chars count=97 -char id=0 x=0 y=444 width=48 height=26 xoffset=-1 yoffset=36 xadvance=50 page=0 chnl=0 -char id=10 x=0 y=0 width=0 height=0 xoffset=-3 yoffset=0 xadvance=6 page=0 chnl=0 -char id=32 x=0 y=0 width=0 height=0 xoffset=-3 yoffset=0 xadvance=50 page=0 chnl=0 -char id=33 x=490 y=82 width=18 height=62 xoffset=13 yoffset=18 xadvance=50 page=0 chnl=0 -char id=34 x=457 y=396 width=29 height=29 xoffset=8 yoffset=18 xadvance=50 page=0 chnl=0 -char id=36 x=0 y=0 width=41 height=82 xoffset=2 yoffset=7 xadvance=50 page=0 chnl=0 -char id=37 x=312 y=151 width=49 height=62 xoffset=-2 yoffset=18 xadvance=50 page=0 chnl=0 -char id=38 x=368 y=82 width=44 height=63 xoffset=1 yoffset=17 xadvance=50 page=0 chnl=0 -char id=39 x=486 y=396 width=14 height=29 xoffset=15 yoffset=18 xadvance=50 page=0 chnl=0 -char id=40 x=77 y=0 width=29 height=78 xoffset=10 yoffset=10 xadvance=50 page=0 chnl=0 -char id=41 x=106 y=0 width=29 height=78 xoffset=5 yoffset=10 xadvance=50 page=0 chnl=0 -char id=42 x=290 y=396 width=46 height=45 xoffset=-1 yoffset=27 xadvance=50 page=0 chnl=0 -char id=43 x=336 y=396 width=42 height=41 xoffset=1 yoffset=31 xadvance=50 page=0 chnl=0 -char id=44 x=483 y=0 width=22 height=30 xoffset=9 yoffset=61 xadvance=50 page=0 chnl=0 -char id=45 x=179 y=444 width=31 height=12 xoffset=7 yoffset=45 xadvance=50 page=0 chnl=0 -char id=46 x=137 y=444 width=19 height=19 xoffset=13 yoffset=61 xadvance=50 page=0 chnl=0 -char id=47 x=149 y=0 width=40 height=77 xoffset=2 yoffset=10 xadvance=50 page=0 chnl=0 -char id=48 x=328 y=82 width=40 height=63 xoffset=2 yoffset=17 xadvance=50 page=0 chnl=0 -char id=49 x=460 y=274 width=40 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=50 x=77 y=151 width=40 height=62 xoffset=2 yoffset=17 xadvance=50 page=0 chnl=0 -char id=51 x=117 y=151 width=39 height=62 xoffset=2 yoffset=18 xadvance=50 page=0 chnl=0 -char id=52 x=0 y=335 width=38 height=61 xoffset=2 yoffset=18 xadvance=50 page=0 chnl=0 -char id=53 x=156 y=151 width=39 height=62 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=54 x=195 y=151 width=42 height=62 xoffset=1 yoffset=18 xadvance=50 page=0 chnl=0 -char id=55 x=38 y=335 width=42 height=61 xoffset=2 yoffset=18 xadvance=50 page=0 chnl=0 -char id=56 x=286 y=82 width=42 height=63 xoffset=1 yoffset=17 xadvance=50 page=0 chnl=0 -char id=57 x=237 y=151 width=42 height=62 xoffset=1 yoffset=17 xadvance=50 page=0 chnl=0 -char id=58 x=483 y=151 width=19 height=49 xoffset=13 yoffset=31 xadvance=50 page=0 chnl=0 -char id=59 x=477 y=213 width=23 height=60 xoffset=9 yoffset=31 xadvance=50 page=0 chnl=0 -char id=60 x=0 y=0 width=0 height=0 xoffset=3 yoffset=0 xadvance=50 page=0 chnl=0 -char id=61 x=418 y=396 width=39 height=30 xoffset=3 yoffset=36 xadvance=50 page=0 chnl=0 -char id=62 x=165 y=335 width=110 height=50 xoffset=3 yoffset=27 xadvance=50 page=0 chnl=0 -char id=63 x=279 y=151 width=33 height=62 xoffset=6 yoffset=18 xadvance=50 page=0 chnl=0 -char id=64 x=396 y=0 width=45 height=75 xoffset=0 yoffset=17 xadvance=50 page=0 chnl=0 -char id=65 x=361 y=151 width=44 height=61 xoffset=0 yoffset=18 xadvance=50 page=0 chnl=0 -char id=66 x=405 y=151 width=40 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=67 x=129 y=82 width=39 height=63 xoffset=3 yoffset=17 xadvance=50 page=0 chnl=0 -char id=68 x=445 y=151 width=38 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=69 x=0 y=213 width=38 height=61 xoffset=4 yoffset=18 xadvance=50 page=0 chnl=0 -char id=70 x=38 y=213 width=38 height=61 xoffset=4 yoffset=18 xadvance=50 page=0 chnl=0 -char id=71 x=168 y=82 width=39 height=63 xoffset=3 yoffset=17 xadvance=50 page=0 chnl=0 -char id=72 x=76 y=213 width=38 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=73 x=114 y=213 width=36 height=61 xoffset=4 yoffset=18 xadvance=50 page=0 chnl=0 -char id=74 x=412 y=82 width=40 height=62 xoffset=0 yoffset=18 xadvance=50 page=0 chnl=0 -char id=75 x=150 y=213 width=42 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=76 x=192 y=213 width=38 height=61 xoffset=6 yoffset=18 xadvance=50 page=0 chnl=0 -char id=77 x=230 y=213 width=40 height=61 xoffset=2 yoffset=18 xadvance=50 page=0 chnl=0 -char id=78 x=270 y=213 width=38 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=79 x=207 y=82 width=38 height=63 xoffset=3 yoffset=17 xadvance=50 page=0 chnl=0 -char id=80 x=308 y=213 width=41 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=81 x=356 y=0 width=40 height=75 xoffset=2 yoffset=17 xadvance=50 page=0 chnl=0 -char id=82 x=349 y=213 width=41 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=83 x=245 y=82 width=41 height=63 xoffset=2 yoffset=17 xadvance=50 page=0 chnl=0 -char id=84 x=390 y=213 width=43 height=61 xoffset=1 yoffset=18 xadvance=50 page=0 chnl=0 -char id=85 x=452 y=82 width=38 height=62 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=86 x=433 y=213 width=44 height=61 xoffset=0 yoffset=18 xadvance=50 page=0 chnl=0 -char id=87 x=0 y=274 width=48 height=61 xoffset=-2 yoffset=18 xadvance=50 page=0 chnl=0 -char id=88 x=48 y=274 width=46 height=61 xoffset=-1 yoffset=18 xadvance=50 page=0 chnl=0 -char id=89 x=94 y=274 width=46 height=61 xoffset=-1 yoffset=18 xadvance=50 page=0 chnl=0 -char id=90 x=140 y=274 width=39 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=91 x=229 y=0 width=25 height=76 xoffset=12 yoffset=11 xadvance=50 page=0 chnl=0 -char id=92 x=189 y=0 width=40 height=77 xoffset=2 yoffset=10 xadvance=50 page=0 chnl=0 -char id=93 x=254 y=0 width=25 height=76 xoffset=8 yoffset=11 xadvance=50 page=0 chnl=0 -char id=94 x=378 y=396 width=40 height=36 xoffset=2 yoffset=18 xadvance=50 page=0 chnl=0 -char id=95 x=0 y=82 width=129 height=69 xoffset=1 yoffset=18 xadvance=50 page=0 chnl=0 -char id=96 x=156 y=444 width=23 height=17 xoffset=8 yoffset=14 xadvance=50 page=0 chnl=0 -char id=97 x=275 y=335 width=40 height=49 xoffset=1 yoffset=31 xadvance=50 page=0 chnl=0 -char id=98 x=0 y=151 width=39 height=62 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=99 x=315 y=335 width=39 height=49 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0 -char id=100 x=39 y=151 width=38 height=62 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=101 x=354 y=335 width=39 height=49 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0 -char id=102 x=179 y=274 width=42 height=61 xoffset=1 yoffset=18 xadvance=50 page=0 chnl=0 -char id=103 x=221 y=274 width=38 height=61 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0 -char id=104 x=259 y=274 width=38 height=61 xoffset=3 yoffset=18 xadvance=50 page=0 chnl=0 -char id=105 x=441 y=0 width=42 height=65 xoffset=3 yoffset=14 xadvance=50 page=0 chnl=0 -char id=106 x=41 y=0 width=36 height=78 xoffset=2 yoffset=14 xadvance=50 page=0 chnl=0 -char id=107 x=297 y=274 width=41 height=61 xoffset=4 yoffset=18 xadvance=50 page=0 chnl=0 -char id=108 x=338 y=274 width=45 height=61 xoffset=-1 yoffset=18 xadvance=50 page=0 chnl=0 -char id=109 x=0 y=396 width=42 height=48 xoffset=1 yoffset=31 xadvance=50 page=0 chnl=0 -char id=110 x=471 y=335 width=38 height=48 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0 -char id=111 x=393 y=335 width=39 height=49 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0 -char id=112 x=383 y=274 width=39 height=61 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0 -char id=113 x=422 y=274 width=38 height=61 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0 -char id=114 x=42 y=396 width=38 height=48 xoffset=5 yoffset=31 xadvance=50 page=0 chnl=0 -char id=115 x=432 y=335 width=39 height=49 xoffset=3 yoffset=31 xadvance=50 page=0 chnl=0 -char id=116 x=124 y=335 width=41 height=59 xoffset=1 yoffset=20 xadvance=50 page=0 chnl=0 -char id=117 x=80 y=396 width=38 height=48 xoffset=3 yoffset=32 xadvance=50 page=0 chnl=0 -char id=118 x=118 y=396 width=44 height=47 xoffset=0 yoffset=32 xadvance=50 page=0 chnl=0 -char id=119 x=162 y=396 width=46 height=47 xoffset=-1 yoffset=32 xadvance=50 page=0 chnl=0 -char id=120 x=208 y=396 width=44 height=47 xoffset=0 yoffset=32 xadvance=50 page=0 chnl=0 -char id=121 x=80 y=335 width=44 height=60 xoffset=0 yoffset=32 xadvance=50 page=0 chnl=0 -char id=122 x=252 y=396 width=38 height=47 xoffset=3 yoffset=32 xadvance=50 page=0 chnl=0 -char id=123 x=279 y=0 width=39 height=76 xoffset=2 yoffset=11 xadvance=50 page=0 chnl=0 -char id=124 x=135 y=0 width=14 height=77 xoffset=15 yoffset=10 xadvance=50 page=0 chnl=0 -char id=125 x=318 y=0 width=38 height=76 xoffset=4 yoffset=11 xadvance=50 page=0 chnl=0 -char id=126 x=96 y=444 width=41 height=22 xoffset=2 yoffset=38 xadvance=50 page=0 chnl=0 -char id=127 x=48 y=444 width=48 height=25 xoffset=-1 yoffset=36 xadvance=50 page=0 chnl=0 -kernings count=0 diff --git a/client/src/main/resources/text/jetbrains-mono.png b/client/src/main/resources/text/jetbrains-mono.png deleted file mode 100644 index 816b56714bc94cc0d6a30d60f5c9b21e7322e6cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35287 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 ace-of-shades-2 nl.andrewl - 1.0.0 + 1.1.0 4.0.0 diff --git a/core/src/main/java/nl/andrewl/aos_core/Net.java b/core/src/main/java/nl/andrewl/aos_core/Net.java index 2c5c0b4..1be6b3c 100644 --- a/core/src/main/java/nl/andrewl/aos_core/Net.java +++ b/core/src/main/java/nl/andrewl/aos_core/Net.java @@ -46,6 +46,8 @@ public final class Net { serializer.registerType(17, ProjectileMessage.class); serializer.registerType(18, ClientHealthMessage.class); serializer.registerType(19, BlockColorMessage.class); + serializer.registerType(20, ChatMessage.class); + serializer.registerType(21, ChatWrittenMessage.class); } public static ExtendedDataInputStream getInputStream(InputStream in) { diff --git a/core/src/main/java/nl/andrewl/aos_core/net/client/ChatMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/client/ChatMessage.java new file mode 100644 index 0000000..a29140c --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/ChatMessage.java @@ -0,0 +1,21 @@ +package nl.andrewl.aos_core.net.client; + +import nl.andrewl.record_net.Message; + +/** + * A message that's sent from the server to clients about a message that has + * appeared in chat. + */ +public record ChatMessage( + long sentAt, + String author, + String message +) implements Message { + public static ChatMessage announce(String message) { + return new ChatMessage(System.currentTimeMillis(), "_ANNOUNCE", message); + } + + public static ChatMessage privateMessage(String message) { + return new ChatMessage(System.currentTimeMillis(), "_PRIVATE", message); + } +} diff --git a/core/src/main/java/nl/andrewl/aos_core/net/client/ChatWrittenMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/client/ChatWrittenMessage.java new file mode 100644 index 0000000..edde8b0 --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/ChatWrittenMessage.java @@ -0,0 +1,10 @@ +package nl.andrewl.aos_core.net.client; + +import nl.andrewl.record_net.Message; + +/** + * A message sent by clients when they write a chat message for others to see. + */ +public record ChatWrittenMessage( + String message +) implements Message {} diff --git a/pom.xml b/pom.xml index 85024e0..a3543c7 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ nl.andrewl ace-of-shades-2 pom - 1.0.0 + 1.1.0 core server diff --git a/server/pom.xml b/server/pom.xml index dbb974e..2f7b82b 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -5,7 +5,7 @@ ace-of-shades-2 nl.andrewl - 1.0.0 + 1.1.0 4.0.0 diff --git a/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java b/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java index 5dbe282..1f9f1c6 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java +++ b/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java @@ -3,7 +3,10 @@ package nl.andrewl.aos2_server; import nl.andrewl.aos2_server.model.ServerPlayer; import nl.andrewl.aos_core.Net; import nl.andrewl.aos_core.model.Team; -import nl.andrewl.aos_core.model.item.*; +import nl.andrewl.aos_core.model.item.BlockItemStack; +import nl.andrewl.aos_core.model.item.Gun; +import nl.andrewl.aos_core.model.item.GunItemStack; +import nl.andrewl.aos_core.model.item.ItemStack; import nl.andrewl.aos_core.net.client.*; import nl.andrewl.aos_core.net.connect.DatagramInit; import nl.andrewl.record_net.Message; @@ -37,10 +40,14 @@ public class PlayerManager { log.info("Registered player \"{}\" with id {}", player.getUsername(), player.getId()); players.put(player.getId(), player); clientHandlers.put(player.getId(), handler); + String joinMessage; Team team = findBestTeamForNewPlayer(); if (team != null) { player.setTeam(team); log.info("Player \"{}\" joined the \"{}\" team.", player.getUsername(), team.getName()); + joinMessage = String.format("%s joined the %s team.", username, team.getName()); + } else { + joinMessage = username + " joined the game."; } player.setPosition(getBestSpawnPoint(player)); // Tell all other players that this one has joined. @@ -53,6 +60,7 @@ public class PlayerManager { player.getInventory().getSelectedItemStack().getType().getId(), player.getInventory().getSelectedBlockValue() ), player); + broadcastTcpMessageToAllBut(ChatMessage.announce(joinMessage), player); return player; } @@ -63,6 +71,7 @@ public class PlayerManager { clientHandlers.remove(player.getId()); broadcastTcpMessage(new PlayerLeaveMessage(player.getId())); log.info("Deregistered player \"{}\" with id {}", player.getUsername(), player.getId()); + broadcastTcpMessage(ChatMessage.announce(player.getUsername() + " left the game.")); } public synchronized void deregisterAll() { @@ -163,6 +172,13 @@ public class PlayerManager { resupply(player); broadcastUdpMessage(player.getUpdateMessage(System.currentTimeMillis())); broadcastUdpMessage(new SoundMessage("death", 1, deathPosition)); + String deathMessage; + if (killedBy != null) { + deathMessage = player.getUsername() + " was killed by " + killedBy.getUsername() + "."; + } else { + deathMessage = player.getUsername() + " died."; + } + broadcastTcpMessage(ChatMessage.announce(deathMessage)); // TODO: Team points or something. } @@ -181,6 +197,7 @@ public class PlayerManager { handler.sendTcpMessage(new ItemStackMessage(i, stack)); } handler.sendDatagramPacket(new ClientHealthMessage(player.getHealth())); + handler.sendTcpMessage(ChatMessage.privateMessage("You've been resupplied at your team base.")); } public void handleUdpInit(DatagramInit init, DatagramPacket packet) {