Controls¶
Improvement: menu scripts register named mod actions (#617), kept in input.actions.Registry
separately from the generated original catalogue. Devices.bindingActive supplies the same
activation logic for both. The controls screen includes live registrations and saves their
qualified-name bindings in OpenReliantActionKeys, OpenReliantActionJoystick and
OpenReliantActionGamepad. Registrations follow their script context; bindings survive reload.
How the payload reads the player's keyboard, joystick and mouse, and turns them into the inputs of the flight model. The names below are those make ghidra-annotate gives the Ghidra project; src/engine/input.zig defines the structures.
Devices¶
input_init (0x004BCD90) creates DirectInput 7 and three devices:
- the keyboard, shared with other programs and read only while the game is in the foreground;
- the mouse, held exclusively while the game is in the foreground;
- a joystick.
input_initfirst enumerates the attached joysticks that have force feedback, andjoystick_found(0x004BD190) opens each it is handed. With none,input_initenumerates any attached joystick and clearsforce_feedback(0x50E1A4); otherwise, while that flag is set,load_force_effects(0x004BD800) loads the effects fromforces\*.frc(Force feedback).
For the joystick, joystick_object_found (0x004BD050) sets the range of each axis the game uses and records that the device has it in joystick_axes (0x5DDC4C), a JoystickAxes with a flag for each axis in the order of DIJOYSTATE:
| Axis | Range | Flag |
|---|---|---|
| X | -1000 to 1000 | x |
| Y | -1000 to 1000 | y |
| Z | 0 to 1000 | z |
| Rz, the twist | -1000 to 1000 | rz |
| First slider | 0 to 1000 | slider |
A dead zone of a tenth of the range applies to the whole device. joystick_buttons (0x5DDC54) holds the button count and joystick_name (0x5DDB48) the product name.
input_acquire (0x004BD780) acquires the three devices, or unacquires them while the word at 0x5DDD28 is set, and input_shutdown (0x004BD3F0) releases them.
Reading¶
simulation_step reads the three devices at the start of each step, so 25 times a second (see
the game loop):
| Function | Into | State |
|---|---|---|
read_keyboard (0x004BD490) |
keyboard (0x595C68) |
256 bytes by DirectInput scan code (DIK_*), nonzero while the key is down |
read_joystick (0x004BD300) |
joystick (0x588340) |
JoystickState, DirectInput's DIJOYSTATE |
read_mouse (0x004BD3A0) |
mouse (0x588398) |
MouseState, DirectInput's DIMOUSESTATE2: the movement since the previous read, and eight buttons |
The front-end screens read the keyboard themselves.
Bindings¶
control_bindings (0x4E2380) holds a ControlBinding for each action: a scan code, a modifier
(none, Shift, Ctrl or Alt, either key of the pair), the name the game shows, and a joystick button
or -1. src/engine/input/controls.zig lists the actions,
numbered as the game numbers them, with the bindings the game starts with; make control-tables
transcribes it from the executable.
load_key_config (0x0042C800) reads the input settings and the bindings from the KeyConfig
and JoyConfig sections of starlancer.ini in the game's directory. The settings are in
KeyConfig:
| Entry | Default | Meaning |
|---|---|---|
ForceFeedback |
1 | force_feedback_setting (0x51DA4C). While it is 0, no force-feedback effect plays (Force feedback). |
JoystickInvert |
1 | joystick_invert (0x51D610). While it is 0, pitch is reversed, from the stick, the keys and the mouse. |
HatEnable |
1 | hat_enabled (0x52029C). While it is set, the hat looks around (The hat). |
TwistEnable |
0 | twist_enabled (0x595D88). While it is set, the joystick's twist rolls the ship. |
Controller |
0 | control_mode (0x57E064), the device the player steers with: 0 the joystick, 1 the keyboard, 2 the mouse. With no joystick, load_key_config makes 0 into 1 (0x0042C8A5), and key_config_defaults chooses 1 (0x0042CAF2). |
Each action also has an entry in each section, named after the action. In KeyConfig the value is
a scan code in decimal, optionally after SHIFT, CONTROL or ALT for a modifier, or
JOY BUTTON and a button number; in JoyConfig it is JOY BUTTON and a button number. Button
numbers start at 0. A missing entry keeps the default binding: load_key_config formats the current
binding the way it writes it, and passes that as the default to GetPrivateProfileStringA for both
sections.
That has two bugs, which only show when the file is edited by hand, since the game always writes both sections:
- When the
KeyConfigentry has a modifier, the check forJOY BUTTONin theJoyConfigvalue starts after the modifier's length, so an action bound to a key with a modifier can never also have a joystick button. - The
JoyConfigdefault is the binding as it was before theKeyConfigentry was read, so aJOY BUTTONinKeyConfigis overwritten by the old button whenJoyConfighas no entry.
Improvement: OpenReliant fixes both: it checks the JoyConfig value from its start, and uses
the button from KeyConfig as the JoyConfig default.
The defaults¶
key_config_defaults (0x0042CAA0) sets the input settings and the bindings to the game's
defaults, which load_key_config then changes from starlancer.ini. hud_init calls the two for
each mission (0x00483F06), the controls screens as they open, and RESET DEFAULTS the first alone.
The settings are those of the table above, but for Controller, which it makes 1 where no joystick
is attached (0x0042CAF2), and the twist, whose setting it turns off (twist_setting,
0x51D458) but not twist_enabled, which goes on rolling until load_key_config sets it from the
setting.
The bindings come from default.txt in the game's directory (game_directory), an ini file of the
same two sections, which the game ships. It clears each action's button first, so that an action
has a button only where the file gives it one, in either section. A KeyConfig value names a key by
the executable's English name, as key_names_english (0x004E6958, a copy of key_names) holds
it, after SHIFT or CONTROL: ], CURSOR UP or SHIFT E. An action the file doesn't name
keeps its key and its modifier, and a name the keys lack keeps the key. It compares four bytes of
ALT, its terminator among them, so that only a value of ALT alone is read as Alt, and as no key.
A JoyConfig value gives a button where a digit follows JOY BUTTON, looked for past
KeyConfig's modifier, as load_key_config looks.
The shipped default.txt differs from the executable's table in three bindings, which are so the
game's defaults: FULL THROTTLE is ] rather than \, JOYSTICK ROLL / rather than Insert, and
COUNTERMEASURES has joystick button 6 besides H.
Fixes:
- OpenReliant looks for
JOY BUTTONin aJoyConfigvalue from its start, as it does forload_key_config. - It chooses the joystick, which steers once one is attached (Porting).
- It turns the twist off with its setting, where the game leaves it rolling, its box shown off, until the bindings load again.
Improvement: when the game folder has no default.txt, OpenReliant uses the executable's
built-in table, including its buttons, where the original leaves every action without a button. A
default.txt in a mod replaces the game's. Gamepads have their own default bindings and don't use
it.
Key names¶
key_names (0x004E5CD0) holds the 89 keys the controls screens bind, each a scan code and a
name, 0x24 bytes. The executable names them in English, in capitals, but the screens show the
names the keyboard's layout gives them: as WinMain starts (0x004A8F82), key_names_rename
(0x004BCF70) renames every binding and every entry of key_names through key_name_get
(0x004BCFF0), and the loaders rename each binding they read a key for (0x0042CA26,
0x0042CCB2). key_name_get asks Windows (GetKeyNameTextA) for the name of the scan code, set
in bits 16 to 23 with the extended flag, bit 24, for a code past 0x80, in 0x1D bytes, or 0x1E for
load_key_config's, terminator included, and names a key Windows has no name for Unknown. A
binding without a key keeps no name (0x0042CA2B), and the controls screens write nothing for it.
So the scan code 0x10 is Q on a US keyboard and A on a French one. default.txt names its keys
by the executable's names all the same (The defaults).
key_names_rename also lowers each of typing_characters (0x00501440), the letters, the space,
the period, the comma and the figures, to the first character of its key's name in the layout
(typing_scan_codes, 0x00501418), the space aside. Nothing else reads the table.
OpenReliant names the keys through SDL (platform/keyboard.zig,
nameKeys): for each scan code, the key SDL gives it in the layout (SDL_GetKeyFromScancode) and
that key's name (SDL_GetKeyName), the character it types, in capitals, for a key that types one,
and else the key's own English name, such as Return, PageUp or Keypad 9. A name is put in the
game's code page and cut to 27 bytes (input.KeyNames). The controls list and a conflict's question
write them.
Improvement: OpenReliant names the keys again as the keyboard's layout changes. The game names them once, as it starts.
Whether an action is active¶
control_active (0x00412630) takes an action and a flag, once. Without once an action counts
for as long as its button or key is down; with it, only once for each press.
- The joystick button, if the action has one and it is down. With
onceit counts only while the button is not latched, and latches it;read_joystickclears the latch inbutton_latched(0x5DDC98) once the button is up. - The key. The keys 1 to 8, scan codes 2 to 9, never count while the word at
0x501EE8is 3: that is the phase of the display's window 11, the communications menu, open, whose number keys call the units in range (hud.md). Otherwise, withoutonce, a key bound with no modifier counts while it is down and neither Shift nor Ctrl is; a key with a modifier counts while it and either key of the modifier are down. Withonce,control_activeaskskey_pressed.
key_pressed (0x004BD570) takes a scan code, a modifier and once, and is what the front-end
screens use too. It keeps a latch for each key, key_latched (0x5D54EC), and one for each
modifier, shift_latched, control_latched and alt_latched. read_keyboard clears a key's
latch once the key is up, and a modifier's once both its keys are up.
- With
once, the key counts while it is down and not latched, and, with no modifier, while no Shift, Ctrl or Alt key is down, or with one, while either key of the modifier is down. Then it latches the key and the modifier. - Without
once, the key counts while it is down and, with no modifier, while no modifier is latched, or with one, while either key of the modifier is down. Then it clears the key's latch and the modifier's.
The hat¶
frame_controls reads the joystick's first hat while hat_enabled is set and the joystick has a
hat. Held straight forward, left, right or back, the hat switches to the cockpit's front, left,
right or rear view, every frame while it is held; diagonals do nothing. hat_glancing
(0x51CF8C) records that the hat is in use, so that the view returns to the front once it is
released. Camera keys pressed in the same frame take priority.
Steering¶
player_controls (0x00413410) is the update of the order numbered 100, Player Control, which
the player's ship follows in flight (see orders). It runs whenever the ship's orders
run: once a frame, from orders_update, and once each simulation step, from simulation_step,
before the objects move. The devices are read only at each step, so the runs in between see the
same state, and what player_controls steps each time it runs changes at a rate that depends on
the frame rate. It sets the ship's roll, pitch, yaw and lateral inputs and its throttle. Axis
values are scaled by 0.001, so the stick's travel spans -1 to 1.
- Joystick (
control_mode0). X yaws and Y pitches. The keys ROLL SHIP CLOCKWISE and ROLL SHIP ANTI-CLOCKWISE roll at 1 and -1, and while JOYSTICK ROLL is held, X rolls instead of yawing. Withtwist_enabledand a twist axis, X, Y and the twist yaw, pitch and roll. The throttle axis, Z or else the first slider, sets the throttle to1 - value * 0.001, so 0 is full throttle and 1000 none. Without either, the keys set it (see below). - Keyboard (1). Each run while ROTATE CLOCKWISE or ROTATE ANTI-CLOCKWISE is held steps yaw by
-0.3 or 0.3, and NOSE UP or NOSE DOWN steps pitch by 0.3 or -0.3; with neither key of a pair
held, that input is zero. The flight model clamps each input to between -1 and 1, so a held key
reaches full deflection on the fourth run. The roll keys roll as with the joystick. While the
word at
0x539A34is 6 or 12, yaw and pitch stay zero. - Mouse (2). The mouse's movement gathers into a stick position, which the order's own data
keeps as two 16-bit counts, each held to within
mouse_range(0x4E2378), 800, of the centre. As a fractionvof 800, each axis gives 0 while|v|is undermouse_dead_band(0x4E237C), 0.3, and1.3 * v - 0.3above it or1.3 * v + 0.3below it. X yaws, and Y pitches reversed, so that moving the mouse forward raises the nose; the roll keys roll and the throttle keys set the throttle. The left button fires the lasers as FIRE LASERS does, and the right launches a missile as LAUNCH MISSILE does, once for each press, whichmouse_missile_latched(0x51CEFA) records.
Fix: player_controls adds the movement of the last read each time it runs, once a frame as
well as once a step, so the faster the frames, the further a movement steers. OpenReliant adds
each read's movement once. OpenReliant holds the mouse to the window, its pointer hidden, while
the player flies in this mode, as the game holds DirectInput's mouse, and lets it go for the pause
menu.
In each mode, half the yaw input is added to the roll input, so the ship banks into turns, and
joystick_invert sets the sign of pitch. STRAFE LEFT and STRAFE RIGHT set the lateral input to -1
and 1. While the player holds the reversed_controls deathmatch power-up (9), all four inputs are
reversed.
While SHIELD BALANCING or POWERBALL WINDOW is held, or the flag at 0x51CF04 is set, the stick
doesn't steer: the four inputs are zeroed, the throttle isn't read, and the stick's position goes
to the shield balance or the power distribution
instead, in that order of priority. The joystick's X and Y are the stick; with the keyboard,
ROTATE CLOCKWISE and ROTATE ANTI-CLOCKWISE count as -1 and 1 across and NOSE UP and NOSE DOWN as
-1 and 1 down; the mouse gives its movement times 64 over 800. 0x51CF04's routine
(0x00413200) turns two angles at 0x51CF30 and 0x51CF00 by the stick, and frame_controls
clears the flag on every frame OBJECTIVES WINDOW isn't pressed. Unknown: what sets the flag,
and what the angles turn.
Throttle¶
With the keyboard or the mouse, or a joystick without a throttle axis, player_controls calls
player_throttle_keys (0x004132C0). It steps throttle_setting (0x51CF7C) and the ship's
throttle by 0.02 each run while ACCELERATE or DECELERATE is held, fifty runs from none to full,
and ZERO THROTTLE and FULL THROTTLE, once for each press, set them to 0 or 1 and stop MATCH
SPEED. Then it sets the ship's throttle to throttle_setting; its check of afterburner first
always passes there, since object_orders has cleared the flag. player_controls keeps the
throttle between 0 and 1, and at most 0.5 while the player holds the half_throttle deathmatch
power-up (7).
MATCH SPEED, once for each press, flips matching_speed (0x579984), putting back
throttle_before_match (0x566794) as it turns off and matching at once as it turns on. While it
is set, match_target_speed (0x00412C10) runs each update: it sets the throttle to the player's
target's speed over the player's cruise speed, at most 1, while the target is within 330000 units
and not exploding, keeping the throttle it found in throttle_before_match; a cloaked target
leaves the throttle as it is. Past that range it puts back throttle_before_match and stops
matching, as it does with no target at all, then without putting anything back. Since it keeps
the throttle each time, what it puts back is the throttle of its last match. OpenReliant runs this
part of player_controls after the rest (input.matchSpeed), where the game runs it among the
keys after the throttle's and the strafe keys; nothing between reads the throttle.
Afterburner and reverse thrust¶
AFTERBURNERS, while held, and AFTERBURNER TOGGLE, which flips afterburner_toggled (0x51CEFE)
once for each press, set the ship's afterburner. REVERSE THRUST, while held, sets its
reverse_thrust. object_orders clears both before each order update, so each lasts until the
order next runs unless set again. After the update it clears both when the ship has no afterburner
fuel, both and the throttle while its engines are disabled (DisableEngines), and reverse_thrust
unless the ship has the can_reverse flag. node_mount_glow (0x00499540) sets that flag when it
mounts an engine glow that burns forward: one whose attachment's Z axis and length point the same
way (rendering). Of the player's ships, only the Wolverine, the Shroud and the
Phoenix carry such a retro thruster, which matches the reverse thrust the loadout screen lists for
them.
While the player asks for either, a warning sounds when fewer than 20 seconds of fuel are left and
another when it is out, each at most once every 1000 ticks, ten seconds; fuel_warning_tick
(0x5799C0) holds the tick before which neither sounds again.
frame_controls reads ATTACK MY TARGET, BACK OFF and HELP ME once for each press, outside a
multiplayer mission, outside the front end's simulator (0x0057E044) and where the game's mode
(0x00524FE4) is 0, and only while the player's target is a hostile ship that can be aimed at:
each gives its command to a wingman the game picks
(The wingmen's commands). It reads PERMISSION TO LAND next, once
for each press outside a multiplayer mission, which asks the carrier the player launched from to
clear the ship to land (Landing).
player_controls also reads FIRE LASERS, LAUNCH MISSILE, CLOAK SHIP, JUMP DRIVE, EJECT and
COUNTERMEASURES, all but FIRE LASERS once for each press. LAUNCH MISSILE and COUNTERMEASURES are in
Missiles. While the byte at 0x529FB8 is set, it
reads none of them, nor MATCH SPEED, AFTERBURNER TOGGLE, the throttle keys or the keys that turn
the ship. Unknown: what sets that byte.
The power distribution¶
The player shares the ship's power between its shields, guns and engines by moving a point on a disc
of radius 64, the power ball (GameObject.power_setting, +0x728). Each system has an anchor on
the ball, a third of a turn from the next: the shields at (0, 1), the guns at (0.866, -0.5) and the
engines at (-0.866, -0.5), the game's 0.866 standing for √3/2, which OpenReliant uses itself.
power_distribute (0x00412560) works out each system's share: power_reach (0x004124E0)
measures the distance from the point to the edge of the disc going away from the system's anchor,
which is 128 at the anchor, 64 in the middle and 0 opposite, and a share is that distance over the
three together. A share s gives a factor of (1.75 - 0.75 * s) * s + 0.5: 1 for an even third,
1.5 for all of the power and 0.5 for none.
| Offset | Factor | What it scales |
|---|---|---|
0x734 |
Guns | How fast the guns recharge (0x004770E0) |
0x738 |
Engines | The cruise speed (Motion) |
0x73C |
Shields | How fast the shields recharge (Shields) |
create_object puts the point at (1, 1) and the three factors at 1.
- FULL POWER TO GUNNERY, FULL POWER TO ENGINES, FULL POWER TO SHIELDS and EQUALIZE POWER, while held and the radio's window is shut, put the point at (54.17, -30.32), (-55.79, -27.94), (0.699, 61.98) and (1, 1), work out the factors and open the power window. EQUALIZE POWER's point is a little off the middle, toward the shields, so its factors aren't all 1.
- While POWERBALL WINDOW is held (
powerball_held,0x51CEF8),power_move(0x00413180) moves the point against the stick by the frame's ticks times its deflection each timeplayer_controlsruns, brings it back to the edge of the disc if it leaves it, and works out the factors again.
The display shows the point and the shares in its power window.
The shield balance¶
While SHIELD BALANCING is held (0x51CEFC), shield_balance (0x00412D40) shifts shields fore or
aft by the stick's Y. Each time player_controls runs with Y past half-way, a quarter of the ship's
shield power moves: from the fore shield to the aft one while Y is above 0.5, and back while it is
below -0.5, as long as the shield it comes from has any left. The quarter comes out of that side's
reserve first (0x51CF78 for the fore shield, 0x51CF34 for the aft one), then out of the shield.
The shield it goes to holds at most five times the shield power, and what goes beyond that is added
to its reserve, which holds as much again. A reserve keeps the other side's shield from
recharging to full. The ship status display shows each reserve as a second arc
outside the fore or aft shield's (Head-up display).
Force feedback¶
With a force-feedback joystick, load_force_effects reads 13 of the files in forces\
(.frc) into force_effects (0x5DDC58), each through the SideWinder Force
Feedback SDK (force_effects_read, 0x004BDB10), and downloads the missile's. While
ForceFeedback is on, each place that plays an effect starts the first of its file's:
| File | Played by |
|---|---|
lc, pc, mb, gl, tc, np, cg, gp, vb, nc |
The player's shot of that gun type, from the Laser Cannon to the Nova Cannon (bullet_place); nc also as the Nova Cannon releases its charge (nova_release) |
prc |
Nothing: bullet_place's switch has no case for the Proton Cannon, whose shot plays lc |
Missile |
The player's missile launch (missile_launch) |
Shake |
A shot striking the player's ship, and each frame while the camera shakes from hits by more than 0.1 (force_shake, 0x004BE000), unless it is playing |
A blow to the player's ship, with a force-feedback joystick only (damage_feedback, 0x00463E10,
from object_damage and object_armor_damage, with the damage after the difficulty's scaling):
- A shot's starts
Shake, and raises the camera's shake (hit_shake) by 0.05 of its damage while it is below 1, up to 1. - Any other adds a push of 300 times its damage on the side struck to the frame's hits
(
force_hits, the latest 20), and raises the shake as much, up to 2.
Each frame mission_frame sums the frame's hits by side (force_hit_pushes, 0x004BE060): the
left's less the right's push across, the fore's less the aft's along. A net above 1 plays a push, a
one-second sine at 2 Hz toward that side, as strong as the net out of 10000, in the next of nine
play slots (force_slots), freeing what played there. A push from the left is aimed at 90000, a
typo for 9000 hundredths of a degree, which DirectInput turns down.
The game never reads the folder's other files: Accl, AcDc, Afterburn, Decl, Guns (the
same as lc), Hullshock to Hullshock3, landhard, Shield, Shock and shiver.
In OpenReliant¶
input/force.zig reads the effects as load_force_effects
does (load), whatever the controller, and plays them as rumble. Each frame it works out how hard
every effect playing pushes at that moment: a waveform slower than 10 Hz swings
the controller's low-frequency motor as it swings, a faster one buzzes the high-frequency motor at
its strength, and envelopes and gains scale both. A file of several effects plays whole, a
sequence one member after the other and a superimposition all at once; the game starts only the
first of the effects the SDK made of it. The platform sends the motors' speeds to SDL at most every
40 ms. Rumble cannot show which way an effect or a push pushes, which a force-feedback joystick
would (#244).
- Improvement: any controller that rumbles plays the effects, gamepads among them.
- Fix: the Proton Cannon's shot plays
prc. - Improvement: a blow shakes the camera whatever the controller (
input.force.HitShake). - Improvement: the files the game never reads play where they fit (
input.force.Unread):Shieldas a blow strikes the player's shields, theHullshockof the side struck as one strikes the hull (left, right, fore and aft in turn),landhardfor a collision,Shockas a shockwave passes, andAfterburnwhile the afterburner burns.
--original plays the game's own effects alone, and shakes the camera for blows only while the
controller rumbles.
Porting¶
The bindings and starlancer.ini hold DirectInput scan codes, which follow the IBM PC's set 1
scan codes, with the extended keys at 0x80 and up. OpenReliant names them in input.Key and
maps SDL's scan codes to them (platform/keyboard.zig).
input.zig ports the input code: key_pressed and
read_keyboard's latches (Keyboard), the joystick (Joystick: joystick_found,
joystick_object_found and read_joystick), and control_active with both keys and buttons
(Devices.active). The joystick is read through JoystickDevice, an interface with the calls the
game makes on its DirectInput device: capabilities, axis ranges, dead zone and polling.
platform/joystick.zig implements it for SDL's joysticks and
gamepads (Platform). Devices holds what the game
keeps in globals: the device states, the bindings and the settings.
playerControls and playerThrottleKeys port the joystick and keyboard parts of the two
routines above. Player holds throttle_setting, matching_speed, afterburner_toggled, the
two held flags and the shield reserves. playerControlOrder runs them as the game does, as the
update of the Player Control order (player_controls, 0x00413410), once a frame from
orders_update (aigeneric.ordersUpdate), followed by MATCH SPEED and the weapons' keys.
input/power.zig ports the power distribution and the shield
balance: reach, shares, distribute, choose for the power keys, move and
balanceShields.
Camera.frameControls ports the hat. load_key_config is ported in
game/interface.zig, with the fixes above, and with it
save_key_config, control_binding_find and key_config_defaults; the controls screen that sets
them is the settings screen (Front end);
profile.zig reads the file as GetPrivateProfileIntA and
GetPrivateProfileStringA do.
Improvement: OpenReliant reads starlancer.ini again whenever a controller is connected or
disconnected, since the settings and bindings depend on the controller. A gamepad gets its own
default bindings, and keeps its buttons in a section of its own, GamepadConfig, since
OpenReliant numbers them otherwise than a joystick's; its right stick, the twist, always rolls
(input.Devices.twistRolls). DeadZone in JoyConfig sets the dead zone, which the original
fixes at a tenth. A joystick that is disconnected is closed and reads as centered, where the
original tries to acquire it again.
Fix: with no joystick attached, the game makes Controller 1 itself, which its controls screen
then writes, so that a game started once without the joystick steers with the keyboard ever after.
OpenReliant keeps the joystick as the choice and steers with the keyboard while none is attached
(input.Devices.controlMode); and its settings screen writes the controls as it is left only
where they are not what the file gives already (Front end).
object_orders (aigeneric.objectOrders) clears the two burns before each order update and, after
it, stops them where the ship is out of fuel or its engines are disabled. Not yet ported: the special
cases player_controls reads for the byte at 0x529FB8, the player's deathmatch power-up and the
flags at 0x51CEF8, 0x51CEFC and 0x51CF04
(#722).