Merge pull request 'init / code cleanup' (#2) from init/code-cleanup into develop
Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
10
Makefile
10
Makefile
@@ -138,6 +138,16 @@ idea: ## Generate IntelliJ IDEA run configurations
|
||||
eclipse: ## Generate Eclipse project files
|
||||
@$(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
|
||||
|
||||
.PHONY: info
|
||||
|
||||
@@ -9,7 +9,6 @@ import net.minecraft.world.level.material.MapColor;
|
||||
/**
|
||||
* Cell Door Block - Iron-like door that cannot be opened by hand.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Features:
|
||||
* - Cannot be opened by clicking (requires redstone)
|
||||
|
||||
@@ -37,7 +37,6 @@ import net.minecraft.world.phys.BlockHitResult;
|
||||
/**
|
||||
* Kidnap Bomb Block - TNT that applies bondage on explosion.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Features:
|
||||
* - TNT-like block that can be ignited
|
||||
@@ -61,9 +60,7 @@ public class BlockKidnapBomb
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BLOCK ENTITY
|
||||
// ========================================
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -82,9 +79,7 @@ public class BlockKidnapBomb
|
||||
: null;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// EXPLOSION HANDLING
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void onCaughtFire(
|
||||
@@ -139,9 +134,7 @@ public class BlockKidnapBomb
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// LOADING ITEMS
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public InteractionResult use(
|
||||
@@ -199,9 +192,7 @@ public class BlockKidnapBomb
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// DROPS WITH NBT
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getDrops(
|
||||
@@ -225,9 +216,7 @@ public class BlockKidnapBomb
|
||||
return List.of(stack);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TOOLTIP
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void appendHoverText(
|
||||
|
||||
@@ -43,7 +43,6 @@ import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
/**
|
||||
* Rope Trap Block - Trap that ties up entities when they walk on it.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Features:
|
||||
* - Flat block (1 pixel tall) placed on solid surfaces
|
||||
@@ -76,9 +75,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// SHAPE AND RENDERING
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public VoxelShape getShape(
|
||||
@@ -105,9 +102,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
|
||||
return RenderShape.MODEL;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// PLACEMENT RULES
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public boolean canSurvive(
|
||||
@@ -159,9 +154,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BLOCK ENTITY
|
||||
// ========================================
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -175,9 +168,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
|
||||
return be instanceof TrapBlockEntity ? (TrapBlockEntity) be : null;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TRAP TRIGGER
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void entityInside(
|
||||
@@ -239,9 +230,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// LOADING ITEMS
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public InteractionResult use(
|
||||
@@ -293,9 +282,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// DROPS WITH NBT
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getDrops(
|
||||
@@ -319,9 +306,7 @@ public class BlockRopeTrap extends Block implements EntityBlock, ICanBeLoaded {
|
||||
return List.of(stack);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TOOLTIP
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void appendHoverText(
|
||||
|
||||
@@ -33,7 +33,6 @@ import net.minecraft.world.phys.BlockHitResult;
|
||||
/**
|
||||
* Trapped Chest Block - Chest that traps players when opened.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Extends vanilla ChestBlock for proper chest behavior.
|
||||
* Sneak + right-click to load bondage items.
|
||||
@@ -47,9 +46,7 @@ public class BlockTrappedChest extends ChestBlock implements ICanBeLoaded {
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BLOCK ENTITY
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||
@@ -67,9 +64,7 @@ public class BlockTrappedChest extends ChestBlock implements ICanBeLoaded {
|
||||
: null;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// INTERACTION - TRAP TRIGGER
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public InteractionResult use(
|
||||
@@ -151,9 +146,7 @@ public class BlockTrappedChest extends ChestBlock implements ICanBeLoaded {
|
||||
return super.use(state, level, pos, player, hand, hit);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// DROPS WITH NBT
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public List<ItemStack> getDrops(
|
||||
@@ -180,9 +173,7 @@ public class BlockTrappedChest extends ChestBlock implements ICanBeLoaded {
|
||||
return drops;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TOOLTIP
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void appendHoverText(
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.tiedup.remake.blocks;
|
||||
/**
|
||||
* Marker interface for blocks that can have bondage items loaded into them.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Implemented by:
|
||||
* - BlockRopesTrap - applies items when entity walks on it
|
||||
|
||||
@@ -19,7 +19,6 @@ import net.minecraftforge.registries.RegistryObject;
|
||||
/**
|
||||
* Mod Blocks Registration
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Handles registration of all TiedUp blocks using DeferredRegister.
|
||||
*
|
||||
@@ -40,9 +39,7 @@ public class ModBlocks {
|
||||
public static final DeferredRegister<Item> BLOCK_ITEMS =
|
||||
DeferredRegister.create(ForgeRegistries.ITEMS, TiedUpMod.MOD_ID);
|
||||
|
||||
// ========================================
|
||||
// PADDED BLOCKS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Base padded block properties.
|
||||
@@ -83,9 +80,7 @@ public class ModBlocks {
|
||||
)
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// TRAP BLOCKS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Rope Trap - Flat trap that ties up entities that walk on it.
|
||||
@@ -114,9 +109,7 @@ public class ModBlocks {
|
||||
BlockTrappedChest::new
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// DOOR BLOCKS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Cell Door - Iron-like door that requires redstone to open.
|
||||
@@ -125,9 +118,7 @@ public class ModBlocks {
|
||||
public static final RegistryObject<BlockCellDoor> CELL_DOOR =
|
||||
registerDoorBlock("cell_door", BlockCellDoor::new);
|
||||
|
||||
// ========================================
|
||||
// CELL SYSTEM BLOCKS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Marker Block - Invisible block for cell spawn points.
|
||||
@@ -154,9 +145,7 @@ public class ModBlocks {
|
||||
BlockCellCore::new
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// REGISTRATION HELPERS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Register a block and its corresponding BlockItem.
|
||||
|
||||
@@ -20,7 +20,6 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
/**
|
||||
* Base BlockEntity for blocks that store bondage items.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Stores up to 6 bondage items:
|
||||
* - Bind (ropes, chains, straitjacket, etc.)
|
||||
@@ -42,9 +41,7 @@ public abstract class BondageItemBlockEntity
|
||||
implements IBondageItemHolder
|
||||
{
|
||||
|
||||
// ========================================
|
||||
// STORED ITEMS
|
||||
// ========================================
|
||||
|
||||
private ItemStack bind = ItemStack.EMPTY;
|
||||
private ItemStack gag = ItemStack.EMPTY;
|
||||
@@ -59,9 +56,7 @@ public abstract class BondageItemBlockEntity
|
||||
*/
|
||||
private final boolean offMode;
|
||||
|
||||
// ========================================
|
||||
// CONSTRUCTORS
|
||||
// ========================================
|
||||
|
||||
public BondageItemBlockEntity(
|
||||
BlockEntityType<?> type,
|
||||
@@ -81,9 +76,7 @@ public abstract class BondageItemBlockEntity
|
||||
this.offMode = offMode;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BIND
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getBind() {
|
||||
@@ -96,9 +89,7 @@ public abstract class BondageItemBlockEntity
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// GAG
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getGag() {
|
||||
@@ -111,9 +102,7 @@ public abstract class BondageItemBlockEntity
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BLINDFOLD
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getBlindfold() {
|
||||
@@ -126,9 +115,7 @@ public abstract class BondageItemBlockEntity
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// EARPLUGS
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getEarplugs() {
|
||||
@@ -141,9 +128,7 @@ public abstract class BondageItemBlockEntity
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// COLLAR
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getCollar() {
|
||||
@@ -156,9 +141,7 @@ public abstract class BondageItemBlockEntity
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CLOTHES
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getClothes() {
|
||||
@@ -171,9 +154,7 @@ public abstract class BondageItemBlockEntity
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STATE
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public boolean isArmed() {
|
||||
@@ -194,9 +175,7 @@ public abstract class BondageItemBlockEntity
|
||||
this.setChangedAndSync();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void load(CompoundTag tag) {
|
||||
@@ -300,9 +279,7 @@ public abstract class BondageItemBlockEntity
|
||||
return tag;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NETWORK SYNC
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Mark dirty and sync to clients.
|
||||
|
||||
@@ -6,7 +6,6 @@ import net.minecraft.world.item.ItemStack;
|
||||
/**
|
||||
* Interface for BlockEntities that store bondage items.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Defines the contract for storing and retrieving bondage items:
|
||||
* - Bind (ropes, chains, etc.)
|
||||
@@ -19,51 +18,37 @@ import net.minecraft.world.item.ItemStack;
|
||||
* Based on original ITileEntityBondageItemHolder from 1.12.2
|
||||
*/
|
||||
public interface IBondageItemHolder {
|
||||
// ========================================
|
||||
// BIND
|
||||
// ========================================
|
||||
|
||||
ItemStack getBind();
|
||||
void setBind(ItemStack bind);
|
||||
|
||||
// ========================================
|
||||
// GAG
|
||||
// ========================================
|
||||
|
||||
ItemStack getGag();
|
||||
void setGag(ItemStack gag);
|
||||
|
||||
// ========================================
|
||||
// BLINDFOLD
|
||||
// ========================================
|
||||
|
||||
ItemStack getBlindfold();
|
||||
void setBlindfold(ItemStack blindfold);
|
||||
|
||||
// ========================================
|
||||
// EARPLUGS
|
||||
// ========================================
|
||||
|
||||
ItemStack getEarplugs();
|
||||
void setEarplugs(ItemStack earplugs);
|
||||
|
||||
// ========================================
|
||||
// COLLAR
|
||||
// ========================================
|
||||
|
||||
ItemStack getCollar();
|
||||
void setCollar(ItemStack collar);
|
||||
|
||||
// ========================================
|
||||
// CLOTHES
|
||||
// ========================================
|
||||
|
||||
ItemStack getClothes();
|
||||
void setClothes(ItemStack clothes);
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Read bondage items from NBT.
|
||||
@@ -78,9 +63,7 @@ public interface IBondageItemHolder {
|
||||
*/
|
||||
CompoundTag writeBondageData(CompoundTag tag);
|
||||
|
||||
// ========================================
|
||||
// STATE
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Check if this holder has any bondage items loaded.
|
||||
|
||||
@@ -6,7 +6,6 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
/**
|
||||
* BlockEntity for kidnap bomb blocks.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Stores bondage items that will be applied when the bomb explodes.
|
||||
* Simple extension of BondageItemBlockEntity.
|
||||
|
||||
@@ -10,7 +10,6 @@ import net.minecraftforge.registries.RegistryObject;
|
||||
/**
|
||||
* Mod Block Entities Registration
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Handles registration of all TiedUp block entities using DeferredRegister.
|
||||
*/
|
||||
@@ -23,9 +22,7 @@ public class ModBlockEntities {
|
||||
TiedUpMod.MOD_ID
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// TRAP BLOCK ENTITIES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
// ========================================
|
||||
// BOMB BLOCK ENTITIES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Kidnap bomb block entity - stores bondage items for explosion effect.
|
||||
@@ -56,9 +51,7 @@ public class ModBlockEntities {
|
||||
).build(null)
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// CHEST BLOCK ENTITIES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Trapped chest block entity - stores bondage items for when player opens it.
|
||||
@@ -72,9 +65,7 @@ public class ModBlockEntities {
|
||||
).build(null)
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// CELL SYSTEM BLOCK ENTITIES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Marker block entity - stores cell UUID for cell system.
|
||||
|
||||
@@ -6,7 +6,6 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
/**
|
||||
* BlockEntity for rope trap blocks.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Stores bondage items that will be applied when an entity walks on the trap.
|
||||
* Simple extension of BondageItemBlockEntity.
|
||||
|
||||
@@ -15,7 +15,6 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
/**
|
||||
* BlockEntity for trapped chest blocks.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Extends ChestBlockEntity for proper chest behavior,
|
||||
* but also stores bondage items for the trap.
|
||||
@@ -37,9 +36,7 @@ public class TrappedChestBlockEntity
|
||||
super(ModBlockEntities.TRAPPED_CHEST.get(), pos, state);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BONDAGE ITEM HOLDER IMPLEMENTATION
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getBind() {
|
||||
@@ -172,9 +169,7 @@ public class TrappedChestBlockEntity
|
||||
return tag;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void load(CompoundTag tag) {
|
||||
@@ -188,9 +183,7 @@ public class TrappedChestBlockEntity
|
||||
writeBondageData(tag);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NETWORK SYNC
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Mark dirty and sync to clients.
|
||||
|
||||
@@ -7,7 +7,6 @@ import net.minecraft.world.item.ItemStack;
|
||||
/**
|
||||
* 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
|
||||
* another player (target). Anyone who delivers the target to the client
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.tiedup.remake.bounty;
|
||||
|
||||
import com.tiedup.remake.core.SettingsAccessor;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.core.SettingsAccessor;
|
||||
import java.util.*;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.ChatFormatting;
|
||||
@@ -20,7 +20,6 @@ import net.minecraft.world.level.saveddata.SavedData;
|
||||
/**
|
||||
* World-saved data manager for bounties.
|
||||
*
|
||||
* Phase 17: Bounty System
|
||||
*
|
||||
* Manages all active bounties, handles expiration, delivery rewards,
|
||||
* and stores bounties for offline players.
|
||||
|
||||
@@ -26,7 +26,11 @@ public final class CampMaidManager {
|
||||
* @param currentTime The current game time
|
||||
* @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.CampData data = ownership.getCamp(campId);
|
||||
if (data == null || !data.isAlive()) {
|
||||
@@ -93,7 +97,10 @@ public final class CampMaidManager {
|
||||
* @param level The server level
|
||||
* @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);
|
||||
List<UUID> result = new ArrayList<>();
|
||||
for (CampOwnership.CampData data : ownership.getAllCamps()) {
|
||||
|
||||
@@ -572,5 +572,4 @@ public class CampOwnership extends SavedData {
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import net.minecraft.world.level.saveddata.SavedData;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Phase 2: SavedData registry for confiscated player inventories.
|
||||
*
|
||||
* When a player is imprisoned:
|
||||
* 1. Their inventory is saved to NBT
|
||||
|
||||
@@ -139,10 +139,7 @@ public class FirstPersonMittensRenderer {
|
||||
*/
|
||||
private static boolean isBindHidingMittens(AbstractClientPlayer player) {
|
||||
net.minecraft.world.item.ItemStack bindStack =
|
||||
V2EquipmentHelper.getInRegion(
|
||||
player,
|
||||
BodyRegionV2.ARMS
|
||||
);
|
||||
V2EquipmentHelper.getInRegion(player, BodyRegionV2.ARMS);
|
||||
if (bindStack.isEmpty()) return false;
|
||||
if (bindStack.getItem() instanceof GenericBind bind) {
|
||||
BindVariant variant = bind.getVariant();
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
package com.tiedup.remake.client;
|
||||
|
||||
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.UnifiedBondageScreen;
|
||||
import com.tiedup.remake.items.base.ItemCollar;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import com.tiedup.remake.core.ModConfig;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
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.action.PacketForceSeatModifier;
|
||||
import com.tiedup.remake.network.action.PacketStruggle;
|
||||
import com.tiedup.remake.network.action.PacketTighten;
|
||||
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.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.client.KeyMapping;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -29,9 +28,9 @@ import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
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.
|
||||
*
|
||||
@@ -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()) {
|
||||
handleStruggleKey();
|
||||
}
|
||||
@@ -284,7 +283,6 @@ public class ModKeybindings {
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 21: Handle struggle key press with new flow.
|
||||
*
|
||||
* Flow:
|
||||
* 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
|
||||
if (com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.hasAnyEquipment(player)) {
|
||||
if (
|
||||
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.hasAnyEquipment(
|
||||
player
|
||||
)
|
||||
) {
|
||||
handleV2Struggle(player);
|
||||
return;
|
||||
}
|
||||
@@ -313,10 +315,11 @@ public class ModKeybindings {
|
||||
// Check if player has bind equipped
|
||||
if (state.isTiedUp()) {
|
||||
// Has bind - struggle against it
|
||||
// Phase 2.5: Check if mini-game is enabled
|
||||
if (ModConfig.SERVER.struggleMiniGameEnabled.get()) {
|
||||
// New: Start struggle mini-game
|
||||
ModNetwork.sendToServer(new PacketV2StruggleStart(BodyRegionV2.ARMS));
|
||||
ModNetwork.sendToServer(
|
||||
new PacketV2StruggleStart(BodyRegionV2.ARMS)
|
||||
);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[CLIENT] Struggle key pressed - starting V2 struggle mini-game"
|
||||
);
|
||||
@@ -359,7 +362,9 @@ public class ModKeybindings {
|
||||
*/
|
||||
private static void handleV2Struggle(Player player) {
|
||||
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;
|
||||
|
||||
@@ -367,9 +372,15 @@ public class ModKeybindings {
|
||||
com.tiedup.remake.v2.BodyRegionV2 bestRegion = null;
|
||||
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();
|
||||
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) {
|
||||
bestPriority = item.getPosePriority(stack);
|
||||
bestRegion = entry.getKey();
|
||||
@@ -379,7 +390,9 @@ public class ModKeybindings {
|
||||
|
||||
if (bestRegion != null) {
|
||||
ModNetwork.sendToServer(
|
||||
new com.tiedup.remake.v2.bondage.network.PacketV2StruggleStart(bestRegion)
|
||||
new com.tiedup.remake.v2.bondage.network.PacketV2StruggleStart(
|
||||
bestRegion
|
||||
)
|
||||
);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[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.
|
||||
*/
|
||||
private static boolean checkCollarOwnership(LivingEntity target, Player player) {
|
||||
ItemStack collarStack = com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.getInRegion(
|
||||
target, BodyRegionV2.NECK
|
||||
);
|
||||
if (!collarStack.isEmpty() && collarStack.getItem() instanceof ItemCollar collar) {
|
||||
private static boolean checkCollarOwnership(
|
||||
LivingEntity target,
|
||||
Player player
|
||||
) {
|
||||
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 false;
|
||||
|
||||
@@ -48,8 +48,10 @@ public class BondageAnimationManager {
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/** Cache of furniture ModifierLayers for NPC entities */
|
||||
private static final Map<UUID, ModifierLayer<IAnimation>> npcFurnitureLayers =
|
||||
new ConcurrentHashMap<>();
|
||||
private static final Map<
|
||||
UUID,
|
||||
ModifierLayer<IAnimation>
|
||||
> npcFurnitureLayers = new ConcurrentHashMap<>();
|
||||
|
||||
/** Factory ID for PlayerAnimator item layer (players only) */
|
||||
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>
|
||||
*/
|
||||
private static final Map<UUID, Integer> furnitureGraceTicks = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, Integer> furnitureGraceTicks =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Initialize the animation system.
|
||||
@@ -119,13 +122,13 @@ public class BondageAnimationManager {
|
||||
|
||||
LOGGER.info(
|
||||
"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 an animation on any entity (player or NPC).
|
||||
@@ -235,7 +238,10 @@ public class BondageAnimationManager {
|
||||
* @param anim The KeyframeAnimation to play
|
||||
* @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()) {
|
||||
return false;
|
||||
}
|
||||
@@ -255,9 +261,7 @@ public class BondageAnimationManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STOP ANIMATION
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Stop any currently playing animation on an entity.
|
||||
@@ -276,9 +280,7 @@ public class BondageAnimationManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// LAYER MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the ModifierLayer for an entity (without creating).
|
||||
@@ -398,9 +400,7 @@ public class BondageAnimationManager {
|
||||
return npcLayers.get(player.getUUID());
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CONTEXT LAYER (lower priority, for sit/kneel/sneak)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get the context animation layer for a player from PlayerAnimationAccess.
|
||||
@@ -431,14 +431,13 @@ public class BondageAnimationManager {
|
||||
LivingEntity entity
|
||||
) {
|
||||
if (entity instanceof IAnimatedPlayer animated) {
|
||||
return npcContextLayers.computeIfAbsent(
|
||||
entity.getUUID(),
|
||||
k -> {
|
||||
ModifierLayer<IAnimation> layer = new ModifierLayer<>();
|
||||
animated.getAnimationStack().addAnimLayer(CONTEXT_LAYER_PRIORITY, layer);
|
||||
return layer;
|
||||
}
|
||||
);
|
||||
return npcContextLayers.computeIfAbsent(entity.getUUID(), k -> {
|
||||
ModifierLayer<IAnimation> layer = new ModifierLayer<>();
|
||||
animated
|
||||
.getAnimationStack()
|
||||
.addAnimLayer(CONTEXT_LAYER_PRIORITY, layer);
|
||||
return layer;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -496,9 +495,7 @@ public class BondageAnimationManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// FURNITURE LAYER (highest priority, for seat poses)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Play a furniture animation on the furniture layer (highest priority).
|
||||
@@ -512,8 +509,15 @@ public class BondageAnimationManager {
|
||||
* @param animation the KeyframeAnimation from FurnitureAnimationContext
|
||||
* @return true if animation started successfully
|
||||
*/
|
||||
public static boolean playFurniture(Player player, KeyframeAnimation animation) {
|
||||
if (player == null || animation == null || !player.level().isClientSide()) {
|
||||
public static boolean playFurniture(
|
||||
Player player,
|
||||
KeyframeAnimation animation
|
||||
) {
|
||||
if (
|
||||
player == null ||
|
||||
animation == null ||
|
||||
!player.level().isClientSide()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -522,11 +526,17 @@ public class BondageAnimationManager {
|
||||
layer.setAnimation(new KeyframeAnimationPlayer(animation));
|
||||
// Reset grace ticks since we just started/refreshed the animation
|
||||
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;
|
||||
}
|
||||
|
||||
LOGGER.warn("Furniture layer not available for player: {}", player.getName().getString());
|
||||
LOGGER.warn(
|
||||
"Furniture layer not available for player: {}",
|
||||
player.getName().getString()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -545,7 +555,10 @@ public class BondageAnimationManager {
|
||||
layer.setAnimation(null);
|
||||
}
|
||||
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) {
|
||||
if (player instanceof AbstractClientPlayer clientPlayer) {
|
||||
try {
|
||||
ModifierLayer<IAnimation> layer = (ModifierLayer<IAnimation>)
|
||||
PlayerAnimationAccess.getPlayerAssociatedData(clientPlayer)
|
||||
.get(FURNITURE_FACTORY_ID);
|
||||
ModifierLayer<IAnimation> layer = (ModifierLayer<
|
||||
IAnimation
|
||||
>) PlayerAnimationAccess.getPlayerAssociatedData(
|
||||
clientPlayer
|
||||
).get(FURNITURE_FACTORY_ID);
|
||||
if (layer != null) {
|
||||
return layer;
|
||||
}
|
||||
@@ -628,17 +643,18 @@ public class BondageAnimationManager {
|
||||
// Player has furniture anim but no seat -- increment grace
|
||||
int ticks = furnitureGraceTicks.merge(uuid, 1, Integer::sum);
|
||||
if (ticks >= FURNITURE_GRACE_TICKS) {
|
||||
LOGGER.info("Removing stale furniture animation for player {} "
|
||||
+ "(not riding ISeatProvider for {} ticks)",
|
||||
player.getName().getString(), ticks);
|
||||
LOGGER.info(
|
||||
"Removing stale furniture animation for player {} " +
|
||||
"(not riding ISeatProvider for {} ticks)",
|
||||
player.getName().getString(),
|
||||
ticks
|
||||
);
|
||||
stopFurniture(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// FALLBACK ANIMATION HANDLING
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Try to find a fallback animation ID when the requested one doesn't exist.
|
||||
@@ -696,9 +712,7 @@ public class BondageAnimationManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CLEANUP
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
/** All NPC layer caches, for bulk cleanup operations. */
|
||||
private static final Map<UUID, ModifierLayer<IAnimation>>[] ALL_NPC_CACHES = new Map[] {
|
||||
npcLayers, npcContextLayers, npcFurnitureLayers
|
||||
};
|
||||
private static final Map<UUID, ModifierLayer<IAnimation>>[] ALL_NPC_CACHES =
|
||||
new Map[] { npcLayers, npcContextLayers, npcFurnitureLayers };
|
||||
|
||||
public static void cleanup(UUID entityId) {
|
||||
for (Map<UUID, ModifierLayer<IAnimation>> cache : ALL_NPC_CACHES) {
|
||||
@@ -733,5 +746,4 @@ public class BondageAnimationManager {
|
||||
furnitureGraceTicks.clear();
|
||||
LOGGER.info("Cleared all NPC animation layers");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public enum AnimationContext {
|
||||
|
||||
STAND_IDLE("stand_idle", false),
|
||||
STAND_WALK("stand_walk", false),
|
||||
STAND_SNEAK("stand_sneak", false),
|
||||
|
||||
@@ -50,15 +50,21 @@ public final class AnimationContextResolver {
|
||||
* @param activeStyle the active movement style from client state, or null
|
||||
* @return the resolved animation context, never null
|
||||
*/
|
||||
public static AnimationContext resolve(Player player, @Nullable PlayerBindState state,
|
||||
@Nullable MovementStyle activeStyle) {
|
||||
public static AnimationContext resolve(
|
||||
Player player,
|
||||
@Nullable PlayerBindState state,
|
||||
@Nullable MovementStyle activeStyle
|
||||
) {
|
||||
boolean sitting = PetBedClientState.get(player.getUUID()) != 0;
|
||||
boolean struggling = state != null && state.isStruggling();
|
||||
boolean sneaking = player.isCrouching();
|
||||
boolean moving = player.getDeltaMovement().horizontalDistanceSqr() > 1.0E-6;
|
||||
boolean moving =
|
||||
player.getDeltaMovement().horizontalDistanceSqr() > 1.0E-6;
|
||||
|
||||
if (sitting) {
|
||||
return struggling ? AnimationContext.SIT_STRUGGLE : AnimationContext.SIT_IDLE;
|
||||
return struggling
|
||||
? AnimationContext.SIT_STRUGGLE
|
||||
: AnimationContext.SIT_IDLE;
|
||||
}
|
||||
if (struggling) {
|
||||
return AnimationContext.STAND_STRUGGLE;
|
||||
@@ -78,12 +84,23 @@ public final class AnimationContextResolver {
|
||||
/**
|
||||
* 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) {
|
||||
case SHUFFLE -> moving ? AnimationContext.SHUFFLE_WALK : AnimationContext.SHUFFLE_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;
|
||||
case SHUFFLE -> moving
|
||||
? AnimationContext.SHUFFLE_WALK
|
||||
: AnimationContext.SHUFFLE_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 kneeling = entity.isKneeling();
|
||||
boolean struggling = entity.isStruggling();
|
||||
boolean moving = entity.getDeltaMovement().horizontalDistanceSqr() > 1.0E-6;
|
||||
boolean moving =
|
||||
entity.getDeltaMovement().horizontalDistanceSqr() > 1.0E-6;
|
||||
|
||||
if (sitting) {
|
||||
return struggling ? AnimationContext.SIT_STRUGGLE : AnimationContext.SIT_IDLE;
|
||||
return struggling
|
||||
? AnimationContext.SIT_STRUGGLE
|
||||
: AnimationContext.SIT_IDLE;
|
||||
}
|
||||
if (kneeling) {
|
||||
return struggling ? AnimationContext.KNEEL_STRUGGLE : AnimationContext.KNEEL_IDLE;
|
||||
return struggling
|
||||
? AnimationContext.KNEEL_STRUGGLE
|
||||
: AnimationContext.KNEEL_IDLE;
|
||||
}
|
||||
if (struggling) {
|
||||
return AnimationContext.STAND_STRUGGLE;
|
||||
|
||||
@@ -6,10 +6,10 @@ import dev.kosmx.playerAnim.minecraftApi.PlayerAnimationRegistry;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
@@ -42,13 +42,15 @@ public final class ContextAnimationFactory {
|
||||
* Cache keyed by "contextSuffix|ownedPartsHashCode".
|
||||
* 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,
|
||||
* 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() {}
|
||||
|
||||
@@ -65,8 +67,14 @@ public final class ContextAnimationFactory {
|
||||
* @return the context animation with disabled parts suppressed, or null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public static KeyframeAnimation create(AnimationContext context, Set<String> disabledParts) {
|
||||
String cacheKey = context.getAnimationSuffix() + "|" + String.join(",", new java.util.TreeSet<>(disabledParts));
|
||||
public static KeyframeAnimation create(
|
||||
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
|
||||
// by checking the MISSING_WARNED set to avoid redundant work.
|
||||
KeyframeAnimation cached = CACHE.get(cacheKey);
|
||||
@@ -77,7 +85,10 @@ public final class ContextAnimationFactory {
|
||||
return null;
|
||||
}
|
||||
|
||||
KeyframeAnimation result = buildContextAnimation(context, disabledParts);
|
||||
KeyframeAnimation result = buildContextAnimation(
|
||||
context,
|
||||
disabledParts
|
||||
);
|
||||
if (result != null) {
|
||||
CACHE.put(cacheKey, result);
|
||||
} else {
|
||||
@@ -100,8 +111,10 @@ public final class ContextAnimationFactory {
|
||||
* </ol>
|
||||
*/
|
||||
@Nullable
|
||||
private static KeyframeAnimation buildContextAnimation(AnimationContext context,
|
||||
Set<String> disabledParts) {
|
||||
private static KeyframeAnimation buildContextAnimation(
|
||||
AnimationContext context,
|
||||
Set<String> disabledParts
|
||||
) {
|
||||
String suffix = context.getAnimationSuffix();
|
||||
|
||||
// Priority 1: GLB-based context animation from ContextGlbRegistry
|
||||
@@ -110,13 +123,17 @@ public final class ContextAnimationFactory {
|
||||
// Priority 2: JSON-based context animation from PlayerAnimationRegistry
|
||||
if (baseAnim == null) {
|
||||
ResourceLocation animId = ResourceLocation.fromNamespaceAndPath(
|
||||
NAMESPACE, "context_" + suffix
|
||||
NAMESPACE,
|
||||
"context_" + suffix
|
||||
);
|
||||
baseAnim = PlayerAnimationRegistry.getAnimation(animId);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -140,8 +157,10 @@ public final class ContextAnimationFactory {
|
||||
* <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>
|
||||
*/
|
||||
private static void disableParts(KeyframeAnimation.AnimationBuilder builder,
|
||||
Set<String> disabledParts) {
|
||||
private static void disableParts(
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
Set<String> disabledParts
|
||||
) {
|
||||
for (String partName : disabledParts) {
|
||||
KeyframeAnimation.StateCollection part = builder.getPart(partName);
|
||||
if (part != null) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
@@ -16,6 +15,7 @@ import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Registry for context animations loaded from GLB files.
|
||||
@@ -70,10 +70,15 @@ public final class ContextGlbRegistry {
|
||||
public static void reload(ResourceManager resourceManager) {
|
||||
Map<String, KeyframeAnimation> newRegistry = new HashMap<>();
|
||||
|
||||
Map<ResourceLocation, Resource> resources = resourceManager.listResources(
|
||||
DIRECTORY, loc -> loc.getPath().endsWith(".glb"));
|
||||
Map<ResourceLocation, Resource> resources =
|
||||
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();
|
||||
Resource resource = entry.getValue();
|
||||
|
||||
@@ -89,15 +94,26 @@ public final class ContextGlbRegistry {
|
||||
KeyframeAnimation anim = GltfPoseConverter.convert(data);
|
||||
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) {
|
||||
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
|
||||
REGISTRY = Collections.unmodifiableMap(newRegistry);
|
||||
LOGGER.info("[ContextGlb] Loaded {} context GLB animations", newRegistry.size());
|
||||
LOGGER.info(
|
||||
"[ContextGlb] Loaded {} context GLB animations",
|
||||
newRegistry.size()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,10 +6,10 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
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
|
||||
@@ -42,9 +42,12 @@ public final class GlbAnimationResolver {
|
||||
* @return parsed GLB data, or null if loading failed
|
||||
*/
|
||||
@Nullable
|
||||
public static GltfData resolveAnimationData(ResourceLocation itemModelLoc,
|
||||
@Nullable ResourceLocation animationSource) {
|
||||
ResourceLocation source = animationSource != null ? animationSource : itemModelLoc;
|
||||
public static GltfData resolveAnimationData(
|
||||
ResourceLocation itemModelLoc,
|
||||
@Nullable ResourceLocation animationSource
|
||||
) {
|
||||
ResourceLocation source =
|
||||
animationSource != null ? animationSource : itemModelLoc;
|
||||
return GltfCache.get(source);
|
||||
}
|
||||
|
||||
@@ -66,8 +69,8 @@ public final class GlbAnimationResolver {
|
||||
*/
|
||||
@Nullable
|
||||
public static String resolve(GltfData data, AnimationContext context) {
|
||||
String prefix = context.getGlbContextPrefix(); // "Sit", "Kneel", "Sneak", "Walk", ""
|
||||
String variant = context.getGlbVariant(); // "Idle" or "Struggle"
|
||||
String prefix = context.getGlbContextPrefix(); // "Sit", "Kneel", "Sneak", "Walk", ""
|
||||
String variant = context.getGlbVariant(); // "Idle" or "Struggle"
|
||||
|
||||
// 1. Exact match: "FullSitIdle" then "SitIdle" (with variants)
|
||||
String exact = prefix + variant;
|
||||
@@ -146,6 +149,8 @@ public final class GlbAnimationResolver {
|
||||
|
||||
if (candidates.isEmpty()) return null;
|
||||
if (candidates.size() == 1) return candidates.get(0);
|
||||
return candidates.get(ThreadLocalRandom.current().nextInt(candidates.size()));
|
||||
return candidates.get(
|
||||
ThreadLocalRandom.current().nextInt(candidates.size())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.DataDrivenItemRegistry;
|
||||
import java.util.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Maps V2 body regions to PlayerAnimator part names.
|
||||
@@ -29,7 +29,12 @@ public final class RegionBoneMapper {
|
||||
|
||||
/** All PlayerAnimator part names for the player model. */
|
||||
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>
|
||||
*/
|
||||
public record BoneOwnership(Set<String> thisParts, Set<String> otherParts) {
|
||||
|
||||
/**
|
||||
* Parts not owned by any item. These are "free" and can be animated
|
||||
* by the winning item IF the GLB contains keyframes for them.
|
||||
@@ -84,20 +88,20 @@ public final class RegionBoneMapper {
|
||||
|
||||
static {
|
||||
Map<BodyRegionV2, Set<String>> map = new EnumMap<>(BodyRegionV2.class);
|
||||
map.put(BodyRegionV2.HEAD, Set.of("head"));
|
||||
map.put(BodyRegionV2.EYES, Set.of("head"));
|
||||
map.put(BodyRegionV2.EARS, Set.of("head"));
|
||||
map.put(BodyRegionV2.MOUTH, Set.of("head"));
|
||||
map.put(BodyRegionV2.NECK, Set.of());
|
||||
map.put(BodyRegionV2.TORSO, Set.of("body"));
|
||||
map.put(BodyRegionV2.ARMS, Set.of("rightArm", "leftArm"));
|
||||
map.put(BodyRegionV2.HANDS, Set.of("rightArm", "leftArm"));
|
||||
map.put(BodyRegionV2.HEAD, Set.of("head"));
|
||||
map.put(BodyRegionV2.EYES, Set.of("head"));
|
||||
map.put(BodyRegionV2.EARS, Set.of("head"));
|
||||
map.put(BodyRegionV2.MOUTH, Set.of("head"));
|
||||
map.put(BodyRegionV2.NECK, Set.of());
|
||||
map.put(BodyRegionV2.TORSO, Set.of("body"));
|
||||
map.put(BodyRegionV2.ARMS, Set.of("rightArm", "leftArm"));
|
||||
map.put(BodyRegionV2.HANDS, Set.of("rightArm", "leftArm"));
|
||||
map.put(BodyRegionV2.FINGERS, Set.of());
|
||||
map.put(BodyRegionV2.WAIST, Set.of("body"));
|
||||
map.put(BodyRegionV2.LEGS, Set.of("rightLeg", "leftLeg"));
|
||||
map.put(BodyRegionV2.FEET, Set.of("rightLeg", "leftLeg"));
|
||||
map.put(BodyRegionV2.TAIL, Set.of());
|
||||
map.put(BodyRegionV2.WINGS, Set.of());
|
||||
map.put(BodyRegionV2.WAIST, Set.of("body"));
|
||||
map.put(BodyRegionV2.LEGS, Set.of("rightLeg", "leftLeg"));
|
||||
map.put(BodyRegionV2.FEET, Set.of("rightLeg", "leftLeg"));
|
||||
map.put(BodyRegionV2.TAIL, Set.of());
|
||||
map.put(BodyRegionV2.WINGS, Set.of());
|
||||
REGION_TO_PARTS = Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
@@ -123,7 +127,9 @@ public final class RegionBoneMapper {
|
||||
* @param equipped map from representative region to equipped ItemStack
|
||||
* @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<>();
|
||||
for (Map.Entry<BodyRegionV2, ItemStack> entry : equipped.entrySet()) {
|
||||
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
|
||||
* @return BoneOwnership describing this item's parts vs other items' parts
|
||||
*/
|
||||
public static BoneOwnership computePerItemParts(Map<BodyRegionV2, ItemStack> equipped,
|
||||
ItemStack winningItemStack) {
|
||||
public static BoneOwnership computePerItemParts(
|
||||
Map<BodyRegionV2, ItemStack> equipped,
|
||||
ItemStack winningItemStack
|
||||
) {
|
||||
Set<String> thisParts = new HashSet<>();
|
||||
Set<String> otherParts = new HashSet<>();
|
||||
|
||||
// Track which ItemStacks we've already processed to avoid duplicate work
|
||||
// (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()) {
|
||||
ItemStack stack = entry.getValue();
|
||||
@@ -199,8 +209,11 @@ public final class RegionBoneMapper {
|
||||
* @param winningItem the actual ItemStack reference (for identity comparison in
|
||||
* {@link #computePerItemParts})
|
||||
*/
|
||||
public record GlbModelResult(ResourceLocation modelLoc, @Nullable ResourceLocation animSource,
|
||||
ItemStack winningItem) {}
|
||||
public record GlbModelResult(
|
||||
ResourceLocation modelLoc,
|
||||
@Nullable ResourceLocation animSource,
|
||||
ItemStack winningItem
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Empty map for hardcoded items (no filtering applied).
|
||||
*/
|
||||
public record V2ItemAnimInfo(ResourceLocation modelLoc, @Nullable ResourceLocation animSource,
|
||||
Set<String> ownedParts, int posePriority,
|
||||
Map<String, Set<String>> animationBones) {}
|
||||
public record V2ItemAnimInfo(
|
||||
ResourceLocation modelLoc,
|
||||
@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.
|
||||
@@ -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)
|
||||
*/
|
||||
@Nullable
|
||||
public static GlbModelResult resolveWinningItem(Map<BodyRegionV2, ItemStack> equipped) {
|
||||
public static GlbModelResult resolveWinningItem(
|
||||
Map<BodyRegionV2, ItemStack> equipped
|
||||
) {
|
||||
ItemStack bestStack = null;
|
||||
ResourceLocation bestModel = null;
|
||||
int bestPriority = Integer.MIN_VALUE;
|
||||
@@ -238,7 +257,10 @@ public final class RegionBoneMapper {
|
||||
ItemStack stack = entry.getValue();
|
||||
if (stack.getItem() instanceof IV2BondageItem v2Item) {
|
||||
ResourceLocation model = v2Item.getModelLocation(stack);
|
||||
if (model != null && v2Item.getPosePriority(stack) > bestPriority) {
|
||||
if (
|
||||
model != null &&
|
||||
v2Item.getPosePriority(stack) > bestPriority
|
||||
) {
|
||||
bestPriority = v2Item.getPosePriority(stack);
|
||||
bestModel = model;
|
||||
bestStack = stack;
|
||||
@@ -252,7 +274,9 @@ public final class RegionBoneMapper {
|
||||
// (the model's own animations are used).
|
||||
ResourceLocation animSource = null;
|
||||
if (bestStack.getItem() instanceof DataDrivenBondageItem) {
|
||||
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(bestStack);
|
||||
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(
|
||||
bestStack
|
||||
);
|
||||
if (def != null) {
|
||||
animSource = def.animationSource();
|
||||
}
|
||||
@@ -273,13 +297,23 @@ public final class RegionBoneMapper {
|
||||
* @return list of V2ItemAnimInfo, sorted by priority descending. Empty if no V2 items.
|
||||
* The first element (if any) is the free-bone donor.
|
||||
*/
|
||||
public static List<V2ItemAnimInfo> resolveAllV2Items(Map<BodyRegionV2, ItemStack> equipped) {
|
||||
record ItemEntry(ItemStack stack, IV2BondageItem v2Item, ResourceLocation model,
|
||||
@Nullable ResourceLocation animSource, Set<String> rawParts, int priority,
|
||||
Map<String, Set<String>> animationBones) {}
|
||||
public static List<V2ItemAnimInfo> resolveAllV2Items(
|
||||
Map<BodyRegionV2, ItemStack> equipped
|
||||
) {
|
||||
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<>();
|
||||
Set<ItemStack> seen = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
Set<ItemStack> seen = Collections.newSetFromMap(
|
||||
new IdentityHashMap<>()
|
||||
);
|
||||
|
||||
for (Map.Entry<BodyRegionV2, ItemStack> entry : equipped.entrySet()) {
|
||||
ItemStack stack = entry.getValue();
|
||||
@@ -299,15 +333,26 @@ public final class RegionBoneMapper {
|
||||
ResourceLocation animSource = null;
|
||||
Map<String, Set<String>> animBones = Map.of();
|
||||
if (stack.getItem() instanceof DataDrivenBondageItem) {
|
||||
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(stack);
|
||||
DataDrivenItemDefinition def = DataDrivenItemRegistry.get(
|
||||
stack
|
||||
);
|
||||
if (def != null) {
|
||||
animSource = def.animationSource();
|
||||
animBones = def.animationBones();
|
||||
}
|
||||
}
|
||||
|
||||
entries.add(new ItemEntry(stack, v2Item, model, animSource, rawParts,
|
||||
v2Item.getPosePriority(stack), animBones));
|
||||
entries.add(
|
||||
new ItemEntry(
|
||||
stack,
|
||||
v2Item,
|
||||
model,
|
||||
animSource,
|
||||
rawParts,
|
||||
v2Item.getPosePriority(stack),
|
||||
animBones
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,8 +368,15 @@ public final class RegionBoneMapper {
|
||||
ownedParts.removeAll(claimed);
|
||||
if (ownedParts.isEmpty()) continue;
|
||||
claimed.addAll(ownedParts);
|
||||
result.add(new V2ItemAnimInfo(e.model(), e.animSource(),
|
||||
Collections.unmodifiableSet(ownedParts), e.priority(), e.animationBones()));
|
||||
result.add(
|
||||
new V2ItemAnimInfo(
|
||||
e.model(),
|
||||
e.animSource(),
|
||||
Collections.unmodifiableSet(ownedParts),
|
||||
e.priority(),
|
||||
e.animationBones()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(result);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.tiedup.remake.client.animation.render;
|
||||
|
||||
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.PoseType;
|
||||
import com.tiedup.remake.state.HumanChairHelper;
|
||||
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.Int2ObjectOpenHashMap;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.tiedup.remake.client.animation.render;
|
||||
|
||||
import com.tiedup.remake.client.state.PetBedClientState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.items.base.ItemBind;
|
||||
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 net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.tiedup.remake.client.animation.render;
|
||||
|
||||
import com.tiedup.remake.client.renderer.layers.ClothesRenderHelper;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.items.base.ItemBind;
|
||||
import com.tiedup.remake.items.base.PoseType;
|
||||
import com.tiedup.remake.items.clothes.ClothesProperties;
|
||||
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.Int2ObjectOpenHashMap;
|
||||
import net.minecraft.client.model.PlayerModel;
|
||||
@@ -48,7 +48,7 @@ public class PlayerArmHideEventHandler {
|
||||
/**
|
||||
* Before player render:
|
||||
* - Hide arms for wrap/latex_sack poses
|
||||
* - Hide outer layers based on clothes settings (Phase 19)
|
||||
* - Hide outer layers based on clothes settings
|
||||
*/
|
||||
@SubscribeEvent
|
||||
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);
|
||||
if (!clothes.isEmpty()) {
|
||||
ClothesProperties props =
|
||||
@@ -126,7 +126,7 @@ public class PlayerArmHideEventHandler {
|
||||
model.leftSleeve.visible = true;
|
||||
model.rightSleeve.visible = true;
|
||||
|
||||
// === RESTORE WEARER LAYERS - Phase 19 ===
|
||||
// === RESTORE WEARER LAYERS ===
|
||||
boolean[] savedLayers = storedLayers.remove(player.getId());
|
||||
if (savedLayers != null) {
|
||||
ClothesRenderHelper.restoreWearerLayers(model, savedLayers);
|
||||
|
||||
@@ -4,6 +4,9 @@ import com.mojang.logging.LogUtils;
|
||||
import com.tiedup.remake.client.animation.AnimationStateRegistry;
|
||||
import com.tiedup.remake.client.animation.BondageAnimationManager;
|
||||
import com.tiedup.remake.client.animation.PendingAnimationManager;
|
||||
import com.tiedup.remake.client.animation.context.AnimationContext;
|
||||
import com.tiedup.remake.client.animation.context.AnimationContextResolver;
|
||||
import com.tiedup.remake.client.animation.context.RegionBoneMapper;
|
||||
import com.tiedup.remake.client.animation.util.AnimationIdBuilder;
|
||||
import com.tiedup.remake.client.events.CellHighlightHandler;
|
||||
import com.tiedup.remake.client.events.LeashProxyClientHandler;
|
||||
@@ -13,15 +16,12 @@ import com.tiedup.remake.client.state.MovementStyleClientState;
|
||||
import com.tiedup.remake.client.state.PetBedClientState;
|
||||
import com.tiedup.remake.items.base.ItemBind;
|
||||
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.bondage.IV2BondageEquipment;
|
||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||
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.UUID;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -109,8 +109,9 @@ public class AnimationTickHandler {
|
||||
|
||||
// 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.
|
||||
boolean isTied = state != null && (state.isTiedUp()
|
||||
|| V2EquipmentHelper.hasAnyEquipment(player));
|
||||
boolean isTied =
|
||||
state != null &&
|
||||
(state.isTiedUp() || V2EquipmentHelper.hasAnyEquipment(player));
|
||||
boolean wasTied =
|
||||
AnimationStateRegistry.getLastTiedState().getOrDefault(uuid, false);
|
||||
|
||||
@@ -175,9 +176,11 @@ public class AnimationTickHandler {
|
||||
|
||||
if (isTied) {
|
||||
// Resolve V2 equipped items
|
||||
IV2BondageEquipment equipment = V2EquipmentHelper.getEquipment(player);
|
||||
Map<BodyRegionV2, ItemStack> equipped = equipment != null
|
||||
? equipment.getAllEquipped() : Map.of();
|
||||
IV2BondageEquipment equipment = V2EquipmentHelper.getEquipment(
|
||||
player
|
||||
);
|
||||
Map<BodyRegionV2, ItemStack> equipped =
|
||||
equipment != null ? equipment.getAllEquipped() : Map.of();
|
||||
|
||||
// Resolve ALL V2 items with GLB models and per-item bone ownership
|
||||
java.util.List<RegionBoneMapper.V2ItemAnimInfo> v2Items =
|
||||
@@ -185,10 +188,22 @@ public class AnimationTickHandler {
|
||||
|
||||
if (!v2Items.isEmpty()) {
|
||||
// V2 path: multi-item composite animation
|
||||
java.util.Set<String> allOwnedParts = RegionBoneMapper.computeAllOwnedParts(v2Items);
|
||||
MovementStyle activeStyle = MovementStyleClientState.get(player.getUUID());
|
||||
AnimationContext context = AnimationContextResolver.resolve(player, state, activeStyle);
|
||||
GltfAnimationApplier.applyMultiItemV2Animation(player, v2Items, context, allOwnedParts);
|
||||
java.util.Set<String> allOwnedParts =
|
||||
RegionBoneMapper.computeAllOwnedParts(v2Items);
|
||||
MovementStyle activeStyle = MovementStyleClientState.get(
|
||||
player.getUUID()
|
||||
);
|
||||
AnimationContext context = AnimationContextResolver.resolve(
|
||||
player,
|
||||
state,
|
||||
activeStyle
|
||||
);
|
||||
GltfAnimationApplier.applyMultiItemV2Animation(
|
||||
player,
|
||||
v2Items,
|
||||
context,
|
||||
allOwnedParts
|
||||
);
|
||||
// Clear V1 tracking so transition back works
|
||||
AnimationStateRegistry.getLastAnimId().remove(uuid);
|
||||
} else {
|
||||
@@ -197,11 +212,19 @@ public class AnimationTickHandler {
|
||||
GltfAnimationApplier.clearV2Animation(player);
|
||||
}
|
||||
String animId = buildAnimationId(player, state);
|
||||
String lastId = AnimationStateRegistry.getLastAnimId().get(uuid);
|
||||
String lastId = AnimationStateRegistry.getLastAnimId().get(
|
||||
uuid
|
||||
);
|
||||
if (!animId.equals(lastId)) {
|
||||
boolean success = BondageAnimationManager.playAnimation(player, animId);
|
||||
boolean success = BondageAnimationManager.playAnimation(
|
||||
player,
|
||||
animId
|
||||
);
|
||||
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)
|
||||
boolean armsBound = V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS);
|
||||
boolean legsBound = V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.LEGS);
|
||||
boolean armsBound = V2EquipmentHelper.isRegionOccupied(
|
||||
player,
|
||||
BodyRegionV2.ARMS
|
||||
);
|
||||
boolean legsBound = V2EquipmentHelper.isRegionOccupied(
|
||||
player,
|
||||
BodyRegionV2.LEGS
|
||||
);
|
||||
|
||||
// V1 fallback: if no V2 regions are set but player is tied, derive from ItemBind NBT
|
||||
if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) {
|
||||
|
||||
@@ -15,8 +15,8 @@ import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.v2.bondage.IV2BondageEquipment;
|
||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@@ -45,7 +45,8 @@ import net.minecraftforge.fml.common.Mod;
|
||||
public class NpcAnimationTickHandler {
|
||||
|
||||
/** 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.
|
||||
@@ -91,18 +92,31 @@ public class NpcAnimationTickHandler {
|
||||
|
||||
if (inPose) {
|
||||
// Resolve V2 equipment map
|
||||
IV2BondageEquipment equipment = V2EquipmentHelper.getEquipment(entity);
|
||||
Map<BodyRegionV2, net.minecraft.world.item.ItemStack> equipped = equipment != null
|
||||
? equipment.getAllEquipped() : Map.of();
|
||||
RegionBoneMapper.GlbModelResult glbResult = RegionBoneMapper.resolveWinningItem(equipped);
|
||||
IV2BondageEquipment equipment = V2EquipmentHelper.getEquipment(
|
||||
entity
|
||||
);
|
||||
Map<BodyRegionV2, net.minecraft.world.item.ItemStack> equipped =
|
||||
equipment != null ? equipment.getAllEquipped() : Map.of();
|
||||
RegionBoneMapper.GlbModelResult glbResult =
|
||||
RegionBoneMapper.resolveWinningItem(equipped);
|
||||
|
||||
if (glbResult != null) {
|
||||
// V2 path: dual-layer animation with per-item bone ownership
|
||||
RegionBoneMapper.BoneOwnership ownership =
|
||||
RegionBoneMapper.computePerItemParts(equipped, glbResult.winningItem());
|
||||
AnimationContext context = AnimationContextResolver.resolveNpc(entity);
|
||||
GltfAnimationApplier.applyV2Animation(entity, glbResult.modelLoc(),
|
||||
glbResult.animSource(), context, ownership);
|
||||
RegionBoneMapper.computePerItemParts(
|
||||
equipped,
|
||||
glbResult.winningItem()
|
||||
);
|
||||
AnimationContext context = AnimationContextResolver.resolveNpc(
|
||||
entity
|
||||
);
|
||||
GltfAnimationApplier.applyV2Animation(
|
||||
entity,
|
||||
glbResult.modelLoc(),
|
||||
glbResult.animSource(),
|
||||
context,
|
||||
ownership
|
||||
);
|
||||
lastNpcAnimId.remove(uuid);
|
||||
} else {
|
||||
// V1 fallback: JSON-based PlayerAnimator animations
|
||||
@@ -118,7 +132,10 @@ public class NpcAnimationTickHandler {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (lastNpcAnimId.containsKey(uuid) || GltfAnimationApplier.hasActiveState(entity)) {
|
||||
if (
|
||||
lastNpcAnimId.containsKey(uuid) ||
|
||||
GltfAnimationApplier.hasActiveState(entity)
|
||||
) {
|
||||
if (GltfAnimationApplier.hasActiveState(entity)) {
|
||||
GltfAnimationApplier.clearV2Animation(entity);
|
||||
} else {
|
||||
@@ -141,7 +158,9 @@ public class NpcAnimationTickHandler {
|
||||
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;
|
||||
boolean hasBind = false;
|
||||
|
||||
@@ -151,8 +170,14 @@ public class NpcAnimationTickHandler {
|
||||
}
|
||||
|
||||
// Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder)
|
||||
boolean armsBound = V2EquipmentHelper.isRegionOccupied(entity, BodyRegionV2.ARMS);
|
||||
boolean legsBound = V2EquipmentHelper.isRegionOccupied(entity, BodyRegionV2.LEGS);
|
||||
boolean armsBound = V2EquipmentHelper.isRegionOccupied(
|
||||
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
|
||||
if (!armsBound && !legsBound && bind.getItem() instanceof ItemBind) {
|
||||
|
||||
@@ -82,9 +82,7 @@ public final class AnimationIdBuilder {
|
||||
return poseType.getBindTypeName();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Unified Build Method
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Build animation ID string for entities.
|
||||
|
||||
@@ -18,7 +18,6 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* Phase 5: Blindfold Rendering
|
||||
*
|
||||
* 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
|
||||
float opacity = hardcore
|
||||
? 1.0F
|
||||
: ModConfig.CLIENT.blindfoldOverlayOpacity
|
||||
.get()
|
||||
.floatValue();
|
||||
: ModConfig.CLIENT.blindfoldOverlayOpacity.get().floatValue();
|
||||
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, opacity);
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.defaultBlendFunc();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.tiedup.remake.client.events;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.items.ModItems;
|
||||
import com.tiedup.remake.items.base.BindVariant;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.tiedup.remake.client.events;
|
||||
|
||||
import com.tiedup.remake.items.base.*;
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.v2.bondage.IV2BondageItem;
|
||||
import com.tiedup.remake.network.selfbondage.PacketSelfBondage;
|
||||
import com.tiedup.remake.v2.bondage.IV2BondageItem;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
|
||||
@@ -31,7 +31,7 @@ public final class GlbParser {
|
||||
private static final int GLB_MAGIC = 0x46546C67; // "glTF"
|
||||
private static final int GLB_VERSION = 2;
|
||||
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() {}
|
||||
|
||||
@@ -43,9 +43,12 @@ public final class GlbParser {
|
||||
* @return parsed GltfData
|
||||
* @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();
|
||||
ByteBuffer buf = ByteBuffer.wrap(allBytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||
ByteBuffer buf = ByteBuffer.wrap(allBytes).order(
|
||||
ByteOrder.LITTLE_ENDIAN
|
||||
);
|
||||
|
||||
// -- Header --
|
||||
int magic = buf.getInt();
|
||||
@@ -54,7 +57,9 @@ public final class GlbParser {
|
||||
}
|
||||
int version = buf.getInt();
|
||||
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();
|
||||
|
||||
@@ -105,12 +110,18 @@ public final class GlbParser {
|
||||
for (int j = 0; j < skinJoints.size(); j++) {
|
||||
int nodeIdx = skinJoints.get(j).getAsInt();
|
||||
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)) {
|
||||
skinJointRemap[j] = filteredJointNodes.size();
|
||||
filteredJointNodes.add(nodeIdx);
|
||||
} 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);
|
||||
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
|
||||
if (node.has("rotation")) {
|
||||
JsonArray r = node.getAsJsonArray("rotation");
|
||||
restRotations[j] = new Quaternionf(
|
||||
r.get(0).getAsFloat(), r.get(1).getAsFloat(),
|
||||
r.get(2).getAsFloat(), r.get(3).getAsFloat()
|
||||
r.get(0).getAsFloat(),
|
||||
r.get(1).getAsFloat(),
|
||||
r.get(2).getAsFloat(),
|
||||
r.get(3).getAsFloat()
|
||||
);
|
||||
} else {
|
||||
restRotations[j] = new Quaternionf(); // identity
|
||||
@@ -151,7 +166,9 @@ public final class GlbParser {
|
||||
if (node.has("translation")) {
|
||||
JsonArray t = node.getAsJsonArray("translation");
|
||||
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 {
|
||||
restTranslations[j] = new Vector3f();
|
||||
@@ -179,7 +196,12 @@ public final class GlbParser {
|
||||
Matrix4f[] inverseBindMatrices = new Matrix4f[jointCount];
|
||||
if (skin.has("inverseBindMatrices")) {
|
||||
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++) {
|
||||
int newJ = skinJointRemap[origJ];
|
||||
if (newJ >= 0) {
|
||||
@@ -200,7 +222,9 @@ public final class GlbParser {
|
||||
if (meshes != null) {
|
||||
for (int mi = 0; mi < meshes.size(); mi++) {
|
||||
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)) {
|
||||
targetMeshIdx = mi;
|
||||
}
|
||||
@@ -238,15 +262,27 @@ public final class GlbParser {
|
||||
|
||||
// -- Read this primitive's vertex data --
|
||||
float[] primPositions = GlbParserUtils.readFloatAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
attributes.get("POSITION").getAsInt()
|
||||
);
|
||||
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];
|
||||
float[] primTexCoords = attributes.has("TEXCOORD_0")
|
||||
? GlbParserUtils.readFloatAccessor(accessors, bufferViews, binData, attributes.get("TEXCOORD_0").getAsInt())
|
||||
: new float[primPositions.length / 3 * 2];
|
||||
? GlbParserUtils.readFloatAccessor(
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
attributes.get("TEXCOORD_0").getAsInt()
|
||||
)
|
||||
: new float[(primPositions.length / 3) * 2];
|
||||
|
||||
int primVertexCount = primPositions.length / 3;
|
||||
|
||||
@@ -254,13 +290,16 @@ public final class GlbParser {
|
||||
int[] primIndices;
|
||||
if (primitive.has("indices")) {
|
||||
primIndices = GlbParserUtils.readIntAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
primitive.get("indices").getAsInt()
|
||||
);
|
||||
} else {
|
||||
// Non-indexed: generate sequential indices
|
||||
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
|
||||
@@ -276,14 +315,19 @@ public final class GlbParser {
|
||||
|
||||
if (attributes.has("JOINTS_0")) {
|
||||
primJoints = GlbParserUtils.readIntAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
attributes.get("JOINTS_0").getAsInt()
|
||||
);
|
||||
// Remap vertex joint indices from original skin order to filtered order
|
||||
for (int i = 0; i < primJoints.length; i++) {
|
||||
int origIdx = primJoints[i];
|
||||
if (origIdx >= 0 && origIdx < skinJointRemap.length) {
|
||||
primJoints[i] = skinJointRemap[origIdx] >= 0 ? skinJointRemap[origIdx] : 0;
|
||||
primJoints[i] =
|
||||
skinJointRemap[origIdx] >= 0
|
||||
? skinJointRemap[origIdx]
|
||||
: 0;
|
||||
} else {
|
||||
primJoints[i] = 0;
|
||||
}
|
||||
@@ -291,7 +335,9 @@ public final class GlbParser {
|
||||
}
|
||||
if (attributes.has("WEIGHTS_0")) {
|
||||
primWeights = GlbParserUtils.readFloatAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
attributes.get("WEIGHTS_0").getAsInt()
|
||||
);
|
||||
}
|
||||
@@ -304,10 +350,18 @@ public final class GlbParser {
|
||||
matName = materialNames[matIdx];
|
||||
}
|
||||
}
|
||||
boolean isTintable = matName != null && matName.startsWith("tintable_");
|
||||
boolean isTintable =
|
||||
matName != null && matName.startsWith("tintable_");
|
||||
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);
|
||||
allNormals.add(primNormals);
|
||||
@@ -327,16 +381,26 @@ public final class GlbParser {
|
||||
|
||||
// Build union of all primitive indices (for backward-compat indices() accessor)
|
||||
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];
|
||||
int offset = 0;
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
// 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];
|
||||
normals = new float[0];
|
||||
texCoords = new float[0];
|
||||
@@ -352,12 +416,23 @@ public final class GlbParser {
|
||||
if (animations != null) {
|
||||
for (int ai = 0; ai < animations.size(); ai++) {
|
||||
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)
|
||||
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) {
|
||||
allClips.put(animName, clip);
|
||||
}
|
||||
@@ -365,19 +440,39 @@ public final class GlbParser {
|
||||
}
|
||||
|
||||
// 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={}",
|
||||
debugName, vertexCount, indices.length, jointCount, allClips.size());
|
||||
LOGGER.info(
|
||||
"[GltfPipeline] Parsed '{}': vertices={}, indices={}, joints={}, animations={}",
|
||||
debugName,
|
||||
vertexCount,
|
||||
indices.length,
|
||||
jointCount,
|
||||
allClips.size()
|
||||
);
|
||||
for (String name : allClips.keySet()) {
|
||||
LOGGER.debug("[GltfPipeline] animation: '{}'", name);
|
||||
}
|
||||
for (int j = 0; j < jointCount; j++) {
|
||||
Quaternionf rq = restRotations[j];
|
||||
Vector3f rt = restTranslations[j];
|
||||
LOGGER.debug(String.format("[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));
|
||||
LOGGER.debug(
|
||||
String.format(
|
||||
"[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)
|
||||
@@ -387,16 +482,28 @@ public final class GlbParser {
|
||||
if (j < animTrans.length && animTrans[j] != null) {
|
||||
Vector3f at = animTrans[j][0]; // first frame
|
||||
Vector3f rt = restTranslations[j];
|
||||
LOGGER.debug(String.format(
|
||||
"[GltfPipeline] joint[%d] '%s' has ANIM TRANSLATION: (%.4f,%.4f,%.4f) vs rest (%.4f,%.4f,%.4f) delta=(%.4f,%.4f,%.4f)",
|
||||
j, jointNames[j],
|
||||
at.x, at.y, at.z,
|
||||
rt.x, rt.y, rt.z,
|
||||
at.x - rt.x, at.y - rt.y, at.z - rt.z));
|
||||
LOGGER.debug(
|
||||
String.format(
|
||||
"[GltfPipeline] joint[%d] '%s' has ANIM TRANSLATION: (%.4f,%.4f,%.4f) vs rest (%.4f,%.4f,%.4f) delta=(%.4f,%.4f,%.4f)",
|
||||
j,
|
||||
jointNames[j],
|
||||
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 {
|
||||
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)
|
||||
@@ -409,10 +516,18 @@ public final class GlbParser {
|
||||
|
||||
// Build raw copies of ALL animation clips (before MC conversion)
|
||||
Map<String, GltfData.AnimationClip> rawAllClips = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, GltfData.AnimationClip> entry : allClips.entrySet()) {
|
||||
rawAllClips.put(entry.getKey(), GlbParserUtils.deepCopyClip(entry.getValue()));
|
||||
for (Map.Entry<
|
||||
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)
|
||||
// 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()) {
|
||||
GlbParserUtils.convertAnimationToMinecraftSpace(clip, jointCount);
|
||||
}
|
||||
convertToMinecraftSpace(positions, normals, restTranslations, restRotations,
|
||||
inverseBindMatrices, null, jointCount); // pass null — clips already converted above
|
||||
LOGGER.debug("[GltfPipeline] Converted all data to Minecraft coordinate space");
|
||||
convertToMinecraftSpace(
|
||||
positions,
|
||||
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(
|
||||
positions, normals, texCoords,
|
||||
indices, meshJoints, weights,
|
||||
jointNames, parentJointIndices,
|
||||
positions,
|
||||
normals,
|
||||
texCoords,
|
||||
indices,
|
||||
meshJoints,
|
||||
weights,
|
||||
jointNames,
|
||||
parentJointIndices,
|
||||
inverseBindMatrices,
|
||||
restRotations, restTranslations,
|
||||
restRotations,
|
||||
restTranslations,
|
||||
rawRestRotations,
|
||||
rawAnimClip,
|
||||
animClip,
|
||||
allClips, rawAllClips,
|
||||
allClips,
|
||||
rawAllClips,
|
||||
parsedPrimitives,
|
||||
vertexCount, jointCount
|
||||
vertexCount,
|
||||
jointCount
|
||||
);
|
||||
}
|
||||
|
||||
@@ -443,9 +575,11 @@ public final class GlbParser {
|
||||
|
||||
private static GltfData.AnimationClip parseAnimation(
|
||||
JsonObject animation,
|
||||
JsonArray accessors, JsonArray bufferViews,
|
||||
JsonArray accessors,
|
||||
JsonArray bufferViews,
|
||||
ByteBuffer binData,
|
||||
int[] nodeToJoint, int jointCount
|
||||
int[] nodeToJoint,
|
||||
int jointCount
|
||||
) {
|
||||
JsonArray channels = animation.getAsJsonArray("channels");
|
||||
JsonArray samplers = animation.getAsJsonArray("samplers");
|
||||
@@ -465,27 +599,35 @@ public final class GlbParser {
|
||||
String path = target.get("path").getAsString();
|
||||
|
||||
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 samplerIdx = channel.get("sampler").getAsInt();
|
||||
JsonObject sampler = samplers.get(samplerIdx).getAsJsonObject();
|
||||
|
||||
float[] times = GlbParserUtils.readFloatAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
sampler.get("input").getAsInt()
|
||||
);
|
||||
|
||||
if ("rotation".equals(path)) {
|
||||
float[] quats = GlbParserUtils.readFloatAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
sampler.get("output").getAsInt()
|
||||
);
|
||||
Quaternionf[] qArr = new Quaternionf[times.length];
|
||||
for (int i = 0; i < times.length; i++) {
|
||||
qArr[i] = new Quaternionf(
|
||||
quats[i * 4], quats[i * 4 + 1],
|
||||
quats[i * 4 + 2], quats[i * 4 + 3]
|
||||
quats[i * 4],
|
||||
quats[i * 4 + 1],
|
||||
quats[i * 4 + 2],
|
||||
quats[i * 4 + 3]
|
||||
);
|
||||
}
|
||||
rotJoints.add(jointIdx);
|
||||
@@ -493,13 +635,17 @@ public final class GlbParser {
|
||||
rotValues.add(qArr);
|
||||
} else if ("translation".equals(path)) {
|
||||
float[] vecs = GlbParserUtils.readFloatAccessor(
|
||||
accessors, bufferViews, binData,
|
||||
accessors,
|
||||
bufferViews,
|
||||
binData,
|
||||
sampler.get("output").getAsInt()
|
||||
);
|
||||
Vector3f[] tArr = new Vector3f[times.length];
|
||||
for (int i = 0; i < times.length; i++) {
|
||||
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);
|
||||
@@ -523,7 +669,8 @@ public final class GlbParser {
|
||||
Quaternionf[] vals = rotValues.get(i);
|
||||
rotations[jIdx] = new Quaternionf[frameCount];
|
||||
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);
|
||||
translations[jIdx] = new Vector3f[frameCount];
|
||||
for (int f = 0; f < frameCount; f++) {
|
||||
translations[jIdx][f] = f < vals.length
|
||||
? new Vector3f(vals[f])
|
||||
: new Vector3f(vals[vals.length - 1]);
|
||||
translations[jIdx][f] =
|
||||
f < vals.length
|
||||
? new Vector3f(vals[f])
|
||||
: new Vector3f(vals[vals.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Log translation channels found
|
||||
if (!transJoints.isEmpty()) {
|
||||
LOGGER.debug("[GltfPipeline] Animation has {} translation channel(s)",
|
||||
transJoints.size());
|
||||
LOGGER.debug(
|
||||
"[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 ----
|
||||
@@ -562,14 +717,17 @@ public final class GlbParser {
|
||||
* For matrices: M → C * M * C where C = diag(-1, -1, 1, 1)
|
||||
*/
|
||||
private static void convertToMinecraftSpace(
|
||||
float[] positions, float[] normals,
|
||||
Vector3f[] restTranslations, Quaternionf[] restRotations,
|
||||
float[] positions,
|
||||
float[] normals,
|
||||
Vector3f[] restTranslations,
|
||||
Quaternionf[] restRotations,
|
||||
Matrix4f[] inverseBindMatrices,
|
||||
GltfData.AnimationClip animClip, int jointCount
|
||||
GltfData.AnimationClip animClip,
|
||||
int jointCount
|
||||
) {
|
||||
// Vertex positions: negate X and Y
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -77,8 +77,10 @@ public final class GlbParserUtils {
|
||||
// ---- Accessor reading utilities ----
|
||||
|
||||
public static float[] readFloatAccessor(
|
||||
JsonArray accessors, JsonArray bufferViews,
|
||||
ByteBuffer binData, int accessorIdx
|
||||
JsonArray accessors,
|
||||
JsonArray bufferViews,
|
||||
ByteBuffer binData,
|
||||
int accessorIdx
|
||||
) {
|
||||
JsonObject accessor = accessors.get(accessorIdx).getAsJsonObject();
|
||||
int count = accessor.get("count").getAsInt();
|
||||
@@ -88,9 +90,14 @@ public final class GlbParserUtils {
|
||||
|
||||
int bvIdx = accessor.get("bufferView").getAsInt();
|
||||
JsonObject bv = bufferViews.get(bvIdx).getAsJsonObject();
|
||||
int byteOffset = (bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0)
|
||||
+ (accessor.has("byteOffset") ? accessor.get("byteOffset").getAsInt() : 0);
|
||||
int byteStride = bv.has("byteStride") ? bv.get("byteStride").getAsInt() : 0;
|
||||
int byteOffset =
|
||||
(bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0) +
|
||||
(accessor.has("byteOffset")
|
||||
? accessor.get("byteOffset").getAsInt()
|
||||
: 0);
|
||||
int byteStride = bv.has("byteStride")
|
||||
? bv.get("byteStride").getAsInt()
|
||||
: 0;
|
||||
|
||||
int totalElements = count * components;
|
||||
float[] result = new float[totalElements];
|
||||
@@ -102,7 +109,10 @@ public final class GlbParserUtils {
|
||||
int pos = byteOffset + i * stride;
|
||||
for (int c = 0; c < components; c++) {
|
||||
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(
|
||||
JsonArray accessors, JsonArray bufferViews,
|
||||
ByteBuffer binData, int accessorIdx
|
||||
JsonArray accessors,
|
||||
JsonArray bufferViews,
|
||||
ByteBuffer binData,
|
||||
int accessorIdx
|
||||
) {
|
||||
JsonObject accessor = accessors.get(accessorIdx).getAsJsonObject();
|
||||
int count = accessor.get("count").getAsInt();
|
||||
@@ -121,9 +133,14 @@ public final class GlbParserUtils {
|
||||
|
||||
int bvIdx = accessor.get("bufferView").getAsInt();
|
||||
JsonObject bv = bufferViews.get(bvIdx).getAsJsonObject();
|
||||
int byteOffset = (bv.has("byteOffset") ? bv.get("byteOffset").getAsInt() : 0)
|
||||
+ (accessor.has("byteOffset") ? accessor.get("byteOffset").getAsInt() : 0);
|
||||
int byteStride = bv.has("byteStride") ? bv.get("byteStride").getAsInt() : 0;
|
||||
int byteOffset =
|
||||
(bv.has("byteOffset") ? bv.get("byteOffset").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[] result = new int[totalElements];
|
||||
@@ -135,22 +152,31 @@ public final class GlbParserUtils {
|
||||
int pos = byteOffset + i * stride;
|
||||
for (int c = 0; c < components; c++) {
|
||||
binData.position(pos + c * componentSize);
|
||||
result[i * components + c] = readComponentAsInt(binData, componentType);
|
||||
result[i * components + c] = readComponentAsInt(
|
||||
binData,
|
||||
componentType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static float readComponentAsFloat(ByteBuffer buf, int componentType) {
|
||||
public static float readComponentAsFloat(
|
||||
ByteBuffer buf,
|
||||
int componentType
|
||||
) {
|
||||
return switch (componentType) {
|
||||
case FLOAT -> buf.getFloat();
|
||||
case BYTE -> buf.get() / 127.0f;
|
||||
case UNSIGNED_BYTE -> (buf.get() & 0xFF) / 255.0f;
|
||||
case SHORT -> buf.getShort() / 32767.0f;
|
||||
case UNSIGNED_SHORT -> (buf.getShort() & 0xFFFF) / 65535.0f;
|
||||
case UNSIGNED_INT -> (buf.getInt() & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL;
|
||||
default -> throw new IllegalArgumentException("Unknown component type: " + componentType);
|
||||
case UNSIGNED_INT -> (buf.getInt() & 0xFFFFFFFFL) /
|
||||
(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_INT -> buf.getInt();
|
||||
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 "VEC4" -> 4;
|
||||
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 SHORT, UNSIGNED_SHORT -> 2;
|
||||
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).
|
||||
*/
|
||||
public static GltfData.AnimationClip deepCopyClip(GltfData.AnimationClip clip) {
|
||||
Quaternionf[][] rawRotations = new Quaternionf[clip.rotations().length][];
|
||||
public static GltfData.AnimationClip deepCopyClip(
|
||||
GltfData.AnimationClip clip
|
||||
) {
|
||||
Quaternionf[][] rawRotations =
|
||||
new Quaternionf[clip.rotations().length][];
|
||||
for (int j = 0; j < clip.rotations().length; j++) {
|
||||
if (clip.rotations()[j] != null) {
|
||||
rawRotations[j] = new Quaternionf[clip.rotations()[j].length];
|
||||
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][];
|
||||
for (int j = 0; j < clip.translations().length; j++) {
|
||||
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++) {
|
||||
rawTranslations[j][f] = new Vector3f(clip.translations()[j][f]);
|
||||
rawTranslations[j][f] = new Vector3f(
|
||||
clip.translations()[j][f]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new GltfData.AnimationClip(
|
||||
clip.timestamps().clone(), rawRotations, rawTranslations,
|
||||
clip.timestamps().clone(),
|
||||
rawRotations,
|
||||
rawTranslations,
|
||||
clip.frameCount()
|
||||
);
|
||||
}
|
||||
@@ -225,7 +267,10 @@ public final class GlbParserUtils {
|
||||
* Convert an animation clip's rotations and translations to MC space.
|
||||
* 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;
|
||||
|
||||
Quaternionf[][] rotations = clip.rotations();
|
||||
|
||||
@@ -13,7 +13,6 @@ import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
@@ -22,6 +21,7 @@ import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* V2 Animation Applier -- manages dual-layer animation for V2 bondage items.
|
||||
@@ -59,22 +59,23 @@ public final class GltfAnimationApplier {
|
||||
* Keyed by "animSource#context#ownedPartsHash".
|
||||
* 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.
|
||||
* 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. */
|
||||
private static final Set<String> failedLoadKeys = ConcurrentHashMap.newKeySet();
|
||||
private static final Set<String> failedLoadKeys =
|
||||
ConcurrentHashMap.newKeySet();
|
||||
|
||||
private GltfAnimationApplier() {}
|
||||
|
||||
// ========================================
|
||||
// INIT (legacy)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Legacy init method -- called by GltfClientSetup.
|
||||
@@ -84,9 +85,7 @@ public final class GltfAnimationApplier {
|
||||
// No-op: animation layers are managed by BondageAnimationManager
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// V2 DUAL-LAYER API
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return true if the item layer animation was applied successfully
|
||||
*/
|
||||
public static boolean applyV2Animation(LivingEntity entity, ResourceLocation modelLoc,
|
||||
@Nullable ResourceLocation animationSource,
|
||||
AnimationContext context, RegionBoneMapper.BoneOwnership ownership) {
|
||||
public static boolean applyV2Animation(
|
||||
LivingEntity entity,
|
||||
ResourceLocation modelLoc,
|
||||
@Nullable ResourceLocation animationSource,
|
||||
AnimationContext context,
|
||||
RegionBoneMapper.BoneOwnership ownership
|
||||
) {
|
||||
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
|
||||
String ownedKey = canonicalPartsKey(ownership.thisParts());
|
||||
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.
|
||||
// Only free parts remain enabled on context.
|
||||
KeyframeAnimation contextAnim = ContextAnimationFactory.create(
|
||||
context, ownership.disabledOnContext());
|
||||
context,
|
||||
ownership.disabledOnContext()
|
||||
);
|
||||
if (contextAnim != null) {
|
||||
BondageAnimationManager.playContext(entity, contextAnim);
|
||||
}
|
||||
@@ -151,18 +157,31 @@ public final class GltfAnimationApplier {
|
||||
|
||||
KeyframeAnimation itemAnim = itemAnimCache.get(itemCacheKey);
|
||||
if (itemAnim == null) {
|
||||
GltfData animData = GlbAnimationResolver.resolveAnimationData(modelLoc, animationSource);
|
||||
GltfData animData = GlbAnimationResolver.resolveAnimationData(
|
||||
modelLoc,
|
||||
animationSource
|
||||
);
|
||||
if (animData == null) {
|
||||
LOGGER.warn("[GltfPipeline] Failed to load animation GLB: {}", animSource);
|
||||
LOGGER.warn(
|
||||
"[GltfPipeline] Failed to load animation GLB: {}",
|
||||
animSource
|
||||
);
|
||||
failedLoadKeys.add(itemCacheKey);
|
||||
activeStateKeys.put(entity.getUUID(), stateKey);
|
||||
return false;
|
||||
}
|
||||
// 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
|
||||
itemAnim = GltfPoseConverter.convertSelective(
|
||||
animData, glbAnimName, ownership.thisParts(), ownership.enabledParts());
|
||||
animData,
|
||||
glbAnimName,
|
||||
ownership.thisParts(),
|
||||
ownership.enabledParts()
|
||||
);
|
||||
itemAnimCache.put(itemCacheKey, itemAnim);
|
||||
}
|
||||
|
||||
@@ -185,16 +204,24 @@ public final class GltfAnimationApplier {
|
||||
* @param allOwnedParts union of all owned parts across all items
|
||||
* @return true if the composite animation was applied
|
||||
*/
|
||||
public static boolean applyMultiItemV2Animation(LivingEntity entity,
|
||||
List<RegionBoneMapper.V2ItemAnimInfo> items,
|
||||
AnimationContext context, Set<String> allOwnedParts) {
|
||||
public static boolean applyMultiItemV2Animation(
|
||||
LivingEntity entity,
|
||||
List<RegionBoneMapper.V2ItemAnimInfo> items,
|
||||
AnimationContext context,
|
||||
Set<String> allOwnedParts
|
||||
) {
|
||||
if (entity == null || items.isEmpty()) return false;
|
||||
|
||||
// Build composite state key
|
||||
StringBuilder keyBuilder = new StringBuilder();
|
||||
for (RegionBoneMapper.V2ItemAnimInfo item : items) {
|
||||
ResourceLocation src = item.animSource() != null ? item.animSource() : item.modelLoc();
|
||||
keyBuilder.append(src).append(':').append(canonicalPartsKey(item.ownedParts())).append(';');
|
||||
ResourceLocation src =
|
||||
item.animSource() != null ? item.animSource() : item.modelLoc();
|
||||
keyBuilder
|
||||
.append(src)
|
||||
.append(':')
|
||||
.append(canonicalPartsKey(item.ownedParts()))
|
||||
.append(';');
|
||||
}
|
||||
keyBuilder.append(context.name());
|
||||
String stateKey = keyBuilder.toString();
|
||||
@@ -205,7 +232,10 @@ public final class GltfAnimationApplier {
|
||||
}
|
||||
|
||||
// === Layer 1: Context animation ===
|
||||
KeyframeAnimation contextAnim = ContextAnimationFactory.create(context, allOwnedParts);
|
||||
KeyframeAnimation contextAnim = ContextAnimationFactory.create(
|
||||
context,
|
||||
allOwnedParts
|
||||
);
|
||||
if (contextAnim != null) {
|
||||
BondageAnimationManager.playContext(entity, contextAnim);
|
||||
}
|
||||
@@ -222,7 +252,8 @@ public final class GltfAnimationApplier {
|
||||
if (compositeAnim == null) {
|
||||
KeyframeAnimation.AnimationBuilder builder =
|
||||
new KeyframeAnimation.AnimationBuilder(
|
||||
dev.kosmx.playerAnim.core.data.AnimationFormat.JSON_EMOTECRAFT);
|
||||
dev.kosmx.playerAnim.core.data.AnimationFormat.JSON_EMOTECRAFT
|
||||
);
|
||||
builder.beginTick = 0;
|
||||
builder.endTick = 1;
|
||||
builder.stopTick = 1;
|
||||
@@ -234,15 +265,27 @@ public final class GltfAnimationApplier {
|
||||
|
||||
for (int i = 0; i < items.size(); 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) {
|
||||
LOGGER.warn("[GltfPipeline] Failed to load GLB for multi-item: {}", animSource);
|
||||
LOGGER.warn(
|
||||
"[GltfPipeline] Failed to load GLB for multi-item: {}",
|
||||
animSource
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
String glbAnimName = GlbAnimationResolver.resolve(animData, context);
|
||||
String glbAnimName = GlbAnimationResolver.resolve(
|
||||
animData,
|
||||
context
|
||||
);
|
||||
GltfData.AnimationClip rawClip;
|
||||
if (glbAnimName != null) {
|
||||
rawClip = animData.getRawAnimation(glbAnimName);
|
||||
@@ -257,7 +300,9 @@ public final class GltfAnimationApplier {
|
||||
// if the item declares per-animation bone filtering.
|
||||
Set<String> effectiveParts = item.ownedParts();
|
||||
if (glbAnimName != null && !item.animationBones().isEmpty()) {
|
||||
Set<String> override = item.animationBones().get(glbAnimName);
|
||||
Set<String> override = item
|
||||
.animationBones()
|
||||
.get(glbAnimName);
|
||||
if (override != null) {
|
||||
Set<String> filtered = new HashSet<>(override);
|
||||
filtered.retainAll(item.ownedParts());
|
||||
@@ -268,11 +313,20 @@ public final class GltfAnimationApplier {
|
||||
}
|
||||
|
||||
GltfPoseConverter.addBonesToBuilder(
|
||||
builder, animData, rawClip, effectiveParts);
|
||||
builder,
|
||||
animData,
|
||||
rawClip,
|
||||
effectiveParts
|
||||
);
|
||||
anyLoaded = true;
|
||||
|
||||
LOGGER.debug("[GltfPipeline] Multi-item: {} -> owned={}, effective={}, anim={}",
|
||||
animSource, item.ownedParts(), effectiveParts, glbAnimName);
|
||||
LOGGER.debug(
|
||||
"[GltfPipeline] Multi-item: {} -> owned={}, effective={}, anim={}",
|
||||
animSource,
|
||||
item.ownedParts(),
|
||||
effectiveParts,
|
||||
glbAnimName
|
||||
);
|
||||
}
|
||||
|
||||
if (!anyLoaded) {
|
||||
@@ -284,9 +338,19 @@ public final class GltfAnimationApplier {
|
||||
// Enable only owned parts on the item layer.
|
||||
// Free parts (head, body, etc. not owned by any item) are disabled here
|
||||
// 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) {
|
||||
KeyframeAnimation.StateCollection part = getPartByName(builder, partName);
|
||||
KeyframeAnimation.StateCollection part = getPartByName(
|
||||
builder,
|
||||
partName
|
||||
);
|
||||
if (part != null) {
|
||||
if (allOwnedParts.contains(partName)) {
|
||||
part.fullyEnablePart(false);
|
||||
@@ -305,9 +369,7 @@ public final class GltfAnimationApplier {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CLEAR / QUERY
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Clear all V2 animation layers from an entity and remove tracking.
|
||||
@@ -342,9 +404,7 @@ public final class GltfAnimationApplier {
|
||||
activeStateKeys.remove(entityId);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CACHE MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Invalidate all cached item animations and tracking state.
|
||||
@@ -373,9 +433,7 @@ public final class GltfAnimationApplier {
|
||||
ContextAnimationFactory.clearCache();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// LEGACY F9 DEBUG TOGGLE
|
||||
// ========================================
|
||||
|
||||
private static boolean debugEnabled = false;
|
||||
|
||||
@@ -386,19 +444,29 @@ public final class GltfAnimationApplier {
|
||||
*/
|
||||
public static void toggle() {
|
||||
debugEnabled = !debugEnabled;
|
||||
LOGGER.info("[GltfPipeline] Debug toggle: {}", debugEnabled ? "ON" : "OFF");
|
||||
LOGGER.info(
|
||||
"[GltfPipeline] Debug toggle: {}",
|
||||
debugEnabled ? "ON" : "OFF"
|
||||
);
|
||||
|
||||
AbstractClientPlayer player = Minecraft.getInstance().player;
|
||||
if (player == null) return;
|
||||
|
||||
if (debugEnabled) {
|
||||
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");
|
||||
RegionBoneMapper.BoneOwnership debugOwnership =
|
||||
new RegionBoneMapper.BoneOwnership(armParts, Set.of());
|
||||
applyV2Animation(player, modelLoc, null, AnimationContext.STAND_IDLE, debugOwnership);
|
||||
applyV2Animation(
|
||||
player,
|
||||
modelLoc,
|
||||
null,
|
||||
AnimationContext.STAND_IDLE,
|
||||
debugOwnership
|
||||
);
|
||||
} else {
|
||||
clearV2Animation(player);
|
||||
}
|
||||
@@ -411,16 +479,17 @@ public final class GltfAnimationApplier {
|
||||
return debugEnabled;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// INTERNAL
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Build cache key for item-layer animations.
|
||||
* Format: "animSource#contextName#sortedParts"
|
||||
*/
|
||||
private static String buildItemCacheKey(ResourceLocation animSource,
|
||||
AnimationContext context, String partsKey) {
|
||||
private static String buildItemCacheKey(
|
||||
ResourceLocation animSource,
|
||||
AnimationContext context,
|
||||
String 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.
|
||||
*/
|
||||
private static KeyframeAnimation.StateCollection getPartByName(
|
||||
KeyframeAnimation.AnimationBuilder builder, String name) {
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
String name
|
||||
) {
|
||||
return switch (name) {
|
||||
case "head" -> builder.head;
|
||||
case "body" -> builder.body;
|
||||
|
||||
@@ -20,16 +20,22 @@ public final class GltfBoneMapper {
|
||||
|
||||
/** Lower bones that represent bend (elbow/knee) */
|
||||
private static final Set<String> LOWER_BONES = Set.of(
|
||||
"leftLowerArm", "rightLowerArm",
|
||||
"leftLowerLeg", "rightLowerLeg"
|
||||
"leftLowerArm",
|
||||
"rightLowerArm",
|
||||
"leftLowerLeg",
|
||||
"rightLowerLeg"
|
||||
);
|
||||
|
||||
/** Maps lower bone name -> corresponding upper bone name */
|
||||
private static final Map<String, String> LOWER_TO_UPPER = Map.of(
|
||||
"leftLowerArm", "leftUpperArm",
|
||||
"rightLowerArm", "rightUpperArm",
|
||||
"leftLowerLeg", "leftUpperLeg",
|
||||
"rightLowerLeg", "rightUpperLeg"
|
||||
"leftLowerArm",
|
||||
"leftUpperArm",
|
||||
"rightLowerArm",
|
||||
"rightUpperArm",
|
||||
"leftLowerLeg",
|
||||
"leftUpperLeg",
|
||||
"rightLowerLeg",
|
||||
"rightUpperLeg"
|
||||
);
|
||||
|
||||
static {
|
||||
@@ -55,7 +61,10 @@ public final class GltfBoneMapper {
|
||||
* @param boneName glTF bone name
|
||||
* @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);
|
||||
if (partName == null) return null;
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ import org.apache.logging.log4j.Logger;
|
||||
public final class GltfCache {
|
||||
|
||||
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() {}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.tiedup.remake.v2.bondage.datadriven.DataDrivenItemReloadListener;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
|
||||
import net.minecraft.util.profiling.ProfilerFiller;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.EntityRenderersEvent;
|
||||
@@ -17,7 +18,6 @@ import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
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.Logger;
|
||||
|
||||
@@ -59,7 +59,9 @@ public final class GltfClientSetup {
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRegisterKeybindings(RegisterKeyMappingsEvent event) {
|
||||
public static void onRegisterKeybindings(
|
||||
RegisterKeyMappingsEvent event
|
||||
) {
|
||||
event.register(TOGGLE_KEY);
|
||||
LOGGER.info("[GltfPipeline] Keybind registered: F9");
|
||||
}
|
||||
@@ -71,16 +73,24 @@ public final class GltfClientSetup {
|
||||
var defaultRenderer = event.getSkin("default");
|
||||
if (defaultRenderer instanceof PlayerRenderer playerRenderer) {
|
||||
playerRenderer.addLayer(new GltfRenderLayer(playerRenderer));
|
||||
playerRenderer.addLayer(new V2BondageRenderLayer<>(playerRenderer));
|
||||
LOGGER.info("[GltfPipeline] Render layers added to 'default' player renderer");
|
||||
playerRenderer.addLayer(
|
||||
new V2BondageRenderLayer<>(playerRenderer)
|
||||
);
|
||||
LOGGER.info(
|
||||
"[GltfPipeline] Render layers added to 'default' player renderer"
|
||||
);
|
||||
}
|
||||
|
||||
// Add both layers to slim player renderer (Alex)
|
||||
var slimRenderer = event.getSkin("slim");
|
||||
if (slimRenderer instanceof PlayerRenderer playerRenderer) {
|
||||
playerRenderer.addLayer(new GltfRenderLayer(playerRenderer));
|
||||
playerRenderer.addLayer(new V2BondageRenderLayer<>(playerRenderer));
|
||||
LOGGER.info("[GltfPipeline] Render layers added to 'slim' player renderer");
|
||||
playerRenderer.addLayer(
|
||||
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.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onRegisterReloadListeners(RegisterClientReloadListenersEvent event) {
|
||||
event.registerReloadListener(new SimplePreparableReloadListener<Void>() {
|
||||
@Override
|
||||
protected Void prepare(ResourceManager resourceManager, ProfilerFiller profiler) {
|
||||
return null;
|
||||
}
|
||||
public static void onRegisterReloadListeners(
|
||||
RegisterClientReloadListenersEvent event
|
||||
) {
|
||||
event.registerReloadListener(
|
||||
new SimplePreparableReloadListener<Void>() {
|
||||
@Override
|
||||
protected Void prepare(
|
||||
ResourceManager resourceManager,
|
||||
ProfilerFiller profiler
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void apply(Void nothing, ResourceManager resourceManager, ProfilerFiller profiler) {
|
||||
GltfCache.clearCache();
|
||||
GltfAnimationApplier.invalidateCache();
|
||||
GltfMeshRenderer.clearRenderTypeCache();
|
||||
// Reload context GLB animations from resource packs FIRST,
|
||||
// then clear the factory cache so it rebuilds against the
|
||||
// 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");
|
||||
@Override
|
||||
protected void apply(
|
||||
Void nothing,
|
||||
ResourceManager resourceManager,
|
||||
ProfilerFiller profiler
|
||||
) {
|
||||
GltfCache.clearCache();
|
||||
GltfAnimationApplier.invalidateCache();
|
||||
GltfMeshRenderer.clearRenderTypeCache();
|
||||
// Reload context GLB animations from resource packs FIRST,
|
||||
// then clear the factory cache so it rebuilds against the
|
||||
// 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");
|
||||
|
||||
// Data-driven bondage item definitions (tiedup_items/*.json)
|
||||
event.registerReloadListener(new DataDrivenItemReloadListener());
|
||||
LOGGER.info("[GltfPipeline] Data-driven item reload listener registered");
|
||||
LOGGER.info(
|
||||
"[GltfPipeline] Data-driven item reload listener registered"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,14 +21,14 @@ import org.joml.Vector3f;
|
||||
public final class GltfData {
|
||||
|
||||
// -- Mesh geometry (flattened arrays) --
|
||||
private final float[] positions; // VEC3, length = vertexCount * 3
|
||||
private final float[] normals; // VEC3, length = vertexCount * 3
|
||||
private final float[] texCoords; // VEC2, length = vertexCount * 2
|
||||
private final int[] indices; // triangle indices
|
||||
private final float[] positions; // VEC3, length = vertexCount * 3
|
||||
private final float[] normals; // VEC3, length = vertexCount * 3
|
||||
private final float[] texCoords; // VEC2, length = vertexCount * 2
|
||||
private final int[] indices; // triangle indices
|
||||
|
||||
// -- Skinning data (per-vertex, 4 influences) --
|
||||
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 int[] joints; // 4 joint indices per vertex, length = vertexCount * 4
|
||||
private final float[] weights; // 4 weights per vertex, length = vertexCount * 4
|
||||
|
||||
// -- Bone hierarchy (MC-converted for skinning) --
|
||||
private final String[] jointNames;
|
||||
@@ -39,6 +39,7 @@ public final class GltfData {
|
||||
|
||||
// -- Raw glTF rotations (unconverted, for pose conversion) --
|
||||
private final Quaternionf[] rawGltfRestRotations;
|
||||
|
||||
@Nullable
|
||||
private final AnimationClip rawGltfAnimation;
|
||||
|
||||
@@ -47,8 +48,8 @@ public final class GltfData {
|
||||
private final AnimationClip animation;
|
||||
|
||||
// -- Multiple named animations --
|
||||
private final Map<String, AnimationClip> namedAnimations; // MC-converted
|
||||
private final Map<String, AnimationClip> rawNamedAnimations; // raw glTF space
|
||||
private final Map<String, AnimationClip> namedAnimations; // MC-converted
|
||||
private final Map<String, AnimationClip> rawNamedAnimations; // raw glTF space
|
||||
|
||||
// -- Per-primitive material/tint info --
|
||||
private final List<Primitive> primitives;
|
||||
@@ -61,18 +62,25 @@ public final class GltfData {
|
||||
* Full constructor with multiple named animations and per-primitive data.
|
||||
*/
|
||||
public GltfData(
|
||||
float[] positions, float[] normals, float[] texCoords,
|
||||
int[] indices, int[] joints, float[] weights,
|
||||
String[] jointNames, int[] parentJointIndices,
|
||||
float[] positions,
|
||||
float[] normals,
|
||||
float[] texCoords,
|
||||
int[] indices,
|
||||
int[] joints,
|
||||
float[] weights,
|
||||
String[] jointNames,
|
||||
int[] parentJointIndices,
|
||||
Matrix4f[] inverseBindMatrices,
|
||||
Quaternionf[] restRotations, Vector3f[] restTranslations,
|
||||
Quaternionf[] restRotations,
|
||||
Vector3f[] restTranslations,
|
||||
Quaternionf[] rawGltfRestRotations,
|
||||
@Nullable AnimationClip rawGltfAnimation,
|
||||
@Nullable AnimationClip animation,
|
||||
Map<String, AnimationClip> namedAnimations,
|
||||
Map<String, AnimationClip> rawNamedAnimations,
|
||||
List<Primitive> primitives,
|
||||
int vertexCount, int jointCount
|
||||
int vertexCount,
|
||||
int jointCount
|
||||
) {
|
||||
this.positions = positions;
|
||||
this.normals = normals;
|
||||
@@ -88,8 +96,12 @@ public final class GltfData {
|
||||
this.rawGltfRestRotations = rawGltfRestRotations;
|
||||
this.rawGltfAnimation = rawGltfAnimation;
|
||||
this.animation = animation;
|
||||
this.namedAnimations = Collections.unmodifiableMap(new LinkedHashMap<>(namedAnimations));
|
||||
this.rawNamedAnimations = Collections.unmodifiableMap(new LinkedHashMap<>(rawNamedAnimations));
|
||||
this.namedAnimations = Collections.unmodifiableMap(
|
||||
new LinkedHashMap<>(namedAnimations)
|
||||
);
|
||||
this.rawNamedAnimations = Collections.unmodifiableMap(
|
||||
new LinkedHashMap<>(rawNamedAnimations)
|
||||
);
|
||||
this.primitives = List.copyOf(primitives);
|
||||
this.vertexCount = vertexCount;
|
||||
this.jointCount = jointCount;
|
||||
@@ -99,81 +111,175 @@ public final class GltfData {
|
||||
* Legacy constructor for backward compatibility (single animation only).
|
||||
*/
|
||||
public GltfData(
|
||||
float[] positions, float[] normals, float[] texCoords,
|
||||
int[] indices, int[] joints, float[] weights,
|
||||
String[] jointNames, int[] parentJointIndices,
|
||||
float[] positions,
|
||||
float[] normals,
|
||||
float[] texCoords,
|
||||
int[] indices,
|
||||
int[] joints,
|
||||
float[] weights,
|
||||
String[] jointNames,
|
||||
int[] parentJointIndices,
|
||||
Matrix4f[] inverseBindMatrices,
|
||||
Quaternionf[] restRotations, Vector3f[] restTranslations,
|
||||
Quaternionf[] restRotations,
|
||||
Vector3f[] restTranslations,
|
||||
Quaternionf[] rawGltfRestRotations,
|
||||
@Nullable AnimationClip rawGltfAnimation,
|
||||
@Nullable AnimationClip animation,
|
||||
int vertexCount, int jointCount
|
||||
int vertexCount,
|
||||
int jointCount
|
||||
) {
|
||||
this(positions, normals, texCoords, indices, joints, weights,
|
||||
jointNames, parentJointIndices, inverseBindMatrices,
|
||||
restRotations, restTranslations, rawGltfRestRotations,
|
||||
rawGltfAnimation, animation,
|
||||
new LinkedHashMap<>(), new LinkedHashMap<>(),
|
||||
this(
|
||||
positions,
|
||||
normals,
|
||||
texCoords,
|
||||
indices,
|
||||
joints,
|
||||
weights,
|
||||
jointNames,
|
||||
parentJointIndices,
|
||||
inverseBindMatrices,
|
||||
restRotations,
|
||||
restTranslations,
|
||||
rawGltfRestRotations,
|
||||
rawGltfAnimation,
|
||||
animation,
|
||||
new LinkedHashMap<>(),
|
||||
new LinkedHashMap<>(),
|
||||
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
|
||||
public AnimationClip rawGltfAnimation() { return rawGltfAnimation; }
|
||||
public AnimationClip rawGltfAnimation() {
|
||||
return rawGltfAnimation;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AnimationClip animation() { return animation; }
|
||||
public int vertexCount() { return vertexCount; }
|
||||
public int jointCount() { return jointCount; }
|
||||
public AnimationClip animation() {
|
||||
return animation;
|
||||
}
|
||||
|
||||
public int vertexCount() {
|
||||
return vertexCount;
|
||||
}
|
||||
|
||||
public int jointCount() {
|
||||
return jointCount;
|
||||
}
|
||||
|
||||
/** 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"). */
|
||||
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. */
|
||||
@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. */
|
||||
@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.
|
||||
*/
|
||||
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
|
||||
private final Vector3f[][] translations; // [jointIndex][frameIndex], null if no anim
|
||||
private final Vector3f[][] translations; // [jointIndex][frameIndex], null if no anim
|
||||
|
||||
private final int frameCount;
|
||||
|
||||
public AnimationClip(float[] timestamps, Quaternionf[][] rotations,
|
||||
@Nullable Vector3f[][] translations, int frameCount) {
|
||||
public AnimationClip(
|
||||
float[] timestamps,
|
||||
Quaternionf[][] rotations,
|
||||
@Nullable Vector3f[][] translations,
|
||||
int frameCount
|
||||
) {
|
||||
this.timestamps = timestamps;
|
||||
this.rotations = rotations;
|
||||
this.translations = translations;
|
||||
this.frameCount = frameCount;
|
||||
}
|
||||
|
||||
public float[] timestamps() { return timestamps; }
|
||||
public Quaternionf[][] rotations() { return rotations; }
|
||||
public float[] timestamps() {
|
||||
return timestamps;
|
||||
}
|
||||
|
||||
public Quaternionf[][] rotations() {
|
||||
return rotations;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Vector3f[][] translations() { return translations; }
|
||||
public int frameCount() { return frameCount; }
|
||||
public Vector3f[][] translations() {
|
||||
return translations;
|
||||
}
|
||||
|
||||
public int frameCount() {
|
||||
return frameCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,7 +60,9 @@ public final class GltfLiveBoneReader {
|
||||
* @return array of joint matrices ready for skinning, or null on failure
|
||||
*/
|
||||
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;
|
||||
|
||||
@@ -83,14 +85,19 @@ public final class GltfLiveBoneReader {
|
||||
if (GltfBoneMapper.isLowerBone(boneName)) {
|
||||
// --- Lower bone: reconstruct from bend values ---
|
||||
localRot = computeLowerBoneLocalRotation(
|
||||
boneName, j, restRotations, emote
|
||||
boneName,
|
||||
j,
|
||||
restRotations,
|
||||
emote
|
||||
);
|
||||
} else if (hasUniqueModelPart(boneName)) {
|
||||
// --- Upper bone with a unique ModelPart ---
|
||||
ModelPart part = GltfBoneMapper.getModelPart(model, boneName);
|
||||
if (part != null) {
|
||||
localRot = computeUpperBoneLocalRotation(
|
||||
part, j, restRotations
|
||||
part,
|
||||
j,
|
||||
restRotations
|
||||
);
|
||||
} else {
|
||||
// Fallback: use rest rotation
|
||||
@@ -108,14 +115,17 @@ public final class GltfLiveBoneReader {
|
||||
|
||||
// Compose with parent to get world transform
|
||||
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 {
|
||||
worldTransforms[j] = new Matrix4f(local);
|
||||
}
|
||||
|
||||
// Final joint matrix = worldTransform * inverseBindMatrix
|
||||
jointMatrices[j] = new Matrix4f(worldTransforms[j])
|
||||
.mul(data.inverseBindMatrices()[j]);
|
||||
jointMatrices[j] = new Matrix4f(worldTransforms[j]).mul(
|
||||
data.inverseBindMatrices()[j]
|
||||
);
|
||||
}
|
||||
|
||||
return jointMatrices;
|
||||
@@ -138,11 +148,16 @@ public final class GltfLiveBoneReader {
|
||||
* the frame relationship.
|
||||
*/
|
||||
private static Quaternionf computeUpperBoneLocalRotation(
|
||||
ModelPart part, int jointIndex,
|
||||
ModelPart part,
|
||||
int jointIndex,
|
||||
Quaternionf[] restRotations
|
||||
) {
|
||||
// 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.
|
||||
return new Quaternionf(delta).mul(restRotations[jointIndex]);
|
||||
}
|
||||
@@ -160,7 +175,8 @@ public final class GltfLiveBoneReader {
|
||||
* No de-parenting needed — same reasoning as upper bones.
|
||||
*/
|
||||
private static Quaternionf computeLowerBoneLocalRotation(
|
||||
String boneName, int jointIndex,
|
||||
String boneName,
|
||||
int jointIndex,
|
||||
Quaternionf[] restRotations,
|
||||
AnimationApplier emote
|
||||
) {
|
||||
@@ -183,11 +199,16 @@ public final class GltfLiveBoneReader {
|
||||
float halfAngle = bendValue * 0.5f;
|
||||
float s = (float) Math.sin(halfAngle);
|
||||
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
|
||||
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
|
||||
// mesh uniformly. No need to read body rotation into joint matrices.
|
||||
return switch (boneName) {
|
||||
case "head" -> true;
|
||||
case "head" -> true;
|
||||
case "leftUpperArm" -> true;
|
||||
case "rightUpperArm"-> true;
|
||||
case "rightUpperArm" -> true;
|
||||
case "leftUpperLeg" -> true;
|
||||
case "rightUpperLeg"-> true;
|
||||
default -> false; // body, torso, lower bones, unknown
|
||||
case "rightUpperLeg" -> true;
|
||||
default -> false; // body, torso, lower bones, unknown
|
||||
};
|
||||
}
|
||||
|
||||
@@ -236,8 +257,11 @@ public final class GltfLiveBoneReader {
|
||||
try {
|
||||
return animated.playerAnimator_getAnimation();
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug("[GltfPipeline] Could not get AnimationApplier for {}: {}",
|
||||
entity.getClass().getSimpleName(), e.getMessage());
|
||||
LOGGER.debug(
|
||||
"[GltfPipeline] Could not get AnimationApplier for {}: {}",
|
||||
entity.getClass().getSimpleName(),
|
||||
e.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -25,13 +25,17 @@ import org.joml.Vector4f;
|
||||
public final class GltfMeshRenderer extends RenderStateShard {
|
||||
|
||||
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. */
|
||||
private static RenderType cachedDefaultRenderType;
|
||||
|
||||
/** 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() {
|
||||
super("tiedup_gltf_renderer", () -> {}, () -> {});
|
||||
@@ -61,15 +65,21 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
* @param texture the texture ResourceLocation
|
||||
* @return the cached or newly created RenderType
|
||||
*/
|
||||
private static RenderType getRenderTypeForTexture(ResourceLocation texture) {
|
||||
return RENDER_TYPE_CACHE.computeIfAbsent(texture,
|
||||
GltfMeshRenderer::createTriangleRenderType);
|
||||
private static RenderType getRenderTypeForTexture(
|
||||
ResourceLocation texture
|
||||
) {
|
||||
return RENDER_TYPE_CACHE.computeIfAbsent(
|
||||
texture,
|
||||
GltfMeshRenderer::createTriangleRenderType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
.setShaderState(RENDERTYPE_ENTITY_CUTOUT_NO_CULL_SHADER)
|
||||
.setTextureState(
|
||||
@@ -112,12 +122,22 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
* @param packedOverlay packed overlay value
|
||||
*/
|
||||
public static void renderSkinned(
|
||||
GltfData data, Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack, MultiBufferSource buffer,
|
||||
int packedLight, int packedOverlay
|
||||
GltfData data,
|
||||
Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack,
|
||||
MultiBufferSource buffer,
|
||||
int packedLight,
|
||||
int packedOverlay
|
||||
) {
|
||||
renderSkinnedInternal(data, jointMatrices, poseStack, buffer,
|
||||
packedLight, packedOverlay, getDefaultRenderType());
|
||||
renderSkinnedInternal(
|
||||
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
|
||||
*/
|
||||
public static void renderSkinned(
|
||||
GltfData data, Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack, MultiBufferSource buffer,
|
||||
int packedLight, int packedOverlay,
|
||||
GltfData data,
|
||||
Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack,
|
||||
MultiBufferSource buffer,
|
||||
int packedLight,
|
||||
int packedOverlay,
|
||||
ResourceLocation texture
|
||||
) {
|
||||
renderSkinnedInternal(data, jointMatrices, poseStack, buffer,
|
||||
packedLight, packedOverlay, getRenderTypeForTexture(texture));
|
||||
renderSkinnedInternal(
|
||||
data,
|
||||
jointMatrices,
|
||||
poseStack,
|
||||
buffer,
|
||||
packedLight,
|
||||
packedOverlay,
|
||||
getRenderTypeForTexture(texture)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal rendering implementation shared by both overloads.
|
||||
*/
|
||||
private static void renderSkinnedInternal(
|
||||
GltfData data, Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack, MultiBufferSource buffer,
|
||||
int packedLight, int packedOverlay,
|
||||
GltfData data,
|
||||
Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack,
|
||||
MultiBufferSource buffer,
|
||||
int packedLight,
|
||||
int packedOverlay,
|
||||
RenderType renderType
|
||||
) {
|
||||
Matrix4f pose = poseStack.last().pose();
|
||||
@@ -167,13 +200,22 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
|
||||
for (int idx : indices) {
|
||||
// Skin this vertex
|
||||
GltfSkinningEngine.skinVertex(data, idx, jointMatrices, outPos, outNormal, tmpPos, tmpNorm);
|
||||
GltfSkinningEngine.skinVertex(
|
||||
data,
|
||||
idx,
|
||||
jointMatrices,
|
||||
outPos,
|
||||
outNormal,
|
||||
tmpPos,
|
||||
tmpNorm
|
||||
);
|
||||
|
||||
// UV coordinates
|
||||
float u = texCoords[idx * 2];
|
||||
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)
|
||||
.uv(u, 1.0f - v)
|
||||
.overlayCoords(packedOverlay)
|
||||
@@ -205,9 +247,12 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
* @param tintColors channel name to RGB int (0xRRGGBB); empty map = white everywhere
|
||||
*/
|
||||
public static void renderSkinnedTinted(
|
||||
GltfData data, Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack, MultiBufferSource buffer,
|
||||
int packedLight, int packedOverlay,
|
||||
GltfData data,
|
||||
Matrix4f[] jointMatrices,
|
||||
PoseStack poseStack,
|
||||
MultiBufferSource buffer,
|
||||
int packedLight,
|
||||
int packedOverlay,
|
||||
RenderType renderType,
|
||||
Map<String, Integer> tintColors
|
||||
) {
|
||||
@@ -226,7 +271,9 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
|
||||
for (GltfData.Primitive prim : primitives) {
|
||||
// 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) {
|
||||
Integer colorInt = tintColors.get(prim.tintChannel());
|
||||
if (colorInt != null) {
|
||||
@@ -237,12 +284,21 @@ public final class GltfMeshRenderer extends RenderStateShard {
|
||||
}
|
||||
|
||||
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 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)
|
||||
.uv(u, 1.0f - v)
|
||||
.overlayCoords(packedOverlay)
|
||||
|
||||
@@ -5,11 +5,11 @@ import dev.kosmx.playerAnim.core.data.KeyframeAnimation;
|
||||
import dev.kosmx.playerAnim.core.util.Ease;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Quaternionf;
|
||||
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")
|
||||
* @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);
|
||||
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 convertClip(data, rawClip, "gltf_" + animationName);
|
||||
@@ -76,8 +82,12 @@ public final class GltfPoseConverter {
|
||||
* are only enabled if the GLB has keyframes for them
|
||||
* @return KeyframeAnimation with selective parts active
|
||||
*/
|
||||
public static KeyframeAnimation convertSelective(GltfData data, @Nullable String animationName,
|
||||
Set<String> ownedParts, Set<String> enabledParts) {
|
||||
public static KeyframeAnimation convertSelective(
|
||||
GltfData data,
|
||||
@Nullable String animationName,
|
||||
Set<String> ownedParts,
|
||||
Set<String> enabledParts
|
||||
) {
|
||||
GltfData.AnimationClip rawClip;
|
||||
String animName;
|
||||
if (animationName != null) {
|
||||
@@ -90,7 +100,13 @@ public final class GltfPoseConverter {
|
||||
if (rawClip == null) {
|
||||
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 enabledParts parts the item may animate (owned + free)
|
||||
*/
|
||||
private static KeyframeAnimation convertClipSelective(GltfData data, GltfData.AnimationClip rawClip,
|
||||
String animName, Set<String> ownedParts, Set<String> enabledParts) {
|
||||
private static KeyframeAnimation convertClipSelective(
|
||||
GltfData data,
|
||||
GltfData.AnimationClip rawClip,
|
||||
String animName,
|
||||
Set<String> ownedParts,
|
||||
Set<String> enabledParts
|
||||
) {
|
||||
KeyframeAnimation.AnimationBuilder builder =
|
||||
new KeyframeAnimation.AnimationBuilder(AnimationFormat.JSON_EMOTECRAFT);
|
||||
new KeyframeAnimation.AnimationBuilder(
|
||||
AnimationFormat.JSON_EMOTECRAFT
|
||||
);
|
||||
|
||||
builder.beginTick = 0;
|
||||
builder.endTick = 1;
|
||||
@@ -129,21 +152,27 @@ public final class GltfPoseConverter {
|
||||
|
||||
// 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.
|
||||
boolean hasExplicitAnim = rawClip != null && (
|
||||
(j < rawClip.rotations().length && rawClip.rotations()[j] != null)
|
||||
|| (rawClip.translations() != null
|
||||
&& j < rawClip.translations().length
|
||||
&& rawClip.translations()[j] != null)
|
||||
);
|
||||
boolean hasExplicitAnim =
|
||||
rawClip != null &&
|
||||
((j < rawClip.rotations().length &&
|
||||
rawClip.rotations()[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];
|
||||
|
||||
// delta_local = inverse(rest_q) * anim_q (in bone-local frame)
|
||||
Quaternionf deltaLocal = new Quaternionf(restQ).invert().mul(animQ);
|
||||
|
||||
// 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());
|
||||
|
||||
// Convert from glTF parent frame to MC model-def frame.
|
||||
@@ -168,7 +197,9 @@ public final class GltfPoseConverter {
|
||||
if (GltfBoneMapper.isLowerBone(boneName)) {
|
||||
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
|
||||
if (upperBone != null) {
|
||||
String upperPart = GltfBoneMapper.getAnimPartName(upperBone);
|
||||
String upperPart = GltfBoneMapper.getAnimPartName(
|
||||
upperBone
|
||||
);
|
||||
if (upperPart != null) {
|
||||
partsWithKeyframes.add(upperPart);
|
||||
}
|
||||
@@ -178,11 +209,21 @@ public final class GltfPoseConverter {
|
||||
}
|
||||
|
||||
// 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();
|
||||
LOGGER.debug("[GltfPipeline] Converted selective animation '{}' (owned: {}, enabled: {}, withKeyframes: {})",
|
||||
animName, ownedParts, enabledParts, partsWithKeyframes);
|
||||
LOGGER.debug(
|
||||
"[GltfPipeline] Converted selective animation '{}' (owned: {}, enabled: {}, withKeyframes: {})",
|
||||
animName,
|
||||
ownedParts,
|
||||
enabledParts,
|
||||
partsWithKeyframes
|
||||
);
|
||||
return anim;
|
||||
}
|
||||
|
||||
@@ -200,10 +241,11 @@ public final class GltfPoseConverter {
|
||||
* @return set of part names that received actual keyframe data from the GLB
|
||||
*/
|
||||
public static Set<String> addBonesToBuilder(
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
GltfData data, @Nullable GltfData.AnimationClip rawClip,
|
||||
Set<String> ownedParts) {
|
||||
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
GltfData data,
|
||||
@Nullable GltfData.AnimationClip rawClip,
|
||||
Set<String> ownedParts
|
||||
) {
|
||||
String[] jointNames = data.jointNames();
|
||||
Quaternionf[] rawRestRotations = data.rawGltfRestRotations();
|
||||
Set<String> partsWithKeyframes = new HashSet<>();
|
||||
@@ -221,23 +263,33 @@ public final class GltfPoseConverter {
|
||||
if (GltfBoneMapper.isLowerBone(boneName)) {
|
||||
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
|
||||
if (upperBone != null) {
|
||||
String upperPart = GltfBoneMapper.getAnimPartName(upperBone);
|
||||
if (upperPart == null || !ownedParts.contains(upperPart)) continue;
|
||||
String upperPart = GltfBoneMapper.getAnimPartName(
|
||||
upperBone
|
||||
);
|
||||
if (
|
||||
upperPart == null || !ownedParts.contains(upperPart)
|
||||
) continue;
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasExplicitAnim = rawClip != null && (
|
||||
(j < rawClip.rotations().length && rawClip.rotations()[j] != null)
|
||||
|| (rawClip.translations() != null
|
||||
&& j < rawClip.translations().length
|
||||
&& rawClip.translations()[j] != null)
|
||||
);
|
||||
boolean hasExplicitAnim =
|
||||
rawClip != null &&
|
||||
((j < rawClip.rotations().length &&
|
||||
rawClip.rotations()[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 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());
|
||||
|
||||
Quaternionf deltaQ = new Quaternionf(deltaParent);
|
||||
@@ -255,8 +307,12 @@ public final class GltfPoseConverter {
|
||||
if (GltfBoneMapper.isLowerBone(boneName)) {
|
||||
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
|
||||
if (upperBone != null) {
|
||||
String upperPart = GltfBoneMapper.getAnimPartName(upperBone);
|
||||
if (upperPart != null) partsWithKeyframes.add(upperPart);
|
||||
String upperPart = GltfBoneMapper.getAnimPartName(
|
||||
upperBone
|
||||
);
|
||||
if (upperPart != null) partsWithKeyframes.add(
|
||||
upperPart
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,16 +337,25 @@ public final class GltfPoseConverter {
|
||||
* @return a static looping KeyframeAnimation with all parts enabled
|
||||
*/
|
||||
public static KeyframeAnimation convertWithSkeleton(
|
||||
GltfData skeleton, GltfData.AnimationClip clip, String animName) {
|
||||
GltfData skeleton,
|
||||
GltfData.AnimationClip clip,
|
||||
String animName
|
||||
) {
|
||||
return convertClip(skeleton, clip, animName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 =
|
||||
new KeyframeAnimation.AnimationBuilder(AnimationFormat.JSON_EMOTECRAFT);
|
||||
new KeyframeAnimation.AnimationBuilder(
|
||||
AnimationFormat.JSON_EMOTECRAFT
|
||||
);
|
||||
|
||||
builder.beginTick = 0;
|
||||
builder.endTick = 1;
|
||||
@@ -307,7 +372,11 @@ public final class GltfPoseConverter {
|
||||
|
||||
if (!GltfBoneMapper.isKnownBone(boneName)) continue;
|
||||
|
||||
Quaternionf animQ = getRawAnimQuaternion(rawClip, rawRestRotations, j);
|
||||
Quaternionf animQ = getRawAnimQuaternion(
|
||||
rawClip,
|
||||
rawRestRotations,
|
||||
j
|
||||
);
|
||||
Quaternionf restQ = rawRestRotations[j];
|
||||
|
||||
// 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)
|
||||
// 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());
|
||||
|
||||
// Convert from glTF parent frame to MC model-def frame.
|
||||
@@ -324,12 +394,24 @@ public final class GltfPoseConverter {
|
||||
deltaQ.x = -deltaQ.x;
|
||||
deltaQ.y = -deltaQ.y;
|
||||
|
||||
LOGGER.debug(String.format(
|
||||
"[GltfPipeline] Bone '%s': restQ=(%.3f,%.3f,%.3f,%.3f) animQ=(%.3f,%.3f,%.3f,%.3f) deltaQ=(%.3f,%.3f,%.3f,%.3f)",
|
||||
boneName,
|
||||
restQ.x, restQ.y, restQ.z, restQ.w,
|
||||
animQ.x, animQ.y, animQ.z, animQ.w,
|
||||
deltaQ.x, deltaQ.y, deltaQ.z, deltaQ.w));
|
||||
LOGGER.debug(
|
||||
String.format(
|
||||
"[GltfPipeline] Bone '%s': restQ=(%.3f,%.3f,%.3f,%.3f) animQ=(%.3f,%.3f,%.3f,%.3f) deltaQ=(%.3f,%.3f,%.3f,%.3f)",
|
||||
boneName,
|
||||
restQ.x,
|
||||
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)) {
|
||||
convertLowerBone(builder, boneName, deltaQ);
|
||||
@@ -341,7 +423,10 @@ public final class GltfPoseConverter {
|
||||
builder.fullyEnableParts();
|
||||
|
||||
KeyframeAnimation anim = builder.build();
|
||||
LOGGER.debug("[GltfPipeline] Converted glTF animation '{}' to KeyframeAnimation", animName);
|
||||
LOGGER.debug(
|
||||
"[GltfPipeline] Converted glTF animation '{}' to KeyframeAnimation",
|
||||
animName
|
||||
);
|
||||
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.
|
||||
*/
|
||||
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
|
||||
&& rawClip.rotations()[jointIndex] != null) {
|
||||
if (
|
||||
rawClip != null &&
|
||||
jointIndex < rawClip.rotations().length &&
|
||||
rawClip.rotations()[jointIndex] != null
|
||||
) {
|
||||
return rawClip.rotations()[jointIndex][0]; // first frame
|
||||
}
|
||||
return rawRestRotations[jointIndex]; // fallback to rest
|
||||
@@ -361,29 +451,36 @@ public final class GltfPoseConverter {
|
||||
|
||||
private static void convertUpperBone(
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
String boneName, Quaternionf deltaQ
|
||||
String boneName,
|
||||
Quaternionf deltaQ
|
||||
) {
|
||||
// Decompose delta quaternion to Euler ZYX
|
||||
// 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)
|
||||
Vector3f euler = new Vector3f();
|
||||
deltaQ.getEulerAnglesZYX(euler);
|
||||
float pitch = euler.x; // X rotation (pitch)
|
||||
float yaw = euler.y; // Y rotation (yaw)
|
||||
float roll = euler.z; // Z rotation (roll)
|
||||
float pitch = euler.x; // X rotation (pitch)
|
||||
float yaw = euler.y; // Y rotation (yaw)
|
||||
float roll = euler.z; // Z rotation (roll)
|
||||
|
||||
LOGGER.debug(String.format(
|
||||
"[GltfPipeline] Upper bone '%s': pitch=%.1f° yaw=%.1f° roll=%.1f°",
|
||||
boneName,
|
||||
Math.toDegrees(pitch),
|
||||
Math.toDegrees(yaw),
|
||||
Math.toDegrees(roll)));
|
||||
LOGGER.debug(
|
||||
String.format(
|
||||
"[GltfPipeline] Upper bone '%s': pitch=%.1f° yaw=%.1f° roll=%.1f°",
|
||||
boneName,
|
||||
Math.toDegrees(pitch),
|
||||
Math.toDegrees(yaw),
|
||||
Math.toDegrees(roll)
|
||||
)
|
||||
);
|
||||
|
||||
// Get the StateCollection for this body part
|
||||
String animPart = GltfBoneMapper.getAnimPartName(boneName);
|
||||
if (animPart == null) return;
|
||||
|
||||
KeyframeAnimation.StateCollection part = getPartByName(builder, animPart);
|
||||
KeyframeAnimation.StateCollection part = getPartByName(
|
||||
builder,
|
||||
animPart
|
||||
);
|
||||
if (part == null) return;
|
||||
|
||||
part.pitch.addKeyFrame(0, pitch, Ease.CONSTANT);
|
||||
@@ -393,12 +490,12 @@ public final class GltfPoseConverter {
|
||||
|
||||
private static void convertLowerBone(
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
String boneName, Quaternionf deltaQ
|
||||
String boneName,
|
||||
Quaternionf deltaQ
|
||||
) {
|
||||
// Extract bend angle and axis from the delta quaternion
|
||||
float angle = 2.0f * (float) Math.acos(
|
||||
Math.min(1.0, Math.abs(deltaQ.w))
|
||||
);
|
||||
float angle =
|
||||
2.0f * (float) Math.acos(Math.min(1.0, Math.abs(deltaQ.w)));
|
||||
|
||||
// Determine bend direction from axis
|
||||
float bendDirection = 0.0f;
|
||||
@@ -411,11 +508,14 @@ public final class GltfPoseConverter {
|
||||
angle = -angle;
|
||||
}
|
||||
|
||||
LOGGER.debug(String.format(
|
||||
"[GltfPipeline] Lower bone '%s': bendAngle=%.1f° bendDir=%.1f°",
|
||||
boneName,
|
||||
Math.toDegrees(angle),
|
||||
Math.toDegrees(bendDirection)));
|
||||
LOGGER.debug(
|
||||
String.format(
|
||||
"[GltfPipeline] Lower bone '%s': bendAngle=%.1f° bendDir=%.1f°",
|
||||
boneName,
|
||||
Math.toDegrees(angle),
|
||||
Math.toDegrees(bendDirection)
|
||||
)
|
||||
);
|
||||
|
||||
// Apply bend to the upper bone's StateCollection
|
||||
String upperBone = GltfBoneMapper.getUpperBoneFor(boneName);
|
||||
@@ -424,7 +524,10 @@ public final class GltfPoseConverter {
|
||||
String animPart = GltfBoneMapper.getAnimPartName(upperBone);
|
||||
if (animPart == null) return;
|
||||
|
||||
KeyframeAnimation.StateCollection part = getPartByName(builder, animPart);
|
||||
KeyframeAnimation.StateCollection part = getPartByName(
|
||||
builder,
|
||||
animPart
|
||||
);
|
||||
if (part == null || !part.isBendable) return;
|
||||
|
||||
part.bend.addKeyFrame(0, angle, Ease.CONSTANT);
|
||||
@@ -432,7 +535,8 @@ public final class GltfPoseConverter {
|
||||
}
|
||||
|
||||
private static KeyframeAnimation.StateCollection getPartByName(
|
||||
KeyframeAnimation.AnimationBuilder builder, String name
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
String name
|
||||
) {
|
||||
return switch (name) {
|
||||
case "head" -> builder.head;
|
||||
@@ -461,17 +565,32 @@ public final class GltfPoseConverter {
|
||||
* @param partsWithKeyframes parts that received actual animation data from the GLB
|
||||
*/
|
||||
private static void enableSelectiveParts(
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
Set<String> ownedParts, Set<String> enabledParts,
|
||||
Set<String> partsWithKeyframes) {
|
||||
String[] allParts = {"head", "body", "rightArm", "leftArm", "rightLeg", "leftLeg"};
|
||||
KeyframeAnimation.AnimationBuilder builder,
|
||||
Set<String> ownedParts,
|
||||
Set<String> enabledParts,
|
||||
Set<String> partsWithKeyframes
|
||||
) {
|
||||
String[] allParts = {
|
||||
"head",
|
||||
"body",
|
||||
"rightArm",
|
||||
"leftArm",
|
||||
"rightLeg",
|
||||
"leftLeg",
|
||||
};
|
||||
for (String partName : allParts) {
|
||||
KeyframeAnimation.StateCollection part = getPartByName(builder, partName);
|
||||
KeyframeAnimation.StateCollection part = getPartByName(
|
||||
builder,
|
||||
partName
|
||||
);
|
||||
if (part != null) {
|
||||
if (ownedParts.contains(partName)) {
|
||||
// Always enable owned parts — the item controls these bones
|
||||
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
|
||||
part.fullyEnablePart(false);
|
||||
} else {
|
||||
|
||||
@@ -24,17 +24,22 @@ import org.joml.Matrix4f;
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class GltfRenderLayer
|
||||
extends RenderLayer<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>> {
|
||||
extends RenderLayer<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>>
|
||||
{
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("GltfPipeline");
|
||||
|
||||
private static final ResourceLocation CUFFS_MODEL =
|
||||
ResourceLocation.fromNamespaceAndPath(
|
||||
"tiedup", "models/gltf/v2/handcuffs/cuffs_prototype.glb"
|
||||
"tiedup",
|
||||
"models/gltf/v2/handcuffs/cuffs_prototype.glb"
|
||||
);
|
||||
|
||||
public GltfRenderLayer(
|
||||
RenderLayerParent<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>> renderer
|
||||
RenderLayerParent<
|
||||
AbstractClientPlayer,
|
||||
PlayerModel<AbstractClientPlayer>
|
||||
> renderer
|
||||
) {
|
||||
super(renderer);
|
||||
}
|
||||
@@ -71,7 +76,9 @@ public class GltfRenderLayer
|
||||
// Live path: read skeleton from HumanoidModel (after PlayerAnimator)
|
||||
PlayerModel<AbstractClientPlayer> parentModel = this.getParentModel();
|
||||
Matrix4f[] joints = GltfLiveBoneReader.computeJointMatricesFromModel(
|
||||
parentModel, data, entity
|
||||
parentModel,
|
||||
data,
|
||||
entity
|
||||
);
|
||||
if (joints == null) {
|
||||
// Fallback to GLB-internal path if live reading fails
|
||||
@@ -84,10 +91,15 @@ public class GltfRenderLayer
|
||||
poseStack.translate(0, ALIGNMENT_Y, 0);
|
||||
|
||||
GltfMeshRenderer.renderSkinned(
|
||||
data, joints, poseStack, buffer,
|
||||
data,
|
||||
joints,
|
||||
poseStack,
|
||||
buffer,
|
||||
packedLight,
|
||||
net.minecraft.client.renderer.entity.LivingEntityRenderer
|
||||
.getOverlayCoords(entity, 0.0f)
|
||||
net.minecraft.client.renderer.entity.LivingEntityRenderer.getOverlayCoords(
|
||||
entity,
|
||||
0.0f
|
||||
)
|
||||
);
|
||||
poseStack.popPose();
|
||||
}
|
||||
|
||||
@@ -43,7 +43,9 @@ public final class GltfSkinningEngine {
|
||||
* @return interpolated joint matrices ready for skinning
|
||||
*/
|
||||
public static Matrix4f[] computeJointMatricesAnimated(
|
||||
GltfData data, GltfData.AnimationClip clip, float time
|
||||
GltfData data,
|
||||
GltfData.AnimationClip clip,
|
||||
float time
|
||||
) {
|
||||
int jointCount = data.jointCount();
|
||||
Matrix4f[] jointMatrices = new Matrix4f[jointCount];
|
||||
@@ -59,14 +61,17 @@ public final class GltfSkinningEngine {
|
||||
|
||||
// Compose with parent
|
||||
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 {
|
||||
worldTransforms[j] = new Matrix4f(local);
|
||||
}
|
||||
|
||||
// Final joint matrix = worldTransform * inverseBindMatrix
|
||||
jointMatrices[j] = new Matrix4f(worldTransforms[j])
|
||||
.mul(data.inverseBindMatrices()[j]);
|
||||
jointMatrices[j] = new Matrix4f(worldTransforms[j]).mul(
|
||||
data.inverseBindMatrices()[j]
|
||||
);
|
||||
}
|
||||
|
||||
return jointMatrices;
|
||||
@@ -75,7 +80,10 @@ public final class GltfSkinningEngine {
|
||||
/**
|
||||
* 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();
|
||||
Matrix4f[] jointMatrices = new Matrix4f[jointCount];
|
||||
Matrix4f[] worldTransforms = new Matrix4f[jointCount];
|
||||
@@ -90,14 +98,17 @@ public final class GltfSkinningEngine {
|
||||
|
||||
// Compose with parent
|
||||
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 {
|
||||
worldTransforms[j] = new Matrix4f(local);
|
||||
}
|
||||
|
||||
// Final joint matrix = worldTransform * inverseBindMatrix
|
||||
jointMatrices[j] = new Matrix4f(worldTransforms[j])
|
||||
.mul(data.inverseBindMatrices()[j]);
|
||||
jointMatrices[j] = new Matrix4f(worldTransforms[j]).mul(
|
||||
data.inverseBindMatrices()[j]
|
||||
);
|
||||
}
|
||||
|
||||
return jointMatrices;
|
||||
@@ -107,9 +118,16 @@ public final class GltfSkinningEngine {
|
||||
* Get the animation rotation for a joint (MC-converted).
|
||||
* Falls back to rest rotation if no animation.
|
||||
*/
|
||||
private static Quaternionf getAnimRotation(GltfData data, GltfData.AnimationClip clip, int jointIndex) {
|
||||
if (clip != null && jointIndex < clip.rotations().length
|
||||
&& clip.rotations()[jointIndex] != null) {
|
||||
private static Quaternionf getAnimRotation(
|
||||
GltfData data,
|
||||
GltfData.AnimationClip clip,
|
||||
int jointIndex
|
||||
) {
|
||||
if (
|
||||
clip != null &&
|
||||
jointIndex < clip.rotations().length &&
|
||||
clip.rotations()[jointIndex] != null
|
||||
) {
|
||||
return clip.rotations()[jointIndex][0]; // first frame
|
||||
}
|
||||
return data.restRotations()[jointIndex];
|
||||
@@ -119,10 +137,17 @@ public final class GltfSkinningEngine {
|
||||
* Get the animation translation for a joint (MC-converted).
|
||||
* Falls back to rest translation if no animation translation exists.
|
||||
*/
|
||||
private static Vector3f getAnimTranslation(GltfData data, GltfData.AnimationClip clip, int jointIndex) {
|
||||
if (clip != null && clip.translations() != null
|
||||
&& jointIndex < clip.translations().length
|
||||
&& clip.translations()[jointIndex] != null) {
|
||||
private static Vector3f getAnimTranslation(
|
||||
GltfData data,
|
||||
GltfData.AnimationClip clip,
|
||||
int jointIndex
|
||||
) {
|
||||
if (
|
||||
clip != null &&
|
||||
clip.translations() != null &&
|
||||
jointIndex < clip.translations().length &&
|
||||
clip.translations()[jointIndex] != null
|
||||
) {
|
||||
return clip.translations()[jointIndex][0]; // first frame
|
||||
}
|
||||
return data.restTranslations()[jointIndex];
|
||||
@@ -144,10 +169,16 @@ public final class GltfSkinningEngine {
|
||||
* @return new Quaternionf with the interpolated rotation (never mutates source data)
|
||||
*/
|
||||
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
|
||||
|| clip.rotations()[jointIndex] == null) {
|
||||
if (
|
||||
clip == null ||
|
||||
jointIndex >= clip.rotations().length ||
|
||||
clip.rotations()[jointIndex] == null
|
||||
) {
|
||||
// No animation data for this joint -- use rest pose (copy to avoid mutation)
|
||||
Quaternionf rest = data.restRotations()[jointIndex];
|
||||
return new Quaternionf(rest);
|
||||
@@ -187,11 +218,17 @@ public final class GltfSkinningEngine {
|
||||
* @return new Vector3f with the interpolated translation (never mutates source data)
|
||||
*/
|
||||
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
|
||||
|| jointIndex >= clip.translations().length
|
||||
|| clip.translations()[jointIndex] == null) {
|
||||
if (
|
||||
clip == 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)
|
||||
Vector3f rest = data.restTranslations()[jointIndex];
|
||||
return new Vector3f(rest);
|
||||
@@ -232,9 +269,13 @@ public final class GltfSkinningEngine {
|
||||
* @param tmpNorm pre-allocated scratch Vector4f for normal transforms
|
||||
*/
|
||||
public static void skinVertex(
|
||||
GltfData data, int vertexIdx, Matrix4f[] jointMatrices,
|
||||
float[] outPos, float[] outNormal,
|
||||
Vector4f tmpPos, Vector4f tmpNorm
|
||||
GltfData data,
|
||||
int vertexIdx,
|
||||
Matrix4f[] jointMatrices,
|
||||
float[] outPos,
|
||||
float[] outNormal,
|
||||
Vector4f tmpPos,
|
||||
Vector4f tmpNorm
|
||||
) {
|
||||
float[] positions = data.positions();
|
||||
float[] normals = data.normals();
|
||||
@@ -252,8 +293,12 @@ public final class GltfSkinningEngine {
|
||||
float nz = normals[vertexIdx * 3 + 2];
|
||||
|
||||
// LBS: v_skinned = Σ(w[i] * jointMatrix[j[i]] * v_rest)
|
||||
float sx = 0, sy = 0, sz = 0;
|
||||
float snx = 0, sny = 0, snz = 0;
|
||||
float sx = 0,
|
||||
sy = 0,
|
||||
sz = 0;
|
||||
float snx = 0,
|
||||
sny = 0,
|
||||
snz = 0;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int ji = joints[vertexIdx * 4 + i];
|
||||
|
||||
@@ -20,7 +20,6 @@ import net.minecraftforge.fml.common.Mod;
|
||||
* Overlay that shows a progress bar for tying/untying/struggling actions.
|
||||
* Displayed above the hotbar when an action is in progress.
|
||||
*
|
||||
* Phase 16: GUI Revamp - Progress bar overlay
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
@Mod.EventBusSubscriber(modid = TiedUpMod.MOD_ID, value = Dist.CLIENT)
|
||||
|
||||
@@ -18,7 +18,6 @@ import net.minecraftforge.fml.common.Mod;
|
||||
* Overlay that shows status icons when player is restrained.
|
||||
* Icons appear in top-left corner showing current bondage state.
|
||||
*
|
||||
* Phase 16: GUI Revamp - Status indicator overlay
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
@Mod.EventBusSubscriber(modid = TiedUpMod.MOD_ID, value = Dist.CLIENT)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.tiedup.remake.client.gui.overlays;
|
||||
|
||||
import com.tiedup.remake.client.ModKeybindings;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.tiedup.remake.client.gui.screens;
|
||||
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.network.item.PacketAdjustItem;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.chat.Component;
|
||||
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.
|
||||
* Shows 3D preview of player with real-time adjustment.
|
||||
*
|
||||
* Phase 16b: GUI Refactoring - Simplified using BaseAdjustmentScreen
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class AdjustmentScreen extends BaseAdjustmentScreen {
|
||||
|
||||
@@ -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.PacketRenameCell;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
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.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Screen for managing player-owned cells.
|
||||
|
||||
@@ -8,13 +8,13 @@ import com.tiedup.remake.network.cell.PacketAssignCellToCollar;
|
||||
import com.tiedup.remake.network.cell.PacketOpenCellSelector.CellOption;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Modal screen for selecting a cell to assign to a collar.
|
||||
|
||||
@@ -234,9 +234,7 @@ public class CommandWandScreen extends Screen {
|
||||
addBottomButtons();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Status Tab
|
||||
// =========================================================================
|
||||
|
||||
/** Calculate Y position after all rendered status content (needs, mood, cell, home) */
|
||||
private int getStatusButtonsY(int startY) {
|
||||
@@ -332,9 +330,7 @@ public class CommandWandScreen extends Screen {
|
||||
this.addRenderableWidget(threatenBtn);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Commands Tab
|
||||
// =========================================================================
|
||||
|
||||
private static final int SECTION_HEADER_H = 16; // header line + text + padding
|
||||
|
||||
@@ -382,9 +378,7 @@ public class CommandWandScreen extends Screen {
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Jobs Tab
|
||||
// =========================================================================
|
||||
|
||||
private void buildJobsTab(int contentX, int contentWidth, int y) {
|
||||
int thirdWidth = (contentWidth - BTN_SPACING * 2) / 3;
|
||||
@@ -463,9 +457,7 @@ public class CommandWandScreen extends Screen {
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Bottom Buttons (all tabs)
|
||||
// =========================================================================
|
||||
|
||||
private void addBottomButtons() {
|
||||
int bottomBtnWidth = 90;
|
||||
@@ -506,9 +498,7 @@ public class CommandWandScreen extends Screen {
|
||||
this.addRenderableWidget(stopBtn);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Button helpers
|
||||
// =========================================================================
|
||||
|
||||
private void addCommandButton(int x, int y, NpcCommand command, int width) {
|
||||
boolean isActive = activeCommand.equals(command.name());
|
||||
@@ -667,9 +657,7 @@ public class CommandWandScreen extends Screen {
|
||||
this.addRenderableWidget(btn);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Network actions
|
||||
// =========================================================================
|
||||
|
||||
private void sendCommand(NpcCommand command) {
|
||||
ModNetwork.sendToServer(
|
||||
@@ -692,7 +680,9 @@ public class CommandWandScreen extends Screen {
|
||||
}
|
||||
|
||||
private void toggleAutoRest() {
|
||||
ModNetwork.sendToServer(PacketNpcCommand.toggleAutoRest(entityUUID, true));
|
||||
ModNetwork.sendToServer(
|
||||
PacketNpcCommand.toggleAutoRest(entityUUID, true)
|
||||
);
|
||||
}
|
||||
|
||||
private void openInventory() {
|
||||
@@ -728,9 +718,7 @@ public class CommandWandScreen extends Screen {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Render
|
||||
// =========================================================================
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
@@ -805,9 +793,7 @@ public class CommandWandScreen extends Screen {
|
||||
super.render(graphics, mouseX, mouseY, partialTick);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Render: Status Tab
|
||||
// =========================================================================
|
||||
|
||||
private void renderStatusContent(
|
||||
GuiGraphics graphics,
|
||||
@@ -1029,9 +1015,7 @@ public class CommandWandScreen extends Screen {
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Render: Commands Tab
|
||||
// =========================================================================
|
||||
|
||||
private void renderCommandContent(
|
||||
GuiGraphics graphics,
|
||||
@@ -1059,9 +1043,7 @@ public class CommandWandScreen extends Screen {
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Render: Jobs Tab
|
||||
// =========================================================================
|
||||
|
||||
private void renderJobsContent(
|
||||
GuiGraphics graphics,
|
||||
@@ -1119,9 +1101,7 @@ public class CommandWandScreen extends Screen {
|
||||
);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Render helpers
|
||||
// =========================================================================
|
||||
|
||||
private void renderSectionHeader(
|
||||
GuiGraphics graphics,
|
||||
|
||||
@@ -18,8 +18,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
* GUI screen for interactive conversations with NPCs.
|
||||
* 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
|
||||
* references this class in an instanceof check.
|
||||
|
||||
@@ -15,7 +15,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
/**
|
||||
* Phase 2.5: Client-side GUI for Lockpick mini-game (Skyrim-style).
|
||||
*
|
||||
* Features:
|
||||
* - Sweet spot is HIDDEN (uniform gray bar)
|
||||
|
||||
@@ -4,13 +4,13 @@ import com.tiedup.remake.client.gui.util.GuiTextureHelper;
|
||||
import com.tiedup.remake.client.gui.widgets.EntityPreviewWidget;
|
||||
import com.tiedup.remake.entities.EntityDamsel;
|
||||
import com.tiedup.remake.entities.NpcInventoryMenu;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Screen for viewing and managing NPC inventory.
|
||||
|
||||
@@ -22,7 +22,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
* - Ask to be untied
|
||||
* - End conversation
|
||||
*
|
||||
* Phase 2: Refactored to extend BaseInteractionScreen
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class PetRequestScreen extends BaseInteractionScreen {
|
||||
|
||||
@@ -2,8 +2,8 @@ package com.tiedup.remake.client.gui.screens;
|
||||
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.network.item.PacketAdjustRemote;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.network.chat.Component;
|
||||
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.
|
||||
* Similar to AdjustmentScreen but operates on a slave entity.
|
||||
*
|
||||
* Phase 16b: GUI Refactoring - Simplified using BaseAdjustmentScreen
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class RemoteAdjustmentScreen extends BaseAdjustmentScreen {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.tiedup.remake.client.gui.screens;
|
||||
|
||||
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.widgets.SlaveEntryWidget;
|
||||
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.PlayerCaptorManager;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@@ -142,7 +142,9 @@ public class SlaveManagementScreen extends BaseScreen {
|
||||
|
||||
IBondageState kidnapped = KidnappedHelper.getKidnappedState(entity);
|
||||
if (kidnapped != null && kidnapped.hasCollar()) {
|
||||
ItemStack collarStack = kidnapped.getEquipment(BodyRegionV2.NECK);
|
||||
ItemStack collarStack = kidnapped.getEquipment(
|
||||
BodyRegionV2.NECK
|
||||
);
|
||||
if (collarStack.getItem() instanceof ItemCollar collar) {
|
||||
if (collar.isOwner(collarStack, player)) {
|
||||
addSlaveEntry(kidnapped);
|
||||
|
||||
@@ -33,7 +33,7 @@ public class UnifiedBondageScreen extends BaseScreen {
|
||||
private static final int MODE_MASTER_BG = 0xFF707070;
|
||||
|
||||
// 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 final ActionPanel.ScreenMode mode;
|
||||
@@ -70,7 +70,10 @@ public class UnifiedBondageScreen extends BaseScreen {
|
||||
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"));
|
||||
this.mode = mode;
|
||||
this.targetEntity = target;
|
||||
@@ -78,17 +81,29 @@ public class UnifiedBondageScreen extends BaseScreen {
|
||||
}
|
||||
|
||||
private LivingEntity getTarget() {
|
||||
return (mode == ActionPanel.ScreenMode.SELF) ? minecraft.player : targetEntity;
|
||||
return (mode == ActionPanel.ScreenMode.SELF)
|
||||
? minecraft.player
|
||||
: targetEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferredWidth() {
|
||||
return GuiLayoutConstants.getResponsiveWidth(this.width, 0.65f, 420, 600);
|
||||
return GuiLayoutConstants.getResponsiveWidth(
|
||||
this.width,
|
||||
0.65f,
|
||||
420,
|
||||
600
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPreferredHeight() {
|
||||
return GuiLayoutConstants.getResponsiveHeight(this.height, 0.75f, 350, 500);
|
||||
return GuiLayoutConstants.getResponsiveHeight(
|
||||
this.height,
|
||||
0.75f,
|
||||
350,
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
@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 ===
|
||||
tabBar = new RegionTabBar(leftPos + 2, contentTop, imageWidth - 4);
|
||||
@@ -119,15 +137,24 @@ public class UnifiedBondageScreen extends BaseScreen {
|
||||
|
||||
int belowTabs = contentTop + 30;
|
||||
int statusBarHeight = 46;
|
||||
int mainContentHeight = imageHeight - (belowTabs - topPos) - statusBarHeight - GuiLayoutConstants.MARGIN_S;
|
||||
int mainContentHeight =
|
||||
imageHeight -
|
||||
(belowTabs - topPos) -
|
||||
statusBarHeight -
|
||||
GuiLayoutConstants.MARGIN_S;
|
||||
|
||||
// === 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();
|
||||
if (target != null) {
|
||||
preview = new EntityPreviewWidget(
|
||||
leftPos + GuiLayoutConstants.MARGIN_M, belowTabs,
|
||||
previewWidth, mainContentHeight, target
|
||||
leftPos + GuiLayoutConstants.MARGIN_M,
|
||||
belowTabs,
|
||||
previewWidth,
|
||||
mainContentHeight,
|
||||
target
|
||||
);
|
||||
preview.setAutoRotate(true);
|
||||
preview.setAutoRotateSpeed(0.3f);
|
||||
@@ -136,28 +163,42 @@ public class UnifiedBondageScreen extends BaseScreen {
|
||||
}
|
||||
|
||||
// === Right panel area ===
|
||||
int rightX = leftPos + GuiLayoutConstants.MARGIN_M + previewWidth + GuiLayoutConstants.MARGIN_M;
|
||||
int rightWidth = imageWidth - (rightX - leftPos) - GuiLayoutConstants.MARGIN_M;
|
||||
int rightX =
|
||||
leftPos +
|
||||
GuiLayoutConstants.MARGIN_M +
|
||||
previewWidth +
|
||||
GuiLayoutConstants.MARGIN_M;
|
||||
int rightWidth =
|
||||
imageWidth - (rightX - leftPos) - GuiLayoutConstants.MARGIN_M;
|
||||
|
||||
// Action panel height
|
||||
int actionPanelHeight = 84;
|
||||
int slotsHeight = mainContentHeight - actionPanelHeight - GuiLayoutConstants.MARGIN_S;
|
||||
int slotsHeight =
|
||||
mainContentHeight - actionPanelHeight - GuiLayoutConstants.MARGIN_S;
|
||||
|
||||
// === Region Slots ===
|
||||
buildSlots(rightX, belowTabs, rightWidth, slotsHeight);
|
||||
|
||||
// === Action Panel ===
|
||||
actionPanel = new ActionPanel(rightX, belowTabs + slotsHeight + GuiLayoutConstants.MARGIN_S,
|
||||
rightWidth, actionPanelHeight);
|
||||
actionPanel = new ActionPanel(
|
||||
rightX,
|
||||
belowTabs + slotsHeight + GuiLayoutConstants.MARGIN_S,
|
||||
rightWidth,
|
||||
actionPanelHeight
|
||||
);
|
||||
actionPanel.setMode(mode);
|
||||
actionPanel.setTargetEntity(getTarget());
|
||||
actionPanel.setKeyInfo(keyUUID, isMasterKey);
|
||||
actionPanel.setOnAdjustRequested(region -> {
|
||||
if (AdjustmentScreen.canOpen()) minecraft.setScreen(new AdjustmentScreen());
|
||||
if (AdjustmentScreen.canOpen()) minecraft.setScreen(
|
||||
new AdjustmentScreen()
|
||||
);
|
||||
});
|
||||
actionPanel.setOnEquipRequested(this::openPicker);
|
||||
actionPanel.setOnCellAssignRequested(() -> {
|
||||
if (targetEntityUUID != null) ModNetwork.sendToServer(new PacketRequestCellList(targetEntityUUID));
|
||||
if (targetEntityUUID != null) ModNetwork.sendToServer(
|
||||
new PacketRequestCellList(targetEntityUUID)
|
||||
);
|
||||
});
|
||||
actionPanel.setOnCloseRequested(this::onClose);
|
||||
actionPanel.clearContext();
|
||||
@@ -165,7 +206,12 @@ public class UnifiedBondageScreen extends BaseScreen {
|
||||
|
||||
// === Status Bar ===
|
||||
int statusY = topPos + imageHeight - statusBarHeight;
|
||||
statusBar = new StatusBarWidget(leftPos, statusY, imageWidth, statusBarHeight);
|
||||
statusBar = new StatusBarWidget(
|
||||
leftPos,
|
||||
statusY,
|
||||
imageWidth,
|
||||
statusBarHeight
|
||||
);
|
||||
statusBar.setMode(mode);
|
||||
statusBar.setTargetEntity(getTarget());
|
||||
statusBar.setOnCloseClicked(this::onClose);
|
||||
@@ -193,7 +239,9 @@ public class UnifiedBondageScreen extends BaseScreen {
|
||||
if (stack.getItem() instanceof ItemKey) {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -212,8 +260,17 @@ public class UnifiedBondageScreen extends BaseScreen {
|
||||
BodyRegionV2 region = regions[i];
|
||||
int slotY = y + i * slotHeight;
|
||||
|
||||
RegionSlotWidget slot = new RegionSlotWidget(x, slotY, width, slotHeight - 2,
|
||||
region, () -> target != null ? V2EquipmentHelper.getInRegion(target, region) : ItemStack.EMPTY);
|
||||
RegionSlotWidget slot = new RegionSlotWidget(
|
||||
x,
|
||||
slotY,
|
||||
width,
|
||||
slotHeight - 2,
|
||||
region,
|
||||
() ->
|
||||
target != null
|
||||
? V2EquipmentHelper.getInRegion(target, region)
|
||||
: ItemStack.EMPTY
|
||||
);
|
||||
slot.setOnClick(this::onSlotClicked);
|
||||
slot.setShowEquipButton(true);
|
||||
slot.setOnEquipClick(s -> openPicker(s.getRegion()));
|
||||
@@ -245,15 +302,30 @@ public class UnifiedBondageScreen extends BaseScreen {
|
||||
actionPanel.clearContext();
|
||||
|
||||
// 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 statusBarHeight = 46;
|
||||
int mainContentHeight = imageHeight - (belowTabs - topPos) - 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 mainContentHeight =
|
||||
imageHeight -
|
||||
(belowTabs - topPos) -
|
||||
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 slotsHeight = mainContentHeight - actionPanelHeight - GuiLayoutConstants.MARGIN_S;
|
||||
int slotsHeight =
|
||||
mainContentHeight - actionPanelHeight - GuiLayoutConstants.MARGIN_S;
|
||||
|
||||
buildSlots(rightX, belowTabs, rightWidth, slotsHeight);
|
||||
autoSelectFirstOccupied();
|
||||
@@ -284,14 +356,23 @@ public class UnifiedBondageScreen extends BaseScreen {
|
||||
}
|
||||
|
||||
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) {
|
||||
if (mode == ActionPanel.ScreenMode.SELF) {
|
||||
ModNetwork.sendToServer(new PacketV2SelfEquip(region, inventorySlot));
|
||||
ModNetwork.sendToServer(
|
||||
new PacketV2SelfEquip(region, inventorySlot)
|
||||
);
|
||||
} else {
|
||||
ModNetwork.sendToServer(new PacketMasterEquip(targetEntityUUID, region, inventorySlot));
|
||||
ModNetwork.sendToServer(
|
||||
new PacketMasterEquip(targetEntityUUID, region, inventorySlot)
|
||||
);
|
||||
}
|
||||
refreshCountdown = 10; // Refresh after server processes
|
||||
}
|
||||
@@ -314,30 +395,65 @@ public class UnifiedBondageScreen extends BaseScreen {
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
// MC-style raised panel
|
||||
GuiRenderUtil.drawMCPanel(graphics, leftPos, topPos, imageWidth, imageHeight);
|
||||
GuiRenderUtil.drawMCPanel(
|
||||
graphics,
|
||||
leftPos,
|
||||
topPos,
|
||||
imageWidth,
|
||||
imageHeight
|
||||
);
|
||||
|
||||
// Title (dark text, vanilla style)
|
||||
String titleText = this.title.getString();
|
||||
if (mode == ActionPanel.ScreenMode.MASTER && targetEntity != null) {
|
||||
titleText += " \u2014 " + targetEntity.getName().getString();
|
||||
}
|
||||
GuiRenderUtil.drawCenteredStringNoShadow(graphics, font, titleText,
|
||||
leftPos + imageWidth / 2, topPos + GuiLayoutConstants.MARGIN_M, TITLE_COLOR);
|
||||
GuiRenderUtil.drawCenteredStringNoShadow(
|
||||
graphics,
|
||||
font,
|
||||
titleText,
|
||||
leftPos + imageWidth / 2,
|
||||
topPos + GuiLayoutConstants.MARGIN_M,
|
||||
TITLE_COLOR
|
||||
);
|
||||
|
||||
// Mode badge (top-right) — sober gray badge
|
||||
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 badgeBg = mode == ActionPanel.ScreenMode.MASTER ? MODE_MASTER_BG : MODE_SELF_BG;
|
||||
graphics.fill(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);
|
||||
int badgeBg =
|
||||
mode == ActionPanel.ScreenMode.MASTER
|
||||
? MODE_MASTER_BG
|
||||
: MODE_SELF_BG;
|
||||
graphics.fill(
|
||||
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
|
||||
super.render(graphics, mouseX, mouseY, partialTick);
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.tiedup.remake.v2.BodyRegionV2;
|
||||
* Color constants for TiedUp! GUI elements.
|
||||
* All colors are in ARGB format (0xAARRGGBB).
|
||||
*
|
||||
* Phase 16: GUI Revamp
|
||||
*/
|
||||
public class GuiColors {
|
||||
|
||||
@@ -65,7 +64,7 @@ public class GuiColors {
|
||||
*/
|
||||
public static int getRegionColor(BodyRegionV2 region) {
|
||||
return switch (region) {
|
||||
case HEAD -> 0xFF9C27B0; // Purple
|
||||
case HEAD -> 0xFF9C27B0; // Purple
|
||||
case EYES -> TYPE_BLINDFOLD;
|
||||
case EARS -> TYPE_EARPLUGS;
|
||||
case MOUTH -> TYPE_GAG;
|
||||
@@ -73,12 +72,12 @@ public class GuiColors {
|
||||
case TORSO -> TYPE_CLOTHES;
|
||||
case ARMS -> TYPE_BIND;
|
||||
case HANDS -> TYPE_MITTENS;
|
||||
case FINGERS -> 0xFFFFAB91; // Light orange
|
||||
case WAIST -> 0xFF795548; // Brown
|
||||
case LEGS -> 0xFF607D8B; // Blue-gray
|
||||
case FEET -> 0xFF78909C; // Light blue-gray
|
||||
case TAIL -> 0xFFCE93D8; // Light purple
|
||||
case WINGS -> 0xFF80DEEA; // Light cyan
|
||||
case FINGERS -> 0xFFFFAB91; // Light orange
|
||||
case WAIST -> 0xFF795548; // Brown
|
||||
case LEGS -> 0xFF607D8B; // Blue-gray
|
||||
case FEET -> 0xFF78909C; // Light blue-gray
|
||||
case TAIL -> 0xFFCE93D8; // Light purple
|
||||
case WINGS -> 0xFF80DEEA; // Light cyan
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,21 +12,27 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
public final class GuiRenderUtil {
|
||||
|
||||
// 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_INNER = 0xFFDBDBDB;
|
||||
public static final int MC_SHADOW_INNER = 0xFF555555;
|
||||
public static final int MC_SHADOW_OUTER = 0xFF373737;
|
||||
public static final int MC_SLOT_BG = 0xFF8B8B8B;
|
||||
public static final int MC_TEXT_DARK = 0xFF404040;
|
||||
public static final int MC_TEXT_GRAY = 0xFF555555;
|
||||
public static final int MC_SHADOW_INNER = 0xFF555555;
|
||||
public static final int MC_SHADOW_OUTER = 0xFF373737;
|
||||
public static final int MC_SLOT_BG = 0xFF8B8B8B;
|
||||
public static final int MC_TEXT_DARK = 0xFF404040;
|
||||
public static final int MC_TEXT_GRAY = 0xFF555555;
|
||||
|
||||
private GuiRenderUtil() {}
|
||||
|
||||
/**
|
||||
* 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
|
||||
graphics.fill(x, y, x + width, y + height, MC_PANEL_BG);
|
||||
// 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 + 1, y + 1, x + 2, y + height - 1, MC_HIGHLIGHT_INNER);
|
||||
// Bottom shadow
|
||||
graphics.fill(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);
|
||||
graphics.fill(
|
||||
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
|
||||
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).
|
||||
*/
|
||||
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);
|
||||
// Top shadow (dark)
|
||||
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 + 1, y + 1, x + 2, y + height - 1, MC_SHADOW_INNER);
|
||||
// Bottom highlight (light)
|
||||
graphics.fill(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);
|
||||
graphics.fill(
|
||||
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)
|
||||
graphics.fill(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);
|
||||
graphics.fill(
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
// Top shadow
|
||||
graphics.fill(x, y, x + width, y + 1, MC_SHADOW_OUTER);
|
||||
// Left shadow
|
||||
graphics.fill(x, y, x + 1, y + height, MC_SHADOW_OUTER);
|
||||
// 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
|
||||
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).
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
graphics.fill(x, y, x + width, y + height, bg);
|
||||
if (enabled) {
|
||||
@@ -96,9 +182,21 @@ public final class GuiRenderUtil {
|
||||
// Left highlight
|
||||
graphics.fill(x, y, x + 1, y + height, MC_HIGHLIGHT_OUTER);
|
||||
// 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
|
||||
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 {
|
||||
// Flat dark border for disabled
|
||||
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).
|
||||
*/
|
||||
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;
|
||||
// Top
|
||||
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).
|
||||
* 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);
|
||||
graphics.drawString(font, text, centerX - textWidth / 2, y, color, false);
|
||||
graphics.drawString(
|
||||
font,
|
||||
text,
|
||||
centerX - textWidth / 2,
|
||||
y,
|
||||
color,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,10 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class ActionPanel extends AbstractWidget {
|
||||
|
||||
public enum ScreenMode { SELF, MASTER }
|
||||
public enum ScreenMode {
|
||||
SELF,
|
||||
MASTER,
|
||||
}
|
||||
|
||||
private record ActionEntry(
|
||||
String labelKey,
|
||||
@@ -86,19 +89,35 @@ public class ActionPanel extends AbstractWidget {
|
||||
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) {
|
||||
this.targetEntity = entity;
|
||||
this.targetEntityUUID = entity != null ? entity.getUUID() : null;
|
||||
}
|
||||
|
||||
public void setKeyInfo(UUID keyUUID, boolean isMasterKey) {
|
||||
this.keyUUID = keyUUID;
|
||||
this.isMasterKey = isMasterKey;
|
||||
}
|
||||
public void setOnAdjustRequested(Consumer<BodyRegionV2> cb) { this.onAdjustRequested = 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; }
|
||||
|
||||
public void setOnAdjustRequested(Consumer<BodyRegionV2> cb) {
|
||||
this.onAdjustRequested = 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.
|
||||
@@ -142,7 +161,9 @@ public class ActionPanel extends AbstractWidget {
|
||||
if (stack.getItem() instanceof ItemKey) {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -151,110 +172,214 @@ public class ActionPanel extends AbstractWidget {
|
||||
|
||||
private void buildSelfActions(Player player) {
|
||||
boolean isEmpty = selectedItem.isEmpty();
|
||||
boolean armsOccupied = V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.ARMS);
|
||||
boolean handsOccupied = V2EquipmentHelper.isRegionOccupied(player, BodyRegionV2.HANDS);
|
||||
boolean armsOccupied = V2EquipmentHelper.isRegionOccupied(
|
||||
player,
|
||||
BodyRegionV2.ARMS
|
||||
);
|
||||
boolean handsOccupied = V2EquipmentHelper.isRegionOccupied(
|
||||
player,
|
||||
BodyRegionV2.HANDS
|
||||
);
|
||||
boolean armsFree = !armsOccupied;
|
||||
boolean handsFree = !handsOccupied;
|
||||
|
||||
boolean isLocked = false;
|
||||
boolean isLockable = 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) {
|
||||
isLocked = lockable.isLocked(selectedItem);
|
||||
isLockable = lockable.isLockable(selectedItem);
|
||||
isJammed = lockable.isJammed(selectedItem);
|
||||
}
|
||||
|
||||
boolean hasLockpick = !ItemLockpick.findLockpickInInventory(player).isEmpty();
|
||||
boolean hasLockpick = !ItemLockpick.findLockpickInInventory(
|
||||
player
|
||||
).isEmpty();
|
||||
boolean hasKnife = !GenericKnife.findKnifeInInventory(player).isEmpty();
|
||||
ItemStack foundKey = findKeyInInventory(player);
|
||||
boolean hasKey = !foundKey.isEmpty();
|
||||
boolean foundKeyIsMaster = hasKey && foundKey.is(ModItems.MASTER_KEY.get());
|
||||
boolean foundKeyIsMaster =
|
||||
hasKey && foundKey.is(ModItems.MASTER_KEY.get());
|
||||
|
||||
if (isEmpty) {
|
||||
// Equip action for empty slot
|
||||
actions.add(new ActionEntry("gui.tiedup.action.equip", true, null,
|
||||
() -> { if (onEquipRequested != null) onEquipRequested.accept(selectedRegion); }));
|
||||
actions.add(
|
||||
new ActionEntry("gui.tiedup.action.equip", true, null, () -> {
|
||||
if (onEquipRequested != null) onEquipRequested.accept(
|
||||
selectedRegion
|
||||
);
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove
|
||||
boolean canRemove = armsFree && handsFree && !isLocked && selectedRegion != BodyRegionV2.ARMS;
|
||||
String removeReason = isLocked ? "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))));
|
||||
boolean canRemove =
|
||||
armsFree &&
|
||||
handsFree &&
|
||||
!isLocked &&
|
||||
selectedRegion != BodyRegionV2.ARMS;
|
||||
String removeReason = isLocked
|
||||
? "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)
|
||||
if (isLocked) {
|
||||
actions.add(new ActionEntry("gui.tiedup.action.struggle", true, null,
|
||||
() -> {
|
||||
ModNetwork.sendToServer(new PacketV2StruggleStart(selectedRegion));
|
||||
if (onCloseRequested != null) onCloseRequested.run();
|
||||
}));
|
||||
actions.add(
|
||||
new ActionEntry(
|
||||
"gui.tiedup.action.struggle",
|
||||
true,
|
||||
null,
|
||||
() -> {
|
||||
ModNetwork.sendToServer(
|
||||
new PacketV2StruggleStart(selectedRegion)
|
||||
);
|
||||
if (onCloseRequested != null) onCloseRequested.run();
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Lockpick
|
||||
if (isLocked) {
|
||||
boolean canPick = hasLockpick && !hasMittens && !isJammed;
|
||||
String pickReason = !hasLockpick ? "gui.tiedup.reason.no_lockpick" :
|
||||
hasMittens ? "gui.tiedup.reason.mittens" :
|
||||
isJammed ? "gui.tiedup.reason.jammed" : null;
|
||||
actions.add(new ActionEntry("gui.tiedup.action.lockpick", canPick, pickReason,
|
||||
() -> {
|
||||
ModNetwork.sendToServer(new PacketLockpickMiniGameStart(selectedRegion));
|
||||
if (onCloseRequested != null) onCloseRequested.run();
|
||||
}));
|
||||
String pickReason = !hasLockpick
|
||||
? "gui.tiedup.reason.no_lockpick"
|
||||
: hasMittens
|
||||
? "gui.tiedup.reason.mittens"
|
||||
: isJammed
|
||||
? "gui.tiedup.reason.jammed"
|
||||
: null;
|
||||
actions.add(
|
||||
new ActionEntry(
|
||||
"gui.tiedup.action.lockpick",
|
||||
canPick,
|
||||
pickReason,
|
||||
() -> {
|
||||
ModNetwork.sendToServer(
|
||||
new PacketLockpickMiniGameStart(selectedRegion)
|
||||
);
|
||||
if (onCloseRequested != null) onCloseRequested.run();
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Cut
|
||||
if (isLocked) {
|
||||
boolean canCut = hasKnife && !hasMittens;
|
||||
String cutReason = !hasKnife ? "gui.tiedup.reason.no_knife" :
|
||||
hasMittens ? "gui.tiedup.reason.mittens" : null;
|
||||
actions.add(new ActionEntry("gui.tiedup.action.cut", canCut, cutReason,
|
||||
() -> {
|
||||
ModNetwork.sendToServer(new PacketSetKnifeCutTarget(selectedRegion));
|
||||
if (onCloseRequested != null) onCloseRequested.run();
|
||||
}));
|
||||
String cutReason = !hasKnife
|
||||
? "gui.tiedup.reason.no_knife"
|
||||
: hasMittens
|
||||
? "gui.tiedup.reason.mittens"
|
||||
: null;
|
||||
actions.add(
|
||||
new ActionEntry(
|
||||
"gui.tiedup.action.cut",
|
||||
canCut,
|
||||
cutReason,
|
||||
() -> {
|
||||
ModNetwork.sendToServer(
|
||||
new PacketSetKnifeCutTarget(selectedRegion)
|
||||
);
|
||||
if (onCloseRequested != null) onCloseRequested.run();
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Adjust (MOUTH, EYES only)
|
||||
if (selectedRegion == BodyRegionV2.MOUTH || selectedRegion == BodyRegionV2.EYES) {
|
||||
actions.add(new ActionEntry("gui.tiedup.action.adjust", true, null,
|
||||
() -> { if (onAdjustRequested != null) onAdjustRequested.accept(selectedRegion); }));
|
||||
if (
|
||||
selectedRegion == BodyRegionV2.MOUTH ||
|
||||
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)
|
||||
if (isLockable && !isLocked) {
|
||||
boolean canLock = hasKey && armsFree;
|
||||
String lockReason = !hasKey ? "gui.tiedup.reason.no_key" :
|
||||
!armsFree ? "gui.tiedup.reason.arms_bound" : null;
|
||||
actions.add(new ActionEntry("gui.tiedup.action.lock", canLock, lockReason,
|
||||
() -> ModNetwork.sendToServer(new PacketV2SelfLock(selectedRegion))));
|
||||
String lockReason = !hasKey
|
||||
? "gui.tiedup.reason.no_key"
|
||||
: !armsFree
|
||||
? "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)
|
||||
if (isLocked) {
|
||||
boolean keyMatches = false;
|
||||
if (hasKey && selectedItem.getItem() instanceof ILockable lockable) {
|
||||
if (
|
||||
hasKey && selectedItem.getItem() instanceof ILockable lockable
|
||||
) {
|
||||
if (foundKeyIsMaster) {
|
||||
keyMatches = true; // Master key matches all locks
|
||||
} else if (foundKey.getItem() instanceof ItemKey itemKey) {
|
||||
UUID lockKeyUUID = lockable.getLockedByKeyUUID(selectedItem);
|
||||
UUID lockKeyUUID = lockable.getLockedByKeyUUID(
|
||||
selectedItem
|
||||
);
|
||||
UUID foundKeyUUID = itemKey.getKeyUUID(foundKey);
|
||||
keyMatches = lockKeyUUID == null || lockKeyUUID.equals(foundKeyUUID);
|
||||
keyMatches =
|
||||
lockKeyUUID == null || lockKeyUUID.equals(foundKeyUUID);
|
||||
}
|
||||
}
|
||||
boolean canUnlock = hasKey && armsFree && keyMatches;
|
||||
String unlockReason = !hasKey ? "gui.tiedup.reason.no_key" :
|
||||
!armsFree ? "gui.tiedup.reason.arms_bound" :
|
||||
!keyMatches ? "gui.tiedup.reason.wrong_key" : null;
|
||||
actions.add(new ActionEntry("gui.tiedup.action.unlock", canUnlock, unlockReason,
|
||||
() -> ModNetwork.sendToServer(new PacketV2SelfUnlock(selectedRegion))));
|
||||
String unlockReason = !hasKey
|
||||
? "gui.tiedup.reason.no_key"
|
||||
: !armsFree
|
||||
? "gui.tiedup.reason.arms_bound"
|
||||
: !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) {
|
||||
actions.add(new ActionEntry("gui.tiedup.action.equip", true, null,
|
||||
() -> { if (onEquipRequested != null) onEquipRequested.accept(selectedRegion); }));
|
||||
actions.add(
|
||||
new ActionEntry("gui.tiedup.action.equip", true, null, () -> {
|
||||
if (onEquipRequested != null) onEquipRequested.accept(
|
||||
selectedRegion
|
||||
);
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove
|
||||
actions.add(new ActionEntry("gui.tiedup.action.remove", !isLocked,
|
||||
isLocked ? "gui.tiedup.reason.locked" : null,
|
||||
() -> ModNetwork.sendToServer(new PacketSlaveItemManage(
|
||||
targetEntityUUID, selectedRegion, PacketSlaveItemManage.Action.REMOVE, keyUUID, isMasterKey))));
|
||||
actions.add(
|
||||
new ActionEntry(
|
||||
"gui.tiedup.action.remove",
|
||||
!isLocked,
|
||||
isLocked ? "gui.tiedup.reason.locked" : null,
|
||||
() ->
|
||||
ModNetwork.sendToServer(
|
||||
new PacketSlaveItemManage(
|
||||
targetEntityUUID,
|
||||
selectedRegion,
|
||||
PacketSlaveItemManage.Action.REMOVE,
|
||||
keyUUID,
|
||||
isMasterKey
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Lock
|
||||
if (isLockable && !isLocked) {
|
||||
actions.add(new ActionEntry("gui.tiedup.action.lock", true, null,
|
||||
() -> ModNetwork.sendToServer(new PacketSlaveItemManage(
|
||||
targetEntityUUID, selectedRegion, PacketSlaveItemManage.Action.LOCK, keyUUID, isMasterKey))));
|
||||
actions.add(
|
||||
new ActionEntry("gui.tiedup.action.lock", true, null, () ->
|
||||
ModNetwork.sendToServer(
|
||||
new PacketSlaveItemManage(
|
||||
targetEntityUUID,
|
||||
selectedRegion,
|
||||
PacketSlaveItemManage.Action.LOCK,
|
||||
keyUUID,
|
||||
isMasterKey
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Unlock
|
||||
if (isLocked) {
|
||||
actions.add(new ActionEntry("gui.tiedup.action.unlock", true, null,
|
||||
() -> ModNetwork.sendToServer(new PacketSlaveItemManage(
|
||||
targetEntityUUID, selectedRegion, PacketSlaveItemManage.Action.UNLOCK, keyUUID, isMasterKey))));
|
||||
actions.add(
|
||||
new ActionEntry("gui.tiedup.action.unlock", true, null, () ->
|
||||
ModNetwork.sendToServer(
|
||||
new PacketSlaveItemManage(
|
||||
targetEntityUUID,
|
||||
selectedRegion,
|
||||
PacketSlaveItemManage.Action.UNLOCK,
|
||||
keyUUID,
|
||||
isMasterKey
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Adjust (MOUTH, EYES)
|
||||
if (selectedRegion == BodyRegionV2.MOUTH || selectedRegion == BodyRegionV2.EYES) {
|
||||
actions.add(new ActionEntry("gui.tiedup.action.adjust", true, null,
|
||||
() -> { if (onAdjustRequested != null) onAdjustRequested.accept(selectedRegion); }));
|
||||
if (
|
||||
selectedRegion == BodyRegionV2.MOUTH ||
|
||||
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)
|
||||
if (selectedRegion == BodyRegionV2.NECK && selectedItem.getItem() instanceof ItemCollar collar) {
|
||||
if (
|
||||
selectedRegion == BodyRegionV2.NECK &&
|
||||
selectedItem.getItem() instanceof ItemCollar collar
|
||||
) {
|
||||
if (collar.hasCellAssigned(selectedItem)) {
|
||||
boolean svcEnabled = collar.isBondageServiceEnabled(selectedItem);
|
||||
String svcKey = svcEnabled ? "gui.tiedup.action.svc_off" : "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))));
|
||||
boolean svcEnabled = collar.isBondageServiceEnabled(
|
||||
selectedItem
|
||||
);
|
||||
String svcKey = svcEnabled
|
||||
? "gui.tiedup.action.svc_off"
|
||||
: "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
|
||||
actions.add(new ActionEntry("gui.tiedup.action.cell_assign", true, null,
|
||||
() -> { if (onCellAssignRequested != null) onCellAssignRequested.run(); }));
|
||||
actions.add(
|
||||
new ActionEntry(
|
||||
"gui.tiedup.action.cell_assign",
|
||||
true,
|
||||
null,
|
||||
() -> {
|
||||
if (
|
||||
onCellAssignRequested != null
|
||||
) onCellAssignRequested.run();
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
// MC-style sunken panel background
|
||||
GuiRenderUtil.drawMCSunkenPanel(graphics, getX(), getY(), width, height);
|
||||
GuiRenderUtil.drawMCSunkenPanel(
|
||||
graphics,
|
||||
getX(),
|
||||
getY(),
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
// Title
|
||||
String title;
|
||||
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()) {
|
||||
title = Component.translatable("gui.tiedup.action.title_empty",
|
||||
Component.translatable("gui.tiedup.region." + selectedRegion.name().toLowerCase())).getString();
|
||||
title = Component.translatable(
|
||||
"gui.tiedup.action.title_empty",
|
||||
Component.translatable(
|
||||
"gui.tiedup.region." + selectedRegion.name().toLowerCase()
|
||||
)
|
||||
).getString();
|
||||
} 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)
|
||||
hoveredIndex = -1;
|
||||
@@ -347,14 +570,27 @@ public class ActionPanel extends AbstractWidget {
|
||||
int btnX = startX + col * (BUTTON_WIDTH + BUTTON_SPACING);
|
||||
int btnY = startY + row * (BUTTON_HEIGHT + 4);
|
||||
|
||||
boolean hovered = mouseX >= btnX && mouseX < btnX + BUTTON_WIDTH
|
||||
&& mouseY >= btnY && mouseY < btnY + BUTTON_HEIGHT;
|
||||
boolean hovered =
|
||||
mouseX >= btnX &&
|
||||
mouseX < btnX + BUTTON_WIDTH &&
|
||||
mouseY >= btnY &&
|
||||
mouseY < btnY + BUTTON_HEIGHT;
|
||||
if (hovered) hoveredIndex = i;
|
||||
|
||||
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 textY = btnY + (BUTTON_HEIGHT - mc.font.lineHeight) / 2;
|
||||
graphics.drawString(mc.font, label, textX, textY, textColor, false);
|
||||
@@ -363,10 +599,16 @@ public class ActionPanel extends AbstractWidget {
|
||||
// Tooltip for disabled button
|
||||
if (hoveredIndex >= 0 && hoveredIndex < actions.size()) {
|
||||
ActionEntry hoverAction = actions.get(hoveredIndex);
|
||||
if (!hoverAction.enabled() && hoverAction.disabledReasonKey() != null) {
|
||||
graphics.renderTooltip(mc.font,
|
||||
if (
|
||||
!hoverAction.enabled() &&
|
||||
hoverAction.disabledReasonKey() != null
|
||||
) {
|
||||
graphics.renderTooltip(
|
||||
mc.font,
|
||||
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 btnY = startY + row * (BUTTON_HEIGHT + 4);
|
||||
|
||||
if (mouseX >= btnX && mouseX < btnX + BUTTON_WIDTH
|
||||
&& mouseY >= btnY && mouseY < btnY + BUTTON_HEIGHT) {
|
||||
if (
|
||||
mouseX >= btnX &&
|
||||
mouseX < btnX + BUTTON_WIDTH &&
|
||||
mouseY >= btnY &&
|
||||
mouseY < btnY + BUTTON_HEIGHT
|
||||
) {
|
||||
if (action.enabled() && action.onClick() != null) {
|
||||
action.onClick().run();
|
||||
playDownSound(mc.getSoundManager());
|
||||
@@ -401,6 +647,9 @@ public class ActionPanel extends AbstractWidget {
|
||||
|
||||
@Override
|
||||
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")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
* Vertical slider widget for adjusting Y position of items.
|
||||
* Displays current value and supports mouse drag and scroll wheel.
|
||||
*
|
||||
* Phase 16b: GUI Refactoring - Fixed alignment and layout
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class AdjustmentSlider extends AbstractWidget {
|
||||
|
||||
@@ -39,16 +39,14 @@ public final class CellListRenderer {
|
||||
) {
|
||||
graphics.drawCenteredString(
|
||||
font,
|
||||
Component.translatable(noItemsKey)
|
||||
.withStyle(ChatFormatting.GRAY),
|
||||
Component.translatable(noItemsKey).withStyle(ChatFormatting.GRAY),
|
||||
centerX,
|
||||
startY + 20,
|
||||
GuiColors.TEXT_DISABLED
|
||||
);
|
||||
graphics.drawCenteredString(
|
||||
font,
|
||||
Component.translatable(hintKey)
|
||||
.withStyle(ChatFormatting.ITALIC),
|
||||
Component.translatable(hintKey).withStyle(ChatFormatting.ITALIC),
|
||||
centerX,
|
||||
startY + 35,
|
||||
GuiColors.TEXT_DISABLED
|
||||
|
||||
@@ -18,7 +18,6 @@ import org.joml.Quaternionf;
|
||||
* Widget that displays a 3D preview of a LivingEntity.
|
||||
* Supports mouse drag rotation and optional auto-rotation.
|
||||
*
|
||||
* Phase 16: GUI Revamp - Reusable entity preview widget
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class EntityPreviewWidget extends AbstractWidget {
|
||||
|
||||
@@ -56,14 +56,27 @@ public class ItemPickerOverlay extends AbstractWidget {
|
||||
this.visible = false;
|
||||
}
|
||||
|
||||
public void setOnItemSelected(BiConsumer<BodyRegionV2, Integer> cb) { this.onItemSelected = cb; }
|
||||
public void setOnCancelled(Runnable cb) { this.onCancelled = cb; }
|
||||
public boolean isOverlayVisible() { return visible; }
|
||||
public void setOnItemSelected(BiConsumer<BodyRegionV2, Integer> cb) {
|
||||
this.onItemSelected = cb;
|
||||
}
|
||||
|
||||
public void setOnCancelled(Runnable cb) {
|
||||
this.onCancelled = cb;
|
||||
}
|
||||
|
||||
public boolean isOverlayVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.isSelfMode = selfMode;
|
||||
this.screenWidth = screenWidth;
|
||||
@@ -89,23 +102,45 @@ public class ItemPickerOverlay extends AbstractWidget {
|
||||
for (int i = 0; i < player.getInventory().getContainerSize(); i++) {
|
||||
ItemStack stack = player.getInventory().getItem(i);
|
||||
if (stack.isEmpty()) continue;
|
||||
if (!(stack.getItem() instanceof IV2BondageItem bondageItem)) continue;
|
||||
if (!bondageItem.getOccupiedRegions(stack).contains(targetRegion)) continue;
|
||||
if (
|
||||
!(stack.getItem() instanceof IV2BondageItem bondageItem)
|
||||
) continue;
|
||||
if (
|
||||
!bondageItem.getOccupiedRegions(stack).contains(targetRegion)
|
||||
) continue;
|
||||
entries.add(new PickerEntry(stack, i));
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
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);
|
||||
}
|
||||
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
|
||||
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;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
@@ -121,13 +156,30 @@ public class ItemPickerOverlay extends AbstractWidget {
|
||||
GuiRenderUtil.drawMCPanel(graphics, panelX, panelY, panelW, panelH);
|
||||
|
||||
// Title (dark text, vanilla style)
|
||||
String title = Component.translatable("gui.tiedup.picker.title",
|
||||
Component.translatable("gui.tiedup.region." + targetRegion.name().toLowerCase())).getString();
|
||||
graphics.drawString(mc.font, title, panelX + PADDING, panelY + PADDING, GuiRenderUtil.MC_TEXT_DARK, false);
|
||||
String title = Component.translatable(
|
||||
"gui.tiedup.picker.title",
|
||||
Component.translatable(
|
||||
"gui.tiedup.region." + targetRegion.name().toLowerCase()
|
||||
)
|
||||
).getString();
|
||||
graphics.drawString(
|
||||
mc.font,
|
||||
title,
|
||||
panelX + PADDING,
|
||||
panelY + PADDING,
|
||||
GuiRenderUtil.MC_TEXT_DARK,
|
||||
false
|
||||
);
|
||||
|
||||
// Entries
|
||||
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++) {
|
||||
int idx = i + scrollOffset;
|
||||
@@ -137,56 +189,123 @@ public class ItemPickerOverlay extends AbstractWidget {
|
||||
int entryX = panelX + PADDING;
|
||||
int entryW = panelW - PADDING * 2;
|
||||
|
||||
boolean hovered = mouseX >= entryX && mouseX < entryX + entryW
|
||||
&& mouseY >= entryY && mouseY < entryY + ENTRY_HEIGHT;
|
||||
boolean hovered =
|
||||
mouseX >= entryX &&
|
||||
mouseX < entryX + entryW &&
|
||||
mouseY >= entryY &&
|
||||
mouseY < entryY + ENTRY_HEIGHT;
|
||||
boolean isArmsWarning = (armsWarningSlot == entry.inventorySlot);
|
||||
|
||||
// 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
|
||||
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)
|
||||
if (hovered && !isArmsWarning) {
|
||||
GuiRenderUtil.drawSlotHover(graphics, entryX, entryY, entryW, ENTRY_HEIGHT - 2);
|
||||
GuiRenderUtil.drawSlotHover(
|
||||
graphics,
|
||||
entryX,
|
||||
entryY,
|
||||
entryW,
|
||||
ENTRY_HEIGHT - 2
|
||||
);
|
||||
}
|
||||
|
||||
// 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
|
||||
String name = entry.stack.getHoverName().getString();
|
||||
graphics.drawString(mc.font, name, entryX + 24, entryY + (ENTRY_HEIGHT - mc.font.lineHeight) / 2,
|
||||
GuiRenderUtil.MC_TEXT_DARK, false);
|
||||
graphics.drawString(
|
||||
mc.font,
|
||||
name,
|
||||
entryX + 24,
|
||||
entryY + (ENTRY_HEIGHT - mc.font.lineHeight) / 2,
|
||||
GuiRenderUtil.MC_TEXT_DARK,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// ARMS warning text
|
||||
if (armsWarningSlot >= 0) {
|
||||
String warning = Component.translatable("gui.tiedup.picker.arms_warning").getString();
|
||||
int warningY = listY + Math.min(entries.size(), maxVisible) * ENTRY_HEIGHT + 2;
|
||||
graphics.drawString(mc.font, warning, panelX + PADDING, warningY, WARNING_COLOR, false);
|
||||
String warning = Component.translatable(
|
||||
"gui.tiedup.picker.arms_warning"
|
||||
).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
|
||||
if (entries.isEmpty()) {
|
||||
String empty = Component.translatable("gui.tiedup.picker.empty").getString();
|
||||
GuiRenderUtil.drawCenteredStringNoShadow(graphics, mc.font, empty,
|
||||
panelX + panelW / 2, listY + 20, GuiRenderUtil.MC_TEXT_GRAY);
|
||||
String empty = Component.translatable(
|
||||
"gui.tiedup.picker.empty"
|
||||
).getString();
|
||||
GuiRenderUtil.drawCenteredStringNoShadow(
|
||||
graphics,
|
||||
mc.font,
|
||||
empty,
|
||||
panelX + panelW / 2,
|
||||
listY + 20,
|
||||
GuiRenderUtil.MC_TEXT_GRAY
|
||||
);
|
||||
}
|
||||
|
||||
// Cancel button (MC-style)
|
||||
int cancelX = panelX + (panelW - CANCEL_BTN_WIDTH) / 2;
|
||||
int cancelY = panelY + panelH - CANCEL_BTN_HEIGHT - PADDING;
|
||||
boolean cancelHovered = mouseX >= cancelX && mouseX < cancelX + CANCEL_BTN_WIDTH
|
||||
&& mouseY >= cancelY && mouseY < cancelY + CANCEL_BTN_HEIGHT;
|
||||
GuiRenderUtil.drawMCButton(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);
|
||||
boolean cancelHovered =
|
||||
mouseX >= cancelX &&
|
||||
mouseX < cancelX + CANCEL_BTN_WIDTH &&
|
||||
mouseY >= cancelY &&
|
||||
mouseY < cancelY + CANCEL_BTN_HEIGHT;
|
||||
GuiRenderUtil.drawMCButton(
|
||||
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
|
||||
@@ -202,8 +321,12 @@ public class ItemPickerOverlay extends AbstractWidget {
|
||||
// Cancel button
|
||||
int cancelX = panelX + (panelW - CANCEL_BTN_WIDTH) / 2;
|
||||
int cancelY = panelY + panelH - CANCEL_BTN_HEIGHT - PADDING;
|
||||
if (mouseX >= cancelX && mouseX < cancelX + CANCEL_BTN_WIDTH
|
||||
&& mouseY >= cancelY && mouseY < cancelY + CANCEL_BTN_HEIGHT) {
|
||||
if (
|
||||
mouseX >= cancelX &&
|
||||
mouseX < cancelX + CANCEL_BTN_WIDTH &&
|
||||
mouseY >= cancelY &&
|
||||
mouseY < cancelY + CANCEL_BTN_HEIGHT
|
||||
) {
|
||||
close();
|
||||
if (onCancelled != null) onCancelled.run();
|
||||
return true;
|
||||
@@ -211,7 +334,13 @@ public class ItemPickerOverlay extends AbstractWidget {
|
||||
|
||||
// Entry clicks
|
||||
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++) {
|
||||
int idx = i + scrollOffset;
|
||||
@@ -221,21 +350,30 @@ public class ItemPickerOverlay extends AbstractWidget {
|
||||
int entryX = panelX + PADDING;
|
||||
int entryW = panelW - PADDING * 2;
|
||||
|
||||
if (mouseX >= entryX && mouseX < entryX + entryW
|
||||
&& mouseY >= entryY && mouseY < entryY + ENTRY_HEIGHT) {
|
||||
|
||||
if (
|
||||
mouseX >= entryX &&
|
||||
mouseX < entryX + entryW &&
|
||||
mouseY >= entryY &&
|
||||
mouseY < entryY + ENTRY_HEIGHT
|
||||
) {
|
||||
// ARMS self-equip warning: double-click confirmation
|
||||
if (isSelfMode && targetRegion == BodyRegionV2.ARMS) {
|
||||
if (armsWarningSlot == entry.inventorySlot) {
|
||||
// Second click — confirm
|
||||
if (onItemSelected != null) onItemSelected.accept(targetRegion, entry.inventorySlot);
|
||||
if (onItemSelected != null) onItemSelected.accept(
|
||||
targetRegion,
|
||||
entry.inventorySlot
|
||||
);
|
||||
close();
|
||||
} else {
|
||||
// First click — show warning
|
||||
armsWarningSlot = entry.inventorySlot;
|
||||
}
|
||||
} else {
|
||||
if (onItemSelected != null) onItemSelected.accept(targetRegion, entry.inventorySlot);
|
||||
if (onItemSelected != null) onItemSelected.accept(
|
||||
targetRegion,
|
||||
entry.inventorySlot
|
||||
);
|
||||
close();
|
||||
}
|
||||
playDownSound(mc.getSoundManager());
|
||||
@@ -244,8 +382,12 @@ public class ItemPickerOverlay extends AbstractWidget {
|
||||
}
|
||||
|
||||
// Click outside panel = cancel
|
||||
if (mouseX < panelX || mouseX > panelX + panelW
|
||||
|| mouseY < panelY || mouseY > panelY + panelH) {
|
||||
if (
|
||||
mouseX < panelX ||
|
||||
mouseX > panelX + panelW ||
|
||||
mouseY < panelY ||
|
||||
mouseY > panelY + panelH
|
||||
) {
|
||||
close();
|
||||
if (onCancelled != null) onCancelled.run();
|
||||
return true;
|
||||
@@ -258,9 +400,18 @@ public class ItemPickerOverlay extends AbstractWidget {
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
|
||||
if (!visible) return false;
|
||||
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);
|
||||
scrollOffset = Math.max(0, Math.min(maxScroll, scrollOffset - (int) delta));
|
||||
scrollOffset = Math.max(
|
||||
0,
|
||||
Math.min(maxScroll, scrollOffset - (int) delta)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -268,7 +419,8 @@ public class ItemPickerOverlay extends AbstractWidget {
|
||||
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
|
||||
if (!visible) return false;
|
||||
// ESC closes overlay
|
||||
if (keyCode == 256) { // GLFW_KEY_ESCAPE
|
||||
if (keyCode == 256) {
|
||||
// GLFW_KEY_ESCAPE
|
||||
close();
|
||||
if (onCancelled != null) onCancelled.run();
|
||||
return true;
|
||||
@@ -278,7 +430,17 @@ public class ItemPickerOverlay extends AbstractWidget {
|
||||
|
||||
@Override
|
||||
protected void updateWidgetNarration(NarrationElementOutput output) {
|
||||
output.add(NarratedElementType.TITLE, Component.translatable("gui.tiedup.picker.title",
|
||||
targetRegion != null ? Component.translatable("gui.tiedup.region." + targetRegion.name().toLowerCase()) : ""));
|
||||
output.add(
|
||||
NarratedElementType.TITLE,
|
||||
Component.translatable(
|
||||
"gui.tiedup.picker.title",
|
||||
targetRegion != null
|
||||
? Component.translatable(
|
||||
"gui.tiedup.region." +
|
||||
targetRegion.name().toLowerCase()
|
||||
)
|
||||
: ""
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,8 +231,12 @@ public class RegionSlotWidget extends AbstractWidget {
|
||||
if (!showEquipButton || !itemGetter.get().isEmpty()) return false;
|
||||
int bx = getEquipButtonX();
|
||||
int by = getEquipButtonY();
|
||||
return mouseX >= bx && mouseX < bx + EQUIP_BUTTON_WIDTH
|
||||
&& mouseY >= by && mouseY < by + EQUIP_BUTTON_HEIGHT;
|
||||
return (
|
||||
mouseX >= bx &&
|
||||
mouseX < bx + EQUIP_BUTTON_WIDTH &&
|
||||
mouseY >= by &&
|
||||
mouseY < by + EQUIP_BUTTON_HEIGHT
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -249,19 +253,32 @@ public class RegionSlotWidget extends AbstractWidget {
|
||||
boolean adjustHovered = isAdjustButtonHovered(mouseX, mouseY);
|
||||
boolean removeHovered = isRemoveButtonHovered(mouseX, mouseY);
|
||||
boolean equipHovered = isEquipButtonHovered(mouseX, mouseY);
|
||||
boolean anyButtonHovered = adjustHovered || removeHovered || equipHovered;
|
||||
boolean anyButtonHovered =
|
||||
adjustHovered || removeHovered || equipHovered;
|
||||
|
||||
// MC-style sunken slot background
|
||||
GuiRenderUtil.drawMCSlot(graphics, getX(), getY(), width, height);
|
||||
|
||||
// Selected: gold highlight border on top of the slot
|
||||
if (selected) {
|
||||
GuiRenderUtil.drawSelectedBorder(graphics, getX(), getY(), width, height);
|
||||
GuiRenderUtil.drawSelectedBorder(
|
||||
graphics,
|
||||
getX(),
|
||||
getY(),
|
||||
width,
|
||||
height
|
||||
);
|
||||
}
|
||||
|
||||
// Hover overlay (vanilla white semi-transparent)
|
||||
if (hovered && !anyButtonHovered && !selected) {
|
||||
GuiRenderUtil.drawSlotHover(graphics, getX(), getY(), width, height);
|
||||
GuiRenderUtil.drawSlotHover(
|
||||
graphics,
|
||||
getX(),
|
||||
getY(),
|
||||
width,
|
||||
height
|
||||
);
|
||||
}
|
||||
|
||||
// Region icon (uniform dark gray square)
|
||||
@@ -325,22 +342,43 @@ public class RegionSlotWidget extends AbstractWidget {
|
||||
);
|
||||
|
||||
// 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;
|
||||
if (player != null) {
|
||||
int current = resistanceItem.getCurrentResistance(stack, player);
|
||||
int current = resistanceItem.getCurrentResistance(
|
||||
stack,
|
||||
player
|
||||
);
|
||||
int base = resistanceItem.getBaseResistance(player);
|
||||
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 barY = getY() + height - RESISTANCE_BAR_HEIGHT - PADDING;
|
||||
int barY =
|
||||
getY() + height - RESISTANCE_BAR_HEIGHT - PADDING;
|
||||
// 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
|
||||
int fillColor = (ratio < 0.30f) ? 0xFFFF4444 : 0xFF44CC44;
|
||||
int fillWidth = Math.round(RESISTANCE_BAR_WIDTH * ratio);
|
||||
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) {
|
||||
int bx = getEquipButtonX();
|
||||
int by = getEquipButtonY();
|
||||
GuiRenderUtil.drawMCButton(graphics, bx, by, EQUIP_BUTTON_WIDTH, EQUIP_BUTTON_HEIGHT, equipHovered, true);
|
||||
String equipLabel = Component.translatable("gui.tiedup.equip").getString();
|
||||
GuiRenderUtil.drawMCButton(
|
||||
graphics,
|
||||
bx,
|
||||
by,
|
||||
EQUIP_BUTTON_WIDTH,
|
||||
EQUIP_BUTTON_HEIGHT,
|
||||
equipHovered,
|
||||
true
|
||||
);
|
||||
String equipLabel = Component.translatable(
|
||||
"gui.tiedup.equip"
|
||||
).getString();
|
||||
GuiRenderUtil.drawCenteredStringNoShadow(
|
||||
graphics,
|
||||
mc.font,
|
||||
@@ -366,7 +414,15 @@ public class RegionSlotWidget extends AbstractWidget {
|
||||
if (showAdjustButton && hasItem) {
|
||||
int buttonX = getAdjustButtonX();
|
||||
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
|
||||
GuiRenderUtil.drawCenteredStringNoShadow(
|
||||
@@ -383,10 +439,23 @@ public class RegionSlotWidget extends AbstractWidget {
|
||||
if (showRemoveButton && hasItem) {
|
||||
int buttonX = getRemoveButtonX();
|
||||
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
|
||||
graphics.fill(buttonX + 1, buttonY + 1, buttonX + REMOVE_BUTTON_SIZE - 1, buttonY + REMOVE_BUTTON_SIZE - 1,
|
||||
removeHovered ? 0x40FF0000 : 0x20FF0000);
|
||||
graphics.fill(
|
||||
buttonX + 1,
|
||||
buttonY + 1,
|
||||
buttonX + REMOVE_BUTTON_SIZE - 1,
|
||||
buttonY + REMOVE_BUTTON_SIZE - 1,
|
||||
removeHovered ? 0x40FF0000 : 0x20FF0000
|
||||
);
|
||||
|
||||
// X icon
|
||||
GuiRenderUtil.drawCenteredStringNoShadow(
|
||||
|
||||
@@ -26,11 +26,31 @@ public class RegionTabBar extends AbstractWidget {
|
||||
* Body zone tabs grouping the 14 BodyRegionV2 values.
|
||||
*/
|
||||
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),
|
||||
ARMS("gui.tiedup.tab.arms", 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);
|
||||
ARMS(
|
||||
"gui.tiedup.tab.arms",
|
||||
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 Set<BodyRegionV2> regions;
|
||||
@@ -41,13 +61,20 @@ public class RegionTabBar extends AbstractWidget {
|
||||
for (BodyRegionV2 r : regions) this.regions.add(r);
|
||||
}
|
||||
|
||||
public String getTranslationKey() { return translationKey; }
|
||||
public Set<BodyRegionV2> getRegions() { return regions; }
|
||||
public String getTranslationKey() {
|
||||
return translationKey;
|
||||
}
|
||||
|
||||
public Set<BodyRegionV2> getRegions() {
|
||||
return regions;
|
||||
}
|
||||
|
||||
/** Check if any region in this tab has an equipped item on the entity. */
|
||||
public boolean hasEquippedItems(LivingEntity entity) {
|
||||
for (BodyRegionV2 region : regions) {
|
||||
if (V2EquipmentHelper.isRegionOccupied(entity, region)) return true;
|
||||
if (
|
||||
V2EquipmentHelper.isRegionOccupied(entity, region)
|
||||
) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -59,10 +86,10 @@ public class RegionTabBar extends AbstractWidget {
|
||||
private static final int DOT_RADIUS = 3;
|
||||
|
||||
// Colors (vanilla MC style)
|
||||
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_HOVER = 0xFFA0A0A0; // Between active and inactive
|
||||
private static final int TEXT_ACTIVE = 0xFF404040; // Dark text
|
||||
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_HOVER = 0xFFA0A0A0; // Between active and inactive
|
||||
private static final int TEXT_ACTIVE = 0xFF404040; // Dark text
|
||||
private static final int TEXT_INACTIVE = 0xFF555555; // Gray text
|
||||
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"));
|
||||
}
|
||||
|
||||
public void setOnTabChanged(Consumer<BodyTab> callback) { this.onTabChanged = callback; }
|
||||
public void setTargetEntity(LivingEntity entity) { this.targetEntity = entity; }
|
||||
public BodyTab getActiveTab() { return activeTab; }
|
||||
public void setOnTabChanged(Consumer<BodyTab> callback) {
|
||||
this.onTabChanged = callback;
|
||||
}
|
||||
|
||||
public void setTargetEntity(LivingEntity entity) {
|
||||
this.targetEntity = entity;
|
||||
}
|
||||
|
||||
public BodyTab getActiveTab() {
|
||||
return activeTab;
|
||||
}
|
||||
|
||||
public void setActiveTab(BodyTab tab) {
|
||||
if (this.activeTab != tab) {
|
||||
@@ -86,7 +121,12 @@ public class RegionTabBar extends AbstractWidget {
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
// Background bar
|
||||
@@ -99,34 +139,89 @@ public class RegionTabBar extends AbstractWidget {
|
||||
BodyTab tab = tabs[i];
|
||||
int tabX = getX() + i * (tabWidth + TAB_SPACING);
|
||||
boolean isActive = (tab == activeTab);
|
||||
boolean isHovered = mouseX >= tabX && mouseX < tabX + tabWidth
|
||||
&& mouseY >= getY() && mouseY < getY() + height;
|
||||
boolean isHovered =
|
||||
mouseX >= tabX &&
|
||||
mouseX < tabX + tabWidth &&
|
||||
mouseY >= getY() &&
|
||||
mouseY < getY() + height;
|
||||
|
||||
int bgColor = isActive ? BG_ACTIVE : (isHovered ? BG_HOVER : BG_INACTIVE);
|
||||
graphics.fill(tabX, getY(), tabX + tabWidth, getY() + height, bgColor);
|
||||
int bgColor = isActive
|
||||
? BG_ACTIVE
|
||||
: (isHovered ? BG_HOVER : BG_INACTIVE);
|
||||
graphics.fill(
|
||||
tabX,
|
||||
getY(),
|
||||
tabX + tabWidth,
|
||||
getY() + height,
|
||||
bgColor
|
||||
);
|
||||
|
||||
if (isActive) {
|
||||
// Active tab: raised 3D look, no bottom border (connects to panel below)
|
||||
// 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
|
||||
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
|
||||
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 {
|
||||
// Inactive tab: full 3D sunken borders
|
||||
// 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
|
||||
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
|
||||
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
|
||||
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
|
||||
String label = Component.translatable(tab.getTranslationKey()).getString();
|
||||
String label = Component.translatable(
|
||||
tab.getTranslationKey()
|
||||
).getString();
|
||||
int textColor = isActive ? TEXT_ACTIVE : TEXT_INACTIVE;
|
||||
int textX = tabX + (tabWidth - mc.font.width(label)) / 2;
|
||||
int textY = getY() + (height - mc.font.lineHeight) / 2;
|
||||
@@ -136,8 +231,13 @@ public class RegionTabBar extends AbstractWidget {
|
||||
if (targetEntity != null && tab.hasEquippedItems(targetEntity)) {
|
||||
int dotX = tabX + tabWidth - DOT_RADIUS - 4;
|
||||
int dotY = getY() + 4;
|
||||
graphics.fill(dotX - DOT_RADIUS, dotY - DOT_RADIUS,
|
||||
dotX + DOT_RADIUS, dotY + DOT_RADIUS, 0xFFCCCCCC);
|
||||
graphics.fill(
|
||||
dotX - DOT_RADIUS,
|
||||
dotY - DOT_RADIUS,
|
||||
dotX + DOT_RADIUS,
|
||||
dotY + DOT_RADIUS,
|
||||
0xFFCCCCCC
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,8 +262,12 @@ public class RegionTabBar extends AbstractWidget {
|
||||
|
||||
@Override
|
||||
protected void updateWidgetNarration(NarrationElementOutput output) {
|
||||
output.add(NarratedElementType.TITLE,
|
||||
Component.translatable("gui.tiedup.tab_bar",
|
||||
Component.translatable(activeTab.getTranslationKey())));
|
||||
output.add(
|
||||
NarratedElementType.TITLE,
|
||||
Component.translatable(
|
||||
"gui.tiedup.tab_bar",
|
||||
Component.translatable(activeTab.getTranslationKey())
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.tiedup.remake.client.gui.widgets;
|
||||
|
||||
import static com.tiedup.remake.client.gui.util.GuiLayoutConstants.*;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.tiedup.remake.client.gui.util.GuiColors;
|
||||
import com.tiedup.remake.items.ItemGpsCollar;
|
||||
import com.tiedup.remake.items.base.ItemCollar;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -43,18 +43,37 @@ public class StatusBarWidget extends AbstractWidget {
|
||||
super(x, y, width, height, Component.literal("Status Bar"));
|
||||
}
|
||||
|
||||
public void setMode(ActionPanel.ScreenMode mode) { this.mode = mode; }
|
||||
public void setTargetEntity(LivingEntity entity) { this.targetEntity = entity; }
|
||||
public void setOnCloseClicked(Runnable cb) { this.onCloseClicked = cb; }
|
||||
public void setMode(ActionPanel.ScreenMode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public void setTargetEntity(LivingEntity entity) {
|
||||
this.targetEntity = entity;
|
||||
}
|
||||
|
||||
public void setOnCloseClicked(Runnable cb) {
|
||||
this.onCloseClicked = cb;
|
||||
}
|
||||
|
||||
@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();
|
||||
Player player = mc.player;
|
||||
if (player == null) return;
|
||||
|
||||
// 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 textY2 = textY1 + mc.font.lineHeight + 2;
|
||||
@@ -68,46 +87,104 @@ public class StatusBarWidget extends AbstractWidget {
|
||||
// Close button (right side, MC-style button)
|
||||
int closeBtnX = getX() + width - CLOSE_BTN_WIDTH - PADDING;
|
||||
int closeBtnY = getY() + (height - CLOSE_BTN_HEIGHT) / 2;
|
||||
boolean closeHovered = mouseX >= closeBtnX && mouseX < closeBtnX + CLOSE_BTN_WIDTH
|
||||
&& mouseY >= closeBtnY && mouseY < closeBtnY + CLOSE_BTN_HEIGHT;
|
||||
GuiRenderUtil.drawMCButton(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);
|
||||
boolean closeHovered =
|
||||
mouseX >= closeBtnX &&
|
||||
mouseX < closeBtnX + CLOSE_BTN_WIDTH &&
|
||||
mouseY >= closeBtnY &&
|
||||
mouseY < closeBtnY + CLOSE_BTN_HEIGHT;
|
||||
GuiRenderUtil.drawMCButton(
|
||||
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();
|
||||
int x = getX() + PADDING;
|
||||
|
||||
// Tool status line 1
|
||||
ItemStack lockpick = ItemLockpick.findLockpickInInventory(player);
|
||||
String pickText = lockpick.isEmpty()
|
||||
? Component.translatable("gui.tiedup.status.no_lockpick").getString()
|
||||
: Component.translatable("gui.tiedup.status.lockpick_uses",
|
||||
lockpick.getMaxDamage() - lockpick.getDamageValue()).getString();
|
||||
graphics.drawString(mc.font, pickText, x, y1, GuiRenderUtil.MC_TEXT_DARK, false);
|
||||
? Component.translatable(
|
||||
"gui.tiedup.status.no_lockpick"
|
||||
).getString()
|
||||
: 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);
|
||||
String knifeText = knife.isEmpty()
|
||||
? Component.translatable("gui.tiedup.status.no_knife").getString()
|
||||
: Component.translatable("gui.tiedup.status.knife_uses",
|
||||
knife.getMaxDamage() - knife.getDamageValue()).getString();
|
||||
graphics.drawString(mc.font, knifeText, x + 150, y1, GuiRenderUtil.MC_TEXT_DARK, false);
|
||||
: Component.translatable(
|
||||
"gui.tiedup.status.knife_uses",
|
||||
knife.getMaxDamage() - knife.getDamageValue()
|
||||
).getString();
|
||||
graphics.drawString(
|
||||
mc.font,
|
||||
knifeText,
|
||||
x + 150,
|
||||
y1,
|
||||
GuiRenderUtil.MC_TEXT_DARK,
|
||||
false
|
||||
);
|
||||
|
||||
// Arms resistance line 2
|
||||
ItemStack armsBind = V2EquipmentHelper.getInRegion(player, BodyRegionV2.ARMS);
|
||||
if (!armsBind.isEmpty() && armsBind.getItem() instanceof IHasResistance res) {
|
||||
ItemStack armsBind = V2EquipmentHelper.getInRegion(
|
||||
player,
|
||||
BodyRegionV2.ARMS
|
||||
);
|
||||
if (
|
||||
!armsBind.isEmpty() &&
|
||||
armsBind.getItem() instanceof IHasResistance res
|
||||
) {
|
||||
int curr = res.getCurrentResistance(armsBind, 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;
|
||||
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();
|
||||
int x = getX() + PADDING;
|
||||
|
||||
@@ -127,18 +204,38 @@ public class StatusBarWidget extends AbstractWidget {
|
||||
|
||||
String keyText;
|
||||
if (keyStack.isEmpty()) {
|
||||
keyText = Component.translatable("gui.tiedup.status.no_key").getString();
|
||||
keyText = Component.translatable(
|
||||
"gui.tiedup.status.no_key"
|
||||
).getString();
|
||||
} else {
|
||||
keyText = Component.translatable("gui.tiedup.status.key_info",
|
||||
keyStack.getHoverName().getString()).getString();
|
||||
keyText = Component.translatable(
|
||||
"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
|
||||
if (targetEntity != null) {
|
||||
String targetText = Component.translatable("gui.tiedup.status.target_info",
|
||||
targetEntity.getName().getString()).getString();
|
||||
graphics.drawString(mc.font, targetText, x, y2, GuiRenderUtil.MC_TEXT_DARK, false);
|
||||
String targetText = Component.translatable(
|
||||
"gui.tiedup.status.target_info",
|
||||
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 closeBtnY = getY() + (height - CLOSE_BTN_HEIGHT) / 2;
|
||||
if (mouseX >= closeBtnX && mouseX < closeBtnX + CLOSE_BTN_WIDTH
|
||||
&& mouseY >= closeBtnY && mouseY < closeBtnY + CLOSE_BTN_HEIGHT) {
|
||||
if (
|
||||
mouseX >= closeBtnX &&
|
||||
mouseX < closeBtnX + CLOSE_BTN_WIDTH &&
|
||||
mouseY >= closeBtnY &&
|
||||
mouseY < closeBtnY + CLOSE_BTN_HEIGHT
|
||||
) {
|
||||
if (onCloseClicked != null) onCloseClicked.run();
|
||||
playDownSound(Minecraft.getInstance().getSoundManager());
|
||||
return true;
|
||||
@@ -159,6 +260,9 @@ public class StatusBarWidget extends AbstractWidget {
|
||||
|
||||
@Override
|
||||
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")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ import com.tiedup.remake.entities.AbstractTiedUpNpc;
|
||||
import com.tiedup.remake.entities.EntityKidnapperArcher;
|
||||
import com.tiedup.remake.entities.EntityMaster;
|
||||
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.PoseType;
|
||||
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.util.SetableSupplier;
|
||||
import dev.kosmx.playerAnim.impl.Helper;
|
||||
@@ -33,8 +33,6 @@ import org.slf4j.Logger;
|
||||
/**
|
||||
* 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:
|
||||
* - Extends PlayerModel for player-like rendering with outer layers
|
||||
@@ -151,9 +149,7 @@ public class DamselModel
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// IMutableModel Implementation
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
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)
|
||||
* - 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 limbSwing Limb swing animation value
|
||||
@@ -193,7 +187,6 @@ public class DamselModel
|
||||
float netHeadYaw,
|
||||
float headPitch
|
||||
) {
|
||||
// Phase 18: Handle archer arm poses BEFORE super call
|
||||
// Only show bow animation when in ranged mode (has active shooting target)
|
||||
if (entity instanceof EntityKidnapperArcher archer) {
|
||||
if (archer.isInRangedMode()) {
|
||||
@@ -222,7 +215,7 @@ public class DamselModel
|
||||
// Arms
|
||||
this.leftArm.visible = true;
|
||||
this.rightArm.visible = true;
|
||||
// Outer layers (Phase 19)
|
||||
// Outer layers
|
||||
this.hat.visible = true;
|
||||
this.jacket.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.jacket.copyFrom(this.body);
|
||||
this.leftSleeve.copyFrom(this.leftArm);
|
||||
@@ -320,10 +313,18 @@ public class DamselModel
|
||||
}
|
||||
|
||||
// Derive bound state from V2 regions (AbstractTiedUpNpc implements IV2EquipmentHolder)
|
||||
boolean armsBound = V2EquipmentHelper.isRegionOccupied(entity, BodyRegionV2.ARMS);
|
||||
boolean legsBound = V2EquipmentHelper.isRegionOccupied(entity, BodyRegionV2.LEGS);
|
||||
boolean armsBound = V2EquipmentHelper.isRegionOccupied(
|
||||
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);
|
||||
legsBound = ItemBind.hasLegsBound(bind);
|
||||
}
|
||||
@@ -374,7 +375,6 @@ public class DamselModel
|
||||
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
|
||||
hideWearerLayersForClothes(entity);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ public class DamselRenderer
|
||||
/**
|
||||
* Create renderer.
|
||||
*
|
||||
* Phase 19: Uses vanilla ModelLayers.PLAYER for full layer support (jacket, sleeves, pants).
|
||||
*/
|
||||
public DamselRenderer(EntityRendererProvider.Context context) {
|
||||
super(
|
||||
@@ -86,14 +85,15 @@ public class DamselRenderer
|
||||
}
|
||||
|
||||
// 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.
|
||||
* 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.
|
||||
*
|
||||
* DOG pose: X rotation is applied in setupRotations() AFTER Y rotation,
|
||||
|
||||
@@ -16,7 +16,6 @@ import net.minecraft.util.Mth;
|
||||
/**
|
||||
* Renderer for EntityKidnapBomb.
|
||||
*
|
||||
* Phase 16: Blocks
|
||||
*
|
||||
* Renders the primed kidnap bomb using our custom block texture.
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
/**
|
||||
* Renderer for EntityRopeArrow.
|
||||
* Phase 15: Uses vanilla arrow texture for simplicity.
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class RopeArrowRenderer extends ArrowRenderer<EntityRopeArrow> {
|
||||
|
||||
@@ -8,7 +8,6 @@ import com.tiedup.remake.items.clothes.ClothesProperties;
|
||||
import com.tiedup.remake.items.clothes.GenericClothes;
|
||||
import java.util.EnumSet;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.client.model.HumanoidModel;
|
||||
import net.minecraft.client.model.PlayerModel;
|
||||
import net.minecraft.client.player.AbstractClientPlayer;
|
||||
@@ -19,6 +18,7 @@ import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Helper class for rendering clothes with dynamic textures.
|
||||
|
||||
@@ -5,9 +5,9 @@ import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Client-side cache for storing clothes configuration of remote players.
|
||||
|
||||
@@ -15,7 +15,6 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
*
|
||||
* Used by SlaveManagementScreen to display owned slaves without spatial queries.
|
||||
*
|
||||
* Phase 17: Terminology Refactoring - Global Collar Registry
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class CollarRegistryClient {
|
||||
|
||||
@@ -21,7 +21,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
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.
|
||||
|
||||
@@ -3,10 +3,10 @@ package com.tiedup.remake.client.texture;
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Handles a dynamically loaded online texture for clothes.
|
||||
|
||||
@@ -16,12 +16,12 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import javax.imageio.ImageIO;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Manager for dynamically loaded online textures.
|
||||
|
||||
@@ -5,10 +5,10 @@ import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.tiedup.remake.bounty.Bounty;
|
||||
import com.tiedup.remake.bounty.BountyManager;
|
||||
import com.tiedup.remake.core.SettingsAccessor;
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.core.SettingsAccessor;
|
||||
import java.util.Optional;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
@@ -21,7 +21,6 @@ import net.minecraft.world.item.ItemStack;
|
||||
/**
|
||||
* Command: /bounty <player>
|
||||
*
|
||||
* Phase 17: Bounty System
|
||||
*
|
||||
* Creates a bounty on a target player using the held item as reward.
|
||||
*
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
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.util.teleport.Position;
|
||||
import com.tiedup.remake.util.teleport.TeleportHelper;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
@@ -21,7 +21,7 @@ import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Collar management commands for Phase 18.
|
||||
* Collar management commands.
|
||||
*
|
||||
* Commands:
|
||||
* /collar claim <player> - Claim ownership of a player's collar
|
||||
|
||||
@@ -83,7 +83,9 @@ public final class CommandHelper {
|
||||
com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper.sync(player);
|
||||
|
||||
// Sync bind state
|
||||
PacketSyncBindState statePacket = PacketSyncBindState.fromPlayer(player);
|
||||
PacketSyncBindState statePacket = PacketSyncBindState.fromPlayer(
|
||||
player
|
||||
);
|
||||
if (statePacket != null) {
|
||||
ModNetwork.sendToPlayer(statePacket, player);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Key management commands for Phase 18.
|
||||
* Key management commands.
|
||||
*
|
||||
* Commands:
|
||||
* /key claim - Claim the key you're holding
|
||||
|
||||
@@ -17,7 +17,7 @@ import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Utility commands for Phase 18.
|
||||
* Utility commands.
|
||||
*
|
||||
* Commands:
|
||||
* /kidnapset - Get a starter kit of mod items
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.tiedup.remake.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
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.GagVariant;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
@@ -27,7 +27,7 @@ import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
/**
|
||||
* NPC management commands for Phase 18.
|
||||
* NPC management commands.
|
||||
*
|
||||
* Commands:
|
||||
* /npc spawn kidnapper [player] - Spawn a kidnapper at location
|
||||
@@ -432,8 +432,7 @@ public class NPCCommand {
|
||||
var entities = level.getEntitiesOfClass(
|
||||
net.minecraft.world.entity.LivingEntity.class,
|
||||
player.getBoundingBox().inflate(radius),
|
||||
e ->
|
||||
e instanceof com.tiedup.remake.entities.AbstractTiedUpNpc
|
||||
e -> e instanceof com.tiedup.remake.entities.AbstractTiedUpNpc
|
||||
);
|
||||
|
||||
for (var entity : entities) {
|
||||
@@ -457,9 +456,7 @@ public class NPCCommand {
|
||||
return String.format("(%.1f, %.1f, %.1f)", x, y, z);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NPC Bondage Commands (from DamselTestCommand)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Find the nearest mod NPC (Damsel or Kidnapper) within 10 blocks.
|
||||
@@ -508,7 +505,10 @@ public class NPCCommand {
|
||||
return 0;
|
||||
}
|
||||
|
||||
npc.equip(BodyRegionV2.ARMS, new ItemStack(ModItems.getBind(BindVariant.ROPES)));
|
||||
npc.equip(
|
||||
BodyRegionV2.ARMS,
|
||||
new ItemStack(ModItems.getBind(BindVariant.ROPES))
|
||||
);
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
@@ -536,7 +536,10 @@ public class NPCCommand {
|
||||
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
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
@@ -566,7 +569,8 @@ public class NPCCommand {
|
||||
return 0;
|
||||
}
|
||||
|
||||
npc.equip(BodyRegionV2.EYES,
|
||||
npc.equip(
|
||||
BodyRegionV2.EYES,
|
||||
new ItemStack(ModItems.getBlindfold(BlindfoldVariant.CLASSIC))
|
||||
);
|
||||
context
|
||||
@@ -599,7 +603,10 @@ public class NPCCommand {
|
||||
return 0;
|
||||
}
|
||||
|
||||
npc.equip(BodyRegionV2.NECK, new ItemStack(ModItems.CLASSIC_COLLAR.get()));
|
||||
npc.equip(
|
||||
BodyRegionV2.NECK,
|
||||
new ItemStack(ModItems.CLASSIC_COLLAR.get())
|
||||
);
|
||||
context
|
||||
.getSource()
|
||||
.sendSuccess(
|
||||
@@ -644,7 +651,10 @@ public class NPCCommand {
|
||||
}
|
||||
|
||||
// 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(
|
||||
new ItemStack(ModItems.getBind(BindVariant.ROPES)),
|
||||
new ItemStack(ModItems.getGag(GagVariant.CLOTH_GAG)),
|
||||
|
||||
@@ -20,7 +20,7 @@ import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
/**
|
||||
* Social and RP commands for Phase 18.
|
||||
* Social and RP commands.
|
||||
*
|
||||
* Commands:
|
||||
* /blockplayer <player> - Block a player from interacting with you
|
||||
@@ -147,9 +147,7 @@ public class SocialCommand {
|
||||
return Commands.literal("talkinfo").executes(SocialCommand::talkInfo);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Block System
|
||||
// ========================================
|
||||
|
||||
private static int blockPlayer(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
@@ -284,9 +282,7 @@ public class SocialCommand {
|
||||
return SocialData.get(level).isBlocked(blocker, blocked);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// RP Commands
|
||||
// ========================================
|
||||
|
||||
private static int noRP(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
@@ -443,9 +439,7 @@ public class SocialCommand {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Talk Area
|
||||
// ========================================
|
||||
|
||||
private static int setTalkArea(
|
||||
CommandContext<CommandSourceStack> context,
|
||||
|
||||
@@ -9,8 +9,8 @@ import com.tiedup.remake.commands.CommandHelper;
|
||||
import com.tiedup.remake.core.SystemMessageManager;
|
||||
import com.tiedup.remake.items.ModItems;
|
||||
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.BlindfoldVariant;
|
||||
import com.tiedup.remake.items.base.EarplugsVariant;
|
||||
import com.tiedup.remake.items.base.GagVariant;
|
||||
import com.tiedup.remake.items.base.ItemCollar;
|
||||
@@ -32,14 +32,18 @@ import net.minecraft.world.item.ItemStack;
|
||||
@SuppressWarnings("null")
|
||||
public class BondageSubCommand {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
public static void register(
|
||||
LiteralArgumentBuilder<CommandSourceStack> root
|
||||
) {
|
||||
// /tiedup tie <player>
|
||||
root.then(
|
||||
Commands.literal("tie")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::tie)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::tie)
|
||||
)
|
||||
);
|
||||
// /tiedup untie <player>
|
||||
@@ -47,8 +51,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("untie")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::untie)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::untie)
|
||||
)
|
||||
);
|
||||
// /tiedup gag <player>
|
||||
@@ -56,8 +62,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("gag")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::gag)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::gag)
|
||||
)
|
||||
);
|
||||
// /tiedup ungag <player>
|
||||
@@ -65,8 +73,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("ungag")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::ungag)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::ungag)
|
||||
)
|
||||
);
|
||||
// /tiedup blindfold <player>
|
||||
@@ -74,8 +84,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("blindfold")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::blindfold)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::blindfold)
|
||||
)
|
||||
);
|
||||
// /tiedup unblind <player>
|
||||
@@ -83,8 +95,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("unblind")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::unblind)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::unblind)
|
||||
)
|
||||
);
|
||||
// /tiedup collar <player>
|
||||
@@ -92,8 +106,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("collar")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::collar)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::collar)
|
||||
)
|
||||
);
|
||||
// /tiedup takecollar <player>
|
||||
@@ -101,8 +117,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("takecollar")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::takecollar)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::takecollar)
|
||||
)
|
||||
);
|
||||
// /tiedup takeearplugs <player>
|
||||
@@ -110,8 +128,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("takeearplugs")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::takeearplugs)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::takeearplugs)
|
||||
)
|
||||
);
|
||||
// /tiedup putearplugs <player>
|
||||
@@ -119,8 +139,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("putearplugs")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::putearplugs)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::putearplugs)
|
||||
)
|
||||
);
|
||||
// /tiedup takeclothes <player>
|
||||
@@ -128,8 +150,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("takeclothes")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::takeclothes)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::takeclothes)
|
||||
)
|
||||
);
|
||||
// /tiedup putclothes <player>
|
||||
@@ -137,8 +161,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("putclothes")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::putclothes)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::putclothes)
|
||||
)
|
||||
);
|
||||
// /tiedup fullyrestrain <player>
|
||||
@@ -146,8 +172,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("fullyrestrain")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::fullyrestrain)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::fullyrestrain)
|
||||
)
|
||||
);
|
||||
// /tiedup enslave <player>
|
||||
@@ -155,8 +183,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("enslave")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::enslave)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::enslave)
|
||||
)
|
||||
);
|
||||
// /tiedup free <player>
|
||||
@@ -164,8 +194,10 @@ public class BondageSubCommand {
|
||||
Commands.literal("free")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(BondageSubCommand::free)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(BondageSubCommand::free)
|
||||
)
|
||||
);
|
||||
// /tiedup adjust <player> <type:gag|blindfold|all> <value>
|
||||
@@ -173,29 +205,26 @@ public class BondageSubCommand {
|
||||
Commands.literal("adjust")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.then(
|
||||
Commands.argument("type", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> {
|
||||
builder.suggest("gag");
|
||||
builder.suggest("blindfold");
|
||||
builder.suggest("all");
|
||||
return builder.buildFuture();
|
||||
})
|
||||
.then(
|
||||
Commands.argument(
|
||||
"value",
|
||||
FloatArgumentType.floatArg(-4.0f, 4.0f)
|
||||
).executes(BondageSubCommand::adjust)
|
||||
)
|
||||
)
|
||||
Commands.argument("player", EntityArgument.player()).then(
|
||||
Commands.argument("type", StringArgumentType.word())
|
||||
.suggests((ctx, builder) -> {
|
||||
builder.suggest("gag");
|
||||
builder.suggest("blindfold");
|
||||
builder.suggest("all");
|
||||
return builder.buildFuture();
|
||||
})
|
||||
.then(
|
||||
Commands.argument(
|
||||
"value",
|
||||
FloatArgumentType.floatArg(-4.0f, 4.0f)
|
||||
).executes(BondageSubCommand::adjust)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Command Implementations
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* /tiedup tie <player>
|
||||
@@ -582,7 +611,9 @@ public class BondageSubCommand {
|
||||
boolean adjustedBlindfold = false;
|
||||
|
||||
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()) {
|
||||
AdjustmentHelper.setAdjustment(gag, value);
|
||||
adjustedGag = true;
|
||||
@@ -590,7 +621,9 @@ public class BondageSubCommand {
|
||||
}
|
||||
|
||||
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()) {
|
||||
AdjustmentHelper.setAdjustment(blindfold, value);
|
||||
adjustedBlindfold = true;
|
||||
|
||||
@@ -21,7 +21,9 @@ import net.minecraft.server.level.ServerPlayer;
|
||||
@SuppressWarnings("null")
|
||||
public class DebtSubCommand {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
public static void register(
|
||||
LiteralArgumentBuilder<CommandSourceStack> root
|
||||
) {
|
||||
// /tiedup debt <player> [set|add|remove <amount>]
|
||||
root.then(
|
||||
Commands.literal("debt")
|
||||
@@ -61,9 +63,7 @@ public class DebtSubCommand {
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Command Implementations
|
||||
// ========================================
|
||||
|
||||
private static int debtShow(CommandContext<CommandSourceStack> context)
|
||||
throws CommandSyntaxException {
|
||||
|
||||
@@ -20,21 +20,23 @@ import net.minecraft.server.level.ServerPlayer;
|
||||
@SuppressWarnings("null")
|
||||
public class InventorySubCommand {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
public static void register(
|
||||
LiteralArgumentBuilder<CommandSourceStack> root
|
||||
) {
|
||||
// /tiedup returnstuff <player>
|
||||
root.then(
|
||||
Commands.literal("returnstuff")
|
||||
.requires(CommandHelper.REQUIRES_OP)
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(InventorySubCommand::returnstuff)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(InventorySubCommand::returnstuff)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Command Implementations
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* /tiedup returnstuff <player>
|
||||
|
||||
@@ -23,7 +23,9 @@ import net.minecraft.world.entity.MobSpawnType;
|
||||
@SuppressWarnings("null")
|
||||
public class MasterTestSubCommand {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
public static void register(
|
||||
LiteralArgumentBuilder<CommandSourceStack> root
|
||||
) {
|
||||
// /tiedup mastertest
|
||||
root.then(
|
||||
Commands.literal("mastertest")
|
||||
@@ -53,9 +55,7 @@ public class MasterTestSubCommand {
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Command Implementations
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* /tiedup mastertest
|
||||
@@ -95,7 +95,10 @@ public class MasterTestSubCommand {
|
||||
master.setPetPlayer(player);
|
||||
master.putPetCollar(player);
|
||||
|
||||
CommandHelper.syncPlayerState(player, PlayerBindState.getInstance(player));
|
||||
CommandHelper.syncPlayerState(
|
||||
player,
|
||||
PlayerBindState.getInstance(player)
|
||||
);
|
||||
|
||||
String masterName = master.getNpcName();
|
||||
if (masterName == null || masterName.isEmpty()) masterName = "Master";
|
||||
@@ -138,7 +141,10 @@ public class MasterTestSubCommand {
|
||||
if (!master.hasPet()) {
|
||||
master.setPetPlayer(player);
|
||||
master.putPetCollar(player);
|
||||
CommandHelper.syncPlayerState(player, PlayerBindState.getInstance(player));
|
||||
CommandHelper.syncPlayerState(
|
||||
player,
|
||||
PlayerBindState.getInstance(player)
|
||||
);
|
||||
}
|
||||
|
||||
master.setMasterState(MasterState.HUMAN_CHAIR);
|
||||
@@ -195,7 +201,10 @@ public class MasterTestSubCommand {
|
||||
if (!master.hasPet()) {
|
||||
master.setPetPlayer(player);
|
||||
master.putPetCollar(player);
|
||||
CommandHelper.syncPlayerState(player, PlayerBindState.getInstance(player));
|
||||
CommandHelper.syncPlayerState(
|
||||
player,
|
||||
PlayerBindState.getInstance(player)
|
||||
);
|
||||
}
|
||||
|
||||
master.setMasterState(targetState);
|
||||
|
||||
@@ -20,7 +20,9 @@ import net.minecraft.server.level.ServerPlayer;
|
||||
@SuppressWarnings("null")
|
||||
public class TestAnimSubCommand {
|
||||
|
||||
public static void register(LiteralArgumentBuilder<CommandSourceStack> root) {
|
||||
public static void register(
|
||||
LiteralArgumentBuilder<CommandSourceStack> root
|
||||
) {
|
||||
// /tiedup testanim <animId> [player]
|
||||
// /tiedup testanim stop [player]
|
||||
root.then(
|
||||
@@ -30,34 +32,36 @@ public class TestAnimSubCommand {
|
||||
Commands.literal("stop")
|
||||
.executes(ctx -> testAnimStop(ctx, null))
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(ctx ->
|
||||
testAnimStop(
|
||||
ctx,
|
||||
EntityArgument.getPlayer(ctx, "player")
|
||||
)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(ctx ->
|
||||
testAnimStop(
|
||||
ctx,
|
||||
EntityArgument.getPlayer(ctx, "player")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(
|
||||
Commands.argument("animId", StringArgumentType.string())
|
||||
.executes(ctx -> testAnim(ctx, null))
|
||||
.then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.executes(ctx ->
|
||||
testAnim(
|
||||
ctx,
|
||||
EntityArgument.getPlayer(ctx, "player")
|
||||
)
|
||||
Commands.argument(
|
||||
"player",
|
||||
EntityArgument.player()
|
||||
).executes(ctx ->
|
||||
testAnim(
|
||||
ctx,
|
||||
EntityArgument.getPlayer(ctx, "player")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Command Implementations
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* /tiedup testanim <animId> [player]
|
||||
|
||||
@@ -10,10 +10,10 @@ import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.state.IRestrainable;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Central coordinator for all MCA-TiedUp integration.
|
||||
@@ -48,9 +48,7 @@ public class MCABondageManager {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// LIFECYCLE EVENTS
|
||||
// ========================================
|
||||
|
||||
/** Dialogue broadcast radius in blocks */
|
||||
private static final double DIALOGUE_RADIUS = 16.0;
|
||||
@@ -290,9 +288,7 @@ public class MCABondageManager {
|
||||
syncBondageState(villager);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// AI CONTROL
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get or create AI controller for villager.
|
||||
@@ -354,9 +350,7 @@ public class MCABondageManager {
|
||||
return getAILevel(villager) != MCABondageAILevel.NONE;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// SYNC
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Sync all bondage state to tracking clients.
|
||||
@@ -403,9 +397,7 @@ public class MCABondageManager {
|
||||
});
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// QUERIES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get IRestrainable adapter for MCA villager.
|
||||
@@ -430,9 +422,7 @@ public class MCABondageManager {
|
||||
return aiControllers.containsKey(living);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CLEANUP
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Remove all tracking for a villager.
|
||||
|
||||
@@ -90,9 +90,7 @@ public class MCABondageAIController {
|
||||
personality != null ? personality : MCAPersonality.UNKNOWN;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// LEVEL MANAGEMENT
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Recalculate and apply appropriate AI level based on current state.
|
||||
@@ -215,9 +213,7 @@ public class MCABondageAIController {
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// SPEED REDUCTION
|
||||
// ========================================
|
||||
|
||||
private void applySpeedReduction() {
|
||||
LivingEntity villager = villagerRef.get();
|
||||
@@ -248,9 +244,7 @@ public class MCABondageAIController {
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// GOAL INJECTION (MODIFIED LEVEL)
|
||||
// ========================================
|
||||
|
||||
private void injectBondageGoals() {
|
||||
LivingEntity villager = villagerRef.get();
|
||||
@@ -326,9 +320,7 @@ public class MCABondageAIController {
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// FULL AI OVERRIDE
|
||||
// ========================================
|
||||
|
||||
private void backupAndClearGoals() {
|
||||
LivingEntity villager = villagerRef.get();
|
||||
@@ -399,9 +391,7 @@ public class MCABondageAIController {
|
||||
);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// MCA BRAIN CONTROL
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Suspend MCA's brain activities (for OVERRIDE level).
|
||||
@@ -503,9 +493,7 @@ public class MCABondageAIController {
|
||||
mcaBrainSuspended = false;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CLEANUP
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Clean up all AI modifications.
|
||||
|
||||
@@ -7,14 +7,14 @@ import com.tiedup.remake.items.base.IHasGaggingEffect;
|
||||
import com.tiedup.remake.items.base.IHasResistance;
|
||||
import com.tiedup.remake.items.base.ILockable;
|
||||
import com.tiedup.remake.items.base.ItemBind;
|
||||
import com.tiedup.remake.state.ICaptor;
|
||||
import com.tiedup.remake.state.IRestrainable;
|
||||
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.util.tasks.ItemTask;
|
||||
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 javax.annotation.Nullable;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
@@ -46,9 +46,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
this.cap = cap;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 1. CAPTURE LIFECYCLE
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public boolean getCapturedBy(ICaptor captor) {
|
||||
@@ -114,9 +112,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
getCapturedBy(newCaptor);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 2. STATE QUERIES - CAPTURE
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public boolean isEnslavable() {
|
||||
@@ -214,9 +210,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 3. STATE QUERIES - BONDAGE EQUIPMENT
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public boolean isTiedUp() {
|
||||
@@ -301,9 +295,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 4. EQUIPMENT MANAGEMENT - PUT ON
|
||||
// ========================================
|
||||
|
||||
public void putBindOn(ItemStack bind) {
|
||||
cap.setBind(bind);
|
||||
@@ -357,9 +349,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
checkMittensAfterApply();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 5. EQUIPMENT MANAGEMENT - UNEQUIP (V2)
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack unequip(BodyRegionV2 region) {
|
||||
@@ -395,7 +385,9 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
ItemStack current = cap.getGag();
|
||||
if (current.isEmpty()) yield ItemStack.EMPTY;
|
||||
cap.setGag(ItemStack.EMPTY);
|
||||
MCABondageManager.getInstance().onSensoryRestrictionChanged(entity);
|
||||
MCABondageManager.getInstance().onSensoryRestrictionChanged(
|
||||
entity
|
||||
);
|
||||
syncToClients();
|
||||
yield current;
|
||||
}
|
||||
@@ -403,7 +395,9 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
ItemStack current = cap.getBlindfold();
|
||||
if (current.isEmpty()) yield ItemStack.EMPTY;
|
||||
cap.setBlindfold(ItemStack.EMPTY);
|
||||
MCABondageManager.getInstance().onSensoryRestrictionChanged(entity);
|
||||
MCABondageManager.getInstance().onSensoryRestrictionChanged(
|
||||
entity
|
||||
);
|
||||
syncToClients();
|
||||
yield current;
|
||||
}
|
||||
@@ -425,9 +419,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 5b. EQUIPMENT MANAGEMENT - TAKE OFF (local helpers)
|
||||
// ========================================
|
||||
|
||||
public ItemStack takeBindOff() {
|
||||
ItemStack current = cap.getBind();
|
||||
@@ -509,9 +501,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
return current;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// V2 Region-Based Equipment Access
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack getEquipment(BodyRegionV2 region) {
|
||||
@@ -537,16 +527,19 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
case NECK -> putCollarOn(stack);
|
||||
case TORSO -> putClothesOn(stack);
|
||||
case HANDS -> putMittensOn(stack);
|
||||
default -> {}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 7. EQUIPMENT MANAGEMENT - REPLACE (V2 region-based)
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public ItemStack replaceEquipment(BodyRegionV2 region, ItemStack newStack, boolean force) {
|
||||
public ItemStack replaceEquipment(
|
||||
BodyRegionV2 region,
|
||||
ItemStack newStack,
|
||||
boolean force
|
||||
) {
|
||||
return switch (region) {
|
||||
case ARMS -> {
|
||||
ItemStack old = cap.getBind();
|
||||
@@ -580,7 +573,9 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
if (!force && isLocked(old, false)) yield ItemStack.EMPTY;
|
||||
cap.setCollar(newStack);
|
||||
if (newStack.getItem() instanceof IHasResistance resistance) {
|
||||
cap.setCollarResistance(resistance.getBaseResistance(entity));
|
||||
cap.setCollarResistance(
|
||||
resistance.getBaseResistance(entity)
|
||||
);
|
||||
}
|
||||
yield old;
|
||||
}
|
||||
@@ -599,9 +594,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 8. BULK OPERATIONS
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void applyBondage(
|
||||
@@ -694,9 +687,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
return count;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 9. CLOTHES PERMISSION SYSTEM
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public boolean canTakeOffClothes(Player player) {
|
||||
@@ -714,9 +705,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 10. SPECIAL INTERACTIONS
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public void tighten(Player tightener) {
|
||||
@@ -777,9 +766,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 11. POST-APPLY CALLBACKS
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Sync bondage state to tracking clients.
|
||||
@@ -819,9 +806,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
syncToClients();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 12. DEATH & LIFECYCLE
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public boolean onDeathKidnapped(Level world) {
|
||||
@@ -835,9 +820,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 13. UTILITY & METADATA
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public UUID getKidnappedUniqueId() {
|
||||
@@ -893,9 +876,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 14.5. RESISTANCE SYSTEM
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public int getCurrentBindResistance() {
|
||||
@@ -917,9 +898,7 @@ public class MCAKidnappedAdapter implements IRestrainable {
|
||||
cap.setCollarResistance(resistance);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 15. ENTITY REFERENCE
|
||||
// ========================================
|
||||
|
||||
@Override
|
||||
public LivingEntity asLivingEntity() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.tiedup.remake.compat.mca.capability;
|
||||
|
||||
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.v2.BodyRegionV2;
|
||||
import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
@@ -20,9 +20,7 @@ import net.minecraftforge.common.util.INBTSerializable;
|
||||
*/
|
||||
public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
|
||||
|
||||
// ========================================
|
||||
// BONDAGE EQUIPMENT SLOTS
|
||||
// ========================================
|
||||
|
||||
private ItemStack bind = ItemStack.EMPTY;
|
||||
private ItemStack gag = ItemStack.EMPTY;
|
||||
@@ -32,9 +30,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
|
||||
private ItemStack clothes = ItemStack.EMPTY;
|
||||
private ItemStack mittens = ItemStack.EMPTY;
|
||||
|
||||
// ========================================
|
||||
// CAPTURE STATE
|
||||
// ========================================
|
||||
|
||||
/** UUID of the captor entity (null if not captured) */
|
||||
@Nullable
|
||||
@@ -47,23 +43,17 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
|
||||
@Nullable
|
||||
private ItemTask salePrice = null;
|
||||
|
||||
// ========================================
|
||||
// RESISTANCE VALUES
|
||||
// ========================================
|
||||
|
||||
private int bindResistance = 0;
|
||||
private int collarResistance = 0;
|
||||
|
||||
// ========================================
|
||||
// TIEDUP TRAIT
|
||||
// ========================================
|
||||
|
||||
/** TiedUp-specific trait (MASO, REBELLIOUS, etc.) */
|
||||
private TiedUpTrait trait = TiedUpTrait.NONE;
|
||||
|
||||
// ========================================
|
||||
// EQUIPMENT GETTERS
|
||||
// ========================================
|
||||
|
||||
public ItemStack getBind() {
|
||||
return bind;
|
||||
@@ -109,9 +99,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// EQUIPMENT SETTERS
|
||||
// ========================================
|
||||
|
||||
public void setBind(ItemStack stack) {
|
||||
this.bind = stack.copy();
|
||||
@@ -153,7 +141,8 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
|
||||
case NECK -> setCollar(stack);
|
||||
case TORSO -> setClothes(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;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STATE QUERIES
|
||||
// ========================================
|
||||
|
||||
public boolean hasBind() {
|
||||
return !bind.isEmpty();
|
||||
@@ -198,9 +185,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
|
||||
return !mittens.isEmpty();
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CAPTURE STATE
|
||||
// ========================================
|
||||
|
||||
@Nullable
|
||||
public UUID getCaptorUUID() {
|
||||
@@ -232,9 +217,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
|
||||
this.salePrice = price;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// RESISTANCE
|
||||
// ========================================
|
||||
|
||||
public int getBindResistance() {
|
||||
return bindResistance;
|
||||
@@ -252,9 +235,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
|
||||
this.collarResistance = Math.max(0, resistance);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TIEDUP TRAIT
|
||||
// ========================================
|
||||
|
||||
public TiedUpTrait getTrait() {
|
||||
return trait;
|
||||
@@ -264,9 +245,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
|
||||
this.trait = trait != null ? trait : TiedUpTrait.NONE;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// CLEAR ALL
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Clear all bondage equipment.
|
||||
@@ -295,9 +274,7 @@ public class MCAKidnappedCapability implements INBTSerializable<CompoundTag> {
|
||||
// trait is NOT cleared - it persists
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// NBT SERIALIZATION
|
||||
// ========================================
|
||||
|
||||
private static final String TAG_BIND = "Bind";
|
||||
private static final String TAG_GAG = "Gag";
|
||||
|
||||
@@ -24,9 +24,7 @@ public class MCADialogueManager {
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
// ========================================
|
||||
// BEING TIED DIALOGUES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get dialogue when being tied up.
|
||||
@@ -151,9 +149,7 @@ public class MCADialogueManager {
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// TIED IDLE DIALOGUES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get dialogue for idle state while tied.
|
||||
@@ -232,9 +228,7 @@ public class MCADialogueManager {
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// STRUGGLE DIALOGUES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get dialogue for struggling.
|
||||
@@ -294,9 +288,7 @@ public class MCADialogueManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// FREED DIALOGUES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get dialogue for being freed.
|
||||
@@ -336,9 +328,7 @@ public class MCADialogueManager {
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// COLLAR DIALOGUES
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get dialogue for collar being put on.
|
||||
@@ -376,9 +366,7 @@ public class MCADialogueManager {
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// BROADCAST HELPER
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Broadcast a dialogue message from a villager to nearby players.
|
||||
@@ -412,9 +400,7 @@ public class MCADialogueManager {
|
||||
});
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// UTILITY
|
||||
// ========================================
|
||||
|
||||
private static String pickRandom(String... options) {
|
||||
return options[RANDOM.nextInt(options.length)];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user