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:
397
src/main/java/com/tiedup/remake/tasks/UntyingPlayerTask.java
Normal file
397
src/main/java/com/tiedup/remake/tasks/UntyingPlayerTask.java
Normal file
@@ -0,0 +1,397 @@
|
||||
package com.tiedup.remake.tasks;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.entities.EntityDamsel;
|
||||
import com.tiedup.remake.entities.NpcTypeHelper;
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.network.action.PacketUntying;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||
import java.util.Map;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Phase 6: Concrete untying task for freeing a tied entity.
|
||||
* Phase 14.2.6: Unified to work with both Players and NPCs.
|
||||
*
|
||||
* Based on original UntyingPlayerTask from 1.12.2
|
||||
*
|
||||
* This task:
|
||||
* 1. Tracks progress as the helper repeatedly right-clicks the tied target
|
||||
* 2. Sends progress updates to both helper and target clients (if player)
|
||||
* 3. Removes all restraints and drops items when the timer completes
|
||||
* 4. Sets up target's restraint state for client-side visualization (if player)
|
||||
*
|
||||
* Epic 5F: Uses V2EquipmentHelper/BodyRegionV2.
|
||||
*/
|
||||
public class UntyingPlayerTask extends UntyingTask {
|
||||
|
||||
/** The player performing the untying action. */
|
||||
private Player helper;
|
||||
|
||||
/**
|
||||
* Create a new untying task.
|
||||
*
|
||||
* @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 UntyingPlayerTask(
|
||||
IBondageState targetState,
|
||||
LivingEntity targetEntity,
|
||||
int seconds,
|
||||
Level level
|
||||
) {
|
||||
super(targetState, targetEntity, seconds, level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new untying task with helper reference.
|
||||
*
|
||||
* @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 helper The player performing the untying
|
||||
*/
|
||||
public UntyingPlayerTask(
|
||||
IBondageState targetState,
|
||||
LivingEntity targetEntity,
|
||||
int seconds,
|
||||
Level level,
|
||||
Player helper
|
||||
) {
|
||||
super(targetState, targetEntity, seconds, level);
|
||||
this.helper = helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the helper (the player doing the untying).
|
||||
*/
|
||||
public void setHelper(Player helper) {
|
||||
this.helper = helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the untying progress.
|
||||
* Called each time the helper right-clicks the tied target.
|
||||
*
|
||||
* In the new progress-based system, this method only:
|
||||
* 1. Marks this tick as "active" (progress will increase in tick())
|
||||
* 2. Validates helper is still close to target
|
||||
*
|
||||
* The actual progress increment and completion check happen in tick().
|
||||
*/
|
||||
@Override
|
||||
public synchronized void update() {
|
||||
// ========================================
|
||||
// SECURITY: Validate helper is still close to target
|
||||
// ========================================
|
||||
if (helper != null && targetEntity != null) {
|
||||
double distance = helper.distanceTo(targetEntity);
|
||||
if (distance > 4.0) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[UntyingPlayerTask] Helper {} moved too far from target ({} blocks), cancelling",
|
||||
helper.getName().getString(),
|
||||
String.format("%.1f", distance)
|
||||
);
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check line-of-sight
|
||||
if (!helper.hasLineOfSight(targetEntity)) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[UntyingPlayerTask] Helper {} lost line of sight to target, cancelling",
|
||||
helper.getName().getString()
|
||||
);
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark this tick as active (player is clicking on target)
|
||||
super.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send progress packets to both helper and target (if players).
|
||||
* Called every tick from RestraintTaskTickHandler.onPlayerTick().
|
||||
*/
|
||||
@Override
|
||||
public void sendProgressPackets() {
|
||||
if (stopped) return;
|
||||
|
||||
String helperName =
|
||||
helper != null ? helper.getName().getString() : "Someone";
|
||||
String targetName = targetEntity.getName().getString();
|
||||
|
||||
// Packet to victim: isHelper=false, shows helper's name
|
||||
if (targetEntity instanceof ServerPlayer serverTarget) {
|
||||
PacketUntying victimPacket = new PacketUntying(
|
||||
this.getState(),
|
||||
this.getMaxSeconds(),
|
||||
false,
|
||||
helperName
|
||||
);
|
||||
ModNetwork.sendToPlayer(victimPacket, serverTarget);
|
||||
}
|
||||
|
||||
// Packet to helper: isHelper=true, shows target's name
|
||||
if (helper instanceof ServerPlayer serverHelper) {
|
||||
PacketUntying helperPacket = new PacketUntying(
|
||||
this.getState(),
|
||||
this.getMaxSeconds(),
|
||||
true,
|
||||
targetName
|
||||
);
|
||||
ModNetwork.sendToPlayer(helperPacket, serverHelper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the task completes successfully.
|
||||
* Drops bondage items, frees the target, and sends completion packets.
|
||||
*/
|
||||
@Override
|
||||
protected void onComplete() {
|
||||
// Verify target entity still exists and is alive
|
||||
if (!isTargetValid()) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[UntyingPlayerTask] Target entity no longer valid, cancelling task"
|
||||
);
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[UntyingPlayerTask] Untying complete for {}",
|
||||
targetEntity.getName().getString()
|
||||
);
|
||||
|
||||
// Drop all bondage items on the ground
|
||||
dropBondageItems();
|
||||
|
||||
// Remove all restraints from target
|
||||
untieTarget();
|
||||
|
||||
// Handle Damsel-specific rewards
|
||||
if (targetEntity instanceof EntityDamsel damsel && NpcTypeHelper.isDamselOnly(targetEntity) && helper != null) {
|
||||
// Reward the savior (gives emeralds and marks player as savior)
|
||||
damsel.rewardSavior(helper);
|
||||
}
|
||||
|
||||
// Mark task as stopped
|
||||
stop();
|
||||
|
||||
// Send completion packets to both parties
|
||||
String helperName =
|
||||
helper != null ? helper.getName().getString() : "Someone";
|
||||
String targetName = targetEntity.getName().getString();
|
||||
|
||||
if (targetEntity instanceof ServerPlayer serverTarget) {
|
||||
PacketUntying completionPacket = new PacketUntying(
|
||||
-1,
|
||||
this.getMaxSeconds(),
|
||||
false,
|
||||
helperName
|
||||
);
|
||||
ModNetwork.sendToPlayer(completionPacket, serverTarget);
|
||||
|
||||
PlayerBindState playerState = PlayerBindState.getInstance(
|
||||
serverTarget
|
||||
);
|
||||
if (playerState != null) {
|
||||
playerState.setRestrainedState(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (helper instanceof ServerPlayer serverHelper) {
|
||||
PacketUntying completionPacket = new PacketUntying(
|
||||
-1,
|
||||
this.getMaxSeconds(),
|
||||
true,
|
||||
targetName
|
||||
);
|
||||
ModNetwork.sendToPlayer(completionPacket, serverHelper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (PacketUntying) 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all bondage items on the ground.
|
||||
* Works for both players (via V2EquipmentHelper) and NPCs (via IBondageState).
|
||||
*/
|
||||
private void dropBondageItems() {
|
||||
// For player targets: use V2EquipmentHelper to get all equipped items
|
||||
if (targetEntity instanceof Player player) {
|
||||
Map<BodyRegionV2, ItemStack> equipped = V2EquipmentHelper.getAllEquipped(player);
|
||||
for (Map.Entry<BodyRegionV2, ItemStack> entry : equipped.entrySet()) {
|
||||
ItemStack stack = entry.getValue();
|
||||
if (!stack.isEmpty()) {
|
||||
// Drop item at player's position
|
||||
ItemEntity itemEntity = new ItemEntity(
|
||||
targetEntity.level(),
|
||||
targetEntity.getX(),
|
||||
targetEntity.getY(),
|
||||
targetEntity.getZ(),
|
||||
stack.copy()
|
||||
);
|
||||
|
||||
targetEntity.level().addFreshEntity(itemEntity);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[UntyingPlayerTask] Dropped {} from region {}",
|
||||
stack.getHoverName().getString(),
|
||||
entry.getKey().name()
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For NPC targets: use IBondageState interface
|
||||
// Drop bind if present
|
||||
ItemStack bind = targetState.getEquipment(BodyRegionV2.ARMS);
|
||||
if (bind != null && !bind.isEmpty()) {
|
||||
ItemEntity itemEntity = new ItemEntity(
|
||||
targetEntity.level(),
|
||||
targetEntity.getX(),
|
||||
targetEntity.getY(),
|
||||
targetEntity.getZ(),
|
||||
bind.copy()
|
||||
);
|
||||
targetEntity.level().addFreshEntity(itemEntity);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[UntyingPlayerTask] Dropped bind: {}",
|
||||
bind.getHoverName().getString()
|
||||
);
|
||||
}
|
||||
|
||||
// Drop gag if present
|
||||
ItemStack gag = targetState.getEquipment(BodyRegionV2.MOUTH);
|
||||
if (gag != null && !gag.isEmpty()) {
|
||||
ItemEntity itemEntity = new ItemEntity(
|
||||
targetEntity.level(),
|
||||
targetEntity.getX(),
|
||||
targetEntity.getY(),
|
||||
targetEntity.getZ(),
|
||||
gag.copy()
|
||||
);
|
||||
targetEntity.level().addFreshEntity(itemEntity);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[UntyingPlayerTask] Dropped gag: {}",
|
||||
gag.getHoverName().getString()
|
||||
);
|
||||
}
|
||||
|
||||
// Drop blindfold if present
|
||||
ItemStack blindfold = targetState.getEquipment(BodyRegionV2.EYES);
|
||||
if (blindfold != null && !blindfold.isEmpty()) {
|
||||
ItemEntity itemEntity = new ItemEntity(
|
||||
targetEntity.level(),
|
||||
targetEntity.getX(),
|
||||
targetEntity.getY(),
|
||||
targetEntity.getZ(),
|
||||
blindfold.copy()
|
||||
);
|
||||
targetEntity.level().addFreshEntity(itemEntity);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[UntyingPlayerTask] Dropped blindfold: {}",
|
||||
blindfold.getHoverName().getString()
|
||||
);
|
||||
}
|
||||
|
||||
// Drop earplugs if present
|
||||
ItemStack earplugs = targetState.getEquipment(BodyRegionV2.EARS);
|
||||
if (earplugs != null && !earplugs.isEmpty()) {
|
||||
ItemEntity itemEntity = new ItemEntity(
|
||||
targetEntity.level(),
|
||||
targetEntity.getX(),
|
||||
targetEntity.getY(),
|
||||
targetEntity.getZ(),
|
||||
earplugs.copy()
|
||||
);
|
||||
targetEntity.level().addFreshEntity(itemEntity);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[UntyingPlayerTask] Dropped earplugs: {}",
|
||||
earplugs.getHoverName().getString()
|
||||
);
|
||||
}
|
||||
|
||||
// Drop mittens if present
|
||||
ItemStack mittens = targetState.getEquipment(BodyRegionV2.HANDS);
|
||||
if (mittens != null && !mittens.isEmpty()) {
|
||||
ItemEntity itemEntity = new ItemEntity(
|
||||
targetEntity.level(),
|
||||
targetEntity.getX(),
|
||||
targetEntity.getY(),
|
||||
targetEntity.getZ(),
|
||||
mittens.copy()
|
||||
);
|
||||
targetEntity.level().addFreshEntity(itemEntity);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[UntyingPlayerTask] Dropped mittens: {}",
|
||||
mittens.getHoverName().getString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all restraints from the target entity.
|
||||
* Works for both players and NPCs via IBondageState interface.
|
||||
*/
|
||||
private void untieTarget() {
|
||||
// For player targets: clear all V2 equipment (fires onUnequipped callbacks)
|
||||
if (targetEntity instanceof Player player) {
|
||||
V2EquipmentHelper.clearAll(player);
|
||||
|
||||
// Phase 17: Free from captivity/leash if applicable (player-specific)
|
||||
PlayerBindState playerState = PlayerBindState.getInstance(player);
|
||||
if (playerState != null && playerState.isCaptive()) {
|
||||
playerState.free();
|
||||
}
|
||||
} else {
|
||||
// For NPC targets: use IBondageState interface directly
|
||||
targetState.unequip(BodyRegionV2.ARMS);
|
||||
targetState.unequip(BodyRegionV2.MOUTH);
|
||||
targetState.unequip(BodyRegionV2.EYES);
|
||||
targetState.unequip(BodyRegionV2.EARS);
|
||||
targetState.unequip(BodyRegionV2.HANDS);
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[UntyingPlayerTask] Fully untied {}",
|
||||
targetEntity.getName().getString()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user