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