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.structure.StructureContext;
11 import net.minecraft.structure.StructurePiece;
12 import net.minecraft.structure.StructurePiecesList;
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.world.FeatureUpdater;
18 import net.minecraft.world.PersistentStateManager;
19 import net.minecraft.world.StructureWorldAccess;
20 import net.minecraft.world.World;
21 import net.minecraft.world.dimension.DimensionType;
22 import net.minecraft.world.gen.StructureAccessor;
23 import net.minecraft.world.gen.chunk.ChunkGenerator;
24 import net.minecraft.world.gen.feature.FeatureConfig;
25 import net.minecraft.world.level.storage.LevelStorage;
26 import net.minecraft.world.storage.RegionBasedStorage;
28 import java.io.IOException;
29 import java.nio.file.Path;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
35 import java.util.Random;
37 import java.util.function.BiFunction;
39 class NBTStructureLoader {
40 private final DimensionId dimensionId;
41 private final Set<String> loadedChunks = new HashSet<>();
43 private FeatureUpdater legacyStructureDataUtil = null;
44 private LevelStorage.Session saveHandler = null;
45 private Path chunkSaveLocation = null;
46 private ChunkLoader chunkLoader;
48 NBTStructureLoader(DimensionId dimensionId, LevelStorage.Session saveHandler, Path worldDirectory) {
49 this.dimensionId = dimensionId;
50 this.configure(saveHandler, worldDirectory);
54 this.legacyStructureDataUtil = null;
55 this.chunkSaveLocation = null;
56 this.loadedChunks.clear();
57 close(this.saveHandler, this.chunkLoader);
58 this.saveHandler = null;
59 this.chunkLoader = null;
62 private void close(AutoCloseable... closeables) {
63 for (AutoCloseable closeable : closeables) {
64 if(closeable == null) continue;
67 } catch (Exception ignored) {
72 void configure(LevelStorage.Session saveHandler, Path worldDirectory) {
73 this.saveHandler = saveHandler;
74 if (worldDirectory != null) {
75 this.chunkSaveLocation = DimensionType.getSaveDirectory(this.dimensionId.getDimensionType(), worldDirectory).resolve("region");
76 this.chunkLoader = new ChunkLoader(this.chunkSaveLocation);
80 private FeatureUpdater getLegacyStructureDataUtil() {
81 if (this.legacyStructureDataUtil == null) {
82 Path dataFolder = this.saveHandler.getWorldDirectory(World.OVERWORLD).resolve("data");
83 this.legacyStructureDataUtil = FeatureUpdater.create(dimensionId.getDimensionType(),
84 new PersistentStateManager(dataFolder.toFile(), MinecraftClient.getInstance().getDataFixer()));
86 return this.legacyStructureDataUtil;
89 private NbtCompound loadStructureStarts(int chunkX, int chunkZ) {
91 NbtCompound compound = this.chunkLoader.readChunk(chunkX, chunkZ);
92 if (compound == null) return null;
93 int dataVersion = compound.contains("DataVersion", 99) ? compound.getInt("DataVersion") : -1;
94 if (dataVersion < 1493) {
95 if (compound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
96 compound = getLegacyStructureDataUtil().getUpdatedReferences(compound);
99 return compound.getCompound("Level").getCompound("Structures").getCompound("Starts");
100 } catch (IOException ignored) {
105 void loadStructures(int chunkX, int chunkZ) {
106 if (saveHandler == null) return;
108 if (!loadedChunks.add(String.format("%s,%s", chunkX, chunkZ))) return;
110 NbtCompound structureStarts = loadStructureStarts(chunkX, chunkZ);
111 if (structureStarts == null || structureStarts.getSize() == 0) return;
113 Map<String, StructureStart<?>> structureStartMap = new HashMap<>();
114 for (String key : structureStarts.getKeys()) {
115 NbtCompound compound = structureStarts.getCompound(key);
116 if (compound.contains("BB")) {
117 structureStartMap.put(key, new SimpleStructureStart(compound));
121 EventBus.publish(new StructuresLoaded(structureStartMap, dimensionId));
124 private static class SimpleStructureStart extends StructureStart<FeatureConfig> {
125 private final BlockBox parsedBoundingBox;
127 SimpleStructureStart(NbtCompound compound) {
131 new StructurePiecesList(createList(compound)));
133 this.parsedBoundingBox = create(compound.getIntArray("BB"));
136 private static List<StructurePiece> createList(NbtCompound compound) {
137 final ArrayList<StructurePiece> pieces = new ArrayList<>();
138 NbtList children = compound.getList("Children", 10);
139 for (int index = 0; index < children.size(); ++index) {
140 NbtCompound child = children.getCompound(index);
141 if (child.contains("BB")) pieces.add(new SimpleStructurePiece(child));
146 private static BlockBox create(int[] compound) {
147 if (compound.length == 6)
148 return new BlockBox(compound[0], compound[1], compound[2], compound[3], compound[4], compound[5]);
150 return new BlockBox(0, 0, 0, 0, 0, 0);
154 public void place(StructureWorldAccess world, StructureAccessor structureAccessor, ChunkGenerator chunkGenerator, Random random, BlockBox chunkBox, ChunkPos chunkPos) {
158 public BlockBox getBoundingBox() {
159 return this.parsedBoundingBox;
163 private static class SimpleStructurePiece extends StructurePiece {
164 SimpleStructurePiece(NbtCompound compound) {
165 super(null, compound);
169 protected void writeNbt(StructureContext context, NbtCompound nbt) {
173 public void generate(StructureWorldAccess structureWorldAccess, StructureAccessor structureAccessor, ChunkGenerator chunkGenerator, Random random, BlockBox blockBox, ChunkPos chunkPos, BlockPos blockPos) {
177 private static class ChunkLoader implements AutoCloseable {
178 private static final BiFunction<Path, Boolean, RegionBasedStorage> creator =
179 ReflectionHelper.getPrivateInstanceBuilder(RegionBasedStorage.class, Path.class, boolean.class);
181 private final RegionBasedStorage regionFileCache;
183 public ChunkLoader(Path file) {
184 this.regionFileCache = creator.apply(file, false);
187 public NbtCompound readChunk(int chunkX, int chunkZ) throws IOException {
188 if (regionFileCache == null) return null;
189 return regionFileCache.getTagAt(new ChunkPos(chunkX, chunkZ));
192 public void close() throws IOException {
193 if (regionFileCache == null) return;
194 regionFileCache.close();