package com.xfestudio.mydimension.network.builder;
import com.xfestudio.mydimension.builder.BuilderMode;
import com.xfestudio.mydimension.builder.BuilderRuntime;
import com.xfestudio.mydimension.builder.PendingBuildData;
import com.xfestudio.mydimension.builder.RealmwrightData;
import com.xfestudio.mydimension.builder.SurfaceMatchMode;
import com.xfestudio.mydimension.builder.anchor.AnchorBindings;
import com.xfestudio.mydimension.builder.anchor.AnchorIndexSavedData;
import com.xfestudio.mydimension.builder.anchor.ResonantSupplyAnchorBlockEntity;
import com.xfestudio.mydimension.builder.history.BuilderHistoryData;
import com.xfestudio.mydimension.builder.history.BuilderTransaction;
import com.xfestudio.mydimension.builder.blueprint.BlueprintTaskManager;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.Supplier;
/** Server-owned state consumed by the five-tab Realmwright screen and HUD. */
public record BuilderSnapshotPacket(boolean enabled, BuilderMode mode, SurfaceMatchMode surfaceMatch,
boolean historyRecording, boolean allowReplacement,
int buildLimit, int demolishLimit,
int maximumBuildLimit, int maximumDemolishLimit,
int reach, String status, @Nullable UUID activeJobId,
int completedBlocks, int totalBlocks,
boolean canUndo, boolean canRedo,
List<Anchor> anchors, List<History> history) {
private static final int MAX_ANCHORS = 256;
private static final int MAX_HISTORY = 1_000;
public BuilderSnapshotPacket {
status = status == null ? "" : status;
anchors = List.copyOf(anchors);
history = List.copyOf(history);
}
public static BuilderSnapshotPacket from(ServerPlayer player, ItemStack scepter) {
BuilderRuntime.Settings settings = BuilderRuntime.settings();
UUID scepterId = RealmwrightData.id(scepter);
PendingBuildData.Task pending = PendingBuildData.get(player.getServer()).get(player.getUUID());
if (pending != null && !pending.scepterId().equals(scepterId)) pending = null;
BlueprintTaskManager.Status running = BlueprintTaskManager.get(player.getServer()).status(player);
BuilderHistoryData historyData = BuilderHistoryData.get(player.getServer());
BuilderTransaction undo = historyData.peekUndo(player.getUUID(), scepterId);
BuilderTransaction redo = historyData.peekRedo(player.getUUID(), scepterId);
List<History> history = historyData.recent(player.getUUID(), scepterId,
Math.min(MAX_HISTORY, settings.undoDepth()))
.stream().map(History::from).toList();
int historyCompleted = pending != null && undo != null && undo.id().equals(pending.transactionId())
? undo.worldDeltas().size() : 0;
PendingBuildData.Progress pendingProgress = pending == null
? new PendingBuildData.Progress(0, 0)
: PendingBuildData.progress(pending.completed(), pending.total(),
pending.missing().size(), historyCompleted);
int completed = running.transactionId() != null ? running.completed()
: pendingProgress.completed();
int missing = running.transactionId() != null ? running.missing()
: pending == null ? 0 : pending.missing().size();
int total = running.transactionId() != null ? running.total()
: pendingProgress.total();
String status = running.transactionId() != null ? "Building blueprint"
: pending == null ? "" : "Waiting for " + missing + " block(s)";
return new BuilderSnapshotPacket(
settings.enabled(),
RealmwrightData.mode(scepter),
RealmwrightData.matchMode(scepter),
RealmwrightData.recordsHistory(scepter),
RealmwrightData.allowsReplacement(scepter),
RealmwrightData.buildLimit(scepter, settings.maxBuildLimit()),
RealmwrightData.demolishLimit(scepter, settings.maxDemolishLimit()),
settings.maxBuildLimit(),
settings.maxDemolishLimit(),
Math.max(1, (int) Math.ceil(settings.blockReach())),
status,
running.transactionId() != null ? running.transactionId()
: pending == null ? null : pending.transactionId(),
completed,
total,
undo != null && undo.state() == BuilderTransaction.State.APPLIED,
redo != null && redo.state() == BuilderTransaction.State.UNDONE,
anchors(player, scepter),
history
);
}
private static List<Anchor> anchors(ServerPlayer player, ItemStack scepter) {
AnchorIndexSavedData index = AnchorIndexSavedData.get(player.getServer());
boolean bindingsChanged = AnchorBindings.pruneMissing(scepter, index);
List<UUID> ids = AnchorBindings.read(scepter);
List<Anchor> result = new ArrayList<>(Math.min(ids.size(), MAX_ANCHORS));
for (int i = 0; i < ids.size() && i < MAX_ANCHORS; i++) {
UUID id = ids.get(i);
AnchorIndexSavedData.AnchorLocation location = index.find(id).orElse(null);
if (location == null) {
bindingsChanged = true;
continue;
}
AnchorStatus status = AnchorStatus.UNLOADED;
boolean owner = false;
boolean publicAccess = false;
ServerLevel level = player.getServer().getLevel(location.dimension());
if (level == null) {
index.unregister(id, location);
bindingsChanged = true;
continue;
} else if (level.hasChunkAt(location.position())) {
if (level.getBlockEntity(location.position()) instanceof ResonantSupplyAnchorBlockEntity anchor
&& anchor.anchorId().equals(id)) {
owner = anchor.ownerId().map(player.getUUID()::equals).orElse(false);
publicAccess = anchor.publicAccess();
status = anchor.canUse(player) ? AnchorStatus.AVAILABLE : AnchorStatus.FORBIDDEN;
} else {
// Heal an index entry whose block was removed by a path that did not leave a
// live matching block entity. This also prevents later snapshots from
// continuing to advertise the obsolete world position.
index.unregister(id, location);
bindingsChanged = true;
continue;
}
}
result.add(new Anchor(id, "Anchor " + (result.size() + 1), location.dimension().location(),
location.position().asLong(), status, owner, publicAccess));
}
if (bindingsChanged) {
// Also removes entries whose index was invalidated while resolving loaded chunks.
AnchorBindings.pruneMissing(scepter, index);
player.getInventory().setChanged();
player.containerMenu.broadcastChanges();
if (player.inventoryMenu != player.containerMenu) {
player.inventoryMenu.broadcastChanges();
}
}
return result;
}
public static void encode(BuilderSnapshotPacket packet, FriendlyByteBuf buffer) {
buffer.writeBoolean(packet.enabled);
buffer.writeEnum(packet.mode);
buffer.writeEnum(packet.surfaceMatch);
buffer.writeBoolean(packet.historyRecording);
buffer.writeBoolean(packet.allowReplacement);
buffer.writeVarInt(packet.buildLimit);
buffer.writeVarInt(packet.demolishLimit);
buffer.writeVarInt(packet.maximumBuildLimit);
buffer.writeVarInt(packet.maximumDemolishLimit);
buffer.writeVarInt(packet.reach);
buffer.writeUtf(packet.status, 256);
writeOptionalUuid(buffer, packet.activeJobId);
buffer.writeVarInt(packet.completedBlocks);
buffer.writeVarInt(packet.totalBlocks);
buffer.writeBoolean(packet.canUndo);
buffer.writeBoolean(packet.canRedo);
buffer.writeVarInt(packet.anchors.size());
for (Anchor anchor : packet.anchors) {
buffer.writeUUID(anchor.id);
buffer.writeUtf(anchor.name, 128);
buffer.writeResourceLocation(anchor.dimension);
buffer.writeLong(anchor.packedPos);
buffer.writeEnum(anchor.status);
buffer.writeBoolean(anchor.owner);
buffer.writeBoolean(anchor.publicAccess);
}
buffer.writeVarInt(packet.history.size());
for (History entry : packet.history) {
buffer.writeUUID(entry.id);
buffer.writeUtf(entry.label, 128);
buffer.writeUtf(entry.dimension, 128);
buffer.writeVarInt(entry.changedBlocks);
buffer.writeLong(entry.createdAt);
buffer.writeEnum(entry.status);
}
}
public static BuilderSnapshotPacket decode(FriendlyByteBuf buffer) {
boolean enabled = buffer.readBoolean();
BuilderMode mode = buffer.readEnum(BuilderMode.class);
SurfaceMatchMode match = buffer.readEnum(SurfaceMatchMode.class);
boolean historyRecording = buffer.readBoolean();
boolean allowReplacement = buffer.readBoolean();
int buildLimit = buffer.readVarInt();
int demolishLimit = buffer.readVarInt();
int maximumBuildLimit = buffer.readVarInt();
int maximumDemolishLimit = buffer.readVarInt();
int reach = buffer.readVarInt();
String status = buffer.readUtf(256);
UUID activeJobId = readOptionalUuid(buffer);
int completedBlocks = buffer.readVarInt();
int totalBlocks = buffer.readVarInt();
boolean canUndo = buffer.readBoolean();
boolean canRedo = buffer.readBoolean();
int anchorCount = boundedCount(buffer.readVarInt(), MAX_ANCHORS, "builder anchors");
List<Anchor> anchors = new ArrayList<>(anchorCount);
for (int i = 0; i < anchorCount; i++) {
anchors.add(new Anchor(buffer.readUUID(), buffer.readUtf(128), buffer.readResourceLocation(),
buffer.readLong(), buffer.readEnum(AnchorStatus.class), buffer.readBoolean(),
buffer.readBoolean()));
}
int historyCount = boundedCount(buffer.readVarInt(), MAX_HISTORY, "builder history");
List<History> history = new ArrayList<>(historyCount);
for (int i = 0; i < historyCount; i++) {
history.add(new History(buffer.readUUID(), buffer.readUtf(128), buffer.readUtf(128),
buffer.readVarInt(), buffer.readLong(), buffer.readEnum(HistoryStatus.class)));
}
return new BuilderSnapshotPacket(enabled, mode, match, historyRecording, allowReplacement,
buildLimit, demolishLimit,
maximumBuildLimit, maximumDemolishLimit, reach, status, activeJobId,
completedBlocks, totalBlocks, canUndo, canRedo, anchors, history);
}
public static void handle(BuilderSnapshotPacket packet,
Supplier<NetworkEvent.Context> contextSupplier) {
NetworkEvent.Context context = contextSupplier.get();
context.enqueueWork(() -> BuilderClientPacketHooks.receiver().snapshot(packet));
context.setPacketHandled(true);
}
private static int boundedCount(int value, int maximum, String label) {
if (value < 0 || value > maximum) {
throw new IllegalArgumentException("Invalid " + label + " count: " + value);
}
return value;
}
private static void writeOptionalUuid(FriendlyByteBuf buffer, @Nullable UUID value) {
buffer.writeBoolean(value != null);
if (value != null) buffer.writeUUID(value);
}
@Nullable
private static UUID readOptionalUuid(FriendlyByteBuf buffer) {
return buffer.readBoolean() ? buffer.readUUID() : null;
}
public record Anchor(UUID id, String name, ResourceLocation dimension, long packedPos,
AnchorStatus status, boolean owner, boolean publicAccess) {
}
public enum AnchorStatus {
AVAILABLE,
UNLOADED,
DISCONNECTED,
FORBIDDEN,
UNKNOWN
}
public record History(UUID id, String label, String dimension, int changedBlocks,
long createdAt, HistoryStatus status) {
private static History from(BuilderTransaction transaction) {
HistoryStatus status = switch (transaction.state()) {
case PREPARED -> HistoryStatus.INCOMPLETE;
case APPLIED -> HistoryStatus.COMPLETE;
case UNDONE -> HistoryStatus.UNDONE;
case CONFLICTED -> HistoryStatus.CONFLICTED;
};
return new History(transaction.id(), transaction.type().name(),
transaction.dimension().location().toString(), transaction.worldDeltas().size(),
transaction.createdAt(), status);
}
}
public enum HistoryStatus {
RUNNING,
INCOMPLETE,
COMPLETE,
UNDONE,
CONFLICTED
}
}
package com.xfestudio.mydimension.network.builder;
import com.xfestudio.mydimension.builder.BuilderMode;
import com.xfestudio.mydimension.builder.BuilderRuntime;
import com.xfestudio.mydimension.builder.PendingBuildData;
import com.xfestudio.mydimension.builder.RealmwrightData;
import com.xfestudio.mydimension.builder.SurfaceMatchMode;
import com.xfestudio.mydimension.builder.anchor.AnchorBindings;
import com.xfestudio.mydimension.builder.anchor.AnchorIndexSavedData;
import com.xfestudio.mydimension.builder.anchor.ResonantSupplyAnchorBlockEntity;
import com.xfestudio.mydimension.builder.history.BuilderHistoryData;
import com.xfestudio.mydimension.builder.history.BuilderTransaction;
import com.xfestudio.mydimension.builder.blueprint.BlueprintTaskManager;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.network.NetworkEvent;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.Supplier;
/** Server-owned state consumed by the five-tab Realmwright screen and HUD. */
public record BuilderSnapshotPacket(boolean enabled, BuilderMode mode, SurfaceMatchMode surfaceMatch,
boolean historyRecording, boolean allowReplacement,
int buildLimit, int demolishLimit,
int maximumBuildLimit, int maximumDemolishLimit,
int reach, String status, @Nullable UUID activeJobId,
int completedBlocks, int totalBlocks,
boolean canUndo, boolean canRedo,
List<Anchor> anchors, List<History> history) {
private static final int MAX_ANCHORS = 256;
private static final int MAX_HISTORY = 1_000;
public BuilderSnapshotPacket {
status = status == null ? "" : status;
anchors = List.copyOf(anchors);
history = List.copyOf(history);
}
public static BuilderSnapshotPacket from(ServerPlayer player, ItemStack scepter) {
BuilderRuntime.Settings settings = BuilderRuntime.settings();
UUID scepterId = RealmwrightData.id(scepter);
PendingBuildData.Task pending = PendingBuildData.get(player.getServer()).get(player.getUUID());
if (pending != null && !pending.scepterId().equals(scepterId)) pending = null;
BlueprintTaskManager.Status running = BlueprintTaskManager.get(player.getServer()).status(player);
BuilderHistoryData historyData = BuilderHistoryData.get(player.getServer());
BuilderTransaction undo = historyData.peekUndo(player.getUUID(), scepterId);
BuilderTransaction redo = historyData.peekRedo(player.getUUID(), scepterId);
List<History> history = historyData.recent(player.getUUID(), scepterId,
Math.min(MAX_HISTORY, settings.undoDepth()))
.stream().map(History::from).toList();
int historyCompleted = pending != null && undo != null && undo.id().equals(pending.transactionId())
? undo.worldDeltas().size() : 0;
PendingBuildData.Progress pendingProgress = pending == null
? new PendingBuildData.Progress(0, 0)
: PendingBuildData.progress(pending.completed(), pending.total(),
pending.missing().size(), historyCompleted);
int completed = running.transactionId() != null ? running.completed()
: pendingProgress.completed();
int missing = running.transactionId() != null ? running.missing()
: pending == null ? 0 : pending.missing().size();
int total = running.transactionId() != null ? running.total()
: pendingProgress.total();
String status = running.transactionId() != null ? "Building blueprint"
: pending == null ? "" : "Waiting for " + missing + " block(s)";
return new BuilderSnapshotPacket(
settings.enabled(),
RealmwrightData.mode(scepter),
RealmwrightData.matchMode(scepter),
RealmwrightData.recordsHistory(scepter),
RealmwrightData.allowsReplacement(scepter),
RealmwrightData.buildLimit(scepter, settings.maxBuildLimit()),
RealmwrightData.demolishLimit(scepter, settings.maxDemolishLimit()),
settings.maxBuildLimit(),
settings.maxDemolishLimit(),
Math.max(1, (int) Math.ceil(settings.blockReach())),
status,
running.transactionId() != null ? running.transactionId()
: pending == null ? null : pending.transactionId(),
completed,
total,
undo != null && undo.state() == BuilderTransaction.State.APPLIED,
redo != null && redo.state() == BuilderTransaction.State.UNDONE,
anchors(player, scepter),
history
);
}
private static List<Anchor> anchors(ServerPlayer player, ItemStack scepter) {
AnchorIndexSavedData index = AnchorIndexSavedData.get(player.getServer());
boolean bindingsChanged = AnchorBindings.pruneMissing(scepter, index);
List<UUID> ids = AnchorBindings.read(scepter);
List<Anchor> result = new ArrayList<>(Math.min(ids.size(), MAX_ANCHORS));
for (int i = 0; i < ids.size() && i < MAX_ANCHORS; i++) {
UUID id = ids.get(i);
AnchorIndexSavedData.AnchorLocation location = index.find(id).orElse(null);
if (location == null) {
bindingsChanged = true;
continue;
}
AnchorStatus status = AnchorStatus.UNLOADED;
boolean owner = false;
boolean publicAccess = false;
ServerLevel level = player.getServer().getLevel(location.dimension());
if (level == null) {
index.unregister(id, location);
bindingsChanged = true;
continue;
} else if (level.hasChunkAt(location.position())) {
if (level.getBlockEntity(location.position()) instanceof ResonantSupplyAnchorBlockEntity anchor
&& anchor.anchorId().equals(id)) {
owner = anchor.ownerId().map(player.getUUID()::equals).orElse(false);
publicAccess = anchor.publicAccess();
status = anchor.canUse(player) ? AnchorStatus.AVAILABLE : AnchorStatus.FORBIDDEN;
} else {
// Heal an index entry whose block was removed by a path that did not leave a
// live matching block entity. This also prevents later snapshots from
// continuing to advertise the obsolete world position.
index.unregister(id, location);
bindingsChanged = true;
continue;
}
}
result.add(new Anchor(id, "Anchor " + (result.size() + 1), location.dimension().location(),
location.position().asLong(), status, owner, publicAccess));
}
if (bindingsChanged) {
// Also removes entries whose index was invalidated while resolving loaded chunks.
AnchorBindings.pruneMissing(scepter, index);
player.getInventory().setChanged();
player.containerMenu.broadcastChanges();
if (player.inventoryMenu != player.containerMenu) {
player.inventoryMenu.broadcastChanges();
}
}
return result;
}
public static void encode(BuilderSnapshotPacket packet, FriendlyByteBuf buffer) {
buffer.writeBoolean(packet.enabled);
buffer.writeEnum(packet.mode);
buffer.writeEnum(packet.surfaceMatch);
buffer.writeBoolean(packet.historyRecording);
buffer.writeBoolean(packet.allowReplacement);
buffer.writeVarInt(packet.buildLimit);
buffer.writeVarInt(packet.demolishLimit);
buffer.writeVarInt(packet.maximumBuildLimit);
buffer.writeVarInt(packet.maximumDemolishLimit);
buffer.writeVarInt(packet.reach);
buffer.writeUtf(packet.status, 256);
writeOptionalUuid(buffer, packet.activeJobId);
buffer.writeVarInt(packet.completedBlocks);
buffer.writeVarInt(packet.totalBlocks);
buffer.writeBoolean(packet.canUndo);
buffer.writeBoolean(packet.canRedo);
buffer.writeVarInt(packet.anchors.size());
for (Anchor anchor : packet.anchors) {
buffer.writeUUID(anchor.id);
buffer.writeUtf(anchor.name, 128);
buffer.writeResourceLocation(anchor.dimension);
buffer.writeLong(anchor.packedPos);
buffer.writeEnum(anchor.status);
buffer.writeBoolean(anchor.owner);
buffer.writeBoolean(anchor.publicAccess);
}
buffer.writeVarInt(packet.history.size());
for (History entry : packet.history) {
buffer.writeUUID(entry.id);
buffer.writeUtf(entry.label, 128);
buffer.writeUtf(entry.dimension, 128);
buffer.writeVarInt(entry.changedBlocks);
buffer.writeLong(entry.createdAt);
buffer.writeEnum(entry.status);
}
}
public static BuilderSnapshotPacket decode(FriendlyByteBuf buffer) {
boolean enabled = buffer.readBoolean();
BuilderMode mode = buffer.readEnum(BuilderMode.class);
SurfaceMatchMode match = buffer.readEnum(SurfaceMatchMode.class);
boolean historyRecording = buffer.readBoolean();
boolean allowReplacement = buffer.readBoolean();
int buildLimit = buffer.readVarInt();
int demolishLimit = buffer.readVarInt();
int maximumBuildLimit = buffer.readVarInt();
int maximumDemolishLimit = buffer.readVarInt();
int reach = buffer.readVarInt();
String status = buffer.readUtf(256);
UUID activeJobId = readOptionalUuid(buffer);
int completedBlocks = buffer.readVarInt();
int totalBlocks = buffer.readVarInt();
boolean canUndo = buffer.readBoolean();
boolean canRedo = buffer.readBoolean();
int anchorCount = boundedCount(buffer.readVarInt(), MAX_ANCHORS, "builder anchors");
List<Anchor> anchors = new ArrayList<>(anchorCount);
for (int i = 0; i < anchorCount; i++) {
anchors.add(new Anchor(buffer.readUUID(), buffer.readUtf(128), buffer.readResourceLocation(),
buffer.readLong(), buffer.readEnum(AnchorStatus.class), buffer.readBoolean(),
buffer.readBoolean()));
}
int historyCount = boundedCount(buffer.readVarInt(), MAX_HISTORY, "builder history");
List<History> history = new ArrayList<>(historyCount);
for (int i = 0; i < historyCount; i++) {
history.add(new History(buffer.readUUID(), buffer.readUtf(128), buffer.readUtf(128),
buffer.readVarInt(), buffer.readLong(), buffer.readEnum(HistoryStatus.class)));
}
return new BuilderSnapshotPacket(enabled, mode, match, historyRecording, allowReplacement,
buildLimit, demolishLimit,
maximumBuildLimit, maximumDemolishLimit, reach, status, activeJobId,
completedBlocks, totalBlocks, canUndo, canRedo, anchors, history);
}
public static void handle(BuilderSnapshotPacket packet,
Supplier<NetworkEvent.Context> contextSupplier) {
NetworkEvent.Context context = contextSupplier.get();
context.enqueueWork(() -> BuilderClientPacketHooks.receiver().snapshot(packet));
context.setPacketHandled(true);
}
private static int boundedCount(int value, int maximum, String label) {
if (value < 0 || value > maximum) {
throw new IllegalArgumentException("Invalid " + label + " count: " + value);
}
return value;
}
private static void writeOptionalUuid(FriendlyByteBuf buffer, @Nullable UUID value) {
buffer.writeBoolean(value != null);
if (value != null) buffer.writeUUID(value);
}
@Nullable
private static UUID readOptionalUuid(FriendlyByteBuf buffer) {
return buffer.readBoolean() ? buffer.readUUID() : null;
}
public record Anchor(UUID id, String name, ResourceLocation dimension, long packedPos,
AnchorStatus status, boolean owner, boolean publicAccess) {
}
public enum AnchorStatus {
AVAILABLE,
UNLOADED,
DISCONNECTED,
FORBIDDEN,
UNKNOWN
}
public record History(UUID id, String label, String dimension, int changedBlocks,
long createdAt, HistoryStatus status) {
private static History from(BuilderTransaction transaction) {
HistoryStatus status = switch (transaction.state()) {
case PREPARED -> HistoryStatus.INCOMPLETE;
case APPLIED -> HistoryStatus.COMPLETE;
case UNDONE -> HistoryStatus.UNDONE;
case CONFLICTED -> HistoryStatus.CONFLICTED;
};
return new History(transaction.id(), transaction.type().name(),
transaction.dimension().location().toString(), transaction.worldDeltas().size(),
transaction.createdAt(), status);
}
}
public enum HistoryStatus {
RUNNING,
INCOMPLETE,
COMPLETE,
UNDONE,
CONFLICTED
}
}