package com.tiedup.remake.cells; import com.tiedup.remake.core.TiedUpMod; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.saveddata.SavedData; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Global registry for camp ownership linking camps to their SlaveTrader. * * This registry tracks: * - Camp UUID -> CampData (trader, maid, alive status) * - When a trader dies, the camp becomes inactive * * Persists across server restarts using Minecraft's SavedData system. */ public class CampOwnership extends SavedData { private static final String DATA_NAME = "tiedup_camp_ownership"; // Camp UUID -> CampData private final Map camps = new ConcurrentHashMap<>(); // Prisoners that have been processed (to avoid re-processing) private final Set processedPrisoners = ConcurrentHashMap.newKeySet(); // ==================== CAMP DATA CLASS ==================== /** * Data structure representing a camp and its owner. * Uses thread-safe collections for concurrent access. */ /** Maid respawn delay in ticks (5 minutes = 6000 ticks) */ public static final long MAID_RESPAWN_DELAY = 6000; public static class CampData { private final UUID campId; private volatile UUID traderUUID; private volatile UUID maidUUID; private volatile boolean isAlive = true; private volatile BlockPos center; private final Set linkedKidnapperUUIDs = ConcurrentHashMap.newKeySet(); /** Time when maid died (for respawn timer), -1 if alive */ private volatile long maidDeathTime = -1; /** Cached positions of LOOT chests (below LOOT markers) */ private final List lootChestPositions = new ArrayList<>(); public CampData(UUID campId) { this.campId = campId; } public CampData( UUID campId, UUID traderUUID, @Nullable UUID maidUUID, BlockPos center ) { this.campId = campId; this.traderUUID = traderUUID; this.maidUUID = maidUUID; this.center = center; this.isAlive = true; } // Getters public UUID getCampId() { return campId; } public UUID getTraderUUID() { return traderUUID; } public UUID getMaidUUID() { return maidUUID; } public boolean isAlive() { return isAlive; } public BlockPos getCenter() { return center; } // Setters public void setTraderUUID(UUID traderUUID) { this.traderUUID = traderUUID; } public void setMaidUUID(UUID maidUUID) { this.maidUUID = maidUUID; } public void setAlive(boolean alive) { this.isAlive = alive; } public void setCenter(BlockPos center) { this.center = center; } // Maid death/respawn public long getMaidDeathTime() { return maidDeathTime; } public void setMaidDeathTime(long time) { this.maidDeathTime = time; } public boolean isMaidDead() { return maidDeathTime >= 0; } public boolean canRespawnMaid(long currentTime) { return ( isMaidDead() && (currentTime - maidDeathTime) >= MAID_RESPAWN_DELAY ); } // Loot chest management public List getLootChestPositions() { return lootChestPositions; } public void addLootChestPosition(BlockPos pos) { if (!lootChestPositions.contains(pos)) { lootChestPositions.add(pos); } } // Kidnapper management public void addKidnapper(UUID kidnapperUUID) { linkedKidnapperUUIDs.add(kidnapperUUID); } public void removeKidnapper(UUID kidnapperUUID) { linkedKidnapperUUIDs.remove(kidnapperUUID); } public Set getLinkedKidnappers() { return Collections.unmodifiableSet(linkedKidnapperUUIDs); } public boolean hasKidnapper(UUID kidnapperUUID) { return linkedKidnapperUUIDs.contains(kidnapperUUID); } public int getKidnapperCount() { return linkedKidnapperUUIDs.size(); } // NBT Serialization public CompoundTag save() { CompoundTag tag = new CompoundTag(); tag.putUUID("campId", campId); if (traderUUID != null) tag.putUUID("traderUUID", traderUUID); if (maidUUID != null) tag.putUUID("maidUUID", maidUUID); tag.putBoolean("isAlive", isAlive); if (center != null) { tag.putInt("centerX", center.getX()); tag.putInt("centerY", center.getY()); tag.putInt("centerZ", center.getZ()); } // Save linked kidnappers if (!linkedKidnapperUUIDs.isEmpty()) { ListTag kidnapperList = new ListTag(); for (UUID uuid : linkedKidnapperUUIDs) { CompoundTag uuidTag = new CompoundTag(); uuidTag.putUUID("uuid", uuid); kidnapperList.add(uuidTag); } tag.put("linkedKidnappers", kidnapperList); } // Save maid death time tag.putLong("maidDeathTime", maidDeathTime); // Save loot chest positions if (!lootChestPositions.isEmpty()) { ListTag lootList = new ListTag(); for (BlockPos pos : lootChestPositions) { CompoundTag posTag = new CompoundTag(); posTag.putInt("x", pos.getX()); posTag.putInt("y", pos.getY()); posTag.putInt("z", pos.getZ()); lootList.add(posTag); } tag.put("lootChestPositions", lootList); } return tag; } public static CampData load(CompoundTag tag) { UUID campId = tag.getUUID("campId"); CampData data = new CampData(campId); if (tag.contains("traderUUID")) data.traderUUID = tag.getUUID( "traderUUID" ); if (tag.contains("maidUUID")) data.maidUUID = tag.getUUID( "maidUUID" ); data.isAlive = tag.getBoolean("isAlive"); if (tag.contains("centerX")) { data.center = new BlockPos( tag.getInt("centerX"), tag.getInt("centerY"), tag.getInt("centerZ") ); } // Load linked kidnappers if (tag.contains("linkedKidnappers")) { ListTag kidnapperList = tag.getList( "linkedKidnappers", Tag.TAG_COMPOUND ); for (int i = 0; i < kidnapperList.size(); i++) { CompoundTag uuidTag = kidnapperList.getCompound(i); if (uuidTag.contains("uuid")) { data.linkedKidnapperUUIDs.add(uuidTag.getUUID("uuid")); } } } // Load maid death time if (tag.contains("maidDeathTime")) { data.maidDeathTime = tag.getLong("maidDeathTime"); } // Load loot chest positions if (tag.contains("lootChestPositions")) { ListTag lootList = tag.getList( "lootChestPositions", Tag.TAG_COMPOUND ); for (int i = 0; i < lootList.size(); i++) { CompoundTag posTag = lootList.getCompound(i); data.lootChestPositions.add( new BlockPos( posTag.getInt("x"), posTag.getInt("y"), posTag.getInt("z") ) ); } } return data; } } // ==================== STATIC ACCESS ==================== /** * Get the CampOwnership registry for a server level. */ public static CampOwnership get(ServerLevel level) { return level .getDataStorage() .computeIfAbsent( CampOwnership::load, CampOwnership::new, DATA_NAME ); } /** * Get the CampOwnership from a MinecraftServer. */ public static CampOwnership get(MinecraftServer server) { ServerLevel overworld = server.overworld(); return get(overworld); } // ==================== CAMP MANAGEMENT ==================== /** * Register a new camp with its trader and maid. * * @param campId The camp/structure UUID * @param traderUUID The SlaveTrader entity UUID * @param maidUUID The Maid entity UUID (can be null) * @param center The center position of the camp */ public void registerCamp( UUID campId, UUID traderUUID, @Nullable UUID maidUUID, BlockPos center ) { CampData data = new CampData(campId, traderUUID, maidUUID, center); camps.put(campId, data); setDirty(); } /** * Check if a camp is alive (has living trader). * * @param campId The camp UUID * @return true if camp exists and is alive */ public boolean isCampAlive(UUID campId) { CampData data = camps.get(campId); return data != null && data.isAlive(); } /** * Get camp data by camp UUID. */ @Nullable public CampData getCamp(UUID campId) { return camps.get(campId); } /** * Get camp data by trader UUID. */ @Nullable public CampData getCampByTrader(UUID traderUUID) { for (CampData camp : camps.values()) { if (traderUUID.equals(camp.getTraderUUID())) { return camp; } } return null; } /** * Get camp data by maid UUID. */ @Nullable public CampData getCampByMaid(UUID maidUUID) { for (CampData camp : camps.values()) { if (maidUUID.equals(camp.getMaidUUID())) { return camp; } } return null; } /** * Find camps near a position. * * @param center The center position * @param radius The search radius * @return List of camps within radius */ public List findCampsNear(BlockPos center, double radius) { List nearby = new ArrayList<>(); double radiusSq = radius * radius; for (CampData camp : camps.values()) { if ( camp.getCenter() != null && camp.getCenter().distSqr(center) <= radiusSq ) { nearby.add(camp); } } return nearby; } /** * Find the nearest alive camp to a position. * * @param pos The position to search from * @param radius Maximum search radius * @return The nearest alive camp, or null */ @Nullable public CampData findNearestAliveCamp(BlockPos pos, double radius) { CampData nearest = null; double nearestDistSq = radius * radius; for (CampData camp : camps.values()) { if (!camp.isAlive() || camp.getCenter() == null) continue; double distSq = camp.getCenter().distSqr(pos); if (distSq < nearestDistSq) { nearestDistSq = distSq; nearest = camp; } } return nearest; } /** * Remove a camp from the registry. */ @Nullable public CampData removeCamp(UUID campId) { CampData removed = camps.remove(campId); if (removed != null) { setDirty(); } return removed; } /** * Get all registered camps. */ public Collection getAllCamps() { return Collections.unmodifiableCollection(camps.values()); } /** * Get all alive camps. */ public List getAliveCamps() { List alive = new ArrayList<>(); for (CampData camp : camps.values()) { if (camp.isAlive()) { alive.add(camp); } } return alive; } /** * Link a kidnapper to a camp. * * @param campId The camp UUID * @param kidnapperUUID The kidnapper UUID */ public void linkKidnapperToCamp(UUID campId, UUID kidnapperUUID) { CampData data = camps.get(campId); if (data != null) { data.addKidnapper(kidnapperUUID); setDirty(); } } /** * Unlink a kidnapper from a camp. * * @param campId The camp UUID * @param kidnapperUUID The kidnapper UUID */ public void unlinkKidnapperFromCamp(UUID campId, UUID kidnapperUUID) { CampData data = camps.get(campId); if (data != null) { data.removeKidnapper(kidnapperUUID); setDirty(); } } /** * Check if a kidnapper is linked to a camp. * * @param campId The camp UUID * @param kidnapperUUID The kidnapper UUID * @return true if the kidnapper is linked to this camp */ public boolean isKidnapperLinked(UUID campId, UUID kidnapperUUID) { CampData data = camps.get(campId); return data != null && data.hasKidnapper(kidnapperUUID); } /** * Find the camp a kidnapper is linked to. * * @param kidnapperUUID The kidnapper UUID * @return The camp data, or null if not linked */ @Nullable public CampData findCampByKidnapper(UUID kidnapperUUID) { for (CampData camp : camps.values()) { if (camp.hasKidnapper(kidnapperUUID)) { return camp; } } return null; } /** * Mark a prisoner as processed (to avoid re-processing). * * @param prisonerId The prisoner's UUID */ public void markPrisonerProcessed(UUID prisonerId) { processedPrisoners.add(prisonerId); setDirty(); } /** * Check if a prisoner has been processed. * * @param prisonerId The prisoner's UUID * @return true if already processed */ public boolean isPrisonerProcessed(UUID prisonerId) { return processedPrisoners.contains(prisonerId); } /** * Remove a prisoner from processed set. * * @param prisonerId The prisoner's UUID */ public void unmarkPrisonerProcessed(UUID prisonerId) { if (processedPrisoners.remove(prisonerId)) { setDirty(); } } /** * Get the set of processed prisoners for a camp (unmodifiable). * * @return Unmodifiable set of processed prisoner UUIDs */ public Set getProcessedPrisoners() { return Collections.unmodifiableSet(processedPrisoners); } // ==================== PERSISTENCE ==================== @Override public @NotNull CompoundTag save(@NotNull CompoundTag tag) { // Save camps ListTag campList = new ListTag(); for (CampData camp : camps.values()) { campList.add(camp.save()); } tag.put("camps", campList); // Save processed prisoners ListTag processedList = new ListTag(); for (UUID uuid : processedPrisoners) { CompoundTag uuidTag = new CompoundTag(); uuidTag.putUUID("uuid", uuid); processedList.add(uuidTag); } tag.put("processedPrisoners", processedList); return tag; } public static CampOwnership load(CompoundTag tag) { CampOwnership registry = new CampOwnership(); // Load camps if (tag.contains("camps")) { ListTag campList = tag.getList("camps", Tag.TAG_COMPOUND); for (int i = 0; i < campList.size(); i++) { CampData camp = CampData.load(campList.getCompound(i)); registry.camps.put(camp.getCampId(), camp); } } // Load processed prisoners if (tag.contains("processedPrisoners")) { ListTag processedList = tag.getList( "processedPrisoners", Tag.TAG_COMPOUND ); for (int i = 0; i < processedList.size(); i++) { CompoundTag uuidTag = processedList.getCompound(i); if (uuidTag.contains("uuid")) { registry.processedPrisoners.add(uuidTag.getUUID("uuid")); } } } return registry; } }