From 68922248ec6b619ef3af862e22b0dfc76bf443f2 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 5 Jul 2025 19:56:16 +0000 Subject: [PATCH] Bedrock animation codec & builders --- .../pack/animation/BedrockAnimation.java | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/main/java/org/geysermc/packgenerator/pack/animation/BedrockAnimation.java diff --git a/src/main/java/org/geysermc/packgenerator/pack/animation/BedrockAnimation.java b/src/main/java/org/geysermc/packgenerator/pack/animation/BedrockAnimation.java new file mode 100644 index 0000000..30e8f42 --- /dev/null +++ b/src/main/java/org/geysermc/packgenerator/pack/animation/BedrockAnimation.java @@ -0,0 +1,150 @@ +package org.geysermc.packgenerator.pack.animation; + +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.util.ExtraCodecs; +import org.geysermc.packgenerator.CodecUtil; +import org.geysermc.packgenerator.pack.BedrockVersion; +import org.joml.Vector3f; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public record BedrockAnimation(BedrockVersion formatVersion, Map definitions) { + public static final BedrockVersion FORMAT_VERSION = BedrockVersion.of(1, 8, 0); + + public static final Codec CODEC = RecordCodecBuilder.create(instance -> + instance.group( + BedrockVersion.STRING_CODEC.fieldOf("format_version").forGetter(BedrockAnimation::formatVersion), + Codec.unboundedMap(Codec.STRING, AnimationDefinition.CODEC).fieldOf("animations").forGetter(BedrockAnimation::definitions) + ).apply(instance, BedrockAnimation::new) + ); + + public void save(Path animationDirectory, String identifier) throws IOException { + CodecUtil.trySaveJson(CODEC, this, animationDirectory.resolve(identifier + ".animation.json")); + } + + public static Builder builder() { + return new Builder(); + } + + public static AnimationDefinition.Builder animation() { + return new AnimationDefinition.Builder(); + } + + public static class Builder { + private final Map animations = new HashMap<>(); + + public Builder withAnimation(String identifier, AnimationDefinition definition) { + animations.put("animation." + identifier, definition); + return this; + } + + public Builder withAnimation(String identifier, AnimationDefinition.Builder builder) { + return withAnimation(identifier, builder.build()); + } + + public BedrockAnimation build() { + return new BedrockAnimation(FORMAT_VERSION, Map.copyOf(animations)); + } + } + + public record AnimationDefinition(LoopMode loopMode, Optional startDelay, Optional loopDelay, Optional animationTimeUpdate, + Optional blendWeight, boolean overridePreviousAnimation, Map bones) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> + instance.group( + LoopMode.CODEC.optionalFieldOf("loop", LoopMode.STOP).forGetter(AnimationDefinition::loopMode), + Codec.STRING.optionalFieldOf("start_delay").forGetter(AnimationDefinition::startDelay), + Codec.STRING.optionalFieldOf("loop_delay").forGetter(AnimationDefinition::loopDelay), + Codec.STRING.optionalFieldOf("anim_time_update").forGetter(AnimationDefinition::animationTimeUpdate), + Codec.STRING.optionalFieldOf("blend_weight").forGetter(AnimationDefinition::blendWeight), + Codec.BOOL.optionalFieldOf("override_previous_animation", false).forGetter(AnimationDefinition::overridePreviousAnimation), + Codec.unboundedMap(Codec.STRING, SimpleAnimation.CODEC).optionalFieldOf("bones", Map.of()).forGetter(AnimationDefinition::bones) + ).apply(instance, AnimationDefinition::new) + ); + + public static class Builder { + private final Map bones = new HashMap<>(); + + private LoopMode loopMode = LoopMode.STOP; + private Optional startDelay; + private Optional loopDelay; + private Optional animationTimeUpdate; + private Optional blendWeight; + private boolean overridePreviousAnimation; + + public Builder withLoopMode(LoopMode loopMode) { + this.loopMode = loopMode; + return this; + } + + public Builder withStartDelay(String startDelay) { + this.startDelay = Optional.of(startDelay); + return this; + } + + public Builder withLoopDelay(String loopDelay) { + this.loopDelay = Optional.of(loopDelay); + return this; + } + + public Builder withAnimationTimeUpdate(String animationTimeUpdate) { + this.animationTimeUpdate = Optional.of(animationTimeUpdate); + return this; + } + + public Builder withBlendWeight(String blendWeight) { + this.blendWeight = Optional.of(blendWeight); + return this; + } + + public Builder overridePreviousAnimation() { + this.overridePreviousAnimation = true; + return this; + } + + public Builder withBone(String bone, SimpleAnimation animation) { + bones.put(bone, animation); + return this; + } + + public Builder withBone(String bone, Vector3f position, Vector3f rotation, Vector3f scale) { + return withBone(bone, new SimpleAnimation(position, rotation, scale)); + } + + public AnimationDefinition build() { + return new AnimationDefinition(loopMode, startDelay, loopDelay, animationTimeUpdate, blendWeight, overridePreviousAnimation, Map.copyOf(bones)); + } + } + } + + public enum LoopMode { + STOP, + LOOP, + HOLD_ON_LAST_FRAME; + + public static final Codec CODEC = Codec.either(Codec.BOOL, Codec.STRING) + .comapFlatMap(either -> either.map(bool -> DataResult.success(bool ? LOOP : STOP), + string -> { + if (string.equals("hold_on_last_frame")) { + return DataResult.success(HOLD_ON_LAST_FRAME); + } + return DataResult.error(() -> "unknown loop mode"); + }), mode -> mode == HOLD_ON_LAST_FRAME ? Either.right("hold_on_last_frame") : Either.left(mode == LOOP)); + } + + public record SimpleAnimation(Vector3f position, Vector3f rotation, Vector3f scale) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> + instance.group( + ExtraCodecs.VECTOR3F.fieldOf("position").forGetter(SimpleAnimation::position), + ExtraCodecs.VECTOR3F.fieldOf("rotation").forGetter(SimpleAnimation::rotation), + ExtraCodecs.VECTOR3F.fieldOf("scale").forGetter(SimpleAnimation::scale) + ).apply(instance, SimpleAnimation::new) + ); + } +}