split PrisonerService + decompose EntityKidnapper
PrisonerService 1057L -> 474L lifecycle + 616L EscapeMonitorService EntityKidnapper 2035L -> 1727L via LootManager, Dialogue, CaptivePriority extraction
This commit is contained in:
@@ -6,40 +6,28 @@ 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.LaborRecord;
|
||||
import com.tiedup.remake.prison.PrisonerManager;
|
||||
import com.tiedup.remake.prison.PrisonerRecord;
|
||||
import com.tiedup.remake.prison.PrisonerState;
|
||||
import com.tiedup.remake.prison.service.BondageService;
|
||||
import com.tiedup.remake.state.CollarRegistry;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.state.ICaptor;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import java.util.*;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
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 org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Centralized prisoner lifecycle service.
|
||||
* Prisoner lifecycle service.
|
||||
*
|
||||
* Manages ALL prisoner state transitions atomically:
|
||||
* Manages prisoner state transitions atomically:
|
||||
* - PrisonerManager (SavedData state)
|
||||
* - CellRegistryV2 (cell assignment)
|
||||
* - Leash/captor refs (IBondageState / ICaptor)
|
||||
* - Escape detection (tick)
|
||||
*
|
||||
* Replaces EscapeService and unifies scattered capture/imprison/extract/return/transfer logic.
|
||||
* 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.
|
||||
@@ -54,29 +42,6 @@ public class PrisonerService {
|
||||
|
||||
private PrisonerService() {}
|
||||
|
||||
// ==================== CONSTANTS (from EscapeService) ====================
|
||||
|
||||
/** Maximum distance from cell for IMPRISONED prisoners (blocks) */
|
||||
public static final double CELL_ESCAPE_DISTANCE = 20.0;
|
||||
|
||||
/** Maximum distance from camp for WORKING prisoners (blocks) */
|
||||
public static final double WORK_ESCAPE_DISTANCE = 100.0;
|
||||
|
||||
/** Maximum time offline before escape (30 minutes in ticks) */
|
||||
public static final long OFFLINE_TIMEOUT_TICKS = 30 * 60 * 20L;
|
||||
|
||||
/** Maximum time in WORKING state before forced return (10 minutes in ticks) */
|
||||
public static final long WORK_TIMEOUT_TICKS = 10 * 60 * 20L;
|
||||
|
||||
/** Maximum time in RETURNING phase before escape (5 minutes in ticks) */
|
||||
public static final long RETURN_TIMEOUT_TICKS = 5 * 60 * 20L;
|
||||
|
||||
/** Maximum time in PENDING_RETURN phase before forced return (2.5 minutes in ticks) */
|
||||
public static final long PENDING_RETURN_TIMEOUT_TICKS = 3000;
|
||||
|
||||
/** Check interval in ticks (every 5 seconds) */
|
||||
public static final int CHECK_INTERVAL_TICKS = 100;
|
||||
|
||||
// ==================== CAPTURE ====================
|
||||
|
||||
/**
|
||||
@@ -506,552 +471,4 @@ public class PrisonerService {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== ESCAPE (from EscapeService) ====================
|
||||
|
||||
/**
|
||||
* CENTRAL ESCAPE METHOD - All escapes must go through here.
|
||||
*
|
||||
* Handles complete cleanup:
|
||||
* - State transition (PrisonerManager)
|
||||
* - Cell registry cleanup
|
||||
* - Guard despawn
|
||||
* - Restraints removal (if online)
|
||||
* - Inventory NOT restored (stays in camp chest as punishment)
|
||||
*
|
||||
* @param level The server level
|
||||
* @param playerId Player UUID
|
||||
* @param reason Reason for escape (for logging)
|
||||
* @return true if escape was processed successfully
|
||||
*/
|
||||
public boolean escape(ServerLevel level, UUID playerId, String reason) {
|
||||
long currentTime = level.getGameTime();
|
||||
PrisonerManager manager = PrisonerManager.get(level);
|
||||
CellRegistryV2 cellRegistry = CellRegistryV2.get(level);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PrisonerService] Processing escape for {} - reason: {}",
|
||||
playerId.toString().substring(0, 8),
|
||||
reason
|
||||
);
|
||||
|
||||
// Step 1: Save guard ID BEFORE state transition (manager.escape() removes the LaborRecord)
|
||||
LaborRecord laborBeforeEscape = manager.getLaborRecord(playerId);
|
||||
UUID guardId = laborBeforeEscape.getGuardId();
|
||||
|
||||
// Step 2: Transition prisoner state to FREE
|
||||
boolean stateChanged = manager.escape(playerId, currentTime, reason);
|
||||
if (!stateChanged) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[PrisonerService] Failed to change state for {} - invalid transition",
|
||||
playerId.toString().substring(0, 8)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Cleanup CellRegistryV2 - remove from all cells
|
||||
int cellsCleared = cellRegistry.releasePrisonerFromAllCells(playerId);
|
||||
if (cellsCleared > 0) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[PrisonerService] Cleared {} from {} cells",
|
||||
playerId.toString().substring(0, 8),
|
||||
cellsCleared
|
||||
);
|
||||
}
|
||||
|
||||
// Step 4: Cleanup guard using saved reference (LaborRecord was removed by manager.escape())
|
||||
if (guardId != null) {
|
||||
net.minecraft.world.entity.Entity guardEntity = level.getEntity(
|
||||
guardId
|
||||
);
|
||||
if (guardEntity != null) {
|
||||
guardEntity.discard();
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[PrisonerService] Despawned guard {} during escape",
|
||||
guardId.toString().substring(0, 8)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Free from restraints (if player is online)
|
||||
// NOTE: Inventory is NOT restored on escape - items remain in camp chest as punishment
|
||||
ServerPlayer player = level
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
IBondageState cap = KidnappedHelper.getKidnappedState(player);
|
||||
if (cap != null) {
|
||||
cap.free(false);
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PrisonerService] Freed {} from restraints (inventory remains in camp chest)",
|
||||
player.getName().getString()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[PrisonerService] Player {} offline - restraints cleanup deferred",
|
||||
playerId.toString().substring(0, 8)
|
||||
);
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PrisonerService] Escape complete for {} - items stay in camp chest",
|
||||
playerId.toString().substring(0, 8)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== RELEASE (from EscapeService) ====================
|
||||
|
||||
/**
|
||||
* CENTRAL RELEASE METHOD - All legitimate releases must go through here.
|
||||
*
|
||||
* Similar to escape() but transitions to PROTECTED state with grace period.
|
||||
* Handles complete cleanup:
|
||||
* - State transition to PROTECTED (PrisonerManager)
|
||||
* - Cell registry cleanup
|
||||
* - Inventory restoration
|
||||
* - Restraints removal (if online)
|
||||
*
|
||||
* @param level The server level
|
||||
* @param playerId Player UUID
|
||||
* @param gracePeriodTicks Grace period before returning to FREE (0 = instant FREE)
|
||||
* @return true if release was processed successfully
|
||||
*/
|
||||
public boolean release(
|
||||
ServerLevel level,
|
||||
UUID playerId,
|
||||
long gracePeriodTicks
|
||||
) {
|
||||
long currentTime = level.getGameTime();
|
||||
PrisonerManager manager = PrisonerManager.get(level);
|
||||
CellRegistryV2 cellRegistry = CellRegistryV2.get(level);
|
||||
com.tiedup.remake.cells.ConfiscatedInventoryRegistry inventoryRegistry =
|
||||
com.tiedup.remake.cells.ConfiscatedInventoryRegistry.get(level);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PrisonerService] Processing release for {} - grace period: {} ticks",
|
||||
playerId.toString().substring(0, 8),
|
||||
gracePeriodTicks
|
||||
);
|
||||
|
||||
// Step 1: Transition prisoner state to PROTECTED (or FREE if gracePeriod = 0)
|
||||
boolean stateChanged = manager.release(
|
||||
playerId,
|
||||
currentTime,
|
||||
gracePeriodTicks
|
||||
);
|
||||
if (!stateChanged) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[PrisonerService] Failed to change state for {} - invalid transition",
|
||||
playerId.toString().substring(0, 8)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Cleanup CellRegistryV2 - remove from all cells
|
||||
int cellsCleared = cellRegistry.releasePrisonerFromAllCells(playerId);
|
||||
if (cellsCleared > 0) {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[PrisonerService] Cleared {} from {} cells",
|
||||
playerId.toString().substring(0, 8),
|
||||
cellsCleared
|
||||
);
|
||||
}
|
||||
|
||||
// Step 3: Restore confiscated inventory (if player is online)
|
||||
ServerPlayer player = level
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayer(playerId);
|
||||
if (player != null) {
|
||||
// Restore inventory
|
||||
if (inventoryRegistry.hasConfiscatedInventory(playerId)) {
|
||||
boolean restored = inventoryRegistry.restoreInventory(player);
|
||||
if (restored) {
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PrisonerService] Restored confiscated inventory for {}",
|
||||
player.getName().getString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Free from restraints
|
||||
IBondageState cap = KidnappedHelper.getKidnappedState(player);
|
||||
if (cap != null) {
|
||||
cap.free(false);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[PrisonerService] Freed {} from restraints",
|
||||
player.getName().getString()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[PrisonerService] Player {} offline - inventory cleanup deferred",
|
||||
playerId.toString().substring(0, 8)
|
||||
);
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PrisonerService] Release complete for {} - full cleanup done",
|
||||
playerId.toString().substring(0, 8)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==================== TICK (escape detection, from EscapeService) ====================
|
||||
|
||||
/**
|
||||
* Tick escape detection.
|
||||
* Called from CampManagementHandler.
|
||||
*
|
||||
* @param server The server
|
||||
* @param currentTime Current game time
|
||||
*/
|
||||
public void tick(MinecraftServer server, long currentTime) {
|
||||
if (currentTime % CHECK_INTERVAL_TICKS != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerLevel level = server.overworld();
|
||||
PrisonerManager manager = PrisonerManager.get(level);
|
||||
CellRegistryV2 cells = CellRegistryV2.get(level);
|
||||
CollarRegistry collars = CollarRegistry.get(level);
|
||||
|
||||
List<EscapeCandidate> escapees = new ArrayList<>();
|
||||
|
||||
for (UUID playerId : manager.getAllPrisonerIds()) {
|
||||
PrisonerRecord record = manager.getRecord(playerId);
|
||||
PrisonerState state = record.getState();
|
||||
|
||||
if (!state.isCaptive()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ServerPlayer player = server.getPlayerList().getPlayer(playerId);
|
||||
|
||||
// === OFFLINE CHECK ===
|
||||
if (player == null) {
|
||||
long timeInState = record.getTimeInState(currentTime);
|
||||
if (timeInState > OFFLINE_TIMEOUT_TICKS) {
|
||||
escapees.add(
|
||||
new EscapeCandidate(playerId, "offline timeout")
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// === COLLAR CHECK ===
|
||||
if (state != PrisonerState.CAPTURED) {
|
||||
boolean hasCollarOwners = collars.hasOwners(playerId);
|
||||
if (!hasCollarOwners) {
|
||||
IBondageState cap = KidnappedHelper.getKidnappedState(
|
||||
player
|
||||
);
|
||||
ItemStack collar =
|
||||
cap != null
|
||||
? cap.getEquipment(BodyRegionV2.NECK)
|
||||
: ItemStack.EMPTY;
|
||||
|
||||
if (
|
||||
!collar.isEmpty() &&
|
||||
com.tiedup.remake.v2.bondage.CollarHelper.isCollar(collar)
|
||||
) {
|
||||
List<UUID> nbtOwners = com.tiedup.remake.v2.bondage.CollarHelper.getOwners(collar);
|
||||
if (!nbtOwners.isEmpty()) {
|
||||
for (UUID ownerUUID : nbtOwners) {
|
||||
collars.registerCollar(playerId, ownerUUID);
|
||||
}
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PrisonerService] Re-synced collar registry for {} - found {} owners in NBT",
|
||||
playerId.toString().substring(0, 8),
|
||||
nbtOwners.size()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[PrisonerService] Collar check failed for {} (state={}): collarEquipped={}, collarItem={}",
|
||||
playerId.toString().substring(0, 8),
|
||||
state,
|
||||
!collar.isEmpty(),
|
||||
collar.isEmpty() ? "none" : collar.getItem().toString()
|
||||
);
|
||||
escapees.add(
|
||||
new EscapeCandidate(playerId, "collar removed")
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// === STATE-SPECIFIC CHECKS ===
|
||||
switch (state) {
|
||||
case IMPRISONED -> {
|
||||
UUID cellId = record.getCellId();
|
||||
if (cellId != null) {
|
||||
CellDataV2 cell = cells.getCell(cellId);
|
||||
if (cell == null) {
|
||||
escapees.add(
|
||||
new EscapeCandidate(
|
||||
playerId,
|
||||
"cell no longer exists"
|
||||
)
|
||||
);
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[PrisonerService] Prisoner {} has orphaned cellId {} - triggering escape",
|
||||
playerId.toString().substring(0, 8),
|
||||
cellId.toString().substring(0, 8)
|
||||
);
|
||||
} else {
|
||||
double distance = Math.sqrt(
|
||||
player
|
||||
.blockPosition()
|
||||
.distSqr(cell.getCorePos())
|
||||
);
|
||||
if (distance > CELL_ESCAPE_DISTANCE) {
|
||||
escapees.add(
|
||||
new EscapeCandidate(
|
||||
playerId,
|
||||
String.format(
|
||||
"too far from cell (%.1f blocks)",
|
||||
distance
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case WORKING -> {
|
||||
LaborRecord labor = manager.getLaborRecord(playerId);
|
||||
|
||||
long timeInPhase = labor.getTimeInPhase(currentTime);
|
||||
if (
|
||||
labor.getPhase() == LaborRecord.WorkPhase.WORKING &&
|
||||
timeInPhase > WORK_TIMEOUT_TICKS
|
||||
) {
|
||||
labor.failTask(currentTime);
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PrisonerService] {} work timeout - forcing return",
|
||||
playerId.toString().substring(0, 8)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
labor.getPhase() == LaborRecord.WorkPhase.RETURNING &&
|
||||
timeInPhase > RETURN_TIMEOUT_TICKS
|
||||
) {
|
||||
escapees.add(
|
||||
new EscapeCandidate(playerId, "return timeout")
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// PENDING_RETURN timeout: maid failed to pick up prisoner — force return to cell
|
||||
if (
|
||||
labor.getPhase() ==
|
||||
LaborRecord.WorkPhase.PENDING_RETURN &&
|
||||
timeInPhase > PENDING_RETURN_TIMEOUT_TICKS
|
||||
) {
|
||||
PrisonerRecord rec = manager.getRecord(playerId);
|
||||
UUID cellId = rec.getCellId();
|
||||
if (cellId != null) {
|
||||
CellDataV2 cell = cells.getCell(cellId);
|
||||
if (cell != null) {
|
||||
BlockPos teleportPos =
|
||||
cell.getSpawnPoint() != null
|
||||
? cell.getSpawnPoint()
|
||||
: cell.getCorePos().above();
|
||||
player.teleportTo(
|
||||
teleportPos.getX() + 0.5,
|
||||
teleportPos.getY(),
|
||||
teleportPos.getZ() + 0.5
|
||||
);
|
||||
IBondageState cap =
|
||||
KidnappedHelper.getKidnappedState(player);
|
||||
if (cap != null) {
|
||||
CompoundTag snapshot =
|
||||
labor.getBondageSnapshot();
|
||||
if (snapshot != null) {
|
||||
BondageService.get().restoreSnapshot(
|
||||
cap,
|
||||
snapshot
|
||||
);
|
||||
}
|
||||
}
|
||||
returnToCell(level, player, null, cell);
|
||||
labor.startRest(currentTime);
|
||||
if (labor.getGuardId() != null) {
|
||||
net.minecraft.world.entity.Entity guardEntity =
|
||||
level.getEntity(labor.getGuardId());
|
||||
if (
|
||||
guardEntity != null
|
||||
) guardEntity.discard();
|
||||
}
|
||||
player.sendSystemMessage(
|
||||
Component.translatable(
|
||||
"msg.tiedup.prison.returned_to_cell"
|
||||
).withStyle(ChatFormatting.GRAY)
|
||||
);
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PrisonerService] Force-returned {} to cell (PENDING_RETURN timeout)",
|
||||
playerId.toString().substring(0, 8)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Cell gone → trigger escape
|
||||
escapees.add(
|
||||
new EscapeCandidate(
|
||||
playerId,
|
||||
"pending_return timeout, cell gone"
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (labor.getGuardId() != null) {
|
||||
net.minecraft.world.entity.Entity guardEntity =
|
||||
level.getEntity(labor.getGuardId());
|
||||
if (guardEntity != null && guardEntity.isAlive()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
UUID campId = record.getCampId();
|
||||
if (campId != null) {
|
||||
List<CellDataV2> campCells = cells.getCellsByCamp(
|
||||
campId
|
||||
);
|
||||
if (campCells.isEmpty()) {
|
||||
escapees.add(
|
||||
new EscapeCandidate(
|
||||
playerId,
|
||||
"camp no longer exists"
|
||||
)
|
||||
);
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[PrisonerService] Worker {} has orphaned campId {} with no cells - triggering escape",
|
||||
playerId.toString().substring(0, 8),
|
||||
campId.toString().substring(0, 8)
|
||||
);
|
||||
} else {
|
||||
BlockPos campCenter = campCells.get(0).getCorePos();
|
||||
double distance = Math.sqrt(
|
||||
player.blockPosition().distSqr(campCenter)
|
||||
);
|
||||
if (distance > WORK_ESCAPE_DISTANCE) {
|
||||
escapees.add(
|
||||
new EscapeCandidate(
|
||||
playerId,
|
||||
String.format(
|
||||
"too far from camp (%.1f blocks)",
|
||||
distance
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case CAPTURED -> {
|
||||
// During transport - handled by kidnapper goals
|
||||
}
|
||||
default -> {
|
||||
// Other states don't need distance checks
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (EscapeCandidate candidate : escapees) {
|
||||
escape(level, candidate.playerId, candidate.reason);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== VALIDATION ====================
|
||||
|
||||
/**
|
||||
* Check if a player should be considered escaped.
|
||||
* Called on-demand (e.g., when player moves).
|
||||
*/
|
||||
@Nullable
|
||||
public String checkEscape(ServerLevel level, ServerPlayer player) {
|
||||
PrisonerManager manager = PrisonerManager.get(level);
|
||||
CellRegistryV2 cells = CellRegistryV2.get(level);
|
||||
CollarRegistry collars = CollarRegistry.get(level);
|
||||
|
||||
UUID playerId = player.getUUID();
|
||||
PrisonerRecord record = manager.getRecord(playerId);
|
||||
PrisonerState state = record.getState();
|
||||
|
||||
if (!state.isCaptive()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!collars.hasOwners(playerId)) {
|
||||
return "collar removed";
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case IMPRISONED -> {
|
||||
UUID cellId = record.getCellId();
|
||||
if (cellId != null) {
|
||||
CellDataV2 cell = cells.getCell(cellId);
|
||||
if (cell != null) {
|
||||
double distance = Math.sqrt(
|
||||
player.blockPosition().distSqr(cell.getCorePos())
|
||||
);
|
||||
if (distance > CELL_ESCAPE_DISTANCE) {
|
||||
return String.format(
|
||||
"too far from cell (%.1f blocks)",
|
||||
distance
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case WORKING -> {
|
||||
UUID campId = record.getCampId();
|
||||
if (campId != null) {
|
||||
List<CellDataV2> campCells = cells.getCellsByCamp(campId);
|
||||
if (!campCells.isEmpty()) {
|
||||
BlockPos campCenter = campCells.get(0).getCorePos();
|
||||
double distance = Math.sqrt(
|
||||
player.blockPosition().distSqr(campCenter)
|
||||
);
|
||||
if (distance > WORK_ESCAPE_DISTANCE) {
|
||||
return String.format(
|
||||
"too far from camp (%.1f blocks)",
|
||||
distance
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
// Other states don't have distance limits
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle player movement event.
|
||||
* Called from event handler to check distance-based escapes.
|
||||
*/
|
||||
public void onPlayerMove(ServerPlayer player, ServerLevel level) {
|
||||
String escapeReason = checkEscape(level, player);
|
||||
if (escapeReason != null) {
|
||||
escape(level, player.getUUID(), escapeReason);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== HELPER ====================
|
||||
|
||||
private record EscapeCandidate(UUID playerId, String reason) {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user