Phase 2.3 : capability system RIG (EntityPatchProvider + events)
5 classes ajoutées dans rig/patch/ : - TiedUpCapabilities.java Holder du Capability<EntityPatch> CAPABILITY_ENTITY (CapabilityToken auto-register) + helpers getEntityPatch / getPlayerPatch / getPlayerPatchAsOptional. Simplifié de EF (pas de ITEM/PROJECTILE/SKILL caps, combat only). - EntityPatchProvider.java ICapabilityProvider + Map<EntityType, Function<Entity, Supplier<EntityPatch<?>>>>. registerEntityPatches() pour commonSetup (EntityType.PLAYER seul Phase 2), registerEntityPatchesClient() pour clientSetup (dispatch LocalPlayerPatch vs ClientPlayerPatch<RemotePlayer> vs ServerPlayerPatch). CUSTOM_CAPABILITIES pour extensions futures. Pas de GlobalMobPatch combat fallback. IMPORTANT : n'enregistre PAS EntityType.VILLAGER (MCA conflict V3-REW-10). - TiedUpCapabilityEvents.java @Mod.EventBusSubscriber sur AttachCapabilitiesEvent<Entity>. Check oldPatch pour éviter double-attach, construit provider, appelle onConstructed eager (D-01 pattern EF), addCapability. Priority NORMAL (order d'attachement ne matière pas, c'est les runtime cross-cap reads qui importent et ceux-là sont déjà lazy dans onConstructed). 3 stubs PlayerPatch subclasses (placeholders Phase 2.4) : - ServerPlayerPatch : overrideRender=false, getArmature=null stub, updateMotion no-op - ClientPlayerPatch<T extends AbstractClientPlayer> : overrideRender=true, @OnlyIn CLIENT - LocalPlayerPatch extends ClientPlayerPatch<LocalPlayer> : vide pour l'instant Ces stubs satisfont le compile de EntityPatchProvider.registerEntityPatchesClient(). Le getArmature() null est non-bloquant Phase 2.3 mais devra être fixé Phase 2.4 pour le vrai rendering (lien avec TiedUpRigRegistry.BIPED à créer Phase 2.7). Compile BUILD SUCCESSFUL + 11 tests bridge GREEN maintenus.
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||||
|
* by the Epic Fight Team, licensed under GPLv3.
|
||||||
|
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.tiedup.remake.rig.patch;
|
||||||
|
|
||||||
|
import net.minecraft.client.player.AbstractClientPlayer;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||||
|
|
||||||
|
import com.tiedup.remake.rig.armature.Armature;
|
||||||
|
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub Phase 2.3 — patch côté client pour les joueurs remote (autres joueurs
|
||||||
|
* dans une session multi). Etoffé Phase 2.4 avec la logique cape / scale /
|
||||||
|
* slim vs default model selection.
|
||||||
|
*
|
||||||
|
* <p>Version minimale : {@code overrideRender=true} (on veut que le renderer
|
||||||
|
* patched intercepte et dispatch au RIG), {@code getArmature=null} stub (TODO
|
||||||
|
* Phase 2.4), {@code updateMotion} no-op (Phase 2.7).</p>
|
||||||
|
*
|
||||||
|
* <p>Forke conceptuellement {@code yesman.epicfight.client.world.capabilites.entitypatch.player.AbstractClientPlayerPatch}
|
||||||
|
* (EF 479 LOC) mais réécrit from scratch (D-04) car ~80% du contenu original
|
||||||
|
* est combat/skill non réutilisable pour TiedUp.</p>
|
||||||
|
*/
|
||||||
|
@OnlyIn(Dist.CLIENT)
|
||||||
|
public class ClientPlayerPatch<T extends AbstractClientPlayer> extends PlayerPatch<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateMotion(boolean considerInaction) {
|
||||||
|
// no-op stub — Phase 2.7
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Armature getArmature() {
|
||||||
|
// TODO Phase 2.4 — retourner TiedUpRigRegistry.BIPED.get()
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean overrideRender() {
|
||||||
|
// On veut que le render pipeline patched prenne la main pour les
|
||||||
|
// remote players visibles.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpenMatrix4f getModelMatrix(float partialTick) {
|
||||||
|
return getMatrix(partialTick);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||||
|
* by the Epic Fight Team, licensed under GPLv3.
|
||||||
|
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.tiedup.remake.rig.patch;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.EntityType;
|
||||||
|
import net.minecraftforge.common.capabilities.Capability;
|
||||||
|
import net.minecraftforge.common.capabilities.ICapabilityProvider;
|
||||||
|
import net.minecraftforge.common.util.LazyOptional;
|
||||||
|
import net.minecraftforge.common.util.NonNullSupplier;
|
||||||
|
|
||||||
|
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatcher + {@link ICapabilityProvider} qui construit l'{@link EntityPatch}
|
||||||
|
* approprié pour un {@link Entity} donné, basé sur son {@link EntityType}.
|
||||||
|
*
|
||||||
|
* <p>Simplifié de EF {@code yesman.epicfight.world.capabilities.provider.EntityPatchProvider} :</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Pas de projectile (ProjectilePatch / ArrowPatch strippés)</li>
|
||||||
|
* <li>Pas de GlobalMobPatch fallback sur gamerule stun</li>
|
||||||
|
* <li>Pas d'EntityPatchRegistryEvent posté à ModLoader (pas de tiers mods EF pour l'instant)</li>
|
||||||
|
* <li>{@link #CUSTOM_CAPABILITIES} reste exposé via {@link #putCustomEntityPatch(EntityType, Function)}
|
||||||
|
* pour un hook d'extension future</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p><b>Attention MCA</b> : on ne doit PAS enregistrer {@link EntityType#VILLAGER}
|
||||||
|
* dans {@link #CAPABILITIES}. MCA gère son propre rendering custom via
|
||||||
|
* {@code MixinVillagerEntityMCAAnimated} et attacher notre patch causerait
|
||||||
|
* double-rendering + rupture des anims MCA (cf.
|
||||||
|
* {@code docs/plans/rig/V3_REWORK_BACKLOG.md} V3-REW-10).</p>
|
||||||
|
*
|
||||||
|
* <p><b>Init order</b> : {@link #registerEntityPatches()} doit être appelé en
|
||||||
|
* {@code FMLCommonSetupEvent} (serveur + client), {@link #registerEntityPatchesClient()}
|
||||||
|
* en {@code FMLClientSetupEvent} uniquement.</p>
|
||||||
|
*/
|
||||||
|
public class EntityPatchProvider implements ICapabilityProvider, NonNullSupplier<EntityPatch<?>> {
|
||||||
|
|
||||||
|
/** Provider map populée au setup, lu au spawn entity. */
|
||||||
|
private static final Map<EntityType<?>, Function<Entity, Supplier<EntityPatch<?>>>> CAPABILITIES = Maps.newHashMap();
|
||||||
|
|
||||||
|
/** Hook extension pour mods/datapacks tiers. Prioritaire sur {@link #CAPABILITIES}. */
|
||||||
|
private static final Map<EntityType<?>, Function<Entity, Supplier<EntityPatch<?>>>> CUSTOM_CAPABILITIES = Maps.newHashMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* À appeler en {@code FMLCommonSetupEvent.event.enqueueWork(...)}.
|
||||||
|
*
|
||||||
|
* <p>Phase 2 : enregistre uniquement {@link EntityType#PLAYER} côté serveur.
|
||||||
|
* Les NPCs TiedUp (Damsel, Maid, Master, Kidnapper) seront ajoutés Phase 5
|
||||||
|
* quand on aura des animations adaptées.</p>
|
||||||
|
*/
|
||||||
|
public static void registerEntityPatches() {
|
||||||
|
CAPABILITIES.put(EntityType.PLAYER, (entityIn) -> ServerPlayerPatch::new);
|
||||||
|
|
||||||
|
TiedUpRigConstants.LOGGER.debug(
|
||||||
|
"EntityPatchProvider: registered {} entity types (common)", CAPABILITIES.size()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* À appeler en {@code FMLClientSetupEvent.event.enqueueWork(...)}.
|
||||||
|
*
|
||||||
|
* <p>Surcharge {@link EntityType#PLAYER} côté client pour dispatcher vers
|
||||||
|
* {@link LocalPlayerPatch} (joueur local) vs {@link ClientPlayerPatch}
|
||||||
|
* (remote players) vs {@link ServerPlayerPatch} (si logical server même JVM).</p>
|
||||||
|
*/
|
||||||
|
public static void registerEntityPatchesClient() {
|
||||||
|
CAPABILITIES.put(EntityType.PLAYER, (entityIn) -> {
|
||||||
|
if (entityIn instanceof net.minecraft.client.player.LocalPlayer) {
|
||||||
|
return LocalPlayerPatch::new;
|
||||||
|
} else if (entityIn instanceof net.minecraft.client.player.RemotePlayer) {
|
||||||
|
return ClientPlayerPatch<net.minecraft.client.player.RemotePlayer>::new;
|
||||||
|
} else if (entityIn instanceof net.minecraft.server.level.ServerPlayer) {
|
||||||
|
return ServerPlayerPatch::new;
|
||||||
|
}
|
||||||
|
return () -> null;
|
||||||
|
});
|
||||||
|
|
||||||
|
TiedUpRigConstants.LOGGER.debug(
|
||||||
|
"EntityPatchProvider: client-side player dispatch installed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enregistre un patch provider custom pour un {@link EntityType} donné.
|
||||||
|
* Utilisé pour extensions tierces (compat mods) ou datapack-driven patches.
|
||||||
|
*
|
||||||
|
* <p>Prioritaire sur {@link #CAPABILITIES} : un call custom override la
|
||||||
|
* version par défaut si elle existe.</p>
|
||||||
|
*/
|
||||||
|
public static void putCustomEntityPatch(EntityType<?> entityType, Function<Entity, Supplier<EntityPatch<?>>> provider) {
|
||||||
|
CUSTOM_CAPABILITIES.put(entityType, provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nettoie les custom patches (typiquement au datapack reload).
|
||||||
|
*/
|
||||||
|
public static void clearCustom() {
|
||||||
|
CUSTOM_CAPABILITIES.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- instance ----------
|
||||||
|
|
||||||
|
private EntityPatch<?> capability;
|
||||||
|
private final LazyOptional<EntityPatch<?>> optional = LazyOptional.of(this);
|
||||||
|
|
||||||
|
public EntityPatchProvider(Entity entity) {
|
||||||
|
Function<Entity, Supplier<EntityPatch<?>>> provider =
|
||||||
|
CUSTOM_CAPABILITIES.getOrDefault(entity.getType(), CAPABILITIES.get(entity.getType()));
|
||||||
|
|
||||||
|
if (provider != null) {
|
||||||
|
try {
|
||||||
|
this.capability = provider.apply(entity).get();
|
||||||
|
} catch (Exception e) {
|
||||||
|
TiedUpRigConstants.LOGGER.error(
|
||||||
|
"EntityPatchProvider: failed to construct patch for {}",
|
||||||
|
entity.getType(), e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true si ce provider a bien construit un patch pour l'entity
|
||||||
|
* (= entity est dans la whitelist PLAYER / future NPCs TiedUp).
|
||||||
|
*/
|
||||||
|
public boolean hasCapability() {
|
||||||
|
return this.capability != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityPatch<?> get() {
|
||||||
|
return this.capability;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> LazyOptional<T> getCapability(Capability<T> cap, Direction side) {
|
||||||
|
return cap == TiedUpCapabilities.CAPABILITY_ENTITY ? this.optional.cast() : LazyOptional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||||
|
* by the Epic Fight Team, licensed under GPLv3.
|
||||||
|
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.tiedup.remake.rig.patch;
|
||||||
|
|
||||||
|
import net.minecraft.client.player.LocalPlayer;
|
||||||
|
import net.minecraftforge.api.distmarker.Dist;
|
||||||
|
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub Phase 2.3 — patch spécifique au joueur local (self). Spécialisation de
|
||||||
|
* {@link ClientPlayerPatch} pour les hooks first-person / caméra / input
|
||||||
|
* capture bondage-specific (struggle keys, adjustment menu).
|
||||||
|
*
|
||||||
|
* <p>Version minimale : hérite de {@link ClientPlayerPatch} sans override.
|
||||||
|
* Phase 2.4 ajoutera les hooks first-person hide (arms/hands invisibles sous
|
||||||
|
* wrap/latex_sack), camera sync leash, etc.</p>
|
||||||
|
*
|
||||||
|
* <p>Forke conceptuellement {@code yesman.epicfight.client.world.capabilites.entitypatch.player.LocalPlayerPatch}
|
||||||
|
* (EF 572 LOC) mais réécrit from scratch (D-04) car skill UI state / input /
|
||||||
|
* camera cinematics non réutilisables.</p>
|
||||||
|
*/
|
||||||
|
@OnlyIn(Dist.CLIENT)
|
||||||
|
public class LocalPlayerPatch extends ClientPlayerPatch<LocalPlayer> {
|
||||||
|
|
||||||
|
// Hooks first-person / caméra / input → Phase 2.4
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||||
|
* by the Epic Fight Team, licensed under GPLv3.
|
||||||
|
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.tiedup.remake.rig.patch;
|
||||||
|
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
|
||||||
|
import com.tiedup.remake.rig.armature.Armature;
|
||||||
|
import com.tiedup.remake.rig.math.OpenMatrix4f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub Phase 2.3 — patch côté serveur pour les joueurs. Version minimale pour
|
||||||
|
* débloquer le dispatch via {@link EntityPatchProvider}. La version complète
|
||||||
|
* (kidnap state sync, struggle progression, ownership) est Phase 2.4+.
|
||||||
|
*
|
||||||
|
* <p>Garanties actuelles :</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code overrideRender() = false} — serveur ne rend rien, pas de besoin
|
||||||
|
* d'intercepter le render pipeline</li>
|
||||||
|
* <li>{@code getArmature() = null} — le serveur n'a pas besoin de mesh data,
|
||||||
|
* il joue des animations "blind" (calcule la pose pour syncer aux clients).
|
||||||
|
* Sera fixé en Phase 2.4 avec un fallback vers {@code TiedUpRigRegistry.BIPED}.</li>
|
||||||
|
* <li>{@code updateMotion} no-op — Phase 2.7 hook tick réel</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public class ServerPlayerPatch extends PlayerPatch<ServerPlayer> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateMotion(boolean considerInaction) {
|
||||||
|
// no-op stub — Phase 2.7
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Armature getArmature() {
|
||||||
|
// TODO Phase 2.4 — retourner TiedUpRigRegistry.BIPED.get()
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean overrideRender() {
|
||||||
|
// Serveur ne rend rien.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpenMatrix4f getModelMatrix(float partialTick) {
|
||||||
|
return getMatrix(partialTick);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||||
|
* by the Epic Fight Team, licensed under GPLv3.
|
||||||
|
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.tiedup.remake.rig.patch;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraftforge.common.capabilities.Capability;
|
||||||
|
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||||
|
import net.minecraftforge.common.capabilities.CapabilityToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holder du {@link Capability} central RIG, analogue à EF
|
||||||
|
* {@code yesman.epicfight.world.capabilities.EpicFightCapabilities} mais réduit
|
||||||
|
* à l'essentiel pour TiedUp :
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #CAPABILITY_ENTITY} — attaché à {@code Player} (Phase 2) et NPCs
|
||||||
|
* TiedUp (Phase 5). Porte l'{@link EntityPatch} qui contient l'Animator,
|
||||||
|
* l'Armature, l'état d'animation, etc.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>EF a également {@code CAPABILITY_ITEM}, {@code CAPABILITY_PROJECTILE},
|
||||||
|
* {@code CAPABILITY_SKILL} — tous combat-only, strippés pour TiedUp.</p>
|
||||||
|
*
|
||||||
|
* <p><b>Auto-registration</b> : {@link CapabilityToken} utilise une TypeToken
|
||||||
|
* pour enregistrer le capability via réflection. Pas besoin d'abonner un
|
||||||
|
* {@code RegisterCapabilitiesEvent} (comportement Forge 1.20.1, déjà utilisé
|
||||||
|
* pour {@code V2_BONDAGE_EQUIPMENT} dans TiedUp).</p>
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public class TiedUpCapabilities {
|
||||||
|
|
||||||
|
public static final Capability<EntityPatch> CAPABILITY_ENTITY =
|
||||||
|
CapabilityManager.get(new CapabilityToken<>() {});
|
||||||
|
|
||||||
|
private TiedUpCapabilities() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extrait l'{@link EntityPatch} d'un entity avec null-check + type-check.
|
||||||
|
*
|
||||||
|
* @return le patch cast au type demandé, ou null si entity null / pas de
|
||||||
|
* capability / type incompatible
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Nullable
|
||||||
|
public static <T extends EntityPatch> T getEntityPatch(@Nullable Entity entity, Class<T> type) {
|
||||||
|
if (entity == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
EntityPatch<?> patch = entity.getCapability(CAPABILITY_ENTITY).orElse(null);
|
||||||
|
if (patch != null && type.isAssignableFrom(patch.getClass())) {
|
||||||
|
return (T) patch;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper court pour extraire un {@link PlayerPatch} d'un {@link Player}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static PlayerPatch<?> getPlayerPatch(@Nullable Player player) {
|
||||||
|
if (player == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
EntityPatch<?> patch = player.getCapability(CAPABILITY_ENTITY).orElse(null);
|
||||||
|
return patch instanceof PlayerPatch<?> pp ? pp : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version {@link Optional} de {@link #getPlayerPatch(Player)} pour les
|
||||||
|
* call sites qui veulent un flow {@code .ifPresent(...)}.
|
||||||
|
*/
|
||||||
|
public static Optional<PlayerPatch<?>> getPlayerPatchAsOptional(@Nullable Entity entity) {
|
||||||
|
if (entity == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
EntityPatch<?> patch = entity.getCapability(CAPABILITY_ENTITY).orElse(null);
|
||||||
|
return patch instanceof PlayerPatch<?> pp ? Optional.of(pp) : Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
|
||||||
|
* by the Epic Fight Team, licensed under GPLv3.
|
||||||
|
* Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.tiedup.remake.rig.patch;
|
||||||
|
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraftforge.event.AttachCapabilitiesEvent;
|
||||||
|
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||||
|
import net.minecraftforge.fml.common.Mod;
|
||||||
|
|
||||||
|
import com.tiedup.remake.core.TiedUpMod;
|
||||||
|
import com.tiedup.remake.rig.TiedUpRigConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscriber Forge qui attache un {@link EntityPatchProvider} sur chaque
|
||||||
|
* {@link Entity} dont l'{@link net.minecraft.world.entity.EntityType} est
|
||||||
|
* enregistré dans {@link EntityPatchProvider#CAPABILITIES CAPABILITIES}
|
||||||
|
* (Phase 2 : PLAYER uniquement).
|
||||||
|
*
|
||||||
|
* <p>Pattern EF {@code yesman.epicfight.events.CapabilityEvents:36-59} mais
|
||||||
|
* simplifié : pas de dual-capability pour CAPABILITY_SKILL (combat strippé).</p>
|
||||||
|
*
|
||||||
|
* <p><b>Init paresseux</b> : l'{@code onConstructed} de {@link EntityPatch}
|
||||||
|
* ne fait PAS de cross-capability lookup (pas de
|
||||||
|
* {@code entity.getCapability(V2_BONDAGE_EQUIPMENT)}) pour éviter toute race
|
||||||
|
* avec l'ordre d'attachement inter-mods Forge. Voir adversarial review
|
||||||
|
* 2026-04-22 §2 pour le raisonnement.</p>
|
||||||
|
*/
|
||||||
|
@Mod.EventBusSubscriber(modid = TiedUpMod.MOD_ID)
|
||||||
|
public class TiedUpCapabilityEvents {
|
||||||
|
|
||||||
|
private static final ResourceLocation ENTITY_CAPABILITY_KEY =
|
||||||
|
ResourceLocation.fromNamespaceAndPath(TiedUpMod.MOD_ID, "rig_entity_cap");
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void attachEntityCapability(AttachCapabilitiesEvent<Entity> event) {
|
||||||
|
Entity entity = event.getObject();
|
||||||
|
|
||||||
|
// Skip si déjà attaché par un autre subscriber (ex. datapack override via
|
||||||
|
// CUSTOM_CAPABILITIES) — évite double-attach silencieux.
|
||||||
|
EntityPatch oldPatch = TiedUpCapabilities.getEntityPatch(entity, EntityPatch.class);
|
||||||
|
if (oldPatch != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityPatchProvider provider = new EntityPatchProvider(entity);
|
||||||
|
if (!provider.hasCapability()) {
|
||||||
|
return; // pas dans la whitelist (VILLAGER MCA, mobs non TiedUp, etc.)
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityPatch patch = provider.getCapability(
|
||||||
|
TiedUpCapabilities.CAPABILITY_ENTITY, null
|
||||||
|
).orElse(null);
|
||||||
|
|
||||||
|
if (patch == null) {
|
||||||
|
TiedUpRigConstants.LOGGER.warn(
|
||||||
|
"TiedUpCapabilityEvents: EntityPatchProvider reported hasCapability but CAPABILITY_ENTITY empty for {}",
|
||||||
|
entity.getType()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eager init (pattern EF) : construire l'Animator + Armature avant que
|
||||||
|
// l'entity commence son premier tick.
|
||||||
|
patch.onConstructed(entity);
|
||||||
|
event.addCapability(ENTITY_CAPABILITY_KEY, provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user