diff --git a/build.gradle b/build.gradle index c9f314ab2..7c1e4bc59 100644 --- a/build.gradle +++ b/build.gradle @@ -82,11 +82,19 @@ sourceSets { compileClasspath += sourceSets.api.output runtimeClasspath += sourceSets.api.output } + siteexport { + compileClasspath += sourceSets.api.output + runtimeClasspath += sourceSets.api.output + compileClasspath += sourceSets.main.output + runtimeClasspath += sourceSets.main.output + } } configurations { apiCompile.extendsFrom(compileClasspath) datagenCompile.extendsFrom(compileClasspath) + siteexportCompile.extendsFrom(compileClasspath) + siteexportRuntime.extendsFrom(runtimeClasspath) } apply from: 'gradle/scripts/spotless.gradle' @@ -177,7 +185,7 @@ publishing { import net.fabricmc.loom.task.RunClientTask; -task generateData(type: RunClientTask, dependsOn: downloadAssets, group: "ae2") { +task generateData(type: RunClientTask, dependsOn: downloadAssets, group: "ae2", description: "Generates various JSON assets for the mod") { classpath = configurations.runtimeClasspath classpath sourceSets.api.output classpath sourceSets.main.output @@ -185,3 +193,10 @@ task generateData(type: RunClientTask, dependsOn: downloadAssets, group: "ae2") systemProperty "appeng.generateData", "true" } build.dependsOn generateData + +task runSiteExport(type: RunClientTask, dependsOn: downloadAssets, group: "ae2", description: "Export game assets for the website") { + classpath = configurations.runtimeClasspath + classpath sourceSets.api.output + classpath sourceSets.main.output + classpath sourceSets.siteexport.output +} diff --git a/src/main/java/appeng/spatial/CachedPlane.java b/src/main/java/appeng/spatial/CachedPlane.java index 801ce4719..bb5943193 100644 --- a/src/main/java/appeng/spatial/CachedPlane.java +++ b/src/main/java/appeng/spatial/CachedPlane.java @@ -334,7 +334,7 @@ public class CachedPlane { } // FIXME check if this makes any sense at all to send changes to players asap - ServerChunkManager serverChunkProvider = (ServerChunkManager) world.getChunkManager(); + ServerChunkManager serverChunkProvider = world.getChunkManager(); serverChunkProvider.tick(() -> false); } diff --git a/src/siteexport/java/appeng/siteexport/Entrypoint.java b/src/siteexport/java/appeng/siteexport/Entrypoint.java new file mode 100644 index 000000000..33234f64e --- /dev/null +++ b/src/siteexport/java/appeng/siteexport/Entrypoint.java @@ -0,0 +1,126 @@ +package appeng.siteexport; + +import java.io.IOException; +import java.nio.file.Paths; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; + +import org.lwjgl.opengl.GL12; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.Framebuffer; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.render.DiffuseLighting; +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.util.collection.DefaultedList; + +import appeng.api.definitions.IDefinitions; +import appeng.core.Api; +import appeng.core.CreativeTab; + +public class Entrypoint implements ClientModInitializer { + + private static final int FB_WIDTH = 128; + private static final int FB_HEIGHT = 128; + + @Override + public void onInitializeClient() { + ClientLifecycleEvents.CLIENT_STARTED.register(client -> { + client.currentScreen = new Screen(Text.of("DUMMY")) { + @Override + protected void init() { + while (true) { + runExport(client); + } +// client.getWindow().close(); + } + }; + }); + } + + private void runExport(MinecraftClient client) { + + // Set up GL state for GUI rendering + RenderSystem.matrixMode(GL12.GL_PROJECTION); + RenderSystem.loadIdentity(); + RenderSystem.ortho(0.0D, 16, 16, 0.0D, 1000.0D, 3000.0D); + RenderSystem.matrixMode(GL12.GL_MODELVIEW); + RenderSystem.loadIdentity(); + RenderSystem.translatef(0.0F, 0.0F, -2000.0F); + DiffuseLighting.enableGuiDepthLighting(); + MatrixStack matrixStack = new MatrixStack(); + + IDefinitions definitions = Api.instance().definitions(); + + NativeImage nativeImage = new NativeImage(FB_WIDTH, FB_HEIGHT, true); + Framebuffer fb = new Framebuffer(FB_WIDTH, FB_HEIGHT, true, false); + fb.setClearColor(0, 0, 0, 0); + fb.clear(false); + + // Iterate over all Applied Energistics items + DefaultedList stacks = DefaultedList.of(); + CreativeTab.INSTANCE.appendStacks(stacks); + + // Compute the square grid size needed to have enough cells for the number of + // items we + // want to render + int s = (int) Math.ceil(Math.sqrt(stacks.size())); + // Add a transparent margin to avoid problems with texture interpolation + // grabbing pixels + // from adjacent items. + final int MARGIN = 1; + final int CELL_WIDTH = FB_WIDTH + 2 * MARGIN; + final int CELL_HEIGHT = FB_HEIGHT + 2 * MARGIN; + NativeImage result = new NativeImage(s * CELL_WIDTH, s * CELL_HEIGHT, true); + + SpriteIndexWriter indexWriter = new SpriteIndexWriter(result.getWidth(), result.getHeight(), s, s, MARGIN); + + int idx = 0; + for (ItemStack stack : stacks) { + // Render the item normally + fb.beginWrite(true); + GlStateManager.clear(GL12.GL_COLOR_BUFFER_BIT | GL12.GL_DEPTH_BUFFER_BIT, false); + client.getItemRenderer().renderInGui(stack, 0, 0); + fb.endWrite(); + + // Load the rendered item back into CPU memory + fb.beginRead(); + nativeImage.loadFromTextureImage(0, false); + nativeImage.mirrorVertically(); + fb.endRead(); + + // Copy it to the sprite-sheet + int xIdx = idx % s; + int xOut = xIdx * CELL_WIDTH + MARGIN; + int yIdx = idx / s; + int yOut = yIdx * CELL_HEIGHT + MARGIN; + idx++; + for (int y = 0; y < FB_HEIGHT; y++) { + for (int x = 0; x < FB_WIDTH; x++) { + result.setPixelColor(xOut + x, yOut + y, nativeImage.getPixelColor(x, y)); + } + } + + // Add it to the index + indexWriter.add(stack, xIdx, yIdx); + } + + try { + result.writeFile(Paths.get("item_sheet.png")); + indexWriter.write(Paths.get("item_sheet.json")); + } catch (IOException e) { + e.printStackTrace(); + } + + nativeImage.close(); + result.close(); + fb.delete(); + + } +} diff --git a/src/siteexport/java/appeng/siteexport/ModVersion.java b/src/siteexport/java/appeng/siteexport/ModVersion.java new file mode 100644 index 000000000..9f3ba54e0 --- /dev/null +++ b/src/siteexport/java/appeng/siteexport/ModVersion.java @@ -0,0 +1,61 @@ +package appeng.siteexport; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; + +public final class ModVersion { + + private static boolean initialized; + + private static String modVersion; + + private ModVersion() { + } + + public static String get() { + if (initialized) { + return modVersion; + } + initialized = true; + + // GHETTO! + Process process = null; + Path tempOutput = null; + try { + tempOutput = Files.createTempFile("gitoutput", ".txt"); + process = new ProcessBuilder("git", "rev-parse", "--short", "HEAD") + .redirectError(ProcessBuilder.Redirect.INHERIT).redirectInput(ProcessBuilder.Redirect.PIPE) + .redirectOutput(tempOutput.toFile()).start(); + if (process.waitFor(10, TimeUnit.SECONDS)) { + String version = new String(Files.readAllBytes(tempOutput), StandardCharsets.UTF_8).trim(); + if (version.isEmpty()) { + throw new Exception("Empty git output"); + } + modVersion = version; + return version; + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } finally { + if (tempOutput != null) { + try { + Files.deleteIfExists(tempOutput); + } catch (Exception e) { + e.printStackTrace(); + } + } + if (process != null) { + try { + process.destroyForcibly(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + return null; + } + +} diff --git a/src/siteexport/java/appeng/siteexport/SpriteIndexWriter.java b/src/siteexport/java/appeng/siteexport/SpriteIndexWriter.java new file mode 100644 index 000000000..e0593ed33 --- /dev/null +++ b/src/siteexport/java/appeng/siteexport/SpriteIndexWriter.java @@ -0,0 +1,184 @@ +package appeng.siteexport; + +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.mojang.serialization.JsonOps; + +import net.minecraft.MinecraftVersion; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtOps; +import net.minecraft.util.registry.Registry; + +public class SpriteIndexWriter { + + private final SpriteIndexJson spriteIndex = new SpriteIndexJson(); + + public SpriteIndexWriter(int width, int height, int rows, int cols, int margin) { + spriteIndex.modVersion = ModVersion.get(); + spriteIndex.width = width; + spriteIndex.height = height; + spriteIndex.cols = cols; + spriteIndex.rows = rows; + spriteIndex.margin = margin; + } + + public void add(ItemStack stack, int x, int y) { + SpriteIndexEntryJson entry = new SpriteIndexEntryJson(stack, x, y); + spriteIndex.sprites.add(entry); + } + + public void write(Path file) throws IOException { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + try (Writer writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { + gson.toJson(spriteIndex, writer); + } + } + + private static class SpriteIndexJson { + + private String generated; + + private String gameVersion; + + private String modVersion; + + private String filename; + + private int width; + + private int height; + + private int cols; + + private int rows; + + private int margin; + + private List sprites = new ArrayList<>(); + + public SpriteIndexJson() { + generated = Instant.now().toString(); + gameVersion = MinecraftVersion.create().getName(); + } + + public String getGenerated() { + return generated; + } + + public void setGenerated(String generated) { + this.generated = generated; + } + + public String getGameVersion() { + return gameVersion; + } + + public void setGameVersion(String gameVersion) { + this.gameVersion = gameVersion; + } + + public String getModVersion() { + return modVersion; + } + + public void setModVersion(String modVersion) { + this.modVersion = modVersion; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getCols() { + return cols; + } + + public void setCols(int cols) { + this.cols = cols; + } + + public int getRows() { + return rows; + } + + public void setRows(int rows) { + this.rows = rows; + } + + public List getSprites() { + return sprites; + } + + public int getMargin() { + return margin; + } + + public void setMargin(int margin) { + this.margin = margin; + } + } + + private static class SpriteIndexEntryJson { + private String itemId; + private JsonElement tag; + private int x; + private int y; + + public SpriteIndexEntryJson(ItemStack is, int x, int y) { + this.itemId = Registry.ITEM.getId(is.getItem()).toString(); + if (is.hasTag()) { + this.tag = NbtOps.INSTANCE.convertMap(JsonOps.INSTANCE, is.getTag()); + } + this.x = x; + this.y = y; + } + + public String getItemId() { + return itemId; + } + + public JsonElement getTag() { + return tag; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + } + +} diff --git a/src/siteexport/resources/fabric.mod.json b/src/siteexport/resources/fabric.mod.json new file mode 100644 index 000000000..a5cb1831d --- /dev/null +++ b/src/siteexport/resources/fabric.mod.json @@ -0,0 +1,23 @@ +{ + "schemaVersion": 1, + "id": "appliedenergistics2siteexport", + "version": "1.0.0", + "name": "Applied Energistics 2 Site Export", + "description": "", + "authors": ["TeamAppliedEnergistics"], + "contact": { + "homepage": "https://ae-mod.info/", + "sources": "https://github.com/AppliedEnergistics/Applied-Energistics-2/" + }, + "license": "LGPL", + "icon": "assets/appliedenergistics2/icon.png", + "environment": "client", + "entrypoints": { + "client": ["appeng.siteexport.Entrypoint"] + }, + "depends": { + "fabricloader": ">=0.7.1", + "fabric": "*", + "minecraft": "1.16.x" + } +}