feat: Add firmament waypoint import / export that remembers relative waypoints
This commit is contained in:
@@ -51,8 +51,7 @@ object ColeWeightCompat {
|
|||||||
val waypoints = Waypoints.useNonEmptyWaypoints()
|
val waypoints = Waypoints.useNonEmptyWaypoints()
|
||||||
?.let { fromFirm(it, origin) }
|
?.let { fromFirm(it, origin) }
|
||||||
if (waypoints == null) {
|
if (waypoints == null) {
|
||||||
source.sendError(tr("firmament.command.waypoint.export.nowaypoints",
|
source.sendError(Waypoints.textNothingToExport())
|
||||||
"No waypoints to export found."))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val data =
|
val data =
|
||||||
@@ -63,11 +62,11 @@ object ColeWeightCompat {
|
|||||||
|
|
||||||
fun importAndInform(
|
fun importAndInform(
|
||||||
source: DefaultSource,
|
source: DefaultSource,
|
||||||
pos: BlockPos,
|
pos: BlockPos?,
|
||||||
positiveFeedback: (Int) -> Text
|
positiveFeedback: (Int) -> Text
|
||||||
) {
|
) {
|
||||||
val text = ClipboardUtils.getTextContents()
|
val text = ClipboardUtils.getTextContents()
|
||||||
val wr = tryParse(text).map { intoFirm(it, pos) }
|
val wr = tryParse(text).map { intoFirm(it, pos ?: BlockPos.ORIGIN) }
|
||||||
val waypoints = wr.getOrElse {
|
val waypoints = wr.getOrElse {
|
||||||
source.sendError(
|
source.sendError(
|
||||||
tr("firmament.command.waypoint.import.cw.error",
|
tr("firmament.command.waypoint.import.cw.error",
|
||||||
@@ -75,6 +74,7 @@ object ColeWeightCompat {
|
|||||||
Firmament.logger.error(it)
|
Firmament.logger.error(it)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
waypoints.lastRelativeImport = pos
|
||||||
Waypoints.waypoints = waypoints
|
Waypoints.waypoints = waypoints
|
||||||
source.sendFeedback(positiveFeedback(waypoints.size))
|
source.sendFeedback(positiveFeedback(waypoints.size))
|
||||||
}
|
}
|
||||||
@@ -93,23 +93,23 @@ object ColeWeightCompat {
|
|||||||
thenLiteral("exportrelativecw") {
|
thenLiteral("exportrelativecw") {
|
||||||
thenExecute {
|
thenExecute {
|
||||||
copyAndInform(source, MC.player?.blockPos ?: BlockPos.ORIGIN) {
|
copyAndInform(source, MC.player?.blockPos ?: BlockPos.ORIGIN) {
|
||||||
tr("firmament.command.waypoint.export.relative",
|
tr("firmament.command.waypoint.export.cw.relative",
|
||||||
"Copied $it relative waypoints to clipboard in ColeWeight format. Make sure to stand in the same position when importing.")
|
"Copied $it relative waypoints to clipboard in ColeWeight format. Make sure to stand in the same position when importing.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
thenLiteral("import") {
|
thenLiteral("importcw") {
|
||||||
thenExecute {
|
thenExecute {
|
||||||
importAndInform(source, BlockPos.ORIGIN) { it: Int ->
|
importAndInform(source, null) {
|
||||||
Text.stringifiedTranslatable("firmament.command.waypoint.import",
|
Text.stringifiedTranslatable("firmament.command.waypoint.import.cw",
|
||||||
it)
|
it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
thenLiteral("importrelative") {
|
thenLiteral("importrelativecw") {
|
||||||
thenExecute {
|
thenExecute {
|
||||||
importAndInform(source, MC.player!!.blockPos) {
|
importAndInform(source, MC.player!!.blockPos) {
|
||||||
tr("firmament.command.waypoint.import.relative",
|
tr("firmament.command.waypoint.import.cw.relative",
|
||||||
"Imported $it 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 $it relative waypoints from clipboard. Make sure you stand in the same position as when you exported these waypoints for them to line up correctly.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
131
src/main/kotlin/features/world/FirmWaypointManager.kt
Normal file
131
src/main/kotlin/features/world/FirmWaypointManager.kt
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package moe.nea.firmament.features.world
|
||||||
|
|
||||||
|
import kotlinx.serialization.serializer
|
||||||
|
import net.minecraft.text.Text
|
||||||
|
import moe.nea.firmament.annotations.Subscribe
|
||||||
|
import moe.nea.firmament.commands.DefaultSource
|
||||||
|
import moe.nea.firmament.commands.RestArgumentType
|
||||||
|
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.util.ClipboardUtils
|
||||||
|
import moe.nea.firmament.util.FirmFormatters
|
||||||
|
import moe.nea.firmament.util.MC
|
||||||
|
import moe.nea.firmament.util.TemplateUtil
|
||||||
|
import moe.nea.firmament.util.data.MultiFileDataHolder
|
||||||
|
import moe.nea.firmament.util.tr
|
||||||
|
|
||||||
|
object FirmWaypointManager {
|
||||||
|
object DataHolder : MultiFileDataHolder<FirmWaypoints>(serializer(), "waypoints")
|
||||||
|
|
||||||
|
val SHARE_PREFIX = "FIRM_WAYPOINTS/"
|
||||||
|
val ENCODED_SHARE_PREFIX = TemplateUtil.getPrefixComparisonSafeBase64Encoding(SHARE_PREFIX)
|
||||||
|
|
||||||
|
fun createExportableCopy(
|
||||||
|
waypoints: FirmWaypoints,
|
||||||
|
): FirmWaypoints {
|
||||||
|
val copy = waypoints.copy(waypoints = waypoints.waypoints.toMutableList())
|
||||||
|
if (waypoints.isRelativeTo != null) {
|
||||||
|
val origin = waypoints.lastRelativeImport
|
||||||
|
if (origin != null) {
|
||||||
|
copy.waypoints.replaceAll {
|
||||||
|
it.copy(
|
||||||
|
x = it.x - origin.x,
|
||||||
|
y = it.y - origin.y,
|
||||||
|
z = it.z - origin.z,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TODO("Add warning!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadWaypoints(waypoints: FirmWaypoints, sendFeedback: (Text) -> Unit) {
|
||||||
|
if (waypoints.isRelativeTo != null) {
|
||||||
|
val origin = MC.player!!.blockPos
|
||||||
|
waypoints.waypoints.replaceAll {
|
||||||
|
it.copy(
|
||||||
|
x = it.x + origin.x,
|
||||||
|
y = it.y + origin.y,
|
||||||
|
z = it.z + origin.z,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
waypoints.lastRelativeImport = origin.toImmutable()
|
||||||
|
sendFeedback(tr("firmament.command.waypoint.import.ordered.success",
|
||||||
|
"Imported ${waypoints.size} relative waypoints. Make sure you stand in the correct spot while loading the waypoints: ${waypoints.isRelativeTo}."))
|
||||||
|
} else {
|
||||||
|
sendFeedback(tr("firmament.command.waypoint.import.success",
|
||||||
|
"Imported ${waypoints.size} waypoints."))
|
||||||
|
}
|
||||||
|
Waypoints.waypoints = waypoints
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOrigin(source: DefaultSource, text: String?) {
|
||||||
|
val waypoints = Waypoints.useEditableWaypoints()
|
||||||
|
waypoints.isRelativeTo = text ?: waypoints.isRelativeTo ?: ""
|
||||||
|
val pos = MC.player!!.blockPos
|
||||||
|
waypoints.lastRelativeImport = pos
|
||||||
|
source.sendFeedback(tr("firmament.command.waypoint.originset",
|
||||||
|
"Set the origin of waypoints to ${FirmFormatters.formatPosition(pos)}. Run /firm waypoints export to save the waypoints relative to this position."))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
fun onCommands(event: CommandEvent.SubCommand) {
|
||||||
|
event.subcommand(Waypoints.WAYPOINTS_SUBCOMMAND) {
|
||||||
|
thenLiteral("setorigin") {
|
||||||
|
thenExecute {
|
||||||
|
setOrigin(source, null)
|
||||||
|
}
|
||||||
|
thenArgument("hint", RestArgumentType) { text ->
|
||||||
|
thenExecute {
|
||||||
|
setOrigin(source, this[text])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thenLiteral("clearorigin") {
|
||||||
|
thenExecute {
|
||||||
|
val waypoints = Waypoints.useEditableWaypoints()
|
||||||
|
waypoints.lastRelativeImport = null
|
||||||
|
waypoints.isRelativeTo = null
|
||||||
|
source.sendFeedback(tr("firmament.command.waypoint.originunset",
|
||||||
|
"Unset the origin of the waypoints. Run /firm waypoints export to save the waypoints with absolute coordinates."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thenLiteral("export") {
|
||||||
|
thenExecute {
|
||||||
|
val waypoints = Waypoints.useNonEmptyWaypoints()
|
||||||
|
if (waypoints == null) {
|
||||||
|
source.sendError(Waypoints.textNothingToExport())
|
||||||
|
return@thenExecute
|
||||||
|
}
|
||||||
|
val exportableWaypoints = createExportableCopy(waypoints)
|
||||||
|
val data = TemplateUtil.encodeTemplate(SHARE_PREFIX, exportableWaypoints)
|
||||||
|
ClipboardUtils.setTextContent(data)
|
||||||
|
source.sendFeedback(tr("firmament.command.waypoint.export",
|
||||||
|
"Copied ${exportableWaypoints.size} waypoints to clipboard in Firmament format."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thenLiteral("import") {
|
||||||
|
thenExecute {
|
||||||
|
val text = ClipboardUtils.getTextContents()
|
||||||
|
if (text.startsWith("[")) {
|
||||||
|
source.sendError(tr("firmament.command.waypoint.import.lookslikecw",
|
||||||
|
"The waypoints in your clipboard look like they might be ColeWeight waypoints. If so, use /firm waypoints importcw or /firm waypoints importrelativecw."))
|
||||||
|
return@thenExecute
|
||||||
|
}
|
||||||
|
val waypoints = TemplateUtil.maybeDecodeTemplate<FirmWaypoints>(SHARE_PREFIX, text)
|
||||||
|
if (waypoints == null) {
|
||||||
|
source.sendError(tr("firmament.command.waypoint.import.error",
|
||||||
|
"Could not import Firmament waypoints from your clipboard. Make sure they are Firmament compatible waypoints."))
|
||||||
|
return@thenExecute
|
||||||
|
}
|
||||||
|
loadWaypoints(waypoints, source::sendFeedback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package moe.nea.firmament.features.world
|
package moe.nea.firmament.features.world
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class FirmWaypoints(
|
data class FirmWaypoints(
|
||||||
var label: String,
|
var label: String,
|
||||||
var id: String,
|
var id: String,
|
||||||
@@ -13,7 +16,11 @@ data class FirmWaypoints(
|
|||||||
var isOrdered: Boolean,
|
var isOrdered: Boolean,
|
||||||
// TODO: val resetOnSwap: Boolean,
|
// TODO: val resetOnSwap: Boolean,
|
||||||
) {
|
) {
|
||||||
|
@Transient
|
||||||
|
var lastRelativeImport: BlockPos? = null
|
||||||
|
|
||||||
val size get() = waypoints.size
|
val size get() = waypoints.size
|
||||||
|
@Serializable
|
||||||
data class Waypoint(
|
data class Waypoint(
|
||||||
val x: Int,
|
val x: Int,
|
||||||
val y: Int,
|
val y: Int,
|
||||||
|
|||||||
@@ -169,6 +169,10 @@ object Waypoints : FirmamentFeature {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
fun <E> List<E>.wrappingWindow(startIndex: Int, windowSize: Int): List<E> {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import kotlin.math.roundToInt
|
|||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
import net.minecraft.text.Text
|
import net.minecraft.text.Text
|
||||||
|
import net.minecraft.util.math.BlockPos
|
||||||
|
|
||||||
object FirmFormatters {
|
object FirmFormatters {
|
||||||
|
|
||||||
@@ -131,4 +132,7 @@ object FirmFormatters {
|
|||||||
return if (boolean == trueIsGood) text.lime() else text.red()
|
return if (boolean == trueIsGood) text.lime() else text.red()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun formatPosition(position: BlockPos): Text {
|
||||||
|
return Text.literal("x: ${position.x}, y: ${position.y}, z: ${position.z}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
63
src/main/kotlin/util/data/MultiFileDataHolder.kt
Normal file
63
src/main/kotlin/util/data/MultiFileDataHolder.kt
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package moe.nea.firmament.util.data
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlin.io.path.createDirectories
|
||||||
|
import kotlin.io.path.deleteExisting
|
||||||
|
import kotlin.io.path.exists
|
||||||
|
import kotlin.io.path.extension
|
||||||
|
import kotlin.io.path.listDirectoryEntries
|
||||||
|
import kotlin.io.path.nameWithoutExtension
|
||||||
|
import kotlin.io.path.readText
|
||||||
|
import kotlin.io.path.writeText
|
||||||
|
import moe.nea.firmament.Firmament
|
||||||
|
|
||||||
|
abstract class MultiFileDataHolder<T>(
|
||||||
|
val dataSerializer: KSerializer<T>,
|
||||||
|
val configName: String
|
||||||
|
) { // TODO: abstract this + ProfileSpecificDataHolder
|
||||||
|
val configDirectory = Firmament.CONFIG_DIR.resolve(configName)
|
||||||
|
private var allData = readValues()
|
||||||
|
protected fun readValues(): MutableMap<String, T> {
|
||||||
|
if (!configDirectory.exists()) {
|
||||||
|
configDirectory.createDirectories()
|
||||||
|
}
|
||||||
|
val profileFiles = configDirectory.listDirectoryEntries()
|
||||||
|
return profileFiles
|
||||||
|
.filter { it.extension == "json" }
|
||||||
|
.mapNotNull {
|
||||||
|
try {
|
||||||
|
it.nameWithoutExtension to Firmament.json.decodeFromString(dataSerializer, it.readText())
|
||||||
|
} catch (e: Exception) { /* Expecting IOException and SerializationException, but Kotlin doesn't allow multi catches*/
|
||||||
|
IDataHolder.badLoads.add(configName)
|
||||||
|
Firmament.logger.error(
|
||||||
|
"Exception during loading of multi file data holder $it ($configName). This will reset that profiles config.",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}.toMap().toMutableMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save() {
|
||||||
|
if (!configDirectory.exists()) {
|
||||||
|
configDirectory.createDirectories()
|
||||||
|
}
|
||||||
|
val c = allData
|
||||||
|
configDirectory.listDirectoryEntries().forEach {
|
||||||
|
if (it.nameWithoutExtension !in c.mapKeys { it.toString() }) {
|
||||||
|
it.deleteExisting()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.forEach { (name, value) ->
|
||||||
|
val f = configDirectory.resolve("$name.json")
|
||||||
|
f.writeText(Firmament.json.encodeToString(dataSerializer, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun list(): Map<String, T> = allData
|
||||||
|
val validPathRegex = "[a-zA-Z0-9_][a-zA-Z0-9\\-_.]*".toPattern()
|
||||||
|
fun insert(name: String, value: T) {
|
||||||
|
require(validPathRegex.matcher(name).matches()) { "Not a valid name: $name" }
|
||||||
|
allData[name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user