P3-11 : add PacketPlayRigAnim + ModNetwork registration (handler stub)

Record packet S->C pour animations cinematic one-shot (capture NPC, hit
stun, death). Carries entityId, animId (ResourceLocation), transitionTime
(float), et priorityOrdinal (byte = Layer.Priority.ordinal()).

Encode via writeVarInt/writeResourceLocation/writeFloat/writeByte ; decode
symétrique. Factory PacketPlayRigAnim.of() pour build lisible avec
Layer.Priority, et getter priority() avec fallback LOWEST si ordinal
hors-range (masking & 0xFF pour gérer byte négatif).

Handler client = stub qui LOGGER.debug + setPacketHandled. Le corps
complet (resolve entity, lookup LivingEntityPatch, animator.playAnimation)
viendra en P3-12 avec la méthode serveur playAnimationSync().

Registration ajoutée en fin de ModNetwork.register() — ID sequential 75
(76 packets total).

7 tests unitaires PacketPlayRigAnimTest : roundtrip nominal, entityId
boundary (0/MAX/MIN), transitionTime zero+negative (pas de validation
silencieuse), loop sur les 5 Layer.Priority, fallback LOWEST sur ordinal
malformé (99, -1, PRIORITY_COUNT), ResourceLocation avec underscores et
slashes, et sanity check equals.

Rig tests : 65 -> 72 GREEN. Full suite : 162 GREEN, 0 failure.
This commit is contained in:
notevil
2026-04-23 15:11:08 +02:00
parent 5d108f51b4
commit 2a4ec170ef
3 changed files with 242 additions and 0 deletions

View File

@@ -64,6 +64,7 @@ import com.tiedup.remake.network.sync.PacketSyncPetBedState;
import com.tiedup.remake.network.sync.PacketSyncStruggleState;
import com.tiedup.remake.network.trader.PacketBuyCaptive;
import com.tiedup.remake.network.trader.PacketOpenTraderScreen;
import com.tiedup.remake.rig.network.PacketPlayRigAnim;
import com.tiedup.remake.v2.bondage.network.PacketSyncV2Equipment;
import com.tiedup.remake.v2.bondage.network.PacketV2LockToggle;
import com.tiedup.remake.v2.bondage.network.PacketV2SelfEquip;
@@ -592,6 +593,14 @@ public class ModNetwork {
PacketSyncMovementStyle::handle
);
// RIG animation system (S2C cinematic one-shot)
reg(
PacketPlayRigAnim.class,
PacketPlayRigAnim::encode,
PacketPlayRigAnim::decode,
PacketPlayRigAnim::handleOnClient
);
TiedUpMod.LOGGER.info("Registered {} network packets", packetId);
}

View File

@@ -0,0 +1,85 @@
/*
* © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
*/
package com.tiedup.remake.rig.network;
import java.util.function.Supplier;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.network.NetworkEvent;
import com.tiedup.remake.rig.TiedUpRigConstants;
import com.tiedup.remake.rig.anim.client.Layer;
/**
* Packet S→C : déclenche une animation one-shot sur une entité distante.
*
* <p>Utilisé pour les animations cinematic serveur-authoritatives (NPC capture
* grab, hit stun, death). Le client résout l'entité par ID, récupère son
* {@code LivingEntityPatch} via {@code TiedUpCapabilities}, et appelle
* {@code animator.playAnimation(anim, transitionTime)} avec la priorité donnée.
*
* <p>Le server-side dispatch (méthode {@code LivingEntityPatch.playAnimationSync})
* sera implémenté en P3-12. Ici on pose juste l'infra packet + handler stub
* qui log et termine.
*
* @param entityId ID runtime de l'entité ciblée (Entity.getId())
* @param animId ResourceLocation de l'animation à jouer
* @param transitionTime durée de blend en secondes (typiquement 0.15F = 3 ticks)
* @param priorityOrdinal ordinal de Layer.Priority (LOWEST=0...HIGHEST=4),
* transmis sous forme byte pour compacité
*/
public record PacketPlayRigAnim(
int entityId,
ResourceLocation animId,
float transitionTime,
byte priorityOrdinal
) {
public static final int PRIORITY_COUNT = Layer.Priority.values().length;
/** Factory avec Layer.Priority (plus expressif qu'un byte direct). */
public static PacketPlayRigAnim of(int entityId, ResourceLocation animId, float transitionTime, Layer.Priority priority) {
return new PacketPlayRigAnim(entityId, animId, transitionTime, (byte) priority.ordinal());
}
public Layer.Priority priority() {
// Guard out-of-range si un peer malveillant envoie un byte invalide
int idx = priorityOrdinal & 0xFF;
if (idx < 0 || idx >= PRIORITY_COUNT) {
return Layer.Priority.LOWEST; // fallback safe
}
return Layer.Priority.values()[idx];
}
public void encode(FriendlyByteBuf buf) {
buf.writeVarInt(this.entityId);
buf.writeResourceLocation(this.animId);
buf.writeFloat(this.transitionTime);
buf.writeByte(this.priorityOrdinal);
}
public static PacketPlayRigAnim decode(FriendlyByteBuf buf) {
int entityId = buf.readVarInt();
ResourceLocation animId = buf.readResourceLocation();
float transitionTime = buf.readFloat();
byte priorityOrdinal = buf.readByte();
return new PacketPlayRigAnim(entityId, animId, transitionTime, priorityOrdinal);
}
/**
* Handler client — stub P3-11. Le corps complet (resolve entity, lookup
* patch, playAnimation) viendra en P3-12.
*/
public static void handleOnClient(PacketPlayRigAnim pkt, Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> {
TiedUpRigConstants.LOGGER.debug(
"[PacketPlayRigAnim] received (stub P3-11): entityId={}, animId={}, transition={}s, priority={}",
pkt.entityId, pkt.animId, pkt.transitionTime, pkt.priority()
);
// TODO P3-12 : resolve entity + patch + animator.playAnimation
});
ctx.get().setPacketHandled(true);
}
}