aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/features/world/Waypoints.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/kotlin/features/world/Waypoints.kt')
-rw-r--r--src/main/kotlin/features/world/Waypoints.kt345
1 files changed, 116 insertions, 229 deletions
diff --git a/src/main/kotlin/features/world/Waypoints.kt b/src/main/kotlin/features/world/Waypoints.kt
index 2e4cb70..b5c2b66 100644
--- a/src/main/kotlin/features/world/Waypoints.kt
+++ b/src/main/kotlin/features/world/Waypoints.kt
@@ -2,36 +2,24 @@ package moe.nea.firmament.features.world
import com.mojang.brigadier.arguments.IntegerArgumentType
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.seconds
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.util.math.BlockPos
import net.minecraft.util.math.Vec3d
-import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.commands.get
import moe.nea.firmament.commands.thenArgument
import moe.nea.firmament.commands.thenExecute
import moe.nea.firmament.commands.thenLiteral
import moe.nea.firmament.events.CommandEvent
-import moe.nea.firmament.events.ProcessChatEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.events.WorldReadyEvent
import moe.nea.firmament.events.WorldRenderLastEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
-import moe.nea.firmament.util.ClipboardUtils
import moe.nea.firmament.util.MC
-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.tr
@@ -43,99 +31,85 @@ object Waypoints : FirmamentFeature {
val tempWaypointDuration by duration("temp-waypoint-duration", 0.seconds, 1.hours) { 30.seconds }
val showIndex by toggle("show-index") { true }
val skipToNearest by toggle("skip-to-nearest") { false }
+ val resetWaypointOrderOnWorldSwap by toggle("reset-order-on-swap") { true }
// TODO: look ahead size
}
- data class TemporaryWaypoint(
- val pos: BlockPos,
- val postedAt: TimeMark,
- )
-
override val config get() = TConfig
-
- val temporaryPlayerWaypointList = mutableMapOf<String, TemporaryWaypoint>()
- val temporaryPlayerWaypointMatcher = "(?i)x: (-?[0-9]+),? y: (-?[0-9]+),? z: (-?[0-9]+)".toPattern()
-
- val waypoints = mutableListOf<BlockPos>()
- var ordered = false
+ var waypoints: FirmWaypoints? = null
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
fun onRenderOrderedWaypoints(event: WorldRenderLastEvent) {
- if (waypoints.isEmpty()) return
+ val w = useNonEmptyWaypoints() ?: return
RenderInWorldContext.renderInWorld(event) {
- if (!ordered) {
- waypoints.withIndex().forEach {
- block(it.value, 0x800050A0.toInt())
- if (TConfig.showIndex)
- withFacingThePlayer(it.value.toCenterPos()) {
- text(Text.literal(it.index.toString()))
- }
+ if (!w.isOrdered) {
+ w.waypoints.withIndex().forEach {
+ block(it.value.blockPos, 0x800050A0.toInt())
+ if (TConfig.showIndex) withFacingThePlayer(it.value.blockPos.toCenterPos()) {
+ text(Text.literal(it.index.toString()))
+ }
}
} else {
- orderedIndex %= waypoints.size
+ orderedIndex %= w.waypoints.size
val firstColor = Color.ofRGBA(0, 200, 40, 180)
color(firstColor)
- tracer(waypoints[orderedIndex].toCenterPos(), lineWidth = 3f)
- waypoints.withIndex().toList()
- .wrappingWindow(orderedIndex, 3)
- .zip(
- listOf(
- firstColor,
- Color.ofRGBA(180, 200, 40, 150),
- Color.ofRGBA(180, 80, 20, 140),
- )
- )
- .reversed()
- .forEach { (waypoint, col) ->
- val (index, pos) = waypoint
- block(pos, col.color)
- if (TConfig.showIndex)
- withFacingThePlayer(pos.toCenterPos()) {
- text(Text.literal(index.toString()))
- }
+ tracer(w.waypoints[orderedIndex].blockPos.toCenterPos(), lineWidth = 3f)
+ w.waypoints.withIndex().toList().wrappingWindow(orderedIndex, 3).zip(listOf(
+ firstColor,
+ Color.ofRGBA(180, 200, 40, 150),
+ Color.ofRGBA(180, 80, 20, 140),
+ )).reversed().forEach { (waypoint, col) ->
+ val (index, pos) = waypoint
+ block(pos.blockPos, col.color)
+ if (TConfig.showIndex) withFacingThePlayer(pos.blockPos.toCenterPos()) {
+ text(Text.literal(index.toString()))
}
+ }
}
}
}
@Subscribe
fun onTick(event: TickEvent) {
- if (waypoints.isEmpty() || !ordered) return
- orderedIndex %= waypoints.size
+ val w = useNonEmptyWaypoints() ?: return
+ if (!w.isOrdered) return
+ orderedIndex %= w.waypoints.size
val p = MC.player?.pos ?: return
if (TConfig.skipToNearest) {
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 {
- if (waypoints[orderedIndex].isWithinDistance(p, 3.0)) {
- orderedIndex = (orderedIndex + 1) % waypoints.size
+ if (w.waypoints[orderedIndex].blockPos.isWithinDistance(p, 3.0)) {
+ orderedIndex = (orderedIndex + 1) % w.waypoints.size
}
}
}
+
+ fun useEditableWaypoints(): FirmWaypoints {
+ var w = waypoints
+ if (w == null) {
+ w = FirmWaypoints("Unlabeled", "unknown", 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
+ }
+
+ val WAYPOINTS_SUBCOMMAND = "waypoints"
+
@Subscribe
- fun onProcessChat(it: ProcessChatEvent) {
- val matcher = temporaryPlayerWaypointMatcher.matcher(it.unformattedString)
- if (it.nameHeuristic != null && TConfig.tempWaypointDuration > 0.seconds && matcher.find()) {
- temporaryPlayerWaypointList[it.nameHeuristic] = TemporaryWaypoint(
- BlockPos(
- matcher.group(1).toInt(),
- matcher.group(2).toInt(),
- matcher.group(3).toInt(),
- ),
- TimeMark.now()
- )
+ fun onWorldSwap(event: WorldReadyEvent) {
+ if (TConfig.resetWaypointOrderOnWorldSwap) {
+ orderedIndex = 0
}
}
@@ -144,41 +118,77 @@ object Waypoints : FirmamentFeature {
event.subcommand("waypoint") {
thenArgument("pos", BlockPosArgumentType.blockPos()) { pos ->
thenExecute {
+ source
val position = pos.get(this).toAbsoluteBlockPos(source.asFakeServer())
- waypoints.add(position)
- source.sendFeedback(
- Text.stringifiedTranslatable(
- "firmament.command.waypoint.added",
- position.x,
- position.y,
- position.z
- )
- )
+ val w = useEditableWaypoints()
+ w.waypoints.add(FirmWaypoints.Waypoint.from(position))
+ source.sendFeedback(Text.stringifiedTranslatable("firmament.command.waypoint.added",
+ position.x,
+ position.y,
+ position.z))
}
}
}
- event.subcommand("waypoints") {
+ event.subcommand(WAYPOINTS_SUBCOMMAND) {
+ thenLiteral("reset") {
+ thenExecute {
+ orderedIndex = 0
+ source.sendFeedback(tr(
+ "firmament.command.waypoint.reset",
+ "Reset your ordered waypoint index back to 0. If you want to delete all waypoints use /firm waypoints clear instead."))
+ }
+ }
+ thenLiteral("changeindex") {
+ thenArgument("from", IntegerArgumentType.integer(0)) { fromIndex ->
+ thenArgument("to", IntegerArgumentType.integer(0)) { toIndex ->
+ thenExecute {
+ val w = useEditableWaypoints()
+ val toIndex = toIndex.get(this)
+ val fromIndex = fromIndex.get(this)
+ if (fromIndex !in w.waypoints.indices) {
+ source.sendError(textInvalidIndex(fromIndex))
+ return@thenExecute
+ }
+ if (toIndex !in w.waypoints.indices) {
+ source.sendError(textInvalidIndex(toIndex))
+ return@thenExecute
+ }
+ val waypoint = w.waypoints.removeAt(fromIndex)
+ w.waypoints.add(
+ if (toIndex > fromIndex) toIndex - 1
+ else toIndex,
+ waypoint)
+ source.sendFeedback(
+ tr("firmament.command.waypoint.indexchange",
+ "Moved waypoint from index $fromIndex to $toIndex. Note that this only matters for ordered waypoints.")
+ )
+ }
+ }
+ }
+ }
thenLiteral("clear") {
thenExecute {
- waypoints.clear()
+ waypoints = null
source.sendFeedback(Text.translatable("firmament.command.waypoint.clear"))
}
}
thenLiteral("toggleordered") {
thenExecute {
- ordered = !ordered
- if (ordered) {
+ val w = useEditableWaypoints()
+ w.isOrdered = !w.isOrdered
+ if (w.isOrdered) {
val p = MC.player?.pos ?: Vec3d.ZERO
- orderedIndex =
- waypoints.withIndex().minByOrNull { it.value.getSquaredDistance(p) }?.index ?: 0
+ orderedIndex = // TODO: this should be extracted to a utility method
+ 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") {
thenExecute {
- if (ordered && waypoints.isNotEmpty()) {
- orderedIndex = (orderedIndex + 1) % waypoints.size
+ val w = useNonEmptyWaypoints()
+ if (w != null && w.isOrdered) {
+ orderedIndex = (orderedIndex + 1) % w.size
source.sendFeedback(Text.translatable("firmament.command.waypoint.skip"))
} else {
source.sendError(Text.translatable("firmament.command.waypoint.skip.error"))
@@ -189,118 +199,27 @@ object Waypoints : FirmamentFeature {
thenArgument("index", IntegerArgumentType.integer(0)) { indexArg ->
thenExecute {
val index = get(indexArg)
- if (index in waypoints.indices) {
- waypoints.removeAt(index)
- source.sendFeedback(Text.stringifiedTranslatable(
- "firmament.command.waypoint.remove",
- index))
+ val w = useNonEmptyWaypoints()
+ if (w != null && index in w.waypoints.indices) {
+ w.waypoints.removeAt(index)
+ source.sendFeedback(Text.stringifiedTranslatable("firmament.command.waypoint.remove",
+ index))
} else {
source.sendError(Text.stringifiedTranslatable("firmament.command.waypoint.remove.error"))
}
}
}
}
- thenLiteral("export") {
- thenExecute {
- val data = Firmament.tightJson.encodeToString<List<ColeWeightWaypoint>>(waypoints.map {
- ColeWeightWaypoint(it.x,
- it.y,
- it.z)
- })
- ClipboardUtils.setTextContent(data)
- source.sendFeedback(tr("firmament.command.waypoint.export",
- "Copied ${waypoints.size} waypoints to clipboard"))
- }
- }
- thenLiteral("exportrelative") {
- thenExecute {
- val playerPos = MC.player!!.blockPos
- val x = playerPos.x
- val y = playerPos.y
- val z = playerPos.z
- val data = Firmament.tightJson.encodeToString<List<ColeWeightWaypoint>>(waypoints.map {
- ColeWeightWaypoint(it.x - x,
- it.y - y,
- it.z - z)
- })
- ClipboardUtils.setTextContent(data)
- 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") {
- thenExecute {
- source.sendFeedback(
- importRelative(BlockPos.ORIGIN)
- ?: Text.stringifiedTranslatable("firmament.command.waypoint.import", waypoints.size),
- )
- }
- }
- thenLiteral("importrelative") {
- thenExecute {
- source.sendFeedback(
- importRelative(MC.player!!.blockPos)
- ?: tr("firmament.command.waypoint.import.relative",
- "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."),
- )
- }
- }
}
}
- fun importRelative(pos: BlockPos): Text? {
- val contents = ClipboardUtils.getTextContents()
- val data = try {
- Firmament.tightJson.decodeFromString<List<ColeWeightWaypoint>>(contents)
- } catch (ex: Exception) {
- Firmament.logger.error("Could not load waypoints from clipboard", ex)
- 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
- fun onRenderTemporaryWaypoints(event: WorldRenderLastEvent) {
- temporaryPlayerWaypointList.entries.removeIf { it.value.postedAt.passedTime() > TConfig.tempWaypointDuration }
- if (temporaryPlayerWaypointList.isEmpty()) return
- RenderInWorldContext.renderInWorld(event) {
- temporaryPlayerWaypointList.forEach { (player, waypoint) ->
- block(waypoint.pos, 0xFFFFFF00.toInt())
- }
- temporaryPlayerWaypointList.forEach { (player, waypoint) ->
- val skin =
- MC.networkHandler?.listedPlayerListEntries?.find { it.profile.name == player }
- ?.skinTextures
- ?.texture
- withFacingThePlayer(waypoint.pos.toCenterPos()) {
- waypoint(waypoint.pos, Text.stringifiedTranslatable("firmament.waypoint.temporary", player))
- if (skin != null) {
- matrixStack.translate(0F, -20F, 0F)
- // Head front
- texture(
- skin, 16, 16,
- 1 / 8f, 1 / 8f,
- 2 / 8f, 2 / 8f,
- )
- // Head overlay
- texture(
- skin, 16, 16,
- 5 / 8f, 1 / 8f,
- 6 / 8f, 2 / 8f,
- )
- }
- }
- }
- }
- }
+ fun textInvalidIndex(index: Int) =
+ tr("firmament.command.waypoint.invalid-index",
+ "Invalid index $index provided.")
- @Subscribe
- fun onWorldReady(event: WorldReadyEvent) {
- temporaryPlayerWaypointList.clear()
- }
+ fun textNothingToExport(): Text =
+ tr("firmament.command.waypoint.export.nowaypoints",
+ "No waypoints to export found. Add some with /firm waypoint ~ ~ ~.")
}
fun <E> List<E>.wrappingWindow(startIndex: Int, windowSize: Int): List<E> {
@@ -313,35 +232,3 @@ fun <E> List<E>.wrappingWindow(startIndex: Int, windowSize: Int): List<E> {
}
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
- )
-}