From a84da715497215848797719fe528c2f5c1d4aed3 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Sun, 24 Mar 2024 00:43:16 +0100 Subject: Add ordered waypoints (ColeWeight compat) --- .../moe/nea/firmament/features/world/Waypoints.kt | 172 +++++++++++++++++++-- 1 file changed, 163 insertions(+), 9 deletions(-) (limited to 'src/main/kotlin/moe/nea/firmament/features/world') diff --git a/src/main/kotlin/moe/nea/firmament/features/world/Waypoints.kt b/src/main/kotlin/moe/nea/firmament/features/world/Waypoints.kt index 05b1ba5..279be6f 100644 --- a/src/main/kotlin/moe/nea/firmament/features/world/Waypoints.kt +++ b/src/main/kotlin/moe/nea/firmament/features/world/Waypoints.kt @@ -1,20 +1,38 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ package moe.nea.firmament.features.world +import me.shedaniel.math.Color +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +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.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.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.render.RenderInWorldContext @@ -34,19 +52,34 @@ object Waypoints : FirmamentFeature { override val config get() = TConfig - val temporaryWaypointList = mutableMapOf() - val temporaryWaypointMatcher = "(?i)x: (-?[0-9]+),? y: (-?[0-9]+),? z: (-?[0-9]+)".toPattern() + val temporaryPlayerWaypointList = mutableMapOf() + val temporaryPlayerWaypointMatcher = "(?i)x: (-?[0-9]+),? y: (-?[0-9]+),? z: (-?[0-9]+)".toPattern() + + val waypoints = mutableListOf() + var ordered = false + 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, + ) + override fun onLoad() { WorldRenderLastEvent.subscribe { event -> - temporaryWaypointList.entries.removeIf { it.value.postedAt.passedTime() > TConfig.tempWaypointDuration } - if (temporaryWaypointList.isNotEmpty()) + temporaryPlayerWaypointList.entries.removeIf { it.value.postedAt.passedTime() > TConfig.tempWaypointDuration } + if (temporaryPlayerWaypointList.isNotEmpty()) RenderInWorldContext.renderInWorld(event) { color(1f, 1f, 0f, 1f) - temporaryWaypointList.forEach { (player, waypoint) -> + temporaryPlayerWaypointList.forEach { (player, waypoint) -> block(waypoint.pos) } color(1f, 1f, 1f, 1f) - temporaryWaypointList.forEach { (player, waypoint) -> + temporaryPlayerWaypointList.forEach { (player, waypoint) -> val skin = MC.networkHandler?.listedPlayerListEntries?.find { it.profile.name == player } ?.skinTextures @@ -73,12 +106,106 @@ object Waypoints : FirmamentFeature { } } WorldReadyEvent.subscribe { - temporaryWaypointList.clear() + temporaryPlayerWaypointList.clear() + } + CommandEvent.SubCommand.subscribe { event -> + event.subcommand("waypoint") { + thenArgument("pos", BlockPosArgumentType.blockPos()) { pos -> + thenExecute { + 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 + ) + ) + } + } + } + event.subcommand("waypoints") { + thenLiteral("clear") { + thenExecute { + waypoints.clear() + source.sendFeedback(Text.translatable("firmament.command.waypoint.clear")) + } + } + thenLiteral("toggleordered") { + thenExecute { + ordered = !ordered + if (ordered) { + val p = MC.player?.pos ?: Vec3d.ZERO + orderedIndex = + waypoints.withIndex().minByOrNull { it.value.getSquaredDistance(p) }?.index ?: 0 + } + source.sendFeedback(Text.translatable("firmament.command.waypoint.ordered.toggle.$ordered")) + } + } + thenLiteral("import") { + thenExecute { + val contents = ClipboardUtils.getTextContents() + val data = try { + Firmament.json.decodeFromString>(contents) + } catch (ex: Exception) { + Firmament.logger.error("Could not load waypoints from clipboard", ex) + source.sendError(Text.translatable("firmament.command.waypoint.import.error")) + return@thenExecute + } + waypoints.clear() + data.mapTo(waypoints) { BlockPos(it.x, it.y, it.z) } + source.sendFeedback( + Text.stringifiedTranslatable( + "firmament.command.waypoint.import", + data.size + ) + ) + } + } + } + } + WorldRenderLastEvent.subscribe { event -> + if (waypoints.isEmpty()) return@subscribe + RenderInWorldContext.renderInWorld(event) { + if (!ordered) { + color(0f, 0.3f, 0.7f, 0.5f) + waypoints.forEach { + block(it) + } + } else { + orderedIndex %= waypoints.size + val firstColor = Color.ofRGBA(0, 200, 40, 180) + color(firstColor) + tracer(waypoints[orderedIndex].toCenterPos(), lineWidth = 3f) + waypoints.wrappingWindow(orderedIndex, 3) + .zip( + listOf( + firstColor, + Color.ofRGBA(180, 200, 40, 150), + Color.ofRGBA(180, 80, 20, 140), + ) + ) + .reversed() + .forEach { (pos, col) -> + color(col) + block(pos) + } + } + } + } + TickEvent.subscribe { + if (waypoints.isEmpty() || !ordered) return@subscribe + orderedIndex %= waypoints.size + val p = MC.player?.pos ?: return@subscribe + if (waypoints[orderedIndex].isWithinDistance(p, 3.0)) { + orderedIndex = (orderedIndex + 1) % waypoints.size + } } ProcessChatEvent.subscribe { - val matcher = temporaryWaypointMatcher.matcher(it.unformattedString) + val matcher = temporaryPlayerWaypointMatcher.matcher(it.unformattedString) if (it.nameHeuristic != null && TConfig.tempWaypointDuration > 0.seconds && matcher.find()) { - temporaryWaypointList[it.nameHeuristic] = TemporaryWaypoint( + temporaryPlayerWaypointList[it.nameHeuristic] = TemporaryWaypoint( BlockPos( matcher.group(1).toInt(), matcher.group(2).toInt(), @@ -90,3 +217,30 @@ object Waypoints : FirmamentFeature { } } } + +fun List.wrappingWindow(startIndex: Int, windowSize: Int): List { + val result = ArrayList(windowSize) + if (startIndex + windowSize < size) { + result.addAll(subList(startIndex, startIndex + windowSize)) + } else { + result.addAll(subList(startIndex, size)) + result.addAll(subList(0, minOf(size - startIndex - windowSize, startIndex))) + } + return result +} + + +fun FabricClientCommandSource.asFakeServer(): ServerCommandSource { + val source = this + return ServerCommandSource( + source.player, + source.position, + source.rotation, + null, + 0, + "FakeServerCommandSource", + Text.literal("FakeServerCommandSource"), + null, + source.player + ) +} -- cgit