Skip to content

Commit

Permalink
Chunk status alert (#11)
Browse files Browse the repository at this point in the history
This PR add some feature for chunk status, as follow.

Indicate if current chunk is unloaded, use last ground height as
reference.

Player will receive an alert when they fly into unloaded chunk after a
certain amount of time. After 3.2 seconds, alert will be shown and will
pitch up by ~10 degrees if enabled.

Screenshot
![image](https://i.imgur.com/epW1ao8.png)
  • Loading branch information
zomabies authored Mar 21, 2024
1 parent 2845d86 commit b8a20e3
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,13 @@ private ConfigCategory computers(Text name, ComputerConfig config, ComputerConfi
.controller(TickBoxControllerBuilder::create)
.build())

.option(LabelOption.create(Text.translatable("config.flightassistant.computers.chunk_state")))
.option(Option.<ComputerConfig.ProtectionMode>createBuilder()
.name(Text.translatable("config.flightassistant.computers.chunk_state.protection"))
.binding(defaults.unloadedChunkProtection, () -> config.unloadedChunkProtection, o -> config.unloadedChunkProtection = o)
.controller(opt -> EnumControllerBuilder.create(opt).enumClass(ComputerConfig.ProtectionMode.class))
.build())

.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package ru.octol1ttle.flightassistant.alerts.nav;

import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawContext;
import net.minecraft.text.Text;
import org.jetbrains.annotations.NotNull;
import ru.octol1ttle.flightassistant.HudComponent;
import ru.octol1ttle.flightassistant.alerts.AlertSoundData;
import ru.octol1ttle.flightassistant.alerts.BaseAlert;
import ru.octol1ttle.flightassistant.alerts.IECAMAlert;
import ru.octol1ttle.flightassistant.computers.safety.ChunkStatusComputer;
import ru.octol1ttle.flightassistant.config.FAConfig;

public class UnloadedChunkAlert extends BaseAlert implements IECAMAlert {
private final ChunkStatusComputer chunkStatus;

public UnloadedChunkAlert(ChunkStatusComputer chunkStatus) {
this.chunkStatus = chunkStatus;
}

@Override
public boolean isTriggered() {
return chunkStatus.isInWarning();
}

@Override
public int render(TextRenderer textRenderer, DrawContext context, int x, int y, boolean highlight) {
return HudComponent.drawHighlightedText(textRenderer, context, Text.translatable("alerts.flightassistant.unloaded_chunk"), x, y,
FAConfig.indicator().warningColor, highlight);
}

@Override
public @NotNull AlertSoundData getSoundData() {
return AlertSoundData.MASTER_WARNING;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class AirDataComputer implements ITickableComputer {
public float flightYaw;
public int groundLevel;
public Float elytraHealth;
public boolean isCurrentChunkLoaded;

public AirDataComputer(MinecraftClient mc) {
this.mc = mc;
Expand All @@ -39,6 +40,7 @@ public AirDataComputer(MinecraftClient mc) {
public void tick() {
velocity = player().getVelocity().multiply(TICKS_PER_SECOND);
roll = computeRoll(RenderSystem.getInverseViewRotationMatrix().invert());
isCurrentChunkLoaded = isCurrentChunkLoaded();
groundLevel = computeGroundLevel();
flightPitch = computeFlightPitch(velocity, pitch());
flightYaw = computeFlightYaw(velocity, yaw());
Expand Down Expand Up @@ -91,6 +93,9 @@ private Float computeElytraHealth() {
}

private int computeGroundLevel() {
if (!isCurrentChunkLoaded) {
return groundLevel; // last known cache
}
BlockPos ground = findGround(player().getBlockPos().mutableCopy());
return ground == null ? voidLevel() : ground.getY();
}
Expand All @@ -101,7 +106,7 @@ public boolean isGround(BlockPos pos) {
}

public BlockPos findGround(BlockPos.Mutable from) {
if (!world().getChunkManager().isChunkLoaded(ChunkSectionPos.getSectionCoord(from.getX()), ChunkSectionPos.getSectionCoord(from.getZ()))) {
if (!isChunkLoadedAt(from)) {
return null;
}
int start = from.getY();
Expand Down Expand Up @@ -159,7 +164,7 @@ public float flightHeading() {

public float heightAboveGround() {
float height = Math.max(0.0f, altitude() - groundLevel);
if (height < 1.0f) {
if (height < 1.0f && isCurrentChunkLoaded) {
throw new AssertionError(height);
}
return height;
Expand All @@ -177,6 +182,15 @@ public World world() {
return player().getWorld();
}

public boolean isChunkLoadedAt(BlockPos pos){
return world().getChunkManager().isChunkLoaded(ChunkSectionPos.getSectionCoord(pos.getX()), ChunkSectionPos.getSectionCoord(pos.getZ()));
}

private boolean isCurrentChunkLoaded(){
BlockPos pos = player().getBlockPos();
return isChunkLoadedAt(pos);
}

public static float validate(float f, float bounds) {
return validate(f, -bounds, bounds);
}
Expand All @@ -202,5 +216,6 @@ public void reset() {
roll = 0.0f;
groundLevel = 0;
elytraHealth = null;
isCurrentChunkLoaded = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import ru.octol1ttle.flightassistant.computers.autoflight.YawController;
import ru.octol1ttle.flightassistant.computers.navigation.FlightPlanner;
import ru.octol1ttle.flightassistant.computers.safety.AlertController;
import ru.octol1ttle.flightassistant.computers.safety.ChunkStatusComputer;
import ru.octol1ttle.flightassistant.computers.safety.ElytraStateController;
import ru.octol1ttle.flightassistant.computers.safety.GPWSComputer;
import ru.octol1ttle.flightassistant.computers.safety.StallComputer;
Expand All @@ -21,6 +22,7 @@
public class ComputerHost {
public final AirDataComputer data;
public final StallComputer stall;
public final ChunkStatusComputer chunkStatus;
public final GPWSComputer gpws;
public final VoidLevelComputer voidLevel;
public final FireworkController firework;
Expand All @@ -38,22 +40,23 @@ public ComputerHost(@NotNull MinecraftClient mc, HudRenderer renderer) {
this.data = new AirDataComputer(mc);
this.time = new TimeComputer();
this.firework = new FireworkController(mc, data, time);
this.chunkStatus = new ChunkStatusComputer(mc, data, time);
this.stall = new StallComputer(firework, data);
this.voidLevel = new VoidLevelComputer(data, firework, stall);
this.plan = new FlightPlanner(data);
this.gpws = new GPWSComputer(data, plan);
this.elytra = new ElytraStateController(data);

this.yaw = new YawController(time, data);
this.pitch = new PitchController(data, stall, time, voidLevel, gpws);
this.pitch = new PitchController(data, stall, time, voidLevel, gpws, chunkStatus);

this.autoflight = new AutoFlightComputer(data, gpws, plan, firework, pitch, yaw);

this.alert = new AlertController(this, mc.getSoundManager(), renderer);

// computers are sorted in the order they should be ticked to avoid errors
this.tickables = new ArrayList<>(List.of(
data, time, stall, gpws, voidLevel, elytra, plan, autoflight, firework, alert, pitch, yaw
data, time, stall, chunkStatus, gpws, voidLevel, elytra, plan, autoflight, firework, alert, pitch, yaw
));
Collections.reverse(this.tickables); // we tick computers in reverse, so reverse the collections so that the order is correct

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ru.octol1ttle.flightassistant.computers.AirDataComputer;
import ru.octol1ttle.flightassistant.computers.ITickableComputer;
import ru.octol1ttle.flightassistant.computers.TimeComputer;
import ru.octol1ttle.flightassistant.computers.safety.ChunkStatusComputer;
import ru.octol1ttle.flightassistant.computers.safety.GPWSComputer;
import ru.octol1ttle.flightassistant.computers.safety.StallComputer;
import ru.octol1ttle.flightassistant.computers.safety.VoidLevelComputer;
Expand All @@ -19,14 +20,16 @@ public class PitchController implements ITickableComputer {
private final TimeComputer time;
private final VoidLevelComputer voidLevel;
private final GPWSComputer gpws;
private final ChunkStatusComputer chunkStatus;
public Float targetPitch = null;

public PitchController(AirDataComputer data, StallComputer stall, TimeComputer time, VoidLevelComputer voidLevel, GPWSComputer gpws) {
public PitchController(AirDataComputer data, StallComputer stall, TimeComputer time, VoidLevelComputer voidLevel, GPWSComputer gpws, ChunkStatusComputer chunkStatus) {
this.data = data;
this.stall = stall;
this.time = time;
this.voidLevel = voidLevel;
this.gpws = gpws;
this.chunkStatus = chunkStatus;
}

@Override
Expand All @@ -45,6 +48,8 @@ public void tick() {
smoothSetPitch(90.0f, MathHelper.clamp(time.deltaTime / gpws.descentImpactTime, 0.001f, 1.0f));
} else if (gpws.shouldCorrectTerrain()) {
smoothSetPitch(FAMathHelper.toDegrees(MathHelper.atan2(gpws.terrainAvoidVector.y, gpws.terrainAvoidVector.x)), time.deltaTime);
} else if (chunkStatus.shouldCorrectTerrain()) {
smoothSetPitch(VoidLevelComputer.OPTIMUM_ALTITUDE_PRESERVATION_PITCH, time.deltaTime);
} else {
smoothSetPitch(targetPitch, time.deltaTime);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import ru.octol1ttle.flightassistant.alerts.nav.MinimumsAlert;
import ru.octol1ttle.flightassistant.alerts.nav.gpws.ExcessiveDescentAlert;
import ru.octol1ttle.flightassistant.alerts.nav.gpws.ExcessiveTerrainClosureAlert;
import ru.octol1ttle.flightassistant.alerts.nav.UnloadedChunkAlert;
import ru.octol1ttle.flightassistant.alerts.nav.gpws.UnsafeTerrainClearanceAlert;
import ru.octol1ttle.flightassistant.alerts.other.ElytraHealthLowAlert;
import ru.octol1ttle.flightassistant.alerts.other.StallAlert;
Expand All @@ -37,6 +38,7 @@ public AlertController(ComputerHost host, SoundManager manager, HudRenderer rend
// TODO: ECAM actions
allAlerts = List.of(
new StallAlert(host.stall),
new UnloadedChunkAlert(host.chunkStatus),
new ExcessiveDescentAlert(host.data, host.gpws), new ExcessiveTerrainClosureAlert(host.gpws, host.time),
new UnsafeTerrainClearanceAlert(host.gpws, host.plan),
new AutopilotOffAlert(host.autoflight), new AutoFireworkOffAlert(host.autoflight),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package ru.octol1ttle.flightassistant.computers.safety;

import net.minecraft.client.MinecraftClient;
import ru.octol1ttle.flightassistant.computers.AirDataComputer;
import ru.octol1ttle.flightassistant.computers.ITickableComputer;
import ru.octol1ttle.flightassistant.computers.TimeComputer;
import ru.octol1ttle.flightassistant.config.FAConfig;

public class ChunkStatusComputer implements ITickableComputer {
private final MinecraftClient mc;
private final AirDataComputer data;
private final TimeComputer time;

private boolean isInWarning;
private float lastEncounteredMS = 0f;
private float lastDiffMS = 0f;
private float offsetMS = 0f; // for single player pause

// milliseconds difference
private static final float WARN_THRESHOLD = 3200f;

public ChunkStatusComputer(MinecraftClient mc, AirDataComputer data, TimeComputer time) {
this.mc = mc;
this.data = data;
this.time = time;
}

@Override
public void tick() {
if (!data.isFlying()) {
reset();
return;
}

if (data.isCurrentChunkLoaded) {
if (time.prevMillis != null) {
lastEncounteredMS = time.prevMillis;
}
offsetMS = 0f;
}

final boolean isSinglePlayerPause = (mc.isInSingleplayer() && mc.isPaused());
if (isSinglePlayerPause && !data.isCurrentChunkLoaded) {
offsetMS = ((time.prevMillis - lastEncounteredMS) - lastDiffMS);
}

if (time.prevMillis != null && lastEncounteredMS > 0f) {
lastDiffMS = (time.prevMillis - offsetMS) - lastEncounteredMS;
}

isInWarning = shouldWarn();
}

public boolean shouldCorrectTerrain() {
return FAConfig.computer().unloadedChunkProtection.recover() && isInWarning();
}

public boolean isInWarning() {
return isInWarning;
}

public float getLastDiffMS() {
return lastDiffMS;
}

private boolean shouldWarn() {
if (data.isFlying() && !data.isCurrentChunkLoaded) {
return lastDiffMS >= WARN_THRESHOLD;
}
return false;
}

@Override
public String getId() {
return "chunk_state";
}

@Override
public void reset() {
isInWarning = false;
lastDiffMS = 0f;
lastEncounteredMS = 0f;
offsetMS = 0f;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import ru.octol1ttle.flightassistant.config.FAConfig;

public class VoidLevelComputer implements ITickableComputer {
private static final int OPTIMUM_ALTITUDE_PRESERVATION_PITCH = 15;
public static final float OPTIMUM_ALTITUDE_PRESERVATION_PITCH = 15f;
private final AirDataComputer data;
private final FireworkController firework;
private final StallComputer stall;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public class ComputerConfig {
@SerialEntry
public boolean openElytraAutomatically = true;

@SerialEntry
public ProtectionMode unloadedChunkProtection = ProtectionMode.HARD;

public enum GlobalAutomationsMode implements NameableEnum {
FULL,
// TODO: LIMIT TO NO_OVERLAYS ON SERVERS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,13 @@ public void render(DrawContext context, TextRenderer textRenderer) {

if (FAConfig.indicator().showGroundAltitude) {
Color color = data.altitude() < safeLevel ? FAConfig.indicator().warningColor : FAConfig.indicator().frameColor;
drawText(textRenderer, context, Text.translatable(data.groundLevel == data.voidLevel() ? "flightassistant.void_level" : "flightassistant.ground_level"), xAltText - 10, bottom, color);
drawText(textRenderer, context, asText("%d", MathHelper.floor(data.heightAboveGround())), xAltText, bottom, color);
drawBorder(context, xAltText - 2, bottom - 2, 28, color);
if (!data.isCurrentChunkLoaded) {
drawText(textRenderer, context, Text.translatable("flightassistant.altitude_short"), xAltText, bottom, FAConfig.indicator().warningColor);
} else {
drawText(textRenderer, context, Text.translatable(data.groundLevel == data.voidLevel() ? "flightassistant.void_level" : "flightassistant.ground_level"), xAltText - 10, bottom, color);
drawText(textRenderer, context, asText("%d", MathHelper.floor(data.heightAboveGround())), xAltText, bottom, color);
drawBorder(context, xAltText - 2, bottom - 2, 28, color);
}
}

if (FAConfig.indicator().showAltitudeScale) {
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/assets/flightassistant/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
"config.flightassistant.computers.elytra_state": "Elytra state",
"config.flightassistant.computers.elytra_state.close_underwater": "Close Elytra when submerged in water",
"config.flightassistant.computers.elytra_state.open_automatically": "Open Elytra automatically",
"config.flightassistant.computers.chunk_state": "Chunk state",
"config.flightassistant.computers.chunk_state.protection": "Chunk state protection mode",

"commands.flightassistant.no_such_waypoint": "There is no waypoint at that index",
"commands.flightassistant.no_such_plan": "There is no plan with that name",
Expand All @@ -107,6 +109,7 @@
"alerts.flightassistant.stall": "STALL",
"alerts.flightassistant.terrain_ahead": "TERRAIN AHEAD",
"alerts.flightassistant.too_low_terrain": "TOO LOW - TERRAIN",
"alerts.flightassistant.unloaded_chunk": "UNLOADED CHUNK",

"alerts.flightassistant.elytra_health_low": "ELYTRA HEALTH LOW",

Expand All @@ -130,6 +133,7 @@
"alerts.flightassistant.fault.computers.void_level": "VOID LVL FAULT (PROT LOST)",
"alerts.flightassistant.fault.computers.yaw_ctl": "YAW CTL FAULT",
"alerts.flightassistant.fault.computers.elytra_state": "ELYTRA STATE FAULT (PROT LOST)",
"alerts.flightassistant.fault.computers.chunk_state": "CHUNK STATE FAULT (PROT LOST)",
"alerts.flightassistant.fault.indicators.alert": "ALERT INDICATOR FAULT",
"alerts.flightassistant.fault.indicators.altitude": "ALTITUDE INDICATOR FAULT",
"alerts.flightassistant.fault.indicators.elytra_health": "ELYTRA HEALTH INDICATOR FAULT",
Expand Down

0 comments on commit b8a20e3

Please sign in to comment.