Skip to content

Commit df647f4

Browse files
committed
ground proximity warning system: fix thresholds being broken, consider spaciousness around player
Signed-off-by: Octol1ttle <[email protected]>
1 parent 7a0c78e commit df647f4

File tree

6 files changed

+100
-67
lines changed

6 files changed

+100
-67
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,7 @@ class FireworkComputer(computers: ComputerBus, private val mc: Minecraft) : Comp
116116

117117
private fun anyTerrainAhead(): Boolean {
118118
val velocity = computers.data.player.forward.scale(FIREWORK_SPEED.toDouble())
119-
val end = computers.data.position.add(velocity.scale(computers.gpws.cautionThreshold))
120-
return computers.gpws.computeObstacleImpactTime(end, velocity) <= computers.gpws.warningThreshold
119+
return computers.gpws.computeObstacleImpactTime(velocity, computers.gpws.cautionThreshold) <= computers.gpws.warningThreshold
121120
}
122121

123122
private fun tryActivateFirework(player: Player) {

src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/data/AirDataComputer.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ru.octol1ttle.flightassistant.impl.computer.data
22

33
import kotlin.math.asin
4+
import kotlin.math.atan2
45
import kotlin.math.max
56
import net.minecraft.client.Minecraft
67
import net.minecraft.client.multiplayer.ClientLevel
@@ -70,6 +71,8 @@ class AirDataComputer(computers: ComputerBus, private val mc: Minecraft) : Compu
7071

7172
val flightPitch: Float
7273
get() = degrees(asin(velocity.normalize().y).toFloat())
74+
val flightYaw: Float
75+
get() = degrees(atan2(-velocity.x, velocity.z).toFloat())
7376

7477
override fun tick() {
7578
forwardVelocity = computeForwardVector(velocity)

src/main/kotlin/ru/octol1ttle/flightassistant/impl/computer/safety/GroundProximityComputer.kt

Lines changed: 80 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ru.octol1ttle.flightassistant.impl.computer.safety
22

3+
import kotlin.math.abs
34
import kotlin.math.max
45
import net.minecraft.network.chat.Component
56
import net.minecraft.resources.ResourceLocation
@@ -22,7 +23,6 @@ import ru.octol1ttle.flightassistant.api.util.inverseMin
2223
import ru.octol1ttle.flightassistant.api.util.throwIfNotInRange
2324
import ru.octol1ttle.flightassistant.config.FAConfig
2425
import ru.octol1ttle.flightassistant.impl.computer.autoflight.base.PitchComputer
25-
import ru.octol1ttle.flightassistant.impl.computer.data.AirDataComputer
2626

2727
class GroundProximityComputer(computers: ComputerBus) : Computer(computers), FlightController {
2828
private var groundImpactTime: Double = Double.MAX_VALUE
@@ -34,13 +34,13 @@ class GroundProximityComputer(computers: ComputerBus) : Computer(computers), Fli
3434

3535
private var anyBlocksAbove = false
3636
val safeThreshold: Double
37-
get() = if (anyBlocksAbove) 7.5 else 10.0
37+
get() = if (anyBlocksAbove) 5.0 else 10.0
3838
val cautionThreshold: Double
39-
get() = if (anyBlocksAbove) 7.5 else 10.0
39+
get() = if (anyBlocksAbove) 3.0 else 7.5
4040
val warningThreshold: Double
41-
get() = if (anyBlocksAbove) 7.5 else 10.0
41+
get() = if (anyBlocksAbove) 1.5 else 3.0
4242
val recoverThreshold: Double
43-
get() = if (anyBlocksAbove) 7.5 else 10.0
43+
get() = 0.75
4444

4545
var groundY: Double? = null
4646
val groundOrVoidY: Double
@@ -57,48 +57,32 @@ class GroundProximityComputer(computers: ComputerBus) : Computer(computers), Fli
5757
groundY = computeGroundY()?.throwIfNotInRange(computers.data.level.bottomY.toDouble()..Double.MAX_VALUE)
5858
}
5959

60-
val data: AirDataComputer = computers.data
61-
if (!data.flying || data.player.isInWater) {
60+
if (!computers.data.flying || computers.data.player.isInWater) {
6261
groundImpactStatus = Status.SAFE
6362
obstacleImpactStatus = Status.SAFE
6463
return
6564
}
6665

67-
anyBlocksAbove = data.level.getHeight(Heightmap.Types.MOTION_BLOCKING, data.player.blockX, data.player.blockZ) > data.player.y
68-
69-
groundImpactTime = computeGroundImpactTime(data).throwIfNotInRange(0.0..Double.MAX_VALUE)
70-
groundImpactStatus =
71-
if (data.fallDistanceSafe) {
72-
Status.SAFE
73-
} else if (groundImpactStatus == Status.SAFE && (data.velocityPerSecond.y > -10 || groundImpactTime > cautionThreshold)) {
74-
Status.SAFE
75-
} else if (data.velocityPerSecond.y > -8.5 || groundImpactTime > safeThreshold) {
76-
Status.SAFE
77-
} else if (groundImpactStatus >= Status.CAUTION && groundImpactTime > warningThreshold) {
78-
Status.CAUTION
79-
} else if (groundImpactStatus >= Status.WARNING && groundImpactTime > recoverThreshold) {
80-
Status.WARNING
81-
} else {
82-
Status.RECOVER
83-
}
66+
anyBlocksAbove = computers.data.level.getHeight(Heightmap.Types.MOTION_BLOCKING, computers.data.player.blockX, computers.data.player.blockZ) > computers.data.player.y
8467

85-
obstacleImpactTime = computeObstacleImpactTime(data, safeThreshold).throwIfNotInRange(0.0..Double.MAX_VALUE)
86-
87-
val damageOnCollision: Double = data.velocity.horizontalDistance() * 10 - 3
88-
obstacleImpactStatus =
89-
if (data.isInvulnerableTo(data.player.damageSources().flyIntoWall())) {
90-
Status.SAFE
91-
} else if (obstacleImpactStatus == Status.SAFE && (damageOnCollision < data.player.health * 0.5f || obstacleImpactTime > groundImpactTime * 1.1f || obstacleImpactTime > cautionThreshold)) {
92-
Status.SAFE
93-
} else if (damageOnCollision < data.player.health * 0.25f || obstacleImpactTime > groundImpactTime * 1.2f || obstacleImpactTime > safeThreshold) {
94-
Status.SAFE
95-
} else if (obstacleImpactStatus >= Status.CAUTION && obstacleImpactTime > warningThreshold) {
96-
Status.CAUTION
97-
} else if (obstacleImpactStatus >= Status.WARNING && obstacleImpactTime > recoverThreshold) {
98-
Status.WARNING
99-
} else {
100-
Status.RECOVER
101-
}
68+
groundImpactTime = computeGroundImpactTime().throwIfNotInRange(0.0..Double.MAX_VALUE)
69+
groundImpactStatus = computeStatus(groundImpactStatus,
70+
{ computers.data.fallDistanceSafe || computers.data.velocityPerSecond.y > -8.5 || groundImpactTime >= safeThreshold },
71+
{ computers.data.velocityPerSecond.y <= -10 && groundImpactTime <= cautionThreshold },
72+
{ groundImpactTime <= warningThreshold },
73+
{ groundImpactTime <= recoverThreshold }
74+
)
75+
76+
obstacleImpactTime = computeObstacleImpactTime(computers.data.velocityPerSecond, safeThreshold).throwIfNotInRange(0.0..Double.MAX_VALUE)
77+
val thresholdMultiplier = computeThresholdMultiplier()
78+
val damageOnCollision: Double = computers.data.velocity.horizontalDistance() * 10 - 3
79+
val invulnerable = computers.data.isInvulnerableTo(computers.data.player.damageSources().flyIntoWall())
80+
obstacleImpactStatus = computeStatus(obstacleImpactStatus,
81+
{ invulnerable || damageOnCollision < computers.data.player.health * 0.25f || obstacleImpactTime > groundImpactTime * 1.2f || obstacleImpactTime >= safeThreshold * thresholdMultiplier },
82+
{ abs(computers.data.yaw - computers.data.flightYaw) < 10.0f && damageOnCollision >= computers.data.player.health * 0.5f && obstacleImpactTime < groundImpactTime * 1.1f && obstacleImpactTime <= cautionThreshold * thresholdMultiplier },
83+
{ obstacleImpactTime <= warningThreshold * thresholdMultiplier },
84+
{ obstacleImpactTime <= recoverThreshold },
85+
)
10286
}
10387

10488
private fun computeGroundY(): Double? {
@@ -117,23 +101,69 @@ class GroundProximityComputer(computers: ComputerBus) : Computer(computers), Fli
117101
return groundY
118102
}
119103

120-
private fun computeGroundImpactTime(data: AirDataComputer): Double {
121-
if (data.velocity.y >= 0.0) {
122-
return Double.MAX_VALUE
104+
private fun computeThresholdMultiplier(): Double {
105+
val maxRaycastDistance = computers.data.forwardVelocityPerSecond.length() * safeThreshold
106+
107+
val xzOffsets = listOf(-1.0, 0.0, 1.0)
108+
val yOffsets = listOf(-1.0, 0.0, 1.0)
109+
110+
val distances = ArrayList<Double>()
111+
for (x in xzOffsets) { for (y in yOffsets) { for (z in xzOffsets) {
112+
val offset = Vec3(x, y, z).normalize().scale(maxRaycastDistance)
113+
if (offset.lengthSqr() == 0.0) {
114+
continue
115+
}
116+
val raycast: BlockHitResult = computers.data.level.clip(
117+
ClipContext(
118+
computers.data.position,
119+
computers.data.position.add(offset),
120+
ClipContext.Block.COLLIDER,
121+
ClipContext.Fluid.ANY,
122+
computers.data.player
123+
)
124+
)
125+
if (raycast.type == HitResult.Type.BLOCK) {
126+
distances.add(computers.data.position.distanceTo(raycast.location))
127+
}
128+
}}}
129+
130+
return distances.average() / maxRaycastDistance
131+
}
132+
133+
private fun computeStatus(current: Status, safe: () -> Boolean, caution: () -> Boolean, warning: () -> Boolean, recover: () -> Boolean): Status {
134+
if (safe()) {
135+
return Status.SAFE
136+
}
137+
138+
var status: Status = current
139+
while (true) {
140+
val next = when (status) {
141+
Status.SAFE -> if (caution()) Status.CAUTION else null
142+
Status.CAUTION -> if (caution() && warning()) Status.WARNING else null
143+
Status.WARNING -> if (caution() && warning() && recover()) Status.RECOVER else null
144+
Status.RECOVER -> null
145+
}
146+
if (next == null) {
147+
break
148+
}
149+
status = next
123150
}
124-
return max(0.0, data.altitude - groundOrVoidY) / -data.velocityPerSecond.y
151+
152+
return status
125153
}
126154

127-
private fun computeObstacleImpactTime(data: AirDataComputer, lookAheadTime: Double): Double {
128-
val end: Vec3 = data.position.add(data.forwardVelocityPerSecond.multiply(lookAheadTime, 0.0, lookAheadTime))
129-
return computeObstacleImpactTime(end, computers.data.forwardVelocityPerSecond)
155+
private fun computeGroundImpactTime(): Double {
156+
if (computers.data.velocity.y >= 0.0) {
157+
return Double.MAX_VALUE
158+
}
159+
return max(0.0, computers.data.altitude - groundOrVoidY) / -computers.data.velocityPerSecond.y
130160
}
131161

132-
fun computeObstacleImpactTime(raycastEnd: Vec3, velocity: Vec3): Double {
162+
fun computeObstacleImpactTime(velocity: Vec3, lookAheadTime: Double): Double {
133163
val result: BlockHitResult = computers.data.level.clip(
134164
ClipContext(
135165
computers.data.position,
136-
raycastEnd,
166+
computers.data.position.add(velocity.scale(lookAheadTime)),
137167
ClipContext.Block.COLLIDER,
138168
ClipContext.Fluid.ANY,
139169
computers.data.player
@@ -205,7 +235,6 @@ class GroundProximityComputer(computers: ComputerBus) : Computer(computers), Fli
205235
groundImpactStatus = Status.SAFE
206236
obstacleImpactTime = Double.MAX_VALUE
207237
obstacleImpactStatus = Status.SAFE
208-
anyBlocksAbove = false
209238
groundY = null
210239
}
211240

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ class AutomationModesDisplay(computers: ComputerBus) : Display(computers) {
2727
}
2828

2929
override fun render(guiGraphics: GuiGraphics) {
30-
if (FAKeyMappings.globalAutomationOverride.isDown) {
31-
return
32-
}
3330
renderThrustMode(guiGraphics)
3431
renderPitchMode(guiGraphics)
3532
renderInput(guiGraphics, headingDisplay, computers.heading.activeInput)
@@ -40,15 +37,14 @@ class AutomationModesDisplay(computers: ComputerBus) : Display(computers) {
4037
val thrustUnusable: Boolean = computers.thrust.noThrustSource || computers.thrust.reverseUnsupported
4138

4239
val input: ControlInput? = computers.thrust.activeInput
40+
if ((input != null && FAKeyMappings.isHoldingThrust()) || FAKeyMappings.globalAutomationOverride.isDown) {
41+
thrustDisplay.render(guiGraphics, Component.translatable("mode.flightassistant.thrust.override").setColor(cautionColor), ControlInput.Status.ACTIVE, cautionColor)
42+
return
43+
}
44+
4345
if (input != null) {
44-
if (FAKeyMappings.isHoldingThrust()) {
45-
thrustDisplay.render(guiGraphics, Component.translatable("mode.flightassistant.thrust.override").setColor(cautionColor), ControlInput.Status.ACTIVE, cautionColor)
46-
} else {
47-
thrustDisplay.render(
48-
guiGraphics, input.text, input.status,
49-
if (thrustUnusable || input.status == ControlInput.Status.ACTIVE && input.priority < ControlInput.Priority.NORMAL) cautionColor else null
50-
)
51-
}
46+
thrustDisplay.render(guiGraphics, input.text, input.status,
47+
if (thrustUnusable || input.status == ControlInput.Status.ACTIVE && input.priority < ControlInput.Priority.NORMAL) cautionColor else null)
5248
return
5349
}
5450

@@ -79,6 +75,10 @@ class AutomationModesDisplay(computers: ComputerBus) : Display(computers) {
7975
}
8076

8177
private fun renderPitchMode(guiGraphics: GuiGraphics) {
78+
if (FAKeyMappings.globalAutomationOverride.isDown) {
79+
pitchDisplay.render(guiGraphics, Component.translatable("mode.flightassistant.vertical.override").setColor(cautionColor), ControlInput.Status.ACTIVE, cautionColor)
80+
return
81+
}
8282
renderInput(guiGraphics, pitchDisplay, computers.pitch.activeInput)
8383
}
8484

@@ -108,8 +108,8 @@ class AutomationModesDisplay(computers: ComputerBus) : Display(computers) {
108108
else null
109109
automationStatusDisplay.render(
110110
guiGraphics,
111-
if (text.siblings.isNotEmpty()) text else null, ControlInput.Status.ACTIVE,
112-
if (FATickCounter.totalTicks % 20 >= 10) color else null
111+
if (text.siblings.isNotEmpty() || color != null) text else null, ControlInput.Status.ACTIVE,
112+
if (FATickCounter.totalTicks % 20 >= 10 || color == null) color else emptyColor
113113
)
114114
}
115115

src/main/resources/assets/flightassistant/lang/en_us.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ menu.flightassistant:
332332

333333
mode.flightassistant:
334334
thrust:
335+
override: THR OVRD
335336
manual:
336337
.: MAN THR
337338
reverse: MAN REV
@@ -346,6 +347,7 @@ mode.flightassistant:
346347
takeoff: THR TO # TODO: fix confusion with TOGA somehow
347348
landing: THR LAND
348349
vertical:
350+
override: PITCH OVRD
349351
void_protection: VOID PROT
350352
void_escape: VOID ESC
351353
stall_protection: STALL PROT

src/main/resources/assets/flightassistant/lang/ru_ru.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ misc.flightassistant:
423423
key.flightassistant:
424424
.: FlightAssistant
425425
toggle_enabled: Включить/выключить мод
426-
open_flight_setup: Открыть экран настройки полёта
426+
open_flightassistant_setup: Открыть экран настройки FlightAssistant
427427
autopilot_disconnect: Отключить автопилот
428428
global_automation_override: Принудительное отключение автоматики (зажать)
429429
hide_current_alert: Скрыть активное предупреждение

0 commit comments

Comments
 (0)