package com.tiedup.remake.worldgen; import com.tiedup.remake.cells.CampOwnership; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.EntityKidnapper; import com.tiedup.remake.entities.EntityKidnapperElite; import com.tiedup.remake.entities.EntityMaid; import com.tiedup.remake.entities.EntitySlaveTrader; import com.tiedup.remake.entities.ModEntities; import java.util.UUID; import javax.annotation.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.RandomSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.MobSpawnType; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.Mirror; import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.TemplateStructurePiece; import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext; import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceType; import net.minecraft.world.level.levelgen.structure.templatesystem.BlockIgnoreProcessor; import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; /** * Structure piece for kidnapper camp tents. * * Phase: Kidnapper Revamp - Cell System * * Handles data markers: * - "kidnapper" - Spawns a regular kidnapper * - "kidnapper_elite" - Spawns an elite kidnapper * - "slave_trader" - Spawns a slave trader (brain of the camp) * - "maid" - Spawns a maid (linked to trader) * - "loot" - Places a loot chest */ public class KidnapperCampPiece extends TemplateStructurePiece { /** Temporary storage for trader UUID during structure generation */ @Nullable private UUID spawnedTraderUUID; /** Temporary storage for camp data during structure generation */ @Nullable private CampOwnership.CampData currentCamp; public KidnapperCampPiece( StructureTemplateManager templateManager, ResourceLocation templateLocation, BlockPos pos, Rotation rotation ) { super( ModStructures.CAMP_PIECE.get(), 0, templateManager, templateLocation, templateLocation.toString(), makeSettings(rotation), pos ); } public KidnapperCampPiece( StructureTemplateManager templateManager, CompoundTag tag ) { super(ModStructures.CAMP_PIECE.get(), tag, templateManager, location -> makeSettings(Rotation.NONE) ); } private static StructurePlaceSettings makeSettings(Rotation rotation) { return new StructurePlaceSettings() .setRotation(rotation) .setMirror(Mirror.NONE) .setRotationPivot(BlockPos.ZERO) .addProcessor(BlockIgnoreProcessor.STRUCTURE_BLOCK) .addProcessor(new MarkerProcessor()); } @Override protected void handleDataMarker( String marker, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox box ) { // Handle data markers from structure blocks // Data markers are placed using jigsaw blocks or structure blocks with "Data" mode if (!(level instanceof ServerLevel serverLevel)) { return; } switch (marker.toLowerCase()) { case "kidnapper" -> { // Spawn a kidnapper at this position spawnKidnapper(serverLevel, pos, random, false); } case "kidnapper_elite" -> { // Spawn elite kidnapper at this position spawnKidnapper(serverLevel, pos, random, true); } case "slave_trader" -> { // Spawn slave trader (brain of camp) spawnSlaveTrader(serverLevel, pos, random); } case "maid" -> { // Spawn maid (linked to trader) spawnMaid(serverLevel, pos, random); } case "loot" -> { // TODO: Place loot chest } default -> { TiedUpMod.LOGGER.debug( "[KidnapperCampPiece] Unknown data marker: {} at {}", marker, pos.toShortString() ); } } } /** * Spawn a kidnapper at the given position. * * @param level The server level * @param pos Position to spawn at * @param random Random source * @param elite Whether to spawn an elite kidnapper */ private void spawnKidnapper( ServerLevel level, BlockPos pos, RandomSource random, boolean elite ) { EntityKidnapper kidnapper; if (elite) { kidnapper = ModEntities.KIDNAPPER_ELITE.get().create(level); } else { kidnapper = ModEntities.KIDNAPPER.get().create(level); } if (kidnapper != null) { kidnapper.moveTo( pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, random.nextFloat() * 360F, 0.0F ); kidnapper.finalizeSpawn( level, level.getCurrentDifficultyAt(pos), MobSpawnType.STRUCTURE, null, null ); level.addFreshEntity(kidnapper); TiedUpMod.LOGGER.info( "[KidnapperCampPiece] Spawned {} at {} from data marker", elite ? "elite kidnapper" : "kidnapper", pos.toShortString() ); } } /** * Spawn a slave trader at the given position. * Creates/updates the camp data in CampOwnership. * * @param level The server level * @param pos Position to spawn at * @param random Random source */ private void spawnSlaveTrader( ServerLevel level, BlockPos pos, RandomSource random ) { EntitySlaveTrader trader = ModEntities.SLAVE_TRADER.get().create(level); if (trader == null) { TiedUpMod.LOGGER.warn( "[KidnapperCampPiece] Failed to create SlaveTrader entity" ); return; } trader.moveTo( pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, random.nextFloat() * 360F, 0.0F ); trader.finalizeSpawn( level, level.getCurrentDifficultyAt(pos), MobSpawnType.STRUCTURE, null, null ); // Create or get the camp in CampOwnership registry CampOwnership registry = CampOwnership.get(level); CampOwnership.CampData camp = registry.findNearestAliveCamp(pos, 50); if (camp == null) { // Create a new camp UUID campId = UUID.randomUUID(); registry.registerCamp(campId, trader.getUUID(), null, pos); camp = registry.getCamp(campId); } else { // Update existing camp with this trader camp.setTraderUUID(trader.getUUID()); camp.setCenter(pos); registry.setDirty(); } // Link trader to camp if (camp != null) { trader.setCampUUID(camp.getCampId()); this.currentCamp = camp; } // Store for maid linking this.spawnedTraderUUID = trader.getUUID(); level.addFreshEntity(trader); TiedUpMod.LOGGER.info( "[KidnapperCampPiece] Spawned slave trader {} at {} from data marker, camp={}", trader.getNpcName(), pos.toShortString(), camp != null ? camp.getCampId().toString().substring(0, 8) : "null" ); } /** * Spawn a maid at the given position. * Must be called AFTER trader spawns to establish link. * * @param level The server level * @param pos Position to spawn at * @param random Random source */ private void spawnMaid( ServerLevel level, BlockPos pos, RandomSource random ) { EntityMaid maid = ModEntities.MAID.get().create(level); if (maid == null) { TiedUpMod.LOGGER.warn( "[KidnapperCampPiece] Failed to create Maid entity" ); return; } maid.moveTo( pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, random.nextFloat() * 360F, 0.0F ); maid.finalizeSpawn( level, level.getCurrentDifficultyAt(pos), MobSpawnType.STRUCTURE, null, null ); // Link to trader (must be spawned after trader) boolean linkedSuccessfully = false; if (this.spawnedTraderUUID != null) { maid.setMasterTraderUUID(this.spawnedTraderUUID); // Find the trader entity and update its maid reference Entity traderEntity = level.getEntity(this.spawnedTraderUUID); if (traderEntity instanceof EntitySlaveTrader trader) { trader.setMaidUUID(maid.getUUID()); linkedSuccessfully = true; TiedUpMod.LOGGER.debug( "[KidnapperCampPiece] Linked maid {} to trader {}", maid.getNpcName(), trader.getNpcName() ); } // Link maid to camp if (this.currentCamp != null) { this.currentCamp.setMaidUUID(maid.getUUID()); CampOwnership.get(level).setDirty(); } } // Fallback: If no trader link established, search for nearby trader/camp if (!linkedSuccessfully) { TiedUpMod.LOGGER.debug( "[KidnapperCampPiece] Maid at {} - no direct trader link, searching nearby...", pos.toShortString() ); // Try to find a nearby camp first CampOwnership registry = CampOwnership.get(level); CampOwnership.CampData camp = registry.findNearestAliveCamp( pos, 50 ); if (camp != null && camp.getTraderUUID() != null) { // Found a camp with a trader - link to it maid.setMasterTraderUUID(camp.getTraderUUID()); camp.setMaidUUID(maid.getUUID()); registry.setDirty(); // Try to update the trader entity too Entity traderEntity = level.getEntity(camp.getTraderUUID()); if (traderEntity instanceof EntitySlaveTrader trader) { trader.setMaidUUID(maid.getUUID()); linkedSuccessfully = true; TiedUpMod.LOGGER.info( "[KidnapperCampPiece] Maid {} linked to trader {} via camp fallback", maid.getNpcName(), trader.getNpcName() ); } } // Last resort: search for nearby trader entities if (!linkedSuccessfully) { java.util.List nearbyTraders = level.getEntitiesOfClass( EntitySlaveTrader.class, maid.getBoundingBox().inflate(50) ); if (!nearbyTraders.isEmpty()) { EntitySlaveTrader trader = nearbyTraders.get(0); maid.setMasterTraderUUID(trader.getUUID()); trader.setMaidUUID(maid.getUUID()); linkedSuccessfully = true; // Update camp if trader has one if (trader.getCampUUID() != null) { CampOwnership.CampData traderCamp = registry.getCamp( trader.getCampUUID() ); if (traderCamp != null) { traderCamp.setMaidUUID(maid.getUUID()); registry.setDirty(); } } TiedUpMod.LOGGER.info( "[KidnapperCampPiece] Maid {} linked to nearby trader {} via entity search fallback", maid.getNpcName(), trader.getNpcName() ); } } } if (!linkedSuccessfully) { TiedUpMod.LOGGER.warn( "[KidnapperCampPiece] Maid spawned at {} but no trader found! Maid will be orphaned.", pos.toShortString() ); } level.addFreshEntity(maid); TiedUpMod.LOGGER.info( "[KidnapperCampPiece] Spawned maid {} at {} from data marker, linked to trader={}", maid.getNpcName(), pos.toShortString(), this.spawnedTraderUUID != null ? this.spawnedTraderUUID.toString().substring(0, 8) : "null" ); } }