mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-31 20:41:29 +01:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
5a6afbbb89
@ -72,6 +72,10 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Junit Testing Framework
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2'
|
||||
@ -121,6 +125,8 @@ dependencies {
|
||||
lwjglApi "org.lwjgl:lwjgl-opengl"
|
||||
lwjglApi "org.lwjgl:lwjgl-opengles"
|
||||
lwjglApi "org.lwjgl:lwjgl-glfw"
|
||||
lwjglApi "org.lwjgl:lwjgl-glfw"
|
||||
lwjglApi 'org.joml:joml:1.9.25'
|
||||
lwjglRuntimeOnly "org.lwjgl:lwjgl::$lwjglNatives"
|
||||
lwjglRuntimeOnly "org.lwjgl:lwjgl-opengl::$lwjglNatives"
|
||||
lwjglRuntimeOnly "org.lwjgl:lwjgl-opengles::$lwjglNatives"
|
||||
|
124
src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java
Normal file
124
src/lwjgl/java/net/minestom/demo/largeframebuffers/Demo.java
Normal file
@ -0,0 +1,124 @@
|
||||
package net.minestom.demo.largeframebuffers;
|
||||
|
||||
import fr.themode.demo.MainDemo;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.entity.GameMode;
|
||||
import net.minestom.server.entity.type.decoration.EntityItemFrame;
|
||||
import net.minestom.server.instance.Instance;
|
||||
import net.minestom.server.instance.InstanceManager;
|
||||
import net.minestom.server.item.ItemStack;
|
||||
import net.minestom.server.item.Material;
|
||||
import net.minestom.server.item.metadata.MapMeta;
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.LargeFramebuffer;
|
||||
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.network.packet.server.play.MapDataPacket;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Demo {
|
||||
|
||||
public static void main(String[] args) {
|
||||
MainDemo.main(args); // used to avoid code duplication
|
||||
initDemo();
|
||||
}
|
||||
|
||||
private static void initDemo() {
|
||||
MinecraftServer.getConnectionManager().addPlayerInitialization(player -> {
|
||||
player.setGameMode(GameMode.CREATIVE);
|
||||
});
|
||||
|
||||
InstanceManager instances = MinecraftServer.getInstanceManager();
|
||||
Instance instance = instances.getInstances().stream().findAny().get();
|
||||
|
||||
LargeDirectFramebuffer directFramebuffer = new LargeDirectFramebuffer(512, 512);
|
||||
LargeGraphics2DFramebuffer graphics2DFramebuffer = new LargeGraphics2DFramebuffer(512, 512);
|
||||
LargeGLFWFramebuffer glfwFramebuffer = new LargeGLFWFramebuffer(512, 512);
|
||||
|
||||
glfwFramebuffer.useMapColors();
|
||||
|
||||
glfwFramebuffer.changeRenderingThreadToCurrent();
|
||||
OpenGLRendering.init();
|
||||
glfwFramebuffer.unbindContextFromThread();
|
||||
|
||||
// renderingLoop(0, directFramebuffer, Demo::directRendering);
|
||||
// renderingLoop(101, graphics2DFramebuffer, Demo::graphics2DRendering);
|
||||
renderingLoop(201, glfwFramebuffer, f -> {});
|
||||
|
||||
glfwFramebuffer.setupRenderLoop(15, TimeUnit.MILLISECOND, () -> OpenGLRendering.render(glfwFramebuffer));
|
||||
|
||||
for (int x = -2; x <= 2; x++) {
|
||||
for (int z = -2; z <= 2; z++) {
|
||||
instance.loadChunk(x, z);
|
||||
}
|
||||
}
|
||||
setupMaps(instance, 0, 10);
|
||||
setupMaps(instance, 101, 20);
|
||||
setupMaps(instance, 201, 30);
|
||||
}
|
||||
|
||||
private static void createFrame(Instance instance, int id, int x, int y, int z) {
|
||||
EntityItemFrame itemFrame = new EntityItemFrame(new Position(x, y, z), EntityItemFrame.ItemFrameOrientation.NORTH);
|
||||
itemFrame.getPosition().setYaw(180f);
|
||||
ItemStack map = new ItemStack(Material.FILLED_MAP, (byte)1);
|
||||
map.setItemMeta(new MapMeta(id));
|
||||
itemFrame.setItemStack(map);
|
||||
itemFrame.setInstance(instance);
|
||||
itemFrame.setCustomNameVisible(true);
|
||||
itemFrame.setCustomName(ColoredText.of("MapID: "+id));
|
||||
}
|
||||
|
||||
private static void setupMaps(Instance instance, int mapIDStart, int zCoordinate) {
|
||||
for (int y = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 4; x++) {
|
||||
createFrame(instance, mapIDStart+y*4+x, 2-x, 45-y, zCoordinate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static <T extends LargeFramebuffer> void renderingLoop(int mapIDStart, T framebuffer, Consumer<T> renderingCode) {
|
||||
final Framebuffer[] subviews = new Framebuffer[4*4];
|
||||
for (int i = 0; i < subviews.length; i++) {
|
||||
int x = (i % 4)*128;
|
||||
int y = (i / 4)*128;
|
||||
subviews[i] = framebuffer.createSubView(x, y);
|
||||
}
|
||||
MinecraftServer.getSchedulerManager().buildTask(() -> {
|
||||
renderingCode.accept(framebuffer);
|
||||
for (int i = 0; i < subviews.length; i++) {
|
||||
final MapDataPacket packet = new MapDataPacket();
|
||||
packet.mapId = mapIDStart + i;
|
||||
|
||||
Framebuffer f = subviews[i];
|
||||
f.preparePacket(packet);
|
||||
sendPacket(packet);
|
||||
}
|
||||
}).repeat(15, TimeUnit.MILLISECOND).schedule();
|
||||
}
|
||||
|
||||
private static void sendPacket(MapDataPacket packet) {
|
||||
MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(p -> p.getPlayerConnection().sendPacket(packet));
|
||||
}
|
||||
|
||||
private static void directRendering(LargeDirectFramebuffer framebuffer) {
|
||||
Arrays.fill(framebuffer.getColors(), 0, 512*40+128, MapColors.COLOR_CYAN.baseColor());
|
||||
Arrays.fill(framebuffer.getColors(), 512*40+128, framebuffer.getColors().length, MapColors.COLOR_RED.baseColor());
|
||||
}
|
||||
|
||||
private static void graphics2DRendering(LargeGraphics2DFramebuffer framebuffer) {
|
||||
Graphics2D renderer = framebuffer.getRenderer();
|
||||
renderer.setColor(Color.BLACK);
|
||||
renderer.clearRect(0,0,512,512);
|
||||
renderer.setColor(Color.WHITE);
|
||||
renderer.drawString("Here's a very very long string that needs multiple maps to fit", 0, 100);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,218 @@
|
||||
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.nio.ByteBuffer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.opengl.GL20.*;
|
||||
|
||||
public final class OpenGLRendering {
|
||||
|
||||
private static int vbo;
|
||||
private static int indexBuffer;
|
||||
private static final int VERTEX_SIZE = 5*4; // position + tex
|
||||
|
||||
// array of vertices (order: X,Y,Z, Tex U, Tex V)
|
||||
private static float[] vertices = {
|
||||
// front face
|
||||
-1f, -1f, -1f, 0, 0,
|
||||
1f, -1f, -1f, 1, 0,
|
||||
1f, 1f, -1f, 1, 1,
|
||||
-1f, 1f, -1f, 0, 1,
|
||||
|
||||
// back face
|
||||
-1f, -1f, 1f, 0, 1,
|
||||
1f, -1f, 1f, 1, 1,
|
||||
1f, 1f, 1f, 1, 0,
|
||||
-1f, 1f, 1f, 0, 0,
|
||||
};
|
||||
|
||||
private static int[] indices = {
|
||||
// south face
|
||||
0,1,2,
|
||||
2,3,0,
|
||||
|
||||
// north face
|
||||
4,5,6,
|
||||
6,7,4,
|
||||
|
||||
// west face
|
||||
0,4,7,
|
||||
7,3,0,
|
||||
|
||||
// east face
|
||||
1,5,6,
|
||||
6,2,1,
|
||||
|
||||
// top face
|
||||
3, 2, 6,
|
||||
6, 7, 3
|
||||
};
|
||||
private static int renderShader;
|
||||
private static Matrix4f projectionMatrix;
|
||||
private static Matrix4f viewMatrix;
|
||||
private static Matrix4f modelMatrix;
|
||||
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();
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
|
||||
|
||||
indexBuffer = glGenBuffers();
|
||||
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);
|
||||
viewMatrix = new Matrix4f().setLookAt(5f, 5f, 5f, 0, 0, 0, 0, -1, 0);
|
||||
modelMatrix = new Matrix4f().identity();
|
||||
int vertexShader = createShader("/shaders/vertex.glsl", GL_VERTEX_SHADER);
|
||||
int fragmentShader = createShader("/shaders/fragment.glsl", GL_FRAGMENT_SHADER);
|
||||
glAttachShader(renderShader, vertexShader);
|
||||
glAttachShader(renderShader, fragmentShader);
|
||||
glLinkProgram(renderShader);
|
||||
if(glGetProgrami(renderShader, GL_LINK_STATUS) == 0) {
|
||||
System.err.println("Link error: "+glGetProgramInfoLog(renderShader));
|
||||
}
|
||||
|
||||
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); {
|
||||
uploadMatrix(projectionUniform, projectionMatrix);
|
||||
uploadMatrix(viewUniform, viewMatrix);
|
||||
|
||||
glUniform1i(boxUniform, 0); // texture unit 0
|
||||
glUniform1i(paletteUniform, 1); // texture unit 1
|
||||
glUniform1f(paletteSizeUniform, 236);
|
||||
}
|
||||
}
|
||||
|
||||
private static int loadTexture(String filename) {
|
||||
int tex = glGenTextures();
|
||||
glBindTexture(GL_TEXTURE_2D, tex);
|
||||
BufferedImage image;
|
||||
try {
|
||||
image = ImageIO.read(OpenGLRendering.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);
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
private static void uploadMatrix(int uniform, Matrix4f matrix) {
|
||||
float[] values = new float[4*4];
|
||||
matrix.get(values);
|
||||
glUniformMatrix4fv(uniform, false, values);
|
||||
}
|
||||
|
||||
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);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
private static long lastTime = System.currentTimeMillis();
|
||||
|
||||
private static int frame = 0;
|
||||
|
||||
static void render(LargeGLFWFramebuffer framebuffer) {
|
||||
if(frame % 100 == 0) {
|
||||
long time = System.currentTimeMillis();
|
||||
long dt = time-lastTime;
|
||||
System.out.println(">> Render time for 100 frames: "+dt);
|
||||
System.out.println(">> Average time per frame: "+(dt/100.0));
|
||||
System.out.println(">> Average FPS: "+(1000.0/(dt/100.0)));
|
||||
lastTime = time;
|
||||
}
|
||||
frame++;
|
||||
|
||||
|
||||
glClearColor(MapColors.COLOR_BLACK.multiply53()/255.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);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
|
||||
glDrawElements(GL_TRIANGLES, indices.length, GL_UNSIGNED_INT, 0);
|
||||
}
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package net.minestom.demo.largeframebuffers;
|
||||
|
||||
import net.minestom.server.map.MapColors;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class PaletteGenerator {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Map<Integer, Integer> colors = new HashMap<>();
|
||||
int highestIndex = 0;
|
||||
for(MapColors c : MapColors.values()) {
|
||||
if (c == MapColors.NONE)
|
||||
continue;
|
||||
for(MapColors.Multiplier m : MapColors.Multiplier.values()) {
|
||||
int index = ((int)m.apply(c)) & 0xFF;
|
||||
if(index > highestIndex) {
|
||||
highestIndex = index;
|
||||
}
|
||||
int rgb = MapColors.PreciseMapColor.toRGB(c, m);
|
||||
colors.put(index, rgb);
|
||||
}
|
||||
}
|
||||
|
||||
BufferedImage paletteTexture = new BufferedImage(highestIndex+1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
for (int i = 0; i <= highestIndex; i++) {
|
||||
int rgb = colors.getOrDefault(i, 0);
|
||||
int argb = (0xFF << 24) | (rgb & 0xFFFFFF);
|
||||
paletteTexture.setRGB(i, 0, argb);
|
||||
}
|
||||
|
||||
try {
|
||||
ImageIO.write(paletteTexture, "png", new File("src/lwjgl/resources/textures/palette.png"));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
import net.minestom.server.timer.Task;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
import static org.lwjgl.glfw.GLFW.glfwTerminate;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
|
||||
abstract class GLFWCapableBuffer {
|
||||
|
||||
protected final byte[] colors;
|
||||
private final ByteBuffer pixels;
|
||||
private final long glfwWindow;
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final ByteBuffer colorsBuffer;
|
||||
private boolean onlyMapColors;
|
||||
|
||||
protected GLFWCapableBuffer(int width, int height) {
|
||||
this(width, height, GLFW_NATIVE_CONTEXT_API, GLFW_OPENGL_API);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the framebuffer and initializes a new context
|
||||
*/
|
||||
protected GLFWCapableBuffer(int width, int height, int apiContext, int clientAPI) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.colors = new byte[width*height];
|
||||
colorsBuffer = BufferUtils.createByteBuffer(width*height);
|
||||
this.pixels = BufferUtils.createByteBuffer(width*height*4);
|
||||
if(!glfwInit()) {
|
||||
throw new RuntimeException("Failed to init GLFW");
|
||||
}
|
||||
|
||||
GLFWErrorCallback.createPrint().set();
|
||||
glfwDefaultWindowHints();
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
||||
|
||||
glfwWindowHint(GLFW_CONTEXT_CREATION_API, apiContext);
|
||||
glfwWindowHint(GLFW_CLIENT_API, clientAPI);
|
||||
|
||||
this.glfwWindow = glfwCreateWindow(width, height, "", 0L, 0L);
|
||||
if(glfwWindow == 0L) {
|
||||
try(var stack = MemoryStack.stackPush()) {
|
||||
PointerBuffer desc = stack.mallocPointer(1);
|
||||
int errcode = glfwGetError(desc);
|
||||
throw new RuntimeException("("+errcode+") Failed to create GLFW Window.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GLFWCapableBuffer unbindContextFromThread() {
|
||||
glfwMakeContextCurrent(0L);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void changeRenderingThreadToCurrent() {
|
||||
glfwMakeContextCurrent(glfwWindow);
|
||||
GL.createCapabilities();
|
||||
}
|
||||
|
||||
public Task setupRenderLoop(long period, TimeUnit unit, Runnable rendering) {
|
||||
return MinecraftServer.getSchedulerManager()
|
||||
.buildTask(new Runnable() {
|
||||
private boolean first = true;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if(first) {
|
||||
changeRenderingThreadToCurrent();
|
||||
first = false;
|
||||
}
|
||||
render(rendering);
|
||||
}
|
||||
})
|
||||
.repeat(period, unit)
|
||||
.schedule();
|
||||
}
|
||||
|
||||
public void render(Runnable rendering) {
|
||||
rendering.run();
|
||||
glfwSwapBuffers(glfwWindow);
|
||||
prepareMapColors();
|
||||
}
|
||||
|
||||
// TODO: provide shader that performs the conversion automatically, would be a lot faster
|
||||
/**
|
||||
* Called in render after glFlush to read the pixel buffer contents and convert it to map colors.
|
||||
* Only call if you do not use {@link #render(Runnable)} nor {@link #setupRenderLoop(long, TimeUnit, Runnable)}
|
||||
*/
|
||||
public void prepareMapColors() {
|
||||
if(onlyMapColors) {
|
||||
colorsBuffer.rewind();
|
||||
glReadPixels(0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, colorsBuffer);
|
||||
colorsBuffer.get(colors);
|
||||
} else {
|
||||
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int i = Framebuffer.index(x, y, width)*4;
|
||||
int red = pixels.get(i) & 0xFF;
|
||||
int green = pixels.get(i+1) & 0xFF;
|
||||
int blue = pixels.get(i+2) & 0xFF;
|
||||
int alpha = pixels.get(i+3) & 0xFF;
|
||||
int argb = (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
colors[Framebuffer.index(x, y, width)] = MapColors.closestColor(argb).getIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
public long getGLFWWindow() {
|
||||
return glfwWindow;
|
||||
}
|
||||
|
||||
public int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int height() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells this buffer that the **RED** channel contains the index of the map color to use.
|
||||
*
|
||||
* This allows for optimizations and fast rendering (because there is no need for a conversion)
|
||||
*/
|
||||
public void useMapColors() {
|
||||
onlyMapColors = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opposite to {@link #useMapColors()}
|
||||
*/
|
||||
public void useRGB() {
|
||||
onlyMapColors = false;
|
||||
}
|
||||
}
|
@ -1,20 +1,13 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
import net.minestom.server.timer.Task;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.*;
|
||||
import static org.lwjgl.opengl.GL11.*;
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_NATIVE_CONTEXT_API;
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_API;
|
||||
|
||||
/**
|
||||
* GLFW-based framebuffer.
|
||||
@ -33,101 +26,20 @@ import static org.lwjgl.opengl.GL11.*;
|
||||
*
|
||||
* This framebuffer is meant to render to a single map (ie it is only compatible with 128x128 rendering)
|
||||
*/
|
||||
public class GLFWFramebuffer implements Framebuffer {
|
||||
public class GLFWFramebuffer extends GLFWCapableBuffer implements Framebuffer {
|
||||
|
||||
private final byte[] colors = new byte[WIDTH*HEIGHT];
|
||||
private final ByteBuffer pixels = BufferUtils.createByteBuffer(WIDTH*HEIGHT*4);
|
||||
private final long glfwWindow;
|
||||
|
||||
public GLFWFramebuffer() {
|
||||
this(GLFW_NATIVE_CONTEXT_API, GLFW_OPENGL_API);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the framebuffer and initializes a new EGL context
|
||||
* Creates the framebuffer and initializes a new context
|
||||
*/
|
||||
public GLFWFramebuffer(int apiContext, int clientAPI) {
|
||||
if(!glfwInit()) {
|
||||
throw new RuntimeException("Failed to init GLFW");
|
||||
}
|
||||
|
||||
GLFWErrorCallback.createPrint().set();
|
||||
glfwDefaultWindowHints();
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
||||
|
||||
glfwWindowHint(GLFW_CONTEXT_CREATION_API, apiContext);
|
||||
glfwWindowHint(GLFW_CLIENT_API, clientAPI);
|
||||
|
||||
this.glfwWindow = glfwCreateWindow(WIDTH, HEIGHT, "", 0L, 0L);
|
||||
if(glfwWindow == 0L) {
|
||||
try(var stack = MemoryStack.stackPush()) {
|
||||
PointerBuffer desc = stack.mallocPointer(1);
|
||||
int errcode = glfwGetError(desc);
|
||||
throw new RuntimeException("("+errcode+") Failed to create GLFW Window.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GLFWFramebuffer unbindContextFromThread() {
|
||||
glfwMakeContextCurrent(0L);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void changeRenderingThreadToCurrent() {
|
||||
glfwMakeContextCurrent(glfwWindow);
|
||||
GL.createCapabilities();
|
||||
}
|
||||
|
||||
public Task setupRenderLoop(long period, TimeUnit unit, Runnable rendering) {
|
||||
return MinecraftServer.getSchedulerManager()
|
||||
.buildTask(new Runnable() {
|
||||
private boolean first = true;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if(first) {
|
||||
changeRenderingThreadToCurrent();
|
||||
first = false;
|
||||
}
|
||||
render(rendering);
|
||||
}
|
||||
})
|
||||
.repeat(period, unit)
|
||||
.schedule();
|
||||
}
|
||||
|
||||
public void render(Runnable rendering) {
|
||||
rendering.run();
|
||||
glfwSwapBuffers(glfwWindow);
|
||||
prepareMapColors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in render after glFlush to read the pixel buffer contents and convert it to map colors.
|
||||
* Only call if you do not use {@link #render(Runnable)} nor {@link #setupRenderLoop(long, TimeUnit, Runnable)}
|
||||
*/
|
||||
public void prepareMapColors() {
|
||||
glReadPixels(0, 0, WIDTH, HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
|
||||
for (int y = 0; y < HEIGHT; y++) {
|
||||
for (int x = 0; x < WIDTH; x++) {
|
||||
int i = Framebuffer.index(x, y)*4;
|
||||
int red = pixels.get(i) & 0xFF;
|
||||
int green = pixels.get(i+1) & 0xFF;
|
||||
int blue = pixels.get(i+2) & 0xFF;
|
||||
int alpha = pixels.get(i+3) & 0xFF;
|
||||
int argb = (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
colors[Framebuffer.index(x, y)] = MapColors.closestColor(argb).getIndex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
public long getGLFWWindow() {
|
||||
return glfwWindow;
|
||||
super(WIDTH, HEIGHT, apiContext, clientAPI);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,27 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.LargeFramebuffer;
|
||||
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_NATIVE_CONTEXT_API;
|
||||
import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_API;
|
||||
|
||||
public class LargeGLFWFramebuffer extends GLFWCapableBuffer implements LargeFramebuffer {
|
||||
public LargeGLFWFramebuffer(int width, int height) {
|
||||
this(width, height, GLFW_NATIVE_CONTEXT_API, GLFW_OPENGL_API);
|
||||
}
|
||||
|
||||
public LargeGLFWFramebuffer(int width, int height, int apiContext, int clientAPI) {
|
||||
super(width, height, apiContext, clientAPI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Framebuffer createSubView(int left, int top) {
|
||||
return new LargeFramebufferDefaultView(this, left, top);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getMapColor(int x, int y) {
|
||||
return colors[Framebuffer.index(x, y, width())];
|
||||
}
|
||||
}
|
33
src/lwjgl/resources/shaders/fragment.glsl
Normal file
33
src/lwjgl/resources/shaders/fragment.glsl
Normal file
@ -0,0 +1,33 @@
|
||||
#version 330
|
||||
|
||||
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);
|
||||
}
|
16
src/lwjgl/resources/shaders/vertex.glsl
Normal file
16
src/lwjgl/resources/shaders/vertex.glsl
Normal file
@ -0,0 +1,16 @@
|
||||
#version 330
|
||||
|
||||
layout(location = 0) in vec3 pos;
|
||||
layout(location = 1) in vec2 texCoords;
|
||||
|
||||
out vec2 uv;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat4 view;
|
||||
uniform mat4 model;
|
||||
|
||||
void main() {
|
||||
mat4 mvp = projection * view * model;
|
||||
uv = texCoords;
|
||||
gl_Position = mvp * vec4(pos, 1.0);
|
||||
}
|
BIN
src/lwjgl/resources/textures/box.png
Normal file
BIN
src/lwjgl/resources/textures/box.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 274 B |
BIN
src/lwjgl/resources/textures/palette.png
Normal file
BIN
src/lwjgl/resources/textures/palette.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 876 B |
@ -1,6 +1,9 @@
|
||||
package net.minestom.server.command;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.permission.Permission;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Represent something which can send commands to the server
|
||||
@ -27,6 +30,62 @@ public interface CommandSender {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all permissions associated to this command sender.
|
||||
* The returned collection should be modified only by subclasses
|
||||
* @return
|
||||
*/
|
||||
Collection<Permission> getAllPermissions();
|
||||
|
||||
/**
|
||||
* Adds a permission to this commandSender
|
||||
* @param permission
|
||||
*/
|
||||
default void addPermission(Permission permission) {
|
||||
getAllPermissions().add(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a permission from this commandSender
|
||||
* @param permission
|
||||
*/
|
||||
default void removePermission(Permission permission) {
|
||||
getAllPermissions().remove(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given permission is possessed by this command sender.
|
||||
* Simple shortcut to <pre>getAllPermissions().contains(permission) && permission.isValidFor(this)</pre> for readability.
|
||||
* @param p permission to check against
|
||||
* @return
|
||||
*/
|
||||
default boolean hasPermission(Permission p) {
|
||||
return getAllPermissions().contains(p) && p.isValidFor(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given permission is possessed by this command sender.
|
||||
* Will call {@link Permission#isValidFor(CommandSender)} on all permissions that are an instance of permissionClass.
|
||||
* If no matching permission is found, this result returns false.
|
||||
*
|
||||
* @param permissionClass
|
||||
* @see #getAllPermissions()
|
||||
* @return
|
||||
*/
|
||||
default boolean hasPermission(Class<? extends Permission> permissionClass) {
|
||||
boolean result = true;
|
||||
boolean foundPerm = false;
|
||||
for(Permission p : getAllPermissions()) {
|
||||
if(permissionClass.isInstance(p)) {
|
||||
foundPerm = true;
|
||||
result &= p.isValidFor(this);
|
||||
}
|
||||
}
|
||||
if(!foundPerm)
|
||||
return false;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if the sender is a player
|
||||
*
|
||||
@ -45,4 +104,21 @@ public interface CommandSender {
|
||||
return this instanceof ConsoleSender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts this object to a Player
|
||||
* No checks are performed, {@link ClassCastException} can very much happen
|
||||
* @see #isPlayer()
|
||||
*/
|
||||
default Player asPlayer() {
|
||||
return (Player)this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts this object to a ConsoleSender
|
||||
* No checks are performed, {@link ClassCastException} can very much happen
|
||||
* @see #isConsole()
|
||||
*/
|
||||
default ConsoleSender asConsole() {
|
||||
return (ConsoleSender)this;
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,25 @@
|
||||
package net.minestom.server.command;
|
||||
|
||||
import net.minestom.server.permission.Permission;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represent the console when sending a command to the server
|
||||
*/
|
||||
public class ConsoleSender implements CommandSender {
|
||||
|
||||
private final List<Permission> permissions = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public void sendMessage(String message) {
|
||||
System.out.println(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Permission> getAllPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.login.JoinGamePacket;
|
||||
import net.minestom.server.network.packet.server.play.*;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.permission.Permission;
|
||||
import net.minestom.server.recipe.Recipe;
|
||||
import net.minestom.server.recipe.RecipeManager;
|
||||
import net.minestom.server.resourcepack.ResourcePack;
|
||||
@ -135,6 +136,8 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
// Tick related
|
||||
private final PlayerTickEvent playerTickEvent = new PlayerTickEvent(this);
|
||||
|
||||
private final List<Permission> permissions = new LinkedList<>();
|
||||
|
||||
public Player(UUID uuid, String username, PlayerConnection playerConnection) {
|
||||
super(EntityType.PLAYER);
|
||||
this.uuid = uuid; // Override Entity#uuid defined in the constructor
|
||||
@ -624,6 +627,11 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
sendMessage(ColoredText.of(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Permission> getAllPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to the player
|
||||
*
|
||||
|
@ -22,6 +22,8 @@ public class EntityItemFrame extends ObjectEntity {
|
||||
super(EntityType.ITEM_FRAME, spawnPosition);
|
||||
this.orientation = orientation;
|
||||
this.rotation = Rotation.NONE;
|
||||
setNoGravity(true);
|
||||
setGravity(0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
48
src/main/java/net/minestom/server/map/LargeFramebuffer.java
Normal file
48
src/main/java/net/minestom/server/map/LargeFramebuffer.java
Normal file
@ -0,0 +1,48 @@
|
||||
package net.minestom.server.map;
|
||||
|
||||
import net.minestom.server.network.packet.server.play.MapDataPacket;
|
||||
|
||||
/**
|
||||
* Framebuffer that is meant to be split in sub-framebuffers. Contrary to Framebuffer, LargeFramebuffer supports sizes over 128x128 pixels.
|
||||
*/
|
||||
public interface LargeFramebuffer {
|
||||
|
||||
int width();
|
||||
int height();
|
||||
|
||||
/**
|
||||
* Returns a new Framebuffer that represent a 128x128 sub-view of this framebuffer.
|
||||
* Implementations are free (but not guaranteed) to throw exceptions if left & top produces out-of-bounds coordinates.
|
||||
* @param left
|
||||
* @param top
|
||||
* @return
|
||||
*/
|
||||
Framebuffer createSubView(int left, int top);
|
||||
|
||||
byte getMapColor(int x, int y);
|
||||
|
||||
/**
|
||||
* Prepares the packet to render a 128x128 sub view of this framebuffer
|
||||
* @param packet
|
||||
* @param left
|
||||
* @param top
|
||||
*/
|
||||
default void preparePacket(MapDataPacket packet, int left, int top) {
|
||||
byte[] colors = new byte[Framebuffer.WIDTH*Framebuffer.WIDTH];
|
||||
int width = Math.min(width(), left+Framebuffer.WIDTH) - left;
|
||||
int height = Math.min(height(), top+Framebuffer.HEIGHT) - top;
|
||||
for (int y = top; y < height; y++) {
|
||||
for (int x = left; x < width; x++) {
|
||||
byte color = getMapColor(left, top);
|
||||
colors[Framebuffer.index(x-left, y-top)] = color;
|
||||
}
|
||||
}
|
||||
|
||||
packet.columns = (short) width;
|
||||
packet.rows = (short) height;
|
||||
packet.icons = new MapDataPacket.Icon[0];
|
||||
packet.x = 0;
|
||||
packet.z = 0;
|
||||
packet.data = colors;
|
||||
}
|
||||
}
|
@ -307,7 +307,7 @@ public enum MapColors {
|
||||
}
|
||||
}
|
||||
|
||||
enum Multiplier {
|
||||
public enum Multiplier {
|
||||
x1_00(MapColors::baseColor, 1.00),
|
||||
x0_53(MapColors::multiply53, 0.53),
|
||||
x0_71(MapColors::multiply71, 0.71),
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.LargeFramebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
|
||||
import java.awt.*;
|
||||
|
@ -0,0 +1,65 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.LargeFramebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
|
||||
/**
|
||||
* Large framebuffer with direct access to the colors array.
|
||||
*
|
||||
* This implementation does not throw errors when accessing out-of-bounds coordinates through sub-views, and will instead
|
||||
* use {@link MapColors#NONE}. This is only the case for sub-views, access through {@link #setMapColor(int, int, byte)}
|
||||
* and {@link #getMapColor(int, int)} will throw an exception if out-of-bounds coordinates are inputted.
|
||||
*/
|
||||
public class LargeDirectFramebuffer implements LargeFramebuffer {
|
||||
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final byte[] colors;
|
||||
|
||||
/**
|
||||
* Creates a new {@link LargeDirectFramebuffer} with the desired size
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public LargeDirectFramebuffer(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.colors = new byte[width*height];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int height() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Framebuffer createSubView(int left, int top) {
|
||||
return new LargeFramebufferDefaultView(this, left, top);
|
||||
}
|
||||
|
||||
public LargeDirectFramebuffer setMapColor(int x, int y, byte color) {
|
||||
if(!bounds(x, y)) throw new IndexOutOfBoundsException("Invalid x;y coordinate: "+x+";"+y);
|
||||
colors[y*width+x] = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getMapColor(int x, int y) {
|
||||
if(!bounds(x, y)) throw new IndexOutOfBoundsException("Invalid x;y coordinate: "+x+";"+y);
|
||||
return colors[y*width+x];
|
||||
}
|
||||
|
||||
private boolean bounds(int x, int y) {
|
||||
return x >= 0 && x < width && y >= 0 && y < height;
|
||||
}
|
||||
|
||||
public byte[] getColors() {
|
||||
return colors;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.LargeFramebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
|
||||
public class LargeFramebufferDefaultView implements Framebuffer {
|
||||
private final LargeFramebuffer parent;
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final byte[] colors = new byte[WIDTH*HEIGHT];
|
||||
|
||||
public LargeFramebufferDefaultView(LargeFramebuffer parent, int x, int y) {
|
||||
this.parent = parent;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
private boolean bounds(int x, int y) {
|
||||
return x >= 0 && x < parent.width() && y >= 0 && y < parent.height();
|
||||
}
|
||||
|
||||
private byte colorOrNone(int x, int y) {
|
||||
if(!bounds(x, y)) return MapColors.NONE.baseColor();
|
||||
return parent.getMapColor(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toMapColors() {
|
||||
for (int y = 0; y < HEIGHT; y++) {
|
||||
for (int x = 0; x < WIDTH; x++) {
|
||||
colors[Framebuffer.index(x, y)] = colorOrNone(x+this.x, y+this.y);
|
||||
}
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package net.minestom.server.map.framebuffers;
|
||||
|
||||
import net.minestom.server.map.Framebuffer;
|
||||
import net.minestom.server.map.LargeFramebuffer;
|
||||
import net.minestom.server.map.MapColors;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferInt;
|
||||
|
||||
/**
|
||||
* LargeFramebuffer that embeds a BufferedImage, allowing for rendering directly via Graphics2D or its pixel array
|
||||
*/
|
||||
public class LargeGraphics2DFramebuffer implements LargeFramebuffer {
|
||||
|
||||
private final byte[] colors;
|
||||
private final BufferedImage backingImage;
|
||||
private final Graphics2D renderer;
|
||||
private final int[] pixels;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
public LargeGraphics2DFramebuffer(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
colors = new byte[width*height];
|
||||
backingImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
renderer = backingImage.createGraphics();
|
||||
pixels = ((DataBufferInt)backingImage.getRaster().getDataBuffer()).getData();
|
||||
}
|
||||
|
||||
public Graphics2D getRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
public BufferedImage getBackingImage() {
|
||||
return backingImage;
|
||||
}
|
||||
|
||||
public int get(int x, int z) {
|
||||
return pixels[x+z*width]; // stride is always the width of the image
|
||||
}
|
||||
|
||||
public LargeGraphics2DFramebuffer set(int x, int z, int rgb) {
|
||||
pixels[x+z*width] = rgb;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int height() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Framebuffer createSubView(int left, int top) {
|
||||
return new LargeFramebufferDefaultView(this, left, top);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getMapColor(int x, int y) {
|
||||
return MapColors.closestColor(get(x, y)).getIndex();
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package net.minestom.server.permission;
|
||||
|
||||
import net.minestom.server.command.CommandSender;
|
||||
|
||||
/**
|
||||
* Basic Permission implementation that only requires the permission to be given to a player to be considered applied
|
||||
* (eg. no arguments)
|
||||
*/
|
||||
public class BasicPermission implements Permission {
|
||||
@Override
|
||||
public boolean isValidFor(CommandSender commandSender) {
|
||||
return true;
|
||||
}
|
||||
}
|
35
src/main/java/net/minestom/server/permission/Permission.java
Normal file
35
src/main/java/net/minestom/server/permission/Permission.java
Normal file
@ -0,0 +1,35 @@
|
||||
package net.minestom.server.permission;
|
||||
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.data.Data;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Representation of a permission granted to a CommandSender
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Permission {
|
||||
|
||||
/**
|
||||
* Does the given commandSender have the permission represented by this object?
|
||||
* @param commandSender
|
||||
* @return true if the commandSender possesses this permission
|
||||
*/
|
||||
boolean isValidFor(CommandSender commandSender);
|
||||
|
||||
/**
|
||||
* Writes any required data for this permission inside the given destination
|
||||
* @param destination Data to write to
|
||||
*/
|
||||
default void write(@NotNull Data destination) {}
|
||||
|
||||
/**
|
||||
* Reads any required data for this permission from the given destination
|
||||
* @param source Data to read from
|
||||
* @return this for chaining
|
||||
*/
|
||||
default Permission read(@Nullable Data source) {
|
||||
return this;
|
||||
}
|
||||
}
|
102
src/test/java/permissions/TestPermissions.java
Normal file
102
src/test/java/permissions/TestPermissions.java
Normal file
@ -0,0 +1,102 @@
|
||||
package permissions;
|
||||
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.command.CommandSender;
|
||||
import net.minestom.server.data.Data;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.permission.Permission;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
// TODO: more tests
|
||||
public class TestPermissions {
|
||||
|
||||
private Player player;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
MinecraftServer.init(); // for entity manager
|
||||
player = new Player(UUID.randomUUID(), "TestPlayer", null) {
|
||||
@Override
|
||||
protected void playerConnectionInit() {}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noPermission() {
|
||||
assertFalse(player.hasPermission(Permission.class));
|
||||
}
|
||||
|
||||
class PermTest1 implements Permission {
|
||||
@Override
|
||||
public boolean isValidFor(CommandSender commandSender) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
class PermTest2 implements Permission {
|
||||
@Override
|
||||
public boolean isValidFor(CommandSender commandSender) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasPermissionClass() {
|
||||
assertFalse(player.hasPermission(Permission.class));
|
||||
player.addPermission(new PermTest1());
|
||||
assertTrue(player.hasPermission(PermTest1.class));
|
||||
assertFalse(player.hasPermission(PermTest2.class));
|
||||
assertTrue(player.hasPermission(Permission.class)); // allow superclasses
|
||||
|
||||
player.addPermission(new PermTest2());
|
||||
assertTrue(player.hasPermission(PermTest2.class));
|
||||
}
|
||||
|
||||
class BooleanPerm implements Permission {
|
||||
private final boolean value;
|
||||
|
||||
BooleanPerm(boolean v) {
|
||||
this.value = v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidFor(CommandSender commandSender) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasTwoPermissionsOfSameClassButContradictEachOther() {
|
||||
player.addPermission(new BooleanPerm(true));
|
||||
assertTrue(player.hasPermission(BooleanPerm.class));
|
||||
player.addPermission(new BooleanPerm(false));
|
||||
assertFalse(player.hasPermission(BooleanPerm.class)); // all permissions must be valid
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singlePermission() {
|
||||
Permission p = commandSender -> true;
|
||||
player.addPermission(p);
|
||||
assertTrue(p.isValidFor(player));
|
||||
assertTrue(player.hasPermission(p));
|
||||
assertTrue(player.hasPermission(Permission.class));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() {
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user