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/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/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/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;
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;
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..fb6bc3284
--- /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:
+ *
+ * - 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.
+ */
+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..294792958
--- /dev/null
+++ b/src/lwjgl/resources/shaders/mapcolorconvert.fragment.glsl
@@ -0,0 +1,32 @@
+#version 330
+
+in vec2 fragCoords;
+out vec4 fragColor;
+
+uniform sampler2D frame;
+uniform sampler2D palette;
+uniform float paletteSize;
+
+void main() {
+ vec2 uv = fragCoords;
+ uv.y = -uv.y;
+ vec3 fragmentColor = texture(frame, 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 - 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
diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java
index 9bc273069..b10342025 100644
--- a/src/main/java/net/minestom/server/MinecraftServer.java
+++ b/src/main/java/net/minestom/server/MinecraftServer.java
@@ -94,6 +94,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/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/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java
index 4cbd0d237..6a88d94d6 100644
--- a/src/main/java/net/minestom/server/entity/Player.java
+++ b/src/main/java/net/minestom/server/entity/Player.java
@@ -287,6 +287,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/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);
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..fc8d1dec4 100644
--- a/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java
+++ b/src/main/java/net/minestom/server/inventory/click/InventoryClickProcessor.java
@@ -508,14 +508,18 @@ 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);
- 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();
@@ -525,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);
diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java
index 9cddf7904..b4b1c6b3a 100644
--- a/src/main/java/net/minestom/server/item/ItemStack.java
+++ b/src/main/java/net/minestom/server/item/ItemStack.java
@@ -3,10 +3,14 @@ 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.inventory.click.ClickType;
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 +617,51 @@ public class ItemStack implements DataContainer {
}
return compound;
}
+
+ // Callback events
+
+ /**
+ * Called when the player right clicks with this item
+ *
+ * @param player the player who used the item
+ * @param hand the hand used
+ */
+ public void onRightClick(Player player, Player.Hand hand) {
+ }
+
+ /**
+ * Called when the player left clicks with this item
+ *
+ * @param player the player who used the item
+ * @param hand the hand used
+ */
+ public void onLeftClick(Player player, Player.Hand hand) {
+ }
+
+ /**
+ * Called when the player right clicks with this item on a block
+ *
+ * @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) {
+ return false;
+ }
+
+ /**
+ * Called when the player click on this item on an inventory
+ *
+ * Executed before any events
+ *
+ * @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) {
+
+ }
}
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);
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/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 935109744..d0a5e34aa 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,40 @@ 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() {
+ 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);
+ }
+ }
+ }
+ }
+
public abstract void enableCompression(int threshold);
/**