fix(P1+P2): swarm review — UX fixes + V1 dead code cleanup

P1 — UX visible:
- Add 4 missing translations (leather_mittens, ball_gag_3d, taser, debug_wand)
- Fix earplugs resistance bucket: "blindfold" → "earplug"
- Split mixin config: core (require=1) + compat MCA (require=0)
- Bump PROTOCOL_VERSION from "1" to "2"
- Add debug_wand item model (no texture — debug only)
- Fix 3 display_name inconsistencies (chain, vine_seed, shock_collar_auto)

P2 — Dead code cleanup:
- Delete IHas3DModelConfig + Model3DConfig (zero imports)
- Remove MovementStyleResolver.resolveV1Fallback() + V1Fallback record + V1 constants
- Remove AnimationTickHandler V1 fallback block + buildAnimationId()
- Document PlayerEquipment.equipInRegion() bypass as intentional (force-equip paths)
This commit is contained in:
NotEvil
2026-04-16 10:58:44 +02:00
parent d75b74f9f9
commit 371a138b71
16 changed files with 58 additions and 264 deletions

View File

@@ -7,16 +7,12 @@ import com.tiedup.remake.client.animation.PendingAnimationManager;
import com.tiedup.remake.client.animation.context.AnimationContext;
import com.tiedup.remake.client.animation.context.AnimationContextResolver;
import com.tiedup.remake.client.animation.context.RegionBoneMapper;
import com.tiedup.remake.client.animation.util.AnimationIdBuilder;
import com.tiedup.remake.client.events.CellHighlightHandler;
import com.tiedup.remake.client.events.LeashProxyClientHandler;
import com.tiedup.remake.client.gltf.GltfAnimationApplier;
import com.tiedup.remake.client.state.ClothesClientCache;
import com.tiedup.remake.client.state.MovementStyleClientState;
import com.tiedup.remake.client.state.PetBedClientState;
import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.util.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
@@ -207,28 +203,9 @@ public class AnimationTickHandler {
);
// Clear V1 tracking so transition back works
AnimationStateRegistry.getLastAnimId().remove(uuid);
} else {
// V1 fallback
if (GltfAnimationApplier.hasActiveState(player)) {
GltfAnimationApplier.clearV2Animation(player);
}
String animId = buildAnimationId(player, state);
String lastId = AnimationStateRegistry.getLastAnimId().get(
uuid
);
if (!animId.equals(lastId)) {
boolean success = BondageAnimationManager.playAnimation(
player,
animId
);
if (success) {
AnimationStateRegistry.getLastAnimId().put(
uuid,
animId
);
}
}
}
// No V2 items with GLB models — nothing to animate.
// V1 items without data-driven definitions are not animated.
} else if (wasTied) {
// Was tied, now free - stop all animations
if (GltfAnimationApplier.hasActiveState(player)) {
@@ -242,53 +219,6 @@ public class AnimationTickHandler {
AnimationStateRegistry.getLastTiedState().put(uuid, isTied);
}
/**
* Build animation ID from player's current state (V1 path).
*/
private static String buildAnimationId(
Player player,
PlayerBindState state
) {
ItemStack bind = state.getEquipment(BodyRegionV2.ARMS);
PoseType poseType = PoseTypeHelper.getPoseType(bind);
// Human chair mode: override DOG pose to HUMAN_CHAIR (straight limbs)
poseType = HumanChairHelper.resolveEffectivePose(poseType, bind);
// Derive bound state from V2 regions (works client-side, synced via capability)
boolean armsBound = V2EquipmentHelper.isRegionOccupied(
player,
BodyRegionV2.ARMS
);
boolean legsBound = V2EquipmentHelper.isRegionOccupied(
player,
BodyRegionV2.LEGS
);
// V1 fallback: if no V2 regions are set but player is tied, derive from bind mode NBT
if (!armsBound && !legsBound && BindModeHelper.isBindItem(bind)) {
armsBound = BindModeHelper.hasArmsBound(bind);
legsBound = BindModeHelper.hasLegsBound(bind);
}
boolean isStruggling = state.isStruggling();
boolean isSneaking = player.isCrouching();
boolean isMoving =
player.getDeltaMovement().horizontalDistanceSqr() > 1e-6;
// Build animation ID with sneak and movement support
return AnimationIdBuilder.build(
poseType,
armsBound,
legsBound,
null,
isStruggling,
true,
isSneaking,
isMoving
);
}
/**
* Player logout event - cleanup animation data.
*/

View File

@@ -1,13 +0,0 @@
package com.tiedup.remake.items.bondage3d;
/**
* Interface for items that have a 3D model configuration.
* Implement this to provide custom position, scale, and rotation for 3D rendering.
*/
public interface IHas3DModelConfig {
/**
* Get the 3D model configuration for rendering.
* @return The Model3DConfig with position, scale, and rotation offsets
*/
Model3DConfig getModelConfig();
}

View File

@@ -1,67 +0,0 @@
package com.tiedup.remake.items.bondage3d;
import java.util.Set;
/**
* Configuration immutable for a 3D item.
* Contains all parameters necessary for rendering.
*/
public record Model3DConfig(
String objPath, // "tiedup:models/obj/ball_gag/model.obj"
String texturePath, // "tiedup:models/obj/ball_gag/texture.png" (or null = use MTL)
float posOffsetX, // Horizontal offset
float posOffsetY, // Vertical offset (negative = lower)
float posOffsetZ, // Depth offset (positive = forward)
float scale, // Scale (1.0 = normal size)
float rotOffsetX, // Additional X rotation
float rotOffsetY, // Additional Y rotation
float rotOffsetZ, // Additional Z rotation
Set<String> tintMaterials // Material names to apply color tint (e.g., "Ball")
) {
/** Config without tinting */
public static Model3DConfig simple(String objPath, String texturePath) {
return new Model3DConfig(
objPath,
texturePath,
0,
0,
0,
1.0f,
0,
0,
0,
Set.of()
);
}
/** Config with position/rotation but no tinting */
public Model3DConfig(
String objPath,
String texturePath,
float posOffsetX,
float posOffsetY,
float posOffsetZ,
float scale,
float rotOffsetX,
float rotOffsetY,
float rotOffsetZ
) {
this(
objPath,
texturePath,
posOffsetX,
posOffsetY,
posOffsetZ,
scale,
rotOffsetX,
rotOffsetY,
rotOffsetZ,
Set.of()
);
}
/** Check if this item supports color tinting */
public boolean supportsTinting() {
return tintMaterials != null && !tintMaterials.isEmpty();
}
}

View File

@@ -93,7 +93,7 @@ import net.minecraftforge.network.simple.SimpleChannel;
*/
public class ModNetwork {
private static final String PROTOCOL_VERSION = "1";
private static final String PROTOCOL_VERSION = "2";
public static final SimpleChannel CHANNEL =
NetworkRegistry.newSimpleChannel(

View File

@@ -424,7 +424,14 @@ public class PlayerEquipment {
/**
* Low-level equip: writes to V2 capability, calls IBondageItem lifecycle hooks, syncs.
* Uses setInRegion directly because V1 items do not implement IV2BondageItem.
*
* <p>INTENTIONALLY bypasses V2EquipmentManager.tryEquip() because:
* <ul>
* <li>V1 items (IBondageItem) do not implement IV2BondageItem, so tryEquip() rejects them</li>
* <li>All callers (commands, NPC AI, NBT restore) are force-equip paths that should not
* be subject to V2 conflict resolution (SMELL-003 reviewed — bypass is by design)</li>
* </ul>
* <p>Basic safety (isRegionOccupied, canEquip) is still checked here.</p>
*/
private void equipInRegion(BodyRegionV2 region, ItemStack stack) {
Player player = host.getPlayer();

View File

@@ -1,14 +1,10 @@
package com.tiedup.remake.v2.bondage.movement;
import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.PoseTypeHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
import java.util.Map;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
/**
* Resolves the winning movement style from a player's equipped bondage items.
@@ -18,32 +14,15 @@ import org.jetbrains.annotations.Nullable;
*
* <p>The winning item's {@link MovementModifier} (if present) overrides the style's
* default speed/jump values. Modifiers from lower-severity items are ignored.</p>
*
* <h3>V1 Compatibility (H6 fix)</h3>
* <p>V1 items (ItemBind) stored in V2 capability
* do not have data-driven definitions. This resolver provides a fallback that
* maps V1 bind mode + pose type to a {@link MovementStyle} with speed values matching
* the original V1 behavior, preventing double stacking between the legacy
* {@code RestraintEffectUtils} attribute modifier and the V2 modifier.</p>
*/
public final class MovementStyleResolver {
private MovementStyleResolver() {}
// --- V1 fallback speed values ---
// V1 used ADDITION(-0.09) on base 0.10 = 0.01 effective = 10% speed
// Expressed as MULTIPLY_BASE fraction: 0.10
private static final float V1_STANDARD_SPEED = 0.10f;
// V1 used ADDITION(-0.10) on base 0.10 = 0.00 effective = 0% speed
// Expressed as MULTIPLY_BASE fraction: 0.0 (fully immobile)
private static final float V1_IMMOBILIZED_SPEED = 0.0f;
/**
* Resolve the winning movement style from all equipped items.
*
* <p>Checks V2 data-driven definitions first, then falls back to V1 {@link ItemBind}
* introspection for items without data-driven definitions.</p>
* <p>Uses V2 data-driven definitions to determine movement style.</p>
*
* @param equipped map of region to ItemStack (from {@code IV2BondageEquipment.getAllEquipped()})
* @return the resolved movement, or {@link ResolvedMovement#NONE} if no styled items
@@ -66,7 +45,6 @@ public final class MovementStyleResolver {
ItemStack stack = entry.getValue();
if (stack.isEmpty()) continue;
// --- Try V2 data-driven definition first ---
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
if (def != null && def.movementStyle() != null) {
MovementStyle style = def.movementStyle();
@@ -90,26 +68,6 @@ public final class MovementStyleResolver {
bestSeverity = severity;
bestRegionOrdinal = regionOrdinal;
}
continue;
}
// --- V1 fallback: ItemBind without data-driven definition ---
V1Fallback fallback = resolveV1Fallback(stack);
if (fallback != null) {
int severity = fallback.style.getSeverity();
int regionOrdinal = region.ordinal();
if (
severity > bestSeverity ||
(severity == bestSeverity &&
regionOrdinal < bestRegionOrdinal)
) {
bestStyle = fallback.style;
bestSpeed = fallback.speed;
bestJumpDisabled = fallback.jumpDisabled;
bestSeverity = severity;
bestRegionOrdinal = regionOrdinal;
}
}
}
@@ -119,56 +77,4 @@ public final class MovementStyleResolver {
return new ResolvedMovement(bestStyle, bestSpeed, bestJumpDisabled);
}
// ==================== V1 Fallback ====================
/**
* Attempt to derive a movement style from a V1 bind item.
*
* <p>Only items with legs bound produce a movement style. The mapping preserves
* the original V1 speed values:</p>
* <ul>
* <li>WRAP / LATEX_SACK: SHUFFLE at 0% speed (full immobilization), jump disabled</li>
* <li>DOG / HUMAN_CHAIR: CRAWL at V1 standard speed (10%), jump disabled</li>
* <li>STANDARD / STRAITJACKET: SHUFFLE at 10% speed, jump disabled</li>
* </ul>
*
* @param stack the ItemStack to inspect
* @return fallback resolution, or null if the item is not a V1 bind or legs are not bound
*/
@Nullable
private static V1Fallback resolveV1Fallback(ItemStack stack) {
if (!BindModeHelper.isBindItem(stack)) {
return null;
}
if (!BindModeHelper.hasLegsBound(stack)) {
return null;
}
PoseType poseType = PoseTypeHelper.getPoseType(stack);
return switch (poseType) {
case WRAP, LATEX_SACK -> new V1Fallback(
MovementStyle.SHUFFLE,
V1_IMMOBILIZED_SPEED,
true
);
case DOG, HUMAN_CHAIR -> new V1Fallback(
MovementStyle.CRAWL,
V1_STANDARD_SPEED,
true
);
default ->
// STANDARD, STRAITJACKET: shuffle at V1 standard speed
new V1Fallback(MovementStyle.SHUFFLE, V1_STANDARD_SPEED, true);
};
}
/** Internal holder for V1 fallback resolution result. */
private record V1Fallback(
MovementStyle style,
float speed,
boolean jumpDisabled
) {}
}

View File

@@ -78,6 +78,9 @@ description='''${mod_description}'''
[[mixins]]
config="tiedup.mixins.json"
[[mixins]]
config="tiedup-compat.mixins.json"
# Features are specific properties of the game environment, that you may want to declare you require. This example declares
# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't
# stop your mod loading on the server for example.

View File

@@ -963,5 +963,10 @@
"command.tiedup.debt.set": "Set %1$s's total debt to %2$s emeralds.",
"command.tiedup.debt.added": "Added %1$s emeralds to %2$s's debt. Remaining: %3$s",
"command.tiedup.debt.removed": "Removed %1$s emeralds from %2$s's debt. Remaining: %3$s",
"command.tiedup.debt.removed_paid": "Removed %1$s emeralds from %2$s's debt. Remaining: %3$s (PAID OFF!)"
"command.tiedup.debt.removed_paid": "Removed %1$s emeralds from %2$s's debt. Remaining: %3$s (PAID OFF!)",
"item.tiedup.leather_mittens": "Leather Mittens",
"item.tiedup.ball_gag_3d": "Ball Gag",
"item.tiedup.taser": "Taser",
"item.tiedup.debug_wand": "Debug Wand"
}

View File

@@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "tiedup:item/debug_wand"
}
}

View File

@@ -1,6 +1,6 @@
{
"type": "tiedup:bondage_item",
"display_name": "Chains",
"display_name": "Chain",
"translation_key": "item.tiedup.chain",
"model": "tiedup:models/gltf/v2/binds/chain.glb",
"regions": [

View File

@@ -17,7 +17,7 @@
"components": {
"lockable": {},
"resistance": {
"id": "blindfold"
"id": "earplug"
}
}
}

View File

@@ -11,7 +11,7 @@
"body"
]
},
"display_name": "Auto Shock Collar",
"display_name": "Automatic Shock Collar",
"translation_key": "item.tiedup.shock_collar_auto",
"model": "tiedup:models/gltf/v2/collars/shock_collar_auto.glb",
"components": {

View File

@@ -1,6 +1,6 @@
{
"type": "tiedup:bondage_item",
"display_name": "Vine Bind",
"display_name": "Vine Seed",
"translation_key": "item.tiedup.vine_seed",
"model": "tiedup:models/gltf/v2/binds/vine_seed.glb",
"regions": [

View File

@@ -0,0 +1,22 @@
{
"required": false,
"minVersion": "0.8",
"package": "com.tiedup.remake.mixin",
"compatibilityLevel": "JAVA_17",
"refmap": "tiedup.refmap.json",
"mixins": [
"MixinMCAVillagerInteraction",
"MixinMCAVillagerLeash",
"MixinMCAOpenAIChatAI",
"MixinMCAMessenger"
],
"client": [
"client/MixinVillagerEntityBaseModelMCA",
"client/MixinVillagerEntityMCAAnimated",
"client/MixinMCASpeechManager",
"client/MixinMCAPlayerExtendedModel"
],
"injectors": {
"defaultRequire": 0
}
}

View File

@@ -6,22 +6,14 @@
"refmap": "tiedup.refmap.json",
"mixins": [
"MixinServerPlayer",
"MixinMCAVillagerInteraction",
"MixinMCAVillagerLeash",
"MixinMCAOpenAIChatAI",
"MixinMCAMessenger",
"MixinLivingEntityBodyRot"
],
"client": [
"client/MixinVillagerEntityBaseModelMCA",
"client/MixinVillagerEntityMCAAnimated",
"client/MixinMCASpeechManager",
"client/MixinMCAPlayerExtendedModel",
"client/MixinPlayerModel",
"client/MixinCamera",
"client/MixinLivingEntitySleeping"
],
"injectors": {
"defaultRequire": 0
"defaultRequire": 1
}
}