Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
608 lines
18 KiB
Java
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() +
|
|
"}"
|
|
);
|
|
}
|
|
}
|