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.CompoundTag;
9 import net.minecraft.nbt.ListTag;
10 import net.minecraft.structure.StructureManager;
11 import net.minecraft.structure.StructurePiece;
12 import net.minecraft.structure.StructureStart;
13 import net.minecraft.util.math.BlockBox;
14 import net.minecraft.util.math.BlockPos;
15 import net.minecraft.util.math.ChunkPos;
16 import net.minecraft.util.registry.DynamicRegistryManager;
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.biome.Biome;
22 import net.minecraft.world.dimension.DimensionType;
23 import net.minecraft.world.gen.StructureAccessor;
24 import net.minecraft.world.gen.chunk.ChunkGenerator;
25 import net.minecraft.world.gen.feature.FeatureConfig;
26 import net.minecraft.world.level.storage.LevelStorage;
27 import net.minecraft.world.storage.RegionBasedStorage;
30 import java.io.IOException;
32 import java.util.function.BiFunction;
34 class NBTStructureLoader {
35 private final DimensionId dimensionId;
36 private final Set<String> loadedChunks = new HashSet<>();
38 private FeatureUpdater legacyStructureDataUtil = null;
39 private LevelStorage.Session saveHandler = null;
40 private File chunkSaveLocation = null;
41 private ChunkLoader chunkLoader;
43 NBTStructureLoader(DimensionId dimensionId, LevelStorage.Session saveHandler, File worldDirectory) {
44 this.dimensionId = dimensionId;
45 this.configure(saveHandler, worldDirectory);
49 this.legacyStructureDataUtil = null;
50 this.chunkSaveLocation = null;
51 this.loadedChunks.clear();
52 close(this.saveHandler, this.chunkLoader);
53 this.saveHandler = null;
54 this.chunkLoader = null;
57 private void close(AutoCloseable... closeables) {
58 for (AutoCloseable closeable : closeables) {
59 if(closeable == null) continue;
62 } catch (Exception ignored) {
67 void configure(LevelStorage.Session saveHandler, File worldDirectory) {
68 this.saveHandler = saveHandler;
69 if (worldDirectory != null) {
70 this.chunkSaveLocation = new File(DimensionType.getSaveDirectory(this.dimensionId.getDimensionType(), worldDirectory), "region");
71 this.chunkLoader = new ChunkLoader(this.chunkSaveLocation);
75 private FeatureUpdater getLegacyStructureDataUtil() {
76 if (this.legacyStructureDataUtil == null) {
77 File dataFolder = new File(this.saveHandler.getWorldDirectory(World.OVERWORLD), "data");
78 this.legacyStructureDataUtil = FeatureUpdater.create(dimensionId.getDimensionType(),
79 new PersistentStateManager(dataFolder, MinecraftClient.getInstance().getDataFixer()));
81 return this.legacyStructureDataUtil;
84 private CompoundTag loadStructureStarts(int chunkX, int chunkZ) {
86 CompoundTag compound = this.chunkLoader.readChunk(chunkX, chunkZ);
87 if (compound == null) return null;
88 int dataVersion = compound.contains("DataVersion", 99) ? compound.getInt("DataVersion") : -1;
89 if (dataVersion < 1493) {
90 if (compound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
91 compound = getLegacyStructureDataUtil().getUpdatedReferences(compound);
94 return compound.getCompound("Level").getCompound("Structures").getCompound("Starts");
95 } catch (IOException ignored) {
100 void loadStructures(int chunkX, int chunkZ) {
101 if (saveHandler == null) return;
103 if (!loadedChunks.add(String.format("%s,%s", chunkX, chunkZ))) return;
105 CompoundTag structureStarts = loadStructureStarts(chunkX, chunkZ);
106 if (structureStarts == null || structureStarts.getSize() == 0) return;
108 Map<String, StructureStart<?>> structureStartMap = new HashMap<>();
109 for (String key : structureStarts.getKeys()) {
110 CompoundTag compound = structureStarts.getCompound(key);
111 if (compound.contains("BB")) {
112 structureStartMap.put(key, new SimpleStructureStart(compound));
116 EventBus.publish(new StructuresLoaded(structureStartMap, dimensionId));
119 private static class SimpleStructureStart extends StructureStart<FeatureConfig> {
120 SimpleStructureStart(CompoundTag compound) {
124 new BlockBox(compound.getIntArray("BB")),
128 ListTag children = compound.getList("Children", 10);
129 for (int index = 0; index < children.size(); ++index) {
130 CompoundTag child = children.getCompound(index);
131 if (child.contains("BB")) this.children.add(new SimpleStructurePiece(child));
136 public void init(DynamicRegistryManager dynamicRegistryManager, ChunkGenerator chunkGenerator, StructureManager structureManager, int i, int j, Biome biome, FeatureConfig featureConfig) {
140 private static class SimpleStructurePiece extends StructurePiece {
141 SimpleStructurePiece(CompoundTag compound) {
142 super(null, compound);
146 protected void toNbt(CompoundTag compoundTag) {
151 public boolean generate(StructureWorldAccess structureWorldAccess, StructureAccessor structureAccessor, ChunkGenerator chunkGenerator, Random random, BlockBox blockBox, ChunkPos chunkPos, BlockPos blockPos) {
156 private static class ChunkLoader implements AutoCloseable {
157 private static final BiFunction<File, Boolean, RegionBasedStorage> creator =
158 ReflectionHelper.getPrivateInstanceBuilder(RegionBasedStorage.class, File.class, boolean.class);
160 private final RegionBasedStorage regionFileCache;
162 public ChunkLoader(File file) {
163 this.regionFileCache = creator.apply(file, false);
166 public CompoundTag readChunk(int chunkX, int chunkZ) throws IOException {
167 if (regionFileCache == null) return null;
168 return regionFileCache.getTagAt(new ChunkPos(chunkX, chunkZ));
171 public void close() throws IOException {
172 if (regionFileCache == null) return;
173 regionFileCache.close();