11package com.lambda.interaction.construction
22
3+ import com.lambda.Lambda.LOG
34import com.lambda.Lambda.mc
45import com.lambda.core.Loadable
56import com.lambda.util.Communication.logError
@@ -14,12 +15,10 @@ import net.minecraft.nbt.NbtSizeTracker
1415import net.minecraft.registry.Registries
1516import net.minecraft.structure.StructureTemplate
1617import net.minecraft.util.WorldSavePath
17- import java.io.File
1818import java.nio.file.*
1919import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE
2020import java.nio.file.StandardWatchEventKinds.ENTRY_DELETE
2121import java.util.concurrent.ConcurrentHashMap
22- import kotlin.io.inputStream
2322import kotlin.io.path.*
2423
2524/* *
@@ -30,8 +29,7 @@ import kotlin.io.path.*
3029@OptIn(ExperimentalPathApi ::class )
3130@Suppress(" JavaIoSerializableObjectMustHaveReadResolve" )
3231object StructureRegistry : ConcurrentHashMap<String, StructureTemplate?>(), Loadable {
33- private val levelSession = mc.levelStorage.createSession(FolderRegister .structure.path)
34- private val structurePath = levelSession.getDirectory(WorldSavePath .ROOT ).normalize()
32+ private val structurePath = FolderRegister .structure
3533 private val pathWatcher = FileSystems .getDefault().newWatchService()
3634 .apply { structurePath.register(this , ENTRY_CREATE , ENTRY_DELETE ) }
3735
@@ -48,56 +46,51 @@ object StructureRegistry : ConcurrentHashMap<String, StructureTemplate?>(), Load
4846 )
4947
5048 /* *
51- * Searches for a structure file by name, trying all supported extensions.
52- *
53- * @param name The name of the structure file without the extension.
54- * @return The corresponding [File] if found, or null otherwise.
55- */
56- private fun findStructureByName (name : String ): File ? =
57- serializers
58- .keys
59- .firstNotNullOfOrNull { extension ->
60- structurePath
61- .resolve(" $name .$extension " )
62- .toFile()
63- .takeIf { it.isFile && it.exists() }
64- }
65-
66- /* *
67- * Loads a structure by name, will attempt a discovery sequence if the structure could not be found
49+ * Loads a structure by relative path, will attempt a discovery sequence if the structure could not be found
6850 * and performs format conversions if necessary
6951 *
70- * @param name The name of the structure to load (without extension).
52+ * @param relativePath The name of the structure to load (without extension).
7153 * @param convert Whether to replace the file after converting it.
7254 * @return The loaded [StructureTemplate], or null if the structure is not found.
7355 */
74- fun loadStructureByName (
75- name : String ,
56+ fun loadStructureByRelativePath (
57+ relativePath : Path ,
7658 convert : Boolean = true,
7759 ): StructureTemplate ? {
7860 if (! structurePath.isDirectory()) {
7961 logError(
80- " Invalid structure template: $name " ,
62+ " Invalid structure template: $relativePath " ,
8163 " The structure folder is not a folder"
8264 )
8365 return null
8466 }
8567
86- // Poll directory file events to load many structures at once
87- // They might not show up in the command suggestion, but they are
88- // present in the map
68+ updateFileWatcher()
69+
70+ return computeIfAbsent(relativePath.pathString.lowercase()) {
71+ loadFileAndCreate(relativePath, convert)?.also {
72+ LOG .info(" Loaded structure template $relativePath by ${it.author} with dimensions ${it.size.toShortString()} " )
73+ }
74+ }
75+ }
76+
77+ /* *
78+ * Poll directory file events to load many structures at once.
79+ * They might not show up in the command suggestion, but they are
80+ * present in the map.
81+ */
82+ private fun updateFileWatcher () {
8983 pathWatcher.poll()?.let { key ->
9084 key.pollEvents()
9185 ?.filterIsInstance<WatchEvent <Path >>()
9286 ?.forEach { event ->
93- val path = event.context()
94- val nameNoExt = path.nameWithoutExtension
87+ val newPath = event.context()
9588
9689 when (event.kind()) {
97- ENTRY_DELETE -> remove(nameNoExt )
90+ ENTRY_DELETE -> remove(newPath.pathString )
9891 ENTRY_CREATE -> {
99- if ( ! contains(nameNoExt) && nameNoExt != name ) {
100- loadStructureByName(path.nameWithoutExtension , convert = true )
92+ computeIfAbsent(newPath.pathString ) {
93+ loadStructureByRelativePath(newPath , convert = true )
10194 }
10295 }
10396 }
@@ -108,42 +101,36 @@ object StructureRegistry : ConcurrentHashMap<String, StructureTemplate?>(), Load
108101 if (! key.reset()) return @forEach
109102 }
110103 }
111-
112- return computeIfAbsent(name.lowercase()) { loadFileAndCreate(name, convert) }
113104 }
114105
115106 /* *
116107 * Loads the structure file and creates a [StructureTemplate].
117108 *
118- * @param name The name of the structure file (without extension).
119109 * @param convert Whether to replace the file after converting it.
120110 * @return The created [StructureTemplate], or null if the structure is not found or invalid.
121111 */
122- private fun loadFileAndCreate (name : String , convert : Boolean ) =
123- findStructureByName(name)
124- ?.let { it to it.extension }
125- ?.let { (file, extension) ->
126- file.inputStream().use { templateStream ->
127- val compound = NbtIo .readCompressed(templateStream, NbtSizeTracker .ofUnlimitedBytes())
128- val template = createStructure(compound, extension)
129-
130- if (convert && extension != " nbt" ) {
131- template?.let { saveStructure(name, it) }
132- }
112+ private fun loadFileAndCreate (path : Path , convert : Boolean ) =
113+ structurePath.resolve(path).inputStream().use { templateStream ->
114+ val compound = NbtIo .readCompressed(templateStream, NbtSizeTracker .ofUnlimitedBytes())
115+ val extension = path.extension
116+ val template = createStructure(compound, extension)
117+
118+ if (convert && extension != " nbt" ) {
119+ template?.let { saveStructure(path.pathString, it) }
120+ }
133121
134- // Verify the structure integrity after it had been
135- // converted to a regular structure template
136- if (compound.isValidStructureTemplate()) {
137- template
138- } else {
139- logError(
140- " Invalid structure template: $name " ,
141- " File does not match template format, it might have been corrupted" ,
142- )
143- null
144- }
145- }
122+ // Verify the structure integrity after it had been
123+ // converted to a regular structure template
124+ if (compound.isValidStructureTemplate()) {
125+ template
126+ } else {
127+ logError(
128+ " Invalid structure template: $path " ,
129+ " File does not match template format, it might have been corrupted" ,
130+ )
131+ null
146132 }
133+ }
147134
148135 /* *
149136 * Creates a [StructureTemplate] from the provided NBT data.
@@ -164,14 +151,14 @@ object StructureRegistry : ConcurrentHashMap<String, StructureTemplate?>(), Load
164151 /* *
165152 * Saves the provided [structure] to disk under the specified [name].
166153 *
167- * @param name The name of the structure file (without the extension) .
154+ * @param relativePath The relative path of the structure to save .
168155 * @param structure The [StructureTemplate] to save.
169156 */
170- private fun saveStructure (name : String , structure : StructureTemplate ) {
171- val path = structurePath.resolve(" $name .nbt" )
157+ private fun saveStructure (relativePath : String , structure : StructureTemplate ) {
158+ val path = structurePath.resolve(" $relativePath .nbt" )
172159 val compound = structure.writeNbt(NbtCompound ())
173160
174- Files .createDirectories(path.parent) // Ensure parent directories exist
161+ Files .createDirectories(path.parent)
175162 path.outputStream().use { output ->
176163 NbtIo .writeCompressed(compound, output)
177164 }
@@ -189,7 +176,7 @@ object StructureRegistry : ConcurrentHashMap<String, StructureTemplate?>(), Load
189176 override fun load (): String {
190177 structurePath.walk()
191178 .filter { it.extension in serializers.keys }
192- .forEach { path -> loadStructureByName(path.nameWithoutExtension ) }
179+ .forEach { loadStructureByRelativePath(structurePath.relativize(it) ) }
193180
194181 return " Loaded $size structure templates"
195182 }
0 commit comments