Files
TiedUp-/src/main/java/com/tiedup/remake/items/GenericKnife.java
NotEvil f6466360b6 Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies,
and third-party source dumps. Add proper README, update .gitignore,
clean up Makefile.
2026-04-12 00:51:22 +02:00

505 lines
16 KiB
Java

package com.tiedup.remake.items;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.IKnife;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.items.base.KnifeVariant;
import com.tiedup.remake.network.sync.SyncManager;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import java.util.List;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.UseAnim;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
/**
* Generic knife item created from KnifeVariant enum.
* Replaces individual knife classes (ItemStoneKnife, ItemIronKnife, ItemGoldenKnife).
*
* v2.5 Changes:
* - Added active cutting mechanic (hold right-click)
* - Per-tier cutting speed: Stone=5, Iron=8, Golden=12 resistance/second
* - Durability consumed per second = cutting speed (1 dura = 1 resistance)
* - Can cut binds directly or locked accessories
*/
public class GenericKnife extends Item implements IKnife {
private final KnifeVariant variant;
public GenericKnife(KnifeVariant variant) {
super(
new Item.Properties()
.stacksTo(1)
.durability(variant.getDurability())
);
this.variant = variant;
}
/**
* Get the variant this knife was created from.
*/
public KnifeVariant getVariant() {
return variant;
}
// ==================== TOOLTIP ====================
@Override
public void appendHoverText(
ItemStack stack,
@Nullable Level level,
List<Component> tooltip,
TooltipFlag flag
) {
super.appendHoverText(stack, level, tooltip, flag);
int remaining = stack.getMaxDamage() - stack.getDamageValue();
int speed = variant.getCuttingSpeed();
int cuttingSeconds = remaining / speed;
// Show cutting speed
tooltip.add(
Component.literal("Cutting speed: " + speed + " res/s").withStyle(
ChatFormatting.GRAY
)
);
// Show cutting time remaining
tooltip.add(
Component.literal(
"Cutting time: " +
cuttingSeconds +
"s (" +
remaining +
" total res)"
).withStyle(ChatFormatting.DARK_GRAY)
);
}
// ==================== USE MECHANICS ====================
@Override
public int getUseDuration(ItemStack stack) {
// Max use time: 5 minutes (very long, will stop naturally when bind breaks)
return 20 * 60 * 5;
}
@Override
public UseAnim getUseAnimation(ItemStack stack) {
return UseAnim.BOW; // Shows a "using" animation
}
/**
* Called when player right-clicks with knife.
* Starts cutting if:
* - Player is tied up (cuts bind)
* - Player has a knife cut target set (cuts accessory lock)
*/
@Override
public InteractionResultHolder<ItemStack> use(
Level level,
Player player,
InteractionHand hand
) {
ItemStack stack = player.getItemInHand(hand);
// Only check on server side for actual state
if (!level.isClientSide) {
PlayerBindState state = PlayerBindState.getInstance(player);
if (state == null) {
return InteractionResultHolder.pass(stack);
}
// v2.5: Block knife usage if wearing mittens
if (state.hasMittens()) {
TiedUpMod.LOGGER.debug(
"[GenericKnife] {} cannot use knife - wearing mittens",
player.getName().getString()
);
return InteractionResultHolder.fail(stack);
}
// Priority 1: If tied up, cut the bind
if (state.isTiedUp()) {
player.startUsingItem(hand);
return InteractionResultHolder.consume(stack);
}
// Priority 2: If accessory target selected (via StruggleChoiceScreen)
if (state.getKnifeCutTarget() != null) {
player.startUsingItem(hand);
return InteractionResultHolder.consume(stack);
}
// Priority 3: If wearing a collar (not tied), auto-target the collar
if (state.hasCollar()) {
state.setKnifeCutTarget(BodyRegionV2.NECK);
player.startUsingItem(hand);
return InteractionResultHolder.consume(stack);
}
// Priority 4: Check other accessories (gag, blindfold, etc.)
if (state.isGagged()) {
state.setKnifeCutTarget(BodyRegionV2.MOUTH);
player.startUsingItem(hand);
return InteractionResultHolder.consume(stack);
}
if (state.isBlindfolded()) {
state.setKnifeCutTarget(BodyRegionV2.EYES);
player.startUsingItem(hand);
return InteractionResultHolder.consume(stack);
}
if (state.hasEarplugs()) {
state.setKnifeCutTarget(BodyRegionV2.EARS);
player.startUsingItem(hand);
return InteractionResultHolder.consume(stack);
}
// Note: Don't auto-target mittens since you need hands to use knife
// Nothing to cut
return InteractionResultHolder.pass(stack);
}
// Client side - check mittens and allow use if valid target or has accessories
PlayerBindState state = PlayerBindState.getInstance(player);
if (
state != null &&
!state.hasMittens() &&
(state.isTiedUp() ||
state.getKnifeCutTarget() != null ||
state.hasCollar() ||
state.isGagged() ||
state.isBlindfolded() ||
state.hasEarplugs())
) {
player.startUsingItem(hand);
return InteractionResultHolder.consume(stack);
}
return InteractionResultHolder.pass(stack);
}
/**
* Called every tick while player holds right-click.
* Performs the actual cutting logic.
*/
@Override
public void onUseTick(
Level level,
LivingEntity entity,
ItemStack stack,
int remainingTicks
) {
if (level.isClientSide || !(entity instanceof ServerPlayer player)) {
return;
}
// Calculate how many ticks have been used
int usedTicks = getUseDuration(stack) - remainingTicks;
// Only process every 20 ticks (1 second)
if (usedTicks > 0 && usedTicks % 20 == 0) {
performCutTick(player, stack);
}
}
/**
* Called when player releases right-click or item breaks.
*/
@Override
public void releaseUsing(
ItemStack stack,
Level level,
LivingEntity entity,
int remainingTicks
) {
if (!level.isClientSide && entity instanceof ServerPlayer player) {
TiedUpMod.LOGGER.debug(
"[GenericKnife] {} stopped cutting",
player.getName().getString()
);
}
}
/**
* Perform one "tick" of cutting (called every second while held).
* Consumes durability and removes resistance based on variant's cutting speed.
*/
private void performCutTick(ServerPlayer player, ItemStack stack) {
PlayerBindState state = PlayerBindState.getInstance(player);
if (state == null) {
player.stopUsingItem();
return;
}
int speed = variant.getCuttingSpeed();
// Determine what to cut
if (state.isTiedUp()) {
// Cut BIND
cutBind(player, state, stack, speed);
} else if (state.getKnifeCutTarget() != null) {
// Cut ACCESSORY
cutAccessory(player, state, stack, speed);
} else {
// Nothing to cut
player.stopUsingItem();
return;
}
// Play cutting sound
player
.level()
.playSound(
null,
player.blockPosition(),
SoundEvents.SHEEP_SHEAR,
SoundSource.PLAYERS,
0.5f,
1.2f
);
// Consume durability equal to cutting speed
stack.hurtAndBreak(speed, player, p ->
p.broadcastBreakEvent(p.getUsedItemHand())
);
// Notify nearby guards (Kidnappers, Maids, Traders) about cutting noise
com.tiedup.remake.minigame.GuardNotificationHelper.notifyNearbyGuards(
player
);
// Force inventory sync so durability bar updates in real-time
player.inventoryMenu.broadcastChanges();
// Sync state to clients
SyncManager.syncBindState(player);
}
/**
* Cut the bind directly.
*/
private void cutBind(
ServerPlayer player,
PlayerBindState state,
ItemStack knifeStack,
int speed
) {
// Get bind stack for ILockable check
ItemStack bindStack = V2EquipmentHelper.getInRegion(
player,
BodyRegionV2.ARMS
);
if (
bindStack.isEmpty() ||
!(bindStack.getItem() instanceof ItemBind bind)
) {
player.stopUsingItem();
return;
}
// Reduce resistance by cutting speed
int currentRes = state.getCurrentBindResistance();
int newRes = Math.max(0, currentRes - speed);
state.setCurrentBindResistance(newRes);
TiedUpMod.LOGGER.debug(
"[GenericKnife] {} cutting bind: resistance {} -> {}",
player.getName().getString(),
currentRes,
newRes
);
// Check if escaped
if (newRes <= 0) {
state.getStruggleBinds().successActionExternal(state);
player.stopUsingItem();
TiedUpMod.LOGGER.info(
"[GenericKnife] {} escaped by cutting bind!",
player.getName().getString()
);
}
}
/**
* Cut an accessory - either removes lock resistance (if locked) or removes the item directly (if unlocked).
*/
private void cutAccessory(
ServerPlayer player,
PlayerBindState state,
ItemStack knifeStack,
int speed
) {
BodyRegionV2 target = state.getKnifeCutTarget();
if (target == null) {
player.stopUsingItem();
return;
}
ItemStack accessory = V2EquipmentHelper.getInRegion(
player,
target
);
if (accessory.isEmpty()) {
// Target doesn't exist
state.clearKnifeCutTarget();
player.stopUsingItem();
return;
}
// Check if the accessory is locked
boolean isLocked = false;
if (accessory.getItem() instanceof ILockable lockable) {
isLocked = lockable.isLocked(accessory);
}
if (!isLocked) {
// NOT locked - directly cut and remove the accessory
IBondageState kidnapped = KidnappedHelper.getKidnappedState(player);
if (kidnapped != null) {
ItemStack removed = removeAccessory(kidnapped, target);
if (!removed.isEmpty()) {
// Drop the removed accessory
kidnapped.kidnappedDropItem(removed);
TiedUpMod.LOGGER.info(
"[GenericKnife] {} cut off unlocked {}",
player.getName().getString(),
target
);
}
}
state.clearKnifeCutTarget();
player.stopUsingItem();
return;
}
// Accessory IS locked - reduce lock resistance
ILockable lockable = (ILockable) accessory.getItem();
int currentRes = lockable.getCurrentLockResistance(accessory);
int newRes = Math.max(0, currentRes - speed);
lockable.setCurrentLockResistance(accessory, newRes);
TiedUpMod.LOGGER.debug(
"[GenericKnife] {} cutting {} lock: resistance {} -> {}",
player.getName().getString(),
target,
currentRes,
newRes
);
// Check if lock is destroyed
if (newRes <= 0) {
// Destroy the lock (remove padlock, clear lock state)
lockable.setLockedByKeyUUID(accessory, null); // Unlocks and clears locked state
lockable.setLockable(accessory, false); // Remove padlock entirely
lockable.clearLockResistance(accessory);
lockable.setJammed(accessory, false);
state.clearKnifeCutTarget();
player.stopUsingItem();
TiedUpMod.LOGGER.info(
"[GenericKnife] {} cut through {} lock!",
player.getName().getString(),
target
);
}
}
/**
* Remove an accessory from the player and return it.
*/
private ItemStack removeAccessory(
IBondageState kidnapped,
BodyRegionV2 target
) {
switch (target) {
case NECK -> {
ItemStack collar = kidnapped.getEquipment(BodyRegionV2.NECK);
if (collar != null && !collar.isEmpty()) {
kidnapped.unequip(BodyRegionV2.NECK);
return collar;
}
}
case MOUTH -> {
ItemStack gag = kidnapped.getEquipment(BodyRegionV2.MOUTH);
if (gag != null && !gag.isEmpty()) {
kidnapped.unequip(BodyRegionV2.MOUTH);
return gag;
}
}
case EYES -> {
ItemStack blindfold = kidnapped.getEquipment(BodyRegionV2.EYES);
if (blindfold != null && !blindfold.isEmpty()) {
kidnapped.unequip(BodyRegionV2.EYES);
return blindfold;
}
}
case EARS -> {
ItemStack earplugs = kidnapped.getEquipment(BodyRegionV2.EARS);
if (earplugs != null && !earplugs.isEmpty()) {
kidnapped.unequip(BodyRegionV2.EARS);
return earplugs;
}
}
case HANDS -> {
ItemStack mittens = kidnapped.getEquipment(BodyRegionV2.HANDS);
if (mittens != null && !mittens.isEmpty()) {
kidnapped.unequip(BodyRegionV2.HANDS);
return mittens;
}
}
default -> {
}
}
return ItemStack.EMPTY;
}
/**
* Find a knife in the player's inventory.
*
* @param player The player to search
* @return The knife ItemStack, or empty if not found
*/
public static ItemStack findKnifeInInventory(Player player) {
// Check main hand first
ItemStack mainHand = player.getMainHandItem();
if (mainHand.getItem() instanceof IKnife) {
return mainHand;
}
// Check off hand
ItemStack offHand = player.getOffhandItem();
if (offHand.getItem() instanceof IKnife) {
return offHand;
}
// Check inventory
for (int i = 0; i < player.getInventory().getContainerSize(); i++) {
ItemStack stack = player.getInventory().getItem(i);
if (stack.getItem() instanceof IKnife) {
return stack;
}
}
return ItemStack.EMPTY;
}
}