init / code cleanup #2

Merged
NotEvil merged 1 commits from init/code-cleanup into develop 2026-04-11 23:27:12 +00:00
482 changed files with 8500 additions and 5155 deletions

View File

@@ -138,6 +138,16 @@ idea: ## Generate IntelliJ IDEA run configurations
eclipse: ## Generate Eclipse project files eclipse: ## Generate Eclipse project files
@$(GRADLE) eclipse @$(GRADLE) eclipse
##@ Formatting
.PHONY: format
format: ## Format code using Prettier
@npx --yes prettier --plugin prettier-plugin-java --tab-width 4 --write "src/**/*.java"
.PHONY: check-format
check-format: ## Check code formatting
@npx --yes prettier --plugin prettier-plugin-java --tab-width 4 --check "src/**/*.java"
##@ Information ##@ Information
.PHONY: info .PHONY: info

View File

@@ -9,7 +9,6 @@ import net.minecraft.world.level.material.MapColor;
/** /**
* Cell Door Block - Iron-like door that cannot be opened by hand. * Cell Door Block - Iron-like door that cannot be opened by hand.
* *
* Phase 16: Blocks
* *
* Features: * Features:
* - Cannot be opened by clicking (requires redstone) * - Cannot be opened by clicking (requires redstone)

View File

@@ -37,7 +37,6 @@ import net.minecraft.world.phys.BlockHitResult;
/** /**
* Kidnap Bomb Block - TNT that applies bondage on explosion. * Kidnap Bomb Block - TNT that applies bondage on explosion.
* *
* Phase 16: Blocks
* *
* Features: * Features:
* - TNT-like block that can be ignited * - TNT-like block that can be ignited
@@ -61,9 +60,7 @@ public class BlockKidnapBomb
); );
} }
// ========================================
// BLOCK ENTITY // BLOCK ENTITY
// ========================================
@Nullable @Nullable
@Override @Override
@@ -82,9 +79,7 @@ public class BlockKidnapBomb
: null; : null;
} }
// ========================================
// EXPLOSION HANDLING // EXPLOSION HANDLING
// ========================================
@Override @Override
public void onCaughtFire( public void onCaughtFire(
@@ -139,9 +134,7 @@ public class BlockKidnapBomb
} }
} }
// ========================================
// LOADING ITEMS // LOADING ITEMS
// ========================================
@Override @Override
public InteractionResult use( public InteractionResult use(
@@ -199,9 +192,7 @@ public class BlockKidnapBomb
return InteractionResult.PASS; return InteractionResult.PASS;
} }
// ========================================
// DROPS WITH NBT // DROPS WITH NBT
// ========================================
@Override @Override
public List<ItemStack> getDrops( public List<ItemStack> getDrops(
@@ -225,9 +216,7 @@ public class BlockKidnapBomb
return List.of(stack); return List.of(stack);
} }
// ========================================
// TOOLTIP // TOOLTIP
// ========================================
@Override @Override
public void appendHoverText( public void appendHoverText(

View File

@@ -43,7 +43,6 @@ import net.minecraft.world.phys.shapes.VoxelShape;
/** /**
* Rope Trap Block - Trap that ties up entities when they walk on it. * Rope Trap Block - Trap that ties up entities when they walk on it.
* *
* Phase 16: Blocks
* *
* Features: * Features:
* - Flat block (1 pixel tall) placed on solid surfaces * - Flat block (1 pixel tall) placed on solid surfaces
@@ -76,9 +75,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
); );
} }
// ========================================
// SHAPE AND RENDERING // SHAPE AND RENDERING
// ========================================
@Override @Override
public VoxelShape getShape( public VoxelShape getShape(
@@ -105,9 +102,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
return RenderShape.MODEL; return RenderShape.MODEL;
} }
// ========================================
// PLACEMENT RULES // PLACEMENT RULES
// ========================================
@Override @Override
public boolean canSurvive( public boolean canSurvive(
@@ -159,9 +154,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
); );
} }
// ========================================
// BLOCK ENTITY // BLOCK ENTITY
// ========================================
@Nullable @Nullable
@Override @Override
@@ -175,9 +168,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
return be instanceof TrapBlockEntity ? (TrapBlockEntity) be : null; return be instanceof TrapBlockEntity ? (TrapBlockEntity) be : null;
} }
// ========================================
// TRAP TRIGGER // TRAP TRIGGER
// ========================================
@Override @Override
public void entityInside( public void entityInside(
@@ -239,9 +230,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
); );
} }
// ========================================
// LOADING ITEMS // LOADING ITEMS
// ========================================
@Override @Override
public InteractionResult use( public InteractionResult use(
@@ -293,9 +282,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
return InteractionResult.PASS; return InteractionResult.PASS;
} }
// ========================================
// DROPS WITH NBT // DROPS WITH NBT
// ========================================
@Override @Override
public List<ItemStack> getDrops( public List<ItemStack> getDrops(
@@ -319,9 +306,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
return List.of(stack); return List.of(stack);
} }
// ========================================
// TOOLTIP // TOOLTIP
// ========================================
@Override @Override
public void appendHoverText( public void appendHoverText(

View File

@@ -33,7 +33,6 @@ import net.minecraft.world.phys.BlockHitResult;
/** /**
* Trapped Chest Block - Chest that traps players when opened. * Trapped Chest Block - Chest that traps players when opened.
* *
* Phase 16: Blocks
* *
* Extends vanilla ChestBlock for proper chest behavior. * Extends vanilla ChestBlock for proper chest behavior.
* Sneak + right-click to load bondage items. * Sneak + right-click to load bondage items.
@@ -47,9 +46,7 @@ public class BlockTrappedChest extends ChestBlock implements ICanBeLoaded {
); );
} }
// ========================================
// BLOCK ENTITY // BLOCK ENTITY
// ========================================
@Override @Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
@@ -67,9 +64,7 @@ public class BlockTrappedChest extends ChestBlock implements ICanBeLoaded {
: null; : null;
} }
// ========================================
// INTERACTION - TRAP TRIGGER // INTERACTION - TRAP TRIGGER
// ========================================
@Override @Override
public InteractionResult use( public InteractionResult use(
@@ -151,9 +146,7 @@ public class BlockTrappedChest extends ChestBlock implements ICanBeLoaded {
return super.use(state, level, pos, player, hand, hit); return super.use(state, level, pos, player, hand, hit);
} }
// ========================================
// DROPS WITH NBT // DROPS WITH NBT
// ========================================
@Override @Override
public List<ItemStack> getDrops( public List<ItemStack> getDrops(
@@ -180,9 +173,7 @@ public class BlockTrappedChest extends ChestBlock implements ICanBeLoaded {
return drops; return drops;
} }
// ========================================
// TOOLTIP // TOOLTIP
// ========================================
@Override @Override
public void appendHoverText( public void appendHoverText(

View File

@@ -3,7 +3,6 @@ package com.tiedup.remake.blocks;
/** /**
* Marker interface for blocks that can have bondage items loaded into them. * Marker interface for blocks that can have bondage items loaded into them.
* *
* Phase 16: Blocks
* *
* Implemented by: * Implemented by:
* - BlockRopesTrap - applies items when entity walks on it * - BlockRopesTrap - applies items when entity walks on it

View File

@@ -19,7 +19,6 @@ import net.minecraftforge.registries.RegistryObject;
/** /**
* Mod Blocks Registration * Mod Blocks Registration
* *
* Phase 16: Blocks
* *
* Handles registration of all TiedUp blocks using DeferredRegister. * Handles registration of all TiedUp blocks using DeferredRegister.
* *
@@ -40,9 +39,7 @@ public class ModBlocks {
public static final DeferredRegister<Item> BLOCK_ITEMS = public static final DeferredRegister<Item> BLOCK_ITEMS =
DeferredRegister.create(ForgeRegistries.ITEMS, TiedUpMod.MOD_ID); DeferredRegister.create(ForgeRegistries.ITEMS, TiedUpMod.MOD_ID);
// ========================================
// PADDED BLOCKS // PADDED BLOCKS
// ========================================
/** /**
* Base padded block properties. * Base padded block properties.
@@ -83,9 +80,7 @@ public class ModBlocks {
) )
); );
// ========================================
// TRAP BLOCKS // TRAP BLOCKS
// ========================================
/** /**
* Rope Trap - Flat trap that ties up entities that walk on it. * Rope Trap - Flat trap that ties up entities that walk on it.
@@ -114,9 +109,7 @@ public class ModBlocks {
BlockTrappedChest::new BlockTrappedChest::new
); );
// ========================================
// DOOR BLOCKS // DOOR BLOCKS
// ========================================
/** /**
* Cell Door - Iron-like door that requires redstone to open. * Cell Door - Iron-like door that requires redstone to open.
@@ -125,9 +118,7 @@ public class ModBlocks {
public static final RegistryObject<BlockCellDoor> CELL_DOOR = public static final RegistryObject<BlockCellDoor> CELL_DOOR =
registerDoorBlock("cell_door", BlockCellDoor::new); registerDoorBlock("cell_door", BlockCellDoor::new);
// ========================================
// CELL SYSTEM BLOCKS // CELL SYSTEM BLOCKS
// ========================================
/** /**
* Marker Block - Invisible block for cell spawn points. * Marker Block - Invisible block for cell spawn points.
@@ -154,9 +145,7 @@ public class ModBlocks {
BlockCellCore::new BlockCellCore::new
); );
// ========================================
// REGISTRATION HELPERS // REGISTRATION HELPERS
// ========================================
/** /**
* Register a block and its corresponding BlockItem. * Register a block and its corresponding BlockItem.

View File

@@ -20,7 +20,6 @@ import net.minecraft.world.level.block.state.BlockState;
/** /**
* Base BlockEntity for blocks that store bondage items. * Base BlockEntity for blocks that store bondage items.
* *
* Phase 16: Blocks
* *
* Stores up to 6 bondage items: * Stores up to 6 bondage items:
* - Bind (ropes, chains, straitjacket, etc.) * - Bind (ropes, chains, straitjacket, etc.)
@@ -42,9 +41,7 @@ public abstract class BondageItemBlockEntity
implements IBondageItemHolder implements IBondageItemHolder
{ {
// ========================================
// STORED ITEMS // STORED ITEMS
// ========================================
private ItemStack bind = ItemStack.EMPTY; private ItemStack bind = ItemStack.EMPTY;
private ItemStack gag = ItemStack.EMPTY; private ItemStack gag = ItemStack.EMPTY;
@@ -59,9 +56,7 @@ public abstract class BondageItemBlockEntity
*/ */
private final boolean offMode; private final boolean offMode;
// ========================================
// CONSTRUCTORS // CONSTRUCTORS
// ========================================
public BondageItemBlockEntity( public BondageItemBlockEntity(
BlockEntityType<?> type, BlockEntityType<?> type,
@@ -81,9 +76,7 @@ public abstract class BondageItemBlockEntity
this.offMode = offMode; this.offMode = offMode;
} }
// ========================================
// BIND // BIND
// ========================================
@Override @Override
public ItemStack getBind() { public ItemStack getBind() {
@@ -96,9 +89,7 @@ public abstract class BondageItemBlockEntity
this.setChangedAndSync(); this.setChangedAndSync();
} }
// ========================================
// GAG // GAG
// ========================================
@Override @Override
public ItemStack getGag() { public ItemStack getGag() {
@@ -111,9 +102,7 @@ public abstract class BondageItemBlockEntity
this.setChangedAndSync(); this.setChangedAndSync();
} }
// ========================================
// BLINDFOLD // BLINDFOLD
// ========================================
@Override @Override
public ItemStack getBlindfold() { public ItemStack getBlindfold() {
@@ -126,9 +115,7 @@ public abstract class BondageItemBlockEntity
this.setChangedAndSync(); this.setChangedAndSync();
} }
// ========================================
// EARPLUGS // EARPLUGS
// ========================================
@Override @Override
public ItemStack getEarplugs() { public ItemStack getEarplugs() {
@@ -141,9 +128,7 @@ public abstract class BondageItemBlockEntity
this.setChangedAndSync(); this.setChangedAndSync();
} }
// ========================================
// COLLAR // COLLAR
// ========================================
@Override @Override
public ItemStack getCollar() { public ItemStack getCollar() {
@@ -156,9 +141,7 @@ public abstract class BondageItemBlockEntity
this.setChangedAndSync(); this.setChangedAndSync();
} }
// ========================================
// CLOTHES // CLOTHES
// ========================================
@Override @Override
public ItemStack getClothes() { public ItemStack getClothes() {
@@ -171,9 +154,7 @@ public abstract class BondageItemBlockEntity
this.setChangedAndSync(); this.setChangedAndSync();
} }
// ========================================
// STATE // STATE
// ========================================
@Override @Override
public boolean isArmed() { public boolean isArmed() {
@@ -194,9 +175,7 @@ public abstract class BondageItemBlockEntity
this.setChangedAndSync(); this.setChangedAndSync();
} }
// ========================================
// NBT SERIALIZATION // NBT SERIALIZATION
// ========================================
@Override @Override
public void load(CompoundTag tag) { public void load(CompoundTag tag) {
@@ -300,9 +279,7 @@ public abstract class BondageItemBlockEntity
return tag; return tag;
} }
// ========================================
// NETWORK SYNC // NETWORK SYNC
// ========================================
/** /**
* Mark dirty and sync to clients. * Mark dirty and sync to clients.

View File

@@ -6,7 +6,6 @@ import net.minecraft.world.item.ItemStack;
/** /**
* Interface for BlockEntities that store bondage items. * Interface for BlockEntities that store bondage items.
* *
* Phase 16: Blocks
* *
* Defines the contract for storing and retrieving bondage items: * Defines the contract for storing and retrieving bondage items:
* - Bind (ropes, chains, etc.) * - Bind (ropes, chains, etc.)
@@ -19,51 +18,37 @@ import net.minecraft.world.item.ItemStack;
* Based on original ITileEntityBondageItemHolder from 1.12.2 * Based on original ITileEntityBondageItemHolder from 1.12.2
*/ */
public interface IBondageItemHolder { public interface IBondageItemHolder {
// ========================================
// BIND // BIND
// ========================================
ItemStack getBind(); ItemStack getBind();
void setBind(ItemStack bind); void setBind(ItemStack bind);
// ========================================
// GAG // GAG
// ========================================
ItemStack getGag(); ItemStack getGag();
void setGag(ItemStack gag); void setGag(ItemStack gag);
// ========================================
// BLINDFOLD // BLINDFOLD
// ========================================
ItemStack getBlindfold(); ItemStack getBlindfold();
void setBlindfold(ItemStack blindfold); void setBlindfold(ItemStack blindfold);
// ========================================
// EARPLUGS // EARPLUGS
// ========================================
ItemStack getEarplugs(); ItemStack getEarplugs();
void setEarplugs(ItemStack earplugs); void setEarplugs(ItemStack earplugs);
// ========================================
// COLLAR // COLLAR
// ========================================
ItemStack getCollar(); ItemStack getCollar();
void setCollar(ItemStack collar); void setCollar(ItemStack collar);
// ========================================
// CLOTHES // CLOTHES
// ========================================
ItemStack getClothes(); ItemStack getClothes();
void setClothes(ItemStack clothes); void setClothes(ItemStack clothes);
// ========================================
// NBT SERIALIZATION // NBT SERIALIZATION
// ========================================
/** /**
* Read bondage items from NBT. * Read bondage items from NBT.
@@ -78,9 +63,7 @@ public interface IBondageItemHolder {
*/ */
CompoundTag writeBondageData(CompoundTag tag); CompoundTag writeBondageData(CompoundTag tag);
// ========================================
// STATE // STATE
// ========================================
/** /**
* Check if this holder has any bondage items loaded. * Check if this holder has any bondage items loaded.

View File

@@ -6,7 +6,6 @@ import net.minecraft.world.level.block.state.BlockState;
/** /**
* BlockEntity for kidnap bomb blocks. * BlockEntity for kidnap bomb blocks.
* *
* Phase 16: Blocks
* *
* Stores bondage items that will be applied when the bomb explodes. * Stores bondage items that will be applied when the bomb explodes.
* Simple extension of BondageItemBlockEntity. * Simple extension of BondageItemBlockEntity.

View File

@@ -10,7 +10,6 @@ import net.minecraftforge.registries.RegistryObject;
/** /**
* Mod Block Entities Registration * Mod Block Entities Registration
* *
* Phase 16: Blocks
* *
* Handles registration of all TiedUp block entities using DeferredRegister. * Handles registration of all TiedUp block entities using DeferredRegister.
*/ */
@@ -23,9 +22,7 @@ public class ModBlockEntities {
TiedUpMod.MOD_ID TiedUpMod.MOD_ID
); );
// ========================================
// TRAP BLOCK ENTITIES // TRAP BLOCK ENTITIES
// ========================================
/** /**
* Trap block entity - stores bondage items for rope trap. * Trap block entity - stores bondage items for rope trap.
@@ -40,9 +37,7 @@ public class ModBlockEntities {
// LOW FIX: Removed BED BLOCK ENTITIES section - feature not implemented // LOW FIX: Removed BED BLOCK ENTITIES section - feature not implemented
// ========================================
// BOMB BLOCK ENTITIES // BOMB BLOCK ENTITIES
// ========================================
/** /**
* Kidnap bomb block entity - stores bondage items for explosion effect. * Kidnap bomb block entity - stores bondage items for explosion effect.
@@ -56,9 +51,7 @@ public class ModBlockEntities {
).build(null) ).build(null)
); );
// ========================================
// CHEST BLOCK ENTITIES // CHEST BLOCK ENTITIES
// ========================================
/** /**
* Trapped chest block entity - stores bondage items for when player opens it. * Trapped chest block entity - stores bondage items for when player opens it.
@@ -72,9 +65,7 @@ public class ModBlockEntities {
).build(null) ).build(null)
); );
// ========================================
// CELL SYSTEM BLOCK ENTITIES // CELL SYSTEM BLOCK ENTITIES
// ========================================
/** /**
* Marker block entity - stores cell UUID for cell system. * Marker block entity - stores cell UUID for cell system.

View File

@@ -6,7 +6,6 @@ import net.minecraft.world.level.block.state.BlockState;
/** /**
* BlockEntity for rope trap blocks. * BlockEntity for rope trap blocks.
* *
* Phase 16: Blocks
* *
* Stores bondage items that will be applied when an entity walks on the trap. * Stores bondage items that will be applied when an entity walks on the trap.
* Simple extension of BondageItemBlockEntity. * Simple extension of BondageItemBlockEntity.

View File

@@ -15,7 +15,6 @@ import net.minecraft.world.level.block.state.BlockState;
/** /**
* BlockEntity for trapped chest blocks. * BlockEntity for trapped chest blocks.
* *
* Phase 16: Blocks
* *
* Extends ChestBlockEntity for proper chest behavior, * Extends ChestBlockEntity for proper chest behavior,
* but also stores bondage items for the trap. * but also stores bondage items for the trap.
@@ -37,9 +36,7 @@ public class TrappedChestBlockEntity
super(ModBlockEntities.TRAPPED_CHEST.get(), pos, state); super(ModBlockEntities.TRAPPED_CHEST.get(), pos, state);
} }
// ========================================
// BONDAGE ITEM HOLDER IMPLEMENTATION // BONDAGE ITEM HOLDER IMPLEMENTATION
// ========================================
@Override @Override
public ItemStack getBind() { public ItemStack getBind() {
@@ -172,9 +169,7 @@ public class TrappedChestBlockEntity
return tag; return tag;
} }
// ========================================
// NBT SERIALIZATION // NBT SERIALIZATION
// ========================================
@Override @Override
public void load(CompoundTag tag) { public void load(CompoundTag tag) {
@@ -188,9 +183,7 @@ public class TrappedChestBlockEntity
writeBondageData(tag); writeBondageData(tag);
} }
// ========================================
// NETWORK SYNC // NETWORK SYNC
// ========================================
/** /**
* Mark dirty and sync to clients. * Mark dirty and sync to clients.

View File

@@ -7,7 +7,6 @@ import net.minecraft.world.item.ItemStack;
/** /**
* Represents a single bounty placed on a player. * Represents a single bounty placed on a player.
* *
* Phase 17: Bounty System
* *
* A bounty is created when a player (client) offers a reward for capturing * A bounty is created when a player (client) offers a reward for capturing
* another player (target). Anyone who delivers the target to the client * another player (target). Anyone who delivers the target to the client

View File

@@ -1,8 +1,8 @@
package com.tiedup.remake.bounty; package com.tiedup.remake.bounty;
import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.core.SettingsAccessor;
import java.util.*; import java.util.*;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
@@ -20,7 +20,6 @@ import net.minecraft.world.level.saveddata.SavedData;
/** /**
* World-saved data manager for bounties. * World-saved data manager for bounties.
* *
* Phase 17: Bounty System
* *
* Manages all active bounties, handles expiration, delivery rewards, * Manages all active bounties, handles expiration, delivery rewards,
* and stores bounties for offline players. * and stores bounties for offline players.

View File

@@ -26,7 +26,11 @@ public final class CampMaidManager {
* @param currentTime The current game time * @param currentTime The current game time
* @param level The server level * @param level The server level
*/ */
public static void markMaidDead(UUID campId, long currentTime, ServerLevel level) { public static void markMaidDead(
UUID campId,
long currentTime,
ServerLevel level
) {
CampOwnership ownership = CampOwnership.get(level); CampOwnership ownership = CampOwnership.get(level);
CampOwnership.CampData data = ownership.getCamp(campId); CampOwnership.CampData data = ownership.getCamp(campId);
if (data == null || !data.isAlive()) { if (data == null || !data.isAlive()) {
@@ -93,7 +97,10 @@ public final class CampMaidManager {
* @param level The server level * @param level The server level
* @return List of camp IDs ready for maid respawn * @return List of camp IDs ready for maid respawn
*/ */
public static List<UUID> getCampsNeedingMaidRespawn(long currentTime, ServerLevel level) { public static List<UUID> getCampsNeedingMaidRespawn(
long currentTime,
ServerLevel level
) {
CampOwnership ownership = CampOwnership.get(level); CampOwnership ownership = CampOwnership.get(level);
List<UUID> result = new ArrayList<>(); List<UUID> result = new ArrayList<>();
for (CampOwnership.CampData data : ownership.getAllCamps()) { for (CampOwnership.CampData data : ownership.getAllCamps()) {

View File

@@ -572,5 +572,4 @@ public class CampOwnership extends SavedData {
return registry; return registry;
} }
} }

View File

@@ -22,7 +22,6 @@ import net.minecraft.world.level.saveddata.SavedData;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
/** /**
* Phase 2: SavedData registry for confiscated player inventories.
* *
* When a player is imprisoned: * When a player is imprisoned:
* 1. Their inventory is saved to NBT * 1. Their inventory is saved to NBT

View File

@@ -139,10 +139,7 @@ public class FirstPersonMittensRenderer {
*/ */
private static boolean isBindHidingMittens(AbstractClientPlayer player) { private static boolean isBindHidingMittens(AbstractClientPlayer player) {
net.minecraft.world.item.ItemStack bindStack = net.minecraft.world.item.ItemStack bindStack =
V2EquipmentHelper.getInRegion( V2EquipmentHelper.getInRegion(player, BodyRegionV2.ARMS);
player,
BodyRegionV2.ARMS
);
if (bindStack.isEmpty()) return false; if (bindStack.isEmpty()) return false;
if (bindStack.getItem() instanceof GenericBind bind) { if (bindStack.getItem() instanceof GenericBind bind) {
BindVariant variant = bind.getVariant(); BindVariant variant = bind.getVariant();

View File

@@ -1,22 +1,21 @@
package com.tiedup.remake.client; package com.tiedup.remake.client;
import com.mojang.blaze3d.platform.InputConstants; import com.mojang.blaze3d.platform.InputConstants;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.client.gui.screens.AdjustmentScreen; import com.tiedup.remake.client.gui.screens.AdjustmentScreen;
import com.tiedup.remake.client.gui.screens.UnifiedBondageScreen; import com.tiedup.remake.client.gui.screens.UnifiedBondageScreen;
import com.tiedup.remake.items.base.ItemCollar;
import org.jetbrains.annotations.Nullable;
import com.tiedup.remake.core.ModConfig; import com.tiedup.remake.core.ModConfig;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.action.PacketForceSeatModifier; import com.tiedup.remake.network.action.PacketForceSeatModifier;
import com.tiedup.remake.network.action.PacketStruggle; import com.tiedup.remake.network.action.PacketStruggle;
import com.tiedup.remake.network.action.PacketTighten; import com.tiedup.remake.network.action.PacketTighten;
import com.tiedup.remake.network.bounty.PacketRequestBounties; import com.tiedup.remake.network.bounty.PacketRequestBounties;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.network.PacketV2StruggleStart;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.v2.bondage.network.PacketV2StruggleStart;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.client.KeyMapping; import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@@ -29,9 +28,9 @@ import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import org.jetbrains.annotations.Nullable;
/** /**
* Phase 7: Client-side keybindings for TiedUp mod.
* *
* Manages key mappings and sends packets to server when keys are pressed. * Manages key mappings and sends packets to server when keys are pressed.
* *
@@ -227,7 +226,7 @@ public class ModKeybindings {
); );
} }
// Check struggle key - Phase 21: Flow based on bind/accessories // Check struggle key - Flow based on bind/accessories
while (STRUGGLE_KEY.consumeClick()) { while (STRUGGLE_KEY.consumeClick()) {
handleStruggleKey(); handleStruggleKey();
} }
@@ -284,7 +283,6 @@ public class ModKeybindings {
} }
/** /**
* Phase 21: Handle struggle key press with new flow.
* *
* Flow: * Flow:
* 1. If bind equipped: Send PacketStruggle to server (struggle against bind) * 1. If bind equipped: Send PacketStruggle to server (struggle against bind)
@@ -300,7 +298,11 @@ public class ModKeybindings {
} }
// V2 path: check if player has V2 equipment to struggle against // V2 path: check if player has V2 equipment to struggle against
if (com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.hasAnyEquipment(player)) { if (
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.hasAnyEquipment(
player
)
) {
handleV2Struggle(player); handleV2Struggle(player);
return; return;
} }
@@ -313,10 +315,11 @@ public class ModKeybindings {
// Check if player has bind equipped // Check if player has bind equipped
if (state.isTiedUp()) { if (state.isTiedUp()) {
// Has bind - struggle against it // Has bind - struggle against it
// Phase 2.5: Check if mini-game is enabled
if (ModConfig.SERVER.struggleMiniGameEnabled.get()) { if (ModConfig.SERVER.struggleMiniGameEnabled.get()) {
// New: Start struggle mini-game // New: Start struggle mini-game
ModNetwork.sendToServer(new PacketV2StruggleStart(BodyRegionV2.ARMS)); ModNetwork.sendToServer(
new PacketV2StruggleStart(BodyRegionV2.ARMS)
);
TiedUpMod.LOGGER.debug( TiedUpMod.LOGGER.debug(
"[CLIENT] Struggle key pressed - starting V2 struggle mini-game" "[CLIENT] Struggle key pressed - starting V2 struggle mini-game"
); );
@@ -359,7 +362,9 @@ public class ModKeybindings {
*/ */
private static void handleV2Struggle(Player player) { private static void handleV2Struggle(Player player) {
java.util.Map<com.tiedup.remake.v2.BodyRegionV2, ItemStack> equipped = java.util.Map<com.tiedup.remake.v2.BodyRegionV2, ItemStack> equipped =
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.getAllEquipped(player); com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.getAllEquipped(
player
);
if (equipped.isEmpty()) return; if (equipped.isEmpty()) return;
@@ -367,9 +372,15 @@ public class ModKeybindings {
com.tiedup.remake.v2.BodyRegionV2 bestRegion = null; com.tiedup.remake.v2.BodyRegionV2 bestRegion = null;
int bestPriority = Integer.MIN_VALUE; int bestPriority = Integer.MIN_VALUE;
for (java.util.Map.Entry<com.tiedup.remake.v2.BodyRegionV2, ItemStack> entry : equipped.entrySet()) { for (java.util.Map.Entry<
com.tiedup.remake.v2.BodyRegionV2,
ItemStack
> entry : equipped.entrySet()) {
ItemStack stack = entry.getValue(); ItemStack stack = entry.getValue();
if (stack.getItem() instanceof com.tiedup.remake.v2.bondage.IV2BondageItem item) { if (
stack.getItem() instanceof
com.tiedup.remake.v2.bondage.IV2BondageItem item
) {
if (item.getPosePriority(stack) > bestPriority) { if (item.getPosePriority(stack) > bestPriority) {
bestPriority = item.getPosePriority(stack); bestPriority = item.getPosePriority(stack);
bestRegion = entry.getKey(); bestRegion = entry.getKey();
@@ -379,7 +390,9 @@ public class ModKeybindings {
if (bestRegion != null) { if (bestRegion != null) {
ModNetwork.sendToServer( ModNetwork.sendToServer(
new com.tiedup.remake.v2.bondage.network.PacketV2StruggleStart(bestRegion) new com.tiedup.remake.v2.bondage.network.PacketV2StruggleStart(
bestRegion
)
); );
TiedUpMod.LOGGER.debug( TiedUpMod.LOGGER.debug(
"[CLIENT] V2 Struggle key pressed - targeting region {}", "[CLIENT] V2 Struggle key pressed - targeting region {}",
@@ -406,11 +419,19 @@ public class ModKeybindings {
/** /**
* Returns true if the given entity has a collar in the NECK region that lists the player as an owner. * Returns true if the given entity has a collar in the NECK region that lists the player as an owner.
*/ */
private static boolean checkCollarOwnership(LivingEntity target, Player player) { private static boolean checkCollarOwnership(
ItemStack collarStack = com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.getInRegion( LivingEntity target,
target, BodyRegionV2.NECK Player player
); ) {
if (!collarStack.isEmpty() && collarStack.getItem() instanceof ItemCollar collar) { ItemStack collarStack =
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.getInRegion(
target,
BodyRegionV2.NECK
);
if (
!collarStack.isEmpty() &&
collarStack.getItem() instanceof ItemCollar collar
) {
return collar.isOwner(collarStack, player); return collar.isOwner(collarStack, player);
} }
return false; return false;

View File

@@ -48,8 +48,10 @@ public class BondageAnimationManager {
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
/** Cache of furniture ModifierLayers for NPC entities */ /** Cache of furniture ModifierLayers for NPC entities */
private static final Map<UUID, ModifierLayer<IAnimation>> npcFurnitureLayers = private static final Map<
new ConcurrentHashMap<>(); UUID,
ModifierLayer<IAnimation>
> npcFurnitureLayers = new ConcurrentHashMap<>();
/** Factory ID for PlayerAnimator item layer (players only) */ /** Factory ID for PlayerAnimator item layer (players only) */
private static final ResourceLocation FACTORY_ID = private static final ResourceLocation FACTORY_ID =
@@ -83,7 +85,8 @@ public class BondageAnimationManager {
* *
* <p>Uses ConcurrentHashMap for safe access from both client tick and render thread.</p> * <p>Uses ConcurrentHashMap for safe access from both client tick and render thread.</p>
*/ */
private static final Map<UUID, Integer> furnitureGraceTicks = new ConcurrentHashMap<>(); private static final Map<UUID, Integer> furnitureGraceTicks =
new ConcurrentHashMap<>();
/** /**
* Initialize the animation system. * Initialize the animation system.
@@ -119,13 +122,13 @@ public class BondageAnimationManager {
LOGGER.info( LOGGER.info(
"BondageAnimationManager: Factories registered — context (pri {}), item (pri {}), furniture (pri {})", "BondageAnimationManager: Factories registered — context (pri {}), item (pri {}), furniture (pri {})",
CONTEXT_LAYER_PRIORITY, ITEM_LAYER_PRIORITY, FURNITURE_LAYER_PRIORITY CONTEXT_LAYER_PRIORITY,
ITEM_LAYER_PRIORITY,
FURNITURE_LAYER_PRIORITY
); );
} }
// ========================================
// PLAY ANIMATION // PLAY ANIMATION
// ========================================
/** /**
* Play an animation on any entity (player or NPC). * Play an animation on any entity (player or NPC).
@@ -235,7 +238,10 @@ public class BondageAnimationManager {
* @param anim The KeyframeAnimation to play * @param anim The KeyframeAnimation to play
* @return true if animation started successfully * @return true if animation started successfully
*/ */
public static boolean playDirect(LivingEntity entity, KeyframeAnimation anim) { public static boolean playDirect(
LivingEntity entity,
KeyframeAnimation anim
) {
if (entity == null || anim == null || !entity.level().isClientSide()) { if (entity == null || anim == null || !entity.level().isClientSide()) {
return false; return false;
} }
@@ -255,9 +261,7 @@ public class BondageAnimationManager {
return false; return false;
} }
// ========================================
// STOP ANIMATION // STOP ANIMATION
// ========================================
/** /**
* Stop any currently playing animation on an entity. * Stop any currently playing animation on an entity.
@@ -276,9 +280,7 @@ public class BondageAnimationManager {
} }
} }
// ========================================
// LAYER MANAGEMENT // LAYER MANAGEMENT
// ========================================
/** /**
* Get the ModifierLayer for an entity (without creating). * Get the ModifierLayer for an entity (without creating).
@@ -398,9 +400,7 @@ public class BondageAnimationManager {
return npcLayers.get(player.getUUID()); return npcLayers.get(player.getUUID());
} }
// ========================================
// CONTEXT LAYER (lower priority, for sit/kneel/sneak) // CONTEXT LAYER (lower priority, for sit/kneel/sneak)
// ========================================
/** /**
* Get the context animation layer for a player from PlayerAnimationAccess. * Get the context animation layer for a player from PlayerAnimationAccess.
@@ -431,14 +431,13 @@ public class BondageAnimationManager {
LivingEntity entity LivingEntity entity
) { ) {
if (entity instanceof IAnimatedPlayer animated) { if (entity instanceof IAnimatedPlayer animated) {
return npcContextLayers.computeIfAbsent( return npcContextLayers.computeIfAbsent(entity.getUUID(), k -> {
entity.getUUID(), ModifierLayer<IAnimation> layer = new ModifierLayer<>();
k -> { animated
ModifierLayer<IAnimation> layer = new ModifierLayer<>(); .getAnimationStack()
animated.getAnimationStack().addAnimLayer(CONTEXT_LAYER_PRIORITY, layer); .addAnimLayer(CONTEXT_LAYER_PRIORITY, layer);
return layer; return layer;
} });
);
} }
return null; return null;
} }
@@ -496,9 +495,7 @@ public class BondageAnimationManager {
} }
} }
// ========================================
// FURNITURE LAYER (highest priority, for seat poses) // FURNITURE LAYER (highest priority, for seat poses)
// ========================================
/** /**
* Play a furniture animation on the furniture layer (highest priority). * Play a furniture animation on the furniture layer (highest priority).
@@ -512,8 +509,15 @@ public class BondageAnimationManager {
* @param animation the KeyframeAnimation from FurnitureAnimationContext * @param animation the KeyframeAnimation from FurnitureAnimationContext
* @return true if animation started successfully * @return true if animation started successfully
*/ */
public static boolean playFurniture(Player player, KeyframeAnimation animation) { public static boolean playFurniture(
if (player == null || animation == null || !player.level().isClientSide()) { Player player,
KeyframeAnimation animation
) {
if (
player == null ||
animation == null ||
!player.level().isClientSide()
) {
return false; return false;
} }
@@ -522,11 +526,17 @@ public class BondageAnimationManager {
layer.setAnimation(new KeyframeAnimationPlayer(animation)); layer.setAnimation(new KeyframeAnimationPlayer(animation));
// Reset grace ticks since we just started/refreshed the animation // Reset grace ticks since we just started/refreshed the animation
furnitureGraceTicks.remove(player.getUUID()); furnitureGraceTicks.remove(player.getUUID());
LOGGER.debug("Playing furniture animation on player: {}", player.getName().getString()); LOGGER.debug(
"Playing furniture animation on player: {}",
player.getName().getString()
);
return true; return true;
} }
LOGGER.warn("Furniture layer not available for player: {}", player.getName().getString()); LOGGER.warn(
"Furniture layer not available for player: {}",
player.getName().getString()
);
return false; return false;
} }
@@ -545,7 +555,10 @@ public class BondageAnimationManager {
layer.setAnimation(null); layer.setAnimation(null);
} }
furnitureGraceTicks.remove(player.getUUID()); furnitureGraceTicks.remove(player.getUUID());
LOGGER.debug("Stopped furniture animation on player: {}", player.getName().getString()); LOGGER.debug(
"Stopped furniture animation on player: {}",
player.getName().getString()
);
} }
/** /**
@@ -573,9 +586,11 @@ public class BondageAnimationManager {
private static ModifierLayer<IAnimation> getFurnitureLayer(Player player) { private static ModifierLayer<IAnimation> getFurnitureLayer(Player player) {
if (player instanceof AbstractClientPlayer clientPlayer) { if (player instanceof AbstractClientPlayer clientPlayer) {
try { try {
ModifierLayer<IAnimation> layer = (ModifierLayer<IAnimation>) ModifierLayer<IAnimation> layer = (ModifierLayer<
PlayerAnimationAccess.getPlayerAssociatedData(clientPlayer) IAnimation
.get(FURNITURE_FACTORY_ID); >) PlayerAnimationAccess.getPlayerAssociatedData(
clientPlayer
).get(FURNITURE_FACTORY_ID);
if (layer != null) { if (layer != null) {
return layer; return layer;
} }
@@ -628,17 +643,18 @@ public class BondageAnimationManager {
// Player has furniture anim but no seat -- increment grace // Player has furniture anim but no seat -- increment grace
int ticks = furnitureGraceTicks.merge(uuid, 1, Integer::sum); int ticks = furnitureGraceTicks.merge(uuid, 1, Integer::sum);
if (ticks >= FURNITURE_GRACE_TICKS) { if (ticks >= FURNITURE_GRACE_TICKS) {
LOGGER.info("Removing stale furniture animation for player {} " LOGGER.info(
+ "(not riding ISeatProvider for {} ticks)", "Removing stale furniture animation for player {} " +
player.getName().getString(), ticks); "(not riding ISeatProvider for {} ticks)",
player.getName().getString(),
ticks
);
stopFurniture(player); stopFurniture(player);
} }
} }
} }
// ========================================
// FALLBACK ANIMATION HANDLING // FALLBACK ANIMATION HANDLING
// ========================================
/** /**
* Try to find a fallback animation ID when the requested one doesn't exist. * Try to find a fallback animation ID when the requested one doesn't exist.
@@ -696,9 +712,7 @@ public class BondageAnimationManager {
return null; return null;
} }
// ========================================
// CLEANUP // CLEANUP
// ========================================
/** /**
* Clean up animation layer for an NPC when it's removed. * Clean up animation layer for an NPC when it's removed.
@@ -706,9 +720,8 @@ public class BondageAnimationManager {
* @param entityId UUID of the removed entity * @param entityId UUID of the removed entity
*/ */
/** All NPC layer caches, for bulk cleanup operations. */ /** All NPC layer caches, for bulk cleanup operations. */
private static final Map<UUID, ModifierLayer<IAnimation>>[] ALL_NPC_CACHES = new Map[] { private static final Map<UUID, ModifierLayer<IAnimation>>[] ALL_NPC_CACHES =
npcLayers, npcContextLayers, npcFurnitureLayers new Map[] { npcLayers, npcContextLayers, npcFurnitureLayers };
};
public static void cleanup(UUID entityId) { public static void cleanup(UUID entityId) {
for (Map<UUID, ModifierLayer<IAnimation>> cache : ALL_NPC_CACHES) { for (Map<UUID, ModifierLayer<IAnimation>> cache : ALL_NPC_CACHES) {
@@ -733,5 +746,4 @@ public class BondageAnimationManager {
furnitureGraceTicks.clear(); furnitureGraceTicks.clear();
LOGGER.info("Cleared all NPC animation layers"); LOGGER.info("Cleared all NPC animation layers");
} }
} }

View File

@@ -17,7 +17,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
*/ */
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public enum AnimationContext { public enum AnimationContext {
STAND_IDLE("stand_idle", false), STAND_IDLE("stand_idle", false),
STAND_WALK("stand_walk", false), STAND_WALK("stand_walk", false),
STAND_SNEAK("stand_sneak", false), STAND_SNEAK("stand_sneak", false),

View File

@@ -50,15 +50,21 @@ public final class AnimationContextResolver {
* @param activeStyle the active movement style from client state, or null * @param activeStyle the active movement style from client state, or null
* @return the resolved animation context, never null * @return the resolved animation context, never null
*/ */
public static AnimationContext resolve(Player player, @Nullable PlayerBindState state, public static AnimationContext resolve(
@Nullable MovementStyle activeStyle) { Player player,
@Nullable PlayerBindState state,
@Nullable MovementStyle activeStyle
) {
boolean sitting = PetBedClientState.get(player.getUUID()) != 0; boolean sitting = PetBedClientState.get(player.getUUID()) != 0;
boolean struggling = state != null && state.isStruggling(); boolean struggling = state != null && state.isStruggling();
boolean sneaking = player.isCrouching(); boolean sneaking = player.isCrouching();
boolean moving = player.getDeltaMovement().horizontalDistanceSqr() > 1.0E-6; boolean moving =
player.getDeltaMovement().horizontalDistanceSqr() > 1.0E-6;
if (sitting) { if (sitting) {
return struggling ? AnimationContext.SIT_STRUGGLE : AnimationContext.SIT_IDLE; return struggling
? AnimationContext.SIT_STRUGGLE
: AnimationContext.SIT_IDLE;
} }
if (struggling) { if (struggling) {
return AnimationContext.STAND_STRUGGLE; return AnimationContext.STAND_STRUGGLE;
@@ -78,12 +84,23 @@ public final class AnimationContextResolver {
/** /**
* Map a movement style + moving flag to the appropriate AnimationContext. * Map a movement style + moving flag to the appropriate AnimationContext.
*/ */
private static AnimationContext resolveStyleContext(MovementStyle style, boolean moving) { private static AnimationContext resolveStyleContext(
MovementStyle style,
boolean moving
) {
return switch (style) { return switch (style) {
case SHUFFLE -> moving ? AnimationContext.SHUFFLE_WALK : AnimationContext.SHUFFLE_IDLE; case SHUFFLE -> moving
case HOP -> moving ? AnimationContext.HOP_WALK : AnimationContext.HOP_IDLE; ? AnimationContext.SHUFFLE_WALK
case WADDLE -> moving ? AnimationContext.WADDLE_WALK : AnimationContext.WADDLE_IDLE; : AnimationContext.SHUFFLE_IDLE;
case CRAWL -> moving ? AnimationContext.CRAWL_MOVE : AnimationContext.CRAWL_IDLE; case HOP -> moving
? AnimationContext.HOP_WALK
: AnimationContext.HOP_IDLE;
case WADDLE -> moving
? AnimationContext.WADDLE_WALK
: AnimationContext.WADDLE_IDLE;
case CRAWL -> moving
? AnimationContext.CRAWL_MOVE
: AnimationContext.CRAWL_IDLE;
}; };
} }
@@ -99,13 +116,18 @@ public final class AnimationContextResolver {
boolean sitting = entity.isSitting(); boolean sitting = entity.isSitting();
boolean kneeling = entity.isKneeling(); boolean kneeling = entity.isKneeling();
boolean struggling = entity.isStruggling(); boolean struggling = entity.isStruggling();
boolean moving = entity.getDeltaMovement().horizontalDistanceSqr() > 1.0E-6; boolean moving =
entity.getDeltaMovement().horizontalDistanceSqr() > 1.0E-6;
if (sitting) { if (sitting) {
return struggling ? AnimationContext.SIT_STRUGGLE : AnimationContext.SIT_IDLE; return struggling
? AnimationContext.SIT_STRUGGLE
: AnimationContext.SIT_IDLE;
} }
if (kneeling) { if (kneeling) {
return struggling ? AnimationContext.KNEEL_STRUGGLE : AnimationContext.KNEEL_IDLE; return struggling
? AnimationContext.KNEEL_STRUGGLE
: AnimationContext.KNEEL_IDLE;
} }
if (struggling) { if (struggling) {
return AnimationContext.STAND_STRUGGLE; return AnimationContext.STAND_STRUGGLE;

View File

@@ -6,10 +6,10 @@ import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
/** /**
@@ -42,13 +42,15 @@ public final class ContextAnimationFactory {
* Cache keyed by "contextSuffix|ownedPartsHashCode". * Cache keyed by "contextSuffix|ownedPartsHashCode".
* Null values are stored as sentinels for missing animations to avoid repeated lookups. * Null values are stored as sentinels for missing animations to avoid repeated lookups.
*/ */
private static final Map<String, KeyframeAnimation> CACHE = new ConcurrentHashMap<>(); private static final Map<String, KeyframeAnimation> CACHE =
new ConcurrentHashMap<>();
/** /**
* Sentinel set used to track cache keys where the base animation was not found, * Sentinel set used to track cache keys where the base animation was not found,
* so we don't log the same warning repeatedly. * so we don't log the same warning repeatedly.
*/ */
private static final Set<String> MISSING_WARNED = ConcurrentHashMap.newKeySet(); private static final Set<String> MISSING_WARNED =
ConcurrentHashMap.newKeySet();
private ContextAnimationFactory() {} private ContextAnimationFactory() {}
@@ -65,8 +67,14 @@ public final class ContextAnimationFactory {
* @return the context animation with disabled parts suppressed, or null if not found * @return the context animation with disabled parts suppressed, or null if not found
*/ */
@Nullable @Nullable
public static KeyframeAnimation create(AnimationContext context, Set<String> disabledParts) { public static KeyframeAnimation create(
String cacheKey = context.getAnimationSuffix() + "|" + String.join(",", new java.util.TreeSet<>(disabledParts)); AnimationContext context,
Set<String> disabledParts
) {
String cacheKey =
context.getAnimationSuffix() +
"|" +
String.join(",", new java.util.TreeSet<>(disabledParts));
// computeIfAbsent cannot store null values, so we handle the missing case // computeIfAbsent cannot store null values, so we handle the missing case
// by checking the MISSING_WARNED set to avoid redundant work. // by checking the MISSING_WARNED set to avoid redundant work.
KeyframeAnimation cached = CACHE.get(cacheKey); KeyframeAnimation cached = CACHE.get(cacheKey);
@@ -77,7 +85,10 @@ public final class ContextAnimationFactory {
return null; return null;
} }
KeyframeAnimation result = buildContextAnimation(context, disabledParts); KeyframeAnimation result = buildContextAnimation(
context,
disabledParts
);
if (result != null) { if (result != null) {
CACHE.put(cacheKey, result); CACHE.put(cacheKey, result);
} else { } else {
@@ -100,8 +111,10 @@ public final class ContextAnimationFactory {
* </ol> * </ol>
*/ */
@Nullable @Nullable
private static KeyframeAnimation buildContextAnimation(AnimationContext context, private static KeyframeAnimation buildContextAnimation(
Set<String> disabledParts) { AnimationContext context,
Set<String> disabledParts
) {
String suffix = context.getAnimationSuffix(); String suffix = context.getAnimationSuffix();
// Priority 1: GLB-based context animation from ContextGlbRegistry // Priority 1: GLB-based context animation from ContextGlbRegistry
@@ -110,13 +123,17 @@ public final class ContextAnimationFactory {
// Priority 2: JSON-based context animation from PlayerAnimationRegistry // Priority 2: JSON-based context animation from PlayerAnimationRegistry
if (baseAnim == null) { if (baseAnim == null) {
ResourceLocation animId = ResourceLocation.fromNamespaceAndPath( ResourceLocation animId = ResourceLocation.fromNamespaceAndPath(
NAMESPACE, "context_" + suffix NAMESPACE,
"context_" + suffix
); );
baseAnim = PlayerAnimationRegistry.getAnimation(animId); baseAnim = PlayerAnimationRegistry.getAnimation(animId);
} }
if (baseAnim == null) { if (baseAnim == null) {
LOGGER.warn("[V2Animation] Context animation not found for suffix: {}", suffix); LOGGER.warn(
"[V2Animation] Context animation not found for suffix: {}",
suffix
);
return null; return null;
} }
@@ -140,8 +157,10 @@ public final class ContextAnimationFactory {
* <p>Unknown part names are silently ignored -- this can happen if the disabled parts set * <p>Unknown part names are silently ignored -- this can happen if the disabled parts set
* includes future bone names not present in the current context animation.</p> * includes future bone names not present in the current context animation.</p>
*/ */
private static void disableParts(KeyframeAnimation.AnimationBuilder builder, private static void disableParts(
Set<String> disabledParts) { KeyframeAnimation.AnimationBuilder builder,
Set<String> disabledParts
) {
for (String partName : disabledParts) { for (String partName : disabledParts) {
KeyframeAnimation.StateCollection part = builder.getPart(partName); KeyframeAnimation.StateCollection part = builder.getPart(partName);
if (part != null) { if (part != null) {

View File

@@ -8,7 +8,6 @@ import java.io.InputStream;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.jetbrains.annotations.Nullable;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.ResourceManager;
@@ -16,6 +15,7 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
/** /**
* Registry for context animations loaded from GLB files. * Registry for context animations loaded from GLB files.
@@ -70,10 +70,15 @@ public final class ContextGlbRegistry {
public static void reload(ResourceManager resourceManager) { public static void reload(ResourceManager resourceManager) {
Map<String, KeyframeAnimation> newRegistry = new HashMap<>(); Map<String, KeyframeAnimation> newRegistry = new HashMap<>();
Map<ResourceLocation, Resource> resources = resourceManager.listResources( Map<ResourceLocation, Resource> resources =
DIRECTORY, loc -> loc.getPath().endsWith(".glb")); resourceManager.listResources(DIRECTORY, loc ->
loc.getPath().endsWith(".glb")
);
for (Map.Entry<ResourceLocation, Resource> entry : resources.entrySet()) { for (Map.Entry<
ResourceLocation,
Resource
> entry : resources.entrySet()) {
ResourceLocation loc = entry.getKey(); ResourceLocation loc = entry.getKey();
Resource resource = entry.getValue(); Resource resource = entry.getValue();
@@ -89,15 +94,26 @@ public final class ContextGlbRegistry {
KeyframeAnimation anim = GltfPoseConverter.convert(data); KeyframeAnimation anim = GltfPoseConverter.convert(data);
newRegistry.put(suffix, anim); newRegistry.put(suffix, anim);
LOGGER.info("[GltfPipeline] Loaded context GLB: '{}' -> suffix '{}'", loc, suffix); LOGGER.info(
"[GltfPipeline] Loaded context GLB: '{}' -> suffix '{}'",
loc,
suffix
);
} catch (Exception e) { } catch (Exception e) {
LOGGER.error("[GltfPipeline] Failed to load context GLB: {}", loc, e); LOGGER.error(
"[GltfPipeline] Failed to load context GLB: {}",
loc,
e
);
} }
} }
// Atomic swap: render thread never sees a partially populated registry // Atomic swap: render thread never sees a partially populated registry
REGISTRY = Collections.unmodifiableMap(newRegistry); REGISTRY = Collections.unmodifiableMap(newRegistry);
LOGGER.info("[ContextGlb] Loaded {} context GLB animations", newRegistry.size()); LOGGER.info(
"[ContextGlb] Loaded {} context GLB animations",
newRegistry.size()
);
} }
/** /**

View File

@@ -6,10 +6,10 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import org.jetbrains.annotations.Nullable;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;
/** /**
* Resolves which named animation to play from a GLB file based on the current * Resolves which named animation to play from a GLB file based on the current
@@ -42,9 +42,12 @@ public final class GlbAnimationResolver {
* @return parsed GLB data, or null if loading failed * @return parsed GLB data, or null if loading failed
*/ */
@Nullable @Nullable
public static GltfData resolveAnimationData(ResourceLocation itemModelLoc, public static GltfData resolveAnimationData(
@Nullable ResourceLocation animationSource) { ResourceLocation itemModelLoc,
ResourceLocation source = animationSource != null ? animationSource : itemModelLoc; @Nullable ResourceLocation animationSource
) {
ResourceLocation source =
animationSource != null ? animationSource : itemModelLoc;
return GltfCache.get(source); return GltfCache.get(source);
} }
@@ -66,8 +69,8 @@ public final class GlbAnimationResolver {
*/ */
@Nullable @Nullable
public static String resolve(GltfData data, AnimationContext context) { public static String resolve(GltfData data, AnimationContext context) {
String prefix = context.getGlbContextPrefix(); // "Sit", "Kneel", "Sneak", "Walk", "" String prefix = context.getGlbContextPrefix(); // "Sit", "Kneel", "Sneak", "Walk", ""
String variant = context.getGlbVariant(); // "Idle" or "Struggle" String variant = context.getGlbVariant(); // "Idle" or "Struggle"
// 1. Exact match: "FullSitIdle" then "SitIdle" (with variants) // 1. Exact match: "FullSitIdle" then "SitIdle" (with variants)
String exact = prefix + variant; String exact = prefix + variant;
@@ -146,6 +149,8 @@ public final class GlbAnimationResolver {
if (candidates.isEmpty()) return null; if (candidates.isEmpty()) return null;
if (candidates.size() == 1) return candidates.get(0); if (candidates.size() == 1) return candidates.get(0);
return candidates.get(ThreadLocalRandom.current().nextInt(candidates.size())); return candidates.get(
ThreadLocalRandom.current().nextInt(candidates.size())
);
} }
} }

View File

@@ -6,11 +6,11 @@ import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemDefinition;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemRegistry;
import java.util.*; import java.util.*;
import org.jetbrains.annotations.Nullable;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;
/** /**
* Maps V2 body regions to PlayerAnimator part names. * Maps V2 body regions to PlayerAnimator part names.
@@ -29,7 +29,12 @@ public final class RegionBoneMapper {
/** All PlayerAnimator part names for the player model. */ /** All PlayerAnimator part names for the player model. */
public static final Set<String> ALL_PARTS = Set.of( public static final Set<String> ALL_PARTS = Set.of(
"head", "body", "rightArm", "leftArm", "rightLeg", "leftLeg" "head",
"body",
"rightArm",
"leftArm",
"rightLeg",
"leftLeg"
); );
/** /**
@@ -46,7 +51,6 @@ public final class RegionBoneMapper {
* the other item takes precedence (the bone goes to {@code otherParts}).</p> * the other item takes precedence (the bone goes to {@code otherParts}).</p>
*/ */
public record BoneOwnership(Set<String> thisParts, Set<String> otherParts) { public record BoneOwnership(Set<String> thisParts, Set<String> otherParts) {
/** /**
* Parts not owned by any item. These are "free" and can be animated * Parts not owned by any item. These are "free" and can be animated
* by the winning item IF the GLB contains keyframes for them. * by the winning item IF the GLB contains keyframes for them.
@@ -84,20 +88,20 @@ public final class RegionBoneMapper {
static { static {
Map<BodyRegionV2, Set<String>> map = new EnumMap<>(BodyRegionV2.class); Map<BodyRegionV2, Set<String>> map = new EnumMap<>(BodyRegionV2.class);
map.put(BodyRegionV2.HEAD, Set.of("head")); map.put(BodyRegionV2.HEAD, Set.of("head"));
map.put(BodyRegionV2.EYES, Set.of("head")); map.put(BodyRegionV2.EYES, Set.of("head"));
map.put(BodyRegionV2.EARS, Set.of("head")); map.put(BodyRegionV2.EARS, Set.of("head"));
map.put(BodyRegionV2.MOUTH, Set.of("head")); map.put(BodyRegionV2.MOUTH, Set.of("head"));
map.put(BodyRegionV2.NECK, Set.of()); map.put(BodyRegionV2.NECK, Set.of());
map.put(BodyRegionV2.TORSO, Set.of("body")); map.put(BodyRegionV2.TORSO, Set.of("body"));
map.put(BodyRegionV2.ARMS, Set.of("rightArm", "leftArm")); map.put(BodyRegionV2.ARMS, Set.of("rightArm", "leftArm"));
map.put(BodyRegionV2.HANDS, Set.of("rightArm", "leftArm")); map.put(BodyRegionV2.HANDS, Set.of("rightArm", "leftArm"));
map.put(BodyRegionV2.FINGERS, Set.of()); map.put(BodyRegionV2.FINGERS, Set.of());
map.put(BodyRegionV2.WAIST, Set.of("body")); map.put(BodyRegionV2.WAIST, Set.of("body"));
map.put(BodyRegionV2.LEGS, Set.of("rightLeg", "leftLeg")); map.put(BodyRegionV2.LEGS, Set.of("rightLeg", "leftLeg"));
map.put(BodyRegionV2.FEET, Set.of("rightLeg", "leftLeg")); map.put(BodyRegionV2.FEET, Set.of("rightLeg", "leftLeg"));
map.put(BodyRegionV2.TAIL, Set.of()); map.put(BodyRegionV2.TAIL, Set.of());
map.put(BodyRegionV2.WINGS, Set.of()); map.put(BodyRegionV2.WINGS, Set.of());
REGION_TO_PARTS = Collections.unmodifiableMap(map); REGION_TO_PARTS = Collections.unmodifiableMap(map);
} }
@@ -123,7 +127,9 @@ public final class RegionBoneMapper {
* @param equipped map from representative region to equipped ItemStack * @param equipped map from representative region to equipped ItemStack
* @return unmodifiable set of owned part name strings * @return unmodifiable set of owned part name strings
*/ */
public static Set<String> computeOwnedParts(Map<BodyRegionV2, ItemStack> equipped) { public static Set<String> computeOwnedParts(
Map<BodyRegionV2, ItemStack> equipped
) {
Set<String> owned = new HashSet<>(); Set<String> owned = new HashSet<>();
for (Map.Entry<BodyRegionV2, ItemStack> entry : equipped.entrySet()) { for (Map.Entry<BodyRegionV2, ItemStack> entry : equipped.entrySet()) {
ItemStack stack = entry.getValue(); ItemStack stack = entry.getValue();
@@ -151,14 +157,18 @@ public final class RegionBoneMapper {
* @param winningItemStack the ItemStack of the highest-priority V2 item with a GLB model * @param winningItemStack the ItemStack of the highest-priority V2 item with a GLB model
* @return BoneOwnership describing this item's parts vs other items' parts * @return BoneOwnership describing this item's parts vs other items' parts
*/ */
public static BoneOwnership computePerItemParts(Map<BodyRegionV2, ItemStack> equipped, public static BoneOwnership computePerItemParts(
ItemStack winningItemStack) { Map<BodyRegionV2, ItemStack> equipped,
ItemStack winningItemStack
) {
Set<String> thisParts = new HashSet<>(); Set<String> thisParts = new HashSet<>();
Set<String> otherParts = new HashSet<>(); Set<String> otherParts = new HashSet<>();
// Track which ItemStacks we've already processed to avoid duplicate work // Track which ItemStacks we've already processed to avoid duplicate work
// (multiple regions can map to the same ItemStack) // (multiple regions can map to the same ItemStack)
Set<ItemStack> processed = Collections.newSetFromMap(new IdentityHashMap<>()); Set<ItemStack> processed = Collections.newSetFromMap(
new IdentityHashMap<>()
);
for (Map.Entry<BodyRegionV2, ItemStack> entry : equipped.entrySet()) { for (Map.Entry<BodyRegionV2, ItemStack> entry : equipped.entrySet()) {
ItemStack stack = entry.getValue(); ItemStack stack = entry.getValue();
@@ -199,8 +209,11 @@ public final class RegionBoneMapper {
* @param winningItem the actual ItemStack reference (for identity comparison in * @param winningItem the actual ItemStack reference (for identity comparison in
* {@link #computePerItemParts}) * {@link #computePerItemParts})
*/ */
public record GlbModelResult(ResourceLocation modelLoc, @Nullable ResourceLocation animSource, public record GlbModelResult(
ItemStack winningItem) {} ResourceLocation modelLoc,
@Nullable ResourceLocation animSource,
ItemStack winningItem
) {}
/** /**
* Animation info for a single equipped V2 item. * Animation info for a single equipped V2 item.
@@ -213,9 +226,13 @@ public final class RegionBoneMapper {
* @param animationBones per-animation bone whitelist from the data-driven definition. * @param animationBones per-animation bone whitelist from the data-driven definition.
* Empty map for hardcoded items (no filtering applied). * Empty map for hardcoded items (no filtering applied).
*/ */
public record V2ItemAnimInfo(ResourceLocation modelLoc, @Nullable ResourceLocation animSource, public record V2ItemAnimInfo(
Set<String> ownedParts, int posePriority, ResourceLocation modelLoc,
Map<String, Set<String>> animationBones) {} @Nullable ResourceLocation animSource,
Set<String> ownedParts,
int posePriority,
Map<String, Set<String>> animationBones
) {}
/** /**
* Find the highest-priority V2 item with a GLB model in the equipped map. * Find the highest-priority V2 item with a GLB model in the equipped map.
@@ -230,7 +247,9 @@ public final class RegionBoneMapper {
* @return the winning item's model info, or null if no V2 item has a GLB model (V1 fallback) * @return the winning item's model info, or null if no V2 item has a GLB model (V1 fallback)
*/ */
@Nullable @Nullable
public static GlbModelResult resolveWinningItem(Map<BodyRegionV2, ItemStack> equipped) { public static GlbModelResult resolveWinningItem(
Map<BodyRegionV2, ItemStack> equipped
) {
ItemStack bestStack = null; ItemStack bestStack = null;
ResourceLocation bestModel = null; ResourceLocation bestModel = null;
int bestPriority = Integer.MIN_VALUE; int bestPriority = Integer.MIN_VALUE;
@@ -238,7 +257,10 @@ public final class RegionBoneMapper {
ItemStack stack = entry.getValue(); ItemStack stack = entry.getValue();
if (stack.getItem() instanceof IV2BondageItem v2Item) { if (stack.getItem() instanceof IV2BondageItem v2Item) {
ResourceLocation model = v2Item.getModelLocation(stack); ResourceLocation model = v2Item.getModelLocation(stack);
if (model != null && v2Item.getPosePriority(stack) > bestPriority) { if (
model != null &&
v2Item.getPosePriority(stack) > bestPriority
) {
bestPriority = v2Item.getPosePriority(stack); bestPriority = v2Item.getPosePriority(stack);
bestModel = model; bestModel = model;
bestStack = stack; bestStack = stack;
@@ -252,7 +274,9 @@ public final class RegionBoneMapper {
// (the model's own animations are used). // (the model's own animations are used).
ResourceLocation animSource = null; ResourceLocation animSource = null;
if (bestStack.getItem() instanceof DataDrivenBondageItem) { if (bestStack.getItem() instanceof DataDrivenBondageItem) {
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(bestStack); DataDrivenItemDefinition def = DataDrivenItemRegistry.get(
bestStack
);
if (def != null) { if (def != null) {
animSource = def.animationSource(); animSource = def.animationSource();
} }
@@ -273,13 +297,23 @@ public final class RegionBoneMapper {
* @return list of V2ItemAnimInfo, sorted by priority descending. Empty if no V2 items. * @return list of V2ItemAnimInfo, sorted by priority descending. Empty if no V2 items.
* The first element (if any) is the free-bone donor. * The first element (if any) is the free-bone donor.
*/ */
public static List<V2ItemAnimInfo> resolveAllV2Items(Map<BodyRegionV2, ItemStack> equipped) { public static List<V2ItemAnimInfo> resolveAllV2Items(
record ItemEntry(ItemStack stack, IV2BondageItem v2Item, ResourceLocation model, Map<BodyRegionV2, ItemStack> equipped
@Nullable ResourceLocation animSource, Set<String> rawParts, int priority, ) {
Map<String, Set<String>> animationBones) {} record ItemEntry(
ItemStack stack,
IV2BondageItem v2Item,
ResourceLocation model,
@Nullable ResourceLocation animSource,
Set<String> rawParts,
int priority,
Map<String, Set<String>> animationBones
) {}
List<ItemEntry> entries = new ArrayList<>(); List<ItemEntry> entries = new ArrayList<>();
Set<ItemStack> seen = Collections.newSetFromMap(new IdentityHashMap<>()); Set<ItemStack> seen = Collections.newSetFromMap(
new IdentityHashMap<>()
);
for (Map.Entry<BodyRegionV2, ItemStack> entry : equipped.entrySet()) { for (Map.Entry<BodyRegionV2, ItemStack> entry : equipped.entrySet()) {
ItemStack stack = entry.getValue(); ItemStack stack = entry.getValue();
@@ -299,15 +333,26 @@ public final class RegionBoneMapper {
ResourceLocation animSource = null; ResourceLocation animSource = null;
Map<String, Set<String>> animBones = Map.of(); Map<String, Set<String>> animBones = Map.of();
if (stack.getItem() instanceof DataDrivenBondageItem) { if (stack.getItem() instanceof DataDrivenBondageItem) {
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack); DataDrivenItemDefinition def = DataDrivenItemRegistry.get(
stack
);
if (def != null) { if (def != null) {
animSource = def.animationSource(); animSource = def.animationSource();
animBones = def.animationBones(); animBones = def.animationBones();
} }
} }
entries.add(new ItemEntry(stack, v2Item, model, animSource, rawParts, entries.add(
v2Item.getPosePriority(stack), animBones)); new ItemEntry(
stack,
v2Item,
model,
animSource,
rawParts,
v2Item.getPosePriority(stack),
animBones
)
);
} }
} }
@@ -323,8 +368,15 @@ public final class RegionBoneMapper {
ownedParts.removeAll(claimed); ownedParts.removeAll(claimed);
if (ownedParts.isEmpty()) continue; if (ownedParts.isEmpty()) continue;
claimed.addAll(ownedParts); claimed.addAll(ownedParts);
result.add(new V2ItemAnimInfo(e.model(), e.animSource(), result.add(
Collections.unmodifiableSet(ownedParts), e.priority(), e.animationBones())); new V2ItemAnimInfo(
e.model(),
e.animSource(),
Collections.unmodifiableSet(ownedParts),
e.priority(),
e.animationBones()
)
);
} }
return Collections.unmodifiableList(result); return Collections.unmodifiableList(result);

View File

@@ -1,11 +1,11 @@
package com.tiedup.remake.client.animation.render; package com.tiedup.remake.client.animation.render;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.state.HumanChairHelper; import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.player.AbstractClientPlayer;

View File

@@ -1,12 +1,12 @@
package com.tiedup.remake.client.animation.render; package com.tiedup.remake.client.animation.render;
import com.tiedup.remake.client.state.PetBedClientState; import com.tiedup.remake.client.state.PetBedClientState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.state.HumanChairHelper; import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;

View File

@@ -1,12 +1,12 @@
package com.tiedup.remake.client.animation.render; package com.tiedup.remake.client.animation.render;
import com.tiedup.remake.client.renderer.layers.ClothesRenderHelper; import com.tiedup.remake.client.renderer.layers.ClothesRenderHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.items.clothes.ClothesProperties; import com.tiedup.remake.items.clothes.ClothesProperties;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.client.model.PlayerModel; import net.minecraft.client.model.PlayerModel;
@@ -48,7 +48,7 @@ public class PlayerArmHideEventHandler {
/** /**
* Before player render: * Before player render:
* - Hide arms for wrap/latex_sack poses * - Hide arms for wrap/latex_sack poses
* - Hide outer layers based on clothes settings (Phase 19) * - Hide outer layers based on clothes settings
*/ */
@SubscribeEvent @SubscribeEvent
public static void onRenderPlayerPre(RenderPlayerEvent.Pre event) { public static void onRenderPlayerPre(RenderPlayerEvent.Pre event) {
@@ -88,7 +88,7 @@ public class PlayerArmHideEventHandler {
} }
} }
// === HIDE WEARER LAYERS (clothes settings) - Phase 19 === // === HIDE WEARER LAYERS (clothes settings) ===
ItemStack clothes = state.getEquipment(BodyRegionV2.TORSO); ItemStack clothes = state.getEquipment(BodyRegionV2.TORSO);
if (!clothes.isEmpty()) { if (!clothes.isEmpty()) {
ClothesProperties props = ClothesProperties props =
@@ -126,7 +126,7 @@ public class PlayerArmHideEventHandler {
model.leftSleeve.visible = true; model.leftSleeve.visible = true;
model.rightSleeve.visible = true; model.rightSleeve.visible = true;
// === RESTORE WEARER LAYERS - Phase 19 === // === RESTORE WEARER LAYERS ===
boolean[] savedLayers = storedLayers.remove(player.getId()); boolean[] savedLayers = storedLayers.remove(player.getId());
if (savedLayers != null) { if (savedLayers != null) {
ClothesRenderHelper.restoreWearerLayers(model, savedLayers); ClothesRenderHelper.restoreWearerLayers(model, savedLayers);

View File

@@ -4,6 +4,9 @@ import com.mojang.logging.LogUtils;
import com.tiedup.remake.client.animation.AnimationStateRegistry; import com.tiedup.remake.client.animation.AnimationStateRegistry;
import com.tiedup.remake.client.animation.BondageAnimationManager; import com.tiedup.remake.client.animation.BondageAnimationManager;
import com.tiedup.remake.client.animation.PendingAnimationManager; import com.tiedup.remake.client.animation.PendingAnimationManager;
import com.tiedup.remake.client.animation.context.AnimationContext;
import com.tiedup.remake.client.animation.context.AnimationContextResolver;
import com.tiedup.remake.client.animation.context.RegionBoneMapper;
import com.tiedup.remake.client.animation.util.AnimationIdBuilder; import com.tiedup.remake.client.animation.util.AnimationIdBuilder;
import com.tiedup.remake.client.events.CellHighlightHandler; import com.tiedup.remake.client.events.CellHighlightHandler;
import com.tiedup.remake.client.events.LeashProxyClientHandler; import com.tiedup.remake.client.events.LeashProxyClientHandler;
@@ -13,15 +16,12 @@ import com.tiedup.remake.client.state.MovementStyleClientState;
import com.tiedup.remake.client.state.PetBedClientState; import com.tiedup.remake.client.state.PetBedClientState;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment; import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.v2.bondage.movement.MovementStyle; import com.tiedup.remake.v2.bondage.movement.MovementStyle;
import com.tiedup.remake.client.animation.context.AnimationContext;
import com.tiedup.remake.client.animation.context.AnimationContextResolver;
import com.tiedup.remake.client.animation.context.RegionBoneMapper;
import com.tiedup.remake.state.HumanChairHelper;
import com.tiedup.remake.state.PlayerBindState;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@@ -109,8 +109,9 @@ public class AnimationTickHandler {
// Check if player has ANY V2 bondage item equipped (not just ARMS). // Check if player has ANY V2 bondage item equipped (not just ARMS).
// isTiedUp() only checks ARMS, but items on LEGS, HEAD, etc. also need animation. // isTiedUp() only checks ARMS, but items on LEGS, HEAD, etc. also need animation.
boolean isTied = state != null && (state.isTiedUp() boolean isTied =
|| V2EquipmentHelper.hasAnyEquipment(player)); state != null &&
(state.isTiedUp() || V2EquipmentHelper.hasAnyEquipment(player));
boolean wasTied = boolean wasTied =
AnimationStateRegistry.getLastTiedState().getOrDefault(uuid, false); AnimationStateRegistry.getLastTiedState().getOrDefault(uuid, false);
@@ -175,9 +176,11 @@ public class AnimationTickHandler {
if (isTied) { if (isTied) {
// Resolve V2 equipped items // Resolve V2 equipped items
IV2BondageEquipment equipment = V2EquipmentHelper.getEquipment(player); IV2BondageEquipment equipment = V2EquipmentHelper.getEquipment(
Map<BodyRegionV2, ItemStack> equipped = equipment != null player
? equipment.getAllEquipped() : Map.of(); );
Map<BodyRegionV2, ItemStack> equipped =
equipment != null ? equipment.getAllEquipped() : Map.of();
// Resolve ALL V2 items with GLB models and per-item bone ownership // Resolve ALL V2 items with GLB models and per-item bone ownership
java.util.List<RegionBoneMapper.V2ItemAnimInfo> v2Items = java.util.List<RegionBoneMapper.V2ItemAnimInfo> v2Items =
@@ -185,10 +188,22 @@ public class AnimationTickHandler {
if (!v2Items.isEmpty()) { if (!v2Items.isEmpty()) {
// V2 path: multi-item composite animation // V2 path: multi-item composite animation
java.util.Set<String> allOwnedParts = RegionBoneMapper.computeAllOwnedParts(v2Items); java.util.Set<String> allOwnedParts =
MovementStyle activeStyle = MovementStyleClientState.get(player.getUUID()); RegionBoneMapper.computeAllOwnedParts(v2Items);
AnimationContext context = AnimationContextResolver.resolve(player, state, activeStyle); MovementStyle activeStyle = MovementStyleClientState.get(
GltfAnimationApplier.applyMultiItemV2Animation(player, v2Items, context, allOwnedParts); player.getUUID()
);
AnimationContext context = AnimationContextResolver.resolve(
player,
state,
activeStyle
);
GltfAnimationApplier.applyMultiItemV2Animation(
player,
v2Items,
context,
allOwnedParts
);
// Clear V1 tracking so transition back works // Clear V1 tracking so transition back works
AnimationStateRegistry.getLastAnimId().remove(uuid); AnimationStateRegistry.getLastAnimId().remove(uuid);
} else { } else {
@@ -197,11 +212,19 @@ public class AnimationTickHandler {
GltfAnimationApplier.clearV2Animation(player); GltfAnimationApplier.clearV2Animation(player);
} }
String animId = buildAnimationId(player, state); String animId = buildAnimationId(player, state);
String lastId = AnimationStateRegistry.getLastAnimId().get(uuid); String lastId = AnimationStateRegistry.getLastAnimId().get(
uuid
);
if (!animId.equals(lastId)) { if (!animId.equals(lastId)) {
boolean success = BondageAnimationManager.playAnimation(player, animId); boolean success = BondageAnimationManager.playAnimation(
player,
animId
);
if (success) { if (success) {
AnimationStateRegistry.getLastAnimId().put(uuid, animId); AnimationStateRegistry.getLastAnimId().put(
uuid,
animId
);
} }
} }
} }
@@ -236,8 +259,14 @@ public class AnimationTickHandler {
} }
// Derive bound state from V2 regions (works client-side, synced via capability) // Derive bound state from V2 regions (works client-side, synced via capability)
boolean armsBound = V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS); boolean armsBound = V2EquipmentHelper.isRegionOccupied(
boolean legsBound = V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.LEGS); player,
BodyRegionV2.ARMS
);
boolean legsBound = V2EquipmentHelper.isRegionOccupied(
player,
BodyRegionV2.LEGS
);
// V1 fallback: if no V2 regions are set but player is tied, derive from ItemBind NBT // V1 fallback: if no V2 regions are set but player is tied, derive from ItemBind NBT
if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) { if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) {

View File

@@ -15,8 +15,8 @@ import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.IV2BondageEquipment; import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@@ -45,7 +45,8 @@ import net.minecraftforge.fml.common.Mod;
public class NpcAnimationTickHandler { public class NpcAnimationTickHandler {
/** Track last animation ID per NPC to avoid redundant updates */ /** Track last animation ID per NPC to avoid redundant updates */
private static final Map<UUID, String> lastNpcAnimId = new ConcurrentHashMap<>(); private static final Map<UUID, String> lastNpcAnimId =
new ConcurrentHashMap<>();
/** /**
* Client tick: update animations for all loaded AbstractTiedUpNpc instances. * Client tick: update animations for all loaded AbstractTiedUpNpc instances.
@@ -91,18 +92,31 @@ public class NpcAnimationTickHandler {
if (inPose) { if (inPose) {
// Resolve V2 equipment map // Resolve V2 equipment map
IV2BondageEquipment equipment = V2EquipmentHelper.getEquipment(entity); IV2BondageEquipment equipment = V2EquipmentHelper.getEquipment(
Map<BodyRegionV2, net.minecraft.world.item.ItemStack> equipped = equipment != null entity
? equipment.getAllEquipped() : Map.of(); );
RegionBoneMapper.GlbModelResult glbResult = RegionBoneMapper.resolveWinningItem(equipped); Map<BodyRegionV2, net.minecraft.world.item.ItemStack> equipped =
equipment != null ? equipment.getAllEquipped() : Map.of();
RegionBoneMapper.GlbModelResult glbResult =
RegionBoneMapper.resolveWinningItem(equipped);
if (glbResult != null) { if (glbResult != null) {
// V2 path: dual-layer animation with per-item bone ownership // V2 path: dual-layer animation with per-item bone ownership
RegionBoneMapper.BoneOwnership ownership = RegionBoneMapper.BoneOwnership ownership =
RegionBoneMapper.computePerItemParts(equipped, glbResult.winningItem()); RegionBoneMapper.computePerItemParts(
AnimationContext context = AnimationContextResolver.resolveNpc(entity); equipped,
GltfAnimationApplier.applyV2Animation(entity, glbResult.modelLoc(), glbResult.winningItem()
glbResult.animSource(), context, ownership); );
AnimationContext context = AnimationContextResolver.resolveNpc(
entity
);
GltfAnimationApplier.applyV2Animation(
entity,
glbResult.modelLoc(),
glbResult.animSource(),
context,
ownership
);
lastNpcAnimId.remove(uuid); lastNpcAnimId.remove(uuid);
} else { } else {
// V1 fallback: JSON-based PlayerAnimator animations // V1 fallback: JSON-based PlayerAnimator animations
@@ -118,7 +132,10 @@ public class NpcAnimationTickHandler {
} }
} }
} else { } else {
if (lastNpcAnimId.containsKey(uuid) || GltfAnimationApplier.hasActiveState(entity)) { if (
lastNpcAnimId.containsKey(uuid) ||
GltfAnimationApplier.hasActiveState(entity)
) {
if (GltfAnimationApplier.hasActiveState(entity)) { if (GltfAnimationApplier.hasActiveState(entity)) {
GltfAnimationApplier.clearV2Animation(entity); GltfAnimationApplier.clearV2Animation(entity);
} else { } else {
@@ -141,7 +158,9 @@ public class NpcAnimationTickHandler {
positionPrefix = "kneel"; positionPrefix = "kneel";
} }
net.minecraft.world.item.ItemStack bind = entity.getEquipment(BodyRegionV2.ARMS); net.minecraft.world.item.ItemStack bind = entity.getEquipment(
BodyRegionV2.ARMS
);
PoseType poseType = PoseType.STANDARD; PoseType poseType = PoseType.STANDARD;
boolean hasBind = false; boolean hasBind = false;
@@ -151,8 +170,14 @@ public class NpcAnimationTickHandler {
} }
// Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder) // Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder)
boolean armsBound = V2EquipmentHelper.isRegionOccupied(entity, BodyRegionV2.ARMS); boolean armsBound = V2EquipmentHelper.isRegionOccupied(
boolean legsBound = V2EquipmentHelper.isRegionOccupied(entity, BodyRegionV2.LEGS); entity,
BodyRegionV2.ARMS
);
boolean legsBound = V2EquipmentHelper.isRegionOccupied(
entity,
BodyRegionV2.LEGS
);
// V1 fallback: if no V2 regions set but NPC has a bind, derive from ItemBind NBT // V1 fallback: if no V2 regions set but NPC has a bind, derive from ItemBind NBT
if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) { if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) {

View File

@@ -82,9 +82,7 @@ public final class AnimationIdBuilder {
return poseType.getBindTypeName(); return poseType.getBindTypeName();
} }
// ========================================
// Unified Build Method // Unified Build Method
// ========================================
/** /**
* Build animation ID string for entities. * Build animation ID string for entities.

View File

@@ -18,7 +18,6 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
/** /**
* Phase 5: Blindfold Rendering
* *
* Based on the original TiedUp! mod (1.12.2) by Yuti & Marl Velius. * Based on the original TiedUp! mod (1.12.2) by Yuti & Marl Velius.
* *
@@ -109,9 +108,7 @@ public class BlindfoldRenderEventHandler {
// Set opacity: hardcore forces full opacity, otherwise use config // Set opacity: hardcore forces full opacity, otherwise use config
float opacity = hardcore float opacity = hardcore
? 1.0F ? 1.0F
: ModConfig.CLIENT.blindfoldOverlayOpacity : ModConfig.CLIENT.blindfoldOverlayOpacity.get().floatValue();
.get()
.floatValue();
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, opacity); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, opacity);
RenderSystem.enableBlend(); RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc(); RenderSystem.defaultBlendFunc();

View File

@@ -1,11 +1,11 @@
package com.tiedup.remake.client.events; package com.tiedup.remake.client.events;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.BindVariant; import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;

View File

@@ -2,8 +2,8 @@ package com.tiedup.remake.client.events;
import com.tiedup.remake.items.base.*; import com.tiedup.remake.items.base.*;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.v2.bondage.IV2BondageItem;
import com.tiedup.remake.network.selfbondage.PacketSelfBondage; import com.tiedup.remake.network.selfbondage.PacketSelfBondage;
import com.tiedup.remake.v2.bondage.IV2BondageItem;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;

View File

@@ -31,7 +31,7 @@ public final class GlbParser {
private static final int GLB_MAGIC = 0x46546C67; // "glTF" private static final int GLB_MAGIC = 0x46546C67; // "glTF"
private static final int GLB_VERSION = 2; private static final int GLB_VERSION = 2;
private static final int CHUNK_JSON = 0x4E4F534A; // "JSON" private static final int CHUNK_JSON = 0x4E4F534A; // "JSON"
private static final int CHUNK_BIN = 0x004E4942; // "BIN\0" private static final int CHUNK_BIN = 0x004E4942; // "BIN\0"
private GlbParser() {} private GlbParser() {}
@@ -43,9 +43,12 @@ public final class GlbParser {
* @return parsed GltfData * @return parsed GltfData
* @throws IOException if the file is malformed or I/O fails * @throws IOException if the file is malformed or I/O fails
*/ */
public static GltfData parse(InputStream input, String debugName) throws IOException { public static GltfData parse(InputStream input, String debugName)
throws IOException {
byte[] allBytes = input.readAllBytes(); byte[] allBytes = input.readAllBytes();
ByteBuffer buf = ByteBuffer.wrap(allBytes).order(ByteOrder.LITTLE_ENDIAN); ByteBuffer buf = ByteBuffer.wrap(allBytes).order(
ByteOrder.LITTLE_ENDIAN
);
// -- Header -- // -- Header --
int magic = buf.getInt(); int magic = buf.getInt();
@@ -54,7 +57,9 @@ public final class GlbParser {
} }
int version = buf.getInt(); int version = buf.getInt();
if (version != GLB_VERSION) { if (version != GLB_VERSION) {
throw new IOException("Unsupported GLB version " + version + " in " + debugName); throw new IOException(
"Unsupported GLB version " + version + " in " + debugName
);
} }
int totalLength = buf.getInt(); int totalLength = buf.getInt();
@@ -105,12 +110,18 @@ public final class GlbParser {
for (int j = 0; j < skinJoints.size(); j++) { for (int j = 0; j < skinJoints.size(); j++) {
int nodeIdx = skinJoints.get(j).getAsInt(); int nodeIdx = skinJoints.get(j).getAsInt();
JsonObject node = nodes.get(nodeIdx).getAsJsonObject(); JsonObject node = nodes.get(nodeIdx).getAsJsonObject();
String name = node.has("name") ? node.get("name").getAsString() : "joint_" + j; String name = node.has("name")
? node.get("name").getAsString()
: "joint_" + j;
if (GltfBoneMapper.isKnownBone(name)) { if (GltfBoneMapper.isKnownBone(name)) {
skinJointRemap[j] = filteredJointNodes.size(); skinJointRemap[j] = filteredJointNodes.size();
filteredJointNodes.add(nodeIdx); filteredJointNodes.add(nodeIdx);
} else { } else {
LOGGER.debug("[GltfPipeline] Skipping non-deforming bone: '{}' (node {})", name, nodeIdx); LOGGER.debug(
"[GltfPipeline] Skipping non-deforming bone: '{}' (node {})",
name,
nodeIdx
);
} }
} }
@@ -134,14 +145,18 @@ public final class GlbParser {
int nodeIdx = filteredJointNodes.get(j); int nodeIdx = filteredJointNodes.get(j);
JsonObject node = nodes.get(nodeIdx).getAsJsonObject(); JsonObject node = nodes.get(nodeIdx).getAsJsonObject();
jointNames[j] = node.has("name") ? node.get("name").getAsString() : "joint_" + j; jointNames[j] = node.has("name")
? node.get("name").getAsString()
: "joint_" + j;
// Rest rotation // Rest rotation
if (node.has("rotation")) { if (node.has("rotation")) {
JsonArray r = node.getAsJsonArray("rotation"); JsonArray r = node.getAsJsonArray("rotation");
restRotations[j] = new Quaternionf( restRotations[j] = new Quaternionf(
r.get(0).getAsFloat(), r.get(1).getAsFloat(), r.get(0).getAsFloat(),
r.get(2).getAsFloat(), r.get(3).getAsFloat() r.get(1).getAsFloat(),
r.get(2).getAsFloat(),
r.get(3).getAsFloat()
); );
} else { } else {
restRotations[j] = new Quaternionf(); // identity restRotations[j] = new Quaternionf(); // identity
@@ -151,7 +166,9 @@ public final class GlbParser {
if (node.has("translation")) { if (node.has("translation")) {
JsonArray t = node.getAsJsonArray("translation"); JsonArray t = node.getAsJsonArray("translation");
restTranslations[j] = new Vector3f( restTranslations[j] = new Vector3f(
t.get(0).getAsFloat(), t.get(1).getAsFloat(), t.get(2).getAsFloat() t.get(0).getAsFloat(),
t.get(1).getAsFloat(),
t.get(2).getAsFloat()
); );
} else { } else {
restTranslations[j] = new Vector3f(); restTranslations[j] = new Vector3f();
@@ -179,7 +196,12 @@ public final class GlbParser {
Matrix4f[] inverseBindMatrices = new Matrix4f[jointCount]; Matrix4f[] inverseBindMatrices = new Matrix4f[jointCount];
if (skin.has("inverseBindMatrices")) { if (skin.has("inverseBindMatrices")) {
int ibmAccessor = skin.get("inverseBindMatrices").getAsInt(); int ibmAccessor = skin.get("inverseBindMatrices").getAsInt();
float[] ibmData = GlbParserUtils.readFloatAccessor(accessors, bufferViews, binData, ibmAccessor); float[] ibmData = GlbParserUtils.readFloatAccessor(
accessors,
bufferViews,
binData,
ibmAccessor
);
for (int origJ = 0; origJ < skinJoints.size(); origJ++) { for (int origJ = 0; origJ < skinJoints.size(); origJ++) {
int newJ = skinJointRemap[origJ]; int newJ = skinJointRemap[origJ];
if (newJ >= 0) { if (newJ >= 0) {
@@ -200,7 +222,9 @@ public final class GlbParser {
if (meshes != null) { if (meshes != null) {
for (int mi = 0; mi < meshes.size(); mi++) { for (int mi = 0; mi < meshes.size(); mi++) {
JsonObject mesh = meshes.get(mi).getAsJsonObject(); JsonObject mesh = meshes.get(mi).getAsJsonObject();
String meshName = mesh.has("name") ? mesh.get("name").getAsString() : ""; String meshName = mesh.has("name")
? mesh.get("name").getAsString()
: "";
if (!"Player".equals(meshName)) { if (!"Player".equals(meshName)) {
targetMeshIdx = mi; targetMeshIdx = mi;
} }
@@ -238,15 +262,27 @@ public final class GlbParser {
// -- Read this primitive's vertex data -- // -- Read this primitive's vertex data --
float[] primPositions = GlbParserUtils.readFloatAccessor( float[] primPositions = GlbParserUtils.readFloatAccessor(
accessors, bufferViews, binData, accessors,
bufferViews,
binData,
attributes.get("POSITION").getAsInt() attributes.get("POSITION").getAsInt()
); );
float[] primNormals = attributes.has("NORMAL") float[] primNormals = attributes.has("NORMAL")
? GlbParserUtils.readFloatAccessor(accessors, bufferViews, binData, attributes.get("NORMAL").getAsInt()) ? GlbParserUtils.readFloatAccessor(
accessors,
bufferViews,
binData,
attributes.get("NORMAL").getAsInt()
)
: new float[primPositions.length]; : new float[primPositions.length];
float[] primTexCoords = attributes.has("TEXCOORD_0") float[] primTexCoords = attributes.has("TEXCOORD_0")
? GlbParserUtils.readFloatAccessor(accessors, bufferViews, binData, attributes.get("TEXCOORD_0").getAsInt()) ? GlbParserUtils.readFloatAccessor(
: new float[primPositions.length / 3 * 2]; accessors,
bufferViews,
binData,
attributes.get("TEXCOORD_0").getAsInt()
)
: new float[(primPositions.length / 3) * 2];
int primVertexCount = primPositions.length / 3; int primVertexCount = primPositions.length / 3;
@@ -254,13 +290,16 @@ public final class GlbParser {
int[] primIndices; int[] primIndices;
if (primitive.has("indices")) { if (primitive.has("indices")) {
primIndices = GlbParserUtils.readIntAccessor( primIndices = GlbParserUtils.readIntAccessor(
accessors, bufferViews, binData, accessors,
bufferViews,
binData,
primitive.get("indices").getAsInt() primitive.get("indices").getAsInt()
); );
} else { } else {
// Non-indexed: generate sequential indices // Non-indexed: generate sequential indices
primIndices = new int[primVertexCount]; primIndices = new int[primVertexCount];
for (int i = 0; i < primVertexCount; i++) primIndices[i] = i; for (int i = 0; i < primVertexCount; i++) primIndices[i] =
i;
} }
// Offset indices by cumulative vertex count from prior primitives // Offset indices by cumulative vertex count from prior primitives
@@ -276,14 +315,19 @@ public final class GlbParser {
if (attributes.has("JOINTS_0")) { if (attributes.has("JOINTS_0")) {
primJoints = GlbParserUtils.readIntAccessor( primJoints = GlbParserUtils.readIntAccessor(
accessors, bufferViews, binData, accessors,
bufferViews,
binData,
attributes.get("JOINTS_0").getAsInt() attributes.get("JOINTS_0").getAsInt()
); );
// Remap vertex joint indices from original skin order to filtered order // Remap vertex joint indices from original skin order to filtered order
for (int i = 0; i < primJoints.length; i++) { for (int i = 0; i < primJoints.length; i++) {
int origIdx = primJoints[i]; int origIdx = primJoints[i];
if (origIdx >= 0 && origIdx < skinJointRemap.length) { if (origIdx >= 0 && origIdx < skinJointRemap.length) {
primJoints[i] = skinJointRemap[origIdx] >= 0 ? skinJointRemap[origIdx] : 0; primJoints[i] =
skinJointRemap[origIdx] >= 0
? skinJointRemap[origIdx]
: 0;
} else { } else {
primJoints[i] = 0; primJoints[i] = 0;
} }
@@ -291,7 +335,9 @@ public final class GlbParser {
} }
if (attributes.has("WEIGHTS_0")) { if (attributes.has("WEIGHTS_0")) {
primWeights = GlbParserUtils.readFloatAccessor( primWeights = GlbParserUtils.readFloatAccessor(
accessors, bufferViews, binData, accessors,
bufferViews,
binData,
attributes.get("WEIGHTS_0").getAsInt() attributes.get("WEIGHTS_0").getAsInt()
); );
} }
@@ -304,10 +350,18 @@ public final class GlbParser {
matName = materialNames[matIdx]; matName = materialNames[matIdx];
} }
} }
boolean isTintable = matName != null && matName.startsWith("tintable_"); boolean isTintable =
matName != null && matName.startsWith("tintable_");
String tintChannel = isTintable ? matName : null; String tintChannel = isTintable ? matName : null;
parsedPrimitives.add(new GltfData.Primitive(primIndices, matName, isTintable, tintChannel)); parsedPrimitives.add(
new GltfData.Primitive(
primIndices,
matName,
isTintable,
tintChannel
)
);
allPositions.add(primPositions); allPositions.add(primPositions);
allNormals.add(primNormals); allNormals.add(primNormals);
@@ -327,16 +381,26 @@ public final class GlbParser {
// Build union of all primitive indices (for backward-compat indices() accessor) // Build union of all primitive indices (for backward-compat indices() accessor)
int totalIndices = 0; int totalIndices = 0;
for (GltfData.Primitive p : parsedPrimitives) totalIndices += p.indices().length; for (GltfData.Primitive p : parsedPrimitives)
totalIndices += p.indices().length;
indices = new int[totalIndices]; indices = new int[totalIndices];
int offset = 0; int offset = 0;
for (GltfData.Primitive p : parsedPrimitives) { for (GltfData.Primitive p : parsedPrimitives) {
System.arraycopy(p.indices(), 0, indices, offset, p.indices().length); System.arraycopy(
p.indices(),
0,
indices,
offset,
p.indices().length
);
offset += p.indices().length; offset += p.indices().length;
} }
} else { } else {
// Animation-only GLB: no mesh data // Animation-only GLB: no mesh data
LOGGER.info("[GltfPipeline] No mesh found in '{}' (animation-only GLB)", debugName); LOGGER.info(
"[GltfPipeline] No mesh found in '{}' (animation-only GLB)",
debugName
);
positions = new float[0]; positions = new float[0];
normals = new float[0]; normals = new float[0];
texCoords = new float[0]; texCoords = new float[0];
@@ -352,12 +416,23 @@ public final class GlbParser {
if (animations != null) { if (animations != null) {
for (int ai = 0; ai < animations.size(); ai++) { for (int ai = 0; ai < animations.size(); ai++) {
JsonObject anim = animations.get(ai).getAsJsonObject(); JsonObject anim = animations.get(ai).getAsJsonObject();
String animName = anim.has("name") ? anim.get("name").getAsString() : "animation_" + ai; String animName = anim.has("name")
? anim.get("name").getAsString()
: "animation_" + ai;
// Strip the "ArmatureName|" prefix if present (Blender convention) // Strip the "ArmatureName|" prefix if present (Blender convention)
if (animName.contains("|")) { if (animName.contains("|")) {
animName = animName.substring(animName.lastIndexOf('|') + 1); animName = animName.substring(
animName.lastIndexOf('|') + 1
);
} }
GltfData.AnimationClip clip = parseAnimation(anim, accessors, bufferViews, binData, nodeToJoint, jointCount); GltfData.AnimationClip clip = parseAnimation(
anim,
accessors,
bufferViews,
binData,
nodeToJoint,
jointCount
);
if (clip != null) { if (clip != null) {
allClips.put(animName, clip); allClips.put(animName, clip);
} }
@@ -365,19 +440,39 @@ public final class GlbParser {
} }
// Default animation = first clip (for backward compat) // Default animation = first clip (for backward compat)
GltfData.AnimationClip animClip = allClips.isEmpty() ? null : allClips.values().iterator().next(); GltfData.AnimationClip animClip = allClips.isEmpty()
? null
: allClips.values().iterator().next();
LOGGER.info("[GltfPipeline] Parsed '{}': vertices={}, indices={}, joints={}, animations={}", LOGGER.info(
debugName, vertexCount, indices.length, jointCount, allClips.size()); "[GltfPipeline] Parsed '{}': vertices={}, indices={}, joints={}, animations={}",
debugName,
vertexCount,
indices.length,
jointCount,
allClips.size()
);
for (String name : allClips.keySet()) { for (String name : allClips.keySet()) {
LOGGER.debug("[GltfPipeline] animation: '{}'", name); LOGGER.debug("[GltfPipeline] animation: '{}'", name);
} }
for (int j = 0; j < jointCount; j++) { for (int j = 0; j < jointCount; j++) {
Quaternionf rq = restRotations[j]; Quaternionf rq = restRotations[j];
Vector3f rt = restTranslations[j]; Vector3f rt = restTranslations[j];
LOGGER.debug(String.format("[GltfPipeline] joint[%d] = '%s', parent=%d, restQ=(%.3f,%.3f,%.3f,%.3f) restT=(%.3f,%.3f,%.3f)", LOGGER.debug(
j, jointNames[j], parentJointIndices[j], String.format(
rq.x, rq.y, rq.z, rq.w, rt.x, rt.y, rt.z)); "[GltfPipeline] joint[%d] = '%s', parent=%d, restQ=(%.3f,%.3f,%.3f,%.3f) restT=(%.3f,%.3f,%.3f)",
j,
jointNames[j],
parentJointIndices[j],
rq.x,
rq.y,
rq.z,
rq.w,
rt.x,
rt.y,
rt.z
)
);
} }
// Log animation translation channels for default clip (BEFORE MC conversion) // Log animation translation channels for default clip (BEFORE MC conversion)
@@ -387,16 +482,28 @@ public final class GlbParser {
if (j < animTrans.length && animTrans[j] != null) { if (j < animTrans.length && animTrans[j] != null) {
Vector3f at = animTrans[j][0]; // first frame Vector3f at = animTrans[j][0]; // first frame
Vector3f rt = restTranslations[j]; Vector3f rt = restTranslations[j];
LOGGER.debug(String.format( LOGGER.debug(
"[GltfPipeline] joint[%d] '%s' has ANIM TRANSLATION: (%.4f,%.4f,%.4f) vs rest (%.4f,%.4f,%.4f) delta=(%.4f,%.4f,%.4f)", String.format(
j, jointNames[j], "[GltfPipeline] joint[%d] '%s' has ANIM TRANSLATION: (%.4f,%.4f,%.4f) vs rest (%.4f,%.4f,%.4f) delta=(%.4f,%.4f,%.4f)",
at.x, at.y, at.z, j,
rt.x, rt.y, rt.z, jointNames[j],
at.x - rt.x, at.y - rt.y, at.z - rt.z)); at.x,
at.y,
at.z,
rt.x,
rt.y,
rt.z,
at.x - rt.x,
at.y - rt.y,
at.z - rt.z
)
);
} }
} }
} else { } else {
LOGGER.debug("[GltfPipeline] Default animation has NO translation channels"); LOGGER.debug(
"[GltfPipeline] Default animation has NO translation channels"
);
} }
// Save raw glTF rotations BEFORE coordinate conversion (for pose converter) // Save raw glTF rotations BEFORE coordinate conversion (for pose converter)
@@ -409,10 +516,18 @@ public final class GlbParser {
// Build raw copies of ALL animation clips (before MC conversion) // Build raw copies of ALL animation clips (before MC conversion)
Map<String, GltfData.AnimationClip> rawAllClips = new LinkedHashMap<>(); Map<String, GltfData.AnimationClip> rawAllClips = new LinkedHashMap<>();
for (Map.Entry<String, GltfData.AnimationClip> entry : allClips.entrySet()) { for (Map.Entry<
rawAllClips.put(entry.getKey(), GlbParserUtils.deepCopyClip(entry.getValue())); String,
GltfData.AnimationClip
> entry : allClips.entrySet()) {
rawAllClips.put(
entry.getKey(),
GlbParserUtils.deepCopyClip(entry.getValue())
);
} }
GltfData.AnimationClip rawAnimClip = rawAllClips.isEmpty() ? null : rawAllClips.values().iterator().next(); GltfData.AnimationClip rawAnimClip = rawAllClips.isEmpty()
? null
: rawAllClips.values().iterator().next();
// Convert from glTF coordinate system (Y-up, faces +Z) to MC (Y-up, faces -Z) // Convert from glTF coordinate system (Y-up, faces +Z) to MC (Y-up, faces -Z)
// This is a 180° rotation around Y: negate X and Z for all spatial data // This is a 180° rotation around Y: negate X and Z for all spatial data
@@ -420,22 +535,39 @@ public final class GlbParser {
for (GltfData.AnimationClip clip : allClips.values()) { for (GltfData.AnimationClip clip : allClips.values()) {
GlbParserUtils.convertAnimationToMinecraftSpace(clip, jointCount); GlbParserUtils.convertAnimationToMinecraftSpace(clip, jointCount);
} }
convertToMinecraftSpace(positions, normals, restTranslations, restRotations, convertToMinecraftSpace(
inverseBindMatrices, null, jointCount); // pass null — clips already converted above positions,
LOGGER.debug("[GltfPipeline] Converted all data to Minecraft coordinate space"); normals,
restTranslations,
restRotations,
inverseBindMatrices,
null,
jointCount
); // pass null — clips already converted above
LOGGER.debug(
"[GltfPipeline] Converted all data to Minecraft coordinate space"
);
return new GltfData( return new GltfData(
positions, normals, texCoords, positions,
indices, meshJoints, weights, normals,
jointNames, parentJointIndices, texCoords,
indices,
meshJoints,
weights,
jointNames,
parentJointIndices,
inverseBindMatrices, inverseBindMatrices,
restRotations, restTranslations, restRotations,
restTranslations,
rawRestRotations, rawRestRotations,
rawAnimClip, rawAnimClip,
animClip, animClip,
allClips, rawAllClips, allClips,
rawAllClips,
parsedPrimitives, parsedPrimitives,
vertexCount, jointCount vertexCount,
jointCount
); );
} }
@@ -443,9 +575,11 @@ public final class GlbParser {
private static GltfData.AnimationClip parseAnimation( private static GltfData.AnimationClip parseAnimation(
JsonObject animation, JsonObject animation,
JsonArray accessors, JsonArray bufferViews, JsonArray accessors,
JsonArray bufferViews,
ByteBuffer binData, ByteBuffer binData,
int[] nodeToJoint, int jointCount int[] nodeToJoint,
int jointCount
) { ) {
JsonArray channels = animation.getAsJsonArray("channels"); JsonArray channels = animation.getAsJsonArray("channels");
JsonArray samplers = animation.getAsJsonArray("samplers"); JsonArray samplers = animation.getAsJsonArray("samplers");
@@ -465,27 +599,35 @@ public final class GlbParser {
String path = target.get("path").getAsString(); String path = target.get("path").getAsString();
int nodeIdx = target.get("node").getAsInt(); int nodeIdx = target.get("node").getAsInt();
if (nodeIdx >= nodeToJoint.length || nodeToJoint[nodeIdx] < 0) continue; if (
nodeIdx >= nodeToJoint.length || nodeToJoint[nodeIdx] < 0
) continue;
int jointIdx = nodeToJoint[nodeIdx]; int jointIdx = nodeToJoint[nodeIdx];
int samplerIdx = channel.get("sampler").getAsInt(); int samplerIdx = channel.get("sampler").getAsInt();
JsonObject sampler = samplers.get(samplerIdx).getAsJsonObject(); JsonObject sampler = samplers.get(samplerIdx).getAsJsonObject();
float[] times = GlbParserUtils.readFloatAccessor( float[] times = GlbParserUtils.readFloatAccessor(
accessors, bufferViews, binData, accessors,
bufferViews,
binData,
sampler.get("input").getAsInt() sampler.get("input").getAsInt()
); );
if ("rotation".equals(path)) { if ("rotation".equals(path)) {
float[] quats = GlbParserUtils.readFloatAccessor( float[] quats = GlbParserUtils.readFloatAccessor(
accessors, bufferViews, binData, accessors,
bufferViews,
binData,
sampler.get("output").getAsInt() sampler.get("output").getAsInt()
); );
Quaternionf[] qArr = new Quaternionf[times.length]; Quaternionf[] qArr = new Quaternionf[times.length];
for (int i = 0; i < times.length; i++) { for (int i = 0; i < times.length; i++) {
qArr[i] = new Quaternionf( qArr[i] = new Quaternionf(
quats[i * 4], quats[i * 4 + 1], quats[i * 4],
quats[i * 4 + 2], quats[i * 4 + 3] quats[i * 4 + 1],
quats[i * 4 + 2],
quats[i * 4 + 3]
); );
} }
rotJoints.add(jointIdx); rotJoints.add(jointIdx);
@@ -493,13 +635,17 @@ public final class GlbParser {
rotValues.add(qArr); rotValues.add(qArr);
} else if ("translation".equals(path)) { } else if ("translation".equals(path)) {
float[] vecs = GlbParserUtils.readFloatAccessor( float[] vecs = GlbParserUtils.readFloatAccessor(
accessors, bufferViews, binData, accessors,
bufferViews,
binData,
sampler.get("output").getAsInt() sampler.get("output").getAsInt()
); );
Vector3f[] tArr = new Vector3f[times.length]; Vector3f[] tArr = new Vector3f[times.length];
for (int i = 0; i < times.length; i++) { for (int i = 0; i < times.length; i++) {
tArr[i] = new Vector3f( tArr[i] = new Vector3f(
vecs[i * 3], vecs[i * 3 + 1], vecs[i * 3 + 2] vecs[i * 3],
vecs[i * 3 + 1],
vecs[i * 3 + 2]
); );
} }
transJoints.add(jointIdx); transJoints.add(jointIdx);
@@ -523,7 +669,8 @@ public final class GlbParser {
Quaternionf[] vals = rotValues.get(i); Quaternionf[] vals = rotValues.get(i);
rotations[jIdx] = new Quaternionf[frameCount]; rotations[jIdx] = new Quaternionf[frameCount];
for (int f = 0; f < frameCount; f++) { for (int f = 0; f < frameCount; f++) {
rotations[jIdx][f] = f < vals.length ? vals[f] : vals[vals.length - 1]; rotations[jIdx][f] =
f < vals.length ? vals[f] : vals[vals.length - 1];
} }
} }
@@ -534,19 +681,27 @@ public final class GlbParser {
Vector3f[] vals = transValues.get(i); Vector3f[] vals = transValues.get(i);
translations[jIdx] = new Vector3f[frameCount]; translations[jIdx] = new Vector3f[frameCount];
for (int f = 0; f < frameCount; f++) { for (int f = 0; f < frameCount; f++) {
translations[jIdx][f] = f < vals.length translations[jIdx][f] =
? new Vector3f(vals[f]) f < vals.length
: new Vector3f(vals[vals.length - 1]); ? new Vector3f(vals[f])
: new Vector3f(vals[vals.length - 1]);
} }
} }
// Log translation channels found // Log translation channels found
if (!transJoints.isEmpty()) { if (!transJoints.isEmpty()) {
LOGGER.debug("[GltfPipeline] Animation has {} translation channel(s)", LOGGER.debug(
transJoints.size()); "[GltfPipeline] Animation has {} translation channel(s)",
transJoints.size()
);
} }
return new GltfData.AnimationClip(timestamps, rotations, translations, frameCount); return new GltfData.AnimationClip(
timestamps,
rotations,
translations,
frameCount
);
} }
// ---- Coordinate system conversion ---- // ---- Coordinate system conversion ----
@@ -562,14 +717,17 @@ public final class GlbParser {
* For matrices: M → C * M * C where C = diag(-1, -1, 1, 1) * For matrices: M → C * M * C where C = diag(-1, -1, 1, 1)
*/ */
private static void convertToMinecraftSpace( private static void convertToMinecraftSpace(
float[] positions, float[] normals, float[] positions,
Vector3f[] restTranslations, Quaternionf[] restRotations, float[] normals,
Vector3f[] restTranslations,
Quaternionf[] restRotations,
Matrix4f[] inverseBindMatrices, Matrix4f[] inverseBindMatrices,
GltfData.AnimationClip animClip, int jointCount GltfData.AnimationClip animClip,
int jointCount
) { ) {
// Vertex positions: negate X and Y // Vertex positions: negate X and Y
for (int i = 0; i < positions.length; i += 3) { for (int i = 0; i < positions.length; i += 3) {
positions[i] = -positions[i]; // X positions[i] = -positions[i]; // X
positions[i + 1] = -positions[i + 1]; // Y positions[i + 1] = -positions[i + 1]; // Y
} }

View File

@@ -77,8 +77,10 @@ public final class GlbParserUtils {
// ---- Accessor reading utilities ---- // ---- Accessor reading utilities ----
public static float[] readFloatAccessor( public static float[] readFloatAccessor(
JsonArray accessors, JsonArray bufferViews, JsonArray accessors,
ByteBuffer binData, int accessorIdx JsonArray bufferViews,
ByteBuffer binData,
int accessorIdx
) { ) {
JsonObject accessor = accessors.get(accessorIdx).getAsJsonObject(); JsonObject accessor = accessors.get(accessorIdx).getAsJsonObject();
int count = accessor.get("count").getAsInt(); int count = accessor.get("count").getAsInt();
@@ -88,9 +90,14 @@ public final class GlbParserUtils {
int bvIdx = accessor.get("bufferView").getAsInt(); int bvIdx = accessor.get("bufferView").getAsInt();
JsonObject bv = bufferViews.get(bvIdx).getAsJsonObject(); JsonObject bv = bufferViews.get(bvIdx).getAsJsonObject();
int byteOffset = (bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0) int byteOffset =
+ (accessor.has("byteOffset") ? accessor.get("byteOffset").getAsInt() : 0); (bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0) +
int byteStride = bv.has("byteStride") ? bv.get("byteStride").getAsInt() : 0; (accessor.has("byteOffset")
? accessor.get("byteOffset").getAsInt()
: 0);
int byteStride = bv.has("byteStride")
? bv.get("byteStride").getAsInt()
: 0;
int totalElements = count * components; int totalElements = count * components;
float[] result = new float[totalElements]; float[] result = new float[totalElements];
@@ -102,7 +109,10 @@ public final class GlbParserUtils {
int pos = byteOffset + i * stride; int pos = byteOffset + i * stride;
for (int c = 0; c < components; c++) { for (int c = 0; c < components; c++) {
binData.position(pos + c * componentSize); binData.position(pos + c * componentSize);
result[i * components + c] = readComponentAsFloat(binData, componentType); result[i * components + c] = readComponentAsFloat(
binData,
componentType
);
} }
} }
@@ -110,8 +120,10 @@ public final class GlbParserUtils {
} }
public static int[] readIntAccessor( public static int[] readIntAccessor(
JsonArray accessors, JsonArray bufferViews, JsonArray accessors,
ByteBuffer binData, int accessorIdx JsonArray bufferViews,
ByteBuffer binData,
int accessorIdx
) { ) {
JsonObject accessor = accessors.get(accessorIdx).getAsJsonObject(); JsonObject accessor = accessors.get(accessorIdx).getAsJsonObject();
int count = accessor.get("count").getAsInt(); int count = accessor.get("count").getAsInt();
@@ -121,9 +133,14 @@ public final class GlbParserUtils {
int bvIdx = accessor.get("bufferView").getAsInt(); int bvIdx = accessor.get("bufferView").getAsInt();
JsonObject bv = bufferViews.get(bvIdx).getAsJsonObject(); JsonObject bv = bufferViews.get(bvIdx).getAsJsonObject();
int byteOffset = (bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0) int byteOffset =
+ (accessor.has("byteOffset") ? accessor.get("byteOffset").getAsInt() : 0); (bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0) +
int byteStride = bv.has("byteStride") ? bv.get("byteStride").getAsInt() : 0; (accessor.has("byteOffset")
? accessor.get("byteOffset").getAsInt()
: 0);
int byteStride = bv.has("byteStride")
? bv.get("byteStride").getAsInt()
: 0;
int totalElements = count * components; int totalElements = count * components;
int[] result = new int[totalElements]; int[] result = new int[totalElements];
@@ -135,22 +152,31 @@ public final class GlbParserUtils {
int pos = byteOffset + i * stride; int pos = byteOffset + i * stride;
for (int c = 0; c < components; c++) { for (int c = 0; c < components; c++) {
binData.position(pos + c * componentSize); binData.position(pos + c * componentSize);
result[i * components + c] = readComponentAsInt(binData, componentType); result[i * components + c] = readComponentAsInt(
binData,
componentType
);
} }
} }
return result; return result;
} }
public static float readComponentAsFloat(ByteBuffer buf, int componentType) { public static float readComponentAsFloat(
ByteBuffer buf,
int componentType
) {
return switch (componentType) { return switch (componentType) {
case FLOAT -> buf.getFloat(); case FLOAT -> buf.getFloat();
case BYTE -> buf.get() / 127.0f; case BYTE -> buf.get() / 127.0f;
case UNSIGNED_BYTE -> (buf.get() & 0xFF) / 255.0f; case UNSIGNED_BYTE -> (buf.get() & 0xFF) / 255.0f;
case SHORT -> buf.getShort() / 32767.0f; case SHORT -> buf.getShort() / 32767.0f;
case UNSIGNED_SHORT -> (buf.getShort() & 0xFFFF) / 65535.0f; case UNSIGNED_SHORT -> (buf.getShort() & 0xFFFF) / 65535.0f;
case UNSIGNED_INT -> (buf.getInt() & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL; case UNSIGNED_INT -> (buf.getInt() & 0xFFFFFFFFL) /
default -> throw new IllegalArgumentException("Unknown component type: " + componentType); (float) 0xFFFFFFFFL;
default -> throw new IllegalArgumentException(
"Unknown component type: " + componentType
);
}; };
} }
@@ -162,7 +188,9 @@ public final class GlbParserUtils {
case UNSIGNED_SHORT -> buf.getShort() & 0xFFFF; case UNSIGNED_SHORT -> buf.getShort() & 0xFFFF;
case UNSIGNED_INT -> buf.getInt(); case UNSIGNED_INT -> buf.getInt();
case FLOAT -> (int) buf.getFloat(); case FLOAT -> (int) buf.getFloat();
default -> throw new IllegalArgumentException("Unknown component type: " + componentType); default -> throw new IllegalArgumentException(
"Unknown component type: " + componentType
);
}; };
} }
@@ -173,7 +201,9 @@ public final class GlbParserUtils {
case "VEC3" -> 3; case "VEC3" -> 3;
case "VEC4" -> 4; case "VEC4" -> 4;
case "MAT4" -> 16; case "MAT4" -> 16;
default -> throw new IllegalArgumentException("Unknown accessor type: " + type); default -> throw new IllegalArgumentException(
"Unknown accessor type: " + type
);
}; };
} }
@@ -182,7 +212,9 @@ public final class GlbParserUtils {
case BYTE, UNSIGNED_BYTE -> 1; case BYTE, UNSIGNED_BYTE -> 1;
case SHORT, UNSIGNED_SHORT -> 2; case SHORT, UNSIGNED_SHORT -> 2;
case UNSIGNED_INT, FLOAT -> 4; case UNSIGNED_INT, FLOAT -> 4;
default -> throw new IllegalArgumentException("Unknown component type: " + componentType); default -> throw new IllegalArgumentException(
"Unknown component type: " + componentType
);
}; };
} }
@@ -191,13 +223,18 @@ public final class GlbParserUtils {
/** /**
* Deep-copy an AnimationClip (preserves original data before MC conversion). * Deep-copy an AnimationClip (preserves original data before MC conversion).
*/ */
public static GltfData.AnimationClip deepCopyClip(GltfData.AnimationClip clip) { public static GltfData.AnimationClip deepCopyClip(
Quaternionf[][] rawRotations = new Quaternionf[clip.rotations().length][]; GltfData.AnimationClip clip
) {
Quaternionf[][] rawRotations =
new Quaternionf[clip.rotations().length][];
for (int j = 0; j < clip.rotations().length; j++) { for (int j = 0; j < clip.rotations().length; j++) {
if (clip.rotations()[j] != null) { if (clip.rotations()[j] != null) {
rawRotations[j] = new Quaternionf[clip.rotations()[j].length]; rawRotations[j] = new Quaternionf[clip.rotations()[j].length];
for (int f = 0; f < clip.rotations()[j].length; f++) { for (int f = 0; f < clip.rotations()[j].length; f++) {
rawRotations[j][f] = new Quaternionf(clip.rotations()[j][f]); rawRotations[j][f] = new Quaternionf(
clip.rotations()[j][f]
);
} }
} }
} }
@@ -206,15 +243,20 @@ public final class GlbParserUtils {
rawTranslations = new Vector3f[clip.translations().length][]; rawTranslations = new Vector3f[clip.translations().length][];
for (int j = 0; j < clip.translations().length; j++) { for (int j = 0; j < clip.translations().length; j++) {
if (clip.translations()[j] != null) { if (clip.translations()[j] != null) {
rawTranslations[j] = new Vector3f[clip.translations()[j].length]; rawTranslations[j] =
new Vector3f[clip.translations()[j].length];
for (int f = 0; f < clip.translations()[j].length; f++) { for (int f = 0; f < clip.translations()[j].length; f++) {
rawTranslations[j][f] = new Vector3f(clip.translations()[j][f]); rawTranslations[j][f] = new Vector3f(
clip.translations()[j][f]
);
} }
} }
} }
} }
return new GltfData.AnimationClip( return new GltfData.AnimationClip(
clip.timestamps().clone(), rawRotations, rawTranslations, clip.timestamps().clone(),
rawRotations,
rawTranslations,
clip.frameCount() clip.frameCount()
); );
} }
@@ -225,7 +267,10 @@ public final class GlbParserUtils {
* Convert an animation clip's rotations and translations to MC space. * Convert an animation clip's rotations and translations to MC space.
* Negate qx/qy for rotations and negate tx/ty for translations. * Negate qx/qy for rotations and negate tx/ty for translations.
*/ */
public static void convertAnimationToMinecraftSpace(GltfData.AnimationClip clip, int jointCount) { public static void convertAnimationToMinecraftSpace(
GltfData.AnimationClip clip,
int jointCount
) {
if (clip == null) return; if (clip == null) return;
Quaternionf[][] rotations = clip.rotations(); Quaternionf[][] rotations = clip.rotations();

View File

@@ -13,7 +13,6 @@ import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@@ -22,6 +21,7 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
/** /**
* V2 Animation Applier -- manages dual-layer animation for V2 bondage items. * V2 Animation Applier -- manages dual-layer animation for V2 bondage items.
@@ -59,22 +59,23 @@ public final class GltfAnimationApplier {
* Keyed by "animSource#context#ownedPartsHash". * Keyed by "animSource#context#ownedPartsHash".
* Same GLB + same context + same owned parts = same KeyframeAnimation. * Same GLB + same context + same owned parts = same KeyframeAnimation.
*/ */
private static final Map<String, KeyframeAnimation> itemAnimCache = new ConcurrentHashMap<>(); private static final Map<String, KeyframeAnimation> itemAnimCache =
new ConcurrentHashMap<>();
/** /**
* Track which composite state is currently active per entity, to avoid redundant replays. * Track which composite state is currently active per entity, to avoid redundant replays.
* Keyed by entity UUID, value is "animSource|context|sortedParts". * Keyed by entity UUID, value is "animSource|context|sortedParts".
*/ */
private static final Map<UUID, String> activeStateKeys = new ConcurrentHashMap<>(); private static final Map<UUID, String> activeStateKeys =
new ConcurrentHashMap<>();
/** Track cache keys where GLB loading failed, to avoid per-tick retries. */ /** Track cache keys where GLB loading failed, to avoid per-tick retries. */
private static final Set<String> failedLoadKeys = ConcurrentHashMap.newKeySet(); private static final Set<String> failedLoadKeys =
ConcurrentHashMap.newKeySet();
private GltfAnimationApplier() {} private GltfAnimationApplier() {}
// ========================================
// INIT (legacy) // INIT (legacy)
// ========================================
/** /**
* Legacy init method -- called by GltfClientSetup. * Legacy init method -- called by GltfClientSetup.
@@ -84,9 +85,7 @@ public final class GltfAnimationApplier {
// No-op: animation layers are managed by BondageAnimationManager // No-op: animation layers are managed by BondageAnimationManager
} }
// ========================================
// V2 DUAL-LAYER API // V2 DUAL-LAYER API
// ========================================
/** /**
* Apply the full V2 animation state: context layer + item layer. * Apply the full V2 animation state: context layer + item layer.
@@ -113,12 +112,17 @@ public final class GltfAnimationApplier {
* @param ownership bone ownership: which parts this item owns vs other items * @param ownership bone ownership: which parts this item owns vs other items
* @return true if the item layer animation was applied successfully * @return true if the item layer animation was applied successfully
*/ */
public static boolean applyV2Animation(LivingEntity entity, ResourceLocation modelLoc, public static boolean applyV2Animation(
@Nullable ResourceLocation animationSource, LivingEntity entity,
AnimationContext context, RegionBoneMapper.BoneOwnership ownership) { ResourceLocation modelLoc,
@Nullable ResourceLocation animationSource,
AnimationContext context,
RegionBoneMapper.BoneOwnership ownership
) {
if (entity == null || modelLoc == null) return false; if (entity == null || modelLoc == null) return false;
ResourceLocation animSource = animationSource != null ? animationSource : modelLoc; ResourceLocation animSource =
animationSource != null ? animationSource : modelLoc;
// Cache key includes both owned and enabled parts for full disambiguation // Cache key includes both owned and enabled parts for full disambiguation
String ownedKey = canonicalPartsKey(ownership.thisParts()); String ownedKey = canonicalPartsKey(ownership.thisParts());
String enabledKey = canonicalPartsKey(ownership.enabledParts()); String enabledKey = canonicalPartsKey(ownership.enabledParts());
@@ -135,7 +139,9 @@ public final class GltfAnimationApplier {
// Parts owned by ANY item (this or others) are disabled on the context layer. // Parts owned by ANY item (this or others) are disabled on the context layer.
// Only free parts remain enabled on context. // Only free parts remain enabled on context.
KeyframeAnimation contextAnim = ContextAnimationFactory.create( KeyframeAnimation contextAnim = ContextAnimationFactory.create(
context, ownership.disabledOnContext()); context,
ownership.disabledOnContext()
);
if (contextAnim != null) { if (contextAnim != null) {
BondageAnimationManager.playContext(entity, contextAnim); BondageAnimationManager.playContext(entity, contextAnim);
} }
@@ -151,18 +157,31 @@ public final class GltfAnimationApplier {
KeyframeAnimation itemAnim = itemAnimCache.get(itemCacheKey); KeyframeAnimation itemAnim = itemAnimCache.get(itemCacheKey);
if (itemAnim == null) { if (itemAnim == null) {
GltfData animData = GlbAnimationResolver.resolveAnimationData(modelLoc, animationSource); GltfData animData = GlbAnimationResolver.resolveAnimationData(
modelLoc,
animationSource
);
if (animData == null) { if (animData == null) {
LOGGER.warn("[GltfPipeline] Failed to load animation GLB: {}", animSource); LOGGER.warn(
"[GltfPipeline] Failed to load animation GLB: {}",
animSource
);
failedLoadKeys.add(itemCacheKey); failedLoadKeys.add(itemCacheKey);
activeStateKeys.put(entity.getUUID(), stateKey); activeStateKeys.put(entity.getUUID(), stateKey);
return false; return false;
} }
// Resolve which named animation to use (with fallback chain + variant selection) // Resolve which named animation to use (with fallback chain + variant selection)
String glbAnimName = GlbAnimationResolver.resolve(animData, context); String glbAnimName = GlbAnimationResolver.resolve(
animData,
context
);
// Pass both owned parts and enabled parts (owned + free) for selective enabling // Pass both owned parts and enabled parts (owned + free) for selective enabling
itemAnim = GltfPoseConverter.convertSelective( itemAnim = GltfPoseConverter.convertSelective(
animData, glbAnimName, ownership.thisParts(), ownership.enabledParts()); animData,
glbAnimName,
ownership.thisParts(),
ownership.enabledParts()
);
itemAnimCache.put(itemCacheKey, itemAnim); itemAnimCache.put(itemCacheKey, itemAnim);
} }
@@ -185,16 +204,24 @@ public final class GltfAnimationApplier {
* @param allOwnedParts union of all owned parts across all items * @param allOwnedParts union of all owned parts across all items
* @return true if the composite animation was applied * @return true if the composite animation was applied
*/ */
public static boolean applyMultiItemV2Animation(LivingEntity entity, public static boolean applyMultiItemV2Animation(
List<RegionBoneMapper.V2ItemAnimInfo> items, LivingEntity entity,
AnimationContext context, Set<String> allOwnedParts) { List<RegionBoneMapper.V2ItemAnimInfo> items,
AnimationContext context,
Set<String> allOwnedParts
) {
if (entity == null || items.isEmpty()) return false; if (entity == null || items.isEmpty()) return false;
// Build composite state key // Build composite state key
StringBuilder keyBuilder = new StringBuilder(); StringBuilder keyBuilder = new StringBuilder();
for (RegionBoneMapper.V2ItemAnimInfo item : items) { for (RegionBoneMapper.V2ItemAnimInfo item : items) {
ResourceLocation src = item.animSource() != null ? item.animSource() : item.modelLoc(); ResourceLocation src =
keyBuilder.append(src).append(':').append(canonicalPartsKey(item.ownedParts())).append(';'); item.animSource() != null ? item.animSource() : item.modelLoc();
keyBuilder
.append(src)
.append(':')
.append(canonicalPartsKey(item.ownedParts()))
.append(';');
} }
keyBuilder.append(context.name()); keyBuilder.append(context.name());
String stateKey = keyBuilder.toString(); String stateKey = keyBuilder.toString();
@@ -205,7 +232,10 @@ public final class GltfAnimationApplier {
} }
// === Layer 1: Context animation === // === Layer 1: Context animation ===
KeyframeAnimation contextAnim = ContextAnimationFactory.create(context, allOwnedParts); KeyframeAnimation contextAnim = ContextAnimationFactory.create(
context,
allOwnedParts
);
if (contextAnim != null) { if (contextAnim != null) {
BondageAnimationManager.playContext(entity, contextAnim); BondageAnimationManager.playContext(entity, contextAnim);
} }
@@ -222,7 +252,8 @@ public final class GltfAnimationApplier {
if (compositeAnim == null) { if (compositeAnim == null) {
KeyframeAnimation.AnimationBuilder builder = KeyframeAnimation.AnimationBuilder builder =
new KeyframeAnimation.AnimationBuilder( new KeyframeAnimation.AnimationBuilder(
dev.kosmx.playerAnim.core.data.AnimationFormat.JSON_EMOTECRAFT); dev.kosmx.playerAnim.core.data.AnimationFormat.JSON_EMOTECRAFT
);
builder.beginTick = 0; builder.beginTick = 0;
builder.endTick = 1; builder.endTick = 1;
builder.stopTick = 1; builder.stopTick = 1;
@@ -234,15 +265,27 @@ public final class GltfAnimationApplier {
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < items.size(); i++) {
RegionBoneMapper.V2ItemAnimInfo item = items.get(i); RegionBoneMapper.V2ItemAnimInfo item = items.get(i);
ResourceLocation animSource = item.animSource() != null ? item.animSource() : item.modelLoc(); ResourceLocation animSource =
item.animSource() != null
? item.animSource()
: item.modelLoc();
GltfData animData = GlbAnimationResolver.resolveAnimationData(item.modelLoc(), item.animSource()); GltfData animData = GlbAnimationResolver.resolveAnimationData(
item.modelLoc(),
item.animSource()
);
if (animData == null) { if (animData == null) {
LOGGER.warn("[GltfPipeline] Failed to load GLB for multi-item: {}", animSource); LOGGER.warn(
"[GltfPipeline] Failed to load GLB for multi-item: {}",
animSource
);
continue; continue;
} }
String glbAnimName = GlbAnimationResolver.resolve(animData, context); String glbAnimName = GlbAnimationResolver.resolve(
animData,
context
);
GltfData.AnimationClip rawClip; GltfData.AnimationClip rawClip;
if (glbAnimName != null) { if (glbAnimName != null) {
rawClip = animData.getRawAnimation(glbAnimName); rawClip = animData.getRawAnimation(glbAnimName);
@@ -257,7 +300,9 @@ public final class GltfAnimationApplier {
// if the item declares per-animation bone filtering. // if the item declares per-animation bone filtering.
Set<String> effectiveParts = item.ownedParts(); Set<String> effectiveParts = item.ownedParts();
if (glbAnimName != null && !item.animationBones().isEmpty()) { if (glbAnimName != null && !item.animationBones().isEmpty()) {
Set<String> override = item.animationBones().get(glbAnimName); Set<String> override = item
.animationBones()
.get(glbAnimName);
if (override != null) { if (override != null) {
Set<String> filtered = new HashSet<>(override); Set<String> filtered = new HashSet<>(override);
filtered.retainAll(item.ownedParts()); filtered.retainAll(item.ownedParts());
@@ -268,11 +313,20 @@ public final class GltfAnimationApplier {
} }
GltfPoseConverter.addBonesToBuilder( GltfPoseConverter.addBonesToBuilder(
builder, animData, rawClip, effectiveParts); builder,
animData,
rawClip,
effectiveParts
);
anyLoaded = true; anyLoaded = true;
LOGGER.debug("[GltfPipeline] Multi-item: {} -> owned={}, effective={}, anim={}", LOGGER.debug(
animSource, item.ownedParts(), effectiveParts, glbAnimName); "[GltfPipeline] Multi-item: {} -> owned={}, effective={}, anim={}",
animSource,
item.ownedParts(),
effectiveParts,
glbAnimName
);
} }
if (!anyLoaded) { if (!anyLoaded) {
@@ -284,9 +338,19 @@ public final class GltfAnimationApplier {
// Enable only owned parts on the item layer. // Enable only owned parts on the item layer.
// Free parts (head, body, etc. not owned by any item) are disabled here // Free parts (head, body, etc. not owned by any item) are disabled here
// so they pass through to the context layer / vanilla animation. // so they pass through to the context layer / vanilla animation.
String[] allPartNames = {"head", "body", "rightArm", "leftArm", "rightLeg", "leftLeg"}; String[] allPartNames = {
"head",
"body",
"rightArm",
"leftArm",
"rightLeg",
"leftLeg",
};
for (String partName : allPartNames) { for (String partName : allPartNames) {
KeyframeAnimation.StateCollection part = getPartByName(builder, partName); KeyframeAnimation.StateCollection part = getPartByName(
builder,
partName
);
if (part != null) { if (part != null) {
if (allOwnedParts.contains(partName)) { if (allOwnedParts.contains(partName)) {
part.fullyEnablePart(false); part.fullyEnablePart(false);
@@ -305,9 +369,7 @@ public final class GltfAnimationApplier {
return true; return true;
} }
// ========================================
// CLEAR / QUERY // CLEAR / QUERY
// ========================================
/** /**
* Clear all V2 animation layers from an entity and remove tracking. * Clear all V2 animation layers from an entity and remove tracking.
@@ -342,9 +404,7 @@ public final class GltfAnimationApplier {
activeStateKeys.remove(entityId); activeStateKeys.remove(entityId);
} }
// ========================================
// CACHE MANAGEMENT // CACHE MANAGEMENT
// ========================================
/** /**
* Invalidate all cached item animations and tracking state. * Invalidate all cached item animations and tracking state.
@@ -373,9 +433,7 @@ public final class GltfAnimationApplier {
ContextAnimationFactory.clearCache(); ContextAnimationFactory.clearCache();
} }
// ========================================
// LEGACY F9 DEBUG TOGGLE // LEGACY F9 DEBUG TOGGLE
// ========================================
private static boolean debugEnabled = false; private static boolean debugEnabled = false;
@@ -386,19 +444,29 @@ public final class GltfAnimationApplier {
*/ */
public static void toggle() { public static void toggle() {
debugEnabled = !debugEnabled; debugEnabled = !debugEnabled;
LOGGER.info("[GltfPipeline] Debug toggle: {}", debugEnabled ? "ON" : "OFF"); LOGGER.info(
"[GltfPipeline] Debug toggle: {}",
debugEnabled ? "ON" : "OFF"
);
AbstractClientPlayer player = Minecraft.getInstance().player; AbstractClientPlayer player = Minecraft.getInstance().player;
if (player == null) return; if (player == null) return;
if (debugEnabled) { if (debugEnabled) {
ResourceLocation modelLoc = ResourceLocation.fromNamespaceAndPath( ResourceLocation modelLoc = ResourceLocation.fromNamespaceAndPath(
"tiedup", "models/gltf/v2/handcuffs/cuffs_prototype.glb" "tiedup",
"models/gltf/v2/handcuffs/cuffs_prototype.glb"
); );
Set<String> armParts = Set.of("rightArm", "leftArm"); Set<String> armParts = Set.of("rightArm", "leftArm");
RegionBoneMapper.BoneOwnership debugOwnership = RegionBoneMapper.BoneOwnership debugOwnership =
new RegionBoneMapper.BoneOwnership(armParts, Set.of()); new RegionBoneMapper.BoneOwnership(armParts, Set.of());
applyV2Animation(player, modelLoc, null, AnimationContext.STAND_IDLE, debugOwnership); applyV2Animation(
player,
modelLoc,
null,
AnimationContext.STAND_IDLE,
debugOwnership
);
} else { } else {
clearV2Animation(player); clearV2Animation(player);
} }
@@ -411,16 +479,17 @@ public final class GltfAnimationApplier {
return debugEnabled; return debugEnabled;
} }
// ========================================
// INTERNAL // INTERNAL
// ========================================
/** /**
* Build cache key for item-layer animations. * Build cache key for item-layer animations.
* Format: "animSource#contextName#sortedParts" * Format: "animSource#contextName#sortedParts"
*/ */
private static String buildItemCacheKey(ResourceLocation animSource, private static String buildItemCacheKey(
AnimationContext context, String partsKey) { ResourceLocation animSource,
AnimationContext context,
String partsKey
) {
return animSource + "#" + context.name() + "#" + partsKey; return animSource + "#" + context.name() + "#" + partsKey;
} }
@@ -436,7 +505,9 @@ public final class GltfAnimationApplier {
* Look up an {@link KeyframeAnimation.StateCollection} by part name on a builder. * Look up an {@link KeyframeAnimation.StateCollection} by part name on a builder.
*/ */
private static KeyframeAnimation.StateCollection getPartByName( private static KeyframeAnimation.StateCollection getPartByName(
KeyframeAnimation.AnimationBuilder builder, String name) { KeyframeAnimation.AnimationBuilder builder,
String name
) {
return switch (name) { return switch (name) {
case "head" -> builder.head; case "head" -> builder.head;
case "body" -> builder.body; case "body" -> builder.body;

View File

@@ -20,16 +20,22 @@ public final class GltfBoneMapper {
/** Lower bones that represent bend (elbow/knee) */ /** Lower bones that represent bend (elbow/knee) */
private static final Set<String> LOWER_BONES = Set.of( private static final Set<String> LOWER_BONES = Set.of(
"leftLowerArm", "rightLowerArm", "leftLowerArm",
"leftLowerLeg", "rightLowerLeg" "rightLowerArm",
"leftLowerLeg",
"rightLowerLeg"
); );
/** Maps lower bone name -> corresponding upper bone name */ /** Maps lower bone name -> corresponding upper bone name */
private static final Map<String, String> LOWER_TO_UPPER = Map.of( private static final Map<String, String> LOWER_TO_UPPER = Map.of(
"leftLowerArm", "leftUpperArm", "leftLowerArm",
"rightLowerArm", "rightUpperArm", "leftUpperArm",
"leftLowerLeg", "leftUpperLeg", "rightLowerArm",
"rightLowerLeg", "rightUpperLeg" "rightUpperArm",
"leftLowerLeg",
"leftUpperLeg",
"rightLowerLeg",
"rightUpperLeg"
); );
static { static {
@@ -55,7 +61,10 @@ public final class GltfBoneMapper {
* @param boneName glTF bone name * @param boneName glTF bone name
* @return the ModelPart, or null if not mapped * @return the ModelPart, or null if not mapped
*/ */
public static ModelPart getModelPart(HumanoidModel<?> model, String boneName) { public static ModelPart getModelPart(
HumanoidModel<?> model,
String boneName
) {
String partName = BONE_TO_PART.get(boneName); String partName = BONE_TO_PART.get(boneName);
if (partName == null) return null; if (partName == null) return null;

View File

@@ -19,7 +19,8 @@ import org.apache.logging.log4j.Logger;
public final class GltfCache { public final class GltfCache {
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline"); private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");
private static final Map<ResourceLocation, GltfData> CACHE = new ConcurrentHashMap<>(); private static final Map<ResourceLocation, GltfData> CACHE =
new ConcurrentHashMap<>();
private GltfCache() {} private GltfCache() {}

View File

@@ -8,6 +8,7 @@ import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemReloadListener;
import net.minecraft.client.KeyMapping; import net.minecraft.client.KeyMapping;
import net.minecraft.client.renderer.entity.player.PlayerRenderer; import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.EntityRenderersEvent; import net.minecraftforge.client.event.EntityRenderersEvent;
@@ -17,7 +18,6 @@ import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@@ -59,7 +59,9 @@ public final class GltfClientSetup {
} }
@SubscribeEvent @SubscribeEvent
public static void onRegisterKeybindings(RegisterKeyMappingsEvent event) { public static void onRegisterKeybindings(
RegisterKeyMappingsEvent event
) {
event.register(TOGGLE_KEY); event.register(TOGGLE_KEY);
LOGGER.info("[GltfPipeline] Keybind registered: F9"); LOGGER.info("[GltfPipeline] Keybind registered: F9");
} }
@@ -71,16 +73,24 @@ public final class GltfClientSetup {
var defaultRenderer = event.getSkin("default"); var defaultRenderer = event.getSkin("default");
if (defaultRenderer instanceof PlayerRenderer playerRenderer) { if (defaultRenderer instanceof PlayerRenderer playerRenderer) {
playerRenderer.addLayer(new GltfRenderLayer(playerRenderer)); playerRenderer.addLayer(new GltfRenderLayer(playerRenderer));
playerRenderer.addLayer(new V2BondageRenderLayer<>(playerRenderer)); playerRenderer.addLayer(
LOGGER.info("[GltfPipeline] Render layers added to 'default' player renderer"); new V2BondageRenderLayer<>(playerRenderer)
);
LOGGER.info(
"[GltfPipeline] Render layers added to 'default' player renderer"
);
} }
// Add both layers to slim player renderer (Alex) // Add both layers to slim player renderer (Alex)
var slimRenderer = event.getSkin("slim"); var slimRenderer = event.getSkin("slim");
if (slimRenderer instanceof PlayerRenderer playerRenderer) { if (slimRenderer instanceof PlayerRenderer playerRenderer) {
playerRenderer.addLayer(new GltfRenderLayer(playerRenderer)); playerRenderer.addLayer(new GltfRenderLayer(playerRenderer));
playerRenderer.addLayer(new V2BondageRenderLayer<>(playerRenderer)); playerRenderer.addLayer(
LOGGER.info("[GltfPipeline] Render layers added to 'slim' player renderer"); new V2BondageRenderLayer<>(playerRenderer)
);
LOGGER.info(
"[GltfPipeline] Render layers added to 'slim' player renderer"
);
} }
} }
@@ -89,32 +99,47 @@ public final class GltfClientSetup {
* This ensures re-exported GLB models are picked up without restarting the game. * This ensures re-exported GLB models are picked up without restarting the game.
*/ */
@SubscribeEvent @SubscribeEvent
public static void onRegisterReloadListeners(RegisterClientReloadListenersEvent event) { public static void onRegisterReloadListeners(
event.registerReloadListener(new SimplePreparableReloadListener<Void>() { RegisterClientReloadListenersEvent event
@Override ) {
protected Void prepare(ResourceManager resourceManager, ProfilerFiller profiler) { event.registerReloadListener(
return null; new SimplePreparableReloadListener<Void>() {
} @Override
protected Void prepare(
ResourceManager resourceManager,
ProfilerFiller profiler
) {
return null;
}
@Override @Override
protected void apply(Void nothing, ResourceManager resourceManager, ProfilerFiller profiler) { protected void apply(
GltfCache.clearCache(); Void nothing,
GltfAnimationApplier.invalidateCache(); ResourceManager resourceManager,
GltfMeshRenderer.clearRenderTypeCache(); ProfilerFiller profiler
// Reload context GLB animations from resource packs FIRST, ) {
// then clear the factory cache so it rebuilds against the GltfCache.clearCache();
// new GLB registry (prevents stale JSON fallback caching). GltfAnimationApplier.invalidateCache();
ContextGlbRegistry.reload(resourceManager); GltfMeshRenderer.clearRenderTypeCache();
ContextAnimationFactory.clearCache(); // Reload context GLB animations from resource packs FIRST,
com.tiedup.remake.v2.furniture.client.FurnitureGltfCache.clear(); // then clear the factory cache so it rebuilds against the
LOGGER.info("[GltfPipeline] Caches cleared on resource reload"); // new GLB registry (prevents stale JSON fallback caching).
ContextGlbRegistry.reload(resourceManager);
ContextAnimationFactory.clearCache();
com.tiedup.remake.v2.furniture.client.FurnitureGltfCache.clear();
LOGGER.info(
"[GltfPipeline] Caches cleared on resource reload"
);
}
} }
}); );
LOGGER.info("[GltfPipeline] Resource reload listener registered"); LOGGER.info("[GltfPipeline] Resource reload listener registered");
// Data-driven bondage item definitions (tiedup_items/*.json) // Data-driven bondage item definitions (tiedup_items/*.json)
event.registerReloadListener(new DataDrivenItemReloadListener()); event.registerReloadListener(new DataDrivenItemReloadListener());
LOGGER.info("[GltfPipeline] Data-driven item reload listener registered"); LOGGER.info(
"[GltfPipeline] Data-driven item reload listener registered"
);
} }
} }

View File

@@ -21,14 +21,14 @@ import org.joml.Vector3f;
public final class GltfData { public final class GltfData {
// -- Mesh geometry (flattened arrays) -- // -- Mesh geometry (flattened arrays) --
private final float[] positions; // VEC3, length = vertexCount * 3 private final float[] positions; // VEC3, length = vertexCount * 3
private final float[] normals; // VEC3, length = vertexCount * 3 private final float[] normals; // VEC3, length = vertexCount * 3
private final float[] texCoords; // VEC2, length = vertexCount * 2 private final float[] texCoords; // VEC2, length = vertexCount * 2
private final int[] indices; // triangle indices private final int[] indices; // triangle indices
// -- Skinning data (per-vertex, 4 influences) -- // -- Skinning data (per-vertex, 4 influences) --
private final int[] joints; // 4 joint indices per vertex, length = vertexCount * 4 private final int[] joints; // 4 joint indices per vertex, length = vertexCount * 4
private final float[] weights; // 4 weights per vertex, length = vertexCount * 4 private final float[] weights; // 4 weights per vertex, length = vertexCount * 4
// -- Bone hierarchy (MC-converted for skinning) -- // -- Bone hierarchy (MC-converted for skinning) --
private final String[] jointNames; private final String[] jointNames;
@@ -39,6 +39,7 @@ public final class GltfData {
// -- Raw glTF rotations (unconverted, for pose conversion) -- // -- Raw glTF rotations (unconverted, for pose conversion) --
private final Quaternionf[] rawGltfRestRotations; private final Quaternionf[] rawGltfRestRotations;
@Nullable @Nullable
private final AnimationClip rawGltfAnimation; private final AnimationClip rawGltfAnimation;
@@ -47,8 +48,8 @@ public final class GltfData {
private final AnimationClip animation; private final AnimationClip animation;
// -- Multiple named animations -- // -- Multiple named animations --
private final Map<String, AnimationClip> namedAnimations; // MC-converted private final Map<String, AnimationClip> namedAnimations; // MC-converted
private final Map<String, AnimationClip> rawNamedAnimations; // raw glTF space private final Map<String, AnimationClip> rawNamedAnimations; // raw glTF space
// -- Per-primitive material/tint info -- // -- Per-primitive material/tint info --
private final List<Primitive> primitives; private final List<Primitive> primitives;
@@ -61,18 +62,25 @@ public final class GltfData {
* Full constructor with multiple named animations and per-primitive data. * Full constructor with multiple named animations and per-primitive data.
*/ */
public GltfData( public GltfData(
float[] positions, float[] normals, float[] texCoords, float[] positions,
int[] indices, int[] joints, float[] weights, float[] normals,
String[] jointNames, int[] parentJointIndices, float[] texCoords,
int[] indices,
int[] joints,
float[] weights,
String[] jointNames,
int[] parentJointIndices,
Matrix4f[] inverseBindMatrices, Matrix4f[] inverseBindMatrices,
Quaternionf[] restRotations, Vector3f[] restTranslations, Quaternionf[] restRotations,
Vector3f[] restTranslations,
Quaternionf[] rawGltfRestRotations, Quaternionf[] rawGltfRestRotations,
@Nullable AnimationClip rawGltfAnimation, @Nullable AnimationClip rawGltfAnimation,
@Nullable AnimationClip animation, @Nullable AnimationClip animation,
Map<String, AnimationClip> namedAnimations, Map<String, AnimationClip> namedAnimations,
Map<String, AnimationClip> rawNamedAnimations, Map<String, AnimationClip> rawNamedAnimations,
List<Primitive> primitives, List<Primitive> primitives,
int vertexCount, int jointCount int vertexCount,
int jointCount
) { ) {
this.positions = positions; this.positions = positions;
this.normals = normals; this.normals = normals;
@@ -88,8 +96,12 @@ public final class GltfData {
this.rawGltfRestRotations = rawGltfRestRotations; this.rawGltfRestRotations = rawGltfRestRotations;
this.rawGltfAnimation = rawGltfAnimation; this.rawGltfAnimation = rawGltfAnimation;
this.animation = animation; this.animation = animation;
this.namedAnimations = Collections.unmodifiableMap(new LinkedHashMap<>(namedAnimations)); this.namedAnimations = Collections.unmodifiableMap(
this.rawNamedAnimations = Collections.unmodifiableMap(new LinkedHashMap<>(rawNamedAnimations)); new LinkedHashMap<>(namedAnimations)
);
this.rawNamedAnimations = Collections.unmodifiableMap(
new LinkedHashMap<>(rawNamedAnimations)
);
this.primitives = List.copyOf(primitives); this.primitives = List.copyOf(primitives);
this.vertexCount = vertexCount; this.vertexCount = vertexCount;
this.jointCount = jointCount; this.jointCount = jointCount;
@@ -99,81 +111,175 @@ public final class GltfData {
* Legacy constructor for backward compatibility (single animation only). * Legacy constructor for backward compatibility (single animation only).
*/ */
public GltfData( public GltfData(
float[] positions, float[] normals, float[] texCoords, float[] positions,
int[] indices, int[] joints, float[] weights, float[] normals,
String[] jointNames, int[] parentJointIndices, float[] texCoords,
int[] indices,
int[] joints,
float[] weights,
String[] jointNames,
int[] parentJointIndices,
Matrix4f[] inverseBindMatrices, Matrix4f[] inverseBindMatrices,
Quaternionf[] restRotations, Vector3f[] restTranslations, Quaternionf[] restRotations,
Vector3f[] restTranslations,
Quaternionf[] rawGltfRestRotations, Quaternionf[] rawGltfRestRotations,
@Nullable AnimationClip rawGltfAnimation, @Nullable AnimationClip rawGltfAnimation,
@Nullable AnimationClip animation, @Nullable AnimationClip animation,
int vertexCount, int jointCount int vertexCount,
int jointCount
) { ) {
this(positions, normals, texCoords, indices, joints, weights, this(
jointNames, parentJointIndices, inverseBindMatrices, positions,
restRotations, restTranslations, rawGltfRestRotations, normals,
rawGltfAnimation, animation, texCoords,
new LinkedHashMap<>(), new LinkedHashMap<>(), indices,
joints,
weights,
jointNames,
parentJointIndices,
inverseBindMatrices,
restRotations,
restTranslations,
rawGltfRestRotations,
rawGltfAnimation,
animation,
new LinkedHashMap<>(),
new LinkedHashMap<>(),
List.of(new Primitive(indices, null, false, null)), List.of(new Primitive(indices, null, false, null)),
vertexCount, jointCount); vertexCount,
jointCount
);
}
public float[] positions() {
return positions;
}
public float[] normals() {
return normals;
}
public float[] texCoords() {
return texCoords;
}
public int[] indices() {
return indices;
}
public int[] joints() {
return joints;
}
public float[] weights() {
return weights;
}
public String[] jointNames() {
return jointNames;
}
public int[] parentJointIndices() {
return parentJointIndices;
}
public Matrix4f[] inverseBindMatrices() {
return inverseBindMatrices;
}
public Quaternionf[] restRotations() {
return restRotations;
}
public Vector3f[] restTranslations() {
return restTranslations;
}
public Quaternionf[] rawGltfRestRotations() {
return rawGltfRestRotations;
} }
public float[] positions() { return positions; }
public float[] normals() { return normals; }
public float[] texCoords() { return texCoords; }
public int[] indices() { return indices; }
public int[] joints() { return joints; }
public float[] weights() { return weights; }
public String[] jointNames() { return jointNames; }
public int[] parentJointIndices() { return parentJointIndices; }
public Matrix4f[] inverseBindMatrices() { return inverseBindMatrices; }
public Quaternionf[] restRotations() { return restRotations; }
public Vector3f[] restTranslations() { return restTranslations; }
public Quaternionf[] rawGltfRestRotations() { return rawGltfRestRotations; }
@Nullable @Nullable
public AnimationClip rawGltfAnimation() { return rawGltfAnimation; } public AnimationClip rawGltfAnimation() {
return rawGltfAnimation;
}
@Nullable @Nullable
public AnimationClip animation() { return animation; } public AnimationClip animation() {
public int vertexCount() { return vertexCount; } return animation;
public int jointCount() { return jointCount; } }
public int vertexCount() {
return vertexCount;
}
public int jointCount() {
return jointCount;
}
/** Per-primitive material and tint metadata. One entry per glTF primitive in the mesh. */ /** Per-primitive material and tint metadata. One entry per glTF primitive in the mesh. */
public List<Primitive> primitives() { return primitives; } public List<Primitive> primitives() {
return primitives;
}
/** All named animations in MC-converted space. Keys are animation names (e.g. "BasicPose", "Struggle"). */ /** All named animations in MC-converted space. Keys are animation names (e.g. "BasicPose", "Struggle"). */
public Map<String, AnimationClip> namedAnimations() { return namedAnimations; } public Map<String, AnimationClip> namedAnimations() {
return namedAnimations;
}
/** Get a specific named animation in MC-converted space, or null if not found. */ /** Get a specific named animation in MC-converted space, or null if not found. */
@Nullable @Nullable
public AnimationClip getAnimation(String name) { return namedAnimations.get(name); } public AnimationClip getAnimation(String name) {
return namedAnimations.get(name);
}
/** Get a specific named animation in raw glTF space, or null if not found. */ /** Get a specific named animation in raw glTF space, or null if not found. */
@Nullable @Nullable
public AnimationClip getRawAnimation(String name) { return rawNamedAnimations.get(name); } public AnimationClip getRawAnimation(String name) {
return rawNamedAnimations.get(name);
}
/** /**
* Animation clip: per-bone timestamps, quaternion rotations, and optional translations. * Animation clip: per-bone timestamps, quaternion rotations, and optional translations.
*/ */
public static final class AnimationClip { public static final class AnimationClip {
private final float[] timestamps; // shared timestamps
private final Quaternionf[][] rotations; // [jointIndex][frameIndex], null if no anim private final float[] timestamps; // shared timestamps
private final Quaternionf[][] rotations; // [jointIndex][frameIndex], null if no anim
@Nullable @Nullable
private final Vector3f[][] translations; // [jointIndex][frameIndex], null if no anim private final Vector3f[][] translations; // [jointIndex][frameIndex], null if no anim
private final int frameCount; private final int frameCount;
public AnimationClip(float[] timestamps, Quaternionf[][] rotations, public AnimationClip(
@Nullable Vector3f[][] translations, int frameCount) { float[] timestamps,
Quaternionf[][] rotations,
@Nullable Vector3f[][] translations,
int frameCount
) {
this.timestamps = timestamps; this.timestamps = timestamps;
this.rotations = rotations; this.rotations = rotations;
this.translations = translations; this.translations = translations;
this.frameCount = frameCount; this.frameCount = frameCount;
} }
public float[] timestamps() { return timestamps; } public float[] timestamps() {
public Quaternionf[][] rotations() { return rotations; } return timestamps;
}
public Quaternionf[][] rotations() {
return rotations;
}
@Nullable @Nullable
public Vector3f[][] translations() { return translations; } public Vector3f[][] translations() {
public int frameCount() { return frameCount; } return translations;
}
public int frameCount() {
return frameCount;
}
} }
/** /**

View File

@@ -60,7 +60,9 @@ public final class GltfLiveBoneReader {
* @return array of joint matrices ready for skinning, or null on failure * @return array of joint matrices ready for skinning, or null on failure
*/ */
public static Matrix4f[] computeJointMatricesFromModel( public static Matrix4f[] computeJointMatricesFromModel(
HumanoidModel<?> model, GltfData data, LivingEntity entity HumanoidModel<?> model,
GltfData data,
LivingEntity entity
) { ) {
if (model == null || data == null || entity == null) return null; if (model == null || data == null || entity == null) return null;
@@ -83,14 +85,19 @@ public final class GltfLiveBoneReader {
if (GltfBoneMapper.isLowerBone(boneName)) { if (GltfBoneMapper.isLowerBone(boneName)) {
// --- Lower bone: reconstruct from bend values --- // --- Lower bone: reconstruct from bend values ---
localRot = computeLowerBoneLocalRotation( localRot = computeLowerBoneLocalRotation(
boneName, j, restRotations, emote boneName,
j,
restRotations,
emote
); );
} else if (hasUniqueModelPart(boneName)) { } else if (hasUniqueModelPart(boneName)) {
// --- Upper bone with a unique ModelPart --- // --- Upper bone with a unique ModelPart ---
ModelPart part = GltfBoneMapper.getModelPart(model, boneName); ModelPart part = GltfBoneMapper.getModelPart(model, boneName);
if (part != null) { if (part != null) {
localRot = computeUpperBoneLocalRotation( localRot = computeUpperBoneLocalRotation(
part, j, restRotations part,
j,
restRotations
); );
} else { } else {
// Fallback: use rest rotation // Fallback: use rest rotation
@@ -108,14 +115,17 @@ public final class GltfLiveBoneReader {
// Compose with parent to get world transform // Compose with parent to get world transform
if (parents[j] >= 0 && worldTransforms[parents[j]] != null) { if (parents[j] >= 0 && worldTransforms[parents[j]] != null) {
worldTransforms[j] = new Matrix4f(worldTransforms[parents[j]]).mul(local); worldTransforms[j] = new Matrix4f(
worldTransforms[parents[j]]
).mul(local);
} else { } else {
worldTransforms[j] = new Matrix4f(local); worldTransforms[j] = new Matrix4f(local);
} }
// Final joint matrix = worldTransform * inverseBindMatrix // Final joint matrix = worldTransform * inverseBindMatrix
jointMatrices[j] = new Matrix4f(worldTransforms[j]) jointMatrices[j] = new Matrix4f(worldTransforms[j]).mul(
.mul(data.inverseBindMatrices()[j]); data.inverseBindMatrices()[j]
);
} }
return jointMatrices; return jointMatrices;
@@ -138,11 +148,16 @@ public final class GltfLiveBoneReader {
* the frame relationship. * the frame relationship.
*/ */
private static Quaternionf computeUpperBoneLocalRotation( private static Quaternionf computeUpperBoneLocalRotation(
ModelPart part, int jointIndex, ModelPart part,
int jointIndex,
Quaternionf[] restRotations Quaternionf[] restRotations
) { ) {
// Reconstruct the MC-frame delta from ModelPart euler angles. // Reconstruct the MC-frame delta from ModelPart euler angles.
Quaternionf delta = new Quaternionf().rotationZYX(part.zRot, part.yRot, part.xRot); Quaternionf delta = new Quaternionf().rotationZYX(
part.zRot,
part.yRot,
part.xRot
);
// Local rotation = delta applied on top of the local rest rotation. // Local rotation = delta applied on top of the local rest rotation.
return new Quaternionf(delta).mul(restRotations[jointIndex]); return new Quaternionf(delta).mul(restRotations[jointIndex]);
} }
@@ -160,7 +175,8 @@ public final class GltfLiveBoneReader {
* No de-parenting needed — same reasoning as upper bones. * No de-parenting needed — same reasoning as upper bones.
*/ */
private static Quaternionf computeLowerBoneLocalRotation( private static Quaternionf computeLowerBoneLocalRotation(
String boneName, int jointIndex, String boneName,
int jointIndex,
Quaternionf[] restRotations, Quaternionf[] restRotations,
AnimationApplier emote AnimationApplier emote
) { ) {
@@ -183,11 +199,16 @@ public final class GltfLiveBoneReader {
float halfAngle = bendValue * 0.5f; float halfAngle = bendValue * 0.5f;
float s = (float) Math.sin(halfAngle); float s = (float) Math.sin(halfAngle);
Quaternionf bendQuat = new Quaternionf( Quaternionf bendQuat = new Quaternionf(
ax * s, 0, az * s, (float) Math.cos(halfAngle) ax * s,
0,
az * s,
(float) Math.cos(halfAngle)
); );
// Local rotation = bend delta applied on top of local rest rotation // Local rotation = bend delta applied on top of local rest rotation
return new Quaternionf(bendQuat).mul(restRotations[jointIndex]); return new Quaternionf(bendQuat).mul(
restRotations[jointIndex]
);
} }
} }
} }
@@ -218,12 +239,12 @@ public final class GltfLiveBoneReader {
// LivingEntityRenderer's PoseStack transform, which applies to the entire // LivingEntityRenderer's PoseStack transform, which applies to the entire
// mesh uniformly. No need to read body rotation into joint matrices. // mesh uniformly. No need to read body rotation into joint matrices.
return switch (boneName) { return switch (boneName) {
case "head" -> true; case "head" -> true;
case "leftUpperArm" -> true; case "leftUpperArm" -> true;
case "rightUpperArm"-> true; case "rightUpperArm" -> true;
case "leftUpperLeg" -> true; case "leftUpperLeg" -> true;
case "rightUpperLeg"-> true; case "rightUpperLeg" -> true;
default -> false; // body, torso, lower bones, unknown default -> false; // body, torso, lower bones, unknown
}; };
} }
@@ -236,8 +257,11 @@ public final class GltfLiveBoneReader {
try { try {
return animated.playerAnimator_getAnimation(); return animated.playerAnimator_getAnimation();
} catch (Exception e) { } catch (Exception e) {
LOGGER.debug("[GltfPipeline] Could not get AnimationApplier for {}: {}", LOGGER.debug(
entity.getClass().getSimpleName(), e.getMessage()); "[GltfPipeline] Could not get AnimationApplier for {}: {}",
entity.getClass().getSimpleName(),
e.getMessage()
);
} }
} }
return null; return null;

View File

@@ -25,13 +25,17 @@ import org.joml.Vector4f;
public final class GltfMeshRenderer extends RenderStateShard { public final class GltfMeshRenderer extends RenderStateShard {
private static final ResourceLocation WHITE_TEXTURE = private static final ResourceLocation WHITE_TEXTURE =
ResourceLocation.fromNamespaceAndPath("tiedup", "models/obj/shared/white.png"); ResourceLocation.fromNamespaceAndPath(
"tiedup",
"models/obj/shared/white.png"
);
/** Cached default RenderType (white texture). Created once, reused every frame. */ /** Cached default RenderType (white texture). Created once, reused every frame. */
private static RenderType cachedDefaultRenderType; private static RenderType cachedDefaultRenderType;
/** Cache for texture-specific RenderTypes, keyed by ResourceLocation. */ /** Cache for texture-specific RenderTypes, keyed by ResourceLocation. */
private static final Map<ResourceLocation, RenderType> RENDER_TYPE_CACHE = new ConcurrentHashMap<>(); private static final Map<ResourceLocation, RenderType> RENDER_TYPE_CACHE =
new ConcurrentHashMap<>();
private GltfMeshRenderer() { private GltfMeshRenderer() {
super("tiedup_gltf_renderer", () -> {}, () -> {}); super("tiedup_gltf_renderer", () -> {}, () -> {});
@@ -61,15 +65,21 @@ public final class GltfMeshRenderer extends RenderStateShard {
* @param texture the texture ResourceLocation * @param texture the texture ResourceLocation
* @return the cached or newly created RenderType * @return the cached or newly created RenderType
*/ */
private static RenderType getRenderTypeForTexture(ResourceLocation texture) { private static RenderType getRenderTypeForTexture(
return RENDER_TYPE_CACHE.computeIfAbsent(texture, ResourceLocation texture
GltfMeshRenderer::createTriangleRenderType); ) {
return RENDER_TYPE_CACHE.computeIfAbsent(
texture,
GltfMeshRenderer::createTriangleRenderType
);
} }
/** /**
* Create a TRIANGLES-mode RenderType for glTF mesh rendering with the given texture. * Create a TRIANGLES-mode RenderType for glTF mesh rendering with the given texture.
*/ */
private static RenderType createTriangleRenderType(ResourceLocation texture) { private static RenderType createTriangleRenderType(
ResourceLocation texture
) {
RenderType.CompositeState state = RenderType.CompositeState.builder() RenderType.CompositeState state = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_ENTITY_CUTOUT_NO_CULL_SHADER) .setShaderState(RENDERTYPE_ENTITY_CUTOUT_NO_CULL_SHADER)
.setTextureState( .setTextureState(
@@ -112,12 +122,22 @@ public final class GltfMeshRenderer extends RenderStateShard {
* @param packedOverlay packed overlay value * @param packedOverlay packed overlay value
*/ */
public static void renderSkinned( public static void renderSkinned(
GltfData data, Matrix4f[] jointMatrices, GltfData data,
PoseStack poseStack, MultiBufferSource buffer, Matrix4f[] jointMatrices,
int packedLight, int packedOverlay PoseStack poseStack,
MultiBufferSource buffer,
int packedLight,
int packedOverlay
) { ) {
renderSkinnedInternal(data, jointMatrices, poseStack, buffer, renderSkinnedInternal(
packedLight, packedOverlay, getDefaultRenderType()); data,
jointMatrices,
poseStack,
buffer,
packedLight,
packedOverlay,
getDefaultRenderType()
);
} }
/** /**
@@ -132,22 +152,35 @@ public final class GltfMeshRenderer extends RenderStateShard {
* @param texture the texture to use for rendering * @param texture the texture to use for rendering
*/ */
public static void renderSkinned( public static void renderSkinned(
GltfData data, Matrix4f[] jointMatrices, GltfData data,
PoseStack poseStack, MultiBufferSource buffer, Matrix4f[] jointMatrices,
int packedLight, int packedOverlay, PoseStack poseStack,
MultiBufferSource buffer,
int packedLight,
int packedOverlay,
ResourceLocation texture ResourceLocation texture
) { ) {
renderSkinnedInternal(data, jointMatrices, poseStack, buffer, renderSkinnedInternal(
packedLight, packedOverlay, getRenderTypeForTexture(texture)); data,
jointMatrices,
poseStack,
buffer,
packedLight,
packedOverlay,
getRenderTypeForTexture(texture)
);
} }
/** /**
* Internal rendering implementation shared by both overloads. * Internal rendering implementation shared by both overloads.
*/ */
private static void renderSkinnedInternal( private static void renderSkinnedInternal(
GltfData data, Matrix4f[] jointMatrices, GltfData data,
PoseStack poseStack, MultiBufferSource buffer, Matrix4f[] jointMatrices,
int packedLight, int packedOverlay, PoseStack poseStack,
MultiBufferSource buffer,
int packedLight,
int packedOverlay,
RenderType renderType RenderType renderType
) { ) {
Matrix4f pose = poseStack.last().pose(); Matrix4f pose = poseStack.last().pose();
@@ -167,13 +200,22 @@ public final class GltfMeshRenderer extends RenderStateShard {
for (int idx : indices) { for (int idx : indices) {
// Skin this vertex // Skin this vertex
GltfSkinningEngine.skinVertex(data, idx, jointMatrices, outPos, outNormal, tmpPos, tmpNorm); GltfSkinningEngine.skinVertex(
data,
idx,
jointMatrices,
outPos,
outNormal,
tmpPos,
tmpNorm
);
// UV coordinates // UV coordinates
float u = texCoords[idx * 2]; float u = texCoords[idx * 2];
float v = texCoords[idx * 2 + 1]; float v = texCoords[idx * 2 + 1];
vc.vertex(pose, outPos[0], outPos[1], outPos[2]) vc
.vertex(pose, outPos[0], outPos[1], outPos[2])
.color(255, 255, 255, 255) .color(255, 255, 255, 255)
.uv(u, 1.0f - v) .uv(u, 1.0f - v)
.overlayCoords(packedOverlay) .overlayCoords(packedOverlay)
@@ -205,9 +247,12 @@ public final class GltfMeshRenderer extends RenderStateShard {
* @param tintColors channel name to RGB int (0xRRGGBB); empty map = white everywhere * @param tintColors channel name to RGB int (0xRRGGBB); empty map = white everywhere
*/ */
public static void renderSkinnedTinted( public static void renderSkinnedTinted(
GltfData data, Matrix4f[] jointMatrices, GltfData data,
PoseStack poseStack, MultiBufferSource buffer, Matrix4f[] jointMatrices,
int packedLight, int packedOverlay, PoseStack poseStack,
MultiBufferSource buffer,
int packedLight,
int packedOverlay,
RenderType renderType, RenderType renderType,
Map<String, Integer> tintColors Map<String, Integer> tintColors
) { ) {
@@ -226,7 +271,9 @@ public final class GltfMeshRenderer extends RenderStateShard {
for (GltfData.Primitive prim : primitives) { for (GltfData.Primitive prim : primitives) {
// Determine color for this primitive // Determine color for this primitive
int r = 255, g = 255, b = 255; int r = 255,
g = 255,
b = 255;
if (prim.tintable() && prim.tintChannel() != null) { if (prim.tintable() && prim.tintChannel() != null) {
Integer colorInt = tintColors.get(prim.tintChannel()); Integer colorInt = tintColors.get(prim.tintChannel());
if (colorInt != null) { if (colorInt != null) {
@@ -237,12 +284,21 @@ public final class GltfMeshRenderer extends RenderStateShard {
} }
for (int idx : prim.indices()) { for (int idx : prim.indices()) {
GltfSkinningEngine.skinVertex(data, idx, jointMatrices, outPos, outNormal, tmpPos, tmpNorm); GltfSkinningEngine.skinVertex(
data,
idx,
jointMatrices,
outPos,
outNormal,
tmpPos,
tmpNorm
);
float u = texCoords[idx * 2]; float u = texCoords[idx * 2];
float v = texCoords[idx * 2 + 1]; float v = texCoords[idx * 2 + 1];
vc.vertex(pose, outPos[0], outPos[1], outPos[2]) vc
.vertex(pose, outPos[0], outPos[1], outPos[2])
.color(r, g, b, 255) .color(r, g, b, 255)
.uv(u, 1.0f - v) .uv(u, 1.0f - v)
.overlayCoords(packedOverlay) .overlayCoords(packedOverlay)

View File

@@ -5,11 +5,11 @@ import dev.kosmx.playerAnim.core.data.KeyframeAnimation;
import dev.kosmx.playerAnim.core.util.Ease; import dev.kosmx.playerAnim.core.util.Ease;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.jetbrains.annotations.Nullable;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf; import org.joml.Quaternionf;
import org.joml.Vector3f; import org.joml.Vector3f;
@@ -52,10 +52,16 @@ public final class GltfPoseConverter {
* @param animationName the name of the animation to convert (e.g. "Struggle", "Idle") * @param animationName the name of the animation to convert (e.g. "Struggle", "Idle")
* @return a static looping KeyframeAnimation suitable for PlayerAnimator * @return a static looping KeyframeAnimation suitable for PlayerAnimator
*/ */
public static KeyframeAnimation convert(GltfData data, String animationName) { public static KeyframeAnimation convert(
GltfData data,
String animationName
) {
GltfData.AnimationClip rawClip = data.getRawAnimation(animationName); GltfData.AnimationClip rawClip = data.getRawAnimation(animationName);
if (rawClip == null) { if (rawClip == null) {
LOGGER.warn("[GltfPipeline] Animation '{}' not found, falling back to default", animationName); LOGGER.warn(
"[GltfPipeline] Animation '{}' not found, falling back to default",
animationName
);
return convert(data); return convert(data);
} }
return convertClip(data, rawClip, "gltf_" + animationName); return convertClip(data, rawClip, "gltf_" + animationName);
@@ -76,8 +82,12 @@ public final class GltfPoseConverter {
* are only enabled if the GLB has keyframes for them * are only enabled if the GLB has keyframes for them
* @return KeyframeAnimation with selective parts active * @return KeyframeAnimation with selective parts active
*/ */
public static KeyframeAnimation convertSelective(GltfData data, @Nullable String animationName, public static KeyframeAnimation convertSelective(
Set<String> ownedParts, Set<String> enabledParts) { GltfData data,
@Nullable String animationName,
Set<String> ownedParts,
Set<String> enabledParts
) {
GltfData.AnimationClip rawClip; GltfData.AnimationClip rawClip;
String animName; String animName;
if (animationName != null) { if (animationName != null) {
@@ -90,7 +100,13 @@ public final class GltfPoseConverter {
if (rawClip == null) { if (rawClip == null) {
rawClip = data.rawGltfAnimation(); rawClip = data.rawGltfAnimation();
} }
return convertClipSelective(data, rawClip, animName, ownedParts, enabledParts); return convertClipSelective(
data,
rawClip,
animName,
ownedParts,
enabledParts
);
} }
/** /**
@@ -105,10 +121,17 @@ public final class GltfPoseConverter {
* @param ownedParts parts the item explicitly owns (always enabled) * @param ownedParts parts the item explicitly owns (always enabled)
* @param enabledParts parts the item may animate (owned + free) * @param enabledParts parts the item may animate (owned + free)
*/ */
private static KeyframeAnimation convertClipSelective(GltfData data, GltfData.AnimationClip rawClip, private static KeyframeAnimation convertClipSelective(
String animName, Set<String> ownedParts, Set<String> enabledParts) { GltfData data,
GltfData.AnimationClip rawClip,
String animName,
Set<String> ownedParts,
Set<String> enabledParts
) {
KeyframeAnimation.AnimationBuilder builder = KeyframeAnimation.AnimationBuilder builder =
new KeyframeAnimation.AnimationBuilder(AnimationFormat.JSON_EMOTECRAFT); new KeyframeAnimation.AnimationBuilder(
AnimationFormat.JSON_EMOTECRAFT
);
builder.beginTick = 0; builder.beginTick = 0;
builder.endTick = 1; builder.endTick = 1;
@@ -129,21 +152,27 @@ public final class GltfPoseConverter {
// Check if this joint has explicit animation data (not just rest pose fallback). // Check if this joint has explicit animation data (not just rest pose fallback).
// A bone counts as explicitly animated if it has rotation OR translation keyframes. // A bone counts as explicitly animated if it has rotation OR translation keyframes.
boolean hasExplicitAnim = rawClip != null && ( boolean hasExplicitAnim =
(j < rawClip.rotations().length && rawClip.rotations()[j] != null) rawClip != null &&
|| (rawClip.translations() != null ((j < rawClip.rotations().length &&
&& j < rawClip.translations().length rawClip.rotations()[j] != null) ||
&& rawClip.translations()[j] != null) (rawClip.translations() != null &&
); j < rawClip.translations().length &&
rawClip.translations()[j] != null));
Quaternionf animQ = getRawAnimQuaternion(rawClip, rawRestRotations, j); Quaternionf animQ = getRawAnimQuaternion(
rawClip,
rawRestRotations,
j
);
Quaternionf restQ = rawRestRotations[j]; Quaternionf restQ = rawRestRotations[j];
// delta_local = inverse(rest_q) * anim_q (in bone-local frame) // delta_local = inverse(rest_q) * anim_q (in bone-local frame)
Quaternionf deltaLocal = new Quaternionf(restQ).invert().mul(animQ); Quaternionf deltaLocal = new Quaternionf(restQ).invert().mul(animQ);
// Convert to PARENT frame: delta_parent = rest * delta_local * inv(rest) // Convert to PARENT frame: delta_parent = rest * delta_local * inv(rest)
Quaternionf deltaParent = new Quaternionf(restQ).mul(deltaLocal) Quaternionf deltaParent = new Quaternionf(restQ)
.mul(deltaLocal)
.mul(new Quaternionf(restQ).invert()); .mul(new Quaternionf(restQ).invert());
// Convert from glTF parent frame to MC model-def frame. // Convert from glTF parent frame to MC model-def frame.
@@ -168,7 +197,9 @@ public final class GltfPoseConverter {
if (GltfBoneMapper.isLowerBone(boneName)) { if (GltfBoneMapper.isLowerBone(boneName)) {
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName); String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
if (upperBone != null) { if (upperBone != null) {
String upperPart = GltfBoneMapper.getAnimPartName(upperBone); String upperPart = GltfBoneMapper.getAnimPartName(
upperBone
);
if (upperPart != null) { if (upperPart != null) {
partsWithKeyframes.add(upperPart); partsWithKeyframes.add(upperPart);
} }
@@ -178,11 +209,21 @@ public final class GltfPoseConverter {
} }
// Selective: enable owned parts always, free parts only if they have keyframes // Selective: enable owned parts always, free parts only if they have keyframes
enableSelectiveParts(builder, ownedParts, enabledParts, partsWithKeyframes); enableSelectiveParts(
builder,
ownedParts,
enabledParts,
partsWithKeyframes
);
KeyframeAnimation anim = builder.build(); KeyframeAnimation anim = builder.build();
LOGGER.debug("[GltfPipeline] Converted selective animation '{}' (owned: {}, enabled: {}, withKeyframes: {})", LOGGER.debug(
animName, ownedParts, enabledParts, partsWithKeyframes); "[GltfPipeline] Converted selective animation '{}' (owned: {}, enabled: {}, withKeyframes: {})",
animName,
ownedParts,
enabledParts,
partsWithKeyframes
);
return anim; return anim;
} }
@@ -200,10 +241,11 @@ public final class GltfPoseConverter {
* @return set of part names that received actual keyframe data from the GLB * @return set of part names that received actual keyframe data from the GLB
*/ */
public static Set<String> addBonesToBuilder( public static Set<String> addBonesToBuilder(
KeyframeAnimation.AnimationBuilder builder, KeyframeAnimation.AnimationBuilder builder,
GltfData data, @Nullable GltfData.AnimationClip rawClip, GltfData data,
Set<String> ownedParts) { @Nullable GltfData.AnimationClip rawClip,
Set<String> ownedParts
) {
String[] jointNames = data.jointNames(); String[] jointNames = data.jointNames();
Quaternionf[] rawRestRotations = data.rawGltfRestRotations(); Quaternionf[] rawRestRotations = data.rawGltfRestRotations();
Set<String> partsWithKeyframes = new HashSet<>(); Set<String> partsWithKeyframes = new HashSet<>();
@@ -221,23 +263,33 @@ public final class GltfPoseConverter {
if (GltfBoneMapper.isLowerBone(boneName)) { if (GltfBoneMapper.isLowerBone(boneName)) {
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName); String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
if (upperBone != null) { if (upperBone != null) {
String upperPart = GltfBoneMapper.getAnimPartName(upperBone); String upperPart = GltfBoneMapper.getAnimPartName(
if (upperPart == null || !ownedParts.contains(upperPart)) continue; upperBone
);
if (
upperPart == null || !ownedParts.contains(upperPart)
) continue;
} }
} }
boolean hasExplicitAnim = rawClip != null && ( boolean hasExplicitAnim =
(j < rawClip.rotations().length && rawClip.rotations()[j] != null) rawClip != null &&
|| (rawClip.translations() != null ((j < rawClip.rotations().length &&
&& j < rawClip.translations().length rawClip.rotations()[j] != null) ||
&& rawClip.translations()[j] != null) (rawClip.translations() != null &&
); j < rawClip.translations().length &&
rawClip.translations()[j] != null));
Quaternionf animQ = getRawAnimQuaternion(rawClip, rawRestRotations, j); Quaternionf animQ = getRawAnimQuaternion(
rawClip,
rawRestRotations,
j
);
Quaternionf restQ = rawRestRotations[j]; Quaternionf restQ = rawRestRotations[j];
Quaternionf deltaLocal = new Quaternionf(restQ).invert().mul(animQ); Quaternionf deltaLocal = new Quaternionf(restQ).invert().mul(animQ);
Quaternionf deltaParent = new Quaternionf(restQ).mul(deltaLocal) Quaternionf deltaParent = new Quaternionf(restQ)
.mul(deltaLocal)
.mul(new Quaternionf(restQ).invert()); .mul(new Quaternionf(restQ).invert());
Quaternionf deltaQ = new Quaternionf(deltaParent); Quaternionf deltaQ = new Quaternionf(deltaParent);
@@ -255,8 +307,12 @@ public final class GltfPoseConverter {
if (GltfBoneMapper.isLowerBone(boneName)) { if (GltfBoneMapper.isLowerBone(boneName)) {
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName); String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
if (upperBone != null) { if (upperBone != null) {
String upperPart = GltfBoneMapper.getAnimPartName(upperBone); String upperPart = GltfBoneMapper.getAnimPartName(
if (upperPart != null) partsWithKeyframes.add(upperPart); upperBone
);
if (upperPart != null) partsWithKeyframes.add(
upperPart
);
} }
} }
} }
@@ -281,16 +337,25 @@ public final class GltfPoseConverter {
* @return a static looping KeyframeAnimation with all parts enabled * @return a static looping KeyframeAnimation with all parts enabled
*/ */
public static KeyframeAnimation convertWithSkeleton( public static KeyframeAnimation convertWithSkeleton(
GltfData skeleton, GltfData.AnimationClip clip, String animName) { GltfData skeleton,
GltfData.AnimationClip clip,
String animName
) {
return convertClip(skeleton, clip, animName); return convertClip(skeleton, clip, animName);
} }
/** /**
* Internal: convert a specific raw animation clip to a KeyframeAnimation. * Internal: convert a specific raw animation clip to a KeyframeAnimation.
*/ */
private static KeyframeAnimation convertClip(GltfData data, GltfData.AnimationClip rawClip, String animName) { private static KeyframeAnimation convertClip(
GltfData data,
GltfData.AnimationClip rawClip,
String animName
) {
KeyframeAnimation.AnimationBuilder builder = KeyframeAnimation.AnimationBuilder builder =
new KeyframeAnimation.AnimationBuilder(AnimationFormat.JSON_EMOTECRAFT); new KeyframeAnimation.AnimationBuilder(
AnimationFormat.JSON_EMOTECRAFT
);
builder.beginTick = 0; builder.beginTick = 0;
builder.endTick = 1; builder.endTick = 1;
@@ -307,7 +372,11 @@ public final class GltfPoseConverter {
if (!GltfBoneMapper.isKnownBone(boneName)) continue; if (!GltfBoneMapper.isKnownBone(boneName)) continue;
Quaternionf animQ = getRawAnimQuaternion(rawClip, rawRestRotations, j); Quaternionf animQ = getRawAnimQuaternion(
rawClip,
rawRestRotations,
j
);
Quaternionf restQ = rawRestRotations[j]; Quaternionf restQ = rawRestRotations[j];
// delta_local = inverse(rest_q) * anim_q (in bone-local frame) // delta_local = inverse(rest_q) * anim_q (in bone-local frame)
@@ -315,7 +384,8 @@ public final class GltfPoseConverter {
// Convert to PARENT frame: delta_parent = rest * delta_local * inv(rest) // Convert to PARENT frame: delta_parent = rest * delta_local * inv(rest)
// Simplifies algebraically to: animQ * inv(restQ) // Simplifies algebraically to: animQ * inv(restQ)
Quaternionf deltaParent = new Quaternionf(restQ).mul(deltaLocal) Quaternionf deltaParent = new Quaternionf(restQ)
.mul(deltaLocal)
.mul(new Quaternionf(restQ).invert()); .mul(new Quaternionf(restQ).invert());
// Convert from glTF parent frame to MC model-def frame. // Convert from glTF parent frame to MC model-def frame.
@@ -324,12 +394,24 @@ public final class GltfPoseConverter {
deltaQ.x = -deltaQ.x; deltaQ.x = -deltaQ.x;
deltaQ.y = -deltaQ.y; deltaQ.y = -deltaQ.y;
LOGGER.debug(String.format( LOGGER.debug(
"[GltfPipeline] Bone '%s': restQ=(%.3f,%.3f,%.3f,%.3f) animQ=(%.3f,%.3f,%.3f,%.3f) deltaQ=(%.3f,%.3f,%.3f,%.3f)", String.format(
boneName, "[GltfPipeline] Bone '%s': restQ=(%.3f,%.3f,%.3f,%.3f) animQ=(%.3f,%.3f,%.3f,%.3f) deltaQ=(%.3f,%.3f,%.3f,%.3f)",
restQ.x, restQ.y, restQ.z, restQ.w, boneName,
animQ.x, animQ.y, animQ.z, animQ.w, restQ.x,
deltaQ.x, deltaQ.y, deltaQ.z, deltaQ.w)); restQ.y,
restQ.z,
restQ.w,
animQ.x,
animQ.y,
animQ.z,
animQ.w,
deltaQ.x,
deltaQ.y,
deltaQ.z,
deltaQ.w
)
);
if (GltfBoneMapper.isLowerBone(boneName)) { if (GltfBoneMapper.isLowerBone(boneName)) {
convertLowerBone(builder, boneName, deltaQ); convertLowerBone(builder, boneName, deltaQ);
@@ -341,7 +423,10 @@ public final class GltfPoseConverter {
builder.fullyEnableParts(); builder.fullyEnableParts();
KeyframeAnimation anim = builder.build(); KeyframeAnimation anim = builder.build();
LOGGER.debug("[GltfPipeline] Converted glTF animation '{}' to KeyframeAnimation", animName); LOGGER.debug(
"[GltfPipeline] Converted glTF animation '{}' to KeyframeAnimation",
animName
);
return anim; return anim;
} }
@@ -350,10 +435,15 @@ public final class GltfPoseConverter {
* Falls back to rest rotation if the clip is null or has no data for this joint. * Falls back to rest rotation if the clip is null or has no data for this joint.
*/ */
private static Quaternionf getRawAnimQuaternion( private static Quaternionf getRawAnimQuaternion(
GltfData.AnimationClip rawClip, Quaternionf[] rawRestRotations, int jointIndex GltfData.AnimationClip rawClip,
Quaternionf[] rawRestRotations,
int jointIndex
) { ) {
if (rawClip != null && jointIndex < rawClip.rotations().length if (
&& rawClip.rotations()[jointIndex] != null) { rawClip != null &&
jointIndex < rawClip.rotations().length &&
rawClip.rotations()[jointIndex] != null
) {
return rawClip.rotations()[jointIndex][0]; // first frame return rawClip.rotations()[jointIndex][0]; // first frame
} }
return rawRestRotations[jointIndex]; // fallback to rest return rawRestRotations[jointIndex]; // fallback to rest
@@ -361,29 +451,36 @@ public final class GltfPoseConverter {
private static void convertUpperBone( private static void convertUpperBone(
KeyframeAnimation.AnimationBuilder builder, KeyframeAnimation.AnimationBuilder builder,
String boneName, Quaternionf deltaQ String boneName,
Quaternionf deltaQ
) { ) {
// Decompose delta quaternion to Euler ZYX // Decompose delta quaternion to Euler ZYX
// JOML's getEulerAnglesZYX stores: euler.x = X rotation, euler.y = Y rotation, euler.z = Z rotation // JOML's getEulerAnglesZYX stores: euler.x = X rotation, euler.y = Y rotation, euler.z = Z rotation
// (the "ZYX" refers to rotation ORDER, not storage order) // (the "ZYX" refers to rotation ORDER, not storage order)
Vector3f euler = new Vector3f(); Vector3f euler = new Vector3f();
deltaQ.getEulerAnglesZYX(euler); deltaQ.getEulerAnglesZYX(euler);
float pitch = euler.x; // X rotation (pitch) float pitch = euler.x; // X rotation (pitch)
float yaw = euler.y; // Y rotation (yaw) float yaw = euler.y; // Y rotation (yaw)
float roll = euler.z; // Z rotation (roll) float roll = euler.z; // Z rotation (roll)
LOGGER.debug(String.format( LOGGER.debug(
"[GltfPipeline] Upper bone '%s': pitch=%.1f° yaw=%.1f° roll=%.1f°", String.format(
boneName, "[GltfPipeline] Upper bone '%s': pitch=%.1f° yaw=%.1f° roll=%.1f°",
Math.toDegrees(pitch), boneName,
Math.toDegrees(yaw), Math.toDegrees(pitch),
Math.toDegrees(roll))); Math.toDegrees(yaw),
Math.toDegrees(roll)
)
);
// Get the StateCollection for this body part // Get the StateCollection for this body part
String animPart = GltfBoneMapper.getAnimPartName(boneName); String animPart = GltfBoneMapper.getAnimPartName(boneName);
if (animPart == null) return; if (animPart == null) return;
KeyframeAnimation.StateCollection part = getPartByName(builder, animPart); KeyframeAnimation.StateCollection part = getPartByName(
builder,
animPart
);
if (part == null) return; if (part == null) return;
part.pitch.addKeyFrame(0, pitch, Ease.CONSTANT); part.pitch.addKeyFrame(0, pitch, Ease.CONSTANT);
@@ -393,12 +490,12 @@ public final class GltfPoseConverter {
private static void convertLowerBone( private static void convertLowerBone(
KeyframeAnimation.AnimationBuilder builder, KeyframeAnimation.AnimationBuilder builder,
String boneName, Quaternionf deltaQ String boneName,
Quaternionf deltaQ
) { ) {
// Extract bend angle and axis from the delta quaternion // Extract bend angle and axis from the delta quaternion
float angle = 2.0f * (float) Math.acos( float angle =
Math.min(1.0, Math.abs(deltaQ.w)) 2.0f * (float) Math.acos(Math.min(1.0, Math.abs(deltaQ.w)));
);
// Determine bend direction from axis // Determine bend direction from axis
float bendDirection = 0.0f; float bendDirection = 0.0f;
@@ -411,11 +508,14 @@ public final class GltfPoseConverter {
angle = -angle; angle = -angle;
} }
LOGGER.debug(String.format( LOGGER.debug(
"[GltfPipeline] Lower bone '%s': bendAngle=%.1f° bendDir=%.1f°", String.format(
boneName, "[GltfPipeline] Lower bone '%s': bendAngle=%.1f° bendDir=%.1f°",
Math.toDegrees(angle), boneName,
Math.toDegrees(bendDirection))); Math.toDegrees(angle),
Math.toDegrees(bendDirection)
)
);
// Apply bend to the upper bone's StateCollection // Apply bend to the upper bone's StateCollection
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName); String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
@@ -424,7 +524,10 @@ public final class GltfPoseConverter {
String animPart = GltfBoneMapper.getAnimPartName(upperBone); String animPart = GltfBoneMapper.getAnimPartName(upperBone);
if (animPart == null) return; if (animPart == null) return;
KeyframeAnimation.StateCollection part = getPartByName(builder, animPart); KeyframeAnimation.StateCollection part = getPartByName(
builder,
animPart
);
if (part == null || !part.isBendable) return; if (part == null || !part.isBendable) return;
part.bend.addKeyFrame(0, angle, Ease.CONSTANT); part.bend.addKeyFrame(0, angle, Ease.CONSTANT);
@@ -432,7 +535,8 @@ public final class GltfPoseConverter {
} }
private static KeyframeAnimation.StateCollection getPartByName( private static KeyframeAnimation.StateCollection getPartByName(
KeyframeAnimation.AnimationBuilder builder, String name KeyframeAnimation.AnimationBuilder builder,
String name
) { ) {
return switch (name) { return switch (name) {
case "head" -> builder.head; case "head" -> builder.head;
@@ -461,17 +565,32 @@ public final class GltfPoseConverter {
* @param partsWithKeyframes parts that received actual animation data from the GLB * @param partsWithKeyframes parts that received actual animation data from the GLB
*/ */
private static void enableSelectiveParts( private static void enableSelectiveParts(
KeyframeAnimation.AnimationBuilder builder, KeyframeAnimation.AnimationBuilder builder,
Set<String> ownedParts, Set<String> enabledParts, Set<String> ownedParts,
Set<String> partsWithKeyframes) { Set<String> enabledParts,
String[] allParts = {"head", "body", "rightArm", "leftArm", "rightLeg", "leftLeg"}; Set<String> partsWithKeyframes
) {
String[] allParts = {
"head",
"body",
"rightArm",
"leftArm",
"rightLeg",
"leftLeg",
};
for (String partName : allParts) { for (String partName : allParts) {
KeyframeAnimation.StateCollection part = getPartByName(builder, partName); KeyframeAnimation.StateCollection part = getPartByName(
builder,
partName
);
if (part != null) { if (part != null) {
if (ownedParts.contains(partName)) { if (ownedParts.contains(partName)) {
// Always enable owned parts — the item controls these bones // Always enable owned parts — the item controls these bones
part.fullyEnablePart(false); part.fullyEnablePart(false);
} else if (enabledParts.contains(partName) && partsWithKeyframes.contains(partName)) { } else if (
enabledParts.contains(partName) &&
partsWithKeyframes.contains(partName)
) {
// Free part WITH keyframes: enable so the GLB animation drives it // Free part WITH keyframes: enable so the GLB animation drives it
part.fullyEnablePart(false); part.fullyEnablePart(false);
} else { } else {

View File

@@ -24,17 +24,22 @@ import org.joml.Matrix4f;
*/ */
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class GltfRenderLayer public class GltfRenderLayer
extends RenderLayer<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>> { extends RenderLayer<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>>
{
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline"); private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");
private static final ResourceLocation CUFFS_MODEL = private static final ResourceLocation CUFFS_MODEL =
ResourceLocation.fromNamespaceAndPath( ResourceLocation.fromNamespaceAndPath(
"tiedup", "models/gltf/v2/handcuffs/cuffs_prototype.glb" "tiedup",
"models/gltf/v2/handcuffs/cuffs_prototype.glb"
); );
public GltfRenderLayer( public GltfRenderLayer(
RenderLayerParent<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>> renderer RenderLayerParent<
AbstractClientPlayer,
PlayerModel<AbstractClientPlayer>
> renderer
) { ) {
super(renderer); super(renderer);
} }
@@ -71,7 +76,9 @@ public class GltfRenderLayer
// Live path: read skeleton from HumanoidModel (after PlayerAnimator) // Live path: read skeleton from HumanoidModel (after PlayerAnimator)
PlayerModel<AbstractClientPlayer> parentModel = this.getParentModel(); PlayerModel<AbstractClientPlayer> parentModel = this.getParentModel();
Matrix4f[] joints = GltfLiveBoneReader.computeJointMatricesFromModel( Matrix4f[] joints = GltfLiveBoneReader.computeJointMatricesFromModel(
parentModel, data, entity parentModel,
data,
entity
); );
if (joints == null) { if (joints == null) {
// Fallback to GLB-internal path if live reading fails // Fallback to GLB-internal path if live reading fails
@@ -84,10 +91,15 @@ public class GltfRenderLayer
poseStack.translate(0, ALIGNMENT_Y, 0); poseStack.translate(0, ALIGNMENT_Y, 0);
GltfMeshRenderer.renderSkinned( GltfMeshRenderer.renderSkinned(
data, joints, poseStack, buffer, data,
joints,
poseStack,
buffer,
packedLight, packedLight,
net.minecraft.client.renderer.entity.LivingEntityRenderer net.minecraft.client.renderer.entity.LivingEntityRenderer.getOverlayCoords(
.getOverlayCoords(entity, 0.0f) entity,
0.0f
)
); );
poseStack.popPose(); poseStack.popPose();
} }

View File

@@ -43,7 +43,9 @@ public final class GltfSkinningEngine {
* @return interpolated joint matrices ready for skinning * @return interpolated joint matrices ready for skinning
*/ */
public static Matrix4f[] computeJointMatricesAnimated( public static Matrix4f[] computeJointMatricesAnimated(
GltfData data, GltfData.AnimationClip clip, float time GltfData data,
GltfData.AnimationClip clip,
float time
) { ) {
int jointCount = data.jointCount(); int jointCount = data.jointCount();
Matrix4f[] jointMatrices = new Matrix4f[jointCount]; Matrix4f[] jointMatrices = new Matrix4f[jointCount];
@@ -59,14 +61,17 @@ public final class GltfSkinningEngine {
// Compose with parent // Compose with parent
if (parents[j] >= 0 && worldTransforms[parents[j]] != null) { if (parents[j] >= 0 && worldTransforms[parents[j]] != null) {
worldTransforms[j] = new Matrix4f(worldTransforms[parents[j]]).mul(local); worldTransforms[j] = new Matrix4f(
worldTransforms[parents[j]]
).mul(local);
} else { } else {
worldTransforms[j] = new Matrix4f(local); worldTransforms[j] = new Matrix4f(local);
} }
// Final joint matrix = worldTransform * inverseBindMatrix // Final joint matrix = worldTransform * inverseBindMatrix
jointMatrices[j] = new Matrix4f(worldTransforms[j]) jointMatrices[j] = new Matrix4f(worldTransforms[j]).mul(
.mul(data.inverseBindMatrices()[j]); data.inverseBindMatrices()[j]
);
} }
return jointMatrices; return jointMatrices;
@@ -75,7 +80,10 @@ public final class GltfSkinningEngine {
/** /**
* Internal: compute joint matrices from a specific animation clip. * Internal: compute joint matrices from a specific animation clip.
*/ */
private static Matrix4f[] computeJointMatricesFromClip(GltfData data, GltfData.AnimationClip clip) { private static Matrix4f[] computeJointMatricesFromClip(
GltfData data,
GltfData.AnimationClip clip
) {
int jointCount = data.jointCount(); int jointCount = data.jointCount();
Matrix4f[] jointMatrices = new Matrix4f[jointCount]; Matrix4f[] jointMatrices = new Matrix4f[jointCount];
Matrix4f[] worldTransforms = new Matrix4f[jointCount]; Matrix4f[] worldTransforms = new Matrix4f[jointCount];
@@ -90,14 +98,17 @@ public final class GltfSkinningEngine {
// Compose with parent // Compose with parent
if (parents[j] >= 0 && worldTransforms[parents[j]] != null) { if (parents[j] >= 0 && worldTransforms[parents[j]] != null) {
worldTransforms[j] = new Matrix4f(worldTransforms[parents[j]]).mul(local); worldTransforms[j] = new Matrix4f(
worldTransforms[parents[j]]
).mul(local);
} else { } else {
worldTransforms[j] = new Matrix4f(local); worldTransforms[j] = new Matrix4f(local);
} }
// Final joint matrix = worldTransform * inverseBindMatrix // Final joint matrix = worldTransform * inverseBindMatrix
jointMatrices[j] = new Matrix4f(worldTransforms[j]) jointMatrices[j] = new Matrix4f(worldTransforms[j]).mul(
.mul(data.inverseBindMatrices()[j]); data.inverseBindMatrices()[j]
);
} }
return jointMatrices; return jointMatrices;
@@ -107,9 +118,16 @@ public final class GltfSkinningEngine {
* Get the animation rotation for a joint (MC-converted). * Get the animation rotation for a joint (MC-converted).
* Falls back to rest rotation if no animation. * Falls back to rest rotation if no animation.
*/ */
private static Quaternionf getAnimRotation(GltfData data, GltfData.AnimationClip clip, int jointIndex) { private static Quaternionf getAnimRotation(
if (clip != null && jointIndex < clip.rotations().length GltfData data,
&& clip.rotations()[jointIndex] != null) { GltfData.AnimationClip clip,
int jointIndex
) {
if (
clip != null &&
jointIndex < clip.rotations().length &&
clip.rotations()[jointIndex] != null
) {
return clip.rotations()[jointIndex][0]; // first frame return clip.rotations()[jointIndex][0]; // first frame
} }
return data.restRotations()[jointIndex]; return data.restRotations()[jointIndex];
@@ -119,10 +137,17 @@ public final class GltfSkinningEngine {
* Get the animation translation for a joint (MC-converted). * Get the animation translation for a joint (MC-converted).
* Falls back to rest translation if no animation translation exists. * Falls back to rest translation if no animation translation exists.
*/ */
private static Vector3f getAnimTranslation(GltfData data, GltfData.AnimationClip clip, int jointIndex) { private static Vector3f getAnimTranslation(
if (clip != null && clip.translations() != null GltfData data,
&& jointIndex < clip.translations().length GltfData.AnimationClip clip,
&& clip.translations()[jointIndex] != null) { int jointIndex
) {
if (
clip != null &&
clip.translations() != null &&
jointIndex < clip.translations().length &&
clip.translations()[jointIndex] != null
) {
return clip.translations()[jointIndex][0]; // first frame return clip.translations()[jointIndex][0]; // first frame
} }
return data.restTranslations()[jointIndex]; return data.restTranslations()[jointIndex];
@@ -144,10 +169,16 @@ public final class GltfSkinningEngine {
* @return new Quaternionf with the interpolated rotation (never mutates source data) * @return new Quaternionf with the interpolated rotation (never mutates source data)
*/ */
private static Quaternionf getInterpolatedRotation( private static Quaternionf getInterpolatedRotation(
GltfData data, GltfData.AnimationClip clip, int jointIndex, float time GltfData data,
GltfData.AnimationClip clip,
int jointIndex,
float time
) { ) {
if (clip == null || jointIndex >= clip.rotations().length if (
|| clip.rotations()[jointIndex] == null) { clip == null ||
jointIndex >= clip.rotations().length ||
clip.rotations()[jointIndex] == null
) {
// No animation data for this joint -- use rest pose (copy to avoid mutation) // No animation data for this joint -- use rest pose (copy to avoid mutation)
Quaternionf rest = data.restRotations()[jointIndex]; Quaternionf rest = data.restRotations()[jointIndex];
return new Quaternionf(rest); return new Quaternionf(rest);
@@ -187,11 +218,17 @@ public final class GltfSkinningEngine {
* @return new Vector3f with the interpolated translation (never mutates source data) * @return new Vector3f with the interpolated translation (never mutates source data)
*/ */
private static Vector3f getInterpolatedTranslation( private static Vector3f getInterpolatedTranslation(
GltfData data, GltfData.AnimationClip clip, int jointIndex, float time GltfData data,
GltfData.AnimationClip clip,
int jointIndex,
float time
) { ) {
if (clip == null || clip.translations() == null if (
|| jointIndex >= clip.translations().length clip == null ||
|| clip.translations()[jointIndex] == null) { clip.translations() == null ||
jointIndex >= clip.translations().length ||
clip.translations()[jointIndex] == null
) {
// No animation data for this joint -- use rest pose (copy to avoid mutation) // No animation data for this joint -- use rest pose (copy to avoid mutation)
Vector3f rest = data.restTranslations()[jointIndex]; Vector3f rest = data.restTranslations()[jointIndex];
return new Vector3f(rest); return new Vector3f(rest);
@@ -232,9 +269,13 @@ public final class GltfSkinningEngine {
* @param tmpNorm pre-allocated scratch Vector4f for normal transforms * @param tmpNorm pre-allocated scratch Vector4f for normal transforms
*/ */
public static void skinVertex( public static void skinVertex(
GltfData data, int vertexIdx, Matrix4f[] jointMatrices, GltfData data,
float[] outPos, float[] outNormal, int vertexIdx,
Vector4f tmpPos, Vector4f tmpNorm Matrix4f[] jointMatrices,
float[] outPos,
float[] outNormal,
Vector4f tmpPos,
Vector4f tmpNorm
) { ) {
float[] positions = data.positions(); float[] positions = data.positions();
float[] normals = data.normals(); float[] normals = data.normals();
@@ -252,8 +293,12 @@ public final class GltfSkinningEngine {
float nz = normals[vertexIdx * 3 + 2]; float nz = normals[vertexIdx * 3 + 2];
// LBS: v_skinned = Σ(w[i] * jointMatrix[j[i]] * v_rest) // LBS: v_skinned = Σ(w[i] * jointMatrix[j[i]] * v_rest)
float sx = 0, sy = 0, sz = 0; float sx = 0,
float snx = 0, sny = 0, snz = 0; sy = 0,
sz = 0;
float snx = 0,
sny = 0,
snz = 0;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
int ji = joints[vertexIdx * 4 + i]; int ji = joints[vertexIdx * 4 + i];

View File

@@ -20,7 +20,6 @@ import net.minecraftforge.fml.common.Mod;
* Overlay that shows a progress bar for tying/untying/struggling actions. * Overlay that shows a progress bar for tying/untying/struggling actions.
* Displayed above the hotbar when an action is in progress. * Displayed above the hotbar when an action is in progress.
* *
* Phase 16: GUI Revamp - Progress bar overlay
*/ */
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
@Mod.EventBusSubscriber(modid = TiedUpMod.MOD_ID, value = Dist.CLIENT) @Mod.EventBusSubscriber(modid = TiedUpMod.MOD_ID, value = Dist.CLIENT)

View File

@@ -18,7 +18,6 @@ import net.minecraftforge.fml.common.Mod;
* Overlay that shows status icons when player is restrained. * Overlay that shows status icons when player is restrained.
* Icons appear in top-left corner showing current bondage state. * Icons appear in top-left corner showing current bondage state.
* *
* Phase 16: GUI Revamp - Status indicator overlay
*/ */
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
@Mod.EventBusSubscriber(modid = TiedUpMod.MOD_ID, value = Dist.CLIENT) @Mod.EventBusSubscriber(modid = TiedUpMod.MOD_ID, value = Dist.CLIENT)

View File

@@ -1,10 +1,10 @@
package com.tiedup.remake.client.gui.overlays; package com.tiedup.remake.client.gui.overlays;
import com.tiedup.remake.client.ModKeybindings; import com.tiedup.remake.client.ModKeybindings;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;

View File

@@ -2,8 +2,8 @@ package com.tiedup.remake.client.gui.screens;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.item.PacketAdjustItem; import com.tiedup.remake.network.item.PacketAdjustItem;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
@@ -16,7 +16,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
* Screen for adjusting Y position of the player's own gags and blindfolds. * Screen for adjusting Y position of the player's own gags and blindfolds.
* Shows 3D preview of player with real-time adjustment. * Shows 3D preview of player with real-time adjustment.
* *
* Phase 16b: GUI Refactoring - Simplified using BaseAdjustmentScreen
*/ */
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class AdjustmentScreen extends BaseAdjustmentScreen { public class AdjustmentScreen extends BaseAdjustmentScreen {

View File

@@ -9,7 +9,6 @@ import com.tiedup.remake.network.cell.PacketOpenCellManager.CellSyncData;
import com.tiedup.remake.network.cell.PacketOpenCellManager.PrisonerInfo; import com.tiedup.remake.network.cell.PacketOpenCellManager.PrisonerInfo;
import com.tiedup.remake.network.cell.PacketRenameCell; import com.tiedup.remake.network.cell.PacketRenameCell;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.Nullable;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Button;
@@ -17,6 +16,7 @@ import net.minecraft.client.gui.components.EditBox;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;
/** /**
* Screen for managing player-owned cells. * Screen for managing player-owned cells.

View File

@@ -8,13 +8,13 @@ import com.tiedup.remake.network.cell.PacketAssignCellToCollar;
import com.tiedup.remake.network.cell.PacketOpenCellSelector.CellOption; import com.tiedup.remake.network.cell.PacketOpenCellSelector.CellOption;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.jetbrains.annotations.Nullable;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Button;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;
/** /**
* Modal screen for selecting a cell to assign to a collar. * Modal screen for selecting a cell to assign to a collar.

View File

@@ -234,9 +234,7 @@ public class CommandWandScreen extends Screen {
addBottomButtons(); addBottomButtons();
} }
// =========================================================================
// Status Tab // Status Tab
// =========================================================================
/** Calculate Y position after all rendered status content (needs, mood, cell, home) */ /** Calculate Y position after all rendered status content (needs, mood, cell, home) */
private int getStatusButtonsY(int startY) { private int getStatusButtonsY(int startY) {
@@ -332,9 +330,7 @@ public class CommandWandScreen extends Screen {
this.addRenderableWidget(threatenBtn); this.addRenderableWidget(threatenBtn);
} }
// =========================================================================
// Commands Tab // Commands Tab
// =========================================================================
private static final int SECTION_HEADER_H = 16; // header line + text + padding private static final int SECTION_HEADER_H = 16; // header line + text + padding
@@ -382,9 +378,7 @@ public class CommandWandScreen extends Screen {
); );
} }
// =========================================================================
// Jobs Tab // Jobs Tab
// =========================================================================
private void buildJobsTab(int contentX, int contentWidth, int y) { private void buildJobsTab(int contentX, int contentWidth, int y) {
int thirdWidth = (contentWidth - BTN_SPACING * 2) / 3; int thirdWidth = (contentWidth - BTN_SPACING * 2) / 3;
@@ -463,9 +457,7 @@ public class CommandWandScreen extends Screen {
); );
} }
// =========================================================================
// Bottom Buttons (all tabs) // Bottom Buttons (all tabs)
// =========================================================================
private void addBottomButtons() { private void addBottomButtons() {
int bottomBtnWidth = 90; int bottomBtnWidth = 90;
@@ -506,9 +498,7 @@ public class CommandWandScreen extends Screen {
this.addRenderableWidget(stopBtn); this.addRenderableWidget(stopBtn);
} }
// =========================================================================
// Button helpers // Button helpers
// =========================================================================
private void addCommandButton(int x, int y, NpcCommand command, int width) { private void addCommandButton(int x, int y, NpcCommand command, int width) {
boolean isActive = activeCommand.equals(command.name()); boolean isActive = activeCommand.equals(command.name());
@@ -667,9 +657,7 @@ public class CommandWandScreen extends Screen {
this.addRenderableWidget(btn); this.addRenderableWidget(btn);
} }
// =========================================================================
// Network actions // Network actions
// =========================================================================
private void sendCommand(NpcCommand command) { private void sendCommand(NpcCommand command) {
ModNetwork.sendToServer( ModNetwork.sendToServer(
@@ -692,7 +680,9 @@ public class CommandWandScreen extends Screen {
} }
private void toggleAutoRest() { private void toggleAutoRest() {
ModNetwork.sendToServer(PacketNpcCommand.toggleAutoRest(entityUUID, true)); ModNetwork.sendToServer(
PacketNpcCommand.toggleAutoRest(entityUUID, true)
);
} }
private void openInventory() { private void openInventory() {
@@ -728,9 +718,7 @@ public class CommandWandScreen extends Screen {
return -1; return -1;
} }
// =========================================================================
// Render // Render
// =========================================================================
@Override @Override
public void render( public void render(
@@ -805,9 +793,7 @@ public class CommandWandScreen extends Screen {
super.render(graphics, mouseX, mouseY, partialTick); super.render(graphics, mouseX, mouseY, partialTick);
} }
// =========================================================================
// Render: Status Tab // Render: Status Tab
// =========================================================================
private void renderStatusContent( private void renderStatusContent(
GuiGraphics graphics, GuiGraphics graphics,
@@ -1029,9 +1015,7 @@ public class CommandWandScreen extends Screen {
} }
} }
// =========================================================================
// Render: Commands Tab // Render: Commands Tab
// =========================================================================
private void renderCommandContent( private void renderCommandContent(
GuiGraphics graphics, GuiGraphics graphics,
@@ -1059,9 +1043,7 @@ public class CommandWandScreen extends Screen {
); );
} }
// =========================================================================
// Render: Jobs Tab // Render: Jobs Tab
// =========================================================================
private void renderJobsContent( private void renderJobsContent(
GuiGraphics graphics, GuiGraphics graphics,
@@ -1119,9 +1101,7 @@ public class CommandWandScreen extends Screen {
); );
} }
// =========================================================================
// Render helpers // Render helpers
// =========================================================================
private void renderSectionHeader( private void renderSectionHeader(
GuiGraphics graphics, GuiGraphics graphics,

View File

@@ -18,8 +18,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
* GUI screen for interactive conversations with NPCs. * GUI screen for interactive conversations with NPCs.
* Displays available conversation topics with effectiveness indicators. * Displays available conversation topics with effectiveness indicators.
* *
* Phase 5: Enhanced Conversation System
* Phase 2: Refactored to extend BaseInteractionScreen
* *
* DISABLED: Conversation system not in use. Kept because PacketEndConversationS2C * DISABLED: Conversation system not in use. Kept because PacketEndConversationS2C
* references this class in an instanceof check. * references this class in an instanceof check.

View File

@@ -15,7 +15,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
/** /**
* Phase 2.5: Client-side GUI for Lockpick mini-game (Skyrim-style).
* *
* Features: * Features:
* - Sweet spot is HIDDEN (uniform gray bar) * - Sweet spot is HIDDEN (uniform gray bar)

View File

@@ -4,13 +4,13 @@ import com.tiedup.remake.client.gui.util.GuiTextureHelper;
import com.tiedup.remake.client.gui.widgets.EntityPreviewWidget; import com.tiedup.remake.client.gui.widgets.EntityPreviewWidget;
import com.tiedup.remake.entities.EntityDamsel; import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.entities.NpcInventoryMenu; import com.tiedup.remake.entities.NpcInventoryMenu;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;
/** /**
* Screen for viewing and managing NPC inventory. * Screen for viewing and managing NPC inventory.

View File

@@ -22,7 +22,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
* - Ask to be untied * - Ask to be untied
* - End conversation * - End conversation
* *
* Phase 2: Refactored to extend BaseInteractionScreen
*/ */
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class PetRequestScreen extends BaseInteractionScreen { public class PetRequestScreen extends BaseInteractionScreen {

View File

@@ -2,8 +2,8 @@ package com.tiedup.remake.client.gui.screens;
import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.item.PacketAdjustRemote; import com.tiedup.remake.network.item.PacketAdjustRemote;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.UUID; import java.util.UUID;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
@@ -15,7 +15,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
* Screen for remotely adjusting Y position of a slave's gags and blindfolds. * Screen for remotely adjusting Y position of a slave's gags and blindfolds.
* Similar to AdjustmentScreen but operates on a slave entity. * Similar to AdjustmentScreen but operates on a slave entity.
* *
* Phase 16b: GUI Refactoring - Simplified using BaseAdjustmentScreen
*/ */
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class RemoteAdjustmentScreen extends BaseAdjustmentScreen { public class RemoteAdjustmentScreen extends BaseAdjustmentScreen {

View File

@@ -1,7 +1,6 @@
package com.tiedup.remake.client.gui.screens; package com.tiedup.remake.client.gui.screens;
import com.tiedup.remake.client.gui.util.GuiColors; import com.tiedup.remake.client.gui.util.GuiColors;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.client.gui.util.GuiLayoutConstants; import com.tiedup.remake.client.gui.util.GuiLayoutConstants;
import com.tiedup.remake.client.gui.widgets.SlaveEntryWidget; import com.tiedup.remake.client.gui.widgets.SlaveEntryWidget;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.items.base.ItemCollar;
@@ -11,6 +10,7 @@ import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.state.PlayerCaptorManager; import com.tiedup.remake.state.PlayerCaptorManager;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@@ -142,7 +142,9 @@ public class SlaveManagementScreen extends BaseScreen {
IBondageState kidnapped = KidnappedHelper.getKidnappedState(entity); IBondageState kidnapped = KidnappedHelper.getKidnappedState(entity);
if (kidnapped != null && kidnapped.hasCollar()) { if (kidnapped != null && kidnapped.hasCollar()) {
ItemStack collarStack = kidnapped.getEquipment(BodyRegionV2.NECK); ItemStack collarStack = kidnapped.getEquipment(
BodyRegionV2.NECK
);
if (collarStack.getItem() instanceof ItemCollar collar) { if (collarStack.getItem() instanceof ItemCollar collar) {
if (collar.isOwner(collarStack, player)) { if (collar.isOwner(collarStack, player)) {
addSlaveEntry(kidnapped); addSlaveEntry(kidnapped);

View File

@@ -33,7 +33,7 @@ public class UnifiedBondageScreen extends BaseScreen {
private static final int MODE_MASTER_BG = 0xFF707070; private static final int MODE_MASTER_BG = 0xFF707070;
// Full-body view for all tabs (zoom per-tab was too finicky to get right) // Full-body view for all tabs (zoom per-tab was too finicky to get right)
private static final float[] TAB_SCALES = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; private static final float[] TAB_SCALES = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
private static final float[] TAB_OFFSETS = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; private static final float[] TAB_OFFSETS = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
private final ActionPanel.ScreenMode mode; private final ActionPanel.ScreenMode mode;
@@ -70,7 +70,10 @@ public class UnifiedBondageScreen extends BaseScreen {
this(ActionPanel.ScreenMode.MASTER, target); this(ActionPanel.ScreenMode.MASTER, target);
} }
private UnifiedBondageScreen(ActionPanel.ScreenMode mode, LivingEntity target) { private UnifiedBondageScreen(
ActionPanel.ScreenMode mode,
LivingEntity target
) {
super(Component.translatable("gui.tiedup.unified_bondage")); super(Component.translatable("gui.tiedup.unified_bondage"));
this.mode = mode; this.mode = mode;
this.targetEntity = target; this.targetEntity = target;
@@ -78,17 +81,29 @@ public class UnifiedBondageScreen extends BaseScreen {
} }
private LivingEntity getTarget() { private LivingEntity getTarget() {
return (mode == ActionPanel.ScreenMode.SELF) ? minecraft.player : targetEntity; return (mode == ActionPanel.ScreenMode.SELF)
? minecraft.player
: targetEntity;
} }
@Override @Override
protected int getPreferredWidth() { protected int getPreferredWidth() {
return GuiLayoutConstants.getResponsiveWidth(this.width, 0.65f, 420, 600); return GuiLayoutConstants.getResponsiveWidth(
this.width,
0.65f,
420,
600
);
} }
@Override @Override
protected int getPreferredHeight() { protected int getPreferredHeight() {
return GuiLayoutConstants.getResponsiveHeight(this.height, 0.75f, 350, 500); return GuiLayoutConstants.getResponsiveHeight(
this.height,
0.75f,
350,
500
);
} }
@Override @Override
@@ -109,7 +124,10 @@ public class UnifiedBondageScreen extends BaseScreen {
} }
} }
int contentTop = topPos + GuiLayoutConstants.TITLE_HEIGHT + GuiLayoutConstants.MARGIN_M; int contentTop =
topPos +
GuiLayoutConstants.TITLE_HEIGHT +
GuiLayoutConstants.MARGIN_M;
// === Tab Bar === // === Tab Bar ===
tabBar = new RegionTabBar(leftPos + 2, contentTop, imageWidth - 4); tabBar = new RegionTabBar(leftPos + 2, contentTop, imageWidth - 4);
@@ -119,15 +137,24 @@ public class UnifiedBondageScreen extends BaseScreen {
int belowTabs = contentTop + 30; int belowTabs = contentTop + 30;
int statusBarHeight = 46; int statusBarHeight = 46;
int mainContentHeight = imageHeight - (belowTabs - topPos) - statusBarHeight - GuiLayoutConstants.MARGIN_S; int mainContentHeight =
imageHeight -
(belowTabs - topPos) -
statusBarHeight -
GuiLayoutConstants.MARGIN_S;
// === Preview (Left, 40%) === // === Preview (Left, 40%) ===
int previewWidth = (int)((imageWidth - GuiLayoutConstants.MARGIN_M * 3) * 0.40f); int previewWidth = (int) ((imageWidth -
GuiLayoutConstants.MARGIN_M * 3) *
0.40f);
LivingEntity target = getTarget(); LivingEntity target = getTarget();
if (target != null) { if (target != null) {
preview = new EntityPreviewWidget( preview = new EntityPreviewWidget(
leftPos + GuiLayoutConstants.MARGIN_M, belowTabs, leftPos + GuiLayoutConstants.MARGIN_M,
previewWidth, mainContentHeight, target belowTabs,
previewWidth,
mainContentHeight,
target
); );
preview.setAutoRotate(true); preview.setAutoRotate(true);
preview.setAutoRotateSpeed(0.3f); preview.setAutoRotateSpeed(0.3f);
@@ -136,28 +163,42 @@ public class UnifiedBondageScreen extends BaseScreen {
} }
// === Right panel area === // === Right panel area ===
int rightX = leftPos + GuiLayoutConstants.MARGIN_M + previewWidth + GuiLayoutConstants.MARGIN_M; int rightX =
int rightWidth = imageWidth - (rightX - leftPos) - GuiLayoutConstants.MARGIN_M; leftPos +
GuiLayoutConstants.MARGIN_M +
previewWidth +
GuiLayoutConstants.MARGIN_M;
int rightWidth =
imageWidth - (rightX - leftPos) - GuiLayoutConstants.MARGIN_M;
// Action panel height // Action panel height
int actionPanelHeight = 84; int actionPanelHeight = 84;
int slotsHeight = mainContentHeight - actionPanelHeight - GuiLayoutConstants.MARGIN_S; int slotsHeight =
mainContentHeight - actionPanelHeight - GuiLayoutConstants.MARGIN_S;
// === Region Slots === // === Region Slots ===
buildSlots(rightX, belowTabs, rightWidth, slotsHeight); buildSlots(rightX, belowTabs, rightWidth, slotsHeight);
// === Action Panel === // === Action Panel ===
actionPanel = new ActionPanel(rightX, belowTabs + slotsHeight + GuiLayoutConstants.MARGIN_S, actionPanel = new ActionPanel(
rightWidth, actionPanelHeight); rightX,
belowTabs + slotsHeight + GuiLayoutConstants.MARGIN_S,
rightWidth,
actionPanelHeight
);
actionPanel.setMode(mode); actionPanel.setMode(mode);
actionPanel.setTargetEntity(getTarget()); actionPanel.setTargetEntity(getTarget());
actionPanel.setKeyInfo(keyUUID, isMasterKey); actionPanel.setKeyInfo(keyUUID, isMasterKey);
actionPanel.setOnAdjustRequested(region -> { actionPanel.setOnAdjustRequested(region -> {
if (AdjustmentScreen.canOpen()) minecraft.setScreen(new AdjustmentScreen()); if (AdjustmentScreen.canOpen()) minecraft.setScreen(
new AdjustmentScreen()
);
}); });
actionPanel.setOnEquipRequested(this::openPicker); actionPanel.setOnEquipRequested(this::openPicker);
actionPanel.setOnCellAssignRequested(() -> { actionPanel.setOnCellAssignRequested(() -> {
if (targetEntityUUID != null) ModNetwork.sendToServer(new PacketRequestCellList(targetEntityUUID)); if (targetEntityUUID != null) ModNetwork.sendToServer(
new PacketRequestCellList(targetEntityUUID)
);
}); });
actionPanel.setOnCloseRequested(this::onClose); actionPanel.setOnCloseRequested(this::onClose);
actionPanel.clearContext(); actionPanel.clearContext();
@@ -165,7 +206,12 @@ public class UnifiedBondageScreen extends BaseScreen {
// === Status Bar === // === Status Bar ===
int statusY = topPos + imageHeight - statusBarHeight; int statusY = topPos + imageHeight - statusBarHeight;
statusBar = new StatusBarWidget(leftPos, statusY, imageWidth, statusBarHeight); statusBar = new StatusBarWidget(
leftPos,
statusY,
imageWidth,
statusBarHeight
);
statusBar.setMode(mode); statusBar.setMode(mode);
statusBar.setTargetEntity(getTarget()); statusBar.setTargetEntity(getTarget());
statusBar.setOnCloseClicked(this::onClose); statusBar.setOnCloseClicked(this::onClose);
@@ -193,7 +239,9 @@ public class UnifiedBondageScreen extends BaseScreen {
if (stack.getItem() instanceof ItemKey) { if (stack.getItem() instanceof ItemKey) {
return stack; // Regular key takes priority return stack; // Regular key takes priority
} }
if (masterKeyStack.isEmpty() && stack.is(ModItems.MASTER_KEY.get())) { if (
masterKeyStack.isEmpty() && stack.is(ModItems.MASTER_KEY.get())
) {
masterKeyStack = stack; // Remember master key as fallback masterKeyStack = stack; // Remember master key as fallback
} }
} }
@@ -212,8 +260,17 @@ public class UnifiedBondageScreen extends BaseScreen {
BodyRegionV2 region = regions[i]; BodyRegionV2 region = regions[i];
int slotY = y + i * slotHeight; int slotY = y + i * slotHeight;
RegionSlotWidget slot = new RegionSlotWidget(x, slotY, width, slotHeight - 2, RegionSlotWidget slot = new RegionSlotWidget(
region, () -> target != null ? V2EquipmentHelper.getInRegion(target, region) : ItemStack.EMPTY); x,
slotY,
width,
slotHeight - 2,
region,
() ->
target != null
? V2EquipmentHelper.getInRegion(target, region)
: ItemStack.EMPTY
);
slot.setOnClick(this::onSlotClicked); slot.setOnClick(this::onSlotClicked);
slot.setShowEquipButton(true); slot.setShowEquipButton(true);
slot.setOnEquipClick(s -> openPicker(s.getRegion())); slot.setOnEquipClick(s -> openPicker(s.getRegion()));
@@ -245,15 +302,30 @@ public class UnifiedBondageScreen extends BaseScreen {
actionPanel.clearContext(); actionPanel.clearContext();
// Recalculate layout for slots // Recalculate layout for slots
int contentTop = topPos + GuiLayoutConstants.TITLE_HEIGHT + GuiLayoutConstants.MARGIN_M; int contentTop =
topPos +
GuiLayoutConstants.TITLE_HEIGHT +
GuiLayoutConstants.MARGIN_M;
int belowTabs = contentTop + 30; int belowTabs = contentTop + 30;
int statusBarHeight = 46; int statusBarHeight = 46;
int mainContentHeight = imageHeight - (belowTabs - topPos) - statusBarHeight - GuiLayoutConstants.MARGIN_S; int mainContentHeight =
int previewWidth = (int)((imageWidth - GuiLayoutConstants.MARGIN_M * 3) * 0.40f); imageHeight -
int rightX = leftPos + GuiLayoutConstants.MARGIN_M + previewWidth + GuiLayoutConstants.MARGIN_M; (belowTabs - topPos) -
int rightWidth = imageWidth - (rightX - leftPos) - GuiLayoutConstants.MARGIN_M; statusBarHeight -
GuiLayoutConstants.MARGIN_S;
int previewWidth = (int) ((imageWidth -
GuiLayoutConstants.MARGIN_M * 3) *
0.40f);
int rightX =
leftPos +
GuiLayoutConstants.MARGIN_M +
previewWidth +
GuiLayoutConstants.MARGIN_M;
int rightWidth =
imageWidth - (rightX - leftPos) - GuiLayoutConstants.MARGIN_M;
int actionPanelHeight = 84; int actionPanelHeight = 84;
int slotsHeight = mainContentHeight - actionPanelHeight - GuiLayoutConstants.MARGIN_S; int slotsHeight =
mainContentHeight - actionPanelHeight - GuiLayoutConstants.MARGIN_S;
buildSlots(rightX, belowTabs, rightWidth, slotsHeight); buildSlots(rightX, belowTabs, rightWidth, slotsHeight);
autoSelectFirstOccupied(); autoSelectFirstOccupied();
@@ -284,14 +356,23 @@ public class UnifiedBondageScreen extends BaseScreen {
} }
private void openPicker(BodyRegionV2 region) { private void openPicker(BodyRegionV2 region) {
pickerOverlay.open(region, mode == ActionPanel.ScreenMode.SELF, this.width, this.height); pickerOverlay.open(
region,
mode == ActionPanel.ScreenMode.SELF,
this.width,
this.height
);
} }
private void onPickerItemSelected(BodyRegionV2 region, int inventorySlot) { private void onPickerItemSelected(BodyRegionV2 region, int inventorySlot) {
if (mode == ActionPanel.ScreenMode.SELF) { if (mode == ActionPanel.ScreenMode.SELF) {
ModNetwork.sendToServer(new PacketV2SelfEquip(region, inventorySlot)); ModNetwork.sendToServer(
new PacketV2SelfEquip(region, inventorySlot)
);
} else { } else {
ModNetwork.sendToServer(new PacketMasterEquip(targetEntityUUID, region, inventorySlot)); ModNetwork.sendToServer(
new PacketMasterEquip(targetEntityUUID, region, inventorySlot)
);
} }
refreshCountdown = 10; // Refresh after server processes refreshCountdown = 10; // Refresh after server processes
} }
@@ -314,30 +395,65 @@ public class UnifiedBondageScreen extends BaseScreen {
} }
@Override @Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { public void render(
GuiGraphics graphics,
int mouseX,
int mouseY,
float partialTick
) {
this.renderBackground(graphics); this.renderBackground(graphics);
// MC-style raised panel // MC-style raised panel
GuiRenderUtil.drawMCPanel(graphics, leftPos, topPos, imageWidth, imageHeight); GuiRenderUtil.drawMCPanel(
graphics,
leftPos,
topPos,
imageWidth,
imageHeight
);
// Title (dark text, vanilla style) // Title (dark text, vanilla style)
String titleText = this.title.getString(); String titleText = this.title.getString();
if (mode == ActionPanel.ScreenMode.MASTER && targetEntity != null) { if (mode == ActionPanel.ScreenMode.MASTER && targetEntity != null) {
titleText += " \u2014 " + targetEntity.getName().getString(); titleText += " \u2014 " + targetEntity.getName().getString();
} }
GuiRenderUtil.drawCenteredStringNoShadow(graphics, font, titleText, GuiRenderUtil.drawCenteredStringNoShadow(
leftPos + imageWidth / 2, topPos + GuiLayoutConstants.MARGIN_M, TITLE_COLOR); graphics,
font,
titleText,
leftPos + imageWidth / 2,
topPos + GuiLayoutConstants.MARGIN_M,
TITLE_COLOR
);
// Mode badge (top-right) — sober gray badge // Mode badge (top-right) — sober gray badge
int badgeWidth = 90; int badgeWidth = 90;
int badgeX = leftPos + imageWidth - badgeWidth - GuiLayoutConstants.MARGIN_M; int badgeX =
leftPos + imageWidth - badgeWidth - GuiLayoutConstants.MARGIN_M;
int badgeY = topPos + GuiLayoutConstants.MARGIN_S; int badgeY = topPos + GuiLayoutConstants.MARGIN_S;
int badgeBg = mode == ActionPanel.ScreenMode.MASTER ? MODE_MASTER_BG : MODE_SELF_BG; int badgeBg =
graphics.fill(badgeX, badgeY, badgeX + badgeWidth, badgeY + 16, badgeBg); mode == ActionPanel.ScreenMode.MASTER
String badgeText = mode == ActionPanel.ScreenMode.MASTER ? MODE_MASTER_BG
? Component.translatable("gui.tiedup.mode.master").getString() : MODE_SELF_BG;
: Component.translatable("gui.tiedup.mode.self").getString(); graphics.fill(
GuiRenderUtil.drawCenteredStringNoShadow(graphics, font, badgeText, badgeX + badgeWidth / 2, badgeY + 4, GuiRenderUtil.MC_TEXT_DARK); badgeX,
badgeY,
badgeX + badgeWidth,
badgeY + 16,
badgeBg
);
String badgeText =
mode == ActionPanel.ScreenMode.MASTER
? Component.translatable("gui.tiedup.mode.master").getString()
: Component.translatable("gui.tiedup.mode.self").getString();
GuiRenderUtil.drawCenteredStringNoShadow(
graphics,
font,
badgeText,
badgeX + badgeWidth / 2,
badgeY + 4,
GuiRenderUtil.MC_TEXT_DARK
);
// Render all widgets // Render all widgets
super.render(graphics, mouseX, mouseY, partialTick); super.render(graphics, mouseX, mouseY, partialTick);

View File

@@ -6,7 +6,6 @@ import com.tiedup.remake.v2.BodyRegionV2;
* Color constants for TiedUp! GUI elements. * Color constants for TiedUp! GUI elements.
* All colors are in ARGB format (0xAARRGGBB). * All colors are in ARGB format (0xAARRGGBB).
* *
* Phase 16: GUI Revamp
*/ */
public class GuiColors { public class GuiColors {
@@ -65,7 +64,7 @@ public class GuiColors {
*/ */
public static int getRegionColor(BodyRegionV2 region) { public static int getRegionColor(BodyRegionV2 region) {
return switch (region) { return switch (region) {
case HEAD -> 0xFF9C27B0; // Purple case HEAD -> 0xFF9C27B0; // Purple
case EYES -> TYPE_BLINDFOLD; case EYES -> TYPE_BLINDFOLD;
case EARS -> TYPE_EARPLUGS; case EARS -> TYPE_EARPLUGS;
case MOUTH -> TYPE_GAG; case MOUTH -> TYPE_GAG;
@@ -73,12 +72,12 @@ public class GuiColors {
case TORSO -> TYPE_CLOTHES; case TORSO -> TYPE_CLOTHES;
case ARMS -> TYPE_BIND; case ARMS -> TYPE_BIND;
case HANDS -> TYPE_MITTENS; case HANDS -> TYPE_MITTENS;
case FINGERS -> 0xFFFFAB91; // Light orange case FINGERS -> 0xFFFFAB91; // Light orange
case WAIST -> 0xFF795548; // Brown case WAIST -> 0xFF795548; // Brown
case LEGS -> 0xFF607D8B; // Blue-gray case LEGS -> 0xFF607D8B; // Blue-gray
case FEET -> 0xFF78909C; // Light blue-gray case FEET -> 0xFF78909C; // Light blue-gray
case TAIL -> 0xFFCE93D8; // Light purple case TAIL -> 0xFFCE93D8; // Light purple
case WINGS -> 0xFF80DEEA; // Light cyan case WINGS -> 0xFF80DEEA; // Light cyan
}; };
} }

View File

@@ -12,21 +12,27 @@ import net.minecraftforge.api.distmarker.OnlyIn;
public final class GuiRenderUtil { public final class GuiRenderUtil {
// Vanilla MC 3D border colors // Vanilla MC 3D border colors
public static final int MC_PANEL_BG = 0xFFC6C6C6; public static final int MC_PANEL_BG = 0xFFC6C6C6;
public static final int MC_HIGHLIGHT_OUTER = 0xFFFFFFFF; public static final int MC_HIGHLIGHT_OUTER = 0xFFFFFFFF;
public static final int MC_HIGHLIGHT_INNER = 0xFFDBDBDB; public static final int MC_HIGHLIGHT_INNER = 0xFFDBDBDB;
public static final int MC_SHADOW_INNER = 0xFF555555; public static final int MC_SHADOW_INNER = 0xFF555555;
public static final int MC_SHADOW_OUTER = 0xFF373737; public static final int MC_SHADOW_OUTER = 0xFF373737;
public static final int MC_SLOT_BG = 0xFF8B8B8B; public static final int MC_SLOT_BG = 0xFF8B8B8B;
public static final int MC_TEXT_DARK = 0xFF404040; public static final int MC_TEXT_DARK = 0xFF404040;
public static final int MC_TEXT_GRAY = 0xFF555555; public static final int MC_TEXT_GRAY = 0xFF555555;
private GuiRenderUtil() {} private GuiRenderUtil() {}
/** /**
* Draw a vanilla MC-style raised panel (light gray with 3D beveled borders). * Draw a vanilla MC-style raised panel (light gray with 3D beveled borders).
*/ */
public static void drawMCPanel(GuiGraphics graphics, int x, int y, int width, int height) { public static void drawMCPanel(
GuiGraphics graphics,
int x,
int y,
int width,
int height
) {
// Fill // Fill
graphics.fill(x, y, x + width, y + height, MC_PANEL_BG); graphics.fill(x, y, x + width, y + height, MC_PANEL_BG);
// Top highlight (outer white, inner light) // Top highlight (outer white, inner light)
@@ -36,17 +42,41 @@ public final class GuiRenderUtil {
graphics.fill(x, y, x + 1, y + height, MC_HIGHLIGHT_OUTER); graphics.fill(x, y, x + 1, y + height, MC_HIGHLIGHT_OUTER);
graphics.fill(x + 1, y + 1, x + 2, y + height - 1, MC_HIGHLIGHT_INNER); graphics.fill(x + 1, y + 1, x + 2, y + height - 1, MC_HIGHLIGHT_INNER);
// Bottom shadow // Bottom shadow
graphics.fill(x, y + height - 1, x + width, y + height, MC_SHADOW_OUTER); graphics.fill(
graphics.fill(x + 1, y + height - 2, x + width - 1, y + height - 1, MC_SHADOW_INNER); x,
y + height - 1,
x + width,
y + height,
MC_SHADOW_OUTER
);
graphics.fill(
x + 1,
y + height - 2,
x + width - 1,
y + height - 1,
MC_SHADOW_INNER
);
// Right shadow // Right shadow
graphics.fill(x + width - 1, y, x + width, y + height, MC_SHADOW_OUTER); graphics.fill(x + width - 1, y, x + width, y + height, MC_SHADOW_OUTER);
graphics.fill(x + width - 2, y + 1, x + width - 1, y + height - 1, MC_SHADOW_INNER); graphics.fill(
x + width - 2,
y + 1,
x + width - 1,
y + height - 1,
MC_SHADOW_INNER
);
} }
/** /**
* Draw a vanilla MC-style sunken panel (inverted 3D borders — dark outside, light inside). * Draw a vanilla MC-style sunken panel (inverted 3D borders — dark outside, light inside).
*/ */
public static void drawMCSunkenPanel(GuiGraphics graphics, int x, int y, int width, int height) { public static void drawMCSunkenPanel(
GuiGraphics graphics,
int x,
int y,
int width,
int height
) {
graphics.fill(x, y, x + width, y + height, MC_SLOT_BG); graphics.fill(x, y, x + width, y + height, MC_SLOT_BG);
// Top shadow (dark) // Top shadow (dark)
graphics.fill(x, y, x + width, y + 1, MC_SHADOW_OUTER); graphics.fill(x, y, x + width, y + 1, MC_SHADOW_OUTER);
@@ -55,39 +85,95 @@ public final class GuiRenderUtil {
graphics.fill(x, y, x + 1, y + height, MC_SHADOW_OUTER); graphics.fill(x, y, x + 1, y + height, MC_SHADOW_OUTER);
graphics.fill(x + 1, y + 1, x + 2, y + height - 1, MC_SHADOW_INNER); graphics.fill(x + 1, y + 1, x + 2, y + height - 1, MC_SHADOW_INNER);
// Bottom highlight (light) // Bottom highlight (light)
graphics.fill(x, y + height - 1, x + width, y + height, MC_HIGHLIGHT_OUTER); graphics.fill(
graphics.fill(x + 1, y + height - 2, x + width - 1, y + height - 1, MC_HIGHLIGHT_INNER); x,
y + height - 1,
x + width,
y + height,
MC_HIGHLIGHT_OUTER
);
graphics.fill(
x + 1,
y + height - 2,
x + width - 1,
y + height - 1,
MC_HIGHLIGHT_INNER
);
// Right highlight (light) // Right highlight (light)
graphics.fill(x + width - 1, y, x + width, y + height, MC_HIGHLIGHT_OUTER); graphics.fill(
graphics.fill(x + width - 2, y + 1, x + width - 1, y + height - 1, MC_HIGHLIGHT_INNER); x + width - 1,
y,
x + width,
y + height,
MC_HIGHLIGHT_OUTER
);
graphics.fill(
x + width - 2,
y + 1,
x + width - 1,
y + height - 1,
MC_HIGHLIGHT_INNER
);
} }
/** /**
* Draw a vanilla MC-style sunken slot. * Draw a vanilla MC-style sunken slot.
*/ */
public static void drawMCSlot(GuiGraphics graphics, int x, int y, int width, int height) { public static void drawMCSlot(
GuiGraphics graphics,
int x,
int y,
int width,
int height
) {
graphics.fill(x, y, x + width, y + height, MC_SLOT_BG); graphics.fill(x, y, x + width, y + height, MC_SLOT_BG);
// Top shadow // Top shadow
graphics.fill(x, y, x + width, y + 1, MC_SHADOW_OUTER); graphics.fill(x, y, x + width, y + 1, MC_SHADOW_OUTER);
// Left shadow // Left shadow
graphics.fill(x, y, x + 1, y + height, MC_SHADOW_OUTER); graphics.fill(x, y, x + 1, y + height, MC_SHADOW_OUTER);
// Bottom highlight // Bottom highlight
graphics.fill(x, y + height - 1, x + width, y + height, MC_HIGHLIGHT_OUTER); graphics.fill(
x,
y + height - 1,
x + width,
y + height,
MC_HIGHLIGHT_OUTER
);
// Right highlight // Right highlight
graphics.fill(x + width - 1, y, x + width, y + height, MC_HIGHLIGHT_OUTER); graphics.fill(
x + width - 1,
y,
x + width,
y + height,
MC_HIGHLIGHT_OUTER
);
} }
/** /**
* Draw vanilla-style slot hover overlay (white semi-transparent). * Draw vanilla-style slot hover overlay (white semi-transparent).
*/ */
public static void drawSlotHover(GuiGraphics graphics, int x, int y, int width, int height) { public static void drawSlotHover(
GuiGraphics graphics,
int x,
int y,
int width,
int height
) {
graphics.fill(x + 1, y + 1, x + width - 1, y + height - 1, 0x80FFFFFF); graphics.fill(x + 1, y + 1, x + width - 1, y + height - 1, 0x80FFFFFF);
} }
/** /**
* Draw a vanilla MC-style button (raised 3D appearance). * Draw a vanilla MC-style button (raised 3D appearance).
*/ */
public static void drawMCButton(GuiGraphics graphics, int x, int y, int width, int height, boolean hovered, boolean enabled) { public static void drawMCButton(
GuiGraphics graphics,
int x,
int y,
int width,
int height,
boolean hovered,
boolean enabled
) {
int bg = enabled ? (hovered ? 0xFFA0A0A0 : MC_SLOT_BG) : 0xFF606060; int bg = enabled ? (hovered ? 0xFFA0A0A0 : MC_SLOT_BG) : 0xFF606060;
graphics.fill(x, y, x + width, y + height, bg); graphics.fill(x, y, x + width, y + height, bg);
if (enabled) { if (enabled) {
@@ -96,9 +182,21 @@ public final class GuiRenderUtil {
// Left highlight // Left highlight
graphics.fill(x, y, x + 1, y + height, MC_HIGHLIGHT_OUTER); graphics.fill(x, y, x + 1, y + height, MC_HIGHLIGHT_OUTER);
// Bottom shadow // Bottom shadow
graphics.fill(x, y + height - 1, x + width, y + height, MC_SHADOW_OUTER); graphics.fill(
x,
y + height - 1,
x + width,
y + height,
MC_SHADOW_OUTER
);
// Right shadow // Right shadow
graphics.fill(x + width - 1, y, x + width, y + height, MC_SHADOW_OUTER); graphics.fill(
x + width - 1,
y,
x + width,
y + height,
MC_SHADOW_OUTER
);
} else { } else {
// Flat dark border for disabled // Flat dark border for disabled
graphics.fill(x, y, x + width, y + 1, 0xFF505050); graphics.fill(x, y, x + width, y + 1, 0xFF505050);
@@ -111,7 +209,13 @@ public final class GuiRenderUtil {
/** /**
* Draw a selected slot highlight border (gold/yellow). * Draw a selected slot highlight border (gold/yellow).
*/ */
public static void drawSelectedBorder(GuiGraphics graphics, int x, int y, int width, int height) { public static void drawSelectedBorder(
GuiGraphics graphics,
int x,
int y,
int width,
int height
) {
int gold = 0xFFFFD700; int gold = 0xFFFFD700;
// Top // Top
graphics.fill(x, y, x + width, y + 1, gold); graphics.fill(x, y, x + width, y + 1, gold);
@@ -131,9 +235,23 @@ public final class GuiRenderUtil {
* Draw centered text WITHOUT shadow (vanilla drawCenteredString always adds shadow). * Draw centered text WITHOUT shadow (vanilla drawCenteredString always adds shadow).
* Use this for dark text on light MC panels. * Use this for dark text on light MC panels.
*/ */
public static void drawCenteredStringNoShadow(GuiGraphics graphics, net.minecraft.client.gui.Font font, String text, int centerX, int y, int color) { public static void drawCenteredStringNoShadow(
GuiGraphics graphics,
net.minecraft.client.gui.Font font,
String text,
int centerX,
int y,
int color
) {
int textWidth = font.width(text); int textWidth = font.width(text);
graphics.drawString(font, text, centerX - textWidth / 2, y, color, false); graphics.drawString(
font,
text,
centerX - textWidth / 2,
y,
color,
false
);
} }
/** /**

View File

@@ -43,7 +43,10 @@ import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class ActionPanel extends AbstractWidget { public class ActionPanel extends AbstractWidget {
public enum ScreenMode { SELF, MASTER } public enum ScreenMode {
SELF,
MASTER,
}
private record ActionEntry( private record ActionEntry(
String labelKey, String labelKey,
@@ -86,19 +89,35 @@ public class ActionPanel extends AbstractWidget {
super(x, y, width, height, Component.literal("Actions")); super(x, y, width, height, Component.literal("Actions"));
} }
public void setMode(ScreenMode mode) { this.mode = mode; } public void setMode(ScreenMode mode) {
this.mode = mode;
}
public void setTargetEntity(LivingEntity entity) { public void setTargetEntity(LivingEntity entity) {
this.targetEntity = entity; this.targetEntity = entity;
this.targetEntityUUID = entity != null ? entity.getUUID() : null; this.targetEntityUUID = entity != null ? entity.getUUID() : null;
} }
public void setKeyInfo(UUID keyUUID, boolean isMasterKey) { public void setKeyInfo(UUID keyUUID, boolean isMasterKey) {
this.keyUUID = keyUUID; this.keyUUID = keyUUID;
this.isMasterKey = isMasterKey; this.isMasterKey = isMasterKey;
} }
public void setOnAdjustRequested(Consumer<BodyRegionV2> cb) { this.onAdjustRequested = cb; }
public void setOnEquipRequested(Consumer<BodyRegionV2> cb) { this.onEquipRequested = cb; } public void setOnAdjustRequested(Consumer<BodyRegionV2> cb) {
public void setOnCellAssignRequested(Runnable cb) { this.onCellAssignRequested = cb; } this.onAdjustRequested = cb;
public void setOnCloseRequested(Runnable cb) { this.onCloseRequested = cb; } }
public void setOnEquipRequested(Consumer<BodyRegionV2> cb) {
this.onEquipRequested = cb;
}
public void setOnCellAssignRequested(Runnable cb) {
this.onCellAssignRequested = cb;
}
public void setOnCloseRequested(Runnable cb) {
this.onCloseRequested = cb;
}
/** /**
* Update the action panel context. Call when the selected slot changes. * Update the action panel context. Call when the selected slot changes.
@@ -142,7 +161,9 @@ public class ActionPanel extends AbstractWidget {
if (stack.getItem() instanceof ItemKey) { if (stack.getItem() instanceof ItemKey) {
return stack; // Regular key takes priority return stack; // Regular key takes priority
} }
if (masterKeyStack.isEmpty() && stack.is(ModItems.MASTER_KEY.get())) { if (
masterKeyStack.isEmpty() && stack.is(ModItems.MASTER_KEY.get())
) {
masterKeyStack = stack; // Remember master key as fallback masterKeyStack = stack; // Remember master key as fallback
} }
} }
@@ -151,110 +172,214 @@ public class ActionPanel extends AbstractWidget {
private void buildSelfActions(Player player) { private void buildSelfActions(Player player) {
boolean isEmpty = selectedItem.isEmpty(); boolean isEmpty = selectedItem.isEmpty();
boolean armsOccupied = V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS); boolean armsOccupied = V2EquipmentHelper.isRegionOccupied(
boolean handsOccupied = V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.HANDS); player,
BodyRegionV2.ARMS
);
boolean handsOccupied = V2EquipmentHelper.isRegionOccupied(
player,
BodyRegionV2.HANDS
);
boolean armsFree = !armsOccupied; boolean armsFree = !armsOccupied;
boolean handsFree = !handsOccupied; boolean handsFree = !handsOccupied;
boolean isLocked = false; boolean isLocked = false;
boolean isLockable = false; boolean isLockable = false;
boolean isJammed = false; boolean isJammed = false;
boolean hasMittens = V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.HANDS); boolean hasMittens = V2EquipmentHelper.isRegionOccupied(
player,
BodyRegionV2.HANDS
);
if (!isEmpty && selectedItem.getItem() instanceof ILockable lockable) { if (!isEmpty && selectedItem.getItem() instanceof ILockable lockable) {
isLocked = lockable.isLocked(selectedItem); isLocked = lockable.isLocked(selectedItem);
isLockable = lockable.isLockable(selectedItem); isLockable = lockable.isLockable(selectedItem);
isJammed = lockable.isJammed(selectedItem); isJammed = lockable.isJammed(selectedItem);
} }
boolean hasLockpick = !ItemLockpick.findLockpickInInventory(player).isEmpty(); boolean hasLockpick = !ItemLockpick.findLockpickInInventory(
player
).isEmpty();
boolean hasKnife = !GenericKnife.findKnifeInInventory(player).isEmpty(); boolean hasKnife = !GenericKnife.findKnifeInInventory(player).isEmpty();
ItemStack foundKey = findKeyInInventory(player); ItemStack foundKey = findKeyInInventory(player);
boolean hasKey = !foundKey.isEmpty(); boolean hasKey = !foundKey.isEmpty();
boolean foundKeyIsMaster = hasKey && foundKey.is(ModItems.MASTER_KEY.get()); boolean foundKeyIsMaster =
hasKey && foundKey.is(ModItems.MASTER_KEY.get());
if (isEmpty) { if (isEmpty) {
// Equip action for empty slot // Equip action for empty slot
actions.add(new ActionEntry("gui.tiedup.action.equip", true, null, actions.add(
() -> { if (onEquipRequested != null) onEquipRequested.accept(selectedRegion); })); new ActionEntry("gui.tiedup.action.equip", true, null, () -> {
if (onEquipRequested != null) onEquipRequested.accept(
selectedRegion
);
})
);
return; return;
} }
// Remove // Remove
boolean canRemove = armsFree && handsFree && !isLocked && selectedRegion != BodyRegionV2.ARMS; boolean canRemove =
String removeReason = isLocked ? "gui.tiedup.reason.locked" : armsFree &&
!armsFree ? "gui.tiedup.reason.arms_bound" : handsFree &&
!handsFree ? "gui.tiedup.reason.hands_bound" : !isLocked &&
selectedRegion == BodyRegionV2.ARMS ? "gui.tiedup.reason.use_struggle" : null; selectedRegion != BodyRegionV2.ARMS;
actions.add(new ActionEntry("gui.tiedup.action.remove", canRemove, removeReason, String removeReason = isLocked
() -> ModNetwork.sendToServer(new PacketV2SelfRemove(selectedRegion)))); ? "gui.tiedup.reason.locked"
: !armsFree
? "gui.tiedup.reason.arms_bound"
: !handsFree
? "gui.tiedup.reason.hands_bound"
: selectedRegion == BodyRegionV2.ARMS
? "gui.tiedup.reason.use_struggle"
: null;
actions.add(
new ActionEntry(
"gui.tiedup.action.remove",
canRemove,
removeReason,
() ->
ModNetwork.sendToServer(
new PacketV2SelfRemove(selectedRegion)
)
)
);
// Struggle (locked items only) // Struggle (locked items only)
if (isLocked) { if (isLocked) {
actions.add(new ActionEntry("gui.tiedup.action.struggle", true, null, actions.add(
() -> { new ActionEntry(
ModNetwork.sendToServer(new PacketV2StruggleStart(selectedRegion)); "gui.tiedup.action.struggle",
if (onCloseRequested != null) onCloseRequested.run(); true,
})); null,
() -> {
ModNetwork.sendToServer(
new PacketV2StruggleStart(selectedRegion)
);
if (onCloseRequested != null) onCloseRequested.run();
}
)
);
} }
// Lockpick // Lockpick
if (isLocked) { if (isLocked) {
boolean canPick = hasLockpick && !hasMittens && !isJammed; boolean canPick = hasLockpick && !hasMittens && !isJammed;
String pickReason = !hasLockpick ? "gui.tiedup.reason.no_lockpick" : String pickReason = !hasLockpick
hasMittens ? "gui.tiedup.reason.mittens" : ? "gui.tiedup.reason.no_lockpick"
isJammed ? "gui.tiedup.reason.jammed" : null; : hasMittens
actions.add(new ActionEntry("gui.tiedup.action.lockpick", canPick, pickReason, ? "gui.tiedup.reason.mittens"
() -> { : isJammed
ModNetwork.sendToServer(new PacketLockpickMiniGameStart(selectedRegion)); ? "gui.tiedup.reason.jammed"
if (onCloseRequested != null) onCloseRequested.run(); : null;
})); actions.add(
new ActionEntry(
"gui.tiedup.action.lockpick",
canPick,
pickReason,
() -> {
ModNetwork.sendToServer(
new PacketLockpickMiniGameStart(selectedRegion)
);
if (onCloseRequested != null) onCloseRequested.run();
}
)
);
} }
// Cut // Cut
if (isLocked) { if (isLocked) {
boolean canCut = hasKnife && !hasMittens; boolean canCut = hasKnife && !hasMittens;
String cutReason = !hasKnife ? "gui.tiedup.reason.no_knife" : String cutReason = !hasKnife
hasMittens ? "gui.tiedup.reason.mittens" : null; ? "gui.tiedup.reason.no_knife"
actions.add(new ActionEntry("gui.tiedup.action.cut", canCut, cutReason, : hasMittens
() -> { ? "gui.tiedup.reason.mittens"
ModNetwork.sendToServer(new PacketSetKnifeCutTarget(selectedRegion)); : null;
if (onCloseRequested != null) onCloseRequested.run(); actions.add(
})); new ActionEntry(
"gui.tiedup.action.cut",
canCut,
cutReason,
() -> {
ModNetwork.sendToServer(
new PacketSetKnifeCutTarget(selectedRegion)
);
if (onCloseRequested != null) onCloseRequested.run();
}
)
);
} }
// Adjust (MOUTH, EYES only) // Adjust (MOUTH, EYES only)
if (selectedRegion == BodyRegionV2.MOUTH || selectedRegion == BodyRegionV2.EYES) { if (
actions.add(new ActionEntry("gui.tiedup.action.adjust", true, null, selectedRegion == BodyRegionV2.MOUTH ||
() -> { if (onAdjustRequested != null) onAdjustRequested.accept(selectedRegion); })); selectedRegion == BodyRegionV2.EYES
) {
actions.add(
new ActionEntry("gui.tiedup.action.adjust", true, null, () -> {
if (onAdjustRequested != null) onAdjustRequested.accept(
selectedRegion
);
})
);
} }
// Lock (self, with key, arms free) // Lock (self, with key, arms free)
if (isLockable && !isLocked) { if (isLockable && !isLocked) {
boolean canLock = hasKey && armsFree; boolean canLock = hasKey && armsFree;
String lockReason = !hasKey ? "gui.tiedup.reason.no_key" : String lockReason = !hasKey
!armsFree ? "gui.tiedup.reason.arms_bound" : null; ? "gui.tiedup.reason.no_key"
actions.add(new ActionEntry("gui.tiedup.action.lock", canLock, lockReason, : !armsFree
() -> ModNetwork.sendToServer(new PacketV2SelfLock(selectedRegion)))); ? "gui.tiedup.reason.arms_bound"
: null;
actions.add(
new ActionEntry(
"gui.tiedup.action.lock",
canLock,
lockReason,
() ->
ModNetwork.sendToServer(
new PacketV2SelfLock(selectedRegion)
)
)
);
} }
// Unlock (self, with matching key, arms free) // Unlock (self, with matching key, arms free)
if (isLocked) { if (isLocked) {
boolean keyMatches = false; boolean keyMatches = false;
if (hasKey && selectedItem.getItem() instanceof ILockable lockable) { if (
hasKey && selectedItem.getItem() instanceof ILockable lockable
) {
if (foundKeyIsMaster) { if (foundKeyIsMaster) {
keyMatches = true; // Master key matches all locks keyMatches = true; // Master key matches all locks
} else if (foundKey.getItem() instanceof ItemKey itemKey) { } else if (foundKey.getItem() instanceof ItemKey itemKey) {
UUID lockKeyUUID = lockable.getLockedByKeyUUID(selectedItem); UUID lockKeyUUID = lockable.getLockedByKeyUUID(
selectedItem
);
UUID foundKeyUUID = itemKey.getKeyUUID(foundKey); UUID foundKeyUUID = itemKey.getKeyUUID(foundKey);
keyMatches = lockKeyUUID == null || lockKeyUUID.equals(foundKeyUUID); keyMatches =
lockKeyUUID == null || lockKeyUUID.equals(foundKeyUUID);
} }
} }
boolean canUnlock = hasKey && armsFree && keyMatches; boolean canUnlock = hasKey && armsFree && keyMatches;
String unlockReason = !hasKey ? "gui.tiedup.reason.no_key" : String unlockReason = !hasKey
!armsFree ? "gui.tiedup.reason.arms_bound" : ? "gui.tiedup.reason.no_key"
!keyMatches ? "gui.tiedup.reason.wrong_key" : null; : !armsFree
actions.add(new ActionEntry("gui.tiedup.action.unlock", canUnlock, unlockReason, ? "gui.tiedup.reason.arms_bound"
() -> ModNetwork.sendToServer(new PacketV2SelfUnlock(selectedRegion)))); : !keyMatches
? "gui.tiedup.reason.wrong_key"
: null;
actions.add(
new ActionEntry(
"gui.tiedup.action.unlock",
canUnlock,
unlockReason,
() ->
ModNetwork.sendToServer(
new PacketV2SelfUnlock(selectedRegion)
)
)
);
} }
} }
@@ -269,71 +394,169 @@ public class ActionPanel extends AbstractWidget {
} }
if (isEmpty) { if (isEmpty) {
actions.add(new ActionEntry("gui.tiedup.action.equip", true, null, actions.add(
() -> { if (onEquipRequested != null) onEquipRequested.accept(selectedRegion); })); new ActionEntry("gui.tiedup.action.equip", true, null, () -> {
if (onEquipRequested != null) onEquipRequested.accept(
selectedRegion
);
})
);
return; return;
} }
// Remove // Remove
actions.add(new ActionEntry("gui.tiedup.action.remove", !isLocked, actions.add(
isLocked ? "gui.tiedup.reason.locked" : null, new ActionEntry(
() -> ModNetwork.sendToServer(new PacketSlaveItemManage( "gui.tiedup.action.remove",
targetEntityUUID, selectedRegion, PacketSlaveItemManage.Action.REMOVE, keyUUID, isMasterKey)))); !isLocked,
isLocked ? "gui.tiedup.reason.locked" : null,
() ->
ModNetwork.sendToServer(
new PacketSlaveItemManage(
targetEntityUUID,
selectedRegion,
PacketSlaveItemManage.Action.REMOVE,
keyUUID,
isMasterKey
)
)
)
);
// Lock // Lock
if (isLockable && !isLocked) { if (isLockable && !isLocked) {
actions.add(new ActionEntry("gui.tiedup.action.lock", true, null, actions.add(
() -> ModNetwork.sendToServer(new PacketSlaveItemManage( new ActionEntry("gui.tiedup.action.lock", true, null, () ->
targetEntityUUID, selectedRegion, PacketSlaveItemManage.Action.LOCK, keyUUID, isMasterKey)))); ModNetwork.sendToServer(
new PacketSlaveItemManage(
targetEntityUUID,
selectedRegion,
PacketSlaveItemManage.Action.LOCK,
keyUUID,
isMasterKey
)
)
)
);
} }
// Unlock // Unlock
if (isLocked) { if (isLocked) {
actions.add(new ActionEntry("gui.tiedup.action.unlock", true, null, actions.add(
() -> ModNetwork.sendToServer(new PacketSlaveItemManage( new ActionEntry("gui.tiedup.action.unlock", true, null, () ->
targetEntityUUID, selectedRegion, PacketSlaveItemManage.Action.UNLOCK, keyUUID, isMasterKey)))); ModNetwork.sendToServer(
new PacketSlaveItemManage(
targetEntityUUID,
selectedRegion,
PacketSlaveItemManage.Action.UNLOCK,
keyUUID,
isMasterKey
)
)
)
);
} }
// Adjust (MOUTH, EYES) // Adjust (MOUTH, EYES)
if (selectedRegion == BodyRegionV2.MOUTH || selectedRegion == BodyRegionV2.EYES) { if (
actions.add(new ActionEntry("gui.tiedup.action.adjust", true, null, selectedRegion == BodyRegionV2.MOUTH ||
() -> { if (onAdjustRequested != null) onAdjustRequested.accept(selectedRegion); })); selectedRegion == BodyRegionV2.EYES
) {
actions.add(
new ActionEntry("gui.tiedup.action.adjust", true, null, () -> {
if (onAdjustRequested != null) onAdjustRequested.accept(
selectedRegion
);
})
);
} }
// Bondage Service toggle (NECK collar only, prison configured) // Bondage Service toggle (NECK collar only, prison configured)
if (selectedRegion == BodyRegionV2.NECK && selectedItem.getItem() instanceof ItemCollar collar) { if (
selectedRegion == BodyRegionV2.NECK &&
selectedItem.getItem() instanceof ItemCollar collar
) {
if (collar.hasCellAssigned(selectedItem)) { if (collar.hasCellAssigned(selectedItem)) {
boolean svcEnabled = collar.isBondageServiceEnabled(selectedItem); boolean svcEnabled = collar.isBondageServiceEnabled(
String svcKey = svcEnabled ? "gui.tiedup.action.svc_off" : "gui.tiedup.action.svc_on"; selectedItem
actions.add(new ActionEntry(svcKey, true, null, );
() -> ModNetwork.sendToServer(new PacketSlaveItemManage( String svcKey = svcEnabled
targetEntityUUID, selectedRegion, ? "gui.tiedup.action.svc_off"
PacketSlaveItemManage.Action.TOGGLE_BONDAGE_SERVICE, keyUUID, isMasterKey)))); : "gui.tiedup.action.svc_on";
actions.add(
new ActionEntry(svcKey, true, null, () ->
ModNetwork.sendToServer(
new PacketSlaveItemManage(
targetEntityUUID,
selectedRegion,
PacketSlaveItemManage.Action.TOGGLE_BONDAGE_SERVICE,
keyUUID,
isMasterKey
)
)
)
);
} }
// Cell assign // Cell assign
actions.add(new ActionEntry("gui.tiedup.action.cell_assign", true, null, actions.add(
() -> { if (onCellAssignRequested != null) onCellAssignRequested.run(); })); new ActionEntry(
"gui.tiedup.action.cell_assign",
true,
null,
() -> {
if (
onCellAssignRequested != null
) onCellAssignRequested.run();
}
)
);
} }
} }
@Override @Override
protected void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { protected void renderWidget(
GuiGraphics graphics,
int mouseX,
int mouseY,
float partialTick
) {
Minecraft mc = Minecraft.getInstance(); Minecraft mc = Minecraft.getInstance();
// MC-style sunken panel background // MC-style sunken panel background
GuiRenderUtil.drawMCSunkenPanel(graphics, getX(), getY(), width, height); GuiRenderUtil.drawMCSunkenPanel(
graphics,
getX(),
getY(),
width,
height
);
// Title // Title
String title; String title;
if (selectedRegion == null) { if (selectedRegion == null) {
title = Component.translatable("gui.tiedup.action.no_selection").getString(); title = Component.translatable(
"gui.tiedup.action.no_selection"
).getString();
} else if (selectedItem.isEmpty()) { } else if (selectedItem.isEmpty()) {
title = Component.translatable("gui.tiedup.action.title_empty", title = Component.translatable(
Component.translatable("gui.tiedup.region." + selectedRegion.name().toLowerCase())).getString(); "gui.tiedup.action.title_empty",
Component.translatable(
"gui.tiedup.region." + selectedRegion.name().toLowerCase()
)
).getString();
} else { } else {
title = (mode == ScreenMode.MASTER ? "\u265B " : "") + selectedItem.getHoverName().getString(); title =
(mode == ScreenMode.MASTER ? "\u265B " : "") +
selectedItem.getHoverName().getString();
} }
graphics.drawString(mc.font, title, getX() + PADDING, getY() + PADDING, TITLE_COLOR, false); graphics.drawString(
mc.font,
title,
getX() + PADDING,
getY() + PADDING,
TITLE_COLOR,
false
);
// Action buttons grid (MC-style buttons) // Action buttons grid (MC-style buttons)
hoveredIndex = -1; hoveredIndex = -1;
@@ -347,14 +570,27 @@ public class ActionPanel extends AbstractWidget {
int btnX = startX + col * (BUTTON_WIDTH + BUTTON_SPACING); int btnX = startX + col * (BUTTON_WIDTH + BUTTON_SPACING);
int btnY = startY + row * (BUTTON_HEIGHT + 4); int btnY = startY + row * (BUTTON_HEIGHT + 4);
boolean hovered = mouseX >= btnX && mouseX < btnX + BUTTON_WIDTH boolean hovered =
&& mouseY >= btnY && mouseY < btnY + BUTTON_HEIGHT; mouseX >= btnX &&
mouseX < btnX + BUTTON_WIDTH &&
mouseY >= btnY &&
mouseY < btnY + BUTTON_HEIGHT;
if (hovered) hoveredIndex = i; if (hovered) hoveredIndex = i;
int textColor = action.enabled() ? TEXT_ENABLED : TEXT_DISABLED; int textColor = action.enabled() ? TEXT_ENABLED : TEXT_DISABLED;
GuiRenderUtil.drawMCButton(graphics, btnX, btnY, BUTTON_WIDTH, BUTTON_HEIGHT, hovered, action.enabled()); GuiRenderUtil.drawMCButton(
graphics,
btnX,
btnY,
BUTTON_WIDTH,
BUTTON_HEIGHT,
hovered,
action.enabled()
);
String label = Component.translatable(action.labelKey()).getString(); String label = Component.translatable(
action.labelKey()
).getString();
int textX = btnX + (BUTTON_WIDTH - mc.font.width(label)) / 2; int textX = btnX + (BUTTON_WIDTH - mc.font.width(label)) / 2;
int textY = btnY + (BUTTON_HEIGHT - mc.font.lineHeight) / 2; int textY = btnY + (BUTTON_HEIGHT - mc.font.lineHeight) / 2;
graphics.drawString(mc.font, label, textX, textY, textColor, false); graphics.drawString(mc.font, label, textX, textY, textColor, false);
@@ -363,10 +599,16 @@ public class ActionPanel extends AbstractWidget {
// Tooltip for disabled button // Tooltip for disabled button
if (hoveredIndex >= 0 && hoveredIndex < actions.size()) { if (hoveredIndex >= 0 && hoveredIndex < actions.size()) {
ActionEntry hoverAction = actions.get(hoveredIndex); ActionEntry hoverAction = actions.get(hoveredIndex);
if (!hoverAction.enabled() && hoverAction.disabledReasonKey() != null) { if (
graphics.renderTooltip(mc.font, !hoverAction.enabled() &&
hoverAction.disabledReasonKey() != null
) {
graphics.renderTooltip(
mc.font,
Component.translatable(hoverAction.disabledReasonKey()), Component.translatable(hoverAction.disabledReasonKey()),
mouseX, mouseY); mouseX,
mouseY
);
} }
} }
} }
@@ -386,8 +628,12 @@ public class ActionPanel extends AbstractWidget {
int btnX = startX + col * (BUTTON_WIDTH + BUTTON_SPACING); int btnX = startX + col * (BUTTON_WIDTH + BUTTON_SPACING);
int btnY = startY + row * (BUTTON_HEIGHT + 4); int btnY = startY + row * (BUTTON_HEIGHT + 4);
if (mouseX >= btnX && mouseX < btnX + BUTTON_WIDTH if (
&& mouseY >= btnY && mouseY < btnY + BUTTON_HEIGHT) { mouseX >= btnX &&
mouseX < btnX + BUTTON_WIDTH &&
mouseY >= btnY &&
mouseY < btnY + BUTTON_HEIGHT
) {
if (action.enabled() && action.onClick() != null) { if (action.enabled() && action.onClick() != null) {
action.onClick().run(); action.onClick().run();
playDownSound(mc.getSoundManager()); playDownSound(mc.getSoundManager());
@@ -401,6 +647,9 @@ public class ActionPanel extends AbstractWidget {
@Override @Override
protected void updateWidgetNarration(NarrationElementOutput output) { protected void updateWidgetNarration(NarrationElementOutput output) {
output.add(NarratedElementType.TITLE, Component.translatable("gui.tiedup.action_panel")); output.add(
NarratedElementType.TITLE,
Component.translatable("gui.tiedup.action_panel")
);
} }
} }

View File

@@ -19,7 +19,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
* Vertical slider widget for adjusting Y position of items. * Vertical slider widget for adjusting Y position of items.
* Displays current value and supports mouse drag and scroll wheel. * Displays current value and supports mouse drag and scroll wheel.
* *
* Phase 16b: GUI Refactoring - Fixed alignment and layout
*/ */
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class AdjustmentSlider extends AbstractWidget { public class AdjustmentSlider extends AbstractWidget {

View File

@@ -39,16 +39,14 @@ public final class CellListRenderer {
) { ) {
graphics.drawCenteredString( graphics.drawCenteredString(
font, font,
Component.translatable(noItemsKey) Component.translatable(noItemsKey).withStyle(ChatFormatting.GRAY),
.withStyle(ChatFormatting.GRAY),
centerX, centerX,
startY + 20, startY + 20,
GuiColors.TEXT_DISABLED GuiColors.TEXT_DISABLED
); );
graphics.drawCenteredString( graphics.drawCenteredString(
font, font,
Component.translatable(hintKey) Component.translatable(hintKey).withStyle(ChatFormatting.ITALIC),
.withStyle(ChatFormatting.ITALIC),
centerX, centerX,
startY + 35, startY + 35,
GuiColors.TEXT_DISABLED GuiColors.TEXT_DISABLED

View File

@@ -18,7 +18,6 @@ import org.joml.Quaternionf;
* Widget that displays a 3D preview of a LivingEntity. * Widget that displays a 3D preview of a LivingEntity.
* Supports mouse drag rotation and optional auto-rotation. * Supports mouse drag rotation and optional auto-rotation.
* *
* Phase 16: GUI Revamp - Reusable entity preview widget
*/ */
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class EntityPreviewWidget extends AbstractWidget { public class EntityPreviewWidget extends AbstractWidget {

View File

@@ -56,14 +56,27 @@ public class ItemPickerOverlay extends AbstractWidget {
this.visible = false; this.visible = false;
} }
public void setOnItemSelected(BiConsumer<BodyRegionV2, Integer> cb) { this.onItemSelected = cb; } public void setOnItemSelected(BiConsumer<BodyRegionV2, Integer> cb) {
public void setOnCancelled(Runnable cb) { this.onCancelled = cb; } this.onItemSelected = cb;
public boolean isOverlayVisible() { return visible; } }
public void setOnCancelled(Runnable cb) {
this.onCancelled = cb;
}
public boolean isOverlayVisible() {
return visible;
}
/** /**
* Open the picker overlay for a specific region. * Open the picker overlay for a specific region.
*/ */
public void open(BodyRegionV2 region, boolean selfMode, int screenWidth, int screenHeight) { public void open(
BodyRegionV2 region,
boolean selfMode,
int screenWidth,
int screenHeight
) {
this.targetRegion = region; this.targetRegion = region;
this.isSelfMode = selfMode; this.isSelfMode = selfMode;
this.screenWidth = screenWidth; this.screenWidth = screenWidth;
@@ -89,23 +102,45 @@ public class ItemPickerOverlay extends AbstractWidget {
for (int i = 0; i < player.getInventory().getContainerSize(); i++) { for (int i = 0; i < player.getInventory().getContainerSize(); i++) {
ItemStack stack = player.getInventory().getItem(i); ItemStack stack = player.getInventory().getItem(i);
if (stack.isEmpty()) continue; if (stack.isEmpty()) continue;
if (!(stack.getItem() instanceof IV2BondageItem bondageItem)) continue; if (
if (!bondageItem.getOccupiedRegions(stack).contains(targetRegion)) continue; !(stack.getItem() instanceof IV2BondageItem bondageItem)
) continue;
if (
!bondageItem.getOccupiedRegions(stack).contains(targetRegion)
) continue;
entries.add(new PickerEntry(stack, i)); entries.add(new PickerEntry(stack, i));
} }
} }
// Panel bounds (centered on screen) // Panel bounds (centered on screen)
private int getPanelWidth() { return Math.min(280, screenWidth - 40); } private int getPanelWidth() {
return Math.min(280, screenWidth - 40);
}
private int getPanelHeight() { private int getPanelHeight() {
int contentH = entries.size() * ENTRY_HEIGHT + CANCEL_BTN_HEIGHT + PADDING * 3 + 20; int contentH =
entries.size() * ENTRY_HEIGHT +
CANCEL_BTN_HEIGHT +
PADDING * 3 +
20;
return Math.min(contentH, screenHeight - 60); return Math.min(contentH, screenHeight - 60);
} }
private int getPanelX() { return (screenWidth - getPanelWidth()) / 2; }
private int getPanelY() { return (screenHeight - getPanelHeight()) / 2; } private int getPanelX() {
return (screenWidth - getPanelWidth()) / 2;
}
private int getPanelY() {
return (screenHeight - getPanelHeight()) / 2;
}
@Override @Override
protected void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { protected void renderWidget(
GuiGraphics graphics,
int mouseX,
int mouseY,
float partialTick
) {
if (!visible) return; if (!visible) return;
Minecraft mc = Minecraft.getInstance(); Minecraft mc = Minecraft.getInstance();
@@ -121,13 +156,30 @@ public class ItemPickerOverlay extends AbstractWidget {
GuiRenderUtil.drawMCPanel(graphics, panelX, panelY, panelW, panelH); GuiRenderUtil.drawMCPanel(graphics, panelX, panelY, panelW, panelH);
// Title (dark text, vanilla style) // Title (dark text, vanilla style)
String title = Component.translatable("gui.tiedup.picker.title", String title = Component.translatable(
Component.translatable("gui.tiedup.region." + targetRegion.name().toLowerCase())).getString(); "gui.tiedup.picker.title",
graphics.drawString(mc.font, title, panelX + PADDING, panelY + PADDING, GuiRenderUtil.MC_TEXT_DARK, false); Component.translatable(
"gui.tiedup.region." + targetRegion.name().toLowerCase()
)
).getString();
graphics.drawString(
mc.font,
title,
panelX + PADDING,
panelY + PADDING,
GuiRenderUtil.MC_TEXT_DARK,
false
);
// Entries // Entries
int listY = panelY + PADDING + mc.font.lineHeight + 8; int listY = panelY + PADDING + mc.font.lineHeight + 8;
int maxVisible = (panelH - PADDING * 3 - mc.font.lineHeight - 8 - CANCEL_BTN_HEIGHT) / ENTRY_HEIGHT; int maxVisible =
(panelH -
PADDING * 3 -
mc.font.lineHeight -
8 -
CANCEL_BTN_HEIGHT) /
ENTRY_HEIGHT;
for (int i = 0; i < Math.min(entries.size(), maxVisible); i++) { for (int i = 0; i < Math.min(entries.size(), maxVisible); i++) {
int idx = i + scrollOffset; int idx = i + scrollOffset;
@@ -137,56 +189,123 @@ public class ItemPickerOverlay extends AbstractWidget {
int entryX = panelX + PADDING; int entryX = panelX + PADDING;
int entryW = panelW - PADDING * 2; int entryW = panelW - PADDING * 2;
boolean hovered = mouseX >= entryX && mouseX < entryX + entryW boolean hovered =
&& mouseY >= entryY && mouseY < entryY + ENTRY_HEIGHT; mouseX >= entryX &&
mouseX < entryX + entryW &&
mouseY >= entryY &&
mouseY < entryY + ENTRY_HEIGHT;
boolean isArmsWarning = (armsWarningSlot == entry.inventorySlot); boolean isArmsWarning = (armsWarningSlot == entry.inventorySlot);
// MC-style slot for each entry // MC-style slot for each entry
GuiRenderUtil.drawMCSlot(graphics, entryX, entryY, entryW, ENTRY_HEIGHT - 2); GuiRenderUtil.drawMCSlot(
graphics,
entryX,
entryY,
entryW,
ENTRY_HEIGHT - 2
);
// Gold border for ARMS warning confirmation // Gold border for ARMS warning confirmation
if (isArmsWarning) { if (isArmsWarning) {
GuiRenderUtil.drawSelectedBorder(graphics, entryX, entryY, entryW, ENTRY_HEIGHT - 2); GuiRenderUtil.drawSelectedBorder(
graphics,
entryX,
entryY,
entryW,
ENTRY_HEIGHT - 2
);
} }
// Vanilla hover overlay (white semi-transparent) // Vanilla hover overlay (white semi-transparent)
if (hovered && !isArmsWarning) { if (hovered && !isArmsWarning) {
GuiRenderUtil.drawSlotHover(graphics, entryX, entryY, entryW, ENTRY_HEIGHT - 2); GuiRenderUtil.drawSlotHover(
graphics,
entryX,
entryY,
entryW,
ENTRY_HEIGHT - 2
);
} }
// Item icon (16x16) // Item icon (16x16)
graphics.renderItem(entry.stack, entryX + 4, entryY + (ENTRY_HEIGHT - 18) / 2); graphics.renderItem(
entry.stack,
entryX + 4,
entryY + (ENTRY_HEIGHT - 18) / 2
);
// Item name // Item name
String name = entry.stack.getHoverName().getString(); String name = entry.stack.getHoverName().getString();
graphics.drawString(mc.font, name, entryX + 24, entryY + (ENTRY_HEIGHT - mc.font.lineHeight) / 2, graphics.drawString(
GuiRenderUtil.MC_TEXT_DARK, false); mc.font,
name,
entryX + 24,
entryY + (ENTRY_HEIGHT - mc.font.lineHeight) / 2,
GuiRenderUtil.MC_TEXT_DARK,
false
);
} }
// ARMS warning text // ARMS warning text
if (armsWarningSlot >= 0) { if (armsWarningSlot >= 0) {
String warning = Component.translatable("gui.tiedup.picker.arms_warning").getString(); String warning = Component.translatable(
int warningY = listY + Math.min(entries.size(), maxVisible) * ENTRY_HEIGHT + 2; "gui.tiedup.picker.arms_warning"
graphics.drawString(mc.font, warning, panelX + PADDING, warningY, WARNING_COLOR, false); ).getString();
int warningY =
listY + Math.min(entries.size(), maxVisible) * ENTRY_HEIGHT + 2;
graphics.drawString(
mc.font,
warning,
panelX + PADDING,
warningY,
WARNING_COLOR,
false
);
} }
// Empty state // Empty state
if (entries.isEmpty()) { if (entries.isEmpty()) {
String empty = Component.translatable("gui.tiedup.picker.empty").getString(); String empty = Component.translatable(
GuiRenderUtil.drawCenteredStringNoShadow(graphics, mc.font, empty, "gui.tiedup.picker.empty"
panelX + panelW / 2, listY + 20, GuiRenderUtil.MC_TEXT_GRAY); ).getString();
GuiRenderUtil.drawCenteredStringNoShadow(
graphics,
mc.font,
empty,
panelX + panelW / 2,
listY + 20,
GuiRenderUtil.MC_TEXT_GRAY
);
} }
// Cancel button (MC-style) // Cancel button (MC-style)
int cancelX = panelX + (panelW - CANCEL_BTN_WIDTH) / 2; int cancelX = panelX + (panelW - CANCEL_BTN_WIDTH) / 2;
int cancelY = panelY + panelH - CANCEL_BTN_HEIGHT - PADDING; int cancelY = panelY + panelH - CANCEL_BTN_HEIGHT - PADDING;
boolean cancelHovered = mouseX >= cancelX && mouseX < cancelX + CANCEL_BTN_WIDTH boolean cancelHovered =
&& mouseY >= cancelY && mouseY < cancelY + CANCEL_BTN_HEIGHT; mouseX >= cancelX &&
GuiRenderUtil.drawMCButton(graphics, cancelX, cancelY, CANCEL_BTN_WIDTH, CANCEL_BTN_HEIGHT, cancelHovered, true); mouseX < cancelX + CANCEL_BTN_WIDTH &&
String cancelText = Component.translatable("gui.tiedup.cancel").getString(); mouseY >= cancelY &&
GuiRenderUtil.drawCenteredStringNoShadow(graphics, mc.font, cancelText, mouseY < cancelY + CANCEL_BTN_HEIGHT;
cancelX + CANCEL_BTN_WIDTH / 2, cancelY + (CANCEL_BTN_HEIGHT - mc.font.lineHeight) / 2, GuiRenderUtil.drawMCButton(
GuiRenderUtil.MC_TEXT_DARK); graphics,
cancelX,
cancelY,
CANCEL_BTN_WIDTH,
CANCEL_BTN_HEIGHT,
cancelHovered,
true
);
String cancelText = Component.translatable(
"gui.tiedup.cancel"
).getString();
GuiRenderUtil.drawCenteredStringNoShadow(
graphics,
mc.font,
cancelText,
cancelX + CANCEL_BTN_WIDTH / 2,
cancelY + (CANCEL_BTN_HEIGHT - mc.font.lineHeight) / 2,
GuiRenderUtil.MC_TEXT_DARK
);
} }
@Override @Override
@@ -202,8 +321,12 @@ public class ItemPickerOverlay extends AbstractWidget {
// Cancel button // Cancel button
int cancelX = panelX + (panelW - CANCEL_BTN_WIDTH) / 2; int cancelX = panelX + (panelW - CANCEL_BTN_WIDTH) / 2;
int cancelY = panelY + panelH - CANCEL_BTN_HEIGHT - PADDING; int cancelY = panelY + panelH - CANCEL_BTN_HEIGHT - PADDING;
if (mouseX >= cancelX && mouseX < cancelX + CANCEL_BTN_WIDTH if (
&& mouseY >= cancelY && mouseY < cancelY + CANCEL_BTN_HEIGHT) { mouseX >= cancelX &&
mouseX < cancelX + CANCEL_BTN_WIDTH &&
mouseY >= cancelY &&
mouseY < cancelY + CANCEL_BTN_HEIGHT
) {
close(); close();
if (onCancelled != null) onCancelled.run(); if (onCancelled != null) onCancelled.run();
return true; return true;
@@ -211,7 +334,13 @@ public class ItemPickerOverlay extends AbstractWidget {
// Entry clicks // Entry clicks
int listY = panelY + PADDING + mc.font.lineHeight + 8; int listY = panelY + PADDING + mc.font.lineHeight + 8;
int maxVisible = (panelH - PADDING * 3 - mc.font.lineHeight - 8 - CANCEL_BTN_HEIGHT) / ENTRY_HEIGHT; int maxVisible =
(panelH -
PADDING * 3 -
mc.font.lineHeight -
8 -
CANCEL_BTN_HEIGHT) /
ENTRY_HEIGHT;
for (int i = 0; i < Math.min(entries.size(), maxVisible); i++) { for (int i = 0; i < Math.min(entries.size(), maxVisible); i++) {
int idx = i + scrollOffset; int idx = i + scrollOffset;
@@ -221,21 +350,30 @@ public class ItemPickerOverlay extends AbstractWidget {
int entryX = panelX + PADDING; int entryX = panelX + PADDING;
int entryW = panelW - PADDING * 2; int entryW = panelW - PADDING * 2;
if (mouseX >= entryX && mouseX < entryX + entryW if (
&& mouseY >= entryY && mouseY < entryY + ENTRY_HEIGHT) { mouseX >= entryX &&
mouseX < entryX + entryW &&
mouseY >= entryY &&
mouseY < entryY + ENTRY_HEIGHT
) {
// ARMS self-equip warning: double-click confirmation // ARMS self-equip warning: double-click confirmation
if (isSelfMode && targetRegion == BodyRegionV2.ARMS) { if (isSelfMode && targetRegion == BodyRegionV2.ARMS) {
if (armsWarningSlot == entry.inventorySlot) { if (armsWarningSlot == entry.inventorySlot) {
// Second click — confirm // Second click — confirm
if (onItemSelected != null) onItemSelected.accept(targetRegion, entry.inventorySlot); if (onItemSelected != null) onItemSelected.accept(
targetRegion,
entry.inventorySlot
);
close(); close();
} else { } else {
// First click — show warning // First click — show warning
armsWarningSlot = entry.inventorySlot; armsWarningSlot = entry.inventorySlot;
} }
} else { } else {
if (onItemSelected != null) onItemSelected.accept(targetRegion, entry.inventorySlot); if (onItemSelected != null) onItemSelected.accept(
targetRegion,
entry.inventorySlot
);
close(); close();
} }
playDownSound(mc.getSoundManager()); playDownSound(mc.getSoundManager());
@@ -244,8 +382,12 @@ public class ItemPickerOverlay extends AbstractWidget {
} }
// Click outside panel = cancel // Click outside panel = cancel
if (mouseX < panelX || mouseX > panelX + panelW if (
|| mouseY < panelY || mouseY > panelY + panelH) { mouseX < panelX ||
mouseX > panelX + panelW ||
mouseY < panelY ||
mouseY > panelY + panelH
) {
close(); close();
if (onCancelled != null) onCancelled.run(); if (onCancelled != null) onCancelled.run();
return true; return true;
@@ -258,9 +400,18 @@ public class ItemPickerOverlay extends AbstractWidget {
public boolean mouseScrolled(double mouseX, double mouseY, double delta) { public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
if (!visible) return false; if (!visible) return false;
int panelH = getPanelHeight(); int panelH = getPanelHeight();
int maxVisible = (panelH - PADDING * 3 - Minecraft.getInstance().font.lineHeight - 8 - CANCEL_BTN_HEIGHT) / ENTRY_HEIGHT; int maxVisible =
(panelH -
PADDING * 3 -
Minecraft.getInstance().font.lineHeight -
8 -
CANCEL_BTN_HEIGHT) /
ENTRY_HEIGHT;
int maxScroll = Math.max(0, entries.size() - maxVisible); int maxScroll = Math.max(0, entries.size() - maxVisible);
scrollOffset = Math.max(0, Math.min(maxScroll, scrollOffset - (int) delta)); scrollOffset = Math.max(
0,
Math.min(maxScroll, scrollOffset - (int) delta)
);
return true; return true;
} }
@@ -268,7 +419,8 @@ public class ItemPickerOverlay extends AbstractWidget {
public boolean keyPressed(int keyCode, int scanCode, int modifiers) { public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (!visible) return false; if (!visible) return false;
// ESC closes overlay // ESC closes overlay
if (keyCode == 256) { // GLFW_KEY_ESCAPE if (keyCode == 256) {
// GLFW_KEY_ESCAPE
close(); close();
if (onCancelled != null) onCancelled.run(); if (onCancelled != null) onCancelled.run();
return true; return true;
@@ -278,7 +430,17 @@ public class ItemPickerOverlay extends AbstractWidget {
@Override @Override
protected void updateWidgetNarration(NarrationElementOutput output) { protected void updateWidgetNarration(NarrationElementOutput output) {
output.add(NarratedElementType.TITLE, Component.translatable("gui.tiedup.picker.title", output.add(
targetRegion != null ? Component.translatable("gui.tiedup.region." + targetRegion.name().toLowerCase()) : "")); NarratedElementType.TITLE,
Component.translatable(
"gui.tiedup.picker.title",
targetRegion != null
? Component.translatable(
"gui.tiedup.region." +
targetRegion.name().toLowerCase()
)
: ""
)
);
} }
} }

View File

@@ -231,8 +231,12 @@ public class RegionSlotWidget extends AbstractWidget {
if (!showEquipButton || !itemGetter.get().isEmpty()) return false; if (!showEquipButton || !itemGetter.get().isEmpty()) return false;
int bx = getEquipButtonX(); int bx = getEquipButtonX();
int by = getEquipButtonY(); int by = getEquipButtonY();
return mouseX >= bx && mouseX < bx + EQUIP_BUTTON_WIDTH return (
&& mouseY >= by && mouseY < by + EQUIP_BUTTON_HEIGHT; mouseX >= bx &&
mouseX < bx + EQUIP_BUTTON_WIDTH &&
mouseY >= by &&
mouseY < by + EQUIP_BUTTON_HEIGHT
);
} }
@Override @Override
@@ -249,19 +253,32 @@ public class RegionSlotWidget extends AbstractWidget {
boolean adjustHovered = isAdjustButtonHovered(mouseX, mouseY); boolean adjustHovered = isAdjustButtonHovered(mouseX, mouseY);
boolean removeHovered = isRemoveButtonHovered(mouseX, mouseY); boolean removeHovered = isRemoveButtonHovered(mouseX, mouseY);
boolean equipHovered = isEquipButtonHovered(mouseX, mouseY); boolean equipHovered = isEquipButtonHovered(mouseX, mouseY);
boolean anyButtonHovered = adjustHovered || removeHovered || equipHovered; boolean anyButtonHovered =
adjustHovered || removeHovered || equipHovered;
// MC-style sunken slot background // MC-style sunken slot background
GuiRenderUtil.drawMCSlot(graphics, getX(), getY(), width, height); GuiRenderUtil.drawMCSlot(graphics, getX(), getY(), width, height);
// Selected: gold highlight border on top of the slot // Selected: gold highlight border on top of the slot
if (selected) { if (selected) {
GuiRenderUtil.drawSelectedBorder(graphics, getX(), getY(), width, height); GuiRenderUtil.drawSelectedBorder(
graphics,
getX(),
getY(),
width,
height
);
} }
// Hover overlay (vanilla white semi-transparent) // Hover overlay (vanilla white semi-transparent)
if (hovered && !anyButtonHovered && !selected) { if (hovered && !anyButtonHovered && !selected) {
GuiRenderUtil.drawSlotHover(graphics, getX(), getY(), width, height); GuiRenderUtil.drawSlotHover(
graphics,
getX(),
getY(),
width,
height
);
} }
// Region icon (uniform dark gray square) // Region icon (uniform dark gray square)
@@ -325,22 +342,43 @@ public class RegionSlotWidget extends AbstractWidget {
); );
// Resistance bar (for items that implement IHasResistance) // Resistance bar (for items that implement IHasResistance)
if (hasItem && stack.getItem() instanceof IHasResistance resistanceItem) { if (
hasItem && stack.getItem() instanceof IHasResistance resistanceItem
) {
Player player = mc.player; Player player = mc.player;
if (player != null) { if (player != null) {
int current = resistanceItem.getCurrentResistance(stack, player); int current = resistanceItem.getCurrentResistance(
stack,
player
);
int base = resistanceItem.getBaseResistance(player); int base = resistanceItem.getBaseResistance(player);
if (base > 0) { if (base > 0) {
float ratio = Math.max(0f, Math.min(1f, (float) current / base)); float ratio = Math.max(
0f,
Math.min(1f, (float) current / base)
);
int barX = getX() + width - RESISTANCE_BAR_WIDTH - PADDING; int barX = getX() + width - RESISTANCE_BAR_WIDTH - PADDING;
int barY = getY() + height - RESISTANCE_BAR_HEIGHT - PADDING; int barY =
getY() + height - RESISTANCE_BAR_HEIGHT - PADDING;
// Sunken bar background // Sunken bar background
graphics.fill(barX, barY, barX + RESISTANCE_BAR_WIDTH, barY + RESISTANCE_BAR_HEIGHT, 0xFF373737); graphics.fill(
barX,
barY,
barX + RESISTANCE_BAR_WIDTH,
barY + RESISTANCE_BAR_HEIGHT,
0xFF373737
);
// Colored fill: red below 30%, green otherwise // Colored fill: red below 30%, green otherwise
int fillColor = (ratio < 0.30f) ? 0xFFFF4444 : 0xFF44CC44; int fillColor = (ratio < 0.30f) ? 0xFFFF4444 : 0xFF44CC44;
int fillWidth = Math.round(RESISTANCE_BAR_WIDTH * ratio); int fillWidth = Math.round(RESISTANCE_BAR_WIDTH * ratio);
if (fillWidth > 0) { if (fillWidth > 0) {
graphics.fill(barX, barY, barX + fillWidth, barY + RESISTANCE_BAR_HEIGHT, fillColor); graphics.fill(
barX,
barY,
barX + fillWidth,
barY + RESISTANCE_BAR_HEIGHT,
fillColor
);
} }
} }
} }
@@ -350,8 +388,18 @@ public class RegionSlotWidget extends AbstractWidget {
if (!hasItem && showEquipButton) { if (!hasItem && showEquipButton) {
int bx = getEquipButtonX(); int bx = getEquipButtonX();
int by = getEquipButtonY(); int by = getEquipButtonY();
GuiRenderUtil.drawMCButton(graphics, bx, by, EQUIP_BUTTON_WIDTH, EQUIP_BUTTON_HEIGHT, equipHovered, true); GuiRenderUtil.drawMCButton(
String equipLabel = Component.translatable("gui.tiedup.equip").getString(); graphics,
bx,
by,
EQUIP_BUTTON_WIDTH,
EQUIP_BUTTON_HEIGHT,
equipHovered,
true
);
String equipLabel = Component.translatable(
"gui.tiedup.equip"
).getString();
GuiRenderUtil.drawCenteredStringNoShadow( GuiRenderUtil.drawCenteredStringNoShadow(
graphics, graphics,
mc.font, mc.font,
@@ -366,7 +414,15 @@ public class RegionSlotWidget extends AbstractWidget {
if (showAdjustButton && hasItem) { if (showAdjustButton && hasItem) {
int buttonX = getAdjustButtonX(); int buttonX = getAdjustButtonX();
int buttonY = getY() + (height - ADJUST_BUTTON_SIZE) / 2; int buttonY = getY() + (height - ADJUST_BUTTON_SIZE) / 2;
GuiRenderUtil.drawMCButton(graphics, buttonX, buttonY, ADJUST_BUTTON_SIZE, ADJUST_BUTTON_SIZE, adjustHovered, true); GuiRenderUtil.drawMCButton(
graphics,
buttonX,
buttonY,
ADJUST_BUTTON_SIZE,
ADJUST_BUTTON_SIZE,
adjustHovered,
true
);
// Gear icon placeholder // Gear icon placeholder
GuiRenderUtil.drawCenteredStringNoShadow( GuiRenderUtil.drawCenteredStringNoShadow(
@@ -383,10 +439,23 @@ public class RegionSlotWidget extends AbstractWidget {
if (showRemoveButton && hasItem) { if (showRemoveButton && hasItem) {
int buttonX = getRemoveButtonX(); int buttonX = getRemoveButtonX();
int buttonY = getY() + (height - REMOVE_BUTTON_SIZE) / 2; int buttonY = getY() + (height - REMOVE_BUTTON_SIZE) / 2;
GuiRenderUtil.drawMCButton(graphics, buttonX, buttonY, REMOVE_BUTTON_SIZE, REMOVE_BUTTON_SIZE, removeHovered, true); GuiRenderUtil.drawMCButton(
graphics,
buttonX,
buttonY,
REMOVE_BUTTON_SIZE,
REMOVE_BUTTON_SIZE,
removeHovered,
true
);
// Red-tinted overlay // Red-tinted overlay
graphics.fill(buttonX + 1, buttonY + 1, buttonX + REMOVE_BUTTON_SIZE - 1, buttonY + REMOVE_BUTTON_SIZE - 1, graphics.fill(
removeHovered ? 0x40FF0000 : 0x20FF0000); buttonX + 1,
buttonY + 1,
buttonX + REMOVE_BUTTON_SIZE - 1,
buttonY + REMOVE_BUTTON_SIZE - 1,
removeHovered ? 0x40FF0000 : 0x20FF0000
);
// X icon // X icon
GuiRenderUtil.drawCenteredStringNoShadow( GuiRenderUtil.drawCenteredStringNoShadow(

View File

@@ -26,11 +26,31 @@ public class RegionTabBar extends AbstractWidget {
* Body zone tabs grouping the 14 BodyRegionV2 values. * Body zone tabs grouping the 14 BodyRegionV2 values.
*/ */
public enum BodyTab { public enum BodyTab {
HEAD("gui.tiedup.tab.head", BodyRegionV2.HEAD, BodyRegionV2.EYES, BodyRegionV2.EARS, BodyRegionV2.MOUTH), HEAD(
"gui.tiedup.tab.head",
BodyRegionV2.HEAD,
BodyRegionV2.EYES,
BodyRegionV2.EARS,
BodyRegionV2.MOUTH
),
UPPER("gui.tiedup.tab.upper", BodyRegionV2.NECK, BodyRegionV2.TORSO), UPPER("gui.tiedup.tab.upper", BodyRegionV2.NECK, BodyRegionV2.TORSO),
ARMS("gui.tiedup.tab.arms", BodyRegionV2.ARMS, BodyRegionV2.HANDS, BodyRegionV2.FINGERS), ARMS(
LOWER("gui.tiedup.tab.lower", BodyRegionV2.WAIST, BodyRegionV2.LEGS, BodyRegionV2.FEET), "gui.tiedup.tab.arms",
SPECIAL("gui.tiedup.tab.special", BodyRegionV2.TAIL, BodyRegionV2.WINGS); BodyRegionV2.ARMS,
BodyRegionV2.HANDS,
BodyRegionV2.FINGERS
),
LOWER(
"gui.tiedup.tab.lower",
BodyRegionV2.WAIST,
BodyRegionV2.LEGS,
BodyRegionV2.FEET
),
SPECIAL(
"gui.tiedup.tab.special",
BodyRegionV2.TAIL,
BodyRegionV2.WINGS
);
private final String translationKey; private final String translationKey;
private final Set<BodyRegionV2> regions; private final Set<BodyRegionV2> regions;
@@ -41,13 +61,20 @@ public class RegionTabBar extends AbstractWidget {
for (BodyRegionV2 r : regions) this.regions.add(r); for (BodyRegionV2 r : regions) this.regions.add(r);
} }
public String getTranslationKey() { return translationKey; } public String getTranslationKey() {
public Set<BodyRegionV2> getRegions() { return regions; } return translationKey;
}
public Set<BodyRegionV2> getRegions() {
return regions;
}
/** Check if any region in this tab has an equipped item on the entity. */ /** Check if any region in this tab has an equipped item on the entity. */
public boolean hasEquippedItems(LivingEntity entity) { public boolean hasEquippedItems(LivingEntity entity) {
for (BodyRegionV2 region : regions) { for (BodyRegionV2 region : regions) {
if (V2EquipmentHelper.isRegionOccupied(entity, region)) return true; if (
V2EquipmentHelper.isRegionOccupied(entity, region)
) return true;
} }
return false; return false;
} }
@@ -59,10 +86,10 @@ public class RegionTabBar extends AbstractWidget {
private static final int DOT_RADIUS = 3; private static final int DOT_RADIUS = 3;
// Colors (vanilla MC style) // Colors (vanilla MC style)
private static final int BG_ACTIVE = 0xFFC6C6C6; // Same as main panel private static final int BG_ACTIVE = 0xFFC6C6C6; // Same as main panel
private static final int BG_INACTIVE = 0xFF8B8B8B; // Darker, slot-like private static final int BG_INACTIVE = 0xFF8B8B8B; // Darker, slot-like
private static final int BG_HOVER = 0xFFA0A0A0; // Between active and inactive private static final int BG_HOVER = 0xFFA0A0A0; // Between active and inactive
private static final int TEXT_ACTIVE = 0xFF404040; // Dark text private static final int TEXT_ACTIVE = 0xFF404040; // Dark text
private static final int TEXT_INACTIVE = 0xFF555555; // Gray text private static final int TEXT_INACTIVE = 0xFF555555; // Gray text
private static final int BAR_BG = 0xFFA0A0A0; private static final int BAR_BG = 0xFFA0A0A0;
@@ -74,9 +101,17 @@ public class RegionTabBar extends AbstractWidget {
super(x, y, width, TAB_HEIGHT, Component.literal("Tab Bar")); super(x, y, width, TAB_HEIGHT, Component.literal("Tab Bar"));
} }
public void setOnTabChanged(Consumer<BodyTab> callback) { this.onTabChanged = callback; } public void setOnTabChanged(Consumer<BodyTab> callback) {
public void setTargetEntity(LivingEntity entity) { this.targetEntity = entity; } this.onTabChanged = callback;
public BodyTab getActiveTab() { return activeTab; } }
public void setTargetEntity(LivingEntity entity) {
this.targetEntity = entity;
}
public BodyTab getActiveTab() {
return activeTab;
}
public void setActiveTab(BodyTab tab) { public void setActiveTab(BodyTab tab) {
if (this.activeTab != tab) { if (this.activeTab != tab) {
@@ -86,7 +121,12 @@ public class RegionTabBar extends AbstractWidget {
} }
@Override @Override
protected void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { protected void renderWidget(
GuiGraphics graphics,
int mouseX,
int mouseY,
float partialTick
) {
Minecraft mc = Minecraft.getInstance(); Minecraft mc = Minecraft.getInstance();
// Background bar // Background bar
@@ -99,34 +139,89 @@ public class RegionTabBar extends AbstractWidget {
BodyTab tab = tabs[i]; BodyTab tab = tabs[i];
int tabX = getX() + i * (tabWidth + TAB_SPACING); int tabX = getX() + i * (tabWidth + TAB_SPACING);
boolean isActive = (tab == activeTab); boolean isActive = (tab == activeTab);
boolean isHovered = mouseX >= tabX && mouseX < tabX + tabWidth boolean isHovered =
&& mouseY >= getY() && mouseY < getY() + height; mouseX >= tabX &&
mouseX < tabX + tabWidth &&
mouseY >= getY() &&
mouseY < getY() + height;
int bgColor = isActive ? BG_ACTIVE : (isHovered ? BG_HOVER : BG_INACTIVE); int bgColor = isActive
graphics.fill(tabX, getY(), tabX + tabWidth, getY() + height, bgColor); ? BG_ACTIVE
: (isHovered ? BG_HOVER : BG_INACTIVE);
graphics.fill(
tabX,
getY(),
tabX + tabWidth,
getY() + height,
bgColor
);
if (isActive) { if (isActive) {
// Active tab: raised 3D look, no bottom border (connects to panel below) // Active tab: raised 3D look, no bottom border (connects to panel below)
// Top highlight // Top highlight
graphics.fill(tabX, getY(), tabX + tabWidth, getY() + 1, GuiRenderUtil.MC_HIGHLIGHT_OUTER); graphics.fill(
tabX,
getY(),
tabX + tabWidth,
getY() + 1,
GuiRenderUtil.MC_HIGHLIGHT_OUTER
);
// Left highlight // Left highlight
graphics.fill(tabX, getY(), tabX + 1, getY() + height, GuiRenderUtil.MC_HIGHLIGHT_OUTER); graphics.fill(
tabX,
getY(),
tabX + 1,
getY() + height,
GuiRenderUtil.MC_HIGHLIGHT_OUTER
);
// Right shadow // Right shadow
graphics.fill(tabX + tabWidth - 1, getY(), tabX + tabWidth, getY() + height, GuiRenderUtil.MC_SHADOW_OUTER); graphics.fill(
tabX + tabWidth - 1,
getY(),
tabX + tabWidth,
getY() + height,
GuiRenderUtil.MC_SHADOW_OUTER
);
} else { } else {
// Inactive tab: full 3D sunken borders // Inactive tab: full 3D sunken borders
// Top shadow // Top shadow
graphics.fill(tabX, getY(), tabX + tabWidth, getY() + 1, GuiRenderUtil.MC_SHADOW_OUTER); graphics.fill(
tabX,
getY(),
tabX + tabWidth,
getY() + 1,
GuiRenderUtil.MC_SHADOW_OUTER
);
// Left shadow // Left shadow
graphics.fill(tabX, getY(), tabX + 1, getY() + height, GuiRenderUtil.MC_SHADOW_OUTER); graphics.fill(
tabX,
getY(),
tabX + 1,
getY() + height,
GuiRenderUtil.MC_SHADOW_OUTER
);
// Bottom highlight // Bottom highlight
graphics.fill(tabX, getY() + height - 1, tabX + tabWidth, getY() + height, GuiRenderUtil.MC_HIGHLIGHT_OUTER); graphics.fill(
tabX,
getY() + height - 1,
tabX + tabWidth,
getY() + height,
GuiRenderUtil.MC_HIGHLIGHT_OUTER
);
// Right highlight // Right highlight
graphics.fill(tabX + tabWidth - 1, getY(), tabX + tabWidth, getY() + height, GuiRenderUtil.MC_HIGHLIGHT_OUTER); graphics.fill(
tabX + tabWidth - 1,
getY(),
tabX + tabWidth,
getY() + height,
GuiRenderUtil.MC_HIGHLIGHT_OUTER
);
} }
// Tab label // Tab label
String label = Component.translatable(tab.getTranslationKey()).getString(); String label = Component.translatable(
tab.getTranslationKey()
).getString();
int textColor = isActive ? TEXT_ACTIVE : TEXT_INACTIVE; int textColor = isActive ? TEXT_ACTIVE : TEXT_INACTIVE;
int textX = tabX + (tabWidth - mc.font.width(label)) / 2; int textX = tabX + (tabWidth - mc.font.width(label)) / 2;
int textY = getY() + (height - mc.font.lineHeight) / 2; int textY = getY() + (height - mc.font.lineHeight) / 2;
@@ -136,8 +231,13 @@ public class RegionTabBar extends AbstractWidget {
if (targetEntity != null && tab.hasEquippedItems(targetEntity)) { if (targetEntity != null && tab.hasEquippedItems(targetEntity)) {
int dotX = tabX + tabWidth - DOT_RADIUS - 4; int dotX = tabX + tabWidth - DOT_RADIUS - 4;
int dotY = getY() + 4; int dotY = getY() + 4;
graphics.fill(dotX - DOT_RADIUS, dotY - DOT_RADIUS, graphics.fill(
dotX + DOT_RADIUS, dotY + DOT_RADIUS, 0xFFCCCCCC); dotX - DOT_RADIUS,
dotY - DOT_RADIUS,
dotX + DOT_RADIUS,
dotY + DOT_RADIUS,
0xFFCCCCCC
);
} }
} }
} }
@@ -162,8 +262,12 @@ public class RegionTabBar extends AbstractWidget {
@Override @Override
protected void updateWidgetNarration(NarrationElementOutput output) { protected void updateWidgetNarration(NarrationElementOutput output) {
output.add(NarratedElementType.TITLE, output.add(
Component.translatable("gui.tiedup.tab_bar", NarratedElementType.TITLE,
Component.translatable(activeTab.getTranslationKey()))); Component.translatable(
"gui.tiedup.tab_bar",
Component.translatable(activeTab.getTranslationKey())
)
);
} }
} }

View File

@@ -1,13 +1,13 @@
package com.tiedup.remake.client.gui.widgets; package com.tiedup.remake.client.gui.widgets;
import static com.tiedup.remake.client.gui.util.GuiLayoutConstants.*; import static com.tiedup.remake.client.gui.util.GuiLayoutConstants.*;
import com.tiedup.remake.v2.BodyRegionV2;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.tiedup.remake.client.gui.util.GuiColors; import com.tiedup.remake.client.gui.util.GuiColors;
import com.tiedup.remake.items.ItemGpsCollar; import com.tiedup.remake.items.ItemGpsCollar;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;

View File

@@ -43,18 +43,37 @@ public class StatusBarWidget extends AbstractWidget {
super(x, y, width, height, Component.literal("Status Bar")); super(x, y, width, height, Component.literal("Status Bar"));
} }
public void setMode(ActionPanel.ScreenMode mode) { this.mode = mode; } public void setMode(ActionPanel.ScreenMode mode) {
public void setTargetEntity(LivingEntity entity) { this.targetEntity = entity; } this.mode = mode;
public void setOnCloseClicked(Runnable cb) { this.onCloseClicked = cb; } }
public void setTargetEntity(LivingEntity entity) {
this.targetEntity = entity;
}
public void setOnCloseClicked(Runnable cb) {
this.onCloseClicked = cb;
}
@Override @Override
protected void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { protected void renderWidget(
GuiGraphics graphics,
int mouseX,
int mouseY,
float partialTick
) {
Minecraft mc = Minecraft.getInstance(); Minecraft mc = Minecraft.getInstance();
Player player = mc.player; Player player = mc.player;
if (player == null) return; if (player == null) return;
// MC-style sunken inset panel (slightly darker than main) // MC-style sunken inset panel (slightly darker than main)
GuiRenderUtil.drawMCSunkenPanel(graphics, getX(), getY(), width, height); GuiRenderUtil.drawMCSunkenPanel(
graphics,
getX(),
getY(),
width,
height
);
int textY1 = getY() + PADDING; int textY1 = getY() + PADDING;
int textY2 = textY1 + mc.font.lineHeight + 2; int textY2 = textY1 + mc.font.lineHeight + 2;
@@ -68,46 +87,104 @@ public class StatusBarWidget extends AbstractWidget {
// Close button (right side, MC-style button) // Close button (right side, MC-style button)
int closeBtnX = getX() + width - CLOSE_BTN_WIDTH - PADDING; int closeBtnX = getX() + width - CLOSE_BTN_WIDTH - PADDING;
int closeBtnY = getY() + (height - CLOSE_BTN_HEIGHT) / 2; int closeBtnY = getY() + (height - CLOSE_BTN_HEIGHT) / 2;
boolean closeHovered = mouseX >= closeBtnX && mouseX < closeBtnX + CLOSE_BTN_WIDTH boolean closeHovered =
&& mouseY >= closeBtnY && mouseY < closeBtnY + CLOSE_BTN_HEIGHT; mouseX >= closeBtnX &&
GuiRenderUtil.drawMCButton(graphics, closeBtnX, closeBtnY, CLOSE_BTN_WIDTH, CLOSE_BTN_HEIGHT, closeHovered, true); mouseX < closeBtnX + CLOSE_BTN_WIDTH &&
String closeText = Component.translatable("gui.tiedup.close_esc").getString(); mouseY >= closeBtnY &&
GuiRenderUtil.drawCenteredStringNoShadow(graphics, mc.font, closeText, mouseY < closeBtnY + CLOSE_BTN_HEIGHT;
closeBtnX + CLOSE_BTN_WIDTH / 2, closeBtnY + (CLOSE_BTN_HEIGHT - mc.font.lineHeight) / 2, GuiRenderUtil.drawMCButton(
GuiRenderUtil.MC_TEXT_DARK); graphics,
closeBtnX,
closeBtnY,
CLOSE_BTN_WIDTH,
CLOSE_BTN_HEIGHT,
closeHovered,
true
);
String closeText = Component.translatable(
"gui.tiedup.close_esc"
).getString();
GuiRenderUtil.drawCenteredStringNoShadow(
graphics,
mc.font,
closeText,
closeBtnX + CLOSE_BTN_WIDTH / 2,
closeBtnY + (CLOSE_BTN_HEIGHT - mc.font.lineHeight) / 2,
GuiRenderUtil.MC_TEXT_DARK
);
} }
private void renderSelfStatus(GuiGraphics graphics, Player player, int y1, int y2) { private void renderSelfStatus(
GuiGraphics graphics,
Player player,
int y1,
int y2
) {
Minecraft mc = Minecraft.getInstance(); Minecraft mc = Minecraft.getInstance();
int x = getX() + PADDING; int x = getX() + PADDING;
// Tool status line 1 // Tool status line 1
ItemStack lockpick = ItemLockpick.findLockpickInInventory(player); ItemStack lockpick = ItemLockpick.findLockpickInInventory(player);
String pickText = lockpick.isEmpty() String pickText = lockpick.isEmpty()
? Component.translatable("gui.tiedup.status.no_lockpick").getString() ? Component.translatable(
: Component.translatable("gui.tiedup.status.lockpick_uses", "gui.tiedup.status.no_lockpick"
lockpick.getMaxDamage() - lockpick.getDamageValue()).getString(); ).getString()
graphics.drawString(mc.font, pickText, x, y1, GuiRenderUtil.MC_TEXT_DARK, false); : Component.translatable(
"gui.tiedup.status.lockpick_uses",
lockpick.getMaxDamage() - lockpick.getDamageValue()
).getString();
graphics.drawString(
mc.font,
pickText,
x,
y1,
GuiRenderUtil.MC_TEXT_DARK,
false
);
ItemStack knife = GenericKnife.findKnifeInInventory(player); ItemStack knife = GenericKnife.findKnifeInInventory(player);
String knifeText = knife.isEmpty() String knifeText = knife.isEmpty()
? Component.translatable("gui.tiedup.status.no_knife").getString() ? Component.translatable("gui.tiedup.status.no_knife").getString()
: Component.translatable("gui.tiedup.status.knife_uses", : Component.translatable(
knife.getMaxDamage() - knife.getDamageValue()).getString(); "gui.tiedup.status.knife_uses",
graphics.drawString(mc.font, knifeText, x + 150, y1, GuiRenderUtil.MC_TEXT_DARK, false); knife.getMaxDamage() - knife.getDamageValue()
).getString();
graphics.drawString(
mc.font,
knifeText,
x + 150,
y1,
GuiRenderUtil.MC_TEXT_DARK,
false
);
// Arms resistance line 2 // Arms resistance line 2
ItemStack armsBind = V2EquipmentHelper.getInRegion(player, BodyRegionV2.ARMS); ItemStack armsBind = V2EquipmentHelper.getInRegion(
if (!armsBind.isEmpty() && armsBind.getItem() instanceof IHasResistance res) { player,
BodyRegionV2.ARMS
);
if (
!armsBind.isEmpty() &&
armsBind.getItem() instanceof IHasResistance res
) {
int curr = res.getCurrentResistance(armsBind, player); int curr = res.getCurrentResistance(armsBind, player);
int max = res.getBaseResistance(player); int max = res.getBaseResistance(player);
String resText = Component.translatable("gui.tiedup.status.arms_resistance", curr, max).getString(); String resText = Component.translatable(
"gui.tiedup.status.arms_resistance",
curr,
max
).getString();
int color = curr < max * 0.3 ? GuiColors.ERROR : GuiColors.SUCCESS; int color = curr < max * 0.3 ? GuiColors.ERROR : GuiColors.SUCCESS;
graphics.drawString(mc.font, resText, x, y2, color, false); graphics.drawString(mc.font, resText, x, y2, color, false);
} }
} }
private void renderMasterStatus(GuiGraphics graphics, Player player, int y1, int y2) { private void renderMasterStatus(
GuiGraphics graphics,
Player player,
int y1,
int y2
) {
Minecraft mc = Minecraft.getInstance(); Minecraft mc = Minecraft.getInstance();
int x = getX() + PADDING; int x = getX() + PADDING;
@@ -127,18 +204,38 @@ public class StatusBarWidget extends AbstractWidget {
String keyText; String keyText;
if (keyStack.isEmpty()) { if (keyStack.isEmpty()) {
keyText = Component.translatable("gui.tiedup.status.no_key").getString(); keyText = Component.translatable(
"gui.tiedup.status.no_key"
).getString();
} else { } else {
keyText = Component.translatable("gui.tiedup.status.key_info", keyText = Component.translatable(
keyStack.getHoverName().getString()).getString(); "gui.tiedup.status.key_info",
keyStack.getHoverName().getString()
).getString();
} }
graphics.drawString(mc.font, keyText, x, y1, GuiRenderUtil.MC_TEXT_DARK, false); graphics.drawString(
mc.font,
keyText,
x,
y1,
GuiRenderUtil.MC_TEXT_DARK,
false
);
// Target info // Target info
if (targetEntity != null) { if (targetEntity != null) {
String targetText = Component.translatable("gui.tiedup.status.target_info", String targetText = Component.translatable(
targetEntity.getName().getString()).getString(); "gui.tiedup.status.target_info",
graphics.drawString(mc.font, targetText, x, y2, GuiRenderUtil.MC_TEXT_DARK, false); targetEntity.getName().getString()
).getString();
graphics.drawString(
mc.font,
targetText,
x,
y2,
GuiRenderUtil.MC_TEXT_DARK,
false
);
} }
} }
@@ -148,8 +245,12 @@ public class StatusBarWidget extends AbstractWidget {
int closeBtnX = getX() + width - CLOSE_BTN_WIDTH - PADDING; int closeBtnX = getX() + width - CLOSE_BTN_WIDTH - PADDING;
int closeBtnY = getY() + (height - CLOSE_BTN_HEIGHT) / 2; int closeBtnY = getY() + (height - CLOSE_BTN_HEIGHT) / 2;
if (mouseX >= closeBtnX && mouseX < closeBtnX + CLOSE_BTN_WIDTH if (
&& mouseY >= closeBtnY && mouseY < closeBtnY + CLOSE_BTN_HEIGHT) { mouseX >= closeBtnX &&
mouseX < closeBtnX + CLOSE_BTN_WIDTH &&
mouseY >= closeBtnY &&
mouseY < closeBtnY + CLOSE_BTN_HEIGHT
) {
if (onCloseClicked != null) onCloseClicked.run(); if (onCloseClicked != null) onCloseClicked.run();
playDownSound(Minecraft.getInstance().getSoundManager()); playDownSound(Minecraft.getInstance().getSoundManager());
return true; return true;
@@ -159,6 +260,9 @@ public class StatusBarWidget extends AbstractWidget {
@Override @Override
protected void updateWidgetNarration(NarrationElementOutput output) { protected void updateWidgetNarration(NarrationElementOutput output) {
output.add(NarratedElementType.TITLE, Component.translatable("gui.tiedup.status_bar")); output.add(
NarratedElementType.TITLE,
Component.translatable("gui.tiedup.status_bar")
);
} }
} }

View File

@@ -9,11 +9,11 @@ import com.tiedup.remake.entities.AbstractTiedUpNpc;
import com.tiedup.remake.entities.EntityKidnapperArcher; import com.tiedup.remake.entities.EntityKidnapperArcher;
import com.tiedup.remake.entities.EntityMaster; import com.tiedup.remake.entities.EntityMaster;
import com.tiedup.remake.entities.ai.master.MasterState; import com.tiedup.remake.entities.ai.master.MasterState;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.PoseType; import com.tiedup.remake.items.base.PoseType;
import com.tiedup.remake.items.clothes.GenericClothes; import com.tiedup.remake.items.clothes.GenericClothes;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import dev.kosmx.playerAnim.core.impl.AnimationProcessor; import dev.kosmx.playerAnim.core.impl.AnimationProcessor;
import dev.kosmx.playerAnim.core.util.SetableSupplier; import dev.kosmx.playerAnim.core.util.SetableSupplier;
import dev.kosmx.playerAnim.impl.Helper; import dev.kosmx.playerAnim.impl.Helper;
@@ -33,8 +33,6 @@ import org.slf4j.Logger;
/** /**
* Model for AbstractTiedUpNpc - Humanoid female NPC. * Model for AbstractTiedUpNpc - Humanoid female NPC.
* *
* Phase 14.2.3: Rendering system
* Phase 19: Extends PlayerModel for full layer support (hat, jacket, sleeves, pants)
* *
* Features: * Features:
* - Extends PlayerModel for player-like rendering with outer layers * - Extends PlayerModel for player-like rendering with outer layers
@@ -151,9 +149,7 @@ public class DamselModel
} }
} }
// ========================================
// IMutableModel Implementation // IMutableModel Implementation
// ========================================
@Override @Override
public void setEmoteSupplier(SetableSupplier<AnimationProcessor> supplier) { public void setEmoteSupplier(SetableSupplier<AnimationProcessor> supplier) {
@@ -174,8 +170,6 @@ public class DamselModel
* - Tied up: Arms behind back, legs frozen (or variant pose based on bind type) * - Tied up: Arms behind back, legs frozen (or variant pose based on bind type)
* - Free: Normal humanoid animations * - Free: Normal humanoid animations
* *
* Phase 15: Different poses for different bind types (straitjacket, wrap, latex_sack)
* Phase 15.1: Hide arms for wrap/latex_sack (matching original mod)
* *
* @param entity AbstractTiedUpNpc instance * @param entity AbstractTiedUpNpc instance
* @param limbSwing Limb swing animation value * @param limbSwing Limb swing animation value
@@ -193,7 +187,6 @@ public class DamselModel
float netHeadYaw, float netHeadYaw,
float headPitch float headPitch
) { ) {
// Phase 18: Handle archer arm poses BEFORE super call
// Only show bow animation when in ranged mode (has active shooting target) // Only show bow animation when in ranged mode (has active shooting target)
if (entity instanceof EntityKidnapperArcher archer) { if (entity instanceof EntityKidnapperArcher archer) {
if (archer.isInRangedMode()) { if (archer.isInRangedMode()) {
@@ -222,7 +215,7 @@ public class DamselModel
// Arms // Arms
this.leftArm.visible = true; this.leftArm.visible = true;
this.rightArm.visible = true; this.rightArm.visible = true;
// Outer layers (Phase 19) // Outer layers
this.hat.visible = true; this.hat.visible = true;
this.jacket.visible = true; this.jacket.visible = true;
this.leftSleeve.visible = true; this.leftSleeve.visible = true;
@@ -302,7 +295,7 @@ public class DamselModel
); );
} }
// Sync outer layers to their parents (Phase 19) // Sync outer layers to their parents
this.hat.copyFrom(this.head); this.hat.copyFrom(this.head);
this.jacket.copyFrom(this.body); this.jacket.copyFrom(this.body);
this.leftSleeve.copyFrom(this.leftArm); this.leftSleeve.copyFrom(this.leftArm);
@@ -320,10 +313,18 @@ public class DamselModel
} }
// Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder) // Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder)
boolean armsBound = V2EquipmentHelper.isRegionOccupied(entity, BodyRegionV2.ARMS); boolean armsBound = V2EquipmentHelper.isRegionOccupied(
boolean legsBound = V2EquipmentHelper.isRegionOccupied(entity, BodyRegionV2.LEGS); entity,
BodyRegionV2.ARMS
);
boolean legsBound = V2EquipmentHelper.isRegionOccupied(
entity,
BodyRegionV2.LEGS
);
if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) { if (
!armsBound && !legsBound && bind.getItem() instanceof ItemBind
) {
armsBound = ItemBind.hasArmsBound(bind); armsBound = ItemBind.hasArmsBound(bind);
legsBound = ItemBind.hasLegsBound(bind); legsBound = ItemBind.hasLegsBound(bind);
} }
@@ -374,7 +375,6 @@ public class DamselModel
this.rightPants.copyFrom(this.rightLeg); this.rightPants.copyFrom(this.rightLeg);
} }
// Phase 19: Hide wearer's outer layers based on clothes settings
// This MUST happen after super.setupAnim() which can reset visibility // This MUST happen after super.setupAnim() which can reset visibility
hideWearerLayersForClothes(entity); hideWearerLayersForClothes(entity);
} }

View File

@@ -44,7 +44,6 @@ public class DamselRenderer
/** /**
* Create renderer. * Create renderer.
* *
* Phase 19: Uses vanilla ModelLayers.PLAYER for full layer support (jacket, sleeves, pants).
*/ */
public DamselRenderer(EntityRendererProvider.Context context) { public DamselRenderer(EntityRendererProvider.Context context) {
super( super(
@@ -86,14 +85,15 @@ public class DamselRenderer
} }
// Add V2 bondage render layer (GLB-based V2 equipment rendering) // Add V2 bondage render layer (GLB-based V2 equipment rendering)
this.addLayer(new com.tiedup.remake.v2.bondage.client.V2BondageRenderLayer<>(this)); this.addLayer(
new com.tiedup.remake.v2.bondage.client.V2BondageRenderLayer<>(this)
);
} }
/** /**
* Render the entity. * Render the entity.
* Uses entity's hasSlimArms() for model selection. * Uses entity's hasSlimArms() for model selection.
* *
* Phase 19: Wearer layer hiding is now handled in DamselModel.setupAnim()
* to ensure it happens after visibility resets. * to ensure it happens after visibility resets.
* *
* DOG pose: X rotation is applied in setupRotations() AFTER Y rotation, * DOG pose: X rotation is applied in setupRotations() AFTER Y rotation,

View File

@@ -16,7 +16,6 @@ import net.minecraft.util.Mth;
/** /**
* Renderer for EntityKidnapBomb. * Renderer for EntityKidnapBomb.
* *
* Phase 16: Blocks
* *
* Renders the primed kidnap bomb using our custom block texture. * Renders the primed kidnap bomb using our custom block texture.
*/ */

View File

@@ -9,7 +9,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
/** /**
* Renderer for EntityRopeArrow. * Renderer for EntityRopeArrow.
* Phase 15: Uses vanilla arrow texture for simplicity.
*/ */
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class RopeArrowRenderer extends ArrowRenderer<EntityRopeArrow> { public class RopeArrowRenderer extends ArrowRenderer<EntityRopeArrow> {

View File

@@ -8,7 +8,6 @@ import com.tiedup.remake.items.clothes.ClothesProperties;
import com.tiedup.remake.items.clothes.GenericClothes; import com.tiedup.remake.items.clothes.GenericClothes;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.UUID; import java.util.UUID;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.model.HumanoidModel; import net.minecraft.client.model.HumanoidModel;
import net.minecraft.client.model.PlayerModel; import net.minecraft.client.model.PlayerModel;
import net.minecraft.client.player.AbstractClientPlayer; import net.minecraft.client.player.AbstractClientPlayer;
@@ -19,6 +18,7 @@ import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;
/** /**
* Helper class for rendering clothes with dynamic textures. * Helper class for rendering clothes with dynamic textures.

View File

@@ -5,9 +5,9 @@ import java.util.EnumSet;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;
/** /**
* Client-side cache for storing clothes configuration of remote players. * Client-side cache for storing clothes configuration of remote players.

View File

@@ -15,7 +15,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
* *
* Used by SlaveManagementScreen to display owned slaves without spatial queries. * Used by SlaveManagementScreen to display owned slaves without spatial queries.
* *
* Phase 17: Terminology Refactoring - Global Collar Registry
*/ */
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class CollarRegistryClient { public class CollarRegistryClient {

View File

@@ -21,7 +21,8 @@ import org.jetbrains.annotations.Nullable;
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class MovementStyleClientState { public class MovementStyleClientState {
private static final Map<UUID, MovementStyle> styles = new ConcurrentHashMap<>(); private static final Map<UUID, MovementStyle> styles =
new ConcurrentHashMap<>();
/** /**
* Set the active movement style for a player. * Set the active movement style for a player.

View File

@@ -3,10 +3,10 @@ package com.tiedup.remake.client.texture;
import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;
/** /**
* Handles a dynamically loaded online texture for clothes. * Handles a dynamically loaded online texture for clothes.

View File

@@ -16,12 +16,12 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import org.jetbrains.annotations.Nullable;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.jetbrains.annotations.Nullable;
/** /**
* Manager for dynamically loaded online textures. * Manager for dynamically loaded online textures.

View File

@@ -5,10 +5,10 @@ import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.tiedup.remake.bounty.Bounty; import com.tiedup.remake.bounty.Bounty;
import com.tiedup.remake.bounty.BountyManager; import com.tiedup.remake.bounty.BountyManager;
import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.core.SettingsAccessor;
import java.util.Optional; import java.util.Optional;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
@@ -21,7 +21,6 @@ import net.minecraft.world.item.ItemStack;
/** /**
* Command: /bounty <player> * Command: /bounty <player>
* *
* Phase 17: Bounty System
* *
* Creates a bounty on a target player using the held item as reward. * Creates a bounty on a target player using the held item as reward.
* *

View File

@@ -1,7 +1,6 @@
package com.tiedup.remake.commands; package com.tiedup.remake.commands;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.tiedup.remake.v2.BodyRegionV2;
import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
@@ -12,6 +11,7 @@ import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.util.teleport.Position; import com.tiedup.remake.util.teleport.Position;
import com.tiedup.remake.util.teleport.TeleportHelper; import com.tiedup.remake.util.teleport.TeleportHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands; import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument; import net.minecraft.commands.arguments.EntityArgument;
@@ -21,7 +21,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
/** /**
* Collar management commands for Phase 18. * Collar management commands.
* *
* Commands: * Commands:
* /collar claim <player> - Claim ownership of a player's collar * /collar claim <player> - Claim ownership of a player's collar

View File

@@ -83,7 +83,9 @@ public final class CommandHelper {
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.sync(player); com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.sync(player);
// Sync bind state // Sync bind state
PacketSyncBindState statePacket = PacketSyncBindState.fromPlayer(player); PacketSyncBindState statePacket = PacketSyncBindState.fromPlayer(
player
);
if (statePacket != null) { if (statePacket != null) {
ModNetwork.sendToPlayer(statePacket, player); ModNetwork.sendToPlayer(statePacket, player);
} }

View File

@@ -14,7 +14,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
/** /**
* Key management commands for Phase 18. * Key management commands.
* *
* Commands: * Commands:
* /key claim - Claim the key you're holding * /key claim - Claim the key you're holding

View File

@@ -17,7 +17,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
/** /**
* Utility commands for Phase 18. * Utility commands.
* *
* Commands: * Commands:
* /kidnapset - Get a starter kit of mod items * /kidnapset - Get a starter kit of mod items

View File

@@ -1,7 +1,6 @@
package com.tiedup.remake.commands; package com.tiedup.remake.commands;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.tiedup.remake.v2.BodyRegionV2;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
@@ -13,6 +12,7 @@ import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.EarplugsVariant; import com.tiedup.remake.items.base.EarplugsVariant;
import com.tiedup.remake.items.base.GagVariant; import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
@@ -27,7 +27,7 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
/** /**
* NPC management commands for Phase 18. * NPC management commands.
* *
* Commands: * Commands:
* /npc spawn kidnapper [player] - Spawn a kidnapper at location * /npc spawn kidnapper [player] - Spawn a kidnapper at location
@@ -432,8 +432,7 @@ public class NPCCommand {
var entities = level.getEntitiesOfClass( var entities = level.getEntitiesOfClass(
net.minecraft.world.entity.LivingEntity.class, net.minecraft.world.entity.LivingEntity.class,
player.getBoundingBox().inflate(radius), player.getBoundingBox().inflate(radius),
e -> e -> e instanceof com.tiedup.remake.entities.AbstractTiedUpNpc
e instanceof com.tiedup.remake.entities.AbstractTiedUpNpc
); );
for (var entity : entities) { for (var entity : entities) {
@@ -457,9 +456,7 @@ public class NPCCommand {
return String.format("(%.1f, %.1f, %.1f)", x, y, z); return String.format("(%.1f, %.1f, %.1f)", x, y, z);
} }
// ========================================
// NPC Bondage Commands (from DamselTestCommand) // NPC Bondage Commands (from DamselTestCommand)
// ========================================
/** /**
* Find the nearest mod NPC (Damsel or Kidnapper) within 10 blocks. * Find the nearest mod NPC (Damsel or Kidnapper) within 10 blocks.
@@ -508,7 +505,10 @@ public class NPCCommand {
return 0; return 0;
} }
npc.equip(BodyRegionV2.ARMS, new ItemStack(ModItems.getBind(BindVariant.ROPES))); npc.equip(
BodyRegionV2.ARMS,
new ItemStack(ModItems.getBind(BindVariant.ROPES))
);
context context
.getSource() .getSource()
.sendSuccess( .sendSuccess(
@@ -536,7 +536,10 @@ public class NPCCommand {
return 0; return 0;
} }
npc.equip(BodyRegionV2.MOUTH, new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG))); npc.equip(
BodyRegionV2.MOUTH,
new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG))
);
context context
.getSource() .getSource()
.sendSuccess( .sendSuccess(
@@ -566,7 +569,8 @@ public class NPCCommand {
return 0; return 0;
} }
npc.equip(BodyRegionV2.EYES, npc.equip(
BodyRegionV2.EYES,
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC)) new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC))
); );
context context
@@ -599,7 +603,10 @@ public class NPCCommand {
return 0; return 0;
} }
npc.equip(BodyRegionV2.NECK, new ItemStack(ModItems.CLASSIC_COLLAR.get())); npc.equip(
BodyRegionV2.NECK,
new ItemStack(ModItems.CLASSIC_COLLAR.get())
);
context context
.getSource() .getSource()
.sendSuccess( .sendSuccess(
@@ -644,7 +651,10 @@ public class NPCCommand {
} }
// Apply full bondage using AbstractTiedUpNpc method // Apply full bondage using AbstractTiedUpNpc method
if (npc instanceof com.tiedup.remake.entities.AbstractTiedUpNpc npcEntity) { if (
npc instanceof
com.tiedup.remake.entities.AbstractTiedUpNpc npcEntity
) {
npcEntity.applyBondage( npcEntity.applyBondage(
new ItemStack(ModItems.getBind(BindVariant.ROPES)), new ItemStack(ModItems.getBind(BindVariant.ROPES)),
new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG)), new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG)),

View File

@@ -20,7 +20,7 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
/** /**
* Social and RP commands for Phase 18. * Social and RP commands.
* *
* Commands: * Commands:
* /blockplayer <player> - Block a player from interacting with you * /blockplayer <player> - Block a player from interacting with you
@@ -147,9 +147,7 @@ public class SocialCommand {
return Commands.literal("talkinfo").executes(SocialCommand::talkInfo); return Commands.literal("talkinfo").executes(SocialCommand::talkInfo);
} }
// ========================================
// Block System // Block System
// ========================================
private static int blockPlayer(CommandContext<CommandSourceStack> context) private static int blockPlayer(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException { throws CommandSyntaxException {
@@ -284,9 +282,7 @@ public class SocialCommand {
return SocialData.get(level).isBlocked(blocker, blocked); return SocialData.get(level).isBlocked(blocker, blocked);
} }
// ========================================
// RP Commands // RP Commands
// ========================================
private static int noRP(CommandContext<CommandSourceStack> context) private static int noRP(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException { throws CommandSyntaxException {
@@ -443,9 +439,7 @@ public class SocialCommand {
return 1; return 1;
} }
// ========================================
// Talk Area // Talk Area
// ========================================
private static int setTalkArea( private static int setTalkArea(
CommandContext<CommandSourceStack> context, CommandContext<CommandSourceStack> context,

View File

@@ -9,8 +9,8 @@ import com.tiedup.remake.commands.CommandHelper;
import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.items.ModItems; import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.AdjustmentHelper; import com.tiedup.remake.items.base.AdjustmentHelper;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.BindVariant; import com.tiedup.remake.items.base.BindVariant;
import com.tiedup.remake.items.base.BlindfoldVariant;
import com.tiedup.remake.items.base.EarplugsVariant; import com.tiedup.remake.items.base.EarplugsVariant;
import com.tiedup.remake.items.base.GagVariant; import com.tiedup.remake.items.base.GagVariant;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.items.base.ItemCollar;
@@ -32,14 +32,18 @@ import net.minecraft.world.item.ItemStack;
@SuppressWarnings("null") @SuppressWarnings("null")
public class BondageSubCommand { public class BondageSubCommand {
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) { public static void register(
LiteralArgumentBuilder<CommandSourceStack> root
) {
// /tiedup tie <player> // /tiedup tie <player>
root.then( root.then(
Commands.literal("tie") Commands.literal("tie")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::tie) "player",
EntityArgument.player()
).executes(BondageSubCommand::tie)
) )
); );
// /tiedup untie <player> // /tiedup untie <player>
@@ -47,8 +51,10 @@ public class BondageSubCommand {
Commands.literal("untie") Commands.literal("untie")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::untie) "player",
EntityArgument.player()
).executes(BondageSubCommand::untie)
) )
); );
// /tiedup gag <player> // /tiedup gag <player>
@@ -56,8 +62,10 @@ public class BondageSubCommand {
Commands.literal("gag") Commands.literal("gag")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::gag) "player",
EntityArgument.player()
).executes(BondageSubCommand::gag)
) )
); );
// /tiedup ungag <player> // /tiedup ungag <player>
@@ -65,8 +73,10 @@ public class BondageSubCommand {
Commands.literal("ungag") Commands.literal("ungag")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::ungag) "player",
EntityArgument.player()
).executes(BondageSubCommand::ungag)
) )
); );
// /tiedup blindfold <player> // /tiedup blindfold <player>
@@ -74,8 +84,10 @@ public class BondageSubCommand {
Commands.literal("blindfold") Commands.literal("blindfold")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::blindfold) "player",
EntityArgument.player()
).executes(BondageSubCommand::blindfold)
) )
); );
// /tiedup unblind <player> // /tiedup unblind <player>
@@ -83,8 +95,10 @@ public class BondageSubCommand {
Commands.literal("unblind") Commands.literal("unblind")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::unblind) "player",
EntityArgument.player()
).executes(BondageSubCommand::unblind)
) )
); );
// /tiedup collar <player> // /tiedup collar <player>
@@ -92,8 +106,10 @@ public class BondageSubCommand {
Commands.literal("collar") Commands.literal("collar")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::collar) "player",
EntityArgument.player()
).executes(BondageSubCommand::collar)
) )
); );
// /tiedup takecollar <player> // /tiedup takecollar <player>
@@ -101,8 +117,10 @@ public class BondageSubCommand {
Commands.literal("takecollar") Commands.literal("takecollar")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::takecollar) "player",
EntityArgument.player()
).executes(BondageSubCommand::takecollar)
) )
); );
// /tiedup takeearplugs <player> // /tiedup takeearplugs <player>
@@ -110,8 +128,10 @@ public class BondageSubCommand {
Commands.literal("takeearplugs") Commands.literal("takeearplugs")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::takeearplugs) "player",
EntityArgument.player()
).executes(BondageSubCommand::takeearplugs)
) )
); );
// /tiedup putearplugs <player> // /tiedup putearplugs <player>
@@ -119,8 +139,10 @@ public class BondageSubCommand {
Commands.literal("putearplugs") Commands.literal("putearplugs")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::putearplugs) "player",
EntityArgument.player()
).executes(BondageSubCommand::putearplugs)
) )
); );
// /tiedup takeclothes <player> // /tiedup takeclothes <player>
@@ -128,8 +150,10 @@ public class BondageSubCommand {
Commands.literal("takeclothes") Commands.literal("takeclothes")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::takeclothes) "player",
EntityArgument.player()
).executes(BondageSubCommand::takeclothes)
) )
); );
// /tiedup putclothes <player> // /tiedup putclothes <player>
@@ -137,8 +161,10 @@ public class BondageSubCommand {
Commands.literal("putclothes") Commands.literal("putclothes")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::putclothes) "player",
EntityArgument.player()
).executes(BondageSubCommand::putclothes)
) )
); );
// /tiedup fullyrestrain <player> // /tiedup fullyrestrain <player>
@@ -146,8 +172,10 @@ public class BondageSubCommand {
Commands.literal("fullyrestrain") Commands.literal("fullyrestrain")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::fullyrestrain) "player",
EntityArgument.player()
).executes(BondageSubCommand::fullyrestrain)
) )
); );
// /tiedup enslave <player> // /tiedup enslave <player>
@@ -155,8 +183,10 @@ public class BondageSubCommand {
Commands.literal("enslave") Commands.literal("enslave")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::enslave) "player",
EntityArgument.player()
).executes(BondageSubCommand::enslave)
) )
); );
// /tiedup free <player> // /tiedup free <player>
@@ -164,8 +194,10 @@ public class BondageSubCommand {
Commands.literal("free") Commands.literal("free")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(BondageSubCommand::free) "player",
EntityArgument.player()
).executes(BondageSubCommand::free)
) )
); );
// /tiedup adjust <player> <type:gag|blindfold|all> <value> // /tiedup adjust <player> <type:gag|blindfold|all> <value>
@@ -173,29 +205,26 @@ public class BondageSubCommand {
Commands.literal("adjust") Commands.literal("adjust")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument("player", EntityArgument.player()).then(
.then( Commands.argument("type", StringArgumentType.word())
Commands.argument("type", StringArgumentType.word()) .suggests((ctx, builder) -> {
.suggests((ctx, builder) -> { builder.suggest("gag");
builder.suggest("gag"); builder.suggest("blindfold");
builder.suggest("blindfold"); builder.suggest("all");
builder.suggest("all"); return builder.buildFuture();
return builder.buildFuture(); })
}) .then(
.then( Commands.argument(
Commands.argument( "value",
"value", FloatArgumentType.floatArg(-4.0f, 4.0f)
FloatArgumentType.floatArg(-4.0f, 4.0f) ).executes(BondageSubCommand::adjust)
).executes(BondageSubCommand::adjust) )
) )
)
) )
); );
} }
// ========================================
// Command Implementations // Command Implementations
// ========================================
/** /**
* /tiedup tie <player> * /tiedup tie <player>
@@ -582,7 +611,9 @@ public class BondageSubCommand {
boolean adjustedBlindfold = false; boolean adjustedBlindfold = false;
if (type.equals("gag") || type.equals("all")) { if (type.equals("gag") || type.equals("all")) {
ItemStack gag = state.getEquipment(com.tiedup.remake.v2.BodyRegionV2.MOUTH); ItemStack gag = state.getEquipment(
com.tiedup.remake.v2.BodyRegionV2.MOUTH
);
if (!gag.isEmpty()) { if (!gag.isEmpty()) {
AdjustmentHelper.setAdjustment(gag, value); AdjustmentHelper.setAdjustment(gag, value);
adjustedGag = true; adjustedGag = true;
@@ -590,7 +621,9 @@ public class BondageSubCommand {
} }
if (type.equals("blindfold") || type.equals("all")) { if (type.equals("blindfold") || type.equals("all")) {
ItemStack blindfold = state.getEquipment(com.tiedup.remake.v2.BodyRegionV2.EYES); ItemStack blindfold = state.getEquipment(
com.tiedup.remake.v2.BodyRegionV2.EYES
);
if (!blindfold.isEmpty()) { if (!blindfold.isEmpty()) {
AdjustmentHelper.setAdjustment(blindfold, value); AdjustmentHelper.setAdjustment(blindfold, value);
adjustedBlindfold = true; adjustedBlindfold = true;

View File

@@ -21,7 +21,9 @@ import net.minecraft.server.level.ServerPlayer;
@SuppressWarnings("null") @SuppressWarnings("null")
public class DebtSubCommand { public class DebtSubCommand {
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) { public static void register(
LiteralArgumentBuilder<CommandSourceStack> root
) {
// /tiedup debt <player> [set|add|remove <amount>] // /tiedup debt <player> [set|add|remove <amount>]
root.then( root.then(
Commands.literal("debt") Commands.literal("debt")
@@ -61,9 +63,7 @@ public class DebtSubCommand {
); );
} }
// ========================================
// Command Implementations // Command Implementations
// ========================================
private static int debtShow(CommandContext<CommandSourceStack> context) private static int debtShow(CommandContext<CommandSourceStack> context)
throws CommandSyntaxException { throws CommandSyntaxException {

View File

@@ -20,21 +20,23 @@ import net.minecraft.server.level.ServerPlayer;
@SuppressWarnings("null") @SuppressWarnings("null")
public class InventorySubCommand { public class InventorySubCommand {
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) { public static void register(
LiteralArgumentBuilder<CommandSourceStack> root
) {
// /tiedup returnstuff <player> // /tiedup returnstuff <player>
root.then( root.then(
Commands.literal("returnstuff") Commands.literal("returnstuff")
.requires(CommandHelper.REQUIRES_OP) .requires(CommandHelper.REQUIRES_OP)
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(InventorySubCommand::returnstuff) "player",
EntityArgument.player()
).executes(InventorySubCommand::returnstuff)
) )
); );
} }
// ========================================
// Command Implementations // Command Implementations
// ========================================
/** /**
* /tiedup returnstuff <player> * /tiedup returnstuff <player>

View File

@@ -23,7 +23,9 @@ import net.minecraft.world.entity.MobSpawnType;
@SuppressWarnings("null") @SuppressWarnings("null")
public class MasterTestSubCommand { public class MasterTestSubCommand {
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) { public static void register(
LiteralArgumentBuilder<CommandSourceStack> root
) {
// /tiedup mastertest // /tiedup mastertest
root.then( root.then(
Commands.literal("mastertest") Commands.literal("mastertest")
@@ -53,9 +55,7 @@ public class MasterTestSubCommand {
); );
} }
// ========================================
// Command Implementations // Command Implementations
// ========================================
/** /**
* /tiedup mastertest * /tiedup mastertest
@@ -95,7 +95,10 @@ public class MasterTestSubCommand {
master.setPetPlayer(player); master.setPetPlayer(player);
master.putPetCollar(player); master.putPetCollar(player);
CommandHelper.syncPlayerState(player, PlayerBindState.getInstance(player)); CommandHelper.syncPlayerState(
player,
PlayerBindState.getInstance(player)
);
String masterName = master.getNpcName(); String masterName = master.getNpcName();
if (masterName == null || masterName.isEmpty()) masterName = "Master"; if (masterName == null || masterName.isEmpty()) masterName = "Master";
@@ -138,7 +141,10 @@ public class MasterTestSubCommand {
if (!master.hasPet()) { if (!master.hasPet()) {
master.setPetPlayer(player); master.setPetPlayer(player);
master.putPetCollar(player); master.putPetCollar(player);
CommandHelper.syncPlayerState(player, PlayerBindState.getInstance(player)); CommandHelper.syncPlayerState(
player,
PlayerBindState.getInstance(player)
);
} }
master.setMasterState(MasterState.HUMAN_CHAIR); master.setMasterState(MasterState.HUMAN_CHAIR);
@@ -195,7 +201,10 @@ public class MasterTestSubCommand {
if (!master.hasPet()) { if (!master.hasPet()) {
master.setPetPlayer(player); master.setPetPlayer(player);
master.putPetCollar(player); master.putPetCollar(player);
CommandHelper.syncPlayerState(player, PlayerBindState.getInstance(player)); CommandHelper.syncPlayerState(
player,
PlayerBindState.getInstance(player)
);
} }
master.setMasterState(targetState); master.setMasterState(targetState);

View File

@@ -20,7 +20,9 @@ import net.minecraft.server.level.ServerPlayer;
@SuppressWarnings("null") @SuppressWarnings("null")
public class TestAnimSubCommand { public class TestAnimSubCommand {
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) { public static void register(
LiteralArgumentBuilder<CommandSourceStack> root
) {
// /tiedup testanim <animId> [player] // /tiedup testanim <animId> [player]
// /tiedup testanim stop [player] // /tiedup testanim stop [player]
root.then( root.then(
@@ -30,34 +32,36 @@ public class TestAnimSubCommand {
Commands.literal("stop") Commands.literal("stop")
.executes(ctx -> testAnimStop(ctx, null)) .executes(ctx -> testAnimStop(ctx, null))
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(ctx -> "player",
testAnimStop( EntityArgument.player()
ctx, ).executes(ctx ->
EntityArgument.getPlayer(ctx, "player") testAnimStop(
) ctx,
EntityArgument.getPlayer(ctx, "player")
) )
)
) )
) )
.then( .then(
Commands.argument("animId", StringArgumentType.string()) Commands.argument("animId", StringArgumentType.string())
.executes(ctx -> testAnim(ctx, null)) .executes(ctx -> testAnim(ctx, null))
.then( .then(
Commands.argument("player", EntityArgument.player()) Commands.argument(
.executes(ctx -> "player",
testAnim( EntityArgument.player()
ctx, ).executes(ctx ->
EntityArgument.getPlayer(ctx, "player") testAnim(
) ctx,
EntityArgument.getPlayer(ctx, "player")
) )
)
) )
) )
); );
} }
// ========================================
// Command Implementations // Command Implementations
// ========================================
/** /**
* /tiedup testanim <animId> [player] * /tiedup testanim <animId> [player]

View File

@@ -10,10 +10,10 @@ import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.state.IRestrainable; import com.tiedup.remake.state.IRestrainable;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import org.jetbrains.annotations.Nullable;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
/** /**
* Central coordinator for all MCA-TiedUp integration. * Central coordinator for all MCA-TiedUp integration.
@@ -48,9 +48,7 @@ public class MCABondageManager {
return INSTANCE; return INSTANCE;
} }
// ========================================
// LIFECYCLE EVENTS // LIFECYCLE EVENTS
// ========================================
/** Dialogue broadcast radius in blocks */ /** Dialogue broadcast radius in blocks */
private static final double DIALOGUE_RADIUS = 16.0; private static final double DIALOGUE_RADIUS = 16.0;
@@ -290,9 +288,7 @@ public class MCABondageManager {
syncBondageState(villager); syncBondageState(villager);
} }
// ========================================
// AI CONTROL // AI CONTROL
// ========================================
/** /**
* Get or create AI controller for villager. * Get or create AI controller for villager.
@@ -354,9 +350,7 @@ public class MCABondageManager {
return getAILevel(villager) != MCABondageAILevel.NONE; return getAILevel(villager) != MCABondageAILevel.NONE;
} }
// ========================================
// SYNC // SYNC
// ========================================
/** /**
* Sync all bondage state to tracking clients. * Sync all bondage state to tracking clients.
@@ -403,9 +397,7 @@ public class MCABondageManager {
}); });
} }
// ========================================
// QUERIES // QUERIES
// ========================================
/** /**
* Get IRestrainable adapter for MCA villager. * Get IRestrainable adapter for MCA villager.
@@ -430,9 +422,7 @@ public class MCABondageManager {
return aiControllers.containsKey(living); return aiControllers.containsKey(living);
} }
// ========================================
// CLEANUP // CLEANUP
// ========================================
/** /**
* Remove all tracking for a villager. * Remove all tracking for a villager.

View File

@@ -90,9 +90,7 @@ public class MCABondageAIController {
personality != null ? personality : MCAPersonality.UNKNOWN; personality != null ? personality : MCAPersonality.UNKNOWN;
} }
// ========================================
// LEVEL MANAGEMENT // LEVEL MANAGEMENT
// ========================================
/** /**
* Recalculate and apply appropriate AI level based on current state. * Recalculate and apply appropriate AI level based on current state.
@@ -215,9 +213,7 @@ public class MCABondageAIController {
} }
} }
// ========================================
// SPEED REDUCTION // SPEED REDUCTION
// ========================================
private void applySpeedReduction() { private void applySpeedReduction() {
LivingEntity villager = villagerRef.get(); LivingEntity villager = villagerRef.get();
@@ -248,9 +244,7 @@ public class MCABondageAIController {
); );
} }
// ========================================
// GOAL INJECTION (MODIFIED LEVEL) // GOAL INJECTION (MODIFIED LEVEL)
// ========================================
private void injectBondageGoals() { private void injectBondageGoals() {
LivingEntity villager = villagerRef.get(); LivingEntity villager = villagerRef.get();
@@ -326,9 +320,7 @@ public class MCABondageAIController {
); );
} }
// ========================================
// FULL AI OVERRIDE // FULL AI OVERRIDE
// ========================================
private void backupAndClearGoals() { private void backupAndClearGoals() {
LivingEntity villager = villagerRef.get(); LivingEntity villager = villagerRef.get();
@@ -399,9 +391,7 @@ public class MCABondageAIController {
); );
} }
// ========================================
// MCA BRAIN CONTROL // MCA BRAIN CONTROL
// ========================================
/** /**
* Suspend MCA's brain activities (for OVERRIDE level). * Suspend MCA's brain activities (for OVERRIDE level).
@@ -503,9 +493,7 @@ public class MCABondageAIController {
mcaBrainSuspended = false; mcaBrainSuspended = false;
} }
// ========================================
// CLEANUP // CLEANUP
// ========================================
/** /**
* Clean up all AI modifications. * Clean up all AI modifications.

View File

@@ -7,14 +7,14 @@ import com.tiedup.remake.items.base.IHasGaggingEffect;
import com.tiedup.remake.items.base.IHasResistance; import com.tiedup.remake.items.base.IHasResistance;
import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.state.IRestrainable; import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.state.IRestrainableEntity; import com.tiedup.remake.state.IRestrainableEntity;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.util.tasks.ItemTask; import com.tiedup.remake.util.tasks.ItemTask;
import com.tiedup.remake.util.teleport.Position; import com.tiedup.remake.util.teleport.Position;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import java.util.UUID; import java.util.UUID;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
@@ -46,9 +46,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
this.cap = cap; this.cap = cap;
} }
// ========================================
// 1. CAPTURE LIFECYCLE // 1. CAPTURE LIFECYCLE
// ========================================
@Override @Override
public boolean getCapturedBy(ICaptor captor) { public boolean getCapturedBy(ICaptor captor) {
@@ -114,9 +112,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
getCapturedBy(newCaptor); getCapturedBy(newCaptor);
} }
// ========================================
// 2. STATE QUERIES - CAPTURE // 2. STATE QUERIES - CAPTURE
// ========================================
@Override @Override
public boolean isEnslavable() { public boolean isEnslavable() {
@@ -214,9 +210,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
return null; return null;
} }
// ========================================
// 3. STATE QUERIES - BONDAGE EQUIPMENT // 3. STATE QUERIES - BONDAGE EQUIPMENT
// ========================================
@Override @Override
public boolean isTiedUp() { public boolean isTiedUp() {
@@ -301,9 +295,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
return false; return false;
} }
// ========================================
// 4. EQUIPMENT MANAGEMENT - PUT ON // 4. EQUIPMENT MANAGEMENT - PUT ON
// ========================================
public void putBindOn(ItemStack bind) { public void putBindOn(ItemStack bind) {
cap.setBind(bind); cap.setBind(bind);
@@ -357,9 +349,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
checkMittensAfterApply(); checkMittensAfterApply();
} }
// ========================================
// 5. EQUIPMENT MANAGEMENT - UNEQUIP (V2) // 5. EQUIPMENT MANAGEMENT - UNEQUIP (V2)
// ========================================
@Override @Override
public ItemStack unequip(BodyRegionV2 region) { public ItemStack unequip(BodyRegionV2 region) {
@@ -395,7 +385,9 @@ public class MCAKidnappedAdapter implements IRestrainable {
ItemStack current = cap.getGag(); ItemStack current = cap.getGag();
if (current.isEmpty()) yield ItemStack.EMPTY; if (current.isEmpty()) yield ItemStack.EMPTY;
cap.setGag(ItemStack.EMPTY); cap.setGag(ItemStack.EMPTY);
MCABondageManager.getInstance().onSensoryRestrictionChanged(entity); MCABondageManager.getInstance().onSensoryRestrictionChanged(
entity
);
syncToClients(); syncToClients();
yield current; yield current;
} }
@@ -403,7 +395,9 @@ public class MCAKidnappedAdapter implements IRestrainable {
ItemStack current = cap.getBlindfold(); ItemStack current = cap.getBlindfold();
if (current.isEmpty()) yield ItemStack.EMPTY; if (current.isEmpty()) yield ItemStack.EMPTY;
cap.setBlindfold(ItemStack.EMPTY); cap.setBlindfold(ItemStack.EMPTY);
MCABondageManager.getInstance().onSensoryRestrictionChanged(entity); MCABondageManager.getInstance().onSensoryRestrictionChanged(
entity
);
syncToClients(); syncToClients();
yield current; yield current;
} }
@@ -425,9 +419,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
}; };
} }
// ========================================
// 5b. EQUIPMENT MANAGEMENT - TAKE OFF (local helpers) // 5b. EQUIPMENT MANAGEMENT - TAKE OFF (local helpers)
// ========================================
public ItemStack takeBindOff() { public ItemStack takeBindOff() {
ItemStack current = cap.getBind(); ItemStack current = cap.getBind();
@@ -509,9 +501,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
return current; return current;
} }
// ========================================
// V2 Region-Based Equipment Access // V2 Region-Based Equipment Access
// ========================================
@Override @Override
public ItemStack getEquipment(BodyRegionV2 region) { public ItemStack getEquipment(BodyRegionV2 region) {
@@ -537,16 +527,19 @@ public class MCAKidnappedAdapter implements IRestrainable {
case NECK -> putCollarOn(stack); case NECK -> putCollarOn(stack);
case TORSO -> putClothesOn(stack); case TORSO -> putClothesOn(stack);
case HANDS -> putMittensOn(stack); case HANDS -> putMittensOn(stack);
default -> {} default -> {
}
} }
} }
// ========================================
// 7. EQUIPMENT MANAGEMENT - REPLACE (V2 region-based) // 7. EQUIPMENT MANAGEMENT - REPLACE (V2 region-based)
// ========================================
@Override @Override
public ItemStack replaceEquipment(BodyRegionV2 region, ItemStack newStack, boolean force) { public ItemStack replaceEquipment(
BodyRegionV2 region,
ItemStack newStack,
boolean force
) {
return switch (region) { return switch (region) {
case ARMS -> { case ARMS -> {
ItemStack old = cap.getBind(); ItemStack old = cap.getBind();
@@ -580,7 +573,9 @@ public class MCAKidnappedAdapter implements IRestrainable {
if (!force && isLocked(old, false)) yield ItemStack.EMPTY; if (!force && isLocked(old, false)) yield ItemStack.EMPTY;
cap.setCollar(newStack); cap.setCollar(newStack);
if (newStack.getItem() instanceof IHasResistance resistance) { if (newStack.getItem() instanceof IHasResistance resistance) {
cap.setCollarResistance(resistance.getBaseResistance(entity)); cap.setCollarResistance(
resistance.getBaseResistance(entity)
);
} }
yield old; yield old;
} }
@@ -599,9 +594,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
}; };
} }
// ========================================
// 8. BULK OPERATIONS // 8. BULK OPERATIONS
// ========================================
@Override @Override
public void applyBondage( public void applyBondage(
@@ -694,9 +687,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
return count; return count;
} }
// ========================================
// 9. CLOTHES PERMISSION SYSTEM // 9. CLOTHES PERMISSION SYSTEM
// ========================================
@Override @Override
public boolean canTakeOffClothes(Player player) { public boolean canTakeOffClothes(Player player) {
@@ -714,9 +705,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
return true; return true;
} }
// ========================================
// 10. SPECIAL INTERACTIONS // 10. SPECIAL INTERACTIONS
// ========================================
@Override @Override
public void tighten(Player tightener) { public void tighten(Player tightener) {
@@ -777,9 +766,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
} }
} }
// ========================================
// 11. POST-APPLY CALLBACKS // 11. POST-APPLY CALLBACKS
// ========================================
/** /**
* Sync bondage state to tracking clients. * Sync bondage state to tracking clients.
@@ -819,9 +806,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
syncToClients(); syncToClients();
} }
// ========================================
// 12. DEATH & LIFECYCLE // 12. DEATH & LIFECYCLE
// ========================================
@Override @Override
public boolean onDeathKidnapped(Level world) { public boolean onDeathKidnapped(Level world) {
@@ -835,9 +820,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
return true; return true;
} }
// ========================================
// 13. UTILITY & METADATA // 13. UTILITY & METADATA
// ========================================
@Override @Override
public UUID getKidnappedUniqueId() { public UUID getKidnappedUniqueId() {
@@ -893,9 +876,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
} }
} }
// ========================================
// 14.5. RESISTANCE SYSTEM // 14.5. RESISTANCE SYSTEM
// ========================================
@Override @Override
public int getCurrentBindResistance() { public int getCurrentBindResistance() {
@@ -917,9 +898,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
cap.setCollarResistance(resistance); cap.setCollarResistance(resistance);
} }
// ========================================
// 15. ENTITY REFERENCE // 15. ENTITY REFERENCE
// ========================================
@Override @Override
public LivingEntity asLivingEntity() { public LivingEntity asLivingEntity() {

View File

@@ -1,8 +1,8 @@
package com.tiedup.remake.compat.mca.capability; package com.tiedup.remake.compat.mca.capability;
import com.tiedup.remake.compat.mca.personality.TiedUpTrait; import com.tiedup.remake.compat.mca.personality.TiedUpTrait;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.util.tasks.ItemTask; import com.tiedup.remake.util.tasks.ItemTask;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.UUID; import java.util.UUID;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@@ -20,9 +20,7 @@ import net.minecraftforge.common.util.INBTSerializable;
*/ */
public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> { public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
// ========================================
// BONDAGE EQUIPMENT SLOTS // BONDAGE EQUIPMENT SLOTS
// ========================================
private ItemStack bind = ItemStack.EMPTY; private ItemStack bind = ItemStack.EMPTY;
private ItemStack gag = ItemStack.EMPTY; private ItemStack gag = ItemStack.EMPTY;
@@ -32,9 +30,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
private ItemStack clothes = ItemStack.EMPTY; private ItemStack clothes = ItemStack.EMPTY;
private ItemStack mittens = ItemStack.EMPTY; private ItemStack mittens = ItemStack.EMPTY;
// ========================================
// CAPTURE STATE // CAPTURE STATE
// ========================================
/** UUID of the captor entity (null if not captured) */ /** UUID of the captor entity (null if not captured) */
@Nullable @Nullable
@@ -47,23 +43,17 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
@Nullable @Nullable
private ItemTask salePrice = null; private ItemTask salePrice = null;
// ========================================
// RESISTANCE VALUES // RESISTANCE VALUES
// ========================================
private int bindResistance = 0; private int bindResistance = 0;
private int collarResistance = 0; private int collarResistance = 0;
// ========================================
// TIEDUP TRAIT // TIEDUP TRAIT
// ========================================
/** TiedUp-specific trait (MASO, REBELLIOUS, etc.) */ /** TiedUp-specific trait (MASO, REBELLIOUS, etc.) */
private TiedUpTrait trait = TiedUpTrait.NONE; private TiedUpTrait trait = TiedUpTrait.NONE;
// ========================================
// EQUIPMENT GETTERS // EQUIPMENT GETTERS
// ========================================
public ItemStack getBind() { public ItemStack getBind() {
return bind; return bind;
@@ -109,9 +99,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
}; };
} }
// ========================================
// EQUIPMENT SETTERS // EQUIPMENT SETTERS
// ========================================
public void setBind(ItemStack stack) { public void setBind(ItemStack stack) {
this.bind = stack.copy(); this.bind = stack.copy();
@@ -153,7 +141,8 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
case NECK -> setCollar(stack); case NECK -> setCollar(stack);
case TORSO -> setClothes(stack); case TORSO -> setClothes(stack);
case HANDS -> setMittens(stack); case HANDS -> setMittens(stack);
default -> {} // Unsupported region — no-op default -> {
} // Unsupported region — no-op
} }
} }
@@ -166,9 +155,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
return old; return old;
} }
// ========================================
// STATE QUERIES // STATE QUERIES
// ========================================
public boolean hasBind() { public boolean hasBind() {
return !bind.isEmpty(); return !bind.isEmpty();
@@ -198,9 +185,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
return !mittens.isEmpty(); return !mittens.isEmpty();
} }
// ========================================
// CAPTURE STATE // CAPTURE STATE
// ========================================
@Nullable @Nullable
public UUID getCaptorUUID() { public UUID getCaptorUUID() {
@@ -232,9 +217,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
this.salePrice = price; this.salePrice = price;
} }
// ========================================
// RESISTANCE // RESISTANCE
// ========================================
public int getBindResistance() { public int getBindResistance() {
return bindResistance; return bindResistance;
@@ -252,9 +235,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
this.collarResistance = Math.max(0, resistance); this.collarResistance = Math.max(0, resistance);
} }
// ========================================
// TIEDUP TRAIT // TIEDUP TRAIT
// ========================================
public TiedUpTrait getTrait() { public TiedUpTrait getTrait() {
return trait; return trait;
@@ -264,9 +245,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
this.trait = trait != null ? trait : TiedUpTrait.NONE; this.trait = trait != null ? trait : TiedUpTrait.NONE;
} }
// ========================================
// CLEAR ALL // CLEAR ALL
// ========================================
/** /**
* Clear all bondage equipment. * Clear all bondage equipment.
@@ -295,9 +274,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
// trait is NOT cleared - it persists // trait is NOT cleared - it persists
} }
// ========================================
// NBT SERIALIZATION // NBT SERIALIZATION
// ========================================
private static final String TAG_BIND = "Bind"; private static final String TAG_BIND = "Bind";
private static final String TAG_GAG = "Gag"; private static final String TAG_GAG = "Gag";

View File

@@ -24,9 +24,7 @@ public class MCADialogueManager {
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
// ========================================
// BEING TIED DIALOGUES // BEING TIED DIALOGUES
// ========================================
/** /**
* Get dialogue when being tied up. * Get dialogue when being tied up.
@@ -151,9 +149,7 @@ public class MCADialogueManager {
}; };
} }
// ========================================
// TIED IDLE DIALOGUES // TIED IDLE DIALOGUES
// ========================================
/** /**
* Get dialogue for idle state while tied. * Get dialogue for idle state while tied.
@@ -232,9 +228,7 @@ public class MCADialogueManager {
}; };
} }
// ========================================
// STRUGGLE DIALOGUES // STRUGGLE DIALOGUES
// ========================================
/** /**
* Get dialogue for struggling. * Get dialogue for struggling.
@@ -294,9 +288,7 @@ public class MCADialogueManager {
} }
} }
// ========================================
// FREED DIALOGUES // FREED DIALOGUES
// ========================================
/** /**
* Get dialogue for being freed. * Get dialogue for being freed.
@@ -336,9 +328,7 @@ public class MCADialogueManager {
}; };
} }
// ========================================
// COLLAR DIALOGUES // COLLAR DIALOGUES
// ========================================
/** /**
* Get dialogue for collar being put on. * Get dialogue for collar being put on.
@@ -376,9 +366,7 @@ public class MCADialogueManager {
}; };
} }
// ========================================
// BROADCAST HELPER // BROADCAST HELPER
// ========================================
/** /**
* Broadcast a dialogue message from a villager to nearby players. * Broadcast a dialogue message from a villager to nearby players.
@@ -412,9 +400,7 @@ public class MCADialogueManager {
}); });
} }
// ========================================
// UTILITY // UTILITY
// ========================================
private static String pickRandom(String... options) { private static String pickRandom(String... options) {
return options[RANDOM.nextInt(options.length)]; return options[RANDOM.nextInt(options.length)];

Some files were not shown because too many files have changed in this diff Show More