package net.minecraft.world.level.levelgen.structure.templatesystem; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.google.common.collect.ImmutableList.Builder; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.datafixers.DataFixer; import com.mojang.logging.LogUtils; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; import net.minecraft.FileUtil; import net.minecraft.ResourceLocationException; import net.minecraft.SharedConstants; import net.minecraft.core.HolderGetter; import net.minecraft.gametest.framework.StructureUtils; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtAccounter; import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.NbtUtils; import net.minecraft.resources.FileToIdConverter; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.util.FastBufferedInputStream; import net.minecraft.util.datafix.DataFixTypes; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.storage.LevelResource; import net.minecraft.world.level.storage.LevelStorageSource; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; public class StructureTemplateManager { private static final Logger LOGGER = LogUtils.getLogger(); public static final String STRUCTURE_RESOURCE_DIRECTORY_NAME = "structure"; private static final String STRUCTURE_GENERATED_DIRECTORY_NAME = "structures"; private static final String STRUCTURE_FILE_EXTENSION = ".nbt"; private static final String STRUCTURE_TEXT_FILE_EXTENSION = ".snbt"; private final Map> structureRepository = Maps.newConcurrentMap(); private final DataFixer fixerUpper; private ResourceManager resourceManager; private final Path generatedDir; private final List sources; private final HolderGetter blockLookup; private static final FileToIdConverter RESOURCE_LISTER = new FileToIdConverter("structure", ".nbt"); public StructureTemplateManager( ResourceManager p_249872_, LevelStorageSource.LevelStorageAccess p_249864_, DataFixer p_249868_, HolderGetter p_256126_ ) { this.resourceManager = p_249872_; this.fixerUpper = p_249868_; this.generatedDir = p_249864_.getLevelPath(LevelResource.GENERATED_DIR).normalize(); this.blockLookup = p_256126_; Builder builder = ImmutableList.builder(); builder.add(new StructureTemplateManager.Source(this::loadFromGenerated, this::listGenerated)); if (SharedConstants.IS_RUNNING_IN_IDE) { builder.add(new StructureTemplateManager.Source(this::loadFromTestStructures, this::listTestStructures)); } builder.add(new StructureTemplateManager.Source(this::loadFromResource, this::listResources)); this.sources = builder.build(); } public StructureTemplate getOrCreate(ResourceLocation p_230360_) { Optional optional = this.get(p_230360_); if (optional.isPresent()) { return optional.get(); } else { StructureTemplate structuretemplate = new StructureTemplate(); this.structureRepository.put(p_230360_, Optional.of(structuretemplate)); return structuretemplate; } } public Optional get(ResourceLocation p_230408_) { return this.structureRepository.computeIfAbsent(p_230408_, this::tryLoad); } public Stream listTemplates() { return this.sources.stream().flatMap(p_230376_ -> p_230376_.lister().get()).distinct(); } private Optional tryLoad(ResourceLocation p_230426_) { for (StructureTemplateManager.Source structuretemplatemanager$source : this.sources) { try { Optional optional = structuretemplatemanager$source.loader().apply(p_230426_); if (optional.isPresent()) { return optional; } } catch (Exception exception) { } } return Optional.empty(); } public void onResourceManagerReload(ResourceManager p_230371_) { this.resourceManager = p_230371_; this.structureRepository.clear(); } private Optional loadFromResource(ResourceLocation p_230428_) { ResourceLocation resourcelocation = RESOURCE_LISTER.idToFile(p_230428_); return this.load( () -> this.resourceManager.open(resourcelocation), p_230366_ -> LOGGER.error("Couldn't load structure {}", p_230428_, p_230366_) ); } private Stream listResources() { return RESOURCE_LISTER.listMatchingResources(this.resourceManager).keySet().stream().map(RESOURCE_LISTER::fileToId); } private Optional loadFromTestStructures(ResourceLocation p_230430_) { return this.loadFromSnbt(p_230430_, StructureUtils.testStructuresDir); } private Stream listTestStructures() { if (!Files.isDirectory(StructureUtils.testStructuresDir)) { return Stream.empty(); } else { List list = new ArrayList<>(); this.listFolderContents(StructureUtils.testStructuresDir, "minecraft", ".snbt", list::add); return list.stream(); } } private Optional loadFromGenerated(ResourceLocation p_230432_) { if (!Files.isDirectory(this.generatedDir)) { return Optional.empty(); } else { Path path = this.createAndValidatePathToGeneratedStructure(p_230432_, ".nbt"); return this.load(() -> new FileInputStream(path.toFile()), p_230400_ -> LOGGER.error("Couldn't load structure from {}", path, p_230400_)); } } private Stream listGenerated() { if (!Files.isDirectory(this.generatedDir)) { return Stream.empty(); } else { try { List list = new ArrayList<>(); try (DirectoryStream directorystream = Files.newDirectoryStream(this.generatedDir, p_230419_ -> Files.isDirectory(p_230419_))) { for (Path path : directorystream) { String s = path.getFileName().toString(); Path path1 = path.resolve("structures"); this.listFolderContents(path1, s, ".nbt", list::add); } } return list.stream(); } catch (IOException ioexception) { return Stream.empty(); } } } private void listFolderContents(Path p_230395_, String p_230396_, String p_230397_, Consumer p_342318_) { int i = p_230397_.length(); Function function = p_230358_ -> p_230358_.substring(0, p_230358_.length() - i); try (Stream stream = Files.find( p_230395_, Integer.MAX_VALUE, (p_341961_, p_341962_) -> p_341962_.isRegularFile() && p_341961_.toString().endsWith(p_230397_) )) { stream.forEach(p_341959_ -> { try { p_342318_.accept(ResourceLocation.fromNamespaceAndPath(p_230396_, function.apply(this.relativize(p_230395_, p_341959_)))); } catch (ResourceLocationException resourcelocationexception) { LOGGER.error("Invalid location while listing folder {} contents", p_230395_, resourcelocationexception); } }); } catch (IOException ioexception) { LOGGER.error("Failed to list folder {} contents", p_230395_, ioexception); } } private String relativize(Path p_230402_, Path p_230403_) { return p_230402_.relativize(p_230403_).toString().replace(File.separator, "/"); } private Optional loadFromSnbt(ResourceLocation p_230368_, Path p_230369_) { if (!Files.isDirectory(p_230369_)) { return Optional.empty(); } else { Path path = FileUtil.createPathToResource(p_230369_, p_230368_.getPath(), ".snbt"); try { Optional optional; try (BufferedReader bufferedreader = Files.newBufferedReader(path)) { String s = IOUtils.toString(bufferedreader); optional = Optional.of(this.readStructure(NbtUtils.snbtToStructure(s))); } return optional; } catch (NoSuchFileException nosuchfileexception) { return Optional.empty(); } catch (CommandSyntaxException | IOException ioexception) { LOGGER.error("Couldn't load structure from {}", path, ioexception); return Optional.empty(); } } } private Optional load(StructureTemplateManager.InputStreamOpener p_230373_, Consumer p_230374_) { try { Optional optional; try ( InputStream inputstream = p_230373_.open(); InputStream inputstream1 = new FastBufferedInputStream(inputstream); ) { optional = Optional.of(this.readStructure(inputstream1)); } return optional; } catch (FileNotFoundException filenotfoundexception) { return Optional.empty(); } catch (Throwable throwable1) { p_230374_.accept(throwable1); return Optional.empty(); } } private StructureTemplate readStructure(InputStream p_230378_) throws IOException { CompoundTag compoundtag = NbtIo.readCompressed(p_230378_, NbtAccounter.unlimitedHeap()); return this.readStructure(compoundtag); } public StructureTemplate readStructure(CompoundTag p_230405_) { StructureTemplate structuretemplate = new StructureTemplate(); int i = NbtUtils.getDataVersion(p_230405_, 500); structuretemplate.load(this.blockLookup, DataFixTypes.STRUCTURE.updateToCurrentVersion(this.fixerUpper, p_230405_, i)); return structuretemplate; } public boolean save(ResourceLocation p_230417_) { Optional optional = this.structureRepository.get(p_230417_); if (optional.isEmpty()) { return false; } else { StructureTemplate structuretemplate = optional.get(); Path path = this.createAndValidatePathToGeneratedStructure(p_230417_, ".nbt"); Path path1 = path.getParent(); if (path1 == null) { return false; } else { try { Files.createDirectories(Files.exists(path1) ? path1.toRealPath() : path1); } catch (IOException ioexception) { LOGGER.error("Failed to create parent directory: {}", path1); return false; } CompoundTag compoundtag = structuretemplate.save(new CompoundTag()); try { try (OutputStream outputstream = new FileOutputStream(path.toFile())) { NbtIo.writeCompressed(compoundtag, outputstream); } return true; } catch (Throwable throwable1) { return false; } } } } public Path createAndValidatePathToGeneratedStructure(ResourceLocation p_345333_, String p_345223_) { if (p_345333_.getPath().contains("//")) { throw new ResourceLocationException("Invalid resource path: " + p_345333_); } else { try { Path path = this.generatedDir.resolve(p_345333_.getNamespace()); Path path1 = path.resolve("structures"); Path path2 = FileUtil.createPathToResource(path1, p_345333_.getPath(), p_345223_); if (path2.startsWith(this.generatedDir) && FileUtil.isPathNormalized(path2) && FileUtil.isPathPortable(path2)) { return path2; } else { throw new ResourceLocationException("Invalid resource path: " + path2); } } catch (InvalidPathException invalidpathexception) { throw new ResourceLocationException("Invalid resource path: " + p_345333_, invalidpathexception); } } } public void remove(ResourceLocation p_230422_) { this.structureRepository.remove(p_230422_); } @FunctionalInterface interface InputStreamOpener { InputStream open() throws IOException; } record Source(Function> loader, Supplier> lister) { } }