From 90fa0485650e6afb714d06b81af0e261801816a0 Mon Sep 17 00:00:00 2001 From: JoshSald <42002468+JoshSald@users.noreply.github.com> Date: Fri, 11 Sep 2026 13:14:32 +0200 Subject: [PATCH 1/5] build: bump toolchain and add version targets for Minecraft 26.1/26.2 Minecraft 26.1 ships fully deobfuscated, which the existing modstitch 0.7.0-unstable/Loom 1.14.6 toolchain has no concept of. Bump modstitch to 0.8.5 (the version that added unobfuscated-MC support, splitting the `loom` platform into `fabric-loom-remap` for existing obfuscated versions and `fabric-loom` for 26.1+), Loom to 1.17.20, Gradle to 9.5.1, Fabric Loader to 0.19.5, and Kotlin to 2.4.20 (needed for the new Java 25 target on 26.1+). Adds versions/26.1.2-fabric (26.1's second patch, since Architectury 20.1.14 requires >=26.1.2) and versions/26.2-fabric with researched dependency coordinates. Existing 1.20.1-1.21.11 fabric versions are updated for the platform rename only and remain otherwise untouched; verified they still compile after the shared toolchain bump. --- build.gradle.kts | 10 ++++++---- gradle.properties | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 2 ++ stonecutter.gradle.kts | 4 ++-- versions/1.20.1-fabric/gradle.properties | 2 +- versions/1.21.1-fabric/gradle.properties | 2 +- versions/1.21.11-fabric/gradle.properties | 2 +- versions/1.21.6-fabric/gradle.properties | 2 +- versions/1.21.9-fabric/gradle.properties | 2 +- versions/26.1.2-fabric/gradle.properties | 16 ++++++++++++++++ versions/26.2-fabric/gradle.properties | 16 ++++++++++++++++ 12 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 versions/26.1.2-fabric/gradle.properties create mode 100644 versions/26.2-fabric/gradle.properties diff --git a/build.gradle.kts b/build.gradle.kts index 24827689..16504318 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ plugins { kotlin("jvm") kotlin("plugin.serialization") - id("dev.isxander.modstitch.base") version "0.7.0-unstable" - id("fabric-loom") version "1.14.6" apply false + id("dev.isxander.modstitch.base") version "0.8.5" + id("fabric-loom") version "1.17.20" apply false id("me.modmuss50.mod-publish-plugin") id("me.fallenbreath.yamlang") version "1.5.0" } @@ -47,15 +47,17 @@ tasks.withType { modstitch { minecraftVersion = minecraft + val j25: Boolean = stonecutter.eval(minecraft, ">=26.1") val j21: Boolean = stonecutter.eval(minecraft, ">=1.20.6") - javaVersion = if (j21) 21 else 17 + val javaVersionNumber: Int = if (j25) 25 else if (j21) 21 else 17 + javaVersion = javaVersionNumber java { withSourcesJar() } kotlin { - jvmToolchain(if (j21) 21 else 17) + jvmToolchain(javaVersionNumber) } // If parchment doesnt exist for a version yet you can safely diff --git a/gradle.properties b/gradle.properties index cd73117b..8cfc89c4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,11 +8,11 @@ org.gradle.configuration-cache=false mod.id=flightassistant mod.name=FlightAssistant mod.group=ru.octol1ttle -mod.version=3.0.0 +mod.version=3.1.0 # Global Dependencies deps.mixin_extras=0.4.1 -deps.fabric_loader=0.16.10 +deps.fabric_loader=0.19.5 deps.flk=1.13.0 publish.modrinth=CWqLEOPt diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cd4b7aa8..cf71939e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index ec47d378..64432e97 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -38,6 +38,8 @@ stonecutter { mc("1.21.6", "fabric") mc("1.21.9", "fabric") mc("1.21.11", "fabric") + mc("26.1.2", "fabric") + mc("26.2", "fabric") } } diff --git a/stonecutter.gradle.kts b/stonecutter.gradle.kts index b2d43790..b6a04fc7 100644 --- a/stonecutter.gradle.kts +++ b/stonecutter.gradle.kts @@ -1,6 +1,6 @@ plugins { - kotlin("jvm") version "2.2.0" apply false - kotlin("plugin.serialization") version "2.2.0" apply false + kotlin("jvm") version "2.4.20" apply false + kotlin("plugin.serialization") version "2.4.20" apply false id("dev.kikugie.stonecutter") id("me.modmuss50.mod-publish-plugin") version "0.8.4" apply false } diff --git a/versions/1.20.1-fabric/gradle.properties b/versions/1.20.1-fabric/gradle.properties index a22bd0ac..ae8405f5 100644 --- a/versions/1.20.1-fabric/gradle.properties +++ b/versions/1.20.1-fabric/gradle.properties @@ -1,4 +1,4 @@ -modstitch.platform=loom +modstitch.platform=fabric-loom-remap # Global deps.parchment=2023.09.03 diff --git a/versions/1.21.1-fabric/gradle.properties b/versions/1.21.1-fabric/gradle.properties index 820147d5..3168084b 100644 --- a/versions/1.21.1-fabric/gradle.properties +++ b/versions/1.21.1-fabric/gradle.properties @@ -1,4 +1,4 @@ -modstitch.platform=loom +modstitch.platform=fabric-loom-remap # Global deps.parchment=2024.11.17 diff --git a/versions/1.21.11-fabric/gradle.properties b/versions/1.21.11-fabric/gradle.properties index 6a41f119..1af32a11 100644 --- a/versions/1.21.11-fabric/gradle.properties +++ b/versions/1.21.11-fabric/gradle.properties @@ -1,4 +1,4 @@ -modstitch.platform=loom +modstitch.platform=fabric-loom-remap # Global deps.parchment=2025.12.20 diff --git a/versions/1.21.6-fabric/gradle.properties b/versions/1.21.6-fabric/gradle.properties index ddff0e39..d5d20c99 100644 --- a/versions/1.21.6-fabric/gradle.properties +++ b/versions/1.21.6-fabric/gradle.properties @@ -1,4 +1,4 @@ -modstitch.platform=loom +modstitch.platform=fabric-loom-remap # Global deps.parchment=2025.06.29 diff --git a/versions/1.21.9-fabric/gradle.properties b/versions/1.21.9-fabric/gradle.properties index 15f9bf23..fde5e70a 100644 --- a/versions/1.21.9-fabric/gradle.properties +++ b/versions/1.21.9-fabric/gradle.properties @@ -1,4 +1,4 @@ -modstitch.platform=loom +modstitch.platform=fabric-loom-remap # Global deps.parchment=2025.10.05 diff --git a/versions/26.1.2-fabric/gradle.properties b/versions/26.1.2-fabric/gradle.properties new file mode 100644 index 00000000..543e8e4f --- /dev/null +++ b/versions/26.1.2-fabric/gradle.properties @@ -0,0 +1,16 @@ +modstitch.platform=fabric-loom + +# Global +# No deps.parchment: no Parchment mappings published for 26.1 yet, falls back to official mappings. +deps.yacl=3.9.6+26.1-fabric +deps.arch_api=20.1.14 + +# Fabric +deps.fapi=0.155.3+26.1.2 +deps.modmenu=18.0.1 +deps.dabr=3.8.4+26.1-fabric + +# Mod +mod.mc_version_range=>=26.1 <26.2 +mod.mc_title=26.1.2 +mod.mc_targets=26.1 26.1.1 26.1.2 diff --git a/versions/26.2-fabric/gradle.properties b/versions/26.2-fabric/gradle.properties new file mode 100644 index 00000000..77410d8c --- /dev/null +++ b/versions/26.2-fabric/gradle.properties @@ -0,0 +1,16 @@ +modstitch.platform=fabric-loom + +# Global +# No deps.parchment: no Parchment mappings published for 26.2 yet, falls back to official mappings. +deps.yacl=3.9.6+26.2-fabric +deps.arch_api=21.1.9 + +# Fabric +deps.fapi=0.160.0+26.2 +deps.modmenu=20.0.2 +deps.dabr=3.8.4+26.2-fabric + +# Mod +mod.mc_version_range=26.2 +mod.mc_title=26.2 +mod.mc_targets=26.2 From c0e2a3d9d5a101153855161cb630c38576b3be7b Mon Sep 17 00:00:00 2001 From: JoshSald <42002468+JoshSald@users.noreply.github.com> Date: Fri, 11 Sep 2026 13:20:53 +0200 Subject: [PATCH 2/5] fix: port HUD rendering to Minecraft 26.1's GuiGraphics rewrite Minecraft 26.1 renamed GuiGraphics to GuiGraphicsExtractor and moved from immediate-mode rendering to a CPU-side render-state extraction model: Screen/Widget `render()` became `extractRenderState()`, AbstractButton's override point became `extractContents()`, AbstractSelectionList.Entry's became `extractContent()`, and `hLine`/`vLine`/`drawString` were renamed to `horizontalLine`/ `verticalLine`/`text`. EditBox also dropped `setFilter` entirely. Adds a `GuiGraphics` typealias (-> GuiGraphicsExtractor) and a `Renderable.render()` extension shim so the ~20 files that just reference the type or call `.render(...)` on a widget keep compiling unchanged; adds `hLine`/`vLine`/`drawString` extension shims backed by the new `fill`/`text` methods. The handful of files that directly override a renamed Mojang method (FABaseScreen, TextOnlyButton, CycleTextOnlyButton, EnrouteWaypointsList, SystemManagementList) get a new `>=26.1` stonecutter branch alongside the existing ones. TypeStrictEditBox replaces `setFilter` with an `insertText` override that reverts invalid input, replicating the old behavior. This is compatibility work only - no HUD behavior or appearance is intended to change on existing versions. --- .../mixin/gui/GuiMixinNew.java | 8 +++- .../net/minecraft/client/gui/GuiGraphics.kt | 4 ++ .../client/gui/components/Renderable.kt | 7 +++ .../util/extensions/GuiGraphicsExtensions.kt | 48 ++++++++++++++++--- .../flightassistant/screen/FABaseScreen.kt | 10 ++++ .../screen/components/CycleTextOnlyButton.kt | 5 +- .../screen/components/TextOnlyButton.kt | 5 +- .../screen/components/TypeStrictEditBox.kt | 21 +++++++- .../fms/enroute/EnrouteWaypointsList.kt | 9 +++- .../screen/system/SystemManagementList.kt | 8 +++- 10 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/net/minecraft/client/gui/GuiGraphics.kt create mode 100644 src/main/kotlin/net/minecraft/client/gui/components/Renderable.kt diff --git a/src/main/java/ru/octol1ttle/flightassistant/mixin/gui/GuiMixinNew.java b/src/main/java/ru/octol1ttle/flightassistant/mixin/gui/GuiMixinNew.java index a4b13173..4cdd78c7 100644 --- a/src/main/java/ru/octol1ttle/flightassistant/mixin/gui/GuiMixinNew.java +++ b/src/main/java/ru/octol1ttle/flightassistant/mixin/gui/GuiMixinNew.java @@ -5,7 +5,13 @@ @Mixin(Gui.class) abstract class GuiMixinNew { -//? if fabric && >=1.21.6 { +//? if fabric && >=26.1 { + /*@org.spongepowered.asm.mixin.injection.Inject(method = "extractRenderState", at = @org.spongepowered.asm.mixin.injection.At(value = "INVOKE", target = "Lnet/minecraft/client/gui/Gui;extractHotbarAndDecorations(Lnet/minecraft/client/gui/GuiGraphicsExtractor;Lnet/minecraft/client/DeltaTracker;)V")) + private void beforeRenderHotbarAndDecorations(net.minecraft.client.gui.GuiGraphicsExtractor guiGraphics, net.minecraft.client.DeltaTracker deltaTracker, org.spongepowered.asm.mixin.injection.callback.CallbackInfo ci) { + ru.octol1ttle.flightassistant.api.util.event.FixedGuiRenderCallback.EVENT.invoker().onRenderGui(guiGraphics, deltaTracker.getGameTimeDeltaPartialTick(true)); + guiGraphics.nextStratum(); + } +*///?} else if fabric && >=1.21.6 { /*@org.spongepowered.asm.mixin.injection.Inject(method = "render", at = @org.spongepowered.asm.mixin.injection.At(value = "INVOKE", target = "Lnet/minecraft/client/gui/Gui;renderHotbarAndDecorations(Lnet/minecraft/client/gui/GuiGraphics;Lnet/minecraft/client/DeltaTracker;)V")) private void beforeRenderHotbarAndDecorations(net.minecraft.client.gui.GuiGraphics guiGraphics, net.minecraft.client.DeltaTracker deltaTracker, org.spongepowered.asm.mixin.injection.callback.CallbackInfo ci) { ru.octol1ttle.flightassistant.api.util.event.FixedGuiRenderCallback.EVENT.invoker().onRenderGui(guiGraphics, deltaTracker.getGameTimeDeltaPartialTick(true)); diff --git a/src/main/kotlin/net/minecraft/client/gui/GuiGraphics.kt b/src/main/kotlin/net/minecraft/client/gui/GuiGraphics.kt new file mode 100644 index 00000000..904d3adf --- /dev/null +++ b/src/main/kotlin/net/minecraft/client/gui/GuiGraphics.kt @@ -0,0 +1,4 @@ +package net.minecraft.client.gui + +//? if >=26.1 +/*typealias GuiGraphics = GuiGraphicsExtractor*/ diff --git a/src/main/kotlin/net/minecraft/client/gui/components/Renderable.kt b/src/main/kotlin/net/minecraft/client/gui/components/Renderable.kt new file mode 100644 index 00000000..e6b0fc47 --- /dev/null +++ b/src/main/kotlin/net/minecraft/client/gui/components/Renderable.kt @@ -0,0 +1,7 @@ +package net.minecraft.client.gui.components + +//? if >=26.1 { +/*fun Renderable.render(guiGraphics: net.minecraft.client.gui.GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) { + this.extractRenderState(guiGraphics, mouseX, mouseY, partialTick) +} +*///?} diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/api/util/extensions/GuiGraphicsExtensions.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/api/util/extensions/GuiGraphicsExtensions.kt index 0caf061f..54d3dd72 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/api/util/extensions/GuiGraphicsExtensions.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/api/util/extensions/GuiGraphicsExtensions.kt @@ -13,6 +13,36 @@ import ru.octol1ttle.flightassistant.config.FAConfig internal val font: Font = mc.font +//? if >=26.1 { +/*fun GuiGraphics.hLine(x1: Int, x2: Int, y: Int, color: Int) { + val minX: Int = minOf(x1, x2) + val maxX: Int = maxOf(x1, x2) + fill(minX, y, maxX + 1, y + 1, color) +} + +fun GuiGraphics.vLine(x: Int, y1: Int, y2: Int, color: Int) { + val minY: Int = minOf(y1, y2) + val maxY: Int = maxOf(y1, y2) + fill(x, minY, x + 1, maxY + 1, color) +} + +fun GuiGraphics.drawString(font: Font, text: String, x: Int, y: Int, color: Int) { + this.text(font, text, x, y, color) +} + +fun GuiGraphics.drawString(font: Font, text: String, x: Int, y: Int, color: Int, shadow: Boolean) { + this.text(font, text, x, y, color, shadow) +} + +fun GuiGraphics.drawString(font: Font, text: Component, x: Int, y: Int, color: Int) { + this.text(font, text, x, y, color) +} + +fun GuiGraphics.drawString(font: Font, text: Component, x: Int, y: Int, color: Int, shadow: Boolean) { + this.text(font, text, x, y, color, shadow) +} +*///?} + val lineHeight: Int get() = font.lineHeight @@ -156,13 +186,19 @@ fun org.joml.Matrix3x2fStack.pop() { popMatrix() } -//? if >=1.21.9 { +//? if >=26.1 { +/*fun GuiGraphics.renderOutline(x: Int, y: Int, width: Int, height: Int, color: Int) { + outline(x, y, width, height, color) +} +*///?} +//? if >=1.21.11 && <26.1 { +/*fun GuiGraphics.renderOutline(x: Int, y: Int, width: Int, height: Int, color: Int) { + renderOutline(x, y, width, height, color) +} +*///?} +//? if >=1.21.9 && <1.21.11 { /*fun GuiGraphics.renderOutline(x: Int, y: Int, width: Int, height: Int, color: Int) { -//? if >=1.21.11 { - /^renderOutline( -^///?} else - submitOutline( - x, y, width, height, color) + submitOutline(x, y, width, height, color) renderDeferredElements() } *///?} \ No newline at end of file diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/FABaseScreen.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/FABaseScreen.kt index 824f4a87..81913bd3 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/FABaseScreen.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/FABaseScreen.kt @@ -21,6 +21,15 @@ abstract class FABaseScreen(val parent: Screen?, title: Component) : Screen(titl this.addRenderableWidget(SmartStringWidget(this.centerX, 7, this.title).middleAligned()) } +//? if >=26.1 { + /*override fun extractRenderState(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) { + this.render(guiGraphics, mouseX, mouseY, delta) + } + + open fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) { + super.extractRenderState(guiGraphics, mouseX, mouseY, delta) + } +*///?} else { override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) { //? if <1.21.6 { this.renderBackground( @@ -30,6 +39,7 @@ abstract class FABaseScreen(val parent: Screen?, title: Component) : Screen(titl //?} super.render(guiGraphics, mouseX, mouseY, delta) } +//?} override fun onClose() { this.minecraft!!.setScreen(parent) diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/components/CycleTextOnlyButton.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/components/CycleTextOnlyButton.kt index 76bc38c3..40c325e5 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/components/CycleTextOnlyButton.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/components/CycleTextOnlyButton.kt @@ -11,6 +11,7 @@ import net.minecraft.network.chat.MutableComponent import net.minecraft.network.chat.Style import net.minecraft.util.Mth import ru.octol1ttle.flightassistant.api.util.extensions.appendWithSeparation +import ru.octol1ttle.flightassistant.api.util.extensions.drawString import ru.octol1ttle.flightassistant.api.util.extensions.font import ru.octol1ttle.flightassistant.api.util.extensions.whiteColor @@ -24,7 +25,9 @@ class CycleTextOnlyButton(x: Int, y: Int, private val entries: refreshMessage() } -//? if >=1.21.11 { +//? if >=26.1 { + /*override fun extractContents( +*///?} else if >=1.21.11 { /*override fun renderContents( *///?} else override fun renderWidget( diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/components/TextOnlyButton.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/components/TextOnlyButton.kt index fd4da20f..e9cc0d48 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/components/TextOnlyButton.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/components/TextOnlyButton.kt @@ -7,12 +7,15 @@ import net.minecraft.network.chat.Component import net.minecraft.network.chat.ComponentUtils import net.minecraft.network.chat.Style import net.minecraft.util.Mth +import ru.octol1ttle.flightassistant.api.util.extensions.drawString import ru.octol1ttle.flightassistant.api.util.extensions.font class TextOnlyButton(val baseX: Int, y: Int, text: Component, onPress: OnPress) : Button(baseX - font.width(text) / 2, y, font.width(text), font.lineHeight, text, onPress, DEFAULT_NARRATION) { var color: Int = 0 -//? if >=1.21.11 { +//? if >=26.1 { + /*override fun extractContents( +*///?} else if >=1.21.11 { /*override fun renderContents( *///?} else override fun renderWidget( diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/components/TypeStrictEditBox.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/components/TypeStrictEditBox.kt index 71c6f031..9211e60c 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/components/TypeStrictEditBox.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/components/TypeStrictEditBox.kt @@ -7,13 +7,32 @@ import net.minecraft.client.gui.components.EditBox import net.minecraft.network.chat.Component import ru.octol1ttle.flightassistant.api.util.extensions.font -class TypeStrictEditBox(x: Int, y: Int, width: Int, height: Int, initialValue: T, onValueChange: Consumer, convertFunction: (String) -> T?, filter: Predicate = Predicates.alwaysTrue()) : EditBox(font, x, y, width, height, Component.empty()) { +class TypeStrictEditBox(x: Int, y: Int, width: Int, height: Int, initialValue: T, onValueChange: Consumer, private val convertFunction: (String) -> T?, private val filter: Predicate = Predicates.alwaysTrue()) : EditBox(font, x, y, width, height, Component.empty()) { init { this.value = initialValue.toString() +//? if <26.1 { this.setFilter { val value: T? = convertFunction.invoke(it) value != null && filter.test(value) } +//?} this.setResponder { onValueChange.accept(convertFunction.invoke(it)!!) } } + +//? if >=26.1 { + /*private fun isValid(candidate: String): Boolean { + val value: T? = convertFunction.invoke(candidate) + return value != null && filter.test(value) + } + + override fun insertText(text: String) { + val previousValue: String = this.value + val previousCursor: Int = this.cursorPosition + super.insertText(text) + if (!isValid(this.value)) { + this.value = previousValue + this.setCursorPosition(previousCursor) + } + } +*///?} } \ No newline at end of file diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/fms/enroute/EnrouteWaypointsList.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/fms/enroute/EnrouteWaypointsList.kt index b179c211..2d239ba9 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/fms/enroute/EnrouteWaypointsList.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/fms/enroute/EnrouteWaypointsList.kt @@ -8,6 +8,9 @@ import net.minecraft.client.gui.components.Tooltip import net.minecraft.client.gui.components.events.GuiEventListener import net.minecraft.client.gui.narration.NarratableEntry import net.minecraft.network.chat.Component +//? if >=26.1 { +/*import net.minecraft.client.gui.components.render +*///?} import ru.octol1ttle.flightassistant.api.computer.ComputerBus import ru.octol1ttle.flightassistant.api.util.extensions.* import ru.octol1ttle.flightassistant.impl.computer.autoflight.FlightPlanComputer @@ -52,7 +55,11 @@ class EnrouteWaypointsList(y0: Int, y1: Int, width: Int, val columns: Float, val private var lastFlightPlanActive: FlightPlanComputer.EnrouteWaypoint.Active? = state.active -//? if >=1.21.9 { +//? if >=26.1 { + /*override fun extractContent(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, hovering: Boolean, partialTick: Float) { + val top = contentY + val index = list.children().indexOf(this) +*///?} else if >=1.21.9 { /*override fun renderContent(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, hovering: Boolean, partialTick: Float) { val top = contentY val index = list.children().indexOf(this) diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/system/SystemManagementList.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/system/SystemManagementList.kt index b6573228..13568d33 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/system/SystemManagementList.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/system/SystemManagementList.kt @@ -8,6 +8,9 @@ import net.minecraft.client.gui.components.events.GuiEventListener import net.minecraft.client.gui.narration.NarratableEntry import net.minecraft.network.chat.Component import net.minecraft.resources.ResourceLocation +//? if >=26.1 { +/*import net.minecraft.client.gui.components.render +*///?} import ru.octol1ttle.flightassistant.api.ModuleController import ru.octol1ttle.flightassistant.api.util.extensions.cautionColor import ru.octol1ttle.flightassistant.api.util.extensions.font @@ -31,7 +34,10 @@ class SystemManagementList(y0: Int, y1: Int, width: Int, baseKey: String, contro val children = listOf(this.displayName, faultText, offText, toggleButton) -//? if >=1.21.9 { +//? if >=26.1 { + /*override fun extractContent(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, hovering: Boolean, partialTick: Float) { + val top = contentY +*///?} else if >=1.21.9 { /*override fun renderContent(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, hovering: Boolean, partialTick: Float) { val top = contentY *///?} else { From a03832fccfa11c177a20c480ccdfa7dc7c729c29 Mon Sep 17 00:00:00 2001 From: JoshSald <42002468+JoshSald@users.noreply.github.com> Date: Fri, 11 Sep 2026 13:21:15 +0200 Subject: [PATCH 3/5] fix: correct HUD world-space projection under Minecraft 26.1 rendering Three separate issues in the world-space -> screen-space pipeline that fed the attitude ladder, heading tape, and flight path vector, found by comparing against Octol1ttle/FlightAssistant#92: - LevelRenderer.renderLevel's three matrices (frustum/projection/ culling-projection) collapsed into a single Matrix4fc parameter on 26.1. It's the frustum/view matrix, not the projection matrix as first assumed - the real projection matrix now lives on CameraRenderState.projectionMatrix. Using the wrong matrix in the wrong role corrupted every downstream world-space HUD calculation. - ScreenSpace read the GL viewport via glGetIntegerv, which is stale during 26.1's new CPU-side render-state extraction phase. Now reads mc.window's dimensions directly instead. - 26.1 uses a reversed-Z depth range, which broke the existing on-screen check: points behind the camera were mirrored back into the visible depth window instead of being rejected, so e.g. both ends of the pitch-limit indicator would render regardless of actual camera orientation. fromWorldSpace now explicitly rejects points at or behind the camera plane (clip.w <= 0) before projecting. Also stopped reading RenderSystem.getModelViewMatrix() on 26.1+, which is equally stale during extraction - it's now taken as identity, matching the reference fix. --- .../LevelRendererMixinNewestest.java | 9 ++++-- .../flightassistant/FlightAssistant.kt | 9 ++++-- .../flightassistant/api/util/ScreenSpace.kt | 30 +++++++++++++++---- .../api/util/event/LevelRenderCallback.kt | 5 ++-- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/main/java/ru/octol1ttle/flightassistant/mixin/level_renderer/LevelRendererMixinNewestest.java b/src/main/java/ru/octol1ttle/flightassistant/mixin/level_renderer/LevelRendererMixinNewestest.java index b9ddd0f7..fb31055b 100644 --- a/src/main/java/ru/octol1ttle/flightassistant/mixin/level_renderer/LevelRendererMixinNewestest.java +++ b/src/main/java/ru/octol1ttle/flightassistant/mixin/level_renderer/LevelRendererMixinNewestest.java @@ -5,10 +5,15 @@ @Mixin(LevelRenderer.class) abstract class LevelRendererMixinNewestest { -//? if >=1.21.9 { +//? if >=26.1 { + /*@org.spongepowered.asm.mixin.injection.Inject(method = "renderLevel", at = @org.spongepowered.asm.mixin.injection.At("HEAD")) + private void onStartRender(com.mojang.blaze3d.resource.GraphicsResourceAllocator graphicsResourceAllocator, net.minecraft.client.DeltaTracker deltaTracker, boolean renderBlockOutline, net.minecraft.client.renderer.state.level.CameraRenderState cameraRenderState, org.joml.Matrix4fc frustumMatrix, com.mojang.blaze3d.buffers.GpuBufferSlice fogBuffer, org.joml.Vector4f fogColor, boolean renderSky, net.minecraft.client.renderer.chunk.ChunkSectionsToRender chunkSectionsToRender, org.spongepowered.asm.mixin.injection.callback.CallbackInfo ci) { + ru.octol1ttle.flightassistant.api.util.event.LevelRenderCallback.EVENT.invoker().onStartRenderLevel(deltaTracker.getGameTimeDeltaPartialTick(true), cameraRenderState.xRot, cameraRenderState.yRot, cameraRenderState.projectionMatrix, frustumMatrix.get3x3(new org.joml.Matrix3f())); + } +*///?} else if >=1.21.9 { /*@org.spongepowered.asm.mixin.injection.Inject(method = "renderLevel", at = @org.spongepowered.asm.mixin.injection.At("HEAD")) private void onStartRender(com.mojang.blaze3d.resource.GraphicsResourceAllocator graphicsResourceAllocator, net.minecraft.client.DeltaTracker deltaTracker, boolean renderBlockOutline, net.minecraft.client.Camera camera, org.joml.Matrix4f frustumMatrix, org.joml.Matrix4f projectionMatrix, org.joml.Matrix4f cullingProjectionMatrix, com.mojang.blaze3d.buffers.GpuBufferSlice fogBuffer, org.joml.Vector4f fogColor, boolean renderSky, org.spongepowered.asm.mixin.injection.callback.CallbackInfo ci) { - ru.octol1ttle.flightassistant.api.util.event.LevelRenderCallback.EVENT.invoker().onStartRenderLevel(deltaTracker.getGameTimeDeltaPartialTick(true), camera, projectionMatrix, frustumMatrix.get3x3(new org.joml.Matrix3f())); + ru.octol1ttle.flightassistant.api.util.event.LevelRenderCallback.EVENT.invoker().onStartRenderLevel(deltaTracker.getGameTimeDeltaPartialTick(true), camera.xRot, camera.yRot, projectionMatrix, frustumMatrix.get3x3(new org.joml.Matrix3f())); } *///?} } diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/FlightAssistant.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/FlightAssistant.kt index d9cc7444..b3825fc5 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/FlightAssistant.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/FlightAssistant.kt @@ -43,18 +43,21 @@ object FlightAssistant { ComputerHost.sendRegistrationEvent() initComplete = true } - LevelRenderCallback.EVENT.register { partialTick, camera, projectionMatrix, frustumMatrix -> + LevelRenderCallback.EVENT.register { partialTick, cameraXRot, cameraYRot, projectionMatrix, frustumMatrix -> FAKeyMappings.checkPressed(ComputerHost) ComputerHost.tick(partialTick) RenderMatrices.projectionMatrix.set(projectionMatrix) RenderMatrices.worldSpaceMatrix.set(frustumMatrix) +//? if >=26.1 { + /*RenderMatrices.modelViewMatrix.identity() +*///?} else RenderMatrices.modelViewMatrix.set(RenderSystem.getModelViewMatrix()) RenderMatrices.worldSpaceNoRollMatrix.set(Matrix4f().apply { - rotate(Axis.XP.rotationDegrees(camera.xRot)) - rotate(Axis.YP.rotationDegrees(camera.yRot + 180.0f)) + rotate(Axis.XP.rotationDegrees(cameraXRot)) + rotate(Axis.YP.rotationDegrees(cameraYRot + 180.0f)) }) RenderMatrices.ready = true diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/api/util/ScreenSpace.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/api/util/ScreenSpace.kt index cc2992d8..36089266 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/api/util/ScreenSpace.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/api/util/ScreenSpace.kt @@ -5,14 +5,18 @@ import org.jetbrains.annotations.Contract import org.joml.Matrix4f import org.joml.Vector3f import org.joml.Vector4f -import org.lwjgl.opengl.GL11 import ru.octol1ttle.flightassistant.FlightAssistant.mc object ScreenSpace { private var viewport: IntArray = IntArray(4) internal fun updateViewport() { - GL11.glGetIntegerv(GL11.GL_VIEWPORT, viewport) + // The HUD is now extracted during the CPU render-state phase (26.x), where the + // GL viewport is stale. Project against the actual framebuffer size instead. + viewport[0] = 0 + viewport[1] = 0 + viewport[2] = mc.window.width + viewport[3] = mc.window.height } /** @@ -52,10 +56,24 @@ object ScreenSpace { val matrixModel = Matrix4f(RenderMatrices.modelViewMatrix) matrixProj.mul(matrixModel) - .project( - transformedCoordinates.x(), transformedCoordinates.y(), transformedCoordinates.z(), viewport, - target - ) + + // On 26.x the projection matrix uses a reversed-Z depth range. Points that are at + // or behind the camera plane (clip.w <= 0) get mirrored back into the visible depth + // window, so the generic z-range check below can no longer reject them. Reject them + // here explicitly, otherwise e.g. both pitch-limit arrows end up drawn at once. + val clipW: Float = + matrixProj.m03() * transformedCoordinates.x + + matrixProj.m13() * transformedCoordinates.y + + matrixProj.m23() * transformedCoordinates.z + + matrixProj.m33() * transformedCoordinates.w + if (clipW <= 0.0f) { + return Vector3f(-1.0f, -1.0f, 2.0f) + } + + matrixProj.project( + transformedCoordinates.x(), transformedCoordinates.y(), transformedCoordinates.z(), viewport, + target + ) return Vector3f( target.x / mc.window.guiScale.toFloat(), diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/api/util/event/LevelRenderCallback.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/api/util/event/LevelRenderCallback.kt index 897ca50f..ac54dab4 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/api/util/event/LevelRenderCallback.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/api/util/event/LevelRenderCallback.kt @@ -2,15 +2,14 @@ package ru.octol1ttle.flightassistant.api.util.event import dev.architectury.event.Event import dev.architectury.event.EventFactory -import net.minecraft.client.Camera import org.joml.Matrix3f -import org.joml.Matrix4f +import org.joml.Matrix4fc fun interface LevelRenderCallback { /** * Called when the LevelRenderer starts rendering the level */ - fun onStartRenderLevel(partialTick: Float, camera: Camera, projectionMatrix: Matrix4f, frustumMatrix: Matrix3f) + fun onStartRenderLevel(partialTick: Float, cameraXRot: Float, cameraYRot: Float, projectionMatrix: Matrix4fc, frustumMatrix: Matrix3f) companion object { @JvmField From 0f05e661473697c6aadcc97749edea012a36bee3 Mon Sep 17 00:00:00 2001 From: JoshSald <42002468+JoshSald@users.noreply.github.com> Date: Fri, 11 Sep 2026 13:21:28 +0200 Subject: [PATCH 4/5] fix: stabilize flight path pitch/yaw at near-zero velocity flightPitch/flightYaw derive the direction of travel via velocity.normalize(), which amplifies floating-point noise into an essentially random angle once velocity gets close to zero (e.g. standing on the ground, or just after landing). StallComputer feeds that angle straight into its max-pitch protection target, so it would intermittently clamp down to near-zero for no real reason while barely moving. Fall back to the player's actual look-based pitch/yaw below a small velocity threshold, where the travel-direction angle isn't meaningful anyway. --- .../flightassistant/impl/computer/data/AirDataComputer.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/data/AirDataComputer.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/data/AirDataComputer.kt index 4c37920d..f3be75ac 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/data/AirDataComputer.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/data/AirDataComputer.kt @@ -70,10 +70,12 @@ class AirDataComputer(computers: ComputerBus, private val mc: Minecraft) : Compu val heading: Float get() = (yaw + 180.0f).throwIfNotInRange(0.0f..360.0f) + // Normalizing a near-zero velocity amplifies noise into a wildly unstable direction, + // so fall back to the player's actual look direction when barely moving. val flightPitch: Float - get() = degrees(asin(velocity.normalize().y).toFloat()) + get() = if (velocity.lengthSqr() < MIN_VELOCITY_LENGTH_SQR) pitch else degrees(asin(velocity.normalize().y).toFloat()) val flightYaw: Float - get() = degrees(atan2(-velocity.x, velocity.z).toFloat()) + get() = if (velocity.lengthSqr() < MIN_VELOCITY_LENGTH_SQR) yaw else degrees(atan2(-velocity.x, velocity.z).toFloat()) override fun tick() { forwardVelocity = computeForwardVector(velocity) @@ -114,5 +116,6 @@ class AirDataComputer(computers: ComputerBus, private val mc: Minecraft) : Compu companion object { val ID: ResourceLocation = FlightAssistant.id("air_data") + private const val MIN_VELOCITY_LENGTH_SQR: Double = 0.0025 // 0.05 blocks/tick } } \ No newline at end of file From 4001918d7e81147ec09561f6ca347730032eeab3 Mon Sep 17 00:00:00 2001 From: JoshSald <42002468+JoshSald@users.noreply.github.com> Date: Fri, 11 Sep 2026 13:22:19 +0200 Subject: [PATCH 5/5] feat: add Auto Thrust, master AutoPilot, and holding pattern keybinds - toggle_auto_thrust toggles Auto Thrust on its own, addressing half of the long-standing TODO on the old autopilot_disconnect binding - toggle_all_autopilot flips Flight Directors, Auto Thrust, and Autopilot together, mirrored off the current autopilot state - toggle_holding_pattern enters/exits a new HoldingPatternLateralMode: it holds at the flight plan's current target waypoint if one exists (via a newly-public FlightPlanComputer.getEnrouteTarget), otherwise at present position and heading. The mode alternates between two points offset from the fix to trace a racetrack, delegating the actual steering to DirectCoordinatesLateralMode. Also added as a HOLD option in the Autoflight setup screen for manual fix/course entry All three keybinds are unbound by default, same as the existing toggle_enabled binding. --- CHANGELOG.md | 8 +++ .../flightassistant/FAKeyMappings.kt | 41 +++++++++++++++- .../computer/autoflight/FlightPlanComputer.kt | 2 +- .../autoflight/modes/BuiltInLateralModes.kt | 49 +++++++++++++++++++ .../screen/autoflight/AutoFlightScreen.kt | 18 +++++++ .../autoflight/AutoFlightScreenState.kt | 6 +++ .../assets/flightassistant/lang/en_us.yml | 7 +++ 7 files changed, 129 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e8a115d..93f2139a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ Please use [Discord](https://discord.gg/5kcBCvnbTp) or [GitHub](https://github.c **A wiki is now available: https://github.com/Octol1ttle/FlightAssistant/wiki** +## 3.1.0 + +- Added support for Minecraft 26.1 +- Added a keybind to toggle Auto Thrust on its own, independent of the rest of the autopilot +- Added a keybind to toggle Flight Directors, Auto Thrust, and Autopilot together with one press +- Added a holding pattern autopilot mode: enter it with a keybind to hold at the waypoint you're currently flying to (or at your present position if there isn't one), or configure a fix and inbound course manually in the Autoflight setup screen +- Fixed the attitude display's pitch-limit indicators sometimes appearing far more than intended, especially at low speed + ## Changes from Beta 1 - Added Chinese localization diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/FAKeyMappings.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/FAKeyMappings.kt index 562ac824..634b3b8d 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/FAKeyMappings.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/FAKeyMappings.kt @@ -1,11 +1,15 @@ package ru.octol1ttle.flightassistant import com.mojang.blaze3d.platform.InputConstants +import kotlin.math.roundToInt import net.minecraft.client.KeyMapping import org.lwjgl.glfw.GLFW import ru.octol1ttle.flightassistant.FlightAssistant.mc import ru.octol1ttle.flightassistant.api.computer.ComputerBus +import ru.octol1ttle.flightassistant.api.util.pointsToDirection import ru.octol1ttle.flightassistant.config.FAConfig +import ru.octol1ttle.flightassistant.impl.computer.autoflight.modes.HoldingPatternLateralMode +import ru.octol1ttle.flightassistant.impl.computer.autoflight.modes.SelectedAltitudeVerticalMode import ru.octol1ttle.flightassistant.screen.FlightAssistantSetupScreen object FAKeyMappings { @@ -24,6 +28,9 @@ object FAKeyMappings { lateinit var openFlightAssistantSetup: KeyMapping lateinit var autopilotDisconnect: KeyMapping + lateinit var toggleAutoThrust: KeyMapping + lateinit var toggleAllAutopilot: KeyMapping + lateinit var toggleHoldingPattern: KeyMapping lateinit var globalAutomationOverride: KeyMapping lateinit var hideCurrentAlert: KeyMapping @@ -38,7 +45,10 @@ object FAKeyMappings { toggleEnabled = addKeyMapping("toggle_enabled", -1) openFlightAssistantSetup = addKeyMapping("open_flightassistant_setup", GLFW.GLFW_KEY_KP_ENTER) - autopilotDisconnect = addKeyMapping("autopilot_disconnect", GLFW.GLFW_KEY_CAPS_LOCK) // TODO: replace with "Toggle FD", "Toggle A/T", "Toggle AP" + autopilotDisconnect = addKeyMapping("autopilot_disconnect", GLFW.GLFW_KEY_CAPS_LOCK) + toggleAutoThrust = addKeyMapping("toggle_auto_thrust", -1) + toggleAllAutopilot = addKeyMapping("toggle_all_autopilot", -1) + toggleHoldingPattern = addKeyMapping("toggle_holding_pattern", -1) globalAutomationOverride = addKeyMapping("global_automation_override", GLFW.GLFW_KEY_LEFT_ALT) hideCurrentAlert = addKeyMapping("hide_current_alert", GLFW.GLFW_KEY_KP_0) @@ -78,6 +88,35 @@ object FAKeyMappings { } computers.autoflight.setAutoPilot(false, alert = false) } + while (toggleAutoThrust.consumeClick()) { + computers.autoflight.setAutoThrust(!computers.autoflight.autoThrust, alert = false) + } + while (toggleAllAutopilot.consumeClick()) { + val enable: Boolean = !computers.autoflight.autopilot + computers.autoflight.setFlightDirectors(enable) + computers.autoflight.setAutoThrust(enable, alert = false) + computers.autoflight.setAutoPilot(enable, alert = false) + } + while (toggleHoldingPattern.consumeClick()) { + if (computers.autoflight.selectedLateralMode is HoldingPatternLateralMode) { + computers.autoflight.selectedLateralMode = null + computers.autoflight.selectedVerticalMode = null + } else { + // Hold at the waypoint currently being flown to, if there is one; otherwise hold at present position. + val target = computers.plan.getEnrouteTarget() + val fixX: Int = target?.coordinatesX ?: computers.data.x.roundToInt() + val fixZ: Int = target?.coordinatesZ ?: computers.data.z.roundToInt() + val fixAltitude: Int = target?.altitude ?: computers.data.altitude.roundToInt() + val inboundCourse: Float = if (target != null) { + pointsToDirection(fixX.toDouble(), fixZ.toDouble(), computers.data.x, computers.data.z).toFloat() + 180.0f + } else { + computers.data.heading + } + + computers.autoflight.selectedLateralMode = HoldingPatternLateralMode(fixX, fixZ, inboundCourse) + computers.autoflight.selectedVerticalMode = SelectedAltitudeVerticalMode(fixAltitude) + } + } while (hideCurrentAlert.consumeClick()) { computers.alert.hideCurrentAlert() diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/autoflight/FlightPlanComputer.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/autoflight/FlightPlanComputer.kt index ed4efa4e..02b1f104 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/autoflight/FlightPlanComputer.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/autoflight/FlightPlanComputer.kt @@ -120,7 +120,7 @@ class FlightPlanComputer(computers: ComputerBus) : Computer(computers) { return enrouteData.singleOrNull { it.active == EnrouteWaypoint.Active.ORIGIN } } - private fun getEnrouteTarget(): EnrouteWaypoint? { + fun getEnrouteTarget(): EnrouteWaypoint? { return enrouteData.singleOrNull { it.active == EnrouteWaypoint.Active.TARGET } } diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/autoflight/modes/BuiltInLateralModes.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/autoflight/modes/BuiltInLateralModes.kt index 70e0e1ae..c98878f3 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/autoflight/modes/BuiltInLateralModes.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/autoflight/modes/BuiltInLateralModes.kt @@ -1,5 +1,8 @@ package ru.octol1ttle.flightassistant.impl.computer.autoflight.modes +import kotlin.math.cos +import kotlin.math.roundToInt +import kotlin.math.sin import net.minecraft.network.chat.Component import org.joml.Vector2d import ru.octol1ttle.flightassistant.api.autoflight.ControlInput @@ -44,4 +47,50 @@ data class TrackNavigationLateralMode(val originX: Int, val originZ: Int, overri val trackNormalized: Vector2d = track.normalize() return closestTrackPoint.add(trackNormalized.mul(computers.data.velocityPerSecond.horizontalDistance() * 3.0)) } +} + +class HoldingPatternLateralMode( + val fixX: Int, + val fixZ: Int, + val inboundCourse: Float, + val legLength: Double = 300.0, + val turnDirection: TurnDirection = TurnDirection.RIGHT, + override val textOverride: Component? = null +) : AutoFlightComputer.LateralMode, AutoFlightComputer.FollowsCoordinatesMode { + override val targetX: Int get() = fixX + override val targetZ: Int get() = fixZ + + // The far turn point of the racetrack: offset from the fix along the outbound leg, + // then offset sideways so alternating direct-to steering traces an oval instead of a straight line. + private val outboundPoint: Vector2d = run { + val outboundCourse: Double = inboundCourse + 180.0 + val sideCourse: Double = outboundCourse + 90.0 * turnDirection.sign + vec2dFromInts(fixX, fixZ) + .add(directionVector(outboundCourse).mul(legLength)) + .add(directionVector(sideCourse).mul(legLength / 3.0)) + } + + private var flyingOutbound: Boolean = true + + override fun getControlInput(computers: ComputerBus): ControlInput { + val fix: Vector2d = vec2dFromInts(fixX, fixZ) + val target: Vector2d = if (flyingOutbound) outboundPoint else fix + if (target.distance(computers.data.x, computers.data.z) < computers.data.velocityPerSecond.horizontalDistance() * 3.0) { + flyingOutbound = !flyingOutbound + } + + return DirectCoordinatesLateralMode(target.x.roundToInt(), target.y.roundToInt()).getControlInput(computers) + .copy(text = Component.translatable("mode.flightassistant.lateral.holding_pattern")) + } + + enum class TurnDirection(val sign: Double) { + LEFT(-1.0), RIGHT(1.0) + } + + companion object { + private fun directionVector(courseDegrees: Double): Vector2d { + val radians: Double = Math.toRadians(courseDegrees) + return Vector2d(-sin(radians), cos(radians)) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/autoflight/AutoFlightScreen.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/autoflight/AutoFlightScreen.kt index 4f9e7cda..d997aa0f 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/autoflight/AutoFlightScreen.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/autoflight/AutoFlightScreen.kt @@ -144,6 +144,24 @@ class AutoFlightScreen(parent: Screen) : FABaseScreen(parent, Component.translat refreshableElements.add(this.addRenderableWidget(TypeStrictEditBox(xEditBox.x + xEditBox.width + 4, xEditBox.y, xEditBox.width, xEditBox.height, state.targetCoordinatesZ, { state.targetCoordinatesZ = it }, String::toIntOrNullWithFallback))) } + AutoFlightScreenState.LateralMode.HOLD -> { + val string: SmartStringWidget = this.addRenderableWidget(SmartStringWidget(baseX, baseY, Component.translatable("menu.flightassistant.autoflight.target.hold_fix"))) + refreshableElements.add(string) + val xEditBox: TypeStrictEditBox = this.addRenderableWidget(TypeStrictEditBox(string.x + string.width, string.y - 2, baseWidth + 4, baseHeight, state.holdFixX, { state.holdFixX = it }, String::toIntOrNullWithFallback)) + refreshableElements.add(xEditBox) + refreshableElements.add(this.addRenderableWidget(TypeStrictEditBox(xEditBox.x + xEditBox.width + 4, xEditBox.y, xEditBox.width, xEditBox.height, state.holdFixZ, { state.holdFixZ = it }, String::toIntOrNullWithFallback))) + + val courseString: SmartStringWidget = this.addRenderableWidget(SmartStringWidget(baseX, baseY + 16, Component.translatable("menu.flightassistant.autoflight.target.hold_course"))) + refreshableElements.add(courseString) + refreshableElements.add( + this.addRenderableWidget( + TypeStrictEditBox( + courseString.x + courseString.width, courseString.y - 2, baseWidth, baseHeight, state.holdInboundCourse, { state.holdInboundCourse = it }, + String::toIntOrNullWithFallback, { it in 0..360 }) + ) + ) + } + AutoFlightScreenState.LateralMode.FLIGHT_PLAN -> Unit } } diff --git a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/autoflight/AutoFlightScreenState.kt b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/autoflight/AutoFlightScreenState.kt index b1d7641f..942542e5 100644 --- a/src/main/kotlin/ru/octol1ttle/flightassistant/screen/autoflight/AutoFlightScreenState.kt +++ b/src/main/kotlin/ru/octol1ttle/flightassistant/screen/autoflight/AutoFlightScreenState.kt @@ -5,6 +5,7 @@ import net.minecraft.network.chat.Component import ru.octol1ttle.flightassistant.impl.computer.autoflight.AutoFlightComputer import ru.octol1ttle.flightassistant.impl.computer.autoflight.modes.DirectCoordinatesLateralMode import ru.octol1ttle.flightassistant.impl.computer.autoflight.modes.HeadingLateralMode +import ru.octol1ttle.flightassistant.impl.computer.autoflight.modes.HoldingPatternLateralMode import ru.octol1ttle.flightassistant.impl.computer.autoflight.modes.PitchVerticalMode import ru.octol1ttle.flightassistant.impl.computer.autoflight.modes.SelectedAltitudeVerticalMode import ru.octol1ttle.flightassistant.impl.computer.autoflight.modes.SpeedThrustMode @@ -24,6 +25,9 @@ class AutoFlightScreenState { var targetHeading: Int = 360 var targetCoordinatesX: Int = 0 var targetCoordinatesZ: Int = 0 + var holdFixX: Int = 0 + var holdFixZ: Int = 0 + var holdInboundCourse: Int = 360 fun apply(autoFlight: AutoFlightComputer) { autoFlight.selectedThrustMode = when (thrustMode) { @@ -39,6 +43,7 @@ class AutoFlightScreenState { autoFlight.selectedLateralMode = when (lateralMode) { LateralMode.HEADING -> HeadingLateralMode(targetHeading) LateralMode.COORDINATES -> DirectCoordinatesLateralMode(targetCoordinatesX, targetCoordinatesZ) + LateralMode.HOLD -> HoldingPatternLateralMode(holdFixX, holdFixZ, holdInboundCourse.toFloat()) LateralMode.FLIGHT_PLAN -> null } } @@ -66,6 +71,7 @@ class AutoFlightScreenState { enum class LateralMode(@JvmField val displayName: Component) : NameableEnum { HEADING(Component.translatable("menu.flightassistant.autoflight.lateral.heading")), COORDINATES(Component.translatable("menu.flightassistant.autoflight.lateral.coordinates")), + HOLD(Component.translatable("menu.flightassistant.autoflight.lateral.hold")), FLIGHT_PLAN(Component.translatable("menu.flightassistant.autoflight.lateral.flight_plan")); override fun getDisplayName(): Component { diff --git a/src/main/resources/assets/flightassistant/lang/en_us.yml b/src/main/resources/assets/flightassistant/lang/en_us.yml index 3e241724..ec3a67ed 100644 --- a/src/main/resources/assets/flightassistant/lang/en_us.yml +++ b/src/main/resources/assets/flightassistant/lang/en_us.yml @@ -291,6 +291,7 @@ menu.flightassistant: mode: 'Lateral mode: ' heading: HDG coordinates: COORDS + hold: HOLD flight_plan: F-PLAN target: speed: 'Target speed: ' @@ -300,6 +301,8 @@ menu.flightassistant: altitude: 'Target altitude: ' heading: 'Target heading: ' coordinates: 'Target coordinates: ' + hold_fix: 'Hold fix coordinates: ' + hold_course: 'Hold inbound course: ' fms: .: Flight planning departure: @@ -372,6 +375,7 @@ mode.flightassistant: heading: HDG direct_coordinates: DIR COORDS track_navigation: TRK NAV + holding_pattern: HOLD localizer: LOC go_around: GA HDG @@ -428,6 +432,9 @@ key.category.flightassistant: toggle_enabled: Toggle mod enabled open_flightassistant_setup: Open FlightAssistant setup screen autopilot_disconnect: Disconnect autopilot + toggle_auto_thrust: Toggle Auto Thrust + toggle_all_autopilot: Toggle all autopilot functionality + toggle_holding_pattern: Enter/exit holding pattern global_automation_override: Global automation override (hold) hide_current_alert: Hide currently active alert show_hidden_alert: Show last hidden alert