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.
|
||||
#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
|
||||
// `InstrumentOperationalFlags` (init $FF = all good). The
|
||||
// chunk3 `FailureProcTable` clears one of these bits or sets one of
|
||||
|
|
|
|||
|
|
@ -25,6 +25,22 @@
|
|||
#define CAM_ROT_FRACT_BITS 15
|
||||
#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 {
|
||||
int32_t worldX; // Q16.16 world units
|
||||
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.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -61,4 +61,22 @@ uint8_t fs2VsiNeedlePos(int16_t value16);
|
|||
// `UpdateSlipSkidIndicator` (chunk4 L2497) consumes.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ void rendererBegin(RenderStateT *state, FramebufferT *fb);
|
|||
void rendererSetDrawColor(RenderStateT *state, ColorE color);
|
||||
void rendererSetHiresColor(RenderStateT *state, uint8_t hiresCode);
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -65,4 +65,10 @@ void sceneryDataFree(SceneryDataT *out);
|
|||
// Human-readable name for a region. Always returns a non-NULL string.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -22,16 +22,6 @@
|
|||
// Default scale factor (window pixels per native pixel).
|
||||
#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
|
||||
// the layout used by the original disassembly so the clipper logic
|
||||
// can be ported one-for-one.
|
||||
|
|
|
|||
|
|
@ -97,11 +97,6 @@ void ww1aceInit(WW1AceStateT *s);
|
|||
// score/bomb counters. Player coords are Q16.16 world-units.
|
||||
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
|
||||
// horizontal velocity (Q8.8 metres/frame). The bomb then falls under
|
||||
// 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 RUDDER_DECAY_K 179 // ~0.70
|
||||
|
||||
// Speed envelope.
|
||||
#define MAX_FORWARD_SPEED_Q88 410 // 1.6 world-units / frame
|
||||
// Speed envelope. AC_MAX_FORWARD_SPEED_Q88 lives in aircraft.h so
|
||||
// audio.c can read it without duplicating the literal.
|
||||
#define DRAG_K 1 // 1/256 ~ 0.4%
|
||||
|
||||
// Lift / gravity.
|
||||
|
|
@ -217,26 +217,23 @@ void aircraftInit(AircraftT *ac) {
|
|||
|
||||
|
||||
static AircraftT recoveryBuf;
|
||||
static bool recoveryValid;
|
||||
|
||||
|
||||
void aircraftArmRecovery(AircraftT *ac) {
|
||||
recoveryBuf = *ac;
|
||||
recoveryValid = true;
|
||||
// Set the snapshot flag before capturing so the restored state
|
||||
// also reports armed -- successive crashes can reuse the same
|
||||
// snapshot without needing another arm pass.
|
||||
ac->hasRecoverySnapshot = true;
|
||||
recoveryBuf = *ac;
|
||||
}
|
||||
|
||||
|
||||
void aircraftRestoreRecovery(AircraftT *ac) {
|
||||
if (!recoveryValid) {
|
||||
if (!ac->hasRecoverySnapshot) {
|
||||
return;
|
||||
}
|
||||
// Preserve the recovery-armed flag so further crashes can use
|
||||
// the same snapshot.
|
||||
bool hasSnap = ac->hasRecoverySnapshot;
|
||||
*ac = recoveryBuf;
|
||||
ac->hasRecoverySnapshot = hasSnap;
|
||||
ac->crashed = false;
|
||||
ac->crashed = false;
|
||||
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).
|
||||
// Trim biases yokeVert -- chunk5 RefreshElevatorIndicator
|
||||
// accumulates trim into the elevator command.
|
||||
int yokePlusTrim = (int)(int8_t)ac->yokeVert + (int)(int8_t)ac->trim;
|
||||
if (yokePlusTrim > 127) yokePlusTrim = 127;
|
||||
if (yokePlusTrim < -127) yokePlusTrim = -127;
|
||||
int yokePlusTrim = fs2ClampInt((int)(int8_t)ac->yokeVert + (int)(int8_t)ac->trim, -127, 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);
|
||||
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) {
|
||||
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 dragDelta = (int16_t)(((int32_t)ac->forwardSpeed * DRAG_K) >> 8);
|
||||
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) {
|
||||
if (audio.device == 0) {
|
||||
return;
|
||||
|
|
@ -207,9 +202,13 @@ void audioUpdate(const AircraftT *ac) {
|
|||
// single SDL_LockAudioDevice store; the audio synth itself
|
||||
// still runs in float because SDL streams float samples.
|
||||
// speedFraction : Q8.8 from 0..256 (= 0.0..1.0)
|
||||
int speedT = (int)ac->forwardSpeed * 256 / AUDIO_SPEED_FULL_Q88;
|
||||
if (speedT < 0) speedT = 0;
|
||||
if (speedT > 256) speedT = 256;
|
||||
int speedT = (int)ac->forwardSpeed * 256 / AC_MAX_FORWARD_SPEED_Q88;
|
||||
if (speedT < 0) {
|
||||
speedT = 0;
|
||||
}
|
||||
if (speedT > 256) {
|
||||
speedT = 256;
|
||||
}
|
||||
// freqQ88 = base + speedT * (max - base) / 256
|
||||
int32_t freqRange = ENGINE_MAX_HZ - ENGINE_BASE_HZ;
|
||||
int32_t freqQ88 = ((int32_t)ENGINE_BASE_HZ << 8)
|
||||
|
|
@ -271,7 +270,9 @@ void audioUpdate(const AircraftT *ac) {
|
|||
if (!ac->onGround) {
|
||||
float speedFrac = (float)speedT / 256.0f;
|
||||
windAmp = sqrtf(speedFrac);
|
||||
if (windAmp > 1.0f) windAmp = 1.0f;
|
||||
if (windAmp > 1.0f) {
|
||||
windAmp = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_LockAudioDevice(audio.device);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "camera.h"
|
||||
#include "chunk5Setup.h"
|
||||
#include "fs2math.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.
|
||||
void cameraGet2x3Matrix(const CameraT *cam, int8_t outRowX[3], int8_t outRowZ[3]) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int x = cam->rot[i][0] >> 8;
|
||||
int z = cam->rot[i][2] >> 8;
|
||||
if (x > 127) x = 127;
|
||||
if (x < -128) x = -128;
|
||||
if (z > 127) z = 127;
|
||||
if (z < -128) z = -128;
|
||||
int x = fs2ClampInt(cam->rot[i][0] >> 8, -128, 127);
|
||||
int z = fs2ClampInt(cam->rot[i][2] >> 8, -128, 127);
|
||||
outRowX[i] = (int8_t)x;
|
||||
outRowZ[i] = (int8_t)z;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@
|
|||
#include <stdio.h>
|
||||
#include "editor.h"
|
||||
#include "aircraft.h"
|
||||
#include "camera.h"
|
||||
#include "font.h"
|
||||
#include "framebuffer.h"
|
||||
#include "fs2math.h"
|
||||
#include "radios.h"
|
||||
#include "timeOfDay.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,
|
||||
const AircraftT *ac, const RadiosT *radios,
|
||||
const TimeOfDayT *tod, const WindStateT *wind);
|
||||
|
|
@ -126,28 +127,6 @@ static void modifyField(FieldIndexE fi, int step,
|
|||
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,
|
||||
const AircraftT *ac, const RadiosT *radios,
|
||||
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_Y: snprintf(buf, n, "%6d", metresFromQ1616(ac->worldY)); 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_PITCH: snprintf(buf, n, "%6d", (ac->pitch * 360) / 256); break;
|
||||
case F_BANK: snprintf(buf, n, "%6d", (ac->bank * 360) / 256); break;
|
||||
case F_YAW: snprintf(buf, n, "%6d", byteAngleToDegrees(ac->yaw)); break;
|
||||
case F_PITCH: snprintf(buf, n, "%6d", byteAngleToDegrees(ac->pitch)); 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_MIXTURE: snprintf(buf, n, "%6d", (ac->mixture * 100) / 256); 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]);
|
||||
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_REALITY: snprintf(buf, n, "%6s", ac->realityMode ? "ON" : "OFF"); 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_COM1_FREQ: snprintf(buf, n, "$%04X", radios->com1Freq); 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_NAV2_OBS: snprintf(buf, n, "%6d", (radios->nav2Obs * 360) / 256); break;
|
||||
case F_NAV1_OBS: snprintf(buf, n, "%6d", byteAngleToDegrees(radios->nav1Obs)); break;
|
||||
case F_NAV2_OBS: snprintf(buf, n, "%6d", byteAngleToDegrees(radios->nav2Obs)); break;
|
||||
default: buf[0] = '\0'; break;
|
||||
}
|
||||
}
|
||||
|
|
@ -194,19 +173,19 @@ static void modifyField(FieldIndexE fi, int step,
|
|||
switch (fi) {
|
||||
case F_WORLD_X: {
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case F_WORLD_Y: {
|
||||
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);
|
||||
break;
|
||||
}
|
||||
case F_WORLD_Z: {
|
||||
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);
|
||||
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_BANK: ac->bank = (uint8_t)(ac->bank + step); break;
|
||||
case F_THROTTLE: {
|
||||
int v = applyStep(ac->throttle, step, 0, 255);
|
||||
int v = fs2StepClamp(ac->throttle, step, 0, 255);
|
||||
ac->throttle = (uint8_t)v;
|
||||
break;
|
||||
}
|
||||
case F_MIXTURE: ac->mixture = (uint8_t)applyStep(ac->mixture, step, 0, 255); break;
|
||||
case F_MAGNETOS: ac->magnetos = (uint8_t)applyStep(ac->magnetos, step, 0, 4); break;
|
||||
case F_MIXTURE: ac->mixture = (uint8_t)fs2StepClamp(ac->mixture, step, 0, 255); 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_FUEL_L: ac->fuelLeft = (uint8_t)applyStep(ac->fuelLeft, step, 0, 255); break;
|
||||
case F_FUEL_R: ac->fuelRight = (uint8_t)applyStep(ac->fuelRight, step, 0, 255); break;
|
||||
case F_FLAPS: ac->flaps = (uint8_t)applyStep(ac->flaps, 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)fs2StepClamp(ac->fuelRight, step, 0, 255); break;
|
||||
case F_FLAPS: ac->flaps = (uint8_t)fs2StepClamp(ac->flaps, step, 0, 255); break;
|
||||
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;
|
||||
break;
|
||||
}
|
||||
case F_LIGHTS: ac->lightsOn = !ac->lightsOn; break;
|
||||
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);
|
||||
break;
|
||||
}
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
|
@ -247,7 +226,7 @@ static void modifyField(FieldIndexE fi, int 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_NAV1_FREQ: radios->nav1Freq = (uint16_t)(radios->nav1Freq + 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;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
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_Y 173
|
||||
|
||||
#define DEG2RAD 0.01745329251994f
|
||||
|
||||
|
||||
typedef struct PixelListT {
|
||||
uint8_t count;
|
||||
|
|
@ -68,7 +66,6 @@ typedef struct 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 drawFailX(FramebufferT *fb, const GaugeT *g);
|
||||
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 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,
|
||||
// throttle, elevator). Each is a small pixel-list sprite that slides
|
||||
// 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
|
||||
// aircraft.c as (yawRate - coordinated-turn yawRate); divide
|
||||
// down to the same -127..+127 range fs2SlipSkidIndex expects.
|
||||
int32_t ssClamped = ac->sideslip_q88 / 8;
|
||||
if (ssClamped > 127) ssClamped = 127;
|
||||
if (ssClamped < -127) ssClamped = -127;
|
||||
int32_t ssClamped = fs2ClampInt(ac->sideslip_q88 / 8, -127, 127);
|
||||
uint8_t idx = fs2SlipSkidIndex((int8_t)ssClamped);
|
||||
int16_t anchorX = (int16_t)((int)idx + 0x0E);
|
||||
drawPixelList(fb, anchorX, 0xB4, &plBall, COLOR_WHITE);
|
||||
|
|
@ -403,6 +388,11 @@ void instrumentsDrawAll(FramebufferT *fb, const AircraftT *ac, const RadiosT *ra
|
|||
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.
|
||||
drawControlIndicators(fb, ac);
|
||||
|
||||
|
|
@ -415,10 +405,10 @@ void instrumentsDrawAll(FramebufferT *fb, const AircraftT *ac, const RadiosT *ra
|
|||
}
|
||||
|
||||
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) {
|
||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 4, "VNE", COLOR_WHITE);
|
||||
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 4, "VNE", COLOR_WHITE);
|
||||
}
|
||||
if (ac->crashed) {
|
||||
// Mirrors FS2 chunk3 `HandleCrashOrSplash` /
|
||||
|
|
@ -434,25 +424,25 @@ void instrumentsDrawAll(FramebufferT *fb, const AircraftT *ac, const RadiosT *ra
|
|||
case CRASH_NONE:
|
||||
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) {
|
||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 24, "DEMO", COLOR_WHITE);
|
||||
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 24, "DEMO", COLOR_WHITE);
|
||||
}
|
||||
if (ac->editMode) {
|
||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 24, "EDIT", COLOR_WHITE);
|
||||
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 24, "EDIT", COLOR_WHITE);
|
||||
}
|
||||
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
|
||||
// bits; we surface them as a status line so the pilot has
|
||||
// something visible to react to.
|
||||
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) {
|
||||
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
|
||||
// (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;
|
||||
default: break;
|
||||
}
|
||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 74, mag, COLOR_ORANGE);
|
||||
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 74, mag, COLOR_ORANGE);
|
||||
}
|
||||
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.
|
||||
|
|
@ -552,13 +542,13 @@ void instrumentsDrawAll(FramebufferT *fb, const AircraftT *ac, const RadiosT *ra
|
|||
}
|
||||
}
|
||||
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) {
|
||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 104, "CARB HEAT", COLOR_ORANGE);
|
||||
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 104, "CARB HEAT", COLOR_ORANGE);
|
||||
}
|
||||
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) {
|
||||
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;
|
||||
default: break;
|
||||
}
|
||||
drawCenteredString(fb, NATIVE_WIDTH / 2, 34, label, COLOR_WHITE);
|
||||
fontDrawStringCentered(fb, NATIVE_WIDTH / 2, 34, label, COLOR_WHITE);
|
||||
}
|
||||
if (ac->slewMode) {
|
||||
// FS2 chunk3 `DrawSlewOverlays`: " 00000 NORTH " at row
|
||||
// 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) {
|
||||
char buf[24];
|
||||
// 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;
|
||||
}
|
||||
|
||||
int defl = deflectionDeg;
|
||||
if (defl > VOR_CDI_FULL_DEG) defl = VOR_CDI_FULL_DEG;
|
||||
if (defl < -VOR_CDI_FULL_DEG) defl = -VOR_CDI_FULL_DEG;
|
||||
int defl = fs2ClampInt(deflectionDeg, -VOR_CDI_FULL_DEG, VOR_CDI_FULL_DEG);
|
||||
int xOffset = (defl * VOR_CDI_HALFWIDTH) / VOR_CDI_FULL_DEG;
|
||||
int16_t nx = (int16_t)(centreX + xOffset);
|
||||
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 "renderer.h"
|
||||
#include "chunk5Transform.h"
|
||||
#include "fs2math.h"
|
||||
#include "sceneryData.h"
|
||||
#include "sceneryProjection.h"
|
||||
#include "sceneryVm.h"
|
||||
|
|
@ -400,42 +401,9 @@ static int runScreenshot(const char *path) {
|
|||
SceneryRegionE shotRegion = SCENERY_FS2_1;
|
||||
const char *regionEnv = getenv("SCENERY_REGION");
|
||||
if (regionEnv != NULL) {
|
||||
if (strcmp(regionEnv, "FS2.1") == 0) {
|
||||
shotRegion = SCENERY_FS2_1;
|
||||
} else if (strcmp(regionEnv, "FS2.1_chicago") == 0) {
|
||||
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;
|
||||
SceneryRegionE r = sceneryDataRegionFromName(regionEnv);
|
||||
if (r != SCENERY_NONE) {
|
||||
shotRegion = r;
|
||||
}
|
||||
}
|
||||
sceneryDataLoad(shotRegion, &screenshotScenery);
|
||||
|
|
@ -917,6 +885,63 @@ int main(int argc, char **argv) {
|
|||
sceneryDataFree(&scenery);
|
||||
}
|
||||
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
|
||||
// accumulate. Trim biases the pitch yoke output.
|
||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_HOME) {
|
||||
int t = (int)aircraft.trim - 4;
|
||||
if (t < -127) t = -127;
|
||||
aircraft.trim = (int8_t)t;
|
||||
aircraft.trim = (int8_t)fs2StepClamp(aircraft.trim, -4, -127, 127);
|
||||
}
|
||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_END) {
|
||||
int t = (int)aircraft.trim + 4;
|
||||
if (t > 127) t = 127;
|
||||
aircraft.trim = (int8_t)t;
|
||||
aircraft.trim = (int8_t)fs2StepClamp(aircraft.trim, +4, -127, 127);
|
||||
}
|
||||
// Flap controls (chunk5 UpdateFlapsIndicator).
|
||||
// INSERT extends flaps in 32-byte-unit clicks;
|
||||
// DELETE retracts.
|
||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_INSERT) {
|
||||
int f = (int)aircraft.flaps + 32;
|
||||
if (f > 255) f = 255;
|
||||
aircraft.flaps = (uint8_t)f;
|
||||
aircraft.flaps = (uint8_t)fs2StepClamp(aircraft.flaps, +32, 0, 255);
|
||||
}
|
||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_DELETE) {
|
||||
int f = (int)aircraft.flaps - 32;
|
||||
if (f < 0) f = 0;
|
||||
aircraft.flaps = (uint8_t)f;
|
||||
aircraft.flaps = (uint8_t)fs2StepClamp(aircraft.flaps, -32, 0, 255);
|
||||
}
|
||||
// Mixture controls (chunk5 UpdateMixtureControl).
|
||||
// PAGEUP+Shift = richer, PAGEDN+Shift = leaner.
|
||||
if (ev.type == SDL_KEYDOWN && (ev.key.keysym.mod & KMOD_SHIFT)
|
||||
&& ev.key.keysym.sym == SDLK_PAGEUP) {
|
||||
int m = (int)aircraft.mixture + 16;
|
||||
if (m > 255) m = 255;
|
||||
aircraft.mixture = (uint8_t)m;
|
||||
aircraft.mixture = (uint8_t)fs2StepClamp(aircraft.mixture, +16, 0, 255);
|
||||
}
|
||||
if (ev.type == SDL_KEYDOWN && (ev.key.keysym.mod & KMOD_SHIFT)
|
||||
&& ev.key.keysym.sym == SDLK_PAGEDOWN) {
|
||||
int m = (int)aircraft.mixture - 16;
|
||||
if (m < 0) m = 0;
|
||||
aircraft.mixture = (uint8_t)m;
|
||||
aircraft.mixture = (uint8_t)fs2StepClamp(aircraft.mixture, -16, 0, 255);
|
||||
}
|
||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_BACKQUOTE) {
|
||||
aircraft.radarView = !aircraft.radarView;
|
||||
}
|
||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_LEFTBRACKET
|
||||
&& aircraft.radarView) {
|
||||
int z = (int)aircraft.radarZoom * 3 / 2;
|
||||
if (z > 32767) {
|
||||
z = 32767;
|
||||
}
|
||||
aircraft.radarZoom = (int16_t)z;
|
||||
aircraft.radarZoom = (int16_t)fs2ClampInt((int)aircraft.radarZoom * 3 / 2, 128, 32767);
|
||||
}
|
||||
if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_RIGHTBRACKET
|
||||
&& aircraft.radarView) {
|
||||
int z = (int)aircraft.radarZoom * 2 / 3;
|
||||
if (z < 128) { // 0.5 in Q8.8
|
||||
z = 128;
|
||||
}
|
||||
aircraft.radarZoom = (int16_t)z;
|
||||
aircraft.radarZoom = (int16_t)fs2ClampInt((int)aircraft.radarZoom * 2 / 3, 128, 32767);
|
||||
}
|
||||
// Joystick button events. Trigger (0) fires
|
||||
// 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) {
|
||||
SDL_Keycode k = ev.key.keysym.sym;
|
||||
int digit = -1;
|
||||
if (k >= SDLK_0 && k <= SDLK_9) digit = (int)(k - SDLK_0);
|
||||
else if (k >= SDLK_KP_0 && k <= SDLK_KP_9) digit = (int)(k - SDLK_KP_0);
|
||||
if (k >= SDLK_0 && k <= SDLK_9) {
|
||||
digit = (int)(k - SDLK_0);
|
||||
} else if (k >= SDLK_KP_0 && k <= SDLK_KP_9) {
|
||||
digit = (int)(k - SDLK_KP_0);
|
||||
}
|
||||
if (digit >= 0) {
|
||||
RadioE w = (radioInputMode == RADIO_INPUT_NAV1) ? RADIO_NAV1
|
||||
: (radioInputMode == RADIO_INPUT_NAV2) ? RADIO_NAV2
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "camera.h"
|
||||
#include "font.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 clearH = FONT_HEIGHT + 1;
|
||||
framebufferFillRect(fb, (int16_t)(r->x - 2), (int16_t)(r->y - 1), (int16_t)(clearW + 2), clearH, COLOR_BLACK);
|
||||
|
||||
// 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);
|
||||
fontDrawStringCentered(fb, (int16_t)(r->x + clearW / 2), r->y, text, COLOR_WHITE);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -89,6 +82,49 @@ void panelDigitsDraw(FramebufferT *fb, const AircraftT *ac, const RadiosT *radio
|
|||
char freqBuf[8];
|
||||
radiosFormatFreq(radios->com1Freq, RADIO_COM1, 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);
|
||||
drawReadout(fb, &readoutNav1, freqBuf);
|
||||
// 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, &readoutRecip, "---");
|
||||
} else {
|
||||
int heading = ((int)ac->yaw * 360) / 256;
|
||||
int heading = byteAngleToDegrees(ac->yaw);
|
||||
heading %= 360;
|
||||
snprintf(buf, sizeof(buf), "%03d", heading);
|
||||
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;
|
||||
// convert to degrees 0..359.
|
||||
int obs1Deg = ((int)radios->nav1Obs * 360) / 256;
|
||||
int obs2Deg = ((int)radios->nav2Obs * 360) / 256;
|
||||
int obs1Deg = byteAngleToDegrees(radios->nav1Obs);
|
||||
int obs2Deg = byteAngleToDegrees(radios->nav2Obs);
|
||||
snprintf(buf, sizeof(buf), "%03d", obs1Deg);
|
||||
drawReadout(fb, &readoutVor1Course, buf);
|
||||
snprintf(buf, sizeof(buf), "%03d", (obs1Deg + 180) % 360);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// NAV / COM / ADF radio state and lookups. See radios.h.
|
||||
|
||||
#include <stddef.h>
|
||||
#include "fs2math.h"
|
||||
#include "math6502.h"
|
||||
#include "radios.h"
|
||||
|
||||
|
|
@ -280,8 +281,7 @@ void radiosEnterDigit(RadiosT *r, RadioE which, uint8_t digit) {
|
|||
khz = (khz % 100) * 10 + digit; // shift the
|
||||
// 3-digit display, dropping the leading digit and
|
||||
// bringing the new digit in at the units place.
|
||||
if (khz < ADF_FREQ_MIN_KHZ) khz = ADF_FREQ_MIN_KHZ;
|
||||
if (khz > ADF_FREQ_MAX_KHZ) khz = ADF_FREQ_MAX_KHZ;
|
||||
khz = fs2ClampInt(khz, ADF_FREQ_MIN_KHZ, ADF_FREQ_MAX_KHZ);
|
||||
r->adfFreq = khzToBcdAdf(khz);
|
||||
return;
|
||||
}
|
||||
|
|
@ -299,8 +299,7 @@ void radiosEnterDigit(RadiosT *r, RadioE which, uint8_t digit) {
|
|||
hund = (hund / 5) * 5;
|
||||
int minHund = (which == RADIO_COM1) ? 11800 : 10800;
|
||||
int maxHund = (which == RADIO_COM1) ? 13695 : 11795;
|
||||
if (hund < minHund) hund = minHund;
|
||||
if (hund > maxHund) hund = maxHund;
|
||||
hund = fs2ClampInt(hund, minHund, maxHund);
|
||||
*freqPtr = intToBcdNavCom(hund);
|
||||
}
|
||||
|
||||
|
|
@ -346,9 +345,7 @@ void radiosUpdate(RadiosT *r, const AircraftT *ac) {
|
|||
// mapped to a degree count for the CDI.
|
||||
uint8_t fromBearing = (uint8_t)(r->nav1RelativeBearing + 128);
|
||||
int8_t radialDelta = (int8_t)(fromBearing - r->nav1Obs);
|
||||
int devDeg = ((int)radialDelta * 360 + 128) / 256;
|
||||
if (devDeg > 32) devDeg = 32;
|
||||
if (devDeg < -32) devDeg = -32;
|
||||
int devDeg = fs2ClampInt(((int)radialDelta * 360 + 128) / 256, -32, 32);
|
||||
r->nav1NeedleDefl = (int8_t)devDeg;
|
||||
// TO/FROM: |radialDelta| < 64 (= 90 deg) means
|
||||
// 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);
|
||||
uint8_t fromBearing = (uint8_t)(r->nav2RelativeBearing + 128);
|
||||
int8_t radialDelta = (int8_t)(fromBearing - r->nav2Obs);
|
||||
int devDeg = ((int)radialDelta * 360 + 128) / 256;
|
||||
if (devDeg > 32) devDeg = 32;
|
||||
if (devDeg < -32) devDeg = -32;
|
||||
int devDeg = fs2ClampInt(((int)radialDelta * 360 + 128) / 256, -32, 32);
|
||||
r->nav2NeedleDefl = (int8_t)devDeg;
|
||||
int adelta = radialDelta < 0 ? -radialDelta : radialDelta;
|
||||
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
|
||||
// SetColor handler so the hires bitplane gets bit-faithful color
|
||||
// (rather than going through the modern palette mapping).
|
||||
|
|
|
|||
|
|
@ -12,31 +12,36 @@
|
|||
|
||||
|
||||
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;
|
||||
} 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] = {
|
||||
[SCENERY_NONE] = { NULL, "(no scenery)" },
|
||||
[SCENERY_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_LA] = { "FS2.1_la", "FS2 base disk - Los Angeles" },
|
||||
[SCENERY_FS2_1_SEATTLE] = { "FS2.1_seattle", "FS2 base disk - Seattle" },
|
||||
[SCENERY_FS2_1_NY] = { "FS2.1_ny", "FS2 base disk - New York / Kennedy" },
|
||||
[SCENERY_SD1] = { "A2.SD1", "Dallas-Ft.Worth, Houston, San Antonio" },
|
||||
[SCENERY_SD2] = { "A2.SD2", "Phoenix, Albuquerque, El Paso" },
|
||||
[SCENERY_SD3] = { "A2.SD3", "San Francisco, Los Angeles, Las Vegas" },
|
||||
[SCENERY_SD4] = { "A2.SD4", "Klamath Falls, Seattle, Great Falls" },
|
||||
[SCENERY_SD5] = { "A2.SD5", "Salt Lake City, Cheyenne, Denver" },
|
||||
[SCENERY_SD6] = { "A2.SD6", "Omaha, Wichita, Kansas City" },
|
||||
[SCENERY_SD7A] = { "A2.SD7A", "Washington, Charlotte" },
|
||||
[SCENERY_SD7B] = { "A2.SD7B", "Jacksonville, Miami" },
|
||||
[SCENERY_SD11] = { "A2.SD11", "Lake Huron, Detroit" },
|
||||
[SCENERY_SD13] = { "A2.SD13", "Japan - Tokyo, Osaka" },
|
||||
[SCENERY_SD14A] = { "A2.SD14A", "Western European Tour (UK, N. France)" },
|
||||
[SCENERY_SD14B] = { "A2.SD14B", "Western European Tour (N. France, W. Germany)" },
|
||||
[SCENERY_SDS1] = { "A2.SDS1", "STAR San Francisco & The Bay Area" }
|
||||
[SCENERY_NONE] = { NULL, NULL, "(no scenery)" },
|
||||
[SCENERY_FS2_1] = { "FS2.1", "FS2.1", "FS2 base disk - WWI Ace training" },
|
||||
[SCENERY_FS2_1_CHICAGO] = { "FS2.1_chicago", "FS2.1", "FS2 base disk - Chicago / Meigs Field" },
|
||||
[SCENERY_FS2_1_LA] = { "FS2.1_la", "FS2.1", "FS2 base disk - Los Angeles" },
|
||||
[SCENERY_FS2_1_SEATTLE] = { "FS2.1_seattle", "FS2.1", "FS2 base disk - Seattle" },
|
||||
[SCENERY_FS2_1_NY] = { "FS2.1_ny", "FS2.1", "FS2 base disk - New York / Kennedy" },
|
||||
[SCENERY_SD1] = { "A2.SD1", "A2.SD1", "Dallas-Ft.Worth, Houston, San Antonio" },
|
||||
[SCENERY_SD2] = { "A2.SD2", "A2.SD2", "Phoenix, Albuquerque, El Paso" },
|
||||
[SCENERY_SD3] = { "A2.SD3", "A2.SD3", "San Francisco, Los Angeles, Las Vegas" },
|
||||
[SCENERY_SD4] = { "A2.SD4", "A2.SD4", "Klamath Falls, Seattle, Great Falls" },
|
||||
[SCENERY_SD5] = { "A2.SD5", "A2.SD5", "Salt Lake City, Cheyenne, Denver" },
|
||||
[SCENERY_SD6] = { "A2.SD6", "A2.SD6", "Omaha, Wichita, Kansas City" },
|
||||
[SCENERY_SD7A] = { "A2.SD7A", "A2.SD7A", "Washington, Charlotte" },
|
||||
[SCENERY_SD7B] = { "A2.SD7B", "A2.SD7B", "Jacksonville, Miami" },
|
||||
[SCENERY_SD11] = { "A2.SD11", "A2.SD11", "Lake Huron, Detroit" },
|
||||
[SCENERY_SD13] = { "A2.SD13", "A2.SD13", "Japan - Tokyo, Osaka" },
|
||||
[SCENERY_SD14A] = { "A2.SD14A", "A2.SD14A", "Western European Tour (UK, N. France)" },
|
||||
[SCENERY_SD14B] = { "A2.SD14B", "A2.SD14B", "Western European Tour (N. France, W. Germany)" },
|
||||
[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/",
|
||||
"/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]));
|
||||
if (f == NULL) {
|
||||
return false;
|
||||
|
|
@ -340,3 +350,25 @@ const char *sceneryDataRegionName(SceneryRegionE region) {
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// SCENERY_DEMAND_LOAD env var gates the .SD demand-load. Set
|
||||
// to "0" to load from byte 0 of the section (the natural
|
||||
// chunk4 ASM behaviour); set to a digit (e.g. "14") to apply a
|
||||
// source-byte SKIP, which is what MAME's captured RAM appears
|
||||
// to have done (RAM[$A887] matches sid $44 byte 14, not byte 0).
|
||||
// The skip lets us experimentally find which entry point makes
|
||||
// the chunk5 VM walk into the water-color SetColor at $B781.
|
||||
// Default behaviour: don't demand-load (keep captured-RAM
|
||||
// residue, which is the only known-working path so far).
|
||||
// Demand-load the section's bytes from the .SD file into the
|
||||
// dispatcher's working RAM (mirrors chunk5 LA63A → chunk3's
|
||||
// SmartPort `ReadBlocks` call). Section ID → file offset uses
|
||||
// ASM-faithful math from chunk4 `ComputeBlockFromSector` /
|
||||
// `FetchSectorFromDisk`.
|
||||
//
|
||||
// Without this, boot Meigs runs from the captured RAM image
|
||||
// baked into `port/sceneryRam_FS2.1.bin` (which is byte-faithful
|
||||
// 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");
|
||||
if (state->sceneryFile != NULL && state->sceneryFileSize > 0
|
||||
&& envDemandLoad != NULL) {
|
||||
int srcSkip = atoi(envDemandLoad);
|
||||
if (state->sceneryFile != NULL && state->sceneryFileSize > 0) {
|
||||
int srcSkip = envDemandLoad != NULL ? atoi(envDemandLoad) : 0;
|
||||
if (srcSkip < 0 || srcSkip > 64) srcSkip = 0;
|
||||
uint8_t count = ram[0x08E6];
|
||||
uint16_t dest = (uint16_t)ram[0x08E7]
|
||||
|
|
@ -407,6 +414,10 @@ static void doHeader(SceneryStateT *state) {
|
|||
}
|
||||
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,
|
||||
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;
|
||||
// good enough for AI jitter).
|
||||
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) {
|
||||
framebufferFillRect(fb, 0, 0, NATIVE_WIDTH, NATIVE_HEIGHT, COLOR_BLACK);
|
||||
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,
|
||||
int16_t playerVelX_q88, int16_t playerVelZ_q88) {
|
||||
if (!s->enabled || s->bombs == 0) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue