Adopting Station Options

From Elite Wiki

This is a first draft. Any and all critiques are welcome (I'm a coder, so of course my writing sucks) It may appear long but it's a quick read! cag


This oxp is driven via the missiontext.plist file. The keys required include three you'd normally send to station.setInterface (title, category, summary). In addition, page descriptors (title, summary) and keys for each option. A very basic missiontext.plist might look like this:

    // F4 screen text
    "myOxp_interface_title" = "myOxp Options";
    "myOxp_interface_summary" = "Edit myOxp options";
    "myOxp_interface_category" = "[interfaces-category-ship-systems]";

    "myOxp_hostVarPrefix" = "$";

    // give each page a label
    "myOxp_optionPages" = "

    // for each page, column alignment (trial & error)
    "myOxp_optionTabStops" = "
        {10.2,  15},

    // for each page, list of option names
    "myOxp_config_options" = "

    // for page 'config', a title
    "myOxp_config_title" = "myOxp Options";

    // for page 'config', introductory text, appears at top of page (optional)
    "myOxp_config_summary" = "blah blah, yada yada";

    // for option 'optionOne' (a boolean, default true)
    "myOxp_optionOne" = "(toggle, true) Turn optionOne on or off";
    "myOxp_optionOne_long" = "When optionOne is turned on, ...";

    // for option 'optionTwo' (a number between 1 and 10, default is 2)
    "myOxp_optionTwo" = "(number, 2, 1:10) Set optionTwo to be ...";
    "myOxp_optionTwo_long" = "Set optionTwo to a value that ...";

(note how a list of strings, like "myOxp_config_options", has list items single quoted while the key and its value are double quotes)

The names of the keys are rigidly constructed. When you register your oxp, you provide a string unique to your oxp, the keyPrefix, usually just your oxp's .name property. In the above example, the string to be passed would be "myOxp_", having added an underscore for readability. The only other naming on your part is the page labels and option names.

So the key for the interface title is built using your keyPrefix and "interface_title", the list of pages is your keyPrefix + "optionPages", etc. The strings supplied for "optionPages" are used to construct the page specific key for title and summary:

keyPrefix + <page label> + "_title"

From the above, the only page is "config", so you supply the title in the key "myOxp_config_title". The options for this page must appear in the key "myOxp_config_options", made from your keyPrefix, a page label and "_options".

Using the option names provided, you add 2 keys for each option:

keyPrefix + <option name>
keyPrefix + <option name> + "_long"

The first provides the option format (more on that later) and the (short) text that describes the option when it is listed on the page. When the option is selected by the player, a new screen appears that displays the text in the "_long" key. It can be as long as you like, as scrolling is supported. This page is also where the player selects or enters the new option value.

Once your missiontext.plist is ready, all you need to do is register your oxp with Station Option, using the following in your startUpComplete function:

worldScripts.station_options.$O_initStationOptions( myOxp, "myOxp_" );

To complete this overview, I need to explain the "hostVarPrefix" & "optionTabStops" keys.

When a player changes the value of "optionOne", it gets stored in


Since all good coders prefix their variable names to avoid clashing with Oolite's own, the "hostVarPrefix" is where you provide that. Some use "_", some use their oxp name and some use both. Whatever it is, the option's value is written to

worldScripts.<oxp>.<hostVarPrefix><option name>

Note that values are not set immediately; the player has the option to quit while discarding changes. It's when the player commits the changes (hits the "Apply changes and exit" button) that these values are written.

When a page of options is formatted, it appears as 3 columns: option's name, its current value and the short string you provided. The "optionTabStops" tells where to vertically align the second and third column. The screen is 32 units wide, so the values column starts at 10.2 and the short string at 15. If your short string runs long, it gets truncated.

Probably the easiest way to get started is to just copy the missiontext.plist from an existing implementation if you don't have one, copy/paste into yours if you do. To date there are 3:

Included in Station Options there is a missiontext-template.plist file. It's a missiontext.plist filled with comments for all the features. While that is mostly duplicated here in the wiki, it's another option (and was the basis for this wiki entry).

Missiontext Keys

Station Interface

For Station Options to set up your entry in the F4 screen, it needs the requisite information for a call to station.setInterface(). Using the keyPrefix you supplied as the second parameter when you registered, Station Options gets the title, summary and category from your missiontext.plist by concatenating "interface_title", "interface_summary" and "interface_category" respectively. So if your keyPrefix is "myOxp_", it needs to find the keys

  • myOxp_interface_title
  • myOxp_interface_summary
  • myOxp_interface_category

In Telescope, these are

"telescope_interface_title" = "Telescope Options";
"telescope_interface_summary" = "Edit all hardware and in-flight options; includes quick start & readme";
"telescope_interface_category" = "[interfaces-category-ship-systems]";

The "interface_title" is what appears in the F4 screen's list of choices and when your entry is highlighted, "interface_summary" appears below the list. The "interface_category" is used for placement in and sorting of the F4 list. It must be one of the following:

(from, line 1712)
"interfaces-category-uncategorised"       = "Uncategorised";
"interfaces-category-bounties"            = "Bounties";
"interfaces-category-deliveries"          = "Deliveries";
"interfaces-category-employment"          = "Employment";
"interfaces-category-help"                = "Help";
"interfaces-category-logs"                = "Logs";
"interfaces-category-news"                = "News";
"interfaces-category-organisations"       = "Organisations";
"interfaces-category-ship-systems"        = "Ship Systems";


For accessing variables in your oxp to get option values and set the variables with the player's choicees, the hostVarPrefix is needed. Station Options works on the assumption that if have an option named "xyz", there is a variable in your oxp with the name "$xyz" if your hostVarPrefix is "$" or "_myOxp_xyz" if your hostVarPrefix is "_myOxp_", etc. The actual assignment is

hostOxp[ hostVarPrefix + option ] = newVal;

where hostOxp is the first parameter when you registered (reference to your oxp script),

option has the name (string) of the option (from the page's "_options" key) and
newVal is the player's setting

You don't need this level of detail but I want to make the point that you must ensure that your variables and option names adhere to this scheme. If your variable names are cryptic, it's time to make friends with your editor's replace utility. (It also make your code easier to read)

For example, in Telescope,

"telescope_hostVarPrefix" = "$";

so when the player sets "SniperRange"

hostVarPrefix + "SniperRange" yields "$SniperRange"

and worldScripts.telescope.$SniperRange is the variable for that option.


Stations Options operates with a two tier set of pages (screens). Here you define the top tier, a list of options. When an option is selected, it gets its own page, the second tier. While there is not limit to the number of options on a page (it scrolls), you may wish to group them into categories, for easier access. You can use a different page for options that apply only to an upgrade, one that deals only with repairs, one for documentation, etc.

Each page must be assigned a different label. Its sole purpose is to generate page specific key names. These labels are single quoted, separated by commas with the whole list being double quoted. The key name for this list is your keyPrefix + "optionPages". Here's one from Telescope:

"telescope_optionPages" = "

These page labels are used to construct three key names for the page's title, summary and list of options. Again, lets assume the keyPrefix is "myOxp_".

  • myOxp_ + <page label> + "_title" yields "myOxp_config_title"
  • myOxp_ + <page label> + "_summary" yields "myOxp_config_summary"
  • myOxp_ + <page label> + "_options" yields "myOxp_config_options"

The "_title" gets displayed at the top of the screen with the "_summary" text right below. The "_summary" can be as long as you want but it will reduce the number of options that can be displayed before scrolling become necessary. The "_options" key has a list of options for the page, in the same format as "optionPages", namely a double quoted value with comma separated, single quoted strings. (echo?)


This key must have a list that parallels "optionPages", one entry for each page label. The value is a list of 2 numbers used for vertical alignment of the 3 columns on the page. The option names are left justified, the option's values line up at the first tab stop and the short description of the options align at the second tab stop. The available space is 32 units (unknown, probably em's) wide and over long lines are shortened (truncated) to fit. So for the "optionPages" given above, a corresponding "optionTabStops" might be:

"telescope_optionTabStops" = "
    {10.4,  14.4},
    {8,     10.5},
    {8.8,   12},

Note that these 2 element arrays use braces not brackets, brackets being already in use for string expansion.

If not provided or some are missing, the option's name, value and description will simply be concatenated (whitespace is your problem).


The list of options for each page are listed in a key whose name is

keyPrefix + <page label> + "_options"

with the page label coming from "_optionPages". You can have any number of options, as the page will scroll, but if you have to scroll twice to reach the end, consider splitting into two pages or shorten your summary if it's quite long.

"telescope_config_options" = "

The key for an option (whose name comes from "_options") is

keyPrefix + <option name>

While it's not absolutely necessary to have a corresponding variable named keyPrefix + <option name>, that is where the option value will be set. It's good practice to define all your variables, so you don't lose track. It's especially important for others reading/modifying your code that a variable doesn't appear as a side effect of another oxp! Besides, its value would be displayed as 'undefined', which just looks bad.

Just to be absolutely clear, the option's value will be set in

worldScripts.myOxp[ hostVarPrefix + <option name> ]



and your script should have a line defining/declaring the variable

this.<option name> = <some default value>

e.g. telescope.js has a variable defined as

this.$SniperRange = 25600;

If your oxp's variable names are not exactly comprehensible, there is a capability to display a different string instead of the variable name. Simply append to your variable name a colon (':') and the string to display.


"..._options" = " 'befuddlingIdentifier: option # 2', ... ";

will display "option # 2" on the option page while the value will be assigned to

worldScripts[ hostOxp ][ hostVarPrefix + befuddlingIdentifier ]

Option Details

Each option listed in the "_options" list require 2 keys

keyPrefix + <option name>
keyPrefix + <option name> + '_long'

The first has the option's particulars and a brief description, the latter should not exceed 32 - <2nd tab stop>. Don't worry about calculating it, just remember the screen is 32 em wide and you've already used some for the option name and value. (Be prepared for some trial and error to get the alignment you want) The second is an extended description, which will scroll, so you can have a much text as you like. I recommend using multiple spaces between sentences, as the space character is very thin.

The option's particulars are set between parentheses and must come before the brief description. In general, it look like this

keyPrefix + <option name> = "( type, default , range ) ...<brief description>..."
e.g. "telescope_AutoLock" = "(number, 1, 0:180) Size of auto lock detection cone.";
"telescope_AutoScan_long" = "Check continually for new visible targets and scan if found. \n\nScanning ...";

Both default & range are optional.

Option type

The "type" is required and must be one of:

"text", "toggle", "number", "decimal", "vector", "bitflags", "choice"
  • "text" is a special case, as we also support displaying text pages (e.g readme) with no associated oxp variable, so "(text,)" will assume there's a variable, "(text)" will not. So if you have
"myOxp_readme" = "(text)blah blah, blah b'blah";
there is no variable set in your oxp but
"myOxp_newName" = "(text,)Enter a name for...";
will set the "newName" option's variable.
  • bitflags requires an extra key
keyPrefix + <option name> + "_bitflagStrs"
which contains a comma delimited list of single quoted strings that represent each flag. These are used on the buttons to toggle the flags. (you hear an echo?)
e.g. "telescope_MassLockViewDirn_bitflagStrs" = "'Forward', 'Aft', 'Port', 'Starboard'";
Also, any text in parentheses is not displayed on the summary page.
e.g. "telescope_MFDAuxDynamic_bitflagStrs" = "'Friendly (clean status)', 'Unsociable (offender status)', 'Active targetting (any ship)', 'Hostile (targetting you)', 'Nearby (within scannerRange)', 'Faraway (beyond scannerRange)', 'Protected (within station Aegis)'";
will appear on the summary page as, for example,
MFDAuxDynamic: Friendly, Unsociable
              -> Unsociable, Active targetting, Hostile

Option default

An option's default value is optional and is used on the reset of an option.

  text        any string in single quotes
  toggle      true or false
  number      any signed integer
  decimal     any signed floating point number
  vector      any # of comma separated decimals, enclosed in ''braces'' { }
  bitflags    any unsigned integer or -1 (all flags set)
  choice      either an index into the list or one of its elements
  • the default must conform to type or it can be null (no quotes) or blank (2 commas); see examples below.
  • the default choice may be a single quoted string (i.e. one of the choices) or a number (ie. a zero-based index into the list of choices)
    • numbers are assumed to be indexes, so if your choice list contains numbers, make sure your default is an index!
  • an option with no default cannot be reset (it's ignored in reset commands).

Option range

A range is optional and sets limits on user input. You can specify a minimum, maximum or both. A range is allowed only for types "number", "decimal", "vector", "bitflags" & "choice".

For "number", "decimal", "bitflags" & "vector elements"

  N :      limits value >= N
  : M      limits value <= M
  N : M    limits N <= value <= M

For "choice", any # of comma separated values, enclosed in braces { }

  • To specify a range but no default, just leave it blank: (number,,0:9)
  • With "bitflags", minimum is forced >= 0, maximum is rounded up to 2^M - 1.
    • If a range is absent, it is calculated based on length of its "_bitflagStrs" key, so bitflags rarely ever needs a range (negative range values are disallowed)
  • With "vector", the range is applied to each element separately.
  • With "choice", the range is instead the complete list of values to choose from, comma separated, single quoted. (I swear I heard an echo!)

Examples: (all white space between parentheses is optional)

(text, 'oolite')          - string with default value
(text, '')                - string with default of empty string
(text, )                  - string with no default => must have corresponding host Variable
( text )                  - string used only to display text  => NO corresponding host Variable
(toggle)                  - boolean
(toggle, false)           - boolean with default
( number )                - integer
( number, null )          - integer which defaults to null
( number, 0, 0 : 2 )      - integer with default and range of valid entries
(decimal, , 0:2.5)        - floating point w/ no default & range 0 <= x <= 2.5
(decimal, -1.5, :0)       - floating point with default, must be negative
(vector,{ 0, 0, 0 }, 0:)  - set of 3 decimals, all must be positive
(vector,{0,255,0}, 0:255) - set of 3 decimals, all must be positive & < 256, e.g. greenColor
(bitflags)                - set of N bitflags (N is # of strings in _bitflagStrs) with default of all clear
(bitflags, -1)            - set of N bitflags (N is # of strings in _bitflagStrs) with default of all set
(bitflags, 0, 0 : 15 )    - set of 4 bitflags (15 is 1111 in binary, i.e. 2^N - 1) with default of all clear
(bitflags, 3, 2 : 31 )    - set of 5 bitflags, where the 1st bit is read-only
(bitflags, 3, 2 : 31 )    - set of 8 bitflags, where the 1st & last 3 bits are read-only
                            (here, _bitflagStrs has 8 strings, all set is 255)
(choice, , {'I accept', 'I will think about it', 'I decline'})
  - set of 3 choices, no default
(choice, 'redColor', {'redColor', 'greenColor', 'blueColor', 'cyanColor', 'yellowColor',})
  - set of 5 choices, with a default of 'redColor'
(choice, 1, {'yes', 'no', 'maybe',})
  - set of 3 choices, with a default of 'no'

Any option which accepts player input ("text", "number", "decimal", "vector") cannot easily be reset because textEntry ignores any choices (buttons). To get around this, entering just a plus sign "+" is reserved for an individual option being reset to default, if one exists.

For consistency with other types that take player input, entering nothing into a text option does NOT set it to an empty string (it's treated like an abort). To set an option to an empty string, enter 2 single quotes: ''

Help for these last 2 points is displayed to the player on the option's page.

What follows is a selection of keys from the telescope 2.0 oxp, examples of each type of option.

"telescope_Keys" = "(text) Explanation of key functions";

"telescope_RemoveInFlight" = "(toggle, false) Remove in-flight configuration";

"telescope_SniperRange" = "(number, 25600, 5000:30000) Max. distance for Sniper ring";

"telescope_Steering" = "(choice, 'Off', {'Off','Nearest','Each in list'}) Auto steering, 3 settings";

"telescope_IdentDelay" = "(number, 4, 0:) Suspension time in computer targetting";

"telescope_ShowMassLock" = "(bitflags, 11) Set conditions when masslock rings are displayed";
"telescope_ShowMassLock_bitflagStrs" = "'Condition Green, weapons off-line', 'Condition Green, weapons online', 'Condition Yellow, weapons off-line', 'Condition Yellow, weapons online', 'Condition Red, weapons off-line', 'Condition Red, weapons online'";

"telescope_SniperRingColor" = "(vector, { 0.3, 0.3, 0.3 }, 0:1) Set color for the sniper ring";

"telescope_MFDPrimaryStatic" = "(bitflags, -1) Limit what is displayed on primary MFD";
"telescope_MFDPrimaryStatic_bitflagStrs" = "'Salvage (cargo, pods, derelicts)', 'Mining (asteroids, boulders, splinters & metal fragments', 'Weapons (mines & missiles)', 'Traders (cargo ships including escorts)', 'Police', 'Pirates (including their victims)', 'Military', 'Aliens', 'Neutral (all other ships)', 'Station', 'Navigation (beacons & buoys)', 'Celestial orbs (Sun, planets & moons)'"; 

Optional Settings

These are global in nature, with reasonalbe defaults, so will rarely need to be changed. (skip this section if this is your first pass).

For simplicity, lets assume the keyPrefix is "myOxp_" and you can alter when you cut/paste.

"myOxp_lowestValue" = "0";
  • in the absence of an input range (see option format below), all numbers must be >= to this value
  • delete this key or set it to "" to turn off this test
"myOxp_highestValue" = "";
  • in the absence of an input range, all numbers must be <=
  • delete this key or set it to "" to turn off this test
"myOxp_maxPrecision" = "7";
  • display limit for large numbers to fit with "_optionTabStops"
  • at worst, toPrecision() adds 3 characters, so at max, this is 10. Given that Oolite uses a proportional font, a bit of trial and error may be required.
  • to disable this test, delete this key or set it to "" (default is to round to integer)
"myOxp_maxVectorLen" = "2";
  • decimal places for displaying arrays
  • if array has N elements: N * maxVectorLen + (N-1 commas)
  • could be set larger if more room is made available by adjusting "_optionTabStops"
  • to disable this test, delete this key or set it to "" (default is to round to integer)
"myOxp_allow_reset_of_page" = "yes";
  • Station Options comes with the ability to reset all options on a page to their "factory" defaults
    • defaults values are supplied with option description and are optional, so resetting a page only alters those options that have a default
    • some option pages have a reset button, if there's a default value for that option; this key does not influence that, just the whole page reset
  • delete this key or set it to "" to turn off this feature (default is "no")
"myOxp_formatting_of_page" = "yes";
  • blocks of text are stretched to the right margin to make them easier to read, like in a newspaper; short lines, including leading whitespace, are left untouched
  • this option allow you to turn this feature off should it cause problems (let me know!)
  • delete this key or set it to "" to turn off this feature (default is "no")

Advanced option keys

In addition to the 2 required keys for each option, there are 3 optional ones for more advanced use cases:

  • "_condition" for hiding options that are not relevant
  • "_assign" for assigning values to option & oxp variables
  • "_execute" for option specific callbacks

They are constructed in the usual manner:

  • keyPrefix + <option name> + "_condition"
  • keyPrefix + <option name> + "_assign"
  • keyPrefix + <option name> + "_execute"

For all of them, the option or variable values are substituted into the expression which is sent to eval(), so the syntax is the same as javaScript


This key has one or more conditions to test before displaying the option. Telescope has an option "LightBalls" (toggle) and a bunch of options to tailor how the light balls are displayed. These have "LightBalls == true" as their _condition line:

"telescope_LargeLightBalls_condition" = "LightBalls == true";

so if light balls are off, none of these other options are displayed.

In addition to testing other options, an oxp var can be tested by using your hostVarPrefix, e.g.:

"telescope_LargeLightBalls_condition" = "$FixedTel == 0 && LightBalls == true";

Here we also checks to see if you have opted for the cheap telescope repair. Variables whose names have your hostVarPrefix are treated as variables from your oxp, so there's no need to have "worldScripts.myOxp." before it!


This key makes assignments immediately when the option is changed. (it does not wait for the "Save change and exit" button). You can set option values and host variables. E.g. suppose you want to record the system in which the option "optIn" was changed and, with hostVarPrefix "$", your _assign line reads ()

"myOxp_optIn_assign" = "$optInSystem = '%H'";

When the player changes "optIn", worldScripts.myOxp.$optInSystem will be assigned the current system, %H.

NB: the assignment will happen even if the player exits discarding changes, so your logic will have to account for that

(in some cases, it won't matter but be aware)

NB: any strings in assignments must be single quoted

In the case of an option, updating its corresponding host variable will remove it from the pending changes list, i.e. it will not appear in the summary and will survive the "Exit discarding changes" button. E.g.

"myOxp_optIn_assign" = "$optIn = optIn";

Multiple assignments are separated by semi-colon, e.g.

"myOxp_optIn_assign" = "optInSystem = '%H'; optInRank = '[commander_rank]'";

Tertiary assignments are supported, as after substituting values, the string is passed to eval().


Does just that, execute the statement provided. This could be used for callbacks specific to an option or anything, really. But I would advise keeping it to a minimum so most of your code is in your script. E.g. suppose you want something to happen when a toggle is switched on

"myOxp_optIn_execute" = "optIn === true ? _playerAccepts() : _playerRegects()";

where you could update missionKeys on the page you're about to return to.

Bare function calls will be assumed to come from hostOxp and so if optIn is true, worldScripts.myOxp._playerAccepts() will be called.

A simpler version of the above example could have the two functions merged into one:

"myOxp_optIn_execute" = "_playerResponds( optIn )";

NB: any strings in expression must be single quoted; if you had wanted to pass the current system, the parameter would have to be '%H'.

Just like "_assign", this happens immediately (otherwise you could do whatever it is in the notifyCallback function).

Both "_assign" and "_execute" are intended for advanced uses that require a dynamic response. For basic option configuration, they are not needed. And as I know someone will ask, "_assign" is executed first followed immediately by "_execute".

Also, by utilizing descriptions.plist keys, all manner of substitutions are possible!. You could make an option's text dynamic:

"myOxp_myToggle" = "(toggle, true) [myOxp_myToggle_text]";

and in descriptions.plist:

"myOxp_myToggle_text" = "[myOxp_myToggle_on_text]";
"myOxp_myToggle_on_text" = "Power down for maintenance";
"myOxp_myToggle_off_text" = "Initiate start-up sequence";

Finally, you'd need an "_execute" like

"myOxp_myToggle_execute" = "_updateMyToggle( myToggle )";

with an oxp funciton

this._updateMyToggle = function( status ) {
  var so = worldScripts.station_options;
  if( status ) {
    so.$O_updateMissionKeys( "myOxp_", 
        { "myOxp_myToggle_text": "[myOxp_myToggle_off_text]" } );
  } else {
    so.$O_updateMissionKeys( "myOxp_", 
        { "myOxp_myToggle_text": "[myOxp_myToggle_on_text]" } );

A more adverturous coder may try it all in the "_execute":

"myOxp_myToggle_execute" = "$O_updateMissionKeys( 'myOxp_', {'myOxp_myToggle_text': (myToggle ? '[myOxp_myToggle_off_text]' : '[myOxp_myToggle_on_text]')} )";

which works (as long as there are no embedded quotes <argh>) but this may prove more difficult to debug.

Did you notice the station_options function call? In addition to prepending "worldScripts.myOxp" to all of your oxp references, "worldScripts.station_options" is prepended to those in station_options; that's the reason for the funky prefix of "$O_".

A word about value substitutions. All 3 ("_confirm", "_assign", "_execute") are processed before execution, replacing options & oxp variables with their values. The only exception is "_assign", where the target of the assignment is left alone (this is why there are separate "_assign" & "_execute" keys).

NB: strings again! In normal string expansion, the [...] key get substituted with the corresponding value regardless of type, which works just fine. But we're sending these statements to eval(), so strings need single quotes. When Station Options does its value substitutions, it will of course add the quotes. Thus the need for quotes is not exactly uniform. For example, let's say your oxp asks the player for a string (like a name) for your option "Recipient" and you also want the system in which this all happened.

"myOxp_Recipient_execute" = "$msgSent( Recipient, '%H' )";

where your oxp has a function "$msgSent" expecting two strings for who and where. See the problem? Both are strings but only one needs quoting! After the value substitution, the string sent to eval is

worldScripts.myOxp.$msgSent( 'John Doe', 'Lave' );

If you had quoted both, $msgSent( 'Recipient', '%H' ), then the string for eval would be

worldScripts.myOxp.$msgSent( 'Recipient', 'Lave' );

which isn't really helpful. String valued options may not be common but it's something to be aware of.

From the above examples,

"telescope_LargeLightBalls_condition" = "$FixedTel == 0 && LightBalls == true";
the right hand side has values for both $FixedTel (e.g. 1) and LightBalls (e.g. true) substituted and the call to eval() is
eval( "1 == 0 && true == true" );
which evaluate to false and the LargeLightBalls options is suppressed.
"myOxp_optIn_assign" = "$optInSystem = '%H'";
the target of the assignment, $optInSystem is left alone, %H gets expanded to, say, 'Lave' and the call to eval() is
eval( "worldScripts.myOxp.$optInSystem = 'Lave'" );
which assigns to the oxp variable the system name.
"myOxp_optIn_execute" = "_playerResponds( optIn )";
the value of the option "optIn" (e.g. true ) gets replaced and the call to eval() is
eval( "worldScripts.myOxp._playerResponds( true )" );

Script Function Calls

You register your oxp in startUpComplet by calling

    hostOxp,                // reference to script
    keyPrefix,              // string
    optionsAllowedCallback, // reference to script function
    callPWSG,               // boolean
    notifyCallback,         // reference to script function
    suppressSummary,        // boolean
    missionKeys             // object

hostOxp (required)
a reference to your oxp script, e.g. worldScripts.myOxp
keyPrefix (required)
a string that's used as the prefix for all your keys in your missiontext.plist file
  • it's used in station_options.js script to calculate key names
e.g. when I call with keyPrefix = "telescope_",
keyPrefix + "interface_title" yields "telescope_interface_title"
which is passed to expandMissionText.
optionsAllowedCallback (optional)
a function in your oxp (hostOxp) that returns a boolean as to whether this utility is to be made available. You may wish to restrict access to main stations only, systems of a certain tech level/government, or based on device health.
The default is to always allow the interface.
e.g. in telescope, it depends on the health of the equipment (I don't restrict
availability based on the type of station, for example)
callPWSG (optional)
a boolean that if true, directs Station Options to call your oxp's playerWillSaveGame function after all the option changes have been assigned to their variables (on the assumption playerWillSaveGame is where you update your missionVariables)
notifyCallback (optional)
a function in your oxp that is called when the player saves option changes and exits
  • it passes 2 arrays of strings:
    • the first has names of variables changed (i.e. entries from '_options' list)
    • the second has names of options pages containing these (i.e. entries from '_optionPages')
suppressSummary (optional)
allows customization of the 'Summary of changes' page presented to player when exiting, the default is to show it
  • true will shut it down completely
  • false (or null) will leave it fully operational
The summary page also reminds (nags) the player, a few times, to save game if autosave is turned off, so
  • "summary" will only suppress the summary but continue to remind (nag)
and for completeness,
  • "autosave" will show the summary but suppress the reminder
missionKeys (optional)
an object containing keys referenced in missiontext.plist or description.plist, exactly the same if it was you calling expandMissionText. Since that is done in Station Options instead, a way is provided to get your keys through (for string expansion).
  • the keys can be the strings inside square brackets in your missiontext.plist
  • the keys can also be any key in your or Station Options' missiontext.plist
  • the values are what is to be substituted
    • if you later set a key's value to "" (empty string), the override stops having effect (the expansion will occur as if key was not present)
in addition to the Special Expansions provided for missiontext.plist files
(see Missiontext.plist)
there are a few more are available:
stn_optns_clockString       current value of clock.clockStringForTime( clock.adjustedSeconds )
stn_optns_curr_system       name of current system
// the rest are oxp dependent
stn_optns_page              name of the option page (from your _optionPages)
stn_optns_page_num          number of current page (e.g "Page [stn_optns_page_num] of 5")
stn_optns_next_page_num     number of next page (e.g "Switch to page [stn_optns_next_page_num]")
stn_optns_option            name of the current option when in an option page, null otherwise
stn_optns_changes_count     number of changes made in current session
stn_optns_changed           a map of {option: value} for changes made in current session

To allow access to all of station_options' missiontext.plist keys, all expansions that involve any missionKeys gets sent to expandDescription() so all manner of substitutions are possible! For example,

 missionKeys = {
  'stn_optns_default_color': 'purpleColor', // change text color
  'stn_optns_scroll_down': '<scroll down>', // change button text 
    // change booolean option page title 
    'stn_optns_choose_boolean': 'Toggle [stn_optns_option_key]',
    // dynamic button text
      'Switch to page [stn_optns_next_page_num] of [stn_optns_page_count]',

With the Advanced_option_keys advanced keys, you can change text based on player's choices, add or remove options or even entire pages. NB: this is the 7th parameter, so if you decide to skip all of the optional ones, add "null" as placeholders in the call

...$O_initStationOptions(myOxp, 'myOxp_', null, null, null, null, myKeys);

As an alternative, you could use the $O_setMissionKeys() function after you register:

...$O_initStationOptions(myOxp, 'myOxp_');
...$O_setMissionKeys('myOxp_', myKeys);

For example, in telescope, the call is:

var ws = worldScripts.telescope;
var so = worldScripts.station_options;

so.$O_initStationOptions( ws, 'telescope_', ws._stnOptionsAllowed, 
                          true, ws._reloadFromStn );

and this is what I do in the functions that were passed

this._stnOptionsAllowed = function() {
  var ws = worldScripts.telescope;
  var ps = player && player.ship;

  return ps && ps.equipmentStatus( 'EQ_TELESCOPE' ) === 'EQUIPMENT_OK'
            && ws.$FixedTel !== 1;

this._reloadFromStn = function( names, pages ) {
  var ws = worldScripts.telescope;

  if( pages.indexOf( 'inflight' ) >= 0 )
  if( pages.indexOf( 'config' ) >= 0 )

In addition to the $O_initStationOptions function, there are getter and setter functions for all of the parameters, in case you decide they should change under certain conditions.

  • $O_getAllowedCallback( keyPrefix )
  • $O_setAllowedCallback( keyPrefix, newFunc )
  • $O_getCallPWSG keyPrefix )
  • $O_setCallPWSG( keyPrefix, newBool )
  • $O_getNotifyCallback( keyPrefix )
  • $O_setNotifyCallback( keyPrefix, newFunc )
  • $O_getSuppressSummary( keyPrefix )
  • $O_setSuppressSummary( keyPrefix, newBool )
  • $O_getMissionKeys( keyPrefix )
  • $O_setMissionKeys( keyPrefix, newKeys )
  • $O_updateMissionKeys( keyPrefix, newKeys )
  • $O_getReminder4Oxp( keyPrefix )

This last one fetches an object containing the current state of the summary report mechanism (if you didn't register with suppressSummary = true)

.reportSummary          boolean
.remindAutosave         boolean
.autosaveStopRemind     int, counter of how many times user was reminded about Autosave
.suppressSummary        boolean