Files
TiedUp-/src/main/java/com/tiedup/remake/state/components/PlayerCaptivity.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

289 lines
9.2 KiB
Java

package com.tiedup.remake.state.components;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.entities.LeashProxyEntity;
import com.tiedup.remake.state.ICaptor;
import com.tiedup.remake.state.IPlayerLeashAccess;
import com.tiedup.remake.state.hosts.IPlayerBindStateHost;
import net.minecraft.world.entity.player.Player;
import org.jetbrains.annotations.Nullable;
/**
* Component responsible for captivity mechanics and leash proxy management.
*
* Single Responsibility: Captivity lifecycle and transport management
* Complexity: VERY HIGH (mixin coupling, network sync, leash proxy coordination)
* Risk: VERY HIGH (critical path, mixin dependency, 4 network sync points)
*
* Mixin Dependency: IPlayerLeashAccess for leash proxy system
* Network Sync: 4 syncEnslavement() calls coordinated via host
*
* Captivity States:
* - Not Captive: No captor, no leash
* - Captive (by entity): Has captor, leashed to entity
* - Pole Binding: No captor, leashed to pole (LeashFenceKnotEntity)
*/
public class PlayerCaptivity {
private final IPlayerBindStateHost host;
public PlayerCaptivity(IPlayerBindStateHost host) {
this.host = host;
}
// ========== Captivity Initiation ==========
/**
* Initiates the capture process by a captor.
* Uses the proxy-based leash system (player is NOT mounted).
*
* Thread Safety: Synchronized to prevent race condition where two kidnappers
* could both pass the isCaptive() check and attempt to capture simultaneously.
*
* @param newCaptor The entity attempting to capture this player
* @return true if capture succeeded
*/
public synchronized boolean getCapturedBy(ICaptor newCaptor) {
Player player = host.getPlayer();
if (player == null || newCaptor == null) return false;
// Must be enslavable (tied up) OR captor can capture (includes collar owner exception)
if (
!isEnslavable() && !newCaptor.canCapture(host.getKidnapped())
) return false;
// Check if already captured (atomic check under synchronization)
if (isCaptive()) return false;
// Free all captives instead of transferring (until multi-captive is implemented)
if (host.getCaptorManager().hasCaptives()) {
TiedUpMod.LOGGER.debug(
"[PlayerCaptivity] {} is being captured - freeing their {} captives",
player.getName().getString(),
host.getCaptorManager().getCaptiveCount()
);
host.getCaptorManager().freeAllCaptives(true);
}
// Use new proxy-based leash system
if (player instanceof IPlayerLeashAccess access) {
access.tiedup$attachLeash(newCaptor.getEntity());
newCaptor.addCaptive(host.getKidnapped());
host.setCaptor(newCaptor);
TiedUpMod.LOGGER.debug(
"[PlayerCaptivity] {} captured by {} (proxy leash)",
player.getName().getString(),
newCaptor.getEntity().getName().getString()
);
// Sync enslavement state to all clients
host.syncEnslavement();
return true;
}
TiedUpMod.LOGGER.error(
"[PlayerCaptivity] Player {} does not implement IPlayerLeashAccess!",
player.getName().getString()
);
return false;
}
// ========== Captivity Release ==========
/**
* Ends captivity with default behavior (drops leash item).
*/
public void free() {
free(true);
}
/**
*
* @param dropLead Whether to drop the leash item
*/
public void free(boolean dropLead) {
Player player = host.getPlayer();
if (player == null) return;
if (!(player instanceof IPlayerLeashAccess access)) {
TiedUpMod.LOGGER.error(
"[PlayerCaptivity] Player {} does not implement IPlayerLeashAccess!",
player.getName().getString()
);
return;
}
ICaptor captor = host.getCaptor();
// Handle pole binding (no captor) - just detach leash
if (captor == null) {
if (access.tiedup$isLeashed()) {
TiedUpMod.LOGGER.debug(
"[PlayerCaptivity] Freeing {} from pole binding",
player.getName().getString()
);
if (dropLead) {
access.tiedup$dropLeash();
}
access.tiedup$detachLeash();
host.syncEnslavement();
}
return;
}
TiedUpMod.LOGGER.debug(
"[PlayerCaptivity] Freeing {} from captivity",
player.getName().getString()
);
// 1. Remove from captor's tracking list
captor.removeCaptive(host.getKidnapped(), false);
// 2. Detach leash proxy
if (dropLead) {
access.tiedup$dropLeash();
}
access.tiedup$detachLeash();
// 3. Reset state
host.setCaptor(null);
// 4. Sync freed state to all clients
host.syncEnslavement();
}
// ========== Captivity Transfer ==========
/**
* Transfers captivity from current captor to a new captor.
*
* Thread Safety: Synchronized to prevent concurrent transfer attempts.
*
* @param newCaptor The new captor entity
*/
public synchronized void transferCaptivityTo(ICaptor newCaptor) {
Player player = host.getPlayer();
ICaptor currentCaptor = host.getCaptor();
if (
player == null ||
newCaptor == null ||
currentCaptor == null ||
!currentCaptor.allowCaptiveTransfer()
) return;
currentCaptor.removeCaptive(host.getKidnapped(), false);
// Re-attach leash to new captor
if (player instanceof IPlayerLeashAccess access) {
access.tiedup$detachLeash();
access.tiedup$attachLeash(newCaptor.getEntity());
}
newCaptor.addCaptive(host.getKidnapped());
host.setCaptor(newCaptor);
// Sync new captor to all clients
host.syncEnslavement();
}
// ========== State Queries ==========
/**
* Check if this player can be captured (leashed).
* Must be tied up to be leashed.
* Collar alone is NOT enough - collar owner exception is handled in canCapture().
*
* @return true if player is tied up and can be leashed
*/
public boolean isEnslavable() {
return host.isTiedUp();
}
/**
* Check if player is currently captured by an entity.
*
* @return true if player has a captor and is leashed
*/
public boolean isCaptive() {
Player player = host.getPlayer();
if (host.getCaptor() == null) return false;
if (player instanceof IPlayerLeashAccess access) {
return access.tiedup$isLeashed();
}
return false;
}
/**
* Get the leash proxy entity (transport system).
*
* @return The leash proxy, or null if not leashed
*/
@Nullable
public LeashProxyEntity getTransport() {
Player player = host.getPlayer();
if (player instanceof IPlayerLeashAccess access) {
return access.tiedup$getLeashProxy();
}
return null;
}
// ========== Captivity Monitoring ==========
/**
* Periodically monitors captivity validity.
* Simplified: If any condition is invalid, free the captive immediately.
*
* Called from RestraintTaskTickHandler every player tick.
*/
public void checkStillCaptive() {
if (!isCaptive()) return;
Player player = host.getPlayer();
if (player == null) return;
// Check if no longer tied/collared
if (!host.isTiedUp() && !host.hasCollar()) {
TiedUpMod.LOGGER.debug(
"[PlayerCaptivity] Auto-freeing {} - no restraints",
player.getName().getString()
);
free();
return;
}
// Check leash proxy status
if (player instanceof IPlayerLeashAccess access) {
LeashProxyEntity proxy = access.tiedup$getLeashProxy();
if (proxy == null || proxy.proxyIsRemoved()) {
TiedUpMod.LOGGER.debug(
"[PlayerCaptivity] Auto-freeing {} - proxy invalid",
player.getName().getString()
);
// Notify captor BEFORE freeing (triggers retrieval behavior)
ICaptor captor = host.getCaptor();
if (captor != null) {
captor.onCaptiveReleased(host.getKidnapped());
}
free();
return;
}
// Check if leash holder is still valid
if (proxy.getLeashHolder() == null) {
TiedUpMod.LOGGER.debug(
"[PlayerCaptivity] Auto-freeing {} - leash holder gone",
player.getName().getString()
);
// Notify captor BEFORE freeing (triggers retrieval behavior)
ICaptor captor = host.getCaptor();
if (captor != null) {
captor.onCaptiveReleased(host.getKidnapped());
}
free();
}
}
}
}