Code/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplateManager.java

316 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.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<ResourceLocation, Optional<StructureTemplate>> structureRepository = Maps.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 p_249872_, LevelStorageSource.LevelStorageAccess p_249864_, DataFixer p_249868_, HolderGetter<Block> p_256126_
) {
this.resourceManager = p_249872_;
this.fixerUpper = p_249868_;
this.generatedDir = p_249864_.getLevelPath(LevelResource.GENERATED_DIR).normalize();
this.blockLookup = p_256126_;
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 p_230360_) {
Optional<StructureTemplate> 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<StructureTemplate> get(ResourceLocation p_230408_) {
return this.structureRepository.computeIfAbsent(p_230408_, this::tryLoad);
}
public Stream<ResourceLocation> listTemplates() {
return this.sources.stream().flatMap(p_230376_ -> p_230376_.lister().get()).distinct();
}
private Optional<StructureTemplate> tryLoad(ResourceLocation p_230426_) {
for (StructureTemplateManager.Source structuretemplatemanager$source : this.sources) {
try {
Optional<StructureTemplate> 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<StructureTemplate> 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<ResourceLocation> listResources() {
return RESOURCE_LISTER.listMatchingResources(this.resourceManager).keySet().stream().map(RESOURCE_LISTER::fileToId);
}
private Optional<StructureTemplate> loadFromTestStructures(ResourceLocation p_230430_) {
return this.loadFromSnbt(p_230430_, StructureUtils.testStructuresDir);
}
private Stream<ResourceLocation> listTestStructures() {
if (!Files.isDirectory(StructureUtils.testStructuresDir)) {
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 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<ResourceLocation> listGenerated() {
if (!Files.isDirectory(this.generatedDir)) {
return Stream.empty();
} else {
try {
List<ResourceLocation> list = new ArrayList<>();
try (DirectoryStream<Path> 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<ResourceLocation> p_342318_) {
int i = p_230397_.length();
Function<String, String> function = p_230358_ -> p_230358_.substring(0, p_230358_.length() - i);
try (Stream<Path> 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<StructureTemplate> 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<StructureTemplate> load(StructureTemplateManager.InputStreamOpener p_230373_, Consumer<Throwable> 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<StructureTemplate> 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<ResourceLocation, Optional<StructureTemplate>> loader, Supplier<Stream<ResourceLocation>> lister) {
}
}