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:
219
src/main/java/com/tiedup/remake/tasks/ForceFeedingTask.java
Normal file
219
src/main/java/com/tiedup/remake/tasks/ForceFeedingTask.java
Normal file
@@ -0,0 +1,219 @@
|
||||
package com.tiedup.remake.tasks;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.entities.EntityDamsel;
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.network.action.PacketForceFeeding;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
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.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Server-side task for force feeding a gagged entity.
|
||||
* Player must keep looking at the target for 5 seconds to complete.
|
||||
*/
|
||||
public class ForceFeedingTask extends TimedInteractTask {
|
||||
|
||||
private Player feeder;
|
||||
private ItemStack foodStack;
|
||||
private int sourceSlot;
|
||||
|
||||
public ForceFeedingTask(
|
||||
IBondageState targetState,
|
||||
LivingEntity targetEntity,
|
||||
int seconds,
|
||||
Level level,
|
||||
Player feeder,
|
||||
ItemStack foodStack,
|
||||
int sourceSlot
|
||||
) {
|
||||
super(targetState, targetEntity, seconds, level);
|
||||
this.feeder = feeder;
|
||||
this.foodStack = foodStack.copy();
|
||||
this.sourceSlot = sourceSlot;
|
||||
}
|
||||
|
||||
public void setFeeder(Player feeder) {
|
||||
this.feeder = feeder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void update() {
|
||||
// Cancel if feeder is gone or dead
|
||||
if (feeder == null || !feeder.isAlive() || feeder.isRemoved()) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetEntity != null) {
|
||||
double distance = feeder.distanceTo(targetEntity);
|
||||
if (distance > 4.0) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[ForceFeedingTask] Feeder {} moved too far from target ({} blocks), cancelling",
|
||||
feeder.getName().getString(),
|
||||
String.format("%.1f", distance)
|
||||
);
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!feeder.hasLineOfSight(targetEntity)) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[ForceFeedingTask] Feeder {} lost line of sight to target, cancelling",
|
||||
feeder.getName().getString()
|
||||
);
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
super.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onComplete() {
|
||||
if (!isTargetValid()) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[ForceFeedingTask] Target entity no longer valid, cancelling task"
|
||||
);
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (feeder == null || !feeder.isAlive()) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[ForceFeedingTask] Feeder no longer valid, cancelling task"
|
||||
);
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the item in the source slot is still edible
|
||||
ItemStack slotStack = feeder.getInventory().getItem(sourceSlot);
|
||||
if (slotStack.isEmpty() || !slotStack.getItem().isEdible()) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[ForceFeedingTask] Food item no longer in slot {}, cancelling",
|
||||
sourceSlot
|
||||
);
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[ForceFeedingTask] Force feeding complete for {}",
|
||||
targetEntity.getName().getString()
|
||||
);
|
||||
|
||||
if (targetEntity instanceof Player targetPlayer) {
|
||||
// Feed the player using vanilla eat mechanics
|
||||
targetPlayer.eat(targetPlayer.level(), slotStack.copy());
|
||||
slotStack.shrink(1);
|
||||
} else if (targetEntity instanceof EntityDamsel damsel) {
|
||||
// Use existing NPC feed method (handles shrink internally)
|
||||
damsel.feedByPlayer(feeder, slotStack);
|
||||
}
|
||||
|
||||
// Play eating sound at target
|
||||
targetEntity
|
||||
.level()
|
||||
.playSound(
|
||||
null,
|
||||
targetEntity.getX(),
|
||||
targetEntity.getY(),
|
||||
targetEntity.getZ(),
|
||||
SoundEvents.GENERIC_EAT,
|
||||
SoundSource.PLAYERS,
|
||||
1.0F,
|
||||
1.0F
|
||||
);
|
||||
|
||||
// Send messages
|
||||
String targetName = targetEntity.getName().getString();
|
||||
String feederName = feeder.getName().getString();
|
||||
|
||||
if (feeder instanceof ServerPlayer serverFeeder) {
|
||||
serverFeeder.displayClientMessage(
|
||||
Component.literal(
|
||||
"You force fed " + targetName + "."
|
||||
).withStyle(ChatFormatting.GRAY),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (targetEntity instanceof ServerPlayer serverTarget) {
|
||||
serverTarget.displayClientMessage(
|
||||
Component.literal("You have been force fed.").withStyle(
|
||||
ChatFormatting.GRAY
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
stop();
|
||||
|
||||
// Send completion packets (stateInfo = -1)
|
||||
if (targetEntity instanceof ServerPlayer serverTarget) {
|
||||
PacketForceFeeding completionPacket = new PacketForceFeeding(
|
||||
-1,
|
||||
this.getMaxSeconds(),
|
||||
false,
|
||||
feederName
|
||||
);
|
||||
ModNetwork.sendToPlayer(completionPacket, serverTarget);
|
||||
}
|
||||
|
||||
if (feeder instanceof ServerPlayer serverFeeder) {
|
||||
PacketForceFeeding completionPacket = new PacketForceFeeding(
|
||||
-1,
|
||||
this.getMaxSeconds(),
|
||||
true,
|
||||
targetName
|
||||
);
|
||||
ModNetwork.sendToPlayer(completionPacket, serverFeeder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendProgressPackets() {
|
||||
if (stopped) return;
|
||||
|
||||
String feederName =
|
||||
feeder != null ? feeder.getName().getString() : "Someone";
|
||||
String targetName = targetEntity.getName().getString();
|
||||
|
||||
// Packet to target (if player): isActiveRole=false, shows feeder's name
|
||||
if (targetEntity instanceof ServerPlayer serverTarget) {
|
||||
PacketForceFeeding victimPacket = new PacketForceFeeding(
|
||||
this.getState(),
|
||||
this.getMaxSeconds(),
|
||||
false,
|
||||
feederName
|
||||
);
|
||||
ModNetwork.sendToPlayer(victimPacket, serverTarget);
|
||||
}
|
||||
|
||||
// Packet to feeder: isActiveRole=true, shows target's name
|
||||
if (feeder instanceof ServerPlayer serverFeeder) {
|
||||
PacketForceFeeding feederPacket = new PacketForceFeeding(
|
||||
this.getState(),
|
||||
this.getMaxSeconds(),
|
||||
true,
|
||||
targetName
|
||||
);
|
||||
ModNetwork.sendToPlayer(feederPacket, serverFeeder);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpTargetState() {
|
||||
// Server-side: nothing to do - client handles its own PlayerStateTask via packets
|
||||
}
|
||||
}
|
||||
146
src/main/java/com/tiedup/remake/tasks/PlayerStateTask.java
Normal file
146
src/main/java/com/tiedup/remake/tasks/PlayerStateTask.java
Normal file
@@ -0,0 +1,146 @@
|
||||
package com.tiedup.remake.tasks;
|
||||
|
||||
import com.tiedup.remake.util.time.Timer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
/**
|
||||
* Phase 6: Lightweight client-side task state for displaying progress.
|
||||
*
|
||||
* Based on original PlayerStateTask from 1.12.2
|
||||
*
|
||||
* This is used on the CLIENT SIDE ONLY to track the progress of
|
||||
* tying/untying tasks for GUI display (progress bars, messages, etc.).
|
||||
*
|
||||
* The server sends progress updates via packets, and this class
|
||||
* stores them and auto-expires if updates stop coming.
|
||||
*/
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class PlayerStateTask {
|
||||
|
||||
private final int maxState; // Total time in seconds
|
||||
private Timer timerOutdating; // Auto-expiration timer (3 seconds)
|
||||
private int state; // Current elapsed time in seconds
|
||||
|
||||
// Role info for progress bar display
|
||||
private final boolean isKidnapper; // true = doing the tying, false = being tied
|
||||
private final String otherEntityName; // Name of the other party
|
||||
|
||||
// Outdating timeout: 3 seconds (60 ticks)
|
||||
// Slightly longer than server's 2 seconds to account for network delay
|
||||
private static final int OUTDATING_TIMEOUT_SECONDS = 3;
|
||||
|
||||
/**
|
||||
* Create a new client-side task state.
|
||||
*
|
||||
* @param maxState Total duration in seconds
|
||||
*/
|
||||
public PlayerStateTask(int maxState) {
|
||||
this(maxState, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new client-side task state with role info.
|
||||
*
|
||||
* @param maxState Total duration in seconds
|
||||
* @param isKidnapper true if this player is doing the tying
|
||||
* @param otherEntityName Name of the other party
|
||||
*/
|
||||
public PlayerStateTask(
|
||||
int maxState,
|
||||
boolean isKidnapper,
|
||||
String otherEntityName
|
||||
) {
|
||||
this.maxState = maxState;
|
||||
this.state = 0;
|
||||
this.isKidnapper = isKidnapper;
|
||||
this.otherEntityName = otherEntityName;
|
||||
this.timerOutdating = new Timer(
|
||||
OUTDATING_TIMEOUT_SECONDS,
|
||||
Minecraft.getInstance().level
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the task progress.
|
||||
* Called when receiving a progress packet from the server.
|
||||
*
|
||||
* @param state Current elapsed time in seconds
|
||||
*/
|
||||
public synchronized void update(int state) {
|
||||
this.state = state;
|
||||
// Reset outdating timer (we received an update from server)
|
||||
this.timerOutdating = new Timer(
|
||||
OUTDATING_TIMEOUT_SECONDS,
|
||||
Minecraft.getInstance().level
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this task state has become outdated.
|
||||
* A task is outdated if we haven't received an update for 3 seconds.
|
||||
*
|
||||
* @return true if outdated
|
||||
*/
|
||||
public boolean isOutdated() {
|
||||
return timerOutdating != null && timerOutdating.isExpired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current progress state (elapsed time).
|
||||
*
|
||||
* @return Elapsed seconds
|
||||
*/
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum duration.
|
||||
*
|
||||
* @return Total seconds
|
||||
*/
|
||||
public int getMaxState() {
|
||||
return maxState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get progress as a percentage (0.0 to 1.0).
|
||||
*
|
||||
* @return Progress percentage
|
||||
*/
|
||||
public float getProgress() {
|
||||
if (maxState <= 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
return Math.min(1.0f, (float) state / (float) maxState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remaining seconds.
|
||||
*
|
||||
* @return Remaining seconds
|
||||
*/
|
||||
public int getRemaining() {
|
||||
return Math.max(0, maxState - state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this player is the kidnapper (doing the tying).
|
||||
*
|
||||
* @return true if kidnapper, false if victim
|
||||
*/
|
||||
public boolean isKidnapper() {
|
||||
return isKidnapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the other party (target if kidnapper, kidnapper if victim).
|
||||
*
|
||||
* @return Other entity name, or null if unknown
|
||||
*/
|
||||
public String getOtherEntityName() {
|
||||
return otherEntityName;
|
||||
}
|
||||
}
|
||||
121
src/main/java/com/tiedup/remake/tasks/TimedInteractTask.java
Normal file
121
src/main/java/com/tiedup/remake/tasks/TimedInteractTask.java
Normal file
@@ -0,0 +1,121 @@
|
||||
package com.tiedup.remake.tasks;
|
||||
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Phase 6: Timed task that involves interacting with a target entity.
|
||||
* Phase 14.2.6: Refactored to support any IBondageState entity (Players + NPCs)
|
||||
*
|
||||
* Based on original TimedInteractTask from 1.12.2
|
||||
*
|
||||
* Extends TimedTask with target tracking:
|
||||
* - Tracks the target entity's IBondageState state
|
||||
* - Can check if the same target is being interacted with (via UUID)
|
||||
* - Abstract method to set up target state
|
||||
*/
|
||||
public abstract class TimedInteractTask extends TimedTask {
|
||||
|
||||
protected final IBondageState targetState; // The target's kidnapped state
|
||||
protected final LivingEntity targetEntity; // The target entity
|
||||
protected final UUID targetUUID; // Target's UUID for comparison
|
||||
|
||||
/**
|
||||
* Create a new timed interaction task.
|
||||
*
|
||||
* @param targetState The target's IBondageState state
|
||||
* @param targetEntity The target entity
|
||||
* @param seconds Total duration in seconds
|
||||
* @param level The world
|
||||
*/
|
||||
public TimedInteractTask(
|
||||
IBondageState targetState,
|
||||
LivingEntity targetEntity,
|
||||
int seconds,
|
||||
Level level
|
||||
) {
|
||||
super(seconds, level);
|
||||
this.targetState = targetState;
|
||||
this.targetEntity = targetEntity;
|
||||
this.targetUUID = targetEntity.getUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this task is targeting the same entity.
|
||||
* Uses UUID comparison to ensure uniqueness.
|
||||
*
|
||||
* @param entity The entity to compare with
|
||||
* @return true if same target
|
||||
*/
|
||||
public boolean isSameTarget(LivingEntity entity) {
|
||||
if (entity == null) {
|
||||
return false;
|
||||
}
|
||||
return targetUUID.equals(entity.getUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target's IBondageState state.
|
||||
*
|
||||
* @return The target's kidnapped state
|
||||
*/
|
||||
public IBondageState getTargetState() {
|
||||
return targetState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target entity.
|
||||
*
|
||||
* @return The target entity
|
||||
*/
|
||||
public LivingEntity getTargetEntity() {
|
||||
return targetEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target's UUID.
|
||||
*
|
||||
* @return The target's UUID
|
||||
*/
|
||||
public UUID getTargetUUID() {
|
||||
return targetUUID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the target entity is still valid (alive and exists).
|
||||
*
|
||||
* @return true if target is valid
|
||||
*/
|
||||
public boolean isTargetValid() {
|
||||
return targetEntity != null && targetEntity.isAlive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the target's state for this task.
|
||||
* This should be called when the task starts to initialize
|
||||
* any necessary state on the target.
|
||||
*
|
||||
* Implementation is task-specific (tying vs untying).
|
||||
*/
|
||||
public abstract void setUpTargetState();
|
||||
|
||||
/**
|
||||
* Called when the task completes successfully.
|
||||
* Default implementation does nothing - subclasses should override.
|
||||
*/
|
||||
@Override
|
||||
protected void onComplete() {
|
||||
// Default: no-op, subclasses implement specific completion logic
|
||||
}
|
||||
|
||||
/**
|
||||
* Send progress packets to relevant players.
|
||||
* Default implementation does nothing - subclasses should override.
|
||||
*/
|
||||
@Override
|
||||
public void sendProgressPackets() {
|
||||
// Default: no-op, subclasses implement packet sending
|
||||
}
|
||||
}
|
||||
205
src/main/java/com/tiedup/remake/tasks/TimedTask.java
Normal file
205
src/main/java/com/tiedup/remake/tasks/TimedTask.java
Normal file
@@ -0,0 +1,205 @@
|
||||
package com.tiedup.remake.tasks;
|
||||
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Phase 6: Base class for all progress-based tasks (tying, untying, etc.)
|
||||
*
|
||||
* Based on original TimedTask from 1.12.2, refactored for continuous click requirement.
|
||||
*
|
||||
* A TimedTask represents a progressive action that requires continuous clicking:
|
||||
* - Progress increases when player clicks on target (update() called)
|
||||
* - Progress decreases when player is not clicking (activeThisTick = false)
|
||||
* - Task completes when progress reaches maxProgress
|
||||
* - Task cancels when progress drops to 0
|
||||
*
|
||||
* This ensures players must HOLD click on the target to complete the action.
|
||||
*/
|
||||
public abstract class TimedTask {
|
||||
|
||||
protected int progress = 0; // Current progress (ticks)
|
||||
protected int maxProgress; // Target progress (seconds * 20 ticks)
|
||||
protected boolean activeThisTick = false; // Was update() called this tick?
|
||||
protected boolean stopped = false; // Task has been stopped/completed
|
||||
protected final Level level; // World reference
|
||||
protected final int seconds; // Total duration in seconds (for display)
|
||||
|
||||
/**
|
||||
* Create a new timed task.
|
||||
*
|
||||
* @param seconds Total duration in seconds
|
||||
* @param level The world (for game time)
|
||||
*/
|
||||
public TimedTask(int seconds, Level level) {
|
||||
this.seconds = seconds;
|
||||
this.maxProgress = seconds * 20; // Convert seconds to ticks
|
||||
this.level = level;
|
||||
this.stopped = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the task.
|
||||
* Resets progress and stopped state.
|
||||
*/
|
||||
public void start() {
|
||||
this.progress = 0;
|
||||
this.stopped = false;
|
||||
this.activeThisTick = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when player clicks on target.
|
||||
* Marks this tick as "active" - progress will increase.
|
||||
*/
|
||||
public synchronized void update() {
|
||||
activeThisTick = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called every tick to process progress.
|
||||
* Increments if active (clicking), decrements if not.
|
||||
*/
|
||||
public void tick() {
|
||||
if (stopped) return;
|
||||
|
||||
if (activeThisTick) {
|
||||
// Player is clicking on target - progress up
|
||||
progress++;
|
||||
if (progress >= maxProgress) {
|
||||
// Task complete!
|
||||
onComplete();
|
||||
}
|
||||
} else {
|
||||
// Player is not clicking - progress down
|
||||
progress--;
|
||||
if (progress <= 0) {
|
||||
progress = 0;
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
// Reset for next tick
|
||||
activeThisTick = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the task to its initial state.
|
||||
*/
|
||||
public void reset() {
|
||||
this.progress = 0;
|
||||
this.stopped = false;
|
||||
this.activeThisTick = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop/cancel the task.
|
||||
* Marks the task as stopped.
|
||||
*/
|
||||
public void stop() {
|
||||
this.stopped = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task has been stopped.
|
||||
*
|
||||
* @return true if stopped
|
||||
*/
|
||||
public boolean isStopped() {
|
||||
return stopped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task has become outdated.
|
||||
* In the new progress-based system, a task is outdated if progress is 0.
|
||||
*
|
||||
* @return true if outdated (no progress)
|
||||
*/
|
||||
public boolean isOutdated() {
|
||||
return progress <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current progress in ticks.
|
||||
*
|
||||
* @return Current progress ticks
|
||||
*/
|
||||
public int getProgress() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the max progress in ticks.
|
||||
*
|
||||
* @return Max progress ticks
|
||||
*/
|
||||
public int getMaxProgress() {
|
||||
return maxProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state (elapsed time in seconds).
|
||||
* Calculates from progress ticks for display compatibility.
|
||||
*
|
||||
* @return Elapsed seconds (progress / 20)
|
||||
*/
|
||||
public int getState() {
|
||||
return progress / 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get progress as a percentage (0.0 to 1.0).
|
||||
*
|
||||
* @return Progress percentage
|
||||
*/
|
||||
public float getProgressPercent() {
|
||||
return (float) progress / maxProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task is complete (progress >= maxProgress).
|
||||
*
|
||||
* @return true if task is done
|
||||
*/
|
||||
public boolean isOver() {
|
||||
return progress >= maxProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task is complete.
|
||||
*
|
||||
* @return true if complete
|
||||
*/
|
||||
public boolean isComplete() {
|
||||
return progress >= maxProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total duration in seconds.
|
||||
*
|
||||
* @return Total seconds
|
||||
*/
|
||||
public int getMaxSeconds() {
|
||||
return seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remaining seconds.
|
||||
*
|
||||
* @return Remaining seconds
|
||||
*/
|
||||
public int getSecondsRemaining() {
|
||||
return Math.max(0, (maxProgress - progress) / 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the task completes successfully.
|
||||
* Subclasses should override to implement completion logic.
|
||||
*/
|
||||
protected abstract void onComplete();
|
||||
|
||||
/**
|
||||
* Send progress packets to relevant players.
|
||||
* Subclasses should override to send UI updates.
|
||||
*/
|
||||
public abstract void sendProgressPackets();
|
||||
}
|
||||
346
src/main/java/com/tiedup/remake/tasks/TyingPlayerTask.java
Normal file
346
src/main/java/com/tiedup/remake/tasks/TyingPlayerTask.java
Normal file
@@ -0,0 +1,346 @@
|
||||
package com.tiedup.remake.tasks;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
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 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;
|
||||
|
||||
/**
|
||||
* Phase 6: Concrete tying task for tying up any IBondageState entity.
|
||||
* Phase 14.2.6: Unified to work with both Players and NPCs.
|
||||
*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
59
src/main/java/com/tiedup/remake/tasks/TyingTask.java
Normal file
59
src/main/java/com/tiedup/remake/tasks/TyingTask.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package com.tiedup.remake.tasks;
|
||||
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Phase 6: Abstract tying task (binding/gagging an entity).
|
||||
* Phase 14.2.6: Refactored to support any IBondageState entity (Players + NPCs)
|
||||
*
|
||||
* Based on original TyingTask from 1.12.2
|
||||
*
|
||||
* Extends TimedInteractTask with item tracking:
|
||||
* - Holds a reference to the bind/gag item being applied
|
||||
* - The item will be consumed when tying completes successfully
|
||||
*/
|
||||
public abstract class TyingTask extends TimedInteractTask {
|
||||
|
||||
protected ItemStack bind; // The bind/gag item being applied
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param seconds Total duration in seconds
|
||||
* @param level The world
|
||||
*/
|
||||
public TyingTask(
|
||||
ItemStack bind,
|
||||
IBondageState targetState,
|
||||
LivingEntity targetEntity,
|
||||
int seconds,
|
||||
Level level
|
||||
) {
|
||||
super(targetState, targetEntity, seconds, level);
|
||||
this.bind = bind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bind/gag item being applied.
|
||||
*
|
||||
* @return The item stack
|
||||
*/
|
||||
public ItemStack getBind() {
|
||||
return bind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bind/gag item.
|
||||
*
|
||||
* @param bind The item stack
|
||||
*/
|
||||
public void setBind(ItemStack bind) {
|
||||
this.bind = bind;
|
||||
}
|
||||
}
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
35
src/main/java/com/tiedup/remake/tasks/UntyingTask.java
Normal file
35
src/main/java/com/tiedup/remake/tasks/UntyingTask.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.tiedup.remake.tasks;
|
||||
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/**
|
||||
* Phase 6: Abstract untying task (freeing a tied entity).
|
||||
* Phase 14.2.6: Refactored to support any IBondageState entity (Players + NPCs)
|
||||
*
|
||||
* Based on original UntyingTask from 1.12.2
|
||||
*
|
||||
* Extends TimedInteractTask for untying operations:
|
||||
* - Frees a tied entity over time
|
||||
* - Drops bondage items on the ground when complete
|
||||
*/
|
||||
public abstract class UntyingTask extends TimedInteractTask {
|
||||
|
||||
/**
|
||||
* Create a new untying task.
|
||||
*
|
||||
* @param targetState The target's IBondageState state
|
||||
* @param targetEntity The target entity
|
||||
* @param seconds Total duration in seconds
|
||||
* @param level The world
|
||||
*/
|
||||
public UntyingTask(
|
||||
IBondageState targetState,
|
||||
LivingEntity targetEntity,
|
||||
int seconds,
|
||||
Level level
|
||||
) {
|
||||
super(targetState, targetEntity, seconds, level);
|
||||
}
|
||||
}
|
||||
94
src/main/java/com/tiedup/remake/tasks/V2TyingPlayerTask.java
Normal file
94
src/main/java/com/tiedup/remake/tasks/V2TyingPlayerTask.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package com.tiedup.remake.tasks;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.v2.bondage.V2EquipResult;
|
||||
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Tying task for V2 bondage items.
|
||||
*
|
||||
* Unlike {@link TyingPlayerTask} which calls {@code targetState.equip(BodyRegionV2.ARMS, bind)} (V1 equip),
|
||||
* this task uses {@link V2EquipmentHelper#equipItem(LivingEntity, ItemStack)} on completion.
|
||||
*
|
||||
* Progress bar, duration, and packet flow are identical to V1 tying.
|
||||
*/
|
||||
public class V2TyingPlayerTask extends TyingPlayerTask {
|
||||
|
||||
/**
|
||||
* The live reference to the player's held ItemStack (for consumption on completion).
|
||||
* The parent class's {@code bind} field holds a copy for display/matching.
|
||||
*/
|
||||
private final ItemStack heldStack;
|
||||
|
||||
/**
|
||||
* Create a V2 tying task.
|
||||
*
|
||||
* @param bind Copy of the item being equipped (for display/matching)
|
||||
* @param heldStack Live reference to the player's held ItemStack (for consumption)
|
||||
* @param targetState The target's IBondageState state
|
||||
* @param targetEntity The target entity
|
||||
* @param seconds Duration in seconds
|
||||
* @param level The world
|
||||
* @param kidnapper The player performing the tying (self for self-bondage)
|
||||
*/
|
||||
public V2TyingPlayerTask(
|
||||
ItemStack bind,
|
||||
ItemStack heldStack,
|
||||
IBondageState targetState,
|
||||
LivingEntity targetEntity,
|
||||
int seconds,
|
||||
Level level,
|
||||
Player kidnapper
|
||||
) {
|
||||
super(bind, targetState, targetEntity, seconds, level, kidnapper);
|
||||
this.heldStack = heldStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 completion: equip via V2EquipmentHelper instead of V1 putBindOn.
|
||||
*
|
||||
* This REPLACES the parent's onComplete entirely. The parent would call
|
||||
* targetState.equip(BodyRegionV2.ARMS, bind) which is V1-only.
|
||||
*/
|
||||
@Override
|
||||
protected void onComplete() {
|
||||
if (!isTargetValid()) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[V2TyingPlayerTask] Target entity no longer valid, cancelling"
|
||||
);
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[V2TyingPlayerTask] Tying complete for {}",
|
||||
targetEntity.getName().getString()
|
||||
);
|
||||
|
||||
// Equip via V2 system
|
||||
V2EquipResult result = V2EquipmentHelper.equipItem(targetEntity, bind);
|
||||
if (result.isSuccess()) {
|
||||
for (ItemStack displaced : result.displaced()) {
|
||||
targetEntity.spawnAtLocation(displaced);
|
||||
}
|
||||
// Consume the held item
|
||||
heldStack.shrink(1);
|
||||
TiedUpMod.LOGGER.debug("[V2TyingPlayerTask] V2 equip succeeded, item consumed");
|
||||
} else {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[V2TyingPlayerTask] V2 equip BLOCKED after tying — regions may have changed"
|
||||
);
|
||||
}
|
||||
|
||||
// Mark task as stopped
|
||||
stop();
|
||||
|
||||
// Send completion packets (shared with V1 via parent)
|
||||
sendCompletionPackets();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user