Files
TiedUp-/src/main/java/com/tiedup/remake/tasks/TyingPlayerTask.java
NotEvil a71093ba9c Remove internal phase comments and format code
Strip all Phase references, TODO/FUTURE roadmap notes, and internal
planning comments from the codebase. Run Prettier for consistent
formatting across all Java files.
2026-04-12 01:25:55 +02:00

356 lines
12 KiB
Java

package com.tiedup.remake.tasks;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.action.PacketTying;
import com.tiedup.remake.state.IBondageState;
import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
/**
*
* Based on original TyingPlayerTask from 1.12.2
*
* This task:
* 1. Tracks progress as the kidnapper repeatedly right-clicks the target
* 2. Sends progress updates to kidnapper (and target if it's a player)
* 3. Applies the bind/gag when the timer completes
* 4. Works with any LivingEntity that has an IBondageState state
*/
public class TyingPlayerTask extends TyingTask {
/** The player performing the tying action. */
protected Player kidnapper;
/** FIX: Source inventory slot to consume from when task completes */
private int sourceSlot = -1;
/** FIX: Source player whose inventory to consume from */
private Player sourcePlayer;
/**
* Create a new tying task.
*
* @param bind The bind/gag item to apply
* @param targetState The target's IBondageState state
* @param targetEntity The target entity (Player or NPC)
* @param seconds Total duration in seconds
* @param level The world
*/
public TyingPlayerTask(
ItemStack bind,
IBondageState targetState,
LivingEntity targetEntity,
int seconds,
Level level
) {
super(bind, targetState, targetEntity, seconds, level);
}
/**
* Create a new tying task with kidnapper reference.
*
* @param bind The bind/gag item to apply
* @param targetState The target's IBondageState state
* @param targetEntity The target entity (Player or NPC)
* @param seconds Total duration in seconds
* @param level The world
* @param kidnapper The player performing the tying
*/
public TyingPlayerTask(
ItemStack bind,
IBondageState targetState,
LivingEntity targetEntity,
int seconds,
Level level,
Player kidnapper
) {
super(bind, targetState, targetEntity, seconds, level);
this.kidnapper = kidnapper;
}
/**
* Set the kidnapper (the player doing the tying).
*/
public void setKidnapper(Player kidnapper) {
this.kidnapper = kidnapper;
}
/**
* FIX: Set the source inventory slot for item consumption.
* Called when task starts to track which slot to consume from.
*/
public void setSourceSlot(int slot) {
this.sourceSlot = slot;
}
/**
* FIX: Set the source player for item consumption.
* Called when task starts to track whose inventory to consume from.
*/
public void setSourcePlayer(Player player) {
this.sourcePlayer = player;
}
/**
* Update the tying progress.
* Called each time the kidnapper right-clicks the target (or left-clicks for self-tying).
*
* In the new progress-based system, this method only:
* 1. Validates kidnapper is still close to target
* 2. Marks this tick as "active" (progress will increase in tick())
*
* The actual progress increment and completion check happen in tick().
*/
@Override
public synchronized void update() {
// Check if this is self-tying (target == kidnapper)
boolean isSelfTying =
kidnapper != null && kidnapper.equals(targetEntity);
// SECURITY: Validate kidnapper is still close to target (skip for self-tying)
if (!isSelfTying && kidnapper != null && targetEntity != null) {
double distance = kidnapper.distanceTo(targetEntity);
if (distance > 4.0) {
TiedUpMod.LOGGER.debug(
"[TyingPlayerTask] Kidnapper {} moved too far from target ({} blocks), cancelling",
kidnapper.getName().getString(),
String.format("%.1f", distance)
);
stop();
return;
}
// Check line-of-sight
if (!kidnapper.hasLineOfSight(targetEntity)) {
TiedUpMod.LOGGER.debug(
"[TyingPlayerTask] Kidnapper {} lost line of sight to target, cancelling",
kidnapper.getName().getString()
);
stop();
return;
}
}
// Mark this tick as active (player is clicking on target)
super.update();
}
/**
* Send progress packets to both kidnapper and target (if players).
* Called every tick from ItemBind or RestraintTaskTickHandler.
*/
@Override
public void sendProgressPackets() {
if (stopped) return;
// Check if this is self-tying (target == kidnapper)
boolean isSelfTying =
kidnapper != null && kidnapper.equals(targetEntity);
String kidnapperName =
kidnapper != null ? kidnapper.getName().getString() : "Someone";
String targetName = targetEntity.getName().getString();
if (isSelfTying) {
// Self-tying: Send single packet with self-tying message
if (kidnapper instanceof ServerPlayer serverPlayer) {
PacketTying selfPacket = new PacketTying(
this.getState(),
this.getMaxSeconds(),
true, // isKidnapper (shows "Tying..." message)
"yourself" // Special indicator for self-tying
);
ModNetwork.sendToPlayer(selfPacket, serverPlayer);
}
} else {
// Normal tying: Send packets to both parties
// Packet to victim: isKidnapper=false, shows kidnapper's name
if (targetEntity instanceof ServerPlayer serverTarget) {
PacketTying victimPacket = new PacketTying(
this.getState(),
this.getMaxSeconds(),
false,
kidnapperName
);
ModNetwork.sendToPlayer(victimPacket, serverTarget);
}
// Packet to kidnapper: isKidnapper=true, shows target's name
if (kidnapper instanceof ServerPlayer serverKidnapper) {
PacketTying kidnapperPacket = new PacketTying(
this.getState(),
this.getMaxSeconds(),
true,
targetName
);
ModNetwork.sendToPlayer(kidnapperPacket, serverKidnapper);
}
}
}
/**
* Called when the task completes successfully.
* Applies the bind, consumes the item, and sends completion packets.
*/
@Override
protected void onComplete() {
// Verify target entity still exists and is alive
if (!isTargetValid()) {
TiedUpMod.LOGGER.warn(
"[TyingPlayerTask] Target entity no longer valid, cancelling task"
);
stop();
return;
}
TiedUpMod.LOGGER.info(
"[TyingPlayerTask] Tying complete for {}",
targetEntity.getName().getString()
);
// Apply the bind/gag to the target
targetState.equip(BodyRegionV2.ARMS, bind);
// FIX: Consume the item from the stored inventory slot
// This prevents duplication by consuming from the exact slot used to start the task
if (sourcePlayer != null && sourceSlot >= 0) {
ItemStack slotStack = sourcePlayer
.getInventory()
.getItem(sourceSlot);
if (
!slotStack.isEmpty() &&
ItemStack.isSameItemSameTags(slotStack, bind)
) {
slotStack.shrink(1);
TiedUpMod.LOGGER.debug(
"[TyingPlayerTask] Consumed bind item from slot {}",
sourceSlot
);
} else {
// Slot changed - try to find and consume from any matching slot
for (
int i = 0;
i < sourcePlayer.getInventory().getContainerSize();
i++
) {
ItemStack checkStack = sourcePlayer
.getInventory()
.getItem(i);
if (
!checkStack.isEmpty() &&
ItemStack.isSameItemSameTags(checkStack, bind)
) {
checkStack.shrink(1);
TiedUpMod.LOGGER.debug(
"[TyingPlayerTask] Consumed bind item from fallback slot {}",
i
);
break;
}
}
}
}
// Track who tied this entity (for reward anti-abuse)
if (
targetEntity instanceof
com.tiedup.remake.entities.EntityDamsel damsel
) {
damsel.setTiedBy(kidnapper);
}
// Mark task as stopped
stop();
sendCompletionPackets();
}
/**
* Send completion packets to kidnapper and/or target.
* Handles both self-tying and normal tying cases.
*/
protected void sendCompletionPackets() {
boolean isSelfTying =
kidnapper != null && kidnapper.equals(targetEntity);
String kidnapperName =
kidnapper != null ? kidnapper.getName().getString() : "Someone";
String targetName = targetEntity.getName().getString();
if (isSelfTying) {
if (kidnapper instanceof ServerPlayer serverPlayer) {
PacketTying completionPacket = new PacketTying(
-1,
this.getMaxSeconds(),
true,
"yourself"
);
ModNetwork.sendToPlayer(completionPacket, serverPlayer);
PlayerBindState playerState = PlayerBindState.getInstance(
serverPlayer
);
if (playerState != null) {
playerState.setRestrainedState(null);
}
}
} else {
if (targetEntity instanceof ServerPlayer serverTarget) {
PacketTying completionPacket = new PacketTying(
-1,
this.getMaxSeconds(),
false,
kidnapperName
);
ModNetwork.sendToPlayer(completionPacket, serverTarget);
PlayerBindState playerState = PlayerBindState.getInstance(
serverTarget
);
if (playerState != null) {
playerState.setRestrainedState(null);
}
}
if (kidnapper instanceof ServerPlayer serverKidnapper) {
PacketTying completionPacket = new PacketTying(
-1,
this.getMaxSeconds(),
true,
targetName
);
ModNetwork.sendToPlayer(completionPacket, serverKidnapper);
}
}
}
/**
* Set up the target's restraint state for client-side progress tracking.
* Only applies to player targets (NPCs don't need client-side state).
*
* Note: On dedicated servers, this is a no-op. The client receives
* progress packets (PacketTying) which create the PlayerStateTask locally.
*/
@Override
public void setUpTargetState() {
// Only set up state for player targets
if (!(targetEntity instanceof Player targetPlayer)) {
return;
}
// Server-side: nothing to do - client handles its own PlayerStateTask via packets
if (!targetPlayer.level().isClientSide) {
return;
}
// Client-side: set up local progress tracking (only reached in single-player/integrated server)
// Note: This code path is rarely used since packets handle client-side state
}
}