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.
This commit is contained in:
504
src/main/java/com/tiedup/remake/items/GenericKnife.java
Normal file
504
src/main/java/com/tiedup/remake/items/GenericKnife.java
Normal file
@@ -0,0 +1,504 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user