From adf34b47421cf2d49416fca4fed8e0e94b99ca6f Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Wed, 12 Aug 2020 19:23:28 +0200 Subject: [PATCH 01/17] Drop-in palette lookup post-processing Directly usable for devs --- .../minestom/demo/largeframebuffers/Demo.java | 6 +- .../largeframebuffers/OpenGLRendering.java | 36 +-- .../map/framebuffers/MapColorRenderer.java | 257 ++++++++++++++++++ src/lwjgl/resources/shaders/fragment.glsl | 23 +- .../shaders/mapcolorconvert.fragment.glsl | 30 ++ .../shaders/mapcolorconvert.vertex.glsl | 10 + 6 files changed, 312 insertions(+), 50 deletions(-) create mode 100644 src/lwjgl/java/net/minestom/server/map/framebuffers/MapColorRenderer.java create mode 100644 src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl create mode 100644 src/lwjgl/resources/shaders/mapcolorconvert.vertex.glsl diff --git a/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java b/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java index 4607b36bb..8e6208f01 100644 --- a/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java +++ b/src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java @@ -16,6 +16,7 @@ import net.minestom.server.map.MapColors; import net.minestom.server.map.framebuffers.LargeDirectFramebuffer; import net.minestom.server.map.framebuffers.LargeGLFWFramebuffer; import net.minestom.server.map.framebuffers.LargeGraphics2DFramebuffer; +import net.minestom.server.map.framebuffers.MapColorRenderer; import net.minestom.server.network.packet.server.play.MapDataPacket; import net.minestom.server.utils.Position; import net.minestom.server.utils.time.TimeUnit; @@ -43,17 +44,16 @@ public class Demo { LargeGraphics2DFramebuffer graphics2DFramebuffer = new LargeGraphics2DFramebuffer(512, 512); LargeGLFWFramebuffer glfwFramebuffer = new LargeGLFWFramebuffer(512, 512); - glfwFramebuffer.useMapColors(); - glfwFramebuffer.changeRenderingThreadToCurrent(); OpenGLRendering.init(); + MapColorRenderer renderer = new MapColorRenderer(glfwFramebuffer, OpenGLRendering::render); glfwFramebuffer.unbindContextFromThread(); // renderingLoop(0, directFramebuffer, Demo::directRendering); // renderingLoop(101, graphics2DFramebuffer, Demo::graphics2DRendering); renderingLoop(201, glfwFramebuffer, f -> {}); - glfwFramebuffer.setupRenderLoop(15, TimeUnit.MILLISECOND, () -> OpenGLRendering.render(glfwFramebuffer)); + glfwFramebuffer.setupRenderLoop(15, TimeUnit.MILLISECOND, renderer); for (int x = -2; x <= 2; x++) { for (int z = -2; z <= 2; z++) { diff --git a/src/lwjgl/java/net/minestom/demo/largeframebuffers/OpenGLRendering.java b/src/lwjgl/java/net/minestom/demo/largeframebuffers/OpenGLRendering.java index a4bb38c5d..6e7b3f6d9 100644 --- a/src/lwjgl/java/net/minestom/demo/largeframebuffers/OpenGLRendering.java +++ b/src/lwjgl/java/net/minestom/demo/largeframebuffers/OpenGLRendering.java @@ -1,14 +1,14 @@ package net.minestom.demo.largeframebuffers; -import net.minestom.server.map.MapColors; -import net.minestom.server.map.framebuffers.LargeGLFWFramebuffer; import org.joml.Matrix4f; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GLUtil; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; -import java.io.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.util.stream.Collectors; @@ -64,13 +64,11 @@ public final class OpenGLRendering { private static int projectionUniform; private static int viewUniform; private static int modelUniform; - private static int paletteTexture; private static int boxTexture; static void init() { GLUtil.setupDebugMessageCallback(); - paletteTexture = loadTexture("palette"); boxTexture = loadTexture("box"); vbo = glGenBuffers(); @@ -81,15 +79,6 @@ public final class OpenGLRendering { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, false, VERTEX_SIZE, 0); // position - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, false, VERTEX_SIZE, 3*4); // color - - glBindBuffer(GL_ARRAY_BUFFER, 0); - // prepare matrices and shader renderShader = glCreateProgram(); projectionMatrix = new Matrix4f().setPerspective((float) (Math.PI/4f), 1f, 0.001f, 100f); @@ -107,8 +96,6 @@ public final class OpenGLRendering { projectionUniform = glGetUniformLocation(renderShader, "projection"); viewUniform = glGetUniformLocation(renderShader, "view"); modelUniform = glGetUniformLocation(renderShader, "model"); - int paletteSizeUniform = glGetUniformLocation(renderShader, "paletteSize"); - int paletteUniform = glGetUniformLocation(renderShader, "palette"); int boxUniform = glGetUniformLocation(renderShader, "box"); glUseProgram(renderShader); { @@ -116,9 +103,8 @@ public final class OpenGLRendering { uploadMatrix(viewUniform, viewMatrix); glUniform1i(boxUniform, 0); // texture unit 0 - glUniform1i(paletteUniform, 1); // texture unit 1 - glUniform1f(paletteSizeUniform, 236); } + glUseProgram(0); } private static int loadTexture(String filename) { @@ -166,7 +152,6 @@ public final class OpenGLRendering { private static int createShader(String filename, int type) { int shader = glCreateShader(type); try(BufferedReader reader = new BufferedReader(new InputStreamReader(OpenGLRendering.class.getResourceAsStream(filename)))) { - StringBuffer buffer = new StringBuffer(); String source = reader.lines().collect(Collectors.joining("\n")); glShaderSource(shader, source); glCompileShader(shader); @@ -180,7 +165,7 @@ public final class OpenGLRendering { private static int frame = 0; - static void render(LargeGLFWFramebuffer framebuffer) { + static void render() { if(frame % 100 == 0) { long time = System.currentTimeMillis(); long dt = time-lastTime; @@ -192,23 +177,24 @@ public final class OpenGLRendering { frame++; - glClearColor(MapColors.COLOR_BLACK.multiply53()/255.0f, 0f, 0f, 1f); + glClearColor(0f, 0f, 0f, 1f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); modelMatrix.rotateY((float) (Math.PI/60f)); - // TODO: render texture glUseProgram(renderShader); { - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, paletteTexture); - glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, boxTexture); uploadMatrix(modelUniform, modelMatrix); glBindBuffer(GL_ARRAY_BUFFER, vbo); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, false, VERTEX_SIZE, 0); // position + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, false, VERTEX_SIZE, 3*4); // color + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); glDrawElements(GL_TRIANGLES, indices.length, GL_UNSIGNED_INT, 0); } diff --git a/src/lwjgl/java/net/minestom/server/map/framebuffers/MapColorRenderer.java b/src/lwjgl/java/net/minestom/server/map/framebuffers/MapColorRenderer.java new file mode 100644 index 000000000..68d3fc658 --- /dev/null +++ b/src/lwjgl/java/net/minestom/server/map/framebuffers/MapColorRenderer.java @@ -0,0 +1,257 @@ +package net.minestom.server.map.framebuffers; + +import org.lwjgl.BufferUtils; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.ByteBuffer; +import java.util.stream.Collectors; + +import static org.lwjgl.opengl.GL30.*; + +/** + * Helper class designed to help OpenGL users to convert their RGB values to map colors inside a post processing pass + * with a shader provided by Minestom. + * + * When rendering to a {@link GLFWFramebuffer} or a {@link LargeGLFWFramebuffer}, wrap your rendering in a MapColorRenderer to render to the GLFW with map colors. + * + * {@link MapColorRenderer} sets up an OpenGL framebuffer with the size of the underlying framebuffer and renders to it. + * The initialization of the framebuffer is done in the constructor. + * Therefore, the constructor call should be done inside the thread linked to the OpenGL context. The context can + * be moved through {@link GLFWCapableBuffer#changeRenderingThreadToCurrent()} and {@link GLFWCapableBuffer#unbindContextFromThread()} + * + *
+ * Resources created in constructor are: + *
  • + * + * + * + * + * + * + * + *
  • + * + * The constructor also puts the given buffer in map color mode. + */ +public class MapColorRenderer implements Runnable { + + private final int fboID; + private final GLFWCapableBuffer framebuffer; + private final Runnable renderCode; + private final int colorTextureID; + private final int width; + private final int height; + private final int renderShader; + private final int screenQuadIndices; + private int paletteTexture; + private float paletteSize; + private final int screenQuadVAO; + + public MapColorRenderer(GLFWCapableBuffer framebuffer, Runnable renderCode) { + this(framebuffer, renderCode, MapColorRenderer.defaultFramebuffer(framebuffer.width(), framebuffer.height())); + } + + public MapColorRenderer(GLFWCapableBuffer framebuffer, Runnable renderCode, FboInitialization fboInitialization) { + this.framebuffer = framebuffer; + this.framebuffer.useMapColors(); + + this.renderCode = renderCode; + this.width = framebuffer.width(); + this.height = framebuffer.height(); + + this.fboID = glGenFramebuffers(); + glBindFramebuffer(GL_FRAMEBUFFER, fboID); + this.colorTextureID = fboInitialization.initFbo(fboID); + + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + throw new RuntimeException("Framebuffer is not complete!"); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // create post-process shader + this.renderShader = glCreateProgram(); + int vertexShader = createShader("/shaders/mapcolorconvert.vertex.glsl", GL_VERTEX_SHADER); + int fragmentShader = createShader("/shaders/mapcolorconvert.fragment.glsl", GL_FRAGMENT_SHADER); + glAttachShader(renderShader, vertexShader); + glAttachShader(renderShader, fragmentShader); + glLinkProgram(renderShader); + if(glGetProgrami(renderShader, GL_LINK_STATUS) == 0) { + throw new RuntimeException("Link error: "+glGetProgramInfoLog(renderShader)); + } + + loadPalette("palette"); + + // create screen quad VAO + screenQuadVAO = glGenBuffers(); + glBindBuffer(GL_ARRAY_BUFFER, screenQuadVAO); + glBufferData(GL_ARRAY_BUFFER, new float[] { + -1f, -1f, + 1f, -1f, + 1f, 1f, + -1f, 1f + }, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + screenQuadIndices = glGenBuffers(); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, screenQuadIndices); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, new int[] {0,1,2, 2,3,0}, GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + int paletteSizeUniform = glGetUniformLocation(renderShader, "paletteSize"); + int paletteUniform = glGetUniformLocation(renderShader, "palette"); + int frameUniform = glGetUniformLocation(renderShader, "frame"); + + glUseProgram(renderShader); { + glUniform1i(frameUniform, 0); // texture unit 0 + glUniform1i(paletteUniform, 1); // texture unit 1 + glUniform1f(paletteSizeUniform, paletteSize); + } + glUseProgram(0); + } + + private static FboInitialization defaultFramebuffer(int width, int height) { + return fboId -> defaultFramebufferInit(fboId, width, height); + } + + private static int defaultFramebufferInit(int fbo, int width, int height) { + // color + int colorTexture = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, colorTexture); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0L); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // attach to framebuffer + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0); + glBindTexture(GL_TEXTURE_2D, 0); + + // depth + int depthStencilBuffer = glGenRenderbuffers(); + glBindRenderbuffer(GL_RENDERBUFFER, depthStencilBuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilBuffer); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + return colorTexture; + } + + @Override + public void run() { + glViewport(0, 0, width, height); + // run user code inside of framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, fboID); + renderCode.run(); + + // run post processing to display on screen + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClearColor(0f, 0f, 0f, 1f); // 0 on RED channel makes maps use NONE + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_DEPTH_TEST); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, colorTextureID); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, paletteTexture); + + glUseProgram(renderShader); { + // render post processing quad + glBindBuffer(GL_ARRAY_BUFFER, screenQuadVAO); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, false, 2*4, 0); // position + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, screenQuadIndices); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); + }; + + glUseProgram(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); + } + + /** + * Frees OpenGL resources used by this renderer. + * You should NOT render with this renderer after this call. + */ + public void cleanupResources() { + glDeleteFramebuffers(fboID); + glDeleteProgram(renderShader); + glDeleteTextures(paletteTexture); + // TODO: more cleanup + } + + private void loadPalette(String filename) { + int tex = glGenTextures(); + glBindTexture(GL_TEXTURE_2D, tex); + BufferedImage image; + try { + image = ImageIO.read(MapColorRenderer.class.getResourceAsStream("/textures/"+filename+".png")); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Missing image "+filename, e); + } + ByteBuffer pixels = BufferUtils.createByteBuffer(image.getWidth()*image.getHeight()*4); + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + int rgb = image.getRGB(x, y); + int alpha = (rgb >> 24) & 0xFF; + int red = (rgb >> 16) & 0xFF; + int green = (rgb >> 8) & 0xFF; + int blue = rgb & 0xFF; + pixels.put((byte) red); + pixels.put((byte) green); + pixels.put((byte) blue); + pixels.put((byte) alpha); + } + } + pixels.flip(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + // closest neighbor required here, as pixels can have very different rgb values, and interpolation will break palette lookup + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); + + this.paletteTexture = tex; + this.paletteSize = image.getWidth(); + } + + private static int createShader(String filename, int type) { + int shader = glCreateShader(type); + try(BufferedReader reader = new BufferedReader(new InputStreamReader(MapColorRenderer.class.getResourceAsStream(filename)))) { + String source = reader.lines().collect(Collectors.joining("\n")); + glShaderSource(shader, source); + glCompileShader(shader); + } catch (IOException e) { + e.printStackTrace(); + } + return shader; + } + + @FunctionalInterface + public interface FboInitialization { + + /** + * Initializes the given framebuffer + * @param fboId + * @return the texture ID of the color texture, used for post processing. + */ + int initFbo(int fboId); + } + +} + + + diff --git a/src/lwjgl/resources/shaders/fragment.glsl b/src/lwjgl/resources/shaders/fragment.glsl index c17da90c7..587f22199 100644 --- a/src/lwjgl/resources/shaders/fragment.glsl +++ b/src/lwjgl/resources/shaders/fragment.glsl @@ -5,29 +5,8 @@ in vec2 uv; out vec4 fragColor; uniform sampler2D box; -uniform sampler2D palette; -uniform float paletteSize; void main() { vec3 vertexColor = texture(box, uv).rgb; - - - // render in map colors - int closest = 0; - uint closestDistance = uint(2147483647); - for(int i = 4; i < paletteSize; i++) { - vec3 mapColor = texture(palette, vec2((i+0.5f)/paletteSize, 0.0)).rgb; - int dr = int((mapColor.r - vertexColor.r)*255); - int dg = int((mapColor.g - vertexColor.g)*255); - int db = int((mapColor.b - vertexColor.b)*255); - - uint d = uint(dr*dr)+uint(dg*dg)+uint(db*db); - if(d < closestDistance) { - closestDistance = d; - closest = i; - } - } - - fragColor = vec4(closest/255.0, closest/255.0, closest/255.0, 1.0); - //fragColor = vec4(vertexColor, 1.0); + fragColor = vec4(vertexColor, 1.0); } \ No newline at end of file diff --git a/src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl b/src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl new file mode 100644 index 000000000..1eeef10ec --- /dev/null +++ b/src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl @@ -0,0 +1,30 @@ +#version 330 + +in vec2 fragCoords; +out vec4 fragColor; + +uniform sampler2D frame; +uniform sampler2D palette; +uniform float paletteSize; + +void main() { + vec3 fragmentColor = texture(frame, fragCoords).rgb; + + // render in map colors + int closest = 0; + uint closestDistance = uint(2147483647); + for(int i = 4; i < paletteSize; i++) { + vec3 mapColor = texture(palette, vec2((i+0.5f)/paletteSize, 0.0)).rgb; + int dr = int((mapColor.r - fragmentColor.r)*255); + int dg = int((mapColor.g - fragmentColor.g)*255); + int db = int((mapColor.b - fragmentColor.b)*255); + + uint d = uint(dr*dr)+uint(dg*dg)+uint(db*db); + if(d < closestDistance) { + closestDistance = d; + closest = i; + } + } + + fragColor = vec4(closest/255.0, closest/255.0, closest/255.0, 1.0); +} \ No newline at end of file diff --git a/src/lwjgl/resources/shaders/mapcolorconvert.vertex.glsl b/src/lwjgl/resources/shaders/mapcolorconvert.vertex.glsl new file mode 100644 index 000000000..b3d557dbe --- /dev/null +++ b/src/lwjgl/resources/shaders/mapcolorconvert.vertex.glsl @@ -0,0 +1,10 @@ +#version 330 + +layout(location = 0) in vec2 pos; + +out vec2 fragCoords; + +void main() { + fragCoords = (pos+vec2(1.0))/2.0; + gl_Position = vec4(pos, 0.0, 1.0); +} \ No newline at end of file From 5e1689415903aa666a7551faefaea7cab42effca Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Wed, 12 Aug 2020 22:53:29 +0200 Subject: [PATCH 02/17] Moved PaletteGenerator outside of demo package and inside core --- .../largeframebuffers => server/map}/PaletteGenerator.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) rename src/lwjgl/java/net/minestom/{demo/largeframebuffers => server/map}/PaletteGenerator.java (93%) diff --git a/src/lwjgl/java/net/minestom/demo/largeframebuffers/PaletteGenerator.java b/src/lwjgl/java/net/minestom/server/map/PaletteGenerator.java similarity index 93% rename from src/lwjgl/java/net/minestom/demo/largeframebuffers/PaletteGenerator.java rename to src/lwjgl/java/net/minestom/server/map/PaletteGenerator.java index 194e0a07a..9f6e2f413 100644 --- a/src/lwjgl/java/net/minestom/demo/largeframebuffers/PaletteGenerator.java +++ b/src/lwjgl/java/net/minestom/server/map/PaletteGenerator.java @@ -1,6 +1,4 @@ -package net.minestom.demo.largeframebuffers; - -import net.minestom.server.map.MapColors; +package net.minestom.server.map; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; From 37547930e9fccf5f2ac5515efb99e356fd014c42 Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Thu, 13 Aug 2020 13:23:26 +0200 Subject: [PATCH 03/17] Invalid Javadoc HTML broke the build, oops --- prismarine-minecraft-data | 2 +- .../map/framebuffers/MapColorRenderer.java | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/prismarine-minecraft-data b/prismarine-minecraft-data index f81048bc2..5466efe52 160000 --- a/prismarine-minecraft-data +++ b/prismarine-minecraft-data @@ -1 +1 @@ -Subproject commit f81048bc208feab0db9bbb759debb7e7fe426b0c +Subproject commit 5466efe528108c7228ebe737ad5ac70af243e2e7 diff --git a/src/lwjgl/java/net/minestom/server/map/framebuffers/MapColorRenderer.java b/src/lwjgl/java/net/minestom/server/map/framebuffers/MapColorRenderer.java index 68d3fc658..fc977dcc6 100644 --- a/src/lwjgl/java/net/minestom/server/map/framebuffers/MapColorRenderer.java +++ b/src/lwjgl/java/net/minestom/server/map/framebuffers/MapColorRenderer.java @@ -25,15 +25,15 @@ import static org.lwjgl.opengl.GL30.*; * *
    * Resources created in constructor are: - *
  • - *
      Framebuffer
    - *
      Color texture (if default fbo initialization chosen)
    - *
      Depth24 Stencil8 render buffer (if default fbo initialization chosen)
    - *
      Post processing shader program
    - *
      Palette texture
    - *
      Screen quad VAO
    - *
      Screen quad index buffer
    - *
  • + *
      + *
    • Framebuffer
    • + *
    • Color texture (if default fbo initialization chosen)
    • + *
    • Depth24 Stencil8 render buffer (if default fbo initialization chosen)
    • + *
    • Post processing shader program
    • + *
    • Palette texture
    • + *
    • Screen quad VAO
    • + *
    • Screen quad index buffer
    • + *
    * * The constructor also puts the given buffer in map color mode. */ From ce509b2bc61e07d9430b413c6ca8a238a5c13fd3 Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Thu, 13 Aug 2020 13:27:39 +0200 Subject: [PATCH 04/17] Invalid Javadoc HTML broke the build, oops 2 --- .../net/minestom/server/map/framebuffers/MapColorRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lwjgl/java/net/minestom/server/map/framebuffers/MapColorRenderer.java b/src/lwjgl/java/net/minestom/server/map/framebuffers/MapColorRenderer.java index fc977dcc6..fb6bc3284 100644 --- a/src/lwjgl/java/net/minestom/server/map/framebuffers/MapColorRenderer.java +++ b/src/lwjgl/java/net/minestom/server/map/framebuffers/MapColorRenderer.java @@ -23,7 +23,7 @@ import static org.lwjgl.opengl.GL30.*; * Therefore, the constructor call should be done inside the thread linked to the OpenGL context. The context can * be moved through {@link GLFWCapableBuffer#changeRenderingThreadToCurrent()} and {@link GLFWCapableBuffer#unbindContextFromThread()} * - *
    + *
    * Resources created in constructor are: *
      *
    • Framebuffer
    • From c0aec9b8b288674fff02bf2f8ee50d025282f9c1 Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Thu, 13 Aug 2020 13:54:57 +0200 Subject: [PATCH 05/17] Make GLFWCapableBuffer public for devs to extend if wanted --- .../net/minestom/server/map/framebuffers/GLFWCapableBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lwjgl/java/net/minestom/server/map/framebuffers/GLFWCapableBuffer.java b/src/lwjgl/java/net/minestom/server/map/framebuffers/GLFWCapableBuffer.java index 26f236ef7..a40bf0270 100644 --- a/src/lwjgl/java/net/minestom/server/map/framebuffers/GLFWCapableBuffer.java +++ b/src/lwjgl/java/net/minestom/server/map/framebuffers/GLFWCapableBuffer.java @@ -17,7 +17,7 @@ import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.glfw.GLFW.glfwTerminate; import static org.lwjgl.opengl.GL11.*; -abstract class GLFWCapableBuffer { +public abstract class GLFWCapableBuffer { protected final byte[] colors; private final ByteBuffer pixels; From b08c1d6898531c90ad9e8aa43fc925235e3e3787 Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Thu, 13 Aug 2020 15:01:43 +0200 Subject: [PATCH 06/17] Post-processing should vertically flip the output --- src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl b/src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl index 1eeef10ec..889fda41f 100644 --- a/src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl +++ b/src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl @@ -8,6 +8,8 @@ uniform sampler2D palette; uniform float paletteSize; void main() { + vec2 uv = fragCoords; + uv.y = -uv.y; vec3 fragmentColor = texture(frame, fragCoords).rgb; // render in map colors From 48d967b89f459f65323e874b0d30010b8aa471fa Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Thu, 13 Aug 2020 15:06:49 +0200 Subject: [PATCH 07/17] Use vertically flipped UV in shader --- src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl b/src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl index 889fda41f..294792958 100644 --- a/src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl +++ b/src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl @@ -10,7 +10,7 @@ uniform float paletteSize; void main() { vec2 uv = fragCoords; uv.y = -uv.y; - vec3 fragmentColor = texture(frame, fragCoords).rgb; + vec3 fragmentColor = texture(frame, uv).rgb; // render in map colors int closest = 0; From f3414224fec38d2f5c4900a69f090cd6cf6bdece Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Thu, 13 Aug 2020 19:00:19 +0200 Subject: [PATCH 08/17] Added ItemStack callbacks for interactions --- .../net/minestom/server/item/ItemStack.java | 36 +++++++++++++++++++ .../server/listener/AnimationListener.java | 3 ++ .../listener/BlockPlacementListener.java | 10 ++++-- .../server/listener/UseItemListener.java | 1 + 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index 9cddf7904..ffb17503d 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -3,10 +3,13 @@ package net.minestom.server.item; import net.minestom.server.chat.ColoredText; import net.minestom.server.data.Data; import net.minestom.server.data.DataContainer; +import net.minestom.server.entity.Player; import net.minestom.server.item.attribute.ItemAttribute; import net.minestom.server.item.metadata.*; import net.minestom.server.item.rule.VanillaStackingRule; import net.minestom.server.registry.Registries; +import net.minestom.server.utils.BlockPosition; +import net.minestom.server.utils.Direction; import net.minestom.server.utils.NBTUtils; import net.minestom.server.utils.validate.Check; import org.jglrxavpok.hephaistos.nbt.NBTCompound; @@ -613,4 +616,37 @@ public class ItemStack implements DataContainer { } return compound; } + + // Callback events + + /** + * Called when the player right clicks with this item + * + * @param player + * @param hand + */ + public void onRightClick(Player player, Player.Hand hand) { + } + + /** + * Called when the player left clicks with this item + * + * @param player + * @param hand + */ + public void onLeftClick(Player player, Player.Hand hand) { + } + + /** + * Called when the player right clicks with this item on a block + * + * @param player + * @param hand + * @param position + * @param blockFace + * @return true if it prevents normal item use (placing blocks for instance) + */ + public boolean onUseOnBlock(Player player, Player.Hand hand, BlockPosition position, Direction blockFace) { + return false; + } } diff --git a/src/main/java/net/minestom/server/listener/AnimationListener.java b/src/main/java/net/minestom/server/listener/AnimationListener.java index 66cd01a8b..571f2586a 100644 --- a/src/main/java/net/minestom/server/listener/AnimationListener.java +++ b/src/main/java/net/minestom/server/listener/AnimationListener.java @@ -2,12 +2,15 @@ package net.minestom.server.listener; import net.minestom.server.entity.Player; import net.minestom.server.event.player.PlayerHandAnimationEvent; +import net.minestom.server.item.ItemStack; import net.minestom.server.network.packet.client.play.ClientAnimationPacket; public class AnimationListener { public static void animationListener(ClientAnimationPacket packet, Player player) { final Player.Hand hand = packet.hand; + final ItemStack itemStack = player.getItemInHand(hand); + itemStack.onLeftClick(player, hand); PlayerHandAnimationEvent handAnimationEvent = new PlayerHandAnimationEvent(player, hand); player.callCancellableEvent(PlayerHandAnimationEvent.class, handAnimationEvent, () -> { switch (hand) { diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index 82e11a3df..2903aae46 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -20,6 +20,7 @@ import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.network.packet.client.play.ClientPlayerBlockPlacementPacket; import net.minestom.server.utils.BlockPosition; +import net.minestom.server.utils.Direction; import net.minestom.server.utils.chunk.ChunkUtils; import java.util.Set; @@ -31,13 +32,19 @@ public class BlockPlacementListener { final Player.Hand hand = packet.hand; final BlockFace blockFace = packet.blockFace; final BlockPosition blockPosition = packet.blockPosition; + final Direction direction = blockFace.toDirection(); final Instance instance = player.getInstance(); if (instance == null) return; + final ItemStack usedItem = player.getItemInHand(hand); + // Interact at block + final boolean cancel = usedItem.onUseOnBlock(player, hand, blockPosition, direction); PlayerBlockInteractEvent playerBlockInteractEvent = new PlayerBlockInteractEvent(player, blockPosition, hand, blockFace); + playerBlockInteractEvent.setCancelled(cancel); + playerBlockInteractEvent.setBlockingItemUse(cancel); player.callCancellableEvent(PlayerBlockInteractEvent.class, playerBlockInteractEvent, () -> { final CustomBlock customBlock = instance.getCustomBlock(blockPosition); if (customBlock != null) { @@ -54,7 +61,6 @@ public class BlockPlacementListener { } // Check if item at hand is a block - final ItemStack usedItem = hand == Player.Hand.MAIN ? playerInventory.getItemInMainHand() : playerInventory.getItemInOffHand(); final Material material = usedItem.getMaterial(); if (material == Material.AIR) { return; @@ -123,7 +129,7 @@ public class BlockPlacementListener { } } else { // Player didn't try to place a block but interacted with one - PlayerUseItemOnBlockEvent event = new PlayerUseItemOnBlockEvent(player, hand, usedItem, blockPosition, blockFace.toDirection()); + PlayerUseItemOnBlockEvent event = new PlayerUseItemOnBlockEvent(player, hand, usedItem, blockPosition, direction); player.callEvent(PlayerUseItemOnBlockEvent.class, event); refreshChunk = true; } diff --git a/src/main/java/net/minestom/server/listener/UseItemListener.java b/src/main/java/net/minestom/server/listener/UseItemListener.java index 571470e76..bee605a16 100644 --- a/src/main/java/net/minestom/server/listener/UseItemListener.java +++ b/src/main/java/net/minestom/server/listener/UseItemListener.java @@ -16,6 +16,7 @@ public class UseItemListener { final PlayerInventory inventory = player.getInventory(); final Player.Hand hand = packet.hand; final ItemStack itemStack = hand == Player.Hand.MAIN ? inventory.getItemInMainHand() : inventory.getItemInOffHand(); + itemStack.onRightClick(player, hand); PlayerUseItemEvent useItemEvent = new PlayerUseItemEvent(player, hand, itemStack); player.callEvent(PlayerUseItemEvent.class, useItemEvent); From 23e82e0ae252863533b735b7b2d0f0b7288b8f4c Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Thu, 13 Aug 2020 19:12:16 +0200 Subject: [PATCH 09/17] Added ItemStack#onInventoryClick --- .../inventory/click/InventoryClickProcessor.java | 6 ++++++ .../java/net/minestom/server/item/ItemStack.java | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java b/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java index ab5c42e3f..321d474f3 100644 --- a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java +++ b/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java @@ -508,6 +508,12 @@ public class InventoryClickProcessor { slot = slot - inventorySlot + PlayerInventoryUtils.OFFSET; } + { + if (clicked != null) { + clicked.onInventoryClick(player, clickType, slot, isPlayerInventory); + } + } + // Reset the didCloseInventory field // Wait for inventory conditions + events to possibly close the inventory player.UNSAFE_changeDidCloseInventory(false); diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index ffb17503d..80feffcba 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -4,6 +4,7 @@ import net.minestom.server.chat.ColoredText; import net.minestom.server.data.Data; import net.minestom.server.data.DataContainer; import net.minestom.server.entity.Player; +import net.minestom.server.inventory.click.ClickType; import net.minestom.server.item.attribute.ItemAttribute; import net.minestom.server.item.metadata.*; import net.minestom.server.item.rule.VanillaStackingRule; @@ -649,4 +650,18 @@ public class ItemStack implements DataContainer { public boolean onUseOnBlock(Player player, Player.Hand hand, BlockPosition position, Direction blockFace) { return false; } + + /** + * Called when the player click on this item on an inventory + *

      + * Executed before any events + * + * @param player + * @param clickType + * @param slot + * @param playerInventory + */ + public void onInventoryClick(Player player, ClickType clickType, int slot, boolean playerInventory) { + + } } From ef28720496995fd247bb450469203e3edd32cc76 Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Thu, 13 Aug 2020 19:14:07 +0200 Subject: [PATCH 10/17] Call InventoryPreClickEvent even without any inventory condition --- .../inventory/click/InventoryClickProcessor.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java b/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java index 321d474f3..fc8d1dec4 100644 --- a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java +++ b/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java @@ -518,10 +518,8 @@ public class InventoryClickProcessor { // Wait for inventory conditions + events to possibly close the inventory player.UNSAFE_changeDidCloseInventory(false); - final List inventoryConditions = isPlayerInventory ? - player.getInventory().getInventoryConditions() : inventory.getInventoryConditions(); - if (!inventoryConditions.isEmpty()) { - + // PreClickEvent + { InventoryPreClickEvent inventoryPreClickEvent = new InventoryPreClickEvent(player, inventory, slot, clickType, clicked, cursor); player.callEvent(InventoryPreClickEvent.class, inventoryPreClickEvent); cursor = inventoryPreClickEvent.getCursorItem(); @@ -531,6 +529,12 @@ public class InventoryClickProcessor { if (inventoryPreClickEvent.isCancelled()) { clickResult.setRefresh(true); } + } + + // Inventory conditions + final List inventoryConditions = isPlayerInventory ? + player.getInventory().getInventoryConditions() : inventory.getInventoryConditions(); + if (!inventoryConditions.isEmpty()) { for (InventoryCondition inventoryCondition : inventoryConditions) { InventoryConditionResult result = new InventoryConditionResult(clicked, cursor); From 8ecb90f3590afb9440b3a2b5868c24f0c22f27e2 Mon Sep 17 00:00:00 2001 From: Eoghanmc22 Date: Thu, 13 Aug 2020 13:52:59 -0400 Subject: [PATCH 11/17] Rate Limiting --- .../net/minestom/server/MinecraftServer.java | 4 +++ .../server/network/PacketProcessor.java | 3 ++ .../network/player/PlayerConnection.java | 32 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 6c214f604..c8ee70346 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -90,6 +90,10 @@ public class MinecraftServer { @Setter private static boolean fixLighting = true; + //Rate Limiting + @Getter @Setter + private static int rateLimit = 0; + // Networking private static PacketProcessor packetProcessor; private static PacketListenerManager packetListenerManager; diff --git a/src/main/java/net/minestom/server/network/PacketProcessor.java b/src/main/java/net/minestom/server/network/PacketProcessor.java index 250cdfdaa..4fcb91e2b 100644 --- a/src/main/java/net/minestom/server/network/PacketProcessor.java +++ b/src/main/java/net/minestom/server/network/PacketProcessor.java @@ -46,6 +46,9 @@ public class PacketProcessor { channel, c -> new NettyPlayerConnection((SocketChannel) channel.channel()) ); + if (MinecraftServer.getRateLimit() > 0) + playerConnection.getPacketCounter().incrementAndGet(); + final ConnectionState connectionState = playerConnection.getConnectionState(); //if (!printBlackList.contains(id)) { diff --git a/src/main/java/net/minestom/server/network/player/PlayerConnection.java b/src/main/java/net/minestom/server/network/player/PlayerConnection.java index 935109744..6aff7a8bf 100644 --- a/src/main/java/net/minestom/server/network/player/PlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/PlayerConnection.java @@ -3,11 +3,16 @@ package net.minestom.server.network.player; import io.netty.buffer.ByteBuf; import lombok.Getter; import lombok.Setter; +import net.minestom.server.MinecraftServer; +import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.Player; import net.minestom.server.network.ConnectionState; import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.login.LoginDisconnect; +import net.minestom.server.network.packet.server.play.DisconnectPacket; import java.net.SocketAddress; +import java.util.concurrent.atomic.AtomicInteger; /** * A PlayerConnection is an object needed for all created player @@ -27,11 +32,38 @@ public abstract class PlayerConnection { private ConnectionState connectionState; private boolean online; + private static final ColoredText rateLimitKickMessage = ColoredText.of("Too Many Packets"); + + //Connection Stats + @Getter + private final AtomicInteger packetCounter = new AtomicInteger(0); + private short tickCounter = 0; + public PlayerConnection() { this.online = true; this.connectionState = ConnectionState.UNKNOWN; } + public void updateStats() { + tickCounter++; + if (tickCounter % 20 == 0 && tickCounter > 0) { + tickCounter = 0; + int i = packetCounter.get(); + packetCounter.set(0); + if (i > MinecraftServer.getRateLimit()) { + if (connectionState == ConnectionState.LOGIN) { + sendPacket(new LoginDisconnect("Too Many Packets")); + } else { + DisconnectPacket disconnectPacket = new DisconnectPacket(); + disconnectPacket.message = rateLimitKickMessage; + sendPacket(disconnectPacket); + } + disconnect(); + refreshOnline(false); + } + } + } + public abstract void enableCompression(int threshold); /** From 3c64def9f977e4cc42bba0673daa4d6380b2b77e Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Thu, 13 Aug 2020 20:24:40 +0200 Subject: [PATCH 12/17] Updated Hydrazine --- build.gradle | 2 +- .../net/minestom/server/item/ItemStack.java | 24 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/build.gradle b/build.gradle index a054c35f3..6463a754a 100644 --- a/build.gradle +++ b/build.gradle @@ -112,7 +112,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok:1.18.12' // Path finding - api 'com.github.MadMartian:hydrazine-path-finding:1.3.0' + api 'com.github.MadMartian:hydrazine-path-finding:1.3.1' api "org.jetbrains.kotlin:kotlin-stdlib-jdk8" api 'com.github.jglrxavpok:Hephaistos:v1.0.5' diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index 80feffcba..b4b1c6b3a 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -623,8 +623,8 @@ public class ItemStack implements DataContainer { /** * Called when the player right clicks with this item * - * @param player - * @param hand + * @param player the player who used the item + * @param hand the hand used */ public void onRightClick(Player player, Player.Hand hand) { } @@ -632,8 +632,8 @@ public class ItemStack implements DataContainer { /** * Called when the player left clicks with this item * - * @param player - * @param hand + * @param player the player who used the item + * @param hand the hand used */ public void onLeftClick(Player player, Player.Hand hand) { } @@ -641,10 +641,10 @@ public class ItemStack implements DataContainer { /** * Called when the player right clicks with this item on a block * - * @param player - * @param hand - * @param position - * @param blockFace + * @param player the player who used the item + * @param hand the hand used + * @param position the position of the interacted block + * @param blockFace the block face * @return true if it prevents normal item use (placing blocks for instance) */ public boolean onUseOnBlock(Player player, Player.Hand hand, BlockPosition position, Direction blockFace) { @@ -656,10 +656,10 @@ public class ItemStack implements DataContainer { *

      * Executed before any events * - * @param player - * @param clickType - * @param slot - * @param playerInventory + * @param player the player who clicked on the item + * @param clickType the click type + * @param slot the slot clicked + * @param playerInventory true if the click is in the player inventory */ public void onInventoryClick(Player player, ClickType clickType, int slot, boolean playerInventory) { From 2b529e05d01d03d972dde5355d4d99d509ca67f1 Mon Sep 17 00:00:00 2001 From: Felix Cravic Date: Thu, 13 Aug 2020 20:43:45 +0200 Subject: [PATCH 13/17] Fixed error with unloaded chunks --- .../java/net/minestom/server/entity/Entity.java | 15 ++++++++++----- .../net/minestom/server/instance/Instance.java | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 68f1cab62..242f8be4d 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -339,8 +339,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { return; } - boolean chunkUnloaded = !ChunkUtils.isLoaded(instance, position.getX(), position.getZ()); - if (chunkUnloaded) { + if (!ChunkUtils.isLoaded(instance, position.getX(), position.getZ())) { // No update for entities in unloaded chunk return; } @@ -392,6 +391,11 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { ); onGround = CollisionUtils.handlePhysics(this, deltaPos, newPosition, newVelocityOut); + // Check chunk + if (!ChunkUtils.isLoaded(instance, newPosition.getX(), newPosition.getZ())) { + return; + } + // World border collision { final WorldBorder worldBorder = instance.getWorldBorder(); @@ -461,10 +465,11 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { for (int y = minY; y <= maxY; y++) { for (int x = minX; x <= maxX; x++) { for (int z = minZ; z <= maxZ; z++) { - chunkUnloaded = !ChunkUtils.isLoaded(instance, x, z); - if (chunkUnloaded) + final Chunk chunk = instance.getChunkAt(x, z); + if (!ChunkUtils.isLoaded(chunk)) continue; - final CustomBlock customBlock = instance.getCustomBlock(x, y, z); + + final CustomBlock customBlock = chunk.getCustomBlock(x, y, z); if (customBlock != null) { tmpPosition.setX(x); tmpPosition.setY(y); diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index 97e1757db..fbca1d2da 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -650,7 +650,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta * @param z the Z position * @return the chunk at the given position, null if not loaded */ - public Chunk getChunkAt(double x, double z) { + public Chunk getChunkAt(float x, float z) { final int chunkX = ChunkUtils.getChunkCoordinate((int) x); final int chunkZ = ChunkUtils.getChunkCoordinate((int) z); return getChunk(chunkX, chunkZ); From d66d87ed971be2541a74cffdc3e4620f7f25797e Mon Sep 17 00:00:00 2001 From: Eoghanmc22 Date: Thu, 13 Aug 2020 14:51:01 -0400 Subject: [PATCH 14/17] Rate Limiting fixes --- .../net/minestom/server/entity/Player.java | 2 ++ .../network/player/NettyPlayerConnection.java | 1 + .../network/player/PlayerConnection.java | 30 ++++++++++--------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index d94684fa1..90bf0dda2 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -290,6 +290,8 @@ public class Player extends LivingEntity implements CommandSender { // Flush all pending packets playerConnection.flush(); + playerConnection.updateStats(); + // Process received packets ClientPlayPacket packet; while ((packet = packets.poll()) != null) { diff --git a/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java b/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java index 0e9d2465e..9d4fbe78c 100644 --- a/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java @@ -59,6 +59,7 @@ public class NettyPlayerConnection extends PlayerConnection { @Override public void writePacket(ByteBuf buffer, boolean copy) { + //System.out.println(getConnectionState() + " out"); if ((encrypted || compressed) && copy) { buffer = buffer.copy(); buffer.retain(); diff --git a/src/main/java/net/minestom/server/network/player/PlayerConnection.java b/src/main/java/net/minestom/server/network/player/PlayerConnection.java index 6aff7a8bf..d0a5e34aa 100644 --- a/src/main/java/net/minestom/server/network/player/PlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/PlayerConnection.java @@ -45,21 +45,23 @@ public abstract class PlayerConnection { } public void updateStats() { - tickCounter++; - if (tickCounter % 20 == 0 && tickCounter > 0) { - tickCounter = 0; - int i = packetCounter.get(); - packetCounter.set(0); - if (i > MinecraftServer.getRateLimit()) { - if (connectionState == ConnectionState.LOGIN) { - sendPacket(new LoginDisconnect("Too Many Packets")); - } else { - DisconnectPacket disconnectPacket = new DisconnectPacket(); - disconnectPacket.message = rateLimitKickMessage; - sendPacket(disconnectPacket); + if (MinecraftServer.getRateLimit() > 0) { + tickCounter++; + if (tickCounter % 20 == 0 && tickCounter > 0) { + tickCounter = 0; + int i = packetCounter.get(); + packetCounter.set(0); + if (i > MinecraftServer.getRateLimit()) { + if (connectionState == ConnectionState.LOGIN) { + sendPacket(new LoginDisconnect("Too Many Packets")); + } else { + DisconnectPacket disconnectPacket = new DisconnectPacket(); + disconnectPacket.message = rateLimitKickMessage; + sendPacket(disconnectPacket); + } + disconnect(); + refreshOnline(false); } - disconnect(); - refreshOnline(false); } } } From 74c99e8886bab6b739cebf3283db35b21deb7b15 Mon Sep 17 00:00:00 2001 From: Eoghanmc22 Date: Thu, 13 Aug 2020 17:23:35 -0400 Subject: [PATCH 15/17] Fix glitchy chunk loading when you first login and don't send chunks to a player that are out of the player's render distance. --- src/main/java/net/minestom/server/entity/Player.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 8977a1258..4cbd0d237 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -558,7 +558,8 @@ public class Player extends LivingEntity implements CommandSender { viewableChunks.add(chunk); chunk.addViewer(this); instance.sendChunk(this, chunk); - updateViewPosition(chunk); + if (chunk.getChunkX() == Math.floorDiv((int) getPosition().getX(), 16) && chunk.getChunkZ() == Math.floorDiv((int) getPosition().getZ(), 16)) + updateViewPosition(chunk); } final boolean isLast = counter.get() == length - 1; if (isLast) { @@ -1170,8 +1171,8 @@ public class Player extends LivingEntity implements CommandSender { * @param newChunk the current/new player chunk */ protected void onChunkChange(Chunk lastChunk, Chunk newChunk) { - final long[] lastVisibleChunks = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), MinecraftServer.CHUNK_VIEW_DISTANCE); - final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), MinecraftServer.CHUNK_VIEW_DISTANCE); + final long[] lastVisibleChunks = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), getChunkRange()); + final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), getChunkRange()); final int[] oldChunks = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks); final int[] newChunks = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunks, lastVisibleChunks); From cc01a48cf15c871db01db9bae8db2ffb5e4e0804 Mon Sep 17 00:00:00 2001 From: Eoghanmc22 Date: Thu, 13 Aug 2020 17:54:55 -0400 Subject: [PATCH 16/17] Minestom now properly adapts when a player changes their render distance, also fixed a bug that pointers to chunks that were unloaded were kept in the viewableChunks list until the player's instance was changed. --- src/main/java/net/minestom/server/entity/Entity.java | 2 +- src/main/java/net/minestom/server/entity/Player.java | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index 242f8be4d..8f34d7d7d 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -984,7 +984,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { final boolean isPlayer = this instanceof Player; if (isPlayer) - ((Player) this).onChunkChange(lastChunk, newChunk); // Refresh loaded chunk + ((Player) this).onChunkChange(newChunk); // Refresh loaded chunk // Refresh entity viewable list final long[] lastVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), MinecraftServer.ENTITY_VIEW_DISTANCE); diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 6a88d94d6..44f591eb3 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -1169,11 +1169,15 @@ public class Player extends LivingEntity implements CommandSender { * It does remove and add the player from the chunks viewers list when removed or added * It also calls the events {@link PlayerChunkUnloadEvent} and {@link PlayerChunkLoadEvent} * - * @param lastChunk the last player chunk * @param newChunk the current/new player chunk */ - protected void onChunkChange(Chunk lastChunk, Chunk newChunk) { - final long[] lastVisibleChunks = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), getChunkRange()); + protected void onChunkChange(Chunk newChunk) { + final long[] lastVisibleChunks = new long[viewableChunks.size()]; + Chunk[] lastViewableChunks = viewableChunks.toArray(new Chunk[0]); + for (int i = 0; i < lastViewableChunks.length; i++) { + Chunk lastViewableChunk = lastViewableChunks[i]; + lastVisibleChunks[i] = ChunkUtils.getChunkIndex(lastViewableChunk.getChunkX(), lastViewableChunk.getChunkZ()); + } final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), getChunkRange()); final int[] oldChunks = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks); final int[] newChunks = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunks, lastVisibleChunks); @@ -1187,6 +1191,7 @@ public class Player extends LivingEntity implements CommandSender { playerConnection.sendPacket(unloadChunkPacket); Chunk chunk = instance.getChunk(chunkPos[0], chunkPos[1]); + viewableChunks.remove(chunk); if (chunk != null) chunk.removeViewer(this); } From d12618af0fceb84da4d76e4d21b74c9aae9817ac Mon Sep 17 00:00:00 2001 From: Eoghanmc22 Date: Thu, 13 Aug 2020 18:50:57 -0400 Subject: [PATCH 17/17] Fix concurrent modify exception. --- .../net/minestom/server/thread/PerGroupChunkProvider.java | 6 ++---- src/main/java/net/minestom/server/utils/MathUtils.java | 8 ++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/minestom/server/thread/PerGroupChunkProvider.java b/src/main/java/net/minestom/server/thread/PerGroupChunkProvider.java index eba0a7696..ab53b1c6b 100644 --- a/src/main/java/net/minestom/server/thread/PerGroupChunkProvider.java +++ b/src/main/java/net/minestom/server/thread/PerGroupChunkProvider.java @@ -109,9 +109,7 @@ public class PerGroupChunkProvider extends ThreadProvider { ArrayList> futures = new ArrayList<>(); - instanceInstanceMap.entrySet().forEach(entry -> { - final Instance instance = entry.getKey(); - final Map instanceMap = entry.getValue(); + instanceInstanceMap.forEach((instance, instanceMap) -> { // True if the instance ended its tick call AtomicBoolean instanceUpdated = new AtomicBoolean(false); @@ -157,7 +155,7 @@ public class PerGroupChunkProvider extends ThreadProvider { } private Map getInstanceMap(Instance instance) { - return instanceInstanceMap.computeIfAbsent(instance, inst -> new HashMap<>()); + return instanceInstanceMap.computeIfAbsent(instance, inst -> new ConcurrentHashMap<>()); } } diff --git a/src/main/java/net/minestom/server/utils/MathUtils.java b/src/main/java/net/minestom/server/utils/MathUtils.java index fc00ee700..6d83fb761 100644 --- a/src/main/java/net/minestom/server/utils/MathUtils.java +++ b/src/main/java/net/minestom/server/utils/MathUtils.java @@ -70,4 +70,12 @@ public final class MathUtils { public static float setBetween(float number, float min, float max) { return number > max ? max : number < min ? min : number; } + + public static int clamp(int value, int min, int max) { + if (value < min) { + return min; + } else { + return Math.min(value, max); + } + } }