Improvement: global mod scripts can register custom orders (#615). The runtime resolves their
qualified names and allocates internal IDs after the original catalogue. aigeneric.infoOf
combines original and registered metadata for stack priorities, player eligibility, retaliation,
avoidance and disruption. The original catalogue remains generated and immutable. Custom
callbacks use the scripting bridge in hooks.Scripts, since the engine doesn't link Luau.
What each object is doing: flying in formation, escorting, docking, exploding, or following the player's controls. An object keeps a stack of orders, the current one on top, which the AI, the mission scripts and the player's controls push and pop, and object_orders runs the current one.
aigeneric.zig holds the stack and runs the orders, ai.zig the steering they turn by, aifuncs.zig the orders that fly a ship, a capital ship's lurch and orders 44 and 45, aieject.zig and tractor.zig those of the ejection, launch.zig the launches and jump.zig the jumps, aiioncan.zig the ion cannon, ai/formations.zig the formations, and ai/orders.zig lists every order with its flags, priorities and routines; make order-tables transcribes that table from the executable. The names below are those make ghidra-annotate gives the Ghidra project, which names each order's routines order_ and the order's name, with _init and _exit for those two.
Ported so far: the stack (order_push, order_pop, orders_clear, orders_pop_all), the queue (order_queue), what runs them (object_orders, orders_update, order_retaliate), and the steering (ai_steer, ai_roll_upright) with its avoidance. The orders says which orders are ported, Player Control being the player's controls. Not ported: the multiplayer orders, which still hold their place on the stack, pushing one still popping and starting what it should, and the orders sent to and from the other machines in a network game (#55).
OpenReliant keeps each object's stack and order state in its slot rather than allocating them with its first order, and hands a fatal "Cannot set ai" back to its caller as an error. Fix: where the game stops with it, OpenReliant logs it in the game's words and the order is not taken (aigeneric.give).
order_groups (0x4E06E0) points at the records of each hundred order numbers: order n is
record n % 100 of group n / 100. Group 0 holds orders 0 to 45, group 1 orders 100 to 122, and
group 2 one empty record, order 200. Each record is an OrderRecord:
| Offset | Size | Field |
|---|---|---|
0x00 |
4 | init: runs before the order's first update; null for none |
0x04 |
4 | update: runs each time object_orders runs the order |
0x08 |
4 | exit: runs when the order is popped or replaced after it has started; null for none |
0x0C |
4 | Flags |
0x10 |
4 | Name: the developers' name, which fatal errors show |
0x14 |
4 | Priority |
| Flag | Name | Meaning |
|---|---|---|
0x1 |
players |
The order may be given to a player's ship. A player's ship refuses the other orders numbered below 100. |
0x2 to 0x10 |
Unknown. Set on some orders; nothing in the payload tests them. | |
0x20 |
one_shot |
The order runs its update once and pops itself, and the order below carries on without starting again. Its init never runs. |
0x40 |
retaliate |
While it runs, the ship can turn on its attacker (see Retaliation). |
0x80 |
avoidance |
While it runs, avoidance_scan (0x00492190) lists the objects the ship could hit, unless the object has no_avoidance, which SetShipAvoidance sets to disable "Avoidance code": up to ten objects with listed components whose collision spheres, widened by a constant, overlap its own, at GameObject offsets 0x6B4 (the count) and 0x6B8, and up to ten others for which 0x00401980, which projects the two objects' motion, answers yes for a time of 50 updates and a margin of 2000 units, at 0x6E0 and 0x6E4. |
0x400 |
send_flight |
While it runs, a multiplayer game sends the ship's steering inputs, throttle, rates and velocity. |
Most orders have priority 0. Warp In, Warp Out, Land, Jump In, Jump Out, the fixed gate jumps, Launch, Dock and Friendly Fire have 1; Eject Player 97, Eject and Eject Spin 98, and Explode 99.
An object's stack, orders (0x684), holds up to 20 OrderEntry records, the current order
first, with order_count (0x680) saying how many. It is allocated with the object's first order,
together with 0x90 bytes at order_state (0x68C) where the current order keeps whatever it
needs between updates.
| Offset | Size | Field |
|---|---|---|
0x00 |
2 | The order's number |
0x02 |
2 | Target kind: 0 a ship, 1 a flight group, 2 a squad, as in the mission's object table |
0x04 |
2 | Target: the ship's slot, or the flight group's or squad's index; -1 for none |
0x06 |
2 | Target component, or -1 for the whole ship |
0x08 |
2 | A running count from 0x5185A8 while the byte at 0x5185B1 is set, as SetAI and SetupLaunch number the orders they give, otherwise zero |
0x0A |
16 | The order's own data, zero when the order is pushed |
player_controls keeps the mouse's stick position in the first two words of its data.
Fix: the game takes a target's slot on trust, and a mission's SetAI can aim an order at any
ship number, past the last of the 400 slots too. OpenReliant reads a target's slot only where it is
one of the slots (aigeneric.Target.slotIn), so an order aimed past them aims at nothing.
order_push (0x0040CC10) takes a slot, an order and its target, and:
- fails if the ship refuses the order (
order_refused,0x0040CA00): the ship is a player's, its slot being belowplayer_slots(0x58832C), which is 1 in a single-player game and the player count or 8 in multiplayer, and the order is numbered below 100 withoutplayers; - succeeds at once if the current order is the same order with the same target;
- fails unless the current order gives way (
order_give_way,0x0040CA50); - removes any equal order, with the same target, from deeper in the stack;
- fails if the stack holds 20 orders;
- pushes the order with its data zeroed. Unless the order is one-shot, it marks the order as
starting (
order_starting,0x688), sets the word at0x620to -1 and zeroes the state.
An object that is exploding, whose pilot has ejected, or with object flag 0x10000000 takes no
order. Otherwise the current order gives way at once when there is none, when it has yet to
start, or when the new order is one-shot. A started order gives way to Explode, and to any order
when its own priority is zero or the new order's is higher, running its exit as it does. Pushing
any other order on it is a fatal error, "Cannot set ai %s on ship %s: Still %s".
order_pop (0x0040CE70) runs the current order's exit if it has started and removes it. Unless
the popped order was one-shot, the order below starts again: it is marked as starting and the state
is zeroed. orders_pop_all (0x0040CF80) pops every order, and orders_clear (0x0040CF50) drops
them all at once when the current order gives way to clearing, so only that order's exit runs.
A mod's script can stop a pop through order_pop's hook (Scripting),
which leaves the order on the stack. OpenReliant's orders_pop_all then stops at the first pop that
leaves the stack no shorter. The orders a mod registers that OpenReliant removes, as the mod's
scripts close or the object goes, pop without the hook.
The script command SetAI pushes an order aimed at the ship, flight group or squad it names, on
each ship it applies to, and ClearAI clears the orders of each ship that is not a player's.
order_target_walk (0x00401CB0) hands each ship an order's target names to a routine, until the
routine says to stop: the ship itself, as the target names it; each ship of a flight group, whole;
and each ship of a squad (squad_walk, 0x00401D80): its members in turn from its first, while
they are its own, a ship as the member names its component, a flight group's ships whole, and a
squad's own walk. Dock, Escort, the search for a new target, the search for a pod to scoop up,
Dark Reign shoot and Launch walk their
targets so. A squad that two members name is walked twice.
Fix: the game walks a squad that holds itself, directly or through other squads, round for
ever, reads a member no record stands for from address zero, and stops with a fatal error at a
member of a kind it does not know. A walk also doubles with each squad of a chain whose squads each
name the next one twice, so a long chain hangs the game. OpenReliant passes over a squad the walk is
already inside, goes into no more than 4096 squads in one walk (bind.Mission.SquadPath), and
passes over such a member. The triggers' count of a squad's members, for_each_ship and in_squad
walk squads the same way (Script VM).
object_orders (0x0040C5F0) runs an object's current order:
-
It starts the queued orders that are due (see below).
-
With a
retaliateorder, it runsorder_retaliate. -
It clears the object's
afterburnerandreverse_thrust, so an order that burns sets them again each time it runs. -
A one-shot order runs its update and pops itself, and
object_ordersthen runs the order below. Any other order runs itsinitfirst if it is starting, then its update.Fix: the game runs the update of the order it found before the
init, even where theinitpopped that order or pushed another, and then marks the order now on top as started, so that order's owninitnever runs. Of the game's orders, only Rotate Boridin breakaway warp projector (37) pops itself in itsinit. OpenReliant runs the update only while the order is still on top, and the order that took its place starts on its next run.Fix: after a one-shot order's update, the game pops the order on top and runs the orders again, whatever the pop did, so a one-shot order whose update pushes another order runs again and again until the stack overflows. OpenReliant runs the orders again only where the pass left the stack shorter, which also stops a one-shot order whose pop a script stops.
-
Afterwards it clears the throttle and both burns while the object's engines are disabled (
DisableEngines), both burns when it has no afterburner fuel, and reverse thrust unless the object hascan_reverse.
It runs from two places:
orders_update(0x0040C8F0) runsobject_ordersonce a frame for every object that is not disabled (DisableObject), the player's ship included, and after it, where the object's guns are not disabled, its turrets (object_step_turrets).mission_frame(0x004924B0),mission_run's work for each frame, calls it (see the game loop).simulation_stepruns it for the player's ship, before the objects move, while the ship's current order isPlayer Control.
So the orders of AI ships run once a frame, and the player's controls once a frame and once each simulation step.
Damage of kinds 0, 1 and 5 adds to an object's recent_damage (0x690), which orders_update
zeroes every 500 ticks, and each hit records the attacker's slot in last_attacker (0x694).
While the current order has retaliate, order_retaliate (0x0040C520) pushes Fight (105),
aimed at the attacker, once recent_damage reaches 0.7 (0x004DC484) of the ship's full armor,
six times its armor class. It does so
only when the attacker is on the other side, is not already the current order's target, and both
ships are fighters (class 1, the word at +0x28 of the combat stats, see
Objects), and not while the ship has do_not_disturb (DoNotDisturb).
An object's orders can wait in a queue of up to 20 for their tick, queued_orders (0xB90) with
queued_order_count (0xB8C): Dark Reign shoot queues the ion cannon's order there
(The ion cannon), and in a network game the orders from the other machines wait
there, as do Fight's next maneuver and Find Scoop Up's Scoop Up on the machine that sends them. A
QueuedOrder is the order's entry, a byte the sender passes, and the tick it is due.
order_queue (0x00402660) adds one due a given number of ticks after frame_start (see
the game loop). An equal order already queued, the same order at the same target,
stays if it is due no sooner, and is replaced otherwise.
object_orders takes each queued order that is due by mission_ticks and has a priority no lower
than the current order's, pushes it with its data, and removes it from the queue. It removes a due
order without pushing it while the object has not been created.
Fixes: a full queue is a fatal error in the game; OpenReliant logs it and leaves the order out. The game writes the queued order's data over the order on top of the stack even where the push did not take; OpenReliant writes it to the order pushed alone.
aigeneric.zig ports the queue (queue, startQueued).
Not ported: the orders sent to and from the other machines
(#55).
Most orders that fly a ship steer with ai_steer (0x00401380), which takes a point to aim at,
a limit, an ease and flags. It sets the pitch, yaw and roll inputs from the point's direction in
the ship's frame, through ai_steer_angles (0x00401710), or ai_steer_axes (0x00401690) when
the word at +0x24 of the ship's flight stats is nonzero. It takes (1 - ease) * 6 times each turn
rate off its input and multiplies the result by 11.46, the degrees in a radian over five, so an
input fills at five degrees off; then it holds each input within the limit, at most 1. While frames
take more than 10 ticks, turns of less than a sixteenth of a turn (22.5 degrees) are halved first,
along with the limit.
ai_steer_angles banks the ship round: within 18 degrees of the nose it simply yaws at the point,
further off it rolls to bring the point overhead, and it pitches only once the roll is within 0.8
radians of where it wants it. A ship flying backwards turns toward the other way about.
ai_steer_axes turns the ship flat: it pitches and yaws at the point together and never rolls, and
with the point behind it, it yaws hard to the side the point lies on. No file sets the word: the
executable's ship_flight_stats (0x004F9E70) holds it for each ship type, and stats_load_ships
leaves it. The capital ships and most other types turn flat; the fighters bank, as do some
support ships, the Nanny and the limpet car among them. The missiles' own flight stats
(missile_flight_stats) turn every missile flat but the fuel pod.
create/flight.zig lists the types.
| Flag | Meaning |
|---|---|
0x1 |
First avoid_near (0x004028F0) moves the point around the objects with components in the ship's first avoidance list. |
0x2 |
First avoid_ahead (0x00402DC0) moves the point around the objects in the second list, projected along their motion. |
0x4 |
Unless avoidance took over, the ship also rolls toward the world's Y axis (ai_roll_upright). |
0x8 |
Pitch stays at 0.2 or more. |
When avoidance moves the point, ai_steer steers with a limit of 1, no ease and without flag
0x8, and returns true. The lists are what avoidance_scan builds, and a ship with
no_avoidance avoids nothing.
Fix: the game follows a null pointer for a ship with no flight stats, a stand-in, that steers, arrives or avoids what lies ahead, and for a collision course with such a ship on either side; OpenReliant leaves the ship's inputs as they are, counts it as not arrived, avoids nothing for it, and takes it for no collision course.
ai_arrive (0x00402140), through ai_arrive_steer (0x00402160), brings a ship to a point,
turned as an orientation, at a least throttle: Follow Curve arrives so at a path's start, and Dock
and Formation use it too. It goes by where the ship stands next. Within 2000 of the point it has
arrived: its throttle is the least it was given and its turning inputs nothing. Otherwise:
- Behind the point along the way the orientation faces, within 18 degrees of that line, and itself
facing that way within 11, it steers at the point with
ai_steer, flags0x3, and rolls to stand as the orientation stands: its roll input is the roll between their up axes less 12 times its roll rate, in degrees over 40. - Otherwise it steers with
ai_steer, flags0x3, round the circle through it that meets the orientation's line at the point, in the plane of that line and the ship: aiming half a radian round the circle ahead of itself, or at the point once within the circle's last half radian. The circle's radius is at least twicespeed_per_pitch_rate(flight stats+0x20); standing ahead of the point, the ship aims across the far side of the smallest.
Either way its throttle is the way left, straight or round the circle, over 4 times its cruise
speed through its inertia (4 * cruise / (1 - inertia)), less 0.1, and never less than the least
it was given.
avoidance_scan (0x00492190) runs for each object in mission_frame's pass that draws them, the
ones it draws, while the object's current order has the avoidance flag and it has no
no_avoidance. It empties the ship's two lists (GameObject + 0x6B4 and +0x6E0, a count and ten
slots each) and fills them from the objects that are not standing in, disabled or jumping, not
planets, not the ship itself or what it fights, and where neither names the other in its first
pass-through slot:
- an object that lists components goes on the first list while its sphere, 10000 wider than the two radii, overlaps the ship's where the step takes them both;
- any other goes on the second, where the ship lists no components, while the ship is on course to
hit it within 50 steps by 2000 (
ai_collision_course,0x00401980).
ai_collision_course first asks that the two be within reach of each other over the steps, at
their cruise speeds, with both radii and the margin. Against a target without components, the
target must then lie ahead of the ship, and the ship close on it by its velocity less twice the
target's, to within both radii and the margin. Against a target with components, the ship's next
position must instead lie within the ship's cruise speed times the steps, and the margin, of a box
of one of the target's parts: of the parts in its root's child list, every part whatever it is
linked to, shown and with a collision tree, each whose sphere, that reach wider, holds the position
has each box of its tree tested, and the position is within reach of a box where it stands nearer
to the box's centre, squared, than the box's half size and the reach, each squared, together.
Fix: the game also hands back the part and the box through two pointers it does not test, which
every caller passes null. Avoid Target (order_avoid_target, 0x0040B330) asks only that its
target's type is below 0x100, not that it lacks components, so the game writes to address zero once
the ship comes within reach of a box of a ship that lists components. OpenReliant hands back
neither.
avoid_near (0x004028F0) works the first list, from the line between where the ship goes next
and the point it steers at. For each object not standing in, exploding or disabled, not farther
behind along that line than both radii, closing along it, and to be met within 250 steps: it
takes the object's box where it will then be, widened by the ship's radius, in the object's frame
and scaled by its visibility. Where the line crosses it, the point moves onto the box widened again
by the ship's radius: onto the face nearest where the line enters, at whichever is nearest the point
of four spots of it, each at the entry along one of the face's two axes and at an edge of the box
along the other. The point is scaled by the visibility again on its way back to the world, rather
than unscaled. The heading stays the one to the first point, but each object after the first tests
the line to the point the one before moved.
avoid_ahead (0x00402DC0), for a ship that lists no components, works the second list: each
object is taken where it will be once the ship has flown to where it is now at the ship's cruise
speed. Where the line to the point passes within 1000 of it, or 500 for an object of another side,
the point moves as far ahead of the ship as the object will be, and above or below the ship, away
from the object along the ship's own up and down axis, by both radii and 2000, or 1000 for another
side's.
Each order's routines are named after it: order_fly_init and order_fly for Fly, for example.
Many take their target's validity from order_target_valid (0x00401870): a targetable object
that is not cloaked, exploding, disabled or ejected, nor has object flag 0x10000000, and, when the
target is a component, one that is there and neither hidden nor has node flag 0x10. Random
choices come from object_random (0x004ADD10), each object's own generator: a seed at +0x638,
set from C's rand() when the object is created, that steps as seed * 0x343FD + 0x269EC3, bits
16 to 30 of it over 32767 giving a number from 0 to 1.
Every order, by its number, with what it does and whether OpenReliant runs it. A multiplayer order OpenReliant does not run yet holds its place on the stack and does nothing (#55).
The Order column holds the developers' names. Scripts and ai/orders.zig use them in lower case,
with underscores, but for the orders the table names nothing, or names as an earlier one, which
take OpenReliant's own names, for what they do differently (tablegen's own_names): 3
launch_jack_hammer, 40 jump_in_spread, 41 jump_out_spread, 106 abandoned and 110
fire_ion_cannon. Order 200 has no name, and goes by its number.
| Number | Order | What it does | Ported |
|---|---|---|---|
| 0 | Do Nothing | Zeroes the throttle and the turning inputs. | Yes |
| 1 | Fly Aimlessly | On starting, takes a figure from 1 to 3 from the ship's own random numbers, keeps where the ship will be next and how it will be turned, its X axis reversed where its next number is odd, and sets the throttle to 0.4 and up to 0.3 more by rand(). Each update it steers with flags 0x3 for point n of its figure, from 1, and for the next once within 1000 of it. Point n lies t = n twentieths of a turn round, (cos t - 1)(figure + 1) times 25000 along the kept X axis and sin(figure t) times 50000 along the kept Z axis from where the order began: figure 1 is a circle, and figures 2 and 3 are wider loops that swing ahead and back two and three times on the way round. It never ends. Improvement: OpenReliant computes the sine and cosine rather than reading the engine's tables (sr_sin, sr_cos). |
Yes |
| 2 | Launch Missile | One-shot: launches a missile at the target from the first of the ship's racks with missiles left that is not a Jack Hammer's (Missiles). | Yes |
| 3 | (nameless) | One-shot: as Launch Missile, from the first rack of Jack Hammers. | Yes |
| 4 | Warp In | A ship arrives through its own tunnel, emerges, then posts JumpedIn (Gates). | Yes |
| 5 | Warp Out | A ship aligns with its target, opens its tunnel and enters it, then queues Warp In with the same sequence number. | Yes |
| 6 | Fly | Flies at the speed in its data, or at full throttle for zero. With a target it flies to it and pops within 2000 units; otherwise it keeps the heading it had when it started, steering at a point 20000 units along it. It steers with flags 0x7 and halves the throttle while avoiding. An object without flight stats is moved along that heading instead; Improvement: OpenReliant draws it gliding on between the ticks (The game loop). |
Yes |
| 7 | Run Away | Flies away from the target at half throttle, for a point on the far side of the ship from the target, 100000 times as far from the ship as the target is. It moves the point round what is near (avoid_near), then steers at it with flags 0x3 and an ease of 0.1, which go round what is near and ahead again. Pops when the target's slot holds a stand-in. |
Yes |
| 8 | Land | The player's ship lands on its carrier, which ends the mission (Landing). | Yes |
| 9 | Escort | On starting, takes the ship its target names, or the ship at the order's number among a flight group's or a squad's ships, counting round them again past the last (escort_count_place, 0x0040AA50); OpenReliant takes none where the group has no ships, which the game walks for ever (Fix). Each update, it pops once that ship's slot holds a stand-in; otherwise it steers for a point 10000 ahead of the ship: within 5000 of it with half its turn and flags 0x4, and farther off with its full turn and flags 0x3. Its throttle is the escorted ship's speed over its own cruise speed, and 0.0001 more for each unit the escorted ship lies ahead along its own heading. |
Yes |
| 10 | Find New Target | Walks the ships its target names, weighing each it can aim at, cloaked or not, by the square of its node's distance from where the ship will be next (Picking a fight). It fights the lightest to fight, pushing Fight, or Torpedo (103) for a ship of the torpedo class; with none, it mills round the lightest to mill round, pushing Mill (120); with neither it pops. | Yes |
| 11 | Explode | A destroyed object's end, by what it is and in one of three styles (Destruction). | Yes |
| 12 | Ripper grabs target object | A Ripper carries its target off (The Ripper). | Yes |
| 13 | Object Attach | On starting, keeps where the ship will stand next in the frame its target will stand in next. Each update it puts the ship there in the target's next frame, turned as the target will be, and gives it the target's turn, velocity, speed and rates of turn, so that it rides the target. | Yes |
| 14 | Formation Regroup | The ships of a flight group meet at their places in its formation about its middle, 10000 back along Z, then turn to face their places, the formation's leader moving the group on (Formations). | Yes |
| 15 | Patrol Route | The ships of a flight group fly round the waypoints of a route, in formation about their leader where they have points in one, and otherwise alone (Formations). Fix: in formation it differs from the original's, whose ships hang back short of their places, so that the formation stops going round the route. | Yes |
| 16 | Toggle Cloak | One-shot: cloaks or uncloaks the ship if its model's header allows a cloak, and the ships being launched from it do the same. | Yes |
| 17 | Ship Follow Curve | Flies the path of the mission's curves from the curve in its data (Following a path). | Yes |
| 18 | Slow Rotate | Zero throttle, yaw input 0.1. | Yes |
| 19, 40 | Jump In | The ship arrives beside its target, flying in from far behind it; 40 then spreads the ships out for 200 ticks, each rolling and pitching away by its place in its group (Jumps). | Yes |
| 20, 41 | Jump Out | The ship turns to where it goes, charges and jumps: to its target, where Jump In of the matching number brings it in, or out of the mission where it names none; a ship jumping with the player's goes in formation behind it (Jumps). | Yes |
| 21 | Find Scoop Up | Starts at its first step, as 115 and 116 do (0x0040B1C0). The first goes on to the second, where a multiplayer game waits for every player unless the target is a ship (ai_sequence_sync, 0x00401000). The second walks the ships its target names for the nearest to where the ship will be next that it can aim at, ejected or not (0x0040B140), and pushes Scoop Up (107) at it; with none it pops. Once Scoop Up is done, the order starts again from its first step. |
Yes |
| 22 to 24 | Random Spin Slow, Medium, Fast | On starting, zero throttle and each turning input 0.1 plus a random number times 0.3, 0.5 or 0.9. Its update does nothing. | Yes |
| 25 | Fixed Gate Jump In | The ship comes in through the tunnel at the object its target names, out beyond its mouth, cut by its portal (Gates). | Yes |
| 26 | Fixed Gate Jump Out | The ship goes out through the nearest gate's tunnel, the player's riding the worm, then comes in through its target's (25) (Gates). | Yes |
| 27 | Formation | On starting, keeps its place abreast of the target, 3000 apart: the ship SetAI numbers n flies n / 2 + 1 places out, to the target's left where n is even and to its right where it is odd. Each update it pops once it can aim at the target no more; otherwise it arrives at its place in the frame the target will stand in next, turned as the target will be, at a least throttle of 0 (Arriving). |
Yes |
| 28 | Fixed Gate Open | A tunnel grows open at the object (Gates). | Yes |
| 29 | Fixed Gate Close | The object's tunnel shrinks away and goes. | Yes |
| 30 | Eject | The pilot leaves the ship in its cockpit, which becomes the pod, and the rest of the ship a new object; the pod clears the ship, and the player's waits to be picked up (Ejection). | Yes |
| 31 | Fixed Gate Collapse | The gate comes down in fireballs, its tunnel burning out (Gates). | Yes |
| 32 | Match Speed | Sets the throttle to the target's speed over the ship's cruise speed. Pops when the target is no longer valid. | Yes |
| 33 | Dark Reign shoot | Picks the nearest ship of its target it can fire at and queues 110 at it; with none, pops (The ion cannon). | Yes |
| 34 | Move to spawn pos | A deathmatch's (deathmatch.cpp). Not read yet. |
No (#55) |
| 35 | Turns object lights on | Turns on the light maps of the parts flagged lightmap (the second pass of their surfaces), heard (bigon), and pops. The rogue base (165) turns on a surface of its first part each 100 ticks, four of them, then those of its other parts, heard at each step, and pops after 500 ticks. Where the light maps aren't drawn (Lmaps, 0x5D5618) it pops at once. A ship type shares its meshes, so every ship of the type goes with it. Fix: the game turns on only the level of detail each part is drawn at, and a second pass for every surface, drawing those with no second texture flat; OpenReliant turns them on at every level, and only where a surface has a second texture. |
Yes |
| 36 | Make Boridin section break away | The Boridin hides the nine parts its breakaway section takes (Bor break off section, its core, cylinder, projector, projector generator and that generator's wreck, two launch doors and Object02), its Bor main body streams great grey smoke for 5000 ticks, and the first Boridin breakaway among the objects stands where the section stood, 4000 along its nose, turned as it was, so that it seems to come away from the Boridin. Its update does nothing. Fix: with no breakaway, the game stops with an assertion; OpenReliant logs it. |
Yes |
| 37 | Rotate Boridin breakaway warp projector | The ship's Bor brkawy proj (its name ending in a space) loops its Rotate Proj track from where it stands, at four times its pace, and the order pops. No shipped mission gives it. |
Yes |
| 38 | Start warp projection from Boridin | The Boridin breakaway projects a warp tunnel ahead of it, with six beams, and never ends (The Boridin's projection). | Yes |
| 39 | Make ripper drop what it's carrying | A Ripper lets go of what it carries (The Ripper). | Yes |
| 42 | Turns object lights off | Turns off the light maps order 35 turns on, at every level, and pops. | Yes |
| 43 | Huuuuuuuge explosion | The Uber Explode at the object, of size 50000 over 1500 ticks (Effects), then it pops. | Yes |
| 44 | Immediately set ship to zero velocity and rotation | object_stop (0x00403000), then it pops. |
Yes |
| 45 | Fly ship backwards | Throttle -0.5, no turning. | Yes |
| 100 | Player Control | The player's controls fly the ship (Controls). | Yes |
| 101 | Multiplayer Control | Disables the object once it has object flag 0x10000000. |
No (#55) |
| 102 | Avoid Target | While on course to hit its target (ai_collision_course, 75 steps ahead with a margin of 2000), pitches at full input away from it at half throttle. Once clear, it flies straight at full throttle until 50 ticks after it started, or 50 to 150 after it last turned away, the extra drawn from its own random numbers, and pops. It pops at once where its target is not a ship. No shipped mission gives it. |
Yes |
| 103 | Torpedo | Find New Target's for a ship of the torpedo class. On starting, full throttle. Each update it pops once it can aim at its target no more; otherwise it steers with a limit of 2 and no ease at the target's node, led along the target's nose by the target's speed times the ticks the torpedo takes to get there at its top speed, less its own velocity times half those ticks, 25 at most. It goes off against what it meets (Collisions). | Yes |
| 104 | Launch | The ship leaves its carrier, in the style the carrier's type picks (Launches). | Yes |
| 105 | Fight | Fights its target by running combat maneuvers, one after another. | Yes |
| 106 | Eject | The ship a pilot has left: destroyed 200 ticks on. | Yes |
| 107 | Scoop Up | A nanny ship or the Antanov takes the player's pod aboard with its tractor beams (Ejection). | Yes |
| 108 | Eject Spin | An AI pilot's ship spins, unpowered, for 200 ticks; then the pilot ejects (Eject). | Yes |
| 109 | Dock | Docks at a port of its target (Docking). | Yes |
| 110 | Dark reign shoot | Fires the ion cannon of the Dark Reign, the Boridin or the rogue base at its ship, which it destroys; ion_cannons_hold_lock keeps its lock (The ion cannon). |
Yes |
| 111 | Ripper end drop object | A Ripper draws its forearms back once it has let go (The Ripper). | Yes |
| 112 | Ripper attach cargo pod to Mammoth | A Ripper fits a cargo pod onto a Mammoth (The Ripper). | Yes |
| 113 | Eject fighter attack | A Sabre flies at the player's pod and shoots it down (Ejection). | Yes |
| 114 | Disrupted | A Havoc's shockwave gives it (Effects). On starting, sets object flag 0x8 (unpowered), keeps the tick to end at, the duration in its data (a word) after frame_start, takes the push in its data after that (three floats) as a knock in the ship's own frame, though the shockwave gives it in the world's, and knocks each turn rate by up to 0.05 either way at random, which the ship tumbles by. It also plays fifteen electric rays over the ship, each from its centre out to its radius in a random direction, 90 either way, with a jitter of 0.6, flickering, dimming as they go dark, and lasting as long as the order, white (0.8, 0.8, 1) and blue (0.3, 0.5, 1) in turn. It pops past that tick, and its exit clears the flag. |
Yes |
| 115, 116 | Make capship list left, right | A capital ship struck by a torpedo lurches (Collisions): its update (capship_list, 0x0040C3A0) sets the roll and yaw inputs to 0.01 and 0.006 over the ship's roll and yaw rates, to the left for 115 and the right for 116, then 200 ticks on to 0.006 and 0.0048 the other way, and 300 ticks after that clears the roll and pops. |
Yes |
| 117 | Friendly Fire | The carrier recalls the player's ship for destroying a friend, and it lands (Friendly fire). | Yes |
| 118 | Eject Player | The player's ship drifts, unpowered, for 400 to 599 ticks, then explodes, unless the pilot ejects first (Destruction, Ejection). | Yes |
| 119 | Ship Follow Curve Backwards | Flies the path of Ship Follow Curve (17) backwards, from its end to its start. | Yes |
| 120 | Mill | On starting, where it can aim at its target, cloaked or not, keeps the tick and a circle facing from the target's node to where the ship will be next. Each update it pops once it can aim at the target no more or 500 ticks have passed; otherwise it flies at full throttle, steering with flags 0x3 for a point on the circle 50000 from the node, which comes round from the ship's side by 0.000005 of its cruise speed a tick. Improvement: OpenReliant computes the sine and cosine rather than reading the engine's tables (sr_sin, sr_cos). |
Yes |
| 121 | Deathmatch Respawn Effect | A deathmatch's (deathmatch.cpp). Not read yet. |
No (#55) |
| 122 | Deathmatch Dark Reign target | It has no routines: nothing to run. | Yes |
| 200 | (nameless) | It has no routines: nothing to run. | Yes |
Ship Follow Curve (17) and Ship Follow Curve Backwards (119) fly a ship along a path of the mission's
curves (The director's camera): from the curve its data names, on through the
curve that carries the path on from each curve's end, over the seconds its data gives. The data holds
the curve (+0x0A), the seconds (+0x0E) and the ship that carries the path (+0x12), whose offset
from where the mission placed it, as the order starts, moves the whole path (curve_ride). Their
state:
| Offset | What it holds |
|---|---|
+0x00 |
The routine motion_follow gets its point from: 0x00403200, or 0x00403600 backwards |
+0x04 |
The fastest the ship moves, as a share of its top speed: 1 |
+0x08 |
The curve it flies now |
+0x0C |
The step |
+0x10 |
The mission's tick (mission_ticks) the curve began |
+0x14 |
The curve's share of the order's seconds, in ticks, as its length is to the path's |
+0x18 |
The path's length |
+0x28 |
Where the carrying ship stood as the order started |
+0x34 |
The share of the way along the curve to the next place a point marks, 0 for none |
| Step | What it does |
|---|---|
| 0 | It arrives (ai_arrive) at the path's start, turned toward the path's point 4 ticks on and at the throttle the path keeps between them over its cruise speed; the curve's clock holds at its start. The order backwards arrives at the path's end instead, turned toward its point 4 ticks back, its motion motion_forward |
| 1 | In a multiplayer game it waits for the other players (ai_sequence_sync, 0x00401000, not ported: #55); in a game of one, it goes on |
| 2 | It flies motion_follow along the path, or motion_follow_backwards where its motion was astern; backwards, motion_follow. The path moves it on to step 3 |
| 3 | The order pops, in a multiplayer game once the other players are there too (not ported: #55), and its exit gives the ship motion_forward, or motion_backward after motion_follow_backwards |
The path's routine, each update of the motion, gives the curve's point as far along as its ticks
have gone by mission_ticks, which runs on through a frame's simulation steps (curve_point),
carried with the carrying ship. Forward, past a place a point marks, the
point has ShipReached, with the ship (event_post_ship_reached, 0x0045AC10), one place an update.
At the curve's end the ship it ends at has ShipReached too, and the next curve begins; with none, the
step moves on. Backwards, past the curve's start, the curve before it begins, found by walking the
path again from the order's curve, or at that curve the step moves on.
Fix: the game divides by nothing for a path of no length and for a curve given no ticks, and
follows a path that comes round on itself for ever as it walks it; OpenReliant gives a curve of a
path of no length all the order's ticks, takes a curve given no ticks to its end going forward and
past its start going backwards, as the game's endless share does, and stops measuring such a path,
and walking it for the curve before one, after as many curves as the mission has
(Curves). Flying forward, the order counts the curves it has moved on to, in
bytes of its state the game's follow orders leave alone, and the path ends once it has taken as
many as the mission has. Walking the path backwards, the game
takes a curve's end for a ship unless its whole reference, kind and all, is 0x0000FFFF
(0x0040359E), and so walks on from a curve that ends at no ship to one that starts or ends at
none; OpenReliant stops at an end whose index is 0xFFFF. It holds a curve's ticks at 65535, where
the game takes them round from nothing. The game moves the step on, and forward posts the end's
ShipReached, at every move past the path's end, so that a second move before the order's update, a
collision's or that of a second step in the same pass, leaves the order running, to fly the path
again once the step comes round; OpenReliant moves it on once.
Formation Regroup (14) and Patrol Route (15) fly a flight group's ships in the formation their
points give them (Formations): a ship's place is its point's place
about the formation's origin. The formation's leader is the first of the mission's ships of the
group that stands at the formation's point nearest its origin (formation_leader, 0x00404230),
and the leader's update moves the whole group on (regroup_sync, 0x00403DE0; patrol_sync,
0x00404330): once every ship of the group still in the mission is ready, they go on together.
Missions give Formation Regroup with SetAI and Patrol Route with SetPatrolRoute
(Commands); no shipped mission gives either.
Both set the throttle by a pace (formation_set_pace, 0x00404210): a pace of p is a speed of
200p, over the ship's top speed. They turn a ship toward a point in one of two ways
(formation_face, 0x004044D0): with ai_steer eased by 0.1 and held within a limit, or on the
level (object_turn_level, 0x00404750). On the level, its yaw input is five degrees, in radians,
toward the side the point lies on, or the angle to it off the nose on the level where that is less;
its pitch input the angle up or down to it, less 6 times its pitch rate, times 0.05 over its flight
stats' pitch rate; and its roll input nothing. Either way the point is ahead once the cosine of its
angle off the nose is 0.9 or more.
Formation Regroup gathers the group at its places about its middle: the middle of the box round where its ships stood at the mission's last sync. Each ship meets the group at its place about the middle, 10000 back along Z, and its throttle goes to nothing. Its state:
| Offset | What it holds |
|---|---|
+0x00 |
Where it meets the group |
+0x0C |
Its place about the group's middle, 10000 on along Z from there |
+0x18 |
The formation's leader |
+0x1C |
The step |
+0x1D |
Whether it is ready |
+0x1E |
Whether it flies straight to its meeting point, rather than along a curve |
+0x1F |
How many of the leader's updates in a row the group has been ready |
+0x20 |
The curve it flies along, laid out as the mission's are (Curves) |
+0x64 |
How it follows the curve: whether it has come to the curve's start (+0x6E), and the step along it it flies toward (+0x74) |
| Step | What it does |
|---|---|
| 0 | It turns to face its meeting point with ai_steer at a limit of 0.75, and is ready once that lies ahead |
| 1 | It flies to its meeting point, straight with ai_steer at a limit of 0.75, or along its curve, and is ready within two of its radii of it, its throttle then nothing |
| 2 | It turns to face its place, and is ready once that lies ahead |
| 3 | It lets go of the turns and the order pops |
Once the group has been ready for 15 of the leader's updates in a row, each ship is ready no more
and goes on to its next step; while any is not ready, the count starts again. Setting off for its
meeting point, a ship flies along a curve where the point lies from 20000 to 400000 away, and
otherwise straight, at a pace of 0.3. The curve runs from where the ship stands to its meeting point
and names no ships, its leaving tangent half the way across, turned a right angle about Y, so that
the ship swings out to the side (regroup_lay_curve, 0x00403F20). The ship first flies to the
curve's start at a pace of 0.3, with ai_steer at a limit of 0.5, then toward the curve's point at
each 32nd of the way in turn, the next once within two of its radii of one, its nose turned straight
at the point each update (curve_path_follow, 0x00404BE0). Past the curve's end the order pops,
its throttle and its turns nothing; the ship is at its meeting point and ready before that.
A ship in no formation, or in no flight group or an empty one, takes none of this: its state stays empty, so that it turns toward the world's origin, its throttle as it was.
Patrol Route flies the group round the waypoints of a route: the route's waypoints in the order
the mission lists them (Binding), from the one the order's target names, and
after the last its first again (waypoint_next, 0x00404000). Its state:
| Offset | What it holds |
|---|---|
+0x00 |
The leader's mark, the point ahead of it that it turns toward |
+0x0C |
Where the leader stood at the mission's last sync |
+0x18 |
The point it flies to at its waypoint |
+0x24 |
Its place about the leader |
+0x30 |
That place 8 of the leader's radii on ahead of the leader |
+0x3C |
The way from the leader to its mark |
+0x48 |
The waypoint |
+0x4C |
The formation's leader |
+0x50 |
The leader's state |
+0x54 |
Its formation point |
+0x56 |
The mode: 1 in formation, 2 alone, and 0, which nothing sets, turning to face its place ahead |
+0x57 |
Whether the group's ships lie near their places alike, within 5000 of each other, which nothing reads |
+0x58 |
How far it stood from its place ahead as it set off, which nothing reads |
+0x5C |
The leader's pace |
+0x60 |
The size of its formation: how far apart the corners of the box round its points lie |
+0x64 |
Whether it is in place |
+0x65 |
Whether the leader is to aim at its waypoint again |
+0x66 |
Whether the leader has come to its waypoint |
Each waypoint starts the pace at 0.15. A ship without a point in a formation flies alone: to its
place in a line of its group's ships through the waypoint, along the way from the next waypoint,
three widths of the group's widest apart, the group's first nearest the next waypoint. It steers
there with ai_steer at a limit of 0.5, and within two of its radii sets off for the next waypoint.
In formation, each ship's place is its point's place, turned as the leader is, about where the leader stands, and it turns on the level toward that place 8 of the leader's radii on ahead of the leader. Turned to it and within 3.2 of its radii of its place, it slides 0.005 of the way there each update. Until it is in place, it flies at 3 of the leader's paces while its place lies ahead of it, and at a twentieth of the leader's pace while its place lies behind it; within two of its radii of its place and turned to it, it is in place, and flies at the leader's pace and a tenth more. The leader is always in place, flies at its own pace, and has come to its waypoint within half its formation's size of it. It turns on the level toward its mark.
Once every ship of the group is in place, each is no longer. Where the leader has come to its waypoint, each sets off for the next waypoint, stopped, its places taken anew. Otherwise the leader aims at its waypoint again: it turns toward it, takes its mark 8 of its radii along its nose turned by its yaw input, at the waypoint's height, and its pace by how far it must turn: 0.5 under 12 degrees, 0.25 under 60, and otherwise 0.15. Each ship reads whether the leader has come to its waypoint in turn, and the leader setting off clears it, so that the ships after the leader in the group's list aim again rather than set off; they keep their places about the leader all the same.
Fix: the game slides a ship toward its place by placing it (object_place), which drops the
move its step has left to make, so that the ship moves by the slide alone, 0.005 of the way each
frame, and hangs back where that pull meets the leader's pace: as far back as the leader's speed
over 0.005 times the frame rate. At 60 frames a second that lies beyond two of a Sabre's radii at
the pace each waypoint starts at, so that the ships are never all in place and the formation never
moves on. OpenReliant slides the ship and the move it has left together, so that it flies on into
its place.
Fix: the leader reads and writes the order state of each ship of its group, whatever order the ship follows, so that a ship given another order since has that order's state taken for the formation's and changed; OpenReliant counts and moves only the ships that follow the leader's order, and a ship whose leader follows another takes its pace as nothing. The game looks for the leader, and measures a formation's size, among the points from the formation's first to the mission's last, so that a later formation's point nearer its origin can take the lead, which no ship of the group stands at; OpenReliant looks among the formation's own points. Where no ship stands at the lead, Patrol Route follows a null pointer, as Formation Regroup does for a ship in a formation but no flight group; OpenReliant has the ship fly the route alone, and leaves Formation Regroup's state empty. The game takes the group's spread from its first ship, and where that ship is no longer in the mission, from whatever its stack holds; OpenReliant takes it from the first it counts.
Fix: Formation Regroup's state runs past the 0x90 bytes the game allocates for it: the curve's
next marker lies at +0x94. The game looks for the points that mark places on that curve by its
address's place among the mission's curves, which it isn't one of, and so takes the points of
whatever curve that happens to give; OpenReliant keeps no marker, and takes it that none does. Where
the curve ends the order, the game goes on to check the meeting point in the state of the order
below; OpenReliant stops there.
Fix: the game takes a Patrol Route's waypoint wherever the order's target points, past the mission's waypoints or before them, and reads past the table's ends for the waypoint after the last and before the first; OpenReliant ends an order whose target is past the waypoints, and stays within them. The game follows a null pointer for a ship with no flight stats, a stand-in, whose pace or level turn the orders set; OpenReliant leaves its throttle and its inputs as they are.
Improvement: the points along a curve are computed rather than taken from the table of the
curves' weights, as for the curves' lengths (Curves); the five degrees and the
turns by which the leader picks its pace are exact; and the angle up or down comes from
std.math.atan2 rather than the engine's table (sr_atan2).
Dock (109) brings a ship into a berth at another's port. Its target names the ship and the port by
its component: the port is the ship's docking points (SHP attachment kind 9), counted part by part
over its root's children. Where the target names no port, or names a flight group or a squad, the
init (order_dock_init, 0x00406B80) takes the first free port of the ships it names, in the order
it walks them: the first port at which no other object's current order is Dock
(0x00406A90). It then picks a style by what the ship is and what it docks at, from a table of an
init, an update and an exit each (0x004E1618): the limpet car's (2), or at the Czar docked its
own (3); the limpet pod's (4); at a Nanny, the Nanny's (1); and otherwise the station's (0). The
data's first byte holds the style.
The station's style finds the docking points (0x00406C80): the ship's own, its first, and the
port, whose part plays its deploy track at 4 from its start. The port is found by counting the
component down at each docking point, so a component past the station's docking points, or a
negative one other than -1, which the init's search leaves alone, names none. A ship without a
docking point stops the game with "Docking information not defined on %s". The check of the
station's port reads the ship's own node again (0x00406E2A, +0x10 where the port's is
+0x20), so a station without the port goes on to the berth with the port's node null, as
order_push cleared the state, and faults there. The berth (0x00406E70) is where the ship's
origin stands docked: the port, less the ship's own docking point turned by the port's
orientation, in the frame the station's part is drawn at, and turned as the port is.
Fix: the game takes the ship's own docking point in its part's own frame alone (the node's
+0x18 and +0x3C, 0x00406E97), which berths a ship whose point is on a part hung from another
part off by that part's place; OpenReliant carries it up the parts it hangs from. Every shipped
model has its docking points on parts that hang from the root.
The station style's state:
| Offset | What it holds |
|---|---|
+0x00 |
The routine motion_follow gets its point from as the ship slides in: 0x00406F20 |
+0x04 |
The fastest the ship slides, a share of its top speed: 0.5 |
+0x08 |
The step: the station style's, or the limpet car's (Limpet docking) |
+0x0C |
The mission's tick (mission_ticks) the slide ends at |
+0x10, +0x14 |
The ship's own docking point's node and place on it |
+0x20, +0x24, +0x30 |
The port's node, its place on it and its orientation |
+0x54 |
Whether the ship came from the port's right, which mirrors the way round |
+0x58 |
Where the ship stood as it began to slide in |
The init (0x00407010) picks the first step by where the ship stands in the port's frame: more
than 100000 behind the port, step 3 where it stands within a fifth of that to the side, and step 2
further out; nearer, step 1 behind the port and step 0 ahead of it. Steps 0 to 4 steer for a point
in the port's frame, mirrored across it for a ship that came from its right, at full throttle
(ai_steer, no flags), rolling the ship to stand as the port stands (its roll input the roll
between them less twice its roll rate, in degrees over 40, within 1), each on to the next within
2000 of its point. Holding the roll leaves the ship its pitch and yaw to come round with: a ship
that turns flat, as capital ships do, flies the whole way round, while a banking one turns only
while the point lies within 18 degrees of its nose or its tail.
| Step | Point, in the port's frame |
|---|---|
| 0 | 100000 to the side |
| 1 | 100000 to the side and 100000 behind |
| 2 | Twice the ship's cruise speed over its yaw rate to the side, 100000 behind |
| 3 | 50000 behind, on the port's line |
| 4 | 10000 behind, on the port's line |
| 5 | The ship latches on: attached, flying motion_follow, the station stopped dead, for 1000 ticks |
| 6 | It slides in: the path's point stands on the port's line behind the berth, as far back as the ship stood as it latched on, times the square of the share of the 1000 ticks left, with the port's up as the way up. Past them, the ship's motion is motion_backward, and the step moves on |
| 7 | It is stopped dead, set in its berth, heard docking (sound 0x37, dock), and has its Docked (event_post); the order pops |
Its exit, the Nanny's too (0x00407D10), leaves the ship's first pass-through slot empty. The
ship stays attached.
Fix: the exit leaves the motion as it is, so a ship whose order ends while it slides in goes on
flying motion_follow, which then reads the next order's state as its path. OpenReliant puts a
ship still on motion_follow back on motion_forward, as Ship Follow Curve's exit does.
Fix: where the ship has no docking point the game stops, and where the station has no port that the component names it faults. OpenReliant logs either, and the order ends.
Fix: the game goes on reading the frames of a station that has gone; OpenReliant ends the order, and until then the slide in holds the ship where it is.
dock_nanny_init (0x004073E0) enables carrier pass-through and plain motion. It stores the
selected port's point, raised by the docking ship's height. dock_nanny_run (0x00407510)
opens entry door 0 or 1, approaches a point 1500 above and 20000 ahead of the port, then
steers into it. Within 500 units it stops and closes the door. After strictly more than
500 ticks, create.rearm refits missiles through the shared create.arm path and restores
countermeasures, afterburner fuel, gun charge and rounds. Player ships retain their chosen
loadout, including modded missile racks. Hull and shields are not repaired.
Exit door 4 or 3 opens. After 400 ticks, Docked is posted and forward motion resumes. The ship uses its afterburner for another 150 ticks, then the exit door closes and the order ends. The player's locked Nanny view (43) returns to the cockpit. Fix: each door closes from its own animation time instead of using the first port's time. Invalid ports or missing carriers end the order without dereferencing missing nodes.
dock_limpet_init (0x00407D30) finds the docking points and works out the berth as the station
style does, and keeps the limpet car's step in the same word, +0x08, starting at 0
(0x00407D5E). dock_limpet_run (0x00407D70) approaches 10000 behind the berth, then uses the
station style's slide callback for 1000 mission ticks at half speed. It stops the carrier during
entry. At the berth, clamp parts 2 and 3 play rotate at speed 4. A visible pod mesh (part 0)
is hidden and replaced by a separate limpet-pod object, attached to the carrier. If it was
already hidden, the mesh shows again and the first existing limpet pod is retired, as in
the original. Three 400-tick waits cover clamp rotation, departure and clamp return.
| Step | What the limpet car does |
|---|---|
| 0 | It flies to the point 10000 behind the berth |
| 1 | It latches on, and the slide starts |
| 2 | It slides in |
| 3 | It is set in its berth, heard docking, and takes or leaves the pod |
| 4 | Its clamps turn, for 400 ticks |
| 5 | It backs away, for 400 ticks |
| 6 | Its clamps turn back, for 400 ticks, and the order ends |
dock_way (0x00406F20) moves the word on by one at the slide's end, whichever style's step it
holds (0x00406FF4). When the carrier explodes, the order ends at step 0, and the limpet car is
destroyed with it at steps 1 to 4.
The order posts Docked if the pod mesh is hidden, Undocked otherwise. Its exit restores
forward motion, clears attachment and clears the first two pass-through entries. The
separate pod's update (0x004084B0) destroys it if its carrier explodes. Undocked is also
exposed to mod scripts through the event declarations.
At the Czar docked, the limpet car's own style (dock_limpet_czar_run, 0x00408220) runs
dock_limpet_run for steps 0 to 2, then takes or leaves no pod. At step 3 it is set in its berth,
heard docking, and its clamps turn, and it has its Docked at once. It holds on for 12000 ticks
before it backs away, and the order ends with its Undocked. Its init and its exit are the limpet
car's (0x00408210, 0x004084A0).
Land (8) brings the player's ship down on its carrier, and ends the mission. PERMISSION TO LAND
gives it (permission_to_land, 0x00453DE0), which frame_controls calls outside a multiplayer
mission: heard at most once in 500 of the timer's ticks (0x0052987C), it aims Land at the ship
the player launched from (player_carrier). In training, the training missions (30 to 35) and the
simulator's training, the flight instructor clears the ship to land where the script's variable 10 (landing_cleared, 0x0052A418) is set. In
any other, unless the ship is landing already, the carrier refuses it, or clears it where variable
10 is set. Mission 1's script sets it as the Reliant jumps in. The pilot asks on the radio, and the
answer comes as a report 300 ticks later (Reports); where the radio's reports
are all taken, nothing answers and nothing lands.
The init (order_land_init, 0x0040EAC0) picks a style by the carrier's type, from a table of an
init and an update each (land_styles, 0x004E1FE8): the Reliant's (1) and the Yamato's (0). Any
other stops the game with "Cannot land on %s". The state's first word holds the style. The
Reliant's init (0x0040F5C0) sets the first step, due in 700 ticks, or at once where the ship is
being sent home for its friendly fire (ending 6 or 7). Its update (0x0040F940) keeps its state
as:
| Offset | What it holds |
|---|---|
+0x00 |
The style |
+0x04 |
The frame's tick the step waits for |
+0x0C |
The step |
+0x10 |
The middle of the tube the ship lands in |
| Step | What it does |
|---|---|
| 0 | Until the step is due, the player flies on (player_controls). Then the cutaway: the scene becomes the landing's (0x00587CD4 = 3), every object but the ship and its carrier is disabled (flag 0x400) and those two enabled, the ship flies by its nose (motion_plain) and passes through everything, and the upper door of the carrier's first launch tube, part 6, plays opendoor2 at 1.23 with the door's sound (0x35, dooropen) where it stands |
| 1 | The ship steers at a point 3000 over the tube's middle (ai_steer, no flags), its throttle 0.0001 for each unit it has to go, at most 0.5. Within 200 of it, it stops, its turns nothing, for 50 ticks |
| 2 | It sinks along its own Y axis, which points below it (motion_downward) |
| 3 | Its throttle is its distance from the tube's middle over 15000. Once it is less than 100 over it, along the carrier's Y axis, it stops, with the landing's sound (0x3E, shipland), for 270 ticks |
| 4 | The mission is over (mission_over) |
The cutaway (0x0040F600) switches to one of the landing's views
at random, and moves the carrier to (0, -1000000, 0), turned as the world is. The ship lands in the
carrier's first launch tube, the one the launch drops ships out of through its lower door
(Launches): its middle is halfway between the middles of the
tube's lower door, part 0, and its upper door, part 6, each the middle of the bounds of the level
its part drew last. The ship stands 20000 ahead of it and 5000 above in the carrier's frame,
looking at the point 3000 over it, stopped, its power shared evenly. The carrier's orders are all
popped, and it stops. The approach leaves the ship pitched about 6 degrees nose down, as it looked
at the point over the tube from where it started, and it sinks along its own Y axis, so that it
comes to rest ahead of the tube's middle, tilted as it came.
Improvement: the ship levels out as it approaches: the point it steers at rises from the
game's height to the ship's own as it nears the tube, eased in and out over the way it comes, so
that it arrives level. It stops dead over the tube, and sinks straight down it, coming to rest
level. Once its top is below the underside of the tube's upper door, the door closes over it, its
opening played back as fast as the launch opens the tube's doors, 2 a step, with the game's sound
of a door closing (0x36, doorclos).
--original lands it as the game does, and leaves the door open.
Fix: a landing on a ship that nothing lands on ends at once, where the game stops, and PERMISSION TO LAND does nothing where the player's ship launched from no carrier, where the game reads through a null pointer.
Improvement: the player's ship lands in a hangar bay of any other carrier that ships launch
from as one (Hangar bays), the Victorious or a mod's carrier among them,
so that PERMISSION TO LAND ends a mod's mission on the carrier the mission launched the player
from. The landing aims at a gate: the component of its order's target, which a mission's SetAI
names with component(carrier, gate), or gate 0 when the order aims at the carrier as a whole, as
the player's request does. Aimed at the whole carrier, the landing is due 700 ticks on, as the
Reliant's is; aimed at a gate, it begins at once, so that a mission's script lands the ship at its
own moment. A carrier whose model has no launch point for the gate is one that nothing lands on.
A ship other than the player's lets the landing go at once, as on the Yamato.
The cutaway is the Reliant's: one of its two views, picked at random, standing by the bay (The landing's views); the carrier moved away and stopped, every other object disabled, and the ship passing through everything. The ship is set out from the mouth of the bay, where the way out from the launch point along its Z axis leaves the carrier's bounds, by six tenths of the bay's depth from the point to the mouth, and at least twenty times the ship's radius, so that the distance fits the carrier and the ship rather than one model. It looks in at the point, and the gate's doors open as the launch opens them. The ship then steers at the launch point, upright, its throttle 0.0001 for each unit it has to go, at most the throttle that flies it at the Yamato's bay speed of 100, and stops within 200 of the point, sounding its landing. The doors close behind it, and 270 ticks later the mission is over.
The Yamato's style lands the ship in the Yamato's landing bay, ship type 0xD5 (Jap Yam Landing
Bay, yamhanger.shp), whose part 6 is the hangar and part 1 the pad. Its init
(land_yamato_init, 0x0040EB60) sets the first step, due in 700 ticks, and for the player's ship
makes the bay in the cutaway slot, passing through everything. Its update (land_yamato_update,
0x0040EE80, steps from the table at 0x0040F59C) keeps the pad's due time at +0x08, and once
the ship stands on the pad, where it stands on it, and how it is turned. A ship other than the
player's lets its landing go at once.
| Step | What it does |
|---|---|
| 0 | Until the step is due, the player flies on. Then the cutaway, and the ship flies by its nose and passes through everything; the scene becomes the landing's, and every other object is disabled |
| 1 | The ship steers at a point 1000 above the hangar's middle, upright, at a throttle that flies it at 100 by its cruise speed, until it is within 2000 of it |
| 2 | It steers 500 ahead along its nose from the point 500 beyond the pad's middle, level with its own underside, its throttle 0.0001 for each unit it has to go less 0.02, at most the bay's, and stops within 500 of it |
| 3 | Stopped, it turns straight along the bay and level: its yaw and its pitch inputs are each the angle off the bay's Z axis less 4 times its rate of turn, times 0.3 over its type's rate. Once both are within 0.05, it stands on the pad and rides it |
| 4 | It waits 200 ticks |
| 5 | The pad plays its floor track (0x004E2048), lowering the ship, with the landing's sound (shipland) |
| 6 | 400 ticks later, the mission is over |
The cutaway (land_yamato_cutaway, 0x0040EBC0) moves the bay to (0, -1000000, 0), turned as the
world is, lights its hangar through mask 0x3B, and switches to one of the Yamato's landing's
views at random, which it keeps (0x0051863C) and nothing reads. The
ship stands in the bay at a fifth of the bay's width and height from their minimums and three
tenths of its length, looking at the hangar's middle, stopped at a throttle of 0.5 with its power
shared evenly.
Fix: where the bay's model lacks the hangar, the game reads past its list of parts; OpenReliant leaves the ship turned as it was.
Improvement: the angles come from std.math.atan2 rather than the engine's table (sr_atan2).
OpenReliant has no parent frames, so the ship on the pad is put back on it each frame
(ailand.hold), as the launches hold their ships (Launches).
The player's hits on friends (friendly_fire.zig):
object_damage and object_armor_damage hand a blow of the player's ship on a friendly object, a
shot's, a Screamer's or a collision's, to friendly_fire_warning (0x00474C80), the armour's only
while the object is not exploding, as component_damage does a shot's or a Screamer's on a friend's
component. The damage after the difficulty's scaling mounts (friendly_fire_damage, 0x00562CEC),
and once past 800 (0x004DC5EC) and 3000 game ticks after the last (friendly_fire_quiet_until,
0x00562CE8), Moose warns the player on the radio, one of ff_001 to ff_004, and the damage
counts from nothing again. The count of warnings (friendly_fire_count, 0x00562CF0) would pick
ff_005 to ff_008 for a second and ff_009 to ff_012 for a third, but the game takes a second
back to the first, so those are never said. friendly_fire_reset (0x00474B20) clears the three as
mission_run starts a mission.
Destroying a friend sends the player home: armour below zero from such a blow, but a collision with
a pilot's pod that anything could harm, or a friend's component destroyed by a shot or a Screamer,
calls player_friend_destroyed (0x00474E00), which marks the player's ship (+0x678, 1), as
the script's FriendlyFire (0x57) does. Each frame after the orders, mission_frame
(0x0049298C) runs friendly_fire_send_home (0x00474B40) for a player's ship in the action
and marked: while the mission goes on and the ship's current order has no priority, the mission's
ending becomes 6 (7 where the mark is 3), and the ship takes Friendly Fire (117), aimed as its
current order is, and is sent off (flag 0x10000000).
Friendly Fire keeps its stage (+0x00) and the frame's tick it ends at (+0x04):
| Stage | What happens |
|---|---|
| 0 | Its init (0x00474E60) has Moose say one of ff_013 to ff_023; the player flies on (player_controls) for 300 ticks |
| 1 | For 700 ticks the ship is steered toward the carrier the player launched from (ai_steer, limit 1, no ease, no flags), the player's controls keeping 0.7 of the throttle and of each turn, the steering 0.3 of the turns |
| 2 | The order ends and the ship, no longer sent off, takes Land on the carrier, which lands it at once (Landing), and Jump Out at itself before it where the carrier is farther than 1000000; it is sent off again. The game also clears the carrier's flag 0x20 |
Fix: the game reads the carrier through a null pointer where the player launched from none; OpenReliant lets the ship fly on as the player has it, and then ends the order.
Not ported: a multiplayer game's side of it
(#55). There, destroying a pilot's pod only a
player could harm marks the ship 2 and tells the others, whose carrier then aborts the mission
(abrt_001, said by pilot 27 for the Reliant and 28 for any other); and another player's ship
sent off jumps out.
The Ripper (type 0x1F) carries cargo pods in the tractor beams of its four back pincers
(airipper.cpp). Mission 1 has one lift sixteen pods onto a Mammoth at Fort Sherman: its script
orders the first grab, and two triggers that resume where they stopped (InterruptTriggerCode)
answer each RipperGrabbedObject with the next drop and each RipperDroppedObject with the next grab.
Two tables of 150 entries serve every Ripper, set up as a mission loads (0x0040FC90) and let go as
it ends (0x0040FCF0). rippercargo (0x00518648) pairs a Ripper with what it carries, filled in
turn from next_rippercargo (0x00518AFC); what a Ripper carries is the first entry naming it
(0x00412340). A set of beams (0x74 bytes each at 0x00518B00, the next at 0x00518640) is
four tractor beams (tractor_beam_mesh, over laser2), one from the first point of each pincer's
first point list, the first two reaching for the middle of the pod's first pair of points and the
last two for that of its second (SHP). An order shows them as it runs
(0x00412200), aimed and faded as a tractor's beams are: each from its pincer's point in the
pincer's frame, to the middle of its pair of the pod's points taken in the frame the pod's
Cargo pod part hangs from (the frame's parent, +0x10), which for the shipped pods is the pod's
own place, so the beams stay on a pod that is disabled and not framed. In OpenReliant, a set keeps
where its beams come from and how solid they are, and drawing makes the beams as it first draws
the set (airipper.GripShown) and keeps them apart from the game's sets (airipper.Drawing).
The Ripper's tracks play on every part of it that has them (0x0049A400): ready to grab, its
forearms reaching out; grab pod, its pincers closing; and cabin turn. A step that waits for
the forearms or the pincers played backwards waits for the root's first child's track to be back
at its start; one that waits for the cabin to turn back waits for the Ripper Cabin part's. Each
wait for the other players between the steps (0x00401000) passes at once in a single-player
game. A Ripper comes to rest where its turning inputs and its throttle are within 0.025, and its
rates of turn within 0.02; Ripper end drop object's last turn needs its inputs and rates below
those values.
Ripper grabs target object (12) lifts its target aboard. Its init (0x0040FD10) makes the target
invulnerable, takes the beams, flies the Ripper by motion_plain, held (attached), and has the two
pass through each other. The Ripper stops 2500 above the target's component, in its frame, where the
target names one; in mission 26 (Rules by mission number), 1000 below the target, which it lifts from
below; else at the
target. Its steps (0x0040FF80), each timed from its start:
| Step | What happens |
|---|---|
| 1 | The Ripper steers at the point (limit 0.3): at full throttle while more than 2000 beyond where it stops, at 0.2 nearer, and not at all within 2000 of the point, or 100 lifting from below; near it and not facing it within 0.7, it holds still. At rest, it plays ready to grab at 15 |
| 2 | From below, it turns to face the target until at rest |
| 3 | 150 ticks: the beams come on, as the square of the time; then the target is heard (sound 0x3D) and no longer frozen |
| 4 | 300 ticks: the target is drawn halfway to 300 from the Ripper, toward it |
| 5 | 500 ticks: the target turns to match the Ripper, easing (cosine_ease) |
| 6 | 300 ticks: it is drawn the rest of the way; then heard (sound 0x3C), and the Ripper plays grab pod at 10 |
| 7 | 150 ticks; then the Ripper plays cabin turn at 4.5 |
| 8 | 500 ticks |
| 9 | The target's part Cargo pod hides and the Ripper's shows; the target is disabled and as invulnerable as before; the Ripper carries it and flies astern (motion_backward); the order ends, with the Ripper's RipperGrabbedObject |
Should the target go first, the Ripper takes back its motion and the order ends. Its exit
(0x00410B50) lets the Ripper go and frees the beams.
Make ripper drop what it's carrying (39) (0x00410B90, 0x00410C00) stops the Ripper, flies it by
motion_plain, held. Where the order names a ship, the Ripper fits what it carries to it instead
(order 112). Else, at rest (its speed and rates below 0.05, by their signs), it plays grab pod from
350 at -10, plays sound 0x3C on no object (-1), and lets go: what it carries stands where its own
Cargo pod is, and shows in its place; the order ends, and Ripper end drop object (111) takes
over. What it drops stays disabled.
Ripper end drop object (111) (0x00410E60, 0x00410E90) lets the Ripper go; it plays ready to grab from 350 at -6, then backs away astern at 0.2 for 50 ticks, and plays cabin turn from 400 at
-4.5. Once its cabin is round, it turns, still flying astern, to put its tail to a point 10000 ahead
of it (limit 2) until at rest, and flies on (motion_forward): neither it nor what it dropped
passes through the other any more, it carries nothing, and it has its RipperDroppedObject.
Ripper attach cargo pod to Mammoth (112) (0x00411200, 0x00411420) fits what the Ripper carries
to the component its target names. The Ripper flies astern to 2500 above the component, in its
frame, or below it on a Sharov or a Boridin: at full throttle beyond 2000, then by motion_plain at
0.2 until within 300. At rest, it faces the component (limit 2) and plays grab pod from 350 at
-10. Then the ship is no longer frozen, and the pod stands where the Ripper's own was; over 150
ticks the beams come on, and it is heard (sound 0x3D), shown in place of the Ripper's and
enabled. Over 1000 ticks it eases onto the component, keeping its turn for the first 0.15 of the
time, then turning, easing, until half of it, to the component's orientation turned a quarter back
about its X on a Mammoth, a Sharov or a Boridin and about its Z on another. Heard as it arrives
(sound 0x3C), the Ripper plays ready to grab from 350 at -6, then cabin turn from 400 at
-4.5, and once its cabin is round turns to put its tail to a point 10000 behind it. Then the pod
is gone into the ship: disabled, no longer targetable and hidden, and the component shows; the
Ripper flies on (motion_forward) and has its RipperDroppedObject, and it carries nothing from
then on.
A Mammoth's cargo slots stay hidden until the Ripper fills them because the mission's script
hides them: it disables the components as it makes the ship (DisableObject), which hides each
assembly. Mission 1 disables a squad that holds the Loadup Mammoth's components 3 to 24 as it
creates the ship's flight group.
Fix: where the next rippercargo entry is still taken, the game stops with "Ripper Grab AI
error: Too many rippers doing their stuff at once."; where a Ripper carries nothing, it stops with
"Can't find the object the ripper grabbed!"; and for a component of a type its table of the Mammoth
and the Stalag lacks, it reads the grab point's height past the table's end. OpenReliant logs the
first and takes the entry, ends the order for the second, and stands 2500 above the component for
the third.
Fix: Ripper attach cargo pod to Mammoth never clears the Ripper's rippercargo entry once the
pod is fitted, so what the Ripper carries stays that pod: each later drop or fit by the same Ripper
takes the pod it fitted first out of the ship again, while the pod last grabbed stays hidden where
it was lifted, and RipperDroppedObject names the wrong pod. OpenReliant clears the entry, as Ripper
end drop object does.
Fix: as its cabin turns back, Ripper attach cargo pod to Mammoth waits for the root's first
child, a forearm with no cabin turn track that the step before has already seen at rest, so the
Ripper turns away while its cabin is still turning. OpenReliant waits for the Ripper Cabin part,
as Ripper end drop object does.
Fix: Make ripper drop what it's carrying takes a negative index other than -1 for a ship's
slot, and its sound 0x3C, which follows an object, plays on none; both read before the objects'
table. OpenReliant takes such an index for none, and plays nothing.
Improvement: the pod's turn as it fits divides the share past 0.15 by the span to 0.5, where
the game multiplies it by its rounded reciprocal (0x004DC540).
Improvement: the beams' orders place what they carry once a tick, and the game draws it there;
OpenReliant draws it on between the ticks, as far as a tick of the step would take it
(create.Slot.glide), as the target is drawn in (steps 4 and 6) and as the pod is carried onto the
component. --no-smooth-motion leaves it where each tick places it.
Find New Target's walk visits each ship with 0x0040AE90, which passes over one the ship cannot aim
at, cloaked or not. It counts the objects with stats whose current order is aimed at that ship and
component: Fight, and Mill. The ship then weighs as one to fight the square of the distance times
one more than the ships that fight it, and as one to mill round, times one more than those and one
more than those that mill round it. The lightest of each is kept, but to fight only a ship that is
not cloaked, not the target set aside for the searcher (+0x6AC, which the radio's menu sets to the
player's target for 3000 ticks, 0x0045517F), and for a fighter one that fewer than two others
fight. A set-aside target whose time is up (+0x6B0 before game_ticks) is set aside no more, but
is passed over as one to fight this walk still.
Quirk: the game weighs the one to fight by 0.7 more (0x004DC484) where the count of objects it
has just walked through equals the player's slot, which never happens; it looks meant to favour the
player's ship (#314). OpenReliant weighs as
the game does.