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 interiorBlocks; private final Set wallBlocks; private final Set breachedPositions; private int totalWallCount; // Interior face direction (which face of Core points inside) @Nullable private Direction interiorFace; // Auto-detected features private final List beds; private final List petBeds; private final List anchors; private final List doors; private final List linkedRedstone; // Prisoners private final List prisonerIds = new CopyOnWriteArrayList<>(); private final Map prisonerTimestamps = new ConcurrentHashMap<>(); // Camp navigation private final List 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 getInteriorBlocks() { return Collections.unmodifiableSet(interiorBlocks); } public Set getWallBlocks() { return Collections.unmodifiableSet(wallBlocks); } public Set 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 getBeds() { return Collections.unmodifiableList(beds); } public List getPetBeds() { return Collections.unmodifiableList(petBeds); } public List getAnchors() { return Collections.unmodifiableList(anchors); } public List getDoors() { return Collections.unmodifiableList(doors); } public List getLinkedRedstone() { return Collections.unmodifiableList(linkedRedstone); } // ==================== PRISONER MANAGEMENT ==================== public List 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 getPathWaypoints() { return Collections.unmodifiableList(pathWaypoints); } public void setPathWaypoints(List 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 positions) { ListTag list = new ListTag(); for (BlockPos pos : positions) { list.add(NbtUtils.writeBlockPos(pos)); } return list; } private static ListTag saveBlockPosList(List 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 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 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() + "}" ); } }