Files
TiedUp-/src/main/java/com/tiedup/remake/cells/CampOwnership.java
NotEvil a71093ba9c Remove internal phase comments and format code
Strip all Phase references, TODO/FUTURE roadmap notes, and internal
planning comments from the codebase. Run Prettier for consistent
formatting across all Java files.
2026-04-12 01:25:55 +02:00

576 lines
17 KiB
Java

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<UUID, CampData> camps = new ConcurrentHashMap<>();
// Prisoners that have been processed (to avoid re-processing)
private final Set<UUID> 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<UUID> 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<BlockPos> 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<BlockPos> 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<UUID> 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<CampData> findCampsNear(BlockPos center, double radius) {
List<CampData> 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<CampData> getAllCamps() {
return Collections.unmodifiableCollection(camps.values());
}
/**
* Get all alive camps.
*/
public List<CampData> getAliveCamps() {
List<CampData> 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<UUID> 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;
}
}