package com.tiedup.remake.cells;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.prison.PrisonerManager;
import com.tiedup.remake.state.IRestrainable;
import com.tiedup.remake.util.KidnappedHelper;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import net.minecraft.ChatFormatting;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
/**
* Handles camp lifecycle events: camp death, prisoner freeing, and camp defense alerts.
*
*
This is a stateless utility class. All state lives in {@link CampOwnership}
* (the SavedData singleton). Methods here orchestrate multi-system side effects
* that don't belong in the data layer.
*/
public final class CampLifecycleManager {
private CampLifecycleManager() {} // utility class
/**
* Mark a camp as dead (trader killed) and perform full cleanup.
* This:
* - Cancels all ransoms for the camp's prisoners
* - Frees all prisoners (untie, unlock collars)
* - Clears all labor states
* - Removes all cells belonging to the camp
* - Makes the camp inactive
*
* @param campId The camp UUID
* @param level The server level for entity lookups
*/
public static void markCampDead(UUID campId, ServerLevel level) {
CampOwnership ownership = CampOwnership.get(level);
CampOwnership.CampData data = ownership.getCamp(campId);
if (data == null) {
return;
}
UUID traderUUID = data.getTraderUUID();
TiedUpMod.LOGGER.info(
"[CampLifecycleManager] Camp {} dying - freeing all prisoners",
campId.toString().substring(0, 8)
);
// PERFORMANCE FIX: Use PrisonerManager's index instead of CellRegistry
// This is O(1) lookup instead of iterating all cells
PrisonerManager manager = PrisonerManager.get(level);
Set prisonerIds = manager.getPrisonersInCamp(campId);
TiedUpMod.LOGGER.debug(
"[CampLifecycleManager] Found {} prisoners in camp {} via index",
prisonerIds.size(),
campId.toString().substring(0, 8)
);
// Cancel ransoms and free each prisoner
for (UUID prisonerId : prisonerIds) {
// Cancel ransom by clearing it
if (manager.getRansomRecord(prisonerId) != null) {
manager.setRansomRecord(prisonerId, null);
TiedUpMod.LOGGER.debug(
"[CampLifecycleManager] Cancelled ransom for prisoner {}",
prisonerId.toString().substring(0, 8)
);
}
// Free the prisoner
ServerPlayer prisoner = level
.getServer()
.getPlayerList()
.getPlayer(prisonerId);
if (prisoner != null) {
// Online: untie, unlock collar, release with 5-min grace period
removeLaborTools(prisoner);
freePrisonerOnCampDeath(prisoner, traderUUID, level);
// Cell cleanup only -- freePrisonerOnCampDeath already called release()
// which transitions to PROTECTED with grace period.
// Calling escape() here would override PROTECTED->FREE, losing the grace.
CellRegistryV2.get(level).releasePrisonerFromAllCells(
prisonerId
);
} else {
// Offline: full escape via PrisonerService (no grace period needed)
com.tiedup.remake.prison.service.PrisonerService.get().escape(
level,
prisonerId,
"camp death"
);
}
ownership.unmarkPrisonerProcessed(prisonerId);
}
// HIGH FIX: Remove all cells belonging to this camp from CellRegistryV2
// Prevents memory leak and stale data in indices
CellRegistryV2 cellRegistry = CellRegistryV2.get(level);
List campCells = cellRegistry.getCellsByCamp(campId);
for (CellDataV2 cell : campCells) {
cellRegistry.removeCell(cell.getId());
TiedUpMod.LOGGER.debug(
"[CampLifecycleManager] Removed cell {} from registry",
cell.getId().toString().substring(0, 8)
);
}
TiedUpMod.LOGGER.info(
"[CampLifecycleManager] Removed {} cells for dead camp {}",
campCells.size(),
campId.toString().substring(0, 8)
);
// Mark camp as dead
data.setAlive(false);
ownership.setDirty();
TiedUpMod.LOGGER.info(
"[CampLifecycleManager] Camp {} is now dead, {} prisoners freed",
campId.toString().substring(0, 8),
prisonerIds.size()
);
}
/**
* Alert all NPCs in a camp to defend against an attacker.
* Called when someone tries to restrain the trader or maid.
*
* @param campId The camp UUID
* @param attacker The player attacking
* @param level The server level
*/
public static void alertCampToDefend(
UUID campId,
Player attacker,
ServerLevel level
) {
CampOwnership ownership = CampOwnership.get(level);
CampOwnership.CampData camp = ownership.getCamp(campId);
if (camp == null) return;
int alertedCount = 0;
// 1. Alert the trader
UUID traderUUID = camp.getTraderUUID();
if (traderUUID != null) {
net.minecraft.world.entity.Entity traderEntity = level.getEntity(
traderUUID
);
if (
traderEntity instanceof
com.tiedup.remake.entities.EntitySlaveTrader trader
) {
if (trader.isAlive() && !trader.isTiedUp()) {
trader.setTarget(attacker);
trader.setLastAttacker(attacker);
alertedCount++;
}
}
}
// 2. Alert the maid
UUID maidUUID = camp.getMaidUUID();
if (maidUUID != null) {
net.minecraft.world.entity.Entity maidEntity = level.getEntity(
maidUUID
);
if (
maidEntity instanceof com.tiedup.remake.entities.EntityMaid maid
) {
if (maid.isAlive() && !maid.isTiedUp()) {
maid.setTarget(attacker);
maid.setMaidState(
com.tiedup.remake.entities.ai.maid.MaidState.DEFENDING
);
alertedCount++;
}
}
}
// 3. Alert all kidnappers
Set kidnapperUUIDs = camp.getLinkedKidnappers();
for (UUID kidnapperUUID : kidnapperUUIDs) {
net.minecraft.world.entity.Entity entity = level.getEntity(
kidnapperUUID
);
if (
entity instanceof
com.tiedup.remake.entities.EntityKidnapper kidnapper
) {
if (kidnapper.isAlive() && !kidnapper.isTiedUp()) {
kidnapper.setTarget(attacker);
kidnapper.setLastAttacker(attacker);
alertedCount++;
}
}
}
TiedUpMod.LOGGER.info(
"[CampLifecycleManager] Camp {} alerted {} NPCs to defend against {}",
campId.toString().substring(0, 8),
alertedCount,
attacker.getName().getString()
);
}
// ==================== PRIVATE HELPERS ====================
/**
* Free a prisoner when their camp dies.
* Untie, unlock collar, cancel sale, notify.
* Uses legitimate removal flag to prevent alerting kidnappers.
*/
private static void freePrisonerOnCampDeath(
ServerPlayer prisoner,
UUID traderUUID,
ServerLevel level
) {
IRestrainable state = KidnappedHelper.getKidnappedState(prisoner);
if (state == null) {
return;
}
// Suppress collar removal alerts - this is a legitimate release (camp death)
ItemCollar.runWithSuppressedAlert(() -> {
// Unlock collar if owned by the dead camp/trader
unlockCollarIfOwnedBy(prisoner, state, traderUUID);
// Remove all restraints (including collar if any)
state.untie(true);
// Cancel sale
state.cancelSale();
});
// Clear client HUD
com.tiedup.remake.network.ModNetwork.sendToPlayer(
new com.tiedup.remake.network.labor.PacketSyncLaborProgress(),
prisoner
);
// Notify prisoner
prisoner.sendSystemMessage(
Component.literal("Your captor has died. You are FREE!").withStyle(
ChatFormatting.GREEN,
ChatFormatting.BOLD
)
);
// Grant grace period (5 minutes = 6000 ticks)
PrisonerManager manager = PrisonerManager.get(level);
manager.release(prisoner.getUUID(), level.getGameTime(), 6000);
prisoner.sendSystemMessage(
Component.literal(
"You have 5 minutes of protection from kidnappers."
).withStyle(ChatFormatting.AQUA)
);
TiedUpMod.LOGGER.info(
"[CampLifecycleManager] Freed prisoner {} on camp death (no alert)",
prisoner.getName().getString()
);
}
/**
* Unlock a prisoner's collar if it's owned by the specified owner (trader/kidnapper).
*/
private static void unlockCollarIfOwnedBy(
ServerPlayer prisoner,
IRestrainable state,
UUID ownerUUID
) {
ItemStack collar = state.getEquipment(BodyRegionV2.NECK);
if (collar.isEmpty()) {
return;
}
if (collar.getItem() instanceof ItemCollar collarItem) {
List owners = collarItem.getOwners(collar);
// If the dead trader/camp is an owner, unlock the collar
if (owners.contains(ownerUUID)) {
if (collar.getItem() instanceof ILockable lockable) {
lockable.setLockedByKeyUUID(collar, null); // Unlock and clear
}
TiedUpMod.LOGGER.debug(
"[CampLifecycleManager] Unlocked collar for {} (owner {} died)",
prisoner.getName().getString(),
ownerUUID.toString().substring(0, 8)
);
}
}
}
/**
* SECURITY: Remove all labor tools from player inventory.
* Prevents prisoners from keeping unbreakable tools when freed/released.
*/
private static void removeLaborTools(ServerPlayer player) {
var inventory = player.getInventory();
int removedCount = 0;
for (int i = 0; i < inventory.getContainerSize(); i++) {
ItemStack stack = inventory.getItem(i);
if (!stack.isEmpty() && stack.hasTag()) {
CompoundTag tag = stack.getTag();
if (tag != null && tag.getBoolean("LaborTool")) {
inventory.setItem(i, ItemStack.EMPTY);
removedCount++;
}
}
}
if (removedCount > 0) {
TiedUpMod.LOGGER.debug(
"[CampLifecycleManager] Removed {} labor tools from {} on camp death",
removedCount,
player.getName().getString()
);
}
}
}