Skip to content

Commit a0ade28

Browse files
committed
Relative structure paths support
1 parent 466f3f3 commit a0ade28

File tree

12 files changed

+120
-104
lines changed

12 files changed

+120
-104
lines changed

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

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package com.lambda.command.commands
22

33
import com.lambda.brigadier.CommandResult
4-
import com.lambda.brigadier.argument.boolean
5-
import com.lambda.brigadier.argument.literal
6-
import com.lambda.brigadier.argument.string
7-
import com.lambda.brigadier.argument.value
4+
import com.lambda.brigadier.argument.*
85
import com.lambda.brigadier.executeWithResult
96
import com.lambda.brigadier.optional
107
import com.lambda.brigadier.required
@@ -17,6 +14,9 @@ import com.lambda.threading.runSafe
1714
import com.lambda.util.Communication.info
1815
import com.lambda.util.extension.CommandBuilder
1916
import com.lambda.util.extension.move
17+
import java.nio.file.InvalidPathException
18+
import java.nio.file.NoSuchFileException
19+
import java.nio.file.Path
2020

2121
object BuildCommand : LambdaCommand(
2222
name = "Build",
@@ -25,33 +25,40 @@ object BuildCommand : LambdaCommand(
2525
) {
2626
override fun CommandBuilder.create() {
2727
required(literal("place")) {
28-
required(string("structure")) { structure ->
28+
required(greedyString("structure")) { structure ->
2929
suggests { _, builder ->
30-
StructureRegistry
31-
.forEach { key, _ -> builder.suggest(key) }
30+
StructureRegistry.forEach { key, _ -> builder.suggest(key) }
3231

3332
builder.buildFuture()
3433
}
3534
optional(boolean("pathing")) { pathing ->
3635
executeWithResult {
37-
val id = structure().value()
38-
val path = if (pathing != null) pathing().value() else false
36+
val pathString = structure().value()
37+
val doPathing = if (pathing != null) pathing().value() else false
3938
runSafe<Unit> {
40-
StructureRegistry
41-
.loadStructureByName(id)
42-
?.let { template ->
43-
info("Building structure $id with dimensions ${template.size.toShortString()} created by ${template.author}")
44-
template.toStructure()
45-
.move(player.blockPos)
46-
.toBlueprint()
47-
.build(pathing = path)
48-
.start(null)
39+
try {
40+
StructureRegistry
41+
.loadStructureByRelativePath(Path.of(pathString))
42+
?.let { template ->
43+
info("Building structure $pathString with dimensions ${template.size.toShortString()} created by ${template.author}")
44+
template.toStructure()
45+
.move(player.blockPos)
46+
.toBlueprint()
47+
.build(pathing = doPathing)
48+
.start(null)
4949

50-
return@executeWithResult CommandResult.success()
51-
}
50+
return@executeWithResult CommandResult.success()
51+
}
52+
} catch (e: InvalidPathException) {
53+
return@executeWithResult CommandResult.failure("Invalid path $pathString")
54+
} catch (e: NoSuchFileException) {
55+
return@executeWithResult CommandResult.failure("Structure $pathString not found")
56+
} catch (e: Exception) {
57+
return@executeWithResult CommandResult.failure(e.message ?: "Failed to load structure $pathString")
58+
}
5259
}
5360

54-
CommandResult.failure("Structure $id not found")
61+
CommandResult.failure("Structure $pathString not found")
5562
}
5663
}
5764
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.lambda.module.modules.player.Replay
1313
import com.lambda.util.FolderRegister
1414
import com.lambda.util.FolderRegister.listRecursive
1515
import com.lambda.util.extension.CommandBuilder
16+
import kotlin.io.path.exists
1617

1718
object ReplayCommand : LambdaCommand(
1819
name = "replay",
@@ -31,7 +32,7 @@ object ReplayCommand : LambdaCommand(
3132
required(literal("load")) {
3233
required(greedyString("replay filepath")) { replayName ->
3334
suggests { _, builder ->
34-
val dir = FolderRegister.replay
35+
val dir = FolderRegister.replay.toFile()
3536
dir.listRecursive { it.isFile }.forEach {
3637
builder.suggest(it.relativeTo(dir).path)
3738
}
@@ -46,7 +47,7 @@ object ReplayCommand : LambdaCommand(
4647
}
4748

4849
try {
49-
Replay.loadRecording(replayFile)
50+
Replay.loadRecording(replayFile.toFile())
5051
} catch (e: JsonSyntaxException) {
5152
return@executeWithResult CommandResult.failure("Failed to load replay file: ${e.message}")
5253
}

common/src/main/kotlin/com/lambda/config/configurations/FriendConfig.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package com.lambda.config.configurations
22

33
import com.lambda.config.Configuration
44
import com.lambda.util.FolderRegister
5+
import java.io.File
56

67
object FriendConfig : Configuration() {
78
override val configName get() = "friends"
8-
override val primary = FolderRegister.config.resolve("$configName.json")
9+
override val primary: File = FolderRegister.config.resolve("$configName.json").toFile()
910
}

common/src/main/kotlin/com/lambda/config/configurations/GuiConfig.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package com.lambda.config.configurations
22

33
import com.lambda.config.Configuration
44
import com.lambda.util.FolderRegister
5+
import java.io.File
56

67
object GuiConfig : Configuration() {
78
override val configName get() = "gui"
8-
override val primary = FolderRegister.config.resolve("$configName.json")
9+
override val primary: File = FolderRegister.config.resolve("$configName.json").toFile()
910
}

common/src/main/kotlin/com/lambda/config/configurations/LambdaConfig.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package com.lambda.config.configurations
22

33
import com.lambda.config.Configuration
44
import com.lambda.util.FolderRegister
5+
import java.io.File
56

67
object LambdaConfig : Configuration() {
78
override val configName get() = "lambda"
8-
override val primary = FolderRegister.config.resolve("$configName.json")
9+
override val primary: File = FolderRegister.config.resolve("$configName.json").toFile()
910
}

common/src/main/kotlin/com/lambda/config/configurations/ModuleConfig.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.lambda.config.Configuration
44
import com.lambda.config.configurations.ModuleConfig.configName
55
import com.lambda.config.configurations.ModuleConfig.primary
66
import com.lambda.util.FolderRegister
7+
import java.io.File
78

89

910
/**
@@ -16,5 +17,5 @@ import com.lambda.util.FolderRegister
1617
*/
1718
object ModuleConfig : Configuration() {
1819
override val configName get() = "modules"
19-
override val primary = FolderRegister.config.resolve("$configName.json")
20+
override val primary: File = FolderRegister.config.resolve("$configName.json").toFile()
2021
}

common/src/main/kotlin/com/lambda/core/Loader.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.lambda.interaction.material.ContainerManager
1515
import com.lambda.module.ModuleRegistry
1616
import com.lambda.sound.SoundRegistry
1717
import com.lambda.util.Communication.ascii
18+
import com.lambda.util.FolderRegister
1819
import kotlin.system.measureTimeMillis
1920
import kotlin.time.DurationUnit
2021
import kotlin.time.toDuration
@@ -26,6 +27,7 @@ object Loader {
2627
get() = "${(System.currentTimeMillis() - started).toDuration(DurationUnit.MILLISECONDS)}"
2728

2829
private val loadables = listOf(
30+
FolderRegister,
2931
ModuleRegistry,
3032
CommandRegistry,
3133
RotationManager,

common/src/main/kotlin/com/lambda/http/Request.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ data class Request(
3737
* @param maxAge The maximum age of the cached resource. Default is 4 days.
3838
*/
3939
fun maybeDownload(name: String, maxAge: Duration = 7.days): File {
40-
val file = cache.resolve(name).createIfNotExists()
40+
val file = cache.resolve(name).toFile().createIfNotExists()
4141

4242
if (
4343
System.currentTimeMillis() - file.lastModified() < maxAge.inWholeMilliseconds

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

Lines changed: 51 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.lambda.interaction.construction
22

3+
import com.lambda.Lambda.LOG
34
import com.lambda.Lambda.mc
45
import com.lambda.core.Loadable
56
import com.lambda.util.Communication.logError
@@ -14,12 +15,10 @@ import net.minecraft.nbt.NbtSizeTracker
1415
import net.minecraft.registry.Registries
1516
import net.minecraft.structure.StructureTemplate
1617
import net.minecraft.util.WorldSavePath
17-
import java.io.File
1818
import java.nio.file.*
1919
import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE
2020
import java.nio.file.StandardWatchEventKinds.ENTRY_DELETE
2121
import java.util.concurrent.ConcurrentHashMap
22-
import kotlin.io.inputStream
2322
import kotlin.io.path.*
2423

2524
/**
@@ -30,8 +29,7 @@ import kotlin.io.path.*
3029
@OptIn(ExperimentalPathApi::class)
3130
@Suppress("JavaIoSerializableObjectMustHaveReadResolve")
3231
object 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
}

common/src/main/kotlin/com/lambda/module/modules/network/PacketLogger.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ object PacketLogger : Module(
7979
val fileName = "packet-log-${getTime(fileFormatter)}.txt"
8080

8181
// ToDo: Organize files with FolderRegister.worldBoundDirectory
82-
file = FolderRegister.packetLogs.resolve(fileName).apply {
82+
file = FolderRegister.packetLogs.resolve(fileName).toFile().apply {
8383
if (!parentFile.exists()) {
8484
parentFile.mkdirs()
8585
}

0 commit comments

Comments
 (0)