PrisonerService 1057L -> 474L lifecycle + 616L EscapeMonitorService EntityKidnapper 2035L -> 1727L via LootManager, Dialogue, CaptivePriority extraction
475 lines
16 KiB
Java
475 lines
16 KiB
Java
package com.tiedup.remake.prison.service;
|
|
|
|
import com.tiedup.remake.cells.CellDataV2;
|
|
import com.tiedup.remake.cells.CellRegistryV2;
|
|
import com.tiedup.remake.core.TiedUpMod;
|
|
import com.tiedup.remake.entities.AbstractTiedUpNpc;
|
|
import com.tiedup.remake.entities.EntityDamsel;
|
|
import com.tiedup.remake.personality.PersonalityState;
|
|
import com.tiedup.remake.prison.PrisonerManager;
|
|
import com.tiedup.remake.prison.PrisonerRecord;
|
|
import com.tiedup.remake.prison.PrisonerState;
|
|
import com.tiedup.remake.state.IBondageState;
|
|
import com.tiedup.remake.state.ICaptor;
|
|
import com.tiedup.remake.util.KidnappedHelper;
|
|
import java.util.*;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
/**
|
|
* Prisoner lifecycle service.
|
|
*
|
|
* Manages prisoner state transitions atomically:
|
|
* - PrisonerManager (SavedData state)
|
|
* - CellRegistryV2 (cell assignment)
|
|
* - Leash/captor refs (IBondageState / ICaptor)
|
|
*
|
|
* Handles: capture, imprison, extractFromCell, returnToCell, transferCaptive.
|
|
* Escape detection and release are handled by {@link EscapeMonitorService}.
|
|
*
|
|
* AI goals manage: Navigation, Teleport, Equipment, Dialogue.
|
|
* PrisonerService manages: State transitions + leash coordination.
|
|
*/
|
|
public class PrisonerService {
|
|
|
|
private static final PrisonerService INSTANCE = new PrisonerService();
|
|
|
|
public static PrisonerService get() {
|
|
return INSTANCE;
|
|
}
|
|
|
|
private PrisonerService() {}
|
|
|
|
// ==================== CAPTURE ====================
|
|
|
|
/**
|
|
* Capture a free entity.
|
|
* FREE → CAPTURED + leash + captor refs.
|
|
*
|
|
* @param level Server level
|
|
* @param captive The entity being captured (must have IBondageState state)
|
|
* @param captor The capturing entity
|
|
* @return true if capture succeeded
|
|
*/
|
|
public boolean capture(
|
|
ServerLevel level,
|
|
LivingEntity captive,
|
|
ICaptor captor
|
|
) {
|
|
UUID captiveId = captive.getUUID();
|
|
PrisonerManager manager = PrisonerManager.get(level);
|
|
long gameTime = level.getGameTime();
|
|
|
|
// 1. PrisonerManager state transition: FREE → CAPTURED
|
|
if (
|
|
!manager.capture(captiveId, captor.getEntity().getUUID(), gameTime)
|
|
) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PrisonerService] capture() failed PrisonerManager transition for {}",
|
|
captiveId.toString().substring(0, 8)
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// 2. Leash attach
|
|
IBondageState kidnappedState = KidnappedHelper.getKidnappedState(
|
|
captive
|
|
);
|
|
if (kidnappedState == null) {
|
|
// Rollback
|
|
manager.escape(
|
|
captiveId,
|
|
gameTime,
|
|
"capture rollback: no kidnapped state"
|
|
);
|
|
return false;
|
|
}
|
|
|
|
boolean leashed;
|
|
if (captive instanceof AbstractTiedUpNpc npc) {
|
|
// NPC: use forceCapturedBy to bypass canCapture() PrisonerManager check
|
|
// (PrisonerManager state is already CAPTURED at this point)
|
|
leashed = npc.forceCapturedBy(captor);
|
|
} else {
|
|
// Player: getCapturedBy checks isEnslavable() first (OR logic),
|
|
// so canCapture() PrisonerManager check is bypassed if tied up.
|
|
// Generic IBondageState: same path.
|
|
leashed = kidnappedState.getCapturedBy(captor);
|
|
}
|
|
|
|
if (!leashed) {
|
|
// Rollback PrisonerManager
|
|
manager.escape(
|
|
captiveId,
|
|
gameTime,
|
|
"capture rollback: leash failed"
|
|
);
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PrisonerService] capture() leash failed for {}, rolled back",
|
|
captiveId.toString().substring(0, 8)
|
|
);
|
|
return false;
|
|
}
|
|
|
|
TiedUpMod.LOGGER.info(
|
|
"[PrisonerService] Captured {} by {}",
|
|
captive.getName().getString(),
|
|
captor.getEntity().getName().getString()
|
|
);
|
|
return true;
|
|
}
|
|
|
|
// ==================== IMPRISONMENT ====================
|
|
|
|
/**
|
|
* Imprison a captured entity in a cell.
|
|
* CAPTURED → IMPRISONED + unleash + CellRegistryV2.assign.
|
|
*
|
|
* The caller handles: teleport, confiscation, bind, dialogue.
|
|
*
|
|
* @param level Server level
|
|
* @param captive The captured entity
|
|
* @param captor The current captor
|
|
* @param campId Camp UUID
|
|
* @param cell The target cell
|
|
* @return true if imprisonment succeeded
|
|
*/
|
|
public boolean imprison(
|
|
ServerLevel level,
|
|
LivingEntity captive,
|
|
ICaptor captor,
|
|
UUID campId,
|
|
CellDataV2 cell
|
|
) {
|
|
if (cell == null) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PrisonerService] imprison() called with null cell"
|
|
);
|
|
return false;
|
|
}
|
|
|
|
UUID captiveId = captive.getUUID();
|
|
PrisonerManager manager = PrisonerManager.get(level);
|
|
CellRegistryV2 cellRegistry = CellRegistryV2.get(level);
|
|
long gameTime = level.getGameTime();
|
|
|
|
// 1. Assign to cell registry
|
|
boolean assigned = cellRegistry.assignPrisoner(cell.getId(), captiveId);
|
|
if (!assigned) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PrisonerService] imprison() cell assignment failed for {}",
|
|
captiveId.toString().substring(0, 8)
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// Defense: if prisoner is FREE, capture first
|
|
boolean defenseCaptured = false;
|
|
PrisonerRecord record = manager.getRecord(captiveId);
|
|
if (record.getState() == PrisonerState.FREE) {
|
|
TiedUpMod.LOGGER.info(
|
|
"[PrisonerService] {} is FREE, capturing before imprison",
|
|
captive.getName().getString()
|
|
);
|
|
manager.capture(captiveId, captor.getEntity().getUUID(), gameTime);
|
|
defenseCaptured = true;
|
|
}
|
|
|
|
// 2. PrisonerManager state transition: CAPTURED → IMPRISONED
|
|
boolean imprisoned = manager.imprison(
|
|
captiveId,
|
|
campId,
|
|
cell.getId(),
|
|
gameTime
|
|
);
|
|
if (!imprisoned) {
|
|
// Rollback cell assignment
|
|
cellRegistry.releasePrisoner(
|
|
cell.getId(),
|
|
captiveId,
|
|
level.getServer()
|
|
);
|
|
// Rollback defense auto-capture if we triggered it
|
|
if (defenseCaptured) {
|
|
manager.escape(captiveId, gameTime, "imprison_rollback");
|
|
}
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PrisonerService] imprison() PrisonerManager transition failed for {} (state={})",
|
|
captiveId.toString().substring(0, 8),
|
|
manager.getState(captiveId)
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// 3. Unleash: remove captor refs and drop leash
|
|
IBondageState kidnappedState = KidnappedHelper.getKidnappedState(
|
|
captive
|
|
);
|
|
if (kidnappedState != null) {
|
|
kidnappedState.free(false); // Drop leash without dialogue/items
|
|
captor.removeCaptive(kidnappedState, false);
|
|
}
|
|
|
|
// 4. Sync PersonalityState cellId for damsels
|
|
if (captive instanceof EntityDamsel damsel) {
|
|
PersonalityState pState = damsel.getPersonalityState();
|
|
if (pState != null) {
|
|
pState.assignCell(cell.getId(), cell, damsel.getUUID());
|
|
}
|
|
}
|
|
|
|
TiedUpMod.LOGGER.info(
|
|
"[PrisonerService] Imprisoned {} in cell {} (camp={})",
|
|
captive.getName().getString(),
|
|
cell.getId().toString().substring(0, 8),
|
|
campId != null ? campId.toString().substring(0, 8) : "null"
|
|
);
|
|
return true;
|
|
}
|
|
|
|
// ==================== EXTRACTION ====================
|
|
|
|
/**
|
|
* Extract a prisoner from their cell.
|
|
* IMPRISONED → WORKING + CellRegistryV2.release + leash to new captor.
|
|
*
|
|
* For: dogwalk, maid fetch, labor extract.
|
|
* Note: If the entity is not tracked by PrisonerManager (e.g., damsels),
|
|
* the PrisonerManager step is skipped — only CellRegistryV2 + leash are handled.
|
|
*
|
|
* @param level Server level
|
|
* @param captive The imprisoned entity
|
|
* @param newCaptor The entity extracting (kidnapper or maid)
|
|
* @param cell The cell to extract from
|
|
* @return true if extraction succeeded
|
|
*/
|
|
public boolean extractFromCell(
|
|
ServerLevel level,
|
|
LivingEntity captive,
|
|
ICaptor newCaptor,
|
|
CellDataV2 cell
|
|
) {
|
|
UUID captiveId = captive.getUUID();
|
|
PrisonerManager manager = PrisonerManager.get(level);
|
|
CellRegistryV2 cellRegistry = CellRegistryV2.get(level);
|
|
long gameTime = level.getGameTime();
|
|
|
|
// 1. PrisonerManager: IMPRISONED → WORKING (players only — damsels aren't tracked)
|
|
boolean isPlayer = captive instanceof Player;
|
|
if (isPlayer) {
|
|
if (!manager.extract(captiveId, gameTime)) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PrisonerService] extractFromCell() PrisonerManager transition failed for {}",
|
|
captiveId.toString().substring(0, 8)
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 2. Release from cell registry
|
|
cellRegistry.releasePrisoner(
|
|
cell.getId(),
|
|
captiveId,
|
|
level.getServer()
|
|
);
|
|
|
|
// 3. Leash to new captor — forceCapturedBy for damsels
|
|
IBondageState kidnappedState = KidnappedHelper.getKidnappedState(
|
|
captive
|
|
);
|
|
if (kidnappedState == null) {
|
|
// Rollback
|
|
if (isPlayer) {
|
|
manager.returnToCell(captiveId, cell.getId(), gameTime);
|
|
}
|
|
cellRegistry.assignPrisoner(cell.getId(), captiveId);
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PrisonerService] extractFromCell() no kidnapped state for {}, rolled back",
|
|
captiveId.toString().substring(0, 8)
|
|
);
|
|
return false;
|
|
}
|
|
|
|
boolean leashed;
|
|
if (captive instanceof AbstractTiedUpNpc npc) {
|
|
leashed = npc.forceCapturedBy(newCaptor);
|
|
} else {
|
|
leashed = kidnappedState.getCapturedBy(newCaptor);
|
|
}
|
|
|
|
if (!leashed) {
|
|
// Rollback: return to IMPRISONED + reassign cell
|
|
if (isPlayer) {
|
|
manager.returnToCell(captiveId, cell.getId(), gameTime);
|
|
}
|
|
cellRegistry.assignPrisoner(cell.getId(), captiveId);
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PrisonerService] extractFromCell() leash failed for {}, rolled back",
|
|
captiveId.toString().substring(0, 8)
|
|
);
|
|
return false;
|
|
}
|
|
|
|
TiedUpMod.LOGGER.info(
|
|
"[PrisonerService] Extracted {} from cell {} by {}",
|
|
captive.getName().getString(),
|
|
cell.getId().toString().substring(0, 8),
|
|
newCaptor.getEntity().getName().getString()
|
|
);
|
|
return true;
|
|
}
|
|
|
|
// ==================== RETURN TO CELL ====================
|
|
|
|
/**
|
|
* Return a prisoner to their cell.
|
|
* WORKING → IMPRISONED + unleash + CellRegistryV2.assign.
|
|
*
|
|
* For: dogwalk return, labor return.
|
|
* Note: If the entity is not tracked by PrisonerManager (e.g., damsels),
|
|
* the PrisonerManager step is skipped — only unleash + CellRegistryV2 are handled.
|
|
*
|
|
* @param level Server level
|
|
* @param captive The working entity
|
|
* @param currentCaptor The current captor holding the leash
|
|
* @param cell The cell to return to
|
|
* @return true if return succeeded
|
|
*/
|
|
public boolean returnToCell(
|
|
ServerLevel level,
|
|
LivingEntity captive,
|
|
@Nullable ICaptor currentCaptor,
|
|
CellDataV2 cell
|
|
) {
|
|
UUID captiveId = captive.getUUID();
|
|
PrisonerManager manager = PrisonerManager.get(level);
|
|
CellRegistryV2 cellRegistry = CellRegistryV2.get(level);
|
|
long gameTime = level.getGameTime();
|
|
|
|
// 1. Unleash
|
|
IBondageState kidnappedState = KidnappedHelper.getKidnappedState(
|
|
captive
|
|
);
|
|
if (kidnappedState != null && kidnappedState.isCaptive()) {
|
|
kidnappedState.free(false);
|
|
}
|
|
if (currentCaptor != null && kidnappedState != null) {
|
|
currentCaptor.removeCaptive(kidnappedState, false);
|
|
}
|
|
|
|
// 2. PrisonerManager: WORKING → IMPRISONED (players only — damsels aren't tracked)
|
|
if (captive instanceof Player) {
|
|
boolean returned = manager.returnToCell(
|
|
captiveId,
|
|
cell.getId(),
|
|
gameTime
|
|
);
|
|
if (!returned) {
|
|
// State might not be WORKING (escape during return, etc.)
|
|
// Force-repair: try capture+imprison path
|
|
PrisonerRecord record = manager.getRecord(captiveId);
|
|
PrisonerState currentState = record.getState();
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PrisonerService] returnToCell() transition failed for {} (state={}). Force-repairing.",
|
|
captiveId.toString().substring(0, 8),
|
|
currentState
|
|
);
|
|
|
|
if (currentState == PrisonerState.FREE) {
|
|
UUID captorId =
|
|
currentCaptor != null
|
|
? currentCaptor.getEntity().getUUID()
|
|
: captiveId;
|
|
manager.capture(captiveId, captorId, gameTime);
|
|
UUID campId = record.getCampId();
|
|
manager.imprison(captiveId, campId, cell.getId(), gameTime);
|
|
} else if (currentState == PrisonerState.CAPTURED) {
|
|
UUID campId = record.getCampId();
|
|
manager.imprison(captiveId, campId, cell.getId(), gameTime);
|
|
}
|
|
// If IMPRISONED already — no transition needed, just reassign cell
|
|
}
|
|
}
|
|
|
|
// 3. Reassign to cell registry
|
|
cellRegistry.assignPrisoner(cell.getId(), captiveId);
|
|
|
|
TiedUpMod.LOGGER.info(
|
|
"[PrisonerService] Returned {} to cell {}",
|
|
captive.getName().getString(),
|
|
cell.getId().toString().substring(0, 8)
|
|
);
|
|
return true;
|
|
}
|
|
|
|
// ==================== TRANSFER ====================
|
|
|
|
/**
|
|
* Transfer a captive from one captor to another.
|
|
* PrisonerManager state unchanged.
|
|
*
|
|
* For: maid→buyer, kidnapper→kidnapper.
|
|
*
|
|
* @param level Server level
|
|
* @param captive The captive entity
|
|
* @param oldCaptor The current captor
|
|
* @param newCaptor The new captor
|
|
* @return true if transfer succeeded
|
|
*/
|
|
public boolean transferCaptive(
|
|
ServerLevel level,
|
|
LivingEntity captive,
|
|
ICaptor oldCaptor,
|
|
ICaptor newCaptor
|
|
) {
|
|
IBondageState kidnappedState = KidnappedHelper.getKidnappedState(
|
|
captive
|
|
);
|
|
if (kidnappedState == null) {
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PrisonerService] transferCaptive() no kidnapped state for {}",
|
|
captive.getName().getString()
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// 1. Remove from old captor
|
|
oldCaptor.removeCaptive(kidnappedState, false);
|
|
|
|
// 2. Clear leash
|
|
kidnappedState.free(false);
|
|
|
|
// 3. Re-leash to new captor
|
|
boolean leashed;
|
|
if (captive instanceof AbstractTiedUpNpc npc) {
|
|
leashed = npc.forceCapturedBy(newCaptor);
|
|
} else {
|
|
leashed = kidnappedState.getCapturedBy(newCaptor);
|
|
}
|
|
|
|
if (!leashed) {
|
|
// Rollback: try to re-leash to old captor
|
|
TiedUpMod.LOGGER.warn(
|
|
"[PrisonerService] transferCaptive() re-leash to new captor failed, attempting rollback"
|
|
);
|
|
if (captive instanceof AbstractTiedUpNpc npc) {
|
|
npc.forceCapturedBy(oldCaptor);
|
|
} else {
|
|
kidnappedState.getCapturedBy(oldCaptor);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TiedUpMod.LOGGER.info(
|
|
"[PrisonerService] Transferred {} from {} to {}",
|
|
captive.getName().getString(),
|
|
oldCaptor.getEntity().getName().getString(),
|
|
newCaptor.getEntity().getName().getString()
|
|
);
|
|
return true;
|
|
}
|
|
|
|
}
|