Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for loading dynamic paintings from resource packs. #1728

Merged
merged 2 commits into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
Loading