Files
TiedUp-/src/main/java/com/tiedup/remake/cells/CellDataV2.java
NotEvil f6466360b6 Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies,
and third-party source dumps. Add proper README, update .gitignore,
clean up Makefile.
2026-04-12 00:51:22 +02:00

608 lines
18 KiB
Java

package com.tiedup.remake.cells;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import org.jetbrains.annotations.Nullable;
/**
* Cell data for Cell System V2.
*
* Named CellDataV2 to coexist with v1 CellData during the migration period.
* Contains geometry (interior + walls from flood-fill), breach tracking,
* prisoner management, and auto-detected features.
*/
public class CellDataV2 {
private static final int MAX_PRISONERS = 4;
// Identity
private final UUID id;
private CellState state;
private final BlockPos corePos;
// Cached from Core BE (for use when chunk is unloaded)
@Nullable
private BlockPos spawnPoint;
@Nullable
private BlockPos deliveryPoint;
// Ownership
@Nullable
private UUID ownerId;
private CellOwnerType ownerType = CellOwnerType.PLAYER;
@Nullable
private String name;
// Geometry (from flood-fill)
private final Set<BlockPos> interiorBlocks;
private final Set<BlockPos> wallBlocks;
private final Set<BlockPos> breachedPositions;
private int totalWallCount;
// Interior face direction (which face of Core points inside)
@Nullable
private Direction interiorFace;
// Auto-detected features
private final List<BlockPos> beds;
private final List<BlockPos> petBeds;
private final List<BlockPos> anchors;
private final List<BlockPos> doors;
private final List<BlockPos> linkedRedstone;
// Prisoners
private final List<UUID> prisonerIds = new CopyOnWriteArrayList<>();
private final Map<UUID, Long> prisonerTimestamps =
new ConcurrentHashMap<>();
// Camp navigation
private final List<BlockPos> pathWaypoints = new CopyOnWriteArrayList<>();
// ==================== CONSTRUCTORS ====================
/**
* Create from a successful flood-fill result.
*/
public CellDataV2(BlockPos corePos, FloodFillResult result) {
this.id = UUID.randomUUID();
this.state = CellState.INTACT;
this.corePos = corePos.immutable();
this.interiorFace = result.getInteriorFace();
this.interiorBlocks = ConcurrentHashMap.newKeySet();
this.interiorBlocks.addAll(result.getInterior());
this.wallBlocks = ConcurrentHashMap.newKeySet();
this.wallBlocks.addAll(result.getWalls());
this.breachedPositions = ConcurrentHashMap.newKeySet();
this.totalWallCount = result.getWalls().size();
this.beds = new CopyOnWriteArrayList<>(result.getBeds());
this.petBeds = new CopyOnWriteArrayList<>(result.getPetBeds());
this.anchors = new CopyOnWriteArrayList<>(result.getAnchors());
this.doors = new CopyOnWriteArrayList<>(result.getDoors());
this.linkedRedstone = new CopyOnWriteArrayList<>(
result.getLinkedRedstone()
);
}
/**
* Create for NBT loading (minimal constructor).
*/
public CellDataV2(UUID id, BlockPos corePos) {
this.id = id;
this.state = CellState.INTACT;
this.corePos = corePos.immutable();
this.interiorBlocks = ConcurrentHashMap.newKeySet();
this.wallBlocks = ConcurrentHashMap.newKeySet();
this.breachedPositions = ConcurrentHashMap.newKeySet();
this.totalWallCount = 0;
this.beds = new CopyOnWriteArrayList<>();
this.petBeds = new CopyOnWriteArrayList<>();
this.anchors = new CopyOnWriteArrayList<>();
this.doors = new CopyOnWriteArrayList<>();
this.linkedRedstone = new CopyOnWriteArrayList<>();
}
// ==================== IDENTITY ====================
public UUID getId() {
return id;
}
public CellState getState() {
return state;
}
public void setState(CellState state) {
this.state = state;
}
public BlockPos getCorePos() {
return corePos;
}
@Nullable
public BlockPos getSpawnPoint() {
return spawnPoint;
}
public void setSpawnPoint(@Nullable BlockPos spawnPoint) {
this.spawnPoint = spawnPoint != null ? spawnPoint.immutable() : null;
}
@Nullable
public BlockPos getDeliveryPoint() {
return deliveryPoint;
}
public void setDeliveryPoint(@Nullable BlockPos deliveryPoint) {
this.deliveryPoint =
deliveryPoint != null ? deliveryPoint.immutable() : null;
}
@Nullable
public Direction getInteriorFace() {
return interiorFace;
}
// ==================== OWNERSHIP ====================
@Nullable
public UUID getOwnerId() {
return ownerId;
}
public void setOwnerId(@Nullable UUID ownerId) {
this.ownerId = ownerId;
}
public CellOwnerType getOwnerType() {
return ownerType;
}
public void setOwnerType(CellOwnerType ownerType) {
this.ownerType = ownerType;
}
@Nullable
public String getName() {
return name;
}
public void setName(@Nullable String name) {
this.name = name;
}
public boolean isOwnedBy(UUID playerId) {
return playerId != null && playerId.equals(ownerId);
}
/**
* Check if a player can manage this cell (open menu, rename, modify settings).
* - OPs (level 2+) can always manage any cell (including camp-owned).
* - Player-owned cells: only the owning player.
* - Camp-owned cells: OPs only.
*
* @param playerId UUID of the player
* @param hasOpPerms true if the player has OP level 2+
* @return true if the player is allowed to manage this cell
*/
public boolean canPlayerManage(UUID playerId, boolean hasOpPerms) {
if (hasOpPerms) return true;
if (isCampOwned()) return false;
return isOwnedBy(playerId);
}
public boolean hasOwner() {
return ownerId != null;
}
public boolean isCampOwned() {
return ownerType == CellOwnerType.CAMP;
}
public boolean isPlayerOwned() {
return ownerType == CellOwnerType.PLAYER;
}
@Nullable
public UUID getCampId() {
return isCampOwned() ? ownerId : null;
}
// ==================== GEOMETRY ====================
public Set<BlockPos> getInteriorBlocks() {
return Collections.unmodifiableSet(interiorBlocks);
}
public Set<BlockPos> getWallBlocks() {
return Collections.unmodifiableSet(wallBlocks);
}
public Set<BlockPos> getBreachedPositions() {
return Collections.unmodifiableSet(breachedPositions);
}
public int getTotalWallCount() {
return totalWallCount;
}
public boolean isContainedInCell(BlockPos pos) {
return interiorBlocks.contains(pos);
}
public boolean isWallBlock(BlockPos pos) {
return wallBlocks.contains(pos);
}
// ==================== BREACH MANAGEMENT ====================
public void addBreach(BlockPos wallPos) {
if (wallBlocks.remove(wallPos)) {
breachedPositions.add(wallPos.immutable());
}
}
public void repairBreach(BlockPos wallPos) {
if (breachedPositions.remove(wallPos)) {
wallBlocks.add(wallPos.immutable());
}
}
public float getBreachPercentage() {
if (totalWallCount == 0) return 0.0f;
return (float) breachedPositions.size() / totalWallCount;
}
// ==================== FEATURES ====================
public List<BlockPos> getBeds() {
return Collections.unmodifiableList(beds);
}
public List<BlockPos> getPetBeds() {
return Collections.unmodifiableList(petBeds);
}
public List<BlockPos> getAnchors() {
return Collections.unmodifiableList(anchors);
}
public List<BlockPos> getDoors() {
return Collections.unmodifiableList(doors);
}
public List<BlockPos> getLinkedRedstone() {
return Collections.unmodifiableList(linkedRedstone);
}
// ==================== PRISONER MANAGEMENT ====================
public List<UUID> getPrisonerIds() {
return Collections.unmodifiableList(prisonerIds);
}
public boolean hasPrisoner(UUID prisonerId) {
return prisonerIds.contains(prisonerId);
}
public boolean isFull() {
return prisonerIds.size() >= MAX_PRISONERS;
}
public boolean isOccupied() {
return !prisonerIds.isEmpty();
}
public int getPrisonerCount() {
return prisonerIds.size();
}
public boolean addPrisoner(UUID prisonerId) {
if (isFull() || prisonerIds.contains(prisonerId)) {
return false;
}
prisonerIds.add(prisonerId);
prisonerTimestamps.put(prisonerId, System.currentTimeMillis());
return true;
}
public boolean removePrisoner(UUID prisonerId) {
boolean removed = prisonerIds.remove(prisonerId);
if (removed) {
prisonerTimestamps.remove(prisonerId);
}
return removed;
}
@Nullable
public Long getPrisonerTimestamp(UUID prisonerId) {
return prisonerTimestamps.get(prisonerId);
}
// ==================== CAMP NAVIGATION ====================
public List<BlockPos> getPathWaypoints() {
return Collections.unmodifiableList(pathWaypoints);
}
public void setPathWaypoints(List<BlockPos> waypoints) {
pathWaypoints.clear();
pathWaypoints.addAll(waypoints);
}
// ==================== WIRE RECONSTRUCTION (client-side) ====================
/** Add a wall block position (used for client-side packet reconstruction). */
public void addWallBlock(BlockPos pos) {
wallBlocks.add(pos.immutable());
}
/** Add a bed position (used for client-side packet reconstruction). */
public void addBed(BlockPos pos) {
beds.add(pos.immutable());
}
/** Add an anchor position (used for client-side packet reconstruction). */
public void addAnchor(BlockPos pos) {
anchors.add(pos.immutable());
}
/** Add a door position (used for client-side packet reconstruction). */
public void addDoor(BlockPos pos) {
doors.add(pos.immutable());
}
// ==================== GEOMETRY UPDATE (rescan) ====================
/**
* Replace geometry with a new flood-fill result (used during rescan).
*/
public void updateGeometry(FloodFillResult result) {
interiorBlocks.clear();
interiorBlocks.addAll(result.getInterior());
wallBlocks.clear();
wallBlocks.addAll(result.getWalls());
breachedPositions.clear();
totalWallCount = result.getWalls().size();
if (result.getInteriorFace() != null) {
this.interiorFace = result.getInteriorFace();
}
beds.clear();
beds.addAll(result.getBeds());
petBeds.clear();
petBeds.addAll(result.getPetBeds());
anchors.clear();
anchors.addAll(result.getAnchors());
doors.clear();
doors.addAll(result.getDoors());
linkedRedstone.clear();
linkedRedstone.addAll(result.getLinkedRedstone());
state = CellState.INTACT;
}
// ==================== NBT PERSISTENCE ====================
public CompoundTag save() {
CompoundTag tag = new CompoundTag();
tag.putUUID("id", id);
tag.putString("state", state.getSerializedName());
tag.put("corePos", NbtUtils.writeBlockPos(corePos));
if (spawnPoint != null) {
tag.put("spawnPoint", NbtUtils.writeBlockPos(spawnPoint));
}
if (deliveryPoint != null) {
tag.put("deliveryPoint", NbtUtils.writeBlockPos(deliveryPoint));
}
if (interiorFace != null) {
tag.putString("interiorFace", interiorFace.getSerializedName());
}
// Ownership
if (ownerId != null) {
tag.putUUID("ownerId", ownerId);
}
tag.putString("ownerType", ownerType.getSerializedName());
if (name != null) {
tag.putString("name", name);
}
// Geometry
tag.put("interior", saveBlockPosSet(interiorBlocks));
tag.put("walls", saveBlockPosSet(wallBlocks));
tag.put("breached", saveBlockPosSet(breachedPositions));
tag.putInt("totalWallCount", totalWallCount);
// Features
tag.put("beds", saveBlockPosList(beds));
tag.put("petBeds", saveBlockPosList(petBeds));
tag.put("anchors", saveBlockPosList(anchors));
tag.put("doors", saveBlockPosList(doors));
tag.put("linkedRedstone", saveBlockPosList(linkedRedstone));
// Prisoners
if (!prisonerIds.isEmpty()) {
ListTag prisonerList = new ListTag();
for (UUID uuid : prisonerIds) {
CompoundTag prisonerTag = new CompoundTag();
prisonerTag.putUUID("id", uuid);
prisonerTag.putLong(
"timestamp",
prisonerTimestamps.getOrDefault(
uuid,
System.currentTimeMillis()
)
);
prisonerList.add(prisonerTag);
}
tag.put("prisoners", prisonerList);
}
// Path waypoints
if (!pathWaypoints.isEmpty()) {
tag.put("pathWaypoints", saveBlockPosList(pathWaypoints));
}
return tag;
}
@Nullable
public static CellDataV2 load(CompoundTag tag) {
if (!tag.contains("id") || !tag.contains("corePos")) {
return null;
}
UUID id = tag.getUUID("id");
BlockPos corePos = NbtUtils.readBlockPos(tag.getCompound("corePos"));
CellDataV2 cell = new CellDataV2(id, corePos);
cell.state = CellState.fromString(tag.getString("state"));
if (tag.contains("spawnPoint")) {
cell.spawnPoint = NbtUtils.readBlockPos(
tag.getCompound("spawnPoint")
);
}
if (tag.contains("deliveryPoint")) {
cell.deliveryPoint = NbtUtils.readBlockPos(
tag.getCompound("deliveryPoint")
);
}
if (tag.contains("interiorFace")) {
cell.interiorFace = Direction.byName(tag.getString("interiorFace"));
}
// Ownership
if (tag.contains("ownerId")) {
cell.ownerId = tag.getUUID("ownerId");
}
if (tag.contains("ownerType")) {
cell.ownerType = CellOwnerType.fromString(
tag.getString("ownerType")
);
}
if (tag.contains("name")) {
cell.name = tag.getString("name");
}
// Geometry
loadBlockPosSet(tag, "interior", cell.interiorBlocks);
loadBlockPosSet(tag, "walls", cell.wallBlocks);
loadBlockPosSet(tag, "breached", cell.breachedPositions);
cell.totalWallCount = tag.getInt("totalWallCount");
// Features
loadBlockPosList(tag, "beds", cell.beds);
loadBlockPosList(tag, "petBeds", cell.petBeds);
loadBlockPosList(tag, "anchors", cell.anchors);
loadBlockPosList(tag, "doors", cell.doors);
loadBlockPosList(tag, "linkedRedstone", cell.linkedRedstone);
// Prisoners
if (tag.contains("prisoners")) {
ListTag prisonerList = tag.getList("prisoners", Tag.TAG_COMPOUND);
for (int i = 0; i < prisonerList.size(); i++) {
CompoundTag prisonerTag = prisonerList.getCompound(i);
UUID prisonerId = prisonerTag.getUUID("id");
cell.prisonerIds.add(prisonerId);
long timestamp = prisonerTag.contains("timestamp")
? prisonerTag.getLong("timestamp")
: System.currentTimeMillis();
cell.prisonerTimestamps.put(prisonerId, timestamp);
}
}
// Path waypoints
loadBlockPosList(tag, "pathWaypoints", cell.pathWaypoints);
return cell;
}
// ==================== NBT HELPERS ====================
private static ListTag saveBlockPosSet(Set<BlockPos> positions) {
ListTag list = new ListTag();
for (BlockPos pos : positions) {
list.add(NbtUtils.writeBlockPos(pos));
}
return list;
}
private static ListTag saveBlockPosList(List<BlockPos> positions) {
ListTag list = new ListTag();
for (BlockPos pos : positions) {
list.add(NbtUtils.writeBlockPos(pos));
}
return list;
}
private static void loadBlockPosSet(
CompoundTag parent,
String key,
Set<BlockPos> target
) {
if (parent.contains(key)) {
ListTag list = parent.getList(key, Tag.TAG_COMPOUND);
for (int i = 0; i < list.size(); i++) {
target.add(NbtUtils.readBlockPos(list.getCompound(i)));
}
}
}
private static void loadBlockPosList(
CompoundTag parent,
String key,
List<BlockPos> target
) {
if (parent.contains(key)) {
ListTag list = parent.getList(key, Tag.TAG_COMPOUND);
for (int i = 0; i < list.size(); i++) {
target.add(NbtUtils.readBlockPos(list.getCompound(i)));
}
}
}
// ==================== DEBUG ====================
@Override
public String toString() {
return (
"CellDataV2{id=" +
id.toString().substring(0, 8) +
"..., state=" +
state +
", core=" +
corePos.toShortString() +
", interior=" +
interiorBlocks.size() +
", walls=" +
wallBlocks.size() +
", prisoners=" +
prisonerIds.size() +
"}"
);
}
}