package com.tiedup.remake.events.camp; import com.tiedup.remake.cells.CampMaidManager; import com.tiedup.remake.cells.CampOwnership; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.EntityMaid; import com.tiedup.remake.entities.EntitySlaveTrader; import com.tiedup.remake.entities.ModEntities; // Prison system v2 import com.tiedup.remake.prison.PrisonerManager; import com.tiedup.remake.prison.service.EscapeMonitorService; import java.util.List; import java.util.UUID; import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; /** * Handles camp management tasks including maid respawn. * * When a maid dies: * - Camp remains alive (trader still exists) * - Prisoners are paused (no new tasks) * - After 5 minutes (6000 ticks), a new maid spawns * - Prisoners are notified and labor resumes */ @Mod.EventBusSubscriber( modid = TiedUpMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE ) public class CampManagementHandler { // Check every 100 ticks (5 seconds) - optimized for performance private static final int CHECK_INTERVAL_TICKS = 100; private static int tickCounter = 0; /** * Periodically check for camps needing maid respawn. */ @SubscribeEvent public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase != TickEvent.Phase.END) return; tickCounter++; if (tickCounter < CHECK_INTERVAL_TICKS) return; tickCounter = 0; // Process all worlds for (ServerLevel level : event.getServer().getAllLevels()) { processCampManagement(level); } } /** * Process camp management for a specific level. */ private static void processCampManagement(ServerLevel level) { // IMPORTANT: Always use server-level registry to avoid dimension fragmentation CampOwnership ownership = CampOwnership.get(level.getServer()); long currentTime = level.getGameTime(); // Get camps that need maid respawn List campsNeedingMaid = CampMaidManager.getCampsNeedingMaidRespawn(currentTime, level); for (UUID campId : campsNeedingMaid) { spawnNewMaidForCamp(level, ownership, campId); } // Prison system v2 - tick escape service (handles escape detection) EscapeMonitorService.get().tick(level.getServer(), currentTime); // Prison system v2 - tick protection expiry PrisonerManager.get(level).tickProtectionExpiry(currentTime); } /** * Spawn a new maid for a camp that needs one. * * @param level The server level * @param ownership The camp ownership data * @param campId The camp UUID */ private static void spawnNewMaidForCamp( ServerLevel level, CampOwnership ownership, UUID campId ) { CampOwnership.CampData campData = ownership.getCamp(campId); if (campData == null || !campData.isAlive()) { return; } BlockPos center = campData.getCenter(); if (center == null) { TiedUpMod.LOGGER.warn( "[CampManagementHandler] Cannot spawn maid - camp {} has no center position", campId.toString().substring(0, 8) ); return; } UUID traderUUID = campData.getTraderUUID(); if (traderUUID == null) { TiedUpMod.LOGGER.warn( "[CampManagementHandler] Cannot spawn maid - camp {} has no trader", campId.toString().substring(0, 8) ); return; } // Find the trader entity Entity traderEntity = level.getEntity(traderUUID); EntitySlaveTrader trader = null; if (traderEntity instanceof EntitySlaveTrader t) { trader = t; } else { // Trader not loaded - search near camp center trader = findTraderNearPosition(level, center, 50); } if (trader == null) { TiedUpMod.LOGGER.warn( "[CampManagementHandler] Cannot spawn maid - trader not found for camp {}", campId.toString().substring(0, 8) ); return; } // Create new maid EntityMaid maid = ModEntities.MAID.get().create(level); if (maid == null) { TiedUpMod.LOGGER.error( "[CampManagementHandler] Failed to create maid entity for camp {}", campId.toString().substring(0, 8) ); return; } // Find spawn position near trader (slightly offset) BlockPos spawnPos = findSafeSpawnPosition( level, trader.blockPosition() ); // Position the maid maid.moveTo( spawnPos.getX() + 0.5, spawnPos.getY(), spawnPos.getZ() + 0.5, trader.getYRot() + 180, // Face opposite direction from trader 0 ); // Link maid to trader maid.setMasterTraderUUID(trader.getUUID()); // Add to world level.addFreshEntity(maid); // Update camp ownership with new maid CampMaidManager.assignNewMaid(campId, maid.getUUID(), level); // Update trader's maid reference trader.setMaidUUID(maid.getUUID()); TiedUpMod.LOGGER.info( "[CampManagementHandler] Spawned replacement maid {} for camp {} at {}", maid.getNpcName(), campId.toString().substring(0, 8), spawnPos.toShortString() ); // Notify prisoners notifyPrisonersOfNewMaid(level, campId, maid.getNpcName()); } /** * Find a trader near the given position. */ private static EntitySlaveTrader findTraderNearPosition( ServerLevel level, BlockPos pos, int radius ) { List traders = level.getEntitiesOfClass( EntitySlaveTrader.class, new net.minecraft.world.phys.AABB(pos).inflate(radius) ); return traders.isEmpty() ? null : traders.get(0); } /** * Find a safe position to spawn the maid. * Tries positions around the target, prioritizing nearby valid spots. */ private static BlockPos findSafeSpawnPosition( ServerLevel level, BlockPos targetPos ) { // Try offsets in a spiral pattern int[][] offsets = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }, { 1, 1 }, { -1, 1 }, { -1, -1 }, { 1, -1 }, { 2, 0 }, { 0, 2 }, { -2, 0 }, { 0, -2 }, }; for (int[] offset : offsets) { BlockPos testPos = targetPos.offset(offset[0], 0, offset[1]); // Check if position is safe (solid ground, air above) if (isValidSpawnPosition(level, testPos)) { return testPos; } // Try one block down BlockPos downPos = testPos.below(); if (isValidSpawnPosition(level, downPos)) { return downPos; } // Try one block up BlockPos upPos = testPos.above(); if (isValidSpawnPosition(level, upPos)) { return upPos; } } // Fallback: use target position return targetPos; } /** * Check if a position is valid for spawning. */ private static boolean isValidSpawnPosition( ServerLevel level, BlockPos pos ) { // Ground must be solid if (!level.getBlockState(pos.below()).isSolid()) { return false; } // Position and above must be passable (air or similar) if (!level.getBlockState(pos).isAir()) { return false; } if (!level.getBlockState(pos.above()).isAir()) { return false; } return true; } /** * Notify all prisoners of this camp that a new maid has arrived. */ private static void notifyPrisonersOfNewMaid( ServerLevel level, UUID campId, String maidName ) { PrisonerManager manager = PrisonerManager.get(level); for (UUID prisonerId : manager.getPrisonersInCamp(campId)) { ServerPlayer player = level .getServer() .getPlayerList() .getPlayer(prisonerId); if (player != null) { player.sendSystemMessage( Component.translatable( "msg.tiedup.event.new_maid_arrived", maidName ).withStyle(ChatFormatting.GOLD) ); } } } }