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

@@ -57,4 +57,4 @@ public enum BodyRegionV2 {
return null;
}
}
}
}

View File

@@ -22,9 +22,7 @@ public class V2Blocks {
public static final DeferredRegister<Block> BLOCKS =
DeferredRegister.create(ForgeRegistries.BLOCKS, TiedUpMod.MOD_ID);
// ========================================
// PET FURNITURE
// ========================================
public static final RegistryObject<Block> PET_BOWL = BLOCKS.register(
"pet_bowl",

View File

@@ -18,9 +18,7 @@ public class V2Items {
TiedUpMod.MOD_ID
);
// ========================================
// BLOCK ITEMS
// ========================================
public static final RegistryObject<Item> PET_BOWL = ITEMS.register(
"pet_bowl",

View File

@@ -63,9 +63,7 @@ public class PetBedBlockEntity extends ObjBlockEntity {
return new float[] { 0.0f, 0.0f, 0.0f };
}
// ========================================
// OCCUPANCY
// ========================================
public boolean isOccupied() {
return occupantUUID != null;
@@ -98,9 +96,7 @@ public class PetBedBlockEntity extends ObjBlockEntity {
setChanged();
}
// ========================================
// OWNERSHIP
// ========================================
@Nullable
public UUID getOwnerUUID() {
@@ -120,9 +116,7 @@ public class PetBedBlockEntity extends ObjBlockEntity {
return ownerUUID == null;
}
// ========================================
// NBT
// ========================================
@Override
protected void saveAdditional(CompoundTag tag) {

View File

@@ -6,7 +6,6 @@ import com.tiedup.remake.network.sync.PacketSyncPetBedState;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -16,6 +15,7 @@ import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
/**
* Server-side tracker for pet bed sit/sleep state per player.

View File

@@ -54,9 +54,7 @@ public class PetBowlBlockEntity extends ObjBlockEntity {
return 1.0f; // Adjust based on model size
}
// ========================================
// FOOD MANAGEMENT
// ========================================
public boolean hasFood() {
return hasFood && foodLevel > 0;
@@ -102,9 +100,7 @@ public class PetBowlBlockEntity extends ObjBlockEntity {
setChanged();
}
// ========================================
// NBT
// ========================================
@Override
protected void saveAdditional(CompoundTag tag) {

View File

@@ -42,9 +42,7 @@ public class PetCageBlock extends Block implements EntityBlock {
public static final DirectionProperty FACING =
HorizontalDirectionalBlock.FACING;
// ========================================
// OBJ MODEL DIMENSIONS (in model space, before rotation/translation)
// ========================================
private static final double MODEL_MIN_X = -1.137;
private static final double MODEL_MAX_X = 1.0;
private static final double MODEL_MIN_Z = -1.122;
@@ -255,9 +253,7 @@ public class PetCageBlock extends Block implements EntityBlock {
super.onRemove(state, level, pos, newState, isMoving);
}
// ========================================
// CAGE COLLISION (model-accurate)
// ========================================
/**
* Compute the cage AABB in world coordinates for a given master position and facing.
@@ -462,9 +458,7 @@ public class PetCageBlock extends Block implements EntityBlock {
);
}
// ========================================
// MULTI-BLOCK LAYOUT (3x3x2)
// ========================================
private static final int[][] GRID_OFFSETS;

View File

@@ -73,9 +73,7 @@ public class PetCageBlockEntity extends ObjBlockEntity {
);
}
// ========================================
// OCCUPANT MANAGEMENT
// ========================================
public boolean hasOccupant() {
return occupantUUID != null;
@@ -105,9 +103,7 @@ public class PetCageBlockEntity extends ObjBlockEntity {
return server.getPlayerList().getPlayer(occupantUUID);
}
// ========================================
// NBT
// ========================================
@Override
protected void saveAdditional(CompoundTag tag) {

View File

@@ -4,13 +4,13 @@ import com.tiedup.remake.core.TiedUpMod;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.jetbrains.annotations.Nullable;
/**
* Server-side manager for pet cage confinement.

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(

View File

@@ -34,7 +34,10 @@ public class DataDrivenIconBakedModel implements BakedModel {
* @param original the original baked model to wrap (provides quads, transforms, etc.)
* @param overrides the custom overrides that resolve icon models from NBT
*/
public DataDrivenIconBakedModel(BakedModel original, DataDrivenIconOverrides overrides) {
public DataDrivenIconBakedModel(
BakedModel original,
DataDrivenIconOverrides overrides
) {
this.original = original;
this.overrides = overrides;
}
@@ -58,7 +61,11 @@ public class DataDrivenIconBakedModel implements BakedModel {
// ===== Delegated methods =====
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction direction, RandomSource random) {
public List<BakedQuad> getQuads(
@Nullable BlockState state,
@Nullable Direction direction,
RandomSource random
) {
return original.getQuads(state, direction, random);
}

View File

@@ -7,7 +7,6 @@ import com.tiedup.remake.v2.furniture.FurniturePlacerItem;
import com.tiedup.remake.v2.furniture.FurnitureRegistry;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.block.model.ItemOverrides;
@@ -20,6 +19,7 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
/**
* Custom {@link ItemOverrides} that switches the rendered item model based on
@@ -43,7 +43,9 @@ import org.apache.logging.log4j.Logger;
@OnlyIn(Dist.CLIENT)
public class DataDrivenIconOverrides extends ItemOverrides {
private static final Logger LOGGER = LogManager.getLogger("DataDrivenIcons");
private static final Logger LOGGER = LogManager.getLogger(
"DataDrivenIcons"
);
/**
* Identifies which type of NBT-driven item this override handles.
@@ -52,7 +54,7 @@ public class DataDrivenIconOverrides extends ItemOverrides {
/** Data-driven bondage items (reads {@code tiedup_item_id} NBT). */
BONDAGE_ITEM,
/** Furniture placer items (reads {@code tiedup_furniture_id} NBT). */
FURNITURE_PLACER
FURNITURE_PLACER,
}
private final Mode mode;
@@ -65,18 +67,21 @@ public class DataDrivenIconOverrides extends ItemOverrides {
* <p>Values are never null (ConcurrentHashMap forbids nulls). Missing icons
* are tracked separately in {@link #knownMissing}.</p>
*/
private final Map<ResourceLocation, BakedModel> iconModelCache = new ConcurrentHashMap<>();
private final Map<ResourceLocation, BakedModel> iconModelCache =
new ConcurrentHashMap<>();
/**
* Set of icon ResourceLocations that were looked up but not found.
* Prevents repeated failed lookups from hitting the model registry every frame.
*/
private final Map<ResourceLocation, Boolean> knownMissing = new ConcurrentHashMap<>();
private final Map<ResourceLocation, Boolean> knownMissing =
new ConcurrentHashMap<>();
/**
* Set of icon ResourceLocations that we already logged a warning for (to avoid log spam).
*/
private final Map<ResourceLocation, Boolean> warnedMissing = new ConcurrentHashMap<>();
private final Map<ResourceLocation, Boolean> warnedMissing =
new ConcurrentHashMap<>();
public DataDrivenIconOverrides(Mode mode) {
super();
@@ -130,11 +135,14 @@ public class DataDrivenIconOverrides extends ItemOverrides {
switch (mode) {
case BONDAGE_ITEM: {
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(
stack
);
return def != null ? def.icon() : null;
}
case FURNITURE_PLACER: {
String furnitureIdStr = FurniturePlacerItem.getFurnitureIdFromStack(stack);
String furnitureIdStr =
FurniturePlacerItem.getFurnitureIdFromStack(stack);
if (furnitureIdStr == null) return null;
FurnitureDefinition def = FurnitureRegistry.get(furnitureIdStr);
return def != null ? def.icon() : null;
@@ -188,7 +196,10 @@ public class DataDrivenIconOverrides extends ItemOverrides {
// Strategy 3: Try as-is with "inventory" variant
// e.g., "tiedup:armbinder" → ModelResourceLocation("tiedup:armbinder", "inventory")
if (!path.contains("/")) {
ModelResourceLocation mrl = new ModelResourceLocation(iconRL, "inventory");
ModelResourceLocation mrl = new ModelResourceLocation(
iconRL,
"inventory"
);
model = mc.getModelManager().getModel(mrl);
if (model != missingModel) {
return model;
@@ -198,9 +209,14 @@ public class DataDrivenIconOverrides extends ItemOverrides {
// Not found — log once
if (!warnedMissing.containsKey(iconRL)) {
warnedMissing.put(iconRL, Boolean.TRUE);
LOGGER.warn("[DataDrivenIcons] Icon model not found for '{}' (mode={}). "
+ "Ensure a model JSON exists at assets/{}/models/{}.json or the item is registered.",
iconRL, mode, iconRL.getNamespace(), iconRL.getPath());
LOGGER.warn(
"[DataDrivenIcons] Icon model not found for '{}' (mode={}). " +
"Ensure a model JSON exists at assets/{}/models/{}.json or the item is registered.",
iconRL,
mode,
iconRL.getNamespace(),
iconRL.getPath()
);
}
return null;

View File

@@ -141,13 +141,15 @@ public class V2ClientSetup {
DataDrivenIconOverrides overrides = new DataDrivenIconOverrides(mode);
DataDrivenIconBakedModel wrapped = new DataDrivenIconBakedModel(
originalItemModel, overrides
originalItemModel,
overrides
);
models.put(itemModelLoc, wrapped);
TiedUpMod.LOGGER.info(
"[V2ClientSetup] Wrapped {} model with icon overrides (mode={})",
itemName, mode
itemName,
mode
);
}
}

View File

@@ -19,11 +19,13 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
@@ -33,8 +35,6 @@ import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.entity.IEntityAdditionalSpawnData;
@@ -59,25 +59,36 @@ import org.jetbrains.annotations.Nullable;
* Forge to include the {@code IEntityAdditionalSpawnData} buffer in the spawn
* packet. Without this override, the entity would be invisible on clients.</p>
*/
public class EntityFurniture extends Entity
implements ISeatProvider, IEntityAdditionalSpawnData {
public class EntityFurniture
extends Entity
implements ISeatProvider, IEntityAdditionalSpawnData
{
// ========== SynchedEntityData Accessors ==========
/** The furniture definition ID (ResourceLocation string form). */
private static final EntityDataAccessor<String> FURNITURE_ID =
SynchedEntityData.defineId(EntityFurniture.class, EntityDataSerializers.STRING);
SynchedEntityData.defineId(
EntityFurniture.class,
EntityDataSerializers.STRING
);
/**
* Bitmask of locked seats. Bit N corresponds to seat index N
* in {@link FurnitureDefinition#seats()}. Max 8 seats per furniture.
*/
private static final EntityDataAccessor<Byte> SEAT_LOCK_BITS =
SynchedEntityData.defineId(EntityFurniture.class, EntityDataSerializers.BYTE);
SynchedEntityData.defineId(
EntityFurniture.class,
EntityDataSerializers.BYTE
);
/** Current animation state (idle, occupied, locking, struggle). */
private static final EntityDataAccessor<Byte> ANIM_STATE =
SynchedEntityData.defineId(EntityFurniture.class, EntityDataSerializers.BYTE);
SynchedEntityData.defineId(
EntityFurniture.class,
EntityDataSerializers.BYTE
);
// ========== Animation State Constants ==========
@@ -166,7 +177,10 @@ public class EntityFurniture extends Entity
public EntityDimensions getDimensions(Pose pose) {
FurnitureDefinition def = getDefinition();
if (def != null) {
return EntityDimensions.fixed(def.hitboxWidth(), def.hitboxHeight());
return EntityDimensions.fixed(
def.hitboxWidth(),
def.hitboxHeight()
);
}
return EntityDimensions.fixed(1.0f, 1.0f);
}
@@ -228,7 +242,9 @@ public class EntityFurniture extends Entity
if (locked) {
writeFurnitureReconnectionTag(serverPlayer, seatId);
} else {
serverPlayer.getPersistentData().remove("tiedup_locked_furniture");
serverPlayer
.getPersistentData()
.remove("tiedup_locked_furniture");
}
}
}
@@ -258,7 +274,10 @@ public class EntityFurniture extends Entity
* @param player the server player locked in this furniture
* @param seatId the seat ID the player is locked in
*/
private void writeFurnitureReconnectionTag(ServerPlayer player, String seatId) {
private void writeFurnitureReconnectionTag(
ServerPlayer player,
String seatId
) {
CompoundTag tag = new CompoundTag();
BlockPos pos = this.blockPosition();
tag.putInt("x", pos.getX());
@@ -311,13 +330,21 @@ public class EntityFurniture extends Entity
* by spacing seats evenly along the furniture's local right axis.</p>
*/
@Override
protected void positionRider(Entity passenger, Entity.MoveFunction moveFunction) {
protected void positionRider(
Entity passenger,
Entity.MoveFunction moveFunction
) {
if (!this.hasPassenger(passenger)) return;
SeatDefinition seat = getSeatForPassenger(passenger);
if (seat == null) {
// Fallback: center of entity at base height
moveFunction.accept(passenger, this.getX(), this.getY(), this.getZ());
moveFunction.accept(
passenger,
this.getX(),
this.getY(),
this.getZ()
);
return;
}
@@ -326,10 +353,13 @@ public class EntityFurniture extends Entity
if (this.level().isClientSide) {
FurnitureDefinition def = getDefinition();
if (def != null) {
double[] pos = com.tiedup.remake.v2.furniture.client.FurnitureSeatPositionHelper
.getSeatWorldPosition(
def, seat.id(),
this.getX(), this.getY(), this.getZ(),
double[] pos =
com.tiedup.remake.v2.furniture.client.FurnitureSeatPositionHelper.getSeatWorldPosition(
def,
seat.id(),
this.getX(),
this.getY(),
this.getZ(),
this.getYRot()
);
if (pos != null) {
@@ -349,7 +379,8 @@ public class EntityFurniture extends Entity
float yawRad = (float) Math.toRadians(this.getYRot());
double rightX = -Math.sin(yawRad + Math.PI / 2.0);
double rightZ = Math.cos(yawRad + Math.PI / 2.0);
double offset = seatCount == 1 ? 0.0 : (seatIdx - (seatCount - 1) / 2.0);
double offset =
seatCount == 1 ? 0.0 : (seatIdx - (seatCount - 1) / 2.0);
moveFunction.accept(
passenger,
@@ -464,7 +495,10 @@ public class EntityFurniture extends Entity
* @return the best matching seat, or null if no occupied lockable seats exist
*/
@Nullable
private SeatDefinition findNearestOccupiedLockableSeat(Player player, FurnitureDefinition def) {
private SeatDefinition findNearestOccupiedLockableSeat(
Player player,
FurnitureDefinition def
) {
Vec3 playerPos = player.getEyePosition();
Vec3 lookDir = player.getLookAngle();
float yawRad = (float) Math.toRadians(this.getYRot());
@@ -482,7 +516,9 @@ public class EntityFurniture extends Entity
SeatDefinition seat = seats.get(i);
// Only consider occupied, lockable seats
if (!seat.lockable() || !seatAssignments.containsValue(seat.id())) continue;
if (
!seat.lockable() || !seatAssignments.containsValue(seat.id())
) continue;
double offset = seatCount == 1 ? 0.0 : (i - (seatCount - 1) / 2.0);
Vec3 seatWorldPos = new Vec3(
@@ -533,9 +569,20 @@ public class EntityFurniture extends Entity
if (passenger instanceof ServerPlayer sp) {
FurnitureDefinition def = getDefinition();
if (def != null && def.feedback().deniedSound() != null) {
sp.level().playSound(null, sp.getX(), sp.getY(), sp.getZ(),
SoundEvent.createVariableRangeEvent(def.feedback().deniedSound()),
SoundSource.PLAYERS, 0.5f, 1.0f);
sp
.level()
.playSound(
null,
sp.getX(),
sp.getY(),
sp.getZ(),
SoundEvent.createVariableRangeEvent(
def.feedback().deniedSound()
),
SoundSource.PLAYERS,
0.5f,
1.0f
);
}
}
@@ -546,7 +593,11 @@ public class EntityFurniture extends Entity
String lockedSeatId = seat.id();
Entity self = this;
this.getServer().execute(() -> {
if (passenger.isAlive() && self.isAlive() && !self.isRemoved()) {
if (
passenger.isAlive() &&
self.isAlive() &&
!self.isRemoved()
) {
passenger.startRiding(self, true);
// Re-assign the specific seat (not just first available)
assignSeat(passenger, lockedSeatId);
@@ -575,7 +626,9 @@ public class EntityFurniture extends Entity
if (!this.level().isClientSide) {
this.entityData.set(ANIM_STATE, STATE_EXITING);
this.transitionTicksLeft = 20;
this.transitionTargetState = this.getPassengers().isEmpty() ? STATE_IDLE : STATE_OCCUPIED;
this.transitionTargetState = this.getPassengers().isEmpty()
? STATE_IDLE
: STATE_OCCUPIED;
}
// Client-side: immediately stop the furniture pose animation on the dismounting player.
@@ -583,8 +636,9 @@ public class EntityFurniture extends Entity
// The safety tick in AnimationTickHandler also catches this after a 3-tick grace period,
// but stopping immediately avoids a visible animation glitch during dismount.
if (this.level().isClientSide && passenger instanceof Player) {
com.tiedup.remake.client.animation.BondageAnimationManager
.stopFurniture((Player) passenger);
com.tiedup.remake.client.animation.BondageAnimationManager.stopFurniture(
(Player) passenger
);
}
}
@@ -598,7 +652,9 @@ public class EntityFurniture extends Entity
// Don't clobber active transitions — let the tick timer handle the reset
if (this.transitionTicksLeft > 0) return;
byte state = this.getPassengers().isEmpty() ? STATE_IDLE : STATE_OCCUPIED;
byte state = this.getPassengers().isEmpty()
? STATE_IDLE
: STATE_OCCUPIED;
this.entityData.set(ANIM_STATE, state);
}
@@ -628,11 +684,16 @@ public class EntityFurniture extends Entity
// Priority 1: Force-mount a leashed captive onto an available seat.
// The interacting player must be a captor with at least one leashed captive,
// and the captive must wear a collar owned by this player (or player is OP).
if (player instanceof ServerPlayer serverPlayer
&& this.getPassengers().size() < def.seats().size()) {
PlayerBindState ownerState = PlayerBindState.getInstance(serverPlayer);
if (
player instanceof ServerPlayer serverPlayer &&
this.getPassengers().size() < def.seats().size()
) {
PlayerBindState ownerState = PlayerBindState.getInstance(
serverPlayer
);
if (ownerState != null) {
PlayerCaptorManager captorManager = ownerState.getCaptorManager();
PlayerCaptorManager captorManager =
ownerState.getCaptorManager();
if (captorManager != null && captorManager.hasCaptives()) {
for (IBondageState captive : captorManager.getCaptives()) {
LivingEntity captiveEntity = captive.asLivingEntity();
@@ -647,11 +708,18 @@ public class EntityFurniture extends Entity
// Verify collar ownership
if (!captive.hasCollar()) continue;
ItemStack collarStack = captive.getEquipment(BodyRegionV2.NECK);
if (collarStack.isEmpty()
|| !(collarStack.getItem() instanceof ItemCollar collar)) continue;
if (!collar.isOwner(collarStack, serverPlayer)
&& !serverPlayer.hasPermissions(2)) continue;
ItemStack collarStack = captive.getEquipment(
BodyRegionV2.NECK
);
if (
collarStack.isEmpty() ||
!(collarStack.getItem() instanceof
ItemCollar collar)
) continue;
if (
!collar.isOwner(collarStack, serverPlayer) &&
!serverPlayer.hasPermissions(2)
) continue;
// Detach leash only (drop the lead, keep tied-up status)
captive.free(false);
@@ -662,10 +730,18 @@ public class EntityFurniture extends Entity
// Play mount sound
FurnitureFeedback feedback = def.feedback();
if (feedback.mountSound() != null) {
this.level().playSound(null,
this.getX(), this.getY(), this.getZ(),
SoundEvent.createVariableRangeEvent(feedback.mountSound()),
SoundSource.BLOCKS, 1.0f, 1.0f);
this.level().playSound(
null,
this.getX(),
this.getY(),
this.getZ(),
SoundEvent.createVariableRangeEvent(
feedback.mountSound()
),
SoundSource.BLOCKS,
1.0f,
1.0f
);
}
// Broadcast updated state to tracking clients
@@ -688,7 +764,10 @@ public class EntityFurniture extends Entity
// Use look direction to pick the nearest occupied, lockable seat.
ItemStack heldItem = player.getItemInHand(hand);
if (isKeyItem(heldItem) && !this.getPassengers().isEmpty()) {
SeatDefinition targetSeat = findNearestOccupiedLockableSeat(player, def);
SeatDefinition targetSeat = findNearestOccupiedLockableSeat(
player,
def
);
if (targetSeat != null) {
boolean wasLocked = isSeatLocked(targetSeat.id());
setSeatLocked(targetSeat.id(), !wasLocked);
@@ -699,15 +778,24 @@ public class EntityFurniture extends Entity
? feedback.unlockSound()
: feedback.lockSound();
if (soundRL != null) {
this.level().playSound(null, this.getX(), this.getY(), this.getZ(),
this.level().playSound(
null,
this.getX(),
this.getY(),
this.getZ(),
SoundEvent.createVariableRangeEvent(soundRL),
SoundSource.BLOCKS, 1.0f, 1.0f);
SoundSource.BLOCKS,
1.0f,
1.0f
);
}
// Set lock/unlock animation with transition timer
boolean nowLocked = !wasLocked;
this.entityData.set(ANIM_STATE,
nowLocked ? STATE_LOCKING : STATE_UNLOCKING);
this.entityData.set(
ANIM_STATE,
nowLocked ? STATE_LOCKING : STATE_UNLOCKING
);
this.transitionTicksLeft = 20;
this.transitionTargetState = STATE_OCCUPIED;
@@ -723,7 +811,10 @@ public class EntityFurniture extends Entity
}
// Priority 3: Empty hand + available seat -> self mount
if (heldItem.isEmpty() && this.getPassengers().size() < def.seats().size()) {
if (
heldItem.isEmpty() &&
this.getPassengers().size() < def.seats().size()
) {
player.startRiding(this);
return InteractionResult.CONSUME;
}
@@ -731,9 +822,16 @@ public class EntityFurniture extends Entity
// No valid action — play denied sound if configured
FurnitureFeedback feedback = def.feedback();
if (feedback != null && feedback.deniedSound() != null) {
this.level().playSound(null, this.getX(), this.getY(), this.getZ(),
this.level().playSound(
null,
this.getX(),
this.getY(),
this.getZ(),
SoundEvent.createVariableRangeEvent(feedback.deniedSound()),
SoundSource.BLOCKS, 0.5f, 1.0f);
SoundSource.BLOCKS,
0.5f,
1.0f
);
}
return InteractionResult.PASS;
@@ -775,10 +873,22 @@ public class EntityFurniture extends Entity
// Creative mode bypasses this check (handled above with instant discard).
if (!this.getPassengers().isEmpty()) {
FurnitureDefinition occupiedDef = getDefinition();
if (occupiedDef != null && occupiedDef.feedback().deniedSound() != null) {
this.level().playSound(null, this.getX(), this.getY(), this.getZ(),
SoundEvent.createVariableRangeEvent(occupiedDef.feedback().deniedSound()),
SoundSource.BLOCKS, 0.5f, 1.0f);
if (
occupiedDef != null &&
occupiedDef.feedback().deniedSound() != null
) {
this.level().playSound(
null,
this.getX(),
this.getY(),
this.getZ(),
SoundEvent.createVariableRangeEvent(
occupiedDef.feedback().deniedSound()
),
SoundSource.BLOCKS,
0.5f,
1.0f
);
}
return true; // Consume the attack but accumulate no damage
}
@@ -790,7 +900,9 @@ public class EntityFurniture extends Entity
if (this.currentDamage >= resistance) {
this.ejectPassengers();
if (def != null && def.dropOnBreak()) {
ItemStack dropStack = FurniturePlacerItem.createStack(def.id());
ItemStack dropStack = FurniturePlacerItem.createStack(
def.id()
);
if (!dropStack.isEmpty()) {
this.spawnAtLocation(dropStack);
}
@@ -850,9 +962,9 @@ public class EntityFurniture extends Entity
passengerUuids.add(passenger.getUUID());
}
boolean removed = seatAssignments.keySet().removeIf(
uuid -> !passengerUuids.contains(uuid)
);
boolean removed = seatAssignments
.keySet()
.removeIf(uuid -> !passengerUuids.contains(uuid));
if (removed) {
TiedUpMod.LOGGER.debug(
@@ -888,7 +1000,8 @@ public class EntityFurniture extends Entity
@Override
protected void readAdditionalSaveData(CompoundTag tag) {
if (tag.contains(FurnitureRegistry.NBT_FURNITURE_ID, 8)) { // 8 = TAG_String
if (tag.contains(FurnitureRegistry.NBT_FURNITURE_ID, 8)) {
// 8 = TAG_String
setFurnitureId(tag.getString(FurnitureRegistry.NBT_FURNITURE_ID));
}
if (tag.contains("facing")) {
@@ -909,7 +1022,8 @@ public class EntityFurniture extends Entity
// Restore seat assignments
seatAssignments.clear();
if (tag.contains("seat_assignments", 10)) { // 10 = TAG_Compound
if (tag.contains("seat_assignments", 10)) {
// 10 = TAG_Compound
CompoundTag assignments = tag.getCompound("seat_assignments");
for (String key : assignments.getAllKeys()) {
try {

View File

@@ -31,6 +31,11 @@ public record FurnitureFeedback(
) {
/** Empty feedback -- all sounds null (use defaults). */
public static final FurnitureFeedback EMPTY = new FurnitureFeedback(
null, null, null, null, null, null
null,
null,
null,
null,
null,
null
);
}

View File

@@ -32,11 +32,15 @@ import org.jetbrains.annotations.Nullable;
*/
public final class FurnitureParser {
private static final Logger LOGGER = LogManager.getLogger("TiedUpFurniture");
private static final Logger LOGGER = LogManager.getLogger(
"TiedUpFurniture"
);
private static final String TAG = "[FurnitureParser]";
/** Strict hex color pattern: # followed by exactly 6 hex digits. */
private static final Pattern HEX_COLOR = Pattern.compile("^#[0-9A-Fa-f]{6}$");
private static final Pattern HEX_COLOR = Pattern.compile(
"^#[0-9A-Fa-f]{6}$"
);
/** Maximum number of seats per furniture (bitmask limit: 8 bits). */
private static final int MAX_SEATS = 8;
@@ -51,7 +55,10 @@ public final class FurnitureParser {
* @return the parsed definition, or null if the file is invalid
*/
@Nullable
public static FurnitureDefinition parse(InputStream input, ResourceLocation fileId) {
public static FurnitureDefinition parse(
InputStream input,
ResourceLocation fileId
) {
try {
JsonObject root = JsonParser.parseReader(
new InputStreamReader(input, StandardCharsets.UTF_8)
@@ -59,7 +66,12 @@ public final class FurnitureParser {
return parseObject(root, fileId);
} catch (Exception e) {
LOGGER.error("{} Failed to parse JSON {}: {}", TAG, fileId, e.getMessage());
LOGGER.error(
"{} Failed to parse JSON {}: {}",
TAG,
fileId,
e.getMessage()
);
return null;
}
}
@@ -72,7 +84,10 @@ public final class FurnitureParser {
* @return the parsed definition, or null if validation fails
*/
@Nullable
public static FurnitureDefinition parseObject(JsonObject root, ResourceLocation fileId) {
public static FurnitureDefinition parseObject(
JsonObject root,
ResourceLocation fileId
) {
// --- Required: id ---
String idStr = getStringOrNull(root, "id");
if (idStr == null || idStr.isEmpty()) {
@@ -81,7 +96,12 @@ public final class FurnitureParser {
}
ResourceLocation id = ResourceLocation.tryParse(idStr);
if (id == null) {
LOGGER.error("{} Skipping {}: invalid id ResourceLocation '{}'", TAG, fileId, idStr);
LOGGER.error(
"{} Skipping {}: invalid id ResourceLocation '{}'",
TAG,
fileId,
idStr
);
return null;
}
@@ -103,7 +123,12 @@ public final class FurnitureParser {
}
ResourceLocation modelLocation = ResourceLocation.tryParse(modelStr);
if (modelLocation == null) {
LOGGER.error("{} Skipping {}: invalid model ResourceLocation '{}'", TAG, fileId, modelStr);
LOGGER.error(
"{} Skipping {}: invalid model ResourceLocation '{}'",
TAG,
fileId,
modelStr
);
return null;
}
@@ -115,15 +140,27 @@ public final class FurnitureParser {
}
// --- Optional: supports_color (default false) ---
boolean supportsColor = getBooleanOrDefault(root, "supports_color", false);
boolean supportsColor = getBooleanOrDefault(
root,
"supports_color",
false
);
// --- Optional: hitbox (defaults: 1.0 x 1.0, clamped [0.1, 5.0]) ---
float hitboxWidth = 1.0f;
float hitboxHeight = 1.0f;
if (root.has("hitbox") && root.get("hitbox").isJsonObject()) {
JsonObject hitbox = root.getAsJsonObject("hitbox");
hitboxWidth = clamp(getFloatOrDefault(hitbox, "width", 1.0f), 0.1f, 5.0f);
hitboxHeight = clamp(getFloatOrDefault(hitbox, "height", 1.0f), 0.1f, 5.0f);
hitboxWidth = clamp(
getFloatOrDefault(hitbox, "width", 1.0f),
0.1f,
5.0f
);
hitboxHeight = clamp(
getFloatOrDefault(hitbox, "height", 1.0f),
0.1f,
5.0f
);
}
// --- Optional: placement ---
@@ -139,14 +176,22 @@ public final class FurnitureParser {
boolean lockable = getBooleanOrDefault(root, "lockable", false);
// --- Optional: break_resistance (default 100, clamped [1, 10000]) ---
float breakResistance = clamp(getFloatOrDefault(root, "break_resistance", 100.0f), 1.0f, 10000.0f);
float breakResistance = clamp(
getFloatOrDefault(root, "break_resistance", 100.0f),
1.0f,
10000.0f
);
// --- Optional: drop_on_break (default true) ---
boolean dropOnBreak = getBooleanOrDefault(root, "drop_on_break", true);
// --- Required: seats (non-empty array, size [1, 8]) ---
if (!root.has("seats") || !root.get("seats").isJsonArray()) {
LOGGER.error("{} Skipping {}: missing or invalid 'seats' array", TAG, fileId);
LOGGER.error(
"{} Skipping {}: missing or invalid 'seats' array",
TAG,
fileId
);
return null;
}
JsonArray seatsArray = root.getAsJsonArray("seats");
@@ -155,18 +200,33 @@ public final class FurnitureParser {
return null;
}
if (seatsArray.size() > MAX_SEATS) {
LOGGER.error("{} Skipping {}: 'seats' array has {} entries (max {})",
TAG, fileId, seatsArray.size(), MAX_SEATS);
LOGGER.error(
"{} Skipping {}: 'seats' array has {} entries (max {})",
TAG,
fileId,
seatsArray.size(),
MAX_SEATS
);
return null;
}
List<SeatDefinition> seats = new ArrayList<>(seatsArray.size());
for (int i = 0; i < seatsArray.size(); i++) {
if (!seatsArray.get(i).isJsonObject()) {
LOGGER.error("{} Skipping {}: seats[{}] is not a JSON object", TAG, fileId, i);
LOGGER.error(
"{} Skipping {}: seats[{}] is not a JSON object",
TAG,
fileId,
i
);
return null;
}
SeatDefinition seat = parseSeat(seatsArray.get(i).getAsJsonObject(), i, lockable, fileId);
SeatDefinition seat = parseSeat(
seatsArray.get(i).getAsJsonObject(),
i,
lockable,
fileId
);
if (seat == null) {
// parseSeat already logged the error
return null;
@@ -184,15 +244,30 @@ public final class FurnitureParser {
String category = getStringOrDefault(root, "category", "furniture");
// --- Optional: icon (item model ResourceLocation for inventory sprite) ---
ResourceLocation icon = parseOptionalResourceLocation(root, "icon", fileId);
ResourceLocation icon = parseOptionalResourceLocation(
root,
"icon",
fileId
);
return new FurnitureDefinition(
id, displayName, translationKey, modelLocation,
tintChannels, supportsColor,
hitboxWidth, hitboxHeight,
snapToWall, floorOnly,
lockable, breakResistance, dropOnBreak,
seats, feedback, category, icon
id,
displayName,
translationKey,
modelLocation,
tintChannels,
supportsColor,
hitboxWidth,
hitboxHeight,
snapToWall,
floorOnly,
lockable,
breakResistance,
dropOnBreak,
seats,
feedback,
category,
icon
);
}
@@ -208,47 +283,85 @@ public final class FurnitureParser {
* @return the parsed seat, or null on validation failure
*/
@Nullable
private static SeatDefinition parseSeat(JsonObject obj, int index,
boolean parentLockable,
ResourceLocation fileId) {
private static SeatDefinition parseSeat(
JsonObject obj,
int index,
boolean parentLockable,
ResourceLocation fileId
) {
// Required: id (must not contain ':')
String seatId = getStringOrNull(obj, "id");
if (seatId == null || seatId.isEmpty()) {
LOGGER.error("{} Skipping {}: seats[{}] missing 'id'", TAG, fileId, index);
LOGGER.error(
"{} Skipping {}: seats[{}] missing 'id'",
TAG,
fileId,
index
);
return null;
}
if (seatId.contains(":")) {
LOGGER.error("{} Skipping {}: seats[{}] id '{}' must not contain ':'",
TAG, fileId, index, seatId);
LOGGER.error(
"{} Skipping {}: seats[{}] id '{}' must not contain ':'",
TAG,
fileId,
index,
seatId
);
return null;
}
// Required: armature
String armature = getStringOrNull(obj, "armature");
if (armature == null || armature.isEmpty()) {
LOGGER.error("{} Skipping {}: seats[{}] missing 'armature'", TAG, fileId, index);
LOGGER.error(
"{} Skipping {}: seats[{}] missing 'armature'",
TAG,
fileId,
index
);
return null;
}
// Optional: blocked_regions (unknown region = fatal for entire furniture)
Set<BodyRegionV2> blockedRegions = parseBlockedRegions(obj, index, fileId);
Set<BodyRegionV2> blockedRegions = parseBlockedRegions(
obj,
index,
fileId
);
if (blockedRegions == null) {
// parseBlockedRegions returns null ONLY on unknown region name (fatal)
return null;
}
// Optional: lockable (inherits from top-level)
boolean seatLockable = getBooleanOrDefault(obj, "lockable", parentLockable);
boolean seatLockable = getBooleanOrDefault(
obj,
"lockable",
parentLockable
);
// Optional: locked_difficulty (clamped [1, 10000], default 1)
int lockedDifficulty = clampInt(getIntOrDefault(obj, "locked_difficulty", 1), 1, 10000);
int lockedDifficulty = clampInt(
getIntOrDefault(obj, "locked_difficulty", 1),
1,
10000
);
// Optional: item_difficulty_bonus (default false)
boolean itemDifficultyBonus = getBooleanOrDefault(obj, "item_difficulty_bonus", false);
boolean itemDifficultyBonus = getBooleanOrDefault(
obj,
"item_difficulty_bonus",
false
);
return new SeatDefinition(
seatId, armature, blockedRegions,
seatLockable, lockedDifficulty, itemDifficultyBonus
seatId,
armature,
blockedRegions,
seatLockable,
lockedDifficulty,
itemDifficultyBonus
);
}
@@ -257,15 +370,25 @@ public final class FurnitureParser {
* Returns null (fatal) if any region name is unknown.
*/
@Nullable
private static Set<BodyRegionV2> parseBlockedRegions(JsonObject obj, int seatIndex,
ResourceLocation fileId) {
if (!obj.has("blocked_regions") || !obj.get("blocked_regions").isJsonArray()) {
return Collections.unmodifiableSet(EnumSet.noneOf(BodyRegionV2.class));
private static Set<BodyRegionV2> parseBlockedRegions(
JsonObject obj,
int seatIndex,
ResourceLocation fileId
) {
if (
!obj.has("blocked_regions") ||
!obj.get("blocked_regions").isJsonArray()
) {
return Collections.unmodifiableSet(
EnumSet.noneOf(BodyRegionV2.class)
);
}
JsonArray arr = obj.getAsJsonArray("blocked_regions");
if (arr.isEmpty()) {
return Collections.unmodifiableSet(EnumSet.noneOf(BodyRegionV2.class));
return Collections.unmodifiableSet(
EnumSet.noneOf(BodyRegionV2.class)
);
}
EnumSet<BodyRegionV2> regions = EnumSet.noneOf(BodyRegionV2.class);
@@ -274,15 +397,25 @@ public final class FurnitureParser {
try {
name = elem.getAsString().toUpperCase();
} catch (Exception e) {
LOGGER.error("{} Skipping {}: seats[{}] invalid element in 'blocked_regions': {}",
TAG, fileId, seatIndex, e.getMessage());
LOGGER.error(
"{} Skipping {}: seats[{}] invalid element in 'blocked_regions': {}",
TAG,
fileId,
seatIndex,
e.getMessage()
);
return null;
}
BodyRegionV2 region = BodyRegionV2.fromName(name);
if (region == null) {
LOGGER.error("{} Skipping {}: seats[{}] unknown body region '{}'",
TAG, fileId, seatIndex, name);
LOGGER.error(
"{} Skipping {}: seats[{}] unknown body region '{}'",
TAG,
fileId,
seatIndex,
name
);
return null;
}
regions.add(region);
@@ -293,7 +426,10 @@ public final class FurnitureParser {
// ===== Feedback Parsing =====
private static FurnitureFeedback parseFeedback(JsonObject obj, ResourceLocation fileId) {
private static FurnitureFeedback parseFeedback(
JsonObject obj,
ResourceLocation fileId
) {
return new FurnitureFeedback(
parseOptionalResourceLocation(obj, "mount_sound", fileId),
parseOptionalResourceLocation(obj, "lock_sound", fileId),
@@ -311,8 +447,14 @@ public final class FurnitureParser {
* Returns empty map if field is absent. Returns null if any value is invalid hex.
*/
@Nullable
private static Map<String, Integer> parseTintChannels(JsonObject root, ResourceLocation fileId) {
if (!root.has("tint_channels") || !root.get("tint_channels").isJsonObject()) {
private static Map<String, Integer> parseTintChannels(
JsonObject root,
ResourceLocation fileId
) {
if (
!root.has("tint_channels") ||
!root.get("tint_channels").isJsonObject()
) {
return Map.of();
}
@@ -324,15 +466,24 @@ public final class FurnitureParser {
try {
hex = entry.getValue().getAsString();
} catch (Exception e) {
LOGGER.error("{} Skipping {}: tint_channels '{}' value is not a string",
TAG, fileId, entry.getKey());
LOGGER.error(
"{} Skipping {}: tint_channels '{}' value is not a string",
TAG,
fileId,
entry.getKey()
);
return null;
}
if (!HEX_COLOR.matcher(hex).matches()) {
LOGGER.error("{} Skipping {}: tint_channels '{}' has invalid hex color '{}' "
+ "(expected '#' followed by 6 hex digits)",
TAG, fileId, entry.getKey(), hex);
LOGGER.error(
"{} Skipping {}: tint_channels '{}' has invalid hex color '{}' " +
"(expected '#' followed by 6 hex digits)",
TAG,
fileId,
entry.getKey(),
hex
);
return null;
}
@@ -355,12 +506,20 @@ public final class FurnitureParser {
}
}
private static String getStringOrDefault(JsonObject obj, String key, String defaultValue) {
private static String getStringOrDefault(
JsonObject obj,
String key,
String defaultValue
) {
String value = getStringOrNull(obj, key);
return (value != null && !value.isEmpty()) ? value : defaultValue;
}
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();
@@ -369,7 +528,11 @@ public final class FurnitureParser {
}
}
private static float getFloatOrDefault(JsonObject obj, String key, float defaultValue) {
private static float getFloatOrDefault(
JsonObject obj,
String key,
float defaultValue
) {
if (!obj.has(key) || obj.get(key).isJsonNull()) return defaultValue;
try {
return obj.get(key).getAsFloat();
@@ -378,7 +541,11 @@ public final class FurnitureParser {
}
}
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();
@@ -389,13 +556,21 @@ public final class FurnitureParser {
@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("{} In {}: invalid ResourceLocation for '{}': '{}'", TAG, fileId, key, value);
LOGGER.warn(
"{} In {}: invalid ResourceLocation for '{}': '{}'",
TAG,
fileId,
key,
value
);
}
return loc;
}

View File

@@ -86,7 +86,8 @@ public class FurniturePlacerItem extends Item {
// Spawn the furniture entity
EntityFurniture furniture = new EntityFurniture(
ModEntities.FURNITURE.get(), level
ModEntities.FURNITURE.get(),
level
);
furniture.setFurnitureId(furnitureIdStr);
furniture.moveTo(spawnPos.x, spawnPos.y, spawnPos.z);
@@ -101,10 +102,19 @@ public class FurniturePlacerItem extends Item {
// Snap to wall: if enabled, check 4 cardinal directions for an adjacent wall
// and rotate the furniture to face it (back against wall), overriding player yaw.
if (def.snapToWall()) {
Direction[] directions = {Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST};
Direction[] directions = {
Direction.NORTH,
Direction.SOUTH,
Direction.EAST,
Direction.WEST,
};
for (Direction dir : directions) {
BlockPos wallPos = spawnBlockPos.relative(dir);
if (level.getBlockState(wallPos).isFaceSturdy(level, wallPos, dir.getOpposite())) {
if (
level
.getBlockState(wallPos)
.isFaceSturdy(level, wallPos, dir.getOpposite())
) {
yaw = dir.toYRot();
break;
}
@@ -155,9 +165,12 @@ public class FurniturePlacerItem extends Item {
public static ItemStack createStack(ResourceLocation furnitureId) {
if (V2BondageItems.FURNITURE_PLACER == null) return ItemStack.EMPTY;
ItemStack stack = new ItemStack(V2BondageItems.FURNITURE_PLACER.get());
stack.getOrCreateTag().putString(
FurnitureRegistry.NBT_FURNITURE_ID, furnitureId.toString()
);
stack
.getOrCreateTag()
.putString(
FurnitureRegistry.NBT_FURNITURE_ID,
furnitureId.toString()
);
return stack;
}
@@ -173,8 +186,12 @@ public class FurniturePlacerItem extends Item {
public static String getFurnitureIdFromStack(ItemStack stack) {
if (stack.isEmpty() || !stack.hasTag()) return null;
// noinspection DataFlowIssue -- hasTag() guarantees non-null
if (!stack.getTag().contains(FurnitureRegistry.NBT_FURNITURE_ID, 8)) return null;
String id = stack.getTag().getString(FurnitureRegistry.NBT_FURNITURE_ID);
if (
!stack.getTag().contains(FurnitureRegistry.NBT_FURNITURE_ID, 8)
) return null;
String id = stack
.getTag()
.getString(FurnitureRegistry.NBT_FURNITURE_ID);
return id.isEmpty() ? null : id;
}
}

View File

@@ -30,7 +30,10 @@ public final class FurnitureRegistry {
* Volatile reference to an unmodifiable map. {@link #reload} builds a new map
* and swaps atomically; consumer threads always see a consistent snapshot.
*/
private static volatile Map<ResourceLocation, FurnitureDefinition> DEFINITIONS = Map.of();
private static volatile Map<
ResourceLocation,
FurnitureDefinition
> DEFINITIONS = Map.of();
private FurnitureRegistry() {}
@@ -41,7 +44,9 @@ public final class FurnitureRegistry {
*
* @param newDefs the new definitions map (will be defensively copied)
*/
public static void reload(Map<ResourceLocation, FurnitureDefinition> newDefs) {
public static void reload(
Map<ResourceLocation, FurnitureDefinition> newDefs
) {
DEFINITIONS = Collections.unmodifiableMap(new HashMap<>(newDefs));
}

View File

@@ -22,30 +22,45 @@ 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 FurnitureServerReloadListener extends SimplePreparableReloadListener<Void> {
public class FurnitureServerReloadListener
extends SimplePreparableReloadListener<Void>
{
private static final Logger LOGGER = LogManager.getLogger("TiedUpFurniture");
private static final Logger LOGGER = LogManager.getLogger(
"TiedUpFurniture"
);
/** Resource directory containing furniture definition JSON files (under data/). */
private static final String DIRECTORY = "tiedup_furniture";
@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) {
protected void apply(
Void nothing,
ResourceManager resourceManager,
ProfilerFiller profiler
) {
Map<ResourceLocation, FurnitureDefinition> 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();
@@ -55,17 +70,28 @@ public class FurnitureServerReloadListener extends SimplePreparableReloadListene
if (def != null) {
// Check for duplicate IDs
if (newDefs.containsKey(def.id())) {
LOGGER.warn("[TiedUpFurniture] Server: Duplicate furniture ID '{}' from file '{}' -- overwriting previous definition",
def.id(), fileId);
LOGGER.warn(
"[TiedUpFurniture] Server: Duplicate furniture ID '{}' from file '{}' -- overwriting previous definition",
def.id(),
fileId
);
}
newDefs.put(def.id(), def);
LOGGER.debug("[TiedUpFurniture] Server loaded: {} -> '{}'", def.id(), def.displayName());
LOGGER.debug(
"[TiedUpFurniture] Server loaded: {} -> '{}'",
def.id(),
def.displayName()
);
} else {
skipped++;
}
} catch (Exception e) {
LOGGER.error("[TiedUpFurniture] Server: Failed to read resource {}: {}", fileId, e.getMessage());
LOGGER.error(
"[TiedUpFurniture] Server: Failed to read resource {}: {}",
fileId,
e.getMessage()
);
skipped++;
}
}
@@ -77,12 +103,20 @@ public class FurnitureServerReloadListener extends SimplePreparableReloadListene
net.minecraft.server.MinecraftServer server =
net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer();
if (server != null) {
for (net.minecraft.server.level.ServerPlayer p : server.getPlayerList().getPlayers()) {
com.tiedup.remake.v2.furniture.network.PacketSyncFurnitureDefinitions.sendToPlayer(p);
for (net.minecraft.server.level.ServerPlayer p : server
.getPlayerList()
.getPlayers()) {
com.tiedup.remake.v2.furniture.network.PacketSyncFurnitureDefinitions.sendToPlayer(
p
);
}
}
LOGGER.info("[TiedUpFurniture] Server loaded {} furniture definitions ({} skipped) from {} JSON files",
newDefs.size(), skipped, resources.size());
LOGGER.info(
"[TiedUpFurniture] Server loaded {} furniture definitions ({} skipped) from {} JSON files",
newDefs.size(),
skipped,
resources.size()
);
}
}

View File

@@ -14,7 +14,6 @@ import org.jetbrains.annotations.Nullable;
* never EntityFurniture directly.</p>
*/
public interface ISeatProvider {
/** All seat definitions for this entity. */
List<SeatDefinition> getSeats();

View File

@@ -7,11 +7,11 @@ import com.tiedup.remake.v2.BodyRegionV2;
import dev.kosmx.playerAnim.core.data.KeyframeAnimation;
import java.util.HashSet;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
/**
* Builds a {@link KeyframeAnimation} for a player seated on furniture.
@@ -43,7 +43,9 @@ import org.apache.logging.log4j.Logger;
@OnlyIn(Dist.CLIENT)
public final class FurnitureAnimationContext {
private static final Logger LOGGER = LogManager.getLogger("FurnitureAnimation");
private static final Logger LOGGER = LogManager.getLogger(
"FurnitureAnimation"
);
private FurnitureAnimationContext() {}
@@ -61,24 +63,30 @@ public final class FurnitureAnimationContext {
*/
@Nullable
public static KeyframeAnimation create(
GltfData.AnimationClip seatClip,
@Nullable GltfData seatSkeleton,
Set<BodyRegionV2> blockedRegions) {
GltfData.AnimationClip seatClip,
@Nullable GltfData seatSkeleton,
Set<BodyRegionV2> blockedRegions
) {
if (seatClip == null) {
LOGGER.warn("[FurnitureAnim] Cannot create animation: seatClip is null");
LOGGER.warn(
"[FurnitureAnim] Cannot create animation: seatClip is null"
);
return null;
}
if (seatSkeleton == null) {
// V1: skeleton parsing not yet implemented. Furniture animation requires
// rest pose data for glTF-to-PlayerAnimator conversion.
LOGGER.debug("[FurnitureAnim] Seat skeleton unavailable (V1), skipping animation");
LOGGER.debug(
"[FurnitureAnim] Seat skeleton unavailable (V1), skipping animation"
);
return null;
}
if (blockedRegions == null || blockedRegions.isEmpty()) {
LOGGER.debug("[FurnitureAnim] No blocked regions, skipping animation");
LOGGER.debug(
"[FurnitureAnim] No blocked regions, skipping animation"
);
return null;
}
@@ -87,9 +95,15 @@ public final class FurnitureAnimationContext {
KeyframeAnimation fullAnim;
try {
fullAnim = GltfPoseConverter.convertWithSkeleton(
seatSkeleton, seatClip, "furniture_seat");
seatSkeleton,
seatClip,
"furniture_seat"
);
} catch (Exception e) {
LOGGER.error("[FurnitureAnim] Failed to convert seat animation clip", e);
LOGGER.error(
"[FurnitureAnim] Failed to convert seat animation clip",
e
);
return null;
}
@@ -101,7 +115,10 @@ public final class FurnitureAnimationContext {
if (blockedParts.isEmpty()) {
// Blocked regions don't map to any animation bones (e.g., only NECK/FINGERS/TAIL/WINGS)
LOGGER.debug("[FurnitureAnim] Blocked regions {} map to zero bones, skipping", blockedRegions);
LOGGER.debug(
"[FurnitureAnim] Blocked regions {} map to zero bones, skipping",
blockedRegions
);
return null;
}
@@ -119,8 +136,12 @@ public final class FurnitureAnimationContext {
disableParts(builder, disabledParts);
KeyframeAnimation result = builder.build();
LOGGER.debug("[FurnitureAnim] Created animation: blocked={}, enabled={}, disabled={}",
blockedRegions, blockedParts, disabledParts);
LOGGER.debug(
"[FurnitureAnim] Created animation: blocked={}, enabled={}, disabled={}",
blockedRegions,
blockedParts,
disabledParts
);
return result;
}
@@ -135,7 +156,9 @@ public final class FurnitureAnimationContext {
* @param disabledParts set of PlayerAnimator part names to disable
*/
private static void disableParts(
KeyframeAnimation.AnimationBuilder builder, Set<String> disabledParts) {
KeyframeAnimation.AnimationBuilder builder,
Set<String> disabledParts
) {
for (String partName : disabledParts) {
KeyframeAnimation.StateCollection part = builder.getPart(partName);
if (part != null) {

View File

@@ -64,11 +64,18 @@ public class FurnitureEntityRenderer extends EntityRenderer<EntityFurniture> {
GltfData meshData = data.furnitureMesh();
// Compute joint matrices: animated if there is an active clip, static otherwise
GltfData.AnimationClip activeClip = resolveActiveAnimation(entity, meshData);
GltfData.AnimationClip activeClip = resolveActiveAnimation(
entity,
meshData
);
Matrix4f[] joints;
if (activeClip != null) {
float time = computeAnimationTime(entity, activeClip, partialTick);
joints = GltfSkinningEngine.computeJointMatricesAnimated(meshData, activeClip, time);
joints = GltfSkinningEngine.computeJointMatricesAnimated(
meshData,
activeClip,
time
);
} else {
joints = GltfSkinningEngine.computeJointMatrices(meshData);
}
@@ -88,15 +95,26 @@ public class FurnitureEntityRenderer extends EntityRenderer<EntityFurniture> {
// has multiple primitives (tintable and non-tintable parts).
Map<String, Integer> tintColors = def.tintChannels();
if (!tintColors.isEmpty() && meshData.primitives().size() > 1) {
RenderType renderType = GltfMeshRenderer.getRenderTypeForDefaultTexture();
RenderType renderType =
GltfMeshRenderer.getRenderTypeForDefaultTexture();
GltfMeshRenderer.renderSkinnedTinted(
meshData, joints, poseStack, buffer,
packedLight, packedOverlay, renderType, tintColors
meshData,
joints,
poseStack,
buffer,
packedLight,
packedOverlay,
renderType,
tintColors
);
} else {
GltfMeshRenderer.renderSkinned(
meshData, joints, poseStack, buffer,
packedLight, packedOverlay
meshData,
joints,
poseStack,
buffer,
packedLight,
packedOverlay
);
}
@@ -117,19 +135,22 @@ public class FurnitureEntityRenderer extends EntityRenderer<EntityFurniture> {
* @return the resolved animation clip, or null for static rendering
*/
private GltfData.AnimationClip resolveActiveAnimation(
EntityFurniture entity, GltfData meshData
EntityFurniture entity,
GltfData meshData
) {
String animName = switch (entity.getAnimState()) {
case EntityFurniture.STATE_OCCUPIED -> "Occupied";
case EntityFurniture.STATE_LOCKING -> "LockClose";
case EntityFurniture.STATE_STRUGGLE -> "Shake";
case EntityFurniture.STATE_UNLOCKING -> "LockOpen";
case EntityFurniture.STATE_ENTERING -> "Occupied"; // furniture plays Occupied during player enter transition
case EntityFurniture.STATE_EXITING -> "Idle"; // furniture transitions to Idle during player exit
case EntityFurniture.STATE_ENTERING -> "Occupied"; // furniture plays Occupied during player enter transition
case EntityFurniture.STATE_EXITING -> "Idle"; // furniture transitions to Idle during player exit
default -> "Idle";
};
GltfData.AnimationClip clip = meshData.getAnimation(animName);
if (clip == null && entity.getAnimState() != EntityFurniture.STATE_IDLE) {
if (
clip == null && entity.getAnimState() != EntityFurniture.STATE_IDLE
) {
// Specific state animation missing: fall back to Idle
clip = meshData.getAnimation("Idle");
}
@@ -150,7 +171,9 @@ public class FurnitureEntityRenderer extends EntityRenderer<EntityFurniture> {
* @return time in frame-space, suitable for {@link GltfSkinningEngine#computeJointMatricesAnimated}
*/
private float computeAnimationTime(
EntityFurniture entity, GltfData.AnimationClip clip, float partialTick
EntityFurniture entity,
GltfData.AnimationClip clip,
float partialTick
) {
int frameCount = clip.frameCount();
if (frameCount <= 1) return 0f;

View File

@@ -3,7 +3,6 @@ package com.tiedup.remake.v2.furniture.client;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
@@ -11,6 +10,7 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
/**
* Lazy-loading cache for parsed multi-armature furniture GLB data.
@@ -31,14 +31,11 @@ public final class FurnitureGltfCache {
* Sentinel value stored in the cache when loading fails, to avoid retrying
* broken resources on every frame.
*/
private static final FurnitureGltfData FAILED_SENTINEL = new FurnitureGltfData(
null,
Map.of(),
Map.of(),
Map.of()
);
private static final FurnitureGltfData FAILED_SENTINEL =
new FurnitureGltfData(null, Map.of(), Map.of(), Map.of());
private static final Map<ResourceLocation, FurnitureGltfData> CACHE = new ConcurrentHashMap<>();
private static final Map<ResourceLocation, FurnitureGltfData> CACHE =
new ConcurrentHashMap<>();
private FurnitureGltfCache() {}
@@ -51,7 +48,10 @@ public final class FurnitureGltfCache {
*/
@Nullable
public static FurnitureGltfData get(ResourceLocation modelLocation) {
FurnitureGltfData cached = CACHE.computeIfAbsent(modelLocation, FurnitureGltfCache::load);
FurnitureGltfData cached = CACHE.computeIfAbsent(
modelLocation,
FurnitureGltfCache::load
);
return cached == FAILED_SENTINEL ? null : cached;
}
@@ -72,12 +72,19 @@ public final class FurnitureGltfCache {
}
try (InputStream is = resource.open()) {
FurnitureGltfData data = FurnitureGlbParser.parse(is, loc.toString());
FurnitureGltfData data = FurnitureGlbParser.parse(
is,
loc.toString()
);
LOGGER.debug("[FurnitureGltf] Cached: {}", loc);
return data;
}
} catch (Exception e) {
LOGGER.error("[FurnitureGltf] Failed to load furniture GLB: {}", loc, e);
LOGGER.error(
"[FurnitureGltf] Failed to load furniture GLB: {}",
loc,
e
);
return FAILED_SENTINEL;
}
}

View File

@@ -38,7 +38,9 @@ public final class FurnitureSeatPositionHelper {
public static double[] getSeatWorldPosition(
FurnitureDefinition def,
String seatId,
double furnitureX, double furnitureY, double furnitureZ,
double furnitureX,
double furnitureY,
double furnitureZ,
float furnitureYRot
) {
ResourceLocation modelLoc = def.modelLocation();
@@ -47,7 +49,9 @@ public final class FurnitureSeatPositionHelper {
FurnitureGltfData gltfData = FurnitureGltfCache.get(modelLoc);
if (gltfData == null) return null;
FurnitureGltfData.SeatTransform transform = gltfData.seatTransforms().get(seatId);
FurnitureGltfData.SeatTransform transform = gltfData
.seatTransforms()
.get(seatId);
if (transform == null) return null;
// The seat transform position is in Minecraft model space (post-conversion):
@@ -67,7 +71,7 @@ public final class FurnitureSeatPositionHelper {
return new double[] {
furnitureX + rx,
furnitureY + sy,
furnitureZ + rz
furnitureZ + rz,
};
}
}

View File

@@ -88,7 +88,10 @@ public class PacketFurnitureEscape {
ctx.setPacketHandled(true);
}
private static void handleOnServer(PacketFurnitureEscape msg, NetworkEvent.Context ctx) {
private static void handleOnServer(
PacketFurnitureEscape msg,
NetworkEvent.Context ctx
) {
ServerPlayer sender = ctx.getSender();
if (sender == null) return;
@@ -103,10 +106,14 @@ public class PacketFurnitureEscape {
if (!entity.isAlive() || entity.isRemoved()) return;
// Validate escape method
if (msg.escapeMethod != METHOD_STRUGGLE && msg.escapeMethod != METHOD_LOCKPICK) {
if (
msg.escapeMethod != METHOD_STRUGGLE &&
msg.escapeMethod != METHOD_LOCKPICK
) {
TiedUpMod.LOGGER.warn(
"[PacketFurnitureEscape] Invalid escape method {} from {}",
msg.escapeMethod, sender.getName().getString()
msg.escapeMethod,
sender.getName().getString()
);
return;
}
@@ -133,7 +140,8 @@ public class PacketFurnitureEscape {
if (!furnitureEntity.hasPassenger(sender)) {
TiedUpMod.LOGGER.debug(
"[PacketFurnitureEscape] Struggle: {} is not a passenger of furniture {}",
sender.getName().getString(), furnitureEntity.getId()
sender.getName().getString(),
furnitureEntity.getId()
);
return;
}
@@ -160,12 +168,18 @@ public class PacketFurnitureEscape {
// Compute difficulty
int baseDifficulty = provider.getLockedDifficulty(seat.id());
int itemBonus = computeItemDifficultyBonus(sender, provider, seat);
int totalDifficulty = Math.min(baseDifficulty + itemBonus, MAX_DIFFICULTY);
int totalDifficulty = Math.min(
baseDifficulty + itemBonus,
MAX_DIFFICULTY
);
TiedUpMod.LOGGER.debug(
"[PacketFurnitureEscape] Struggle: {} on seat '{}' — difficulty {} (base {} + items {})",
sender.getName().getString(), seat.id(),
totalDifficulty, baseDifficulty, itemBonus
sender.getName().getString(),
seat.id(),
totalDifficulty,
baseDifficulty,
itemBonus
);
// Difficulty 0: immediate escape (no minigame needed)
@@ -181,13 +195,16 @@ public class PacketFurnitureEscape {
TiedUpMod.LOGGER.info(
"[PacketFurnitureEscape] {} escaped furniture {} (difficulty was 0)",
sender.getName().getString(), furnitureEntity.getId()
sender.getName().getString(),
furnitureEntity.getId()
);
return;
}
// Respect server config: if struggle minigame is disabled, skip
if (!com.tiedup.remake.core.ModConfig.SERVER.struggleMiniGameEnabled.get()) {
if (
!com.tiedup.remake.core.ModConfig.SERVER.struggleMiniGameEnabled.get()
) {
TiedUpMod.LOGGER.debug(
"[PacketFurnitureEscape] Struggle minigame disabled by server config"
);
@@ -196,9 +213,13 @@ public class PacketFurnitureEscape {
// Launch struggle minigame session via StruggleSessionManager
StruggleSessionManager manager = StruggleSessionManager.getInstance();
ContinuousStruggleMiniGameState session = manager.startFurnitureStruggleSession(
sender, furnitureEntity.getId(), seat.id(), totalDifficulty
);
ContinuousStruggleMiniGameState session =
manager.startFurnitureStruggleSession(
sender,
furnitureEntity.getId(),
seat.id(),
totalDifficulty
);
if (session != null) {
// Send START packet to open the struggle GUI on the client
@@ -255,7 +276,11 @@ public class PacketFurnitureEscape {
// Use look-direction-based seat targeting (same vector math as
// EntityFurniture.findNearestOccupiedLockableSeat) instead of
// blindly picking the first locked seat.
SeatDefinition targetSeat = findNearestLockedOccupiedSeat(sender, provider, furnitureEntity);
SeatDefinition targetSeat = findNearestLockedOccupiedSeat(
sender,
provider,
furnitureEntity
);
if (targetSeat == null) {
TiedUpMod.LOGGER.debug(
"[PacketFurnitureEscape] Lockpick: no locked occupied seat found"
@@ -264,30 +289,52 @@ public class PacketFurnitureEscape {
}
// Find the passenger in this seat (needed for item bonus computation)
Entity passenger = findPassengerInSeat(provider, furnitureEntity, targetSeat.id());
Entity passenger = findPassengerInSeat(
provider,
furnitureEntity,
targetSeat.id()
);
// Compute difficulty
int baseDifficulty = provider.getLockedDifficulty(targetSeat.id());
int itemBonus = 0;
if (passenger instanceof LivingEntity livingPassenger) {
itemBonus = computeItemDifficultyBonus(livingPassenger, provider, targetSeat);
itemBonus = computeItemDifficultyBonus(
livingPassenger,
provider,
targetSeat
);
}
int totalDifficulty = Math.min(baseDifficulty + itemBonus, MAX_DIFFICULTY);
int totalDifficulty = Math.min(
baseDifficulty + itemBonus,
MAX_DIFFICULTY
);
TiedUpMod.LOGGER.debug(
"[PacketFurnitureEscape] Lockpick: {} on seat '{}' -- difficulty {} (base {} + items {})",
sender.getName().getString(), targetSeat.id(),
totalDifficulty, baseDifficulty, itemBonus
sender.getName().getString(),
targetSeat.id(),
totalDifficulty,
baseDifficulty,
itemBonus
);
// Difficulty 0: immediate success — unlock + dismount + consume lockpick
if (totalDifficulty == 0) {
completeLockpickSuccess(sender, furnitureEntity, provider, targetSeat, passenger);
completeLockpickSuccess(
sender,
furnitureEntity,
provider,
targetSeat,
passenger
);
return;
}
// Respect server config: if lockpick minigame is disabled, skip
if (!com.tiedup.remake.core.ModConfig.SERVER.lockpickMiniGameEnabled.get()) {
if (
!com.tiedup.remake.core.ModConfig.SERVER.lockpickMiniGameEnabled.get()
) {
TiedUpMod.LOGGER.debug(
"[PacketFurnitureEscape] Lockpick minigame disabled by server config"
);
@@ -298,11 +345,15 @@ public class PacketFurnitureEscape {
ItemStack lockpickStack = ItemLockpick.findLockpickInInventory(sender);
if (lockpickStack.isEmpty()) return; // double-check; hasLockpickInInventory passed above
int remainingUses = lockpickStack.getMaxDamage() - lockpickStack.getDamageValue();
int remainingUses =
lockpickStack.getMaxDamage() - lockpickStack.getDamageValue();
// Sweet spot width scales inversely with difficulty: harder locks = narrower sweet spot.
// Base width 0.15 at difficulty 1, down to 0.03 at MAX_DIFFICULTY.
float sweetSpotWidth = Math.max(0.03f, 0.15f - (totalDifficulty / (float) MAX_DIFFICULTY) * 0.12f);
float sweetSpotWidth = Math.max(
0.03f,
0.15f - (totalDifficulty / (float) MAX_DIFFICULTY) * 0.12f
);
// Start lockpick session via LockpickSessionManager.
// The existing lockpick session uses a targetSlot (BodyRegionV2 ordinal) for
@@ -311,7 +362,8 @@ public class PacketFurnitureEscape {
// For now, we use the simplified approach: start the session and let the existing
// PacketLockpickAttempt handler manage the sweet-spot interaction. On success,
// the furniture-specific completion is handled by a post-session check.
LockpickSessionManager lockpickManager = LockpickSessionManager.getInstance();
LockpickSessionManager lockpickManager =
LockpickSessionManager.getInstance();
LockpickMiniGameState session = lockpickManager.startLockpickSession(
sender,
furnitureEntity.getId(), // repurpose targetSlot as entity ID
@@ -350,8 +402,11 @@ public class PacketFurnitureEscape {
TiedUpMod.LOGGER.info(
"[PacketFurnitureEscape] {} started lockpick on seat '{}' of furniture {} (difficulty {}, sweet spot width {})",
sender.getName().getString(), targetSeat.id(),
furnitureEntity.getId(), totalDifficulty, sweetSpotWidth
sender.getName().getString(),
targetSeat.id(),
furnitureEntity.getId(),
totalDifficulty,
sweetSpotWidth
);
}
@@ -368,7 +423,9 @@ public class PacketFurnitureEscape {
) {
provider.setSeatLocked(targetSeat.id(), false);
if (passenger instanceof ServerPlayer passengerPlayer) {
passengerPlayer.getPersistentData().remove("tiedup_locked_furniture");
passengerPlayer
.getPersistentData()
.remove("tiedup_locked_furniture");
}
if (passenger != null) {
passenger.stopRiding();
@@ -378,7 +435,9 @@ public class PacketFurnitureEscape {
ItemStack lockpickStack = ItemLockpick.findLockpickInInventory(sender);
if (!lockpickStack.isEmpty()) {
lockpickStack.setDamageValue(lockpickStack.getDamageValue() + 1);
if (lockpickStack.getDamageValue() >= lockpickStack.getMaxDamage()) {
if (
lockpickStack.getDamageValue() >= lockpickStack.getMaxDamage()
) {
lockpickStack.shrink(1);
}
}
@@ -390,7 +449,9 @@ public class PacketFurnitureEscape {
TiedUpMod.LOGGER.info(
"[PacketFurnitureEscape] {} lockpicked seat '{}' on furniture {} (difficulty was 0)",
sender.getName().getString(), targetSeat.id(), furnitureEntity.getId()
sender.getName().getString(),
targetSeat.id(),
furnitureEntity.getId()
);
}
@@ -411,8 +472,11 @@ public class PacketFurnitureEscape {
return 0;
}
Set<BodyRegionV2> blockedRegions = provider.getBlockedRegions(seat.id());
Map<BodyRegionV2, ItemStack> equipped = V2EquipmentHelper.getAllEquipped(passenger);
Set<BodyRegionV2> blockedRegions = provider.getBlockedRegions(
seat.id()
);
Map<BodyRegionV2, ItemStack> equipped =
V2EquipmentHelper.getAllEquipped(passenger);
int bonus = 0;
for (Map.Entry<BodyRegionV2, ItemStack> entry : equipped.entrySet()) {
@@ -436,7 +500,9 @@ public class PacketFurnitureEscape {
if (stack.getItem() instanceof ItemLockpick) return true;
}
// Also check offhand
if (player.getOffhandItem().getItem() instanceof ItemLockpick) return true;
if (
player.getOffhandItem().getItem() instanceof ItemLockpick
) return true;
return false;
}
@@ -526,7 +592,9 @@ public class PacketFurnitureEscape {
String seatId
) {
for (Entity passenger : furnitureEntity.getPassengers()) {
SeatDefinition passengerSeat = provider.getSeatForPassenger(passenger);
SeatDefinition passengerSeat = provider.getSeatForPassenger(
passenger
);
if (passengerSeat != null && passengerSeat.id().equals(seatId)) {
return passenger;
}

View File

@@ -1,11 +1,11 @@
package com.tiedup.remake.v2.furniture.network;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.furniture.EntityFurniture;
import com.tiedup.remake.v2.furniture.FurnitureDefinition;
import com.tiedup.remake.v2.furniture.FurnitureFeedback;
@@ -48,7 +48,10 @@ public class PacketFurnitureForcemount {
// ==================== Codec ====================
public static void encode(PacketFurnitureForcemount msg, FriendlyByteBuf buf) {
public static void encode(
PacketFurnitureForcemount msg,
FriendlyByteBuf buf
) {
buf.writeInt(msg.furnitureEntityId);
buf.writeUUID(msg.captiveUUID);
}
@@ -68,7 +71,10 @@ public class PacketFurnitureForcemount {
ctx.setPacketHandled(true);
}
private static void handleOnServer(PacketFurnitureForcemount msg, NetworkEvent.Context ctx) {
private static void handleOnServer(
PacketFurnitureForcemount msg,
NetworkEvent.Context ctx
) {
ServerPlayer sender = ctx.getSender();
if (sender == null) return;
@@ -126,8 +132,10 @@ public class PacketFurnitureForcemount {
}
ItemStack collarStack = captiveState.getEquipment(BodyRegionV2.NECK);
if (collarStack.isEmpty()
|| !(collarStack.getItem() instanceof ItemCollar collar)) {
if (
collarStack.isEmpty() ||
!(collarStack.getItem() instanceof ItemCollar collar)
) {
TiedUpMod.LOGGER.debug(
"[PacketFurnitureForcemount] Invalid collar item on captive"
);
@@ -174,13 +182,21 @@ public class PacketFurnitureForcemount {
FurnitureFeedback feedback = def.feedback();
ResourceLocation mountSoundRL = feedback.mountSound();
if (mountSoundRL != null) {
SoundEvent sound = SoundEvent.createVariableRangeEvent(mountSoundRL);
entity.level().playSound(
null,
entity.getX(), entity.getY(), entity.getZ(),
sound, SoundSource.BLOCKS,
1.0f, 1.0f
SoundEvent sound = SoundEvent.createVariableRangeEvent(
mountSoundRL
);
entity
.level()
.playSound(
null,
entity.getX(),
entity.getY(),
entity.getZ(),
sound,
SoundSource.BLOCKS,
1.0f,
1.0f
);
}
}
@@ -202,15 +218,20 @@ public class PacketFurnitureForcemount {
* Find a living entity by UUID within a reasonable range of the sender.
* Checks players first (O(1) lookup), then falls back to entity search.
*/
private static LivingEntity findCaptiveByUUID(ServerPlayer sender, UUID uuid) {
private static LivingEntity findCaptiveByUUID(
ServerPlayer sender,
UUID uuid
) {
// Try player lookup first (fast)
net.minecraft.world.entity.player.Player player =
sender.level().getPlayerByUUID(uuid);
net.minecraft.world.entity.player.Player player = sender
.level()
.getPlayerByUUID(uuid);
if (player != null) return player;
// Search nearby entities (64 block radius)
AABB searchBox = sender.getBoundingBox().inflate(64);
for (LivingEntity nearby : sender.level()
for (LivingEntity nearby : sender
.level()
.getEntitiesOfClass(LivingEntity.class, searchBox)) {
if (nearby.getUUID().equals(uuid)) {
return nearby;
@@ -224,12 +245,20 @@ public class PacketFurnitureForcemount {
*
* @return the seat ID, or null if all seats are occupied
*/
private static String findFirstAvailableSeat(ISeatProvider provider, Entity furniture) {
private static String findFirstAvailableSeat(
ISeatProvider provider,
Entity furniture
) {
for (SeatDefinition seat : provider.getSeats()) {
boolean occupied = false;
for (Entity passenger : furniture.getPassengers()) {
SeatDefinition passengerSeat = provider.getSeatForPassenger(passenger);
if (passengerSeat != null && passengerSeat.id().equals(seat.id())) {
SeatDefinition passengerSeat = provider.getSeatForPassenger(
passenger
);
if (
passengerSeat != null &&
passengerSeat.id().equals(seat.id())
) {
occupied = true;
break;
}

View File

@@ -63,7 +63,10 @@ public class PacketFurnitureLock {
ctx.setPacketHandled(true);
}
private static void handleOnServer(PacketFurnitureLock msg, NetworkEvent.Context ctx) {
private static void handleOnServer(
PacketFurnitureLock msg,
NetworkEvent.Context ctx
) {
ServerPlayer sender = ctx.getSender();
if (sender == null) return;
@@ -78,8 +81,9 @@ public class PacketFurnitureLock {
if (!entity.isAlive() || entity.isRemoved()) return;
// Sender must hold a key item in either hand
boolean hasKey = (sender.getMainHandItem().getItem() instanceof ItemMasterKey)
|| (sender.getOffhandItem().getItem() instanceof ItemMasterKey);
boolean hasKey =
(sender.getMainHandItem().getItem() instanceof ItemMasterKey) ||
(sender.getOffhandItem().getItem() instanceof ItemMasterKey);
if (!hasKey) {
TiedUpMod.LOGGER.debug(
"[PacketFurnitureLock] {} does not hold a key item in either hand",
@@ -93,7 +97,8 @@ public class PacketFurnitureLock {
if (seat == null) {
TiedUpMod.LOGGER.debug(
"[PacketFurnitureLock] Seat '{}' not found on entity {}",
msg.seatId, msg.entityId
msg.seatId,
msg.entityId
);
return;
}
@@ -135,22 +140,32 @@ public class PacketFurnitureLock {
? feedback.unlockSound()
: feedback.lockSound();
if (soundRL != null) {
SoundEvent sound = SoundEvent.createVariableRangeEvent(soundRL);
entity.level().playSound(
null, // null = play for all nearby players
entity.getX(), entity.getY(), entity.getZ(),
sound, SoundSource.BLOCKS,
1.0f, 1.0f
SoundEvent sound = SoundEvent.createVariableRangeEvent(
soundRL
);
entity
.level()
.playSound(
null, // null = play for all nearby players
entity.getX(),
entity.getY(),
entity.getZ(),
sound,
SoundSource.BLOCKS,
1.0f,
1.0f
);
}
}
// Set lock/unlock animation state. The next updateAnimState() call
// (from tick or passenger change) will reset it to OCCUPIED/IDLE.
boolean nowLocked = !wasLocked;
furniture.setAnimState(nowLocked
? EntityFurniture.STATE_LOCKING
: EntityFurniture.STATE_UNLOCKING);
furniture.setAnimState(
nowLocked
? EntityFurniture.STATE_LOCKING
: EntityFurniture.STATE_UNLOCKING
);
// Broadcast updated state to all tracking clients
PacketSyncFurnitureState.sendToTracking(furniture);
@@ -162,7 +177,10 @@ public class PacketFurnitureLock {
/**
* Find a SeatDefinition by ID from the provider's seat list.
*/
private static SeatDefinition findSeatById(ISeatProvider provider, String seatId) {
private static SeatDefinition findSeatById(
ISeatProvider provider,
String seatId
) {
for (SeatDefinition seat : provider.getSeats()) {
if (seat.id().equals(seatId)) return seat;
}
@@ -178,7 +196,9 @@ public class PacketFurnitureLock {
String seatId
) {
for (Entity passenger : furnitureEntity.getPassengers()) {
SeatDefinition passengerSeat = provider.getSeatForPassenger(passenger);
SeatDefinition passengerSeat = provider.getSeatForPassenger(
passenger
);
if (passengerSeat != null && passengerSeat.id().equals(seatId)) {
return true;
}

View File

@@ -40,7 +40,9 @@ import org.apache.logging.log4j.Logger;
*/
public class PacketSyncFurnitureDefinitions {
private static final Logger LOGGER = LogManager.getLogger("PacketSyncFurnitureDefinitions");
private static final Logger LOGGER = LogManager.getLogger(
"PacketSyncFurnitureDefinitions"
);
/**
* Safety cap on the number of definitions to prevent memory exhaustion
@@ -65,13 +67,18 @@ public class PacketSyncFurnitureDefinitions {
private final Map<ResourceLocation, FurnitureDefinition> definitions;
public PacketSyncFurnitureDefinitions(Map<ResourceLocation, FurnitureDefinition> definitions) {
public PacketSyncFurnitureDefinitions(
Map<ResourceLocation, FurnitureDefinition> definitions
) {
this.definitions = definitions;
}
// ==================== Codec ====================
public static void encode(PacketSyncFurnitureDefinitions msg, FriendlyByteBuf buf) {
public static void encode(
PacketSyncFurnitureDefinitions msg,
FriendlyByteBuf buf
) {
buf.writeVarInt(msg.definitions.size());
for (FurnitureDefinition def : msg.definitions.values()) {
@@ -91,7 +98,9 @@ public class PacketSyncFurnitureDefinitions {
// Tint channels
buf.writeVarInt(def.tintChannels().size());
for (Map.Entry<String, Integer> entry : def.tintChannels().entrySet()) {
for (Map.Entry<String, Integer> entry : def
.tintChannels()
.entrySet()) {
buf.writeUtf(entry.getKey());
buf.writeInt(entry.getValue());
}
@@ -138,7 +147,10 @@ public class PacketSyncFurnitureDefinitions {
buf.writeBoolean(seat.itemDifficultyBonus());
}
private static void encodeFeedback(FurnitureFeedback feedback, FriendlyByteBuf buf) {
private static void encodeFeedback(
FurnitureFeedback feedback,
FriendlyByteBuf buf
) {
writeOptionalRL(buf, feedback.mountSound());
writeOptionalRL(buf, feedback.lockSound());
writeOptionalRL(buf, feedback.unlockSound());
@@ -147,7 +159,10 @@ public class PacketSyncFurnitureDefinitions {
writeOptionalRL(buf, feedback.deniedSound());
}
private static void writeOptionalRL(FriendlyByteBuf buf, ResourceLocation rl) {
private static void writeOptionalRL(
FriendlyByteBuf buf,
ResourceLocation rl
) {
buf.writeBoolean(rl != null);
if (rl != null) {
buf.writeResourceLocation(rl);
@@ -203,11 +218,23 @@ public class PacketSyncFurnitureDefinitions {
ResourceLocation icon = readOptionalRL(buf);
FurnitureDefinition def = new FurnitureDefinition(
id, displayName, translationKey, modelLocation,
Map.copyOf(tintChannels), supportsColor,
hitboxWidth, hitboxHeight, snapToWall, floorOnly,
lockable, breakResistance, dropOnBreak,
List.copyOf(seats), feedback, category, icon
id,
displayName,
translationKey,
modelLocation,
Map.copyOf(tintChannels),
supportsColor,
hitboxWidth,
hitboxHeight,
snapToWall,
floorOnly,
lockable,
breakResistance,
dropOnBreak,
List.copyOf(seats),
feedback,
category,
icon
);
defs.put(id, def);
@@ -235,8 +262,12 @@ public class PacketSyncFurnitureDefinitions {
boolean itemDifficultyBonus = buf.readBoolean();
return new SeatDefinition(
id, armatureName, Set.copyOf(blockedRegions),
lockable, lockedDifficulty, itemDifficultyBonus
id,
armatureName,
Set.copyOf(blockedRegions),
lockable,
lockedDifficulty,
itemDifficultyBonus
);
}
@@ -249,8 +280,12 @@ public class PacketSyncFurnitureDefinitions {
ResourceLocation deniedSound = readOptionalRL(buf);
return new FurnitureFeedback(
mountSound, lockSound, unlockSound,
struggleLoopSound, escapeSound, deniedSound
mountSound,
lockSound,
unlockSound,
struggleLoopSound,
escapeSound,
deniedSound
);
}
@@ -276,8 +311,10 @@ public class PacketSyncFurnitureDefinitions {
@OnlyIn(Dist.CLIENT)
private static void handleOnClient(PacketSyncFurnitureDefinitions msg) {
FurnitureRegistry.reload(msg.definitions);
LOGGER.debug("Client received {} furniture definitions from server",
msg.definitions.size());
LOGGER.debug(
"Client received {} furniture definitions from server",
msg.definitions.size()
);
}
// ==================== Server-side Helpers ====================

View File

@@ -33,7 +33,11 @@ public class PacketSyncFurnitureState {
private final byte lockBits;
private final byte animState;
public PacketSyncFurnitureState(int entityId, byte lockBits, byte animState) {
public PacketSyncFurnitureState(
int entityId,
byte lockBits,
byte animState
) {
this.entityId = entityId;
this.lockBits = lockBits;
this.animState = animState;
@@ -41,7 +45,10 @@ public class PacketSyncFurnitureState {
// ==================== Codec ====================
public static void encode(PacketSyncFurnitureState msg, FriendlyByteBuf buf) {
public static void encode(
PacketSyncFurnitureState msg,
FriendlyByteBuf buf
) {
buf.writeInt(msg.entityId);
buf.writeByte(msg.lockBits);
buf.writeByte(msg.animState);