Checkpoint.
This commit is contained in:
parent
9f8b0de850
commit
a9561702fc
23 changed files with 329 additions and 289 deletions
|
|
@ -59,6 +59,11 @@ typedef enum ViewDirectionE {
|
||||||
// Compose a uint8 throttle/flaps/mixture value from a percent 0..100.
|
// Compose a uint8 throttle/flaps/mixture value from a percent 0..100.
|
||||||
#define AC_BYTE_PCT(p) ((uint8_t)(((int)(p) * 255) / 100))
|
#define AC_BYTE_PCT(p) ((uint8_t)(((int)(p) * 255) / 100))
|
||||||
|
|
||||||
|
// Top-of-envelope forward speed in Q8.8 world-units/frame
|
||||||
|
// (~1.6 wu/frame). audio.c references this to compute its wind hiss
|
||||||
|
// amplitude.
|
||||||
|
#define AC_MAX_FORWARD_SPEED_Q88 410
|
||||||
|
|
||||||
// Reality-mode instrument failure bits. Mirrors chunk3
|
// Reality-mode instrument failure bits. Mirrors chunk3
|
||||||
// `InstrumentOperationalFlags` (init $FF = all good). The
|
// `InstrumentOperationalFlags` (init $FF = all good). The
|
||||||
// chunk3 `FailureProcTable` clears one of these bits or sets one of
|
// chunk3 `FailureProcTable` clears one of these bits or sets one of
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,22 @@
|
||||||
#define CAM_ROT_FRACT_BITS 15
|
#define CAM_ROT_FRACT_BITS 15
|
||||||
#define CAM_ROT_ONE (1 << CAM_ROT_FRACT_BITS)
|
#define CAM_ROT_ONE (1 << CAM_ROT_FRACT_BITS)
|
||||||
|
|
||||||
|
|
||||||
|
static inline int32_t metresFromQ1616(int32_t v_q1616) {
|
||||||
|
return v_q1616 >> CAM_POS_FRACT_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline int32_t q1616FromMetres(int32_t m) {
|
||||||
|
return m * CAM_POS_FRACT_ONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Convert FS2 byte angle (0..255 == 0..359 degrees) to degrees.
|
||||||
|
static inline int16_t byteAngleToDegrees(uint8_t angle) {
|
||||||
|
return (int16_t)(((int32_t)angle * 360) / 256);
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct CameraT {
|
typedef struct CameraT {
|
||||||
int32_t worldX; // Q16.16 world units
|
int32_t worldX; // Q16.16 world units
|
||||||
int32_t worldY;
|
int32_t worldY;
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,8 @@ int16_t fontDrawChar(FramebufferT *fb, int16_t x, int16_t y, char ch, ColorE col
|
||||||
// Draw a NUL-terminated string. Returns the X advance.
|
// Draw a NUL-terminated string. Returns the X advance.
|
||||||
int16_t fontDrawString(FramebufferT *fb, int16_t x, int16_t y, const char *s, ColorE color);
|
int16_t fontDrawString(FramebufferT *fb, int16_t x, int16_t y, const char *s, ColorE color);
|
||||||
|
|
||||||
|
// Draw `s` horizontally centred around screen column `cx`, with the
|
||||||
|
// top-left of the glyph row at `y`. Returns the X advance.
|
||||||
|
int16_t fontDrawStringCentered(FramebufferT *fb, int16_t cx, int16_t y, const char *s, ColorE color);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -61,4 +61,22 @@ uint8_t fs2VsiNeedlePos(int16_t value16);
|
||||||
// `UpdateSlipSkidIndicator` (chunk4 L2497) consumes.
|
// `UpdateSlipSkidIndicator` (chunk4 L2497) consumes.
|
||||||
uint8_t fs2SlipSkidIndex(int8_t slipValue);
|
uint8_t fs2SlipSkidIndex(int8_t slipValue);
|
||||||
|
|
||||||
|
|
||||||
|
// Clamp `v` to [lo, hi].
|
||||||
|
static inline int fs2ClampInt(int v, int lo, int hi) {
|
||||||
|
if (v < lo) {
|
||||||
|
return lo;
|
||||||
|
}
|
||||||
|
if (v > hi) {
|
||||||
|
return hi;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Add `step` to `v`, then clamp to [lo, hi].
|
||||||
|
static inline int fs2StepClamp(int v, int step, int lo, int hi) {
|
||||||
|
return fs2ClampInt(v + step, lo, hi);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ void rendererBegin(RenderStateT *state, FramebufferT *fb);
|
||||||
void rendererSetDrawColor(RenderStateT *state, ColorE color);
|
void rendererSetDrawColor(RenderStateT *state, ColorE color);
|
||||||
void rendererSetHiresColor(RenderStateT *state, uint8_t hiresCode);
|
void rendererSetHiresColor(RenderStateT *state, uint8_t hiresCode);
|
||||||
void rendererSetFillColors(RenderStateT *state, ColorE fill, ColorE altFill);
|
void rendererSetFillColors(RenderStateT *state, ColorE fill, ColorE altFill);
|
||||||
void rendererSwapFillColors(RenderStateT *state);
|
|
||||||
|
|
||||||
void rendererDrawLine(RenderStateT *state, int16_t x1, int16_t y1, int16_t x2, int16_t y2);
|
void rendererDrawLine(RenderStateT *state, int16_t x1, int16_t y1, int16_t x2, int16_t y2);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,4 +65,10 @@ void sceneryDataFree(SceneryDataT *out);
|
||||||
// Human-readable name for a region. Always returns a non-NULL string.
|
// Human-readable name for a region. Always returns a non-NULL string.
|
||||||
const char *sceneryDataRegionName(SceneryRegionE region);
|
const char *sceneryDataRegionName(SceneryRegionE region);
|
||||||
|
|
||||||
|
// Reverse-lookup: map the basename string used by SCENERY_REGION
|
||||||
|
// (e.g. "FS2.1", "FS2.1_chicago", "SD3", "SD14B") back to its enum.
|
||||||
|
// Accepts the bare SD form ("SD3") as well as the "A2.SD3" full form.
|
||||||
|
// Returns SCENERY_NONE if unrecognised.
|
||||||
|
SceneryRegionE sceneryDataRegionFromName(const char *name);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,6 @@
|
||||||
// Default scale factor (window pixels per native pixel).
|
// Default scale factor (window pixels per native pixel).
|
||||||
#define WINDOW_SCALE 4
|
#define WINDOW_SCALE 4
|
||||||
|
|
||||||
// 16-bit signed scenery coordinate.
|
|
||||||
typedef int16_t Coord16T;
|
|
||||||
|
|
||||||
// One vertex in scenery / camera space.
|
|
||||||
typedef struct VertexT {
|
|
||||||
Coord16T x;
|
|
||||||
Coord16T y;
|
|
||||||
Coord16T z;
|
|
||||||
} VertexT;
|
|
||||||
|
|
||||||
// Cohen-Sutherland-style outcode bits for frustum clipping. Mirrors
|
// Cohen-Sutherland-style outcode bits for frustum clipping. Mirrors
|
||||||
// the layout used by the original disassembly so the clipper logic
|
// the layout used by the original disassembly so the clipper logic
|
||||||
// can be ported one-for-one.
|
// can be ported one-for-one.
|
||||||
|
|
|
||||||
|
|
@ -97,11 +97,6 @@ void ww1aceInit(WW1AceStateT *s);
|
||||||
// score/bomb counters. Player coords are Q16.16 world-units.
|
// score/bomb counters. Player coords are Q16.16 world-units.
|
||||||
void ww1aceToggle(WW1AceStateT *s, int32_t playerX, int32_t playerZ);
|
void ww1aceToggle(WW1AceStateT *s, int32_t playerX, int32_t playerZ);
|
||||||
|
|
||||||
// Legacy bomb-drop entry that just decrements the bomb count. Prefer
|
|
||||||
// `ww1aceDropBombAt` so the bomb is added to the in-flight pool with
|
|
||||||
// the player's actual position + velocity.
|
|
||||||
void ww1aceDropBomb(WW1AceStateT *s);
|
|
||||||
|
|
||||||
// Drop a bomb at the player's current world position with the player's
|
// Drop a bomb at the player's current world position with the player's
|
||||||
// horizontal velocity (Q8.8 metres/frame). The bomb then falls under
|
// horizontal velocity (Q8.8 metres/frame). The bomb then falls under
|
||||||
// gravity in `ww1aceUpdate` and tries to score a hit on a ground
|
// gravity in `ww1aceUpdate` and tries to score a hit on a ground
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@
|
||||||
#define DEFAULT_DECAY_K 218 // ~0.85 self-centring
|
#define DEFAULT_DECAY_K 218 // ~0.85 self-centring
|
||||||
#define RUDDER_DECAY_K 179 // ~0.70
|
#define RUDDER_DECAY_K 179 // ~0.70
|
||||||
|
|
||||||
// Speed envelope.
|
// Speed envelope. AC_MAX_FORWARD_SPEED_Q88 lives in aircraft.h so
|
||||||
#define MAX_FORWARD_SPEED_Q88 410 // 1.6 world-units / frame
|
// audio.c can read it without duplicating the literal.
|
||||||
#define DRAG_K 1 // 1/256 ~ 0.4%
|
#define DRAG_K 1 // 1/256 ~ 0.4%
|
||||||
|
|
||||||
// Lift / gravity.
|
// Lift / gravity.
|
||||||
|
|
@ -217,26 +217,23 @@ void aircraftInit(AircraftT *ac) {
|
||||||
|
|
||||||
|
|
||||||
static AircraftT recoveryBuf;
|
static AircraftT recoveryBuf;
|
||||||
static bool recoveryValid;
|
|
||||||
|
|
||||||
|
|
||||||
void aircraftArmRecovery(AircraftT *ac) {
|
void aircraftArmRecovery(AircraftT *ac) {
|
||||||
recoveryBuf = *ac;
|
// Set the snapshot flag before capturing so the restored state
|
||||||
recoveryValid = true;
|
// also reports armed -- successive crashes can reuse the same
|
||||||
|
// snapshot without needing another arm pass.
|
||||||
ac->hasRecoverySnapshot = true;
|
ac->hasRecoverySnapshot = true;
|
||||||
|
recoveryBuf = *ac;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void aircraftRestoreRecovery(AircraftT *ac) {
|
void aircraftRestoreRecovery(AircraftT *ac) {
|
||||||
if (!recoveryValid) {
|
if (!ac->hasRecoverySnapshot) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Preserve the recovery-armed flag so further crashes can use
|
|
||||||
// the same snapshot.
|
|
||||||
bool hasSnap = ac->hasRecoverySnapshot;
|
|
||||||
*ac = recoveryBuf;
|
*ac = recoveryBuf;
|
||||||
ac->hasRecoverySnapshot = hasSnap;
|
ac->crashed = false;
|
||||||
ac->crashed = false;
|
|
||||||
ac->crashType = CRASH_NONE;
|
ac->crashType = CRASH_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -559,9 +556,7 @@ static void stepFlight(AircraftT *ac, WindStateT *wind) {
|
||||||
// (using the >> 7 form keeps int8 input valued at full scale).
|
// (using the >> 7 form keeps int8 input valued at full scale).
|
||||||
// Trim biases yokeVert -- chunk5 RefreshElevatorIndicator
|
// Trim biases yokeVert -- chunk5 RefreshElevatorIndicator
|
||||||
// accumulates trim into the elevator command.
|
// accumulates trim into the elevator command.
|
||||||
int yokePlusTrim = (int)(int8_t)ac->yokeVert + (int)(int8_t)ac->trim;
|
int yokePlusTrim = fs2ClampInt((int)(int8_t)ac->yokeVert + (int)(int8_t)ac->trim, -127, 127);
|
||||||
if (yokePlusTrim > 127) yokePlusTrim = 127;
|
|
||||||
if (yokePlusTrim < -127) yokePlusTrim = -127;
|
|
||||||
int16_t pitchTarget = (int16_t)(((int32_t)yokePlusTrim * MAX_PITCH_RATE_Q88) / 127);
|
int16_t pitchTarget = (int16_t)(((int32_t)yokePlusTrim * MAX_PITCH_RATE_Q88) / 127);
|
||||||
int16_t bankTarget = (int16_t)(((int32_t)(int8_t)ac->yokeHoriz * MAX_BANK_RATE_Q88) / 127);
|
int16_t bankTarget = (int16_t)(((int32_t)(int8_t)ac->yokeHoriz * MAX_BANK_RATE_Q88) / 127);
|
||||||
ac->pitchRate = q88Lerp(ac->pitchRate, pitchTarget, INPUT_SMOOTH_K);
|
ac->pitchRate = q88Lerp(ac->pitchRate, pitchTarget, INPUT_SMOOTH_K);
|
||||||
|
|
@ -597,7 +592,7 @@ static void stepFlight(AircraftT *ac, WindStateT *wind) {
|
||||||
if (ac->fuelLeft == 0 && ac->fuelRight == 0) {
|
if (ac->fuelLeft == 0 && ac->fuelRight == 0) {
|
||||||
effectiveThrottle = 0;
|
effectiveThrottle = 0;
|
||||||
}
|
}
|
||||||
int16_t targetSpeed = (int16_t)(((int32_t)effectiveThrottle * MAX_FORWARD_SPEED_Q88) / 255);
|
int16_t targetSpeed = (int16_t)(((int32_t)effectiveThrottle * AC_MAX_FORWARD_SPEED_Q88) / 255);
|
||||||
int16_t engineDelta = (int16_t)((((int32_t)targetSpeed - ac->forwardSpeed) * ENGINE_SMOOTH_K) >> 8);
|
int16_t engineDelta = (int16_t)((((int32_t)targetSpeed - ac->forwardSpeed) * ENGINE_SMOOTH_K) >> 8);
|
||||||
int16_t dragDelta = (int16_t)(((int32_t)ac->forwardSpeed * DRAG_K) >> 8);
|
int16_t dragDelta = (int16_t)(((int32_t)ac->forwardSpeed * DRAG_K) >> 8);
|
||||||
ac->forwardSpeed = q88Add(ac->forwardSpeed, q88Add(engineDelta, (int16_t)-dragDelta));
|
ac->forwardSpeed = q88Add(ac->forwardSpeed, q88Add(engineDelta, (int16_t)-dragDelta));
|
||||||
|
|
|
||||||
|
|
@ -193,11 +193,6 @@ void audioTriggerGun(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// AC_RATE_FRACT_ONE * 1.6 = 410, the FS2 max forward speed in Q8.8.
|
|
||||||
// See MAX_FORWARD_SPEED_Q88 in aircraft.c.
|
|
||||||
#define AUDIO_SPEED_FULL_Q88 410
|
|
||||||
|
|
||||||
|
|
||||||
void audioUpdate(const AircraftT *ac) {
|
void audioUpdate(const AircraftT *ac) {
|
||||||
if (audio.device == 0) {
|
if (audio.device == 0) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -207,9 +202,13 @@ void audioUpdate(const AircraftT *ac) {
|
||||||
// single SDL_LockAudioDevice store; the audio synth itself
|
// single SDL_LockAudioDevice store; the audio synth itself
|
||||||
// still runs in float because SDL streams float samples.
|
// still runs in float because SDL streams float samples.
|
||||||
// speedFraction : Q8.8 from 0..256 (= 0.0..1.0)
|
// speedFraction : Q8.8 from 0..256 (= 0.0..1.0)
|
||||||
int speedT = (int)ac->forwardSpeed * 256 / AUDIO_SPEED_FULL_Q88;
|
int speedT = (int)ac->forwardSpeed * 256 / AC_MAX_FORWARD_SPEED_Q88;
|
||||||
if (speedT < 0) speedT = 0;
|
if (speedT < 0) {
|
||||||
if (speedT > 256) speedT = 256;
|
speedT = 0;
|
||||||
|
}
|
||||||
|
if (speedT > 256) {
|
||||||
|
speedT = 256;
|
||||||
|
}
|
||||||
// freqQ88 = base + speedT * (max - base) / 256
|
// freqQ88 = base + speedT * (max - base) / 256
|
||||||
int32_t freqRange = ENGINE_MAX_HZ - ENGINE_BASE_HZ;
|
int32_t freqRange = ENGINE_MAX_HZ - ENGINE_BASE_HZ;
|
||||||
int32_t freqQ88 = ((int32_t)ENGINE_BASE_HZ << 8)
|
int32_t freqQ88 = ((int32_t)ENGINE_BASE_HZ << 8)
|
||||||
|
|
@ -271,7 +270,9 @@ void audioUpdate(const AircraftT *ac) {
|
||||||
if (!ac->onGround) {
|
if (!ac->onGround) {
|
||||||
float speedFrac = (float)speedT / 256.0f;
|
float speedFrac = (float)speedT / 256.0f;
|
||||||
windAmp = sqrtf(speedFrac);
|
windAmp = sqrtf(speedFrac);
|
||||||
if (windAmp > 1.0f) windAmp = 1.0f;
|
if (windAmp > 1.0f) {
|
||||||
|
windAmp = 1.0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_LockAudioDevice(audio.device);
|
SDL_LockAudioDevice(audio.device);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "camera.h"
|
#include "camera.h"
|
||||||
#include "chunk5Setup.h"
|
#include "chunk5Setup.h"
|
||||||
|
#include "fs2math.h"
|
||||||
#include "math6502.h"
|
#include "math6502.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -71,12 +72,8 @@ void cameraTransform(const CameraT *cam, int32_t wx_q1616, int32_t wy_q1616, int
|
||||||
// matRow2 and the int16 RAM mirror. Verified against MAME 2026-05-07.
|
// matRow2 and the int16 RAM mirror. Verified against MAME 2026-05-07.
|
||||||
void cameraGet2x3Matrix(const CameraT *cam, int8_t outRowX[3], int8_t outRowZ[3]) {
|
void cameraGet2x3Matrix(const CameraT *cam, int8_t outRowX[3], int8_t outRowZ[3]) {
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
int x = cam->rot[i][0] >> 8;
|
int x = fs2ClampInt(cam->rot[i][0] >> 8, -128, 127);
|
||||||
int z = cam->rot[i][2] >> 8;
|
int z = fs2ClampInt(cam->rot[i][2] >> 8, -128, 127);
|
||||||
if (x > 127) x = 127;
|
|
||||||
if (x < -128) x = -128;
|
|
||||||
if (z > 127) z = 127;
|
|
||||||
if (z < -128) z = -128;
|
|
||||||
outRowX[i] = (int8_t)x;
|
outRowX[i] = (int8_t)x;
|
||||||
outRowZ[i] = (int8_t)z;
|
outRowZ[i] = (int8_t)z;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "editor.h"
|
#include "editor.h"
|
||||||
#include "aircraft.h"
|
#include "aircraft.h"
|
||||||
|
#include "camera.h"
|
||||||
#include "font.h"
|
#include "font.h"
|
||||||
#include "framebuffer.h"
|
#include "framebuffer.h"
|
||||||
|
#include "fs2math.h"
|
||||||
#include "radios.h"
|
#include "radios.h"
|
||||||
#include "timeOfDay.h"
|
#include "timeOfDay.h"
|
||||||
#include "wind.h"
|
#include "wind.h"
|
||||||
|
|
@ -117,7 +119,6 @@ static const FieldT fields[F_COUNT] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static int applyStep(int v, int step, int lo, int hi);
|
|
||||||
static void formatValue(char *buf, size_t n, FieldIndexE fi,
|
static void formatValue(char *buf, size_t n, FieldIndexE fi,
|
||||||
const AircraftT *ac, const RadiosT *radios,
|
const AircraftT *ac, const RadiosT *radios,
|
||||||
const TimeOfDayT *tod, const WindStateT *wind);
|
const TimeOfDayT *tod, const WindStateT *wind);
|
||||||
|
|
@ -126,28 +127,6 @@ static void modifyField(FieldIndexE fi, int step,
|
||||||
TimeOfDayT *tod, WindStateT *wind);
|
TimeOfDayT *tod, WindStateT *wind);
|
||||||
|
|
||||||
|
|
||||||
static int applyStep(int v, int step, int lo, int hi) {
|
|
||||||
int r = v + step;
|
|
||||||
if (r < lo) {
|
|
||||||
r = lo;
|
|
||||||
}
|
|
||||||
if (r > hi) {
|
|
||||||
r = hi;
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int16_t metresFromQ1616(int32_t v_q1616) {
|
|
||||||
return (int16_t)(v_q1616 >> CAM_POS_FRACT_BITS);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int32_t q1616FromMetres(int16_t m) {
|
|
||||||
return (int32_t)m * CAM_POS_FRACT_ONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void formatValue(char *buf, size_t n, FieldIndexE fi,
|
static void formatValue(char *buf, size_t n, FieldIndexE fi,
|
||||||
const AircraftT *ac, const RadiosT *radios,
|
const AircraftT *ac, const RadiosT *radios,
|
||||||
const TimeOfDayT *tod, const WindStateT *wind) {
|
const TimeOfDayT *tod, const WindStateT *wind) {
|
||||||
|
|
@ -155,9 +134,9 @@ static void formatValue(char *buf, size_t n, FieldIndexE fi,
|
||||||
case F_WORLD_X: snprintf(buf, n, "%6d", metresFromQ1616(ac->worldX)); break;
|
case F_WORLD_X: snprintf(buf, n, "%6d", metresFromQ1616(ac->worldX)); break;
|
||||||
case F_WORLD_Y: snprintf(buf, n, "%6d", metresFromQ1616(ac->worldY)); break;
|
case F_WORLD_Y: snprintf(buf, n, "%6d", metresFromQ1616(ac->worldY)); break;
|
||||||
case F_WORLD_Z: snprintf(buf, n, "%6d", metresFromQ1616(ac->worldZ)); break;
|
case F_WORLD_Z: snprintf(buf, n, "%6d", metresFromQ1616(ac->worldZ)); break;
|
||||||
case F_YAW: snprintf(buf, n, "%6d", (ac->yaw * 360) / 256); break;
|
case F_YAW: snprintf(buf, n, "%6d", byteAngleToDegrees(ac->yaw)); break;
|
||||||
case F_PITCH: snprintf(buf, n, "%6d", (ac->pitch * 360) / 256); break;
|
case F_PITCH: snprintf(buf, n, "%6d", byteAngleToDegrees(ac->pitch)); break;
|
||||||
case F_BANK: snprintf(buf, n, "%6d", (ac->bank * 360) / 256); break;
|
case F_BANK: snprintf(buf, n, "%6d", byteAngleToDegrees(ac->bank)); break;
|
||||||
case F_THROTTLE: snprintf(buf, n, "%6d", (ac->throttle * 100) / 256); break;
|
case F_THROTTLE: snprintf(buf, n, "%6d", (ac->throttle * 100) / 256); break;
|
||||||
case F_MIXTURE: snprintf(buf, n, "%6d", (ac->mixture * 100) / 256); break;
|
case F_MIXTURE: snprintf(buf, n, "%6d", (ac->mixture * 100) / 256); break;
|
||||||
case F_MAGNETOS: snprintf(buf, n, "%6d", ac->magnetos); break;
|
case F_MAGNETOS: snprintf(buf, n, "%6d", ac->magnetos); break;
|
||||||
|
|
@ -174,15 +153,15 @@ static void formatValue(char *buf, size_t n, FieldIndexE fi,
|
||||||
snprintf(buf, n, "%6s", names[tod->season & 3]);
|
snprintf(buf, n, "%6s", names[tod->season & 3]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case F_WIND_DIR: snprintf(buf, n, "%6d", (wind->surface.direction * 360) / 256); break;
|
case F_WIND_DIR: snprintf(buf, n, "%6d", byteAngleToDegrees(wind->surface.direction)); break;
|
||||||
case F_WIND_SPD: snprintf(buf, n, "%6d", wind->surface.magnitude); break;
|
case F_WIND_SPD: snprintf(buf, n, "%6d", wind->surface.magnitude); break;
|
||||||
case F_REALITY: snprintf(buf, n, "%6s", ac->realityMode ? "ON" : "OFF"); break;
|
case F_REALITY: snprintf(buf, n, "%6s", ac->realityMode ? "ON" : "OFF"); break;
|
||||||
case F_NAV1_FREQ: snprintf(buf, n, "$%04X", radios->nav1Freq); break;
|
case F_NAV1_FREQ: snprintf(buf, n, "$%04X", radios->nav1Freq); break;
|
||||||
case F_NAV2_FREQ: snprintf(buf, n, "$%04X", radios->nav2Freq); break;
|
case F_NAV2_FREQ: snprintf(buf, n, "$%04X", radios->nav2Freq); break;
|
||||||
case F_COM1_FREQ: snprintf(buf, n, "$%04X", radios->com1Freq); break;
|
case F_COM1_FREQ: snprintf(buf, n, "$%04X", radios->com1Freq); break;
|
||||||
case F_ADF_FREQ: snprintf(buf, n, "$%04X", radios->adfFreq); break;
|
case F_ADF_FREQ: snprintf(buf, n, "$%04X", radios->adfFreq); break;
|
||||||
case F_NAV1_OBS: snprintf(buf, n, "%6d", (radios->nav1Obs * 360) / 256); break;
|
case F_NAV1_OBS: snprintf(buf, n, "%6d", byteAngleToDegrees(radios->nav1Obs)); break;
|
||||||
case F_NAV2_OBS: snprintf(buf, n, "%6d", (radios->nav2Obs * 360) / 256); break;
|
case F_NAV2_OBS: snprintf(buf, n, "%6d", byteAngleToDegrees(radios->nav2Obs)); break;
|
||||||
default: buf[0] = '\0'; break;
|
default: buf[0] = '\0'; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -194,19 +173,19 @@ static void modifyField(FieldIndexE fi, int step,
|
||||||
switch (fi) {
|
switch (fi) {
|
||||||
case F_WORLD_X: {
|
case F_WORLD_X: {
|
||||||
int16_t m = metresFromQ1616(ac->worldX);
|
int16_t m = metresFromQ1616(ac->worldX);
|
||||||
m = (int16_t)applyStep(m, step, -32768, 32767);
|
m = (int16_t)fs2StepClamp(m, step, -32768, 32767);
|
||||||
ac->worldX = q1616FromMetres(m);
|
ac->worldX = q1616FromMetres(m);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case F_WORLD_Y: {
|
case F_WORLD_Y: {
|
||||||
int16_t m = metresFromQ1616(ac->worldY);
|
int16_t m = metresFromQ1616(ac->worldY);
|
||||||
m = (int16_t)applyStep(m, step, 0, 30000);
|
m = (int16_t)fs2StepClamp(m, step, 0, 30000);
|
||||||
ac->worldY = q1616FromMetres(m);
|
ac->worldY = q1616FromMetres(m);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case F_WORLD_Z: {
|
case F_WORLD_Z: {
|
||||||
int16_t m = metresFromQ1616(ac->worldZ);
|
int16_t m = metresFromQ1616(ac->worldZ);
|
||||||
m = (int16_t)applyStep(m, step, -32768, 32767);
|
m = (int16_t)fs2StepClamp(m, step, -32768, 32767);
|
||||||
ac->worldZ = q1616FromMetres(m);
|
ac->worldZ = q1616FromMetres(m);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -214,29 +193,29 @@ static void modifyField(FieldIndexE fi, int step,
|
||||||
case F_PITCH: ac->pitch = (uint8_t)(ac->pitch + step); break;
|
case F_PITCH: ac->pitch = (uint8_t)(ac->pitch + step); break;
|
||||||
case F_BANK: ac->bank = (uint8_t)(ac->bank + step); break;
|
case F_BANK: ac->bank = (uint8_t)(ac->bank + step); break;
|
||||||
case F_THROTTLE: {
|
case F_THROTTLE: {
|
||||||
int v = applyStep(ac->throttle, step, 0, 255);
|
int v = fs2StepClamp(ac->throttle, step, 0, 255);
|
||||||
ac->throttle = (uint8_t)v;
|
ac->throttle = (uint8_t)v;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case F_MIXTURE: ac->mixture = (uint8_t)applyStep(ac->mixture, step, 0, 255); break;
|
case F_MIXTURE: ac->mixture = (uint8_t)fs2StepClamp(ac->mixture, step, 0, 255); break;
|
||||||
case F_MAGNETOS: ac->magnetos = (uint8_t)applyStep(ac->magnetos, step, 0, 4); break;
|
case F_MAGNETOS: ac->magnetos = (uint8_t)fs2StepClamp(ac->magnetos, step, 0, 4); break;
|
||||||
case F_CARB_HEAT: ac->carbHeatOn = !ac->carbHeatOn; break;
|
case F_CARB_HEAT: ac->carbHeatOn = !ac->carbHeatOn; break;
|
||||||
case F_FUEL_L: ac->fuelLeft = (uint8_t)applyStep(ac->fuelLeft, step, 0, 255); break;
|
case F_FUEL_L: ac->fuelLeft = (uint8_t)fs2StepClamp(ac->fuelLeft, step, 0, 255); break;
|
||||||
case F_FUEL_R: ac->fuelRight = (uint8_t)applyStep(ac->fuelRight, step, 0, 255); break;
|
case F_FUEL_R: ac->fuelRight = (uint8_t)fs2StepClamp(ac->fuelRight, step, 0, 255); break;
|
||||||
case F_FLAPS: ac->flaps = (uint8_t)applyStep(ac->flaps, step, 0, 255); break;
|
case F_FLAPS: ac->flaps = (uint8_t)fs2StepClamp(ac->flaps, step, 0, 255); break;
|
||||||
case F_TRIM: {
|
case F_TRIM: {
|
||||||
int v = applyStep((int8_t)ac->trim, step, -84, 84);
|
int v = fs2StepClamp((int8_t)ac->trim, step, -84, 84);
|
||||||
ac->trim = (uint8_t)v;
|
ac->trim = (uint8_t)v;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case F_LIGHTS: ac->lightsOn = !ac->lightsOn; break;
|
case F_LIGHTS: ac->lightsOn = !ac->lightsOn; break;
|
||||||
case F_TOD_HOURS: {
|
case F_TOD_HOURS: {
|
||||||
int hr = applyStep(tod->hours, step, 0, 23);
|
int hr = fs2StepClamp(tod->hours, step, 0, 23);
|
||||||
timeOfDaySet(tod, (uint8_t)hr, tod->minutes);
|
timeOfDaySet(tod, (uint8_t)hr, tod->minutes);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case F_TOD_MIN: {
|
case F_TOD_MIN: {
|
||||||
int mn = applyStep(tod->minutes, step, 0, 59);
|
int mn = fs2StepClamp(tod->minutes, step, 0, 59);
|
||||||
timeOfDaySet(tod, tod->hours, (uint8_t)mn);
|
timeOfDaySet(tod, tod->hours, (uint8_t)mn);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -247,7 +226,7 @@ static void modifyField(FieldIndexE fi, int step,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case F_WIND_DIR: wind->surface.direction = (uint8_t)(wind->surface.direction + step); break;
|
case F_WIND_DIR: wind->surface.direction = (uint8_t)(wind->surface.direction + step); break;
|
||||||
case F_WIND_SPD: wind->surface.magnitude = (uint8_t)applyStep(wind->surface.magnitude, step, 0, 255); break;
|
case F_WIND_SPD: wind->surface.magnitude = (uint8_t)fs2StepClamp(wind->surface.magnitude, step, 0, 255); break;
|
||||||
case F_REALITY: ac->realityMode = !ac->realityMode; break;
|
case F_REALITY: ac->realityMode = !ac->realityMode; break;
|
||||||
case F_NAV1_FREQ: radios->nav1Freq = (uint16_t)(radios->nav1Freq + step); break;
|
case F_NAV1_FREQ: radios->nav1Freq = (uint16_t)(radios->nav1Freq + step); break;
|
||||||
case F_NAV2_FREQ: radios->nav2Freq = (uint16_t)(radios->nav2Freq + step); break;
|
case F_NAV2_FREQ: radios->nav2Freq = (uint16_t)(radios->nav2Freq + step); break;
|
||||||
|
|
|
||||||
|
|
@ -106,3 +106,13 @@ int16_t fontDrawString(FramebufferT *fb, int16_t x, int16_t y, const char *s, Co
|
||||||
}
|
}
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int16_t fontDrawStringCentered(FramebufferT *fb, int16_t cx, int16_t y, const char *s, ColorE color) {
|
||||||
|
int len = 0;
|
||||||
|
while (s[len] != '\0') {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
int16_t startX = (int16_t)(cx - (len * (FONT_WIDTH + 1)) / 2);
|
||||||
|
return fontDrawString(fb, startX, y, s, color);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ void hudDraw(FramebufferT *fb, const CameraT *cam) {
|
||||||
fontDrawString(fb, 6, (int16_t)(panelTop + 12), buf, COLOR_ORANGE);
|
fontDrawString(fb, 6, (int16_t)(panelTop + 12), buf, COLOR_ORANGE);
|
||||||
|
|
||||||
// Heading: 0..359 deg derived from yaw byte angle.
|
// Heading: 0..359 deg derived from yaw byte angle.
|
||||||
int heading = (int)(cam->yaw * 360 / 256);
|
int heading = byteAngleToDegrees(cam->yaw);
|
||||||
snprintf(buf, sizeof(buf), "HDG %03d", heading);
|
snprintf(buf, sizeof(buf), "HDG %03d", heading);
|
||||||
fontDrawString(fb, 6, (int16_t)(panelTop + 24), buf, COLOR_ORANGE);
|
fontDrawString(fb, 6, (int16_t)(panelTop + 24), buf, COLOR_ORANGE);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,6 @@ static const GaugeT adfGauge = { 180, 175, 12 };
|
||||||
#define VOR2_FLAG_X 168
|
#define VOR2_FLAG_X 168
|
||||||
#define VOR2_FLAG_Y 173
|
#define VOR2_FLAG_Y 173
|
||||||
|
|
||||||
#define DEG2RAD 0.01745329251994f
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct PixelListT {
|
typedef struct PixelListT {
|
||||||
uint8_t count;
|
uint8_t count;
|
||||||
|
|
@ -68,7 +66,6 @@ typedef struct PixelListT {
|
||||||
} PixelListT;
|
} PixelListT;
|
||||||
|
|
||||||
|
|
||||||
static void drawCenteredString(FramebufferT *fb, int16_t cx, int16_t cy, const char *s, ColorE color);
|
|
||||||
static void drawControlIndicators(FramebufferT *fb, const AircraftT *ac);
|
static void drawControlIndicators(FramebufferT *fb, const AircraftT *ac);
|
||||||
static void drawFailX(FramebufferT *fb, const GaugeT *g);
|
static void drawFailX(FramebufferT *fb, const GaugeT *g);
|
||||||
static void drawHorizonDisc(FramebufferT *fb, const GaugeT *g, const AircraftT *ac);
|
static void drawHorizonDisc(FramebufferT *fb, const GaugeT *g, const AircraftT *ac);
|
||||||
|
|
@ -97,16 +94,6 @@ static const PixelListT plBall = { 12, plBallData };
|
||||||
static const PixelListT plFlapsTrimMixture = { 4, plFlapsTrimMixtureData };
|
static const PixelListT plFlapsTrimMixture = { 4, plFlapsTrimMixtureData };
|
||||||
|
|
||||||
|
|
||||||
static void drawCenteredString(FramebufferT *fb, int16_t cx, int16_t cy, const char *s, ColorE color) {
|
|
||||||
int len = 0;
|
|
||||||
while (s[len] != '\0') {
|
|
||||||
len++;
|
|
||||||
}
|
|
||||||
int16_t startX = (int16_t)(cx - (len * (FONT_WIDTH + 1)) / 2);
|
|
||||||
fontDrawString(fb, startX, cy, s, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Draw the four FS2 control-position markers (aileron, rudder,
|
// Draw the four FS2 control-position markers (aileron, rudder,
|
||||||
// throttle, elevator). Each is a small pixel-list sprite that slides
|
// throttle, elevator). Each is a small pixel-list sprite that slides
|
||||||
// along a fixed track on the panel. FS2 anchors:
|
// along a fixed track on the panel. FS2 anchors:
|
||||||
|
|
@ -240,9 +227,7 @@ static void drawSlipSkidBall(FramebufferT *fb, const AircraftT *ac) {
|
||||||
// bank pushes it sideways. Port's sideslip_q88 is computed in
|
// bank pushes it sideways. Port's sideslip_q88 is computed in
|
||||||
// aircraft.c as (yawRate - coordinated-turn yawRate); divide
|
// aircraft.c as (yawRate - coordinated-turn yawRate); divide
|
||||||
// down to the same -127..+127 range fs2SlipSkidIndex expects.
|
// down to the same -127..+127 range fs2SlipSkidIndex expects.
|
||||||
int32_t ssClamped = ac->sideslip_q88 / 8;
|
int32_t ssClamped = fs2ClampInt(ac->sideslip_q88 / 8, -127, 127);
|
||||||
if (ssClamped > 127) ssClamped = 127;
|
|
||||||
if (ssClamped < -127) ssClamped = -127;
|
|
||||||
uint8_t idx = fs2SlipSkidIndex((int8_t)ssClamped);
|
uint8_t idx = fs2SlipSkidIndex((int8_t)ssClamped);
|
||||||
int16_t anchorX = (int16_t)((int)idx + 0x0E);
|
int16_t anchorX = (int16_t)((int)idx + 0x0E);
|
||||||
drawPixelList(fb, anchorX, 0xB4, &plBall, COLOR_WHITE);
|
drawPixelList(fb, anchorX, 0xB4, &plBall, COLOR_WHITE);
|
||||||
|
|
@ -403,6 +388,11 @@ void instrumentsDrawAll(FramebufferT *fb, const AircraftT *ac, const RadiosT *ra
|
||||||
drawSlipSkidBall(fb, ac);
|
drawSlipSkidBall(fb, ac);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FS2 chunk5 DrawMagCompass paints `msg_magcompass` (a 3-digit
|
||||||
|
// heading readout), not a graphical compass card. Port renders
|
||||||
|
// the same digit readout via `readoutHeading` over in
|
||||||
|
// panelDigits.c -- no separate rose drawing needed.
|
||||||
|
|
||||||
// Aileron / rudder / throttle / elevator position markers.
|
// Aileron / rudder / throttle / elevator position markers.
|
||||||
drawControlIndicators(fb, ac);
|
drawControlIndicators(fb, ac);
|
||||||
|
|
||||||
|
|
@ -415,10 +405,10 @@ void instrumentsDrawAll(FramebufferT *fb, const AircraftT *ac, const RadiosT *ra
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ac->stalled) {
|
if (ac->stalled) {
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 4, "STALL", COLOR_WHITE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 4, "STALL", COLOR_WHITE);
|
||||||
}
|
}
|
||||||
if (ac->envelopeWarning && !ac->stalled) {
|
if (ac->envelopeWarning && !ac->stalled) {
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 4, "VNE", COLOR_WHITE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 4, "VNE", COLOR_WHITE);
|
||||||
}
|
}
|
||||||
if (ac->crashed) {
|
if (ac->crashed) {
|
||||||
// Mirrors FS2 chunk3 `HandleCrashOrSplash` /
|
// Mirrors FS2 chunk3 `HandleCrashOrSplash` /
|
||||||
|
|
@ -434,25 +424,25 @@ void instrumentsDrawAll(FramebufferT *fb, const AircraftT *ac, const RadiosT *ra
|
||||||
case CRASH_NONE:
|
case CRASH_NONE:
|
||||||
default: msg = "CRASH"; break;
|
default: msg = "CRASH"; break;
|
||||||
}
|
}
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 14, msg, COLOR_WHITE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 14, msg, COLOR_WHITE);
|
||||||
}
|
}
|
||||||
if (ac->demoMode && !ac->slewMode) {
|
if (ac->demoMode && !ac->slewMode) {
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 24, "DEMO", COLOR_WHITE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 24, "DEMO", COLOR_WHITE);
|
||||||
}
|
}
|
||||||
if (ac->editMode) {
|
if (ac->editMode) {
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 24, "EDIT", COLOR_WHITE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 24, "EDIT", COLOR_WHITE);
|
||||||
}
|
}
|
||||||
if (ac->realityMode) {
|
if (ac->realityMode) {
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 44, "REALITY", COLOR_WHITE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 44, "REALITY", COLOR_WHITE);
|
||||||
}
|
}
|
||||||
// Engine-fault flags: chunk3 SetEngineFault01/23 OR-in the
|
// Engine-fault flags: chunk3 SetEngineFault01/23 OR-in the
|
||||||
// bits; we surface them as a status line so the pilot has
|
// bits; we surface them as a status line so the pilot has
|
||||||
// something visible to react to.
|
// something visible to react to.
|
||||||
if (ac->engineFaults & AC_ENG_FAULT_LEFT) {
|
if (ac->engineFaults & AC_ENG_FAULT_LEFT) {
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 54, "L MAG FAIL", COLOR_HAZE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 54, "L MAG FAIL", COLOR_HAZE);
|
||||||
}
|
}
|
||||||
if (ac->engineFaults & AC_ENG_FAULT_RIGHT) {
|
if (ac->engineFaults & AC_ENG_FAULT_RIGHT) {
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 64, "R MAG FAIL", COLOR_HAZE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 64, "R MAG FAIL", COLOR_HAZE);
|
||||||
}
|
}
|
||||||
// Magneto state indicator. Mirrors FS2 chunk5 DrawMagnetoState
|
// Magneto state indicator. Mirrors FS2 chunk5 DrawMagnetoState
|
||||||
// (the string drawn alongside the magneto knob graphics on the
|
// (the string drawn alongside the magneto knob graphics on the
|
||||||
|
|
@ -468,10 +458,10 @@ void instrumentsDrawAll(FramebufferT *fb, const AircraftT *ac, const RadiosT *ra
|
||||||
case 4: mag = "MAG START"; break;
|
case 4: mag = "MAG START"; break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 74, mag, COLOR_ORANGE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 74, mag, COLOR_ORANGE);
|
||||||
}
|
}
|
||||||
if (ac->paused) {
|
if (ac->paused) {
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 84, "PAUSED", COLOR_WHITE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 84, "PAUSED", COLOR_WHITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throttle / Mixture / Flaps / Trim / Fuel position markers.
|
// Throttle / Mixture / Flaps / Trim / Fuel position markers.
|
||||||
|
|
@ -552,13 +542,13 @@ void instrumentsDrawAll(FramebufferT *fb, const AircraftT *ac, const RadiosT *ra
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ac->lightsOn) {
|
if (ac->lightsOn) {
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 94, "LIGHTS ON", COLOR_ORANGE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 94, "LIGHTS ON", COLOR_ORANGE);
|
||||||
}
|
}
|
||||||
if (ac->carbHeatOn) {
|
if (ac->carbHeatOn) {
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 104, "CARB HEAT", COLOR_ORANGE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 104, "CARB HEAT", COLOR_ORANGE);
|
||||||
}
|
}
|
||||||
if (ac->radarView) {
|
if (ac->radarView) {
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 4, "RADAR VIEW", COLOR_WHITE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 4, "RADAR VIEW", COLOR_WHITE);
|
||||||
}
|
}
|
||||||
if (ac->viewDirection != VIEW_FORWARD) {
|
if (ac->viewDirection != VIEW_FORWARD) {
|
||||||
const char *label = "VIEW";
|
const char *label = "VIEW";
|
||||||
|
|
@ -569,12 +559,12 @@ void instrumentsDrawAll(FramebufferT *fb, const AircraftT *ac, const RadiosT *ra
|
||||||
case VIEW_DOWN: label = "DOWN VIEW"; break;
|
case VIEW_DOWN: label = "DOWN VIEW"; break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 34, label, COLOR_WHITE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 34, label, COLOR_WHITE);
|
||||||
}
|
}
|
||||||
if (ac->slewMode) {
|
if (ac->slewMode) {
|
||||||
// FS2 chunk3 `DrawSlewOverlays`: " 00000 NORTH " at row
|
// FS2 chunk3 `DrawSlewOverlays`: " 00000 NORTH " at row
|
||||||
// 2 col $0A, " 00000 EAST " at row 2 col $4C.
|
// 2 col $0A, " 00000 EAST " at row 2 col $4C.
|
||||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 24, "SLEW", COLOR_WHITE);
|
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 24, "SLEW", COLOR_WHITE);
|
||||||
if (ac->showSlewDigits) {
|
if (ac->showSlewDigits) {
|
||||||
char buf[24];
|
char buf[24];
|
||||||
// worldX/Z are Q16.16; truncate to integer world unit.
|
// worldX/Z are Q16.16; truncate to integer world unit.
|
||||||
|
|
@ -607,9 +597,7 @@ static void drawVorCdiNeedle(FramebufferT *fb, int16_t centreX, int16_t centreY,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int defl = deflectionDeg;
|
int defl = fs2ClampInt(deflectionDeg, -VOR_CDI_FULL_DEG, VOR_CDI_FULL_DEG);
|
||||||
if (defl > VOR_CDI_FULL_DEG) defl = VOR_CDI_FULL_DEG;
|
|
||||||
if (defl < -VOR_CDI_FULL_DEG) defl = -VOR_CDI_FULL_DEG;
|
|
||||||
int xOffset = (defl * VOR_CDI_HALFWIDTH) / VOR_CDI_FULL_DEG;
|
int xOffset = (defl * VOR_CDI_HALFWIDTH) / VOR_CDI_FULL_DEG;
|
||||||
int16_t nx = (int16_t)(centreX + xOffset);
|
int16_t nx = (int16_t)(centreX + xOffset);
|
||||||
for (int dy = 1; dy <= VOR_CDI_NEEDLE_H; dy++) {
|
for (int dy = 1; dy <= VOR_CDI_NEEDLE_H; dy++) {
|
||||||
|
|
|
||||||
140
port/src/main.c
140
port/src/main.c
|
|
@ -21,6 +21,7 @@
|
||||||
#include "radios.h"
|
#include "radios.h"
|
||||||
#include "renderer.h"
|
#include "renderer.h"
|
||||||
#include "chunk5Transform.h"
|
#include "chunk5Transform.h"
|
||||||
|
#include "fs2math.h"
|
||||||
#include "sceneryData.h"
|
#include "sceneryData.h"
|
||||||
#include "sceneryProjection.h"
|
#include "sceneryProjection.h"
|
||||||
#include "sceneryVm.h"
|
#include "sceneryVm.h"
|
||||||
|
|
@ -400,42 +401,9 @@ static int runScreenshot(const char *path) {
|
||||||
SceneryRegionE shotRegion = SCENERY_FS2_1;
|
SceneryRegionE shotRegion = SCENERY_FS2_1;
|
||||||
const char *regionEnv = getenv("SCENERY_REGION");
|
const char *regionEnv = getenv("SCENERY_REGION");
|
||||||
if (regionEnv != NULL) {
|
if (regionEnv != NULL) {
|
||||||
if (strcmp(regionEnv, "FS2.1") == 0) {
|
SceneryRegionE r = sceneryDataRegionFromName(regionEnv);
|
||||||
shotRegion = SCENERY_FS2_1;
|
if (r != SCENERY_NONE) {
|
||||||
} else if (strcmp(regionEnv, "FS2.1_chicago") == 0) {
|
shotRegion = r;
|
||||||
shotRegion = SCENERY_FS2_1_CHICAGO;
|
|
||||||
} else if (strcmp(regionEnv, "FS2.1_la") == 0) {
|
|
||||||
shotRegion = SCENERY_FS2_1_LA;
|
|
||||||
} else if (strcmp(regionEnv, "FS2.1_seattle") == 0) {
|
|
||||||
shotRegion = SCENERY_FS2_1_SEATTLE;
|
|
||||||
} else if (strcmp(regionEnv, "FS2.1_ny") == 0) {
|
|
||||||
shotRegion = SCENERY_FS2_1_NY;
|
|
||||||
} else if (strcmp(regionEnv, "SD1") == 0) {
|
|
||||||
shotRegion = SCENERY_SD1;
|
|
||||||
} else if (strcmp(regionEnv, "SD2") == 0) {
|
|
||||||
shotRegion = SCENERY_SD2;
|
|
||||||
} else if (strcmp(regionEnv, "SD3") == 0) {
|
|
||||||
shotRegion = SCENERY_SD3;
|
|
||||||
} else if (strcmp(regionEnv, "SD4") == 0) {
|
|
||||||
shotRegion = SCENERY_SD4;
|
|
||||||
} else if (strcmp(regionEnv, "SD5") == 0) {
|
|
||||||
shotRegion = SCENERY_SD5;
|
|
||||||
} else if (strcmp(regionEnv, "SD6") == 0) {
|
|
||||||
shotRegion = SCENERY_SD6;
|
|
||||||
} else if (strcmp(regionEnv, "SD7A") == 0) {
|
|
||||||
shotRegion = SCENERY_SD7A;
|
|
||||||
} else if (strcmp(regionEnv, "SD7B") == 0) {
|
|
||||||
shotRegion = SCENERY_SD7B;
|
|
||||||
} else if (strcmp(regionEnv, "SD11") == 0) {
|
|
||||||
shotRegion = SCENERY_SD11;
|
|
||||||
} else if (strcmp(regionEnv, "SD13") == 0) {
|
|
||||||
shotRegion = SCENERY_SD13;
|
|
||||||
} else if (strcmp(regionEnv, "SD14A") == 0) {
|
|
||||||
shotRegion = SCENERY_SD14A;
|
|
||||||
} else if (strcmp(regionEnv, "SD14B") == 0) {
|
|
||||||
shotRegion = SCENERY_SD14B;
|
|
||||||
} else if (strcmp(regionEnv, "SDS1") == 0) {
|
|
||||||
shotRegion = SCENERY_SDS1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sceneryDataLoad(shotRegion, &screenshotScenery);
|
sceneryDataLoad(shotRegion, &screenshotScenery);
|
||||||
|
|
@ -917,6 +885,63 @@ int main(int argc, char **argv) {
|
||||||
sceneryDataFree(&scenery);
|
sceneryDataFree(&scenery);
|
||||||
}
|
}
|
||||||
scenery = alt;
|
scenery = alt;
|
||||||
|
// Each city's .SD ships its own primary
|
||||||
|
// airport with the COM station record
|
||||||
|
// ($1E) at a fixed scenery position --
|
||||||
|
// that's where we want the player parked
|
||||||
|
// on first load.
|
||||||
|
//
|
||||||
|
// The boot Meigs reference (x=95.6m,
|
||||||
|
// y=3m, z=268m) is what FS2 sets in
|
||||||
|
// chunk5 InitialZeroPageData; for the
|
||||||
|
// other regions FS2 leaves the aircraft
|
||||||
|
// at Meigs and expects the player to
|
||||||
|
// fly over -- but for parity-with-
|
||||||
|
// expectation we drop the player at
|
||||||
|
// the city's primary airport instead.
|
||||||
|
// Coordinates below are from sweeping
|
||||||
|
// each .SD's $1E COM record positions
|
||||||
|
// against the dispatcher's section
|
||||||
|
// culls (= the position that both
|
||||||
|
// matches a COM record AND has at
|
||||||
|
// least one in-frustum scenery section
|
||||||
|
// visible from that position).
|
||||||
|
//
|
||||||
|
// Y=3 is the chunk5 IntegrateClimbRate
|
||||||
|
// ground clamp = "parked on runway".
|
||||||
|
int32_t sx_m = 96, sy_m = 3, sz_m = 268;
|
||||||
|
switch (title.region) {
|
||||||
|
case SCENERY_FS2_1_CHICAGO:
|
||||||
|
// KCGX / Meigs Field --
|
||||||
|
// FS2's iconic Chicago
|
||||||
|
// start. Same coords as
|
||||||
|
// the FS2.1 base boot
|
||||||
|
// (= peninsula in Lake
|
||||||
|
// Michigan just south of
|
||||||
|
// downtown).
|
||||||
|
sx_m = 96; sz_m = 268;
|
||||||
|
break;
|
||||||
|
case SCENERY_FS2_1_LA:
|
||||||
|
// KLAX (Los Angeles Intl).
|
||||||
|
sx_m = 200; sz_m = 0;
|
||||||
|
break;
|
||||||
|
case SCENERY_FS2_1_SEATTLE:
|
||||||
|
// KSEA (Sea-Tac).
|
||||||
|
sx_m = 970; sz_m = 0;
|
||||||
|
break;
|
||||||
|
case SCENERY_FS2_1_NY:
|
||||||
|
// KJFK (New York Kennedy).
|
||||||
|
sx_m = 400; sz_m = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
aircraft.worldX = AC_WORLD_UNITS(sx_m);
|
||||||
|
aircraft.worldY = AC_WORLD_UNITS(sy_m);
|
||||||
|
aircraft.worldZ = AC_WORLD_UNITS(sz_m);
|
||||||
|
aircraft.throttle = 0; // parked
|
||||||
|
aircraft.forwardSpeed = 0;
|
||||||
|
aircraft.onGround = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1067,60 +1092,40 @@ int main(int argc, char **argv) {
|
||||||
// elevator trim by 4 byte-units; long-held keys
|
// elevator trim by 4 byte-units; long-held keys
|
||||||
// accumulate. Trim biases the pitch yoke output.
|
// accumulate. Trim biases the pitch yoke output.
|
||||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_HOME) {
|
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_HOME) {
|
||||||
int t = (int)aircraft.trim - 4;
|
aircraft.trim = (int8_t)fs2StepClamp(aircraft.trim, -4, -127, 127);
|
||||||
if (t < -127) t = -127;
|
|
||||||
aircraft.trim = (int8_t)t;
|
|
||||||
}
|
}
|
||||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_END) {
|
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_END) {
|
||||||
int t = (int)aircraft.trim + 4;
|
aircraft.trim = (int8_t)fs2StepClamp(aircraft.trim, +4, -127, 127);
|
||||||
if (t > 127) t = 127;
|
|
||||||
aircraft.trim = (int8_t)t;
|
|
||||||
}
|
}
|
||||||
// Flap controls (chunk5 UpdateFlapsIndicator).
|
// Flap controls (chunk5 UpdateFlapsIndicator).
|
||||||
// INSERT extends flaps in 32-byte-unit clicks;
|
// INSERT extends flaps in 32-byte-unit clicks;
|
||||||
// DELETE retracts.
|
// DELETE retracts.
|
||||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_INSERT) {
|
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_INSERT) {
|
||||||
int f = (int)aircraft.flaps + 32;
|
aircraft.flaps = (uint8_t)fs2StepClamp(aircraft.flaps, +32, 0, 255);
|
||||||
if (f > 255) f = 255;
|
|
||||||
aircraft.flaps = (uint8_t)f;
|
|
||||||
}
|
}
|
||||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_DELETE) {
|
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_DELETE) {
|
||||||
int f = (int)aircraft.flaps - 32;
|
aircraft.flaps = (uint8_t)fs2StepClamp(aircraft.flaps, -32, 0, 255);
|
||||||
if (f < 0) f = 0;
|
|
||||||
aircraft.flaps = (uint8_t)f;
|
|
||||||
}
|
}
|
||||||
// Mixture controls (chunk5 UpdateMixtureControl).
|
// Mixture controls (chunk5 UpdateMixtureControl).
|
||||||
// PAGEUP+Shift = richer, PAGEDN+Shift = leaner.
|
// PAGEUP+Shift = richer, PAGEDN+Shift = leaner.
|
||||||
if (ev.type == SDL_KEYDOWN && (ev.key.keysym.mod & KMOD_SHIFT)
|
if (ev.type == SDL_KEYDOWN && (ev.key.keysym.mod & KMOD_SHIFT)
|
||||||
&& ev.key.keysym.sym == SDLK_PAGEUP) {
|
&& ev.key.keysym.sym == SDLK_PAGEUP) {
|
||||||
int m = (int)aircraft.mixture + 16;
|
aircraft.mixture = (uint8_t)fs2StepClamp(aircraft.mixture, +16, 0, 255);
|
||||||
if (m > 255) m = 255;
|
|
||||||
aircraft.mixture = (uint8_t)m;
|
|
||||||
}
|
}
|
||||||
if (ev.type == SDL_KEYDOWN && (ev.key.keysym.mod & KMOD_SHIFT)
|
if (ev.type == SDL_KEYDOWN && (ev.key.keysym.mod & KMOD_SHIFT)
|
||||||
&& ev.key.keysym.sym == SDLK_PAGEDOWN) {
|
&& ev.key.keysym.sym == SDLK_PAGEDOWN) {
|
||||||
int m = (int)aircraft.mixture - 16;
|
aircraft.mixture = (uint8_t)fs2StepClamp(aircraft.mixture, -16, 0, 255);
|
||||||
if (m < 0) m = 0;
|
|
||||||
aircraft.mixture = (uint8_t)m;
|
|
||||||
}
|
}
|
||||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_BACKQUOTE) {
|
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_BACKQUOTE) {
|
||||||
aircraft.radarView = !aircraft.radarView;
|
aircraft.radarView = !aircraft.radarView;
|
||||||
}
|
}
|
||||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_LEFTBRACKET
|
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_LEFTBRACKET
|
||||||
&& aircraft.radarView) {
|
&& aircraft.radarView) {
|
||||||
int z = (int)aircraft.radarZoom * 3 / 2;
|
aircraft.radarZoom = (int16_t)fs2ClampInt((int)aircraft.radarZoom * 3 / 2, 128, 32767);
|
||||||
if (z > 32767) {
|
|
||||||
z = 32767;
|
|
||||||
}
|
|
||||||
aircraft.radarZoom = (int16_t)z;
|
|
||||||
}
|
}
|
||||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_RIGHTBRACKET
|
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_RIGHTBRACKET
|
||||||
&& aircraft.radarView) {
|
&& aircraft.radarView) {
|
||||||
int z = (int)aircraft.radarZoom * 2 / 3;
|
aircraft.radarZoom = (int16_t)fs2ClampInt((int)aircraft.radarZoom * 2 / 3, 128, 32767);
|
||||||
if (z < 128) { // 0.5 in Q8.8
|
|
||||||
z = 128;
|
|
||||||
}
|
|
||||||
aircraft.radarZoom = (int16_t)z;
|
|
||||||
}
|
}
|
||||||
// Joystick button events. Trigger (0) fires
|
// Joystick button events. Trigger (0) fires
|
||||||
// gun, second (1) drops a bomb, third (2)
|
// gun, second (1) drops a bomb, third (2)
|
||||||
|
|
@ -1180,8 +1185,11 @@ int main(int argc, char **argv) {
|
||||||
if (ev.type == SDL_KEYDOWN && radioInputMode != RADIO_INPUT_NONE) {
|
if (ev.type == SDL_KEYDOWN && radioInputMode != RADIO_INPUT_NONE) {
|
||||||
SDL_Keycode k = ev.key.keysym.sym;
|
SDL_Keycode k = ev.key.keysym.sym;
|
||||||
int digit = -1;
|
int digit = -1;
|
||||||
if (k >= SDLK_0 && k <= SDLK_9) digit = (int)(k - SDLK_0);
|
if (k >= SDLK_0 && k <= SDLK_9) {
|
||||||
else if (k >= SDLK_KP_0 && k <= SDLK_KP_9) digit = (int)(k - SDLK_KP_0);
|
digit = (int)(k - SDLK_0);
|
||||||
|
} else if (k >= SDLK_KP_0 && k <= SDLK_KP_9) {
|
||||||
|
digit = (int)(k - SDLK_KP_0);
|
||||||
|
}
|
||||||
if (digit >= 0) {
|
if (digit >= 0) {
|
||||||
RadioE w = (radioInputMode == RADIO_INPUT_NAV1) ? RADIO_NAV1
|
RadioE w = (radioInputMode == RADIO_INPUT_NAV1) ? RADIO_NAV1
|
||||||
: (radioInputMode == RADIO_INPUT_NAV2) ? RADIO_NAV2
|
: (radioInputMode == RADIO_INPUT_NAV2) ? RADIO_NAV2
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "camera.h"
|
||||||
#include "font.h"
|
#include "font.h"
|
||||||
#include "panelDigits.h"
|
#include "panelDigits.h"
|
||||||
|
|
||||||
|
|
@ -69,15 +70,7 @@ static void drawReadout(FramebufferT *fb, const ReadoutT *r, const char *text) {
|
||||||
int16_t clearW = (int16_t)(r->cellsWide * FS2_CHAR_ADVANCE_HIRES + (FS2_CHAR_WIDTH_HIRES - FS2_CHAR_ADVANCE_HIRES));
|
int16_t clearW = (int16_t)(r->cellsWide * FS2_CHAR_ADVANCE_HIRES + (FS2_CHAR_WIDTH_HIRES - FS2_CHAR_ADVANCE_HIRES));
|
||||||
int16_t clearH = FONT_HEIGHT + 1;
|
int16_t clearH = FONT_HEIGHT + 1;
|
||||||
framebufferFillRect(fb, (int16_t)(r->x - 2), (int16_t)(r->y - 1), (int16_t)(clearW + 2), clearH, COLOR_BLACK);
|
framebufferFillRect(fb, (int16_t)(r->x - 2), (int16_t)(r->y - 1), (int16_t)(clearW + 2), clearH, COLOR_BLACK);
|
||||||
|
fontDrawStringCentered(fb, (int16_t)(r->x + clearW / 2), r->y, text, COLOR_WHITE);
|
||||||
// Centre our narrower text inside the erased area.
|
|
||||||
int textLen = 0;
|
|
||||||
while (text[textLen] != '\0') {
|
|
||||||
textLen++;
|
|
||||||
}
|
|
||||||
int16_t textW = (int16_t)(textLen * (FONT_WIDTH + 1));
|
|
||||||
int16_t textX = (int16_t)(r->x + (clearW - textW) / 2);
|
|
||||||
fontDrawString(fb, textX, r->y, text, COLOR_WHITE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -89,6 +82,49 @@ void panelDigitsDraw(FramebufferT *fb, const AircraftT *ac, const RadiosT *radio
|
||||||
char freqBuf[8];
|
char freqBuf[8];
|
||||||
radiosFormatFreq(radios->com1Freq, RADIO_COM1, freqBuf);
|
radiosFormatFreq(radios->com1Freq, RADIO_COM1, freqBuf);
|
||||||
drawReadout(fb, &readoutCom1, freqBuf);
|
drawReadout(fb, &readoutCom1, freqBuf);
|
||||||
|
|
||||||
|
// ATIS chunked text (FS2 chunk5 UpdateCOMMessageChunks). When
|
||||||
|
// tuned to an active COM station, scroll a synthesized info
|
||||||
|
// string ("KMDW WIND 270/12 ALT 30.05 RWY 18L") through a
|
||||||
|
// dedicated cell to the right of the COM frequency. Rolls one
|
||||||
|
// char left per frame; wraps with a 4-space gap. We synthesize
|
||||||
|
// the text from the station name + time + airframe state since
|
||||||
|
// FS2's actual ATIS payload isn't in our station db.
|
||||||
|
static char atisText[96];
|
||||||
|
static int atisIdx;
|
||||||
|
static int atisLen;
|
||||||
|
static uint16_t lastComFreq;
|
||||||
|
if (radios->com1Station != NULL) {
|
||||||
|
if (radios->com1Freq != lastComFreq) {
|
||||||
|
const char *nm = radios->com1Station->name[0]
|
||||||
|
? radios->com1Station->name : "ATIS";
|
||||||
|
int hh = tod ? tod->hours : 12;
|
||||||
|
snprintf(atisText, sizeof(atisText),
|
||||||
|
"%s WIND 270/12 ALT 30.05 RWY 18L TIME %02d:00 ",
|
||||||
|
nm, hh);
|
||||||
|
atisLen = (int)strlen(atisText);
|
||||||
|
atisIdx = 0;
|
||||||
|
lastComFreq = radios->com1Freq;
|
||||||
|
}
|
||||||
|
if (atisLen > 0) {
|
||||||
|
// Display 8 chars from atisIdx, wrapping.
|
||||||
|
char window[9];
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
window[j] = atisText[(atisIdx + j) % atisLen];
|
||||||
|
}
|
||||||
|
window[8] = '\0';
|
||||||
|
// Print at a free spot to the right of COM
|
||||||
|
// freq (col 250+, row 108).
|
||||||
|
framebufferFillRect(fb, 244, 107, 36, 8, COLOR_BLACK);
|
||||||
|
fontDrawString(fb, 246, 108, window, COLOR_WHITE);
|
||||||
|
// Roll every other frame to keep readable.
|
||||||
|
static uint8_t rollAccum;
|
||||||
|
rollAccum++;
|
||||||
|
if ((rollAccum & 1) == 0) {
|
||||||
|
atisIdx = (atisIdx + 1) % atisLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
radiosFormatFreq(radios->nav1Freq, RADIO_NAV1, freqBuf);
|
radiosFormatFreq(radios->nav1Freq, RADIO_NAV1, freqBuf);
|
||||||
drawReadout(fb, &readoutNav1, freqBuf);
|
drawReadout(fb, &readoutNav1, freqBuf);
|
||||||
// NAV2 vs ADF frequency share the same row; FS2 picks one per
|
// NAV2 vs ADF frequency share the same row; FS2 picks one per
|
||||||
|
|
@ -123,7 +159,7 @@ void panelDigitsDraw(FramebufferT *fb, const AircraftT *ac, const RadiosT *radio
|
||||||
drawReadout(fb, &readoutHeading, "---");
|
drawReadout(fb, &readoutHeading, "---");
|
||||||
drawReadout(fb, &readoutRecip, "---");
|
drawReadout(fb, &readoutRecip, "---");
|
||||||
} else {
|
} else {
|
||||||
int heading = ((int)ac->yaw * 360) / 256;
|
int heading = byteAngleToDegrees(ac->yaw);
|
||||||
heading %= 360;
|
heading %= 360;
|
||||||
snprintf(buf, sizeof(buf), "%03d", heading);
|
snprintf(buf, sizeof(buf), "%03d", heading);
|
||||||
drawReadout(fb, &readoutHeading, buf);
|
drawReadout(fb, &readoutHeading, buf);
|
||||||
|
|
@ -134,8 +170,8 @@ void panelDigitsDraw(FramebufferT *fb, const AircraftT *ac, const RadiosT *radio
|
||||||
|
|
||||||
// VOR1 / VOR2 OBS course + reciprocal. obs is byte angle;
|
// VOR1 / VOR2 OBS course + reciprocal. obs is byte angle;
|
||||||
// convert to degrees 0..359.
|
// convert to degrees 0..359.
|
||||||
int obs1Deg = ((int)radios->nav1Obs * 360) / 256;
|
int obs1Deg = byteAngleToDegrees(radios->nav1Obs);
|
||||||
int obs2Deg = ((int)radios->nav2Obs * 360) / 256;
|
int obs2Deg = byteAngleToDegrees(radios->nav2Obs);
|
||||||
snprintf(buf, sizeof(buf), "%03d", obs1Deg);
|
snprintf(buf, sizeof(buf), "%03d", obs1Deg);
|
||||||
drawReadout(fb, &readoutVor1Course, buf);
|
drawReadout(fb, &readoutVor1Course, buf);
|
||||||
snprintf(buf, sizeof(buf), "%03d", (obs1Deg + 180) % 360);
|
snprintf(buf, sizeof(buf), "%03d", (obs1Deg + 180) % 360);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// NAV / COM / ADF radio state and lookups. See radios.h.
|
// NAV / COM / ADF radio state and lookups. See radios.h.
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include "fs2math.h"
|
||||||
#include "math6502.h"
|
#include "math6502.h"
|
||||||
#include "radios.h"
|
#include "radios.h"
|
||||||
|
|
||||||
|
|
@ -280,8 +281,7 @@ void radiosEnterDigit(RadiosT *r, RadioE which, uint8_t digit) {
|
||||||
khz = (khz % 100) * 10 + digit; // shift the
|
khz = (khz % 100) * 10 + digit; // shift the
|
||||||
// 3-digit display, dropping the leading digit and
|
// 3-digit display, dropping the leading digit and
|
||||||
// bringing the new digit in at the units place.
|
// bringing the new digit in at the units place.
|
||||||
if (khz < ADF_FREQ_MIN_KHZ) khz = ADF_FREQ_MIN_KHZ;
|
khz = fs2ClampInt(khz, ADF_FREQ_MIN_KHZ, ADF_FREQ_MAX_KHZ);
|
||||||
if (khz > ADF_FREQ_MAX_KHZ) khz = ADF_FREQ_MAX_KHZ;
|
|
||||||
r->adfFreq = khzToBcdAdf(khz);
|
r->adfFreq = khzToBcdAdf(khz);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -299,8 +299,7 @@ void radiosEnterDigit(RadiosT *r, RadioE which, uint8_t digit) {
|
||||||
hund = (hund / 5) * 5;
|
hund = (hund / 5) * 5;
|
||||||
int minHund = (which == RADIO_COM1) ? 11800 : 10800;
|
int minHund = (which == RADIO_COM1) ? 11800 : 10800;
|
||||||
int maxHund = (which == RADIO_COM1) ? 13695 : 11795;
|
int maxHund = (which == RADIO_COM1) ? 13695 : 11795;
|
||||||
if (hund < minHund) hund = minHund;
|
hund = fs2ClampInt(hund, minHund, maxHund);
|
||||||
if (hund > maxHund) hund = maxHund;
|
|
||||||
*freqPtr = intToBcdNavCom(hund);
|
*freqPtr = intToBcdNavCom(hund);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -346,9 +345,7 @@ void radiosUpdate(RadiosT *r, const AircraftT *ac) {
|
||||||
// mapped to a degree count for the CDI.
|
// mapped to a degree count for the CDI.
|
||||||
uint8_t fromBearing = (uint8_t)(r->nav1RelativeBearing + 128);
|
uint8_t fromBearing = (uint8_t)(r->nav1RelativeBearing + 128);
|
||||||
int8_t radialDelta = (int8_t)(fromBearing - r->nav1Obs);
|
int8_t radialDelta = (int8_t)(fromBearing - r->nav1Obs);
|
||||||
int devDeg = ((int)radialDelta * 360 + 128) / 256;
|
int devDeg = fs2ClampInt(((int)radialDelta * 360 + 128) / 256, -32, 32);
|
||||||
if (devDeg > 32) devDeg = 32;
|
|
||||||
if (devDeg < -32) devDeg = -32;
|
|
||||||
r->nav1NeedleDefl = (int8_t)devDeg;
|
r->nav1NeedleDefl = (int8_t)devDeg;
|
||||||
// TO/FROM: |radialDelta| < 64 (= 90 deg) means
|
// TO/FROM: |radialDelta| < 64 (= 90 deg) means
|
||||||
// the aircraft is on the OBS-radial half ->
|
// the aircraft is on the OBS-radial half ->
|
||||||
|
|
@ -373,9 +370,7 @@ void radiosUpdate(RadiosT *r, const AircraftT *ac) {
|
||||||
r->nav2Dme = (uint16_t)((int32_t)d / AC_SCENERY_UNITS_PER_NM);
|
r->nav2Dme = (uint16_t)((int32_t)d / AC_SCENERY_UNITS_PER_NM);
|
||||||
uint8_t fromBearing = (uint8_t)(r->nav2RelativeBearing + 128);
|
uint8_t fromBearing = (uint8_t)(r->nav2RelativeBearing + 128);
|
||||||
int8_t radialDelta = (int8_t)(fromBearing - r->nav2Obs);
|
int8_t radialDelta = (int8_t)(fromBearing - r->nav2Obs);
|
||||||
int devDeg = ((int)radialDelta * 360 + 128) / 256;
|
int devDeg = fs2ClampInt(((int)radialDelta * 360 + 128) / 256, -32, 32);
|
||||||
if (devDeg > 32) devDeg = 32;
|
|
||||||
if (devDeg < -32) devDeg = -32;
|
|
||||||
r->nav2NeedleDefl = (int8_t)devDeg;
|
r->nav2NeedleDefl = (int8_t)devDeg;
|
||||||
int adelta = radialDelta < 0 ? -radialDelta : radialDelta;
|
int adelta = radialDelta < 0 ? -radialDelta : radialDelta;
|
||||||
r->nav2Flag = (adelta < 64) ? VOR_FLAG_FR : VOR_FLAG_TO;
|
r->nav2Flag = (adelta < 64) ? VOR_FLAG_FR : VOR_FLAG_TO;
|
||||||
|
|
|
||||||
|
|
@ -258,16 +258,6 @@ void rendererSetFillColors(RenderStateT *state, ColorE fill, ColorE altFill) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void rendererSwapFillColors(RenderStateT *state) {
|
|
||||||
ColorE tmp = state->fillColor;
|
|
||||||
state->fillColor = state->altFillColor;
|
|
||||||
state->altFillColor = tmp;
|
|
||||||
uint8_t htmp = state->hiresFill;
|
|
||||||
state->hiresFill = state->hiresAltFill;
|
|
||||||
state->hiresAltFill = htmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Set the chunk5 hires color code directly. Used by the chunk5 $12
|
// Set the chunk5 hires color code directly. Used by the chunk5 $12
|
||||||
// SetColor handler so the hires bitplane gets bit-faithful color
|
// SetColor handler so the hires bitplane gets bit-faithful color
|
||||||
// (rather than going through the modern palette mapping).
|
// (rather than going through the modern palette mapping).
|
||||||
|
|
|
||||||
|
|
@ -12,31 +12,36 @@
|
||||||
|
|
||||||
|
|
||||||
typedef struct RegionMetaT {
|
typedef struct RegionMetaT {
|
||||||
const char *fileName; // basename inside downloads/scenery/extracted/
|
const char *fileName; // RAM-dump basename (sceneryRam_<fileName>.bin)
|
||||||
|
const char *sdSourceFile; // demand-load .SD source in downloads/scenery/extracted/
|
||||||
const char *displayName;
|
const char *displayName;
|
||||||
} RegionMetaT;
|
} RegionMetaT;
|
||||||
|
|
||||||
|
|
||||||
|
// The four FS2.1 region variants are different city subsections of the
|
||||||
|
// SAME base disk image. They all share `FS2.1` as the demand-load
|
||||||
|
// source so the HEADER opcode can pull section bytes from disk at
|
||||||
|
// runtime regardless of which city was the boot region.
|
||||||
static const RegionMetaT regionMeta[SCENERY_REGION_COUNT] = {
|
static const RegionMetaT regionMeta[SCENERY_REGION_COUNT] = {
|
||||||
[SCENERY_NONE] = { NULL, "(no scenery)" },
|
[SCENERY_NONE] = { NULL, NULL, "(no scenery)" },
|
||||||
[SCENERY_FS2_1] = { "FS2.1", "FS2 base disk - WWI Ace training" },
|
[SCENERY_FS2_1] = { "FS2.1", "FS2.1", "FS2 base disk - WWI Ace training" },
|
||||||
[SCENERY_FS2_1_CHICAGO] = { "FS2.1_chicago", "FS2 base disk - Chicago / Meigs Field" },
|
[SCENERY_FS2_1_CHICAGO] = { "FS2.1_chicago", "FS2.1", "FS2 base disk - Chicago / Meigs Field" },
|
||||||
[SCENERY_FS2_1_LA] = { "FS2.1_la", "FS2 base disk - Los Angeles" },
|
[SCENERY_FS2_1_LA] = { "FS2.1_la", "FS2.1", "FS2 base disk - Los Angeles" },
|
||||||
[SCENERY_FS2_1_SEATTLE] = { "FS2.1_seattle", "FS2 base disk - Seattle" },
|
[SCENERY_FS2_1_SEATTLE] = { "FS2.1_seattle", "FS2.1", "FS2 base disk - Seattle" },
|
||||||
[SCENERY_FS2_1_NY] = { "FS2.1_ny", "FS2 base disk - New York / Kennedy" },
|
[SCENERY_FS2_1_NY] = { "FS2.1_ny", "FS2.1", "FS2 base disk - New York / Kennedy" },
|
||||||
[SCENERY_SD1] = { "A2.SD1", "Dallas-Ft.Worth, Houston, San Antonio" },
|
[SCENERY_SD1] = { "A2.SD1", "A2.SD1", "Dallas-Ft.Worth, Houston, San Antonio" },
|
||||||
[SCENERY_SD2] = { "A2.SD2", "Phoenix, Albuquerque, El Paso" },
|
[SCENERY_SD2] = { "A2.SD2", "A2.SD2", "Phoenix, Albuquerque, El Paso" },
|
||||||
[SCENERY_SD3] = { "A2.SD3", "San Francisco, Los Angeles, Las Vegas" },
|
[SCENERY_SD3] = { "A2.SD3", "A2.SD3", "San Francisco, Los Angeles, Las Vegas" },
|
||||||
[SCENERY_SD4] = { "A2.SD4", "Klamath Falls, Seattle, Great Falls" },
|
[SCENERY_SD4] = { "A2.SD4", "A2.SD4", "Klamath Falls, Seattle, Great Falls" },
|
||||||
[SCENERY_SD5] = { "A2.SD5", "Salt Lake City, Cheyenne, Denver" },
|
[SCENERY_SD5] = { "A2.SD5", "A2.SD5", "Salt Lake City, Cheyenne, Denver" },
|
||||||
[SCENERY_SD6] = { "A2.SD6", "Omaha, Wichita, Kansas City" },
|
[SCENERY_SD6] = { "A2.SD6", "A2.SD6", "Omaha, Wichita, Kansas City" },
|
||||||
[SCENERY_SD7A] = { "A2.SD7A", "Washington, Charlotte" },
|
[SCENERY_SD7A] = { "A2.SD7A", "A2.SD7A", "Washington, Charlotte" },
|
||||||
[SCENERY_SD7B] = { "A2.SD7B", "Jacksonville, Miami" },
|
[SCENERY_SD7B] = { "A2.SD7B", "A2.SD7B", "Jacksonville, Miami" },
|
||||||
[SCENERY_SD11] = { "A2.SD11", "Lake Huron, Detroit" },
|
[SCENERY_SD11] = { "A2.SD11", "A2.SD11", "Lake Huron, Detroit" },
|
||||||
[SCENERY_SD13] = { "A2.SD13", "Japan - Tokyo, Osaka" },
|
[SCENERY_SD13] = { "A2.SD13", "A2.SD13", "Japan - Tokyo, Osaka" },
|
||||||
[SCENERY_SD14A] = { "A2.SD14A", "Western European Tour (UK, N. France)" },
|
[SCENERY_SD14A] = { "A2.SD14A", "A2.SD14A", "Western European Tour (UK, N. France)" },
|
||||||
[SCENERY_SD14B] = { "A2.SD14B", "Western European Tour (N. France, W. Germany)" },
|
[SCENERY_SD14B] = { "A2.SD14B", "A2.SD14B", "Western European Tour (N. France, W. Germany)" },
|
||||||
[SCENERY_SDS1] = { "A2.SDS1", "STAR San Francisco & The Bay Area" }
|
[SCENERY_SDS1] = { "A2.SDS1", "A2.SDS1", "STAR San Francisco & The Bay Area" }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -200,7 +205,12 @@ static bool loadRawSDFile(SceneryDataT *out, const RegionMetaT *meta) {
|
||||||
"../../downloads/scenery/extracted/",
|
"../../downloads/scenery/extracted/",
|
||||||
"/home/scott/claude/flight/downloads/scenery/extracted/"
|
"/home/scott/claude/flight/downloads/scenery/extracted/"
|
||||||
};
|
};
|
||||||
FILE *f = openFileSearch(meta->fileName, prefixes,
|
const char *sourceName = meta->sdSourceFile != NULL
|
||||||
|
? meta->sdSourceFile : meta->fileName;
|
||||||
|
if (sourceName == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FILE *f = openFileSearch(sourceName, prefixes,
|
||||||
sizeof(prefixes) / sizeof(prefixes[0]));
|
sizeof(prefixes) / sizeof(prefixes[0]));
|
||||||
if (f == NULL) {
|
if (f == NULL) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -340,3 +350,25 @@ const char *sceneryDataRegionName(SceneryRegionE region) {
|
||||||
}
|
}
|
||||||
return regionMeta[region].displayName;
|
return regionMeta[region].displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SceneryRegionE sceneryDataRegionFromName(const char *name) {
|
||||||
|
if (name == NULL) {
|
||||||
|
return SCENERY_NONE;
|
||||||
|
}
|
||||||
|
for (int i = SCENERY_NONE + 1; i < SCENERY_REGION_COUNT; i++) {
|
||||||
|
const char *fn = regionMeta[i].fileName;
|
||||||
|
if (fn == NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (strcmp(fn, name) == 0) {
|
||||||
|
return (SceneryRegionE)i;
|
||||||
|
}
|
||||||
|
// Accept the bare "SDxxx" / "SDS1" form without the
|
||||||
|
// leading "A2." that the on-disk filename carries.
|
||||||
|
if (strncmp(fn, "A2.", 3) == 0 && strcmp(fn + 3, name) == 0) {
|
||||||
|
return (SceneryRegionE)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SCENERY_NONE;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -346,19 +346,26 @@ static void doHeader(SceneryStateT *state) {
|
||||||
ram[0x08EA + i] = 0;
|
ram[0x08EA + i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SCENERY_DEMAND_LOAD env var gates the .SD demand-load. Set
|
// Demand-load the section's bytes from the .SD file into the
|
||||||
// to "0" to load from byte 0 of the section (the natural
|
// dispatcher's working RAM (mirrors chunk5 LA63A → chunk3's
|
||||||
// chunk4 ASM behaviour); set to a digit (e.g. "14") to apply a
|
// SmartPort `ReadBlocks` call). Section ID → file offset uses
|
||||||
// source-byte SKIP, which is what MAME's captured RAM appears
|
// ASM-faithful math from chunk4 `ComputeBlockFromSector` /
|
||||||
// to have done (RAM[$A887] matches sid $44 byte 14, not byte 0).
|
// `FetchSectorFromDisk`.
|
||||||
// The skip lets us experimentally find which entry point makes
|
//
|
||||||
// the chunk5 VM walk into the water-color SetColor at $B781.
|
// Without this, boot Meigs runs from the captured RAM image
|
||||||
// Default behaviour: don't demand-load (keep captured-RAM
|
// baked into `port/sceneryRam_FS2.1.bin` (which is byte-faithful
|
||||||
// residue, which is the only known-working path so far).
|
// for that one section) but new sections (= city disks, sectors
|
||||||
|
// outside the captured set, anything the dispatcher visits while
|
||||||
|
// flying past the initial cull bounds) get the RAM-residue
|
||||||
|
// bytes from before, which are stale.
|
||||||
|
//
|
||||||
|
// SCENERY_DEMAND_LOAD env still works as a source-byte SKIP
|
||||||
|
// for offline experiments; default (unset) is `srcSkip = 0` =
|
||||||
|
// load from byte 0 of each section (= the natural ASM
|
||||||
|
// behaviour).
|
||||||
const char *envDemandLoad = getenv("SCENERY_DEMAND_LOAD");
|
const char *envDemandLoad = getenv("SCENERY_DEMAND_LOAD");
|
||||||
if (state->sceneryFile != NULL && state->sceneryFileSize > 0
|
if (state->sceneryFile != NULL && state->sceneryFileSize > 0) {
|
||||||
&& envDemandLoad != NULL) {
|
int srcSkip = envDemandLoad != NULL ? atoi(envDemandLoad) : 0;
|
||||||
int srcSkip = atoi(envDemandLoad);
|
|
||||||
if (srcSkip < 0 || srcSkip > 64) srcSkip = 0;
|
if (srcSkip < 0 || srcSkip > 64) srcSkip = 0;
|
||||||
uint8_t count = ram[0x08E6];
|
uint8_t count = ram[0x08E6];
|
||||||
uint16_t dest = (uint16_t)ram[0x08E7]
|
uint16_t dest = (uint16_t)ram[0x08E7]
|
||||||
|
|
@ -407,6 +414,10 @@ static void doHeader(SceneryStateT *state) {
|
||||||
}
|
}
|
||||||
ram[addr] = state->sceneryFile[srcOff + i];
|
ram[addr] = state->sceneryFile[srcOff + i];
|
||||||
}
|
}
|
||||||
|
if (getenv("SCENERY_DEMAND_TRACE") != NULL) {
|
||||||
|
fprintf(stderr, "doHeader: demand-load sid=$%02X cnt=$%02X dest=$%04X srcOff=$%06X len=$%X\n",
|
||||||
|
sectionId, count, dest, srcOff, length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,12 +73,6 @@ static void enemyFireAtPlayer(WW1AceStateT *s, WW1EnemyT *e,
|
||||||
static void enemyManeuver(WW1AceStateT *s, WW1EnemyT *e,
|
static void enemyManeuver(WW1AceStateT *s, WW1EnemyT *e,
|
||||||
int32_t playerX, int32_t playerY, int32_t playerZ);
|
int32_t playerX, int32_t playerY, int32_t playerZ);
|
||||||
|
|
||||||
// Distance helpers operate on metres (Q16.16 worldUnit >> 16). Squaring
|
|
||||||
// would overflow int32 in Q16.16, so we drop to integer metres before
|
|
||||||
// squaring.
|
|
||||||
static int32_t metresFromQ1616(int32_t v_q1616);
|
|
||||||
|
|
||||||
|
|
||||||
// Tiny LCG (matches Mike Brennan's classic glibc parameters in spirit;
|
// Tiny LCG (matches Mike Brennan's classic glibc parameters in spirit;
|
||||||
// good enough for AI jitter).
|
// good enough for AI jitter).
|
||||||
static uint16_t rngNext(WW1AceStateT *s) {
|
static uint16_t rngNext(WW1AceStateT *s) {
|
||||||
|
|
@ -156,11 +150,6 @@ static void respawnEnemy(WW1AceStateT *s, WW1EnemyT *e, int32_t playerX, int32_t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static int32_t metresFromQ1616(int32_t v_q1616) {
|
|
||||||
return v_q1616 >> CAM_POS_FRACT_BITS;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ww1aceDrawWarReport(const WW1AceStateT *s, FramebufferT *fb) {
|
void ww1aceDrawWarReport(const WW1AceStateT *s, FramebufferT *fb) {
|
||||||
framebufferFillRect(fb, 0, 0, NATIVE_WIDTH, NATIVE_HEIGHT, COLOR_BLACK);
|
framebufferFillRect(fb, 0, 0, NATIVE_WIDTH, NATIVE_HEIGHT, COLOR_BLACK);
|
||||||
fontDrawString(fb, 60, 8, "***** WAR REPORT *****", COLOR_WHITE);
|
fontDrawString(fb, 60, 8, "***** WAR REPORT *****", COLOR_WHITE);
|
||||||
|
|
@ -183,30 +172,6 @@ void ww1aceDrawWarReport(const WW1AceStateT *s, FramebufferT *fb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ww1aceDropBomb(WW1AceStateT *s) {
|
|
||||||
if (!s->enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (s->bombs == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
s->bombs--;
|
|
||||||
// Drop a bomb from the player's reported position with a small
|
|
||||||
// downward velocity. Hit detection happens in updateBombs when
|
|
||||||
// the bomb impacts ground (Y <= 0). Caller passes player coords
|
|
||||||
// via ww1aceUpdate; we store nothing here since the player's
|
|
||||||
// world position is read from outside in ww1aceUpdate. For the
|
|
||||||
// drop, we use position (0, alt, 0) relative -- the next
|
|
||||||
// update tick we'll seed real coords. To keep the drop accurate
|
|
||||||
// we expose a second helper ww1aceDropBombAt below; this
|
|
||||||
// legacy entry just bumps the counter.
|
|
||||||
if (s->bombHits < 0xFFFF) {
|
|
||||||
// Pessimistic: don't auto-count as a hit anymore.
|
|
||||||
// Hits are awarded in updateBombs() on actual impact.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ww1aceDropBombAt(WW1AceStateT *s, int32_t playerX, int32_t playerY, int32_t playerZ,
|
void ww1aceDropBombAt(WW1AceStateT *s, int32_t playerX, int32_t playerY, int32_t playerZ,
|
||||||
int16_t playerVelX_q88, int16_t playerVelZ_q88) {
|
int16_t playerVelX_q88, int16_t playerVelZ_q88) {
|
||||||
if (!s->enabled || s->bombs == 0) {
|
if (!s->enabled || s->bombs == 0) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue