1
0
mirror of https://github.com/GeyserMC/GeyserOptionalPack.git synced 2025-12-19 14:59:14 +00:00

Add json patching renderer

This commit is contained in:
rtm516
2025-08-28 01:24:30 +01:00
parent 730cf0cbc9
commit 1e462c34ee
10 changed files with 163 additions and 69 deletions

View File

@@ -12,6 +12,7 @@ repositories {
dependencies {
implementation 'com.google.code.gson:gson:2.13.1'
implementation 'org.reflections:reflections:0.10.2'
}
jar {

View File

@@ -0,0 +1,15 @@
package org.geysermc.optionalpack;
import java.io.InputStream;
public class BedrockResourcesWrapper {
private static final String BEDROCK_RESOURCES_URL = "https://raw.githubusercontent.com/Mojang/bedrock-samples/refs/tags/v" + Constants.BEDROCK_TARGET_VERSION + "/resource_pack/%s";
public static String getResourceAsString(String path) {
return HTTP.getAsString(BEDROCK_RESOURCES_URL.formatted(path));
}
public static InputStream getResource(String path) {
return HTTP.request(BEDROCK_RESOURCES_URL.formatted(path));
}
}

View File

@@ -0,0 +1,11 @@
package org.geysermc.optionalpack;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class Constants {
public static final String JAVA_TARGET_VERSION = "1.21.8";
public static final String BEDROCK_TARGET_VERSION = "1.21.100.6";
public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
}

View File

@@ -10,20 +10,17 @@ import java.util.List;
import java.util.Map;
public class LauncherMetaWrapper {
private static final String TARGET_VERSION = "1.21.8";
private static final Path CLIENT_JAR = OptionalPack.TEMP_PATH.resolve("client.jar");
private static final String LAUNCHER_META_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
private static final Gson GSON = new Gson();
public static Path getLatest() {
OptionalPack.log("Downloading " + TARGET_VERSION + " client.jar from Mojang...");
OptionalPack.log("Downloading " + Constants.JAVA_TARGET_VERSION + " client.jar from Mojang...");
VersionManifest versionManifest = GSON.fromJson(HTTP.getAsString(LAUNCHER_META_URL), VersionManifest.class);
VersionManifest versionManifest = Constants.GSON.fromJson(HTTP.getAsString(LAUNCHER_META_URL), VersionManifest.class);
for (Version version : versionManifest.versions()) {
if (version.id().equals(TARGET_VERSION)) {
VersionInfo versionInfo = GSON.fromJson(HTTP.getAsString(version.url()), VersionInfo.class);
if (version.id().equals(Constants.JAVA_TARGET_VERSION)) {
VersionInfo versionInfo = Constants.GSON.fromJson(HTTP.getAsString(version.url()), VersionInfo.class);
VersionDownload client = versionInfo.downloads().get("client");
if (!Files.exists(CLIENT_JAR) || !client.sha1.equals(getSha1(CLIENT_JAR))) {
// Download the client jar
@@ -62,8 +59,6 @@ public class LauncherMetaWrapper {
}
}
public record VersionManifest(
LatestVersion latest,
List<Version> versions

View File

@@ -27,6 +27,7 @@ package org.geysermc.optionalpack;
import org.geysermc.optionalpack.renderers.Renderer;
import org.geysermc.optionalpack.renderers.SweepAttackRenderer;
import org.reflections.Reflections;
import javax.imageio.ImageIO;
import java.io.*;
@@ -38,6 +39,8 @@ import java.text.DecimalFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
@@ -47,13 +50,20 @@ public class OptionalPack {
public static final Path TEMP_PATH = Path.of("temp");
public static final Path WORKING_PATH = TEMP_PATH.resolve("optionalpack");
/*
List of renderers that will be used to convert sprites for the resource pack.
They are executed in order from start to end.
*/
private static List<Renderer> renderers = List.of(
new SweepAttackRenderer()
);
private static final Renderer[] RENDERERS;
static {
Reflections reflections = new Reflections("org.geysermc.optionalpack.renderers");
Set<Class<? extends Renderer>> renderers = reflections.getSubTypesOf(Renderer.class);
RENDERERS = renderers.stream().map(rendererClass -> {
try {
return rendererClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
return null;
}
}).filter(Objects::nonNull).toArray(Renderer[]::new);
}
public static void main(String[] args) {
Instant start = Instant.now();
@@ -73,13 +83,16 @@ public class OptionalPack {
JavaResources.extract(clientJar);
/* Step 3: Rendering sprites in a format that we use in the resource pack */
for (Renderer renderer : renderers) {
for (Renderer renderer : RENDERERS) {
log("Rendering " + renderer.getName() + "...");
File imageFile = WORKING_PATH.resolve(renderer.getDestination()).toFile();
if (imageFile.mkdirs()) {
ImageIO.write(renderer.render(), "PNG", imageFile);
File destinationFolder = renderer.getDestinationPath().toFile().getParentFile();
if (!destinationFolder.exists()) {
if (!destinationFolder.mkdirs()) {
throw new IOException("Failed to create directory: " + destinationFolder);
}
}
renderer.render();
}
// Step 4: Compile pack folder into a mcpack.
log("Zipping as GeyserOptionalPack.mcpack...");
@@ -88,7 +101,7 @@ public class OptionalPack {
// Step 5: Cleanup temporary folders and files
log("Clearing temporary files...");
clientJar.close();
deleteDirectory(WORKING_PATH.toFile());
// deleteDirectory(WORKING_PATH.toFile());
// Step 6: Finish!!
DecimalFormat r3 = new DecimalFormat("0.000");

View File

@@ -0,0 +1,75 @@
package org.geysermc.optionalpack.renderers;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.geysermc.optionalpack.BedrockResourcesWrapper;
import org.geysermc.optionalpack.Constants;
import java.io.FileWriter;
import java.io.IOException;
public class JsonPatchRenderer implements Renderer {
private final String name;
private final String file;
private final String patch;
public JsonPatchRenderer(String name, String file, String patch) {
this.name = name;
this.file = file;
this.patch = patch;
}
@Override
public String getName() {
return name;
}
@Override
public String getDestination() {
return file;
}
@Override
public void render() throws IOException {
JsonObject sourceJson = JsonParser.parseString(BedrockResourcesWrapper.getResourceAsString(getDestination())).getAsJsonObject();
JsonObject patchJson = JsonParser.parseString(patch).getAsJsonObject();
JsonObject merged = mergeJsonObjects(sourceJson, patchJson);
try (FileWriter writer = new FileWriter(getDestinationPath().toFile())) {
writer.write(Constants.GSON.toJson(merged));
}
}
/**
* Merges two JsonObjects. In case of conflicts, values from obj2 take precedence.
* If both values are JsonObjects, they are merged recursively.
*
* @param obj1 The first JsonObject
* @param obj2 The second JsonObject
* @return The merged JsonObject
*/
private static JsonObject mergeJsonObjects(JsonObject obj1, JsonObject obj2) {
JsonObject merged = obj1.deepCopy(); // Start with a copy of the first
for (String key : obj2.keySet()) {
JsonElement value2 = obj2.get(key);
if (merged.has(key)) {
JsonElement value1 = merged.get(key);
// If both are JsonObjects, recursively merge
if (value1.isJsonObject() && value2.isJsonObject()) {
merged.add(key, mergeJsonObjects(value1.getAsJsonObject(), value2.getAsJsonObject()));
} else {
// Override with value from obj2
merged.add(key, value2);
}
} else {
merged.add(key, value2);
}
}
return merged;
}
}

View File

@@ -25,8 +25,11 @@
package org.geysermc.optionalpack.renderers;
import org.geysermc.optionalpack.OptionalPack;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Path;
public interface Renderer {
/**
@@ -46,10 +49,22 @@ public interface Renderer {
}
/**
* Draws the image as a BufferedImage.
* Gets the destination path as a Path object, or null if no destination is set.
*
* @return The destination path
*/
default Path getDestinationPath() {
String destination = getDestination();
if (destination.isEmpty()) {
return null;
}
return OptionalPack.WORKING_PATH.resolve(destination);
}
/**
* Renders the output of the renderer.
*
* @return The rendered output as a BufferedImage.
* @throws IOException If an error occurs during rendering.
*/
BufferedImage render() throws IOException;
void render() throws IOException;
}

View File

@@ -27,7 +27,9 @@ package org.geysermc.optionalpack.renderers;
import org.geysermc.optionalpack.JavaResources;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -58,7 +60,7 @@ public class VerticalSpriteSheetRenderer implements Renderer {
}
@Override
public BufferedImage render() throws IOException {
public void render() throws IOException {
List<BufferedImage> sprites = new ArrayList<>();
for (String path : spritePaths) {
// Retrieve the image from the client jar
@@ -72,6 +74,7 @@ public class VerticalSpriteSheetRenderer implements Renderer {
BufferedImage sprite = sprites.get(i);
canvas.getGraphics().drawImage(sprite, 0, i * sprite.getHeight(), null);
}
return canvas;
ImageIO.write(canvas, "PNG", getDestinationPath().toFile());
}
}

View File

@@ -0,0 +1,9 @@
package org.geysermc.optionalpack.renderers.particles;
import org.geysermc.optionalpack.renderers.JsonPatchRenderer;
public class BasicBubbleParticleRenderer extends JsonPatchRenderer {
public BasicBubbleParticleRenderer() {
super("Basic Bubble Particle", "particles/basic_bubble_manual.json", "{\"particle_effect\": {\"components\": {\"minecraft:particle_expire_if_not_in_blocks\": []}}}");
}
}

View File

@@ -1,43 +0,0 @@
{
"format_version": "1.10.0",
"particle_effect": {
"description": {
"identifier": "minecraft:basic_bubble_particle_manual",
"basic_render_parameters": {
"material": "particles_alpha",
"texture": "textures/particle/particles"
}
},
"components": {
"minecraft:emitter_rate_manual": {
"max_particles": 100
},
"minecraft:emitter_lifetime_expression": {
"activation_expression": 1,
"expiration_expression": 0
},
"minecraft:emitter_shape_point": {
},
"minecraft:particle_lifetime_expression": {
"max_lifetime": "2 / ((Math.Random(0.0, 1.0) * 0.8 + 0.2) * 5)"
},
"minecraft:particle_expire_if_not_in_blocks": [],
"minecraft:particle_motion_dynamic": {
"linear_acceleration": [ 0, 0.8, 0 ],
"linear_drag_coefficient": 5.25
},
"minecraft:particle_appearance_billboard": {
"size": [ "(0.05*variable.particle_random_1+0.1)*(variable.particle_random_2*0.9+0.2)", "(0.05*variable.particle_random_1+0.1)*(variable.particle_random_2*0.9+0.2)" ],
"facing_camera_mode": "lookat_xyz",
"uv": {
"texture_width": 128,
"texture_height": 128,
"uv": [ 0, 16 ],
"uv_size": [ 8, 8 ]
}
},
"minecraft:particle_appearance_lighting": {}
}
}
}