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:
@@ -12,6 +12,7 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.google.code.gson:gson:2.13.1'
|
implementation 'com.google.code.gson:gson:2.13.1'
|
||||||
|
implementation 'org.reflections:reflections:0.10.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/main/java/org/geysermc/optionalpack/Constants.java
Normal file
11
src/main/java/org/geysermc/optionalpack/Constants.java
Normal 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();
|
||||||
|
}
|
||||||
@@ -10,20 +10,17 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class LauncherMetaWrapper {
|
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 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 String LAUNCHER_META_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
|
||||||
private static final Gson GSON = new Gson();
|
|
||||||
|
|
||||||
public static Path getLatest() {
|
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()) {
|
for (Version version : versionManifest.versions()) {
|
||||||
if (version.id().equals(TARGET_VERSION)) {
|
if (version.id().equals(Constants.JAVA_TARGET_VERSION)) {
|
||||||
VersionInfo versionInfo = GSON.fromJson(HTTP.getAsString(version.url()), VersionInfo.class);
|
VersionInfo versionInfo = Constants.GSON.fromJson(HTTP.getAsString(version.url()), VersionInfo.class);
|
||||||
VersionDownload client = versionInfo.downloads().get("client");
|
VersionDownload client = versionInfo.downloads().get("client");
|
||||||
if (!Files.exists(CLIENT_JAR) || !client.sha1.equals(getSha1(CLIENT_JAR))) {
|
if (!Files.exists(CLIENT_JAR) || !client.sha1.equals(getSha1(CLIENT_JAR))) {
|
||||||
// Download the client jar
|
// Download the client jar
|
||||||
@@ -62,8 +59,6 @@ public class LauncherMetaWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public record VersionManifest(
|
public record VersionManifest(
|
||||||
LatestVersion latest,
|
LatestVersion latest,
|
||||||
List<Version> versions
|
List<Version> versions
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ package org.geysermc.optionalpack;
|
|||||||
|
|
||||||
import org.geysermc.optionalpack.renderers.Renderer;
|
import org.geysermc.optionalpack.renderers.Renderer;
|
||||||
import org.geysermc.optionalpack.renderers.SweepAttackRenderer;
|
import org.geysermc.optionalpack.renderers.SweepAttackRenderer;
|
||||||
|
import org.reflections.Reflections;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@@ -38,6 +39,8 @@ import java.text.DecimalFormat;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
import java.util.zip.ZipInputStream;
|
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 TEMP_PATH = Path.of("temp");
|
||||||
public static final Path WORKING_PATH = TEMP_PATH.resolve("optionalpack");
|
public static final Path WORKING_PATH = TEMP_PATH.resolve("optionalpack");
|
||||||
|
|
||||||
/*
|
private static final Renderer[] RENDERERS;
|
||||||
List of renderers that will be used to convert sprites for the resource pack.
|
|
||||||
They are executed in order from start to end.
|
static {
|
||||||
*/
|
Reflections reflections = new Reflections("org.geysermc.optionalpack.renderers");
|
||||||
private static List<Renderer> renderers = List.of(
|
Set<Class<? extends Renderer>> renderers = reflections.getSubTypesOf(Renderer.class);
|
||||||
new SweepAttackRenderer()
|
|
||||||
);
|
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) {
|
public static void main(String[] args) {
|
||||||
Instant start = Instant.now();
|
Instant start = Instant.now();
|
||||||
@@ -73,12 +83,15 @@ public class OptionalPack {
|
|||||||
JavaResources.extract(clientJar);
|
JavaResources.extract(clientJar);
|
||||||
|
|
||||||
/* Step 3: Rendering sprites in a format that we use in the resource pack */
|
/* 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() + "...");
|
log("Rendering " + renderer.getName() + "...");
|
||||||
File imageFile = WORKING_PATH.resolve(renderer.getDestination()).toFile();
|
File destinationFolder = renderer.getDestinationPath().toFile().getParentFile();
|
||||||
if (imageFile.mkdirs()) {
|
if (!destinationFolder.exists()) {
|
||||||
ImageIO.write(renderer.render(), "PNG", imageFile);
|
if (!destinationFolder.mkdirs()) {
|
||||||
|
throw new IOException("Failed to create directory: " + destinationFolder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
renderer.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Compile pack folder into a mcpack.
|
// Step 4: Compile pack folder into a mcpack.
|
||||||
@@ -88,7 +101,7 @@ public class OptionalPack {
|
|||||||
// Step 5: Cleanup temporary folders and files
|
// Step 5: Cleanup temporary folders and files
|
||||||
log("Clearing temporary files...");
|
log("Clearing temporary files...");
|
||||||
clientJar.close();
|
clientJar.close();
|
||||||
deleteDirectory(WORKING_PATH.toFile());
|
// deleteDirectory(WORKING_PATH.toFile());
|
||||||
|
|
||||||
// Step 6: Finish!!
|
// Step 6: Finish!!
|
||||||
DecimalFormat r3 = new DecimalFormat("0.000");
|
DecimalFormat r3 = new DecimalFormat("0.000");
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,8 +25,11 @@
|
|||||||
|
|
||||||
package org.geysermc.optionalpack.renderers;
|
package org.geysermc.optionalpack.renderers;
|
||||||
|
|
||||||
|
import org.geysermc.optionalpack.OptionalPack;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public interface Renderer {
|
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.
|
* @throws IOException If an error occurs during rendering.
|
||||||
*/
|
*/
|
||||||
BufferedImage render() throws IOException;
|
void render() throws IOException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ package org.geysermc.optionalpack.renderers;
|
|||||||
|
|
||||||
import org.geysermc.optionalpack.JavaResources;
|
import org.geysermc.optionalpack.JavaResources;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -58,7 +60,7 @@ public class VerticalSpriteSheetRenderer implements Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BufferedImage render() throws IOException {
|
public void render() throws IOException {
|
||||||
List<BufferedImage> sprites = new ArrayList<>();
|
List<BufferedImage> sprites = new ArrayList<>();
|
||||||
for (String path : spritePaths) {
|
for (String path : spritePaths) {
|
||||||
// Retrieve the image from the client jar
|
// Retrieve the image from the client jar
|
||||||
@@ -72,6 +74,7 @@ public class VerticalSpriteSheetRenderer implements Renderer {
|
|||||||
BufferedImage sprite = sprites.get(i);
|
BufferedImage sprite = sprites.get(i);
|
||||||
canvas.getGraphics().drawImage(sprite, 0, i * sprite.getHeight(), null);
|
canvas.getGraphics().drawImage(sprite, 0, i * sprite.getHeight(), null);
|
||||||
}
|
}
|
||||||
return canvas;
|
|
||||||
|
ImageIO.write(canvas, "PNG", getDestinationPath().toFile());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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\": []}}}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user