diff --git a/src/main/java/org/geysermc/packgenerator/pack/BedrockTextures.java b/src/main/java/org/geysermc/packgenerator/pack/BedrockTextures.java index 3e14945..2900aae 100644 --- a/src/main/java/org/geysermc/packgenerator/pack/BedrockTextures.java +++ b/src/main/java/org/geysermc/packgenerator/pack/BedrockTextures.java @@ -14,6 +14,7 @@ public record BedrockTextures(Map textures) { .xmap(pairs -> pairs.stream().map(pair -> Pair.of(pair.getFirst(), pair.getSecond().getFirst().getSecond())).collect(Pair.toMap()), map -> map.entrySet().stream().map(entry -> Pair.of(entry.getKey(), List.of(Pair.of("textures", entry.getValue())))).toList()) .xmap(BedrockTextures::new, BedrockTextures::textures); + public static final String TEXTURES_FOLDER = "textures/"; public Builder toBuilder() { Builder builder = builder(); @@ -26,7 +27,6 @@ public record BedrockTextures(Map textures) { } public static class Builder { - private static final String TEXTURES_FOLDER = "textures/"; private final Map textures = new HashMap<>(); public Builder withItemTexture(GeyserMapping mapping, String texturePath) { diff --git a/src/main/java/org/geysermc/packgenerator/pack/BedrockVersion.java b/src/main/java/org/geysermc/packgenerator/pack/BedrockVersion.java index 68e9bd2..085b533 100644 --- a/src/main/java/org/geysermc/packgenerator/pack/BedrockVersion.java +++ b/src/main/java/org/geysermc/packgenerator/pack/BedrockVersion.java @@ -1,6 +1,7 @@ package org.geysermc.packgenerator.pack; import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; import java.util.List; @@ -8,6 +9,20 @@ public record BedrockVersion(int major, int minor, int patch) { public static final Codec CODEC = Codec.INT.listOf(3, 3) .xmap(list -> BedrockVersion.of(list.getFirst(), list.get(1), list.getLast()), version -> List.of(version.major, version.minor, version.patch)); + public static final Codec STRING_CODEC = Codec.STRING.comapFlatMap(string -> { + String[] segments = string.split("\."); + if (segments.length != 3) { + return DataResult.error(() -> "Semantic version must consist of 3 versions"); + } + try { + int major = Integer.parseInt(segments[0]); + int minor = Integer.parseInt(segments[1]); + int patch = Integer.parseInt(segments[2]); + return DataResult.success(new BedrockVersion(major, minor, patch)); + } catch (NumberFormatException exception) { + return DataResult.error(() -> "Failed to parse semantic version number"); + } + }, version -> String.format("%d.%d.%d", version.major, version.minor, version.patch)); public static BedrockVersion of(int patch) { return of(0, 0, patch); diff --git a/src/main/java/org/geysermc/packgenerator/pack/attachable/BedrockAttachable.java b/src/main/java/org/geysermc/packgenerator/pack/attachable/BedrockAttachable.java new file mode 100644 index 0000000..7bf7ebf --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/pack/attachable/BedrockAttachable.java @@ -0,0 +1,137 @@ +package org.geysermc.packgenerator.pack.attachable; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.entity.EquipmentSlot; +import org.geysermc.packgenerator.PackConstants; +import org.geysermc.packgenerator.pack.BedrockTextures; +import org.geysermc.packgenerator.pack.BedrockVersion; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public record BedrockAttachable(BedrockVersion formatVersion, AttachableInfo info) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> + instance.group( + BedrockVersion.STRING_CODEC.fieldOf("format_version").forGetter(BedrockAttachable::formatVersion), + AttachableInfo.CODEC.fieldOf("description").fieldOf("minecraft:attachable").forGetter(BedrockAttachable::info) + ).apply(instance, BedrockAttachable::new) + ); + + public static Builder builder(ResourceLocation identifier) { + return new Builder(identifier); + } + + public static BedrockAttachable equipment(ResourceLocation identifier, EquipmentSlot slot, String texture) { + return builder(identifier) + .withMaterial(DisplaySlot.DEFAULT, "armor") + .withMaterial(DisplaySlot.ENCHANTED, "armor_enchanted") + .withTexture(DisplaySlot.DEFAULT, texture) + .withTexture(DisplaySlot.ENCHANTED, VanillaTextures.ENCHANTED_ACTOR_GLINT) + .withGeometry(DisplaySlot.DEFAULT, VanillaGeometries.fromEquipmentSlot(slot)) + .withRenderController(VanillaRenderControllers.ARMOR) + .build(); + } + + public static class Builder { + private final ResourceLocation identifier; + private final EnumMap materials = new EnumMap<>(DisplaySlot.class); + private final EnumMap textures = new EnumMap<>(DisplaySlot.class); + private final EnumMap geometries = new EnumMap<>(DisplaySlot.class); + private final Map animations = new HashMap<>(); + private final Map scripts = new HashMap<>(); + private final List renderControllers = new ArrayList<>(); + + public Builder(ResourceLocation identifier) { + this.identifier = identifier; + } + + public Builder withMaterial(DisplaySlot slot, String material) { + materials.put(slot, material); + return this; + } + + public Builder withTexture(DisplaySlot slot, String texture) { + textures.put(slot, BedrockTextures.TEXTURES_FOLDER + texture); + return this; + } + + public Builder withGeometry(DisplaySlot slot, String geometry) { + geometries.put(slot, geometry); + return this; + } + + public Builder withAnimation(String key, String animation) { + animations.put(key, animation); + return this; + } + + public Builder withScript(String key, String script) { + scripts.put(key, script); + return this; + } + + public Builder withRenderController(String controller) { + renderControllers.add(controller); + return this; + } + + public BedrockAttachable build() { + return new BedrockAttachable(PackConstants.ENGINE_VERSION, + new AttachableInfo(identifier, verifyDefault(materials), verifyDefault(textures), verifyDefault(geometries), Map.copyOf(animations), Map.copyOf(scripts), List.copyOf(renderControllers))); + } + + private static DisplayMap verifyDefault(EnumMap map) { + if (!map.containsKey(DisplaySlot.DEFAULT)) { + throw new IllegalStateException("DisplayMap must have a default key"); + } + return new DisplayMap(map); + } + } + + public record AttachableInfo(ResourceLocation identifier, DisplayMap materials, DisplayMap textures, + DisplayMap geometry, Map animations, Map scripts, + List renderControllers) { + private static final Codec> STRING_MAP_CODEC = Codec.unboundedMap(Codec.STRING, Codec.STRING); + public static final Codec CODEC = RecordCodecBuilder.create(instance -> + instance.group( + ResourceLocation.CODEC.fieldOf("identifier").forGetter(AttachableInfo::identifier), + DisplayMap.CODEC.fieldOf("materials").forGetter(AttachableInfo::materials), + DisplayMap.CODEC.fieldOf("textures").forGetter(AttachableInfo::textures), + DisplayMap.CODEC.fieldOf("geometry").forGetter(AttachableInfo::geometry), + STRING_MAP_CODEC.optionalFieldOf("animations", Map.of()).forGetter(AttachableInfo::animations), + STRING_MAP_CODEC.optionalFieldOf("scripts", Map.of()).forGetter(AttachableInfo::scripts), + Codec.STRING.listOf().optionalFieldOf("render_controllers", List.of()).forGetter(AttachableInfo::renderControllers) + ).apply(instance, AttachableInfo::new) + ); + } + + public record DisplayMap(EnumMap map) { + public static final Codec CODEC = Codec.unboundedMap(DisplaySlot.CODEC, Codec.STRING) + .xmap(map -> map.isEmpty() ? new DisplayMap(new EnumMap<>(DisplaySlot.class)) : new DisplayMap(new EnumMap<>(map)), DisplayMap::map); + } + + public enum DisplaySlot implements StringRepresentable { + DEFAULT("default"), + ENCHANTED("enchanted"); + + public static final Codec CODEC = StringRepresentable.fromEnum(DisplaySlot::values); + + private final String name; + + DisplaySlot(String name) { + this.name = name; + } + + @Override + public @NotNull String getSerializedName() { + return name; + } + } +} diff --git a/src/main/java/org/geysermc/packgenerator/pack/attachable/VanillaGeometries.java b/src/main/java/org/geysermc/packgenerator/pack/attachable/VanillaGeometries.java new file mode 100644 index 0000000..19a6970 --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/pack/attachable/VanillaGeometries.java @@ -0,0 +1,20 @@ +package org.geysermc.packgenerator.pack.attachable; + +import net.minecraft.world.entity.EquipmentSlot; + +public class VanillaGeometries { + public static final String HELMET = "geometry.player.armor.helmet"; + public static final String CHESTPLATE = "geometry.player.armor.chestplate"; + public static final String LEGGINGS = "geometry.player.armor.leggings"; + public static final String BOOTS = "geometry.player.armor.boots"; + + public static String fromEquipmentSlot(EquipmentSlot slot) { + return switch (slot) { + case FEET -> BOOTS; + case LEGS -> LEGGINGS; + case CHEST -> CHESTPLATE; + case HEAD -> HELMET; + default -> null; + }; + } +} diff --git a/src/main/java/org/geysermc/packgenerator/pack/attachable/VanillaMaterials.java b/src/main/java/org/geysermc/packgenerator/pack/attachable/VanillaMaterials.java new file mode 100644 index 0000000..ebf1880 --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/pack/attachable/VanillaMaterials.java @@ -0,0 +1,6 @@ +package org.geysermc.packgenerator.pack.attachable; + +public class VanillaMaterials { + public static final String ARMOR = "armor"; + public static final String ARMOR_ENCHANTED = "armor_enchanted"; +} diff --git a/src/main/java/org/geysermc/packgenerator/pack/attachable/VanillaRenderControllers.java b/src/main/java/org/geysermc/packgenerator/pack/attachable/VanillaRenderControllers.java new file mode 100644 index 0000000..e99490d --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/pack/attachable/VanillaRenderControllers.java @@ -0,0 +1,5 @@ +package org.geysermc.packgenerator.pack.attachable; + +public class VanillaRenderControllers { + public static final String ARMOR = "controller.render.armor"; +} diff --git a/src/main/java/org/geysermc/packgenerator/pack/attachable/VanillaTextures.java b/src/main/java/org/geysermc/packgenerator/pack/attachable/VanillaTextures.java new file mode 100644 index 0000000..31101e4 --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/pack/attachable/VanillaTextures.java @@ -0,0 +1,5 @@ +package org.geysermc.packgenerator.pack.attachable; + +public class VanillaTextures { + public static final String ENCHANTED_ACTOR_GLINT = "misc/enchanted_actor_glint"; +}