1 package com.irtimaled.bbor.client.interop;
3 import com.irtimaled.bbor.common.EventBus;
4 import com.irtimaled.bbor.common.ReflectionHelper;
5 import com.irtimaled.bbor.common.events.StructuresLoaded;
6 import com.irtimaled.bbor.common.models.DimensionId;
7 import net.minecraft.client.MinecraftClient;
8 import net.minecraft.nbt.NbtCompound;
9 import net.minecraft.nbt.NbtList;
10 import net.minecraft.server.world.ServerWorld;
11 import net.minecraft.structure.StructureManager;
12 import net.minecraft.structure.StructurePiece;
13 import net.minecraft.structure.StructureStart;
14 import net.minecraft.util.math.BlockBox;
15 import net.minecraft.util.math.BlockPos;
16 import net.minecraft.util.math.ChunkPos;
17 import net.minecraft.util.registry.DynamicRegistryManager;
18 import net.minecraft.world.FeatureUpdater;
19 import net.minecraft.world.HeightLimitView;
20 import net.minecraft.world.PersistentStateManager;
21 import net.minecraft.world.StructureWorldAccess;
22 import net.minecraft.world.World;
23 import net.minecraft.world.biome.Biome;
24 import net.minecraft.world.dimension.DimensionType;
25 import net.minecraft.world.gen.StructureAccessor;
26 import net.minecraft.world.gen.chunk.ChunkGenerator;
27 import net.minecraft.world.gen.feature.FeatureConfig;
28 import net.minecraft.world.level.storage.LevelStorage;
29 import net.minecraft.world.storage.RegionBasedStorage;
32 import java.io.IOException;
33 import java.util.HashMap;
34 import java.util.HashSet;
36 import java.util.Random;
38 import java.util.function.BiFunction;
40 class NBTStructureLoader {
41 private final DimensionId dimensionId;
42 private final Set<String> loadedChunks = new HashSet<>();
44 private FeatureUpdater legacyStructureDataUtil = null;
45 private LevelStorage.Session saveHandler = null;
46 private File chunkSaveLocation = null;
47 private ChunkLoader chunkLoader;
49 NBTStructureLoader(DimensionId dimensionId, LevelStorage.Session saveHandler, File worldDirectory) {
50 this.dimensionId = dimensionId;
51 this.configure(saveHandler, worldDirectory);
55 this.legacyStructureDataUtil = null;
56 this.chunkSaveLocation = null;
57 this.loadedChunks.clear();
58 close(this.saveHandler, this.chunkLoader);
59 this.saveHandler = null;
60 this.chunkLoader = null;
63 private void close(AutoCloseable... closeables) {
64 for (AutoCloseable closeable : closeables) {
65 if(closeable == null) continue;
68 } catch (Exception ignored) {
73 void configure(LevelStorage.Session saveHandler, File worldDirectory) {
74 this.saveHandler = saveHandler;
75 if (worldDirectory != null) {
76 this.chunkSaveLocation = new File(DimensionType.getSaveDirectory(this.dimensionId.getDimensionType(), worldDirectory), "region");
77 this.chunkLoader = new ChunkLoader(this.chunkSaveLocation);
81 private FeatureUpdater getLegacyStructureDataUtil() {
82 if (this.legacyStructureDataUtil == null) {
83 File dataFolder = new File(this.saveHandler.getWorldDirectory(World.OVERWORLD), "data");
84 this.legacyStructureDataUtil = FeatureUpdater.create(dimensionId.getDimensionType(),
85 new PersistentStateManager(dataFolder, MinecraftClient.getInstance().getDataFixer()));
87 return this.legacyStructureDataUtil;
90 private NbtCompound loadStructureStarts(int chunkX, int chunkZ) {
92 NbtCompound compound = this.chunkLoader.readChunk(chunkX, chunkZ);
93 if (compound == null) return null;
94 int dataVersion = compound.contains("DataVersion", 99) ? compound.getInt("DataVersion") : -1;
95 if (dataVersion < 1493) {
96 if (compound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
97 compound = getLegacyStructureDataUtil().getUpdatedReferences(compound);
100 return compound.getCompound("Level").getCompound("Structures").getCompound("Starts");
101 } catch (IOException ignored) {
106 void loadStructures(int chunkX, int chunkZ) {
107 if (saveHandler == null) return;
109 if (!loadedChunks.add(String.format("%s,%s", chunkX, chunkZ))) return;
111 NbtCompound structureStarts = loadStructureStarts(chunkX, chunkZ);
112 if (structureStarts == null || structureStarts.getSize() == 0) return;
114 Map<String, StructureStart<?>> structureStartMap = new HashMap<>();
115 for (String key : structureStarts.getKeys()) {
116 NbtCompound compound = structureStarts.getCompound(key);
117 if (compound.contains("BB")) {
118 structureStartMap.put(key, new SimpleStructureStart(compound));
122 EventBus.publish(new StructuresLoaded(structureStartMap, dimensionId));
125 private static class SimpleStructureStart extends StructureStart<FeatureConfig> {
126 private final BlockBox parsedBoundingBox;
128 SimpleStructureStart(NbtCompound compound) {
134 this.parsedBoundingBox = create(compound.getIntArray("BB"));
136 NbtList children = compound.getList("Children", 10);
137 for (int index = 0; index < children.size(); ++index) {
138 NbtCompound child = children.getCompound(index);
139 if (child.contains("BB")) this.children.add(new SimpleStructurePiece(child));
143 private static BlockBox create(int[] compound) {
144 if (compound.length == 6)
145 return new BlockBox(compound[0], compound[1], compound[2], compound[3], compound[4], compound[5]);
147 return new BlockBox(0, 0, 0, 0, 0, 0);
151 public void init(DynamicRegistryManager dynamicRegistryManager, ChunkGenerator chunkGenerator, StructureManager structureManager, ChunkPos chunkPos, Biome biome, FeatureConfig featureConfig, HeightLimitView heightLimitView) {
155 protected BlockBox calculateBoundingBox() {
156 return this.parsedBoundingBox;
160 private static class SimpleStructurePiece extends StructurePiece {
161 SimpleStructurePiece(NbtCompound compound) {
162 super(null, compound);
166 protected void writeNbt(ServerWorld serverWorld, NbtCompound nbtCompound) {
170 public boolean generate(StructureWorldAccess structureWorldAccess, StructureAccessor structureAccessor, ChunkGenerator chunkGenerator, Random random, BlockBox blockBox, ChunkPos chunkPos, BlockPos blockPos) {
175 private static class ChunkLoader implements AutoCloseable {
176 private static final BiFunction<File, Boolean, RegionBasedStorage> creator =
177 ReflectionHelper.getPrivateInstanceBuilder(RegionBasedStorage.class, File.class, boolean.class);
179 private final RegionBasedStorage regionFileCache;
181 public ChunkLoader(File file) {
182 this.regionFileCache = creator.apply(file, false);
185 public NbtCompound readChunk(int chunkX, int chunkZ) throws IOException {
186 if (regionFileCache == null) return null;
187 return regionFileCache.getTagAt(new ChunkPos(chunkX, chunkZ));
190 public void close() throws IOException {
191 if (regionFileCache == null) return;
192 regionFileCache.close();