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 {
|
||||
implementation 'com.google.code.gson:gson:2.13.1'
|
||||
implementation 'org.reflections:reflections:0.10.2'
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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