Strip all Phase references, TODO/FUTURE roadmap notes, and internal planning comments from the codebase. Run Prettier for consistent formatting across all Java files.
356 lines
12 KiB
Java
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
|
|
}
|
|
}
|