Skip to content

Commit 94efb70

Browse files
committed
Proper structure template file discovery
1 parent 7eacafc commit 94efb70

File tree

2 files changed

+110
-25
lines changed

2 files changed

+110
-25
lines changed

common/src/main/kotlin/com/lambda/command/commands/BuildCommand.kt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ import com.lambda.brigadier.argument.value
77
import com.lambda.brigadier.executeWithResult
88
import com.lambda.brigadier.required
99
import com.lambda.command.LambdaCommand
10+
import com.lambda.interaction.construction.Blueprint.Companion.toStructure
11+
import com.lambda.interaction.construction.DynamicBlueprint.Companion.toBlueprint
12+
import com.lambda.interaction.construction.StaticBlueprint.Companion.toBlueprint
1013
import com.lambda.interaction.construction.StructureRegistry
14+
import com.lambda.task.tasks.BuildTask.Companion.build
15+
import com.lambda.threading.runSafe
1116
import com.lambda.util.Communication.info
1217
import com.lambda.util.extension.CommandBuilder
18+
import com.lambda.util.extension.move
1319

1420
object BuildCommand : LambdaCommand(
1521
name = "Build",
@@ -20,17 +26,25 @@ object BuildCommand : LambdaCommand(
2026
required(literal("place")) {
2127
required(identifier("structure")) { structure ->
2228
suggests { _, builder ->
23-
StructureRegistry.keys
29+
StructureRegistry.streamTemplates()
2430
.forEach { builder.suggest(it.path) }
2531

2632
builder.buildFuture()
2733
}
2834

2935
executeWithResult {
30-
StructureRegistry.loadStructure(structure().value())?.let { template ->
31-
info("Building structure: ${template.size}")
36+
runSafe<Unit> {
37+
val id = structure().value()
38+
StructureRegistry.loadStructure(id)?.let { template ->
39+
info("Building structure ${id.path} with size ${template.size.toShortString()} by ${template.author}")
40+
template.toStructure()
41+
.move(player.blockPos)
42+
.toBlueprint()
43+
.build()
44+
.start(null)
3245

33-
return@executeWithResult CommandResult.success()
46+
return@executeWithResult CommandResult.success()
47+
}
3448
}
3549

3650
CommandResult.failure("Structure not found")

common/src/main/kotlin/com/lambda/interaction/construction/StructureRegistry.kt

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,15 @@ import net.minecraft.structure.StructureTemplate
1414
import net.minecraft.util.Identifier
1515
import net.minecraft.util.PathUtil
1616
import 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.*
2019
import 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")
2523
object 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

Comments
 (0)