From a0b6ac5b04f0d411664270b449614993c74778e7 Mon Sep 17 00:00:00 2001 From: notevil Date: Fri, 24 Apr 2026 13:01:30 +0200 Subject: [PATCH] =?UTF-8?q?Implement=20datapack=20armature=20registry=20?= =?UTF-8?q?=E2=80=94=20enable=20auto-register=20of=20anim=20JSONs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace InstantiateInvoker.getArmature() throw with real lookup in TiedUpArmatures registry. This unblocks the datapack auto-register path for anim JSONs placed in assets/tiedup/animmodels/animations/. 5 Phase 3 placeholder JSONs updated with EF-native 'constructor' block — they now auto-register via AnimationManager.readResourcepackAnimation without any Java-side hardcoding. Data-driven pipeline now fully working end-to-end : modder drops a JSON in their datapack's animmodels folder, it's automatically picked up at reload, and referenced via resolveWithFallback() from item bindings. No recompile needed for new items/anims. --- .../tiedup/remake/rig/TiedUpArmatures.java | 47 +++++++++++++++++ .../remake/rig/util/InstantiateInvoker.java | 51 ++++++++++++++++++- .../animations/armbinder_equip_oneshot.json | 8 ++- .../animmodels/animations/armbinder_idle.json | 8 +-- .../animations/armbinder_struggle.json | 4 ++ .../animmodels/animations/armbinder_walk.json | 5 +- .../animations/classic_collar_idle.json | 4 ++ 7 files changed, 120 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/tiedup/remake/rig/TiedUpArmatures.java b/src/main/java/com/tiedup/remake/rig/TiedUpArmatures.java index 202ec42..82d6e5e 100644 --- a/src/main/java/com/tiedup/remake/rig/TiedUpArmatures.java +++ b/src/main/java/com/tiedup/remake/rig/TiedUpArmatures.java @@ -11,6 +11,7 @@ import java.util.Map; import net.minecraft.resources.ResourceLocation; +import com.tiedup.remake.rig.armature.Armature; import com.tiedup.remake.rig.armature.HumanoidArmature; import com.tiedup.remake.rig.armature.Joint; import com.tiedup.remake.rig.asset.AssetAccessor; @@ -72,6 +73,21 @@ public final class TiedUpArmatures { private static final ResourceLocation BIPED_REGISTRY_NAME = ResourceLocation.fromNamespaceAndPath(TiedUpRigConstants.MODID, "armature/biped"); + /** Short-form ID utilisé par les invocation_commands data-driven (ex. + * {@code "tiedup:biped"} dans le bloc "constructor" des anim JSONs). + * C'est ce que l'utilisateur écrit dans les datapacks — plus concis que + * {@code tiedup:armature/biped} et aligné sur la convention EF + * (cf. {@code assets/epicfight/armatures/biped.json} référencé comme + * {@code "epicfight:biped"} dans les exports DatapackEditScreen). + * + *

Tant que Phase 2.7 n'a pas livré un vrai registry JSON + * {@link TiedUpRigRegistry}, {@link #get(ResourceLocation)} ne reconnaît + * que cet ID — tout autre ID retombe sur le fallback + * {@code InstantiateInvoker.getArmature} qui warn + renvoie BIPED.

+ */ + private static final ResourceLocation BIPED_SHORT_ID = + ResourceLocation.fromNamespaceAndPath(TiedUpRigConstants.MODID, "biped"); + /** * Holder idiome pour init lazy + thread-safe sans synchronized. * @@ -227,4 +243,35 @@ public final class TiedUpArmatures { target.put(name, j); return j; } + + /** + * Lookup d'un {@link AssetAccessor} d'armature par ID datapack. Point + * d'entrée du data-driven : quand {@code AnimationManager.readResourcepackAnimation} + * parse le bloc {@code "constructor"} d'un JSON anim utilisateur et que + * {@link com.tiedup.remake.rig.util.InstantiateInvoker} rencontre un arg de + * type {@code Armature}, il appelle cette méthode via {@code getArmature(id)}. + * + *

Phase 2.4 scope : seul l'ID canonique {@code tiedup:biped} + * (et son équivalent long-form {@code tiedup:armature/biped}) est reconnu. + * Tout autre ID retourne {@code null} — le caller décide du fallback + * (InstantiateInvoker warn + BIPED, cf. sa Javadoc).

+ * + *

Phase 2.7 future : quand {@link TiedUpRigRegistry} exposera un + * registry JSON Blender-authored, ce lookup interrogera le registry map + * et supportera les armatures tierces (datapack mods). API conservée + * stable pour les call sites InstantiateInvoker + test smoke.

+ * + * @param id l'ID à résoudre (ex. {@code tiedup:biped} ou + * {@code tiedup:armature/biped}). Jamais null — caller check. + * @return l'{@link AssetAccessor} correspondant, ou {@code null} si l'ID + * n'est pas connu du registry. Pas de fallback automatique ici — + * c'est au caller de décider (InstantiateInvoker log+BIPED, + * test strict fail, etc.). + */ + public static AssetAccessor get(ResourceLocation id) { + if (BIPED_SHORT_ID.equals(id) || BIPED_REGISTRY_NAME.equals(id)) { + return BIPED; + } + return null; + } } diff --git a/src/main/java/com/tiedup/remake/rig/util/InstantiateInvoker.java b/src/main/java/com/tiedup/remake/rig/util/InstantiateInvoker.java index aba2f97..ae7377e 100644 --- a/src/main/java/com/tiedup/remake/rig/util/InstantiateInvoker.java +++ b/src/main/java/com/tiedup/remake/rig/util/InstantiateInvoker.java @@ -19,7 +19,10 @@ import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.InteractionHand; +import com.tiedup.remake.rig.TiedUpArmatures; +import com.tiedup.remake.rig.TiedUpRigConstants; import com.tiedup.remake.rig.armature.Joint; import com.tiedup.remake.rig.asset.AssetAccessor; import com.tiedup.remake.rig.exception.AnimationInvokeException; @@ -55,8 +58,54 @@ public class InstantiateInvoker { registerKeyword(InteractionHand.class, InteractionHand::valueOf); } + /** + * Résout un ID d'armature à partir d'une String décodée depuis un + * {@code invocation_command} datapack (ex. bloc {@code "constructor"} des + * anim JSON user, cf. {@code AnimationManager.readResourcepackAnimation}). + * + *

Phase 2.4 : TiedUp n'a qu'une seule armature canonique + * ({@link TiedUpArmatures#BIPED}). Les ID reconnus sont {@code tiedup:biped} + * et {@code tiedup:armature/biped}. Tout autre ID déclenche un WARN et + * retombe sur {@code BIPED} pour éviter un crash au datapack reload — c'est + * plus utile en dev (un typo dans un JSON modder ne tue pas le pipeline + * anim complet).

+ * + *

Phase 2.7 : quand {@link TiedUpArmatures#get(ResourceLocation)} + * supportera de vrais registry JSON Blender-authored tiers, ce fallback + * restera en place comme filet de sécurité — les ID inconnus seront + * probablement des erreurs de datapack, pas de bug runtime.

+ * + *

Divergence EF : upstream ({@code Armatures.getOrCreate(id, Armature::new)}) + * instancie une Armature vide à la volée pour chaque ID inconnu. On ne le + * fait PAS ici car une Armature sans hiérarchie provoquerait un NPE au + * premier {@code searchJointByName} — mieux vaut un warn + fallback BIPED + * visible que des NPE silencieux en fond de renderer.

+ */ private static AssetAccessor getArmature(String id) { - throw new AnimationInvokeException("Armature registry not yet implemented — Phase 2 (lookup for '" + id + "')"); + ResourceLocation rl; + + try { + rl = ResourceLocation.parse(id); + } catch (net.minecraft.ResourceLocationException e) { + TiedUpRigConstants.LOGGER.warn( + "[InstantiateInvoker] Invalid armature ID '{}' in invocation_command, falling back to TiedUpArmatures.BIPED. Cause: {}", + id, e.getMessage() + ); + return TiedUpArmatures.BIPED; + } + + AssetAccessor arm = TiedUpArmatures.get(rl); + + if (arm == null) { + TiedUpRigConstants.LOGGER.warn( + "[InstantiateInvoker] Armature not found in registry: '{}', falling back to TiedUpArmatures.BIPED. " + + "Check your datapack invocation_command — only 'tiedup:biped' is recognized in Phase 2.4.", + rl + ); + return TiedUpArmatures.BIPED; + } + + return arm; } public static void registerPrimitive(String keyword, Class clz, Function decoder) { diff --git a/src/main/resources/assets/tiedup/animmodels/animations/armbinder_equip_oneshot.json b/src/main/resources/assets/tiedup/animmodels/animations/armbinder_equip_oneshot.json index 023d5e7..f6b62ca 100644 --- a/src/main/resources/assets/tiedup/animmodels/animations/armbinder_equip_oneshot.json +++ b/src/main/resources/assets/tiedup/animmodels/animations/armbinder_equip_oneshot.json @@ -1,6 +1,10 @@ { - "_comment": "PLACEHOLDER gameday — oneshot d'équipement pour armbinder (2 keyframes Root, 0.5s non-repeat). Testera le trigger on_equip via PacketPlayRigAnim.handleOnClient + LivingEntityPatch.playAnimationSync. F6 overlay doit montrer une brève spike d'animation HIGH priority au moment de l'équipement, puis retour au base layer IDLE. Remplacement Blender (bras qui se replient derrière le dos) quand dispo.", - "_comment_registration": "Même statut que les autres placeholders : résolution vers EMPTY_ANIMATION. La spike visuelle via PacketPlayRigAnim testera le binding du oneshot même si l'effet rendu est nul.", + "_comment": "PLACEHOLDER gameday — oneshot d'équipement pour armbinder (2 keyframes Root, 0.5s non-repeat). Testera le trigger on_equip via PacketPlayRigAnim.handleOnClient + LivingEntityPatch.playAnimationSync. F6 overlay doit montrer une brève spike d'animation au moment de l'équipement, puis retour au base layer IDLE. Remplacement Blender (bras qui se replient derrière le dos) quand dispo.", + "_comment_registration": "Auto-registered via AnimationManager.readResourcepackAnimation. isRepeat=false (oneshot, joue une seule fois puis layer off).", + "_comment_layer_priority": "User decision: HIGH priority + COMPOSITE_LAYER — pas applicable via JSON Phase 2.4 car les properties LAYER_TYPE/PRIORITY ne sont PAS serializable (créées sans name/Codec dans ClientAnimationProperties). Defaults appliqués: BASE_LAYER + LOWEST. Phase future: ajouter des Codec et ré-enregistrer ces StaticAnimationProperty via ctor named pour permettre override JSON.", + "constructor": { + "invocation_command": "(0.15#F,false#Z,tiedup:armbinder_equip_oneshot#java.lang.String,tiedup:biped#com.tiedup.remake.rig.armature.Armature)#com.tiedup.remake.rig.anim.types.StaticAnimation" + }, "format": "ATTRIBUTES", "animation": [ { diff --git a/src/main/resources/assets/tiedup/animmodels/animations/armbinder_idle.json b/src/main/resources/assets/tiedup/animmodels/animations/armbinder_idle.json index d2103e1..702ee2f 100644 --- a/src/main/resources/assets/tiedup/animmodels/animations/armbinder_idle.json +++ b/src/main/resources/assets/tiedup/animmodels/animations/armbinder_idle.json @@ -1,7 +1,9 @@ { - "_comment": "PLACEHOLDER gameday — identity idle pour armbinder (2 keyframes Root, 2s loop). Même structure que context_stand_idle.json : le but n'est PAS d'avoir un rendu visuel different mais de valider le pipeline P3-05 end-to-end (binding résolu, anim jouée, F6 overlay 'motion: IDLE, bindings: N'). À remplacer par un export Blender authored (bras derrière le dos, épaules sanglées) quand dispo.", - "_comment_registration": "Ce JSON n'est PAS auto-registered dans AnimationManager.animationByName : readResourcepackAnimation exige un bloc 'constructor' absent ici, et InstantiateInvoker.getArmature() throw actuellement (Armature registry pas Phase 2). resolveWithFallback retourne donc EMPTY_ANIMATION, ce qui est OK pour le smoke test du pipeline (la map livingAnimations reçoit bien un accessor, juste vide).", - "_comment_armature": "Pas de champ 'armature' ici — le loader ne lit pas ce champ au runtime. L'armature est résolue par le call site.", + "_comment": "PLACEHOLDER gameday — identity idle pour armbinder (2 keyframes Root, 2s loop). Le but n'est PAS d'avoir un rendu visuel different mais de valider le pipeline P3-05 end-to-end (binding résolu, anim jouée, F6 overlay 'motion: IDLE, bindings: N'). À remplacer par un export Blender authored (bras derrière le dos, épaules sanglées) quand dispo.", + "_comment_registration": "Le bloc 'constructor' ci-dessous fait que AnimationManager.apply auto-register cette anim dans animationByName via readResourcepackAnimation. invocation_command = ctor StaticAnimation(float, boolean, String, Armature) — le String est le registryName (tiedup:armbinder_idle), le Armature est résolu via InstantiateInvoker → TiedUpArmatures.get → BIPED.", + "constructor": { + "invocation_command": "(0.15#F,true#Z,tiedup:armbinder_idle#java.lang.String,tiedup:biped#com.tiedup.remake.rig.armature.Armature)#com.tiedup.remake.rig.anim.types.StaticAnimation" + }, "format": "ATTRIBUTES", "animation": [ { diff --git a/src/main/resources/assets/tiedup/animmodels/animations/armbinder_struggle.json b/src/main/resources/assets/tiedup/animmodels/animations/armbinder_struggle.json index 47d61f5..b2cbb2b 100644 --- a/src/main/resources/assets/tiedup/animmodels/animations/armbinder_struggle.json +++ b/src/main/resources/assets/tiedup/animmodels/animations/armbinder_struggle.json @@ -1,5 +1,9 @@ { "_comment": "PLACEHOLDER gameday — struggle bondage pour armbinder (2 keyframes Root, 0.8s loop). Testera le binding de la motion custom TiedUpLivingMotions.STRUGGLE_BOUND. Remplacement Blender (gesticulation limitée bras sanglés) quand dispo.", + "_comment_registration": "Auto-registered via AnimationManager.readResourcepackAnimation. isRepeat=true car le struggle tourne en boucle tant que le joueur maintient l'input.", + "constructor": { + "invocation_command": "(0.15#F,true#Z,tiedup:armbinder_struggle#java.lang.String,tiedup:biped#com.tiedup.remake.rig.armature.Armature)#com.tiedup.remake.rig.anim.types.StaticAnimation" + }, "format": "ATTRIBUTES", "animation": [ { diff --git a/src/main/resources/assets/tiedup/animmodels/animations/armbinder_walk.json b/src/main/resources/assets/tiedup/animmodels/animations/armbinder_walk.json index 76006fb..5058592 100644 --- a/src/main/resources/assets/tiedup/animmodels/animations/armbinder_walk.json +++ b/src/main/resources/assets/tiedup/animmodels/animations/armbinder_walk.json @@ -1,6 +1,9 @@ { "_comment": "PLACEHOLDER gameday — identity walk pour armbinder (2 keyframes Root, 1s loop). Le but est de tester le switch de motion IDLE→WALK au runtime : F6 overlay doit afficher 'motion: WALK' quand le joueur bouge. Identité visuelle pour l'instant, remplacement Blender-authored (démarche contrainte, épaules raides) quand dispo.", - "_comment_registration": "Même statut que armbinder_idle.json : pas de bloc 'constructor' => resolveWithFallback retourne EMPTY_ANIMATION. Valide le switch de motion côté binding map sans besoin d'un StaticAnimation réel.", + "_comment_registration": "Auto-registered par AnimationManager.readResourcepackAnimation via le bloc 'constructor' ci-dessous. isRepeat=true (loop walk), transition=0.15s (GENERAL).", + "constructor": { + "invocation_command": "(0.15#F,true#Z,tiedup:armbinder_walk#java.lang.String,tiedup:biped#com.tiedup.remake.rig.armature.Armature)#com.tiedup.remake.rig.anim.types.StaticAnimation" + }, "format": "ATTRIBUTES", "animation": [ { diff --git a/src/main/resources/assets/tiedup/animmodels/animations/classic_collar_idle.json b/src/main/resources/assets/tiedup/animmodels/animations/classic_collar_idle.json index 2ad8d23..f66a401 100644 --- a/src/main/resources/assets/tiedup/animmodels/animations/classic_collar_idle.json +++ b/src/main/resources/assets/tiedup/animmodels/animations/classic_collar_idle.json @@ -1,5 +1,9 @@ { "_comment": "PLACEHOLDER gameday — identity idle pour classic_collar (NECK region). Apairé avec armbinder_* pour tester le cas multi-item composition : armbinder (ARMS pose_priority 30) + classic_collar (NECK pose_priority 5) doivent tous deux écrire dans la map livingAnimations (pose_priority ASC sort => collar appliqué d'abord, armbinder écrase). F6 overlay doit afficher 'bindings: 2+' quand les deux items sont équipés.", + "_comment_registration": "Auto-registered via AnimationManager.readResourcepackAnimation. isRepeat=true (idle loop), 2s boucle.", + "constructor": { + "invocation_command": "(0.15#F,true#Z,tiedup:classic_collar_idle#java.lang.String,tiedup:biped#com.tiedup.remake.rig.armature.Armature)#com.tiedup.remake.rig.anim.types.StaticAnimation" + }, "format": "ATTRIBUTES", "animation": [ {