minecraft-src/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java
2025-07-04 03:45:38 +03:00

405 lines
13 KiB
Java

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.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
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<ResourceLocation, Optional<StructureTemplate>> structureRepository = Maps.<ResourceLocation, Optional<StructureTemplate>>newConcurrentMap();
private final DataFixer fixerUpper;
private ResourceManager resourceManager;
private final Path generatedDir;
private final List<StructureTemplateManager.Source> sources;
private final HolderGetter<Block> blockLookup;
private static final FileToIdConverter RESOURCE_LISTER = new FileToIdConverter("structure", ".nbt");
public StructureTemplateManager(
ResourceManager resourceManager, LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper, HolderGetter<Block> blockLookup
) {
this.resourceManager = resourceManager;
this.fixerUpper = fixerUpper;
this.generatedDir = levelStorageAccess.getLevelPath(LevelResource.GENERATED_DIR).normalize();
this.blockLookup = blockLookup;
Builder<StructureTemplateManager.Source> 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 id) {
Optional<StructureTemplate> optional = this.get(id);
if (optional.isPresent()) {
return (StructureTemplate)optional.get();
} else {
StructureTemplate structureTemplate = new StructureTemplate();
this.structureRepository.put(id, Optional.of(structureTemplate));
return structureTemplate;
}
}
public Optional<StructureTemplate> get(ResourceLocation id) {
return (Optional<StructureTemplate>)this.structureRepository.computeIfAbsent(id, this::tryLoad);
}
public Stream<ResourceLocation> listTemplates() {
return this.sources.stream().flatMap(source -> (Stream)source.lister().get()).distinct();
}
private Optional<StructureTemplate> tryLoad(ResourceLocation id) {
for (StructureTemplateManager.Source source : this.sources) {
try {
Optional<StructureTemplate> optional = (Optional<StructureTemplate>)source.loader().apply(id);
if (optional.isPresent()) {
return optional;
}
} catch (Exception var5) {
}
}
return Optional.empty();
}
public void onResourceManagerReload(ResourceManager resourceManager) {
this.resourceManager = resourceManager;
this.structureRepository.clear();
}
private Optional<StructureTemplate> loadFromResource(ResourceLocation id) {
ResourceLocation resourceLocation = RESOURCE_LISTER.idToFile(id);
return this.load(() -> this.resourceManager.open(resourceLocation), throwable -> LOGGER.error("Couldn't load structure {}", id, throwable));
}
private Stream<ResourceLocation> listResources() {
return RESOURCE_LISTER.listMatchingResources(this.resourceManager).keySet().stream().map(RESOURCE_LISTER::fileToId);
}
private Optional<StructureTemplate> loadFromTestStructures(ResourceLocation id) {
return this.loadFromSnbt(id, StructureUtils.testStructuresDir);
}
private Stream<ResourceLocation> listTestStructures() {
if (!Files.isDirectory(StructureUtils.testStructuresDir, new LinkOption[0])) {
return Stream.empty();
} else {
List<ResourceLocation> list = new ArrayList();
this.listFolderContents(StructureUtils.testStructuresDir, "minecraft", ".snbt", list::add);
return list.stream();
}
}
private Optional<StructureTemplate> loadFromGenerated(ResourceLocation id) {
if (!Files.isDirectory(this.generatedDir, new LinkOption[0])) {
return Optional.empty();
} else {
Path path = this.createAndValidatePathToGeneratedStructure(id, ".nbt");
return this.load(() -> new FileInputStream(path.toFile()), throwable -> LOGGER.error("Couldn't load structure from {}", path, throwable));
}
}
private Stream<ResourceLocation> listGenerated() {
if (!Files.isDirectory(this.generatedDir, new LinkOption[0])) {
return Stream.empty();
} else {
try {
List<ResourceLocation> list = new ArrayList();
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(this.generatedDir, pathx -> Files.isDirectory(pathx, new LinkOption[0]));
try {
for (Path path : directoryStream) {
String string = path.getFileName().toString();
Path path2 = path.resolve("structures");
this.listFolderContents(path2, string, ".nbt", list::add);
}
} catch (Throwable var8) {
if (directoryStream != null) {
try {
directoryStream.close();
} catch (Throwable var7) {
var8.addSuppressed(var7);
}
}
throw var8;
}
if (directoryStream != null) {
directoryStream.close();
}
return list.stream();
} catch (IOException var9) {
return Stream.empty();
}
}
}
private void listFolderContents(Path folder, String namespace, String extension, Consumer<ResourceLocation> output) {
int i = extension.length();
Function<String, String> function = string -> string.substring(0, string.length() - i);
try {
Stream<Path> stream = Files.find(
folder,
Integer.MAX_VALUE,
(path, basicFileAttributes) -> basicFileAttributes.isRegularFile() && path.toString().endsWith(extension),
new FileVisitOption[0]
);
try {
stream.forEach(path2 -> {
try {
output.accept(ResourceLocation.fromNamespaceAndPath(namespace, (String)function.apply(this.relativize(folder, path2))));
} catch (ResourceLocationException var7x) {
LOGGER.error("Invalid location while listing folder {} contents", folder, var7x);
}
});
} catch (Throwable var11) {
if (stream != null) {
try {
stream.close();
} catch (Throwable var10) {
var11.addSuppressed(var10);
}
}
throw var11;
}
if (stream != null) {
stream.close();
}
} catch (IOException var12) {
LOGGER.error("Failed to list folder {} contents", folder, var12);
}
}
private String relativize(Path root, Path path) {
return root.relativize(path).toString().replace(File.separator, "/");
}
private Optional<StructureTemplate> loadFromSnbt(ResourceLocation id, Path path) {
if (!Files.isDirectory(path, new LinkOption[0])) {
return Optional.empty();
} else {
Path path2 = FileUtil.createPathToResource(path, id.getPath(), ".snbt");
try {
BufferedReader bufferedReader = Files.newBufferedReader(path2);
Optional var6;
try {
String string = IOUtils.toString(bufferedReader);
var6 = Optional.of(this.readStructure(NbtUtils.snbtToStructure(string)));
} catch (Throwable var8) {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (Throwable var7) {
var8.addSuppressed(var7);
}
}
throw var8;
}
if (bufferedReader != null) {
bufferedReader.close();
}
return var6;
} catch (NoSuchFileException var9) {
return Optional.empty();
} catch (CommandSyntaxException | IOException var10) {
LOGGER.error("Couldn't load structure from {}", path2, var10);
return Optional.empty();
}
}
}
private Optional<StructureTemplate> load(StructureTemplateManager.InputStreamOpener inputStream, Consumer<Throwable> onError) {
try {
InputStream inputStream2 = inputStream.open();
Optional var5;
try {
InputStream inputStream3 = new FastBufferedInputStream(inputStream2);
try {
var5 = Optional.of(this.readStructure(inputStream3));
} catch (Throwable var9) {
try {
inputStream3.close();
} catch (Throwable var8) {
var9.addSuppressed(var8);
}
throw var9;
}
inputStream3.close();
} catch (Throwable var10) {
if (inputStream2 != null) {
try {
inputStream2.close();
} catch (Throwable var7) {
var10.addSuppressed(var7);
}
}
throw var10;
}
if (inputStream2 != null) {
inputStream2.close();
}
return var5;
} catch (FileNotFoundException var11) {
return Optional.empty();
} catch (Throwable var12) {
onError.accept(var12);
return Optional.empty();
}
}
private StructureTemplate readStructure(InputStream stream) throws IOException {
CompoundTag compoundTag = NbtIo.readCompressed(stream, NbtAccounter.unlimitedHeap());
return this.readStructure(compoundTag);
}
public StructureTemplate readStructure(CompoundTag nbt) {
StructureTemplate structureTemplate = new StructureTemplate();
int i = NbtUtils.getDataVersion(nbt, 500);
structureTemplate.load(this.blockLookup, DataFixTypes.STRUCTURE.updateToCurrentVersion(this.fixerUpper, nbt, i));
return structureTemplate;
}
public boolean save(ResourceLocation id) {
Optional<StructureTemplate> optional = (Optional<StructureTemplate>)this.structureRepository.get(id);
if (optional.isEmpty()) {
return false;
} else {
StructureTemplate structureTemplate = (StructureTemplate)optional.get();
Path path = this.createAndValidatePathToGeneratedStructure(id, ".nbt");
Path path2 = path.getParent();
if (path2 == null) {
return false;
} else {
try {
Files.createDirectories(Files.exists(path2, new LinkOption[0]) ? path2.toRealPath() : path2);
} catch (IOException var13) {
LOGGER.error("Failed to create parent directory: {}", path2);
return false;
}
CompoundTag compoundTag = structureTemplate.save(new CompoundTag());
try {
OutputStream outputStream = new FileOutputStream(path.toFile());
try {
NbtIo.writeCompressed(compoundTag, outputStream);
} catch (Throwable var11) {
try {
outputStream.close();
} catch (Throwable var10) {
var11.addSuppressed(var10);
}
throw var11;
}
outputStream.close();
return true;
} catch (Throwable var12) {
return false;
}
}
}
}
public Path createAndValidatePathToGeneratedStructure(ResourceLocation location, String extension) {
if (location.getPath().contains("//")) {
throw new ResourceLocationException("Invalid resource path: " + location);
} else {
try {
Path path = this.generatedDir.resolve(location.getNamespace());
Path path2 = path.resolve("structures");
Path path3 = FileUtil.createPathToResource(path2, location.getPath(), extension);
if (path3.startsWith(this.generatedDir) && FileUtil.isPathNormalized(path3) && FileUtil.isPathPortable(path3)) {
return path3;
} else {
throw new ResourceLocationException("Invalid resource path: " + path3);
}
} catch (InvalidPathException var6) {
throw new ResourceLocationException("Invalid resource path: " + location, var6);
}
}
}
public void remove(ResourceLocation id) {
this.structureRepository.remove(id);
}
@FunctionalInterface
interface InputStreamOpener {
InputStream open() throws IOException;
}
record Source(Function<ResourceLocation, Optional<StructureTemplate>> loader, Supplier<Stream<ResourceLocation>> lister) {
}
}