Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The [user guide](guide/README.md) covers installing, configuring and playing Ope
| [`formats/bink.md`](formats/bink.md) | `.bik` movies: the Bink container. |
| [`formats/frc.md`](formats/frc.md) | `.frc` force-feedback effects. |
| [`formats/dte.md`](formats/dte.md) | `.DTE` missions: directory, ships, triggers, and the script VM. |
| [`formats/mission-source.md`](formats/mission-source.md) | Mission source: a mission as Luau text, which `sltool dte decompile` writes. |
| [`formats/mission-source.md`](formats/mission-source.md) | Mission source: a mission as Luau text, which `sltool dte decompile` writes and `sltool dte build` compiles. |
| [`formats/stats.md`](formats/stats.md) | Ship, gun, missile and pilot stat tables. |
| [`formats/save.md`](formats/save.md) | Saved games: the IFF files of the campaign, the autosave and the restart point. |
| [`formats/profile.md`](formats/profile.md) | `profile.bin`: the pilot's profile, the call sign and the pilot's record. |
Expand Down
2 changes: 1 addition & 1 deletion docs/engine/movies.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ and plays:
| 7 | `new_chapter1_thread1.bik` | `rameses_alive` (18) is other than 1 |
| 11 | `new_chapter2_thread1.bik` | `czar_alive` (29) is other than 1 |
| 11 | `new_chapter2_thread2.bik` | `krasnaya_alive` (17) is other than 1 |
| 11 | `new_chapter2_thread3.bik` | Variable 6 is other than 1; it then sets `chapter2_thread3_shown` (34) |
| 11 | `new_chapter2_thread3.bik` | `nicolai_petrov_alive` (6) is other than 1; it then sets `chapter2_thread3_shown` (34) |

Each attempt at a mission clears variable 34. The function has reports for mission 16 too,
`new_chapter3_thread1.bik`, `new_chapter3_thread2.bik` and `new_chapter2_thread3.bik`, which are
Expand Down
4 changes: 2 additions & 2 deletions docs/engine/script-vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ own: what the engine does with each is in the last column.
| Number | Name | In the shipped campaign | What the engine does with it |
|---|---|---|---|
| 5 | `mcgann_alive` | Whether McGann lives. Mission 15 clears it as his Phoenix is destroyed; mission 9 reads it | The news report after mission 16 plays once it's clear, but mission 16 ends no chapter, so it never plays |
| 6 | | Mission 27 clears it as it starts. `mission191.dte`, which the campaign never flies, reads it as its Petrov wing's leader alive. **Unknown:** what mission 27 clears it for | The third news report after mission 11 (`new_chapter2_thread3.bik`) plays once it's clear, which it never is then |
| 6 | `nicolai_petrov_alive` | Whether Nicolai Petrov lives. The Dreamcast's mission 12, which the PC game lacks, clears it as its Petrov's Basilisk is destroyed, `mission191.dte`, which the campaign never flies, reads it as its Petrov wing's leader alive, and mission 27 clears it as it starts. **Unverified:** that this Petrov is Nicolai: the missions don't name him, but the game knows two Petrovs, and Ivan's flag is 7 | The third news report after mission 11 (`new_chapter2_thread3.bik`) plays once it's clear, which it never is then |
| 7 | `ivan_petrov_alive` | Whether Ivan Petrov lives. Mission 28 clears it as his Basilisk is destroyed | The story's end plays his news report once it's clear |
| 8 | `kulov_alive` | Whether Kulov lives. Mission 28 clears it as the Boridin breakaway is destroyed | The story's end plays his news report once it's clear |
| 11 | `al_rahan_alive` | Whether Al-Rahan lives, 0 in a new campaign. Mission 7 clears it as he dies, and mission 24 sets it as it starts, before it reads it | |
Expand All @@ -133,7 +133,7 @@ own: what the engine does with each is in the last column.
| 30 | `ghost_alive` | Whether Ghost lives. Mission 1 clears it as Ghost dies; mission 4 has Petrov say a line by it | |
| 32 | `reliant_alive` | Whether the Reliant flies. Missions 7, 8 and 18 clear it as she's lost | While it's set, mission 8 ends in a landing on the Reliant rather than the Yamato, and a total failure on the Reliant ends the pilot's career in the transfer off the Reliant |
| 34 | `chapter2_thread3_shown` | Never set in the shipped campaign, as the report that sets it never plays | Set as the third news report after mission 11 plays; cleared before each attempt at a mission |
| 35 | | 1 in a new campaign and never cleared; mission 26 reads it. Not kept with the saved game. **Unknown:** what it was meant to hold | |
| 35 | `red_dragon_alive` | Whether the Red Dragon, the Golden Warriors' leader, lives. 1 in a new campaign and not kept with the saved game; mission 26 brings him in while it is set. **Unverified:** that it says whether he lives, since no script clears it, not even mission 26's as he dies | |
| 36 | `yamato_alive` | Whether the Yamato lives. Mission 25's second part and mission 27 clear it as she's destroyed | Once it's clear, those missions end without the landing, and a total failure in them ends the pilot's career in the shuttle at Fort Bear |

No script names variables 2, 16, 20, 21, 24, 25, 26 and 31: a new campaign sets 16, 20, 21 and 31 to
Expand Down
27 changes: 7 additions & 20 deletions docs/formats/dte.md
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ alone.

A mission of OpenReliant's making holds what the template's missions hold: its section 24 is theirs
(`write.template.command_flags`), and its records carry the values most of theirs carry where their
fields are not known. Mission 0, the sandbox, is written so by the build
fields are not known (`Ship.blank` and the other records'). Mission 0, the sandbox, is written so by the build
([`mission0.zig`](../../src/openreliant/mission0.zig)).

### Writing the script
Expand Down Expand Up @@ -720,7 +720,7 @@ the original: the same instructions at the same places, and the same constants.
instruction reaches may differ: the block's padding, the unused arm slots of a random choice and
the constant table's filler, which hold stale bytes in the shipped missions.

## Mission source groundwork
## Mission source

Mission source compiles to a standard `.DTE`
([#1126](https://github.com/OpenReliant/openreliant/issues/1126)), and [Mission
Expand All @@ -729,24 +729,11 @@ model is the one [#608](https://github.com/OpenReliant/openreliant/issues/608) s
declared under names of their own, apart from their display names, references that say what kind
of record they name, and nothing dropped without a word. Its script is the statements above.

[`dte/source.zig`](../../src/formats/dte/source.zig) has `Symbols(Record)`, a table of one kind of
record, each under its name ([#609](https://github.com/OpenReliant/openreliant/issues/609)). Each
kind of record, and each script bank, has a table of its own, so the same name can stand for
records in two of them. A name has nothing to do with the record's display name, its object id or
its place in the file. The table gives a record's index by its name, and the compiler checks that
the index fits the file's field.

Names don't depend on the base game's content either. A record can use a ship type a mod adds, by
its number, and the compiler is to look up named content in the game's records with its mods, in
their load order.

The table borrows the records and their names. After a name or the order changes, a new table
gives each record its new index.

Luau's parser reads the text ([Reading the text](mission-source.md#reading-the-text)). **Not
implemented yet:** building missions from text, which follows under
[#1126](https://github.com/OpenReliant/openreliant/issues/1126). The binary reader, writer and
assembler stay the format's implementation.
[`dte/source.zig`](../../src/formats/dte/source.zig) and its children write a mission as source,
hold the text as Luau's parser reads it, and compile it into a mission file through the writer and
the assembler above ([Building a mission](mission-source.md#building-a-mission)).
[`dte/same.zig`](../../src/formats/dte/same.zig) checks that a mission built again is the same
mission ([The round trip](mission-source.md#the-round-trip)).

## Prior art

Expand Down
63 changes: 45 additions & 18 deletions docs/formats/mission-source.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ Mission source is a mission written as text. `sltool dte decompile` writes a mis
its `.DTE` file, and `sltool dte build` compiles source into a standard `.DTE` that plays in the
original too ([#1126](https://github.com/OpenReliant/openreliant/issues/1126)). The syntax is a
subset of Luau, which Luau's own parser reads as the mission is built: nothing in the file runs, in
OpenReliant or anywhere else. **Not implemented yet:** `sltool dte build`, the compiler on top of
the parser ([Reading the text](#reading-the-text)).
OpenReliant or anywhere else.

```luau
-- mission1.dte as source.
Expand Down Expand Up @@ -50,8 +49,9 @@ At its top level it holds declarations and nothing else:
- `mission(...)` gives the mission's own details.

Comments go anywhere, and blank lines don't count. Every name is the file's: a declaration can use
any name the file declares, before or after it. A name is a Luau identifier, `[A-Za-z_][A-Za-z0-9_]*`,
that isn't a Luau keyword or one of the names this page gives the source itself.
any name the file declares, before or after it. A name is a Luau identifier,
`[A-Za-z_][A-Za-z0-9_]*`, that isn't a Luau keyword, a command's name or one of the names this page
gives the source itself.

The order of the declarations of a kind is the order of the mission's records of that kind, since
the script and the records refer to records by their place: the first ship declared is ship 0, the
Expand Down Expand Up @@ -231,7 +231,7 @@ Luau's syntax:

| Expression | Source |
|---|---|
| A number | `50`. Where the parameter takes a named value, it is written by its name: `"find_new_target"` for an order, `"shot_at"` for a condition, and a part's name for a part. A value the game doesn't name stays a number |
| A number | `50`. Where the parameter takes a named value, it is written by its name: `"find_new_target"` for an order, `"shot_at"` for a condition, `"coalition_fighter"` for a pilot, `"talking"` for a head movement, `"ice_field"` for an environment effect, and a part's name for a part. A value the game doesn't name stays a number |
| Text | `"new_sim02.wav"`, as a Luau string |
| A ship, flight group, squad, curve or global | Its name |
| A ship's component | `component(ship, 3)` |
Expand All @@ -257,11 +257,17 @@ The source holds what the game reads of a mission. It leaves out:
places and angles, which equal the authored ones in every shipped mission, the objects' trigger
slices, a flight group's ships, the parts' and triggers' places in the script, the string pool,
and the directory;
- the objects' numbers. The shipped missions number their ships, flight groups and squads in the
order their authors made them, with gaps. The compiler numbers them itself: first the objects
that `last_event` names, in the order the source names them, since the script names those by a
byte, then the ships, the flight groups and the squads, each in order;
- the script's flags, which the editor link sets ([The editor link](../engine/editor-link.md)).

The rest is parked as unknown and unused: fields and sections that nothing in the game reads. The
compiler writes them as the shipped missions most often hold them. OpenReliant could keep data of
its own there, as it keeps the mission's name in section 21, since the original ignores them.
compiler writes the fields as most of the shipped missions' records hold them (`dte.Ship.blank` and
the other records'), or 0 where they hold no value most, and leaves the sections empty, as mission 0
does. OpenReliant could keep data
of its own there, as it keeps the mission's name in section 21, since the original ignores them.
**Unverified:** that the original reads none of them; OpenReliant's engine, which follows the
original's disassembly, reads none.

Expand All @@ -282,7 +288,22 @@ original's disassembly, reads none.
A reference to a record that the mission doesn't have, such as a curve's ship in some of the
leftover missions, is written by its index: `ship_index(59031)`, and likewise `flight_group_index`,
`squad_index`, `curve_index`, `global_index`, `part_index`, `object_index`, `formation_index` and
`formation_point_index`.
`formation_point_index`. `object_index` names an object by its number in the built mission, and a
number past its records gets an entry of no record in the object table.

## Building a mission

```bash
sltool dte decompile mission1.dte > mission1.mission.luau
sltool dte build mission1.mission.luau mission1.dte
```

`sltool dte build` compiles the source with
[`compile.zig`](../../src/formats/dte/source/compile.zig). It stops at the first error, which it
gives with its line and column, such as ``patrol.mission.luau:12:5: `nobody` is not declared``. Each
kind's records are written in the order of their declarations. The script holds the blocks the
triggers run, in the order the triggers first run them, then the parts' blocks in order, each
written as the developers' compiler wrote its statements ([Statements](dte.md#statements)).

## Reading the text

Expand All @@ -299,17 +320,23 @@ unsupported node with its place, for the compiler to refuse. An expression in br
type assertion such as `x :: number`, is the expression itself, type annotations on locals and
parameters change nothing, and a string in backquotes without values is text like any other.

`sltool dte check` writes each mission it checks as source and parses it, and fails on an error or
a construct mission source doesn't use.

## The round trip

`sltool dte decompile` names each record after its display name: the name in lower case, a leading
tag such as `(F)` or `(GV)` left out, each run of other characters an underscore, and an underscore
where a small letter meets a capital, with a number after it where two would clash. The name is the record's from then on, and renaming the display
name doesn't change it.

Built again, a decompiled mission is the same mission: the same records and routines, decoded, with
names compared as text and references by what they name. The string pool's layout, its stale
strings, the sections' layout and the parked bytes may differ. To keep a shipped mission byte for
byte, edit its `.DTE` in place instead.
where a small letter meets a capital, with a number after it where two would clash. The name is the
record's from then on, and renaming the display name doesn't change it. A squad's members are
written as the game walks them, from the squad's first member while they are its own, and a
formation's points as OpenReliant finds them, from the formation's first point on, those that name
it (`dte.FormationPoint.Walk`).

Built again, a decompiled mission is the same mission
([`same.zig`](../../src/formats/dte/same.zig)): the same records and code, decoded, with names
compared as text, an object by the record the game takes it for, and a formation's point by its
place among the formation's. The string pool's layout, its stale strings, the objects' numbers, the
sections' layout and the parked bytes may differ. Every shipped mission comes back as the same
mission. To keep a shipped mission byte for byte, edit its `.DTE` in place instead.

`sltool dte check` runs the round trip: it writes the mission as source, parses it, fails on an
error or a construct mission source doesn't use, builds the source again, compares the two missions
and binds the new one as starting a mission does.
Loading
Loading