diff --git a/polymer-core/src/testmod/java/eu/pb4/polymertest/TestMod.java b/polymer-core/src/testmod/java/eu/pb4/polymertest/TestMod.java index 5a722b93..e299c57c 100644 --- a/polymer-core/src/testmod/java/eu/pb4/polymertest/TestMod.java +++ b/polymer-core/src/testmod/java/eu/pb4/polymertest/TestMod.java @@ -13,8 +13,9 @@ import eu.pb4.polymer.core.impl.client.InternalClientRegistry; import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; import eu.pb4.polymer.resourcepack.extras.api.ResourcePackExtras; +import eu.pb4.polymer.resourcepack.extras.api.format.atlas.AtlasAsset; import eu.pb4.polymer.resourcepack.extras.api.format.item.ItemAsset; -import eu.pb4.polymer.resourcepack.extras.api.format.model.Model; +import eu.pb4.polymer.resourcepack.extras.api.format.model.ModelAsset; import eu.pb4.polymer.virtualentity.api.tracker.EntityTrackedData; import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; @@ -503,6 +504,7 @@ public void onInitialize() { var vanillaJar = PolymerCommonUtils.getClientJarRoot(); var itemsBase = vanillaJar.resolve("/assets/minecraft/items/"); var modelsBase = vanillaJar.resolve("/assets/minecraft/models/"); + var atlasBase = vanillaJar.resolve("/assets/minecraft/atlases/"); try { var value = new MutableInt(); @@ -535,7 +537,7 @@ public void onInitialize() { } count.increment(); try { - var asset = Model.fromJson(Files.readString(path)); + var asset = ModelAsset.fromJson(Files.readString(path)); //System.out.println(path + ">" + asset); value.increment(); } catch (Throwable e) { @@ -548,6 +550,28 @@ public void onInitialize() { e.printStackTrace(); } + try { + var value = new MutableInt(); + var count = new MutableInt(); + Files.walk(atlasBase).forEach(path -> { + if (!path.toString().endsWith(".json")) { + return; + } + count.increment(); + try { + var asset = AtlasAsset.fromJson(Files.readString(path)); + //System.out.println(path + ">" + asset); + value.increment(); + } catch (Throwable e) { + System.err.println("Error while parsing file: " + path); + e.printStackTrace(); + } + }); + System.out.println("Parsed " + value + " out of " + count + " atlases!"); + } catch (IOException e) { + e.printStackTrace(); + } + }).run(); } diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/ResourcePackExtras.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/ResourcePackExtras.java index e49d42c3..013e514b 100644 --- a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/ResourcePackExtras.java +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/ResourcePackExtras.java @@ -61,7 +61,7 @@ public static Identifier bridgeModel(Identifier model) { /** * Adds a bridge, allowing you to access any model from selected folder as `namespace:-/modelpath`. * - * @param modelFolderId Model folder to bridge. For example "mod:block" will bridge all models from "assets/mod/models/block" + * @param modelFolderId ModelAsset folder to bridge. For example "mod:block" will bridge all models from "assets/mod/models/block" * @return Success of addition. */ public boolean addBridgedModelsFolder(Identifier modelFolderId) { diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/AtlasAsset.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/AtlasAsset.java new file mode 100644 index 00000000..940cf0fa --- /dev/null +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/AtlasAsset.java @@ -0,0 +1,110 @@ +package eu.pb4.polymer.resourcepack.extras.api.format.atlas; + +import com.google.gson.JsonParser; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import eu.pb4.polymer.resourcepack.api.WritableAsset; +import eu.pb4.polymer.resourcepack.mixin.accessors.BlockEntryAccessor; +import net.minecraft.resource.metadata.BlockEntry; +import net.minecraft.util.Identifier; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.regex.Pattern; + +public record AtlasAsset(List sources) implements WritableAsset.Json { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + AtlasSource.CODEC.listOf().fieldOf("sources").forGetter(AtlasAsset::sources) + ).apply(instance, AtlasAsset::new)); + + public String toJson() { + return CODEC.encodeStart(JsonOps.INSTANCE, this).getOrThrow().toString(); + } + + public static AtlasAsset fromJson(String json) { + return CODEC.decode(JsonOps.INSTANCE, JsonParser.parseString(json)).getOrThrow().getFirst(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private final List sources = new ArrayList<>(); + + private Builder() {} + + public Builder single(Identifier resource, Identifier sprite) { + return this.add(new SingleAtlasSource(resource, Optional.ofNullable(sprite))); + } + + public Builder single(Identifier resource) { + return this.add(new SingleAtlasSource(resource, Optional.empty())); + } + + public Builder directory(String source, String prefix) { + return this.add(new DirectoryAtlasSource(source, prefix)); + } + + public Builder filter(BlockEntry entry) { + return this.add(new FilterAtlasSource(entry)); + } + + public Builder filter(Pattern namespace, Pattern path) { + return this.add(new FilterAtlasSource(BlockEntryAccessor.createBlockEntry(Optional.ofNullable(namespace), Optional.ofNullable(path)))); + } + + public Builder filterNamespace(Pattern namespace) { + return this.add(new FilterAtlasSource(BlockEntryAccessor.createBlockEntry(Optional.ofNullable(namespace), Optional.empty()))); + } + + public Builder filterPath(Pattern path) { + return this.add(new FilterAtlasSource(BlockEntryAccessor.createBlockEntry(Optional.empty(), Optional.ofNullable(path)))); + } + + public Builder unstitch(Identifier resource, double divisorX, double divisorY, Consumer regionConsumer) { + var arr = new ArrayList(); + regionConsumer.accept(arr::add); + return this.add(new UnstitchAtlasSource(resource, arr, divisorX, divisorY)); + } + + public Builder unstitch(Identifier resource, Consumer regionConsumer) { + var arr = new ArrayList(); + regionConsumer.accept(arr::add); + return this.add(new UnstitchAtlasSource(resource, arr)); + } + + public Builder palettedPermutations(List textures, Identifier paletteKey, Map permutations) { + return this.add(new PalettedPermutationsAtlasSource(textures, paletteKey, permutations)); + } + + public Builder palettedPermutations(PalettedPermutationsAtlasSource.Builder builder) { + return this.add(builder.build()); + } + + public Builder palettedPermutations(Identifier paletteKey, Consumer builderConsumer) { + var builder = PalettedPermutationsAtlasSource.builder(paletteKey); + builderConsumer.accept(builder); + return this.add(builder.build()); + } + + public Builder add(AtlasSource source) { + this.sources.add(source); + return this; + } + + public AtlasAsset build() { + return new AtlasAsset(sources); + } + + public interface RegionConsumer extends Consumer { + default void accept(Identifier sprite, double x, double y, double width, double height) { + this.accept(new UnstitchAtlasSource.Region(sprite, x, y, width, height)); + } + } + } +} diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/AtlasSource.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/AtlasSource.java new file mode 100644 index 00000000..9e74649a --- /dev/null +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/AtlasSource.java @@ -0,0 +1,25 @@ +package eu.pb4.polymer.resourcepack.extras.api.format.atlas; + +import com.google.gson.JsonParser; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.MapCodec; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; +import net.minecraft.util.dynamic.Codecs; + +import java.util.List; +import java.util.function.Function; + +public interface AtlasSource { + Codec CODEC = Codec.lazyInitialized(() -> AtlasSource.TYPES.getCodec(Identifier.CODEC).dispatch(AtlasSource::codec, Function.identity())); + Codecs.IdMapper> TYPES = Util.make(new Codecs.IdMapper<>(), m -> { + m.put(Identifier.ofVanilla("single"), SingleAtlasSource.CODEC); + m.put(Identifier.ofVanilla("directory"), DirectoryAtlasSource.CODEC); + m.put(Identifier.ofVanilla("filter"), FilterAtlasSource.CODEC); + m.put(Identifier.ofVanilla("unstitch"), UnstitchAtlasSource.CODEC); + m.put(Identifier.ofVanilla("paletted_permutations"), PalettedPermutationsAtlasSource.CODEC); + }); + + MapCodec codec(); +} diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/DirectoryAtlasSource.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/DirectoryAtlasSource.java new file mode 100644 index 00000000..654d330e --- /dev/null +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/DirectoryAtlasSource.java @@ -0,0 +1,17 @@ +package eu.pb4.polymer.resourcepack.extras.api.format.atlas; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +public record DirectoryAtlasSource(String source, String prefix) implements AtlasSource { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + Codec.STRING.fieldOf("source").forGetter(DirectoryAtlasSource::source), + Codec.STRING.fieldOf("prefix").forGetter(DirectoryAtlasSource::prefix)) + .apply(instance, DirectoryAtlasSource::new)); + + @Override + public MapCodec codec() { + return CODEC; + } +} diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/FilterAtlasSource.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/FilterAtlasSource.java new file mode 100644 index 00000000..82655969 --- /dev/null +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/FilterAtlasSource.java @@ -0,0 +1,27 @@ +package eu.pb4.polymer.resourcepack.extras.api.format.atlas; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import eu.pb4.polymer.resourcepack.mixin.accessors.BlockEntryAccessor; +import net.minecraft.resource.metadata.BlockEntry; + +import java.util.Optional; +import java.util.regex.Pattern; + +public record FilterAtlasSource(BlockEntry pattern) implements AtlasSource { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + BlockEntry.CODEC.fieldOf("pattern").forGetter(FilterAtlasSource::pattern)) + .apply(instance, FilterAtlasSource::new)); + + public FilterAtlasSource(Optional namespace, Optional path) { + this(BlockEntryAccessor.createBlockEntry(namespace, path)); + } + + public FilterAtlasSource(Pattern namespace, Pattern path) { + this(Optional.ofNullable(namespace), Optional.ofNullable(path)); + } + @Override + public MapCodec codec() { + return CODEC; + } +} diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/PalettedPermutationsAtlasSource.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/PalettedPermutationsAtlasSource.java new file mode 100644 index 00000000..a7dffec9 --- /dev/null +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/PalettedPermutationsAtlasSource.java @@ -0,0 +1,58 @@ +package eu.pb4.polymer.resourcepack.extras.api.format.atlas; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.util.Identifier; + +import java.util.*; + +public record PalettedPermutationsAtlasSource(List textures, Identifier paletteKey, Map permutations) implements AtlasSource { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + Codec.list(Identifier.CODEC).fieldOf("textures").forGetter(PalettedPermutationsAtlasSource::textures), + Identifier.CODEC.fieldOf("palette_key").forGetter(PalettedPermutationsAtlasSource::paletteKey), + Codec.unboundedMap(Codec.STRING, Identifier.CODEC).fieldOf("permutations").forGetter(PalettedPermutationsAtlasSource::permutations) + ).apply(instance, PalettedPermutationsAtlasSource::new)); + + @Override + public MapCodec codec() { + return CODEC; + } + + public static Builder builder(Identifier paletteKey) { + return new Builder(paletteKey); + } + + public static class Builder { + private final Identifier paletteKey; + private final List textures = new ArrayList<>(); + private final Map permutations = new HashMap<>(); + private Builder(Identifier paletteKey) { + this.paletteKey = paletteKey; + } + + public Builder texture(Identifier texture) { + this.textures.add(texture); + return this; + } + + public Builder textures(Collection textures) { + this.textures.addAll(textures); + return this; + } + + public Builder permutation(String suffix, Identifier paletteKey) { + this.permutations.put(suffix, paletteKey); + return this; + } + + public Builder permutations(Map permutations) { + this.permutations.putAll(permutations); + return this; + } + + public PalettedPermutationsAtlasSource build() { + return new PalettedPermutationsAtlasSource(new ArrayList<>(this.textures), paletteKey, new HashMap<>(permutations)); + } + } +} diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/SingleAtlasSource.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/SingleAtlasSource.java new file mode 100644 index 00000000..9fda798a --- /dev/null +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/SingleAtlasSource.java @@ -0,0 +1,19 @@ +package eu.pb4.polymer.resourcepack.extras.api.format.atlas; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.util.Identifier; + +import java.util.Optional; + +public record SingleAtlasSource(Identifier resource, Optional sprite) implements AtlasSource { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + Identifier.CODEC.fieldOf("resource").forGetter(SingleAtlasSource::resource), + Identifier.CODEC.optionalFieldOf("sprite").forGetter(SingleAtlasSource::sprite) + ).apply(instance, SingleAtlasSource::new)); + + @Override + public MapCodec codec() { + return CODEC; + } +} diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/UnstitchAtlasSource.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/UnstitchAtlasSource.java new file mode 100644 index 00000000..d2504ef0 --- /dev/null +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/atlas/UnstitchAtlasSource.java @@ -0,0 +1,38 @@ +package eu.pb4.polymer.resourcepack.extras.api.format.atlas; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.util.Identifier; +import net.minecraft.util.dynamic.Codecs; + +import java.util.List; + +public record UnstitchAtlasSource(Identifier resource, List regions, double divisorX, + double divisorY) implements AtlasSource { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + Identifier.CODEC.fieldOf("resource").forGetter(UnstitchAtlasSource::resource), + Codecs.nonEmptyList(Region.CODEC.listOf()).fieldOf("regions").forGetter(UnstitchAtlasSource::regions), + Codec.DOUBLE.optionalFieldOf("divisor_x", 1.0).forGetter(UnstitchAtlasSource::divisorX), + Codec.DOUBLE.optionalFieldOf("divisor_y", 1.0).forGetter(UnstitchAtlasSource::divisorY) + ).apply(instance, UnstitchAtlasSource::new)); + + public UnstitchAtlasSource(Identifier resource, List regions) { + this(resource, regions, 1, 1); + } + + @Override + public MapCodec codec() { + return CODEC; + } + + public record Region(Identifier sprite, double x, double y, double width, double height) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Identifier.CODEC.fieldOf("sprite").forGetter(Region::sprite), + Codec.DOUBLE.fieldOf("x").forGetter(Region::x), + Codec.DOUBLE.fieldOf("y").forGetter(Region::y), + Codec.DOUBLE.fieldOf("width").forGetter(Region::width), + Codec.DOUBLE.fieldOf("height").forGetter(Region::height) + ).apply(instance, Region::new)); + } +} diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/item/ItemAsset.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/item/ItemAsset.java index f9587f82..e65ea19b 100644 --- a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/item/ItemAsset.java +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/item/ItemAsset.java @@ -5,9 +5,10 @@ import com.mojang.serialization.JsonOps; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import eu.pb4.polymer.resourcepack.api.WritableAsset; import eu.pb4.polymer.resourcepack.extras.api.format.item.model.ItemModel; -public record ItemAsset(ItemModel model, Properties properties) { +public record ItemAsset(ItemModel model, Properties properties) implements WritableAsset.Json { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( ItemModel.CODEC.fieldOf("model").forGetter(ItemAsset::model), Properties.CODEC.forGetter(ItemAsset::properties) diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/item/model/RangeDispatchItemModel.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/item/model/RangeDispatchItemModel.java index c58d967f..37be7885 100644 --- a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/item/model/RangeDispatchItemModel.java +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/item/model/RangeDispatchItemModel.java @@ -5,6 +5,7 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import eu.pb4.polymer.resourcepack.extras.api.format.item.property.numeric.NumericProperty; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -27,4 +28,39 @@ public record Entry(float threshold, ItemModel model) { ItemModel.CODEC.fieldOf("model").forGetter(Entry::model) ).apply(instance, Entry::new)); } + + public static Builder builder(NumericProperty property) { + return new Builder(property); + } + + public static class Builder { + private final NumericProperty property; + private final List entries = new ArrayList<>(); + private float scale = 1; + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private Optional fallbackModel = Optional.empty(); + private Builder(NumericProperty property) { + this.property = property; + } + + public Builder scale(float scale) { + this.scale = scale; + return this; + } + + public Builder entry(float threshold, ItemModel model) { + this.entries.add(new Entry(threshold, model)); + return this; + } + + public Builder fallback(ItemModel model) { + this.fallbackModel = Optional.ofNullable(model); + return this; + } + + public RangeDispatchItemModel build() { + return new RangeDispatchItemModel(this.property, this.scale, new ArrayList<>(this.entries), this.fallbackModel); + } + } } diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/item/model/SelectItemModel.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/item/model/SelectItemModel.java index 8f14b140..da9c5be5 100644 --- a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/item/model/SelectItemModel.java +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/item/model/SelectItemModel.java @@ -6,6 +6,7 @@ import eu.pb4.polymer.resourcepack.extras.api.format.item.property.select.SelectProperty; import net.minecraft.util.dynamic.Codecs; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -37,4 +38,37 @@ public static Codec> createCodec(Codec valueCodec) { ); } } + + public static , V> Builder builder(T property) { + return new Builder<>(property); + } + + public static class Builder, V> { + private final T property; + private final List> cases = new ArrayList<>(); + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private Optional fallbackModel = Optional.empty(); + private Builder(T property) { + this.property = property; + } + + public Builder withCase(V value, ItemModel model) { + this.cases.add(new Case<>(List.of(value), model)); + return this; + } + + public Builder withCase(List value, ItemModel model) { + this.cases.add(new Case<>(value, model)); + return this; + } + + public Builder fallback(ItemModel model) { + this.fallbackModel = Optional.ofNullable(model); + return this; + } + + public SelectItemModel build() { + return new SelectItemModel<>(new Switch<>(this.property, new ArrayList<>(this.cases)), this.fallbackModel); + } + } } diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/model/Model.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/model/Model.java deleted file mode 100644 index 205fafac..00000000 --- a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/model/Model.java +++ /dev/null @@ -1,57 +0,0 @@ -package eu.pb4.polymer.resourcepack.extras.api.format.model; - -import com.google.gson.JsonParser; -import com.mojang.serialization.Codec; -import com.mojang.serialization.JsonOps; -import com.mojang.serialization.codecs.RecordCodecBuilder; -import net.minecraft.item.ModelTransformationMode; -import net.minecraft.util.Identifier; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -public record Model(Optional parent, Optional> elements, Map textures, - Map display, - Optional guiLight, - boolean ambientOcclusion) { - public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - Identifier.CODEC.optionalFieldOf("parent").forGetter(Model::parent), - ModelElement.CODEC.listOf().optionalFieldOf("elements").forGetter(Model::elements), - Codec.unboundedMap(Codec.STRING, Codec.STRING).optionalFieldOf("textures", Map.of()).forGetter(Model::textures), - Codec.unboundedMap(ModelTransformationMode.CODEC, ModelTransformation.CODEC).optionalFieldOf("display", Map.of()).forGetter(Model::display), - GuiLight.CODEC.optionalFieldOf("gui_light").forGetter(Model::guiLight), - Codec.BOOL.optionalFieldOf("ambientocclusion", true).forGetter(Model::ambientOcclusion) - ).apply(instance, Model::new)); - - public Model(Optional parent, Optional> elements, Map textures, - Map display, - Optional guiLight) { - this(parent, elements, textures, display, guiLight, true); - } - - public Model(Optional parent, Optional> elements, Map textures, - Map display) { - this(parent, elements, textures, display, Optional.empty(), true); - } - - public Model(Optional parent, Optional> elements, Map textures) { - this(parent, elements, textures, Map.of(), Optional.empty(), true); - } - - public Model(Identifier parent, Map textures) { - this(Optional.of(parent), Optional.empty(), textures, Map.of(), Optional.empty(), true); - } - - public Model(List elements, Map textures) { - this(Optional.empty(), Optional.of(elements), textures, Map.of(), Optional.empty(), true); - } - - public String toJson() { - return CODEC.encodeStart(JsonOps.INSTANCE, this).getOrThrow().toString(); - } - - public static Model fromJson(String json) { - return CODEC.decode(JsonOps.INSTANCE, JsonParser.parseString(json)).getOrThrow().getFirst(); - } -} diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/model/ModelAsset.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/model/ModelAsset.java new file mode 100644 index 00000000..b0fee835 --- /dev/null +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/model/ModelAsset.java @@ -0,0 +1,135 @@ +package eu.pb4.polymer.resourcepack.extras.api.format.model; + +import com.google.gson.JsonParser; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import eu.pb4.polymer.resourcepack.api.WritableAsset; +import net.minecraft.item.ModelTransformationMode; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec3d; + +import java.util.*; +import java.util.function.Consumer; + +public record ModelAsset(Optional parent, Optional> elements, Map textures, + Map display, + Optional guiLight, + boolean ambientOcclusion) implements WritableAsset.Json { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Identifier.CODEC.optionalFieldOf("parent").forGetter(ModelAsset::parent), + ModelElement.CODEC.listOf().optionalFieldOf("elements").forGetter(ModelAsset::elements), + Codec.unboundedMap(Codec.STRING, Codec.STRING).optionalFieldOf("textures", Map.of()).forGetter(ModelAsset::textures), + Codec.unboundedMap(ModelTransformationMode.CODEC, ModelTransformation.CODEC).optionalFieldOf("display", Map.of()).forGetter(ModelAsset::display), + GuiLight.CODEC.optionalFieldOf("gui_light").forGetter(ModelAsset::guiLight), + Codec.BOOL.optionalFieldOf("ambientocclusion", true).forGetter(ModelAsset::ambientOcclusion) + ).apply(instance, ModelAsset::new)); + + public ModelAsset(Optional parent, Optional> elements, Map textures, + Map display, + Optional guiLight) { + this(parent, elements, textures, display, guiLight, true); + } + + public ModelAsset(Optional parent, Optional> elements, Map textures, + Map display) { + this(parent, elements, textures, display, Optional.empty(), true); + } + + public ModelAsset(Optional parent, Optional> elements, Map textures) { + this(parent, elements, textures, Map.of(), Optional.empty(), true); + } + + public ModelAsset(Identifier parent, Map textures) { + this(Optional.of(parent), Optional.empty(), textures, Map.of(), Optional.empty(), true); + } + + public ModelAsset(List elements, Map textures) { + this(Optional.empty(), Optional.of(elements), textures, Map.of(), Optional.empty(), true); + } + + public String toJson() { + return CODEC.encodeStart(JsonOps.INSTANCE, this).getOrThrow().toString(); + } + + public static ModelAsset fromJson(String json) { + return CODEC.decode(JsonOps.INSTANCE, JsonParser.parseString(json)).getOrThrow().getFirst(); + } + + public static Builder builder() { + return new Builder(); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public static class Builder { + private Optional parent = Optional.empty(); + private Optional> elements = Optional.empty(); + private final Map textures = new HashMap<>(); + private final Map display = new HashMap<>(); + private Optional guiLight = Optional.empty(); + private boolean ambientOcclusion = true; + private Builder() {} + + public Builder parent(Identifier parent) { + this.parent = Optional.ofNullable(parent); + return this; + } + + public Builder withElements() { + if (this.elements.isEmpty()) { + this.elements = Optional.of(new ArrayList<>()); + } + return this; + } + + public Builder withElements(List elements) { + this.elements = Optional.ofNullable(elements); + return this; + } + + public Builder element(ModelElement element) { + this.withElements(); + //noinspection OptionalGetWithoutIsPresent + this.elements.get().add(element); + return this; + } + + public Builder elements(Collection element) { + this.withElements(); + //noinspection OptionalGetWithoutIsPresent + this.elements.get().addAll(element); + return this; + } + + public Builder element(Vec3d from, Vec3d to, Consumer builderConsumer) { + var builder = ModelElement.builder(from, to); + builderConsumer.accept(builder); + return this.element(builder.build()); + } + + public Builder texture(String key, String value) { + this.textures.put(key, value); + return this; + } + + public Builder texture(Map textures) { + this.textures.putAll(textures); + return this; + } + + public Builder guiLight(GuiLight guiLight) { + this.guiLight = Optional.ofNullable(guiLight); + return this; + } + + public Builder ambientOcclusion(boolean ambientOcclusion) { + this.ambientOcclusion = ambientOcclusion; + return this; + } + + + public ModelAsset build() { + return new ModelAsset(this.parent, this.elements.map(ArrayList::new), new HashMap<>(this.textures), new HashMap<>(this.display), this.guiLight, this.ambientOcclusion); + } + } +} diff --git a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/model/ModelElement.java b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/model/ModelElement.java index f30f9e11..b43fc0eb 100644 --- a/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/model/ModelElement.java +++ b/polymer-resource-pack-extras/src/main/java/eu/pb4/polymer/resourcepack/extras/api/format/model/ModelElement.java @@ -2,10 +2,13 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; +import it.unimi.dsi.fastutil.floats.FloatList; import net.minecraft.util.dynamic.Codecs; import net.minecraft.util.math.Direction; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; +import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -81,4 +84,92 @@ public Face(String texture) { this(List.of(), texture, Optional.empty(), 0, -1); } } + + public static Builder builder(Vec3d from, Vec3d to) { + return new Builder(from, to); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public static class Builder { + private final Vec3d from; + private final Vec3d to; + private final Map faces = new EnumMap<>(Direction.class); + private Optional rotation = Optional.empty(); + private boolean shade = true; + private int lightEmission = 0; + + private Builder(Vec3d from, Vec3d to) { + this.from = from; + this.to = to; + } + + public Builder rotation(Vec3d origin, Direction.Axis axis, float angle, boolean rescale) { + return this.rotation(new Rotation(origin, axis, angle, rescale)); + } + + public Builder rotation(Vec3d origin, Direction.Axis axis, float angle) { + return this.rotation(new Rotation(origin, axis, angle)); + } + + public Builder rotation(Direction.Axis axis, float angle) { + return this.rotation(new Rotation(axis, angle)); + } + + public Builder face(Direction direction, Face face) { + this.faces.put(direction, face); + return this; + } + + public Builder face(Direction direction, float u1, float v1, float u2, float v2, String texture, Direction cullFace, int rotation, int tint) { + return this.face(direction, new Face(FloatList.of(u1, v1, u2, v2), texture, Optional.ofNullable(cullFace), rotation, tint)); + } + + public Builder face(Direction direction, float u1, float v1, float u2, float v2, String texture, Direction cullFace, int rotation) { + return this.face(direction, new Face(FloatList.of(u1, v1, u2, v2), texture, Optional.ofNullable(cullFace), rotation)); + } + + public Builder face(Direction direction, float u1, float v1, float u2, float v2, String texture, Direction cullFace) { + return this.face(direction, new Face(FloatList.of(u1, v1, u2, v2), texture, Optional.ofNullable(cullFace))); + } + + public Builder face(Direction direction, float u1, float v1, float u2, float v2, String texture) { + return this.face(direction, new Face(FloatList.of(u1, v1, u2, v2), texture)); + } + + public Builder face(Direction direction, String texture, Direction cullFace, int rotation, int tint) { + return this.face(direction, new Face(List.of(), texture, Optional.ofNullable(cullFace), rotation, tint)); + } + + public Builder face(Direction direction, String texture, int rotation, int tint) { + return this.face(direction, new Face(List.of(), texture, Optional.empty(), rotation, tint)); + } + + public Builder face(Direction direction, String texture, int rotation) { + return this.face(direction, new Face(List.of(), texture, Optional.empty(), rotation)); + } + + public Builder face(Direction direction, String texture) { + return this.face(direction, new Face(texture)); + } + + public Builder rotation(Rotation rotation) { + this.rotation = Optional.ofNullable(rotation); + return this; + } + + public Builder shade(boolean shade) { + this.shade = shade; + return this; + } + + public Builder lightEmission(int lightEmission) { + this.lightEmission = MathHelper.clamp(lightEmission, 0, 15); + return this; + } + + + public ModelElement build() { + return new ModelElement(from, to, new EnumMap<>(this.faces), this.rotation, this.shade, this.lightEmission); + } + } } diff --git a/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/ResourcePackBuilder.java b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/ResourcePackBuilder.java index cf052c51..9d5e7d89 100644 --- a/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/ResourcePackBuilder.java +++ b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/ResourcePackBuilder.java @@ -15,6 +15,12 @@ @ApiStatus.NonExtendable public interface ResourcePackBuilder { boolean addData(String path, byte[] data); + default boolean addData(String path, WritableAsset data) { + return this.addData(path, data.toBytes()); + } + default boolean addStringData(String path, String data) { + return addData(path, data.getBytes(StandardCharsets.UTF_8)); + } boolean copyAssets(String modId); @@ -87,6 +93,16 @@ default boolean copyResourcePackFromPath(Path root, String field) { byte @Nullable [] getDataOrSource(String path); + default @Nullable String getStringData(String path) { + var data = getData(path); + return data != null ? new String(data, StandardCharsets.UTF_8) : null; + } + + default @Nullable String getStringDataOrSource(String path) { + var data = getDataOrSource(path); + return data != null ? new String(data, StandardCharsets.UTF_8) : null; + } + void forEachFile(BiConsumer consumer); boolean addAssetsSource(String modId); diff --git a/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/WritableAsset.java b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/WritableAsset.java new file mode 100644 index 00000000..e796e5f9 --- /dev/null +++ b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/api/WritableAsset.java @@ -0,0 +1,15 @@ +package eu.pb4.polymer.resourcepack.api; + +import java.nio.charset.StandardCharsets; + +public interface WritableAsset { + byte[] toBytes(); + + interface Json extends WritableAsset { + String toJson(); + @Override + default byte[] toBytes() { + return toJson().getBytes(StandardCharsets.UTF_8); + } + } +} diff --git a/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/mixin/accessors/BlockEntryAccessor.java b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/mixin/accessors/BlockEntryAccessor.java new file mode 100644 index 00000000..e789bc83 --- /dev/null +++ b/polymer-resource-pack/src/main/java/eu/pb4/polymer/resourcepack/mixin/accessors/BlockEntryAccessor.java @@ -0,0 +1,16 @@ +package eu.pb4.polymer.resourcepack.mixin.accessors; + +import net.minecraft.resource.metadata.BlockEntry; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.Optional; +import java.util.regex.Pattern; + +@Mixin(BlockEntry.class) +public interface BlockEntryAccessor { + @Invoker("") + static BlockEntry createBlockEntry(Optional namespace, Optional path) { + throw new UnsupportedOperationException(); + } +} diff --git a/polymer-resource-pack/src/main/resources/polymer-resource-pack.mixins.json b/polymer-resource-pack/src/main/resources/polymer-resource-pack.mixins.json index 91e7e3f9..821bc6bf 100644 --- a/polymer-resource-pack/src/main/resources/polymer-resource-pack.mixins.json +++ b/polymer-resource-pack/src/main/resources/polymer-resource-pack.mixins.json @@ -5,6 +5,7 @@ "compatibilityLevel": "JAVA_21", "plugin": "eu.pb4.polymer.resourcepack.mixin.PolymerResourcePackMixinConfigPlugin", "mixins": [ + "accessors.BlockEntryAccessor", "accessors.PackOverlaysMetadataAccessor", "accessors.ResourceFilterAccessor" ],