@@ -14,17 +14,15 @@ import net.minecraft.structure.StructureTemplate
1414import net.minecraft.util.Identifier
1515import net.minecraft.util.PathUtil
1616import net.minecraft.util.WorldSavePath
17- import java.nio.file.Files
18- import java.nio.file.LinkOption
19- import java.nio.file.StandardOpenOption
17+ import java.nio.file.*
18+ import kotlin.io.path.*
2019import java.util.concurrent.ConcurrentHashMap
21- import kotlin.io.path.inputStream
22- import kotlin.io.path.notExists
20+ import kotlin.streams.asSequence
2321
2422@Suppress(" JavaIoSerializableObjectMustHaveReadResolve" )
2523object StructureRegistry : ConcurrentHashMap<Identifier, StructureTemplate?>() {
2624 private val levelSession = mc.levelStorage.createSession(FolderRegister .structure.path)
27- private val generatedPath = levelSession.getDirectory(WorldSavePath .ROOT ).normalize()
25+ private val structurePath = levelSession.getDirectory(WorldSavePath .ROOT ).normalize()
2826
2927 /* *
3028 * Loads a structure from disk based on the provided [id].
@@ -33,19 +31,19 @@ object StructureRegistry : ConcurrentHashMap<Identifier, StructureTemplate?>() {
3331 * @return The loaded [StructureTemplate], or null if the structure is not found.
3432 */
3533 fun loadStructure (id : Identifier ): StructureTemplate ? {
36- val path = PathUtil .getResourcePath(generatedPath , id.path, " .nbt" )
34+ val path = PathUtil .getResourcePath(structurePath , id.path, " .nbt" )
3735
38- return if (! Files .isDirectory(generatedPath, LinkOption . NOFOLLOW_LINKS ) || path.notExists()) null
36+ return if (! structurePath .isDirectory() || path.notExists()) null
3937 else computeIfAbsent(id) {
40- val compound = path.inputStream(StandardOpenOption .READ )
41- .use { template ->
42- NbtIo .readCompressed(
43- template,
44- NbtSizeTracker .ofUnlimitedBytes()
45- ).also { template.close() }
38+ path.inputStream().use { templateStream ->
39+ val compound = NbtIo .readCompressed(templateStream, NbtSizeTracker .ofUnlimitedBytes())
40+ if (compound.isValidStructureTemplate()) {
41+ createStructure(compound)
42+ } else {
43+ logError(" Invalid structure template: ${path.fileName} " , " File does not match template format" )
44+ null
4645 }
47-
48- createStructure(nbt = compound)
46+ }
4947 }
5048 }
5149
@@ -61,9 +59,9 @@ object StructureRegistry : ConcurrentHashMap<Identifier, StructureTemplate?>() {
6159
6260 template.readNbtOrException(
6361 Registries .BLOCK .readOnlyWrapper,
64- DataFixTypes .STRUCTURE .update(mc.dataFixer, nbt, version),
65- )?.let { err ->
66- this @StructureRegistry. logError(" Could not create structure from file" , err .message ? : " " )
62+ DataFixTypes .STRUCTURE .update(mc.dataFixer, nbt, version)
63+ )?.let { error ->
64+ logError(" Could not create structure from file" , error .message ? : " " )
6765 return null
6866 }
6967
@@ -77,9 +75,82 @@ object StructureRegistry : ConcurrentHashMap<Identifier, StructureTemplate?>() {
7775 * @param structure The [StructureTemplate] to save.
7876 */
7977 fun saveStructure (name : String , structure : StructureTemplate ) {
80- val path = PathUtil .getResourcePath(generatedPath , name, " .nbt" )
78+ val path = PathUtil .getResourcePath(structurePath , name, " .nbt" )
8179 val compound = structure.writeNbt(NbtCompound ())
8280
83- NbtIo .writeCompressed(compound, path)
81+ Files .createDirectories(path.parent) // Ensure parent directories exist
82+ path.outputStream().use { output ->
83+ NbtIo .writeCompressed(compound, output)
84+ }
8485 }
86+
87+ /* *
88+ * Streams all available structure templates from the directory.
89+ *
90+ * @return A [Sequence] of [Identifier]s of the available templates.
91+ */
92+ fun streamTemplates (): Sequence <Identifier > {
93+ return if (! structurePath.isDirectory()) {
94+ emptySequence()
95+ } else {
96+ try {
97+ structurePath.walk().filter { it.isRegularFile() && it.extension == " nbt" }
98+ .filter { it.isValidNbtStructure() }
99+ .mapNotNull { it.toIdentifier() }
100+ } catch (e: Exception ) {
101+ logError(" Error streaming structure templates" , e)
102+ emptySequence()
103+ }
104+ }
105+ }
106+
107+ /* *
108+ * Converts a file [Path] to an [Identifier].
109+ *
110+ * @param this@pathToIdentifier The file path to convert.
111+ * @return The resulting [Identifier], or null if the path is invalid.
112+ */
113+ private fun Path.toIdentifier (): Identifier ? {
114+ return try {
115+ val relativePath = structurePath.relativize(this ).invariantSeparatorsPathString
116+ val namespace = " minecraft"
117+ val pathWithoutExtension = relativePath.removeSuffix(" .nbt" )
118+ Identifier (namespace, pathWithoutExtension)
119+ } catch (e: Exception ) {
120+ this @StructureRegistry.logError(" Invalid path for structure template" , e.message ? : " " )
121+ null
122+ }
123+ }
124+
125+ /* *
126+ * Walks through the [Path] hierarchy recursively and returns a [Sequence] of paths.
127+ */
128+ private fun Path.walk (): Sequence <Path > = Files .walk(this ).asSequence()
129+
130+ /* *
131+ * Checks whether the NBT file at the given [this@isValidNbtStructure] is a valid Minecraft structure template.
132+ *
133+ * @param this@isValidNbtStructure The path to the NBT file.
134+ * @return True if the NBT file is a valid structure template, false otherwise.
135+ */
136+ private fun Path.isValidNbtStructure () =
137+ runCatching {
138+ inputStream().use { input ->
139+ NbtIo .readCompressed(input, NbtSizeTracker .ofUnlimitedBytes())
140+ .isValidStructureTemplate()
141+ }
142+ }.getOrDefault(false )
143+
144+ /* *
145+ * Verifies that the provided NBT data represents a valid Minecraft structure template.
146+ *
147+ * @param this@isValidStructureTemplate The [NbtCompound] to validate.
148+ * @return True if the NBT contains valid structure template data, false otherwise.
149+ */
150+ private fun NbtCompound.isValidStructureTemplate () =
151+ contains(" DataVersion" )
152+ && contains(" blocks" )
153+ && contains(" entities" )
154+ && contains(" palette" )
155+ && contains(" size" )
85156}
0 commit comments