Remove internal phase comments and format code

Strip all Phase references, TODO/FUTURE roadmap notes, and internal
planning comments from the codebase. Run Prettier for consistent
formatting across all Java files.
This commit is contained in:
NotEvil
2026-04-12 01:24:49 +02:00
parent 73d70e212d
commit a71093ba9c
482 changed files with 8500 additions and 5155 deletions

View File

@@ -1,7 +1,6 @@
package com.tiedup.remake.v2.bondage;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.Map;
import java.util.UUID;
import net.minecraft.core.BlockPos;
@@ -23,7 +22,6 @@ import org.jetbrains.annotations.Nullable;
*/
@AutoRegisterCapability
public interface IV2BondageEquipment {
/**
* Get the item equipped in the given region.
* @return The ItemStack in that region, or {@link ItemStack#EMPTY} — never null.
@@ -72,9 +70,7 @@ public interface IV2BondageEquipment {
*/
void deserializeNBT(CompoundTag tag);
// ========================================
// Pole leash persistence
// ========================================
/**
* Whether the player was leashed to a pole when they disconnected.
@@ -84,12 +80,14 @@ public interface IV2BondageEquipment {
/**
* Get the saved pole position, or null if none.
*/
@Nullable BlockPos getSavedPolePosition();
@Nullable
BlockPos getSavedPolePosition();
/**
* Get the saved pole dimension, or null if none.
*/
@Nullable ResourceKey<Level> getSavedPoleDimension();
@Nullable
ResourceKey<Level> getSavedPoleDimension();
/**
* Save the pole leash state for restoration on reconnect.
@@ -101,9 +99,7 @@ public interface IV2BondageEquipment {
*/
void clearSavedPoleLeash();
// ========================================
// Captor persistence
// ========================================
/**
* Whether the player had a saved captor when they disconnected.
@@ -113,7 +109,8 @@ public interface IV2BondageEquipment {
/**
* Get the saved captor UUID, or null if none.
*/
@Nullable UUID getSavedCaptorUUID();
@Nullable
UUID getSavedCaptorUUID();
/**
* Save the captor UUID for restoration on reconnect.
@@ -124,4 +121,4 @@ public interface IV2BondageEquipment {
* Clear saved captor state.
*/
void clearSavedCaptor();
}
}

View File

@@ -1,7 +1,6 @@
package com.tiedup.remake.v2.bondage;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.Set;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.LivingEntity;
@@ -16,7 +15,6 @@ import org.jetbrains.annotations.Nullable;
* and lifecycle hooks.
*/
public interface IV2BondageItem {
// ===== REGIONS =====
/**
@@ -164,4 +162,4 @@ public interface IV2BondageItem {
default boolean canUnequip(ItemStack stack, LivingEntity entity) {
return true;
}
}
}

View File

@@ -11,7 +11,6 @@ package com.tiedup.remake.v2.bondage;
* Players use Forge capabilities instead — they do NOT implement this.
*/
public interface IV2EquipmentHolder {
/**
* Get the V2 equipment storage for this entity.
* @return The equipment instance, never null.

View File

@@ -16,11 +16,13 @@ import net.minecraftforge.registries.RegistryObject;
public class V2BondageItems {
public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(
ForgeRegistries.ITEMS, TiedUpMod.MOD_ID
ForgeRegistries.ITEMS,
TiedUpMod.MOD_ID
);
public static final RegistryObject<Item> V2_HANDCUFFS = ITEMS.register(
"v2_handcuffs", V2Handcuffs::new
"v2_handcuffs",
V2Handcuffs::new
);
/**
@@ -28,7 +30,8 @@ public class V2BondageItems {
* behavior varies per-stack via the {@code tiedup_item_id} NBT tag.
*/
public static final RegistryObject<Item> DATA_DRIVEN_ITEM = ITEMS.register(
"data_driven_item", DataDrivenBondageItem::new
"data_driven_item",
DataDrivenBondageItem::new
);
/**
@@ -38,6 +41,7 @@ public class V2BondageItems {
* NBT tag on each stack.
*/
public static final RegistryObject<Item> FURNITURE_PLACER = ITEMS.register(
"furniture_placer", FurniturePlacerItem::new
"furniture_placer",
FurniturePlacerItem::new
);
}

View File

@@ -8,7 +8,6 @@ import net.minecraft.world.item.ItemStack;
* Carries displaced stacks for swap/supersede cases.
*/
public record V2EquipResult(Type type, List<ItemStack> displaced) {
public enum Type {
/** Item equipped successfully into empty regions. */
SUCCESS,
@@ -17,19 +16,29 @@ public record V2EquipResult(Type type, List<ItemStack> displaced) {
/** Global item superseded multiple sub-region items. */
SUPERSEDED,
/** Item could not be equipped due to unresolvable conflicts. */
BLOCKED
BLOCKED,
}
/** Convenience: check if equip was blocked. */
public boolean isBlocked() { return type == Type.BLOCKED; }
public boolean isBlocked() {
return type == Type.BLOCKED;
}
/** Convenience: check if equip succeeded (any non-blocked result). */
public boolean isSuccess() { return type != Type.BLOCKED; }
public boolean isSuccess() {
return type != Type.BLOCKED;
}
// ===== Factory methods =====
public static final V2EquipResult SUCCESS = new V2EquipResult(Type.SUCCESS, List.of());
public static final V2EquipResult BLOCKED = new V2EquipResult(Type.BLOCKED, List.of());
public static final V2EquipResult SUCCESS = new V2EquipResult(
Type.SUCCESS,
List.of()
);
public static final V2EquipResult BLOCKED = new V2EquipResult(
Type.BLOCKED,
List.of()
);
public static V2EquipResult swapped(ItemStack displaced) {
return new V2EquipResult(Type.SWAPPED, List.of(displaced));

View File

@@ -1,8 +1,7 @@
package com.tiedup.remake.v2.bondage;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
@@ -36,7 +35,11 @@ public final class V2EquipmentManager {
/**
* Check if an item can be equipped without any conflicts (stack-aware).
*/
public static boolean canEquip(IV2BondageEquipment equip, IV2BondageItem item, ItemStack newStack) {
public static boolean canEquip(
IV2BondageEquipment equip,
IV2BondageItem item,
ItemStack newStack
) {
return findAllConflicts(equip, item, newStack).isEmpty();
}
@@ -66,14 +69,24 @@ public final class V2EquipmentManager {
}
// 2. Existing items' getBlockedRegions() block new item's regions
for (Map.Entry<BodyRegionV2, ItemStack> entry : equip.getAllEquipped().entrySet()) {
for (Map.Entry<BodyRegionV2, ItemStack> entry : equip
.getAllEquipped()
.entrySet()) {
ItemStack equipped = entry.getValue();
if (seen.containsKey(equipped)) continue;
if (equipped.getItem() instanceof IV2BondageItem equippedItem) {
for (BodyRegionV2 newRegion : item.getOccupiedRegions(newStack)) {
if (equippedItem.getBlockedRegions(equipped).contains(newRegion)) {
for (BodyRegionV2 newRegion : item.getOccupiedRegions(
newStack
)) {
if (
equippedItem
.getBlockedRegions(equipped)
.contains(newRegion)
) {
seen.put(equipped, Boolean.TRUE);
conflicts.add(new ConflictEntry(entry.getKey(), equipped));
conflicts.add(
new ConflictEntry(entry.getKey(), equipped)
);
break; // One conflict per item is enough
}
}
@@ -116,7 +129,8 @@ public final class V2EquipmentManager {
}
// De-duplicate conflicts by stack identity
IdentityHashMap<ItemStack, ConflictEntry> uniqueConflicts = new IdentityHashMap<>();
IdentityHashMap<ItemStack, ConflictEntry> uniqueConflicts =
new IdentityHashMap<>();
for (ConflictEntry c : conflicts) {
uniqueConflicts.putIfAbsent(c.stack(), c);
}
@@ -125,7 +139,9 @@ public final class V2EquipmentManager {
if (uniqueConflicts.size() == 1) {
ConflictEntry conflict = uniqueConflicts.values().iterator().next();
ItemStack conflictStack = conflict.stack();
if (conflictStack.getItem() instanceof IV2BondageItem conflictItem) {
if (
conflictStack.getItem() instanceof IV2BondageItem conflictItem
) {
if (conflictItem.canUnequip(conflictStack, entity)) {
removeAllRegionsOf(equip, conflictStack);
conflictItem.onUnequipped(conflictStack, entity);
@@ -171,7 +187,10 @@ public final class V2EquipmentManager {
if (cs.getItem() instanceof IV2BondageItem ci) {
ci.onUnequipped(cs, entity);
} else {
TiedUpMod.LOGGER.warn("[V2] Supersede removed non-V2 item {} from equipment", cs);
TiedUpMod.LOGGER.warn(
"[V2] Supersede removed non-V2 item {} from equipment",
cs
);
}
displaced.add(cs);
}
@@ -199,7 +218,10 @@ public final class V2EquipmentManager {
* Remove an item from all regions by identity scan.
* Uses full BodyRegionV2.values() scan to prevent orphan stacks.
*/
public static void removeAllRegionsOf(IV2BondageEquipment equip, ItemStack stack) {
public static void removeAllRegionsOf(
IV2BondageEquipment equip,
ItemStack stack
) {
for (BodyRegionV2 region : BodyRegionV2.values()) {
//noinspection ObjectEquality — intentional identity comparison
if (equip.getInRegion(region) == stack) {
@@ -207,5 +229,4 @@ public final class V2EquipmentManager {
}
}
}
}
}

View File

@@ -42,11 +42,15 @@ public class V2BondageEquipment implements IV2BondageEquipment {
private final EnumMap<BodyRegionV2, ItemStack> regions;
// Pole leash persistence
@Nullable private BlockPos savedPolePosition;
@Nullable private ResourceKey<Level> savedPoleDimension;
@Nullable
private BlockPos savedPolePosition;
@Nullable
private ResourceKey<Level> savedPoleDimension;
// Captor persistence
@Nullable private UUID savedCaptorUUID;
@Nullable
private UUID savedCaptorUUID;
public V2BondageEquipment() {
this.regions = new EnumMap<>(BodyRegionV2.class);
@@ -104,11 +108,16 @@ public class V2BondageEquipment implements IV2BondageEquipment {
public boolean isRegionBlocked(BodyRegionV2 region) {
if (region == null) return false;
// Check if any equipped item's getBlockedRegions() includes this region
for (Map.Entry<BodyRegionV2, ItemStack> entry : getAllEquipped().entrySet()) {
for (Map.Entry<
BodyRegionV2,
ItemStack
> entry : getAllEquipped().entrySet()) {
ItemStack stack = entry.getValue();
if (stack.getItem() instanceof IV2BondageItem item) {
if (item.getBlockedRegions(stack).contains(region)
&& !item.getOccupiedRegions(stack).contains(region)) {
if (
item.getBlockedRegions(stack).contains(region) &&
!item.getOccupiedRegions(stack).contains(region)
) {
// Blocked by another item (not self-blocking via occupation)
return true;
}
@@ -137,9 +146,7 @@ public class V2BondageEquipment implements IV2BondageEquipment {
}
}
// ========================================
// Pole leash persistence
// ========================================
@Override
public boolean wasLeashedToPole() {
@@ -170,9 +177,7 @@ public class V2BondageEquipment implements IV2BondageEquipment {
this.savedPoleDimension = null;
}
// ========================================
// Captor persistence
// ========================================
@Override
public boolean hasSavedCaptor() {
@@ -195,9 +200,7 @@ public class V2BondageEquipment implements IV2BondageEquipment {
this.savedCaptorUUID = null;
}
// ========================================
// NBT serialization
// ========================================
@Override
public CompoundTag serializeNBT() {
@@ -238,7 +241,10 @@ public class V2BondageEquipment implements IV2BondageEquipment {
// Pole leash persistence
if (savedPolePosition != null && savedPoleDimension != null) {
root.putLong("pole_position", savedPolePosition.asLong());
root.putString("pole_dimension", savedPoleDimension.location().toString());
root.putString(
"pole_dimension",
savedPoleDimension.location().toString()
);
}
// Captor persistence
@@ -279,7 +285,8 @@ public class V2BondageEquipment implements IV2BondageEquipment {
if (!key.endsWith(NBT_ALSO_SUFFIX)) continue;
String primaryRegionName = key.substring(
0, key.length() - NBT_ALSO_SUFFIX.length()
0,
key.length() - NBT_ALSO_SUFFIX.length()
);
ItemStack primaryStack = loadedStacks.get(primaryRegionName);
if (primaryStack == null) continue;
@@ -298,11 +305,15 @@ public class V2BondageEquipment implements IV2BondageEquipment {
if (tag.contains("pole_position") && tag.contains("pole_dimension")) {
try {
savedPolePosition = BlockPos.of(tag.getLong("pole_position"));
savedPoleDimension = ResourceKey.create(Registries.DIMENSION,
new ResourceLocation(tag.getString("pole_dimension")));
savedPoleDimension = ResourceKey.create(
Registries.DIMENSION,
new ResourceLocation(tag.getString("pole_dimension"))
);
} catch (net.minecraft.ResourceLocationException e) {
com.tiedup.remake.core.TiedUpMod.LOGGER.warn(
"Invalid pole dimension in NBT, clearing saved pole data: {}", e.getMessage());
"Invalid pole dimension in NBT, clearing saved pole data: {}",
e.getMessage()
);
savedPolePosition = null;
savedPoleDimension = null;
}
@@ -318,4 +329,4 @@ public class V2BondageEquipment implements IV2BondageEquipment {
savedCaptorUUID = null;
}
}
}
}

View File

@@ -59,4 +59,4 @@ public class V2BondageEquipmentProvider
public void invalidate() {
optional.invalidate();
}
}
}

View File

@@ -5,9 +5,9 @@ import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
import com.tiedup.remake.v2.bondage.IV2BondageItem;
import com.tiedup.remake.v2.bondage.IV2EquipmentHolder;
import com.tiedup.remake.v2.bondage.V2EquipResult;
import com.tiedup.remake.v2.bondage.V2EquipmentManager;
import com.tiedup.remake.v2.bondage.IV2EquipmentHolder;
import com.tiedup.remake.v2.bondage.network.PacketSyncV2Equipment;
import java.util.Map;
import net.minecraft.server.level.ServerPlayer;
@@ -24,7 +24,6 @@ import org.jetbrains.annotations.Nullable;
* is the authority for equipment state.
*
* Currently dispatches only for Players (via Forge capability).
* Phase 6 will add Damsel/ArmorStand support.
*/
public final class V2EquipmentHelper {
@@ -40,7 +39,8 @@ public final class V2EquipmentHelper {
public static IV2BondageEquipment getEquipment(LivingEntity entity) {
if (entity == null) return null;
if (entity instanceof Player player) {
return player.getCapability(V2BondageEquipmentProvider.V2_BONDAGE_EQUIPMENT)
return player
.getCapability(V2BondageEquipmentProvider.V2_BONDAGE_EQUIPMENT)
.orElse(null);
}
// V2 equipment holders (EntityDamsel, etc.)
@@ -54,7 +54,10 @@ public final class V2EquipmentHelper {
* Get the item in a specific region for an entity.
* @return The ItemStack, or {@link ItemStack#EMPTY} if empty or entity unsupported.
*/
public static ItemStack getInRegion(LivingEntity entity, BodyRegionV2 region) {
public static ItemStack getInRegion(
LivingEntity entity,
BodyRegionV2 region
) {
IV2BondageEquipment equip = getEquipment(entity);
if (equip == null) return ItemStack.EMPTY;
return equip.getInRegion(region);
@@ -63,7 +66,10 @@ public final class V2EquipmentHelper {
/**
* Check if a region is directly occupied on the given entity.
*/
public static boolean isRegionOccupied(LivingEntity entity, BodyRegionV2 region) {
public static boolean isRegionOccupied(
LivingEntity entity,
BodyRegionV2 region
) {
IV2BondageEquipment equip = getEquipment(entity);
if (equip == null) return false;
return equip.isRegionOccupied(region);
@@ -72,7 +78,10 @@ public final class V2EquipmentHelper {
/**
* Check if a region is blocked by any equipped item's {@link IV2BondageItem#getBlockedRegions()}.
*/
public static boolean isRegionBlocked(LivingEntity entity, BodyRegionV2 region) {
public static boolean isRegionBlocked(
LivingEntity entity,
BodyRegionV2 region
) {
IV2BondageEquipment equip = getEquipment(entity);
if (equip == null) return false;
return equip.isRegionBlocked(region);
@@ -82,7 +91,9 @@ public final class V2EquipmentHelper {
* Get all equipped items (de-duplicated) for an entity.
* @return Unmodifiable map, or empty map if entity unsupported.
*/
public static Map<BodyRegionV2, ItemStack> getAllEquipped(LivingEntity entity) {
public static Map<BodyRegionV2, ItemStack> getAllEquipped(
LivingEntity entity
) {
IV2BondageEquipment equip = getEquipment(entity);
if (equip == null) return Map.of();
return equip.getAllEquipped();
@@ -110,10 +121,15 @@ public final class V2EquipmentHelper {
* @param stack The ItemStack to equip (must implement IV2BondageItem)
* @return The equip result, or {@link V2EquipResult#BLOCKED} if invalid
*/
public static V2EquipResult equipItem(LivingEntity entity, ItemStack stack) {
public static V2EquipResult equipItem(
LivingEntity entity,
ItemStack stack
) {
if (entity.level().isClientSide) return V2EquipResult.BLOCKED;
if (stack == null || stack.isEmpty()) return V2EquipResult.BLOCKED;
if (!(stack.getItem() instanceof IV2BondageItem item)) return V2EquipResult.BLOCKED;
if (
!(stack.getItem() instanceof IV2BondageItem item)
) return V2EquipResult.BLOCKED;
IV2BondageEquipment equip = getEquipment(entity);
if (equip == null) return V2EquipResult.BLOCKED;
@@ -122,7 +138,12 @@ public final class V2EquipmentHelper {
// Copy the stack so the original isn't mutated
ItemStack equipCopy = stack.copy();
V2EquipResult result = V2EquipmentManager.tryEquip(equip, item, equipCopy, entity);
V2EquipResult result = V2EquipmentManager.tryEquip(
equip,
item,
equipCopy,
entity
);
if (result.isSuccess()) {
item.onEquipped(equipCopy, entity);
@@ -139,7 +160,10 @@ public final class V2EquipmentHelper {
* @param region The region to unequip from
* @return The removed ItemStack, or {@link ItemStack#EMPTY} if nothing removed
*/
public static ItemStack unequipFromRegion(LivingEntity entity, BodyRegionV2 region) {
public static ItemStack unequipFromRegion(
LivingEntity entity,
BodyRegionV2 region
) {
return unequipFromRegion(entity, region, false);
}
@@ -225,13 +249,13 @@ public final class V2EquipmentHelper {
}
PacketSyncV2Equipment packet = new PacketSyncV2Equipment(
entity.getId(), equip.serializeNBT()
entity.getId(),
equip.serializeNBT()
);
if (entity instanceof ServerPlayer serverPlayer) {
ModNetwork.sendToAllTrackingAndSelf(packet, serverPlayer);
} else {
// Phase 6: NPC support — send to all tracking the entity
ModNetwork.sendToAllTrackingEntity(packet, entity);
}
}
@@ -250,8 +274,9 @@ public final class V2EquipmentHelper {
if (equip == null) return;
PacketSyncV2Equipment packet = new PacketSyncV2Equipment(
entity.getId(), equip.serializeNBT()
entity.getId(),
equip.serializeNBT()
);
ModNetwork.sendToPlayer(packet, target);
}
}
}

View File

@@ -17,9 +17,9 @@ import com.tiedup.remake.v2.furniture.SeatDefinition;
import java.util.Map;
import java.util.Set;
import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
@@ -51,8 +51,10 @@ import org.joml.Matrix4f;
* ALL entities (no local-player-only guard).
*/
@OnlyIn(Dist.CLIENT)
public class V2BondageRenderLayer<T extends LivingEntity, M extends HumanoidModel<T>>
extends RenderLayer<T, M> {
public class V2BondageRenderLayer<
T extends LivingEntity,
M extends HumanoidModel<T>
> extends RenderLayer<T, M> {
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");
@@ -84,9 +86,9 @@ public class V2BondageRenderLayer<T extends LivingEntity, M extends HumanoidMode
// Get V2 equipment via capability (Players) or IV2EquipmentHolder (Damsels)
IV2BondageEquipment equipment = null;
if (entity instanceof Player player) {
equipment = player.getCapability(
V2BondageEquipmentProvider.V2_BONDAGE_EQUIPMENT
).orElse(null);
equipment = player
.getCapability(V2BondageEquipmentProvider.V2_BONDAGE_EQUIPMENT)
.orElse(null);
} else if (entity instanceof IV2EquipmentHolder holder) {
equipment = holder.getV2Equipment();
}
@@ -102,7 +104,10 @@ public class V2BondageRenderLayer<T extends LivingEntity, M extends HumanoidMode
// Skip rendering items in regions blocked by furniture/seat provider
Set<BodyRegionV2> furnitureBlocked = Set.of();
if (entity.isPassenger() && entity.getVehicle() instanceof ISeatProvider provider) {
if (
entity.isPassenger() &&
entity.getVehicle() instanceof ISeatProvider provider
) {
SeatDefinition seat = provider.getSeatForPassenger(entity);
if (seat != null) {
furnitureBlocked = seat.blockedRegions();
@@ -133,7 +138,8 @@ public class V2BondageRenderLayer<T extends LivingEntity, M extends HumanoidMode
} else {
isSlim = false;
}
ResourceLocation modelLocation = (isSlim && bondageItem.supportsSlimModel(stack))
ResourceLocation modelLocation = (isSlim &&
bondageItem.supportsSlimModel(stack))
? bondageItem.getSlimModelLocation(stack)
: null;
if (modelLocation == null) {
@@ -146,16 +152,22 @@ public class V2BondageRenderLayer<T extends LivingEntity, M extends HumanoidMode
// Load GLB data from cache
GltfData data = GltfCache.get(modelLocation);
if (data == null) {
LOGGER.debug("[V2Render] Failed to load GLB for item {}: {}",
stack.getItem(), modelLocation);
LOGGER.debug(
"[V2Render] Failed to load GLB for item {}: {}",
stack.getItem(),
modelLocation
);
continue;
}
// Compute joint matrices for this specific GLB model
// Each GLB has its own skeleton, so matrices are per-item
Matrix4f[] joints = GltfLiveBoneReader.computeJointMatricesFromModel(
parentModel, data, entity
);
Matrix4f[] joints =
GltfLiveBoneReader.computeJointMatricesFromModel(
parentModel,
data,
entity
);
if (joints == null) {
// Fallback to GLB-internal skinning
joints = GltfSkinningEngine.computeJointMatrices(data);
@@ -169,20 +181,31 @@ public class V2BondageRenderLayer<T extends LivingEntity, M extends HumanoidMode
Map<String, Integer> tintColors = TintColorResolver.resolve(stack);
if (!tintColors.isEmpty() && data.primitives().size() > 1) {
// Multi-primitive mesh with tint data: render per-primitive with colors
RenderType renderType = GltfMeshRenderer.getRenderTypeForDefaultTexture();
RenderType renderType =
GltfMeshRenderer.getRenderTypeForDefaultTexture();
GltfMeshRenderer.renderSkinnedTinted(
data, joints, poseStack, buffer,
packedLight, packedOverlay, renderType, tintColors
data,
joints,
poseStack,
buffer,
packedLight,
packedOverlay,
renderType,
tintColors
);
} else {
// Standard path: single primitive or no tint data
GltfMeshRenderer.renderSkinned(
data, joints, poseStack, buffer,
packedLight, packedOverlay
data,
joints,
poseStack,
buffer,
packedLight,
packedOverlay
);
}
poseStack.popPose();
}
}
}
}

View File

@@ -137,12 +137,18 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
IV2BondageEquipment equip = V2EquipmentHelper.getEquipment(entity);
if (equip != null) {
int maxDifficulty = -1;
for (Map.Entry<BodyRegionV2, ItemStack> entry : equip.getAllEquipped().entrySet()) {
for (Map.Entry<BodyRegionV2, ItemStack> entry : equip
.getAllEquipped()
.entrySet()) {
ItemStack stack = entry.getValue();
if (stack.getItem() == this) {
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
DataDrivenItemDefinition def =
DataDrivenItemRegistry.get(stack);
if (def != null) {
maxDifficulty = Math.max(maxDifficulty, def.escapeDifficulty());
maxDifficulty = Math.max(
maxDifficulty,
def.escapeDifficulty()
);
}
}
}
@@ -157,12 +163,18 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
@Override
public void notifyStruggle(LivingEntity entity) {
// Play a generic chain sound for data-driven items
entity.level().playSound(
null, entity.getX(), entity.getY(), entity.getZ(),
net.minecraft.sounds.SoundEvents.CHAIN_STEP,
net.minecraft.sounds.SoundSource.PLAYERS,
0.4f, 1.0f
);
entity
.level()
.playSound(
null,
entity.getX(),
entity.getY(),
entity.getZ(),
net.minecraft.sounds.SoundEvents.CHAIN_STEP,
net.minecraft.sounds.SoundSource.PLAYERS,
0.4f,
1.0f
);
}
// ===== DISPLAY NAME =====
@@ -189,7 +201,9 @@ public class DataDrivenBondageItem extends AbstractV2BondageItem {
public static ItemStack createStack(ResourceLocation itemId) {
if (V2BondageItems.DATA_DRIVEN_ITEM == null) return ItemStack.EMPTY;
ItemStack stack = new ItemStack(V2BondageItems.DATA_DRIVEN_ITEM.get());
stack.getOrCreateTag().putString(DataDrivenItemRegistry.NBT_ITEM_ID, itemId.toString());
stack
.getOrCreateTag()
.putString(DataDrivenItemRegistry.NBT_ITEM_ID, itemId.toString());
return stack;
}
}

View File

@@ -78,7 +78,8 @@ public record DataDrivenItemDefinition(
* Optional per-item overrides for the movement style's default values.
* Requires {@code movementStyle} to be non-null (ignored otherwise).
*/
@Nullable com.tiedup.remake.v2.bondage.movement.MovementModifier movementModifier,
@Nullable
com.tiedup.remake.v2.bondage.movement.MovementModifier movementModifier,
/**
* Per-animation bone whitelist. Maps animation name (e.g. "idle", "struggle")

View File

@@ -52,7 +52,9 @@ import org.jetbrains.annotations.Nullable;
*/
public final class DataDrivenItemParser {
private static final Logger LOGGER = LogManager.getLogger("DataDrivenItems");
private static final Logger LOGGER = LogManager.getLogger(
"DataDrivenItems"
);
private DataDrivenItemParser() {}
@@ -64,7 +66,10 @@ public final class DataDrivenItemParser {
* @return the parsed definition, or null if the file is invalid
*/
@Nullable
public static DataDrivenItemDefinition parse(InputStream input, ResourceLocation fileId) {
public static DataDrivenItemDefinition parse(
InputStream input,
ResourceLocation fileId
) {
try {
JsonObject root = JsonParser.parseReader(
new InputStreamReader(input, StandardCharsets.UTF_8)
@@ -72,7 +77,11 @@ public final class DataDrivenItemParser {
return parseObject(root, fileId);
} catch (Exception e) {
LOGGER.error("[DataDrivenItems] Failed to parse JSON {}: {}", fileId, e.getMessage());
LOGGER.error(
"[DataDrivenItems] Failed to parse JSON {}: {}",
fileId,
e.getMessage()
);
return null;
}
}
@@ -85,19 +94,28 @@ public final class DataDrivenItemParser {
* @return the parsed definition, or null if validation fails
*/
@Nullable
public static DataDrivenItemDefinition parseObject(JsonObject root, ResourceLocation fileId) {
public static DataDrivenItemDefinition parseObject(
JsonObject root,
ResourceLocation fileId
) {
// Validate type field
String type = getStringOrNull(root, "type");
if (!"tiedup:bondage_item".equals(type)) {
LOGGER.error("[DataDrivenItems] Skipping {}: invalid or missing type '{}' (expected 'tiedup:bondage_item')",
fileId, type);
LOGGER.error(
"[DataDrivenItems] Skipping {}: invalid or missing type '{}' (expected 'tiedup:bondage_item')",
fileId,
type
);
return null;
}
// Required: display_name
String displayName = getStringOrNull(root, "display_name");
if (displayName == null || displayName.isEmpty()) {
LOGGER.error("[DataDrivenItems] Skipping {}: missing 'display_name'", fileId);
LOGGER.error(
"[DataDrivenItems] Skipping {}: missing 'display_name'",
fileId
);
return null;
}
@@ -107,33 +125,63 @@ public final class DataDrivenItemParser {
// Required: model
String modelStr = getStringOrNull(root, "model");
if (modelStr == null || modelStr.isEmpty()) {
LOGGER.error("[DataDrivenItems] Skipping {}: missing 'model'", fileId);
LOGGER.error(
"[DataDrivenItems] Skipping {}: missing 'model'",
fileId
);
return null;
}
ResourceLocation modelLocation = ResourceLocation.tryParse(modelStr);
if (modelLocation == null) {
LOGGER.error("[DataDrivenItems] Skipping {}: invalid model ResourceLocation '{}'", fileId, modelStr);
LOGGER.error(
"[DataDrivenItems] Skipping {}: invalid model ResourceLocation '{}'",
fileId,
modelStr
);
return null;
}
// Optional: slim_model
ResourceLocation slimModelLocation = parseOptionalResourceLocation(root, "slim_model", fileId);
ResourceLocation slimModelLocation = parseOptionalResourceLocation(
root,
"slim_model",
fileId
);
// Optional: texture
ResourceLocation texturePath = parseOptionalResourceLocation(root, "texture", fileId);
ResourceLocation texturePath = parseOptionalResourceLocation(
root,
"texture",
fileId
);
// Optional: animation_source
ResourceLocation animationSource = parseOptionalResourceLocation(root, "animation_source", fileId);
ResourceLocation animationSource = parseOptionalResourceLocation(
root,
"animation_source",
fileId
);
// Required: regions (non-empty)
Set<BodyRegionV2> occupiedRegions = parseRegions(root, "regions", fileId);
Set<BodyRegionV2> occupiedRegions = parseRegions(
root,
"regions",
fileId
);
if (occupiedRegions == null || occupiedRegions.isEmpty()) {
LOGGER.error("[DataDrivenItems] Skipping {}: missing or empty 'regions'", fileId);
LOGGER.error(
"[DataDrivenItems] Skipping {}: missing or empty 'regions'",
fileId
);
return null;
}
// Optional: blocked_regions (defaults to regions)
Set<BodyRegionV2> blockedRegions = parseRegions(root, "blocked_regions", fileId);
Set<BodyRegionV2> blockedRegions = parseRegions(
root,
"blocked_regions",
fileId
);
if (blockedRegions == null || blockedRegions.isEmpty()) {
blockedRegions = occupiedRegions;
}
@@ -148,13 +196,25 @@ public final class DataDrivenItemParser {
boolean lockable = getBooleanOrDefault(root, "lockable", true);
// Optional: supports_color (default false)
boolean supportsColor = getBooleanOrDefault(root, "supports_color", false);
boolean supportsColor = getBooleanOrDefault(
root,
"supports_color",
false
);
// Optional: tint_channels (default empty)
Map<String, Integer> tintChannels = parseTintChannels(root, "tint_channels", fileId);
Map<String, Integer> tintChannels = parseTintChannels(
root,
"tint_channels",
fileId
);
// Optional: icon (item model ResourceLocation for inventory sprite)
ResourceLocation icon = parseOptionalResourceLocation(root, "icon", fileId);
ResourceLocation icon = parseOptionalResourceLocation(
root,
"icon",
fileId
);
// Optional: movement_style (requires valid MovementStyle name)
MovementStyle movementStyle = null;
@@ -162,14 +222,21 @@ public final class DataDrivenItemParser {
if (movementStyleStr != null && !movementStyleStr.isEmpty()) {
movementStyle = MovementStyle.fromName(movementStyleStr);
if (movementStyle == null) {
LOGGER.warn("[DataDrivenItems] In {}: unknown movement_style '{}', ignoring",
fileId, movementStyleStr);
LOGGER.warn(
"[DataDrivenItems] In {}: unknown movement_style '{}', ignoring",
fileId,
movementStyleStr
);
}
}
// Optional: movement_modifier (requires movement_style to be set)
MovementModifier movementModifier = null;
if (movementStyle != null && root.has("movement_modifier") && root.get("movement_modifier").isJsonObject()) {
if (
movementStyle != null &&
root.has("movement_modifier") &&
root.get("movement_modifier").isJsonObject()
) {
JsonObject modObj = root.getAsJsonObject("movement_modifier");
Float speedMul = getFloatOrNull(modObj, "speed_multiplier");
Boolean jumpDis = getBooleanOrNull(modObj, "jump_disabled");
@@ -177,14 +244,22 @@ public final class DataDrivenItemParser {
movementModifier = new MovementModifier(speedMul, jumpDis);
}
} else if (movementStyle == null && root.has("movement_modifier")) {
LOGGER.warn("[DataDrivenItems] In {}: movement_modifier ignored because movement_style is absent",
fileId);
LOGGER.warn(
"[DataDrivenItems] In {}: movement_modifier ignored because movement_style is absent",
fileId
);
}
// Required: animation_bones (per-animation bone whitelist)
Map<String, Set<String>> animationBones = parseAnimationBones(root, fileId);
Map<String, Set<String>> animationBones = parseAnimationBones(
root,
fileId
);
if (animationBones == null) {
LOGGER.error("[DataDrivenItems] Skipping {}: missing or invalid 'animation_bones'", fileId);
LOGGER.error(
"[DataDrivenItems] Skipping {}: missing or invalid 'animation_bones'",
fileId
);
return null;
}
@@ -200,14 +275,30 @@ public final class DataDrivenItemParser {
if (idPath.endsWith(".json")) {
idPath = idPath.substring(0, idPath.length() - 5);
}
ResourceLocation id = new ResourceLocation(fileId.getNamespace(), idPath);
ResourceLocation id = new ResourceLocation(
fileId.getNamespace(),
idPath
);
return new DataDrivenItemDefinition(
id, displayName, translationKey, modelLocation, slimModelLocation,
texturePath, animationSource, occupiedRegions, blockedRegions,
posePriority, escapeDifficulty,
lockable, supportsColor, tintChannels, icon,
movementStyle, movementModifier, animationBones
id,
displayName,
translationKey,
modelLocation,
slimModelLocation,
texturePath,
animationSource,
occupiedRegions,
blockedRegions,
posePriority,
escapeDifficulty,
lockable,
supportsColor,
tintChannels,
icon,
movementStyle,
movementModifier,
animationBones
);
}
@@ -223,7 +314,11 @@ public final class DataDrivenItemParser {
}
}
private static int getIntOrDefault(JsonObject obj, String key, int defaultValue) {
private static int getIntOrDefault(
JsonObject obj,
String key,
int defaultValue
) {
if (!obj.has(key) || obj.get(key).isJsonNull()) return defaultValue;
try {
return obj.get(key).getAsInt();
@@ -232,7 +327,11 @@ public final class DataDrivenItemParser {
}
}
private static boolean getBooleanOrDefault(JsonObject obj, String key, boolean defaultValue) {
private static boolean getBooleanOrDefault(
JsonObject obj,
String key,
boolean defaultValue
) {
if (!obj.has(key) || obj.get(key).isJsonNull()) return defaultValue;
try {
return obj.get(key).getAsBoolean();
@@ -263,13 +362,20 @@ public final class DataDrivenItemParser {
@Nullable
private static ResourceLocation parseOptionalResourceLocation(
JsonObject obj, String key, ResourceLocation fileId
JsonObject obj,
String key,
ResourceLocation fileId
) {
String value = getStringOrNull(obj, key);
if (value == null || value.isEmpty()) return null;
ResourceLocation loc = ResourceLocation.tryParse(value);
if (loc == null) {
LOGGER.warn("[DataDrivenItems] In {}: invalid ResourceLocation for '{}': '{}'", fileId, key, value);
LOGGER.warn(
"[DataDrivenItems] In {}: invalid ResourceLocation for '{}': '{}'",
fileId,
key,
value
);
}
return loc;
}
@@ -279,7 +385,11 @@ public final class DataDrivenItemParser {
* Unknown region names are logged as warnings and skipped.
*/
@Nullable
private static Set<BodyRegionV2> parseRegions(JsonObject obj, String key, ResourceLocation fileId) {
private static Set<BodyRegionV2> parseRegions(
JsonObject obj,
String key,
ResourceLocation fileId
) {
if (!obj.has(key) || !obj.get(key).isJsonArray()) return null;
JsonArray arr = obj.getAsJsonArray(key);
@@ -293,12 +403,20 @@ public final class DataDrivenItemParser {
if (region != null) {
regions.add(region);
} else {
LOGGER.warn("[DataDrivenItems] In {}: unknown region '{}' in '{}', skipping",
fileId, name, key);
LOGGER.warn(
"[DataDrivenItems] In {}: unknown region '{}' in '{}', skipping",
fileId,
name,
key
);
}
} catch (Exception e) {
LOGGER.warn("[DataDrivenItems] In {}: invalid element in '{}': {}",
fileId, key, e.getMessage());
LOGGER.warn(
"[DataDrivenItems] In {}: invalid element in '{}': {}",
fileId,
key,
e.getMessage()
);
}
}
@@ -321,18 +439,29 @@ public final class DataDrivenItemParser {
* @param fileId the source file for error messages
* @return an unmodifiable map of channel names to RGB ints, or empty map if absent
*/
private static Map<String, Integer> parseTintChannels(JsonObject obj, String key, ResourceLocation fileId) {
private static Map<String, Integer> parseTintChannels(
JsonObject obj,
String key,
ResourceLocation fileId
) {
if (!obj.has(key) || !obj.get(key).isJsonObject()) return Map.of();
JsonObject channels = obj.getAsJsonObject(key);
Map<String, Integer> result = new LinkedHashMap<>();
for (Map.Entry<String, JsonElement> entry : channels.entrySet()) {
try {
String hex = entry.getValue().getAsString();
int color = Integer.parseInt(hex.startsWith("#") ? hex.substring(1) : hex, 16);
int color = Integer.parseInt(
hex.startsWith("#") ? hex.substring(1) : hex,
16
);
result.put(entry.getKey(), color);
} catch (NumberFormatException e) {
LOGGER.warn("[DataDrivenItems] In {}: invalid hex color '{}' for tint channel '{}'",
fileId, entry.getValue(), entry.getKey());
LOGGER.warn(
"[DataDrivenItems] In {}: invalid hex color '{}' for tint channel '{}'",
fileId,
entry.getValue(),
entry.getKey()
);
}
}
return Collections.unmodifiableMap(result);
@@ -340,7 +469,12 @@ public final class DataDrivenItemParser {
/** Valid PlayerAnimator bone names for animation_bones validation. */
private static final Set<String> VALID_BONE_NAMES = Set.of(
"head", "body", "rightArm", "leftArm", "rightLeg", "leftLeg"
"head",
"body",
"rightArm",
"leftArm",
"rightLeg",
"leftLeg"
);
/**
@@ -364,14 +498,23 @@ public final class DataDrivenItemParser {
* @return unmodifiable map of animation name to bone set, or null if absent/invalid
*/
@Nullable
private static Map<String, Set<String>> parseAnimationBones(JsonObject obj, ResourceLocation fileId) {
if (!obj.has("animation_bones") || !obj.get("animation_bones").isJsonObject()) {
private static Map<String, Set<String>> parseAnimationBones(
JsonObject obj,
ResourceLocation fileId
) {
if (
!obj.has("animation_bones") ||
!obj.get("animation_bones").isJsonObject()
) {
return null;
}
JsonObject bonesObj = obj.getAsJsonObject("animation_bones");
if (bonesObj.size() == 0) {
LOGGER.error("[DataDrivenItems] In {}: 'animation_bones' is empty", fileId);
LOGGER.error(
"[DataDrivenItems] In {}: 'animation_bones' is empty",
fileId
);
return null;
}
@@ -381,8 +524,11 @@ public final class DataDrivenItemParser {
JsonElement value = entry.getValue();
if (!value.isJsonArray()) {
LOGGER.warn("[DataDrivenItems] In {}: animation_bones['{}'] is not an array, skipping",
fileId, animName);
LOGGER.warn(
"[DataDrivenItems] In {}: animation_bones['{}'] is not an array, skipping",
fileId,
animName
);
continue;
}
@@ -394,29 +540,41 @@ public final class DataDrivenItemParser {
if (VALID_BONE_NAMES.contains(boneName)) {
bones.add(boneName);
} else {
LOGGER.warn("[DataDrivenItems] In {}: animation_bones['{}'] contains unknown bone '{}', skipping",
fileId, animName, boneName);
LOGGER.warn(
"[DataDrivenItems] In {}: animation_bones['{}'] contains unknown bone '{}', skipping",
fileId,
animName,
boneName
);
}
} catch (Exception e) {
LOGGER.warn("[DataDrivenItems] In {}: invalid element in animation_bones['{}']",
fileId, animName);
LOGGER.warn(
"[DataDrivenItems] In {}: invalid element in animation_bones['{}']",
fileId,
animName
);
}
}
if (!bones.isEmpty()) {
result.put(animName, Collections.unmodifiableSet(bones));
} else {
LOGGER.warn("[DataDrivenItems] In {}: animation_bones['{}'] resolved to empty set, skipping",
fileId, animName);
LOGGER.warn(
"[DataDrivenItems] In {}: animation_bones['{}'] resolved to empty set, skipping",
fileId,
animName
);
}
}
if (result.isEmpty()) {
LOGGER.error("[DataDrivenItems] In {}: 'animation_bones' has no valid entries", fileId);
LOGGER.error(
"[DataDrivenItems] In {}: 'animation_bones' has no valid entries",
fileId
);
return null;
}
return Collections.unmodifiableMap(result);
}
}

View File

@@ -29,7 +29,10 @@ public final class DataDrivenItemRegistry {
* Volatile reference to an unmodifiable map. Reload builds a new map
* and swaps atomically; consumer threads always see a consistent snapshot.
*/
private static volatile Map<ResourceLocation, DataDrivenItemDefinition> DEFINITIONS = Map.of();
private static volatile Map<
ResourceLocation,
DataDrivenItemDefinition
> DEFINITIONS = Map.of();
private DataDrivenItemRegistry() {}
@@ -39,7 +42,9 @@ public final class DataDrivenItemRegistry {
*
* @param newDefs the new definitions map (will be defensively copied)
*/
public static void reload(Map<ResourceLocation, DataDrivenItemDefinition> newDefs) {
public static void reload(
Map<ResourceLocation, DataDrivenItemDefinition> newDefs
) {
DEFINITIONS = Collections.unmodifiableMap(new HashMap<>(newDefs));
}
@@ -53,8 +58,12 @@ public final class DataDrivenItemRegistry {
*
* @param newDefs the definitions to merge (will overwrite existing entries with same key)
*/
public static void mergeAll(Map<ResourceLocation, DataDrivenItemDefinition> newDefs) {
Map<ResourceLocation, DataDrivenItemDefinition> merged = new HashMap<>(DEFINITIONS);
public static void mergeAll(
Map<ResourceLocation, DataDrivenItemDefinition> newDefs
) {
Map<ResourceLocation, DataDrivenItemDefinition> merged = new HashMap<>(
DEFINITIONS
);
merged.putAll(newDefs);
DEFINITIONS = Collections.unmodifiableMap(merged);
}
@@ -81,7 +90,9 @@ public final class DataDrivenItemRegistry {
if (stack.isEmpty()) return null;
CompoundTag tag = stack.getTag();
if (tag == null || !tag.contains(NBT_ITEM_ID)) return null;
ResourceLocation id = ResourceLocation.tryParse(tag.getString(NBT_ITEM_ID));
ResourceLocation id = ResourceLocation.tryParse(
tag.getString(NBT_ITEM_ID)
);
if (id == null) return null;
return DEFINITIONS.get(id);
}

View File

@@ -21,50 +21,80 @@ import org.apache.logging.log4j.Logger;
* <p>Follows the same pattern as {@link com.tiedup.remake.client.animation.context.ContextGlbRegistry}:
* prepare phase is a no-op, apply phase scans + parses + atomic-swaps the registry.</p>
*/
public class DataDrivenItemReloadListener extends SimplePreparableReloadListener<Void> {
public class DataDrivenItemReloadListener
extends SimplePreparableReloadListener<Void>
{
private static final Logger LOGGER = LogManager.getLogger("DataDrivenItems");
private static final Logger LOGGER = LogManager.getLogger(
"DataDrivenItems"
);
/** Resource directory containing item definition JSON files. */
private static final String DIRECTORY = "tiedup_items";
@Override
protected Void prepare(ResourceManager resourceManager, ProfilerFiller profiler) {
protected Void prepare(
ResourceManager resourceManager,
ProfilerFiller profiler
) {
// No preparation needed — parsing happens in apply phase
return null;
}
@Override
protected void apply(Void nothing, ResourceManager resourceManager, ProfilerFiller profiler) {
Map<ResourceLocation, DataDrivenItemDefinition> newDefs = new HashMap<>();
protected void apply(
Void nothing,
ResourceManager resourceManager,
ProfilerFiller profiler
) {
Map<ResourceLocation, DataDrivenItemDefinition> newDefs =
new HashMap<>();
Map<ResourceLocation, Resource> resources = resourceManager.listResources(
DIRECTORY, loc -> loc.getPath().endsWith(".json")
);
Map<ResourceLocation, Resource> resources =
resourceManager.listResources(DIRECTORY, loc ->
loc.getPath().endsWith(".json")
);
int skipped = 0;
for (Map.Entry<ResourceLocation, Resource> entry : resources.entrySet()) {
for (Map.Entry<
ResourceLocation,
Resource
> entry : resources.entrySet()) {
ResourceLocation fileId = entry.getKey();
Resource resource = entry.getValue();
try (InputStream input = resource.open()) {
DataDrivenItemDefinition def = DataDrivenItemParser.parse(input, fileId);
DataDrivenItemDefinition def = DataDrivenItemParser.parse(
input,
fileId
);
if (def != null) {
// Check for duplicate IDs
if (newDefs.containsKey(def.id())) {
LOGGER.warn("[DataDrivenItems] Duplicate item ID '{}' from file '{}' — overwriting previous definition",
def.id(), fileId);
LOGGER.warn(
"[DataDrivenItems] Duplicate item ID '{}' from file '{}' — overwriting previous definition",
def.id(),
fileId
);
}
newDefs.put(def.id(), def);
LOGGER.debug("[DataDrivenItems] Loaded: {} -> '{}'", def.id(), def.displayName());
LOGGER.debug(
"[DataDrivenItems] Loaded: {} -> '{}'",
def.id(),
def.displayName()
);
} else {
skipped++;
}
} catch (Exception e) {
LOGGER.error("[DataDrivenItems] Failed to read resource {}: {}", fileId, e.getMessage());
LOGGER.error(
"[DataDrivenItems] Failed to read resource {}: {}",
fileId,
e.getMessage()
);
skipped++;
}
}
@@ -73,7 +103,11 @@ public class DataDrivenItemReloadListener extends SimplePreparableReloadListener
// overwrite client-only definitions on integrated server
DataDrivenItemRegistry.mergeAll(newDefs);
LOGGER.info("[DataDrivenItems] Loaded {} item definitions ({} skipped) from {} JSON files",
newDefs.size(), skipped, resources.size());
LOGGER.info(
"[DataDrivenItems] Loaded {} item definitions ({} skipped) from {} JSON files",
newDefs.size(),
skipped,
resources.size()
);
}
}

View File

@@ -23,50 +23,80 @@ import org.apache.logging.log4j.Logger;
* <p>Registered via {@link net.minecraftforge.event.AddReloadListenerEvent} in
* {@link com.tiedup.remake.core.TiedUpMod.ForgeEvents}.</p>
*/
public class DataDrivenItemServerReloadListener extends SimplePreparableReloadListener<Void> {
public class DataDrivenItemServerReloadListener
extends SimplePreparableReloadListener<Void>
{
private static final Logger LOGGER = LogManager.getLogger("DataDrivenItems");
private static final Logger LOGGER = LogManager.getLogger(
"DataDrivenItems"
);
/** Resource directory containing item definition JSON files (under data/). */
private static final String DIRECTORY = "tiedup_items";
@Override
protected Void prepare(ResourceManager resourceManager, ProfilerFiller profiler) {
protected Void prepare(
ResourceManager resourceManager,
ProfilerFiller profiler
) {
// No preparation needed -- parsing happens in apply phase
return null;
}
@Override
protected void apply(Void nothing, ResourceManager resourceManager, ProfilerFiller profiler) {
Map<ResourceLocation, DataDrivenItemDefinition> newDefs = new HashMap<>();
protected void apply(
Void nothing,
ResourceManager resourceManager,
ProfilerFiller profiler
) {
Map<ResourceLocation, DataDrivenItemDefinition> newDefs =
new HashMap<>();
Map<ResourceLocation, Resource> resources = resourceManager.listResources(
DIRECTORY, loc -> loc.getPath().endsWith(".json")
);
Map<ResourceLocation, Resource> resources =
resourceManager.listResources(DIRECTORY, loc ->
loc.getPath().endsWith(".json")
);
int skipped = 0;
for (Map.Entry<ResourceLocation, Resource> entry : resources.entrySet()) {
for (Map.Entry<
ResourceLocation,
Resource
> entry : resources.entrySet()) {
ResourceLocation fileId = entry.getKey();
Resource resource = entry.getValue();
try (InputStream input = resource.open()) {
DataDrivenItemDefinition def = DataDrivenItemParser.parse(input, fileId);
DataDrivenItemDefinition def = DataDrivenItemParser.parse(
input,
fileId
);
if (def != null) {
// Check for duplicate IDs
if (newDefs.containsKey(def.id())) {
LOGGER.warn("[DataDrivenItems] Server: Duplicate item ID '{}' from file '{}' -- overwriting previous definition",
def.id(), fileId);
LOGGER.warn(
"[DataDrivenItems] Server: Duplicate item ID '{}' from file '{}' -- overwriting previous definition",
def.id(),
fileId
);
}
newDefs.put(def.id(), def);
LOGGER.debug("[DataDrivenItems] Server loaded: {} -> '{}'", def.id(), def.displayName());
LOGGER.debug(
"[DataDrivenItems] Server loaded: {} -> '{}'",
def.id(),
def.displayName()
);
} else {
skipped++;
}
} catch (Exception e) {
LOGGER.error("[DataDrivenItems] Server: Failed to read resource {}: {}", fileId, e.getMessage());
LOGGER.error(
"[DataDrivenItems] Server: Failed to read resource {}: {}",
fileId,
e.getMessage()
);
skipped++;
}
}
@@ -75,7 +105,11 @@ public class DataDrivenItemServerReloadListener extends SimplePreparableReloadLi
// definitions aren't overwritten on integrated server
DataDrivenItemRegistry.mergeAll(newDefs);
LOGGER.info("[DataDrivenItems] Server loaded {} item definitions ({} skipped) from {} JSON files",
newDefs.size(), skipped, resources.size());
LOGGER.info(
"[DataDrivenItems] Server loaded {} item definitions ({} skipped) from {} JSON files",
newDefs.size(),
skipped,
resources.size()
);
}
}

View File

@@ -32,8 +32,10 @@ import org.jetbrains.annotations.Nullable;
* Subclasses implement: getOccupiedRegions(), getModelLocation(), getPosePriority(),
* getResistanceId(), notifyStruggle().
*/
public abstract class AbstractV2BondageItem extends Item
implements IV2BondageItem, ILockable, IHasResistance {
public abstract class AbstractV2BondageItem
extends Item
implements IV2BondageItem, ILockable, IHasResistance
{
protected AbstractV2BondageItem(Properties properties) {
super(properties);
@@ -45,7 +47,11 @@ public abstract class AbstractV2BondageItem extends Item
// Right-click in air does nothing for self-equip — consistent with V1 behavior.
@Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
public InteractionResultHolder<ItemStack> use(
Level level,
Player player,
InteractionHand hand
) {
return InteractionResultHolder.pass(player.getItemInHand(hand));
}
@@ -53,7 +59,10 @@ public abstract class AbstractV2BondageItem extends Item
@Override
public InteractionResult interactLivingEntity(
ItemStack stack, Player player, LivingEntity target, InteractionHand hand
ItemStack stack,
Player player,
LivingEntity target,
InteractionHand hand
) {
// Client returns SUCCESS for arm swing animation. Server may reject —
// minor visual desync is accepted Forge pattern (same as vanilla food/bow).
@@ -94,7 +103,10 @@ public abstract class AbstractV2BondageItem extends Item
@Override
public void appendHoverText(
ItemStack stack, @Nullable Level level, List<Component> tooltip, TooltipFlag flag
ItemStack stack,
@Nullable Level level,
List<Component> tooltip,
TooltipFlag flag
) {
super.appendHoverText(stack, level, tooltip, flag);
// Lock status from ILockable
@@ -102,19 +114,29 @@ public abstract class AbstractV2BondageItem extends Item
// Escape difficulty
int difficulty = getEscapeDifficulty(stack);
if (difficulty > 0) {
tooltip.add(Component.translatable("item.tiedup.tooltip.escape_difficulty", difficulty)
.withStyle(ChatFormatting.GRAY));
tooltip.add(
Component.translatable(
"item.tiedup.tooltip.escape_difficulty",
difficulty
).withStyle(ChatFormatting.GRAY)
);
}
}
// ===== IV2BondageItem DEFAULTS =====
@Override
public int getEscapeDifficulty() { return 0; }
public int getEscapeDifficulty() {
return 0;
}
@Override
public boolean supportsColor() { return false; }
public boolean supportsColor() {
return false;
}
@Override
public boolean supportsSlimModel() { return false; }
public boolean supportsSlimModel() {
return false;
}
}

View File

@@ -20,7 +20,8 @@ public class V2Handcuffs extends AbstractV2BondageItem {
Collections.unmodifiableSet(EnumSet.of(BodyRegionV2.ARMS));
private static final ResourceLocation MODEL = new ResourceLocation(
TiedUpMod.MOD_ID, "models/gltf/v2/handcuffs/cuffs_prototype.glb"
TiedUpMod.MOD_ID,
"models/gltf/v2/handcuffs/cuffs_prototype.glb"
);
public V2Handcuffs() {
@@ -28,27 +29,43 @@ public class V2Handcuffs extends AbstractV2BondageItem {
}
@Override
public Set<BodyRegionV2> getOccupiedRegions() { return REGIONS; }
public Set<BodyRegionV2> getOccupiedRegions() {
return REGIONS;
}
@Override
public ResourceLocation getModelLocation() { return MODEL; }
public ResourceLocation getModelLocation() {
return MODEL;
}
@Override
public int getPosePriority() { return 30; }
public int getPosePriority() {
return 30;
}
@Override
public int getEscapeDifficulty() { return 100; }
public int getEscapeDifficulty() {
return 100;
}
@Override
public String getResistanceId() { return "handcuffs"; }
public String getResistanceId() {
return "handcuffs";
}
@Override
public void notifyStruggle(LivingEntity entity) {
entity.level().playSound(
null, entity.getX(), entity.getY(), entity.getZ(),
net.minecraft.sounds.SoundEvents.CHAIN_STEP,
net.minecraft.sounds.SoundSource.PLAYERS,
0.4f, 1.0f
);
entity
.level()
.playSound(
null,
entity.getX(),
entity.getY(),
entity.getZ(),
net.minecraft.sounds.SoundEvents.CHAIN_STEP,
net.minecraft.sounds.SoundSource.PLAYERS,
0.4f,
1.0f
);
}
}

View File

@@ -1,7 +1,7 @@
package com.tiedup.remake.v2.bondage.movement;
import org.jetbrains.annotations.Nullable;
import com.tiedup.remake.v2.BodyRegionV2;
import org.jetbrains.annotations.Nullable;
/**
* Movement styles that change how a bound player physically moves.
@@ -16,7 +16,6 @@ import com.tiedup.remake.v2.BodyRegionV2;
* to avoid pulling server-only classes into client code.</p>
*/
public enum MovementStyle {
/** Swaying side-to-side gait, visual zigzag via animation. Jump allowed. */
WADDLE(1, 0.6f, false),
@@ -33,7 +32,11 @@ public enum MovementStyle {
private final float defaultSpeedMultiplier;
private final boolean defaultJumpDisabled;
MovementStyle(int severity, float defaultSpeedMultiplier, boolean defaultJumpDisabled) {
MovementStyle(
int severity,
float defaultSpeedMultiplier,
boolean defaultJumpDisabled
) {
this.severity = severity;
this.defaultSpeedMultiplier = defaultSpeedMultiplier;
this.defaultJumpDisabled = defaultJumpDisabled;

View File

@@ -61,18 +61,23 @@ public class MovementStyleManager {
// RestraintEffectUtils used this UUID with ADDITION operation and addPermanentModifier().
// Players upgrading from V1 may still have this modifier saved in their NBT.
// Removed on tick to prevent double stacking with V2 MULTIPLY_BASE modifiers.
private static final UUID V1_BIND_SPEED_MODIFIER_UUID =
UUID.fromString("7f3c7c8e-9d4e-4c7a-8e5f-1a2b3c4d5e6f");
private static final UUID V1_BIND_SPEED_MODIFIER_UUID = UUID.fromString(
"7f3c7c8e-9d4e-4c7a-8e5f-1a2b3c4d5e6f"
);
// --- Unique UUIDs for AttributeModifiers (one per style to allow clean removal) ---
private static final UUID WADDLE_SPEED_UUID =
UUID.fromString("d7a1c001-0000-0000-0000-000000000001");
private static final UUID SHUFFLE_SPEED_UUID =
UUID.fromString("d7a1c001-0000-0000-0000-000000000002");
private static final UUID HOP_SPEED_UUID =
UUID.fromString("d7a1c001-0000-0000-0000-000000000003");
private static final UUID CRAWL_SPEED_UUID =
UUID.fromString("d7a1c001-0000-0000-0000-000000000004");
private static final UUID WADDLE_SPEED_UUID = UUID.fromString(
"d7a1c001-0000-0000-0000-000000000001"
);
private static final UUID SHUFFLE_SPEED_UUID = UUID.fromString(
"d7a1c001-0000-0000-0000-000000000002"
);
private static final UUID HOP_SPEED_UUID = UUID.fromString(
"d7a1c001-0000-0000-0000-000000000003"
);
private static final UUID CRAWL_SPEED_UUID = UUID.fromString(
"d7a1c001-0000-0000-0000-000000000004"
);
// --- Hop tuning constants ---
private static final double HOP_Y_IMPULSE = 0.28;
@@ -119,7 +124,11 @@ public class MovementStyleManager {
// --- Skip conditions ---
// Update last position even when suspended to prevent false movement
// detection on resume (e.g., teleport while riding)
if (player.isPassenger() || player.isDeadOrDying() || state.isStruggling()) {
if (
player.isPassenger() ||
player.isDeadOrDying() ||
state.isStruggling()
) {
state.lastX = player.getX();
state.lastY = player.getY();
state.lastZ = player.getZ();
@@ -138,8 +147,8 @@ public class MovementStyleManager {
// --- Resolve current style from equipped items ---
IV2BondageEquipment equipment = V2EquipmentHelper.getEquipment(player);
Map<BodyRegionV2, ItemStack> equipped = equipment != null
? equipment.getAllEquipped() : Map.of();
Map<BodyRegionV2, ItemStack> equipped =
equipment != null ? equipment.getAllEquipped() : Map.of();
ResolvedMovement resolved = MovementStyleResolver.resolve(equipped);
// --- Compare with current active style ---
@@ -163,7 +172,9 @@ public class MovementStyleManager {
// Sync to all tracking clients (animation + crawl pose)
ModNetwork.sendToAllTrackingAndSelf(
new PacketSyncMovementStyle(player.getUUID(), newStyle), player);
new PacketSyncMovementStyle(player.getUUID(), newStyle),
player
);
}
// --- Per-style tick ---
@@ -229,15 +240,18 @@ public class MovementStyleManager {
private static double getJumpBoostFactor(Player player) {
var jumpBoost = player.getEffect(MobEffects.JUMP);
if (jumpBoost != null) {
return 1.0 + (jumpBoost.getAmplifier() + 1) * 0.1 / 0.42;
return 1.0 + ((jumpBoost.getAmplifier() + 1) * 0.1) / 0.42;
}
return 1.0;
}
// ==================== Lifecycle ====================
private static void onActivate(ServerPlayer player, PlayerBindState state,
MovementStyle style) {
private static void onActivate(
ServerPlayer player,
PlayerBindState state,
MovementStyle style
) {
switch (style) {
case WADDLE -> activateWaddle(player, state);
case SHUFFLE -> activateShuffle(player, state);
@@ -246,8 +260,11 @@ public class MovementStyleManager {
}
}
private static void onDeactivate(ServerPlayer player, PlayerBindState state,
MovementStyle style) {
private static void onDeactivate(
ServerPlayer player,
PlayerBindState state,
MovementStyle style
) {
switch (style) {
case WADDLE -> deactivateWaddle(player, state);
case SHUFFLE -> deactivateShuffle(player, state);
@@ -267,12 +284,22 @@ public class MovementStyleManager {
// ==================== Waddle ====================
private static void activateWaddle(ServerPlayer player, PlayerBindState state) {
applySpeedModifier(player, WADDLE_SPEED_UUID, "tiedup.waddle_speed",
state.getResolvedMovementSpeed());
private static void activateWaddle(
ServerPlayer player,
PlayerBindState state
) {
applySpeedModifier(
player,
WADDLE_SPEED_UUID,
"tiedup.waddle_speed",
state.getResolvedMovementSpeed()
);
}
private static void deactivateWaddle(ServerPlayer player, PlayerBindState state) {
private static void deactivateWaddle(
ServerPlayer player,
PlayerBindState state
) {
removeSpeedModifier(player, WADDLE_SPEED_UUID);
}
@@ -283,31 +310,54 @@ public class MovementStyleManager {
// ==================== Shuffle ====================
private static void activateShuffle(ServerPlayer player, PlayerBindState state) {
applySpeedModifier(player, SHUFFLE_SPEED_UUID, "tiedup.shuffle_speed",
state.getResolvedMovementSpeed());
private static void activateShuffle(
ServerPlayer player,
PlayerBindState state
) {
applySpeedModifier(
player,
SHUFFLE_SPEED_UUID,
"tiedup.shuffle_speed",
state.getResolvedMovementSpeed()
);
}
private static void deactivateShuffle(ServerPlayer player, PlayerBindState state) {
private static void deactivateShuffle(
ServerPlayer player,
PlayerBindState state
) {
removeSpeedModifier(player, SHUFFLE_SPEED_UUID);
}
private static void tickShuffle(ServerPlayer player, PlayerBindState state) {
private static void tickShuffle(
ServerPlayer player,
PlayerBindState state
) {
// Shuffle: speed reduction via attribute is sufficient. No per-tick work.
}
// ==================== Hop ====================
private static void activateHop(ServerPlayer player, PlayerBindState state) {
private static void activateHop(
ServerPlayer player,
PlayerBindState state
) {
// Apply base speed reduction (~15% base speed between hops)
applySpeedModifier(player, HOP_SPEED_UUID, "tiedup.hop_speed",
state.getResolvedMovementSpeed());
applySpeedModifier(
player,
HOP_SPEED_UUID,
"tiedup.hop_speed",
state.getResolvedMovementSpeed()
);
state.hopCooldown = 0;
state.hopStartupPending = true;
state.hopStartupTicks = HOP_STARTUP_DELAY_TICKS;
}
private static void deactivateHop(ServerPlayer player, PlayerBindState state) {
private static void deactivateHop(
ServerPlayer player,
PlayerBindState state
) {
removeSpeedModifier(player, HOP_SPEED_UUID);
state.hopCooldown = 0;
state.hopStartupPending = false;
@@ -325,8 +375,9 @@ public class MovementStyleManager {
* </ul>
*/
private static void tickHop(ServerPlayer player, PlayerBindState state) {
boolean isMoving = player.distanceToSqr(state.lastX, state.lastY, state.lastZ)
> MOVEMENT_THRESHOLD_SQ;
boolean isMoving =
player.distanceToSqr(state.lastX, state.lastY, state.lastZ) >
MOVEMENT_THRESHOLD_SQ;
// Decrement cooldown
if (state.hopCooldown > 0) {
@@ -351,8 +402,10 @@ public class MovementStyleManager {
} else if (!isMoving) {
state.hopNotMovingTicks++;
// Reset startup if not moving for >= 2 consecutive ticks
if (state.hopNotMovingTicks >= HOP_STARTUP_RESET_TICKS
&& !state.hopStartupPending) {
if (
state.hopNotMovingTicks >= HOP_STARTUP_RESET_TICKS &&
!state.hopStartupPending
) {
state.hopStartupPending = true;
state.hopStartupTicks = HOP_STARTUP_DELAY_TICKS;
}
@@ -373,9 +426,9 @@ public class MovementStyleManager {
Vec3 currentMotion = player.getDeltaMovement();
player.setDeltaMovement(
currentMotion.x + forward.x * HOP_FORWARD_IMPULSE,
HOP_Y_IMPULSE,
currentMotion.z + forward.z * HOP_FORWARD_IMPULSE
currentMotion.x + forward.x * HOP_FORWARD_IMPULSE,
HOP_Y_IMPULSE,
currentMotion.z + forward.z * HOP_FORWARD_IMPULSE
);
state.hopCooldown = HOP_COOLDOWN_TICKS;
@@ -386,15 +439,25 @@ public class MovementStyleManager {
// ==================== Crawl ====================
private static void activateCrawl(ServerPlayer player, PlayerBindState state) {
applySpeedModifier(player, CRAWL_SPEED_UUID, "tiedup.crawl_speed",
state.getResolvedMovementSpeed());
private static void activateCrawl(
ServerPlayer player,
PlayerBindState state
) {
applySpeedModifier(
player,
CRAWL_SPEED_UUID,
"tiedup.crawl_speed",
state.getResolvedMovementSpeed()
);
player.setForcedPose(Pose.SWIMMING);
player.refreshDimensions();
state.pendingPoseRestore = false;
}
private static void deactivateCrawl(ServerPlayer player, PlayerBindState state) {
private static void deactivateCrawl(
ServerPlayer player,
PlayerBindState state
) {
removeSpeedModifier(player, CRAWL_SPEED_UUID);
// Space check: can the player stand up?
@@ -427,8 +490,10 @@ public class MovementStyleManager {
* Called every tick regardless of active style (step 2 in tick flow).
* Retries until space is available for the player to stand.
*/
private static void tryRestoreStandingPose(ServerPlayer player,
PlayerBindState state) {
private static void tryRestoreStandingPose(
ServerPlayer player,
PlayerBindState state
) {
EntityDimensions standDims = player.getDimensions(Pose.STANDING);
AABB standBox = standDims.makeBoundingBox(player.position());
boolean canStand = player.level().noCollision(player, standBox);
@@ -437,8 +502,10 @@ public class MovementStyleManager {
player.setForcedPose(null);
player.refreshDimensions();
state.pendingPoseRestore = false;
LOGGER.debug("Restored standing pose for {} (pending pose restore cleared)",
player.getName().getString());
LOGGER.debug(
"Restored standing pose for {} (pending pose restore cleared)",
player.getName().getString()
);
}
}
@@ -457,10 +524,15 @@ public class MovementStyleManager {
*/
private static void cleanupV1Modifier(ServerPlayer player) {
AttributeInstance attr = player.getAttribute(Attributes.MOVEMENT_SPEED);
if (attr != null && attr.getModifier(V1_BIND_SPEED_MODIFIER_UUID) != null) {
if (
attr != null &&
attr.getModifier(V1_BIND_SPEED_MODIFIER_UUID) != null
) {
attr.removeModifier(V1_BIND_SPEED_MODIFIER_UUID);
LOGGER.info("Removed stale V1 speed modifier from player {}",
player.getName().getString());
LOGGER.info(
"Removed stale V1 speed modifier from player {}",
player.getName().getString()
);
}
}
@@ -481,8 +553,12 @@ public class MovementStyleManager {
* @param name modifier name (for debugging in F3 screen)
* @param multiplier the desired speed fraction (0.0-1.0)
*/
private static void applySpeedModifier(ServerPlayer player, UUID uuid, String name,
float multiplier) {
private static void applySpeedModifier(
ServerPlayer player,
UUID uuid,
String name,
float multiplier
) {
AttributeInstance attr = player.getAttribute(Attributes.MOVEMENT_SPEED);
if (attr == null) return;
@@ -491,8 +567,14 @@ public class MovementStyleManager {
// MULTIPLY_BASE: value of -(1 - multiplier) reduces base speed to multiplier fraction
double value = -(1.0 - multiplier);
attr.addTransientModifier(new AttributeModifier(uuid, name,
value, AttributeModifier.Operation.MULTIPLY_BASE));
attr.addTransientModifier(
new AttributeModifier(
uuid,
name,
value,
AttributeModifier.Operation.MULTIPLY_BASE
)
);
}
/**

View File

@@ -47,7 +47,9 @@ public final class MovementStyleResolver {
* @param equipped map of region to ItemStack (from {@code IV2BondageEquipment.getAllEquipped()})
* @return the resolved movement, or {@link ResolvedMovement#NONE} if no styled items
*/
public static ResolvedMovement resolve(Map<BodyRegionV2, ItemStack> equipped) {
public static ResolvedMovement resolve(
Map<BodyRegionV2, ItemStack> equipped
) {
if (equipped == null || equipped.isEmpty()) {
return ResolvedMovement.NONE;
}
@@ -70,16 +72,20 @@ public final class MovementStyleResolver {
int severity = style.getSeverity();
int regionOrdinal = region.ordinal();
if (severity > bestSeverity
|| (severity == bestSeverity && regionOrdinal < bestRegionOrdinal)) {
if (
severity > bestSeverity ||
(severity == bestSeverity &&
regionOrdinal < bestRegionOrdinal)
) {
bestStyle = style;
MovementModifier mod = def.movementModifier();
bestSpeed = (mod != null && mod.speedMultiplier() != null)
? mod.speedMultiplier()
: style.getDefaultSpeedMultiplier();
bestJumpDisabled = (mod != null && mod.jumpDisabled() != null)
? mod.jumpDisabled()
: style.isDefaultJumpDisabled();
? mod.speedMultiplier()
: style.getDefaultSpeedMultiplier();
bestJumpDisabled = (mod != null &&
mod.jumpDisabled() != null)
? mod.jumpDisabled()
: style.isDefaultJumpDisabled();
bestSeverity = severity;
bestRegionOrdinal = regionOrdinal;
}
@@ -92,8 +98,11 @@ public final class MovementStyleResolver {
int severity = fallback.style.getSeverity();
int regionOrdinal = region.ordinal();
if (severity > bestSeverity
|| (severity == bestSeverity && regionOrdinal < bestRegionOrdinal)) {
if (
severity > bestSeverity ||
(severity == bestSeverity &&
regionOrdinal < bestRegionOrdinal)
) {
bestStyle = fallback.style;
bestSpeed = fallback.speed;
bestJumpDisabled = fallback.jumpDisabled;
@@ -139,10 +148,16 @@ public final class MovementStyleResolver {
PoseType poseType = bindItem.getPoseType();
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);
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);
@@ -150,5 +165,9 @@ public final class MovementStyleResolver {
}
/** Internal holder for V1 fallback resolution result. */
private record V1Fallback(MovementStyle style, float speed, boolean jumpDisabled) {}
private record V1Fallback(
MovementStyle style,
float speed,
boolean jumpDisabled
) {}
}

View File

@@ -18,7 +18,10 @@ public record ResolvedMovement(
/** Final jump-disabled flag (style default or item override). */
boolean jumpDisabled
) {
/** Sentinel for "no movement style active". */
public static final ResolvedMovement NONE = new ResolvedMovement(null, 1.0f, false);
public static final ResolvedMovement NONE = new ResolvedMovement(
null,
1.0f,
false
);
}

View File

@@ -25,7 +25,9 @@ import org.apache.logging.log4j.Logger;
*/
public class PacketSyncV2Equipment {
private static final Logger LOGGER = LogManager.getLogger("PacketSyncV2Equipment");
private static final Logger LOGGER = LogManager.getLogger(
"PacketSyncV2Equipment"
);
private final int entityId;
private final CompoundTag data;
@@ -45,12 +47,18 @@ public class PacketSyncV2Equipment {
public static PacketSyncV2Equipment decode(FriendlyByteBuf buf) {
int entityId = buf.readInt();
CompoundTag data = buf.readNbt();
return new PacketSyncV2Equipment(entityId, data != null ? data : new CompoundTag());
return new PacketSyncV2Equipment(
entityId,
data != null ? data : new CompoundTag()
);
}
// ==================== Handler ====================
public static void handle(PacketSyncV2Equipment msg, Supplier<NetworkEvent.Context> ctxSupplier) {
public static void handle(
PacketSyncV2Equipment msg,
Supplier<NetworkEvent.Context> ctxSupplier
) {
NetworkEvent.Context ctx = ctxSupplier.get();
ctx.enqueueWork(() -> {
if (FMLEnvironment.dist == Dist.CLIENT) {
@@ -74,7 +82,8 @@ public class PacketSyncV2Equipment {
holder.getV2Equipment().deserializeNBT(msg.data);
return;
}
living.getCapability(V2BondageEquipmentProvider.V2_BONDAGE_EQUIPMENT)
living
.getCapability(V2BondageEquipmentProvider.V2_BONDAGE_EQUIPMENT)
.ifPresent(equip -> equip.deserializeNBT(msg.data));
}
}

View File

@@ -36,13 +36,20 @@ import net.minecraftforge.network.NetworkEvent;
*/
public class PacketV2LockToggle {
public enum Action { LOCK, UNLOCK }
public enum Action {
LOCK,
UNLOCK,
}
private final int targetEntityId;
private final BodyRegionV2 region;
private final Action action;
public PacketV2LockToggle(int targetEntityId, BodyRegionV2 region, Action action) {
public PacketV2LockToggle(
int targetEntityId,
BodyRegionV2 region,
Action action
) {
this.targetEntityId = targetEntityId;
this.region = region;
this.action = action;
@@ -62,7 +69,10 @@ public class PacketV2LockToggle {
);
}
public static void handle(PacketV2LockToggle msg, Supplier<NetworkEvent.Context> ctxSupplier) {
public static void handle(
PacketV2LockToggle msg,
Supplier<NetworkEvent.Context> ctxSupplier
) {
NetworkEvent.Context ctx = ctxSupplier.get();
ctx.enqueueWork(() -> {
ServerPlayer sender = ctx.getSender();
@@ -75,13 +85,18 @@ public class PacketV2LockToggle {
}
private static void handleServer(
ServerPlayer sender, int targetEntityId, BodyRegionV2 region, Action action
ServerPlayer sender,
int targetEntityId,
BodyRegionV2 region,
Action action
) {
Entity rawTarget = sender.level().getEntity(targetEntityId);
if (!(rawTarget instanceof LivingEntity target)) return;
// Distance + line-of-sight validation
if (sender.distanceTo(target) > 4.0 || !sender.hasLineOfSight(target)) return;
if (
sender.distanceTo(target) > 4.0 || !sender.hasLineOfSight(target)
) return;
ItemStack stack = V2EquipmentHelper.getInRegion(target, region);
if (stack.isEmpty()) return;
@@ -91,8 +106,9 @@ public class PacketV2LockToggle {
ItemStack mainHand = sender.getItemInHand(InteractionHand.MAIN_HAND);
ItemStack offHand = sender.getItemInHand(InteractionHand.OFF_HAND);
boolean hasMasterKey = mainHand.getItem() instanceof ItemMasterKey
|| offHand.getItem() instanceof ItemMasterKey;
boolean hasMasterKey =
mainHand.getItem() instanceof ItemMasterKey ||
offHand.getItem() instanceof ItemMasterKey;
ItemKey heldKey = null;
ItemStack heldKeyStack = ItemStack.EMPTY;
@@ -115,8 +131,11 @@ public class PacketV2LockToggle {
lockable.setLockedByKeyUUID(stack, keyUUID);
lockable.initializeLockResistance(stack);
TiedUpMod.LOGGER.debug("[V2LockToggle] Locked region {} on entity {}",
region.name(), target.getName().getString());
TiedUpMod.LOGGER.debug(
"[V2LockToggle] Locked region {} on entity {}",
region.name(),
target.getName().getString()
);
}
case UNLOCK -> {
if (!lockable.isLocked(stack)) return;
@@ -133,8 +152,11 @@ public class PacketV2LockToggle {
return;
}
TiedUpMod.LOGGER.debug("[V2LockToggle] Unlocked region {} on entity {}",
region.name(), target.getName().getString());
TiedUpMod.LOGGER.debug(
"[V2LockToggle] Unlocked region {} on entity {}",
region.name(),
target.getName().getString()
);
}
}

View File

@@ -20,7 +20,9 @@ import org.apache.logging.log4j.Logger;
*/
public class PacketV2SelfEquip {
private static final Logger LOGGER = LogManager.getLogger("PacketV2SelfEquip");
private static final Logger LOGGER = LogManager.getLogger(
"PacketV2SelfEquip"
);
private final BodyRegionV2 region;
private final int inventorySlot;
@@ -36,10 +38,16 @@ public class PacketV2SelfEquip {
}
public static PacketV2SelfEquip decode(FriendlyByteBuf buf) {
return new PacketV2SelfEquip(buf.readEnum(BodyRegionV2.class), buf.readVarInt());
return new PacketV2SelfEquip(
buf.readEnum(BodyRegionV2.class),
buf.readVarInt()
);
}
public static void handle(PacketV2SelfEquip msg, Supplier<NetworkEvent.Context> ctxSupplier) {
public static void handle(
PacketV2SelfEquip msg,
Supplier<NetworkEvent.Context> ctxSupplier
) {
NetworkEvent.Context ctx = ctxSupplier.get();
ctx.enqueueWork(() -> {
ServerPlayer player = ctx.getSender();
@@ -47,26 +55,46 @@ public class PacketV2SelfEquip {
if (!PacketRateLimiter.allowPacket(player, "action")) return;
// Validate slot index
if (msg.inventorySlot < 0 || msg.inventorySlot >= player.getInventory().getContainerSize()) return;
if (
msg.inventorySlot < 0 ||
msg.inventorySlot >= player.getInventory().getContainerSize()
) return;
ItemStack stack = player.getInventory().getItem(msg.inventorySlot);
if (stack.isEmpty()) return;
if (!(stack.getItem() instanceof IV2BondageItem bondageItem)) return;
if (
!(stack.getItem() instanceof IV2BondageItem bondageItem)
) return;
// Warn if data-driven item has no definition (missing JSON or reload issue)
if (bondageItem instanceof DataDrivenBondageItem && DataDrivenItemRegistry.get(stack) == null) {
LOGGER.warn("[V2SelfEquip] Data-driven item in slot {} has no definition — equip blocked. Stack NBT: {}",
msg.inventorySlot, stack.getTag());
if (
bondageItem instanceof DataDrivenBondageItem &&
DataDrivenItemRegistry.get(stack) == null
) {
LOGGER.warn(
"[V2SelfEquip] Data-driven item in slot {} has no definition — equip blocked. Stack NBT: {}",
msg.inventorySlot,
stack.getTag()
);
return;
}
// Validate item targets this region
if (!bondageItem.getOccupiedRegions(stack).contains(msg.region)) return;
if (
!bondageItem.getOccupiedRegions(stack).contains(msg.region)
) return;
// Furniture seat blocks this region
if (player.isPassenger() && player.getVehicle() instanceof com.tiedup.remake.v2.furniture.ISeatProvider provider) {
com.tiedup.remake.v2.furniture.SeatDefinition seat = provider.getSeatForPassenger(player);
if (seat != null && seat.blockedRegions().contains(msg.region)) {
if (
player.isPassenger() &&
player.getVehicle() instanceof
com.tiedup.remake.v2.furniture.ISeatProvider provider
) {
com.tiedup.remake.v2.furniture.SeatDefinition seat =
provider.getSeatForPassenger(player);
if (
seat != null && seat.blockedRegions().contains(msg.region)
) {
return; // Region blocked by furniture
}
}
@@ -80,7 +108,9 @@ public class PacketV2SelfEquip {
if (result.displaced() != null) {
for (ItemStack displaced : result.displaced()) {
if (!displaced.isEmpty()) {
player.getInventory().placeItemBackInInventory(displaced);
player
.getInventory()
.placeItemBackInInventory(displaced);
}
}
}

View File

@@ -31,7 +31,10 @@ public class PacketV2SelfLock {
return new PacketV2SelfLock(buf.readEnum(BodyRegionV2.class));
}
public static void handle(PacketV2SelfLock msg, Supplier<NetworkEvent.Context> ctxSupplier) {
public static void handle(
PacketV2SelfLock msg,
Supplier<NetworkEvent.Context> ctxSupplier
) {
NetworkEvent.Context ctx = ctxSupplier.get();
ctx.enqueueWork(() -> {
ServerPlayer player = ctx.getSender();
@@ -39,17 +42,31 @@ public class PacketV2SelfLock {
if (!PacketRateLimiter.allowPacket(player, "action")) return;
// Arms must be free to self-lock
if (V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS)) return;
if (
V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS)
) return;
ItemStack equipped = V2EquipmentHelper.getInRegion(player, msg.region);
ItemStack equipped = V2EquipmentHelper.getInRegion(
player,
msg.region
);
if (equipped.isEmpty()) return;
if (!(equipped.getItem() instanceof ILockable lockable)) return;
if (!lockable.isLockable(equipped) || lockable.isLocked(equipped)) return;
if (
!lockable.isLockable(equipped) || lockable.isLocked(equipped)
) return;
// Furniture seat blocks this region
if (player.isPassenger() && player.getVehicle() instanceof com.tiedup.remake.v2.furniture.ISeatProvider provider) {
com.tiedup.remake.v2.furniture.SeatDefinition seat = provider.getSeatForPassenger(player);
if (seat != null && seat.blockedRegions().contains(msg.region)) {
if (
player.isPassenger() &&
player.getVehicle() instanceof
com.tiedup.remake.v2.furniture.ISeatProvider provider
) {
com.tiedup.remake.v2.furniture.SeatDefinition seat =
provider.getSeatForPassenger(player);
if (
seat != null && seat.blockedRegions().contains(msg.region)
) {
return; // Region blocked by furniture
}
}

View File

@@ -36,7 +36,10 @@ public class PacketV2SelfRemove {
return new PacketV2SelfRemove(buf.readEnum(BodyRegionV2.class));
}
public static void handle(PacketV2SelfRemove msg, Supplier<NetworkEvent.Context> ctxSupplier) {
public static void handle(
PacketV2SelfRemove msg,
Supplier<NetworkEvent.Context> ctxSupplier
) {
NetworkEvent.Context ctx = ctxSupplier.get();
ctx.enqueueWork(() -> {
ServerPlayer player = ctx.getSender();
@@ -56,25 +59,33 @@ public class PacketV2SelfRemove {
// Arm restraints cannot be self-removed — must use struggle
if (item.getOccupiedRegions(stack).contains(BodyRegionV2.ARMS)) {
TiedUpMod.LOGGER.debug("[V2SelfRemove] Blocked: item occupies ARMS, must struggle");
TiedUpMod.LOGGER.debug(
"[V2SelfRemove] Blocked: item occupies ARMS, must struggle"
);
return;
}
// Cannot manipulate buckles/clasps with bound arms
if (V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS)) {
TiedUpMod.LOGGER.debug("[V2SelfRemove] Blocked: player's ARMS are occupied");
TiedUpMod.LOGGER.debug(
"[V2SelfRemove] Blocked: player's ARMS are occupied"
);
return;
}
// Cannot manipulate buckles/clasps with covered hands
if (V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.HANDS)) {
TiedUpMod.LOGGER.debug("[V2SelfRemove] Blocked: player's HANDS are occupied");
TiedUpMod.LOGGER.debug(
"[V2SelfRemove] Blocked: player's HANDS are occupied"
);
return;
}
// Check item allows unequip (not locked)
if (!item.canUnequip(stack, player)) {
TiedUpMod.LOGGER.debug("[V2SelfRemove] Blocked: item canUnequip=false (locked?)");
TiedUpMod.LOGGER.debug(
"[V2SelfRemove] Blocked: item canUnequip=false (locked?)"
);
return;
}

View File

@@ -32,7 +32,10 @@ public class PacketV2SelfUnlock {
return new PacketV2SelfUnlock(buf.readEnum(BodyRegionV2.class));
}
public static void handle(PacketV2SelfUnlock msg, Supplier<NetworkEvent.Context> ctxSupplier) {
public static void handle(
PacketV2SelfUnlock msg,
Supplier<NetworkEvent.Context> ctxSupplier
) {
NetworkEvent.Context ctx = ctxSupplier.get();
ctx.enqueueWork(() -> {
ServerPlayer player = ctx.getSender();
@@ -40,24 +43,39 @@ public class PacketV2SelfUnlock {
if (!PacketRateLimiter.allowPacket(player, "action")) return;
// Arms must be free to self-unlock
if (V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS)) return;
if (
V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS)
) return;
ItemStack equipped = V2EquipmentHelper.getInRegion(player, msg.region);
ItemStack equipped = V2EquipmentHelper.getInRegion(
player,
msg.region
);
if (equipped.isEmpty()) return;
if (!(equipped.getItem() instanceof ILockable lockable)) return;
if (!lockable.isLocked(equipped)) return;
// Furniture seat blocks this region
if (player.isPassenger() && player.getVehicle() instanceof com.tiedup.remake.v2.furniture.ISeatProvider provider) {
com.tiedup.remake.v2.furniture.SeatDefinition seat = provider.getSeatForPassenger(player);
if (seat != null && seat.blockedRegions().contains(msg.region)) {
if (
player.isPassenger() &&
player.getVehicle() instanceof
com.tiedup.remake.v2.furniture.ISeatProvider provider
) {
com.tiedup.remake.v2.furniture.SeatDefinition seat =
provider.getSeatForPassenger(player);
if (
seat != null && seat.blockedRegions().contains(msg.region)
) {
return; // Region blocked by furniture
}
}
// Find matching key in inventory
UUID lockedByUUID = lockable.getLockedByKeyUUID(equipped);
ItemStack keyStack = findMatchingKeyInInventory(player, lockedByUUID);
ItemStack keyStack = findMatchingKeyInInventory(
player,
lockedByUUID
);
if (keyStack.isEmpty()) return;
lockable.setLockedByKeyUUID(equipped, null);
@@ -70,7 +88,10 @@ public class PacketV2SelfUnlock {
* Find a key in the player's inventory that matches the lock UUID,
* or a master key that unlocks anything.
*/
private static ItemStack findMatchingKeyInInventory(ServerPlayer player, UUID lockedByUUID) {
private static ItemStack findMatchingKeyInInventory(
ServerPlayer player,
UUID lockedByUUID
) {
for (int i = 0; i < player.getInventory().getContainerSize(); i++) {
ItemStack s = player.getInventory().getItem(i);
if (s.isEmpty()) continue;
@@ -78,7 +99,10 @@ public class PacketV2SelfUnlock {
if (s.is(ModItems.MASTER_KEY.get())) return s;
// Regular key: must match the lock UUID
if (s.getItem() instanceof ItemKey key) {
if (lockedByUUID != null && lockedByUUID.equals(key.getKeyUUID(s))) {
if (
lockedByUUID != null &&
lockedByUUID.equals(key.getKeyUUID(s))
) {
return s;
}
}

View File

@@ -50,7 +50,10 @@ public class PacketV2StruggleStart {
return new PacketV2StruggleStart(buf.readEnum(BodyRegionV2.class));
}
public static void handle(PacketV2StruggleStart msg, Supplier<NetworkEvent.Context> ctxSupplier) {
public static void handle(
PacketV2StruggleStart msg,
Supplier<NetworkEvent.Context> ctxSupplier
) {
NetworkEvent.Context ctx = ctxSupplier.get();
ctx.enqueueWork(() -> {
ServerPlayer player = ctx.getSender();
@@ -72,7 +75,9 @@ public class PacketV2StruggleStart {
if (!resistanceItem.canBeStruggledOut(stack)) return;
// RISK-002 fix: respect server config
if (!com.tiedup.remake.core.ModConfig.SERVER.struggleMiniGameEnabled.get()) return;
if (
!com.tiedup.remake.core.ModConfig.SERVER.struggleMiniGameEnabled.get()
) return;
int resistance = resistanceItem.getCurrentResistance(stack, player);
boolean isLocked = false;
@@ -87,9 +92,13 @@ public class PacketV2StruggleStart {
if (resistance <= 0) return;
StruggleSessionManager manager = StruggleSessionManager.getInstance();
ContinuousStruggleMiniGameState session = manager.startV2StruggleSession(
player, region, resistance, isLocked
);
ContinuousStruggleMiniGameState session =
manager.startV2StruggleSession(
player,
region,
resistance,
isLocked
);
if (session != null) {
ModNetwork.sendToPlayer(