diff --git a/src/main/java/fi/dy/masa/litematica/data/EntitiesDataStorage.java b/src/main/java/fi/dy/masa/litematica/data/EntitiesDataStorage.java index df5f146a54..2eaf204afb 100644 --- a/src/main/java/fi/dy/masa/litematica/data/EntitiesDataStorage.java +++ b/src/main/java/fi/dy/masa/litematica/data/EntitiesDataStorage.java @@ -9,16 +9,25 @@ import com.mojang.datafixers.util.Either; import net.minecraft.block.BlockEntityProvider; import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.entity.Entity; import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtList; +import net.minecraft.registry.Registries; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; import fi.dy.masa.malilib.interfaces.IClientTickHandler; import fi.dy.masa.malilib.network.ClientPlayHandler; import fi.dy.masa.malilib.network.IPluginClientPlayHandler; +import fi.dy.masa.malilib.util.Constants; +import fi.dy.masa.malilib.util.NBTUtils; +import fi.dy.masa.malilib.util.WorldUtils; import fi.dy.masa.litematica.Litematica; import fi.dy.masa.litematica.Reference; import fi.dy.masa.litematica.config.Configs; @@ -47,6 +56,8 @@ public static EntitiesDataStorage getInstance() // Requests to be executed private Set pendingBlockEntitiesQueue = new LinkedHashSet<>(); private Set pendingEntitiesQueue = new LinkedHashSet<>(); + private Set pendingChunks = new LinkedHashSet<>(); + private Set completedChunks = new LinkedHashSet<>(); // To save vanilla query packet transaction private Map> transactionToBlockPosOrEntityId = new HashMap<>(); @@ -257,7 +268,7 @@ private void requestQueryBlockEntity(BlockPos pos) { handler.getDataQueryHandler().queryBlockNbt(pos, nbtCompound -> { - handleBlockEntityData(pos, nbtCompound); + handleBlockEntityData(pos, nbtCompound, null); }); transactionToBlockPosOrEntityId.put(((IMixinDataQueryHandler) handler.getDataQueryHandler()).currentTransactionId(), Either.left(pos)); } @@ -302,24 +313,59 @@ private void requestServuxEntityData(int entityId) HANDLER.encodeClientData(ServuxLitematicaPacket.EntityRequest(entityId)); } - // BlockEntity.createNbtWithIdentifyingData + // The minY, maxY should be calculated based on the Selection Box... But for now, we can just grab the entire chunk. + public void requestServuxBulkEntityData(ChunkPos chunkPos, int minY, int maxY) + { + if (this.hasServuxServer() == false) + { + return; + } + + NbtCompound req = new NbtCompound(); + + if (this.completedChunks.contains(chunkPos)) + { + this.completedChunks.remove(chunkPos); + } + + this.pendingChunks.add(chunkPos); + + if (minY < -60) + { + minY = -60; + } + if (maxY > 319) + { + maxY = 319; + } + + req.putInt("minY", minY); + req.putInt("maxY", maxY); + + Litematica.debugLog("EntitiesDataStorage#requestServuxBulkEntityData(): for chunkPos [{}] to Servux (minY [{}], maxY [{}])", chunkPos.toString(), minY, maxY); + HANDLER.encodeClientData(ServuxLitematicaPacket.BulkNbtRequest(chunkPos, req)); + } + @Nullable - public BlockEntity handleBlockEntityData(BlockPos pos, NbtCompound nbt) + public BlockEntity handleBlockEntityData(BlockPos pos, NbtCompound nbt, @Nullable Identifier type) { pendingBlockEntitiesQueue.remove(pos); if (nbt == null || this.getWorld() == null) return null; BlockEntity blockEntity = this.getWorld().getBlockEntity(pos); - if (blockEntity != null) + if (blockEntity != null && (type == null || type.equals(BlockEntityType.getId(blockEntity.getType())))) { blockEntity.read(nbt, this.getWorld().getRegistryManager()); return blockEntity; } - else + + BlockEntityType beType = Registries.BLOCK_ENTITY_TYPE.get(type); + if (beType != null && beType.supports(this.getWorld().getBlockState(pos))) { - BlockEntity blockEntity2 = BlockEntity.createFromNbt(pos, this.getWorld().getBlockState(pos), nbt, mc.world.getRegistryManager()); + BlockEntity blockEntity2 = beType.instantiate(pos, this.getWorld().getBlockState(pos)); if (blockEntity2 != null) { + blockEntity2.read(nbt, this.getWorld().getRegistryManager()); this.getWorld().addBlockEntity(blockEntity2); return blockEntity2; } @@ -328,7 +374,6 @@ public BlockEntity handleBlockEntityData(BlockPos pos, NbtCompound nbt) return null; } - // Entity.saveSelfNbt @Nullable public Entity handleEntityData(int entityId, NbtCompound nbt) { @@ -342,16 +387,86 @@ public Entity handleEntityData(int entityId, NbtCompound nbt) return entity; } + public void handleBulkEntityData(int transactionId, @Nullable NbtCompound nbt) + { + if (nbt == null) + { + return; + } + NbtList tileList = nbt.contains("TileEntities") ? nbt.getList("TileEntities", Constants.NBT.TAG_COMPOUND) : new NbtList(); + NbtList entityList = nbt.contains("Entities") ? nbt.getList("Entities", Constants.NBT.TAG_COMPOUND) : new NbtList(); + ChunkPos chunkPos = new ChunkPos(nbt.getInt("chunkX"), nbt.getInt("chunkZ")); + + for (int i = 0; i < tileList.size(); ++i) + { + NbtCompound te = tileList.getCompound(i); + BlockPos pos = NBTUtils.readBlockPos(te); + Identifier type = Identifier.of(te.getString("id")); + + handleBlockEntityData(pos, te, type); + } + + for (int i = 0; i < entityList.size(); ++i) + { + NbtCompound ent = entityList.getCompound(i); + Vec3d pos = NBTUtils.readEntityPositionFromTag(ent); + int entityId = ent.getInt("entityId"); + + handleEntityData(entityId, ent); + } + + if (this.pendingChunks.contains(chunkPos)) + { + this.pendingChunks.remove(chunkPos); + } + + this.completedChunks.add(chunkPos); + + Litematica.debugLog("EntitiesDataStorage#handleBulkEntityData(): [ChunkPos {}] received TE: [{}], and E: [{}] entiries from Servux", chunkPos.toString(), tileList.size(), entityList.size()); + } + public void handleVanillaQueryNbt(int transactionId, NbtCompound nbt) { Either either = transactionToBlockPosOrEntityId.remove(transactionId); if (either != null) { - either.ifLeft(pos -> handleBlockEntityData(pos, nbt)) + either.ifLeft(pos -> handleBlockEntityData(pos, nbt, null)) .ifRight(entityId -> handleEntityData(entityId, nbt)); } } + public boolean hasPendingChunk(ChunkPos pos) + { + if (this.hasServuxServer()) + { + return this.pendingChunks.contains(pos); + } + else + { + return false; + } + } + + public boolean hasCompletedChunk(ChunkPos pos) + { + if (this.hasServuxServer()) + { + return this.completedChunks.contains(pos); + } + else + { + return true; + } + } + + public void markCompletedChunkDirty(ChunkPos pos) + { + if (this.hasServuxServer()) + { + this.completedChunks.remove(pos); + } + } + // TODO --> Only in case we need to save config settings in the future public JsonObject toJson() { diff --git a/src/main/java/fi/dy/masa/litematica/network/ServuxLitematicaHandler.java b/src/main/java/fi/dy/masa/litematica/network/ServuxLitematicaHandler.java index b972a7abdd..93feac669d 100644 --- a/src/main/java/fi/dy/masa/litematica/network/ServuxLitematicaHandler.java +++ b/src/main/java/fi/dy/masa/litematica/network/ServuxLitematicaHandler.java @@ -79,7 +79,7 @@ public

void decodeClientData(Identifier channel, this.servuxRegistered = true; } } - case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> EntitiesDataStorage.getInstance().handleBlockEntityData(packet.getPos(), packet.getCompound()); + case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> EntitiesDataStorage.getInstance().handleBlockEntityData(packet.getPos(), packet.getCompound(), null); case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> EntitiesDataStorage.getInstance().handleEntityData(packet.getEntityId(), packet.getCompound()); case PACKET_S2C_NBT_RESPONSE_DATA -> { @@ -88,7 +88,7 @@ public

void decodeClientData(Identifier channel, this.readingSessionKey = Random.create(Util.getMeasuringTimeMs()).nextLong(); } - Litematica.debugLog("ServuxLitematicaHandler#decodeClientData(): received Entity Data Packet Slice of size {} (in bytes) // reading session key [{}]", packet.getTotalSize(), this.readingSessionKey); + //Litematica.debugLog("ServuxLitematicaHandler#decodeClientData(): received Entity Data Packet Slice of size {} (in bytes) // reading session key [{}]", packet.getTotalSize(), this.readingSessionKey); PacketByteBuf fullPacket = PacketSplitter.receive(this, this.readingSessionKey, packet.getBuffer()); if (fullPacket != null) @@ -96,8 +96,7 @@ public

void decodeClientData(Identifier channel, try { this.readingSessionKey = -1; - EntitiesDataStorage.getInstance().handleEntityData(fullPacket.readVarInt(), fullPacket.readNbt()); - // FIXME --> handleBulkData + EntitiesDataStorage.getInstance().handleBulkEntityData(fullPacket.readVarInt(), fullPacket.readNbt()); } catch (Exception e) { diff --git a/src/main/java/fi/dy/masa/litematica/network/ServuxLitematicaPacket.java b/src/main/java/fi/dy/masa/litematica/network/ServuxLitematicaPacket.java index 4c15f8cf08..090ea92761 100644 --- a/src/main/java/fi/dy/masa/litematica/network/ServuxLitematicaPacket.java +++ b/src/main/java/fi/dy/masa/litematica/network/ServuxLitematicaPacket.java @@ -8,6 +8,7 @@ import net.minecraft.network.codec.PacketCodec; import net.minecraft.network.packet.CustomPayload; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; import fi.dy.masa.malilib.network.IClientPayloadData; import fi.dy.masa.litematica.Litematica; @@ -18,6 +19,7 @@ public class ServuxLitematicaPacket implements IClientPayloadData private int entityId; private BlockPos pos; private NbtCompound nbt; + private ChunkPos chunkPos; private PacketByteBuf buffer; public static final int PROTOCOL_VERSION = 1; @@ -27,6 +29,7 @@ private ServuxLitematicaPacket(Type type) this.transactionId = -1; this.entityId = -1; this.pos = BlockPos.ORIGIN; + this.chunkPos = ChunkPos.ORIGIN; this.nbt = new NbtCompound(); this.clearPacket(); } @@ -88,6 +91,17 @@ public static ServuxLitematicaPacket EntityRequest(int entityId) return packet; } + public static ServuxLitematicaPacket BulkNbtRequest(ChunkPos chunkPos, @Nullable NbtCompound nbt) + { + var packet = new ServuxLitematicaPacket(Type.PACKET_C2S_BULK_ENTITY_NBT_REQUEST); + packet.chunkPos = chunkPos; + if (nbt != null) + { + packet.nbt.copyFrom(nbt); + } + return packet; + } + // Nbt Packet, using Packet Splitter public static ServuxLitematicaPacket ResponseS2CStart(@Nonnull NbtCompound nbt) { @@ -173,6 +187,8 @@ public NbtCompound getCompound() return this.nbt; } + public ChunkPos getChunkPos() { return this.chunkPos; } + public PacketByteBuf getBuffer() { return this.buffer; @@ -245,6 +261,18 @@ public void toPacket(PacketByteBuf output) Litematica.logger.error("ServuxEntitiesPacket#toPacket: error writing Entity Response to packet: [{}]", e.getLocalizedMessage()); } } + case PACKET_C2S_BULK_ENTITY_NBT_REQUEST -> + { + try + { + output.writeChunkPos(this.chunkPos); + output.writeNbt(this.nbt); + } + catch (Exception e) + { + Litematica.logger.error("ServuxEntitiesPacket#toPacket: error writing Bulk Entity Request to packet: [{}]", e.getLocalizedMessage()); + } + } case PACKET_S2C_NBT_RESPONSE_DATA, PACKET_C2S_NBT_RESPONSE_DATA -> { // Write Packet Buffer (Slice) @@ -335,6 +363,17 @@ public static ServuxLitematicaPacket fromPacket(PacketByteBuf input) Litematica.logger.error("ServuxEntitiesPacket#fromPacket: error reading Entity Response from packet: [{}]", e.getLocalizedMessage()); } } + case PACKET_C2S_BULK_ENTITY_NBT_REQUEST -> + { + try + { + return ServuxLitematicaPacket.BulkNbtRequest(input.readChunkPos(), input.readNbt()); + } + catch (Exception e) + { + Litematica.logger.error("ServuxEntitiesPacket#fromPacket: error reading Bulk Entity Request from packet: [{}]", e.getLocalizedMessage()); + } + } case PACKET_S2C_NBT_RESPONSE_DATA -> { // Read Packet Buffer Slice @@ -425,6 +464,7 @@ public enum Type PACKET_C2S_ENTITY_REQUEST(4), PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE(5), PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE(6), + PACKET_C2S_BULK_ENTITY_NBT_REQUEST(7), // For Packet Splitter (Oversize Packets, S2C) PACKET_S2C_NBT_RESPONSE_START(10), PACKET_S2C_NBT_RESPONSE_DATA(11), diff --git a/src/main/java/fi/dy/masa/litematica/scheduler/tasks/TaskSaveSchematic.java b/src/main/java/fi/dy/masa/litematica/scheduler/tasks/TaskSaveSchematic.java index 0566d16b31..d4c41b1679 100644 --- a/src/main/java/fi/dy/masa/litematica/scheduler/tasks/TaskSaveSchematic.java +++ b/src/main/java/fi/dy/masa/litematica/scheduler/tasks/TaskSaveSchematic.java @@ -1,14 +1,13 @@ package fi.dy.masa.litematica.scheduler.tasks; import java.io.File; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; +import java.util.*; import javax.annotation.Nullable; import com.google.common.collect.ImmutableMap; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.World; +import fi.dy.masa.litematica.data.EntitiesDataStorage; import fi.dy.masa.litematica.data.SchematicHolder; import fi.dy.masa.litematica.render.infohud.InfoHud; import fi.dy.masa.litematica.schematic.LitematicaSchematic; @@ -59,7 +58,35 @@ protected boolean canProcessChunk(ChunkPos pos) { return this.schematicWorld != null && this.schematicWorld.getChunkManager().isChunkLoaded(pos.x, pos.z); } - + + // Request entity data from Servux, if the ClientWorld matches, and treat it as not yet loaded + if (EntitiesDataStorage.getInstance().hasServuxServer() && + Objects.equals(EntitiesDataStorage.getInstance().getWorld(), this.clientWorld)) + { + if (EntitiesDataStorage.getInstance().hasCompletedChunk(pos)) + { + return this.areSurroundingChunksLoaded(pos, this.clientWorld, 0); + } + else if (EntitiesDataStorage.getInstance().hasPendingChunk(pos) == false) + { + ImmutableMap volumes = PositionUtils.getBoxesWithinChunk(pos.x, pos.z, this.subRegions); + int minY = 319; // Invert Values + int maxY = -60; + + for (Map.Entry volumeEntry : volumes.entrySet()) + { + IntBoundingBox bb = volumeEntry.getValue(); + + minY = Math.min(bb.minY, minY); + maxY = Math.max(bb.maxY, maxY); + } + + EntitiesDataStorage.getInstance().requestServuxBulkEntityData(pos, minY, maxY); + } + + return false; + } + return this.areSurroundingChunksLoaded(pos, this.clientWorld, 0); } @@ -75,6 +102,14 @@ protected boolean processChunk(ChunkPos pos) this.schematic.takeEntitiesFromWorldWithinChunk(world, pos.x, pos.z, volumes, this.subRegions, this.existingEntities, this.origin); } + if (EntitiesDataStorage.getInstance().hasServuxServer() && + EntitiesDataStorage.getInstance().hasCompletedChunk(pos) && + Objects.equals(EntitiesDataStorage.getInstance().getWorld(), this.clientWorld)) + { + EntitiesDataStorage.getInstance().markCompletedChunkDirty(pos); + // Mark Dirty for refresh + } + return true; } diff --git a/src/main/java/fi/dy/masa/litematica/util/WorldUtils.java b/src/main/java/fi/dy/masa/litematica/util/WorldUtils.java index b59007439d..855843edc6 100644 --- a/src/main/java/fi/dy/masa/litematica/util/WorldUtils.java +++ b/src/main/java/fi/dy/masa/litematica/util/WorldUtils.java @@ -1,19 +1,13 @@ package fi.dy.masa.litematica.util; +import javax.annotation.Nullable; import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; -import javax.annotation.Nullable; - -import net.minecraft.block.Block; -import net.minecraft.block.BlockState; -import net.minecraft.block.Blocks; -import net.minecraft.block.ComparatorBlock; -import net.minecraft.block.RepeaterBlock; -import net.minecraft.block.SlabBlock; +import net.minecraft.block.*; import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.SignBlockEntity; import net.minecraft.block.entity.SignText; @@ -39,15 +33,14 @@ import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.HitResult; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Direction; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.Vec3d; -import net.minecraft.util.math.Vec3i; +import net.minecraft.util.math.*; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.ChunkStatus; - +import fi.dy.masa.malilib.gui.Message; +import fi.dy.masa.malilib.gui.Message.MessageType; +import fi.dy.masa.malilib.interfaces.IStringConsumer; +import fi.dy.masa.malilib.util.*; import fi.dy.masa.litematica.Litematica; import fi.dy.masa.litematica.config.Configs; import fi.dy.masa.litematica.config.Hotkeys; @@ -67,15 +60,6 @@ import fi.dy.masa.litematica.util.RayTraceUtils.RayTraceWrapper.HitType; import fi.dy.masa.litematica.world.SchematicWorldHandler; import fi.dy.masa.litematica.world.WorldSchematic; -import fi.dy.masa.malilib.gui.Message; -import fi.dy.masa.malilib.gui.Message.MessageType; -import fi.dy.masa.malilib.interfaces.IStringConsumer; -import fi.dy.masa.malilib.util.FileUtils; -import fi.dy.masa.malilib.util.InfoUtils; -import fi.dy.masa.malilib.util.IntBoundingBox; -import fi.dy.masa.malilib.util.LayerRange; -import fi.dy.masa.malilib.util.MessageOutputType; -import fi.dy.masa.malilib.util.StringUtils; public class WorldUtils {