Skip to content

Commit

Permalink
Add support for loading dynamic paintings from resource packs. (#1728)
Browse files Browse the repository at this point in the history
* Add support for loading dynamic paintings from resource packs.

* Refactor iterating over datapack registry entries to eliminate duplicate code.
  • Loading branch information
leMaik authored Jul 6, 2024
1 parent ee088a7 commit 72bef91
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 117 deletions.
121 changes: 68 additions & 53 deletions chunky/src/java/se/llbit/chunky/entity/PaintingEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

public class PaintingEntity extends Entity {

static class Painting {
public static class Painting {

protected final Quad[] quads;
protected final Material material;
Expand Down Expand Up @@ -78,58 +78,7 @@ public Painting(Texture painting, int w, int h) {
static final Map<String, Painting> paintings = new HashMap<>();

static {
paintings.put("Kebab", new Painting(Texture.paintingKebab, 1, 1));
paintings.put("minecraft:kebab", new Painting(Texture.paintingKebab, 1, 1));
paintings.put("Aztec", new Painting(Texture.paintingAztec, 1, 1));
paintings.put("minecraft:aztec", new Painting(Texture.paintingAztec, 1, 1));
paintings.put("Alban", new Painting(Texture.paintingAlban, 1, 1));
paintings.put("minecraft:alban", new Painting(Texture.paintingAlban, 1, 1));
paintings.put("Aztec2", new Painting(Texture.paintingAztec2, 1, 1));
paintings.put("minecraft:aztec2", new Painting(Texture.paintingAztec2, 1, 1));
paintings.put("Bomb", new Painting(Texture.paintingBomb, 1, 1));
paintings.put("minecraft:bomb", new Painting(Texture.paintingBomb, 1, 1));
paintings.put("Plant", new Painting(Texture.paintingPlant, 1, 1));
paintings.put("minecraft:plant", new Painting(Texture.paintingPlant, 1, 1));
paintings.put("Wasteland", new Painting(Texture.paintingWasteland, 1, 1));
paintings.put("minecraft:wasteland", new Painting(Texture.paintingWasteland, 1, 1));
paintings.put("Wanderer", new Painting(Texture.paintingWanderer, 1, 2));
paintings.put("minecraft:wanderer", new Painting(Texture.paintingWanderer, 1, 2));
paintings.put("Graham", new Painting(Texture.paintingGraham, 1, 2));
paintings.put("minecraft:graham", new Painting(Texture.paintingGraham, 1, 2));
paintings.put("Pool", new Painting(Texture.paintingPool, 2, 1));
paintings.put("minecraft:pool", new Painting(Texture.paintingPool, 2, 1));
paintings.put("Courbet", new Painting(Texture.paintingCourbet, 2, 1));
paintings.put("minecraft:courbet", new Painting(Texture.paintingCourbet, 2, 1));
paintings.put("Sunset", new Painting(Texture.paintingSunset, 2, 1));
paintings.put("minecraft:sunset", new Painting(Texture.paintingSunset, 2, 1));
paintings.put("Sea", new Painting(Texture.paintingSea, 2, 1));
paintings.put("minecraft:sea", new Painting(Texture.paintingSea, 2, 1));
paintings.put("Creebet", new Painting(Texture.paintingCreebet, 2, 1));
paintings.put("minecraft:creebet", new Painting(Texture.paintingCreebet, 2, 1));
paintings.put("Match", new Painting(Texture.paintingMatch, 2, 2));
paintings.put("minecraft:match", new Painting(Texture.paintingMatch, 2, 2));
paintings.put("Bust", new Painting(Texture.paintingBust, 2, 2));
paintings.put("minecraft:bust", new Painting(Texture.paintingBust, 2, 2));
paintings.put("Stage", new Painting(Texture.paintingStage, 2, 2));
paintings.put("minecraft:stage", new Painting(Texture.paintingStage, 2, 2));
paintings.put("Void", new Painting(Texture.paintingVoid, 2, 2));
paintings.put("minecraft:void", new Painting(Texture.paintingVoid, 2, 2));
paintings.put("SkullAndRoses", new Painting(Texture.paintingSkullAndRoses, 2, 2));
paintings.put("minecraft:skull_and_roses", new Painting(Texture.paintingSkullAndRoses, 2, 2));
paintings.put("Wither", new Painting(Texture.paintingWither, 2, 2));
paintings.put("minecraft:wither", new Painting(Texture.paintingWither, 2, 2));
paintings.put("Fighters", new Painting(Texture.paintingFighters, 4, 2));
paintings.put("minecraft:fighters", new Painting(Texture.paintingFighters, 4, 2));
paintings.put("Skeleton", new Painting(Texture.paintingSkeleton, 4, 3));
paintings.put("minecraft:skeleton", new Painting(Texture.paintingSkeleton, 4, 3));
paintings.put("DonkeyKong", new Painting(Texture.paintingDonkeyKong, 4, 3));
paintings.put("minecraft:donkey_kong", new Painting(Texture.paintingDonkeyKong, 4, 3));
paintings.put("Pointer", new Painting(Texture.paintingPointer, 4, 4));
paintings.put("minecraft:pointer", new Painting(Texture.paintingPointer, 4, 4));
paintings.put("Pigscene", new Painting(Texture.paintingPigscene, 4, 4));
paintings.put("minecraft:pigscene", new Painting(Texture.paintingPigscene, 4, 4));
paintings.put("BurningSkull", new Painting(Texture.paintingBurningSkull, 4, 4));
paintings.put("minecraft:burning_skull", new Painting(Texture.paintingBurningSkull, 4, 4));
resetPaintings();
}

private static final Material BACK_MATERIAL = new TextureMaterial(Texture.paintingBack);
Expand Down Expand Up @@ -204,4 +153,70 @@ public static Entity fromJson(JsonObject json) {
double angle = json.get("angle").doubleValue(0.0);
return new PaintingEntity(position, art, angle);
}

public static void resetPaintings() {
paintings.clear();

// hard-coded pre-24w18a paintings with legacy aliases
paintings.put("Kebab", new Painting(Texture.paintingKebab, 1, 1));
paintings.put("minecraft:kebab", new Painting(Texture.paintingKebab, 1, 1));
paintings.put("Aztec", new Painting(Texture.paintingAztec, 1, 1));
paintings.put("minecraft:aztec", new Painting(Texture.paintingAztec, 1, 1));
paintings.put("Alban", new Painting(Texture.paintingAlban, 1, 1));
paintings.put("minecraft:alban", new Painting(Texture.paintingAlban, 1, 1));
paintings.put("Aztec2", new Painting(Texture.paintingAztec2, 1, 1));
paintings.put("minecraft:aztec2", new Painting(Texture.paintingAztec2, 1, 1));
paintings.put("Bomb", new Painting(Texture.paintingBomb, 1, 1));
paintings.put("minecraft:bomb", new Painting(Texture.paintingBomb, 1, 1));
paintings.put("Plant", new Painting(Texture.paintingPlant, 1, 1));
paintings.put("minecraft:plant", new Painting(Texture.paintingPlant, 1, 1));
paintings.put("Wasteland", new Painting(Texture.paintingWasteland, 1, 1));
paintings.put("minecraft:wasteland", new Painting(Texture.paintingWasteland, 1, 1));
paintings.put("Wanderer", new Painting(Texture.paintingWanderer, 1, 2));
paintings.put("minecraft:wanderer", new Painting(Texture.paintingWanderer, 1, 2));
paintings.put("Graham", new Painting(Texture.paintingGraham, 1, 2));
paintings.put("minecraft:graham", new Painting(Texture.paintingGraham, 1, 2));
paintings.put("Pool", new Painting(Texture.paintingPool, 2, 1));
paintings.put("minecraft:pool", new Painting(Texture.paintingPool, 2, 1));
paintings.put("Courbet", new Painting(Texture.paintingCourbet, 2, 1));
paintings.put("minecraft:courbet", new Painting(Texture.paintingCourbet, 2, 1));
paintings.put("Sunset", new Painting(Texture.paintingSunset, 2, 1));
paintings.put("minecraft:sunset", new Painting(Texture.paintingSunset, 2, 1));
paintings.put("Sea", new Painting(Texture.paintingSea, 2, 1));
paintings.put("minecraft:sea", new Painting(Texture.paintingSea, 2, 1));
paintings.put("Creebet", new Painting(Texture.paintingCreebet, 2, 1));
paintings.put("minecraft:creebet", new Painting(Texture.paintingCreebet, 2, 1));
paintings.put("Match", new Painting(Texture.paintingMatch, 2, 2));
paintings.put("minecraft:match", new Painting(Texture.paintingMatch, 2, 2));
paintings.put("Bust", new Painting(Texture.paintingBust, 2, 2));
paintings.put("minecraft:bust", new Painting(Texture.paintingBust, 2, 2));
paintings.put("Stage", new Painting(Texture.paintingStage, 2, 2));
paintings.put("minecraft:stage", new Painting(Texture.paintingStage, 2, 2));
paintings.put("Void", new Painting(Texture.paintingVoid, 2, 2));
paintings.put("minecraft:void", new Painting(Texture.paintingVoid, 2, 2));
paintings.put("SkullAndRoses", new Painting(Texture.paintingSkullAndRoses, 2, 2));
paintings.put("minecraft:skull_and_roses", new Painting(Texture.paintingSkullAndRoses, 2, 2));
paintings.put("Wither", new Painting(Texture.paintingWither, 2, 2));
paintings.put("minecraft:wither", new Painting(Texture.paintingWither, 2, 2));
paintings.put("Fighters", new Painting(Texture.paintingFighters, 4, 2));
paintings.put("minecraft:fighters", new Painting(Texture.paintingFighters, 4, 2));
paintings.put("Skeleton", new Painting(Texture.paintingSkeleton, 4, 3));
paintings.put("minecraft:skeleton", new Painting(Texture.paintingSkeleton, 4, 3));
paintings.put("DonkeyKong", new Painting(Texture.paintingDonkeyKong, 4, 3));
paintings.put("minecraft:donkey_kong", new Painting(Texture.paintingDonkeyKong, 4, 3));
paintings.put("Pointer", new Painting(Texture.paintingPointer, 4, 4));
paintings.put("minecraft:pointer", new Painting(Texture.paintingPointer, 4, 4));
paintings.put("Pigscene", new Painting(Texture.paintingPigscene, 4, 4));
paintings.put("minecraft:pigscene", new Painting(Texture.paintingPigscene, 4, 4));
paintings.put("BurningSkull", new Painting(Texture.paintingBurningSkull, 4, 4));
paintings.put("minecraft:burning_skull", new Painting(Texture.paintingBurningSkull, 4, 4));
}

public static void registerPainting(String id, Painting painting) {
paintings.put(id, painting);
}

public static boolean containsPainting(String id) {
return paintings.containsKey(id);
}
}
47 changes: 47 additions & 0 deletions chunky/src/java/se/llbit/chunky/resources/DataPackUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package se.llbit.chunky.resources;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class DataPackUtil {
private DataPackUtil() {
}

public static void forEachDataRegistryEntry(LayeredResourcePacks resourcePacks, String registryPath, Consumer<DataRegistryEntry> consumer) {
for (LayeredResourcePacks.Entry data : resourcePacks.getAllEntries("data")) {
try (Stream<Path> namespaces = Files.list(data.getPath())) {
namespaces.forEach(ns -> {
String namespace = String.valueOf(ns.getFileName());
Path entriesPath = ns;
for (String part : registryPath.split("/")) {
entriesPath = entriesPath.resolve(part);
}
try (Stream<Path> entriesStream = Files.walk(entriesPath)) {
entriesStream
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
.forEach(file -> {
String name = file.getFileName().toString();
if (name.toLowerCase().endsWith(".json")) {
name = name.substring(0, name.length() - ".json".length());
}
consumer.accept(new DataRegistryEntry(namespace, name, file));
});
} catch (IOException ignored) {
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

public record DataRegistryEntry(String namespace, String name, Path path) {
public String getNamespacedName() {
return namespace + ":" + name;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Optional;
import java.util.stream.Stream;

public class ResourcePackBiomeLoader implements ResourcePackLoader.PackLoader {
public ResourcePackBiomeLoader() {
Expand All @@ -57,69 +53,36 @@ protected static class BiomeEffects {

@Override
public boolean load(LayeredResourcePacks resourcePacks) {
for (LayeredResourcePacks.Entry data : resourcePacks.getAllEntries("data")) {
try (Stream<Path> namespaces = Files.list(data.getPath())) {
namespaces.forEach(ns -> {
String namespace = String.valueOf(ns.getFileName());
DataPackUtil.forEachDataRegistryEntry(resourcePacks, "worldgen/biome", biome -> {
if (!Biomes.contains(biome.getNamespacedName())) {
try (Reader f = Files.newBufferedReader(biome.path())) {
BiomeJson json = GSON.fromJson(f, BiomeJson.class);

Path biomes = ns.resolve("worldgen").resolve("biome");
try (Stream<Path> biomeStream = Files.walk(biomes)) {
biomeStream
.filter(p -> Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS))
.forEach(biome -> {
if (biome.toString().endsWith(".json")) {
String biomeName = getBiomeName(biomes.relativize(biome));
String resourceLocation = namespace + ":" + biomeName;
BiomeBuilder builder = Biome.create(biome.getNamespacedName(), biome.name(), json.temperature, json.downfall);
Optional.ofNullable(json.effects.foliage_color).ifPresent(builder::foliageColor);
Optional.ofNullable(json.effects.grass_color).ifPresent(builder::grassColor);
Optional.ofNullable(json.effects.water_color).ifPresent(builder::waterColor);
Optional.ofNullable(json.effects.grass_color_modifier).ifPresent(modifier -> {
switch (modifier.toLowerCase()) {
case "none":
break;
case "dark_forest":
builder.darkForest();
break;
case "swamp":
builder.swamp();
break;
default:
Log.warnf("Unsupported biome `grass_modifier_color`: %s", modifier);
}
});
// TODO Custom fog colors

if (!Biomes.contains(resourceLocation)) {
try (Reader f = Files.newBufferedReader(biome)) {
BiomeJson json = GSON.fromJson(f, BiomeJson.class);

BiomeBuilder builder = Biome.create(resourceLocation, biomeName, json.temperature, json.downfall);
Optional.ofNullable(json.effects.foliage_color).ifPresent(builder::foliageColor);
Optional.ofNullable(json.effects.grass_color).ifPresent(builder::grassColor);
Optional.ofNullable(json.effects.water_color).ifPresent(builder::waterColor);
Optional.ofNullable(json.effects.grass_color_modifier).ifPresent(modifier -> {
switch (modifier.toLowerCase()) {
case "none":
break;
case "dark_forest":
builder.darkForest();
break;
case "swamp":
builder.swamp();
break;
default:
Log.warnf("Unsupported biome `grass_modifier_color`: %s", modifier);
}
});
// TODO Custom fog colors

Biomes.register(builder);
} catch (IOException ignored) {
}
}
}
});
} catch (IOException ignored) {
}
});
} catch (IOException e) {
throw new RuntimeException(e);
Biomes.register(builder);
} catch (IOException ignored) {
}
}
}

});
return false;
}

private static String getBiomeName(Path biome) {
ArrayList<String> path = new ArrayList<>();
biome.iterator().forEachRemaining(p -> path.add(String.valueOf(p)));

String out = String.join("/", path);
if (out.toLowerCase().endsWith(".json")) {
out = out.substring(0, out.length() - ".json".length());
}
return out;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package se.llbit.chunky.resources;

import se.llbit.chunky.PersistentSettings;
import se.llbit.chunky.entity.PaintingEntity;
import se.llbit.chunky.world.biome.Biomes;
import se.llbit.log.Log;

Expand All @@ -38,6 +39,7 @@ public class ResourcePackLoader {
static {
ResourcePackLoader.PACK_LOADER_FACTORIES.add(() -> new ResourcePackTextureLoader(TexturePackLoader.ALL_TEXTURES));
ResourcePackLoader.PACK_LOADER_FACTORIES.add(ResourcePackBiomeLoader::new);
ResourcePackLoader.PACK_LOADER_FACTORIES.add(ResourcePackPaintingLoader::new);
}

public interface PackLoader {
Expand Down Expand Up @@ -119,6 +121,7 @@ public static void loadAndPersistResourcePacks(List<File> resourcePacks) {
public static void loadResourcePacks(List<File> resourcePacks) {
TextureCache.reset();
Biomes.reset();
PaintingEntity.resetPaintings();

if (ResourcePackLoader.resourcePacks != null) {
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package se.llbit.chunky.resources;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import se.llbit.chunky.entity.PaintingEntity;
import se.llbit.chunky.resources.texturepack.SimpleTexture;
import se.llbit.log.Log;
import se.llbit.util.Pair;

import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;

public class ResourcePackPaintingLoader implements ResourcePackLoader.PackLoader {

protected static final Gson GSON = new GsonBuilder()
.disableJdkUnsafe()
.setLenient()
.create();

protected static class PaintingVariantJson {
public String asset_id;
public int width;
public int height;

public Pair<String, String> getAsset() {
String[] parts = asset_id.split(":");
return new Pair<>(parts[0], parts[1]);
}
}

@Override
public boolean load(LayeredResourcePacks resourcePacks) {
DataPackUtil.forEachDataRegistryEntry(resourcePacks, "painting_variant", paintingVariant -> {
if (!PaintingEntity.containsPainting(paintingVariant.getNamespacedName())) {
try (Reader f = Files.newBufferedReader(paintingVariant.path())) {
PaintingVariantJson json = GSON.fromJson(f, PaintingVariantJson.class);
Texture paintingTexture = new Texture();
Pair<String, String> asset = json.getAsset();
if (!ResourcePackLoader.loadResources(
ResourcePackTextureLoader.singletonLoader(json.asset_id, new SimpleTexture("assets/" + asset.thing1 + "/textures/painting/" + asset.thing2, paintingTexture)))
) {
Log.warnf("Failed to load painting texture: %s", json.asset_id);
}
PaintingEntity.registerPainting(paintingVariant.getNamespacedName(), new PaintingEntity.Painting(paintingTexture, json.width, json.height));
} catch (IOException ignored) {
Log.warnf("Failed to load painting variant: %s", paintingVariant.getNamespacedName());
}
}
});
return false;
}
}

0 comments on commit 72bef91

Please sign in to comment.