refactor: Split waypoint classes

This commit is contained in:
Linnea Gräf
2025-03-22 11:27:03 +01:00
parent aa327dcfd0
commit 588f8bbb82
4 changed files with 205 additions and 153 deletions

View File

@@ -0,0 +1,37 @@
package moe.nea.firmament.features.world
import kotlinx.serialization.Serializable
import net.minecraft.text.Text
import moe.nea.firmament.Firmament
import moe.nea.firmament.util.ErrorUtil
object ColeWeightCompat {
@Serializable
data class ColeWeightWaypoint(
val x: Int,
val y: Int,
val z: Int,
val r: Int = 0,
val g: Int = 0,
val b: Int = 0,
)
fun intoFirm(waypoints: List<ColeWeightWaypoint>): FirmWaypoints {
val w = waypoints.map {
FirmWaypoints.Waypoint(it.x, it.y, it.z)
}
return FirmWaypoints(
"Imported Waypoints",
"imported",
null,
w.toMutableList(),
false
)
}
fun tryParse(string: String): Result<List<ColeWeightWaypoint>> {
return runCatching {
Firmament.tightJson.decodeFromString<List<ColeWeightWaypoint>>(string)
}
}
}

View File

@@ -0,0 +1,28 @@
package moe.nea.firmament.features.world
import net.minecraft.util.math.BlockPos
data class FirmWaypoints(
var label: String,
var id: String,
/**
* A hint to indicate where to stand while loading the waypoints.
*/
var isRelativeTo: String?,
var waypoints: MutableList<Waypoint>,
var isOrdered: Boolean,
// TODO: val resetOnSwap: Boolean,
) {
val size get() = waypoints.size
data class Waypoint(
val x: Int,
val y: Int,
val z: Int,
) {
val blockPos get() = BlockPos(x, y, z)
companion object {
fun from(blockPos: BlockPos) = Waypoint(blockPos.x, blockPos.y, blockPos.z)
}
}
}

View File

@@ -2,21 +2,12 @@ package moe.nea.firmament.features.world
import com.mojang.brigadier.arguments.IntegerArgumentType import com.mojang.brigadier.arguments.IntegerArgumentType
import me.shedaniel.math.Color import me.shedaniel.math.Color
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
import net.minecraft.command.argument.BlockPosArgumentType import net.minecraft.command.argument.BlockPosArgumentType
import net.minecraft.server.command.CommandOutput
import net.minecraft.server.command.ServerCommandSource
import net.minecraft.text.Text import net.minecraft.text.Text
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.util.math.Vec3d import net.minecraft.util.math.Vec3d
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.commands.get import moe.nea.firmament.commands.get
import moe.nea.firmament.commands.thenArgument import moe.nea.firmament.commands.thenArgument
@@ -32,6 +23,7 @@ import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.ClipboardUtils import moe.nea.firmament.util.ClipboardUtils
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
import moe.nea.firmament.util.TimeMark import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.mc.asFakeServer
import moe.nea.firmament.util.render.RenderInWorldContext import moe.nea.firmament.util.render.RenderInWorldContext
import moe.nea.firmament.util.tr import moe.nea.firmament.util.tr
@@ -56,52 +48,33 @@ object Waypoints : FirmamentFeature {
val temporaryPlayerWaypointList = mutableMapOf<String, TemporaryWaypoint>() val temporaryPlayerWaypointList = mutableMapOf<String, TemporaryWaypoint>()
val temporaryPlayerWaypointMatcher = "(?i)x: (-?[0-9]+),? y: (-?[0-9]+),? z: (-?[0-9]+)".toPattern() val temporaryPlayerWaypointMatcher = "(?i)x: (-?[0-9]+),? y: (-?[0-9]+),? z: (-?[0-9]+)".toPattern()
val waypoints = mutableListOf<BlockPos>() var waypoints: FirmWaypoints? = null
var ordered = false
var orderedIndex = 0 var orderedIndex = 0
@Serializable
data class ColeWeightWaypoint(
val x: Int,
val y: Int,
val z: Int,
val r: Int = 0,
val g: Int = 0,
val b: Int = 0,
)
@Subscribe @Subscribe
fun onRenderOrderedWaypoints(event: WorldRenderLastEvent) { fun onRenderOrderedWaypoints(event: WorldRenderLastEvent) {
if (waypoints.isEmpty()) return val w = useNonEmptyWaypoints() ?: return
RenderInWorldContext.renderInWorld(event) { RenderInWorldContext.renderInWorld(event) {
if (!ordered) { if (!w.isOrdered) {
waypoints.withIndex().forEach { w.waypoints.withIndex().forEach {
block(it.value, 0x800050A0.toInt()) block(it.value.blockPos, 0x800050A0.toInt())
if (TConfig.showIndex) if (TConfig.showIndex) withFacingThePlayer(it.value.blockPos.toCenterPos()) {
withFacingThePlayer(it.value.toCenterPos()) {
text(Text.literal(it.index.toString())) text(Text.literal(it.index.toString()))
} }
} }
} else { } else {
orderedIndex %= waypoints.size orderedIndex %= w.waypoints.size
val firstColor = Color.ofRGBA(0, 200, 40, 180) val firstColor = Color.ofRGBA(0, 200, 40, 180)
color(firstColor) color(firstColor)
tracer(waypoints[orderedIndex].toCenterPos(), lineWidth = 3f) tracer(w.waypoints[orderedIndex].blockPos.toCenterPos(), lineWidth = 3f)
waypoints.withIndex().toList() w.waypoints.withIndex().toList().wrappingWindow(orderedIndex, 3).zip(listOf(
.wrappingWindow(orderedIndex, 3)
.zip(
listOf(
firstColor, firstColor,
Color.ofRGBA(180, 200, 40, 150), Color.ofRGBA(180, 200, 40, 150),
Color.ofRGBA(180, 80, 20, 140), Color.ofRGBA(180, 80, 20, 140),
) )).reversed().forEach { (waypoint, col) ->
)
.reversed()
.forEach { (waypoint, col) ->
val (index, pos) = waypoint val (index, pos) = waypoint
block(pos, col.color) block(pos.blockPos, col.color)
if (TConfig.showIndex) if (TConfig.showIndex) withFacingThePlayer(pos.blockPos.toCenterPos()) {
withFacingThePlayer(pos.toCenterPos()) {
text(Text.literal(index.toString())) text(Text.literal(index.toString()))
} }
} }
@@ -111,15 +84,17 @@ object Waypoints : FirmamentFeature {
@Subscribe @Subscribe
fun onTick(event: TickEvent) { fun onTick(event: TickEvent) {
if (waypoints.isEmpty() || !ordered) return val w = useNonEmptyWaypoints() ?: return
orderedIndex %= waypoints.size if (!w.isOrdered) return
orderedIndex %= w.waypoints.size
val p = MC.player?.pos ?: return val p = MC.player?.pos ?: return
if (TConfig.skipToNearest) { if (TConfig.skipToNearest) {
orderedIndex = orderedIndex =
(waypoints.withIndex().minBy { it.value.getSquaredDistance(p) }.index + 1) % waypoints.size (w.waypoints.withIndex().minBy { it.value.blockPos.getSquaredDistance(p) }.index + 1) % w.waypoints.size
} else { } else {
if (waypoints[orderedIndex].isWithinDistance(p, 3.0)) { if (w.waypoints[orderedIndex].blockPos.isWithinDistance(p, 3.0)) {
orderedIndex = (orderedIndex + 1) % waypoints.size orderedIndex = (orderedIndex + 1) % w.waypoints.size
} }
} }
} }
@@ -128,57 +103,70 @@ object Waypoints : FirmamentFeature {
fun onProcessChat(it: ProcessChatEvent) { fun onProcessChat(it: ProcessChatEvent) {
val matcher = temporaryPlayerWaypointMatcher.matcher(it.unformattedString) val matcher = temporaryPlayerWaypointMatcher.matcher(it.unformattedString)
if (it.nameHeuristic != null && TConfig.tempWaypointDuration > 0.seconds && matcher.find()) { if (it.nameHeuristic != null && TConfig.tempWaypointDuration > 0.seconds && matcher.find()) {
temporaryPlayerWaypointList[it.nameHeuristic] = TemporaryWaypoint( temporaryPlayerWaypointList[it.nameHeuristic] = TemporaryWaypoint(BlockPos(
BlockPos(
matcher.group(1).toInt(), matcher.group(1).toInt(),
matcher.group(2).toInt(), matcher.group(2).toInt(),
matcher.group(3).toInt(), matcher.group(3).toInt(),
), ), TimeMark.now())
TimeMark.now()
)
} }
} }
fun useEditableWaypoints(): FirmWaypoints {
var w = waypoints
if (w == null) {
w = FirmWaypoints("Unlabeled", "unlabeled", null, mutableListOf(), false)
waypoints = w
}
return w
}
fun useNonEmptyWaypoints(): FirmWaypoints? {
val w = waypoints
if (w == null) return null
if (w.waypoints.isEmpty()) return null
return w
}
@Subscribe @Subscribe
fun onCommand(event: CommandEvent.SubCommand) { fun onCommand(event: CommandEvent.SubCommand) {
event.subcommand("waypoint") { event.subcommand("waypoint") {
thenArgument("pos", BlockPosArgumentType.blockPos()) { pos -> thenArgument("pos", BlockPosArgumentType.blockPos()) { pos ->
thenExecute { thenExecute {
source
val position = pos.get(this).toAbsoluteBlockPos(source.asFakeServer()) val position = pos.get(this).toAbsoluteBlockPos(source.asFakeServer())
waypoints.add(position) val w = useEditableWaypoints()
source.sendFeedback( w.waypoints.add(FirmWaypoints.Waypoint.from(position))
Text.stringifiedTranslatable( source.sendFeedback(Text.stringifiedTranslatable("firmament.command.waypoint.added",
"firmament.command.waypoint.added",
position.x, position.x,
position.y, position.y,
position.z position.z))
)
)
} }
} }
} }
event.subcommand("waypoints") { event.subcommand("waypoints") {
thenLiteral("clear") { thenLiteral("clear") {
thenExecute { thenExecute {
waypoints.clear() waypoints?.waypoints?.clear()
source.sendFeedback(Text.translatable("firmament.command.waypoint.clear")) source.sendFeedback(Text.translatable("firmament.command.waypoint.clear"))
} }
} }
thenLiteral("toggleordered") { thenLiteral("toggleordered") {
thenExecute { thenExecute {
ordered = !ordered val w = useEditableWaypoints()
if (ordered) { w.isOrdered = !w.isOrdered
if (w.isOrdered) {
val p = MC.player?.pos ?: Vec3d.ZERO val p = MC.player?.pos ?: Vec3d.ZERO
orderedIndex = orderedIndex = // TODO: this should be extracted to a utility method
waypoints.withIndex().minByOrNull { it.value.getSquaredDistance(p) }?.index ?: 0 w.waypoints.withIndex().minByOrNull { it.value.blockPos.getSquaredDistance(p) }?.index ?: 0
} }
source.sendFeedback(Text.translatable("firmament.command.waypoint.ordered.toggle.$ordered")) source.sendFeedback(Text.translatable("firmament.command.waypoint.ordered.toggle.${w.isOrdered}"))
} }
} }
thenLiteral("skip") { thenLiteral("skip") {
thenExecute { thenExecute {
if (ordered && waypoints.isNotEmpty()) { val w = useNonEmptyWaypoints()
orderedIndex = (orderedIndex + 1) % waypoints.size if (w != null && w.isOrdered) {
orderedIndex = (orderedIndex + 1) % w.size
source.sendFeedback(Text.translatable("firmament.command.waypoint.skip")) source.sendFeedback(Text.translatable("firmament.command.waypoint.skip"))
} else { } else {
source.sendError(Text.translatable("firmament.command.waypoint.skip.error")) source.sendError(Text.translatable("firmament.command.waypoint.skip.error"))
@@ -189,10 +177,10 @@ object Waypoints : FirmamentFeature {
thenArgument("index", IntegerArgumentType.integer(0)) { indexArg -> thenArgument("index", IntegerArgumentType.integer(0)) { indexArg ->
thenExecute { thenExecute {
val index = get(indexArg) val index = get(indexArg)
if (index in waypoints.indices) { val w = useNonEmptyWaypoints()
waypoints.removeAt(index) if (w != null && index in w.waypoints.indices) {
source.sendFeedback(Text.stringifiedTranslatable( w.waypoints.removeAt(index)
"firmament.command.waypoint.remove", source.sendFeedback(Text.stringifiedTranslatable("firmament.command.waypoint.remove",
index)) index))
} else { } else {
source.sendError(Text.stringifiedTranslatable("firmament.command.waypoint.remove.error")) source.sendError(Text.stringifiedTranslatable("firmament.command.waypoint.remove.error"))
@@ -202,47 +190,48 @@ object Waypoints : FirmamentFeature {
} }
thenLiteral("export") { thenLiteral("export") {
thenExecute { thenExecute {
val data = Firmament.tightJson.encodeToString<List<ColeWeightWaypoint>>(waypoints.map { TODO()
ColeWeightWaypoint(it.x, // val data = Firmament.tightJson.encodeToString<List<ColeWeightWaypoint>>(waypoints.map {
it.y, // ColeWeightWaypoint(it.x,
it.z) // it.y,
}) // it.z)
ClipboardUtils.setTextContent(data) // })
source.sendFeedback(tr("firmament.command.waypoint.export", // ClipboardUtils.setTextContent(data)
"Copied ${waypoints.size} waypoints to clipboard")) // source.sendFeedback(tr("firmament.command.waypoint.export",
// "Copied ${waypoints.size} waypoints to clipboard"))
} }
} }
thenLiteral("exportrelative") { thenLiteral("exportrelative") {
thenExecute { thenExecute {
val playerPos = MC.player!!.blockPos TODO()
val x = playerPos.x // val playerPos = MC.player!!.blockPos
val y = playerPos.y // val x = playerPos.x
val z = playerPos.z // val y = playerPos.y
val data = Firmament.tightJson.encodeToString<List<ColeWeightWaypoint>>(waypoints.map { // val z = playerPos.z
ColeWeightWaypoint(it.x - x, // val data = Firmament.tightJson.encodeToString<List<ColeWeightWaypoint>>(waypoints.map {
it.y - y, // ColeWeightWaypoint(it.x - x,
it.z - z) // it.y - y,
}) // it.z - z)
ClipboardUtils.setTextContent(data) // })
source.sendFeedback(tr("firmament.command.waypoint.export.relative", // ClipboardUtils.setTextContent(data)
"Copied ${waypoints.size} relative waypoints to clipboard. Make sure to stand in the same position when importing.")) // source.sendFeedback(tr("firmament.command.waypoint.export.relative",
// "Copied ${waypoints.size} relative waypoints to clipboard. Make sure to stand in the same position when importing."))
//
} }
} }
thenLiteral("import") { thenLiteral("import") {
thenExecute { thenExecute {
source.sendFeedback( source.sendFeedback(
importRelative(BlockPos.ORIGIN) importRelative(BlockPos.ORIGIN)// TODO: rework imports
?: Text.stringifiedTranslatable("firmament.command.waypoint.import", waypoints.size), ?: Text.stringifiedTranslatable("firmament.command.waypoint.import", useNonEmptyWaypoints()?.waypoints?.size),
) )
} }
} }
thenLiteral("importrelative") { thenLiteral("importrelative") {
thenExecute { thenExecute {
source.sendFeedback( source.sendFeedback(
importRelative(MC.player!!.blockPos) importRelative(MC.player!!.blockPos) ?: tr("firmament.command.waypoint.import.relative",
?: tr("firmament.command.waypoint.import.relative", "Imported ${useNonEmptyWaypoints()?.waypoints?.size} relative waypoints from clipboard. Make sure you stand in the same position as when you exported these waypoints for them to line up correctly."),
"Imported ${waypoints.size} relative waypoints from clipboard. Make sure you stand in the same position as when you exported these waypoints for them to line up correctly."),
) )
} }
} }
@@ -251,15 +240,10 @@ object Waypoints : FirmamentFeature {
fun importRelative(pos: BlockPos): Text? { fun importRelative(pos: BlockPos): Text? {
val contents = ClipboardUtils.getTextContents() val contents = ClipboardUtils.getTextContents()
val data = try { val cw = ColeWeightCompat.tryParse(contents).map { ColeWeightCompat.intoFirm(it) }
Firmament.tightJson.decodeFromString<List<ColeWeightWaypoint>>(contents) waypoints = cw.getOrNull() // TODO: directly parse firm waypoints
} catch (ex: Exception) { return null // TODO: show error if this does not work
Firmament.logger.error("Could not load waypoints from clipboard", ex) // TODO: make relative imports work again
return (Text.translatable("firmament.command.waypoint.import.error"))
}
waypoints.clear()
data.mapTo(waypoints) { BlockPos(it.x + pos.x, it.y + pos.y, it.z + pos.z) }
return null
} }
@Subscribe @Subscribe
@@ -267,14 +251,12 @@ object Waypoints : FirmamentFeature {
temporaryPlayerWaypointList.entries.removeIf { it.value.postedAt.passedTime() > TConfig.tempWaypointDuration } temporaryPlayerWaypointList.entries.removeIf { it.value.postedAt.passedTime() > TConfig.tempWaypointDuration }
if (temporaryPlayerWaypointList.isEmpty()) return if (temporaryPlayerWaypointList.isEmpty()) return
RenderInWorldContext.renderInWorld(event) { RenderInWorldContext.renderInWorld(event) {
temporaryPlayerWaypointList.forEach { (player, waypoint) -> temporaryPlayerWaypointList.forEach { (_, waypoint) ->
block(waypoint.pos, 0xFFFFFF00.toInt()) block(waypoint.pos, 0xFFFFFF00.toInt())
} }
temporaryPlayerWaypointList.forEach { (player, waypoint) -> temporaryPlayerWaypointList.forEach { (player, waypoint) ->
val skin = val skin =
MC.networkHandler?.listedPlayerListEntries?.find { it.profile.name == player } MC.networkHandler?.listedPlayerListEntries?.find { it.profile.name == player }?.skinTextures?.texture
?.skinTextures
?.texture
withFacingThePlayer(waypoint.pos.toCenterPos()) { withFacingThePlayer(waypoint.pos.toCenterPos()) {
waypoint(waypoint.pos, Text.stringifiedTranslatable("firmament.waypoint.temporary", player)) waypoint(waypoint.pos, Text.stringifiedTranslatable("firmament.waypoint.temporary", player))
if (skin != null) { if (skin != null) {
@@ -313,35 +295,3 @@ fun <E> List<E>.wrappingWindow(startIndex: Int, windowSize: Int): List<E> {
} }
return result return result
} }
fun FabricClientCommandSource.asFakeServer(): ServerCommandSource {
val source = this
return ServerCommandSource(
object : CommandOutput {
override fun sendMessage(message: Text?) {
source.player.sendMessage(message, false)
}
override fun shouldReceiveFeedback(): Boolean {
return true
}
override fun shouldTrackOutput(): Boolean {
return true
}
override fun shouldBroadcastConsoleToOps(): Boolean {
return true
}
},
source.position,
source.rotation,
null,
0,
"FakeServerCommandSource",
Text.literal("FakeServerCommandSource"),
null,
source.player
)
}

View File

@@ -0,0 +1,37 @@
package moe.nea.firmament.util.mc
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource
import net.minecraft.server.command.CommandOutput
import net.minecraft.server.command.ServerCommandSource
import net.minecraft.text.Text
fun FabricClientCommandSource.asFakeServer(): ServerCommandSource {
val source = this
return ServerCommandSource(
object : CommandOutput {
override fun sendMessage(message: Text?) {
source.player.sendMessage(message, false)
}
override fun shouldReceiveFeedback(): Boolean {
return true
}
override fun shouldTrackOutput(): Boolean {
return true
}
override fun shouldBroadcastConsoleToOps(): Boolean {
return true
}
},
source.position,
source.rotation,
null,
0,
"FakeServerCommandSource",
Text.literal("FakeServerCommandSource"),
null,
source.player
)
}