Skip to content

Commit 1530404

Browse files
committed
flight plan saving & loading
Signed-off-by: Octol1ttle <l1ttleofficial@outlook.com>
1 parent 207af60 commit 1530404

File tree

13 files changed

+147
-38
lines changed

13 files changed

+147
-38
lines changed

build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
kotlin("jvm")
3+
kotlin("plugin.serialization")
34
id("dev.isxander.modstitch.base") version "0.7.0-unstable"
45
id("me.modmuss50.mod-publish-plugin")
56
id("me.fallenbreath.yamlang") version "1.5.0"
@@ -130,6 +131,8 @@ modstitch {
130131
// If you want to create proxy configurations for more source sets, such as client source sets,
131132
// use the modstitch.createProxyConfigurations(sourceSets["client"]) function.
132133
dependencies {
134+
modstitchImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
135+
133136
modstitch.loom {
134137
modstitchModImplementation("net.fabricmc.fabric-api:fabric-api:${property("deps.fapi")}")
135138
modstitchModImplementation("net.fabricmc:fabric-language-kotlin:${property("deps.flk")}+kotlin.2.1.0")

src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/autoflight/FlightPlanComputer.kt

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package ru.octol1ttle.flightassistant.impl.computer.autoflight
22

3+
import dev.isxander.yacl3.api.NameableEnum
34
import java.time.Duration
45
import java.util.UUID
56
import kotlin.math.abs
67
import kotlin.math.roundToLong
8+
import kotlinx.serialization.Serializable
9+
import kotlinx.serialization.Transient
710
import net.minecraft.SharedConstants
811
import net.minecraft.network.chat.Component
912
import net.minecraft.resources.ResourceLocation
@@ -27,7 +30,8 @@ class FlightPlanComputer(computers: ComputerBus) : Computer(computers) {
2730
private set
2831

2932
var departureData: DepartureData = DepartureData.DEFAULT
30-
val enrouteData: MutableList<EnrouteWaypoint> = ArrayList()
33+
var enrouteData: MutableList<EnrouteWaypoint> = ArrayList()
34+
private set
3135
var arrivalData: ArrivalData = ArrivalData.DEFAULT
3236

3337
var groundSpeeds: LimitedFIFOQueue<Double> = LimitedFIFOQueue(SharedConstants.TICKS_PER_SECOND * 5)
@@ -262,6 +266,12 @@ class FlightPlanComputer(computers: ComputerBus) : Computer(computers) {
262266
}
263267
}
264268

269+
fun load(plan: FlightPlan) {
270+
departureData = plan.departure
271+
enrouteData = ArrayList(plan.enroute)
272+
arrivalData = plan.arrival
273+
}
274+
265275
override fun reset() {
266276
currentPhase = FlightPhase.UNKNOWN
267277
groundSpeeds.clear()
@@ -282,6 +292,7 @@ class FlightPlanComputer(computers: ComputerBus) : Computer(computers) {
282292
GO_AROUND
283293
}
284294

295+
@Serializable
285296
data class DepartureData(val coordinatesX: Int = 0, val coordinatesZ: Int = 0, val elevation: Int = 0, val takeoffThrust: Float = 0.0f) {
286297
fun isDefault(): Boolean {
287298
return this == DEFAULT
@@ -296,7 +307,8 @@ class FlightPlanComputer(computers: ComputerBus) : Computer(computers) {
296307
}
297308
}
298309

299-
data class EnrouteWaypoint(val coordinatesX: Int = 0, val coordinatesZ: Int = 0, val altitude: Int = 0, val speed: Int = 0, var active: Active? = null, val uuid: UUID = UUID.randomUUID()) {
310+
@Serializable
311+
data class EnrouteWaypoint(val coordinatesX: Int = 0, val coordinatesZ: Int = 0, val altitude: Int = 0, val speed: Int = 0, @Transient var active: Active? = null, @Transient val uuid: UUID = UUID.randomUUID()) {
300312
enum class Active {
301313
ORIGIN,
302314
TARGET
@@ -307,10 +319,15 @@ class FlightPlanComputer(computers: ComputerBus) : Computer(computers) {
307319
}
308320
}
309321

322+
@Serializable
310323
data class ArrivalData(val coordinatesX: Int = 0, val coordinatesZ: Int = 0, val elevation: Int = 0, val landingThrust: Float = 0.0f, val minimums: Int = 0, val minimumsType: MinimumsType = MinimumsType.ABSOLUTE, val goAroundAltitude: Int = 0, val approachReEntryWaypointIndex: Int = 0) {
311-
enum class MinimumsType {
312-
ABSOLUTE,
313-
RELATIVE
324+
enum class MinimumsType(@JvmField val displayName: Component) : NameableEnum {
325+
ABSOLUTE(Component.translatable("menu.flightassistant.fms.arrival.minimums.absolute")),
326+
RELATIVE(Component.translatable("menu.flightassistant.fms.arrival.minimums.relative"));
327+
328+
override fun getDisplayName(): Component {
329+
return this.displayName
330+
}
314331
}
315332

316333
fun isDefault(): Boolean {
@@ -325,4 +342,9 @@ class FlightPlanComputer(computers: ComputerBus) : Computer(computers) {
325342
val DEFAULT: ArrivalData = ArrivalData()
326343
}
327344
}
345+
346+
@Serializable
347+
data class FlightPlan(val departure: DepartureData, val enroute: List<EnrouteWaypoint>, val arrival: ArrivalData) {
348+
constructor(flightPlan: FlightPlanComputer) : this(flightPlan.departureData, flightPlan.enrouteData, flightPlan.arrivalData)
349+
}
328350
}

src/main/kotlin/ru/octol1ttle/flightassistant/impl/display/CourseDeviationDisplay.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class CourseDeviationDisplay(computers: ComputerBus) : Display(computers) {
3535
}
3636

3737
private fun GuiGraphics.renderVerticalDeviation() {
38-
val deviation = (computers.plan.getVerticalDeviation(computers.hudData.lerpedPosition) ?: return).coerceIn(-10.0, 10.0)
38+
val deviation = (computers.plan.getVerticalDeviation(computers.hudData.lerpedPosition) ?: return).coerceIn(-12.5, 12.5)
3939
val pixelsPerBlock = 4
4040

4141
val step = 5

src/main/kotlin/ru/octol1ttle/flightassistant/screen/FlightAssistantSetupScreen.kt

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
package ru.octol1ttle.flightassistant.screen
22

3+
import dev.isxander.yacl3.platform.YACLPlatform
4+
import java.io.FileReader
5+
import java.io.FileWriter
6+
import java.nio.file.Files
7+
import java.nio.file.Path
8+
import kotlinx.serialization.json.Json
9+
import net.minecraft.ChatFormatting
10+
import net.minecraft.client.gui.GuiGraphics
311
import net.minecraft.client.gui.components.Button
412
import net.minecraft.client.gui.components.StringWidget
513
import net.minecraft.network.chat.CommonComponents
614
import net.minecraft.network.chat.Component
15+
import org.lwjgl.PointerBuffer
16+
import org.lwjgl.system.MemoryUtil
17+
import org.lwjgl.util.tinyfd.TinyFileDialogs
18+
import ru.octol1ttle.flightassistant.FlightAssistant
19+
import ru.octol1ttle.flightassistant.api.util.extensions.drawMiddleAlignedString
720
import ru.octol1ttle.flightassistant.config.FAConfigScreen
821
import ru.octol1ttle.flightassistant.impl.computer.ComputerHost
22+
import ru.octol1ttle.flightassistant.impl.computer.autoflight.FlightPlanComputer
923
import ru.octol1ttle.flightassistant.impl.display.HudDisplayHost
1024
import ru.octol1ttle.flightassistant.screen.autoflight.AutoFlightScreen
1125
import ru.octol1ttle.flightassistant.screen.fms.arrival.ArrivalScreen
@@ -14,6 +28,8 @@ import ru.octol1ttle.flightassistant.screen.fms.enroute.EnrouteScreen
1428
import ru.octol1ttle.flightassistant.screen.system.SystemManagementScreen
1529

1630
class FlightAssistantSetupScreen : FABaseScreen(null, Component.translatable("menu.flightassistant")) {
31+
private var saveLoadError = false
32+
1733
override fun init() {
1834
super.init()
1935

@@ -50,6 +66,54 @@ class FlightAssistantSetupScreen : FABaseScreen(null, Component.translatable("me
5066
this.minecraft!!.setScreen(ArrivalScreen(this))
5167
}.pos(this.centerX + 50, this.centerY + 20).width(80).build())
5268

69+
this.addRenderableWidget(Button.builder(Component.translatable("menu.flightassistant.fms.save")) {
70+
val patterns = PointerBuffer.create(MemoryUtil.memUTF8(".json"))
71+
try {
72+
Files.createDirectories(PLANS_PATH)
73+
74+
val title = "Save flight plan"
75+
val description = "Flight plans"
76+
77+
val path = TinyFileDialogs.tinyfd_saveFileDialog(title, PLANS_PATH.resolve("flight_plan.json").toAbsolutePath().toString(), patterns, description)
78+
if (path != null) {
79+
FileWriter(path).use { it.write(Json.encodeToString(FlightPlanComputer.FlightPlan(computers.plan))) }
80+
81+
FlightAssistant.logger.info("Saved flight plan to $path")
82+
}
83+
saveLoadError = false
84+
} catch (e: Throwable) {
85+
FlightAssistant.logger.error("Unable to save flight plan", e)
86+
saveLoadError = true
87+
} finally {
88+
patterns.free()
89+
}
90+
}.pos(this.centerX - 90, this.centerY + 50).width(80).build())
91+
this.addRenderableWidget(Button.builder(Component.translatable("menu.flightassistant.fms.load")) {
92+
val patterns = PointerBuffer.create(MemoryUtil.memUTF8(".json"))
93+
try {
94+
Files.createDirectories(PLANS_PATH)
95+
96+
val title = "Load flight plan"
97+
val description = "Flight plans"
98+
99+
val path = TinyFileDialogs.tinyfd_openFileDialog(title, PLANS_PATH.resolve("flight_plan.json").toAbsolutePath().toString(), patterns, description, false)
100+
if (path != null) {
101+
FileReader(path).use { computers.plan.load(Json.decodeFromString(it.readText())) }
102+
DepartureScreen.reload(computers.plan.departureData)
103+
EnrouteScreen.reload(computers.plan.enrouteData)
104+
ArrivalScreen.reload(computers.plan.arrivalData)
105+
106+
FlightAssistant.logger.info("Loaded flight plan from $path")
107+
}
108+
saveLoadError = false
109+
} catch (e: Throwable) {
110+
FlightAssistant.logger.error("Unable to load flight plan", e)
111+
saveLoadError = true
112+
} finally {
113+
patterns.free()
114+
}
115+
}.pos(this.centerX + 10, this.centerY + 50).width(80).build())
116+
53117
this.addRenderableWidget(Button.builder(Component.translatable("menu.flightassistant.config")) {
54118
this.minecraft!!.setScreen(FAConfigScreen.generate(this))
55119
}.pos(10, this.height - 30).width(120).build())
@@ -58,4 +122,16 @@ class FlightAssistantSetupScreen : FABaseScreen(null, Component.translatable("me
58122
this.onClose()
59123
}.pos(this.width - 90, this.height - 30).width(80).build())
60124
}
125+
126+
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
127+
super.render(guiGraphics, mouseX, mouseY, delta)
128+
129+
if (saveLoadError) {
130+
guiGraphics.drawMiddleAlignedString(Component.translatable("menu.flightassistant.fms.error"), this.centerX, this.centerY + 75, ChatFormatting.RED.color!!, true)
131+
}
132+
}
133+
134+
companion object {
135+
val PLANS_PATH: Path = YACLPlatform.getConfigDir().resolve("${FlightAssistant.MOD_ID}/plans")
136+
}
61137
}

src/main/kotlin/ru/octol1ttle/flightassistant/screen/fms/arrival/ArrivalScreen.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import net.minecraft.client.gui.screens.Screen
77
import net.minecraft.network.chat.CommonComponents
88
import net.minecraft.network.chat.Component
99
import ru.octol1ttle.flightassistant.api.util.extensions.toIntOrNullWithFallback
10+
import ru.octol1ttle.flightassistant.impl.computer.autoflight.FlightPlanComputer
1011
import ru.octol1ttle.flightassistant.screen.FABaseScreen
1112
import ru.octol1ttle.flightassistant.screen.components.CycleTextOnlyButton
1213
import ru.octol1ttle.flightassistant.screen.components.SmartStringWidget
@@ -37,8 +38,8 @@ class ArrivalScreen(parent: Screen) : FABaseScreen(parent, Component.translatabl
3738

3839
val minimums = this.addRenderableWidget(SmartStringWidget(baseX, baseY + 64, Component.translatable("menu.flightassistant.fms.arrival.minimums")))
3940
val minimumsEditBox = this.addRenderableWidget(TypeStrictEditBox(minimums.x + minimums.width, minimums.y - 2, baseWidth, baseHeight, state.minimums, { state.minimums = it }, String::toIntOrNullWithFallback,
40-
{ if (state.minimumsType == ArrivalScreenState.MinimumsType.RELATIVE) it >= 0 else true }))
41-
this.addRenderableWidget(CycleTextOnlyButton(minimumsEditBox.x + minimumsEditBox.width + 5, minimumsEditBox.y + 2, ArrivalScreenState.MinimumsType.entries, state.minimumsType) { state.minimumsType = it; })
41+
{ if (state.minimumsType == FlightPlanComputer.ArrivalData.MinimumsType.RELATIVE) it >= 0 else true }))
42+
this.addRenderableWidget(CycleTextOnlyButton(minimumsEditBox.x + minimumsEditBox.width + 5, minimumsEditBox.y + 2, FlightPlanComputer.ArrivalData.MinimumsType.entries, state.minimumsType) { state.minimumsType = it; })
4243

4344
val goAroundAltitude = this.addRenderableWidget(SmartStringWidget(baseX, baseY + 80, Component.translatable("menu.flightassistant.fms.arrival.go_around_altitude")))
4445
this.addRenderableWidget(TypeStrictEditBox(goAroundAltitude.x + goAroundAltitude.width, goAroundAltitude.y - 2, baseWidth + 4, baseHeight, state.goAroundAltitude, { state.goAroundAltitude = it }, String::toIntOrNullWithFallback))
@@ -48,7 +49,7 @@ class ArrivalScreen(parent: Screen) : FABaseScreen(parent, Component.translatabl
4849
{ it >= 0 }))
4950

5051
discardChanges = this.addRenderableWidget(Button.builder(Component.translatable("menu.flightassistant.fms.discard_changes")) { _: Button? ->
51-
state = lastState.copy()
52+
state = ArrivalScreenState.load(computers.plan.arrivalData)
5253
this.rebuildWidgets()
5354
}.pos(this.width - 200, this.height - 30).width(100).build())
5455

@@ -58,19 +59,21 @@ class ArrivalScreen(parent: Screen) : FABaseScreen(parent, Component.translatabl
5859
}
5960

6061
override fun onClose() {
61-
lastState = state.copy()
6262
state.save(computers.plan)
6363
super.onClose()
6464
}
6565

6666
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
67-
discardChanges.active = state != lastState
67+
discardChanges.active = state != ArrivalScreenState.load(computers.plan.arrivalData)
6868

6969
super.render(guiGraphics, mouseX, mouseY, delta)
7070
}
7171

7272
companion object {
73-
private var lastState = ArrivalScreenState()
7473
private var state = ArrivalScreenState()
74+
75+
fun reload(data: FlightPlanComputer.ArrivalData) {
76+
state = ArrivalScreenState.load(data)
77+
}
7578
}
7679
}
Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package ru.octol1ttle.flightassistant.screen.fms.arrival
22

3-
import dev.isxander.yacl3.api.NameableEnum
4-
import net.minecraft.network.chat.Component
53
import ru.octol1ttle.flightassistant.impl.computer.autoflight.FlightPlanComputer
64

75
data class ArrivalScreenState(
@@ -10,24 +8,17 @@ data class ArrivalScreenState(
108
var elevation: Int = 0,
119
var landingThrustPercent: Int = 0,
1210
var minimums: Int = 0,
13-
var minimumsType: MinimumsType = MinimumsType.ABSOLUTE,
11+
var minimumsType: FlightPlanComputer.ArrivalData.MinimumsType = FlightPlanComputer.ArrivalData.MinimumsType.ABSOLUTE,
1412
var goAroundAltitude: Int = 0,
1513
var approachReEntryWaypointIndex: Int = 0
1614
) {
17-
fun load(flightPlan: FlightPlanComputer) {
18-
TODO()
19-
}
20-
2115
fun save(flightPlan: FlightPlanComputer) {
22-
flightPlan.arrivalData = FlightPlanComputer.ArrivalData(coordinatesX, coordinatesZ, elevation, landingThrustPercent / 100.0f, minimums, minimumsType.type, goAroundAltitude, approachReEntryWaypointIndex)
16+
flightPlan.arrivalData = FlightPlanComputer.ArrivalData(coordinatesX, coordinatesZ, elevation, landingThrustPercent / 100.0f, minimums, minimumsType, goAroundAltitude, approachReEntryWaypointIndex)
2317
}
2418

25-
enum class MinimumsType(@JvmField val displayName: Component, val type: FlightPlanComputer.ArrivalData.MinimumsType) : NameableEnum {
26-
ABSOLUTE(Component.translatable("menu.flightassistant.fms.arrival.minimums.absolute"), FlightPlanComputer.ArrivalData.MinimumsType.ABSOLUTE),
27-
RELATIVE(Component.translatable("menu.flightassistant.fms.arrival.minimums.relative"), FlightPlanComputer.ArrivalData.MinimumsType.RELATIVE);
28-
29-
override fun getDisplayName(): Component {
30-
return this.displayName
19+
companion object {
20+
fun load(data: FlightPlanComputer.ArrivalData): ArrivalScreenState {
21+
return ArrivalScreenState(data.coordinatesX, data.coordinatesZ, data.elevation, (data.landingThrust / 100.0f).toInt(), data.minimums, data.minimumsType, data.goAroundAltitude, data.approachReEntryWaypointIndex)
3122
}
3223
}
3324
}

src/main/kotlin/ru/octol1ttle/flightassistant/screen/fms/departure/DepartureScreen.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import net.minecraft.client.gui.screens.Screen
77
import net.minecraft.network.chat.CommonComponents
88
import net.minecraft.network.chat.Component
99
import ru.octol1ttle.flightassistant.api.util.extensions.toIntOrNullWithFallback
10+
import ru.octol1ttle.flightassistant.impl.computer.autoflight.FlightPlanComputer
1011
import ru.octol1ttle.flightassistant.screen.FABaseScreen
1112
import ru.octol1ttle.flightassistant.screen.components.SmartStringWidget
1213
import ru.octol1ttle.flightassistant.screen.components.TypeStrictEditBox
@@ -35,7 +36,7 @@ class DepartureScreen(parent: Screen) : FABaseScreen(parent, Component.translata
3536
this.addRenderableWidget(TypeStrictEditBox(takeoffThrust.x + takeoffThrust.width, takeoffThrust.y - 2, baseWidth, baseHeight, state.takeoffThrustPercent, { state.takeoffThrustPercent = it }, String::toIntOrNullWithFallback, { it in 0..100 }))
3637

3738
discardChanges = this.addRenderableWidget(Button.builder(Component.translatable("menu.flightassistant.fms.discard_changes")) { _: Button? ->
38-
state = lastState.copy()
39+
state = DepartureScreenState.load(computers.plan.departureData)
3940
this.rebuildWidgets()
4041
}.pos(this.width - 200, this.height - 30).width(100).build())
4142

@@ -45,19 +46,21 @@ class DepartureScreen(parent: Screen) : FABaseScreen(parent, Component.translata
4546
}
4647

4748
override fun onClose() {
48-
lastState = state.copy()
4949
state.save(computers.plan)
5050
super.onClose()
5151
}
5252

5353
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
54-
discardChanges.active = state != lastState
54+
discardChanges.active = state != DepartureScreenState.load(computers.plan.departureData)
5555

5656
super.render(guiGraphics, mouseX, mouseY, delta)
5757
}
5858

5959
companion object {
60-
private var lastState: DepartureScreenState = DepartureScreenState()
6160
private var state: DepartureScreenState = DepartureScreenState()
61+
62+
fun reload(data: FlightPlanComputer.DepartureData) {
63+
state = DepartureScreenState.load(data)
64+
}
6265
}
6366
}

src/main/kotlin/ru/octol1ttle/flightassistant/screen/fms/departure/DepartureScreenState.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ data class DepartureScreenState(
88
var elevation: Int = 0,
99
var takeoffThrustPercent: Int = 0
1010
) {
11-
fun load(flightPlan: FlightPlanComputer) {
12-
TODO()
13-
}
14-
1511
fun save(flightPlan: FlightPlanComputer) {
1612
flightPlan.departureData = FlightPlanComputer.DepartureData(coordinatesX, coordinatesZ, elevation, takeoffThrustPercent / 100.0f)
1713
}
14+
15+
companion object {
16+
fun load(data: FlightPlanComputer.DepartureData): DepartureScreenState {
17+
return DepartureScreenState(data.coordinatesX, data.coordinatesZ, data.elevation, (data.takeoffThrust * 100.0f).toInt())
18+
}
19+
}
1820
}

src/main/kotlin/ru/octol1ttle/flightassistant/screen/fms/enroute/EnrouteScreen.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ class EnrouteScreen(parent: Screen) : FABaseScreen(parent, Component.translatabl
5454
)).setTooltip(Tooltip.create(LEGEND_TOOLTIP_TEXT))
5555

5656
discardChanges = this.addRenderableWidget(Button.builder(Component.translatable("menu.flightassistant.fms.discard_changes")) { _: Button? ->
57-
state = EnrouteScreenState.load(computers.plan)
57+
state = EnrouteScreenState.load(computers.plan.enrouteData)
5858
this.rebuildWidgets()
5959
}.pos(this.width - 290, this.height - 30).width(100).build())
6060

@@ -71,7 +71,7 @@ class EnrouteScreen(parent: Screen) : FABaseScreen(parent, Component.translatabl
7171
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
7272
deleteAll.active = state.waypoints.isNotEmpty()
7373

74-
val hasUnsavedChanges: Boolean = !state.equals(EnrouteScreenState.load(computers.plan))
74+
val hasUnsavedChanges: Boolean = !state.equals(EnrouteScreenState.load(computers.plan.enrouteData))
7575
save.active = hasUnsavedChanges
7676
discardChanges.active = hasUnsavedChanges
7777
done.active = !hasUnsavedChanges
@@ -95,5 +95,9 @@ class EnrouteScreen(parent: Screen) : FABaseScreen(parent, Component.translatabl
9595
)
9696

9797
private var state: EnrouteScreenState = EnrouteScreenState()
98+
99+
fun reload(waypoints: List<FlightPlanComputer.EnrouteWaypoint>) {
100+
state = EnrouteScreenState.load(waypoints)
101+
}
98102
}
99103
}

0 commit comments

Comments
 (0)