diff --git a/build.gradle b/build.gradle index 060ef0c..eef3430 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'fabric-loom' version '1.6-SNAPSHOT' id 'maven-publish' id 'com.github.johnrengelman.shadow' version '8.1.1' + id 'org.jetbrains.kotlin.jvm' version '1.9.23' } version = project.mod_version @@ -17,6 +18,7 @@ repositories { // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. // See https://docs.gradle.org/current/userguide/declaring_repositories.html // for more information about repositories. + maven { url 'https://maven.dblsaiko.net' } //qcommon } loom { @@ -41,7 +43,9 @@ dependencies { modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" shadow(implementation(project(":J65el02"))) - + + shadow(implementation(group: "net.dblsaiko.qcommon.croco", name: "croco", version: "2.1.3")) + } processResources { diff --git a/src/client/java/net/brokenmoon/redcontrol/RedControlClient.java b/src/client/java/net/brokenmoon/redcontrol/RedControlClient.java index 5eef227..c8e35db 100644 --- a/src/client/java/net/brokenmoon/redcontrol/RedControlClient.java +++ b/src/client/java/net/brokenmoon/redcontrol/RedControlClient.java @@ -1,13 +1,16 @@ package net.brokenmoon.redcontrol; +import net.brokenmoon.redcontrol.blocks.TerminalEntity; import net.brokenmoon.redcontrol.screen.CpuScreen; -import net.brokenmoon.redcontrol.screen.MonitorScreen; +import net.brokenmoon.redcontrol.screen.TerminalScreen; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; -import net.minecraft.client.MinecraftClient; +import net.minecraft.client.world.ClientWorld; import net.minecraft.text.Text; import net.minecraft.util.math.BlockPos; +import java.util.Optional; + public class RedControlClient implements ClientModInitializer { @Override public void onInitializeClient() { @@ -15,16 +18,23 @@ public class RedControlClient implements ClientModInitializer { ClientPlayNetworking.registerGlobalReceiver( RedControlNetworking.CPUGUI_PACKET_ID, (client, handler, buf, responseSender) -> { BlockPos blockPos = buf.readBlockPos(); - client.execute(() -> { - client.setScreen(new CpuScreen(Text.literal("cpu"), blockPos)); - }); + client.execute(() -> client.setScreen(new CpuScreen(Text.literal("cpu"), blockPos))); }); ClientPlayNetworking.registerGlobalReceiver( RedControlNetworking.MONITOR_PACKET_ID, (client, handler, buf, responseSender) -> { BlockPos blockPos = buf.readBlockPos(); - client.execute(() -> { - client.setScreen(new MonitorScreen(Text.literal("monitor"))); - }); + ClientWorld cw = client.world; + if (cw != null) { + Optional te = cw.getBlockEntity(blockPos,RedControl.TERMINAL_BLOCK_ENTITY); + if (te.isEmpty()) { + RedControl.LOGGER.warn("Server just sent us a Terminal packet, terminal is not at position given {}?",blockPos); + return; + } + client.execute(() -> client.setScreen(new TerminalScreen(te.get()))); + } else { + RedControl.LOGGER.warn("Server just send us a packet... we dont have a world... HOW"); + } }); + Shaders.INSTANCE.init(); } } \ No newline at end of file diff --git a/src/client/java/net/brokenmoon/redcontrol/Shaders.kt b/src/client/java/net/brokenmoon/redcontrol/Shaders.kt new file mode 100644 index 0000000..e4caa8d --- /dev/null +++ b/src/client/java/net/brokenmoon/redcontrol/Shaders.kt @@ -0,0 +1,93 @@ +package net.brokenmoon.redcontrol + +import net.brokenmoon.redcontrol.RedControl.modloc +import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener +import net.fabricmc.fabric.api.resource.ResourceManagerHelper +import net.minecraft.resource.ResourceManager +import net.minecraft.resource.ResourceReloader.Synchronizer +import net.minecraft.resource.ResourceType +import net.minecraft.util.Identifier +import net.minecraft.util.profiler.Profiler +import org.lwjgl.opengl.GL11 +import org.lwjgl.opengl.GL30 +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executor +import kotlin.jvm.optionals.getOrNull + +object Shaders { + + private var screen = 0 + + fun screen() = screen + + fun init() { + ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(object : IdentifiableResourceReloadListener { + + override fun reload(s: Synchronizer, rm: ResourceManager, profiler: Profiler, profiler1: Profiler, executor: Executor, executor1: Executor): CompletableFuture { + return CompletableFuture.runAsync({ + if (screen != 0) GL30.glDeleteProgram(screen) + + screen = loadShader(rm, "screen") + }, executor1).thenCompose { s.whenPrepared(null) } + } + + override fun getFabricId(): Identifier = modloc("shaders") + + }) + } + + private fun loadShader(rm: ResourceManager, id: String): Int { + val vshs = rm.getResource(modloc("shaders/$id.vert")).getOrNull()?.inputStream?.bufferedReader()?.readText() + val fshs = rm.getResource(modloc("shaders/$id.frag")).getOrNull()?.inputStream?.bufferedReader()?.readText() + + val vsh = GL30.glCreateShader(GL30.GL_VERTEX_SHADER) + val fsh = GL30.glCreateShader(GL30.GL_FRAGMENT_SHADER) + val prog = GL30.glCreateProgram() + + // No goto? I'll make my own. + run { + GL30.glShaderSource(vsh, vshs) + GL30.glShaderSource(fsh, fshs) + + GL30.glCompileShader(vsh) + if (GL30.glGetShaderi(vsh, GL30.GL_COMPILE_STATUS) == GL11.GL_FALSE) { + // TODO use logger + val log = GL30.glGetShaderInfoLog(vsh, 32768) + println("Failed to compile vertex shader '$id'") + for (line in log.lineSequence()) println(line) + return@run + } + + GL30.glCompileShader(fsh) + if (GL30.glGetShaderi(fsh, GL30.GL_COMPILE_STATUS) == GL11.GL_FALSE) { + // TODO use logger + val log = GL30.glGetShaderInfoLog(fsh, 32768) + println("Failed to compile fragment shader '$id'") + for (line in log.lineSequence()) println(line) + return@run + } + + GL30.glAttachShader(prog, vsh) + GL30.glAttachShader(prog, fsh) + GL30.glLinkProgram(prog) + + if (GL30.glGetProgrami(prog, GL30.GL_LINK_STATUS) == GL11.GL_FALSE) { + // TODO use logger + val log = GL30.glGetProgramInfoLog(prog, 32768) + println("Failed to link program '$id'") + for (line in log.lineSequence()) println(line) + return@run + } + + GL30.glDeleteShader(vsh) + GL30.glDeleteShader(fsh) + return prog + } + + GL30.glDeleteShader(vsh) + GL30.glDeleteShader(fsh) + GL30.glDeleteProgram(prog) + return 0 + } + +} \ No newline at end of file diff --git a/src/client/java/net/brokenmoon/redcontrol/screen/MonitorScreen.java b/src/client/java/net/brokenmoon/redcontrol/screen/MonitorScreen.java deleted file mode 100644 index 8f764a6..0000000 --- a/src/client/java/net/brokenmoon/redcontrol/screen/MonitorScreen.java +++ /dev/null @@ -1,31 +0,0 @@ -package net.brokenmoon.redcontrol.screen; - -import net.brokenmoon.redcontrol.RedControl; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.text.Text; -import net.minecraft.util.Identifier; - -public class MonitorScreen extends Screen { - Identifier screenTexture = new Identifier("redcontrol", "gui/display.png"); - public MonitorScreen(Text title) { - super(title); - } - - @Override - protected void init() { - super.init(); - RedControl.LOGGER.info("Opened Monitor Screen"); - } - - @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { - super.render(context, mouseX, mouseY, delta); - context.drawTexture(screenTexture,0,0,0,0,350,230); - } - - @Override - public boolean shouldPause() { - return false; - } -} diff --git a/src/client/java/net/brokenmoon/redcontrol/screen/TerminalScreen.kt b/src/client/java/net/brokenmoon/redcontrol/screen/TerminalScreen.kt new file mode 100644 index 0000000..9646153 --- /dev/null +++ b/src/client/java/net/brokenmoon/redcontrol/screen/TerminalScreen.kt @@ -0,0 +1,261 @@ +package net.brokenmoon.redcontrol.screen + +import com.mojang.blaze3d.platform.GlStateManager +import com.mojang.blaze3d.platform.TextureUtil +import com.mojang.blaze3d.systems.RenderSystem +import io.netty.buffer.Unpooled +import net.brokenmoon.redcontrol.RedControlNetworking +import net.brokenmoon.redcontrol.Shaders +import net.brokenmoon.redcontrol.blocks.TerminalEntity +import net.dblsaiko.qcommon.croco.Mat4 +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking +import net.minecraft.client.MinecraftClient +import net.minecraft.client.gl.Framebuffer +import net.minecraft.client.gl.SimpleFramebuffer +import net.minecraft.client.gui.DrawContext +import net.minecraft.client.gui.screen.Screen +import net.minecraft.client.render.VertexFormat.DrawMode +import net.minecraft.client.render.VertexFormats +import net.minecraft.network.PacketByteBuf +import net.minecraft.text.Text +import net.minecraft.util.math.Vec3d +import org.lwjgl.BufferUtils +import org.lwjgl.glfw.GLFW +import org.lwjgl.opengl.* +import org.lwjgl.opengl.GL11.GL_FLOAT +import org.lwjgl.opengl.GL11.GL_TRIANGLES +import kotlin.experimental.xor +import kotlin.math.round + +private val buf = BufferUtils.createByteBuffer(16384) + +private val vbo = GL30.glGenBuffers() +private val vao = GL30.glGenVertexArrays() +private val screenTex = createTexture() +private val charsetTex = createTexture() + +class TerminalScreen(val te: TerminalEntity) : Screen(Text.translatable("block.redcontrol.terminal")) { + + private var uMvp = 0 + private var uCharset = 0 + private var uScreen = 0 + private var aXyz = 0 + private var aUv = 0 + + private var fb: Framebuffer? = null + + override fun tick() { + val minecraft = client ?: return + val dist = minecraft.player?.getCameraPosVec(1f)?.squaredDistanceTo(Vec3d.ofCenter(te.pos)) + ?: Double.POSITIVE_INFINITY + if (dist > 10 * 10) minecraft.setScreen(null) + } + + override fun render(ctx: DrawContext, mouseX: Int, mouseY: Int, delta: Float) { + renderBackground(ctx,mouseX,mouseY,delta) + val matrices = ctx.matrices + val sh = Shaders.screen() + val fb = fb ?: return + val mc = client ?: return + + fb.setTexFilter(if ((mc.window.scaleFactor.toInt() % 2) == 0) GL11.GL_NEAREST else GL11.GL_LINEAR) + + fb.beginWrite(true) + val mat = Mat4.ortho(0.0f, 1.0f, 1.0f, 0.0f, -1.0f, 1.0f) + + GL30.glUseProgram(sh) + GL30.glBindVertexArray(vao) + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo) + + GL20.glEnableVertexAttribArray(aXyz) + GL20.glEnableVertexAttribArray(aUv) + + RenderSystem.activeTexture(GL13.GL_TEXTURE0) + //RenderSystem.enableTexture() //TODO: figure out why this does not exists + RenderSystem.bindTexture(screenTex) + + buf.clear() + val fbuf = buf.asFloatBuffer() + mat.intoBuffer(fbuf) + fbuf.flip() + GL30.glUniformMatrix4fv(uMvp, false, fbuf) + + GL30.glUniform1i(uScreen, 0) + + buf.clear() + buf.put(te.screen) + + if (te.cm == 1 || (te.cm == 2 && (System.currentTimeMillis() / 500) % 2 == 0L)) { + val ci = te.cx + te.cy * 80 + buf.put(ci, te.screen[ci] xor 0x80.toByte()) + } + + buf.rewind() + GlStateManager._texImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_R16I, 80, 60, 0, GL30.GL_RED_INTEGER, GL11.GL_UNSIGNED_BYTE, buf.asIntBuffer()) + + RenderSystem.activeTexture(GL13.GL_TEXTURE2) + //RenderSystem.enableTexture() //TODO: figure out why this does not exists + RenderSystem.bindTexture(charsetTex) + GL30.glUniform1i(uCharset, 2) + + buf.clear() + buf.put(te.charset) + buf.rewind() + GlStateManager._texImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_R16I, 8, 256, 0, GL30.GL_RED_INTEGER, GL11.GL_UNSIGNED_BYTE, buf.asIntBuffer()) + + GL11.glDrawArrays(GL_TRIANGLES, 0, 6) + + GL20.glDisableVertexAttribArray(aXyz) + GL20.glDisableVertexAttribArray(aUv) + + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, 0) + GL30.glBindVertexArray(0) + GL30.glUseProgram(0) + + RenderSystem.bindTexture(0) + //RenderSystem.disableTexture() //TODO: figure out why this does not exists + RenderSystem.activeTexture(GL13.GL_TEXTURE0) + RenderSystem.bindTexture(0) + + mc.framebuffer.beginWrite(true) + + val swidth = 8 * 80 * 0.5 + val sheight = 8 * 50 * 0.5 + val x1 = round(width / 2.0 - swidth / 2.0) + val y1 = round(height / 2.0 - sheight / 2.0) + + matrices.push() + matrices.translate(x1, y1, -2000.0) // why the -2000? not sure + + val shader = mc.gameRenderer.blitScreenProgram + shader.addSampler("DiffuseSampler", fb.colorAttachment) + shader.modelViewMat?.set(matrices.peek().positionMatrix) + shader.projectionMat?.set(RenderSystem.getProjectionMatrix()) + shader.bind() + + val t = RenderSystem.renderThreadTesselator() + val buf = t.buffer + buf.begin(DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR) + buf.vertex(0.0, 0.0, 0.0).texture(0f, 1f).color(255, 255, 255, 255).next() + buf.vertex(0.0, sheight, 0.0).texture(0f, 0f).color(255, 255, 255, 255).next() + buf.vertex(swidth, sheight, 0.0).texture(1f, 0f).color(255, 255, 255, 255).next() + buf.vertex(swidth, 0.0, 0.0).texture(1f, 1f).color(255, 255, 255, 255).next() + buf.end() + //BufferRenderer.postDraw(buf) //TODO: figure out why this is gone + + shader.unbind() + + matrices.pop() + } + + override fun keyPressed(key: Int, scancode: Int, modifiers: Int): Boolean { + if (super.keyPressed(key, scancode, modifiers)) return true + + val result: Byte? = when (key) { + GLFW.GLFW_KEY_BACKSPACE -> 0x08 + GLFW.GLFW_KEY_ENTER -> 0x0D + GLFW.GLFW_KEY_HOME -> 0x80 + GLFW.GLFW_KEY_END -> 0x81 + GLFW.GLFW_KEY_UP -> 0x82 + GLFW.GLFW_KEY_DOWN -> 0x83 + GLFW.GLFW_KEY_LEFT -> 0x84 + GLFW.GLFW_KEY_RIGHT -> 0x85 + else -> null + }?.toByte() + + if (result != null) pushKey(result) + + return result != null + } + + override fun charTyped(c: Char, modifiers: Int): Boolean { + if (super.charTyped(c, modifiers)) return true + + val result: Byte? = when (c) { + in '\u0001'..'\u007F' -> c.code.toByte() + else -> null + } + + if (result != null) pushKey(result) + + return result != null + } + + private fun pushKey(c: Byte) { + val buffer = PacketByteBuf(Unpooled.buffer()) + buffer.writeBlockPos(te.pos) + buffer.writeByte(c.toInt()) + ClientPlayNetworking.send(RedControlNetworking.KEY_PRESS, buffer) + } + + override fun init() { + //client!!.keyboard.setRepeatEvents(true) //TODO: figure out why this does not exists + + initDrawData() + initFb() + } + + private fun initDrawData() { + val sh = Shaders.screen() + + GL30.glUseProgram(sh) + GL30.glBindVertexArray(vao) + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo) + + uMvp = GL30.glGetUniformLocation(sh, "mvp") + uCharset = GL30.glGetUniformLocation(sh, "charset") + uScreen = GL30.glGetUniformLocation(sh, "screen") + + aXyz = GL30.glGetAttribLocation(sh, "xyz") + aUv = GL30.glGetAttribLocation(sh, "uv") + + GL20.glVertexAttribPointer(aXyz, 3, GL_FLOAT, false, 20, 0) + GL20.glVertexAttribPointer(aUv, 2, GL_FLOAT, false, 20, 12) + + buf.clear() + + floatArrayOf( + 0f, 0f, 0f, 0f, 0f, + 1f, 1f, 0f, 1f, 1f, + 1f, 0f, 0f, 1f, 0f, + + 0f, 0f, 0f, 0f, 0f, + 0f, 1f, 0f, 0f, 1f, + 1f, 1f, 0f, 1f, 1f + ).forEach { buf.putFloat(it) } + + buf.rewind() + + GL30.glBufferData(GL30.GL_ARRAY_BUFFER, buf, GL15.GL_STATIC_DRAW) + + GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, 0) + GL30.glBindVertexArray(0) + GL30.glUseProgram(0) + } + + private fun initFb() { + fb?.delete() + val scale = 4 + fb = SimpleFramebuffer(80 * 8 * scale, 50 * 8 * scale, false, MinecraftClient.IS_SYSTEM_MAC) + } + + override fun removed() { + //client!!.keyboard.setRepeatEvents(false) //TODO: check if this is still okay. or if we need to change this + fb?.delete() + fb = null + } + + override fun shouldPause() = false + +} + +private fun createTexture(): Int { + val tex = TextureUtil.generateTextureId() + RenderSystem.bindTexture(tex) + RenderSystem.texParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST) + RenderSystem.texParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST) + RenderSystem.texParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT) + RenderSystem.texParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT) + RenderSystem.bindTexture(0) + return tex +} \ No newline at end of file diff --git a/src/main/java/net/brokenmoon/redcontrol/RedControl.java b/src/main/java/net/brokenmoon/redcontrol/RedControl.java index 420b04d..c1d493d 100644 --- a/src/main/java/net/brokenmoon/redcontrol/RedControl.java +++ b/src/main/java/net/brokenmoon/redcontrol/RedControl.java @@ -2,10 +2,10 @@ package net.brokenmoon.redcontrol; import net.brokenmoon.redcontrol.blockentities.CpuEntity; import net.brokenmoon.redcontrol.blockentities.DriveEntity; -import net.brokenmoon.redcontrol.blockentities.MonitorEntity; import net.brokenmoon.redcontrol.blocks.CpuBlock; import net.brokenmoon.redcontrol.blocks.DriveBlock; -import net.brokenmoon.redcontrol.blocks.MonitorBlock; +import net.brokenmoon.redcontrol.blocks.TerminalBlock; +import net.brokenmoon.redcontrol.blocks.TerminalEntity; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.item.v1.FabricItemSettings; @@ -14,7 +14,6 @@ import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; import net.fabricmc.fabric.api.resource.ResourceManagerHelper; import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener; -import net.minecraft.block.Block; import net.minecraft.block.entity.BlockEntityType; import net.minecraft.item.BlockItem; import net.minecraft.item.Item; @@ -23,7 +22,6 @@ import net.minecraft.registry.Registries; import net.minecraft.resource.Resource; import net.minecraft.resource.ResourceManager; import net.minecraft.resource.ResourceType; -import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; import org.slf4j.Logger; @@ -42,13 +40,13 @@ public class RedControl implements ModInitializer { //Blocks public static final CpuBlock CPU = new CpuBlock(FabricBlockSettings.create().strength(4.0f)); - public static final MonitorBlock MONITOR = new MonitorBlock(FabricBlockSettings.create().strength(4.0f)); + public static final TerminalBlock TERMINAL = new TerminalBlock(FabricBlockSettings.create().strength(4.0f)); public static final DriveBlock DRIVE = new DriveBlock(FabricBlockSettings.create().strength(4.0f)); //Items //Block Entities public static final BlockEntityType CPU_BLOCK_ENTITY = Registry.register(Registries.BLOCK_ENTITY_TYPE, modloc("cpu_block_entity"), FabricBlockEntityTypeBuilder.create(CpuEntity::new, CPU).build()); - public static final BlockEntityType MONITOR_BLOCK_ENTITY = Registry.register(Registries.BLOCK_ENTITY_TYPE, modloc("monitor_block_entity"), FabricBlockEntityTypeBuilder.create(MonitorEntity::new, MONITOR).build()); + public static final BlockEntityType TERMINAL_BLOCK_ENTITY = Registry.register(Registries.BLOCK_ENTITY_TYPE, modloc("monitor_block_entity"), FabricBlockEntityTypeBuilder.create(TerminalEntity::new, TERMINAL).build()); public static final BlockEntityType DRIVE_BLOCK_ENTITY = Registry.register(Registries.BLOCK_ENTITY_TYPE, modloc("drive_block_entity"), FabricBlockEntityTypeBuilder.create(DriveEntity::new, DRIVE).build()); public static final Item SQUEAKY_HAMMER = new Item(new FabricItemSettings()); @@ -69,55 +67,48 @@ public class RedControl implements ModInitializer { public void reload(ResourceManager manager) { images.clear(); //remove all previous images for(Map.Entry entry : manager.findResources("image", path -> true).entrySet()) { - try(InputStream stream = entry.getValue().getInputStream();) { + try(InputStream stream = entry.getValue().getInputStream()) { String[] split = entry.getKey().getPath().split("/"); String file = split[split.length-1]; LOGGER.info("Found image {}",file); images.put(file,stream.readAllBytes()); } catch(Exception e) { - LOGGER.error("Error occurred while loading resource json {}", entry.getKey().toString(), e); + LOGGER.error("Error occurred while loading resource binary {}", entry.getKey().toString(), e); } - } } }); LOGGER.info("Initializing RedControl!"); Registry.register(Registries.BLOCK, modloc("cpu"), CPU); - Registry.register(Registries.BLOCK, modloc("monitor"), MONITOR); + Registry.register(Registries.BLOCK, modloc("monitor"), TERMINAL); Registry.register(Registries.BLOCK, modloc("disk_drive"), DRIVE); Registry.register(Registries.ITEM, modloc("cpu"), new BlockItem(CPU, new FabricItemSettings())); - Registry.register(Registries.ITEM, modloc("monitor"), new BlockItem(MONITOR, new FabricItemSettings())); + Registry.register(Registries.ITEM, modloc("monitor"), new BlockItem(TERMINAL, new FabricItemSettings())); Registry.register(Registries.ITEM, modloc("disk_drive"), new BlockItem(DRIVE, new FabricItemSettings())); Registry.register(Registries.ITEM, modloc("squeaky_hammer"), SQUEAKY_HAMMER); //Packets - ServerPlayNetworking.registerGlobalReceiver(RedControlNetworking.CPU_START, ((server, player, handler, buf, responseSender) -> { - server.execute(() -> { - BlockPos blockPos = buf.readBlockPos(); - CpuEntity cpu = (CpuEntity) player.getWorld().getBlockEntity(blockPos); - cpu.start(); - LOGGER.info("Starting cpu at {}",blockPos); - }); - })); - - ServerPlayNetworking.registerGlobalReceiver(RedControlNetworking.CPU_STOP, ((server, player, handler, buf, responseSender) -> { - server.execute(() -> { - BlockPos blockPos = buf.readBlockPos(); - CpuEntity cpu = (CpuEntity) player.getWorld().getBlockEntity(blockPos); - cpu.stop(); - LOGGER.info("Stopping cpu at {}",blockPos); - }); - })); - - ServerPlayNetworking.registerGlobalReceiver(RedControlNetworking.CPU_RESET, ((server, player, handler, buf, responseSender) -> { - server.execute(() -> { - BlockPos blockPos = buf.readBlockPos(); - CpuEntity cpu = (CpuEntity) player.getWorld().getBlockEntity(blockPos); - cpu.reset(); - LOGGER.info("Resetting cpu at {}", blockPos); - }); - })); + ServerPlayNetworking.registerGlobalReceiver(RedControlNetworking.CPU_START, ((server, player, handler, buf, responseSender) -> server.execute(() -> { + BlockPos blockPos = buf.readBlockPos(); + CpuEntity cpu = (CpuEntity) player.getWorld().getBlockEntity(blockPos); + cpu.start(); + LOGGER.info("Starting cpu at {}",blockPos); + }))); + + ServerPlayNetworking.registerGlobalReceiver(RedControlNetworking.CPU_STOP, ((server, player, handler, buf, responseSender) -> server.execute(() -> { + BlockPos blockPos = buf.readBlockPos(); + CpuEntity cpu = (CpuEntity) player.getWorld().getBlockEntity(blockPos); + cpu.stop(); + LOGGER.info("Stopping cpu at {}",blockPos); + }))); + + ServerPlayNetworking.registerGlobalReceiver(RedControlNetworking.CPU_RESET, ((server, player, handler, buf, responseSender) -> server.execute(() -> { + BlockPos blockPos = buf.readBlockPos(); + CpuEntity cpu = (CpuEntity) player.getWorld().getBlockEntity(blockPos); + cpu.reset(); + LOGGER.info("Resetting cpu at {}", blockPos); + }))); } static Identifier modloc(String path) {return new Identifier("redcontrol",path);} diff --git a/src/main/java/net/brokenmoon/redcontrol/RedControlNetworking.java b/src/main/java/net/brokenmoon/redcontrol/RedControlNetworking.java index 854eede..7e0d709 100644 --- a/src/main/java/net/brokenmoon/redcontrol/RedControlNetworking.java +++ b/src/main/java/net/brokenmoon/redcontrol/RedControlNetworking.java @@ -2,10 +2,13 @@ package net.brokenmoon.redcontrol; import net.minecraft.util.Identifier; +import static net.brokenmoon.redcontrol.RedControl.modloc; + public class RedControlNetworking { - public static final Identifier CPUGUI_PACKET_ID = new Identifier("redcontrol", "open_cpu_gui"); - public static final Identifier MONITOR_PACKET_ID = new Identifier("redcontrol", "open_monitor"); - public static final Identifier CPU_START = new Identifier("redcontrol", "start_cpu"); - public static final Identifier CPU_STOP = new Identifier("redcontrol", "stop_cpu"); - public static final Identifier CPU_RESET = new Identifier("redcontrol", "reset_cpu"); + public static final Identifier CPUGUI_PACKET_ID = modloc("open_cpu_gui"); + public static final Identifier MONITOR_PACKET_ID = modloc("open_monitor"); + public static final Identifier CPU_START = modloc("start_cpu"); + public static final Identifier CPU_STOP = modloc("stop_cpu"); + public static final Identifier CPU_RESET = modloc("reset_cpu"); + public static final Identifier KEY_PRESS = modloc("key_press"); } diff --git a/src/main/java/net/brokenmoon/redcontrol/blockentities/MonitorEntity.java b/src/main/java/net/brokenmoon/redcontrol/blockentities/MonitorEntity.java deleted file mode 100644 index a4d39c2..0000000 --- a/src/main/java/net/brokenmoon/redcontrol/blockentities/MonitorEntity.java +++ /dev/null @@ -1,276 +0,0 @@ -package net.brokenmoon.redcontrol.blockentities; - -import net.brokenmoon.redcontrol.RedControl; -import net.minecraft.block.BlockState; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.network.listener.ClientPlayPacketListener; -import net.minecraft.network.packet.Packet; -import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket; -import net.minecraft.util.math.BlockPos; -import org.jetbrains.annotations.Nullable; - -public class MonitorEntity extends Peripheral { - - public static final int WIDTH = 80; - public static final int HEIGHT = 50; - - private int accessRow; - private int cursorX; - private int cursorY; - private int cursorMode = 2; // (0: hidden, 1: solid, 2: blink) - private int keyBufferStart; - private int keyBufferPos; - private int blitMode; // (1: fill, 2: invert; 3: shift) - private int blitXStartOrFill; - private int blitYStart; - private int blitXOffset; - private int blitYOffset; - private int blitWidth; - private int blitHeight; - - private byte[] keyBuffer = new byte[0x10]; - private byte[][] windowData = new byte[HEIGHT][WIDTH]; - - public String[] getText(){ - String[] l = new String[HEIGHT]; - for(int y = 0; y < HEIGHT; y++) { - String temp = ""; - for (int x = 0; x < WIDTH; x++) { - temp += (char) windowData[y][x]; - } - l[y] = temp; - } - return l; - } - - private boolean isDisplayDirty; - private boolean isCursorDirty; - - public MonitorEntity(BlockPos pos, BlockState state) { - super(RedControl.MONITOR_BLOCK_ENTITY, pos, state, 1); - } - - @Override - public void write(int address, int data) { - switch (address) { - case 0x00: - this.accessRow = data; - break; - case 0x01: - this.isCursorDirty = this.cursorX != data; - this.cursorX = data; - break; - case 0x02: - this.isCursorDirty = this.cursorY != data; - this.cursorY = data; - break; - case 0x03: - this.isCursorDirty = this.cursorMode != data; - this.cursorMode = data; - break; - case 0x04: - this.keyBufferStart = data & 0x0f; - break; - case 0x05: - this.keyBufferPos = data & 0x0f; - break; - case 0x06: - break; - case 0x07: - this.blitMode = data; - break; - case 0x08: - this.blitXStartOrFill = data; - break; - case 0x09: - this.blitYStart = data; - break; - case 0x0A: - this.blitXOffset = data; - break; - case 0x0B: - this.blitYOffset = data; - break; - case 0x0C: - this.blitWidth = data; - break; - case 0x0D: - this.blitHeight = data; - break; - default: - if (address >= 0x10 && address < 0x60) { - this.isDisplayDirty = true; - this.windowData[this.accessRow][address - 0x10] = (byte) data; - } - if (this.isDisplayDirty) { - this.isDisplayDirty = false; - this.update(); - } - } - } - - @Override - public int read(int address) { - switch (address) { - case 0x00: - return this.accessRow; - case 0x01: - return this.cursorX; - case 0x02: - return this.cursorY; - case 0x03: - return this.cursorMode; - case 0x04: - return this.keyBufferStart; - case 0x05: - return this.keyBufferPos; - case 0x06: - return this.keyBuffer[this.keyBufferStart] & 0xff; - case 0x07: - return this.blitMode; - case 0x08: - return this.blitXStartOrFill; - case 0x09: - return this.blitYStart; - case 0x0A: - return this.blitXOffset; - case 0x0B: - return this.blitYOffset; - case 0x0C: - return this.blitWidth; - case 0x0D: - return this.blitHeight; - default: - if (address >= 0x10 && address < 0x60) { - return this.windowData[this.accessRow][address - 0x10] & 0xff; - } - return 0; - } - } - - @Override - public void update() { - int maxWidth = Math.min(WIDTH, this.blitWidth + this.blitXOffset); - int maxHeight = Math.min(HEIGHT, this.blitHeight + this.blitYOffset); - - int row = this.blitYOffset; - int col; - - this.isDisplayDirty |= this.blitMode != 0; - - switch (this.blitMode) { - case 1: // fill - for (; row < maxHeight; row++) { - for (col = this.blitXOffset; col < maxWidth; col++) { - this.windowData[row][col] = (byte) this.blitXStartOrFill; - } - } - break; - case 2: // invert - for (; row < maxHeight; row++) { - for (col = this.blitXOffset; col < maxWidth; col++) { - this.windowData[row][col] ^= 0x80; - } - } - break; - case 3: // shift - int shiftX = this.blitXStartOrFill - this.blitXOffset; - int shiftY = this.blitYStart - this.blitYOffset; - for (; row < maxHeight; row++) { - int srcRow = row + shiftY; - if (srcRow >= 0 & srcRow < HEIGHT) { - for (col = this.blitXOffset; col < maxWidth; col++) { - int srcCol = col + shiftX; - if (srcCol >= 0 && srcCol < WIDTH) { - this.windowData[row][col] = this.windowData[srcRow][srcCol]; - } - } - } - } - break; - } - - this.blitMode = 0; - - if (this.isCursorDirty) { - this.isCursorDirty = false; - this.updateCursor(this.cursorX, this.cursorY, this.cursorMode); - } - } - - private void updateCursor(int cursorX, int cursorY, int cursorMode) { - } - - /** - * Appends a key code to the key buffer. - * - * @param key The key code - */ - public void onKey(byte key) { - int nextPos = (this.keyBufferPos + 1) & 0x0f; - if (nextPos != this.keyBufferStart) { - this.keyBuffer[this.keyBufferPos] = key; - this.keyBufferPos = nextPos; - } - } - - @Override - protected void writeNbt(NbtCompound nbt) { - nbt.putByteArray("keyBuffer", this.keyBuffer); - for(int i = 0; i < windowData.length; i++){ - nbt.putByteArray("winData" + i, windowData[i]); - } - - nbt.putInt("acessRow", accessRow); - nbt.putInt("cursorX", cursorX); - nbt.putInt("cursorY", cursorY); - nbt.putInt("cursorMode", cursorMode); - nbt.putInt("keyBufferStart", keyBufferStart); - nbt.putInt("keyBufferPos", keyBufferPos); - nbt.putInt("blitMode", blitMode); - nbt.putInt("blitXStartOrFill", blitXStartOrFill); - nbt.putInt("blitYStart", blitYStart); - nbt.putInt("blitXOffset", blitXOffset); - nbt.putInt("blitYOffset", blitYOffset); - nbt.putInt("blitWidth", blitWidth); - nbt.putInt("blitHeight", blitHeight); - - super.writeNbt(nbt); - } - - @Override - public void readNbt(NbtCompound nbt) { - this.keyBuffer = nbt.getByteArray("keyBuffer"); - - for(int i = 0; i < windowData.length; i++){ - windowData[i] = nbt.getByteArray("winData" + i); - } - - accessRow = nbt.getInt("accessRow"); - cursorX = nbt.getInt("cursorX"); - cursorY = nbt.getInt("cursorY"); - cursorMode = nbt.getInt("cursorMode"); - keyBufferStart = nbt.getInt("keyBufferStart"); - keyBufferPos = nbt.getInt("keyBufferPos"); - blitMode = nbt.getInt("blitMode"); - blitXStartOrFill = nbt.getInt("blitXStartOrFill"); - blitYStart = nbt.getInt("blitYStart"); - blitXOffset = nbt.getInt("blitXOffset"); - blitYOffset = nbt.getInt("blitYOffset"); - blitWidth = nbt.getInt("blitWidth"); - blitHeight = nbt.getInt("blitHeight"); - - super.readNbt(nbt); - } - - @Nullable - @Override - public Packet toUpdatePacket() { - return BlockEntityUpdateS2CPacket.create(this); - } - - @Override - public NbtCompound toInitialChunkDataNbt() { - return createNbt(); - } -} diff --git a/src/main/java/net/brokenmoon/redcontrol/blocks/CpuBlock.java b/src/main/java/net/brokenmoon/redcontrol/blocks/CpuBlock.java index b1362c3..05581ed 100644 --- a/src/main/java/net/brokenmoon/redcontrol/blocks/CpuBlock.java +++ b/src/main/java/net/brokenmoon/redcontrol/blocks/CpuBlock.java @@ -1,12 +1,9 @@ package net.brokenmoon.redcontrol.blocks; import com.mojang.serialization.MapCodec; -import com.simon816.j65el02.Cpu; import net.brokenmoon.redcontrol.RedControl; import net.brokenmoon.redcontrol.RedControlNetworking; import net.brokenmoon.redcontrol.blockentities.CpuEntity; -import net.brokenmoon.redcontrol.blockentities.MonitorEntity; -import net.brokenmoon.redcontrol.blockentities.Peripheral; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.block.*; @@ -42,7 +39,7 @@ public class CpuBlock extends NetworkCarrier { @Override public BlockEntityTicker getTicker(World world, BlockState state, BlockEntityType type) { - return validateTicker(type, RedControl.CPU_BLOCK_ENTITY, (world1, pos, state1, be) -> CpuEntity.tick(world1, pos, state1, be)); + return validateTicker(type, RedControl.CPU_BLOCK_ENTITY, CpuEntity::tick); } @Override diff --git a/src/main/java/net/brokenmoon/redcontrol/blocks/DriveBlock.java b/src/main/java/net/brokenmoon/redcontrol/blocks/DriveBlock.java index 4b1c4a1..b72745d 100644 --- a/src/main/java/net/brokenmoon/redcontrol/blocks/DriveBlock.java +++ b/src/main/java/net/brokenmoon/redcontrol/blocks/DriveBlock.java @@ -3,7 +3,6 @@ package net.brokenmoon.redcontrol.blocks; import com.mojang.serialization.MapCodec; import net.brokenmoon.redcontrol.RedControl; import net.brokenmoon.redcontrol.blockentities.DriveEntity; -import net.brokenmoon.redcontrol.blockentities.MonitorEntity; import net.minecraft.block.BlockState; import net.minecraft.block.BlockWithEntity; import net.minecraft.block.entity.BlockEntity; diff --git a/src/main/java/net/brokenmoon/redcontrol/blocks/MonitorBlock.java b/src/main/java/net/brokenmoon/redcontrol/blocks/MonitorBlock.java deleted file mode 100644 index 57e3ac6..0000000 --- a/src/main/java/net/brokenmoon/redcontrol/blocks/MonitorBlock.java +++ /dev/null @@ -1,60 +0,0 @@ -package net.brokenmoon.redcontrol.blocks; - -import com.mojang.serialization.MapCodec; -import net.brokenmoon.redcontrol.RedControl; -import net.brokenmoon.redcontrol.RedControlNetworking; -import net.brokenmoon.redcontrol.blockentities.MonitorEntity; -import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; -import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; -import net.minecraft.block.Block; -import net.minecraft.block.BlockEntityProvider; -import net.minecraft.block.BlockState; -import net.minecraft.block.BlockWithEntity; -import net.minecraft.block.entity.BlockEntity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.network.PacketByteBuf; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.text.Text; -import net.minecraft.util.ActionResult; -import net.minecraft.util.Hand; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.World; -import org.jetbrains.annotations.Nullable; - -public class MonitorBlock extends NetworkCarrier { - public MonitorBlock(Settings settings) { - super(settings); - } - - @Override - protected MapCodec getCodec() { - return null; - } - - @Nullable - @Override - public BlockEntity createBlockEntity(BlockPos pos, BlockState state) { - return new MonitorEntity(pos, state); - } - - @Override - public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { - if (!world.isClient) { - if (player.getStackInHand(hand).getItem() == RedControl.SQUEAKY_HAMMER) { - super.onUse(state,world,pos,player,hand,hit); - MonitorEntity monitor = (MonitorEntity) world.getBlockEntity(pos); - player.sendMessage(Text.literal(String.valueOf(monitor.getBus().hashCode())), false); - String[] text = monitor.getText(); - for (String s : text) { - player.sendMessage(Text.literal(s), false); - } - } - PacketByteBuf byteBuf = PacketByteBufs.create(); - byteBuf.writeBlockPos(pos); - ServerPlayNetworking.send((ServerPlayerEntity) player, RedControlNetworking.MONITOR_PACKET_ID,byteBuf); - } - - return ActionResult.SUCCESS; - } -} diff --git a/src/main/java/net/brokenmoon/redcontrol/blocks/Terminal.kt b/src/main/java/net/brokenmoon/redcontrol/blocks/Terminal.kt new file mode 100644 index 0000000..63b7ec4 --- /dev/null +++ b/src/main/java/net/brokenmoon/redcontrol/blocks/Terminal.kt @@ -0,0 +1,220 @@ +package net.brokenmoon.redcontrol.blocks + +import com.mojang.serialization.MapCodec +import net.brokenmoon.redcontrol.RedControl +import net.brokenmoon.redcontrol.RedControlNetworking +import net.brokenmoon.redcontrol.blockentities.Peripheral +import net.brokenmoon.redcontrol.util.unsigned +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking +import net.minecraft.block.BlockState +import net.minecraft.block.BlockWithEntity +import net.minecraft.entity.player.PlayerEntity +import net.minecraft.nbt.NbtCompound +import net.minecraft.server.network.ServerPlayerEntity +import net.minecraft.util.ActionResult +import net.minecraft.util.ActionResult.FAIL +import net.minecraft.util.ActionResult.SUCCESS +import net.minecraft.util.Hand +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.world.World +import java.io.FileNotFoundException +import kotlin.experimental.xor + +class TerminalBlock(settings: Settings) : NetworkCarrier(settings) { + + @Suppress("OVERRIDE_DEPRECATION") + override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hit: BlockHitResult): ActionResult { + if (world.getBlockEntity(pos) !is TerminalEntity) return FAIL + if (!world.isClient) { + val bbuf = PacketByteBufs.create() + bbuf.writeBlockPos(pos) + ServerPlayNetworking.send(player as ServerPlayerEntity,RedControlNetworking.MONITOR_PACKET_ID,bbuf) + } + return SUCCESS + } + + override fun getCodec(): MapCodec? { + return null + } + + + override fun createBlockEntity(pos: BlockPos, state: BlockState) = TerminalEntity(pos, state) + +} + +class TerminalEntity(pos: BlockPos, state: BlockState) : Peripheral(RedControl.TERMINAL_BLOCK_ENTITY, pos, state,1) { + + val screen = ByteArray(80 * 50) { 0x20 } + val charset = RedControl.images["charset.bin"]?.clone()?: throw FileNotFoundException("expected a charset.bin in images datapack") + val kb = ByteArray(16) + + var command: Byte = 0 + + var row = 0 + var cx = 0 + var cy = 0 + var cm = 2 + var kbs = 0 + var kbp = 0 + + var bx1 = 0 + var by1 = 0 + var bx2 = 0 + var by2 = 0 + var bw = 0 + var bh = 0 + + var char = 0 + + fun pushKey(byte: Byte): Boolean { + return if ((kbp + 1) % 16 != kbs) { + kb[kbp] = byte + kbp = (kbp + 1) % 16 + true + } else false + } + + fun getIndices(x1: Int, y1: Int, w: Int, h: Int): Sequence = sequence { + for (i in 0 until h) for (j in 0 until w) { + val x = j + x1 + val y = i + y1 + + if (x in 0 until 80 && y in 0 until 60) + yield(x + 80 * y) + } + } + + override fun read(address: Int): Int = readData(address.toByte()).toInt() + override fun update() { + val data = this + var error = false + + when (data.command.unsigned) { + 1 -> data.getIndices(data.bx2, data.by2, data.bw, data.bh).forEach { data.screen[it] = data.bx1.toByte() } + 2 -> data.getIndices(data.bx2, data.by2, data.bw, data.bh).forEach { data.screen[it] = data.screen[it] xor 0x80.toByte() } + 3 -> data.getIndices(data.bx2, data.by2, data.bw, data.bh).zip(data.getIndices(data.bx1, data.by1, data.bw, data.bh)).forEach { (dest, src) -> data.screen[dest] = data.screen[src] } + 4 -> RedControl.images["charset.bin"]!!.copyInto(data.charset) + 255 -> Unit + else -> error = true + } + + if (data.command in 1..4) world?.updateListeners(pos, cachedState, cachedState, 3) + + data.command = if (error) -1 else 0 + } + + override fun write(address: Int, data: Int) = storeData(address.toByte(),data.toByte()) + + private fun readData(at: Byte): Byte { + return when (val at = at.unsigned) { + 0x00 -> row.toByte() + 0x01 -> cx.toByte() + 0x02 -> cy.toByte() + 0x03 -> cm.toByte() + 0x04 -> kbs.toByte() + 0x05 -> kbp.toByte() + 0x06 -> kb[kbs] + 0x07 -> command + 0x08 -> bx1.toByte() + 0x09 -> by1.toByte() + 0x0A -> bx2.toByte() + 0x0B -> by2.toByte() + 0x0C -> bw.toByte() + 0x0D -> bh.toByte() + 0x0E -> char.toByte() + in 0x10..0x5F -> screen[row * 80 + at - 0x10] + in 0x60..0x67 -> charset[char * 8 + at - 0x60] + else -> 0 + } + } + + private fun storeData(at: Byte, data: Byte) { + when (val at = at.unsigned) { + 0x00 -> row = data.unsigned % 50 + 0x01 -> cx = data.unsigned % 80 + 0x02 -> cy = data.unsigned % 50 + 0x03 -> cm = data.unsigned % 3 + 0x04 -> kbs = data.unsigned % 16 + 0x05 -> kbp = data.unsigned % 16 + 0x06 -> kb[kbs] = data + 0x07 -> command = data + 0x08 -> bx1 = data.unsigned % 80 + 0x09 -> by1 = data.unsigned % 50 + 0x0A -> bx2 = data.unsigned % 80 + 0x0B -> by2 = data.unsigned % 50 + 0x0C -> bw = data.unsigned + 0x0D -> bh = data.unsigned + 0x0E -> char = data.unsigned + in 0x10..0x5F -> screen[row * 80 + at - 0x10] = data + in 0x60..0x67 -> charset[char * 8 + at - 0x60] = data + } + + val needsClientUpdate = at.unsigned in setOf(0x01, 0x02, 0x03) + (0x10..0x67) + if (needsClientUpdate) + getWorld()?.updateListeners(getPos(), cachedState, cachedState, 3) + markDirty() + } + + override fun toInitialChunkDataNbt(): NbtCompound { + val tag = super.toInitialChunkDataNbt() + // these are big, TODO: only send changed data + tag.putByteArray("screen", screen) + tag.putByteArray("charset", charset) + tag.putByte("cx", cx.toByte()) + tag.putByte("cy", cy.toByte()) + tag.putByte("cm", cm.toByte()) + return tag + } + + override fun writeNbt(tag: NbtCompound) { + super.writeNbt(tag) + tag.putByteArray("screen", screen) + tag.putByteArray("charset", charset) + tag.putByteArray("kb", kb) + tag.putByte("command", command) + tag.putByte("row", row.toByte()) + tag.putByte("cx", cx.toByte()) + tag.putByte("cy", cy.toByte()) + tag.putByte("cm", cm.toByte()) + tag.putByte("kbs", kbs.toByte()) + tag.putByte("kbp", kbp.toByte()) + tag.putByte("bx1", bx1.toByte()) + tag.putByte("by1", by1.toByte()) + tag.putByte("bx2", bx2.toByte()) + tag.putByte("by2", by2.toByte()) + tag.putByte("bw", bw.toByte()) + tag.putByte("bh", bh.toByte()) + tag.putByte("char", char.toByte()) + } + + override fun readNbt(tag: NbtCompound) { + super.readNbt(tag) + val world = getWorld() + + tag.getByteArray("screen").copyInto(screen) + tag.getByteArray("charset").copyInto(charset) + cx = tag.getByte("cx").unsigned + cy = tag.getByte("cy").unsigned + cm = tag.getByte("cm").unsigned + + if (world == null || !world.isClient) { + tag.getByteArray("screen").copyInto(screen) + tag.getByteArray("charset").copyInto(charset) + tag.getByteArray("kb").copyInto(kb) + command = tag.getByte("command") + row = tag.getByte("row").unsigned + kbs = tag.getByte("kbs").unsigned + kbp = tag.getByte("kbp").unsigned + bx1 = tag.getByte("bx1").unsigned + by1 = tag.getByte("by1").unsigned + bx2 = tag.getByte("bx2").unsigned + by2 = tag.getByte("by2").unsigned + bw = tag.getByte("bw").unsigned + bh = tag.getByte("bh").unsigned + char = tag.getByte("char").unsigned + } + } + +} \ No newline at end of file diff --git a/src/main/java/net/brokenmoon/redcontrol/util/Numbers.kt b/src/main/java/net/brokenmoon/redcontrol/util/Numbers.kt new file mode 100644 index 0000000..69007e8 --- /dev/null +++ b/src/main/java/net/brokenmoon/redcontrol/util/Numbers.kt @@ -0,0 +1,24 @@ +package net.brokenmoon.redcontrol.util + +val Byte.unsigned: Int + get() = this.toInt() and 0xFF + +val Short.unsigned: Int + get() = this.toInt() and 0xFFFF + +val Int.unsigned: Long + get() = this.toLong() and 0xFFFFFFFF + +infix fun Int.pmod(i: Int): Int = (this % i).let { if (it < 0) it + i else it } +infix fun Long.pmod(i: Int): Int = (this % i).let { if (it < 0) (it + i).toInt() else it.toInt() } +infix fun Long.pmod(l: Long): Long = (this % l).let { if (it < 0) it + l else it } + +infix fun Byte.pmod(i: Int): Byte = (this % i).let { if (it < 0) (it + i).toByte() else it.toByte() } + +infix fun Byte.shr(i: Int): Byte = (this.toInt() shr i).toByte() +infix fun Byte.ushr(i: Int): Byte = (this.unsigned ushr i).toByte() +infix fun Byte.shl(i: Int): Byte = (this.toInt() shl i).toByte() + +infix fun Short.shr(i: Int): Short = (this.toInt() shr i).toShort() +infix fun Short.ushr(i: Int): Short = (this.unsigned ushr i).toShort() +infix fun Short.shl(i: Int): Short = (this.toInt() shl i).toShort() \ No newline at end of file diff --git a/src/main/resources/assets/redcontrol/shaders/screen.frag b/src/main/resources/assets/redcontrol/shaders/screen.frag new file mode 100644 index 0000000..2c6b916 --- /dev/null +++ b/src/main/resources/assets/redcontrol/shaders/screen.frag @@ -0,0 +1,64 @@ +#version 330 core + +#define SCREEN_WIDTH 80 +#define SCREEN_HEIGHT 50 + +#define BGCOLOR vec3(0.09, 0.07, 0) +#define FGCOLOR vec3(0.78, 0.57, 0.01) + +uniform usampler2D charset; +uniform usampler2D screen; + +in vec2 f_uv; + +out vec4 fragColor; + +float get_pixel(in ivec2 px) { + // where is this character on the screen? (0,0) - (SCREEN_WIDTH,SCREEN_HEIGHT) + ivec2 char = px / 8; + + // which pixel of this character is this? (0,0)-(8,8) + ivec2 chPixel = px % 8; + + // which character is this? 0-255 + int chIndex = int(texelFetch(screen, char, 0).x); + + // the bitmap of the currently drawing line of the character + int lineData = int(texelFetch(charset, ivec2(chPixel.y, chIndex), 0).x); + + return float((lineData >> (7 - chPixel.x)) & 1); +} + +float get_pixel_with_fx(in vec2 screenPos) { + ivec2 px = ivec2(screenPos); + vec2 partial = fract(screenPos); + + float strength = get_pixel(px); + + float x = 0; + for (int dist = 1; dist < 6; dist++) { + float f = get_pixel(px - ivec2(dist, 0)) * 0.3; + if (f > 0) { + x = f * pow(0.5, dist + partial.x - 1); + break; + } + } + strength = min(1, strength + x); + + if (px.y % 2 == 0) strength *= 0.9; + strength *= mix(0.25, 1, sin(partial.y * 3.141592654)); + + strength *= dot(vec3(0, 0, 1), normalize(vec3(px - 0.5 * vec2(SCREEN_WIDTH * 8, SCREEN_HEIGHT * 8), 300))); + + return strength; +} + +void main() { + vec2 screenPos = vec2(f_uv.x * SCREEN_WIDTH * 8, f_uv.y * SCREEN_HEIGHT * 8); + + float strength = get_pixel_with_fx(screenPos); + + vec3 color = mix(BGCOLOR, FGCOLOR, strength); + + fragColor = vec4(color, 1); +} \ No newline at end of file diff --git a/src/main/resources/assets/redcontrol/shaders/screen.vert b/src/main/resources/assets/redcontrol/shaders/screen.vert new file mode 100644 index 0000000..896db48 --- /dev/null +++ b/src/main/resources/assets/redcontrol/shaders/screen.vert @@ -0,0 +1,13 @@ +#version 330 core + +in vec3 xyz; +in vec2 uv; + +out vec2 f_uv; + +uniform mat4 mvp; + +void main() { + f_uv = uv; + gl_Position = mvp * vec4(xyz, 1); +} \ No newline at end of file diff --git a/src/main/resources/data/redcontrol/image/charset.bin b/src/main/resources/data/redcontrol/image/charset.bin new file mode 100644 index 0000000..5837a15 Binary files /dev/null and b/src/main/resources/data/redcontrol/image/charset.bin differ