mirror of
https://github.com/LeavesMC/Leaves.git
synced 2025-12-19 14:59:32 +00:00
Support Servux Litematics protocol (#448)
--------- Co-authored-by: Lumine1909 <133463833+Lumine1909@users.noreply.github.com> Co-authored-by: violetc <58360096+s-yh-china@users.noreply.github.com>
This commit is contained in:
@@ -34,6 +34,7 @@ subprojects {
|
||||
options.encoding = Charsets.UTF_8.name()
|
||||
options.release = 21
|
||||
options.isFork = true
|
||||
options.forkOptions.memoryMaximumSize = "6g"
|
||||
}
|
||||
tasks.withType<Javadoc> {
|
||||
options.encoding = Charsets.UTF_8.name()
|
||||
|
||||
@@ -3,6 +3,5 @@ version=1.21.4-R0.1-SNAPSHOT
|
||||
mcVersion=1.21.4
|
||||
paperRef=9b1798d6438107fdf0d5939b79a8cf71f4d16e2c
|
||||
preVersion=false
|
||||
org.gradle.jvmargs=-Xmx2G
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
@@ -7,6 +7,9 @@ import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.leavesmc.leaves.bot.ServerBot;
|
||||
import org.leavesmc.leaves.command.LeavesCommand;
|
||||
@@ -797,6 +800,23 @@ public final class LeavesConfig {
|
||||
|
||||
@GlobalConfig("hud-metadata-protocol-share-seed")
|
||||
public boolean hudMetadataShareSeed = true;
|
||||
|
||||
@GlobalConfig(value = "litematics-protocol", validator = LitematicsProtocolValidator.class)
|
||||
public boolean litematicsProtocol = false;
|
||||
|
||||
public static class LitematicsProtocolValidator extends BooleanConfigValidator {
|
||||
@Override
|
||||
public void verify(Boolean old, Boolean value) throws IllegalArgumentException {
|
||||
PluginManager pluginManager = MinecraftServer.getServer().server.getPluginManager();
|
||||
if (value) {
|
||||
if (pluginManager.getPermission("leaves.protocol.litematics") == null) {
|
||||
pluginManager.addPermission(new Permission("leaves.protocol.litematics", PermissionDefault.OP));
|
||||
}
|
||||
} else {
|
||||
pluginManager.removePermission("leaves.protocol.litematics");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GlobalConfig("bbor-protocol")
|
||||
|
||||
@@ -3,10 +3,13 @@ package org.leavesmc.leaves.protocol.servux;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ServuxProtocol {
|
||||
|
||||
public static final String PROTOCOL_ID = "servux";
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(PROTOCOL_ID.toUpperCase());
|
||||
public static final String SERVUX_STRING = ProtocolUtils.buildProtocolVersion(PROTOCOL_ID);
|
||||
|
||||
@Contract("_ -> new")
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
import net.minecraft.world.level.material.Fluids;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.ticks.ScheduledTick;
|
||||
import net.minecraft.world.ticks.TickPriority;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
import org.leavesmc.leaves.protocol.servux.ServuxProtocol;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.container.LitematicaBlockStateContainer;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.FileType;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.NbtUtils;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.PositionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public record LitematicaSchematic(Map<String, SubRegion> subRegions, SchematicMetadata metadata) {
|
||||
|
||||
public static final int MINECRAFT_DATA_VERSION = SharedConstants.getProtocolVersion();
|
||||
public static final int SCHEMATIC_VERSION = 7;
|
||||
|
||||
@NotNull
|
||||
@Unmodifiable
|
||||
public Map<String, BlockPos> getAreaSizes() {
|
||||
ImmutableMap.Builder<String, BlockPos> builder = ImmutableMap.builderWithExpectedSize(subRegions.size());
|
||||
for (Map.Entry<String, SubRegion> entry : subRegions.entrySet()) {
|
||||
builder.put(entry.getKey(), entry.getValue().size());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public SubRegion getSubRegion(String name) {
|
||||
return Objects.requireNonNull(subRegions.get(name));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Contract("_ -> new")
|
||||
public static LitematicaSchematic readFromNBT(@NotNull CompoundTag nbt) {
|
||||
if (nbt.contains("Version", Tag.TAG_INT)) {
|
||||
final int version = nbt.getInt("Version");
|
||||
final int minecraftDataVersion = nbt.contains("MinecraftDataVersion") ? nbt.getInt("MinecraftDataVersion") : SharedConstants.getProtocolVersion();
|
||||
|
||||
if (version >= 1 && version <= SCHEMATIC_VERSION) {
|
||||
SchematicMetadata metadata = SchematicMetadata.readFromNBT(nbt.getCompound("Metadata"), version, minecraftDataVersion, FileType.LITEMATICA_SCHEMATIC);
|
||||
Map<String, SubRegion> subRegions = readSubRegionsFromNBT(nbt.getCompound("Regions"), version, minecraftDataVersion);
|
||||
return new LitematicaSchematic(subRegions, metadata);
|
||||
} else {
|
||||
throw new RuntimeException("Unsupported or future schematic version");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("The schematic doesn't have version information, and can't be safely loaded!");
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Map<String, SubRegion> readSubRegionsFromNBT(@NotNull CompoundTag tag, int version, int minecraftDataVersion) {
|
||||
Map<String, SubRegion> subRegions = new HashMap<>();
|
||||
for (String regionName : tag.getAllKeys()) {
|
||||
Tag region = tag.get(regionName);
|
||||
if (region == null || region.getId() != Tag.TAG_COMPOUND) {
|
||||
throw new RuntimeException("Unknown region: " + regionName);
|
||||
}
|
||||
subRegions.put(regionName, SubRegion.readFromNBT(tag.getCompound(regionName), version, minecraftDataVersion));
|
||||
}
|
||||
return subRegions;
|
||||
}
|
||||
|
||||
public record SubRegion(
|
||||
LitematicaBlockStateContainer blockContainers,
|
||||
Map<BlockPos, CompoundTag> tileEntities,
|
||||
Map<BlockPos, ScheduledTick<Block>> pendingBlockTicks,
|
||||
Map<BlockPos, ScheduledTick<Fluid>> pendingFluidTicks,
|
||||
List<EntityInfo> entities,
|
||||
BlockPos position,
|
||||
BlockPos size
|
||||
) {
|
||||
@NotNull
|
||||
@Contract("_, _, _ -> new")
|
||||
public static SubRegion readFromNBT(@NotNull CompoundTag regionTag, int version, int minecraftDataVersion) {
|
||||
BlockPos position = NbtUtils.readBlockPos(regionTag.getCompound("Position"));
|
||||
BlockPos size = NbtUtils.readBlockPos(regionTag.getCompound("Size"));
|
||||
if (position == null || size == null) {
|
||||
throw new IllegalArgumentException("Invalid region");
|
||||
}
|
||||
|
||||
Map<BlockPos, CompoundTag> tileEntities;
|
||||
List<EntityInfo> entities;
|
||||
if (version >= 2) {
|
||||
tileEntities = readTileEntitiesFromNBT(regionTag.getList("TileEntities", Tag.TAG_COMPOUND));
|
||||
entities = readEntitiesFromNBT(regionTag.getList("Entities", Tag.TAG_COMPOUND));
|
||||
} else {
|
||||
tileEntities = readTileEntitiesFromNBT_v1(regionTag.getList("TileEntities", Tag.TAG_COMPOUND));
|
||||
entities = readEntitiesFromNBT_v1(regionTag.getList("Entities", Tag.TAG_COMPOUND));
|
||||
}
|
||||
|
||||
Map<BlockPos, ScheduledTick<Block>> pendingBlockTicks = null;
|
||||
if (version >= 3) {
|
||||
pendingBlockTicks = readPendingTicksFromNBT(regionTag.getList("PendingBlockTicks", Tag.TAG_COMPOUND), BuiltInRegistries.BLOCK, "Block", Blocks.AIR);
|
||||
}
|
||||
|
||||
Map<BlockPos, ScheduledTick<Fluid>> pendingFluidTicks = null;
|
||||
if (version >= 5) {
|
||||
pendingFluidTicks = readPendingTicksFromNBT(regionTag.getList("PendingFluidTicks", Tag.TAG_COMPOUND), BuiltInRegistries.FLUID, "Fluid", Fluids.EMPTY);
|
||||
}
|
||||
|
||||
LitematicaBlockStateContainer blockContainers = null;
|
||||
if (regionTag.contains("BlockStates", Tag.TAG_LONG_ARRAY)) {
|
||||
ListTag palette = regionTag.getList("BlockStatePalette", Tag.TAG_COMPOUND);
|
||||
long[] blockStateArr = regionTag.getLongArray("BlockStates");
|
||||
BlockPos posEndRel = PositionUtils.getRelativeEndPositionFromAreaSize(size).offset(position);
|
||||
BlockPos posMin = PositionUtils.getMinCorner(position, posEndRel);
|
||||
BlockPos posMax = PositionUtils.getMaxCorner(position, posEndRel);
|
||||
BlockPos containerSize = posMax.subtract(posMin).offset(1, 1, 1);
|
||||
blockContainers = LitematicaBlockStateContainer.createFrom(palette, blockStateArr, containerSize);
|
||||
if (minecraftDataVersion < MINECRAFT_DATA_VERSION) {
|
||||
ServuxProtocol.LOGGER.warn("Cannot process minecraft data version: {}", minecraftDataVersion);
|
||||
}
|
||||
}
|
||||
|
||||
return new SubRegion(blockContainers, tileEntities, pendingBlockTicks, pendingFluidTicks, entities, position, size);
|
||||
}
|
||||
|
||||
private static List<EntityInfo> readEntitiesFromNBT(ListTag tagList) {
|
||||
List<EntityInfo> entityList = new ArrayList<>();
|
||||
final int size = tagList.size();
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
CompoundTag entityData = tagList.getCompound(i);
|
||||
Vec3 posVec = NbtUtils.readEntityPositionFromTag(entityData);
|
||||
|
||||
if (posVec != null && !entityData.isEmpty()) {
|
||||
entityList.add(new EntityInfo(posVec, entityData));
|
||||
}
|
||||
}
|
||||
|
||||
return entityList;
|
||||
}
|
||||
|
||||
private static Map<BlockPos, CompoundTag> readTileEntitiesFromNBT(ListTag tagList) {
|
||||
Map<BlockPos, CompoundTag> tileMap = new HashMap<>();
|
||||
final int size = tagList.size();
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
CompoundTag tag = tagList.getCompound(i);
|
||||
BlockPos pos = NbtUtils.readBlockPos(tag);
|
||||
|
||||
if (pos != null && !tag.isEmpty()) {
|
||||
tileMap.put(pos, tag);
|
||||
}
|
||||
}
|
||||
|
||||
return tileMap;
|
||||
}
|
||||
|
||||
private static <T> Map<BlockPos, ScheduledTick<T>> readPendingTicksFromNBT(ListTag tagList, Registry<T> registry, String tagName, T emptyValue) {
|
||||
Map<BlockPos, ScheduledTick<T>> tickMap = new HashMap<>();
|
||||
final int size = tagList.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
CompoundTag tag = tagList.getCompound(i);
|
||||
|
||||
// XXX these were accidentally saved as longs in version 3
|
||||
if (!tag.contains("Time", Tag.TAG_ANY_NUMERIC)) {
|
||||
continue;
|
||||
}
|
||||
T target;
|
||||
ResourceLocation resourceLocation = ResourceLocation.tryParse(tag.getString(tagName));
|
||||
if (resourceLocation == null) {
|
||||
continue;
|
||||
}
|
||||
Optional<Holder.Reference<T>> tReference = registry.get(resourceLocation);
|
||||
if (tReference.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
target = tReference.get().value();
|
||||
if (target == emptyValue) {
|
||||
continue;
|
||||
}
|
||||
BlockPos pos = new BlockPos(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
|
||||
TickPriority priority = TickPriority.byValue(tag.getInt("Priority"));
|
||||
int scheduledTime = tag.getInt("Time");
|
||||
long subTick = tag.getLong("SubTick");
|
||||
tickMap.put(pos, new ScheduledTick<>(target, pos, scheduledTime, priority, subTick));
|
||||
}
|
||||
|
||||
return tickMap;
|
||||
}
|
||||
|
||||
private static List<EntityInfo> readEntitiesFromNBT_v1(ListTag tagList) {
|
||||
List<EntityInfo> entityList = new ArrayList<>();
|
||||
final int size = tagList.size();
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
CompoundTag tag = tagList.getCompound(i);
|
||||
Vec3 posVec = NbtUtils.readVec3(tag);
|
||||
CompoundTag entityData = tag.getCompound("EntityData");
|
||||
|
||||
if (posVec != null && !entityData.isEmpty()) {
|
||||
// Update the correct position to the TileEntity NBT, where it is stored in version 2
|
||||
NbtUtils.writeEntityPositionToTag(posVec, entityData);
|
||||
entityList.add(new EntityInfo(posVec, entityData));
|
||||
}
|
||||
}
|
||||
|
||||
return entityList;
|
||||
}
|
||||
|
||||
private static Map<BlockPos, CompoundTag> readTileEntitiesFromNBT_v1(ListTag tagList) {
|
||||
Map<BlockPos, CompoundTag> tileMap = new HashMap<>();
|
||||
final int size = tagList.size();
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
CompoundTag tag = tagList.getCompound(i);
|
||||
CompoundTag tileNbt = tag.getCompound("TileNBT");
|
||||
|
||||
// Note: This within-schematic relative position is not inside the tile tag!
|
||||
BlockPos pos = NbtUtils.readBlockPos(tag);
|
||||
|
||||
if (pos != null && !tileNbt.isEmpty()) {
|
||||
// Update the correct position to the entity NBT, where it is stored in version 2
|
||||
NbtUtils.writeBlockPosToTag(pos, tileNbt);
|
||||
tileMap.put(pos, tileNbt);
|
||||
}
|
||||
}
|
||||
|
||||
return tileMap;
|
||||
}
|
||||
}
|
||||
|
||||
public record EntityInfo(Vec3 posVec, CompoundTag nbt) {
|
||||
public EntityInfo(Vec3 posVec, CompoundTag nbt) {
|
||||
this.posVec = posVec;
|
||||
|
||||
if (nbt.contains("SleepingX", Tag.TAG_INT)) {
|
||||
nbt.putInt("SleepingX", Mth.floor(posVec.x));
|
||||
}
|
||||
if (nbt.contains("SleepingY", Tag.TAG_INT)) {
|
||||
nbt.putInt("SleepingY", Mth.floor(posVec.y));
|
||||
}
|
||||
if (nbt.contains("SleepingZ", Tag.TAG_INT)) {
|
||||
nbt.putInt("SleepingZ", Mth.floor(posVec.z));
|
||||
}
|
||||
|
||||
this.nbt = nbt;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics;
|
||||
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.FileType;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.NbtUtils;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.Schema;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
|
||||
public record SchematicMetadata(
|
||||
String name,
|
||||
String author,
|
||||
String description,
|
||||
Vec3i enclosingSize,
|
||||
long timeCreated,
|
||||
long timeModified,
|
||||
int minecraftDataVersion,
|
||||
int schematicVersion,
|
||||
Schema schema,
|
||||
FileType type,
|
||||
int regionCount,
|
||||
long totalVolume,
|
||||
long totalBlocks,
|
||||
@Nullable int[] thumbnailPixelData
|
||||
) {
|
||||
@NotNull
|
||||
@Contract("_, _, _, _ -> new")
|
||||
public static SchematicMetadata readFromNBT(@NotNull CompoundTag nbt, int version, int minecraftDataVersion, FileType fileType) {
|
||||
String name = nbt.getString("Name");
|
||||
String author = nbt.getString("Author");
|
||||
String description = nbt.getString("Description");
|
||||
int regionCount = nbt.getInt("RegionCount");
|
||||
long timeCreated = nbt.getLong("TimeCreated");
|
||||
long timeModified = nbt.getLong("TimeModified");
|
||||
|
||||
long totalVolume = -1;
|
||||
if (nbt.contains("TotalVolume", Tag.TAG_ANY_NUMERIC)) {
|
||||
totalVolume = nbt.getInt("TotalVolume");
|
||||
}
|
||||
|
||||
long totalBlocks = -1;
|
||||
if (nbt.contains("TotalBlocks", Tag.TAG_ANY_NUMERIC)) {
|
||||
totalBlocks = nbt.getInt("TotalBlocks");
|
||||
}
|
||||
|
||||
Vec3i enclosingSize = Vec3i.ZERO;
|
||||
if (nbt.contains("EnclosingSize", Tag.TAG_COMPOUND)) {
|
||||
enclosingSize = Objects.requireNonNullElse(NbtUtils.readVec3iFromTag(nbt.getCompound("EnclosingSize")), Vec3i.ZERO);
|
||||
}
|
||||
|
||||
int[] thumbnailPixelData = null;
|
||||
if (nbt.contains("PreviewImageData", Tag.TAG_INT_ARRAY)) {
|
||||
thumbnailPixelData = nbt.getIntArray("PreviewImageData");
|
||||
}
|
||||
|
||||
return new SchematicMetadata(name, author, description, enclosingSize, timeCreated, timeModified, minecraftDataVersion, version, Schema.getSchemaByDataVersion(minecraftDataVersion), fileType, regionCount, totalVolume, totalBlocks, thumbnailPixelData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.leavesmc.leaves.LeavesConfig;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesCustomPayload;
|
||||
import org.leavesmc.leaves.protocol.core.LeavesProtocol;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolHandler;
|
||||
import org.leavesmc.leaves.protocol.core.ProtocolUtils;
|
||||
import org.leavesmc.leaves.protocol.servux.PacketSplitter;
|
||||
import org.leavesmc.leaves.protocol.servux.ServuxProtocol;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.placement.SchematicPlacement;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.NbtUtils;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.ReplaceBehavior;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@LeavesProtocol(namespace = "servux")
|
||||
public class ServuxLitematicsProtocol {
|
||||
|
||||
public static final ResourceLocation CHANNEL = ServuxProtocol.id("litematics");
|
||||
public static final int PROTOCOL_VERSION = 1;
|
||||
|
||||
private static final CompoundTag metadata = new CompoundTag();
|
||||
private static final Map<UUID, Long> playerSession = new HashMap<>();
|
||||
|
||||
@ProtocolHandler.Init
|
||||
public static void init() {
|
||||
metadata.putString("name", "litematic_data");
|
||||
metadata.putString("id", CHANNEL.toString());
|
||||
metadata.putInt("version", PROTOCOL_VERSION);
|
||||
metadata.putString("servux", ServuxProtocol.SERVUX_STRING);
|
||||
}
|
||||
|
||||
public static boolean hasPermission(ServerPlayer player) {
|
||||
CraftPlayer bukkitEntity = player.getBukkitEntity();
|
||||
return bukkitEntity.hasPermission("leaves.protocol.litematics");
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return LeavesConfig.protocol.servux.litematicsProtocol;
|
||||
}
|
||||
|
||||
public static void encodeServerData(ServerPlayer player, @NotNull ServuxLitematicaPayload packet) {
|
||||
if (packet.packetType.equals(ServuxLitematicaPayloadType.PACKET_S2C_NBT_RESPONSE_START)) {
|
||||
FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer());
|
||||
buffer.writeVarInt(packet.getTransactionId());
|
||||
buffer.writeNbt(packet.getCompound());
|
||||
PacketSplitter.send(ServuxLitematicsProtocol::sendWithSplitter, buffer, player);
|
||||
} else {
|
||||
ProtocolUtils.sendPayloadPacket(player, packet);
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendWithSplitter(ServerPlayer player, FriendlyByteBuf buf) {
|
||||
ServuxLitematicaPayload payload = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_NBT_RESPONSE_DATA);
|
||||
payload.buffer = buf;
|
||||
payload.nbt = new CompoundTag();
|
||||
encodeServerData(player, payload);
|
||||
}
|
||||
|
||||
@ProtocolHandler.PayloadReceiver(payload = ServuxLitematicaPayload.class, payloadId = "litematics")
|
||||
public static void onPacketReceive(ServerPlayer player, ServuxLitematicaPayload payload) {
|
||||
if (!isEnabled() || !hasPermission(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (payload.packetType) {
|
||||
case PACKET_C2S_METADATA_REQUEST -> {
|
||||
ServuxLitematicaPayload send = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_METADATA);
|
||||
send.nbt.merge(metadata);
|
||||
encodeServerData(player, send);
|
||||
}
|
||||
|
||||
case PACKET_C2S_BULK_ENTITY_NBT_REQUEST -> onBulkEntityRequest(player, payload.getChunkPos(), payload.getCompound());
|
||||
|
||||
case PACKET_C2S_NBT_RESPONSE_DATA -> {
|
||||
ServuxProtocol.LOGGER.debug("nbt response data");
|
||||
UUID uuid = player.getUUID();
|
||||
Long session = playerSession.getOrDefault(uuid, new Random().nextLong());
|
||||
playerSession.put(uuid, session);
|
||||
FriendlyByteBuf fullPacket = PacketSplitter.receive(session, payload.getBuffer());
|
||||
if (fullPacket == null) {
|
||||
ServuxProtocol.LOGGER.debug("packet is none");
|
||||
return;
|
||||
}
|
||||
playerSession.remove(uuid);
|
||||
fullPacket.readVarInt();
|
||||
CompoundTag compoundTag = fullPacket.readNbt();
|
||||
if (compoundTag == null) {
|
||||
ServuxProtocol.LOGGER.error("cannot read nbt tag from packet");
|
||||
return;
|
||||
}
|
||||
handleClientPasteRequest(player, compoundTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void onBulkEntityRequest(ServerPlayer player, ChunkPos chunkPos, CompoundTag req) {
|
||||
if (req == null || req.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerLevel world = player.serverLevel();
|
||||
ChunkAccess chunk = world.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.FULL, false);
|
||||
|
||||
if (chunk == null) {
|
||||
return;
|
||||
}
|
||||
if ((req.contains("Task") && req.getString("Task").equals("BulkEntityRequest")) ||
|
||||
!req.contains("Task")) {
|
||||
ServuxProtocol.LOGGER.debug("litematic_data: Sending Bulk NBT Data for ChunkPos [{}] to player {}", chunkPos, player.getName().getString());
|
||||
|
||||
long timeStart = System.currentTimeMillis();
|
||||
ListTag tileList = new ListTag();
|
||||
ListTag entityList = new ListTag();
|
||||
int minY = req.getInt("minY");
|
||||
int maxY = req.getInt("maxY");
|
||||
BlockPos pos1 = new BlockPos(chunkPos.getMinBlockX(), minY, chunkPos.getMinBlockZ());
|
||||
BlockPos pos2 = new BlockPos(chunkPos.getMaxBlockX(), maxY, chunkPos.getMaxBlockZ());
|
||||
AABB bb = AABB.encapsulatingFullBlocks(pos1, pos2);
|
||||
Set<BlockPos> teSet = chunk.getBlockEntitiesPos();
|
||||
List<Entity> entities = world.getEntitiesOfClass(Entity.class, bb, e -> !(e instanceof Player));
|
||||
for (BlockPos tePos : teSet) {
|
||||
if ((tePos.getX() < chunkPos.getMinBlockX() || tePos.getX() > chunkPos.getMaxBlockX()) ||
|
||||
(tePos.getZ() < chunkPos.getMinBlockZ() || tePos.getZ() > chunkPos.getMaxBlockZ()) ||
|
||||
(tePos.getY() < minY || tePos.getY() > maxY)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockEntity be = world.getBlockEntity(tePos);
|
||||
CompoundTag beTag = be != null ? be.saveWithId(player.registryAccess()) : new CompoundTag();
|
||||
tileList.add(beTag);
|
||||
}
|
||||
|
||||
for (Entity entity : entities) {
|
||||
CompoundTag entTag = new CompoundTag();
|
||||
|
||||
if (entity.save(entTag)) {
|
||||
Vec3 posVec = new Vec3(entity.getX() - pos1.getX(), entity.getY() - pos1.getY(), entity.getZ() - pos1.getZ());
|
||||
NbtUtils.writeEntityPositionToTag(posVec, entTag);
|
||||
entTag.putInt("entityId", entity.getId());
|
||||
entityList.add(entTag);
|
||||
}
|
||||
}
|
||||
|
||||
CompoundTag output = new CompoundTag();
|
||||
output.putString("Task", "BulkEntityReply");
|
||||
output.put("TileEntities", tileList);
|
||||
output.put("Entities", entityList);
|
||||
output.putInt("chunkX", chunkPos.x);
|
||||
output.putInt("chunkZ", chunkPos.z);
|
||||
ServuxProtocol.LOGGER.debug("process bulk entity used: {}ms", System.currentTimeMillis() - timeStart);
|
||||
|
||||
ServuxLitematicaPayload send = new ServuxLitematicaPayload(ServuxLitematicaPayloadType.PACKET_S2C_NBT_RESPONSE_START);
|
||||
send.nbt.merge(output);
|
||||
encodeServerData(player, send);
|
||||
}
|
||||
}
|
||||
|
||||
public static void handleClientPasteRequest(ServerPlayer player, @NotNull CompoundTag tags) {
|
||||
if (tags.getString("Task").equals("LitematicaPaste")) {
|
||||
ServuxProtocol.LOGGER.debug("litematic_data: Servux Paste request from player {}", player.getName().getString());
|
||||
ServerLevel serverLevel = player.serverLevel();
|
||||
long timeStart = System.currentTimeMillis();
|
||||
SchematicPlacement placement = SchematicPlacement.createFromNbt(tags);
|
||||
ReplaceBehavior replaceMode = ReplaceBehavior.fromStringStatic(tags.getString("ReplaceMode"));
|
||||
MinecraftServer server = MinecraftServer.getServer();
|
||||
server.scheduleOnMain(() -> {
|
||||
placement.pasteTo(serverLevel, replaceMode);
|
||||
long timeElapsed = System.currentTimeMillis() - timeStart;
|
||||
player.getBukkitEntity().sendActionBar(
|
||||
Component.text("Pasted ")
|
||||
.append(Component.text(placement.getName()).color(NamedTextColor.AQUA))
|
||||
.append(Component.text(" to world "))
|
||||
.append(Component.text(serverLevel.serverLevelData.getLevelName()).color(NamedTextColor.LIGHT_PURPLE))
|
||||
.append(Component.text(" in "))
|
||||
.append(Component.text(timeElapsed).color(NamedTextColor.GREEN))
|
||||
.append(Component.text("ms"))
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public enum ServuxLitematicaPayloadType {
|
||||
PACKET_S2C_METADATA(1),
|
||||
PACKET_C2S_METADATA_REQUEST(2),
|
||||
PACKET_C2S_BLOCK_ENTITY_REQUEST(3),
|
||||
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),
|
||||
// For Packet Splitter (Oversize Packets, C2S)
|
||||
PACKET_C2S_NBT_RESPONSE_START(12),
|
||||
PACKET_C2S_NBT_RESPONSE_DATA(13);
|
||||
|
||||
private static final class Helper {
|
||||
static Map<Integer, ServuxLitematicaPayloadType> ID_TO_TYPE = new HashMap<>();
|
||||
}
|
||||
|
||||
public final int type;
|
||||
|
||||
ServuxLitematicaPayloadType(int type) {
|
||||
this.type = type;
|
||||
ServuxLitematicaPayloadType.Helper.ID_TO_TYPE.put(type, this);
|
||||
}
|
||||
|
||||
public static ServuxLitematicaPayloadType fromId(int id) {
|
||||
return ServuxLitematicaPayloadType.Helper.ID_TO_TYPE.get(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServuxLitematicaPayload implements LeavesCustomPayload<ServuxLitematicaPayload> {
|
||||
|
||||
private final ServuxLitematicaPayloadType packetType;
|
||||
private final int transactionId;
|
||||
private int entityId;
|
||||
private BlockPos pos;
|
||||
private CompoundTag nbt;
|
||||
private ChunkPos chunkPos;
|
||||
private FriendlyByteBuf buffer;
|
||||
public static final int PROTOCOL_VERSION = 1;
|
||||
|
||||
private ServuxLitematicaPayload(ServuxLitematicaPayloadType type) {
|
||||
this.packetType = type;
|
||||
this.transactionId = -1;
|
||||
this.entityId = -1;
|
||||
this.pos = BlockPos.ZERO;
|
||||
this.chunkPos = ChunkPos.ZERO;
|
||||
this.nbt = new CompoundTag();
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return PROTOCOL_VERSION;
|
||||
}
|
||||
|
||||
public int getTransactionId() {
|
||||
return this.transactionId;
|
||||
}
|
||||
|
||||
public int getEntityId() {
|
||||
return this.entityId;
|
||||
}
|
||||
|
||||
public BlockPos getPos() {
|
||||
return this.pos;
|
||||
}
|
||||
|
||||
public CompoundTag getCompound() {
|
||||
return this.nbt;
|
||||
}
|
||||
|
||||
public ChunkPos getChunkPos() {
|
||||
return this.chunkPos;
|
||||
}
|
||||
|
||||
public FriendlyByteBuf getBuffer() {
|
||||
return this.buffer;
|
||||
}
|
||||
|
||||
public boolean hasBuffer() {
|
||||
return this.buffer != null && this.buffer.isReadable();
|
||||
}
|
||||
|
||||
public boolean hasNbt() {
|
||||
return this.nbt != null && !this.nbt.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return !this.hasBuffer() && !this.hasNbt();
|
||||
}
|
||||
|
||||
@New
|
||||
public static ServuxLitematicaPayload decode(ResourceLocation location, FriendlyByteBuf input) {
|
||||
ServuxLitematicaPayloadType type = ServuxLitematicaPayloadType.fromId(input.readVarInt());
|
||||
if (type == null) {
|
||||
throw new IllegalStateException("invalid packet type received");
|
||||
}
|
||||
|
||||
ServuxLitematicaPayload payload = new ServuxLitematicaPayload(type);
|
||||
switch (type) {
|
||||
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> {
|
||||
input.readVarInt();
|
||||
payload.pos = input.readBlockPos().immutable();
|
||||
}
|
||||
|
||||
case PACKET_C2S_ENTITY_REQUEST -> {
|
||||
input.readVarInt();
|
||||
payload.entityId = input.readVarInt();
|
||||
}
|
||||
|
||||
case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> {
|
||||
payload.pos = input.readBlockPos().immutable();
|
||||
payload.nbt = input.readNbt();
|
||||
}
|
||||
|
||||
case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> {
|
||||
payload.entityId = input.readVarInt();
|
||||
payload.nbt = input.readNbt();
|
||||
}
|
||||
|
||||
case PACKET_C2S_BULK_ENTITY_NBT_REQUEST -> {
|
||||
payload.chunkPos = input.readChunkPos();
|
||||
payload.nbt = input.readNbt();
|
||||
}
|
||||
|
||||
case PACKET_C2S_NBT_RESPONSE_DATA, PACKET_S2C_NBT_RESPONSE_DATA -> payload.buffer = new FriendlyByteBuf(input.readBytes(input.readableBytes()));
|
||||
|
||||
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA -> payload.nbt = input.readNbt();
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(FriendlyByteBuf output) {
|
||||
output.writeVarInt(this.packetType.type);
|
||||
|
||||
switch (this.packetType) {
|
||||
case PACKET_C2S_BLOCK_ENTITY_REQUEST -> {
|
||||
output.writeVarInt(this.transactionId);
|
||||
output.writeBlockPos(this.pos);
|
||||
}
|
||||
|
||||
case PACKET_C2S_ENTITY_REQUEST -> {
|
||||
output.writeVarInt(this.transactionId);
|
||||
output.writeVarInt(this.entityId);
|
||||
}
|
||||
|
||||
case PACKET_S2C_BLOCK_NBT_RESPONSE_SIMPLE -> {
|
||||
output.writeBlockPos(this.pos);
|
||||
output.writeNbt(this.nbt);
|
||||
}
|
||||
|
||||
case PACKET_S2C_ENTITY_NBT_RESPONSE_SIMPLE -> {
|
||||
output.writeVarInt(this.entityId);
|
||||
output.writeNbt(this.nbt);
|
||||
}
|
||||
|
||||
case PACKET_C2S_BULK_ENTITY_NBT_REQUEST -> {
|
||||
output.writeChunkPos(this.chunkPos);
|
||||
output.writeNbt(this.nbt);
|
||||
}
|
||||
|
||||
case PACKET_S2C_NBT_RESPONSE_DATA, PACKET_C2S_NBT_RESPONSE_DATA -> output.writeBytes(this.buffer.readBytes(this.buffer.readableBytes()));
|
||||
|
||||
case PACKET_C2S_METADATA_REQUEST, PACKET_S2C_METADATA -> output.writeNbt(this.nbt);
|
||||
|
||||
default -> ServuxProtocol.LOGGER.error("ServuxLitematicaPacket#toPacket: Unknown packet type!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation id() {
|
||||
return CHANNEL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.container;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
|
||||
public class LitematicaBitArray {
|
||||
/**
|
||||
* The long array that is used to store the data for this BitArray.
|
||||
*/
|
||||
private final long[] longArray;
|
||||
/**
|
||||
* Number of bits a single entry takes up
|
||||
*/
|
||||
private final int bitsPerEntry;
|
||||
/**
|
||||
* The maximum value for a single entry. This also works as a bitmask for a single entry.
|
||||
* For instance, if bitsPerEntry were 5, this value would be 31 (ie, {@code 0b00011111}).
|
||||
*/
|
||||
private final long maxEntryValue;
|
||||
/**
|
||||
* Number of entries in this array (<b>not</b> the length of the long array that internally backs this array)
|
||||
*/
|
||||
private final long arraySize;
|
||||
|
||||
public LitematicaBitArray(int bitsPerEntryIn, long arraySizeIn) {
|
||||
this(bitsPerEntryIn, arraySizeIn, null);
|
||||
}
|
||||
|
||||
public LitematicaBitArray(int bitsPerEntryIn, long arraySizeIn, @Nullable long[] longArrayIn) {
|
||||
Validate.inclusiveBetween(1L, 32L, bitsPerEntryIn);
|
||||
this.arraySize = arraySizeIn;
|
||||
this.bitsPerEntry = bitsPerEntryIn;
|
||||
this.maxEntryValue = (1L << bitsPerEntryIn) - 1L;
|
||||
|
||||
this.longArray = Objects.requireNonNullElseGet(longArrayIn, () -> new long[(int) (roundUp(arraySizeIn * bitsPerEntryIn, 64L) / 64L)]);
|
||||
}
|
||||
|
||||
public void setAt(long index, int value) {
|
||||
long startOffset = index * (long) this.bitsPerEntry;
|
||||
int startArrIndex = (int) (startOffset >> 6); // startOffset / 64
|
||||
int endArrIndex = (int) (((index + 1L) * (long) this.bitsPerEntry - 1L) >> 6);
|
||||
int startBitOffset = (int) (startOffset & 0x3F); // startOffset % 64
|
||||
this.longArray[startArrIndex] = this.longArray[startArrIndex] & ~(this.maxEntryValue << startBitOffset) | ((long) value & this.maxEntryValue) << startBitOffset;
|
||||
|
||||
if (startArrIndex != endArrIndex) {
|
||||
int endOffset = 64 - startBitOffset;
|
||||
int j1 = this.bitsPerEntry - endOffset;
|
||||
this.longArray[endArrIndex] = this.longArray[endArrIndex] >>> j1 << j1 | ((long) value & this.maxEntryValue) >> endOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public int getAt(long index) {
|
||||
long startOffset = index * (long) this.bitsPerEntry;
|
||||
int startArrIndex = (int) (startOffset >> 6); // startOffset / 64
|
||||
int endArrIndex = (int) (((index + 1L) * (long) this.bitsPerEntry - 1L) >> 6);
|
||||
int startBitOffset = (int) (startOffset & 0x3F); // startOffset % 64
|
||||
|
||||
if (startArrIndex == endArrIndex) {
|
||||
return (int) (this.longArray[startArrIndex] >>> startBitOffset & this.maxEntryValue);
|
||||
} else {
|
||||
int endOffset = 64 - startBitOffset;
|
||||
return (int) ((this.longArray[startArrIndex] >>> startBitOffset | this.longArray[endArrIndex] << endOffset) & this.maxEntryValue);
|
||||
}
|
||||
}
|
||||
|
||||
public long size() {
|
||||
return this.arraySize;
|
||||
}
|
||||
|
||||
public static long roundUp(long value, long interval) {
|
||||
if (interval == 0L) {
|
||||
return 0L;
|
||||
} else if (value == 0L) {
|
||||
return interval;
|
||||
} else {
|
||||
if (value < 0L) {
|
||||
interval *= -1L;
|
||||
}
|
||||
|
||||
long remainder = value % interval;
|
||||
|
||||
return remainder == 0L ? value : value + interval - remainder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.container;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class LitematicaBlockStateContainer {
|
||||
|
||||
public static final BlockState AIR_BLOCK_STATE = Blocks.AIR.defaultBlockState();
|
||||
protected LitematicaBitArray storage;
|
||||
protected LitematicaBlockStatePalette palette;
|
||||
protected final Vec3i size;
|
||||
protected final int sizeX;
|
||||
protected final int sizeY;
|
||||
protected final int sizeZ;
|
||||
protected final int sizeLayer;
|
||||
protected final long totalVolume;
|
||||
protected int bits;
|
||||
|
||||
public LitematicaBlockStateContainer(int sizeX, int sizeY, int sizeZ, int bits, @Nullable long[] backingLongArray) {
|
||||
this.sizeX = sizeX;
|
||||
this.sizeY = sizeY;
|
||||
this.sizeZ = sizeZ;
|
||||
this.sizeLayer = sizeX * sizeZ;
|
||||
this.totalVolume = (long) this.sizeX * (long) this.sizeY * (long) this.sizeZ;
|
||||
this.size = new Vec3i(this.sizeX, this.sizeY, this.sizeZ);
|
||||
|
||||
this.setBits(bits, backingLongArray);
|
||||
}
|
||||
|
||||
public Vec3i getSize() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public LitematicaBitArray getArray() {
|
||||
return this.storage;
|
||||
}
|
||||
|
||||
public BlockState get(int x, int y, int z) {
|
||||
BlockState state = this.palette.getBlockState(this.storage.getAt(this.getIndex(x, y, z)));
|
||||
return state == null ? AIR_BLOCK_STATE : state;
|
||||
}
|
||||
|
||||
protected int getIndex(int x, int y, int z) {
|
||||
return (y * this.sizeLayer) + z * this.sizeX + x;
|
||||
}
|
||||
|
||||
protected void setBits(int bitsIn, @Nullable long[] backingLongArray) {
|
||||
if (bitsIn != this.bits) {
|
||||
this.bits = bitsIn;
|
||||
|
||||
if (this.bits <= 4) {
|
||||
this.bits = Math.max(2, this.bits);
|
||||
this.palette = new LitematicaBlockStatePaletteLinear(this.bits, this);
|
||||
} else {
|
||||
this.palette = new LitematicaBlockStatePaletteHashMap(this.bits, this);
|
||||
}
|
||||
|
||||
this.palette.idFor(AIR_BLOCK_STATE);
|
||||
|
||||
if (backingLongArray != null) {
|
||||
this.storage = new LitematicaBitArray(this.bits, this.totalVolume, backingLongArray);
|
||||
} else {
|
||||
this.storage = new LitematicaBitArray(this.bits, this.totalVolume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int onResize(int bits, BlockState state) {
|
||||
LitematicaBitArray oldStorage = this.storage;
|
||||
LitematicaBlockStatePalette oldPalette = this.palette;
|
||||
final long storageLength = oldStorage.size();
|
||||
|
||||
this.setBits(bits, null);
|
||||
|
||||
LitematicaBitArray newStorage = this.storage;
|
||||
|
||||
for (long index = 0; index < storageLength; ++index) {
|
||||
newStorage.setAt(index, oldStorage.getAt(index));
|
||||
}
|
||||
|
||||
this.palette.readFromNBT(oldPalette.writeToNBT());
|
||||
|
||||
return this.palette.idFor(state);
|
||||
}
|
||||
|
||||
public LitematicaBlockStatePalette getPalette() {
|
||||
return this.palette;
|
||||
}
|
||||
|
||||
public static LitematicaBlockStateContainer createFrom(ListTag palette, long[] blockStates, BlockPos size) {
|
||||
int bits = Math.max(2, Integer.SIZE - Integer.numberOfLeadingZeros(palette.size() - 1));
|
||||
LitematicaBlockStateContainer container = new LitematicaBlockStateContainer(size.getX(), size.getY(), size.getZ(), bits, blockStates);
|
||||
container.palette.readFromNBT(palette);
|
||||
return container;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.container;
|
||||
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface LitematicaBlockStatePalette {
|
||||
/**
|
||||
* Gets the palette id for the given block state and adds
|
||||
* the state to the palette if it doesn't exist there yet.
|
||||
*/
|
||||
int idFor(BlockState state);
|
||||
|
||||
/**
|
||||
* Gets the block state by the palette id.
|
||||
*/
|
||||
@Nullable
|
||||
BlockState getBlockState(int indexKey);
|
||||
|
||||
void readFromNBT(ListTag tagList);
|
||||
|
||||
ListTag writeToNBT();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.container;
|
||||
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.Int2ObjectBiMap;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class LitematicaBlockStatePaletteHashMap implements LitematicaBlockStatePalette {
|
||||
|
||||
private final Int2ObjectBiMap<BlockState> statePaletteMap;
|
||||
private final LitematicaBlockStateContainer blockStateContainer;
|
||||
private final int bits;
|
||||
|
||||
public LitematicaBlockStatePaletteHashMap(int bitsIn, LitematicaBlockStateContainer blockStateContainer) {
|
||||
this.bits = bitsIn;
|
||||
this.blockStateContainer = blockStateContainer;
|
||||
this.statePaletteMap = Int2ObjectBiMap.create(1 << bitsIn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int idFor(BlockState state) {
|
||||
int i = this.statePaletteMap.getRawId(state);
|
||||
|
||||
if (i == -1) {
|
||||
i = this.statePaletteMap.add(state);
|
||||
|
||||
if (i >= (1 << this.bits)) {
|
||||
i = this.blockStateContainer.onResize(this.bits + 1, state);
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BlockState getBlockState(int indexKey) {
|
||||
return this.statePaletteMap.get(indexKey);
|
||||
}
|
||||
|
||||
private void requestNewId(BlockState state) {
|
||||
final int origId = this.statePaletteMap.add(state);
|
||||
|
||||
if (origId >= (1 << this.bits)) {
|
||||
int newId = this.blockStateContainer.onResize(this.bits + 1, LitematicaBlockStateContainer.AIR_BLOCK_STATE);
|
||||
|
||||
if (newId <= origId) {
|
||||
this.statePaletteMap.add(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFromNBT(ListTag tagList) {
|
||||
Registry<Block> lookup = MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.BLOCK);
|
||||
|
||||
final int size = tagList.size();
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
CompoundTag tag = tagList.getCompound(i);
|
||||
BlockState state = NbtUtils.readBlockState(lookup, tag);
|
||||
|
||||
if (i > 0 || state != LitematicaBlockStateContainer.AIR_BLOCK_STATE) {
|
||||
this.requestNewId(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListTag writeToNBT() {
|
||||
ListTag tagList = new ListTag();
|
||||
|
||||
for (int id = 0; id < this.statePaletteMap.size(); ++id) {
|
||||
BlockState state = this.statePaletteMap.get(id);
|
||||
|
||||
if (state == null) {
|
||||
state = LitematicaBlockStateContainer.AIR_BLOCK_STATE;
|
||||
}
|
||||
|
||||
CompoundTag tag = NbtUtils.writeBlockState(state);
|
||||
tagList.add(tag);
|
||||
}
|
||||
|
||||
return tagList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.container;
|
||||
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class LitematicaBlockStatePaletteLinear implements LitematicaBlockStatePalette {
|
||||
|
||||
private final BlockState[] states;
|
||||
private final LitematicaBlockStateContainer blockStateContainer;
|
||||
private final int bits;
|
||||
private int currentSize;
|
||||
|
||||
public LitematicaBlockStatePaletteLinear(int bitsIn, LitematicaBlockStateContainer blockStateContainer) {
|
||||
this.states = new BlockState[1 << bitsIn];
|
||||
this.bits = bitsIn;
|
||||
this.blockStateContainer = blockStateContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int idFor(BlockState state) {
|
||||
for (int i = 0; i < this.currentSize; ++i) {
|
||||
if (this.states[i] == state) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
final int size = this.currentSize;
|
||||
|
||||
if (size < this.states.length) {
|
||||
this.states[size] = state;
|
||||
++this.currentSize;
|
||||
return size;
|
||||
} else {
|
||||
return this.blockStateContainer.onResize(this.bits + 1, state);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BlockState getBlockState(int indexKey) {
|
||||
return indexKey >= 0 && indexKey < this.currentSize ? this.states[indexKey] : null;
|
||||
}
|
||||
|
||||
private void requestNewId(BlockState state) {
|
||||
final int size = this.currentSize;
|
||||
|
||||
if (size < this.states.length) {
|
||||
this.states[size] = state;
|
||||
++this.currentSize;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFromNBT(ListTag tagList) {
|
||||
Registry<Block> lookup = MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.BLOCK);
|
||||
|
||||
final int size = tagList.size();
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
CompoundTag tag = tagList.getCompound(i);
|
||||
BlockState state = NbtUtils.readBlockState(lookup, tag);
|
||||
|
||||
if (i > 0 || state != LitematicaBlockStateContainer.AIR_BLOCK_STATE) {
|
||||
this.requestNewId(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListTag writeToNBT() {
|
||||
ListTag tagList = new ListTag();
|
||||
|
||||
for (int id = 0; id < this.currentSize; ++id) {
|
||||
BlockState state = this.states[id];
|
||||
|
||||
if (state == null) {
|
||||
state = LitematicaBlockStateContainer.AIR_BLOCK_STATE;
|
||||
}
|
||||
|
||||
CompoundTag tag = NbtUtils.writeBlockState(state);
|
||||
tagList.add(tag);
|
||||
}
|
||||
|
||||
return tagList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.placement;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import net.minecraft.core.BlockBox;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.block.Mirror;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.leavesmc.leaves.protocol.servux.ServuxProtocol;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.LitematicaSchematic;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.selection.Box;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.IntBoundingBox;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.PositionUtils;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.ReplaceBehavior;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.utils.SchematicPlacingUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class SchematicPlacement {
|
||||
|
||||
private final Map<String, SubRegionPlacement> relativeSubRegionPlacements = new HashMap<>();
|
||||
private final LitematicaSchematic schematic;
|
||||
private final BlockPos origin;
|
||||
private final String name;
|
||||
private Rotation rotation = Rotation.NONE;
|
||||
private Mirror mirror = Mirror.NONE;
|
||||
|
||||
private SchematicPlacement(LitematicaSchematic schematic, BlockPos origin, String name) {
|
||||
this.schematic = schematic;
|
||||
this.origin = origin;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static SchematicPlacement createFromNbt(CompoundTag tags) {
|
||||
SchematicPlacement placement = new SchematicPlacement(
|
||||
LitematicaSchematic.readFromNBT(tags.getCompound("Schematics")),
|
||||
NbtUtils.readBlockPos(tags, "Origin").orElseThrow(),
|
||||
tags.getString("Name")
|
||||
);
|
||||
placement.mirror = Mirror.values()[tags.getInt("Mirror")];
|
||||
placement.rotation = Rotation.values()[tags.getInt("Rotation")];
|
||||
for (String name : tags.getCompound("SubRegions").getAllKeys()) {
|
||||
CompoundTag compound = tags.getCompound("SubRegions").getCompound(name);
|
||||
var sub = new SubRegionPlacement(
|
||||
compound.getString("Name"),
|
||||
NbtUtils.readBlockPos(compound, "Pos").orElseThrow(),
|
||||
Rotation.values()[compound.getInt("Rotation")],
|
||||
Mirror.values()[compound.getInt("Mirror")],
|
||||
compound.getBoolean("Enabled"),
|
||||
compound.getBoolean("IgnoreEntities")
|
||||
);
|
||||
placement.relativeSubRegionPlacements.put(name, sub);
|
||||
}
|
||||
return placement;
|
||||
}
|
||||
|
||||
public boolean ignoreEntities() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public LitematicaSchematic getSchematic() {
|
||||
return schematic;
|
||||
}
|
||||
|
||||
public BlockPos getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
public Rotation getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public Mirror getMirror() {
|
||||
return mirror;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SubRegionPlacement getRelativeSubRegionPlacement(String areaName) {
|
||||
return this.relativeSubRegionPlacements.get(areaName);
|
||||
}
|
||||
|
||||
public ImmutableMap<String, Box> getSubRegionBoxes(SubRegionPlacement.RequiredEnabled required) {
|
||||
ImmutableMap.Builder<String, Box> builder = ImmutableMap.builder();
|
||||
Map<String, BlockPos> areaSizes = this.schematic.getAreaSizes();
|
||||
|
||||
for (Map.Entry<String, SubRegionPlacement> entry : this.relativeSubRegionPlacements.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
BlockPos areaSize = areaSizes.get(name);
|
||||
|
||||
if (areaSize == null) {
|
||||
ServuxProtocol.LOGGER.warn("SchematicPlacement.getSubRegionBoxes(): Size for sub-region '{}' not found in the schematic '{}'", name, this.schematic.metadata().name());
|
||||
continue;
|
||||
}
|
||||
|
||||
SubRegionPlacement placement = entry.getValue();
|
||||
|
||||
if (placement.matchesRequirement(required)) {
|
||||
putBoxPosIntoBuilder(builder, name, areaSize, placement);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void putBoxPosIntoBuilder(ImmutableMap.Builder<String, Box> builder, String name, BlockPos areaSize, SubRegionPlacement placement) {
|
||||
BlockPos boxOriginRelative = placement.pos();
|
||||
|
||||
BlockPos boxOriginAbsolute = PositionUtils.getTransformedBlockPos(boxOriginRelative, this.mirror, this.rotation).offset(this.origin);
|
||||
BlockPos pos2 = PositionUtils.getRelativeEndPositionFromAreaSize(areaSize);
|
||||
pos2 = PositionUtils.getTransformedBlockPos(pos2, this.mirror, this.rotation);
|
||||
pos2 = PositionUtils.getTransformedBlockPos(pos2, placement.mirror(), placement.rotation()).offset(boxOriginAbsolute);
|
||||
|
||||
builder.put(name, new Box(boxOriginAbsolute, pos2, name));
|
||||
}
|
||||
|
||||
public static IntBoundingBox getBoundsWithinChunkForBox(Box box, int chunkX, int chunkZ) {
|
||||
final int chunkXMin = chunkX << 4;
|
||||
final int chunkZMin = chunkZ << 4;
|
||||
final int chunkXMax = chunkXMin + 15;
|
||||
final int chunkZMax = chunkZMin + 15;
|
||||
BlockPos boxPos1 = box.pos1();
|
||||
BlockPos boxPos2 = box.pos2();
|
||||
if (boxPos1 == null || boxPos2 == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int x1 = boxPos1.getX();
|
||||
int x2 = boxPos2.getX();
|
||||
int y1 = boxPos1.getY();
|
||||
int y2 = boxPos2.getY();
|
||||
int z1 = boxPos1.getZ();
|
||||
int z2 = boxPos2.getZ();
|
||||
final int boxXMin = Math.min(x1, x2);
|
||||
final int boxZMin = Math.min(z1, z2);
|
||||
final int boxXMax = Math.max(x1, x2);
|
||||
final int boxZMax = Math.max(z1, z2);
|
||||
|
||||
boolean notOverlapping = boxXMin > chunkXMax || boxZMin > chunkZMax || boxXMax < chunkXMin || boxZMax < chunkZMin;
|
||||
|
||||
if (!notOverlapping) {
|
||||
final int xMin = Math.max(chunkXMin, boxXMin);
|
||||
final int yMin = Math.min(y1, y2);
|
||||
final int zMin = Math.max(chunkZMin, boxZMin);
|
||||
final int xMax = Math.min(chunkXMax, boxXMax);
|
||||
final int yMax = Math.max(y1, y2);
|
||||
final int zMax = Math.min(chunkZMax, boxZMax);
|
||||
|
||||
return new IntBoundingBox(xMin, yMin, zMin, xMax, yMax, zMax);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ImmutableMap<String, Box> getSubRegionBoxFor(String regionName, SubRegionPlacement.RequiredEnabled required) {
|
||||
SubRegionPlacement placement = this.relativeSubRegionPlacements.get(regionName);
|
||||
if (placement == null) return ImmutableMap.of();
|
||||
ImmutableMap.Builder<String, Box> builder = ImmutableMap.builder();
|
||||
Map<String, BlockPos> areaSizes = this.schematic.getAreaSizes();
|
||||
|
||||
if (placement.matchesRequirement(required)) {
|
||||
BlockPos areaSize = areaSizes.get(regionName);
|
||||
|
||||
if (areaSize != null) {
|
||||
putBoxPosIntoBuilder(builder, regionName, areaSize, placement);
|
||||
} else {
|
||||
ServuxProtocol.LOGGER.warn("SchematicPlacement.getSubRegionBoxFor(): Size for sub-region '{}' not found in the schematic '{}'", regionName, this.schematic.metadata().name());
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public Set<String> getRegionsTouchingChunk(int chunkX, int chunkZ) {
|
||||
ImmutableMap<String, Box> map = this.getSubRegionBoxes(SubRegionPlacement.RequiredEnabled.PLACEMENT_ENABLED);
|
||||
final int chunkXMin = chunkX << 4;
|
||||
final int chunkZMin = chunkZ << 4;
|
||||
final int chunkXMax = chunkXMin + 15;
|
||||
final int chunkZMax = chunkZMin + 15;
|
||||
Set<String> set = new HashSet<>();
|
||||
|
||||
for (Map.Entry<String, Box> entry : map.entrySet()) {
|
||||
Box box = entry.getValue();
|
||||
BlockPos boxPos1 = box.pos1();
|
||||
BlockPos boxPos2 = box.pos2();
|
||||
if (boxPos1 == null || boxPos2 == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int x1 = boxPos1.getX();
|
||||
int x2 = boxPos2.getX();
|
||||
int z1 = boxPos1.getZ();
|
||||
int z2 = boxPos2.getZ();
|
||||
final int boxXMin = Math.min(x1, x2);
|
||||
final int boxZMin = Math.min(z1, z2);
|
||||
final int boxXMax = Math.max(x1, x2);
|
||||
final int boxZMax = Math.max(z1, z2);
|
||||
|
||||
boolean notOverlapping = boxXMin > chunkXMax || boxZMin > chunkZMax || boxXMax < chunkXMin || boxZMax < chunkZMin;
|
||||
|
||||
if (!notOverlapping) {
|
||||
set.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public IntBoundingBox getBoxWithinChunkForRegion(String regionName, int chunkX, int chunkZ) {
|
||||
Box box = this.getSubRegionBoxFor(regionName, SubRegionPlacement.RequiredEnabled.PLACEMENT_ENABLED).get(regionName);
|
||||
return box != null ? getBoundsWithinChunkForBox(box, chunkX, chunkZ) : null;
|
||||
}
|
||||
|
||||
private Box getEnclosingBox() {
|
||||
ImmutableMap<String, Box> boxes = this.getSubRegionBoxes(SubRegionPlacement.RequiredEnabled.ANY);
|
||||
BlockPos pos1 = null;
|
||||
BlockPos pos2 = null;
|
||||
|
||||
for (Box box : boxes.values()) {
|
||||
BlockPos tmp;
|
||||
BlockPos boxPos1 = box.pos1();
|
||||
BlockPos boxPos2 = box.pos2();
|
||||
if (boxPos1 == null || boxPos2 == null) continue;
|
||||
tmp = PositionUtils.getMinCorner(boxPos1, boxPos2);
|
||||
|
||||
if (pos1 == null) {
|
||||
pos1 = tmp;
|
||||
} else if (tmp.getX() < pos1.getX() || tmp.getY() < pos1.getY() || tmp.getZ() < pos1.getZ()) {
|
||||
pos1 = PositionUtils.getMinCorner(tmp, pos1);
|
||||
}
|
||||
|
||||
tmp = PositionUtils.getMaxCorner(boxPos1, boxPos2);
|
||||
|
||||
if (pos2 == null) {
|
||||
pos2 = tmp;
|
||||
} else if (tmp.getX() > pos2.getX() || tmp.getY() > pos2.getY() || tmp.getZ() > pos2.getZ()) {
|
||||
pos2 = PositionUtils.getMaxCorner(tmp, pos2);
|
||||
}
|
||||
}
|
||||
|
||||
if (pos1 != null) {
|
||||
return new Box(pos1, pos2, "Enclosing Box (Servux)");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Stream<ChunkPos> streamChunkPos(@NotNull BlockBox box) {
|
||||
AABB aabb = box.aabb();
|
||||
int i = SectionPos.blockToSectionCoord(aabb.minX);
|
||||
int j = SectionPos.blockToSectionCoord(aabb.minZ);
|
||||
int k = SectionPos.blockToSectionCoord(aabb.maxX);
|
||||
int l = SectionPos.blockToSectionCoord(aabb.maxZ);
|
||||
return ChunkPos.rangeClosed(new ChunkPos(i, j), new ChunkPos(k, l));
|
||||
}
|
||||
|
||||
public void pasteTo(ServerLevel serverWorld, ReplaceBehavior replaceBehavior) {
|
||||
Box enclosingBox = this.getEnclosingBox();
|
||||
if (enclosingBox == null || enclosingBox.pos1() == null || enclosingBox.pos2() == null) {
|
||||
ServuxProtocol.LOGGER.error("receiver a null enclosing box");
|
||||
return;
|
||||
}
|
||||
streamChunkPos(Objects.requireNonNull(enclosingBox.toVanilla())).forEach(chunkPos ->
|
||||
SchematicPlacingUtils.placeToWorldWithinChunk(serverWorld, chunkPos, this, replaceBehavior, false)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.placement;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Mirror;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
import org.leavesmc.leaves.protocol.servux.ServuxProtocol;
|
||||
|
||||
public record SubRegionPlacement(
|
||||
String name,
|
||||
BlockPos pos,
|
||||
Rotation rotation,
|
||||
Mirror mirror,
|
||||
boolean enabled,
|
||||
boolean ignoreEntities
|
||||
) {
|
||||
public SubRegionPlacement(String name, BlockPos pos) {
|
||||
this(name, pos, Rotation.NONE, Mirror.NONE, true, false);
|
||||
}
|
||||
|
||||
public boolean matchesRequirement(RequiredEnabled required) {
|
||||
if (required == RequiredEnabled.ANY) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (required == RequiredEnabled.PLACEMENT_ENABLED) {
|
||||
return this.enabled();
|
||||
}
|
||||
|
||||
ServuxProtocol.LOGGER.warn("RequiredEnabled.RENDERING_ENABLED is not supported on server side!");
|
||||
return false;
|
||||
}
|
||||
|
||||
public enum RequiredEnabled {
|
||||
ANY,
|
||||
PLACEMENT_ENABLED,
|
||||
RENDERING_ENABLED
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.selection;
|
||||
|
||||
import net.minecraft.core.BlockBox;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public record Box(@Nullable BlockPos pos1, @Nullable BlockPos pos2, String name) {
|
||||
public Box() {
|
||||
this(BlockPos.ZERO, BlockPos.ZERO, "Unnamed");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockBox toVanilla() {
|
||||
if (pos1 != null && pos2 != null) {
|
||||
return new BlockBox(pos1, pos2);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.utils;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntitySpawnReason;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntityUtils {
|
||||
|
||||
@Nullable
|
||||
private static Entity createEntityFromNBTSingle(CompoundTag nbt, Level world) {
|
||||
try {
|
||||
Optional<Entity> optional = EntityType.create(nbt, world, EntitySpawnReason.LOAD);
|
||||
|
||||
if (optional.isPresent()) {
|
||||
Entity entity = optional.get();
|
||||
entity.setUUID(UUID.randomUUID());
|
||||
return entity;
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: This does NOT spawn any of the entities in the world!
|
||||
*
|
||||
* @param nbt ()
|
||||
* @param world ()
|
||||
* @return ()
|
||||
*/
|
||||
@Nullable
|
||||
public static Entity createEntityAndPassengersFromNBT(CompoundTag nbt, Level world) {
|
||||
Entity entity = createEntityFromNBTSingle(nbt, world);
|
||||
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
if (nbt.contains("Passengers", Tag.TAG_LIST)) {
|
||||
ListTag tagList = nbt.getList("Passengers", Tag.TAG_LIST);
|
||||
|
||||
for (int i = 0; i < tagList.size(); ++i) {
|
||||
Entity passenger = createEntityAndPassengersFromNBT(tagList.getCompound(i), world);
|
||||
|
||||
if (passenger != null) {
|
||||
passenger.startRiding(entity, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
public static void spawnEntityAndPassengersInWorld(Entity entity, Level world) {
|
||||
ImmutableList<Entity> passengers = entity.passengers;
|
||||
if (world.addFreshEntity(entity) && !passengers.isEmpty()) {
|
||||
for (Entity passenger : passengers) {
|
||||
passenger.absMoveTo(
|
||||
entity.getX(),
|
||||
entity.getY() + entity.getPassengerRidingPosition(passenger).y(),
|
||||
entity.getZ(),
|
||||
passenger.getYRot(), passenger.getXRot()
|
||||
);
|
||||
setEntityRotations(passenger, passenger.getYRot(), passenger.getXRot());
|
||||
spawnEntityAndPassengersInWorld(passenger, world);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setEntityRotations(Entity entity, float yaw, float pitch) {
|
||||
entity.setYRot(yaw);
|
||||
entity.yRotO = yaw;
|
||||
|
||||
entity.setXRot(pitch);
|
||||
entity.xRotO = pitch;
|
||||
|
||||
if (entity instanceof LivingEntity livingBase) {
|
||||
livingBase.yHeadRot = yaw;
|
||||
livingBase.yBodyRot = yaw;
|
||||
livingBase.yHeadRotO = yaw;
|
||||
livingBase.yBodyRotO = yaw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.utils;
|
||||
|
||||
public enum FileType {
|
||||
INVALID,
|
||||
UNKNOWN,
|
||||
JSON,
|
||||
LITEMATICA_SCHEMATIC,
|
||||
SCHEMATICA_SCHEMATIC,
|
||||
SPONGE_SCHEMATIC,
|
||||
VANILLA_STRUCTURE
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.utils;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterators;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class Int2ObjectBiMap<K> implements Iterable<K> {
|
||||
|
||||
private static final Object EMPTY = null;
|
||||
private K[] values;
|
||||
private int[] ids;
|
||||
private K[] idToValues;
|
||||
private int nextId;
|
||||
private int size;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Int2ObjectBiMap(int size) {
|
||||
this.values = (K[]) (new Object[size]);
|
||||
this.ids = new int[size];
|
||||
this.idToValues = (K[]) (new Object[size]);
|
||||
}
|
||||
|
||||
private Int2ObjectBiMap(K[] values, int[] ids, K[] idToValues, int nextId, int size) {
|
||||
this.values = values;
|
||||
this.ids = ids;
|
||||
this.idToValues = idToValues;
|
||||
this.nextId = nextId;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public static <A> Int2ObjectBiMap<A> create(int expectedSize) {
|
||||
return new Int2ObjectBiMap<>((int) ((float) expectedSize / 0.8F));
|
||||
}
|
||||
|
||||
public int getRawId(@Nullable K value) {
|
||||
return this.getIdFromIndex(this.findIndex(value, this.getIdealIndex(value)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public K get(int index) {
|
||||
return index >= 0 && index < this.idToValues.length ? this.idToValues[index] : null;
|
||||
}
|
||||
|
||||
private int getIdFromIndex(int index) {
|
||||
return index == -1 ? -1 : this.ids[index];
|
||||
}
|
||||
|
||||
public boolean contains(K value) {
|
||||
return this.getRawId(value) != -1;
|
||||
}
|
||||
|
||||
public boolean containsKey(int index) {
|
||||
return this.get(index) != null;
|
||||
}
|
||||
|
||||
public int add(K value) {
|
||||
int i = this.nextId();
|
||||
this.put(value, i);
|
||||
return i;
|
||||
}
|
||||
|
||||
private int nextId() {
|
||||
while (this.nextId < this.idToValues.length && this.idToValues[this.nextId] != null) {
|
||||
this.nextId++;
|
||||
}
|
||||
|
||||
return this.nextId;
|
||||
}
|
||||
|
||||
private void resize(int newSize) {
|
||||
K[] objects = this.values;
|
||||
int[] is = this.ids;
|
||||
Int2ObjectBiMap<K> int2ObjectBiMap = new Int2ObjectBiMap<>(newSize);
|
||||
|
||||
for (int i = 0; i < objects.length; i++) {
|
||||
if (objects[i] != null) {
|
||||
int2ObjectBiMap.put(objects[i], is[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.values = int2ObjectBiMap.values;
|
||||
this.ids = int2ObjectBiMap.ids;
|
||||
this.idToValues = int2ObjectBiMap.idToValues;
|
||||
this.nextId = int2ObjectBiMap.nextId;
|
||||
this.size = int2ObjectBiMap.size;
|
||||
}
|
||||
|
||||
public void put(K value, int id) {
|
||||
int i = Math.max(id, this.size + 1);
|
||||
if ((float) i >= (float) this.values.length * 0.8F) {
|
||||
int j = this.values.length << 1;
|
||||
|
||||
while (j < id) {
|
||||
j <<= 1;
|
||||
}
|
||||
|
||||
this.resize(j);
|
||||
}
|
||||
|
||||
int j = this.findFree(this.getIdealIndex(value));
|
||||
this.values[j] = value;
|
||||
this.ids[j] = id;
|
||||
this.idToValues[id] = value;
|
||||
this.size++;
|
||||
if (id == this.nextId) {
|
||||
this.nextId++;
|
||||
}
|
||||
}
|
||||
|
||||
private static int idealHash(int value) {
|
||||
value ^= value >>> 16;
|
||||
value *= -2048144789;
|
||||
value ^= value >>> 13;
|
||||
value *= -1028477387;
|
||||
return value ^ value >>> 16;
|
||||
}
|
||||
|
||||
private int getIdealIndex(@Nullable K value) {
|
||||
|
||||
return (idealHash(System.identityHashCode(value)) & 2147483647) % this.values.length;
|
||||
}
|
||||
|
||||
private int findIndex(@Nullable K value, int id) {
|
||||
for (int i = id; i < this.values.length; i++) {
|
||||
if (this.values[i] == value) {
|
||||
return i;
|
||||
}
|
||||
|
||||
if (this.values[i] == EMPTY) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < id; i++) {
|
||||
if (this.values[i] == value) {
|
||||
return i;
|
||||
}
|
||||
|
||||
if (this.values[i] == EMPTY) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int findFree(int size) {
|
||||
for (int i = size; i < this.values.length; i++) {
|
||||
if (this.values[i] == EMPTY) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
for (int ix = 0; ix < size; ix++) {
|
||||
if (this.values[ix] == EMPTY) {
|
||||
return ix;
|
||||
}
|
||||
}
|
||||
|
||||
throw new RuntimeException("Overflowed :(");
|
||||
}
|
||||
|
||||
public @NotNull Iterator<K> iterator() {
|
||||
return Iterators.filter(Iterators.forArray(this.idToValues), Predicates.notNull());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
Arrays.fill(this.values, null);
|
||||
Arrays.fill(this.idToValues, null);
|
||||
this.nextId = 0;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return this.size;
|
||||
}
|
||||
|
||||
public Int2ObjectBiMap<K> copy() {
|
||||
return new Int2ObjectBiMap<>(this.values.clone(), this.ids.clone(), this.idToValues.clone(), this.nextId, this.size);
|
||||
}
|
||||
|
||||
public K getOrThrow(int index) {
|
||||
K object = this.get(index);
|
||||
if (object == null) {
|
||||
throw new IllegalArgumentException("No value with id " + index);
|
||||
} else {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.utils;
|
||||
|
||||
import net.minecraft.core.Vec3i;
|
||||
|
||||
public record IntBoundingBox(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||
|
||||
public boolean containsPos(Vec3i pos) {
|
||||
return pos.getX() >= this.minX && pos.getX() <= this.maxX && pos.getZ() >= this.minZ && pos.getZ() <= this.maxZ && pos.getY() >= this.minY && pos.getY() <= this.maxY;
|
||||
}
|
||||
|
||||
public boolean intersects(IntBoundingBox box) {
|
||||
return this.maxX >= box.minX && this.minX <= box.maxX && this.maxZ >= box.minZ && this.minZ <= box.maxZ && this.maxY >= box.minY && this.minY <= box.maxY;
|
||||
}
|
||||
|
||||
public IntBoundingBox expand(int amount) {
|
||||
return this.expand(amount, amount, amount);
|
||||
}
|
||||
|
||||
public IntBoundingBox expand(int x, int y, int z) {
|
||||
return new IntBoundingBox(this.minX - x, this.minY - y, this.minZ - z, this.maxX + x, this.maxY + y, this.maxZ + z);
|
||||
}
|
||||
|
||||
public IntBoundingBox shrink(int x, int y, int z) {
|
||||
return this.expand(-x, -y, -z);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.utils;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.DoubleTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class NbtUtils {
|
||||
|
||||
public static void writeBlockPosToTag(Vec3i pos, CompoundTag tag) {
|
||||
tag.putInt("x", pos.getX());
|
||||
tag.putInt("y", pos.getY());
|
||||
tag.putInt("z", pos.getZ());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static BlockPos readBlockPos(@Nullable CompoundTag tag) {
|
||||
if (tag != null &&
|
||||
tag.contains("x", Tag.TAG_INT) &&
|
||||
tag.contains("y", Tag.TAG_INT) &&
|
||||
tag.contains("z", Tag.TAG_INT)) {
|
||||
return new BlockPos(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void writeEntityPositionToTag(Vec3 pos, CompoundTag tag) {
|
||||
ListTag posList = new ListTag();
|
||||
|
||||
posList.add(DoubleTag.valueOf(pos.x));
|
||||
posList.add(DoubleTag.valueOf(pos.y));
|
||||
posList.add(DoubleTag.valueOf(pos.z));
|
||||
tag.put("Pos", posList);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Vec3 readVec3(@Nullable CompoundTag tag) {
|
||||
if (tag != null &&
|
||||
tag.contains("dx", Tag.TAG_DOUBLE) &&
|
||||
tag.contains("dy", Tag.TAG_DOUBLE) &&
|
||||
tag.contains("dz", Tag.TAG_DOUBLE)) {
|
||||
return new Vec3(tag.getDouble("dx"), tag.getDouble("dy"), tag.getDouble("dz"));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Vec3 readEntityPositionFromTag(@Nullable CompoundTag tag) {
|
||||
if (tag != null && tag.contains("Pos", Tag.TAG_LIST)) {
|
||||
ListTag tagList = tag.getList("Pos", Tag.TAG_DOUBLE);
|
||||
|
||||
if (tagList.getElementType() == Tag.TAG_DOUBLE && tagList.size() == 3) {
|
||||
return new Vec3(tagList.getDouble(0), tagList.getDouble(1), tagList.getDouble(2));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Vec3i readVec3iFromTag(@Nullable CompoundTag tag) {
|
||||
if (tag != null &&
|
||||
tag.contains("x", Tag.TAG_INT) &&
|
||||
tag.contains("y", Tag.TAG_INT) &&
|
||||
tag.contains("z", Tag.TAG_INT)) {
|
||||
return new Vec3i(tag.getInt("x"), tag.getInt("y"), tag.getInt("z"));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.utils;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.level.block.Mirror;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.placement.SchematicPlacement;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.placement.SubRegionPlacement;
|
||||
|
||||
public class PositionUtils {
|
||||
|
||||
public static Direction rotateYCounterclockwise(Direction direction) {
|
||||
Direction var10000;
|
||||
switch (direction.ordinal()) {
|
||||
case 2 -> var10000 = Direction.WEST;
|
||||
case 3 -> var10000 = Direction.EAST;
|
||||
case 4 -> var10000 = Direction.SOUTH;
|
||||
case 5 -> var10000 = Direction.NORTH;
|
||||
default -> throw new IllegalStateException("Unable to get CCW facing of " + direction);
|
||||
}
|
||||
|
||||
return var10000;
|
||||
}
|
||||
|
||||
public static BlockPos getMinCorner(BlockPos pos1, BlockPos pos2) {
|
||||
return new BlockPos(Math.min(pos1.getX(), pos2.getX()), Math.min(pos1.getY(), pos2.getY()), Math.min(pos1.getZ(), pos2.getZ()));
|
||||
}
|
||||
|
||||
public static BlockPos getMaxCorner(BlockPos pos1, BlockPos pos2) {
|
||||
return new BlockPos(Math.max(pos1.getX(), pos2.getX()), Math.max(pos1.getY(), pos2.getY()), Math.max(pos1.getZ(), pos2.getZ()));
|
||||
}
|
||||
|
||||
public static BlockPos getTransformedPlacementPosition(BlockPos posWithinSub, SchematicPlacement schematicPlacement, SubRegionPlacement placement) {
|
||||
BlockPos pos = posWithinSub;
|
||||
pos = getTransformedBlockPos(pos, schematicPlacement.getMirror(), schematicPlacement.getRotation());
|
||||
pos = getTransformedBlockPos(pos, placement.mirror(), placement.rotation());
|
||||
return pos;
|
||||
}
|
||||
|
||||
public static BlockPos getRelativeEndPositionFromAreaSize(Vec3i size) {
|
||||
int x = size.getX();
|
||||
int y = size.getY();
|
||||
int z = size.getZ();
|
||||
|
||||
x = x >= 0 ? x - 1 : x + 1;
|
||||
y = y >= 0 ? y - 1 : y + 1;
|
||||
z = z >= 0 ? z - 1 : z + 1;
|
||||
|
||||
return new BlockPos(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirrors and then rotates the given position around the origin
|
||||
*/
|
||||
public static BlockPos getTransformedBlockPos(BlockPos pos, Mirror mirror, Rotation rotation) {
|
||||
int x = pos.getX();
|
||||
int y = pos.getY();
|
||||
int z = pos.getZ();
|
||||
boolean isMirrored = true;
|
||||
|
||||
switch (mirror) {
|
||||
case LEFT_RIGHT -> z = -z; // LEFT_RIGHT is essentially NORTH_SOUTH
|
||||
case FRONT_BACK -> x = -x; // FRONT_BACK is essentially EAST_WEST
|
||||
default -> isMirrored = false;
|
||||
}
|
||||
|
||||
return switch (rotation) {
|
||||
case CLOCKWISE_90 -> new BlockPos(-z, y, x);
|
||||
case COUNTERCLOCKWISE_90 -> new BlockPos(z, y, -x);
|
||||
case CLOCKWISE_180 -> new BlockPos(-x, y, -z);
|
||||
default -> isMirrored ? new BlockPos(x, y, z) : pos;
|
||||
};
|
||||
}
|
||||
|
||||
public static BlockPos getReverseTransformedBlockPos(BlockPos pos, Mirror mirror, Rotation rotation) {
|
||||
int x = pos.getX();
|
||||
int y = pos.getY();
|
||||
int z = pos.getZ();
|
||||
boolean isRotated = true;
|
||||
int tmp = x;
|
||||
|
||||
switch (rotation) {
|
||||
case CLOCKWISE_90 -> {
|
||||
x = z;
|
||||
z = -tmp;
|
||||
}
|
||||
case COUNTERCLOCKWISE_90 -> {
|
||||
x = -z;
|
||||
z = tmp;
|
||||
}
|
||||
case CLOCKWISE_180 -> {
|
||||
x = -x;
|
||||
z = -z;
|
||||
}
|
||||
default -> isRotated = false;
|
||||
}
|
||||
|
||||
switch (mirror) {
|
||||
case LEFT_RIGHT -> z = -z; // LEFT_RIGHT is essentially NORTH_SOUTH
|
||||
case FRONT_BACK -> x = -x; // FRONT_BACK is essentially EAST_WEST
|
||||
default -> {
|
||||
if (!isRotated) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new BlockPos(x, y, z);
|
||||
}
|
||||
|
||||
public static Vec3 getTransformedPosition(Vec3 originalPos, Mirror mirror, Rotation rotation) {
|
||||
double x = originalPos.x;
|
||||
double y = originalPos.y;
|
||||
double z = originalPos.z;
|
||||
boolean transformed = true;
|
||||
|
||||
switch (mirror) {
|
||||
case LEFT_RIGHT -> z = 1.0D - z;
|
||||
case FRONT_BACK -> x = 1.0D - x;
|
||||
default -> transformed = false;
|
||||
}
|
||||
|
||||
return switch (rotation) {
|
||||
case COUNTERCLOCKWISE_90 -> new Vec3(z, y, 1.0D - x);
|
||||
case CLOCKWISE_90 -> new Vec3(1.0D - z, y, x);
|
||||
case CLOCKWISE_180 -> new Vec3(1.0D - x, y, 1.0D - z);
|
||||
default -> transformed ? new Vec3(x, y, z) : originalPos;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.utils;
|
||||
|
||||
public enum ReplaceBehavior {
|
||||
NONE("none"),
|
||||
ALL("all"),
|
||||
WITH_NON_AIR("with_non_air");
|
||||
|
||||
private final String configString;
|
||||
|
||||
ReplaceBehavior(String configString) {
|
||||
this.configString = configString;
|
||||
}
|
||||
|
||||
public static ReplaceBehavior fromStringStatic(String name) {
|
||||
for (ReplaceBehavior val : ReplaceBehavior.values()) {
|
||||
if (val.configString.equalsIgnoreCase(name)) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
return ReplaceBehavior.NONE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.utils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public enum Schema {
|
||||
// TODO --> Add Schema Versions to this as versions get released
|
||||
// Minecraft Data Versions
|
||||
SCHEMA_25W03A(4304, "25w03a"), // Entity Data Components ( https://www.minecraft.net/en-us/article/minecraft-snapshot-25w03a )
|
||||
SCHEMA_25W02A(4298, "25w02a"),
|
||||
SCHEMA_1_21_04(4189, "1.21.4"),
|
||||
SCHEMA_24W46A(4178, "24w46a"),
|
||||
SCHEMA_24W44A(4174, "24w44a"),
|
||||
SCHEMA_1_21_03(4082, "1.21.3"),
|
||||
SCHEMA_1_21_02(4080, "1.21.2"),
|
||||
SCHEMA_24W40A(4072, "24w40a"),
|
||||
SCHEMA_24W37A(4065, "24w37a"),
|
||||
SCHEMA_24W35A(4062, "24w35a"),
|
||||
SCHEMA_24W33A(4058, "24w33a"),
|
||||
SCHEMA_1_21_01(3955, "1.21.1"),
|
||||
SCHEMA_1_21_00(3953, "1.21"),
|
||||
SCHEMA_24W21A(3946, "24w21a"),
|
||||
SCHEMA_24W18A(3940, "24w18a"),
|
||||
SCHEMA_1_20_05(3837, "1.20.5"),
|
||||
SCHEMA_24W14A(3827, "24w14a"),
|
||||
SCHEMA_24W13A(3826, "24w13a"),
|
||||
SCHEMA_24W12A(3824, "24w12a"),
|
||||
SCHEMA_24W10A(3821, "24w10a"),
|
||||
SCHEMA_24W09A(3819, "24w09a"), // Data Components ( https://minecraft.wiki/w/Data_component_format )
|
||||
SCHEMA_24W07A(3817, "24w07a"),
|
||||
SCHEMA_24W03A(3804, "24w03a"),
|
||||
SCHEMA_23W51A(3801, "23w51a"),
|
||||
SCHEMA_1_20_04(3700, "1.20.4"),
|
||||
SCHEMA_23W46A(3691, "23w46a"),
|
||||
SCHEMA_23W43B(3687, "23w43b"),
|
||||
SCHEMA_23W40A(3679, "23w40a"),
|
||||
SCHEMA_1_20_02(3578, "1.20.2"),
|
||||
SCHEMA_23W35A(3571, "23w35a"),
|
||||
SCHEMA_23W31A(3567, "23w31a"),
|
||||
SCHEMA_1_20_01(3465, "1.20.1"),
|
||||
SCHEMA_1_20_00(3463, "1.20"),
|
||||
SCHEMA_23W18A(3453, "23w18a"),
|
||||
SCHEMA_23W16A(3449, "23w16a"),
|
||||
SCHEMA_23W12A(3442, "23w12a"),
|
||||
SCHEMA_1_19_04(3337, "1.19.4"),
|
||||
SCHEMA_1_19_03(3218, "1.19.3"),
|
||||
SCHEMA_1_19_02(3120, "1.19.2"),
|
||||
SCHEMA_1_19_01(3117, "1.19.1"),
|
||||
SCHEMA_1_19_00(3105, "1.19"),
|
||||
SCHEMA_22W19A(3096, "22w19a"),
|
||||
SCHEMA_22W16A(3091, "22w16a"),
|
||||
SCHEMA_22W11A(3080, "22w11a"),
|
||||
SCHEMA_1_18_02(2975, "1.18.2"),
|
||||
SCHEMA_1_18_01(2865, "1.18.1"),
|
||||
SCHEMA_1_18_00(2860, "1.18"),
|
||||
SCHEMA_21W44A(2845, "21w44a"),
|
||||
SCHEMA_21W41A(2839, "21w41a"),
|
||||
SCHEMA_21W37A(2834, "21w37a"),
|
||||
SCHEMA_1_17_01(2730, "1.17.1"),
|
||||
SCHEMA_1_17_00(2724, "1.17"),
|
||||
SCHEMA_21W20A(2715, "21w20a"),
|
||||
SCHEMA_21W15A(2709, "21w15a"),
|
||||
SCHEMA_21W10A(2699, "21w10a"),
|
||||
SCHEMA_21W05A(2690, "21w05a"),
|
||||
SCHEMA_20W49A(2685, "20w49a"),
|
||||
SCHEMA_20W45A(2681, "20w45a"),
|
||||
SCHEMA_1_16_05(2586, "1.16.5"),
|
||||
SCHEMA_1_16_04(2584, "1.16.4"),
|
||||
SCHEMA_1_16_03(2580, "1.16.3"),
|
||||
SCHEMA_1_16_02(2578, "1.16.2"),
|
||||
SCHEMA_1_16_01(2567, "1.16.1"),
|
||||
SCHEMA_1_16_00(2566, "1.16"),
|
||||
SCHEMA_20W22A(2555, "20w22a"),
|
||||
SCHEMA_20W15A(2525, "20w15a"),
|
||||
SCHEMA_20W06A(2504, "20w06a"),
|
||||
SCHEMA_1_15_02(2230, "1.15.2"),
|
||||
SCHEMA_1_15_01(2227, "1.15.1"),
|
||||
SCHEMA_1_15_00(2225, "1.15"),
|
||||
SCHEMA_19W46B(2217, "19w46b"),
|
||||
SCHEMA_19W40A(2208, "19w40a"),
|
||||
SCHEMA_19W34A(2200, "19w34a"),
|
||||
SCHEMA_1_14_04(1976, "1.14.4"),
|
||||
SCHEMA_1_14_03(1968, "1.14.3"),
|
||||
SCHEMA_1_14_02(1963, "1.14.2"),
|
||||
SCHEMA_1_14_01(1957, "1.14.1"),
|
||||
SCHEMA_1_14_00(1952, "1.14"),
|
||||
SCHEMA_19W14B(1945, "19w14b"),
|
||||
SCHEMA_19W08B(1934, "19w08b"),
|
||||
SCHEMA_18W50A(1919, "18w50a"),
|
||||
SCHEMA_18W43A(1901, "18w43a"),
|
||||
SCHEMA_1_13_02(1631, "1.13.2"),
|
||||
SCHEMA_1_13_01(1628, "1.13.1"),
|
||||
SCHEMA_1_13_00(1519, "1.13"),
|
||||
SCHEMA_18W22C(1499, "18w22c"),
|
||||
SCHEMA_18W14B(1481, "18w14b"),
|
||||
SCHEMA_18W07C(1469, "18w07c"),
|
||||
SCHEMA_17W50A(1457, "17w50a"),
|
||||
SCHEMA_17W47A(1451, "17w47a"), // The Flattening ( https://minecraft.wiki/w/Java_Edition_1.13/Flattening )
|
||||
SCHEMA_17W46A(1449, "17w46a"),
|
||||
SCHEMA_17W43A(1444, "17w43a"),
|
||||
SCHEMA_1_12_02(1343, "1.12.2"),
|
||||
SCHEMA_1_12_01(1241, "1.12.1"),
|
||||
SCHEMA_1_12_00(1139, "1.12"),
|
||||
SCHEMA_1_11_02(922, "1.11.2"),
|
||||
SCHEMA_1_11_00(819, "1.11"),
|
||||
SCHEMA_1_10_02(512, "1.10.2"),
|
||||
SCHEMA_1_10_00(510, "1.10"),
|
||||
SCHEMA_1_09_04(184, "1.9.4"),
|
||||
SCHEMA_1_09_00(169, "1.9"),
|
||||
SCHEMA_15W32A(100, "15w32a");
|
||||
|
||||
private final int schemaId;
|
||||
private final String str;
|
||||
|
||||
Schema(int id, String ver) {
|
||||
this.schemaId = id;
|
||||
this.str = ver;
|
||||
}
|
||||
|
||||
public int getDataVersion() {
|
||||
return this.schemaId;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return this.str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Schema of the closest dataVersion, or below it.
|
||||
*
|
||||
* @param dataVersion (Schema ID)
|
||||
* @return (Schema | null)
|
||||
*/
|
||||
public static @Nullable Schema getSchemaByDataVersion(int dataVersion) {
|
||||
for (Schema schema : Schema.values()) {
|
||||
if (schema.getDataVersion() <= dataVersion) {
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MC: " + this.getString() + " [Schema: " + this.getDataVersion() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
package org.leavesmc.leaves.protocol.servux.litematics.utils;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.entity.Display;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.decoration.ItemFrame;
|
||||
import net.minecraft.world.entity.decoration.Painting;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.Mirror;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.ticks.LevelTicks;
|
||||
import net.minecraft.world.ticks.ScheduledTick;
|
||||
import org.leavesmc.leaves.protocol.servux.ServuxProtocol;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.LitematicaSchematic;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.container.LitematicaBlockStateContainer;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.placement.SchematicPlacement;
|
||||
import org.leavesmc.leaves.protocol.servux.litematics.placement.SubRegionPlacement;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class SchematicPlacingUtils {
|
||||
public static void placeToWorldWithinChunk(
|
||||
Level world,
|
||||
ChunkPos chunkPos,
|
||||
SchematicPlacement schematicPlacement,
|
||||
ReplaceBehavior replace,
|
||||
boolean notifyNeighbors
|
||||
) {
|
||||
LitematicaSchematic schematic = schematicPlacement.getSchematic();
|
||||
Set<String> regionsTouchingChunk = schematicPlacement.getRegionsTouchingChunk(chunkPos.x, chunkPos.z);
|
||||
BlockPos origin = schematicPlacement.getOrigin();
|
||||
|
||||
for (String regionName : regionsTouchingChunk) {
|
||||
LitematicaSchematic.SubRegion subRegion = schematic.getSubRegion(regionName);
|
||||
LitematicaBlockStateContainer container = subRegion.blockContainers();
|
||||
|
||||
if (container == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SubRegionPlacement placement = schematicPlacement.getRelativeSubRegionPlacement(regionName);
|
||||
if (placement == null) {
|
||||
ServuxProtocol.LOGGER.error("receiver a null placement for region: {}", regionName);
|
||||
continue;
|
||||
}
|
||||
if (!placement.enabled()) {
|
||||
continue;
|
||||
}
|
||||
Map<BlockPos, CompoundTag> blockEntityMap = subRegion.tileEntities();
|
||||
Map<BlockPos, ScheduledTick<Block>> scheduledBlockTicks = subRegion.pendingBlockTicks();
|
||||
Map<BlockPos, ScheduledTick<Fluid>> scheduledFluidTicks = subRegion.pendingFluidTicks();
|
||||
|
||||
if (!placeBlocksWithinChunk(world, chunkPos, regionName, container, blockEntityMap,
|
||||
origin, schematicPlacement, placement, scheduledBlockTicks, scheduledFluidTicks, replace, notifyNeighbors)) {
|
||||
ServuxProtocol.LOGGER.warn("Invalid/missing schematic data in schematic '{}' for sub-region '{}'", schematic.metadata().name(), regionName);
|
||||
}
|
||||
|
||||
List<LitematicaSchematic.EntityInfo> entityList = subRegion.entities();
|
||||
|
||||
if (!schematicPlacement.ignoreEntities() &&
|
||||
!placement.ignoreEntities() && entityList != null) {
|
||||
placeEntitiesToWorldWithinChunk(world, chunkPos, entityList, origin, schematicPlacement, placement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean placeBlocksWithinChunk(
|
||||
Level world, ChunkPos chunkPos, String regionName,
|
||||
LitematicaBlockStateContainer container,
|
||||
Map<BlockPos, CompoundTag> blockEntityMap,
|
||||
BlockPos origin,
|
||||
SchematicPlacement schematicPlacement,
|
||||
SubRegionPlacement placement,
|
||||
@Nullable Map<BlockPos, ScheduledTick<Block>> scheduledBlockTicks,
|
||||
@Nullable Map<BlockPos, ScheduledTick<Fluid>> scheduledFluidTicks,
|
||||
ReplaceBehavior replace, boolean notifyNeighbors
|
||||
) {
|
||||
IntBoundingBox bounds = schematicPlacement.getBoxWithinChunkForRegion(regionName, chunkPos.x, chunkPos.z);
|
||||
Vec3i regionSize = schematicPlacement.getSchematic().getSubRegion(regionName).size();
|
||||
|
||||
if (bounds == null || container == null || blockEntityMap == null || regionSize == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockPos regionPos = placement.pos();
|
||||
|
||||
// These are the untransformed relative positions
|
||||
BlockPos posEndRel = (new BlockPos(PositionUtils.getRelativeEndPositionFromAreaSize(regionSize))).offset(regionPos);
|
||||
BlockPos posMinRel = PositionUtils.getMinCorner(regionPos, posEndRel);
|
||||
|
||||
// The transformed sub-region origin position
|
||||
BlockPos regionPosTransformed = PositionUtils.getTransformedBlockPos(regionPos, schematicPlacement.getMirror(), schematicPlacement.getRotation());
|
||||
|
||||
// The relative offset of the affected region's corners, to the sub-region's origin corner
|
||||
BlockPos boxMinRel = new BlockPos(bounds.minX() - origin.getX() - regionPosTransformed.getX(), 0, bounds.minZ() - origin.getZ() - regionPosTransformed.getZ());
|
||||
BlockPos boxMaxRel = new BlockPos(bounds.maxX() - origin.getX() - regionPosTransformed.getX(), 0, bounds.maxZ() - origin.getZ() - regionPosTransformed.getZ());
|
||||
|
||||
// Reverse transform that relative offset, to get the untransformed orientation's offsets
|
||||
boxMinRel = PositionUtils.getReverseTransformedBlockPos(boxMinRel, placement.mirror(), placement.rotation());
|
||||
boxMaxRel = PositionUtils.getReverseTransformedBlockPos(boxMaxRel, placement.mirror(), placement.rotation());
|
||||
|
||||
boxMinRel = PositionUtils.getReverseTransformedBlockPos(boxMinRel, schematicPlacement.getMirror(), schematicPlacement.getRotation());
|
||||
boxMaxRel = PositionUtils.getReverseTransformedBlockPos(boxMaxRel, schematicPlacement.getMirror(), schematicPlacement.getRotation());
|
||||
|
||||
// Get the offset relative to the sub-region's minimum corner, instead of the origin corner (which can be at any corner)
|
||||
boxMinRel = boxMinRel.subtract(posMinRel.subtract(regionPos));
|
||||
boxMaxRel = boxMaxRel.subtract(posMinRel.subtract(regionPos));
|
||||
|
||||
BlockPos posMin = PositionUtils.getMinCorner(boxMinRel, boxMaxRel);
|
||||
BlockPos posMax = PositionUtils.getMaxCorner(boxMinRel, boxMaxRel);
|
||||
|
||||
final int startX = posMin.getX();
|
||||
final int startZ = posMin.getZ();
|
||||
final int endX = posMax.getX();
|
||||
final int endZ = posMax.getZ();
|
||||
final int startY = 0;
|
||||
final int endY = Math.abs(regionSize.getY()) - 1;
|
||||
BlockPos.MutableBlockPos posMutable = new BlockPos.MutableBlockPos();
|
||||
if (startX < 0 || startZ < 0 || endX >= container.getSize().getX() || endZ >= container.getSize().getZ()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Rotation rotationCombined = schematicPlacement.getRotation().getRotated(placement.rotation());
|
||||
final Mirror mirrorMain = schematicPlacement.getMirror();
|
||||
final BlockState barrier = Blocks.BARRIER.defaultBlockState();
|
||||
Mirror mirrorSub = placement.mirror();
|
||||
final boolean ignoreInventories = false;
|
||||
|
||||
if (mirrorSub != Mirror.NONE &&
|
||||
(schematicPlacement.getRotation() == Rotation.CLOCKWISE_90 ||
|
||||
schematicPlacement.getRotation() == Rotation.COUNTERCLOCKWISE_90)) {
|
||||
mirrorSub = mirrorSub == Mirror.FRONT_BACK ? Mirror.LEFT_RIGHT : Mirror.FRONT_BACK;
|
||||
}
|
||||
|
||||
final int posMinRelMinusRegX = posMinRel.getX() - regionPos.getX();
|
||||
final int posMinRelMinusRegY = posMinRel.getY() - regionPos.getY();
|
||||
final int posMinRelMinusRegZ = posMinRel.getZ() - regionPos.getZ();
|
||||
|
||||
for (int y = startY; y <= endY; ++y) {
|
||||
for (int z = startZ; z <= endZ; ++z) {
|
||||
for (int x = startX; x <= endX; ++x) {
|
||||
BlockState state = container.get(x, y, z);
|
||||
|
||||
if (state.getBlock() == Blocks.STRUCTURE_VOID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
posMutable.set(x, y, z);
|
||||
CompoundTag teNBT = blockEntityMap.get(posMutable);
|
||||
|
||||
posMutable.set(posMinRelMinusRegX + x,
|
||||
posMinRelMinusRegY + y,
|
||||
posMinRelMinusRegZ + z);
|
||||
|
||||
BlockPos pos = PositionUtils.getTransformedPlacementPosition(posMutable, schematicPlacement, placement);
|
||||
pos = pos.offset(regionPosTransformed).offset(origin);
|
||||
|
||||
BlockState stateOld = world.getBlockState(pos);
|
||||
|
||||
if ((replace == ReplaceBehavior.NONE && !stateOld.isAir()) ||
|
||||
(replace == ReplaceBehavior.WITH_NON_AIR && state.isAir())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mirrorMain != Mirror.NONE) {
|
||||
state = state.mirror(mirrorMain);
|
||||
}
|
||||
if (mirrorSub != Mirror.NONE) {
|
||||
state = state.mirror(mirrorSub);
|
||||
}
|
||||
if (rotationCombined != Rotation.NONE) {
|
||||
state = state.rotate(rotationCombined);
|
||||
}
|
||||
|
||||
BlockEntity te = world.getBlockEntity(pos);
|
||||
|
||||
if (te != null) {
|
||||
if (te instanceof Container) {
|
||||
((Container) te).clearContent();
|
||||
}
|
||||
|
||||
world.setBlock(pos, barrier, 4 | 16 | (notifyNeighbors ? 0 : 1024));
|
||||
}
|
||||
|
||||
if (world.setBlock(pos, state, 2 | 16 | (notifyNeighbors ? 0 : 1024)) && teNBT != null) {
|
||||
te = world.getBlockEntity(pos);
|
||||
|
||||
if (te == null) {
|
||||
continue;
|
||||
}
|
||||
teNBT = teNBT.copy();
|
||||
NbtUtils.writeBlockPosToTag(pos, teNBT);
|
||||
|
||||
try {
|
||||
te.loadWithComponents(teNBT, world.registryAccess().freeze());
|
||||
|
||||
} catch (Exception e) {
|
||||
ServuxProtocol.LOGGER.warn("Failed to load BlockEntity data for {} @ {}", state, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ServerLevel serverWorld = (ServerLevel) world;
|
||||
IntBoundingBox box = new IntBoundingBox(startX, startY, startZ, endX, endY, endZ);
|
||||
|
||||
if (scheduledBlockTicks != null && !scheduledBlockTicks.isEmpty()) {
|
||||
LevelTicks<Block> scheduler = serverWorld.getBlockTicks();
|
||||
|
||||
for (Map.Entry<BlockPos, ScheduledTick<Block>> entry : scheduledBlockTicks.entrySet()) {
|
||||
BlockPos pos = entry.getKey();
|
||||
|
||||
if (!box.containsPos(pos)) {
|
||||
continue;
|
||||
}
|
||||
posMutable.set(posMinRelMinusRegX + pos.getX(),
|
||||
posMinRelMinusRegY + pos.getY(),
|
||||
posMinRelMinusRegZ + pos.getZ());
|
||||
|
||||
pos = PositionUtils.getTransformedPlacementPosition(posMutable, schematicPlacement, placement);
|
||||
pos = pos.offset(regionPosTransformed).offset(origin);
|
||||
ScheduledTick<Block> tick = entry.getValue();
|
||||
|
||||
if (world.getBlockState(pos).getBlock() == tick.type()) {
|
||||
scheduler.schedule(new ScheduledTick<>(tick.type(), pos, tick.triggerTick(), tick.priority(), tick.subTickOrder()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scheduledFluidTicks != null && !scheduledFluidTicks.isEmpty()) {
|
||||
LevelTicks<Fluid> scheduler = serverWorld.getFluidTicks();
|
||||
|
||||
for (Map.Entry<BlockPos, ScheduledTick<Fluid>> entry : scheduledFluidTicks.entrySet()) {
|
||||
BlockPos pos = entry.getKey();
|
||||
|
||||
if (!box.containsPos(pos)) {
|
||||
continue;
|
||||
}
|
||||
posMutable.set(posMinRelMinusRegX + pos.getX(),
|
||||
posMinRelMinusRegY + pos.getY(),
|
||||
posMinRelMinusRegZ + pos.getZ());
|
||||
|
||||
pos = PositionUtils.getTransformedPlacementPosition(posMutable, schematicPlacement, placement);
|
||||
pos = pos.offset(regionPosTransformed).offset(origin);
|
||||
ScheduledTick<Fluid> tick = entry.getValue();
|
||||
|
||||
if (world.getBlockState(pos).getFluidState().getType() == tick.type()) {
|
||||
scheduler.schedule(new ScheduledTick<>(tick.type(), pos, tick.triggerTick(), tick.priority(), tick.subTickOrder()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!notifyNeighbors) {
|
||||
return true;
|
||||
}
|
||||
for (int y = startY; y <= endY; ++y) {
|
||||
for (int z = startZ; z <= endZ; ++z) {
|
||||
for (int x = startX; x <= endX; ++x) {
|
||||
posMutable.set(posMinRelMinusRegX + x,
|
||||
posMinRelMinusRegY + y,
|
||||
posMinRelMinusRegZ + z);
|
||||
BlockPos pos = PositionUtils.getTransformedPlacementPosition(posMutable, schematicPlacement, placement);
|
||||
pos = pos.offset(regionPosTransformed).offset(origin);
|
||||
world.updateNeighborsAt(pos, world.getBlockState(pos).getBlock());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void placeEntitiesToWorldWithinChunk(
|
||||
Level world, ChunkPos chunkPos,
|
||||
List<LitematicaSchematic.EntityInfo> entityList,
|
||||
BlockPos origin,
|
||||
SchematicPlacement schematicPlacement,
|
||||
SubRegionPlacement placement
|
||||
) {
|
||||
BlockPos regionPos = placement.pos();
|
||||
|
||||
if (entityList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BlockPos regionPosRelTransformed = PositionUtils.getTransformedBlockPos(regionPos, schematicPlacement.getMirror(), schematicPlacement.getRotation());
|
||||
final int offX = regionPosRelTransformed.getX() + origin.getX();
|
||||
final int offY = regionPosRelTransformed.getY() + origin.getY();
|
||||
final int offZ = regionPosRelTransformed.getZ() + origin.getZ();
|
||||
final double minX = (chunkPos.x << 4);
|
||||
final double minZ = (chunkPos.z << 4);
|
||||
final double maxX = (chunkPos.x << 4) + 16;
|
||||
final double maxZ = (chunkPos.z << 4) + 16;
|
||||
|
||||
final Rotation rotationCombined = schematicPlacement.getRotation().getRotated(placement.rotation());
|
||||
final Mirror mirrorMain = schematicPlacement.getMirror();
|
||||
Mirror mirrorSub = placement.mirror();
|
||||
|
||||
if (mirrorSub != Mirror.NONE &&
|
||||
(schematicPlacement.getRotation() == Rotation.CLOCKWISE_90 ||
|
||||
schematicPlacement.getRotation() == Rotation.COUNTERCLOCKWISE_90)) {
|
||||
mirrorSub = mirrorSub == Mirror.FRONT_BACK ? Mirror.LEFT_RIGHT : Mirror.FRONT_BACK;
|
||||
}
|
||||
|
||||
for (LitematicaSchematic.EntityInfo info : entityList) {
|
||||
Vec3 pos = info.posVec();
|
||||
pos = PositionUtils.getTransformedPosition(pos, schematicPlacement.getMirror(), schematicPlacement.getRotation());
|
||||
pos = PositionUtils.getTransformedPosition(pos, placement.mirror(), placement.rotation());
|
||||
double x = pos.x + offX;
|
||||
double y = pos.y + offY;
|
||||
double z = pos.z + offZ;
|
||||
float[] origRot = new float[2];
|
||||
|
||||
if (!(x >= minX && x < maxX && z >= minZ && z < maxZ)) {
|
||||
continue;
|
||||
}
|
||||
CompoundTag tag = info.nbt().copy();
|
||||
String id = tag.getString("id");
|
||||
|
||||
// Avoid warning about invalid hanging position.
|
||||
// Note that this position isn't technically correct, but it only needs to be within 16 blocks
|
||||
// of the entity position to avoid the warning.
|
||||
if (id.equals("minecraft:glow_item_frame") ||
|
||||
id.equals("minecraft:item_frame") ||
|
||||
id.equals("minecraft:leash_knot") ||
|
||||
id.equals("minecraft:painting")) {
|
||||
Vec3 p = NbtUtils.readEntityPositionFromTag(tag);
|
||||
|
||||
if (p == null) {
|
||||
p = new Vec3(x, y, z);
|
||||
NbtUtils.writeEntityPositionToTag(p, tag);
|
||||
}
|
||||
|
||||
tag.putInt("TileX", (int) p.x);
|
||||
tag.putInt("TileY", (int) p.y);
|
||||
tag.putInt("TileZ", (int) p.z);
|
||||
}
|
||||
|
||||
ListTag rotation = tag.getList("Rotation", Tag.TAG_FLOAT);
|
||||
origRot[0] = rotation.getFloat(0);
|
||||
origRot[1] = rotation.getFloat(1);
|
||||
|
||||
Entity entity = EntityUtils.createEntityAndPassengersFromNBT(tag, world);
|
||||
|
||||
if (entity == null) {
|
||||
continue;
|
||||
}
|
||||
rotateEntity(entity, x, y, z, rotationCombined, mirrorMain, mirrorSub);
|
||||
|
||||
// Update the sleeping position to the current position
|
||||
if (entity instanceof LivingEntity living && living.isSleeping()) {
|
||||
living.setSleepingPos(BlockPos.containing(x, y, z));
|
||||
}
|
||||
|
||||
// Hack fix to fix the painting position offsets.
|
||||
// The vanilla code will end up moving the position by one in two of the orientations,
|
||||
// because it sets the hanging position to the given position (floored)
|
||||
// and then it offsets the position from the hanging position
|
||||
// by 0.5 or 1.0 blocks depending on the painting size.
|
||||
if (entity instanceof Painting paintingEntity) {
|
||||
Direction right = PositionUtils.rotateYCounterclockwise(paintingEntity.getDirection());
|
||||
|
||||
if ((paintingEntity.getVariant().value().width() % 2) == 0 &&
|
||||
right.getAxisDirection() == Direction.AxisDirection.POSITIVE) {
|
||||
x -= 1.0 * right.getStepX();
|
||||
z -= 1.0 * right.getStepZ();
|
||||
}
|
||||
|
||||
if ((paintingEntity.getVariant().value().height() % 2) == 0) {
|
||||
y -= 1.0;
|
||||
}
|
||||
|
||||
entity.teleportTo(x, y, z);
|
||||
}
|
||||
if (entity instanceof ItemFrame frameEntity) {
|
||||
if (frameEntity.getYRot() != origRot[0] && (frameEntity.getXRot() == 90.0F || frameEntity.getXRot() == -90.0F)) {
|
||||
// Fix Yaw only if Pitch is +/- 90.0F (Floor, Ceiling mounted)
|
||||
frameEntity.setYRot(origRot[0]);
|
||||
}
|
||||
}
|
||||
|
||||
EntityUtils.spawnEntityAndPassengersInWorld(entity, world);
|
||||
|
||||
if (entity instanceof Display) {
|
||||
entity.tick(); // Required to set the full data for rendering
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void rotateEntity(
|
||||
Entity entity, double x, double y, double z,
|
||||
Rotation rotationCombined, Mirror mirrorMain, Mirror mirrorSub
|
||||
) {
|
||||
float rotationYaw = entity.getYRot();
|
||||
|
||||
if (mirrorMain != Mirror.NONE) {
|
||||
rotationYaw = entity.mirror(mirrorMain);
|
||||
}
|
||||
if (mirrorSub != Mirror.NONE) {
|
||||
rotationYaw = entity.mirror(mirrorSub);
|
||||
}
|
||||
if (rotationCombined != Rotation.NONE) {
|
||||
rotationYaw += entity.getYRot() - entity.rotate(rotationCombined);
|
||||
}
|
||||
|
||||
entity.absMoveTo(x, y, z, rotationYaw, entity.getXRot());
|
||||
EntityUtils.setEntityRotations(entity, rotationYaw, entity.getXRot());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user