Skip to content

Commit 1e462c3

Browse files
committed
Add json patching renderer
1 parent 730cf0c commit 1e462c3

File tree

10 files changed

+163
-69
lines changed

10 files changed

+163
-69
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ repositories {
1212

1313
dependencies {
1414
implementation 'com.google.code.gson:gson:2.13.1'
15+
implementation 'org.reflections:reflections:0.10.2'
1516
}
1617

1718
jar {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.geysermc.optionalpack;
2+
3+
import java.io.InputStream;
4+
5+
public class BedrockResourcesWrapper {
6+
private static final String BEDROCK_RESOURCES_URL = "https://raw.githubusercontent.com/Mojang/bedrock-samples/refs/tags/v" + Constants.BEDROCK_TARGET_VERSION + "/resource_pack/%s";
7+
8+
public static String getResourceAsString(String path) {
9+
return HTTP.getAsString(BEDROCK_RESOURCES_URL.formatted(path));
10+
}
11+
12+
public static InputStream getResource(String path) {
13+
return HTTP.request(BEDROCK_RESOURCES_URL.formatted(path));
14+
}
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.geysermc.optionalpack;
2+
3+
import com.google.gson.Gson;
4+
import com.google.gson.GsonBuilder;
5+
6+
public class Constants {
7+
public static final String JAVA_TARGET_VERSION = "1.21.8";
8+
public static final String BEDROCK_TARGET_VERSION = "1.21.100.6";
9+
10+
public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
11+
}

src/main/java/org/geysermc/optionalpack/LauncherMetaWrapper.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,17 @@
1010
import java.util.Map;
1111

1212
public class LauncherMetaWrapper {
13-
private static final String TARGET_VERSION = "1.21.8";
14-
1513
private static final Path CLIENT_JAR = OptionalPack.TEMP_PATH.resolve("client.jar");
1614
private static final String LAUNCHER_META_URL = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
17-
private static final Gson GSON = new Gson();
1815

1916
public static Path getLatest() {
20-
OptionalPack.log("Downloading " + TARGET_VERSION + " client.jar from Mojang...");
17+
OptionalPack.log("Downloading " + Constants.JAVA_TARGET_VERSION + " client.jar from Mojang...");
2118

22-
VersionManifest versionManifest = GSON.fromJson(HTTP.getAsString(LAUNCHER_META_URL), VersionManifest.class);
19+
VersionManifest versionManifest = Constants.GSON.fromJson(HTTP.getAsString(LAUNCHER_META_URL), VersionManifest.class);
2320

2421
for (Version version : versionManifest.versions()) {
25-
if (version.id().equals(TARGET_VERSION)) {
26-
VersionInfo versionInfo = GSON.fromJson(HTTP.getAsString(version.url()), VersionInfo.class);
22+
if (version.id().equals(Constants.JAVA_TARGET_VERSION)) {
23+
VersionInfo versionInfo = Constants.GSON.fromJson(HTTP.getAsString(version.url()), VersionInfo.class);
2724
VersionDownload client = versionInfo.downloads().get("client");
2825
if (!Files.exists(CLIENT_JAR) || !client.sha1.equals(getSha1(CLIENT_JAR))) {
2926
// Download the client jar
@@ -62,8 +59,6 @@ private static String getSha1(Path path) {
6259
}
6360
}
6461

65-
66-
6762
public record VersionManifest(
6863
LatestVersion latest,
6964
List<Version> versions

src/main/java/org/geysermc/optionalpack/OptionalPack.java

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import org.geysermc.optionalpack.renderers.Renderer;
2929
import org.geysermc.optionalpack.renderers.SweepAttackRenderer;
30+
import org.reflections.Reflections;
3031

3132
import javax.imageio.ImageIO;
3233
import java.io.*;
@@ -38,6 +39,8 @@
3839
import java.time.Duration;
3940
import java.time.Instant;
4041
import java.util.List;
42+
import java.util.Objects;
43+
import java.util.Set;
4144
import java.util.zip.ZipEntry;
4245
import java.util.zip.ZipFile;
4346
import java.util.zip.ZipInputStream;
@@ -47,13 +50,20 @@ public class OptionalPack {
4750
public static final Path TEMP_PATH = Path.of("temp");
4851
public static final Path WORKING_PATH = TEMP_PATH.resolve("optionalpack");
4952

50-
/*
51-
List of renderers that will be used to convert sprites for the resource pack.
52-
They are executed in order from start to end.
53-
*/
54-
private static List<Renderer> renderers = List.of(
55-
new SweepAttackRenderer()
56-
);
53+
private static final Renderer[] RENDERERS;
54+
55+
static {
56+
Reflections reflections = new Reflections("org.geysermc.optionalpack.renderers");
57+
Set<Class<? extends Renderer>> renderers = reflections.getSubTypesOf(Renderer.class);
58+
59+
RENDERERS = renderers.stream().map(rendererClass -> {
60+
try {
61+
return rendererClass.getDeclaredConstructor().newInstance();
62+
} catch (Exception e) {
63+
return null;
64+
}
65+
}).filter(Objects::nonNull).toArray(Renderer[]::new);
66+
}
5767

5868
public static void main(String[] args) {
5969
Instant start = Instant.now();
@@ -73,12 +83,15 @@ public static void main(String[] args) {
7383
JavaResources.extract(clientJar);
7484

7585
/* Step 3: Rendering sprites in a format that we use in the resource pack */
76-
for (Renderer renderer : renderers) {
86+
for (Renderer renderer : RENDERERS) {
7787
log("Rendering " + renderer.getName() + "...");
78-
File imageFile = WORKING_PATH.resolve(renderer.getDestination()).toFile();
79-
if (imageFile.mkdirs()) {
80-
ImageIO.write(renderer.render(), "PNG", imageFile);
88+
File destinationFolder = renderer.getDestinationPath().toFile().getParentFile();
89+
if (!destinationFolder.exists()) {
90+
if (!destinationFolder.mkdirs()) {
91+
throw new IOException("Failed to create directory: " + destinationFolder);
92+
}
8193
}
94+
renderer.render();
8295
}
8396

8497
// Step 4: Compile pack folder into a mcpack.
@@ -88,7 +101,7 @@ public static void main(String[] args) {
88101
// Step 5: Cleanup temporary folders and files
89102
log("Clearing temporary files...");
90103
clientJar.close();
91-
deleteDirectory(WORKING_PATH.toFile());
104+
// deleteDirectory(WORKING_PATH.toFile());
92105

93106
// Step 6: Finish!!
94107
DecimalFormat r3 = new DecimalFormat("0.000");
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package org.geysermc.optionalpack.renderers;
2+
3+
import com.google.gson.JsonElement;
4+
import com.google.gson.JsonObject;
5+
import com.google.gson.JsonParser;
6+
import org.geysermc.optionalpack.BedrockResourcesWrapper;
7+
import org.geysermc.optionalpack.Constants;
8+
9+
import java.io.FileWriter;
10+
import java.io.IOException;
11+
12+
public class JsonPatchRenderer implements Renderer {
13+
private final String name;
14+
private final String file;
15+
private final String patch;
16+
17+
public JsonPatchRenderer(String name, String file, String patch) {
18+
this.name = name;
19+
this.file = file;
20+
this.patch = patch;
21+
}
22+
23+
@Override
24+
public String getName() {
25+
return name;
26+
}
27+
28+
@Override
29+
public String getDestination() {
30+
return file;
31+
}
32+
33+
@Override
34+
public void render() throws IOException {
35+
JsonObject sourceJson = JsonParser.parseString(BedrockResourcesWrapper.getResourceAsString(getDestination())).getAsJsonObject();
36+
JsonObject patchJson = JsonParser.parseString(patch).getAsJsonObject();
37+
38+
JsonObject merged = mergeJsonObjects(sourceJson, patchJson);
39+
40+
try (FileWriter writer = new FileWriter(getDestinationPath().toFile())) {
41+
writer.write(Constants.GSON.toJson(merged));
42+
}
43+
}
44+
45+
/**
46+
* Merges two JsonObjects. In case of conflicts, values from obj2 take precedence.
47+
* If both values are JsonObjects, they are merged recursively.
48+
*
49+
* @param obj1 The first JsonObject
50+
* @param obj2 The second JsonObject
51+
* @return The merged JsonObject
52+
*/
53+
private static JsonObject mergeJsonObjects(JsonObject obj1, JsonObject obj2) {
54+
JsonObject merged = obj1.deepCopy(); // Start with a copy of the first
55+
56+
for (String key : obj2.keySet()) {
57+
JsonElement value2 = obj2.get(key);
58+
if (merged.has(key)) {
59+
JsonElement value1 = merged.get(key);
60+
61+
// If both are JsonObjects, recursively merge
62+
if (value1.isJsonObject() && value2.isJsonObject()) {
63+
merged.add(key, mergeJsonObjects(value1.getAsJsonObject(), value2.getAsJsonObject()));
64+
} else {
65+
// Override with value from obj2
66+
merged.add(key, value2);
67+
}
68+
} else {
69+
merged.add(key, value2);
70+
}
71+
}
72+
73+
return merged;
74+
}
75+
}

src/main/java/org/geysermc/optionalpack/renderers/Renderer.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@
2525

2626
package org.geysermc.optionalpack.renderers;
2727

28+
import org.geysermc.optionalpack.OptionalPack;
29+
2830
import java.awt.image.BufferedImage;
2931
import java.io.IOException;
32+
import java.nio.file.Path;
3033

3134
public interface Renderer {
3235
/**
@@ -46,10 +49,22 @@ default String getDestination() {
4649
}
4750

4851
/**
49-
* Draws the image as a BufferedImage.
52+
* Gets the destination path as a Path object, or null if no destination is set.
53+
*
54+
* @return The destination path
55+
*/
56+
default Path getDestinationPath() {
57+
String destination = getDestination();
58+
if (destination.isEmpty()) {
59+
return null;
60+
}
61+
return OptionalPack.WORKING_PATH.resolve(destination);
62+
}
63+
64+
/**
65+
* Renders the output of the renderer.
5066
*
51-
* @return The rendered output as a BufferedImage.
5267
* @throws IOException If an error occurs during rendering.
5368
*/
54-
BufferedImage render() throws IOException;
69+
void render() throws IOException;
5570
}

src/main/java/org/geysermc/optionalpack/renderers/VerticalSpriteSheetRenderer.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727

2828
import org.geysermc.optionalpack.JavaResources;
2929

30+
import javax.imageio.ImageIO;
3031
import java.awt.image.BufferedImage;
32+
import java.io.File;
3133
import java.io.IOException;
3234
import java.util.ArrayList;
3335
import java.util.List;
@@ -58,7 +60,7 @@ public String getDestination() {
5860
}
5961

6062
@Override
61-
public BufferedImage render() throws IOException {
63+
public void render() throws IOException {
6264
List<BufferedImage> sprites = new ArrayList<>();
6365
for (String path : spritePaths) {
6466
// Retrieve the image from the client jar
@@ -72,6 +74,7 @@ public BufferedImage render() throws IOException {
7274
BufferedImage sprite = sprites.get(i);
7375
canvas.getGraphics().drawImage(sprite, 0, i * sprite.getHeight(), null);
7476
}
75-
return canvas;
77+
78+
ImageIO.write(canvas, "PNG", getDestinationPath().toFile());
7679
}
7780
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.geysermc.optionalpack.renderers.particles;
2+
3+
import org.geysermc.optionalpack.renderers.JsonPatchRenderer;
4+
5+
public class BasicBubbleParticleRenderer extends JsonPatchRenderer {
6+
public BasicBubbleParticleRenderer() {
7+
super("Basic Bubble Particle", "particles/basic_bubble_manual.json", "{\"particle_effect\": {\"components\": {\"minecraft:particle_expire_if_not_in_blocks\": []}}}");
8+
}
9+
}

src/main/resources/optionalpack/particles/basic_bubble_manual.json

Lines changed: 0 additions & 43 deletions
This file was deleted.

0 commit comments

Comments
 (0)