Strip all Phase references, TODO/FUTURE roadmap notes, and internal planning comments from the codebase. Run Prettier for consistent formatting across all Java files.
576 lines
17 KiB
Java
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;
|
|
}
|
|
}
|