<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.pioneerspacesim.net/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Zonkmachine</id>
	<title>PioneerWiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.pioneerspacesim.net/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Zonkmachine"/>
	<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/wiki/Special:Contributions/Zonkmachine"/>
	<updated>2026-06-10T17:30:13Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.31.0</generator>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_game:_Event-based_programming&amp;diff=4781</id>
		<title>Interacting with the game: Event-based programming</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_game:_Event-based_programming&amp;diff=4781"/>
		<updated>2026-06-08T12:03:10Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* More events */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
==Events==&lt;br /&gt;
&lt;br /&gt;
The Lua scripts are all executed at startup. If you were to add a single file named &amp;lt;code&amp;gt;hello_world.lua&amp;lt;/code&amp;gt; to the '''data/modules''' directory containing the following:&lt;br /&gt;
&lt;br /&gt;
 print(&amp;quot;Hello, World!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
you would literally see the words, &amp;quot;''Hello, World!''&amp;quot; appear in Pioneer's output (if running in a terminal) shortly before the main menu appears. You would also see it in the Lua console, if you were to open it.&lt;br /&gt;
&lt;br /&gt;
All file-scoped imperative statements in all Lua files are executed at that time. The way to get Lua code to interact with the game itself, beyond that time, is to write functions and to connect them to event handlers. Many events are triggered by Pioneer during the course of play, all of which will cause any functions which are connected to them, to run. Most will provide those functions with arguments.&lt;br /&gt;
&lt;br /&gt;
Here is a quick list of some of the more commonly used events:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; is triggered when the player clicks on a new game button in the main menu, or when the player loads a game.&lt;br /&gt;
* &amp;lt;code&amp;gt;onEnterSystem&amp;lt;/code&amp;gt; is triggered whenever any ship arrives in the current star system after a hyperspace journey.&lt;br /&gt;
* &amp;lt;code&amp;gt;onLeaveSystem&amp;lt;/code&amp;gt; is triggered whenever any ship leaves the current star system by hyperspacing.&lt;br /&gt;
* &amp;lt;code&amp;gt;onShipDestroyed&amp;lt;/code&amp;gt; is triggered whenever any ship is destroyed.&lt;br /&gt;
* &amp;lt;code&amp;gt;onShipDocked&amp;lt;/code&amp;gt; is triggered whenever any ship docks at a starport.&lt;br /&gt;
&lt;br /&gt;
There are many more. All are fully documented in the [https://codedoc.pioneerspacesim.net/#LuaClass:Event Pioneer Codedoc]. Of the five that I have listed, only &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; does not provide the function with any arguments. The other four provide a reference to the ship in question, and the latter two each also provide an additional argument (a reference to the attacker, and the starport, respectively).&lt;br /&gt;
&lt;br /&gt;
==Writing a function for an event==&lt;br /&gt;
&lt;br /&gt;
An event handling function does not have to return anything. It will be passed any arguments specified in the documentation, which it can either deal with, or ignore. It has access to any variables that are declared in the same file scope, including named functions and tables.&lt;br /&gt;
&lt;br /&gt;
Here is an adaptation of the 'Hello World' message above to be event driven. It's now triggered on game start and will still turn up on the command line, but now much later in the start sequence, pretty much when the game starts and you find yourself docked at a starport.&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local welcome = function ()&lt;br /&gt;
     print(&amp;quot;Hello, World!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, welcome)&lt;br /&gt;
&lt;br /&gt;
We move on. The same function again but now instead the message is presented on the player's ship console and is now welcoming them to Pioneer. We need to add the ''' 'Comms' ''' module to the script and the function name has changed to ''' 'onGameStart' ''', same as the event, which is common practice in Pioneer.&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
The latest code may not work as intended. The reason is that ''' 'onGameStart' ''' is not when the game starts but when it is being launched after pressing the button on the main menu to start on Mars, or whatever location you prefer. Let's see if there is an event that better suits our purpose. ''' 'onShipDocked''''?. This will make the message trigger every time we dock at a space station, on the ground or in orbit. ''' 'onShipDocked' ''' will not trigger on launching a saved game or when we start a new game, docked at a starport. Now the script works just fine and will launch the message the next time you land or dock with a space station.&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function ()&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
If you look at the comms log after docking/landing, you may see the greeting posted more than once. This is because the same function is triggered for all ships in the vicinity, not only the player's. The Pioneer universe is populated by ships and characters and they follow pretty much the same rules as the player. To fix this we need to test if the ship is the player first. Modify the ''' 'onShipDocked' ''' function in the previous example like this:&lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
         Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
An alternative solution to the last code snippet would be to test for if the ship is '''not''' the player:&lt;br /&gt;
&lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if not ship:IsPlayer() then return end&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==Setting a timer==&lt;br /&gt;
Yet another way to avoid the problem with ''' 'onGameStart' ''' missing your function initially is to place a [https://codedoc.pioneerspacesim.net/#CClass:Timer Timer] to delay the event for a short while. Just a second or so to allow the game to load completely. CallAt takes two arguments, the time of action and a function to be carried out. Example form the Timer documentation:&lt;br /&gt;
 Timer:CallAt(Game.time+30, function ()&lt;br /&gt;
     Comms.Message(&amp;quot;Special offer expired, sorry.&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
A complete example of the welcome message reworked with a timer. ''' 'Game.time' ''' gives us the game time right now so if we call the timer with ''' 'Game.time + 1' ''' we give it a second to think things through.&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     if not Game.player then return end&lt;br /&gt;
 &lt;br /&gt;
     Timer:CallAt(Game.time + 1, function ()&lt;br /&gt;
         Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
==Event arguments==&lt;br /&gt;
&lt;br /&gt;
As mentioned before '''[https://codedoc.pioneerspacesim.net/#LuaClass:Event:onShipDocked onShipDocked]''' passes two arguments to the function. A reference to the ship and a reference to the spacestation.&lt;br /&gt;
 local onShipDocked = function (ship, station)&lt;br /&gt;
Through these arguments we also get access to some of the ''' 'ship' ''' and ''' 'station' ''' methods without having to include any modules.&lt;br /&gt;
''' 'ship:IsPlayer()' ''' is for free. Unlimited power is now at your fingertips! ''' 'Comms.Message' ''' takes a second argument for the sender of the message. ''' 'station.label' ''' gives us the name of the space station.&lt;br /&gt;
&lt;br /&gt;
 Comms.Message (&amp;quot;Congratulations! Your ship has been upgraded for free!&amp;quot;, station.label)&lt;br /&gt;
 ship:SetShipType('xylophis')&lt;br /&gt;
&lt;br /&gt;
==More events==&lt;br /&gt;
&lt;br /&gt;
An event can be registered only once per file. If you need more than one function called by the same event you must wrap them in a function.&lt;br /&gt;
&lt;br /&gt;
Example from '''[https://github.com/pioneerspacesim/pioneer/blob/6111dad3f0b14d64df844511bf3c92b9e583ec76/data/modules/MusicPlayer.lua#L131-L138 /data/modules/MusicPlayer.lua]''':&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function ()&lt;br /&gt;
     MusicPlayer.rebuildSongList()&lt;br /&gt;
     if Game.player:GetDockedWith() and music[&amp;quot;docked&amp;quot;] then&lt;br /&gt;
         MusicPlayer.playRandomSongFromCategory(&amp;quot;docked&amp;quot;)&lt;br /&gt;
     else)&lt;br /&gt;
         playAmbient()&lt;br /&gt;
     end&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
If you register an event more than once in the same file only the last instance will be registered and you will see a log warning on the command line and in output.txt.&lt;br /&gt;
&lt;br /&gt;
Example: '''test_module_1.lua'''&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function ()&lt;br /&gt;
     print(&amp;quot;Only the first instance of onGameStart() will be registered&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function ()&lt;br /&gt;
     print(&amp;quot;Only the last instance of onGameStart() will be registered&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Output. Warning and message:&lt;br /&gt;
 ...&lt;br /&gt;
 Info: Loading [00%]: Sound::Init took 11.47ms&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() started&lt;br /&gt;
 Warning: Module modules.test_module_1 overwriting event callback function: 0x57d5ed8b5d50&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() took 84.46ms&lt;br /&gt;
 Info: Loading [17%]: GalaxyGenerator::Init() started&lt;br /&gt;
 Info: Creating new galaxy generator 'legacy' version  &lt;br /&gt;
 ...&lt;br /&gt;
 Info: building follow-on industry distillery after surface_aquaponics at station Pluto Research Base&lt;br /&gt;
 Info: PROFILE | Economy.PrecacheSystem(&amp;quot;Sol&amp;quot;) took 2.4609ms&lt;br /&gt;
 Info: Only the last instance of onGameStart() will be registered&lt;br /&gt;
 Warning: lodos_shield.model: no materials defined!&lt;br /&gt;
 Info: CreateCollisionMesh for : (lodos_shield)&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
Use instead:&lt;br /&gt;
 local message1 = function ()&lt;br /&gt;
 	print(&amp;quot;This is message1 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local message2 = function ()&lt;br /&gt;
 	print(&amp;quot;This is message2 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function()&lt;br /&gt;
 	message1()&lt;br /&gt;
 	message2()&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
output:&lt;br /&gt;
 ...&lt;br /&gt;
 Info: building follow-on industry distillery after surface_aquaponics at station Pluto Research Base&lt;br /&gt;
 Info: PROFILE | Economy.PrecacheSystem(&amp;quot;Sol&amp;quot;) took 2.4510ms&lt;br /&gt;
 Info: This is message1 calling!&lt;br /&gt;
 Info: This is message2 calling!&lt;br /&gt;
 Warning: skipjack_shield.model: no materials defined!&lt;br /&gt;
 Info: CreateCollisionMesh for : (skipjack_shield)&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
Events can be registered anytime and anywhere in a file but typically they are grouped at the end of a file/module.&lt;br /&gt;
&lt;br /&gt;
If you need to redirect an event to another function you need to first deregister the event.&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local message1 = function ()&lt;br /&gt;
     print(&amp;quot;This is message1 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local message2 = function ()&lt;br /&gt;
     print(&amp;quot;This is message2 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, message1)&lt;br /&gt;
 &lt;br /&gt;
 print(&amp;quot;Now deregistering 'onGameStart message1' and registering 'onGameStart message2'&amp;quot;)&lt;br /&gt;
 print(&amp;quot;No warning message will be given below&amp;quot;)&lt;br /&gt;
 Event.Deregister(&amp;quot;onGameStart&amp;quot;, message1)&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, message2)&lt;br /&gt;
 print(&amp;quot;See!?&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
output:&lt;br /&gt;
 ...&lt;br /&gt;
 Info: Loading [00%]: Sound::Init took 10.81ms&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() started&lt;br /&gt;
 Info: Now deregistering 'onGameStart message1' and registering 'onGameStart message2'&lt;br /&gt;
 Info: No warning message will be given below&lt;br /&gt;
 Info: See!?&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() took 99.92ms&lt;br /&gt;
 Info: Loading [17%]: GalaxyGenerator::Init() started&lt;br /&gt;
 ...&lt;br /&gt;
 Info: building follow-on industry distillery after surface_aquaponics at station Pluto Research Base&lt;br /&gt;
 Info: PROFILE | Economy.PrecacheSystem(&amp;quot;Sol&amp;quot;) took 2.4787ms&lt;br /&gt;
 Info: This is message2 calling!&lt;br /&gt;
 Warning: storeria_shield.model: no materials defined!&lt;br /&gt;
 Info: CreateCollisionMesh for : (storeria_shield)&lt;br /&gt;
 ...&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_game:_Event-based_programming&amp;diff=4780</id>
		<title>Interacting with the game: Event-based programming</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_game:_Event-based_programming&amp;diff=4780"/>
		<updated>2026-06-08T12:01:50Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* More events */ fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
==Events==&lt;br /&gt;
&lt;br /&gt;
The Lua scripts are all executed at startup. If you were to add a single file named &amp;lt;code&amp;gt;hello_world.lua&amp;lt;/code&amp;gt; to the '''data/modules''' directory containing the following:&lt;br /&gt;
&lt;br /&gt;
 print(&amp;quot;Hello, World!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
you would literally see the words, &amp;quot;''Hello, World!''&amp;quot; appear in Pioneer's output (if running in a terminal) shortly before the main menu appears. You would also see it in the Lua console, if you were to open it.&lt;br /&gt;
&lt;br /&gt;
All file-scoped imperative statements in all Lua files are executed at that time. The way to get Lua code to interact with the game itself, beyond that time, is to write functions and to connect them to event handlers. Many events are triggered by Pioneer during the course of play, all of which will cause any functions which are connected to them, to run. Most will provide those functions with arguments.&lt;br /&gt;
&lt;br /&gt;
Here is a quick list of some of the more commonly used events:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; is triggered when the player clicks on a new game button in the main menu, or when the player loads a game.&lt;br /&gt;
* &amp;lt;code&amp;gt;onEnterSystem&amp;lt;/code&amp;gt; is triggered whenever any ship arrives in the current star system after a hyperspace journey.&lt;br /&gt;
* &amp;lt;code&amp;gt;onLeaveSystem&amp;lt;/code&amp;gt; is triggered whenever any ship leaves the current star system by hyperspacing.&lt;br /&gt;
* &amp;lt;code&amp;gt;onShipDestroyed&amp;lt;/code&amp;gt; is triggered whenever any ship is destroyed.&lt;br /&gt;
* &amp;lt;code&amp;gt;onShipDocked&amp;lt;/code&amp;gt; is triggered whenever any ship docks at a starport.&lt;br /&gt;
&lt;br /&gt;
There are many more. All are fully documented in the [https://codedoc.pioneerspacesim.net/#LuaClass:Event Pioneer Codedoc]. Of the five that I have listed, only &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; does not provide the function with any arguments. The other four provide a reference to the ship in question, and the latter two each also provide an additional argument (a reference to the attacker, and the starport, respectively).&lt;br /&gt;
&lt;br /&gt;
==Writing a function for an event==&lt;br /&gt;
&lt;br /&gt;
An event handling function does not have to return anything. It will be passed any arguments specified in the documentation, which it can either deal with, or ignore. It has access to any variables that are declared in the same file scope, including named functions and tables.&lt;br /&gt;
&lt;br /&gt;
Here is an adaptation of the 'Hello World' message above to be event driven. It's now triggered on game start and will still turn up on the command line, but now much later in the start sequence, pretty much when the game starts and you find yourself docked at a starport.&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local welcome = function ()&lt;br /&gt;
     print(&amp;quot;Hello, World!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, welcome)&lt;br /&gt;
&lt;br /&gt;
We move on. The same function again but now instead the message is presented on the player's ship console and is now welcoming them to Pioneer. We need to add the ''' 'Comms' ''' module to the script and the function name has changed to ''' 'onGameStart' ''', same as the event, which is common practice in Pioneer.&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
The latest code may not work as intended. The reason is that ''' 'onGameStart' ''' is not when the game starts but when it is being launched after pressing the button on the main menu to start on Mars, or whatever location you prefer. Let's see if there is an event that better suits our purpose. ''' 'onShipDocked''''?. This will make the message trigger every time we dock at a space station, on the ground or in orbit. ''' 'onShipDocked' ''' will not trigger on launching a saved game or when we start a new game, docked at a starport. Now the script works just fine and will launch the message the next time you land or dock with a space station.&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function ()&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
If you look at the comms log after docking/landing, you may see the greeting posted more than once. This is because the same function is triggered for all ships in the vicinity, not only the player's. The Pioneer universe is populated by ships and characters and they follow pretty much the same rules as the player. To fix this we need to test if the ship is the player first. Modify the ''' 'onShipDocked' ''' function in the previous example like this:&lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
         Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
An alternative solution to the last code snippet would be to test for if the ship is '''not''' the player:&lt;br /&gt;
&lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if not ship:IsPlayer() then return end&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==Setting a timer==&lt;br /&gt;
Yet another way to avoid the problem with ''' 'onGameStart' ''' missing your function initially is to place a [https://codedoc.pioneerspacesim.net/#CClass:Timer Timer] to delay the event for a short while. Just a second or so to allow the game to load completely. CallAt takes two arguments, the time of action and a function to be carried out. Example form the Timer documentation:&lt;br /&gt;
 Timer:CallAt(Game.time+30, function ()&lt;br /&gt;
     Comms.Message(&amp;quot;Special offer expired, sorry.&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
A complete example of the welcome message reworked with a timer. ''' 'Game.time' ''' gives us the game time right now so if we call the timer with ''' 'Game.time + 1' ''' we give it a second to think things through.&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     if not Game.player then return end&lt;br /&gt;
 &lt;br /&gt;
     Timer:CallAt(Game.time + 1, function ()&lt;br /&gt;
         Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
==Event arguments==&lt;br /&gt;
&lt;br /&gt;
As mentioned before '''[https://codedoc.pioneerspacesim.net/#LuaClass:Event:onShipDocked onShipDocked]''' passes two arguments to the function. A reference to the ship and a reference to the spacestation.&lt;br /&gt;
 local onShipDocked = function (ship, station)&lt;br /&gt;
Through these arguments we also get access to some of the ''' 'ship' ''' and ''' 'station' ''' methods without having to include any modules.&lt;br /&gt;
''' 'ship:IsPlayer()' ''' is for free. Unlimited power is now at your fingertips! ''' 'Comms.Message' ''' takes a second argument for the sender of the message. ''' 'station.label' ''' gives us the name of the space station.&lt;br /&gt;
&lt;br /&gt;
 Comms.Message (&amp;quot;Congratulations! Your ship has been upgraded for free!&amp;quot;, station.label)&lt;br /&gt;
 ship:SetShipType('xylophis')&lt;br /&gt;
&lt;br /&gt;
==More events==&lt;br /&gt;
&lt;br /&gt;
An event can be registered only once per file. If you need more than one function called by the same event you must wrap them in a function.&lt;br /&gt;
&lt;br /&gt;
Example from '''[https://github.com/pioneerspacesim/pioneer/blob/6111dad3f0b14d64df844511bf3c92b9e583ec76/data/modules/MusicPlayer.lua#L131-L138 /data/modules/MusicPlayer.lua]''':&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function ()&lt;br /&gt;
     MusicPlayer.rebuildSongList()&lt;br /&gt;
     if Game.player:GetDockedWith() and music[&amp;quot;docked&amp;quot;] then&lt;br /&gt;
         MusicPlayer.playRandomSongFromCategory(&amp;quot;docked&amp;quot;)&lt;br /&gt;
     else)&lt;br /&gt;
         playAmbient()&lt;br /&gt;
     end&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
If you register an event more than once in the same file only the last instance will be registered and you will see a log warning on the command line and in output.txt.&lt;br /&gt;
&lt;br /&gt;
Example - test_module_1.lua:&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function ()&lt;br /&gt;
     print(&amp;quot;Only the first instance of onGameStart() will be registered&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function ()&lt;br /&gt;
     print(&amp;quot;Only the last instance of onGameStart() will be registered&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Output. Warning and message:&lt;br /&gt;
 ...&lt;br /&gt;
 Info: Loading [00%]: Sound::Init took 11.47ms&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() started&lt;br /&gt;
 Warning: Module modules.test_module_1 overwriting event callback function: 0x57d5ed8b5d50&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() took 84.46ms&lt;br /&gt;
 Info: Loading [17%]: GalaxyGenerator::Init() started&lt;br /&gt;
 Info: Creating new galaxy generator 'legacy' version  &lt;br /&gt;
 ...&lt;br /&gt;
 Info: building follow-on industry distillery after surface_aquaponics at station Pluto Research Base&lt;br /&gt;
 Info: PROFILE | Economy.PrecacheSystem(&amp;quot;Sol&amp;quot;) took 2.4609ms&lt;br /&gt;
 Info: Only the last instance of onGameStart() will be registered&lt;br /&gt;
 Warning: lodos_shield.model: no materials defined!&lt;br /&gt;
 Info: CreateCollisionMesh for : (lodos_shield)&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
Use instead:&lt;br /&gt;
 local message1 = function ()&lt;br /&gt;
 	print(&amp;quot;This is message1 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local message2 = function ()&lt;br /&gt;
 	print(&amp;quot;This is message2 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function()&lt;br /&gt;
 	message1()&lt;br /&gt;
 	message2()&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
output:&lt;br /&gt;
 ...&lt;br /&gt;
 Info: building follow-on industry distillery after surface_aquaponics at station Pluto Research Base&lt;br /&gt;
 Info: PROFILE | Economy.PrecacheSystem(&amp;quot;Sol&amp;quot;) took 2.4510ms&lt;br /&gt;
 Info: This is message1 calling!&lt;br /&gt;
 Info: This is message2 calling!&lt;br /&gt;
 Warning: skipjack_shield.model: no materials defined!&lt;br /&gt;
 Info: CreateCollisionMesh for : (skipjack_shield)&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
Events can be registered anytime and anywhere in a file but typically they are grouped at the end of a file/module.&lt;br /&gt;
&lt;br /&gt;
If you need to redirect an event to another function you need to first deregister the event.&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local message1 = function ()&lt;br /&gt;
     print(&amp;quot;This is message1 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local message2 = function ()&lt;br /&gt;
     print(&amp;quot;This is message2 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, message1)&lt;br /&gt;
 &lt;br /&gt;
 print(&amp;quot;Now deregistering 'onGameStart message1' and registering 'onGameStart message2'&amp;quot;)&lt;br /&gt;
 print(&amp;quot;No warning message will be given below&amp;quot;)&lt;br /&gt;
 Event.Deregister(&amp;quot;onGameStart&amp;quot;, message1)&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, message2)&lt;br /&gt;
 print(&amp;quot;See!?&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
output:&lt;br /&gt;
 ...&lt;br /&gt;
 Info: Loading [00%]: Sound::Init took 10.81ms&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() started&lt;br /&gt;
 Info: Now deregistering 'onGameStart message1' and registering 'onGameStart message2'&lt;br /&gt;
 Info: No warning message will be given below&lt;br /&gt;
 Info: See!?&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() took 99.92ms&lt;br /&gt;
 Info: Loading [17%]: GalaxyGenerator::Init() started&lt;br /&gt;
 ...&lt;br /&gt;
 Info: building follow-on industry distillery after surface_aquaponics at station Pluto Research Base&lt;br /&gt;
 Info: PROFILE | Economy.PrecacheSystem(&amp;quot;Sol&amp;quot;) took 2.4787ms&lt;br /&gt;
 Info: This is message2 calling!&lt;br /&gt;
 Warning: storeria_shield.model: no materials defined!&lt;br /&gt;
 Info: CreateCollisionMesh for : (storeria_shield)&lt;br /&gt;
 ...&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_game:_Event-based_programming&amp;diff=4779</id>
		<title>Interacting with the game: Event-based programming</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_game:_Event-based_programming&amp;diff=4779"/>
		<updated>2026-06-08T12:00:03Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* More events */ fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
==Events==&lt;br /&gt;
&lt;br /&gt;
The Lua scripts are all executed at startup. If you were to add a single file named &amp;lt;code&amp;gt;hello_world.lua&amp;lt;/code&amp;gt; to the '''data/modules''' directory containing the following:&lt;br /&gt;
&lt;br /&gt;
 print(&amp;quot;Hello, World!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
you would literally see the words, &amp;quot;''Hello, World!''&amp;quot; appear in Pioneer's output (if running in a terminal) shortly before the main menu appears. You would also see it in the Lua console, if you were to open it.&lt;br /&gt;
&lt;br /&gt;
All file-scoped imperative statements in all Lua files are executed at that time. The way to get Lua code to interact with the game itself, beyond that time, is to write functions and to connect them to event handlers. Many events are triggered by Pioneer during the course of play, all of which will cause any functions which are connected to them, to run. Most will provide those functions with arguments.&lt;br /&gt;
&lt;br /&gt;
Here is a quick list of some of the more commonly used events:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; is triggered when the player clicks on a new game button in the main menu, or when the player loads a game.&lt;br /&gt;
* &amp;lt;code&amp;gt;onEnterSystem&amp;lt;/code&amp;gt; is triggered whenever any ship arrives in the current star system after a hyperspace journey.&lt;br /&gt;
* &amp;lt;code&amp;gt;onLeaveSystem&amp;lt;/code&amp;gt; is triggered whenever any ship leaves the current star system by hyperspacing.&lt;br /&gt;
* &amp;lt;code&amp;gt;onShipDestroyed&amp;lt;/code&amp;gt; is triggered whenever any ship is destroyed.&lt;br /&gt;
* &amp;lt;code&amp;gt;onShipDocked&amp;lt;/code&amp;gt; is triggered whenever any ship docks at a starport.&lt;br /&gt;
&lt;br /&gt;
There are many more. All are fully documented in the [https://codedoc.pioneerspacesim.net/#LuaClass:Event Pioneer Codedoc]. Of the five that I have listed, only &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; does not provide the function with any arguments. The other four provide a reference to the ship in question, and the latter two each also provide an additional argument (a reference to the attacker, and the starport, respectively).&lt;br /&gt;
&lt;br /&gt;
==Writing a function for an event==&lt;br /&gt;
&lt;br /&gt;
An event handling function does not have to return anything. It will be passed any arguments specified in the documentation, which it can either deal with, or ignore. It has access to any variables that are declared in the same file scope, including named functions and tables.&lt;br /&gt;
&lt;br /&gt;
Here is an adaptation of the 'Hello World' message above to be event driven. It's now triggered on game start and will still turn up on the command line, but now much later in the start sequence, pretty much when the game starts and you find yourself docked at a starport.&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local welcome = function ()&lt;br /&gt;
     print(&amp;quot;Hello, World!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, welcome)&lt;br /&gt;
&lt;br /&gt;
We move on. The same function again but now instead the message is presented on the player's ship console and is now welcoming them to Pioneer. We need to add the ''' 'Comms' ''' module to the script and the function name has changed to ''' 'onGameStart' ''', same as the event, which is common practice in Pioneer.&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
The latest code may not work as intended. The reason is that ''' 'onGameStart' ''' is not when the game starts but when it is being launched after pressing the button on the main menu to start on Mars, or whatever location you prefer. Let's see if there is an event that better suits our purpose. ''' 'onShipDocked''''?. This will make the message trigger every time we dock at a space station, on the ground or in orbit. ''' 'onShipDocked' ''' will not trigger on launching a saved game or when we start a new game, docked at a starport. Now the script works just fine and will launch the message the next time you land or dock with a space station.&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function ()&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
If you look at the comms log after docking/landing, you may see the greeting posted more than once. This is because the same function is triggered for all ships in the vicinity, not only the player's. The Pioneer universe is populated by ships and characters and they follow pretty much the same rules as the player. To fix this we need to test if the ship is the player first. Modify the ''' 'onShipDocked' ''' function in the previous example like this:&lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
         Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
An alternative solution to the last code snippet would be to test for if the ship is '''not''' the player:&lt;br /&gt;
&lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if not ship:IsPlayer() then return end&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==Setting a timer==&lt;br /&gt;
Yet another way to avoid the problem with ''' 'onGameStart' ''' missing your function initially is to place a [https://codedoc.pioneerspacesim.net/#CClass:Timer Timer] to delay the event for a short while. Just a second or so to allow the game to load completely. CallAt takes two arguments, the time of action and a function to be carried out. Example form the Timer documentation:&lt;br /&gt;
 Timer:CallAt(Game.time+30, function ()&lt;br /&gt;
     Comms.Message(&amp;quot;Special offer expired, sorry.&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
A complete example of the welcome message reworked with a timer. ''' 'Game.time' ''' gives us the game time right now so if we call the timer with ''' 'Game.time + 1' ''' we give it a second to think things through.&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     if not Game.player then return end&lt;br /&gt;
 &lt;br /&gt;
     Timer:CallAt(Game.time + 1, function ()&lt;br /&gt;
         Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
==Event arguments==&lt;br /&gt;
&lt;br /&gt;
As mentioned before '''[https://codedoc.pioneerspacesim.net/#LuaClass:Event:onShipDocked onShipDocked]''' passes two arguments to the function. A reference to the ship and a reference to the spacestation.&lt;br /&gt;
 local onShipDocked = function (ship, station)&lt;br /&gt;
Through these arguments we also get access to some of the ''' 'ship' ''' and ''' 'station' ''' methods without having to include any modules.&lt;br /&gt;
''' 'ship:IsPlayer()' ''' is for free. Unlimited power is now at your fingertips! ''' 'Comms.Message' ''' takes a second argument for the sender of the message. ''' 'station.label' ''' gives us the name of the space station.&lt;br /&gt;
&lt;br /&gt;
 Comms.Message (&amp;quot;Congratulations! Your ship has been upgraded for free!&amp;quot;, station.label)&lt;br /&gt;
 ship:SetShipType('xylophis')&lt;br /&gt;
&lt;br /&gt;
==More events==&lt;br /&gt;
&lt;br /&gt;
An event can be registered only once per file. If you need more than one function called by the same event you must wrap them in a function.&lt;br /&gt;
&lt;br /&gt;
Example from '''[https://github.com/pioneerspacesim/pioneer/blob/6111dad3f0b14d64df844511bf3c92b9e583ec76/data/modules/MusicPlayer.lua#L131-L138 /data/modules/MusicPlayer.lua]''':&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function ()&lt;br /&gt;
     MusicPlayer.rebuildSongList()&lt;br /&gt;
     if Game.player:GetDockedWith() and music[&amp;quot;docked&amp;quot;] then&lt;br /&gt;
         MusicPlayer.playRandomSongFromCategory(&amp;quot;docked&amp;quot;)&lt;br /&gt;
     else)&lt;br /&gt;
         playAmbient()&lt;br /&gt;
     end&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
If you register an event more than once in the same file only the last instance will be registered and you will see a log warning on the command line and in output.txt.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function ()&lt;br /&gt;
     print(&amp;quot;Only the first instance of onGameStart() will be registered&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function ()&lt;br /&gt;
     print(&amp;quot;Only the last instance of onGameStart() will be registered&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Output. Warning and message:&lt;br /&gt;
 ...&lt;br /&gt;
 Info: Loading [00%]: Sound::Init took 11.47ms&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() started&lt;br /&gt;
 Warning: Module modules.test_1 overwriting event callback function: 0x57d5ed8b5d50&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() took 84.46ms&lt;br /&gt;
 Info: Loading [17%]: GalaxyGenerator::Init() started&lt;br /&gt;
 Info: Creating new galaxy generator 'legacy' version  &lt;br /&gt;
 ...&lt;br /&gt;
 Info: building follow-on industry distillery after surface_aquaponics at station Pluto Research Base&lt;br /&gt;
 Info: PROFILE | Economy.PrecacheSystem(&amp;quot;Sol&amp;quot;) took 2.4609ms&lt;br /&gt;
 Info: Only the last instance of onGameStart() will be registered&lt;br /&gt;
 Warning: lodos_shield.model: no materials defined!&lt;br /&gt;
 Info: CreateCollisionMesh for : (lodos_shield)&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
Use instead:&lt;br /&gt;
 local message1 = function ()&lt;br /&gt;
 	print(&amp;quot;This is message1 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local message2 = function ()&lt;br /&gt;
 	print(&amp;quot;This is message2 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function()&lt;br /&gt;
 	message1()&lt;br /&gt;
 	message2()&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
output:&lt;br /&gt;
 ...&lt;br /&gt;
 Info: building follow-on industry distillery after surface_aquaponics at station Pluto Research Base&lt;br /&gt;
 Info: PROFILE | Economy.PrecacheSystem(&amp;quot;Sol&amp;quot;) took 2.4510ms&lt;br /&gt;
 Info: This is message1 calling!&lt;br /&gt;
 Info: This is message2 calling!&lt;br /&gt;
 Warning: skipjack_shield.model: no materials defined!&lt;br /&gt;
 Info: CreateCollisionMesh for : (skipjack_shield)&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
Events can be registered anytime and anywhere in a file but typically they are grouped at the end of a file/module.&lt;br /&gt;
&lt;br /&gt;
If you need to redirect an event to another function you need to first deregister the event.&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local message1 = function ()&lt;br /&gt;
     print(&amp;quot;This is message1 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local message2 = function ()&lt;br /&gt;
     print(&amp;quot;This is message2 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, message1)&lt;br /&gt;
 &lt;br /&gt;
 print(&amp;quot;Now deregistering 'onGameStart message1' and registering 'onGameStart message2'&amp;quot;)&lt;br /&gt;
 print(&amp;quot;No warning message will be given below&amp;quot;)&lt;br /&gt;
 Event.Deregister(&amp;quot;onGameStart&amp;quot;, message1)&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, message2)&lt;br /&gt;
 print(&amp;quot;See!?&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
output:&lt;br /&gt;
 ...&lt;br /&gt;
 Info: Loading [00%]: Sound::Init took 10.81ms&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() started&lt;br /&gt;
 Info: Now deregistering 'onGameStart message1' and registering 'onGameStart message2'&lt;br /&gt;
 Info: No warning message will be given below&lt;br /&gt;
 Info: See!?&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() took 99.92ms&lt;br /&gt;
 Info: Loading [17%]: GalaxyGenerator::Init() started&lt;br /&gt;
 ...&lt;br /&gt;
 Info: building follow-on industry distillery after surface_aquaponics at station Pluto Research Base&lt;br /&gt;
 Info: PROFILE | Economy.PrecacheSystem(&amp;quot;Sol&amp;quot;) took 2.4787ms&lt;br /&gt;
 Info: This is message2 calling!&lt;br /&gt;
 Warning: storeria_shield.model: no materials defined!&lt;br /&gt;
 Info: CreateCollisionMesh for : (storeria_shield)&lt;br /&gt;
 ...&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_game:_Event-based_programming&amp;diff=4778</id>
		<title>Interacting with the game: Event-based programming</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_game:_Event-based_programming&amp;diff=4778"/>
		<updated>2026-06-08T11:54:21Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: More events&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
==Events==&lt;br /&gt;
&lt;br /&gt;
The Lua scripts are all executed at startup. If you were to add a single file named &amp;lt;code&amp;gt;hello_world.lua&amp;lt;/code&amp;gt; to the '''data/modules''' directory containing the following:&lt;br /&gt;
&lt;br /&gt;
 print(&amp;quot;Hello, World!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
you would literally see the words, &amp;quot;''Hello, World!''&amp;quot; appear in Pioneer's output (if running in a terminal) shortly before the main menu appears. You would also see it in the Lua console, if you were to open it.&lt;br /&gt;
&lt;br /&gt;
All file-scoped imperative statements in all Lua files are executed at that time. The way to get Lua code to interact with the game itself, beyond that time, is to write functions and to connect them to event handlers. Many events are triggered by Pioneer during the course of play, all of which will cause any functions which are connected to them, to run. Most will provide those functions with arguments.&lt;br /&gt;
&lt;br /&gt;
Here is a quick list of some of the more commonly used events:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; is triggered when the player clicks on a new game button in the main menu, or when the player loads a game.&lt;br /&gt;
* &amp;lt;code&amp;gt;onEnterSystem&amp;lt;/code&amp;gt; is triggered whenever any ship arrives in the current star system after a hyperspace journey.&lt;br /&gt;
* &amp;lt;code&amp;gt;onLeaveSystem&amp;lt;/code&amp;gt; is triggered whenever any ship leaves the current star system by hyperspacing.&lt;br /&gt;
* &amp;lt;code&amp;gt;onShipDestroyed&amp;lt;/code&amp;gt; is triggered whenever any ship is destroyed.&lt;br /&gt;
* &amp;lt;code&amp;gt;onShipDocked&amp;lt;/code&amp;gt; is triggered whenever any ship docks at a starport.&lt;br /&gt;
&lt;br /&gt;
There are many more. All are fully documented in the [https://codedoc.pioneerspacesim.net/#LuaClass:Event Pioneer Codedoc]. Of the five that I have listed, only &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; does not provide the function with any arguments. The other four provide a reference to the ship in question, and the latter two each also provide an additional argument (a reference to the attacker, and the starport, respectively).&lt;br /&gt;
&lt;br /&gt;
==Writing a function for an event==&lt;br /&gt;
&lt;br /&gt;
An event handling function does not have to return anything. It will be passed any arguments specified in the documentation, which it can either deal with, or ignore. It has access to any variables that are declared in the same file scope, including named functions and tables.&lt;br /&gt;
&lt;br /&gt;
Here is an adaptation of the 'Hello World' message above to be event driven. It's now triggered on game start and will still turn up on the command line, but now much later in the start sequence, pretty much when the game starts and you find yourself docked at a starport.&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local welcome = function ()&lt;br /&gt;
     print(&amp;quot;Hello, World!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, welcome)&lt;br /&gt;
&lt;br /&gt;
We move on. The same function again but now instead the message is presented on the player's ship console and is now welcoming them to Pioneer. We need to add the ''' 'Comms' ''' module to the script and the function name has changed to ''' 'onGameStart' ''', same as the event, which is common practice in Pioneer.&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
The latest code may not work as intended. The reason is that ''' 'onGameStart' ''' is not when the game starts but when it is being launched after pressing the button on the main menu to start on Mars, or whatever location you prefer. Let's see if there is an event that better suits our purpose. ''' 'onShipDocked''''?. This will make the message trigger every time we dock at a space station, on the ground or in orbit. ''' 'onShipDocked' ''' will not trigger on launching a saved game or when we start a new game, docked at a starport. Now the script works just fine and will launch the message the next time you land or dock with a space station.&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function ()&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
If you look at the comms log after docking/landing, you may see the greeting posted more than once. This is because the same function is triggered for all ships in the vicinity, not only the player's. The Pioneer universe is populated by ships and characters and they follow pretty much the same rules as the player. To fix this we need to test if the ship is the player first. Modify the ''' 'onShipDocked' ''' function in the previous example like this:&lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
         Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
An alternative solution to the last code snippet would be to test for if the ship is '''not''' the player:&lt;br /&gt;
&lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if not ship:IsPlayer() then return end&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==Setting a timer==&lt;br /&gt;
Yet another way to avoid the problem with ''' 'onGameStart' ''' missing your function initially is to place a [https://codedoc.pioneerspacesim.net/#CClass:Timer Timer] to delay the event for a short while. Just a second or so to allow the game to load completely. CallAt takes two arguments, the time of action and a function to be carried out. Example form the Timer documentation:&lt;br /&gt;
 Timer:CallAt(Game.time+30, function ()&lt;br /&gt;
     Comms.Message(&amp;quot;Special offer expired, sorry.&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
A complete example of the welcome message reworked with a timer. ''' 'Game.time' ''' gives us the game time right now so if we call the timer with ''' 'Game.time + 1' ''' we give it a second to think things through.&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     if not Game.player then return end&lt;br /&gt;
 &lt;br /&gt;
     Timer:CallAt(Game.time + 1, function ()&lt;br /&gt;
         Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
==Event arguments==&lt;br /&gt;
&lt;br /&gt;
As mentioned before '''[https://codedoc.pioneerspacesim.net/#LuaClass:Event:onShipDocked onShipDocked]''' passes two arguments to the function. A reference to the ship and a reference to the spacestation.&lt;br /&gt;
 local onShipDocked = function (ship, station)&lt;br /&gt;
Through these arguments we also get access to some of the ''' 'ship' ''' and ''' 'station' ''' methods without having to include any modules.&lt;br /&gt;
''' 'ship:IsPlayer()' ''' is for free. Unlimited power is now at your fingertips! ''' 'Comms.Message' ''' takes a second argument for the sender of the message. ''' 'station.label' ''' gives us the name of the space station.&lt;br /&gt;
&lt;br /&gt;
 Comms.Message (&amp;quot;Congratulations! Your ship has been upgraded for free!&amp;quot;, station.label)&lt;br /&gt;
 ship:SetShipType('xylophis')&lt;br /&gt;
&lt;br /&gt;
==More events==&lt;br /&gt;
&lt;br /&gt;
An event can be registered only once per file. If you need more than one function called by the same event you must wrap them in a function.&lt;br /&gt;
&lt;br /&gt;
Example from '''[https://github.com/pioneerspacesim/pioneer/blob/6111dad3f0b14d64df844511bf3c92b9e583ec76/data/modules/MusicPlayer.lua#L131-L138 /data/modules/MusicPlayer.lua]''':&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function ()&lt;br /&gt;
     MusicPlayer.rebuildSongList()&lt;br /&gt;
     if Game.player:GetDockedWith() and music[&amp;quot;docked&amp;quot;] then&lt;br /&gt;
         MusicPlayer.playRandomSongFromCategory(&amp;quot;docked&amp;quot;)&lt;br /&gt;
     else)&lt;br /&gt;
         playAmbient()&lt;br /&gt;
     end&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
If you register an event more than once in the same file only the last instance will be registered and you will see a log warning on the command line and in output.txt.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function ()&lt;br /&gt;
     print(&amp;quot;Only the first instance of onGameStart() will be registered&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function ()&lt;br /&gt;
     print(&amp;quot;Only the last instance of onGameStart() will be registered&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Output. Warning and message:&lt;br /&gt;
 ...&lt;br /&gt;
 Info: Loading [00%]: Sound::Init took 11.47ms&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() started&lt;br /&gt;
 Warning: Module modules.test_1 overwriting event callback function: 0x57d5ed8b5d50&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() took 84.46ms&lt;br /&gt;
 Info: Loading [17%]: GalaxyGenerator::Init() started&lt;br /&gt;
 Info: Creating new galaxy generator 'legacy' version  &lt;br /&gt;
 ...&lt;br /&gt;
 Info: building follow-on industry distillery after surface_aquaponics at station Pluto Research Base&lt;br /&gt;
 Info: PROFILE | Economy.PrecacheSystem(&amp;quot;Sol&amp;quot;) took 2.4609ms&lt;br /&gt;
 Info: Only the last instance of onGameStart() will be registered&lt;br /&gt;
 Warning: lodos_shield.model: no materials defined!&lt;br /&gt;
 Info: CreateCollisionMesh for : (lodos_shield)&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
Use instead:&lt;br /&gt;
 local message1 = function ()&lt;br /&gt;
 	print(&amp;quot;This is message1 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local message2 = function ()&lt;br /&gt;
 	print(&amp;quot;This is message2 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, function()&lt;br /&gt;
 	message1()&lt;br /&gt;
 	message2()&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
output:&lt;br /&gt;
 ...&lt;br /&gt;
 Info: building follow-on industry distillery after surface_aquaponics at station Pluto Research Base&lt;br /&gt;
 Info: PROFILE | Economy.PrecacheSystem(&amp;quot;Sol&amp;quot;) took 2.4510ms&lt;br /&gt;
 Info: This is message1 calling!&lt;br /&gt;
 Info: This is message2 calling!&lt;br /&gt;
 Warning: skipjack_shield.model: no materials defined!&lt;br /&gt;
 Info: CreateCollisionMesh for : (skipjack_shield)&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
Events can be registered anytime and anywhere in a file but typically they are grouped at the end of a file/module.&lt;br /&gt;
&lt;br /&gt;
If you need to redirect an event to another function you need to first deregister the event.&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local message1 = function ()&lt;br /&gt;
     print(&amp;quot;This is message1 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local message2 = function ()&lt;br /&gt;
     print(&amp;quot;This is message2 calling!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, message1)&lt;br /&gt;
 &lt;br /&gt;
 print(&amp;quot;Now deregistering 'onGameStart message1' and registering 'onGameStart message2.&amp;quot;)&lt;br /&gt;
 print(&amp;quot;No warning message will be given below&amp;quot;)&lt;br /&gt;
 Event.Deregister(&amp;quot;onGameStart&amp;quot;, message1)&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, message2)&lt;br /&gt;
 print(&amp;quot;See!?&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
output:&lt;br /&gt;
 ...&lt;br /&gt;
 Info: Loading [00%]: Sound::Init took 10.81ms&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() started&lt;br /&gt;
 Info: Now deregistering 'onGameStart message1' and registering 'onGameStart message2.&lt;br /&gt;
 Info: No warning message will be given below&lt;br /&gt;
 Info: See!?&lt;br /&gt;
 Info: Loading [08%]: Lua::InitModules() took 99.92ms&lt;br /&gt;
 Info: Loading [17%]: GalaxyGenerator::Init() started&lt;br /&gt;
 ...&lt;br /&gt;
 Info: building follow-on industry distillery after surface_aquaponics at station Pluto Research Base&lt;br /&gt;
 Info: PROFILE | Economy.PrecacheSystem(&amp;quot;Sol&amp;quot;) took 2.4787ms&lt;br /&gt;
 Info: This is message2 calling!&lt;br /&gt;
 Warning: storeria_shield.model: no materials defined!&lt;br /&gt;
 Info: CreateCollisionMesh for : (storeria_shield)&lt;br /&gt;
 ...&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4777</id>
		<title>Strings and translation</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4777"/>
		<updated>2026-05-28T21:14:37Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Lua Standard Library - String manipulation */ string.scase&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Internationalization==&lt;br /&gt;
Recommended reading: [https://dev.pioneerspacesim.net/contribute/translations Pioneer translators wikipage]&lt;br /&gt;
&lt;br /&gt;
It is important that scripters are familiar with the translation system. In the previous example '''hello_world.lua''' used strings directly like &amp;lt;code&amp;gt;print(&amp;quot;Hello, World!&amp;quot;)&amp;lt;/code&amp;gt;. In order for Pioneer to be translated we instead use tokens and calls on the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module to substitute it for the string in the chosen language. The translated strings are stored together with their 'key' in json format, one language per file that are kept in the [https://github.com/pioneerspacesim/pioneer/tree/master/data/lang data/lang/] directory. If the script file in which they are used is from the '''data/modules''' directory they have names starting with '''module-''' and ending with the name of the module written in one word, in small caps, and with the '.lua' ending omitted. The final translation from English to other languages is done on Pioneer's project page at [https://www.transifex.com/pioneer/pioneer/ Transifex], a dedicated online translation tool. The translations are committed to the pioneer/master branch on GitHub by a bot and are not to be edited manually apart from the '''en.json''' files.&lt;br /&gt;
&lt;br /&gt;
==Translating strings==&lt;br /&gt;
&lt;br /&gt;
Below is a minimal example with one string added into the translation system. It takes two files, one in '''data/modules''' and one in '''data/lang/module-hello'''. The print statement isn't wrapped in a function so it will be executed when the '''hello.lua''' is being parsed on startup. You will have to scroll through the command-line history to find it interleaved with the other output.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/hello.lua'''.&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(l.HELLO)&lt;br /&gt;
&lt;br /&gt;
And a corresponding file containing the translated string, and a description field shown to the translator, giving the context. &lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-hello/en.json'''&lt;br /&gt;
 {&lt;br /&gt;
     &amp;quot;HELLO&amp;quot;: {&lt;br /&gt;
         &amp;quot;description&amp;quot;: &amp;quot;A classic message.&amp;quot;,&lt;br /&gt;
         &amp;quot;message&amp;quot;: &amp;quot;Hello World!&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In short, any script containing strings that needs translation needs to load the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module:&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
Then load the translation resource they want to use in their module, often just named &amp;lt;code&amp;gt;'l'&amp;lt;/code&amp;gt; as in:&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
You may load more than one translation resource in your script but they will need to have unique names:&lt;br /&gt;
 local lh = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 local lg = Lang.GetResource(&amp;quot;module-goodbye&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Working with strings==&lt;br /&gt;
&lt;br /&gt;
Recommended chapters from [https://www.lua.org/pil/contents.html Programming in Lua (first edition) ]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/2.4.html 2.4 – Strings]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/3.4.html 3.4 – Concatenation]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/20.html 20 – The String Library]&amp;lt;br&amp;gt;&lt;br /&gt;
And from the [https://www.lua.org/manual/5.2/ Lua 5.2 Reference Manual]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/manual/5.2/manual.html#6.4 6.4 – String Manipulation]&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Concatenation===&lt;br /&gt;
&lt;br /&gt;
Instead of serving ready made sentences we can stitch them together with the Lua string concatenation operator &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt;. The following codelines result in the same output.&lt;br /&gt;
 print(&amp;quot;Hello Clarice!&amp;quot;)&lt;br /&gt;
 print(&amp;quot;Hello&amp;quot; .. &amp;quot; &amp;quot; .. &amp;quot;Clarice!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
This could be created by the more generic:&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local NameGen = require 'NameGen'&lt;br /&gt;
 &lt;br /&gt;
 local GREETING = 'Hello'&lt;br /&gt;
 local FBIAGENT = NameGen.Surname()&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship == Game.player then&lt;br /&gt;
         Comms.Message(GREETING .. ' ' .. FBIAGENT .. '!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; operator is used all over the codebase. Try a search for its occurrence. On Linux I would do &amp;lt;code&amp;gt;grep -R &amp;quot; \.\. &amp;quot; data/modules/ data/libs/ data/pigui/&amp;lt;/code&amp;gt; and that will render a lot of output.&lt;br /&gt;
&lt;br /&gt;
===Token concatenation===&lt;br /&gt;
&lt;br /&gt;
You concatenate strings but you also concatenate the string tokens. In the example below from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/modules/DonateToCranks/DonateToCranks.lua#L14-L21 DonateToCranks.lua]''' we see the table &amp;lt;code&amp;gt;flavours&amp;lt;/code&amp;gt; being populated by strings from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/lang/module-donatetocranks/en.json data/lang/module-donatetocranks/en.lua]'''&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 for i = 0,9 do&lt;br /&gt;
 	table.insert(flavours, {&lt;br /&gt;
 		title     = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE&amp;quot;],&lt;br /&gt;
 		desc      = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_DESC&amp;quot;],&lt;br /&gt;
 		message   = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_MESSAGE&amp;quot;],&lt;br /&gt;
 	})&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
The first string in the table is given the value of &amp;lt;code&amp;gt;l[FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE]&amp;lt;/code&amp;gt; is concatenated to &amp;lt;code&amp;gt;l[FLAVOUR_0_ADTITLE]&amp;lt;/code&amp;gt; which corresponds to the first string in the language module which is seen below.&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;FLAVOUR_0_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE!&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_DESC&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;The Church of The Celestial Flying Spaghetti Monster needs YOUR money to spread the word of god.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_MESSAGE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Please select an amount to donate to the Church of the Celestial Flying Spaghetti Monster.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_1_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 &lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
===Lua Standard Library - String manipulation===&lt;br /&gt;
&lt;br /&gt;
I would start to search the '''data/''' register for ''' 'string' '''. to get a feeling for how it's used. Again on Linux i would use the 'grep' command. &amp;lt;code&amp;gt;grep -R &amp;quot;string\.&amp;quot; data/&amp;lt;/code&amp;gt;. You will see that the two most frequently used functions is &amp;lt;code&amp;gt;string.format()&amp;lt;/code&amp;gt;, which is a part of the lua standard library, and &amp;lt;code&amp;gt;string.interp()&amp;lt;/code&amp;gt;, which is a Pioneer global function residing in '''data/libs/autoload.lua''', and is described in the next paragraph about [https://wiki.pioneerspacesim.net/wiki/Strings_and_translation#String_variables String variables].&lt;br /&gt;
&lt;br /&gt;
Some examples from the Lua standard library:&lt;br /&gt;
 Comms.Message(string.lower('wHaT cAsE sHoULd I usE?'))&lt;br /&gt;
&lt;br /&gt;
We also have extended the string class with our own Pioneer mods. '''string.scase''' Will capitalize the first letter in every sentence in a string. As we will see later a string can contain variables. If a string variable is both a word and is used anywhere in a sentence, then we need to be able to capitalize it on the fly if it is used as the first word.&lt;br /&gt;
 string.scase(&amp;quot;a sentence. another sentence.&amp;quot;)&lt;br /&gt;
Would return: &amp;quot;A sentence. Another sentence.&amp;quot;&lt;br /&gt;
 Comms.Message(string.scase(string.lower('wHaT cAsE sHoULd I usE? AnY casE yoU waNT!')))&lt;br /&gt;
&lt;br /&gt;
Back to the standard library. Maybe not the most easy function to fit into Pioneer, but it's there:&lt;br /&gt;
 Comms.Message('Today is opposite day!')&lt;br /&gt;
 Comms.Message(string.reverse('No, today is not opposite day!'))&lt;br /&gt;
&lt;br /&gt;
A more complex example from '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/pigui/modules/hyperjump-planner.lua#L141 data/pigui/modules/hyperjump-planner.lua]'''&amp;lt;br&amp;gt;&lt;br /&gt;
Here &amp;lt;code&amp;gt;string.format(&amp;quot;%.2f&amp;quot;, distance)&amp;lt;/code&amp;gt; is used to set the number of decimals in the jump to two. The units are translatable but the system name isn't (jump_sys.name). '''lc''' is used as the short name for the '''core''' translation resource.&lt;br /&gt;
&lt;br /&gt;
 textLine = jumpIndex ..&amp;quot;: &amp;quot;.. jump_sys.name .. &amp;quot; (&amp;quot; .. string.format(&amp;quot;%.2f&amp;quot;, distance) .. lc.UNIT_LY .. &amp;quot; - &amp;quot; .. fuel .. lc.UNIT_TONNES..&amp;quot;)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here is what the final formatted text looks like (right side, two selected jump routes):&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:hyperjumpplanner.png]]&lt;br /&gt;
&lt;br /&gt;
===PiGUI - Format===&lt;br /&gt;
&lt;br /&gt;
Pioneer has it's own set of formatting functions accessible through PiGUI, our UI. Some of it is realised on the C++ side and described in codedoc [https://codedoc.pioneerspacesim.net/#CClass:Format here]. Most of the functions, however, are strictly Lua and reside in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/libs/text.lua#L146-L385 data/pigui/libs/text.lua]. You access the functions with '''require''' and make sure that '''PiGUI''' is in the scope.&lt;br /&gt;
&lt;br /&gt;
 local ui = require 'pigui'&lt;br /&gt;
 local Format = require 'Format'&lt;br /&gt;
&lt;br /&gt;
Here is an example from the System overview. The table to the right is relying heavily on the Format functions.&lt;br /&gt;
[[File:Systemoverview.png]]&lt;br /&gt;
&lt;br /&gt;
The code responsible for the table is here in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/modules/system-view-ui.lua#L811-L840 data/pigui/modules/system-view-ui.lua] with an excerpt showed below. The examples below take 1 or two arguments and the lines ending with 'or nil' is what makes the System overview info window omit values that don't apply to the selected system body.&lt;br /&gt;
&lt;br /&gt;
 ...&lt;br /&gt;
 { name = lc.ESCAPE_VELOCITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Speed(body.escapeVelocity , true) or nil },&lt;br /&gt;
 { name = lc.MEAN_DENSITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Mass(body.meanDensity)..&amp;quot;/m³&amp;quot; or nil },&lt;br /&gt;
 { name = lc.ORBITAL_PERIOD, icon = icons.body_orbit_period,&lt;br /&gt;
     value = op and op &amp;gt; 0 and ui.Format.Duration(op, 2) or nil },&lt;br /&gt;
 { name = lc.DAY_LENGTH, icon = icons.body_day_length,&lt;br /&gt;
     value = rp &amp;gt; 0 and ui.Format.Duration(rp, 2) or nil },&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
==String variables==&lt;br /&gt;
&lt;br /&gt;
When you want a string to present variable data such as numbers and names you use the &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; function. &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; accepts a table '{}' and the corresponding keys are then embedded in the translated strings enclosed by curly brackets '{}'.&lt;br /&gt;
&lt;br /&gt;
Using ''''module-stationrefuelling'''' we can do:&lt;br /&gt;
 print(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = 'JFK', fee = 50}))&lt;br /&gt;
&lt;br /&gt;
Example from the actual StationRefuelling module:&lt;br /&gt;
&lt;br /&gt;
'''data/modules/StationRefuelling/'''&lt;br /&gt;
 Comms.Message(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = station.label,fee = Format.Money(fee)}))&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/en.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Welcome to {station}. Your landing fee of {fee} has been deducted.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/it.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Benvenuti a {station}. Vi è stata dedotta una tassa di atterraggio di {fee}.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Mission flavours==&lt;br /&gt;
&lt;br /&gt;
A script can define a mission and then place many instances of it onto many bulletin boards. A script that introduces many instances in the game should provide some variety; it would harm immersion if, for examle, all delivery missions were worded identically. The easiest way to achieve multiple versions, or &amp;quot;flavours&amp;quot;, is by just making a number of different strings and then choosing between them with a simple random function. This is how the [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L18:L48 deny] and [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L282:L320 pirate taunt] messages are done in the DeliverPackage module.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/DeliverPackage/DeliverPackage.lua'''&lt;br /&gt;
 local num_deny = 8&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 if not qualified then&lt;br /&gt;
     local introtext = l[&amp;quot;DENY_&amp;quot;..Engine.rand:Integer(1,num_deny)-1]&lt;br /&gt;
         form:SetMessage(introtext)&lt;br /&gt;
     return&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-deliverpackage/en.json'''&lt;br /&gt;
 &amp;quot;DENY_0&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm sorry, but I don't think I can trust you with this delivery.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;DENY_1&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Come back when you have some qualifications.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;DENY_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm not willing to risk this delivery on someone without any qualifications like you.&amp;quot;&lt;br /&gt;
You don't have to use a string variable that has been passed to the translation file. The pirate taunts, for example, have access to the clients name and the destination of the package but this information is only used in some of the strings.&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;That package isn't going to reach its destination today.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_8&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;You're not getting to {location} today!&amp;quot;&lt;br /&gt;
Another way to add variation is to have a table of strings that are variations of a theme, a flavour. In the example below, again from the DeliverPackage module, you have two of the lines from '''FLAVOUR_0''' AND '''FLAVOUR_1''' compared.&lt;br /&gt;
 &amp;quot;FLAVOUR_0_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;GOING TO the {system} system? Money paid for delivery of a small package.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_0_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Unacceptable! You took forever over that delivery. I'm not willing to pay you.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;FLAVOUR_1_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;WANTED. Delivery of a package to the {system} system.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_1_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm frustrated by the late delivery of my package, and I refuse to pay you.&amp;quot;&lt;br /&gt;
There are ten flavours in DeliverPackage of five strings each, each forming a short story line. They are static in that you only see lines from within the same flavour. You know what lines will follow if you accept the mission and after a short time of playing you know this already from seeing the ad if delivering things is your forte. It's hard to share strings in between the flavours as they would need to be much more similar. It's a trade off. You could build a very complex interaction with the customers but in the end it needs to be translatable.&lt;br /&gt;
&lt;br /&gt;
Generally, the more specific the text is, the stranger it will appear to see it multiple times in the game. For example, consider the following two examples:&lt;br /&gt;
 - &amp;quot;Contract is to move X tonnes of Y to system Z&amp;quot;&lt;br /&gt;
 - &amp;quot;My uncle's cat got sick, so I can't travel right now, so I need you to take my contract for me, to move X tonnes of Y to my home system Z&amp;quot;&lt;br /&gt;
The former might not need any flavours, as it's unpersonal, and very general. Seeing the second string multiple times does break immersion. Solution is to not show it often, either by making the mission rare / single occurance (no script to date does this) or have many flavours, making probability of the same twice low.&lt;br /&gt;
&lt;br /&gt;
==Trailing commas==&lt;br /&gt;
&lt;br /&gt;
The two most common text based files you will come across when you work with module scripting are Lua and JSON. While JSON is strictly used for information, Lua is used most as a scripting language but in some instances it's used for data. See for instance '''data/modules/CargoRun/CargoTypes.lua'''. It is basically just tables of data. In any way, we have used both in this chapter and it's probably a good time to address a &lt;br /&gt;
difference between the two that could otherwise create confusion. Trailing commas after the last element in a table. The short version is: In Lua, you can use trailing commas on the last element in a table. In JSON you cannot.&lt;br /&gt;
&lt;br /&gt;
===Lua===&lt;br /&gt;
&lt;br /&gt;
Here are the last four ladies in the Hawaiian culture file. '''data/culture/haw.lua'''. These are the files we stitch together characters/clients names from. As you see, the last element, ''''Wanaao'''' has a trailing comma after her. This is perfectly fine.&lt;br /&gt;
&lt;br /&gt;
    'Pohaku',&lt;br /&gt;
    'Oke',&lt;br /&gt;
    'Wailani',&lt;br /&gt;
    'Wanaao',         -- Lua. A comma here is fine!&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===JSON===&lt;br /&gt;
&lt;br /&gt;
Here we see the last two elements in the '''data/lang/core/en.json'''. It keeps some of the strings used in the game and every language has it's own version of it. Trailing commas in JSON is explicitly forbidden in the official description of the file format. There are derivative interpreters that are relaxed and allow trailing commas but this is not the case with the one used in Pioneer.&lt;br /&gt;
&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;ZOOM_IN&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Zoom in&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;ZOOM_OUT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Zoom out&amp;quot;&lt;br /&gt;
   }                   -- JSON. A comma here is NOT fine!&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The fastest way to find out what could happen is to simply just put a comma in that place and start Pioneer. Open the '''System oveview''' and click on a planet and look at the data presented in the pop up window. There is now a lot of '''[NO_JSON]''' data presented in there.&lt;br /&gt;
&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Zoom out&amp;quot;&lt;br /&gt;
   },                   -- ;o -onoh!&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Also, another difference between the file formats is that the comment I added above in the Lua file is a real Lua comment and would work and be ignored by the language. The  ;o emoji comment in the JSON file wouldn't work. JSON doesn't have comments in it. You would have to assign a separate element to be used as a comment like the translation file above which has elements beginning with '''&amp;quot;description&amp;quot;:'''.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Pioneer_Wiki&amp;diff=4774</id>
		<title>Pioneer Wiki</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Pioneer_Wiki&amp;diff=4774"/>
		<updated>2026-04-13T19:41:22Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: Fix link&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
Pioneer is a space adventure game set in the Milkyway galaxy at the turn of the 33rd century. The game is open-ended, and you are free to explore the millions of star systems in the game. You can land on planets, slingshot past gas giants, and burn yourself to a crisp flying between binary star systems.&lt;br /&gt;
&lt;br /&gt;
If you are new to pioneer you may want to know [[How_to_start|how to start]].&lt;br /&gt;
&lt;br /&gt;
*[https://pioneerspacesim.net/page/download/ Download Pioneer] for Windows and Linux &lt;br /&gt;
&lt;br /&gt;
Please make this wiki nice. Add stuff about mods, dev, whatever. Be awesome! [[Getting_started|How to get started]].&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
{| width=&amp;quot;100%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background:#bbffbb; border:2px solid #99ee99; padding:2px 2px 4px 4px; vertical-align: top&amp;quot; width=&amp;quot;33%&amp;quot; | &lt;br /&gt;
=== Community ===&lt;br /&gt;
&lt;br /&gt;
*[https://pioneerspacesim.net/ Pioneer Space Sim Homepage]&lt;br /&gt;
*[https://github.com/pioneerspacesim/pioneer/ Main Github repository]&lt;br /&gt;
*[https://spacesimcentral.com/viewforum.php?f=49 Community forum] &lt;br /&gt;
*[https://forum.pioneerspacesim.net/ Developer forum] &lt;br /&gt;
*[https://www.reddit.com/r/pioneerspacesim Pioneer Space Sim on Reddit]&lt;br /&gt;
*[https://pioneerspacesim.itch.io/pioneer Pioneer Space Sim on itch]&lt;br /&gt;
*[[IRC|Pioneer Space Sim on IRC, Channel: #pioneer, network: libera.chat]] (where devs hang out)&lt;br /&gt;
*[https://twitter.com/pioneerspacesim @pioneerspacesim] on Twitter&lt;br /&gt;
*[https://mstdn.games/@pioneerspacesim @pioneerspacesim] on Mastodon at [https://mstdn.games mstdn.games]&lt;br /&gt;
*[https://www.youtube.com/channel/UCbFD5Oxc1C9zxqnF4OitGjQ Pioneer Space Simulator] on Youtube&lt;br /&gt;
*[https://discord.com/invite/RQQe3A7 discord]&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
*[[Manual|Basic Manual]] &lt;br /&gt;
*[[Flight_UI|Flight UI]] &lt;br /&gt;
*[[Keyboard_and_mouse_controls|Keyboard and mouse controls]] &lt;br /&gt;
*[[Tutorials|Tutorials]] (Outdated and WIP) &lt;br /&gt;
*[[Mission_Types_and_BBS|Mission Types and BBS]] &lt;br /&gt;
*[[Settings_Menu|Settings Menu]] &lt;br /&gt;
*[[FAQ|FAQ]] &lt;br /&gt;
*[[Media_Coverage|Media Coverage]] &lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;background:#fff9b1; border:2px solid #f0fe66; padding:2px 2px 4px 4px; vertical-align: top&amp;quot; width=&amp;quot;33%&amp;quot; |&lt;br /&gt;
&lt;br /&gt;
=== The Pioneer Universe ===&lt;br /&gt;
&lt;br /&gt;
*[[Pioneer_Universe|Pioneer Universe]] &lt;br /&gt;
*[[:Category:Ships| Ships]] (work in progress) &lt;br /&gt;
*[[:Category:Manufacturers| Manufacturers]] (work in progress) &lt;br /&gt;
*[[Places_of_interest|Places of interest]] &lt;br /&gt;
*[[Ship_Equipment|Ship Equipment]] (work in progress) &lt;br /&gt;
*[[Custom Star Systems|Custom Star Systems]] (Work in progress)&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;amp;nbsp;&lt;br /&gt;
&lt;br /&gt;
{| width=&amp;quot;100%&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| style=&amp;quot;background:#f0f0ff; border:2px solid #c6c9ff; padding:2px 2px 4px 4px; vertical-align: top&amp;quot; width=&amp;quot;33%&amp;quot; |&lt;br /&gt;
&lt;br /&gt;
=== Content Creation ===&lt;br /&gt;
&lt;br /&gt;
*[[Mods|Mods]]&lt;br /&gt;
*[[Scripting_and_Mission_Creation|Scripting and Mission Creation]] &lt;br /&gt;
*[[Custom_Systems|Custom Systems]] &lt;br /&gt;
*[[Design_and_Concept_art|Design and Concept art]] &lt;br /&gt;
*[[Modeling,_Texturing,_Graphics_Design|Modeling, Texturing, Graphics Design]] &lt;br /&gt;
*[[Music_and_Sound|Music and Sound]] &lt;br /&gt;
*[[Translations|Translations]] &lt;br /&gt;
*[[Licensing|Licensing]] &lt;br /&gt;
&lt;br /&gt;
| style=&amp;quot;background:#fff3e3; border:2px solid #ffc9c9; padding:2px 2px 4px 4px; vertical-align: top&amp;quot; width=&amp;quot;33%&amp;quot; | &lt;br /&gt;
=== Development ===&lt;br /&gt;
&lt;br /&gt;
*[[Design_Scope|Game Scope]] &lt;br /&gt;
*[[How_you_can_contribute|How you can contribute]] &lt;br /&gt;
*[[Getting_Started_with_Development|Getting Started with Development]] &lt;br /&gt;
**[[How_to_communicate|How to communicate]] &lt;br /&gt;
**[[Using_git_and_GitHub|Using git and GitHub]] &lt;br /&gt;
**[[Coding Conventions]]&lt;br /&gt;
**[[Development_team|Development team]]  &lt;br /&gt;
**[[Engine_Overview|Engine internals overview]] &lt;br /&gt;
**[[Development_Tools| Development tools]]&lt;br /&gt;
*[[Development_Model|Development Model]] &lt;br /&gt;
*[[Design_Workflow|Design and Project Management Workflow]] &lt;br /&gt;
*[https://github.com/pioneerspacesim/pioneer/issues Issue tracker] &lt;br /&gt;
*[[Roadmap|Roadmap]]&lt;br /&gt;
*[[Making a release]]&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Pioneer is brought to you by a flock of [https://github.com/pioneerspacesim/pioneer/blob/master/AUTHORS.txt opensource beardy-weirdies], and is [http://www.gnu.org/licenses/gpl.html Free Software]&lt;br /&gt;
&lt;br /&gt;
__NOTOC__&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Surviving_a_reload&amp;diff=4757</id>
		<title>Surviving a reload</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Surviving_a_reload&amp;diff=4757"/>
		<updated>2025-11-15T19:38:30Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: fixup - layout&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;When a game is saved, a script is responsible for informing Pioneer exactly which of its data needs to be saved. If nothing is specified, nothing will be saved at all. Similarly, after a game is loaded, a script is responsible for restoring all of its saved data; re-creating bulletin board adverts, the player's mission details, and any run-time state.&lt;br /&gt;
&lt;br /&gt;
Here is a simple form. If a new game is started, you will see this on every bulletin board that you visit:&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
     form:SetMessage(&amp;quot;Hello!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert('Click here for a greeting', onChat)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
However, if you save the game, then reload, you will not see it on any bulletin board that had been created in the current system before you saved. onCreateBB will be triggered for any subsequently created bulletin boards, but not for those that already existed. It isn't appropriate to have scripts create new adverts just because a player has loaded; instead, it's the responsibility of the script to tell Pioneer what to save, and to use those saved data to restore all adverts after the game is loaded. The same goes for player missions, and any working data that the script is using.&lt;br /&gt;
&lt;br /&gt;
There are two things we need to do to achieve all of this:&lt;br /&gt;
&lt;br /&gt;
* We need to track everything that the script is doing.&lt;br /&gt;
* We need to be able to pack this away for saving, and bring it back after loading.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Tracking everything that we are doing==&lt;br /&gt;
&lt;br /&gt;
Any local table that is declared in file scope is visible to the entire script, without affecting any other scripts. Essential data should be kept in such tables.&lt;br /&gt;
&lt;br /&gt;
==Tracking adverts==&lt;br /&gt;
It's important to be able to re-create an advert, so part of the process of making one should be storing that information. Your script will need to make a note of in which station the advert was placed, which flavour was used (if you have flavours), the face data that was used on the form and any unique information that was used, such as specific mission details, reward, etc. The simplest way to keep all of this together is in a table, gathering up the variables by name into keys of the same name. It could look something like this:&lt;br /&gt;
&lt;br /&gt;
 local advert = {&lt;br /&gt;
     station = station,&lt;br /&gt;
     flavour = flavour, -- This will be a table&lt;br /&gt;
     client = client,   -- This will be a table (a character)&lt;br /&gt;
     target = target,&lt;br /&gt;
     destination = destination,&lt;br /&gt;
     reward = reward,&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In the case of the 'greeting', the first example above, we would just have to save the message ('Hello!'), Advert string ('Click here for a greeting') and the station. It's a very simple example but there are real modules that aren't much more complex than that.&lt;br /&gt;
&lt;br /&gt;
Remember, the &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method takes three arguments: The advert text, the &amp;lt;code&amp;gt;onChat&amp;lt;/code&amp;gt; function and the &amp;lt;code&amp;gt;onDelete&amp;lt;/code&amp;gt; function. It also returns a unique number, which lends itself nicely to storing the information away. That same unique number is passed to the onChat function, allowing onChat to check that stored information.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;onDelete()&amp;lt;/code&amp;gt; function (again, we call it that by convention only) also accepts this reference as its argument, and is called whenever an advert is destroyed, whether that destruction be explicit (&amp;lt;code&amp;gt;RemoveAdvert()&amp;lt;/code&amp;gt;) or implicit (the player hyperspaces away).&lt;br /&gt;
&lt;br /&gt;
So, we can track adverts that have been created, and stop tracking any that have gone away. We'll do that using the reference returned by &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; as the key to a file-scoped local table:&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 local ads = {}&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
     form:SetFace(ads[ref].character)&lt;br /&gt;
     form:SetMessage(ads[ref].flavour.title)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onDelete = function (ref)&lt;br /&gt;
     ads[ref] = nil&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     local flavour = flavours[Engine.rand:Integer(1, #flavours)]&lt;br /&gt;
     ref = station:AddAdvert(flavour.title, onChat, onDelete)&lt;br /&gt;
     ads[ref] = {&lt;br /&gt;
         station = station,&lt;br /&gt;
         flavour = flavour,&lt;br /&gt;
         character = Character.new()&lt;br /&gt;
     }&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
==Tracking missions==&lt;br /&gt;
&lt;br /&gt;
This is less of a challenge. Missions created with &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; are stored in the &amp;lt;code&amp;gt;PersistentCharacters.player.missions&amp;lt;/code&amp;gt; table. You also need to register them locally in your script. When &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; is called, it returns a reference to the mission. That reference can be stored in a file-local table that can be iterated through when we need to access a mission. We can wrap these functions up:&lt;br /&gt;
&lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local addMission = function(mission)&lt;br /&gt;
     table.insert(missions,Mission.New(mission))&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local removeMission = function(mref)&lt;br /&gt;
     local ref&lt;br /&gt;
     for i,v in pairs(missions) do&lt;br /&gt;
         if v == mission then&lt;br /&gt;
             ref = i&lt;br /&gt;
             break&lt;br /&gt;
         end&lt;br /&gt;
     end&lt;br /&gt;
     mission:Remove()&lt;br /&gt;
     missions[ref] = nil&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
Now, instead of directly calling &amp;lt;code&amp;gt;Mission.Add()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;mission:Remove()&amp;lt;/code&amp;gt;, we call our local functions, &amp;lt;code&amp;gt;addMission()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;removeMission()&amp;lt;/code&amp;gt;. They will send their argument on to the instance in '''Mission.lua''', and also store or remove the mission ref in the missions table. There is now a record of which missions belong to this script. &amp;lt;code&amp;gt;addMission()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;removeMission()&amp;lt;/code&amp;gt; in the example above is taken from '''SearchRescue.lua'''. Look how &amp;lt;code&amp;gt;removeMission()&amp;lt;/code&amp;gt; is used in the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/SearchRescue/SearchRescue.lua#L241-L252 Search and Rescue module]'''.&lt;br /&gt;
&lt;br /&gt;
==Serializing: Packing away for saving and loading==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;Serializer&amp;lt;/code&amp;gt; object provides one method: &amp;lt;code&amp;gt;Register&amp;lt;/code&amp;gt;. It takes three arguments. The first is a name; a simple string with which to uniquely identify this script. It's probably sensible to re-use the name that was used for the &amp;lt;code&amp;gt;Translate&amp;lt;/code&amp;gt; object's flavour methods. The second and third arguments are both names of functions that each returns a single table. The second argument is your '''serializer''' function and the third is your '''unserializer''' function. By convention, we name these &amp;lt;code&amp;gt;serialize&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;unserialize&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;serialize&amp;lt;/code&amp;gt; must return a table, and this table must contain everything that you need to store to get your script working after a reload. It will be run when the player saves the game. The table can contain any pure-lua types, and SystemPath, Body and SceneGraph.ModelSkin core types, anything else will explode, like a ShipDef/EquipDef or a StarSystem.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;unserialize&amp;lt;/code&amp;gt; accepts a table, and does something with it. It will be run by the serializer after the game is loaded, immediately before the &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; event is triggered. It is passed the data that serialize returned at save time.&lt;br /&gt;
&lt;br /&gt;
The most common way to deal with this is as follows (and this is very cut down):&lt;br /&gt;
&lt;br /&gt;
 local table_stuff_this_script_uses = {}&lt;br /&gt;
 local loaded_data&lt;br /&gt;
 &lt;br /&gt;
 -- The rest of the script goes here!&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart&lt;br /&gt;
     if loaded_data then&lt;br /&gt;
         for k,v in loaded_data.table_stuff_this_script_uses do&lt;br /&gt;
             table_stuff_this_script_uses[k] = v&lt;br /&gt;
         end&lt;br /&gt;
         loaded_data = nil&lt;br /&gt;
     else&lt;br /&gt;
         -- New game; do other stuff here perhaps&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local serialize = function ()&lt;br /&gt;
     return {table_stuff_this_script_uses = table_stuff_this_script_uses}&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local unserialize = function (data)&lt;br /&gt;
     loaded_data = data&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
 Serializer:Register(&amp;quot;TestModule&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
==Complete module==&lt;br /&gt;
&lt;br /&gt;
Now we can finally start to put together complete modules that will save, reload, and behave consistently throughout the game. Many of the modules in Pioneer don't register a mission. As an example of this, see '''BreakdownServicing''', '''DonateToCranks''', and '''CrewContracts'''. In the same spirit, let's complete the first example above, 'Click here for a greeting', and make it survive a reload.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local Event = require 'Event'&lt;br /&gt;
local Serializer = require 'Serializer'&lt;br /&gt;
&lt;br /&gt;
local ads = {}&lt;br /&gt;
&lt;br /&gt;
local onChat = function (form, ref, option)&lt;br /&gt;
    local ad = ads[ref]&lt;br /&gt;
    form:Clear()&lt;br /&gt;
    form:SetMessage(ad.bodytext)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local onDelete = function (ref)&lt;br /&gt;
    ads[ref] = nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- when we enter a system the BBS is created and this function is called&lt;br /&gt;
local onCreateBB = function (station)&lt;br /&gt;
&lt;br /&gt;
    local ad = {&lt;br /&gt;
        headline = 'Click here for a greeting',&lt;br /&gt;
        bodytext = &amp;quot;Hello!&amp;quot;,&lt;br /&gt;
        station  = station&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    -- create one per BBS&lt;br /&gt;
    local ref = station:AddAdvert({&lt;br /&gt;
        description = ad.headline,&lt;br /&gt;
        onChat      = onChat,&lt;br /&gt;
        onDelete    = onDelete}&lt;br /&gt;
    )&lt;br /&gt;
    ads[ref] = ad&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local loaded_data&lt;br /&gt;
&lt;br /&gt;
local onGameStart = function ()&lt;br /&gt;
    ads = {}&lt;br /&gt;
&lt;br /&gt;
    if not loaded_data or not loaded_data.ads then return end&lt;br /&gt;
&lt;br /&gt;
    for k,ad in pairs(loaded_data.ads or {}) do&lt;br /&gt;
        local ref = ad.station:AddAdvert({&lt;br /&gt;
            description = ad.headline,&lt;br /&gt;
            onChat      = onChat,&lt;br /&gt;
            onDelete    = onDelete})&lt;br /&gt;
        ads[ref] = ad&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    loaded_data = nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local serialize = function ()&lt;br /&gt;
    return { ads = ads }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local unserialize = function (data)&lt;br /&gt;
    loaded_data = data&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
Serializer:Register(&amp;quot;message&amp;quot;, serialize, unserialize)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The '''Advice''' module generously gave up parts of its code to complete this script.&lt;br /&gt;
&lt;br /&gt;
Although the example is functional, it has been coded with brevity alone in mind. I would not recommend using this as the basis for a mission without at least completely rewriting &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, and possibly tidying up the temporary loop variable names.&lt;br /&gt;
&lt;br /&gt;
==Don't serialize strings==&lt;br /&gt;
&lt;br /&gt;
When we add the strings literally to the ad as we did in the example above, they will be serialized and saved on game end. Considering this is happening on all active stations, this will waste disk space.&lt;br /&gt;
&lt;br /&gt;
 local ad = {&lt;br /&gt;
     headline = 'Click here for a greeting',&lt;br /&gt;
     bodytext = 'Hello!',&lt;br /&gt;
     station  = station&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
What we want to do instead is save the information to recreate the same strings. If for instance we have a set of flavours that are selected with a random number, then generate that number and save it in a variable. The ad example above can then be rewritten as such. We only need the station and the flavour number to recreate the ad.&lt;br /&gt;
&lt;br /&gt;
 local ad = {&lt;br /&gt;
     station = station,&lt;br /&gt;
     n = n&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The working example in the paragraph above can be updated in this way. When you have the translated strings in a separate module you would start the script by pulling in the strings to the script in an array or set of arrays. In the following example we instead add the strings to an array directly but it shows the general idea.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local Engine = require 'Engine'&lt;br /&gt;
local Event = require 'Event'&lt;br /&gt;
local Game = require 'Game'&lt;br /&gt;
local Rand = require 'Rand'&lt;br /&gt;
local Serializer = require 'Serializer'&lt;br /&gt;
&lt;br /&gt;
local ads = {}&lt;br /&gt;
&lt;br /&gt;
local flavours = {&lt;br /&gt;
    {flavour = 'flavour 1', title = 'title 1', text = 'text 1'},&lt;br /&gt;
    {flavour = 'flavour 2', title = 'title 2', text = 'text 2'},&lt;br /&gt;
    {flavour = 'flavour 3', title = 'title 3', text = 'text 3'}&lt;br /&gt;
}&lt;br /&gt;
local onChat = function (form, ref, option)&lt;br /&gt;
    local ad = ads[ref]&lt;br /&gt;
    form:Clear()&lt;br /&gt;
    form:SetMessage(flavours[ad.n].text)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local onDelete = function (ref)&lt;br /&gt;
    ads[ref] = nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- when we enter a system the BBS is created and this function is called&lt;br /&gt;
local onCreateBB = function (station)&lt;br /&gt;
&lt;br /&gt;
local rand = Rand.New(Game.time)&lt;br /&gt;
local n = rand:Integer(1, #flavours)&lt;br /&gt;
&lt;br /&gt;
local ad = {&lt;br /&gt;
    station  = station,&lt;br /&gt;
    n = n&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- create one per BBS&lt;br /&gt;
local ref = station:AddAdvert({&lt;br /&gt;
        title       = flavours[n].title,&lt;br /&gt;
        description = flavours[n].flavour,&lt;br /&gt;
        onChat      = onChat,&lt;br /&gt;
        onDelete    = onDelete}&lt;br /&gt;
    )&lt;br /&gt;
    ads[ref] = ad&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local loaded_data&lt;br /&gt;
&lt;br /&gt;
local onGameStart = function ()&lt;br /&gt;
    ads = {}&lt;br /&gt;
&lt;br /&gt;
    if not loaded_data or not loaded_data.ads then return end&lt;br /&gt;
&lt;br /&gt;
    for k,ad in pairs(loaded_data.ads or {}) do&lt;br /&gt;
        local ref = ad.station:AddAdvert({&lt;br /&gt;
            title = flavours[ad.n].title,&lt;br /&gt;
            description = flavours[ad.n].flavour,&lt;br /&gt;
            onChat      = onChat,&lt;br /&gt;
            onDelete    = onDelete})&lt;br /&gt;
            ads[ref]    = ad&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    loaded_data = nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local serialize = function ()&lt;br /&gt;
    return { ads = ads }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local unserialize = function (data)&lt;br /&gt;
    loaded_data = data&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
Serializer:Register(&amp;quot;message&amp;quot;, serialize, unserialize)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The codedoc is complete and accurate, and the scripts provided with Pioneer are good examples themselves. The API is extensive, but if you find that there are additional things you would like to be able to do, or information you would like from Pioneer, the dev team are willing to extend the API to accommodate script writers. Simply create a new issue on the [https://github.com/pioneerspacesim/pioneer/issues Github issue tracker].&lt;br /&gt;
&lt;br /&gt;
Help is also always available on the [https://forum.pioneerspacesim.net/ Pioneer dev forum], and many of the dev team can be found on [[IRC]] at all hours.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Surviving_a_reload&amp;diff=4756</id>
		<title>Surviving a reload</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Surviving_a_reload&amp;diff=4756"/>
		<updated>2025-11-11T18:44:59Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Serializing: Packing away for saving and loading */ Improve language&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;When a game is saved, a script is responsible for informing Pioneer exactly which of its data needs to be saved. If nothing is specified, nothing will be saved at all. Similarly, after a game is loaded, a script is responsible for restoring all of its saved data; re-creating bulletin board adverts, the player's mission details, and any run-time state.&lt;br /&gt;
&lt;br /&gt;
Here is a simple form. If a new game is started, you will see this on every bulletin board that you visit:&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
     form:SetMessage(&amp;quot;Hello!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert('Click here for a greeting', onChat)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
However, if you save the game, then reload, you will not see it on any bulletin board that had been created in the current system before you saved. onCreateBB will be triggered for any subsequently created bulletin boards, but not for those that already existed. It isn't appropriate to have scripts create new adverts just because a player has loaded; instead, it's the responsibility of the script to tell Pioneer what to save, and to use those saved data to restore all adverts after the game is loaded. The same goes for player missions, and any working data that the script is using.&lt;br /&gt;
&lt;br /&gt;
There are two things we need to do to achieve all of this:&lt;br /&gt;
&lt;br /&gt;
* We need to track everything that the script is doing.&lt;br /&gt;
* We need to be able to pack this away for saving, and bring it back after loading.&lt;br /&gt;
&lt;br /&gt;
==Tracking everything that we are doing==&lt;br /&gt;
&lt;br /&gt;
Any local table that is declared in file scope is visible to the entire script, without affecting any other scripts. Essential data should be kept in such tables.&lt;br /&gt;
&lt;br /&gt;
==Tracking adverts==&lt;br /&gt;
It's important to be able to re-create an advert, so part of the process of making one should be storing that information. Your script will need to make a note of in which station the advert was placed, which flavour was used (if you have flavours), the face data that was used on the form and any unique information that was used, such as specific mission details, reward, etc. The simplest way to keep all of this together is in a table, gathering up the variables by name into keys of the same name. It could look something like this:&lt;br /&gt;
&lt;br /&gt;
 local advert = {&lt;br /&gt;
     station = station,&lt;br /&gt;
     flavour = flavour, -- This will be a table&lt;br /&gt;
     client = client,   -- This will be a table (a character)&lt;br /&gt;
     target = target,&lt;br /&gt;
     destination = destination,&lt;br /&gt;
     reward = reward,&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In the case of the 'greeting', the first example above, we would just have to save the message ('Hello!'), Advert string ('Click here for a greeting') and the station. It's a very simple example but there are real modules that aren't much more complex than that.&lt;br /&gt;
&lt;br /&gt;
Remember, the &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method takes three arguments: The advert text, the &amp;lt;code&amp;gt;onChat&amp;lt;/code&amp;gt; function and the &amp;lt;code&amp;gt;onDelete&amp;lt;/code&amp;gt; function. It also returns a unique number, which lends itself nicely to storing the information away. That same unique number is passed to the onChat function, allowing onChat to check that stored information.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;onDelete()&amp;lt;/code&amp;gt; function (again, we call it that by convention only) also accepts this reference as its argument, and is called whenever an advert is destroyed, whether that destruction be explicit (&amp;lt;code&amp;gt;RemoveAdvert()&amp;lt;/code&amp;gt;) or implicit (the player hyperspaces away).&lt;br /&gt;
&lt;br /&gt;
So, we can track adverts that have been created, and stop tracking any that have gone away. We'll do that using the reference returned by &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; as the key to a file-scoped local table:&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 local ads = {}&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
     form:SetFace(ads[ref].character)&lt;br /&gt;
     form:SetMessage(ads[ref].flavour.title)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onDelete = function (ref)&lt;br /&gt;
     ads[ref] = nil&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     local flavour = flavours[Engine.rand:Integer(1, #flavours)]&lt;br /&gt;
     ref = station:AddAdvert(flavour.title, onChat, onDelete)&lt;br /&gt;
     ads[ref] = {&lt;br /&gt;
         station = station,&lt;br /&gt;
         flavour = flavour,&lt;br /&gt;
         character = Character.new()&lt;br /&gt;
     }&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
==Tracking missions==&lt;br /&gt;
&lt;br /&gt;
This is less of a challenge. Missions created with &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; are stored in the &amp;lt;code&amp;gt;PersistentCharacters.player.missions&amp;lt;/code&amp;gt; table. You also need to register them locally in your script. When &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; is called, it returns a reference to the mission. That reference can be stored in a file-local table that can be iterated through when we need to access a mission. We can wrap these functions up:&lt;br /&gt;
&lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local addMission = function(mission)&lt;br /&gt;
     table.insert(missions,Mission.New(mission))&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local removeMission = function(mref)&lt;br /&gt;
     local ref&lt;br /&gt;
     for i,v in pairs(missions) do&lt;br /&gt;
         if v == mission then&lt;br /&gt;
             ref = i&lt;br /&gt;
             break&lt;br /&gt;
         end&lt;br /&gt;
     end&lt;br /&gt;
     mission:Remove()&lt;br /&gt;
     missions[ref] = nil&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
Now, instead of directly calling &amp;lt;code&amp;gt;Mission.Add()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;mission:Remove()&amp;lt;/code&amp;gt;, we call our local functions, &amp;lt;code&amp;gt;addMission()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;removeMission()&amp;lt;/code&amp;gt;. They will send their argument on to the instance in '''Mission.lua''', and also store or remove the mission ref in the missions table. There is now a record of which missions belong to this script. &amp;lt;code&amp;gt;addMission()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;removeMission()&amp;lt;/code&amp;gt; in the example above is taken from '''SearchRescue.lua'''. Look how &amp;lt;code&amp;gt;removeMission()&amp;lt;/code&amp;gt; is used in the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/SearchRescue/SearchRescue.lua#L241-L252 Search and Rescue module]'''.&lt;br /&gt;
&lt;br /&gt;
==Serializing: Packing away for saving and loading==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;Serializer&amp;lt;/code&amp;gt; object provides one method: &amp;lt;code&amp;gt;Register&amp;lt;/code&amp;gt;. It takes three arguments. The first is a name; a simple string with which to uniquely identify this script. It's probably sensible to re-use the name that was used for the &amp;lt;code&amp;gt;Translate&amp;lt;/code&amp;gt; object's flavour methods. The second and third arguments are both names of functions that each returns a single table. The second argument is your '''serializer''' function and the third is your '''unserializer''' function. By convention, we name these &amp;lt;code&amp;gt;serialize&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;unserialize&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;serialize&amp;lt;/code&amp;gt; must return a table, and this table must contain everything that you need to store to get your script working after a reload. It will be run when the player saves the game. The table can contain any pure-lua types, and SystemPath, Body and SceneGraph.ModelSkin core types, anything else will explode, like a ShipDef/EquipDef or a StarSystem.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;unserialize&amp;lt;/code&amp;gt; accepts a table, and does something with it. It will be run by the serializer after the game is loaded, immediately before the &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; event is triggered. It is passed the data that serialize returned at save time.&lt;br /&gt;
&lt;br /&gt;
The most common way to deal with this is as follows (and this is very cut down):&lt;br /&gt;
&lt;br /&gt;
 local table_stuff_this_script_uses = {}&lt;br /&gt;
 local loaded_data&lt;br /&gt;
 &lt;br /&gt;
 -- The rest of the script goes here!&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart&lt;br /&gt;
     if loaded_data then&lt;br /&gt;
         for k,v in loaded_data.table_stuff_this_script_uses do&lt;br /&gt;
             table_stuff_this_script_uses[k] = v&lt;br /&gt;
         end&lt;br /&gt;
         loaded_data = nil&lt;br /&gt;
     else&lt;br /&gt;
         -- New game; do other stuff here perhaps&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local serialize = function ()&lt;br /&gt;
     return {table_stuff_this_script_uses = table_stuff_this_script_uses}&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local unserialize = function (data)&lt;br /&gt;
     loaded_data = data&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
 Serializer:Register(&amp;quot;TestModule&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
==Complete module==&lt;br /&gt;
&lt;br /&gt;
Now we can finally start to put together complete modules that will save, reload, and behave consistently throughout the game. Many of the modules in Pioneer don't register a mission. As an example of this, see '''BreakdownServicing''', '''DonateToCranks''', and '''CrewContracts'''. In the same spirit, let's complete the first example above, 'Click here for a greeting', and make it survive a reload.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local Event = require 'Event'&lt;br /&gt;
local Serializer = require 'Serializer'&lt;br /&gt;
&lt;br /&gt;
local ads = {}&lt;br /&gt;
&lt;br /&gt;
local onChat = function (form, ref, option)&lt;br /&gt;
    local ad = ads[ref]&lt;br /&gt;
    form:Clear()&lt;br /&gt;
    form:SetMessage(ad.bodytext)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local onDelete = function (ref)&lt;br /&gt;
    ads[ref] = nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- when we enter a system the BBS is created and this function is called&lt;br /&gt;
local onCreateBB = function (station)&lt;br /&gt;
&lt;br /&gt;
    local ad = {&lt;br /&gt;
        headline = 'Click here for a greeting',&lt;br /&gt;
        bodytext = &amp;quot;Hello!&amp;quot;,&lt;br /&gt;
        station  = station&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    -- create one per BBS&lt;br /&gt;
    local ref = station:AddAdvert({&lt;br /&gt;
        description = ad.headline,&lt;br /&gt;
        onChat      = onChat,&lt;br /&gt;
        onDelete    = onDelete}&lt;br /&gt;
    )&lt;br /&gt;
    ads[ref] = ad&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local loaded_data&lt;br /&gt;
&lt;br /&gt;
local onGameStart = function ()&lt;br /&gt;
    ads = {}&lt;br /&gt;
&lt;br /&gt;
    if not loaded_data or not loaded_data.ads then return end&lt;br /&gt;
&lt;br /&gt;
    for k,ad in pairs(loaded_data.ads or {}) do&lt;br /&gt;
        local ref = ad.station:AddAdvert({&lt;br /&gt;
            description = ad.headline,&lt;br /&gt;
            onChat      = onChat,&lt;br /&gt;
            onDelete    = onDelete})&lt;br /&gt;
        ads[ref] = ad&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    loaded_data = nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local serialize = function ()&lt;br /&gt;
    return { ads = ads }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local unserialize = function (data)&lt;br /&gt;
    loaded_data = data&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
Serializer:Register(&amp;quot;message&amp;quot;, serialize, unserialize)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The '''Advice''' module generously gave up parts of its code to complete this script.&lt;br /&gt;
&lt;br /&gt;
Although the example is functional, it has been coded with brevity alone in mind. I would not recommend using this as the basis for a mission without at least completely rewriting &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, and possibly tidying up the temporary loop variable names.&lt;br /&gt;
&lt;br /&gt;
==Don't serialize strings==&lt;br /&gt;
&lt;br /&gt;
When we add the strings literally to the ad as we did in the example above, they will be serialized and saved on game end. Considering this is happening on all active stations, this will waste disk space.&lt;br /&gt;
&lt;br /&gt;
 local ad = {&lt;br /&gt;
     headline = 'Click here for a greeting',&lt;br /&gt;
     bodytext = 'Hello!',&lt;br /&gt;
     station  = station&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
What we want to do instead is save the information to recreate the same strings. If for instance we have a set of flavours that are selected with a random number, then generate that number and save it in a variable. The ad example above can then be rewritten as such. We only need the station and the flavour number to recreate the ad.&lt;br /&gt;
&lt;br /&gt;
 local ad = {&lt;br /&gt;
     station = station,&lt;br /&gt;
     n = n&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The working example in the paragraph above can be updated in this way. When you have the translated strings in a separate module you would start the script by pulling in the strings to the script in an array or set of arrays. In the following example we instead add the strings to an array directly but it shows the general idea.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local Engine = require 'Engine'&lt;br /&gt;
local Event = require 'Event'&lt;br /&gt;
local Game = require 'Game'&lt;br /&gt;
local Rand = require 'Rand'&lt;br /&gt;
local Serializer = require 'Serializer'&lt;br /&gt;
&lt;br /&gt;
local ads = {}&lt;br /&gt;
&lt;br /&gt;
local flavours = {&lt;br /&gt;
    {flavour = 'flavour 1', title = 'title 1', text = 'text 1'},&lt;br /&gt;
    {flavour = 'flavour 2', title = 'title 2', text = 'text 2'},&lt;br /&gt;
    {flavour = 'flavour 3', title = 'title 3', text = 'text 3'}&lt;br /&gt;
}&lt;br /&gt;
local onChat = function (form, ref, option)&lt;br /&gt;
    local ad = ads[ref]&lt;br /&gt;
    form:Clear()&lt;br /&gt;
    form:SetMessage(flavours[ad.n].text)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local onDelete = function (ref)&lt;br /&gt;
    ads[ref] = nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- when we enter a system the BBS is created and this function is called&lt;br /&gt;
local onCreateBB = function (station)&lt;br /&gt;
&lt;br /&gt;
local rand = Rand.New(Game.time)&lt;br /&gt;
local n = rand:Integer(1, #flavours)&lt;br /&gt;
&lt;br /&gt;
local ad = {&lt;br /&gt;
    station  = station,&lt;br /&gt;
    n = n&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- create one per BBS&lt;br /&gt;
local ref = station:AddAdvert({&lt;br /&gt;
        title       = flavours[n].title,&lt;br /&gt;
        description = flavours[n].flavour,&lt;br /&gt;
        onChat      = onChat,&lt;br /&gt;
        onDelete    = onDelete}&lt;br /&gt;
    )&lt;br /&gt;
    ads[ref] = ad&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local loaded_data&lt;br /&gt;
&lt;br /&gt;
local onGameStart = function ()&lt;br /&gt;
    ads = {}&lt;br /&gt;
&lt;br /&gt;
    if not loaded_data or not loaded_data.ads then return end&lt;br /&gt;
&lt;br /&gt;
    for k,ad in pairs(loaded_data.ads or {}) do&lt;br /&gt;
        local ref = ad.station:AddAdvert({&lt;br /&gt;
            title = flavours[ad.n].title,&lt;br /&gt;
            description = flavours[ad.n].flavour,&lt;br /&gt;
            onChat      = onChat,&lt;br /&gt;
            onDelete    = onDelete})&lt;br /&gt;
            ads[ref]    = ad&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    loaded_data = nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local serialize = function ()&lt;br /&gt;
    return { ads = ads }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local unserialize = function (data)&lt;br /&gt;
    loaded_data = data&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
Serializer:Register(&amp;quot;message&amp;quot;, serialize, unserialize)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The codedoc is complete and accurate, and the scripts provided with Pioneer are good examples themselves. The API is extensive, but if you find that there are additional things you would like to be able to do, or information you would like from Pioneer, the dev team are willing to extend the API to accommodate script writers. Simply create a new issue on the [https://github.com/pioneerspacesim/pioneer/issues Github issue tracker].&lt;br /&gt;
&lt;br /&gt;
Help is also always available on the [https://forum.pioneerspacesim.net/ Pioneer dev forum], and many of the dev team can be found on [[IRC]] at all hours.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4755</id>
		<title>Missions and the mission list</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4755"/>
		<updated>2025-11-09T22:34:34Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* The player's mission list */ fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The player's mission list==&lt;br /&gt;
&lt;br /&gt;
Once the player has negotiated with your form, there might well be a mission in play. It could be a delivery, an assassination, a rush to tell somebody not to leave because so-and-so loves them... the possibilities are limited only by your creativity. The player needs a way to keep track of all the missions that they have agreed to undertake. Pioneer provides this through the player's mission screen, which they can access at any time using the F3 button, and looking under the missions tab. The content of this screen is controlled by some methods on the &amp;lt;code&amp;gt;Player&amp;lt;/code&amp;gt; object, which can always be found at &amp;lt;code&amp;gt;Game.player&amp;lt;/code&amp;gt;, and which inherits from &amp;lt;code&amp;gt;Ship&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Body&amp;lt;/code&amp;gt;. Missions are added to the screen using the &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; method. It takes a table of info, and returns an integer reference to that mission, which should be stored so that it can be updated or removed later. Below follows a typical use case from the [https://codedoc.pioneerspacesim.net/#LuaClass:Mission:New codedoc].&lt;br /&gt;
&lt;br /&gt;
Create a new mission and add it to the player’s mission list while retrieving the reference number:&lt;br /&gt;
&lt;br /&gt;
 ref = Mission.New({&lt;br /&gt;
     'type'      = 'Delivery', -- Must be a translatable token!&lt;br /&gt;
     'client'    = Character.New(),&lt;br /&gt;
     'due'       = Game.time + 3*24*60*60,       -- three days&lt;br /&gt;
     'reward'    = 123.45,&lt;br /&gt;
     'location'  = SystemPath:New(0,0,0,0,16),   -- Mars High, Sol&lt;br /&gt;
     'status'    = 'ACTIVE',&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
In practice, it might look more like this:&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 600, -- ten minutes' time&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
We don't recommend using &amp;lt;code&amp;gt;Game.player.frameBody.path&amp;lt;/code&amp;gt; here. We're only using it because it always returns something, whether docked or not. A real mission would probably use a space station here. For this demonstration we've generated 'Taxi' missions that are already a known mission type, registered by '''Taxi.lua''' who wouldn't know about it because the mission scripts remembers it's own missions and has no way of knowing about missions registered by another module. So far no modules use another modules mission type and we only use it here for the sake of demonstration.&lt;br /&gt;
&lt;br /&gt;
This creates a mission visible on the mission screen:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist3.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Registering a mission type==&lt;br /&gt;
Before a module can create missions that are visible in the player's mission list, it needs to register a mission type. It's a painless task.&lt;br /&gt;
&lt;br /&gt;
'''[https://codedoc.pioneerspacesim.net/#LuaClass:Mission:RegisterType LuaClass:Mission:RegisterType]'''&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType(typeid, display, onClick)&lt;br /&gt;
&lt;br /&gt;
Apart from a unique string and a translatable mission name, you have the option to pass a function that takes care of the mission info presented when you press the '''More info...''' button in the missions list. By convention, this function is named '''buildMissionDescription'''. This would typically happen at the very end of the module. Here is what it looks like at the end of [https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L513 '''DeliverPackage.lua''']&lt;br /&gt;
&lt;br /&gt;
 Event.Register(&amp;quot;onGameEnd&amp;quot;, onGameEnd)&lt;br /&gt;
 Event.Register(&amp;quot;onReputationChanged&amp;quot;, onReputationChanged)&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
 &lt;br /&gt;
 Serializer:Register(&amp;quot;DeliverPackage&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The next example is extended with a much scaled-down version of the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L401-https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L434 onShipDocked]''' function from the '''DeliverPackage''' module. Since these missions are recognized by a module in Pioneer already ('Taxi') they will 'probably' be handled at a later stage if we fail to remove them with this test mission. If you generate a new mission you must also handle removing it or the mission will remain in the game forever. There is no automatic logic, and no automatic removal. Your script must keep track of them. We'll wrap the action in a '''Timer''' function so we don't have to deal with the BBS form this time.&lt;br /&gt;
&lt;br /&gt;
 Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 ...&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Start the game and have a look under the '''Missions''' tab. After 2 seconds two missions are registered. One that should be finished in 10 seconds and another with a due time set 10 seconds after the first one. There is a second timer that checks in with the missions to see how it's going after 15 seconds. One should be completed successfully and the other will fail.&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function (ship, station)&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Missions Created:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist1.png]]&lt;br /&gt;
&lt;br /&gt;
1 Mission failed and 1 mission completed:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist2.png]]&lt;br /&gt;
&lt;br /&gt;
More info? If you look to the right on the mission list there is a button named '''More info...''' which will let you see more specific details of your mission. This will need to be specified in the mission script itself so if you press this button in the example above Pioneer will crash. The common name for the function you need to write in your script is '''buildMissionDescription'''. Here is what it looks like in '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L462-L490 DeliverPackage.lua]'''.&lt;br /&gt;
&lt;br /&gt;
==Mission description==&lt;br /&gt;
If you don't include the '''buildMissionDescription''' argument when you register the mission type, the mission list will not include a '''More info...''' button for the mission type. In the example above we used an existing mission type, '''Taxi''', but we didn't do it from within '''Taxi.lua''' so when '''More info...''' is pressed '''buildMissionDescription''' will be called in '''Taxi.lua''' but it will not now anything about the mission so the function will break. Try and comment out the entire '''buildMissionDescription''' in '''Taxi.lua''' and launch Pioneer once more. The Missions will register but now without the '''More info...''' button. To register a mission type without '''buildMissionDescription''' you simply leave it out.&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI)&lt;br /&gt;
&lt;br /&gt;
You can also declare a minimal '''buildMissionDescription''' that returns nil. This is fine. It will include the button which, if pressed, will just exit immediately and not try and open a new window.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI, buildMissionDescription)&lt;br /&gt;
&lt;br /&gt;
The function responsible for interpreting the mission description is '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/modules/info-view/04-missions.lua#L42-L77 drawMissionDescription()]''' in '''/data/pigui/modules/info-view/04-missions.lua'''. It takes a table '''desc''' as it's argument. A minimal working mission description could look something like the example below. We set '''desc.description''' to an empty string and '''desc.details''' to a table with only one element '''false'''. '''desc.client''' is set to '''mission.client'''. This only works as part of an actual mission script where you already have a registered mission with a client. As you can see the '''Mission Details''' title and the '''Go back''' button are generated automatically.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
Let's fixup the earlier example with it's own mission type and a working '''buildMissionDescription'''.&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Test&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Test&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
  &lt;br /&gt;
 Mission.RegisterType('Test', 'Test', buildMissionDescription)&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
[[File:Minimaldescription.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
That's basically it. Here follows a description of the elements you can add to '''buildMissionDescription'''. Try and expand our latest script on you own.&lt;br /&gt;
&lt;br /&gt;
===desc.description===&lt;br /&gt;
This is a string and will typically be based on the conversation from onChat.&lt;br /&gt;
&lt;br /&gt;
 desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===desc.client===&lt;br /&gt;
Takes a character as it's input and usually this means the character passed via the mission argument.&lt;br /&gt;
&lt;br /&gt;
 desc.client = mission.client&lt;br /&gt;
&lt;br /&gt;
===desc.details===&lt;br /&gt;
This is a table that is passed to '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/libs/text-table.lua#L15-L28 textTable.draw()]''' in '''data/pigui/libs/text-table.lua'''.&lt;br /&gt;
&lt;br /&gt;
 desc.details = {&lt;br /&gt;
     {&amp;quot;data here&amp;quot;, &amp;quot;more data&amp;quot;},&lt;br /&gt;
              ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====string====&lt;br /&gt;
A string will be presented in the form of a header with a delimiter underneath.&lt;br /&gt;
 &amp;quot;Just a string&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====table====&lt;br /&gt;
A table that takes strings and data/numbers in a key/value sort of way. Only one post in the table will cause a crash and any number of entries above the first two will be discarded.&lt;br /&gt;
&lt;br /&gt;
   {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
   {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
 --{&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
   {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;} -- Two entries in the table with one being just an empty string works.&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====spacing====&lt;br /&gt;
The key word '''false''' can be used to insert some padding.&lt;br /&gt;
&lt;br /&gt;
 false,&lt;br /&gt;
&lt;br /&gt;
===desc.location===&lt;br /&gt;
Add a button to set the mission location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.location = mission.location&lt;br /&gt;
&lt;br /&gt;
===desc.returnLocation===&lt;br /&gt;
Add a button to set the return location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.returnLocation = mission.domicile&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     desc.details = {&lt;br /&gt;
         &amp;quot;This will be a 'title' with a delimiter underneath&amp;quot;,&lt;br /&gt;
         {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
         {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
         false, -- some space.&lt;br /&gt;
      -- {&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
         {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
     }&lt;br /&gt;
  &lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Moremissiondetails.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Maintaining immersion==&lt;br /&gt;
&lt;br /&gt;
Fictionally, of course, the bulletin board is visible to any and all ships that dock at the space station, not just the player. It is important that bulletin board missions are not all scaled to the capabilities of the player. Delivery missions with unreasonable deadlines should not be ruled out. Neither should cargo missions requiring much more cargo space than the player's ship has, or combat missions for which the player is completely unqualified.&lt;br /&gt;
&lt;br /&gt;
These missions should deal with the player gracefully; either allowing them to fail, and providing consequences, or preventing them from being given the mission.&lt;br /&gt;
&lt;br /&gt;
It's also important, if your script serves many instances of a mission, to periodically clear away bulletin board adverts and place new ones. Not just those with obvious time constraints, but any others; the assumption that the player should make is that perhaps some other character has taken these missions.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=User:Zonkmachine&amp;diff=4754</id>
		<title>User:Zonkmachine</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=User:Zonkmachine&amp;diff=4754"/>
		<updated>2025-11-09T20:57:41Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: dead links&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Links to wiki test pages and other WIP stuff.&lt;br /&gt;
&lt;br /&gt;
Surviving a reload&lt;br /&gt;
*[[Surviving_a_reload_WIP|Surviving a reload WIP]]&lt;br /&gt;
&lt;br /&gt;
Scripting Tutorial TODO:&lt;br /&gt;
*[[Interacting_with_the_player:_BBS_forms|BBS forms]]&lt;br /&gt;
Update BBS images. ''fixed''.&lt;br /&gt;
&lt;br /&gt;
Also mention that the posts are sorted. ''fixed''&lt;br /&gt;
&lt;br /&gt;
Add some info on multiple BBS ads and updating the BBS over time, '''onUpdateBB'''.&lt;br /&gt;
&lt;br /&gt;
Facegen - facemorph:&lt;br /&gt;
*[[Facemorph|Facemorph]]&lt;br /&gt;
&lt;br /&gt;
Facegen - picture cleanup:&lt;br /&gt;
*[[Facegen_picture_cleanup|Facegen - picture cleanup]]&lt;br /&gt;
&lt;br /&gt;
More facegen - hair:&lt;br /&gt;
*[[Facegen_hair|Facegen - more and hair]]&lt;br /&gt;
&lt;br /&gt;
Code example for [[Missions_and_NPC_Interaction|Missions and NPC Interaction]]:&lt;br /&gt;
*[[Missions_and_NPC_Interaction_Code_Example|Missions and NPC Interaction Code Example]]&lt;br /&gt;
&lt;br /&gt;
Snippets for new tutorial:&lt;br /&gt;
*[[Useful_functions|Useful functions]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
'''Sally. At night a stand up commedian in Cydonia. Daytime, selling parts her cousins strip from ships that does not belong to them:'''&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:Passiveaggressive.png]]&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=User:Zonkmachine&amp;diff=4753</id>
		<title>User:Zonkmachine</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=User:Zonkmachine&amp;diff=4753"/>
		<updated>2025-11-09T20:54:27Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Links to wiki test pages and other WIP stuff.&lt;br /&gt;
&lt;br /&gt;
*[[Buildings_and_other_structures|Buildings and stuff]]&lt;br /&gt;
&lt;br /&gt;
Surviving a reload&lt;br /&gt;
*[[Surviving_a_reload_WIP|Surviving a reload WIP]]&lt;br /&gt;
&lt;br /&gt;
Scripting Tutorial TODO:&lt;br /&gt;
*[[Interacting_with_the_player:_BBS_forms|BBS forms]]&lt;br /&gt;
Update BBS images. ''fixed''.&lt;br /&gt;
&lt;br /&gt;
Also mention that the posts are sorted. ''fixed''&lt;br /&gt;
&lt;br /&gt;
Add some info on multiple BBS ads and updating the BBS over time, '''onUpdateBB'''.&lt;br /&gt;
&lt;br /&gt;
Facegen - facemorph:&lt;br /&gt;
*[[Facemorph|Facemorph]]&lt;br /&gt;
&lt;br /&gt;
Facegen - picture cleanup:&lt;br /&gt;
*[[Facegen_picture_cleanup|Facegen - picture cleanup]]&lt;br /&gt;
&lt;br /&gt;
More facegen - hair:&lt;br /&gt;
*[[Facegen_hair|Facegen - more and hair]]&lt;br /&gt;
&lt;br /&gt;
Code example for [[Missions_and_NPC_Interaction|Missions and NPC Interaction]]:&lt;br /&gt;
*[[Missions_and_NPC_Interaction_Code_Example|Missions and NPC Interaction Code Example]]&lt;br /&gt;
&lt;br /&gt;
Snippets for new tutorial:&lt;br /&gt;
*[[Useful_functions|Useful functions]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
'''Sally. At night a stand up commedian in Cydonia. Daytime, selling parts her cousins strip from ships that does not belong to them:'''&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:Passiveaggressive.png]]&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Scripting_and_Mission_Creation&amp;diff=4752</id>
		<title>Scripting and Mission Creation</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Scripting_and_Mission_Creation&amp;diff=4752"/>
		<updated>2025-11-09T20:36:11Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: Fix link&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;See [http://lua-users.org/wiki/TutorialDirectory Lua Tutorial] to learn basics of lua.&lt;br /&gt;
&lt;br /&gt;
*[[Introduction_to_Mission_Scripting|Introduction to Mission Scripting]] &lt;br /&gt;
**[[Interacting_with_the_game:_Event-based_programming|Interacting with the game: Event based programming]] &lt;br /&gt;
**[[Strings_and_translation|Strings and translation]]&lt;br /&gt;
**[[Interacting_with_the_player:_BBS_forms|Interacting with the player: BBS forms]] &lt;br /&gt;
**[[Missions_and_the_mission_list|Missions and the mission list]]&lt;br /&gt;
**[[Missions_and_NPC_Interaction|Missions and NPC interaction]]&lt;br /&gt;
**[[Surviving_a_reload|Surviving a reload]]&lt;br /&gt;
**[[Old_scripting_info|Older pages]]&lt;br /&gt;
*[[GUI introduction]]  &lt;br /&gt;
*[http://codedoc.pioneerspacesim.net Lua API Reference] &lt;br /&gt;
*[[Ship_AI|Ship AI]]&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_player:_BBS_forms&amp;diff=4751</id>
		<title>Interacting with the player: BBS forms</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_player:_BBS_forms&amp;diff=4751"/>
		<updated>2025-11-09T20:17:29Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Adding options to the form */ fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The bulletin board system is currently the only place where real dialogue between a script and the player can take place. Bulletin boards can exist within any &amp;lt;code&amp;gt;SpaceStation&amp;lt;/code&amp;gt; in the current system. They are created in a station the first time that a ship is either spawned, or lands, in that station. They continue to exist until the player leaves the system or quits the game. They are not saved in saved games, although a saved game contains information about which ones did exist.&lt;br /&gt;
&lt;br /&gt;
When a bulletin board is created, the &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; event is triggered, and passes the &amp;lt;code&amp;gt;SpaceStation&amp;lt;/code&amp;gt; body in which that bulletin board was created. There is an exception to this: The event is not triggered after loading a game for those bulletin boards which, having existed at the time of saving, are re-created. The consequences of this will be covered later (see &amp;quot;[[Surviving a reload]]&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
There are two components to any mission's entry on a bulletin board: The advert and the form. The advert is the part that is displayed on the main bulletin board list, along with all the other entries. The form is the part that appears on screen when the player clicks the advert's button.&lt;br /&gt;
&lt;br /&gt;
==The BBS advert==&lt;br /&gt;
&lt;br /&gt;
Adverts are placed onto a BBS by calling the station's &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method, once the bulletin board has been created. Depending on the nature of your script, you might want to always place one advert on every station (as seen with the Breakdowns &amp;amp; Servicing script), or you might want to place an arbitrary number of adverts on a given station (as seen with deliveries, or assassinations).&lt;br /&gt;
&lt;br /&gt;
The opportunities to add an advert are presented by two events. &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; is the obvious one; there is also &amp;lt;code&amp;gt;onUpdateBB&amp;lt;/code&amp;gt;, which is called for all existing bulletin boards in the current system, approximately once every hour or two. The actual interval is not particularly predictable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method takes three arguments. The first is the text that will appear on the advert. The second is the function that will be called when the player clicks the advert. The third is optional, and is a function that can be called when the advert is deleted for any reason.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; returns a reference number, which can subsequently be used to remove the advert using &amp;lt;code&amp;gt;RemoveAdvert()&amp;lt;/code&amp;gt;. It represents the number of times a module has generated an advert on a specific station. If no ads has been removed it will be equal to the number of ads on the station.&lt;br /&gt;
&lt;br /&gt;
It's the job of the scripter to decide how many, if any, adverts to add to a bulletin board when &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; is triggered, and whether to add or remove any when &amp;lt;code&amp;gt;onUpdateBB&amp;lt;/code&amp;gt; is triggered. Tests could include the population of the system, the type of starport or the type of planet. In the future, tests will be able to include the government type of the system.&lt;br /&gt;
&lt;br /&gt;
One important thing to bear in mind is that the script cannot query a bulletin board to find out what adverts already exist. Each script must carefully track each advert that it has created if it is to have any control over how long they remain, and to be able to re-create them after a reload.&lt;br /&gt;
&lt;br /&gt;
Here is an example of adding an advert. The effect of clicking the advert is simply to send a message to the player console with the name of the station and the reference number, which is one in this case as we're only generating one advert. Furthermore, the advert is only added if the station is in space. There is no mission here, it is simply to illustrate the mechanics of adding an advert. Launch Pioneer and choose a new game with start on Barnard's star.&lt;br /&gt;
&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 local Comms = require &amp;quot;Comms&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local ref  -- Variable to save the advert's reference number&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     -- This function can be in any scope that's visible when AddAdvert() is called&lt;br /&gt;
     local sendStationName = function ()&lt;br /&gt;
         Comms.ImportantMessage(station.label .. &amp;quot;\n&amp;quot; .. &amp;quot;Add ref number: &amp;quot; .. ref)&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     if station.type == 'STARPORT_ORBITAL' then&lt;br /&gt;
         ref = station:AddAdvert('Need the name of this station?',sendStationName)&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
This code will create an advert:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS1.png]]&lt;br /&gt;
&lt;br /&gt;
Looking at the image, you will notice that the advert has appeared at the top of the bulletin board and that only one of two fields is filled with text. The BBS ad has two components, the '''title''' on top and the '''description'''. Only the description is present and since the BBS posts are sorted on first the title and secondly on the description, since the title is blank the post will show up on top. The icon used is the default. '''AddAdvert''' has two versions and we used a simpler legacy form.&lt;br /&gt;
&lt;br /&gt;
 -- Legacy form&lt;br /&gt;
 ref = station:AddAdvert(description, onChat, onDelete)&lt;br /&gt;
&lt;br /&gt;
Clicking on the advert causes this to happen:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS2.png]]&lt;br /&gt;
&lt;br /&gt;
Even though the only thing our function did was to send a message to the console, you can see that Pioneer automatically created a form for our advert. The only control is the 'Hang up' button.&lt;br /&gt;
&lt;br /&gt;
Going back to the World View you can see the message on the Comms terminal:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS3.png]]&lt;br /&gt;
&lt;br /&gt;
==More AddAdvert()==&lt;br /&gt;
&lt;br /&gt;
As we saw, the legacy form of '''AddAdvert''' created an add that lacked a title and with a default icon. Lets rewrite the example above in the full form. [https://codedoc.pioneerspacesim.net/index.html#LuaClass:SpaceStation:AddAdvert LuaClass:SpaceStation:AddAdvert].&lt;br /&gt;
&lt;br /&gt;
AddAdvert takes a table as argument.&lt;br /&gt;
 ref = station:AddAdvert({&lt;br /&gt;
     description = description,&lt;br /&gt;
     icon        = icon,&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
     isEnabled   = isEnabled,&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
Let's try the earlier example but simplified a bit to run on all stations.&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 local Comms = require &amp;quot;Comms&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local ref  -- Variable to save the advert's reference number&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     -- This function can be in any scope that's visible when AddAdvert() is called&lt;br /&gt;
     local sendStationName = function ()&lt;br /&gt;
         Comms.ImportantMessage(station.label .. &amp;quot;\n&amp;quot; .. &amp;quot;Add ref number: &amp;quot; .. ref)&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     ref = station:AddAdvert({&lt;br /&gt;
         title = 'STATION NAME',&lt;br /&gt;
         description = 'Need the name of this station?',&lt;br /&gt;
         icon        = 'news',&lt;br /&gt;
         onChat      = sendStationName,&lt;br /&gt;
         onDelete    = onDelete,&lt;br /&gt;
     })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
The ad now looks complete. It will also show up on the BBS sorted on the title.&lt;br /&gt;
&lt;br /&gt;
[[File:BBS5.png]]&lt;br /&gt;
&lt;br /&gt;
==The BBS form==&lt;br /&gt;
&lt;br /&gt;
Once the player has clicked on an advert, they are presented with a form. Each advert has only one form. The content of the form is added by the script, and can be modified at any time. It consists of a title, a face, a message, and one or more clickable options. The 'one' being the &amp;lt;code&amp;gt;Hang up&amp;lt;/code&amp;gt; button that is generated automatically and always is present. The form itself is passed to the function specified in the &amp;lt;code&amp;gt;SpaceStation.AddAdvert()&amp;lt;/code&amp;gt; method. In the example above, that function would be &amp;lt;code&amp;gt;sendStationName()&amp;lt;/code&amp;gt;, which simply ignored any parameters sent to it. This resulted in the form being blank. The form object which is passed to this function has methods for adding the content. &amp;lt;code&amp;gt;SetTitle()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;SetMessage()&amp;lt;/code&amp;gt; each accept a string. &amp;lt;code&amp;gt;SetFace()&amp;lt;/code&amp;gt; takes a table of information which defines the photofit face on the left but in the following example &amp;lt;code&amp;gt;SetFace()&amp;lt;/code&amp;gt; is called without any arguments and the face will therefore be completely randomized.&lt;br /&gt;
&lt;br /&gt;
A minimal example without any clickable options, just the default 'Hang up':&lt;br /&gt;
&lt;br /&gt;
 local Event = import(&amp;quot;Event&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local populateForm = function (form)&lt;br /&gt;
     form:SetTitle('This appears above the face picture')&lt;br /&gt;
     form:SetFace()&lt;br /&gt;
     form:SetMessage([[This is the main message.&lt;br /&gt;
 &lt;br /&gt;
 It is normally a multi-line string.]])&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
      title = 'AD TITLE',&lt;br /&gt;
      description = 'This appears in the advert list',&lt;br /&gt;
      icon        = 'news',&lt;br /&gt;
      onChat      = populateForm,&lt;br /&gt;
      onDelete    = onDelete,&lt;br /&gt;
  })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
As before, an advert was created:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS4.png]]&lt;br /&gt;
&lt;br /&gt;
We have clicked on the ad and can see that &amp;lt;code&amp;gt;populateForm&amp;lt;/code&amp;gt; was called, and it successfully filled the form with content. If you 'hang up' and press the ad again a completely new face is generated. Lets improve on this and make the face persistent for the existence of the advert. Making it reappear in a saved game would take some more work (see &amp;quot;[[Surviving a reload]]&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
 local Character = require &amp;quot;Character&amp;quot;&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local client = {}&lt;br /&gt;
 local message&lt;br /&gt;
 &lt;br /&gt;
 local populateForm = function (form)&lt;br /&gt;
     form:SetTitle('Pizza time!')&lt;br /&gt;
     form:SetFace(client)&lt;br /&gt;
     form:SetMessage(message)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     client = Character.New()&lt;br /&gt;
     message = &amp;quot;I'm &amp;quot; .. client.name .. &amp;quot; and I need some pizza. I'm thinking pepperoni and cheeze. You feelin me?&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
         title = 'PIZZA PARTY!',&lt;br /&gt;
         description = 'Special delivery needed',&lt;br /&gt;
         icon        = 'news',&lt;br /&gt;
         onChat      = populateForm,&lt;br /&gt;
         onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
We introduce a new module ''' '[https://codedoc.pioneerspacesim.net/#LuaClass:Character Character]' ''' which basically is an RPG style character sheet with methods to work with it. It's a tool to generate and work with non-player characters (NPC's). &amp;lt;code&amp;gt;client = Character.New()&amp;lt;/code&amp;gt; sets client to an all new character with basic characteristics, name, title, and looks. A character can be sent directly to the SetFace method and that's what we're after here. We also used the 'clients' name.&lt;br /&gt;
&lt;br /&gt;
==Adding options to the form==&lt;br /&gt;
&lt;br /&gt;
In the example above, we created a function named &amp;lt;code&amp;gt;populateForm()&amp;lt;/code&amp;gt; which was run when the advert button was clicked. That's not the only time it can be run; it is also run whenever options on its form are clicked. To make use of this, it is passed two additional parameters, both of which &amp;lt;code&amp;gt;populateForm()&amp;lt;/code&amp;gt; ignored. The first parameter is the form object, the second is the advert's unique reference and the third is the number of the option that was clicked. Because it handles all chat events, by convention we instead name this function &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, which is how it shall be named from now on.&lt;br /&gt;
&lt;br /&gt;
In the first example that called the function 'sendStationName' we caught the reference number from ''' 'AddAdvert()' ''' in a variable named  ''' 'ref' '''. As you may remember, ''' 'AddAdvert()' ''' takes as one of its arguments a function to execute when the advert is clicked, '''onChat()'''. The function is passed three arguments. The second of these arguments is the reference number so we could have picked it up from within ''' 'sendStationName' '''. The ''' 'ref' ''' number is useful if your script adds several adverts, each of which might have slightly differently worded content.&lt;br /&gt;
&lt;br /&gt;
You've already learned the methods: ''' 'SetTitle()' ''', ''' 'SetMessage()' ''', and  ''' 'SetFace()' '''. Let's add the rest of the form() methods:&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:AddOption() ''': adds clickable options with buttons. It takes two arguments: A string to set the text of the button and the option nr that will be sent to the form. This value is 0 when first called from the BBS by clicking the add. Every option form will have a default 'Hang up' option which returns -1.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:Clear() ''': removes the Message and Options, but preserves the Title and Face.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:Close() ''': Closes the form.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:RemoveAdvertOnClose() ''': Closes the form and removes the ad.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Form options are declared like this:&lt;br /&gt;
&lt;br /&gt;
 form:AddOption('I am option one',1)&lt;br /&gt;
 form:AddOption('I am option two',2)&lt;br /&gt;
&lt;br /&gt;
These options will appear with the specified caption, and will call &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, which will receive the form object, the advert reference and the option number that was specified after the caption in &amp;lt;code&amp;gt;AddOption()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
 &lt;br /&gt;
     -- option 0 is called when the form is first activated from the&lt;br /&gt;
     -- bulletin board&lt;br /&gt;
     if option == 0 then&lt;br /&gt;
 &lt;br /&gt;
         form:SetTitle(&amp;quot;Favourite colour&amp;quot;)&lt;br /&gt;
         form:SetMessage(&amp;quot;What's your favourite colour?&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         form:AddOption(&amp;quot;Red&amp;quot;,      1)&lt;br /&gt;
         form:AddOption(&amp;quot;Green&amp;quot;,    2)&lt;br /&gt;
         form:AddOption(&amp;quot;Blue&amp;quot;,     3)&lt;br /&gt;
 &lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 1 - red&lt;br /&gt;
     if option == 1 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 2 - green&lt;br /&gt;
     if option == 2 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh green, the colour of trees.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 3 - blue&lt;br /&gt;
     if option == 3 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh blue, the colour of the ocean.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- only option left is -1, hang up&lt;br /&gt;
     form:Close()&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
     title = 'COLORS',&lt;br /&gt;
     description = &amp;quot;Let's talk colors!&amp;quot;,&lt;br /&gt;
     icon        = 'news',&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
Here, every time &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt; is called, regardless of the specified option, the form is cleared. The option is checked, and the relevant content is added to the form. Any other functions can be called from here, and this is how the script gets input from the player.&lt;br /&gt;
&lt;br /&gt;
An alternative format might be to present the options in a table:&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
 &lt;br /&gt;
     local options = {&lt;br /&gt;
         [0] = function ()&lt;br /&gt;
             form:SetTitle(&amp;quot;Favourite colour&amp;quot;)&lt;br /&gt;
             form:SetMessage(&amp;quot;What's your favourite colour?&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
             form:AddOption(&amp;quot;Red&amp;quot;,      1)&lt;br /&gt;
             form:AddOption(&amp;quot;Green&amp;quot;,    2)&lt;br /&gt;
             form:AddOption(&amp;quot;Blue&amp;quot;,     3)&lt;br /&gt;
         end,&lt;br /&gt;
         [1] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [2] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh green, the colour of trees.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [3] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh blue, the colour of the ocean.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [-1] = function ()&lt;br /&gt;
             form:Close()&lt;br /&gt;
         end&lt;br /&gt;
         }&lt;br /&gt;
         options[option]()&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
     title = 'COLORS',&lt;br /&gt;
     description = &amp;quot;Let's talk colors!&amp;quot;,&lt;br /&gt;
     icon        = 'news',&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
Try this out:&lt;br /&gt;
&lt;br /&gt;
* To understand what ''' 'form:Clear()' ''' does, comment out the line above containing it and restart Pioneer. As the form is no longer cleared and the 'Hang up' button is included automatically, there now appears to be only one form. The color selection buttons still works though.&lt;br /&gt;
&lt;br /&gt;
* Try adding 'Go back' buttons from the color options:&lt;br /&gt;
 [1] = function ()&lt;br /&gt;
     form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
     form:AddOption(&amp;quot;Go back&amp;quot;, 0)&lt;br /&gt;
 end,&lt;br /&gt;
&lt;br /&gt;
* Try both suggestions above at the same time. ''' 'form:Clear()' ''' is your friend.&lt;br /&gt;
&lt;br /&gt;
* Stick this code into the second onChat example above (or adopt it to the first one) to try out ''' 'form:RemoveAdvertOnClose()' ''':&lt;br /&gt;
 form:AddOption(&amp;quot;Report post&amp;quot;, 4)   -- In option[0]&lt;br /&gt;
 &lt;br /&gt;
           -- ...&lt;br /&gt;
 &lt;br /&gt;
 [4] = function ()&lt;br /&gt;
     form:SetMessage(&amp;quot;The ad has been reported and will not be shown on your BBS. &amp;quot; ..&lt;br /&gt;
                     &amp;quot;Thank you for helping us improve Haber Connect!&amp;quot;)&lt;br /&gt;
     form:RemoveAdvertOnClose()&lt;br /&gt;
 end,&lt;br /&gt;
&lt;br /&gt;
==Where to go from here==&lt;br /&gt;
To see simple complete examples of BBS interaction used in the game, look in &amp;lt;code&amp;gt;data/modules/DonateToCranks/DonateToCranks.lua&amp;lt;/code&amp;gt;, and its accompanying language file &amp;lt;code&amp;gt;data/lang/modules-donatetocranks/en.json&amp;lt;/code&amp;gt;, or the Advice module &amp;lt;code&amp;gt;data/modules/Advice/Advice.lua&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;data/lang/modules-advice/en.json&amp;lt;/code&amp;gt;.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_player:_BBS_forms&amp;diff=4750</id>
		<title>Interacting with the player: BBS forms</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_player:_BBS_forms&amp;diff=4750"/>
		<updated>2025-11-09T20:04:56Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Adding options to the form */ text fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The bulletin board system is currently the only place where real dialogue between a script and the player can take place. Bulletin boards can exist within any &amp;lt;code&amp;gt;SpaceStation&amp;lt;/code&amp;gt; in the current system. They are created in a station the first time that a ship is either spawned, or lands, in that station. They continue to exist until the player leaves the system or quits the game. They are not saved in saved games, although a saved game contains information about which ones did exist.&lt;br /&gt;
&lt;br /&gt;
When a bulletin board is created, the &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; event is triggered, and passes the &amp;lt;code&amp;gt;SpaceStation&amp;lt;/code&amp;gt; body in which that bulletin board was created. There is an exception to this: The event is not triggered after loading a game for those bulletin boards which, having existed at the time of saving, are re-created. The consequences of this will be covered later (see &amp;quot;[[Surviving a reload]]&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
There are two components to any mission's entry on a bulletin board: The advert and the form. The advert is the part that is displayed on the main bulletin board list, along with all the other entries. The form is the part that appears on screen when the player clicks the advert's button.&lt;br /&gt;
&lt;br /&gt;
==The BBS advert==&lt;br /&gt;
&lt;br /&gt;
Adverts are placed onto a BBS by calling the station's &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method, once the bulletin board has been created. Depending on the nature of your script, you might want to always place one advert on every station (as seen with the Breakdowns &amp;amp; Servicing script), or you might want to place an arbitrary number of adverts on a given station (as seen with deliveries, or assassinations).&lt;br /&gt;
&lt;br /&gt;
The opportunities to add an advert are presented by two events. &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; is the obvious one; there is also &amp;lt;code&amp;gt;onUpdateBB&amp;lt;/code&amp;gt;, which is called for all existing bulletin boards in the current system, approximately once every hour or two. The actual interval is not particularly predictable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method takes three arguments. The first is the text that will appear on the advert. The second is the function that will be called when the player clicks the advert. The third is optional, and is a function that can be called when the advert is deleted for any reason.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; returns a reference number, which can subsequently be used to remove the advert using &amp;lt;code&amp;gt;RemoveAdvert()&amp;lt;/code&amp;gt;. It represents the number of times a module has generated an advert on a specific station. If no ads has been removed it will be equal to the number of ads on the station.&lt;br /&gt;
&lt;br /&gt;
It's the job of the scripter to decide how many, if any, adverts to add to a bulletin board when &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; is triggered, and whether to add or remove any when &amp;lt;code&amp;gt;onUpdateBB&amp;lt;/code&amp;gt; is triggered. Tests could include the population of the system, the type of starport or the type of planet. In the future, tests will be able to include the government type of the system.&lt;br /&gt;
&lt;br /&gt;
One important thing to bear in mind is that the script cannot query a bulletin board to find out what adverts already exist. Each script must carefully track each advert that it has created if it is to have any control over how long they remain, and to be able to re-create them after a reload.&lt;br /&gt;
&lt;br /&gt;
Here is an example of adding an advert. The effect of clicking the advert is simply to send a message to the player console with the name of the station and the reference number, which is one in this case as we're only generating one advert. Furthermore, the advert is only added if the station is in space. There is no mission here, it is simply to illustrate the mechanics of adding an advert. Launch Pioneer and choose a new game with start on Barnard's star.&lt;br /&gt;
&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 local Comms = require &amp;quot;Comms&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local ref  -- Variable to save the advert's reference number&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     -- This function can be in any scope that's visible when AddAdvert() is called&lt;br /&gt;
     local sendStationName = function ()&lt;br /&gt;
         Comms.ImportantMessage(station.label .. &amp;quot;\n&amp;quot; .. &amp;quot;Add ref number: &amp;quot; .. ref)&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     if station.type == 'STARPORT_ORBITAL' then&lt;br /&gt;
         ref = station:AddAdvert('Need the name of this station?',sendStationName)&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
This code will create an advert:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS1.png]]&lt;br /&gt;
&lt;br /&gt;
Looking at the image, you will notice that the advert has appeared at the top of the bulletin board and that only one of two fields is filled with text. The BBS ad has two components, the '''title''' on top and the '''description'''. Only the description is present and since the BBS posts are sorted on first the title and secondly on the description, since the title is blank the post will show up on top. The icon used is the default. '''AddAdvert''' has two versions and we used a simpler legacy form.&lt;br /&gt;
&lt;br /&gt;
 -- Legacy form&lt;br /&gt;
 ref = station:AddAdvert(description, onChat, onDelete)&lt;br /&gt;
&lt;br /&gt;
Clicking on the advert causes this to happen:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS2.png]]&lt;br /&gt;
&lt;br /&gt;
Even though the only thing our function did was to send a message to the console, you can see that Pioneer automatically created a form for our advert. The only control is the 'Hang up' button.&lt;br /&gt;
&lt;br /&gt;
Going back to the World View you can see the message on the Comms terminal:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS3.png]]&lt;br /&gt;
&lt;br /&gt;
==More AddAdvert()==&lt;br /&gt;
&lt;br /&gt;
As we saw, the legacy form of '''AddAdvert''' created an add that lacked a title and with a default icon. Lets rewrite the example above in the full form. [https://codedoc.pioneerspacesim.net/index.html#LuaClass:SpaceStation:AddAdvert LuaClass:SpaceStation:AddAdvert].&lt;br /&gt;
&lt;br /&gt;
AddAdvert takes a table as argument.&lt;br /&gt;
 ref = station:AddAdvert({&lt;br /&gt;
     description = description,&lt;br /&gt;
     icon        = icon,&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
     isEnabled   = isEnabled,&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
Let's try the earlier example but simplified a bit to run on all stations.&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 local Comms = require &amp;quot;Comms&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local ref  -- Variable to save the advert's reference number&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     -- This function can be in any scope that's visible when AddAdvert() is called&lt;br /&gt;
     local sendStationName = function ()&lt;br /&gt;
         Comms.ImportantMessage(station.label .. &amp;quot;\n&amp;quot; .. &amp;quot;Add ref number: &amp;quot; .. ref)&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     ref = station:AddAdvert({&lt;br /&gt;
         title = 'STATION NAME',&lt;br /&gt;
         description = 'Need the name of this station?',&lt;br /&gt;
         icon        = 'news',&lt;br /&gt;
         onChat      = sendStationName,&lt;br /&gt;
         onDelete    = onDelete,&lt;br /&gt;
     })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
The ad now looks complete. It will also show up on the BBS sorted on the title.&lt;br /&gt;
&lt;br /&gt;
[[File:BBS5.png]]&lt;br /&gt;
&lt;br /&gt;
==The BBS form==&lt;br /&gt;
&lt;br /&gt;
Once the player has clicked on an advert, they are presented with a form. Each advert has only one form. The content of the form is added by the script, and can be modified at any time. It consists of a title, a face, a message, and one or more clickable options. The 'one' being the &amp;lt;code&amp;gt;Hang up&amp;lt;/code&amp;gt; button that is generated automatically and always is present. The form itself is passed to the function specified in the &amp;lt;code&amp;gt;SpaceStation.AddAdvert()&amp;lt;/code&amp;gt; method. In the example above, that function would be &amp;lt;code&amp;gt;sendStationName()&amp;lt;/code&amp;gt;, which simply ignored any parameters sent to it. This resulted in the form being blank. The form object which is passed to this function has methods for adding the content. &amp;lt;code&amp;gt;SetTitle()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;SetMessage()&amp;lt;/code&amp;gt; each accept a string. &amp;lt;code&amp;gt;SetFace()&amp;lt;/code&amp;gt; takes a table of information which defines the photofit face on the left but in the following example &amp;lt;code&amp;gt;SetFace()&amp;lt;/code&amp;gt; is called without any arguments and the face will therefore be completely randomized.&lt;br /&gt;
&lt;br /&gt;
A minimal example without any clickable options, just the default 'Hang up':&lt;br /&gt;
&lt;br /&gt;
 local Event = import(&amp;quot;Event&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local populateForm = function (form)&lt;br /&gt;
     form:SetTitle('This appears above the face picture')&lt;br /&gt;
     form:SetFace()&lt;br /&gt;
     form:SetMessage([[This is the main message.&lt;br /&gt;
 &lt;br /&gt;
 It is normally a multi-line string.]])&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
      title = 'AD TITLE',&lt;br /&gt;
      description = 'This appears in the advert list',&lt;br /&gt;
      icon        = 'news',&lt;br /&gt;
      onChat      = populateForm,&lt;br /&gt;
      onDelete    = onDelete,&lt;br /&gt;
  })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
As before, an advert was created:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS4.png]]&lt;br /&gt;
&lt;br /&gt;
We have clicked on the ad and can see that &amp;lt;code&amp;gt;populateForm&amp;lt;/code&amp;gt; was called, and it successfully filled the form with content. If you 'hang up' and press the ad again a completely new face is generated. Lets improve on this and make the face persistent for the existence of the advert. Making it reappear in a saved game would take some more work (see &amp;quot;[[Surviving a reload]]&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
 local Character = require &amp;quot;Character&amp;quot;&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local client = {}&lt;br /&gt;
 local message&lt;br /&gt;
 &lt;br /&gt;
 local populateForm = function (form)&lt;br /&gt;
     form:SetTitle('Pizza time!')&lt;br /&gt;
     form:SetFace(client)&lt;br /&gt;
     form:SetMessage(message)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     client = Character.New()&lt;br /&gt;
     message = &amp;quot;I'm &amp;quot; .. client.name .. &amp;quot; and I need some pizza. I'm thinking pepperoni and cheeze. You feelin me?&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
         title = 'PIZZA PARTY!',&lt;br /&gt;
         description = 'Special delivery needed',&lt;br /&gt;
         icon        = 'news',&lt;br /&gt;
         onChat      = populateForm,&lt;br /&gt;
         onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
We introduce a new module ''' '[https://codedoc.pioneerspacesim.net/#LuaClass:Character Character]' ''' which basically is an RPG style character sheet with methods to work with it. It's a tool to generate and work with non-player characters (NPC's). &amp;lt;code&amp;gt;client = Character.New()&amp;lt;/code&amp;gt; sets client to an all new character with basic characteristics, name, title, and looks. A character can be sent directly to the SetFace method and that's what we're after here. We also used the 'clients' name.&lt;br /&gt;
&lt;br /&gt;
==Adding options to the form==&lt;br /&gt;
&lt;br /&gt;
In the example above, we created a function named &amp;lt;code&amp;gt;populateForm()&amp;lt;/code&amp;gt; which was run when the advert button was clicked. That's not the only time it can be run; it is also run whenever options on its form are clicked. To make use of this, it is passed two additional parameters, both of which &amp;lt;code&amp;gt;populateForm()&amp;lt;/code&amp;gt; ignored. The first parameter is the form object, the second is the advert's unique reference and the third is the number of the option that was clicked. Because it handles all chat events, by convention we instead name this function &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, which is how it shall be named from now on.&lt;br /&gt;
&lt;br /&gt;
In the first example that called the function 'sendStationName' we caught the reference number from ''' 'AddAdvert()' ''' in a variable named  ''' 'ref' '''. As you may remember, ''' 'AddAdvert()' ''' takes as one of its arguments a function to execute when the advert is clicked, '''onChat()'''. The function is passed three arguments. The second of these arguments is the reference number so we could have picked it up from within ''' 'sendStationName' '''. The ''' 'ref' ''' number is useful if your script adds several adverts, each of which might have slightly differently worded content.&lt;br /&gt;
&lt;br /&gt;
You've already learned the methods: ''' 'SetTitle()' ''', ''' 'SetMessage()' ''', and  ''' 'SetFace()' '''. Let's add the rest of the form() methods:&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:AddOption() ''': adds clickable options with buttons. It takes two arguments: A string to set the text of the button and the option nr that will be sent to the form. This value is 0 when first called from the BBS by clicking the add. Every option form will have a default 'Hang up' option which returns -1.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:Clear() ''': removes the Message and Options, but preserves the Title and Face.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:Close() ''': Closes the form.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:RemoveAdvertOnClose() ''': Closes the form and removes the ad.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Form options are declared like this:&lt;br /&gt;
&lt;br /&gt;
 form:AddOption('I am option one',1)&lt;br /&gt;
 form:AddOption('I am option two',2)&lt;br /&gt;
&lt;br /&gt;
These options will appear with the specified caption, and will call &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, which will receive the form object, the advert reference and the option number that was specified after the caption in &amp;lt;code&amp;gt;AddOption()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
 &lt;br /&gt;
     -- option 0 is called when the form is first activated from the&lt;br /&gt;
     -- bulletin board&lt;br /&gt;
     if option == 0 then&lt;br /&gt;
 &lt;br /&gt;
         form:SetTitle(&amp;quot;Favourite colour&amp;quot;)&lt;br /&gt;
         form:SetMessage(&amp;quot;What's your favourite colour?&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         form:AddOption(&amp;quot;Red&amp;quot;,      1)&lt;br /&gt;
         form:AddOption(&amp;quot;Green&amp;quot;,    2)&lt;br /&gt;
         form:AddOption(&amp;quot;Blue&amp;quot;,     3)&lt;br /&gt;
 &lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 1 - red&lt;br /&gt;
     if option == 1 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 2 - green&lt;br /&gt;
     if option == 2 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh green, the colour of trees.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 3 - blue&lt;br /&gt;
     if option == 3 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh blue, the colour of the ocean.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- only option left is -1, hang up&lt;br /&gt;
     form:Close()&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
     title = 'COLORS',&lt;br /&gt;
     description = &amp;quot;Let's talk colors!&amp;quot;,&lt;br /&gt;
     icon        = 'news',&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
Here, every time &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt; is called, regardless of the specified option, the form is cleared. The option is checked, and the relevant content is added to the form. Any other functions can be called from here, and this is how the script gets input from the player.&lt;br /&gt;
&lt;br /&gt;
An alternative format might be to present the options in a table:&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
 &lt;br /&gt;
     local options = {&lt;br /&gt;
         [0] = function ()&lt;br /&gt;
             form:SetTitle(&amp;quot;Favourite colour&amp;quot;)&lt;br /&gt;
             form:SetMessage(&amp;quot;What's your favourite colour?&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
             form:AddOption(&amp;quot;Red&amp;quot;,      1)&lt;br /&gt;
             form:AddOption(&amp;quot;Green&amp;quot;,    2)&lt;br /&gt;
             form:AddOption(&amp;quot;Blue&amp;quot;,     3)&lt;br /&gt;
         end,&lt;br /&gt;
         [1] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [2] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh green, the colour of trees.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [3] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh blue, the colour of the ocean.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [-1] = function ()&lt;br /&gt;
             form:Close()&lt;br /&gt;
         end&lt;br /&gt;
         }&lt;br /&gt;
         options[option]()&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
     title = 'COLORS',&lt;br /&gt;
     description = &amp;quot;Let's talk colors!&amp;quot;,&lt;br /&gt;
     icon        = 'news',&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
Try this out:&lt;br /&gt;
&lt;br /&gt;
* To understand what ''' 'form:Clear()' ''' does, comment out the line above containing it and restart Pioneer. As the form is no longer cleared and the 'Hang up' button is included automatically, there now appears to be only one form. The color selection buttons still works though.&lt;br /&gt;
&lt;br /&gt;
* Try adding 'Go back' buttons from the color options:&lt;br /&gt;
 [1] = function ()&lt;br /&gt;
     form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
     form:AddOption(&amp;quot;Go back&amp;quot;, 0)&lt;br /&gt;
 end,&lt;br /&gt;
&lt;br /&gt;
* Try both suggestions above at the same time. ''' 'form:Clear()' ''' is your friend.&lt;br /&gt;
&lt;br /&gt;
* Stick this code into the second onChat example above (or adopt it to the first one) to try out ''' 'form:RemoveAdvertOnClose()' ''':&lt;br /&gt;
 form:AddOption(&amp;quot;Report post&amp;quot;, 4)   -- In option[0]&lt;br /&gt;
 &lt;br /&gt;
           -- ...&lt;br /&gt;
 &lt;br /&gt;
 [4] = function ()&lt;br /&gt;
     form:SetMessage(&amp;quot;The ad has been reported and will not be shown on your BBS. &amp;quot; ..&lt;br /&gt;
                     &amp;quot;Thank you for helping us to improve Haber Connect!&amp;quot;)&lt;br /&gt;
     form:RemoveAdvertOnClose()&lt;br /&gt;
 end,&lt;br /&gt;
&lt;br /&gt;
==Where to go from here==&lt;br /&gt;
To see simple complete examples of BBS interaction used in the game, look in &amp;lt;code&amp;gt;data/modules/DonateToCranks/DonateToCranks.lua&amp;lt;/code&amp;gt;, and its accompanying language file &amp;lt;code&amp;gt;data/lang/modules-donatetocranks/en.json&amp;lt;/code&amp;gt;, or the Advice module &amp;lt;code&amp;gt;data/modules/Advice/Advice.lua&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;data/lang/modules-advice/en.json&amp;lt;/code&amp;gt;.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_player:_BBS_forms&amp;diff=4749</id>
		<title>Interacting with the player: BBS forms</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_player:_BBS_forms&amp;diff=4749"/>
		<updated>2025-11-09T18:04:49Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Adding options to the form */ Superfluous comment&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The bulletin board system is currently the only place where real dialogue between a script and the player can take place. Bulletin boards can exist within any &amp;lt;code&amp;gt;SpaceStation&amp;lt;/code&amp;gt; in the current system. They are created in a station the first time that a ship is either spawned, or lands, in that station. They continue to exist until the player leaves the system or quits the game. They are not saved in saved games, although a saved game contains information about which ones did exist.&lt;br /&gt;
&lt;br /&gt;
When a bulletin board is created, the &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; event is triggered, and passes the &amp;lt;code&amp;gt;SpaceStation&amp;lt;/code&amp;gt; body in which that bulletin board was created. There is an exception to this: The event is not triggered after loading a game for those bulletin boards which, having existed at the time of saving, are re-created. The consequences of this will be covered later (see &amp;quot;[[Surviving a reload]]&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
There are two components to any mission's entry on a bulletin board: The advert and the form. The advert is the part that is displayed on the main bulletin board list, along with all the other entries. The form is the part that appears on screen when the player clicks the advert's button.&lt;br /&gt;
&lt;br /&gt;
==The BBS advert==&lt;br /&gt;
&lt;br /&gt;
Adverts are placed onto a BBS by calling the station's &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method, once the bulletin board has been created. Depending on the nature of your script, you might want to always place one advert on every station (as seen with the Breakdowns &amp;amp; Servicing script), or you might want to place an arbitrary number of adverts on a given station (as seen with deliveries, or assassinations).&lt;br /&gt;
&lt;br /&gt;
The opportunities to add an advert are presented by two events. &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; is the obvious one; there is also &amp;lt;code&amp;gt;onUpdateBB&amp;lt;/code&amp;gt;, which is called for all existing bulletin boards in the current system, approximately once every hour or two. The actual interval is not particularly predictable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method takes three arguments. The first is the text that will appear on the advert. The second is the function that will be called when the player clicks the advert. The third is optional, and is a function that can be called when the advert is deleted for any reason.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; returns a reference number, which can subsequently be used to remove the advert using &amp;lt;code&amp;gt;RemoveAdvert()&amp;lt;/code&amp;gt;. It represents the number of times a module has generated an advert on a specific station. If no ads has been removed it will be equal to the number of ads on the station.&lt;br /&gt;
&lt;br /&gt;
It's the job of the scripter to decide how many, if any, adverts to add to a bulletin board when &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; is triggered, and whether to add or remove any when &amp;lt;code&amp;gt;onUpdateBB&amp;lt;/code&amp;gt; is triggered. Tests could include the population of the system, the type of starport or the type of planet. In the future, tests will be able to include the government type of the system.&lt;br /&gt;
&lt;br /&gt;
One important thing to bear in mind is that the script cannot query a bulletin board to find out what adverts already exist. Each script must carefully track each advert that it has created if it is to have any control over how long they remain, and to be able to re-create them after a reload.&lt;br /&gt;
&lt;br /&gt;
Here is an example of adding an advert. The effect of clicking the advert is simply to send a message to the player console with the name of the station and the reference number, which is one in this case as we're only generating one advert. Furthermore, the advert is only added if the station is in space. There is no mission here, it is simply to illustrate the mechanics of adding an advert. Launch Pioneer and choose a new game with start on Barnard's star.&lt;br /&gt;
&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 local Comms = require &amp;quot;Comms&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local ref  -- Variable to save the advert's reference number&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     -- This function can be in any scope that's visible when AddAdvert() is called&lt;br /&gt;
     local sendStationName = function ()&lt;br /&gt;
         Comms.ImportantMessage(station.label .. &amp;quot;\n&amp;quot; .. &amp;quot;Add ref number: &amp;quot; .. ref)&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     if station.type == 'STARPORT_ORBITAL' then&lt;br /&gt;
         ref = station:AddAdvert('Need the name of this station?',sendStationName)&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
This code will create an advert:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS1.png]]&lt;br /&gt;
&lt;br /&gt;
Looking at the image, you will notice that the advert has appeared at the top of the bulletin board and that only one of two fields is filled with text. The BBS ad has two components, the '''title''' on top and the '''description'''. Only the description is present and since the BBS posts are sorted on first the title and secondly on the description, since the title is blank the post will show up on top. The icon used is the default. '''AddAdvert''' has two versions and we used a simpler legacy form.&lt;br /&gt;
&lt;br /&gt;
 -- Legacy form&lt;br /&gt;
 ref = station:AddAdvert(description, onChat, onDelete)&lt;br /&gt;
&lt;br /&gt;
Clicking on the advert causes this to happen:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS2.png]]&lt;br /&gt;
&lt;br /&gt;
Even though the only thing our function did was to send a message to the console, you can see that Pioneer automatically created a form for our advert. The only control is the 'Hang up' button.&lt;br /&gt;
&lt;br /&gt;
Going back to the World View you can see the message on the Comms terminal:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS3.png]]&lt;br /&gt;
&lt;br /&gt;
==More AddAdvert()==&lt;br /&gt;
&lt;br /&gt;
As we saw, the legacy form of '''AddAdvert''' created an add that lacked a title and with a default icon. Lets rewrite the example above in the full form. [https://codedoc.pioneerspacesim.net/index.html#LuaClass:SpaceStation:AddAdvert LuaClass:SpaceStation:AddAdvert].&lt;br /&gt;
&lt;br /&gt;
AddAdvert takes a table as argument.&lt;br /&gt;
 ref = station:AddAdvert({&lt;br /&gt;
     description = description,&lt;br /&gt;
     icon        = icon,&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
     isEnabled   = isEnabled,&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
Let's try the earlier example but simplified a bit to run on all stations.&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 local Comms = require &amp;quot;Comms&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local ref  -- Variable to save the advert's reference number&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     -- This function can be in any scope that's visible when AddAdvert() is called&lt;br /&gt;
     local sendStationName = function ()&lt;br /&gt;
         Comms.ImportantMessage(station.label .. &amp;quot;\n&amp;quot; .. &amp;quot;Add ref number: &amp;quot; .. ref)&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     ref = station:AddAdvert({&lt;br /&gt;
         title = 'STATION NAME',&lt;br /&gt;
         description = 'Need the name of this station?',&lt;br /&gt;
         icon        = 'news',&lt;br /&gt;
         onChat      = sendStationName,&lt;br /&gt;
         onDelete    = onDelete,&lt;br /&gt;
     })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
The ad now looks complete. It will also show up on the BBS sorted on the title.&lt;br /&gt;
&lt;br /&gt;
[[File:BBS5.png]]&lt;br /&gt;
&lt;br /&gt;
==The BBS form==&lt;br /&gt;
&lt;br /&gt;
Once the player has clicked on an advert, they are presented with a form. Each advert has only one form. The content of the form is added by the script, and can be modified at any time. It consists of a title, a face, a message, and one or more clickable options. The 'one' being the &amp;lt;code&amp;gt;Hang up&amp;lt;/code&amp;gt; button that is generated automatically and always is present. The form itself is passed to the function specified in the &amp;lt;code&amp;gt;SpaceStation.AddAdvert()&amp;lt;/code&amp;gt; method. In the example above, that function would be &amp;lt;code&amp;gt;sendStationName()&amp;lt;/code&amp;gt;, which simply ignored any parameters sent to it. This resulted in the form being blank. The form object which is passed to this function has methods for adding the content. &amp;lt;code&amp;gt;SetTitle()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;SetMessage()&amp;lt;/code&amp;gt; each accept a string. &amp;lt;code&amp;gt;SetFace()&amp;lt;/code&amp;gt; takes a table of information which defines the photofit face on the left but in the following example &amp;lt;code&amp;gt;SetFace()&amp;lt;/code&amp;gt; is called without any arguments and the face will therefore be completely randomized.&lt;br /&gt;
&lt;br /&gt;
A minimal example without any clickable options, just the default 'Hang up':&lt;br /&gt;
&lt;br /&gt;
 local Event = import(&amp;quot;Event&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local populateForm = function (form)&lt;br /&gt;
     form:SetTitle('This appears above the face picture')&lt;br /&gt;
     form:SetFace()&lt;br /&gt;
     form:SetMessage([[This is the main message.&lt;br /&gt;
 &lt;br /&gt;
 It is normally a multi-line string.]])&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
      title = 'AD TITLE',&lt;br /&gt;
      description = 'This appears in the advert list',&lt;br /&gt;
      icon        = 'news',&lt;br /&gt;
      onChat      = populateForm,&lt;br /&gt;
      onDelete    = onDelete,&lt;br /&gt;
  })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
As before, an advert was created:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS4.png]]&lt;br /&gt;
&lt;br /&gt;
We have clicked on the ad and can see that &amp;lt;code&amp;gt;populateForm&amp;lt;/code&amp;gt; was called, and it successfully filled the form with content. If you 'hang up' and press the ad again a completely new face is generated. Lets improve on this and make the face persistent for the existence of the advert. Making it reappear in a saved game would take some more work (see &amp;quot;[[Surviving a reload]]&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
 local Character = require &amp;quot;Character&amp;quot;&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local client = {}&lt;br /&gt;
 local message&lt;br /&gt;
 &lt;br /&gt;
 local populateForm = function (form)&lt;br /&gt;
     form:SetTitle('Pizza time!')&lt;br /&gt;
     form:SetFace(client)&lt;br /&gt;
     form:SetMessage(message)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     client = Character.New()&lt;br /&gt;
     message = &amp;quot;I'm &amp;quot; .. client.name .. &amp;quot; and I need some pizza. I'm thinking pepperoni and cheeze. You feelin me?&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
         title = 'PIZZA PARTY!',&lt;br /&gt;
         description = 'Special delivery needed',&lt;br /&gt;
         icon        = 'news',&lt;br /&gt;
         onChat      = populateForm,&lt;br /&gt;
         onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
We introduce a new module ''' '[https://codedoc.pioneerspacesim.net/#LuaClass:Character Character]' ''' which basically is an RPG style character sheet with methods to work with it. It's a tool to generate and work with non-player characters (NPC's). &amp;lt;code&amp;gt;client = Character.New()&amp;lt;/code&amp;gt; sets client to an all new character with basic characteristics, name, title, and looks. A character can be sent directly to the SetFace method and that's what we're after here. We also used the 'clients' name.&lt;br /&gt;
&lt;br /&gt;
==Adding options to the form==&lt;br /&gt;
&lt;br /&gt;
In the example above, we created a function named &amp;lt;code&amp;gt;populateForm()&amp;lt;/code&amp;gt; which was run when the advert button was clicked. That's not the only time it can be run; it is also run whenever options on its form are clicked. To make use of this, it is passed two additional parameters, both of which &amp;lt;code&amp;gt;populateForm()&amp;lt;/code&amp;gt; ignored. The first parameter is the form object, the second is the advert's unique reference and the third is the number of the option that was clicked. Because it handles all chat events, by convention we instead name this function &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, which is how it shall be named from now on.&lt;br /&gt;
&lt;br /&gt;
In the first example that called the function 'sendStationName' we caught the reference number from ''' 'AddAdvert()' ''' in a variable named  ''' 'ref' '''. As you may remember, ''' 'AddAdvert()' ''' takes as one of its arguments a function to execute when the advert is clicked, '''onChat()'''. The function is passed three arguments. The second of these arguments is the reference number so we could have picked it up from within ''' 'sendStationName' '''. The ''' 'ref' ''' number is useful if your script adds several adverts, each of which might have slightly differently worded content.&lt;br /&gt;
&lt;br /&gt;
You've already learned the methods: ''' 'SetTitle()' ''', ''' 'SetMessage()' ''', and  ''' 'SetFace()' '''. Let's add the rest of the form() methods:&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:AddOption() ''': adds clickable options with buttons. It takes two arguments: A string to set the text of the button and the option nr that will be sent to the form. This value is 0 when first called from the BBS by clicking the add. Every option form will have a default 'Hang up' option which returns -1.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:Clear() ''': removes the Message and Options, but preserves the Title and Face.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:Close() ''': Closes the form.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:RemoveAdvertOnClose() ''': Closes the form and removes the ad.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Form options are declared like this:&lt;br /&gt;
&lt;br /&gt;
 form:AddOption('I am option one',1)&lt;br /&gt;
 form:AddOption('I am option two',2)&lt;br /&gt;
&lt;br /&gt;
These options will appear with the specified caption, and will call &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, which will receive the form object, the advert reference and the option number that was specified after the caption in &amp;lt;code&amp;gt;AddOption()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
 &lt;br /&gt;
     -- option 0 is called when the form is first activated from the&lt;br /&gt;
     -- bulletin board&lt;br /&gt;
     if option == 0 then&lt;br /&gt;
 &lt;br /&gt;
         form:SetTitle(&amp;quot;Favourite colour&amp;quot;)&lt;br /&gt;
         form:SetMessage(&amp;quot;What's your favourite colour?&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         form:AddOption(&amp;quot;Red&amp;quot;,      1)&lt;br /&gt;
         form:AddOption(&amp;quot;Green&amp;quot;,    2)&lt;br /&gt;
         form:AddOption(&amp;quot;Blue&amp;quot;,     3)&lt;br /&gt;
 &lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 1 - red&lt;br /&gt;
     if option == 1 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 2 - green&lt;br /&gt;
     if option == 2 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh green, the colour of trees.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 3 - blue&lt;br /&gt;
     if option == 3 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh blue, the colour of the ocean.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- only option left is -1, hang up&lt;br /&gt;
     form:Close()&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
     title = 'COLORS',&lt;br /&gt;
     description = &amp;quot;Let's talk colors!&amp;quot;,&lt;br /&gt;
     icon        = 'news',&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
Here, every time &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt; is called, regardless of the specified option, the form is cleared. The option is checked, and the relevant content is added to the form. Any other functions can be called from here, and this is how the script gets input from the player.&lt;br /&gt;
&lt;br /&gt;
An alternative format might be this:&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
 &lt;br /&gt;
     local options = {&lt;br /&gt;
         [0] = function ()&lt;br /&gt;
             form:SetTitle(&amp;quot;Favourite colour&amp;quot;)&lt;br /&gt;
             form:SetMessage(&amp;quot;What's your favourite colour?&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
             form:AddOption(&amp;quot;Red&amp;quot;,      1)&lt;br /&gt;
             form:AddOption(&amp;quot;Green&amp;quot;,    2)&lt;br /&gt;
             form:AddOption(&amp;quot;Blue&amp;quot;,     3)&lt;br /&gt;
         end,&lt;br /&gt;
         [1] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [2] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh green, the colour of trees.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [3] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh blue, the colour of the ocean.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [-1] = function ()&lt;br /&gt;
             form:Close()&lt;br /&gt;
         end&lt;br /&gt;
         }&lt;br /&gt;
         options[option]()&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
     title = 'COLORS',&lt;br /&gt;
     description = &amp;quot;Let's talk colors!&amp;quot;,&lt;br /&gt;
     icon        = 'news',&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
Try this out:&lt;br /&gt;
&lt;br /&gt;
* To understand what ''' 'form:Clear()' ''' does, comment out the line above containing it and restart Pioneer. As the form is no longer cleared and the 'Hang up' button is included automatically, there now appears to be only one form. The color selection buttons still works though.&lt;br /&gt;
&lt;br /&gt;
* Try adding 'Go back' buttons from the color options:&lt;br /&gt;
 [1] = function ()&lt;br /&gt;
     form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
     form:AddOption(&amp;quot;Go back&amp;quot;, 0)&lt;br /&gt;
 end,&lt;br /&gt;
&lt;br /&gt;
* Try both suggestions above at the same time. ''' 'form:Clear()' ''' is your friend.&lt;br /&gt;
&lt;br /&gt;
* Stick this code into the second onChat example above (or adopt it to the first one) to try out ''' 'form:RemoveAdvertOnClose()' ''':&lt;br /&gt;
 form:AddOption(&amp;quot;Report post&amp;quot;, 4)   -- In option[0]&lt;br /&gt;
 &lt;br /&gt;
           -- ...&lt;br /&gt;
 &lt;br /&gt;
 [4] = function ()&lt;br /&gt;
     form:SetMessage(&amp;quot;The ad has been reported and will not be shown on your BBS. &amp;quot; ..&lt;br /&gt;
                     &amp;quot;Thank you for helping us to improve Haber Connect!&amp;quot;)&lt;br /&gt;
     form:RemoveAdvertOnClose()&lt;br /&gt;
 end,&lt;br /&gt;
&lt;br /&gt;
==Where to go from here==&lt;br /&gt;
To see simple complete examples of BBS interaction used in the game, look in &amp;lt;code&amp;gt;data/modules/DonateToCranks/DonateToCranks.lua&amp;lt;/code&amp;gt;, and its accompanying language file &amp;lt;code&amp;gt;data/lang/modules-donatetocranks/en.json&amp;lt;/code&amp;gt;, or the Advice module &amp;lt;code&amp;gt;data/modules/Advice/Advice.lua&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;data/lang/modules-advice/en.json&amp;lt;/code&amp;gt;.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_player:_BBS_forms&amp;diff=4748</id>
		<title>Interacting with the player: BBS forms</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_player:_BBS_forms&amp;diff=4748"/>
		<updated>2025-11-09T17:34:50Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Adding options to the form */ remove ref to legacy code&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The bulletin board system is currently the only place where real dialogue between a script and the player can take place. Bulletin boards can exist within any &amp;lt;code&amp;gt;SpaceStation&amp;lt;/code&amp;gt; in the current system. They are created in a station the first time that a ship is either spawned, or lands, in that station. They continue to exist until the player leaves the system or quits the game. They are not saved in saved games, although a saved game contains information about which ones did exist.&lt;br /&gt;
&lt;br /&gt;
When a bulletin board is created, the &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; event is triggered, and passes the &amp;lt;code&amp;gt;SpaceStation&amp;lt;/code&amp;gt; body in which that bulletin board was created. There is an exception to this: The event is not triggered after loading a game for those bulletin boards which, having existed at the time of saving, are re-created. The consequences of this will be covered later (see &amp;quot;[[Surviving a reload]]&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
There are two components to any mission's entry on a bulletin board: The advert and the form. The advert is the part that is displayed on the main bulletin board list, along with all the other entries. The form is the part that appears on screen when the player clicks the advert's button.&lt;br /&gt;
&lt;br /&gt;
==The BBS advert==&lt;br /&gt;
&lt;br /&gt;
Adverts are placed onto a BBS by calling the station's &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method, once the bulletin board has been created. Depending on the nature of your script, you might want to always place one advert on every station (as seen with the Breakdowns &amp;amp; Servicing script), or you might want to place an arbitrary number of adverts on a given station (as seen with deliveries, or assassinations).&lt;br /&gt;
&lt;br /&gt;
The opportunities to add an advert are presented by two events. &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; is the obvious one; there is also &amp;lt;code&amp;gt;onUpdateBB&amp;lt;/code&amp;gt;, which is called for all existing bulletin boards in the current system, approximately once every hour or two. The actual interval is not particularly predictable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method takes three arguments. The first is the text that will appear on the advert. The second is the function that will be called when the player clicks the advert. The third is optional, and is a function that can be called when the advert is deleted for any reason.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; returns a reference number, which can subsequently be used to remove the advert using &amp;lt;code&amp;gt;RemoveAdvert()&amp;lt;/code&amp;gt;. It represents the number of times a module has generated an advert on a specific station. If no ads has been removed it will be equal to the number of ads on the station.&lt;br /&gt;
&lt;br /&gt;
It's the job of the scripter to decide how many, if any, adverts to add to a bulletin board when &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; is triggered, and whether to add or remove any when &amp;lt;code&amp;gt;onUpdateBB&amp;lt;/code&amp;gt; is triggered. Tests could include the population of the system, the type of starport or the type of planet. In the future, tests will be able to include the government type of the system.&lt;br /&gt;
&lt;br /&gt;
One important thing to bear in mind is that the script cannot query a bulletin board to find out what adverts already exist. Each script must carefully track each advert that it has created if it is to have any control over how long they remain, and to be able to re-create them after a reload.&lt;br /&gt;
&lt;br /&gt;
Here is an example of adding an advert. The effect of clicking the advert is simply to send a message to the player console with the name of the station and the reference number, which is one in this case as we're only generating one advert. Furthermore, the advert is only added if the station is in space. There is no mission here, it is simply to illustrate the mechanics of adding an advert. Launch Pioneer and choose a new game with start on Barnard's star.&lt;br /&gt;
&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 local Comms = require &amp;quot;Comms&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local ref  -- Variable to save the advert's reference number&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     -- This function can be in any scope that's visible when AddAdvert() is called&lt;br /&gt;
     local sendStationName = function ()&lt;br /&gt;
         Comms.ImportantMessage(station.label .. &amp;quot;\n&amp;quot; .. &amp;quot;Add ref number: &amp;quot; .. ref)&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     if station.type == 'STARPORT_ORBITAL' then&lt;br /&gt;
         ref = station:AddAdvert('Need the name of this station?',sendStationName)&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
This code will create an advert:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS1.png]]&lt;br /&gt;
&lt;br /&gt;
Looking at the image, you will notice that the advert has appeared at the top of the bulletin board and that only one of two fields is filled with text. The BBS ad has two components, the '''title''' on top and the '''description'''. Only the description is present and since the BBS posts are sorted on first the title and secondly on the description, since the title is blank the post will show up on top. The icon used is the default. '''AddAdvert''' has two versions and we used a simpler legacy form.&lt;br /&gt;
&lt;br /&gt;
 -- Legacy form&lt;br /&gt;
 ref = station:AddAdvert(description, onChat, onDelete)&lt;br /&gt;
&lt;br /&gt;
Clicking on the advert causes this to happen:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS2.png]]&lt;br /&gt;
&lt;br /&gt;
Even though the only thing our function did was to send a message to the console, you can see that Pioneer automatically created a form for our advert. The only control is the 'Hang up' button.&lt;br /&gt;
&lt;br /&gt;
Going back to the World View you can see the message on the Comms terminal:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS3.png]]&lt;br /&gt;
&lt;br /&gt;
==More AddAdvert()==&lt;br /&gt;
&lt;br /&gt;
As we saw, the legacy form of '''AddAdvert''' created an add that lacked a title and with a default icon. Lets rewrite the example above in the full form. [https://codedoc.pioneerspacesim.net/index.html#LuaClass:SpaceStation:AddAdvert LuaClass:SpaceStation:AddAdvert].&lt;br /&gt;
&lt;br /&gt;
AddAdvert takes a table as argument.&lt;br /&gt;
 ref = station:AddAdvert({&lt;br /&gt;
     description = description,&lt;br /&gt;
     icon        = icon,&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
     isEnabled   = isEnabled,&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
Let's try the earlier example but simplified a bit to run on all stations.&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 local Comms = require &amp;quot;Comms&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local ref  -- Variable to save the advert's reference number&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     -- This function can be in any scope that's visible when AddAdvert() is called&lt;br /&gt;
     local sendStationName = function ()&lt;br /&gt;
         Comms.ImportantMessage(station.label .. &amp;quot;\n&amp;quot; .. &amp;quot;Add ref number: &amp;quot; .. ref)&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     ref = station:AddAdvert({&lt;br /&gt;
         title = 'STATION NAME',&lt;br /&gt;
         description = 'Need the name of this station?',&lt;br /&gt;
         icon        = 'news',&lt;br /&gt;
         onChat      = sendStationName,&lt;br /&gt;
         onDelete    = onDelete,&lt;br /&gt;
     })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
The ad now looks complete. It will also show up on the BBS sorted on the title.&lt;br /&gt;
&lt;br /&gt;
[[File:BBS5.png]]&lt;br /&gt;
&lt;br /&gt;
==The BBS form==&lt;br /&gt;
&lt;br /&gt;
Once the player has clicked on an advert, they are presented with a form. Each advert has only one form. The content of the form is added by the script, and can be modified at any time. It consists of a title, a face, a message, and one or more clickable options. The 'one' being the &amp;lt;code&amp;gt;Hang up&amp;lt;/code&amp;gt; button that is generated automatically and always is present. The form itself is passed to the function specified in the &amp;lt;code&amp;gt;SpaceStation.AddAdvert()&amp;lt;/code&amp;gt; method. In the example above, that function would be &amp;lt;code&amp;gt;sendStationName()&amp;lt;/code&amp;gt;, which simply ignored any parameters sent to it. This resulted in the form being blank. The form object which is passed to this function has methods for adding the content. &amp;lt;code&amp;gt;SetTitle()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;SetMessage()&amp;lt;/code&amp;gt; each accept a string. &amp;lt;code&amp;gt;SetFace()&amp;lt;/code&amp;gt; takes a table of information which defines the photofit face on the left but in the following example &amp;lt;code&amp;gt;SetFace()&amp;lt;/code&amp;gt; is called without any arguments and the face will therefore be completely randomized.&lt;br /&gt;
&lt;br /&gt;
A minimal example without any clickable options, just the default 'Hang up':&lt;br /&gt;
&lt;br /&gt;
 local Event = import(&amp;quot;Event&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local populateForm = function (form)&lt;br /&gt;
     form:SetTitle('This appears above the face picture')&lt;br /&gt;
     form:SetFace()&lt;br /&gt;
     form:SetMessage([[This is the main message.&lt;br /&gt;
 &lt;br /&gt;
 It is normally a multi-line string.]])&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
      title = 'AD TITLE',&lt;br /&gt;
      description = 'This appears in the advert list',&lt;br /&gt;
      icon        = 'news',&lt;br /&gt;
      onChat      = populateForm,&lt;br /&gt;
      onDelete    = onDelete,&lt;br /&gt;
  })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
As before, an advert was created:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS4.png]]&lt;br /&gt;
&lt;br /&gt;
We have clicked on the ad and can see that &amp;lt;code&amp;gt;populateForm&amp;lt;/code&amp;gt; was called, and it successfully filled the form with content. If you 'hang up' and press the ad again a completely new face is generated. Lets improve on this and make the face persistent for the existence of the advert. Making it reappear in a saved game would take some more work (see &amp;quot;[[Surviving a reload]]&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
 local Character = require &amp;quot;Character&amp;quot;&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local client = {}&lt;br /&gt;
 local message&lt;br /&gt;
 &lt;br /&gt;
 local populateForm = function (form)&lt;br /&gt;
     form:SetTitle('Pizza time!')&lt;br /&gt;
     form:SetFace(client)&lt;br /&gt;
     form:SetMessage(message)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     client = Character.New()&lt;br /&gt;
     message = &amp;quot;I'm &amp;quot; .. client.name .. &amp;quot; and I need some pizza. I'm thinking pepperoni and cheeze. You feelin me?&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
         title = 'PIZZA PARTY!',&lt;br /&gt;
         description = 'Special delivery needed',&lt;br /&gt;
         icon        = 'news',&lt;br /&gt;
         onChat      = populateForm,&lt;br /&gt;
         onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
We introduce a new module ''' '[https://codedoc.pioneerspacesim.net/#LuaClass:Character Character]' ''' which basically is an RPG style character sheet with methods to work with it. It's a tool to generate and work with non-player characters (NPC's). &amp;lt;code&amp;gt;client = Character.New()&amp;lt;/code&amp;gt; sets client to an all new character with basic characteristics, name, title, and looks. A character can be sent directly to the SetFace method and that's what we're after here. We also used the 'clients' name.&lt;br /&gt;
&lt;br /&gt;
==Adding options to the form==&lt;br /&gt;
&lt;br /&gt;
In the example above, we created a function named &amp;lt;code&amp;gt;populateForm()&amp;lt;/code&amp;gt; which was run when the advert button was clicked. That's not the only time it can be run; it is also run whenever options on its form are clicked. To make use of this, it is passed two additional parameters, both of which &amp;lt;code&amp;gt;populateForm()&amp;lt;/code&amp;gt; ignored. The first parameter is the form object, the second is the advert's unique reference and the third is the number of the option that was clicked. Because it handles all chat events, by convention we instead name this function &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, which is how it shall be named from now on.&lt;br /&gt;
&lt;br /&gt;
In the first example that called the function 'sendStationName' we caught the reference number from ''' 'AddAdvert()' ''' in a variable named  ''' 'ref' '''. As you may remember, ''' 'AddAdvert()' ''' takes as one of its arguments a function to execute when the advert is clicked, '''onChat()'''. The function is passed three arguments. The second of these arguments is the reference number so we could have picked it up from within ''' 'sendStationName' '''. The ''' 'ref' ''' number is useful if your script adds several adverts, each of which might have slightly differently worded content.&lt;br /&gt;
&lt;br /&gt;
You've already learned the methods: ''' 'SetTitle()' ''', ''' 'SetMessage()' ''', and  ''' 'SetFace()' '''. Let's add the rest of the form() methods:&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:AddOption() ''': adds clickable options with buttons. It takes two arguments: A string to set the text of the button and the option nr that will be sent to the form. This value is 0 when first called from the BBS by clicking the add. Every option form will have a default 'Hang up' option which returns -1.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:Clear() ''': removes the Message and Options, but preserves the Title and Face.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:Close() ''': Closes the form.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:RemoveAdvertOnClose() ''': Closes the form and removes the ad.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Form options are declared like this:&lt;br /&gt;
&lt;br /&gt;
 form:AddOption('I am option one',1)&lt;br /&gt;
 form:AddOption('I am option two',2)&lt;br /&gt;
&lt;br /&gt;
These options will appear with the specified caption, and will call &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, which will receive the form object, the advert reference and the option number that was specified after the caption in &amp;lt;code&amp;gt;AddOption()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The onChat function below is adapted from an earlier example from the codedoc:&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
 &lt;br /&gt;
     -- option 0 is called when the form is first activated from the&lt;br /&gt;
     -- bulletin board&lt;br /&gt;
     if option == 0 then&lt;br /&gt;
 &lt;br /&gt;
         form:SetTitle(&amp;quot;Favourite colour&amp;quot;)&lt;br /&gt;
         form:SetMessage(&amp;quot;What's your favourite colour?&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         form:AddOption(&amp;quot;Red&amp;quot;,      1)&lt;br /&gt;
         form:AddOption(&amp;quot;Green&amp;quot;,    2)&lt;br /&gt;
         form:AddOption(&amp;quot;Blue&amp;quot;,     3)&lt;br /&gt;
 &lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 1 - red&lt;br /&gt;
     if option == 1 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 2 - green&lt;br /&gt;
     if option == 2 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh green, the colour of trees.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 3 - blue&lt;br /&gt;
     if option == 3 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh blue, the colour of the ocean.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- only option left is -1, hang up&lt;br /&gt;
     form:Close()&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
     title = 'COLORS',&lt;br /&gt;
     description = &amp;quot;Let's talk colors!&amp;quot;,&lt;br /&gt;
     icon        = 'news',&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
Here, every time &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt; is called, regardless of the specified option, the form is cleared. The option is checked, and the relevant content is added to the form. Any other functions can be called from here, and this is how the script gets input from the player.&lt;br /&gt;
&lt;br /&gt;
An alternative format might be this:&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
 &lt;br /&gt;
     local options = {&lt;br /&gt;
         [0] = function ()&lt;br /&gt;
             form:SetTitle(&amp;quot;Favourite colour&amp;quot;)&lt;br /&gt;
             form:SetMessage(&amp;quot;What's your favourite colour?&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
             form:AddOption(&amp;quot;Red&amp;quot;,      1)&lt;br /&gt;
             form:AddOption(&amp;quot;Green&amp;quot;,    2)&lt;br /&gt;
             form:AddOption(&amp;quot;Blue&amp;quot;,     3)&lt;br /&gt;
         end,&lt;br /&gt;
         [1] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [2] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh green, the colour of trees.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [3] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh blue, the colour of the ocean.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [-1] = function ()&lt;br /&gt;
             form:Close()&lt;br /&gt;
         end&lt;br /&gt;
         }&lt;br /&gt;
         options[option]()&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
     title = 'COLORS',&lt;br /&gt;
     description = &amp;quot;Let's talk colors!&amp;quot;,&lt;br /&gt;
     icon        = 'news',&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
Try this out:&lt;br /&gt;
&lt;br /&gt;
* To understand what ''' 'form:Clear()' ''' does, comment out the line above containing it and restart Pioneer. As the form is no longer cleared and the 'Hang up' button is included automatically, there now appears to be only one form. The color selection buttons still works though.&lt;br /&gt;
&lt;br /&gt;
* Try adding 'Go back' buttons from the color options:&lt;br /&gt;
 [1] = function ()&lt;br /&gt;
     form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
     form:AddOption(&amp;quot;Go back&amp;quot;, 0)&lt;br /&gt;
 end,&lt;br /&gt;
&lt;br /&gt;
* Try both suggestions above at the same time. ''' 'form:Clear()' ''' is your friend.&lt;br /&gt;
&lt;br /&gt;
* Stick this code into the second onChat example above (or adopt it to the first one) to try out ''' 'form:RemoveAdvertOnClose()' ''':&lt;br /&gt;
 form:AddOption(&amp;quot;Report post&amp;quot;, 4)   -- In option[0]&lt;br /&gt;
 &lt;br /&gt;
           -- ...&lt;br /&gt;
 &lt;br /&gt;
 [4] = function ()&lt;br /&gt;
     form:SetMessage(&amp;quot;The ad has been reported and will not be shown on your BBS. &amp;quot; ..&lt;br /&gt;
                     &amp;quot;Thank you for helping us to improve Haber Connect!&amp;quot;)&lt;br /&gt;
     form:RemoveAdvertOnClose()&lt;br /&gt;
 end,&lt;br /&gt;
&lt;br /&gt;
==Where to go from here==&lt;br /&gt;
To see simple complete examples of BBS interaction used in the game, look in &amp;lt;code&amp;gt;data/modules/DonateToCranks/DonateToCranks.lua&amp;lt;/code&amp;gt;, and its accompanying language file &amp;lt;code&amp;gt;data/lang/modules-donatetocranks/en.json&amp;lt;/code&amp;gt;, or the Advice module &amp;lt;code&amp;gt;data/modules/Advice/Advice.lua&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;data/lang/modules-advice/en.json&amp;lt;/code&amp;gt;.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_player:_BBS_forms&amp;diff=4747</id>
		<title>Interacting with the player: BBS forms</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_player:_BBS_forms&amp;diff=4747"/>
		<updated>2025-11-08T18:37:16Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* The BBS advert */ fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The bulletin board system is currently the only place where real dialogue between a script and the player can take place. Bulletin boards can exist within any &amp;lt;code&amp;gt;SpaceStation&amp;lt;/code&amp;gt; in the current system. They are created in a station the first time that a ship is either spawned, or lands, in that station. They continue to exist until the player leaves the system or quits the game. They are not saved in saved games, although a saved game contains information about which ones did exist.&lt;br /&gt;
&lt;br /&gt;
When a bulletin board is created, the &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; event is triggered, and passes the &amp;lt;code&amp;gt;SpaceStation&amp;lt;/code&amp;gt; body in which that bulletin board was created. There is an exception to this: The event is not triggered after loading a game for those bulletin boards which, having existed at the time of saving, are re-created. The consequences of this will be covered later (see &amp;quot;[[Surviving a reload]]&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
There are two components to any mission's entry on a bulletin board: The advert and the form. The advert is the part that is displayed on the main bulletin board list, along with all the other entries. The form is the part that appears on screen when the player clicks the advert's button.&lt;br /&gt;
&lt;br /&gt;
==The BBS advert==&lt;br /&gt;
&lt;br /&gt;
Adverts are placed onto a BBS by calling the station's &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method, once the bulletin board has been created. Depending on the nature of your script, you might want to always place one advert on every station (as seen with the Breakdowns &amp;amp; Servicing script), or you might want to place an arbitrary number of adverts on a given station (as seen with deliveries, or assassinations).&lt;br /&gt;
&lt;br /&gt;
The opportunities to add an advert are presented by two events. &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; is the obvious one; there is also &amp;lt;code&amp;gt;onUpdateBB&amp;lt;/code&amp;gt;, which is called for all existing bulletin boards in the current system, approximately once every hour or two. The actual interval is not particularly predictable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method takes three arguments. The first is the text that will appear on the advert. The second is the function that will be called when the player clicks the advert. The third is optional, and is a function that can be called when the advert is deleted for any reason.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; returns a reference number, which can subsequently be used to remove the advert using &amp;lt;code&amp;gt;RemoveAdvert()&amp;lt;/code&amp;gt;. It represents the number of times a module has generated an advert on a specific station. If no ads has been removed it will be equal to the number of ads on the station.&lt;br /&gt;
&lt;br /&gt;
It's the job of the scripter to decide how many, if any, adverts to add to a bulletin board when &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; is triggered, and whether to add or remove any when &amp;lt;code&amp;gt;onUpdateBB&amp;lt;/code&amp;gt; is triggered. Tests could include the population of the system, the type of starport or the type of planet. In the future, tests will be able to include the government type of the system.&lt;br /&gt;
&lt;br /&gt;
One important thing to bear in mind is that the script cannot query a bulletin board to find out what adverts already exist. Each script must carefully track each advert that it has created if it is to have any control over how long they remain, and to be able to re-create them after a reload.&lt;br /&gt;
&lt;br /&gt;
Here is an example of adding an advert. The effect of clicking the advert is simply to send a message to the player console with the name of the station and the reference number, which is one in this case as we're only generating one advert. Furthermore, the advert is only added if the station is in space. There is no mission here, it is simply to illustrate the mechanics of adding an advert. Launch Pioneer and choose a new game with start on Barnard's star.&lt;br /&gt;
&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 local Comms = require &amp;quot;Comms&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local ref  -- Variable to save the advert's reference number&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     -- This function can be in any scope that's visible when AddAdvert() is called&lt;br /&gt;
     local sendStationName = function ()&lt;br /&gt;
         Comms.ImportantMessage(station.label .. &amp;quot;\n&amp;quot; .. &amp;quot;Add ref number: &amp;quot; .. ref)&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     if station.type == 'STARPORT_ORBITAL' then&lt;br /&gt;
         ref = station:AddAdvert('Need the name of this station?',sendStationName)&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
This code will create an advert:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS1.png]]&lt;br /&gt;
&lt;br /&gt;
Looking at the image, you will notice that the advert has appeared at the top of the bulletin board and that only one of two fields is filled with text. The BBS ad has two components, the '''title''' on top and the '''description'''. Only the description is present and since the BBS posts are sorted on first the title and secondly on the description, since the title is blank the post will show up on top. The icon used is the default. '''AddAdvert''' has two versions and we used a simpler legacy form.&lt;br /&gt;
&lt;br /&gt;
 -- Legacy form&lt;br /&gt;
 ref = station:AddAdvert(description, onChat, onDelete)&lt;br /&gt;
&lt;br /&gt;
Clicking on the advert causes this to happen:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS2.png]]&lt;br /&gt;
&lt;br /&gt;
Even though the only thing our function did was to send a message to the console, you can see that Pioneer automatically created a form for our advert. The only control is the 'Hang up' button.&lt;br /&gt;
&lt;br /&gt;
Going back to the World View you can see the message on the Comms terminal:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS3.png]]&lt;br /&gt;
&lt;br /&gt;
==More AddAdvert()==&lt;br /&gt;
&lt;br /&gt;
As we saw, the legacy form of '''AddAdvert''' created an add that lacked a title and with a default icon. Lets rewrite the example above in the full form. [https://codedoc.pioneerspacesim.net/index.html#LuaClass:SpaceStation:AddAdvert LuaClass:SpaceStation:AddAdvert].&lt;br /&gt;
&lt;br /&gt;
AddAdvert takes a table as argument.&lt;br /&gt;
 ref = station:AddAdvert({&lt;br /&gt;
     description = description,&lt;br /&gt;
     icon        = icon,&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
     isEnabled   = isEnabled,&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
Let's try the earlier example but simplified a bit to run on all stations.&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 local Comms = require &amp;quot;Comms&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local ref  -- Variable to save the advert's reference number&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     -- This function can be in any scope that's visible when AddAdvert() is called&lt;br /&gt;
     local sendStationName = function ()&lt;br /&gt;
         Comms.ImportantMessage(station.label .. &amp;quot;\n&amp;quot; .. &amp;quot;Add ref number: &amp;quot; .. ref)&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     ref = station:AddAdvert({&lt;br /&gt;
         title = 'STATION NAME',&lt;br /&gt;
         description = 'Need the name of this station?',&lt;br /&gt;
         icon        = 'news',&lt;br /&gt;
         onChat      = sendStationName,&lt;br /&gt;
         onDelete    = onDelete,&lt;br /&gt;
     })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
The ad now looks complete. It will also show up on the BBS sorted on the title.&lt;br /&gt;
&lt;br /&gt;
[[File:BBS5.png]]&lt;br /&gt;
&lt;br /&gt;
==The BBS form==&lt;br /&gt;
&lt;br /&gt;
Once the player has clicked on an advert, they are presented with a form. Each advert has only one form. The content of the form is added by the script, and can be modified at any time. It consists of a title, a face, a message, and one or more clickable options. The 'one' being the &amp;lt;code&amp;gt;Hang up&amp;lt;/code&amp;gt; button that is generated automatically and always is present. The form itself is passed to the function specified in the &amp;lt;code&amp;gt;SpaceStation.AddAdvert()&amp;lt;/code&amp;gt; method. In the example above, that function would be &amp;lt;code&amp;gt;sendStationName()&amp;lt;/code&amp;gt;, which simply ignored any parameters sent to it. This resulted in the form being blank. The form object which is passed to this function has methods for adding the content. &amp;lt;code&amp;gt;SetTitle()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;SetMessage()&amp;lt;/code&amp;gt; each accept a string. &amp;lt;code&amp;gt;SetFace()&amp;lt;/code&amp;gt; takes a table of information which defines the photofit face on the left but in the following example &amp;lt;code&amp;gt;SetFace()&amp;lt;/code&amp;gt; is called without any arguments and the face will therefore be completely randomized.&lt;br /&gt;
&lt;br /&gt;
A minimal example without any clickable options, just the default 'Hang up':&lt;br /&gt;
&lt;br /&gt;
 local Event = import(&amp;quot;Event&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local populateForm = function (form)&lt;br /&gt;
     form:SetTitle('This appears above the face picture')&lt;br /&gt;
     form:SetFace()&lt;br /&gt;
     form:SetMessage([[This is the main message.&lt;br /&gt;
 &lt;br /&gt;
 It is normally a multi-line string.]])&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
      title = 'AD TITLE',&lt;br /&gt;
      description = 'This appears in the advert list',&lt;br /&gt;
      icon        = 'news',&lt;br /&gt;
      onChat      = populateForm,&lt;br /&gt;
      onDelete    = onDelete,&lt;br /&gt;
  })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
As before, an advert was created:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS4.png]]&lt;br /&gt;
&lt;br /&gt;
We have clicked on the ad and can see that &amp;lt;code&amp;gt;populateForm&amp;lt;/code&amp;gt; was called, and it successfully filled the form with content. If you 'hang up' and press the ad again a completely new face is generated. Lets improve on this and make the face persistent for the existence of the advert. Making it reappear in a saved game would take some more work (see &amp;quot;[[Surviving a reload]]&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
 local Character = require &amp;quot;Character&amp;quot;&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local client = {}&lt;br /&gt;
 local message&lt;br /&gt;
 &lt;br /&gt;
 local populateForm = function (form)&lt;br /&gt;
     form:SetTitle('Pizza time!')&lt;br /&gt;
     form:SetFace(client)&lt;br /&gt;
     form:SetMessage(message)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     client = Character.New()&lt;br /&gt;
     message = &amp;quot;I'm &amp;quot; .. client.name .. &amp;quot; and I need some pizza. I'm thinking pepperoni and cheeze. You feelin me?&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
         title = 'PIZZA PARTY!',&lt;br /&gt;
         description = 'Special delivery needed',&lt;br /&gt;
         icon        = 'news',&lt;br /&gt;
         onChat      = populateForm,&lt;br /&gt;
         onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
We introduce a new module ''' '[https://codedoc.pioneerspacesim.net/#LuaClass:Character Character]' ''' which basically is an RPG style character sheet with methods to work with it. It's a tool to generate and work with non-player characters (NPC's). &amp;lt;code&amp;gt;client = Character.New()&amp;lt;/code&amp;gt; sets client to an all new character with basic characteristics, name, title, and looks. A character can be sent directly to the SetFace method and that's what we're after here. We also used the 'clients' name.&lt;br /&gt;
&lt;br /&gt;
==Adding options to the form==&lt;br /&gt;
&lt;br /&gt;
In the example above, we created a function named &amp;lt;code&amp;gt;populateForm()&amp;lt;/code&amp;gt; which was run when the advert button was clicked. That's not the only time it can be run; it is also run whenever options on its form are clicked. To make use of this, it is passed two additional parameters, both of which &amp;lt;code&amp;gt;populateForm()&amp;lt;/code&amp;gt; ignored. The first parameter is the form object, the second is the advert's unique reference and the third is the number of the option that was clicked. Because it handles all chat events, by convention we instead name this function &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, which is how it shall be named from now on.&lt;br /&gt;
&lt;br /&gt;
In the first example that called the function 'sendStationName' we caught the reference number from ''' 'AddAdvert()' ''' in a variable named  ''' 'ref' '''. As you may remember, ''' 'AddAdvert()' ''' takes two arguments; A string and a function. The function is passed three arguments. The second of these arguments is the reference number so we could have picked it up from within ''' 'sendStationName' '''. The ''' 'ref' ''' number is useful if your script adds several adverts, each of which might have slightly differently worded content.&lt;br /&gt;
&lt;br /&gt;
You've already learned the methods: ''' 'SetTitle()' ''', ''' 'SetMessage()' ''', and  ''' 'SetFace()' '''. Let's add the rest of the form() methods:&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:AddOption() ''': adds clickable options with buttons. It takes two arguments: A string to set the text of the button and the option nr that will be sent to the form. This value is 0 when first called from the BBS by clicking the add. Every option form will have a default 'Hang up' option which returns -1.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:Clear() ''': removes the Message and Options, but preserves the Title and Face.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:Close() ''': Closes the form.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:RemoveAdvertOnClose() ''': Closes the form and removes the ad.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Form options are declared like this:&lt;br /&gt;
&lt;br /&gt;
 form:AddOption('I am option one',1)&lt;br /&gt;
 form:AddOption('I am option two',2)&lt;br /&gt;
&lt;br /&gt;
These options will appear with the specified caption, and will call &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, which will receive the form object, the advert reference and the option number that was specified after the caption in &amp;lt;code&amp;gt;AddOption()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The onChat function below is adapted from an earlier example from the codedoc:&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
 &lt;br /&gt;
     -- option 0 is called when the form is first activated from the&lt;br /&gt;
     -- bulletin board&lt;br /&gt;
     if option == 0 then&lt;br /&gt;
 &lt;br /&gt;
         form:SetTitle(&amp;quot;Favourite colour&amp;quot;)&lt;br /&gt;
         form:SetMessage(&amp;quot;What's your favourite colour?&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         form:AddOption(&amp;quot;Red&amp;quot;,      1)&lt;br /&gt;
         form:AddOption(&amp;quot;Green&amp;quot;,    2)&lt;br /&gt;
         form:AddOption(&amp;quot;Blue&amp;quot;,     3)&lt;br /&gt;
 &lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 1 - red&lt;br /&gt;
     if option == 1 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 2 - green&lt;br /&gt;
     if option == 2 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh green, the colour of trees.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 3 - blue&lt;br /&gt;
     if option == 3 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh blue, the colour of the ocean.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- only option left is -1, hang up&lt;br /&gt;
     form:Close()&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
     title = 'COLORS',&lt;br /&gt;
     description = &amp;quot;Let's talk colors!&amp;quot;,&lt;br /&gt;
     icon        = 'news',&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
Here, every time &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt; is called, regardless of the specified option, the form is cleared. The option is checked, and the relevant content is added to the form. Any other functions can be called from here, and this is how the script gets input from the player.&lt;br /&gt;
&lt;br /&gt;
An alternative format might be this:&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
 &lt;br /&gt;
     local options = {&lt;br /&gt;
         [0] = function ()&lt;br /&gt;
             form:SetTitle(&amp;quot;Favourite colour&amp;quot;)&lt;br /&gt;
             form:SetMessage(&amp;quot;What's your favourite colour?&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
             form:AddOption(&amp;quot;Red&amp;quot;,      1)&lt;br /&gt;
             form:AddOption(&amp;quot;Green&amp;quot;,    2)&lt;br /&gt;
             form:AddOption(&amp;quot;Blue&amp;quot;,     3)&lt;br /&gt;
         end,&lt;br /&gt;
         [1] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [2] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh green, the colour of trees.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [3] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh blue, the colour of the ocean.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [-1] = function ()&lt;br /&gt;
             form:Close()&lt;br /&gt;
         end&lt;br /&gt;
         }&lt;br /&gt;
         options[option]()&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
     title = 'COLORS',&lt;br /&gt;
     description = &amp;quot;Let's talk colors!&amp;quot;,&lt;br /&gt;
     icon        = 'news',&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
Try this out:&lt;br /&gt;
&lt;br /&gt;
* To understand what ''' 'form:Clear()' ''' does, comment out the line above containing it and restart Pioneer. As the form is no longer cleared and the 'Hang up' button is included automatically, there now appears to be only one form. The color selection buttons still works though.&lt;br /&gt;
&lt;br /&gt;
* Try adding 'Go back' buttons from the color options:&lt;br /&gt;
 [1] = function ()&lt;br /&gt;
     form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
     form:AddOption(&amp;quot;Go back&amp;quot;, 0)&lt;br /&gt;
 end,&lt;br /&gt;
&lt;br /&gt;
* Try both suggestions above at the same time. ''' 'form:Clear()' ''' is your friend.&lt;br /&gt;
&lt;br /&gt;
* Stick this code into the second onChat example above (or adopt it to the first one) to try out ''' 'form:RemoveAdvertOnClose()' ''':&lt;br /&gt;
 form:AddOption(&amp;quot;Report post&amp;quot;, 4)   -- In option[0]&lt;br /&gt;
 &lt;br /&gt;
           -- ...&lt;br /&gt;
 &lt;br /&gt;
 [4] = function ()&lt;br /&gt;
     form:SetMessage(&amp;quot;The ad has been reported and will not be shown on your BBS. &amp;quot; ..&lt;br /&gt;
                     &amp;quot;Thank you for helping us to improve Haber Connect!&amp;quot;)&lt;br /&gt;
     form:RemoveAdvertOnClose()&lt;br /&gt;
 end,&lt;br /&gt;
&lt;br /&gt;
==Where to go from here==&lt;br /&gt;
To see simple complete examples of BBS interaction used in the game, look in &amp;lt;code&amp;gt;data/modules/DonateToCranks/DonateToCranks.lua&amp;lt;/code&amp;gt;, and its accompanying language file &amp;lt;code&amp;gt;data/lang/modules-donatetocranks/en.json&amp;lt;/code&amp;gt;, or the Advice module &amp;lt;code&amp;gt;data/modules/Advice/Advice.lua&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;data/lang/modules-advice/en.json&amp;lt;/code&amp;gt;.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_player:_BBS_forms&amp;diff=4746</id>
		<title>Interacting with the player: BBS forms</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_player:_BBS_forms&amp;diff=4746"/>
		<updated>2025-11-08T17:54:54Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* The BBS form */ Always one button&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The bulletin board system is currently the only place where real dialogue between a script and the player can take place. Bulletin boards can exist within any &amp;lt;code&amp;gt;SpaceStation&amp;lt;/code&amp;gt; in the current system. They are created in a station the first time that a ship is either spawned, or lands, in that station. They continue to exist until the player leaves the system or quits the game. They are not saved in saved games, although a saved game contains information about which ones did exist.&lt;br /&gt;
&lt;br /&gt;
When a bulletin board is created, the &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; event is triggered, and passes the &amp;lt;code&amp;gt;SpaceStation&amp;lt;/code&amp;gt; body in which that bulletin board was created. There is an exception to this: The event is not triggered after loading a game for those bulletin boards which, having existed at the time of saving, are re-created. The consequences of this will be covered later (see &amp;quot;[[Surviving a reload]]&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
There are two components to any mission's entry on a bulletin board: The advert and the form. The advert is the part that is displayed on the main bulletin board list, along with all the other entries. The form is the part that appears on screen when the player clicks the advert's button.&lt;br /&gt;
&lt;br /&gt;
==The BBS advert==&lt;br /&gt;
&lt;br /&gt;
Adverts are placed onto a BBS by calling the station's &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method, once the bulletin board has been created. Depending on the nature of your script, you might want to always place one advert on every station (as seen with the Breakdowns &amp;amp; Servicing script), or you might want to place an arbitrary number of adverts on a given station (as seen with deliveries, or assassinations).&lt;br /&gt;
&lt;br /&gt;
The opportunities to add an advert are presented by two events. &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; is the obvious one; there is also &amp;lt;code&amp;gt;onUpdateBB&amp;lt;/code&amp;gt;, which is called for all existing bulletin boards in the current system, approximately once every hour or two. The actual interval is not particularly predictable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method takes three arguments. The first is the text that will appear on the advert. The second is the function that will be called when the player clicks the advert. The third is optional, and is a function that can be called when the advert is deleted for any reason.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; returns a reference number, which can subsequently be used to remove the advert using &amp;lt;code&amp;gt;RemoveAdvert()&amp;lt;/code&amp;gt;. It represents the number of times a module has generated an advert on a specific station. If no ads has been removed it will be equal to the number of ads on the station.&lt;br /&gt;
&lt;br /&gt;
It's the job of the scripter to decide how many, if any, adverts to add to a bulletin board when &amp;lt;code&amp;gt;onCreateBB&amp;lt;/code&amp;gt; is triggered, and whether to add or remove any when &amp;lt;code&amp;gt;onUpdateBB&amp;lt;/code&amp;gt; is triggered. Tests could include the population of the system, the type of starport or the type of planet. In the future, tests will be able to include the government type of the system.&lt;br /&gt;
&lt;br /&gt;
One important thing to bear in mind is that the script cannot query a bulletin board to find out what adverts already exist. Each script must carefully track each advert that it has created if it is to have any control over how long they remain, and to be able to re-create them after a reload.&lt;br /&gt;
&lt;br /&gt;
Here is an example of adding an advert. The effect of clicking the advert is simply to send a message to the player console with the name of the station and the reference number, which is one in this case as we're only generating one advert. Furthermore, the advert is only added if the station is in space. There is no mission here, it is simply to illustrate the mechanics of adding an advert. Launch Pioneer and choose a new game with start on Barnard's star.&lt;br /&gt;
&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 local Comms = require &amp;quot;Comms&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local ref  -- Variable to save the advert's reference number&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     -- This function can be in any scope that's visible when AddAdvert() is called&lt;br /&gt;
     local sendStationName = function ()&lt;br /&gt;
         Comms.ImportantMessage(station.label .. &amp;quot;\n&amp;quot; .. &amp;quot;Add ref number: &amp;quot; .. ref)&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     if station.type == 'STARPORT_ORBITAL' then&lt;br /&gt;
         ref = station:AddAdvert('Need the name of this station?',sendStationName)&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
This code will create an advert:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS1.png]]&lt;br /&gt;
&lt;br /&gt;
Looking at the image, you will notice that the advert has appeared at the top of the bulletin board and that only one of two fields is filled with text. The BBS ad has two components, the '''title''' on top and the '''description'''. Only the description is present and since the BBS posts are sorted on first the title and secondly on the description, since the title is blank the post will show up on top. The icon used is the default. '''AddAdvert''' has two versions and we used a simpler legacy form. See &lt;br /&gt;
&lt;br /&gt;
 -- Legacy form&lt;br /&gt;
 ref = station:AddAdvert(description, onChat, onDelete)&lt;br /&gt;
&lt;br /&gt;
Clicking on the advert causes this to happen:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS2.png]]&lt;br /&gt;
&lt;br /&gt;
Even though the only thing our function did was to send a message to the console, you can see that Pioneer automatically created a form for our advert. The only control is the 'Hang up' button.&lt;br /&gt;
&lt;br /&gt;
Going back to the World View you can see the message on the Comms terminal:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS3.png]]&lt;br /&gt;
&lt;br /&gt;
==More AddAdvert()==&lt;br /&gt;
&lt;br /&gt;
As we saw, the legacy form of '''AddAdvert''' created an add that lacked a title and with a default icon. Lets rewrite the example above in the full form. [https://codedoc.pioneerspacesim.net/index.html#LuaClass:SpaceStation:AddAdvert LuaClass:SpaceStation:AddAdvert].&lt;br /&gt;
&lt;br /&gt;
AddAdvert takes a table as argument.&lt;br /&gt;
 ref = station:AddAdvert({&lt;br /&gt;
     description = description,&lt;br /&gt;
     icon        = icon,&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
     isEnabled   = isEnabled,&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
Let's try the earlier example but simplified a bit to run on all stations.&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 local Comms = require &amp;quot;Comms&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local ref  -- Variable to save the advert's reference number&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     -- This function can be in any scope that's visible when AddAdvert() is called&lt;br /&gt;
     local sendStationName = function ()&lt;br /&gt;
         Comms.ImportantMessage(station.label .. &amp;quot;\n&amp;quot; .. &amp;quot;Add ref number: &amp;quot; .. ref)&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     ref = station:AddAdvert({&lt;br /&gt;
         title = 'STATION NAME',&lt;br /&gt;
         description = 'Need the name of this station?',&lt;br /&gt;
         icon        = 'news',&lt;br /&gt;
         onChat      = sendStationName,&lt;br /&gt;
         onDelete    = onDelete,&lt;br /&gt;
     })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
The ad now looks complete. It will also show up on the BBS sorted on the title.&lt;br /&gt;
&lt;br /&gt;
[[File:BBS5.png]]&lt;br /&gt;
&lt;br /&gt;
==The BBS form==&lt;br /&gt;
&lt;br /&gt;
Once the player has clicked on an advert, they are presented with a form. Each advert has only one form. The content of the form is added by the script, and can be modified at any time. It consists of a title, a face, a message, and one or more clickable options. The 'one' being the &amp;lt;code&amp;gt;Hang up&amp;lt;/code&amp;gt; button that is generated automatically and always is present. The form itself is passed to the function specified in the &amp;lt;code&amp;gt;SpaceStation.AddAdvert()&amp;lt;/code&amp;gt; method. In the example above, that function would be &amp;lt;code&amp;gt;sendStationName()&amp;lt;/code&amp;gt;, which simply ignored any parameters sent to it. This resulted in the form being blank. The form object which is passed to this function has methods for adding the content. &amp;lt;code&amp;gt;SetTitle()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;SetMessage()&amp;lt;/code&amp;gt; each accept a string. &amp;lt;code&amp;gt;SetFace()&amp;lt;/code&amp;gt; takes a table of information which defines the photofit face on the left but in the following example &amp;lt;code&amp;gt;SetFace()&amp;lt;/code&amp;gt; is called without any arguments and the face will therefore be completely randomized.&lt;br /&gt;
&lt;br /&gt;
A minimal example without any clickable options, just the default 'Hang up':&lt;br /&gt;
&lt;br /&gt;
 local Event = import(&amp;quot;Event&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 local populateForm = function (form)&lt;br /&gt;
     form:SetTitle('This appears above the face picture')&lt;br /&gt;
     form:SetFace()&lt;br /&gt;
     form:SetMessage([[This is the main message.&lt;br /&gt;
 &lt;br /&gt;
 It is normally a multi-line string.]])&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
      title = 'AD TITLE',&lt;br /&gt;
      description = 'This appears in the advert list',&lt;br /&gt;
      icon        = 'news',&lt;br /&gt;
      onChat      = populateForm,&lt;br /&gt;
      onDelete    = onDelete,&lt;br /&gt;
  })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
As before, an advert was created:&lt;br /&gt;
&lt;br /&gt;
[[File:BBS4.png]]&lt;br /&gt;
&lt;br /&gt;
We have clicked on the ad and can see that &amp;lt;code&amp;gt;populateForm&amp;lt;/code&amp;gt; was called, and it successfully filled the form with content. If you 'hang up' and press the ad again a completely new face is generated. Lets improve on this and make the face persistent for the existence of the advert. Making it reappear in a saved game would take some more work (see &amp;quot;[[Surviving a reload]]&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
 local Character = require &amp;quot;Character&amp;quot;&lt;br /&gt;
 local Event = require &amp;quot;Event&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
 local client = {}&lt;br /&gt;
 local message&lt;br /&gt;
 &lt;br /&gt;
 local populateForm = function (form)&lt;br /&gt;
     form:SetTitle('Pizza time!')&lt;br /&gt;
     form:SetFace(client)&lt;br /&gt;
     form:SetMessage(message)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
 &lt;br /&gt;
     client = Character.New()&lt;br /&gt;
     message = &amp;quot;I'm &amp;quot; .. client.name .. &amp;quot; and I need some pizza. I'm thinking pepperoni and cheeze. You feelin me?&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
         title = 'PIZZA PARTY!',&lt;br /&gt;
         description = 'Special delivery needed',&lt;br /&gt;
         icon        = 'news',&lt;br /&gt;
         onChat      = populateForm,&lt;br /&gt;
         onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
We introduce a new module ''' '[https://codedoc.pioneerspacesim.net/#LuaClass:Character Character]' ''' which basically is an RPG style character sheet with methods to work with it. It's a tool to generate and work with non-player characters (NPC's). &amp;lt;code&amp;gt;client = Character.New()&amp;lt;/code&amp;gt; sets client to an all new character with basic characteristics, name, title, and looks. A character can be sent directly to the SetFace method and that's what we're after here. We also used the 'clients' name.&lt;br /&gt;
&lt;br /&gt;
==Adding options to the form==&lt;br /&gt;
&lt;br /&gt;
In the example above, we created a function named &amp;lt;code&amp;gt;populateForm()&amp;lt;/code&amp;gt; which was run when the advert button was clicked. That's not the only time it can be run; it is also run whenever options on its form are clicked. To make use of this, it is passed two additional parameters, both of which &amp;lt;code&amp;gt;populateForm()&amp;lt;/code&amp;gt; ignored. The first parameter is the form object, the second is the advert's unique reference and the third is the number of the option that was clicked. Because it handles all chat events, by convention we instead name this function &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, which is how it shall be named from now on.&lt;br /&gt;
&lt;br /&gt;
In the first example that called the function 'sendStationName' we caught the reference number from ''' 'AddAdvert()' ''' in a variable named  ''' 'ref' '''. As you may remember, ''' 'AddAdvert()' ''' takes two arguments; A string and a function. The function is passed three arguments. The second of these arguments is the reference number so we could have picked it up from within ''' 'sendStationName' '''. The ''' 'ref' ''' number is useful if your script adds several adverts, each of which might have slightly differently worded content.&lt;br /&gt;
&lt;br /&gt;
You've already learned the methods: ''' 'SetTitle()' ''', ''' 'SetMessage()' ''', and  ''' 'SetFace()' '''. Let's add the rest of the form() methods:&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:AddOption() ''': adds clickable options with buttons. It takes two arguments: A string to set the text of the button and the option nr that will be sent to the form. This value is 0 when first called from the BBS by clicking the add. Every option form will have a default 'Hang up' option which returns -1.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:Clear() ''': removes the Message and Options, but preserves the Title and Face.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:Close() ''': Closes the form.&amp;lt;br&amp;gt;&lt;br /&gt;
* ''' form:RemoveAdvertOnClose() ''': Closes the form and removes the ad.&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Form options are declared like this:&lt;br /&gt;
&lt;br /&gt;
 form:AddOption('I am option one',1)&lt;br /&gt;
 form:AddOption('I am option two',2)&lt;br /&gt;
&lt;br /&gt;
These options will appear with the specified caption, and will call &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, which will receive the form object, the advert reference and the option number that was specified after the caption in &amp;lt;code&amp;gt;AddOption()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The onChat function below is adapted from an earlier example from the codedoc:&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
 &lt;br /&gt;
     -- option 0 is called when the form is first activated from the&lt;br /&gt;
     -- bulletin board&lt;br /&gt;
     if option == 0 then&lt;br /&gt;
 &lt;br /&gt;
         form:SetTitle(&amp;quot;Favourite colour&amp;quot;)&lt;br /&gt;
         form:SetMessage(&amp;quot;What's your favourite colour?&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
         form:AddOption(&amp;quot;Red&amp;quot;,      1)&lt;br /&gt;
         form:AddOption(&amp;quot;Green&amp;quot;,    2)&lt;br /&gt;
         form:AddOption(&amp;quot;Blue&amp;quot;,     3)&lt;br /&gt;
 &lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 1 - red&lt;br /&gt;
     if option == 1 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 2 - green&lt;br /&gt;
     if option == 2 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh green, the colour of trees.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- option 3 - blue&lt;br /&gt;
     if option == 3 then&lt;br /&gt;
         form:SetMessage(&amp;quot;Ahh blue, the colour of the ocean.&amp;quot;)&lt;br /&gt;
         return&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- only option left is -1, hang up&lt;br /&gt;
     form:Close()&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
     title = 'COLORS',&lt;br /&gt;
     description = &amp;quot;Let's talk colors!&amp;quot;,&lt;br /&gt;
     icon        = 'news',&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
Here, every time &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt; is called, regardless of the specified option, the form is cleared. The option is checked, and the relevant content is added to the form. Any other functions can be called from here, and this is how the script gets input from the player.&lt;br /&gt;
&lt;br /&gt;
An alternative format might be this:&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
 &lt;br /&gt;
     local options = {&lt;br /&gt;
         [0] = function ()&lt;br /&gt;
             form:SetTitle(&amp;quot;Favourite colour&amp;quot;)&lt;br /&gt;
             form:SetMessage(&amp;quot;What's your favourite colour?&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
             form:AddOption(&amp;quot;Red&amp;quot;,      1)&lt;br /&gt;
             form:AddOption(&amp;quot;Green&amp;quot;,    2)&lt;br /&gt;
             form:AddOption(&amp;quot;Blue&amp;quot;,     3)&lt;br /&gt;
         end,&lt;br /&gt;
         [1] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [2] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh green, the colour of trees.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [3] = function ()&lt;br /&gt;
             form:SetMessage(&amp;quot;Ahh blue, the colour of the ocean.&amp;quot;)&lt;br /&gt;
         end,&lt;br /&gt;
         [-1] = function ()&lt;br /&gt;
             form:Close()&lt;br /&gt;
         end&lt;br /&gt;
         }&lt;br /&gt;
         options[option]()&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert({&lt;br /&gt;
     title = 'COLORS',&lt;br /&gt;
     description = &amp;quot;Let's talk colors!&amp;quot;,&lt;br /&gt;
     icon        = 'news',&lt;br /&gt;
     onChat      = onChat,&lt;br /&gt;
     onDelete    = onDelete,&lt;br /&gt;
 })&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
Try this out:&lt;br /&gt;
&lt;br /&gt;
* To understand what ''' 'form:Clear()' ''' does, comment out the line above containing it and restart Pioneer. As the form is no longer cleared and the 'Hang up' button is included automatically, there now appears to be only one form. The color selection buttons still works though.&lt;br /&gt;
&lt;br /&gt;
* Try adding 'Go back' buttons from the color options:&lt;br /&gt;
 [1] = function ()&lt;br /&gt;
     form:SetMessage(&amp;quot;Ahh red, the colour of raspberries.&amp;quot;)&lt;br /&gt;
     form:AddOption(&amp;quot;Go back&amp;quot;, 0)&lt;br /&gt;
 end,&lt;br /&gt;
&lt;br /&gt;
* Try both suggestions above at the same time. ''' 'form:Clear()' ''' is your friend.&lt;br /&gt;
&lt;br /&gt;
* Stick this code into the second onChat example above (or adopt it to the first one) to try out ''' 'form:RemoveAdvertOnClose()' ''':&lt;br /&gt;
 form:AddOption(&amp;quot;Report post&amp;quot;, 4)   -- In option[0]&lt;br /&gt;
 &lt;br /&gt;
           -- ...&lt;br /&gt;
 &lt;br /&gt;
 [4] = function ()&lt;br /&gt;
     form:SetMessage(&amp;quot;The ad has been reported and will not be shown on your BBS. &amp;quot; ..&lt;br /&gt;
                     &amp;quot;Thank you for helping us to improve Haber Connect!&amp;quot;)&lt;br /&gt;
     form:RemoveAdvertOnClose()&lt;br /&gt;
 end,&lt;br /&gt;
&lt;br /&gt;
==Where to go from here==&lt;br /&gt;
To see simple complete examples of BBS interaction used in the game, look in &amp;lt;code&amp;gt;data/modules/DonateToCranks/DonateToCranks.lua&amp;lt;/code&amp;gt;, and its accompanying language file &amp;lt;code&amp;gt;data/lang/modules-donatetocranks/en.json&amp;lt;/code&amp;gt;, or the Advice module &amp;lt;code&amp;gt;data/modules/Advice/Advice.lua&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;data/lang/modules-advice/en.json&amp;lt;/code&amp;gt;.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Mods&amp;diff=4745</id>
		<title>Mods</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Mods&amp;diff=4745"/>
		<updated>2025-10-23T17:25:32Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* List of known Mods */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Available Mods ==&lt;br /&gt;
&lt;br /&gt;
=== How to install Mods ===&lt;br /&gt;
&lt;br /&gt;
Since Alpha 22, Pioneer has had support for modding. This is done by allowing core game data to be added to or given preference by data in your userdir (that is, &amp;lt;code&amp;gt;'My Docs/Pioneer/mods'&amp;lt;/code&amp;gt; for Windows, &amp;lt;code&amp;gt;'~/.pioneer/mods'&amp;lt;/code&amp;gt; for Linux, and &amp;lt;code&amp;gt;'~/Library/Application Support/Pioneer/mods'&amp;lt;/code&amp;gt; for OSX).&lt;br /&gt;
&lt;br /&gt;
Once you have downloaded a mod, you simply place the .zip file in the /mods folder described above. There is no need to extract the files.&lt;br /&gt;
&lt;br /&gt;
If a file in a .zip  is identically named with one found in the Main pioneer/data directory, the version in the .zip will be used. You can use this fact to easily modify the core game files of Pioneer&lt;br /&gt;
&lt;br /&gt;
This method can be used for any file under the main Pioneer/data directory and applies to ships, buildings and stations, systems, music, fonts, textures, whatever.&lt;br /&gt;
&lt;br /&gt;
However, there are still some mods not packaged in the mod format (.zip). Overtime these are being repackaged by members of the Pioneer community, but in case you have a mod that is not in the correct format then the data in these style of mods must be placed in the correct folder within your main Pioneer/data directory and not in the mods directory described above. But be warned, make a back up of the files you are about to overwrite as this can not be undone and if you do want to return to a 'vanilla' Pioneer, you will have to re-install Pioneer.&lt;br /&gt;
&lt;br /&gt;
== List of known Mods ==&lt;br /&gt;
&lt;br /&gt;
Overtime all the mods here can also be found on the Pioneer Mods page at sourceforge https://sourceforge.net/projects/modsforpioneer/files/Alpha%2030%20mods/&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Current known Mod Packages&lt;br /&gt;
|-&lt;br /&gt;
!Mod Name&lt;br /&gt;
!Mod Description&lt;br /&gt;
!Author(s)&lt;br /&gt;
!Version&lt;br /&gt;
!Tested with Pioneer*&lt;br /&gt;
!Size&lt;br /&gt;
!Download link&lt;br /&gt;
|-&lt;br /&gt;
|Skyboxes&lt;br /&gt;
|Seven new skyboxes, ranging from slight variations on the Milky Way backdrop to spectacular nebulae.&lt;br /&gt;
|[https://github.com/pebblegarden pebblegarden]&lt;br /&gt;
|0.1&lt;br /&gt;
|20250203&lt;br /&gt;
|12 mb&lt;br /&gt;
|[https://www.dropbox.com/scl/fi/cbzretcjuvgg2sxjivjcq/pebblegarden-skyboxes-v0.1.zip?rlkey=74byb7c1phsgqhx4jpvi6vet5&amp;amp;st=jk13ntui&amp;amp;dl=0 skyboxes]&lt;br /&gt;
|-&lt;br /&gt;
|Retro ships&lt;br /&gt;
|A mod containing the original versions of four reworked OPLI ships: Amphiesma, Xylophis, Sinonatrix and Natrix.&lt;br /&gt;
|[https://github.com/Maks2103 Maks2103]&lt;br /&gt;
|0.7&lt;br /&gt;
|20250203&lt;br /&gt;
|2.19 mb&lt;br /&gt;
|[https://www.dropbox.com/scl/fi/fx4w9j1nvo7ydevgb33rh/retro-0.7.zip?rlkey=0wu609kk5isv4r7v1p1u9wcgo&amp;amp;st=bsbf0epi&amp;amp;dl=0 retro ships]&lt;br /&gt;
|-&lt;br /&gt;
|Retro ships&lt;br /&gt;
|A mod containing the original versions of three reworked OPLI ships: Amphiesma, Xylophis and Sinonatrix.&lt;br /&gt;
|[https://github.com/Maks2103 Maks2103]&lt;br /&gt;
|0.6&lt;br /&gt;
|20240710&lt;br /&gt;
|2.19 mb&lt;br /&gt;
|[https://www.dropbox.com/scl/fi/wf2r59dzr2sfvfwto812x/retro-0.6.zip?rlkey=cqlbhf4ctkm5cztzedt65sgvy&amp;amp;e=1&amp;amp;st=phbzmg0k&amp;amp;dl=0 retro ships]&lt;br /&gt;
|-&lt;br /&gt;
|ARGH's original unabridged Extend Sol&lt;br /&gt;
|The original unabridged version of extended Sol, with additional dwarfs, many more major asteroids and a high populated system&lt;br /&gt;
|ARGHouse&lt;br /&gt;
|1.3.1&lt;br /&gt;
|20210818&lt;br /&gt;
|65.7 kb&lt;br /&gt;
|[https://drive.google.com/file/d/1NGTbK1xLdjY4NkN92FtvZc5L6seXlgvW/view?usp=sharing]&lt;br /&gt;
|-&lt;br /&gt;
|Sol Extended&lt;br /&gt;
|Extends the Sol system with additional dwarf planets and spaceports.&lt;br /&gt;
|EpsilonEridani&lt;br /&gt;
|Work-In-Progress&lt;br /&gt;
|20210818&lt;br /&gt;
|25.5 kb&lt;br /&gt;
|[https://libera.ems.host/_matrix/media/r0/download/matrix.org/BJozxFpJFXnoHiRMQNqpVPiG/00_sol_extended.lua 00_sol_extended.lua]&lt;br /&gt;
|-&lt;br /&gt;
|Honk&lt;br /&gt;
|A simple mod which gives the player the ability to sound various car or ship horns.&lt;br /&gt;
|[[User:WKFO|WKFO]]&lt;br /&gt;
|1.0&lt;br /&gt;
|2021-02-03&lt;br /&gt;
|~60 kb&lt;br /&gt;
|[https://www.dropbox.com/s/nq0rh3dexibi0pe/honk.zip?dl=0]&lt;br /&gt;
|-&lt;br /&gt;
|Monolithic Carrier&lt;br /&gt;
|A not-that-big bulk cargo ship by Haber Corporation.&lt;br /&gt;
|[[User:WKFO|WKFO]]&lt;br /&gt;
|0.9&lt;br /&gt;
|2022-02-03&lt;br /&gt;
|3.3 MB&lt;br /&gt;
|[https://www.dropbox.com/s/n2gyaqbt9e5xe79/monocarrier.zip?dl=0]&lt;br /&gt;
|-&lt;br /&gt;
|Graveyard&lt;br /&gt;
|Example of bundling multiple files in a mod. Adds a custom system &amp;quot;Graveyard&amp;quot; (up and to the right of Sol) and a script that does something when you jump there.&lt;br /&gt;
|[[User:RobN|RobN]]&lt;br /&gt;
|1.0&lt;br /&gt;
|20131229&lt;br /&gt;
|1kb&lt;br /&gt;
|[http://eatenbyagrue.org/f/pioneer/graveyard.zip graveyard.zip]&lt;br /&gt;
|-&lt;br /&gt;
|RedSol&lt;br /&gt;
|Example of replacing a core file. Replaces the system def for Sol with one that has a red star. &lt;br /&gt;
|[[User:RobN|RobN]]&lt;br /&gt;
|1.0&lt;br /&gt;
|Alpha22&lt;br /&gt;
|1kb&lt;br /&gt;
|[http://eatenbyagrue.org/f/pioneer/redsol.zip redsol.zip]&lt;br /&gt;
|-&lt;br /&gt;
|Skyboxes&lt;br /&gt;
|Nebula backgrounds when in space&lt;br /&gt;
|[https://spacesimcentral.com/community/pioneer/i-dont-like-the-skybox/#post-46527 pebblegarden]&lt;br /&gt;
|1.0&lt;br /&gt;
|2015&lt;br /&gt;
|8Mb&lt;br /&gt;
|[http://spacesimcentral.com/ssc/index.php?app=core&amp;amp;module=attach&amp;amp;section=attach&amp;amp;attach_id=2563]&lt;br /&gt;
|-&lt;br /&gt;
|Lanner ship&lt;br /&gt;
|The Lanner ship&lt;br /&gt;
|unclear&lt;br /&gt;
|1.0&lt;br /&gt;
|2015&lt;br /&gt;
|1.58 Mb&lt;br /&gt;
|[https://spacesimcentral.com/community/pioneer-mods/lanner-for-current-pioneer-build/#post-4439]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Vuzz's Milkyway skybox&lt;br /&gt;
|Vuzz's skybox is the most realistic version of the Milky Way for Pioneer.&lt;br /&gt;
|[[User:impaktor|impaktor]]&lt;br /&gt;
|&lt;br /&gt;
|20150912&lt;br /&gt;
|1.34MB&lt;br /&gt;
|[https://spacesimcentral.com/community/pioneer-mods/vuzzs-milky-way-skybox-mod/#post-4460]&lt;br /&gt;
|-&lt;br /&gt;
|Moon Heightmap&lt;br /&gt;
|Higher res heightmap which replaces the 2mb default heightmap.&lt;br /&gt;
|Ae-2222&lt;br /&gt;
|1.0&lt;br /&gt;
|Alpha26  - - (all current versions)&lt;br /&gt;
|33Mb&lt;br /&gt;
|[http://wikisend.com/download/359748/moon-hi-res-heightmap.zip moon-hi-res-heightmap.zip]&lt;br /&gt;
|-&lt;br /&gt;
|Character Generator Upgrade (facegen mod)&lt;br /&gt;
|A small upgrade that adds some new hair, eyes, clothing, uniforms, additional accessories and other improvements to the existing character generator.&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|1.1&lt;br /&gt;
|Compatible with most as well as current v20140217&lt;br /&gt;
|8Mb&lt;br /&gt;
|[http://www.mediafire.com/download/pr55p4r7g87s9uf/facegen_mod_bao_pioneer20140205_bao_V1.1.zip]&lt;br /&gt;
|-&lt;br /&gt;
|Wave Explorer (ship mod) includes 2 custom pilots&lt;br /&gt;
|HiRes2048x2048_Wave-e -see info below or visit https://forum.pioneerspacesim.net/viewtopic.php?f=3&amp;amp;t=89&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|3.5&lt;br /&gt;
|v20140217 - (all current versions)&lt;br /&gt;
|18Mb&lt;br /&gt;
|[http://www.mediafire.com/download/s7d4rqk2fio9gwu/Ship2048x2048_wave-e-w-pilots_bao_pioneer20140217_bao_V3.5.zip]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Wave Explorer (ship mod) includes 2 custom pilots&lt;br /&gt;
|LoRes1024x1024 version of the Wave-e (for increased graphics performance on lower end systems) see info below or visit: https://forum.pioneerspacesim.net/viewtopic.php?f=3&amp;amp;t=89&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|3.5&lt;br /&gt;
|v20140217 - (all current versions)&lt;br /&gt;
|8Mb&lt;br /&gt;
|[http://www.mediafire.com/download/dgxjgo2tf7dgob3/Ship1024x1024_wave-e-w-pilots_bao_pioneer20140217_bao_V3.5.zip]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Apollo LEM Lunar orbit save file for Alpha26&lt;br /&gt;
|Low fuel LEM landing challenge (-w- cargo to jettison) 3 saves with Lunar lander in unpowered orbit over moon with low fuel(requires HighRes.Moon_Apollo.LEM.a26Mod)&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|1.0&lt;br /&gt;
|Alpha26&lt;br /&gt;
|520kb&lt;br /&gt;
|[http://www.mediafire.com/?sy5d6i4n8vz10c4]&lt;br /&gt;
|-&lt;br /&gt;
|Interstellar Shuttle 1700&lt;br /&gt;
|Adds a modified version of the standard shuttle to the ship roster with hyperdrive capability, ecm, missile, cargo/fuel scoop options and more cargo space.(includes a new shuttle skin and solar array for bottom of existing standard shuttle (readme inside zip file)&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|1.3&lt;br /&gt;
|Alpha 30&lt;br /&gt;
|3560kb&lt;br /&gt;
|[http://www.mediafire.com/?46ci6m4677y5wg3]&lt;br /&gt;
|-&lt;br /&gt;
|Enterprise 1701a (static ship)&lt;br /&gt;
|Replaces the Long Range Cruiser (static Ship) with the NCC 1701a &amp;quot;Enterprise&amp;quot; at realistic scale. (readme inside zip file)&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|1.2&lt;br /&gt;
|Alpha 30&lt;br /&gt;
|1.78mb&lt;br /&gt;
|[http://www.mediafire.com/?g4ng4dk46kdns8v]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Ship Decals Mod 256x256&lt;br /&gt;
|This is version 4.0 - includes 8 ship decals.           IMAGE here: http://img708.imageshack.us/img708/7146/decalsversion4.png &lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|4.0&lt;br /&gt;
|Alpha 27-30&lt;br /&gt;
|3mb&lt;br /&gt;
|[http://www.mediafire.com/?14bchs29dn06nn2]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Controller Templates&lt;br /&gt;
|Not really a Mod - This is for people who have - or are familiar with either Xpadder, 3D Pro Joystick or the G-13 Gameboard. Extreme 3D Pro Joystick Xpadder profile and G-13 gameboard Basic Template - printable key &amp;amp; joystick button reference. Version 3  (More info see above)&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|3.0&lt;br /&gt;
|Most Alphas up to 26&lt;br /&gt;
|4490kb&lt;br /&gt;
|[https://docs.google.com/open?id=0B5dK9aniVGUnM09GRU1qVFJWemM]   [http://www.mediafire.com/?ncnh6bpdvna34n2]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|3DPro Joystick Profile&lt;br /&gt;
|This is an update supporting the new view keys, for Extreme 3D Pro Joystick Xpadder profile with Template - printable joystick button reference. Version 5  (see pic: http://imageshack.us/a/img822/864/ztf.png )&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|5.0&lt;br /&gt;
|Alphas after 28 (all current versions)&lt;br /&gt;
|2.31mb&lt;br /&gt;
|[http://www.mediafire.com/?btgkn3sjx0ianuu]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Pioneer default keyboard reference&lt;br /&gt;
|In pdf printable format(see PNG pic: http://img203.imageshack.us/img203/2079/5dh.png )&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|v201305.93bao&lt;br /&gt;
|Alphas after 28(all current versions)&lt;br /&gt;
|7.3mb&lt;br /&gt;
|[http://www.mediafire.com/?y5a2sapya7vfhhr]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Pioneer custom mouse pointers&lt;br /&gt;
|Small simple mods containing only custom mouse pointers&lt;br /&gt;
|[[User:Joonicks|joonicks]]&lt;br /&gt;
|&lt;br /&gt;
|20151001&lt;br /&gt;
|1-4kb&lt;br /&gt;
|[http://joonicks.eu/pioneer_mods]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*'' Version number represents what the module was tested/built against. There is no guarantee that it will work for later versions.''&lt;br /&gt;
&lt;br /&gt;
== List of known Mod Compilations ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Current known Mod Compilation Packages&lt;br /&gt;
|-&lt;br /&gt;
!Mod Name&lt;br /&gt;
!Mod Description&lt;br /&gt;
!Author(s)&lt;br /&gt;
!Version&lt;br /&gt;
!Tested with Pioneer*&lt;br /&gt;
!Size&lt;br /&gt;
!Download link&lt;br /&gt;
|-&lt;br /&gt;
|HiRes Moon &amp;amp; Apollo LEM Mod&lt;br /&gt;
|High res Moon heightmap &amp;amp; flyable Apollo Eagle Lander with docs &amp;amp; nav maps.&lt;br /&gt;
|contrib by Ae-2222 &amp;amp; [[User:baobobafet|baobobafet]]&lt;br /&gt;
|2.0&lt;br /&gt;
|Alpha 30&lt;br /&gt;
|35.3Mb&lt;br /&gt;
|[https://docs.google.com/open?id=0B4TOJEZxkYiVUGZqVklKVEE0WUE]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
*'' Version number represents what the module compilation was tested/built against. There is no guarantee that it will work for later versions.''&lt;br /&gt;
&lt;br /&gt;
'''''The who/when/where for mod storage/testing/package compilation is still to be established. As with all things concerning Pioneer it is up to interested parties to resolve this. There is the beginnings of a discussion on the [[Talk:Mods|discussion page]] as not everyone is on the mailing list yet (another could be started on a specific task page if anyone wants to). You need to register to edit pages and contribute.'''''&lt;br /&gt;
&lt;br /&gt;
== Creating A New Ship Mod ==&lt;br /&gt;
This is in response to the couple of people who are interested in doing art, perhaps even leading the direction it can go in.&lt;br /&gt;
&lt;br /&gt;
For programmers there's a path which involves creating a GitHub account, forking the Pioneer repository, creating branches, configuring remotes, making commits and submitting Pull Requests.&lt;br /&gt;
&lt;br /&gt;
For Artists and scripters there's another way which is hopefully preferrable for everyone! &lt;br /&gt;
&lt;br /&gt;
To follow this &amp;quot;tutorial&amp;quot; you'll need at least a way of creating &amp;quot;.zip&amp;quot; files, on Windows I use the 7-zip program.&lt;br /&gt;
&lt;br /&gt;
This example is being written for Windows but the ideas are broadly transferrable even if the exact file paths and locations aren't.&lt;br /&gt;
&lt;br /&gt;
=== MyFirstShip mod ===&lt;br /&gt;
There's links to a few existing mods above but they're very simple things:&lt;br /&gt;
* They're just zip files containing some directories and files,&lt;br /&gt;
* Their contents are &amp;quot;Added&amp;quot; to Pioneer unless replace something like a script, ship, texture, etc,&lt;br /&gt;
* They use the same directory structure as Pioneer.&lt;br /&gt;
The example that most players are enthusiastic about is adding a new ship which is the first step on the long road to a total conversion! &lt;br /&gt;
&lt;br /&gt;
(For additional information see: [https://wiki.pioneerspacesim.net/wiki/Making_your_first_ship Making_your_first_ship] )&lt;br /&gt;
&lt;br /&gt;
To start with we want to make this task easy for ourselves so lets try the following to create a &amp;quot;mod&amp;quot; called &amp;quot;myfirstship&amp;quot;:&lt;br /&gt;
* Go to wherever you've extracted your Pioneer download to,&lt;br /&gt;
* Open the folder &amp;quot;data&amp;quot; and look down the folder names as there's two we're interested in,&lt;br /&gt;
** &amp;quot;ships&amp;quot; and &amp;quot;models&amp;quot;,&lt;br /&gt;
* open the &amp;quot;ships&amp;quot; folder,&lt;br /&gt;
** copy and rename the file &amp;quot;wave.lua&amp;quot; to &amp;quot;myfirstship.lua&amp;quot;,&lt;br /&gt;
** open the file in a text editor and change the following lines like so&lt;br /&gt;
*** from: name='Wave Heavy Hypersonic Fighter'&lt;br /&gt;
*** from: model='wave',&lt;br /&gt;
*** To: name='My First Ship Tutorial'&lt;br /&gt;
*** To: model='myfirstship',&lt;br /&gt;
*** save your changes to the &amp;quot;myfirstship.lua&amp;quot; file,&lt;br /&gt;
*** now go back up to the data folder,&lt;br /&gt;
* open the &amp;quot;models&amp;quot; folder, then the &amp;quot;ships&amp;quot; folder,&lt;br /&gt;
** copy and rename the folder &amp;quot;wave&amp;quot; to &amp;quot;myfirstship&amp;quot;,&lt;br /&gt;
** open the &amp;quot;myfirstship&amp;quot; folder,&lt;br /&gt;
*** rename the file &amp;quot;wave.model&amp;quot; to &amp;quot;myfirstships.model&amp;quot;,&lt;br /&gt;
Now run Pioneer with the parameter &amp;quot;-mv myfirstship&amp;quot; so the command line should look like &amp;quot;pioneer.exe -mv myfirstship&amp;quot; this will open it in the modelviewer mode so you can see if everything has worked.&lt;br /&gt;
&lt;br /&gt;
If you run Pioneer as usual then you should be able to go to the ship yard and buy your very own &amp;quot;My First Ship Tutorial&amp;quot; spaceship. Of course it will look like the Wave heavy Hypersonic Fighter, and it will handle like it and be like it in every possible way because it's a perfect copy of it &lt;br /&gt;
 &lt;br /&gt;
If you want to make it look different so it's easier to spot then a quick thing you can do is edit the texture &amp;quot;wave.png&amp;quot; in your &amp;quot;/data/models/ships/myfirstship/&amp;quot; folder, perhaps just paint the white a bright red or something which will make it easy to tell that you've changed it.&lt;br /&gt;
 &lt;br /&gt;
Obviously we're not done turning all this effort into a mod yet, we've just copied a ship definition (&amp;quot;myfirstship.lua&amp;quot;) and the model data for it.&lt;br /&gt;
&lt;br /&gt;
So lets open a new folder window (if on Windows) and:&lt;br /&gt;
* Create a new folder somewhere you won't lose it, &amp;quot;My Documents&amp;quot; for windows users or the home folder on Linux etc, call it &amp;quot;tutorial&amp;quot; this time (to avoid confusion),&lt;br /&gt;
* In this folder create two more called &amp;quot;ships&amp;quot; and &amp;quot;models&amp;quot;,&lt;br /&gt;
* With the &amp;quot;models&amp;quot; folder create another folder called &amp;quot;ships&amp;quot;,&lt;br /&gt;
* by now you should have something that looks like:&lt;br /&gt;
** /tutorial/ships/&lt;br /&gt;
** /tutorial/models/ships/&lt;br /&gt;
* now you're going to _move_ the &amp;quot;myfirstships.lua&amp;quot; file you edited earlier out of the &amp;quot;/pioneer/data/ships/&amp;quot; into the folder &amp;quot;/tutorial/ships/&amp;quot; folder,&lt;br /&gt;
* next you must _move_ the folder &amp;quot;myfirstship&amp;quot; from &amp;quot;/pioneer/data/models/ships/&amp;quot; to &amp;quot;/tutorial/models/ships/&amp;quot;,&lt;br /&gt;
* now create a &amp;quot;readme.txt&amp;quot; file in the &amp;quot;tutorial&amp;quot; folder and add your name to it for now,&lt;br /&gt;
* Add everything inside the &amp;quot;tutorial&amp;quot; folder to a zip archive - to do this with 7-zip just selected everything inside the tutorial folder, right-click, open the 7-zip submenu and choose the &amp;quot;Add to archive&amp;quot;,&lt;br /&gt;
* Make sure that you choose to use &amp;quot;zip&amp;quot; compression and call the archive &amp;quot;myfirstship.zip&amp;quot;.&lt;br /&gt;
* Finally, put the file &amp;quot;myfirstship.zip&amp;quot; into the &amp;quot;mods&amp;quot; directory - this IS NOT usually where you extracted the Pioneer download.&lt;br /&gt;
** On Windows it will be in a folder under your &amp;quot;My Documents&amp;quot;/&amp;quot;Documents&amp;quot; directory for example:&lt;br /&gt;
*** &amp;quot;/My Documents/Pioneer/mods/&amp;quot;&lt;br /&gt;
* Now double check that you really did MOVE the files and folders, i.e; go back to where you first copied the wave files and folders and make sure that there aren't any &amp;quot;myfirsthip&amp;quot; files or folder there because they should now all be inside the zip file (and safely backed up in the &amp;quot;tutorial&amp;quot; folder).&lt;br /&gt;
** The reason this step is important is that you might have made a mistake with the zipping process and the mod is actually now broken, however it will look like it works because the original files are still being found by Pioneer in it's own data directory.&lt;br /&gt;
* Finally, the mod should have been packaged up into a zip, it should be in the &amp;quot;mods&amp;quot; directory, and you've made sure the Pioneer data folder is clean.&lt;br /&gt;
* Run Pioneer and make sure your mod works.&lt;br /&gt;
 &lt;br /&gt;
There are many steps that take just a second or two and lots of bits you can skip once you know what you're doing.&lt;br /&gt;
 &lt;br /&gt;
You can have as many ships within a single mod as you like, this technique just gives you a copy of the Wave to edit and play with.&lt;br /&gt;
&lt;br /&gt;
You can, and should, replace it with your own mesh and edit the parameters it uses for thrust and equipment etc.&lt;br /&gt;
&lt;br /&gt;
=== So how does this get my ships into the Pioneer download? ===&lt;br /&gt;
Easy, you create your mod, test it, try it out, share it via this forums download area so that others can play it, respond to criticisms to improve it and then ask the Core Team to include it in the main distribution.&lt;br /&gt;
&lt;br /&gt;
This is basically the same process that we go through for programmers submissions but without having to understand Git and GitHub.&lt;br /&gt;
&lt;br /&gt;
It's also more direct to players and fans of the game who often use several mods together to make Pioneer play how they want it too.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Mods&amp;diff=4742</id>
		<title>Mods</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Mods&amp;diff=4742"/>
		<updated>2025-05-09T15:56:53Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* List of known Mods */ layout fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Available Mods ==&lt;br /&gt;
&lt;br /&gt;
=== How to install Mods ===&lt;br /&gt;
&lt;br /&gt;
Since Alpha 22, Pioneer has had support for modding. This is done by allowing core game data to be added to or given preference by data in your userdir (that is, &amp;lt;code&amp;gt;'My Docs/Pioneer/mods'&amp;lt;/code&amp;gt; for Windows, &amp;lt;code&amp;gt;'~/.pioneer/mods'&amp;lt;/code&amp;gt; for Linux, and &amp;lt;code&amp;gt;'~/Library/Application Support/Pioneer/mods'&amp;lt;/code&amp;gt; for OSX).&lt;br /&gt;
&lt;br /&gt;
Once you have downloaded a mod, you simply place the .zip file in the /mods folder described above. There is no need to extract the files.&lt;br /&gt;
&lt;br /&gt;
If a file in a .zip  is identically named with one found in the Main pioneer/data directory, the version in the .zip will be used. You can use this fact to easily modify the core game files of Pioneer&lt;br /&gt;
&lt;br /&gt;
This method can be used for any file under the main Pioneer/data directory and applies to ships, buildings and stations, systems, music, fonts, textures, whatever.&lt;br /&gt;
&lt;br /&gt;
However, there are still some mods not packaged in the mod format (.zip). Overtime these are being repackaged by members of the Pioneer community, but in case you have a mod that is not in the correct format then the data in these style of mods must be placed in the correct folder within your main Pioneer/data directory and not in the mods directory described above. But be warned, make a back up of the files you are about to overwrite as this can not be undone and if you do want to return to a 'vanilla' Pioneer, you will have to re-install Pioneer.&lt;br /&gt;
&lt;br /&gt;
== List of known Mods ==&lt;br /&gt;
&lt;br /&gt;
Overtime all the mods here can also be found on the Pioneer Mods page at sourceforge https://sourceforge.net/projects/modsforpioneer/files/Alpha%2030%20mods/&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Current known Mod Packages&lt;br /&gt;
|-&lt;br /&gt;
!Mod Name&lt;br /&gt;
!Mod Description&lt;br /&gt;
!Author(s)&lt;br /&gt;
!Version&lt;br /&gt;
!Tested with Pioneer*&lt;br /&gt;
!Size&lt;br /&gt;
!Download link&lt;br /&gt;
|-&lt;br /&gt;
|Skyboxes&lt;br /&gt;
|Seven new skyboxes, ranging from slight variations on the Milky Way backdrop to spectacular nebulae.&lt;br /&gt;
|[https://github.com/pebblegarden pebblegarden]&lt;br /&gt;
|0.1&lt;br /&gt;
|20250203&lt;br /&gt;
|12 mb&lt;br /&gt;
|[https://www.dropbox.com/scl/fi/cbzretcjuvgg2sxjivjcq/pebblegarden-skyboxes-v0.1.zip?rlkey=74byb7c1phsgqhx4jpvi6vet5&amp;amp;st=jk13ntui&amp;amp;dl=0 skyboxes]&lt;br /&gt;
|-&lt;br /&gt;
|Retro ships&lt;br /&gt;
|A mod containing the original versions of four reworked OPLI ships: Amphiesma, Xylophis Sinonatrix and Natrix.&lt;br /&gt;
|[https://github.com/Maks2103 Maks2103]&lt;br /&gt;
|0.7&lt;br /&gt;
|20250203&lt;br /&gt;
|2.19 mb&lt;br /&gt;
|[https://www.dropbox.com/scl/fi/fx4w9j1nvo7ydevgb33rh/retro-0.7.zip?rlkey=0wu609kk5isv4r7v1p1u9wcgo&amp;amp;st=bsbf0epi&amp;amp;dl=0 retro ships]&lt;br /&gt;
|-&lt;br /&gt;
|Retro ships&lt;br /&gt;
|A mod containing the original versions of three reworked OPLI ships: Amphiesma, Xylophis and Sinonatrix.&lt;br /&gt;
|[https://github.com/Maks2103 Maks2103]&lt;br /&gt;
|0.6&lt;br /&gt;
|20240710&lt;br /&gt;
|2.19 mb&lt;br /&gt;
|[https://www.dropbox.com/scl/fi/wf2r59dzr2sfvfwto812x/retro-0.6.zip?rlkey=cqlbhf4ctkm5cztzedt65sgvy&amp;amp;e=1&amp;amp;st=phbzmg0k&amp;amp;dl=0 retro ships]&lt;br /&gt;
|-&lt;br /&gt;
|ARGH's original unabridged Extend Sol&lt;br /&gt;
|The original unabridged version of extended Sol, with additional dwarfs, many more major asteroids and a high populated system&lt;br /&gt;
|ARGHouse&lt;br /&gt;
|1.3.1&lt;br /&gt;
|20210818&lt;br /&gt;
|65.7 kb&lt;br /&gt;
|[https://drive.google.com/file/d/1NGTbK1xLdjY4NkN92FtvZc5L6seXlgvW/view?usp=sharing]&lt;br /&gt;
|-&lt;br /&gt;
|Sol Extended&lt;br /&gt;
|Extends the Sol system with additional dwarf planets and spaceports.&lt;br /&gt;
|EpsilonEridani&lt;br /&gt;
|Work-In-Progress&lt;br /&gt;
|20210818&lt;br /&gt;
|25.5 kb&lt;br /&gt;
|[https://libera.ems.host/_matrix/media/r0/download/matrix.org/BJozxFpJFXnoHiRMQNqpVPiG/00_sol_extended.lua 00_sol_extended.lua]&lt;br /&gt;
|-&lt;br /&gt;
|Honk&lt;br /&gt;
|A simple mod which gives the player the ability to sound various car or ship horns.&lt;br /&gt;
|[[User:WKFO|WKFO]]&lt;br /&gt;
|1.0&lt;br /&gt;
|2021-02-03&lt;br /&gt;
|~60 kb&lt;br /&gt;
|[https://www.dropbox.com/s/nq0rh3dexibi0pe/honk.zip?dl=0]&lt;br /&gt;
|-&lt;br /&gt;
|Monolithic Carrier&lt;br /&gt;
|A not-that-big bulk cargo ship by Haber Corporation.&lt;br /&gt;
|[[User:WKFO|WKFO]]&lt;br /&gt;
|0.9&lt;br /&gt;
|2022-02-03&lt;br /&gt;
|3.3 MB&lt;br /&gt;
|[https://www.dropbox.com/s/n2gyaqbt9e5xe79/monocarrier.zip?dl=0]&lt;br /&gt;
|-&lt;br /&gt;
|Graveyard&lt;br /&gt;
|Example of bundling multiple files in a mod. Adds a custom system &amp;quot;Graveyard&amp;quot; (up and to the right of Sol) and a script that does something when you jump there.&lt;br /&gt;
|[[User:RobN|RobN]]&lt;br /&gt;
|1.0&lt;br /&gt;
|20131229&lt;br /&gt;
|1kb&lt;br /&gt;
|[http://eatenbyagrue.org/f/pioneer/graveyard.zip graveyard.zip]&lt;br /&gt;
|-&lt;br /&gt;
|RedSol&lt;br /&gt;
|Example of replacing a core file. Replaces the system def for Sol with one that has a red star. &lt;br /&gt;
|[[User:RobN|RobN]]&lt;br /&gt;
|1.0&lt;br /&gt;
|Alpha22&lt;br /&gt;
|1kb&lt;br /&gt;
|[http://eatenbyagrue.org/f/pioneer/redsol.zip redsol.zip]&lt;br /&gt;
|-&lt;br /&gt;
|Skyboxes&lt;br /&gt;
|Nebula backgrounds when in space&lt;br /&gt;
|[https://spacesimcentral.com/community/pioneer/i-dont-like-the-skybox/#post-46527 pebblegarden]&lt;br /&gt;
|1.0&lt;br /&gt;
|2015&lt;br /&gt;
|8Mb&lt;br /&gt;
|[http://spacesimcentral.com/ssc/index.php?app=core&amp;amp;module=attach&amp;amp;section=attach&amp;amp;attach_id=2563]&lt;br /&gt;
|-&lt;br /&gt;
|Lanner ship&lt;br /&gt;
|The Lanner ship&lt;br /&gt;
|unclear&lt;br /&gt;
|1.0&lt;br /&gt;
|2015&lt;br /&gt;
|1.58 Mb&lt;br /&gt;
|[https://spacesimcentral.com/community/pioneer-mods/lanner-for-current-pioneer-build/#post-4439]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Vuzz's Milkyway skybox&lt;br /&gt;
|Vuzz's skybox is the most realistic version of the Milky Way for Pioneer.&lt;br /&gt;
|[[User:impaktor|impaktor]]&lt;br /&gt;
|&lt;br /&gt;
|20150912&lt;br /&gt;
|1.34MB&lt;br /&gt;
|[https://spacesimcentral.com/community/pioneer-mods/vuzzs-milky-way-skybox-mod/#post-4460]&lt;br /&gt;
|-&lt;br /&gt;
|Moon Heightmap&lt;br /&gt;
|Higher res heightmap which replaces the 2mb default heightmap.&lt;br /&gt;
|Ae-2222&lt;br /&gt;
|1.0&lt;br /&gt;
|Alpha26  - - (all current versions)&lt;br /&gt;
|33Mb&lt;br /&gt;
|[http://wikisend.com/download/359748/moon-hi-res-heightmap.zip moon-hi-res-heightmap.zip]&lt;br /&gt;
|-&lt;br /&gt;
|Character Generator Upgrade (facegen mod)&lt;br /&gt;
|A small upgrade that adds some new hair, eyes, clothing, uniforms, additional accessories and other improvements to the existing character generator.&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|1.1&lt;br /&gt;
|Compatible with most as well as current v20140217&lt;br /&gt;
|8Mb&lt;br /&gt;
|[http://www.mediafire.com/download/pr55p4r7g87s9uf/facegen_mod_bao_pioneer20140205_bao_V1.1.zip]&lt;br /&gt;
|-&lt;br /&gt;
|Wave Explorer (ship mod) includes 2 custom pilots&lt;br /&gt;
|HiRes2048x2048_Wave-e -see info below or visit https://forum.pioneerspacesim.net/viewtopic.php?f=3&amp;amp;t=89&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|3.5&lt;br /&gt;
|v20140217 - (all current versions)&lt;br /&gt;
|18Mb&lt;br /&gt;
|[http://www.mediafire.com/download/s7d4rqk2fio9gwu/Ship2048x2048_wave-e-w-pilots_bao_pioneer20140217_bao_V3.5.zip]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Wave Explorer (ship mod) includes 2 custom pilots&lt;br /&gt;
|LoRes1024x1024 version of the Wave-e (for increased graphics performance on lower end systems) see info below or visit: https://forum.pioneerspacesim.net/viewtopic.php?f=3&amp;amp;t=89&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|3.5&lt;br /&gt;
|v20140217 - (all current versions)&lt;br /&gt;
|8Mb&lt;br /&gt;
|[http://www.mediafire.com/download/dgxjgo2tf7dgob3/Ship1024x1024_wave-e-w-pilots_bao_pioneer20140217_bao_V3.5.zip]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Apollo LEM Lunar orbit save file for Alpha26&lt;br /&gt;
|Low fuel LEM landing challenge (-w- cargo to jettison) 3 saves with Lunar lander in unpowered orbit over moon with low fuel(requires HighRes.Moon_Apollo.LEM.a26Mod)&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|1.0&lt;br /&gt;
|Alpha26&lt;br /&gt;
|520kb&lt;br /&gt;
|[http://www.mediafire.com/?sy5d6i4n8vz10c4]&lt;br /&gt;
|-&lt;br /&gt;
|Interstellar Shuttle 1700&lt;br /&gt;
|Adds a modified version of the standard shuttle to the ship roster with hyperdrive capability, ecm, missile, cargo/fuel scoop options and more cargo space.(includes a new shuttle skin and solar array for bottom of existing standard shuttle (readme inside zip file)&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|1.3&lt;br /&gt;
|Alpha 30&lt;br /&gt;
|3560kb&lt;br /&gt;
|[http://www.mediafire.com/?46ci6m4677y5wg3]&lt;br /&gt;
|-&lt;br /&gt;
|Enterprise 1701a (static ship)&lt;br /&gt;
|Replaces the Long Range Cruiser (static Ship) with the NCC 1701a &amp;quot;Enterprise&amp;quot; at realistic scale. (readme inside zip file)&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|1.2&lt;br /&gt;
|Alpha 30&lt;br /&gt;
|1.78mb&lt;br /&gt;
|[http://www.mediafire.com/?g4ng4dk46kdns8v]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Ship Decals Mod 256x256&lt;br /&gt;
|This is version 4.0 - includes 8 ship decals.           IMAGE here: http://img708.imageshack.us/img708/7146/decalsversion4.png &lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|4.0&lt;br /&gt;
|Alpha 27-30&lt;br /&gt;
|3mb&lt;br /&gt;
|[http://www.mediafire.com/?14bchs29dn06nn2]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Controller Templates&lt;br /&gt;
|Not really a Mod - This is for people who have - or are familiar with either Xpadder, 3D Pro Joystick or the G-13 Gameboard. Extreme 3D Pro Joystick Xpadder profile and G-13 gameboard Basic Template - printable key &amp;amp; joystick button reference. Version 3  (More info see above)&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|3.0&lt;br /&gt;
|Most Alphas up to 26&lt;br /&gt;
|4490kb&lt;br /&gt;
|[https://docs.google.com/open?id=0B5dK9aniVGUnM09GRU1qVFJWemM]   [http://www.mediafire.com/?ncnh6bpdvna34n2]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|3DPro Joystick Profile&lt;br /&gt;
|This is an update supporting the new view keys, for Extreme 3D Pro Joystick Xpadder profile with Template - printable joystick button reference. Version 5  (see pic: http://imageshack.us/a/img822/864/ztf.png )&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|5.0&lt;br /&gt;
|Alphas after 28 (all current versions)&lt;br /&gt;
|2.31mb&lt;br /&gt;
|[http://www.mediafire.com/?btgkn3sjx0ianuu]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Pioneer default keyboard reference&lt;br /&gt;
|In pdf printable format(see PNG pic: http://img203.imageshack.us/img203/2079/5dh.png )&lt;br /&gt;
|[[User:baobobafet|baobobafet]]&lt;br /&gt;
|v201305.93bao&lt;br /&gt;
|Alphas after 28(all current versions)&lt;br /&gt;
|7.3mb&lt;br /&gt;
|[http://www.mediafire.com/?y5a2sapya7vfhhr]&lt;br /&gt;
|-&lt;br /&gt;
|-&lt;br /&gt;
|Pioneer custom mouse pointers&lt;br /&gt;
|Small simple mods containing only custom mouse pointers&lt;br /&gt;
|[[User:Joonicks|joonicks]]&lt;br /&gt;
|&lt;br /&gt;
|20151001&lt;br /&gt;
|1-4kb&lt;br /&gt;
|[http://joonicks.eu/pioneer_mods]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
*'' Version number represents what the module was tested/built against. There is no guarantee that it will work for later versions.''&lt;br /&gt;
&lt;br /&gt;
== List of known Mod Compilations ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+Current known Mod Compilation Packages&lt;br /&gt;
|-&lt;br /&gt;
!Mod Name&lt;br /&gt;
!Mod Description&lt;br /&gt;
!Author(s)&lt;br /&gt;
!Version&lt;br /&gt;
!Tested with Pioneer*&lt;br /&gt;
!Size&lt;br /&gt;
!Download link&lt;br /&gt;
|-&lt;br /&gt;
|HiRes Moon &amp;amp; Apollo LEM Mod&lt;br /&gt;
|High res Moon heightmap &amp;amp; flyable Apollo Eagle Lander with docs &amp;amp; nav maps.&lt;br /&gt;
|contrib by Ae-2222 &amp;amp; [[User:baobobafet|baobobafet]]&lt;br /&gt;
|2.0&lt;br /&gt;
|Alpha 30&lt;br /&gt;
|35.3Mb&lt;br /&gt;
|[https://docs.google.com/open?id=0B4TOJEZxkYiVUGZqVklKVEE0WUE]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
*'' Version number represents what the module compilation was tested/built against. There is no guarantee that it will work for later versions.''&lt;br /&gt;
&lt;br /&gt;
'''''The who/when/where for mod storage/testing/package compilation is still to be established. As with all things concerning Pioneer it is up to interested parties to resolve this. There is the beginnings of a discussion on the [[Talk:Mods|discussion page]] as not everyone is on the mailing list yet (another could be started on a specific task page if anyone wants to). You need to register to edit pages and contribute.'''''&lt;br /&gt;
&lt;br /&gt;
== Creating A New Ship Mod ==&lt;br /&gt;
This is in response to the couple of people who are interested in doing art, perhaps even leading the direction it can go in.&lt;br /&gt;
&lt;br /&gt;
For programmers there's a path which involves creating a GitHub account, forking the Pioneer repository, creating branches, configuring remotes, making commits and submitting Pull Requests.&lt;br /&gt;
&lt;br /&gt;
For Artists and scripters there's another way which is hopefully preferrable for everyone! &lt;br /&gt;
&lt;br /&gt;
To follow this &amp;quot;tutorial&amp;quot; you'll need at least a way of creating &amp;quot;.zip&amp;quot; files, on Windows I use the 7-zip program.&lt;br /&gt;
&lt;br /&gt;
This example is being written for Windows but the ideas are broadly transferrable even if the exact file paths and locations aren't.&lt;br /&gt;
&lt;br /&gt;
=== MyFirstShip mod ===&lt;br /&gt;
There's links to a few existing mods above but they're very simple things:&lt;br /&gt;
* They're just zip files containing some directories and files,&lt;br /&gt;
* Their contents are &amp;quot;Added&amp;quot; to Pioneer unless replace something like a script, ship, texture, etc,&lt;br /&gt;
* They use the same directory structure as Pioneer.&lt;br /&gt;
The example that most players are enthusiastic about is adding a new ship which is the first step on the long road to a total conversion! &lt;br /&gt;
&lt;br /&gt;
(For additional information see: [https://wiki.pioneerspacesim.net/wiki/Making_your_first_ship Making_your_first_ship] )&lt;br /&gt;
&lt;br /&gt;
To start with we want to make this task easy for ourselves so lets try the following to create a &amp;quot;mod&amp;quot; called &amp;quot;myfirstship&amp;quot;:&lt;br /&gt;
* Go to wherever you've extracted your Pioneer download to,&lt;br /&gt;
* Open the folder &amp;quot;data&amp;quot; and look down the folder names as there's two we're interested in,&lt;br /&gt;
** &amp;quot;ships&amp;quot; and &amp;quot;models&amp;quot;,&lt;br /&gt;
* open the &amp;quot;ships&amp;quot; folder,&lt;br /&gt;
** copy and rename the file &amp;quot;wave.lua&amp;quot; to &amp;quot;myfirstship.lua&amp;quot;,&lt;br /&gt;
** open the file in a text editor and change the following lines like so&lt;br /&gt;
*** from: name='Wave Heavy Hypersonic Fighter'&lt;br /&gt;
*** from: model='wave',&lt;br /&gt;
*** To: name='My First Ship Tutorial'&lt;br /&gt;
*** To: model='myfirstship',&lt;br /&gt;
*** save your changes to the &amp;quot;myfirstship.lua&amp;quot; file,&lt;br /&gt;
*** now go back up to the data folder,&lt;br /&gt;
* open the &amp;quot;models&amp;quot; folder, then the &amp;quot;ships&amp;quot; folder,&lt;br /&gt;
** copy and rename the folder &amp;quot;wave&amp;quot; to &amp;quot;myfirstship&amp;quot;,&lt;br /&gt;
** open the &amp;quot;myfirstship&amp;quot; folder,&lt;br /&gt;
*** rename the file &amp;quot;wave.model&amp;quot; to &amp;quot;myfirstships.model&amp;quot;,&lt;br /&gt;
Now run Pioneer with the parameter &amp;quot;-mv myfirstship&amp;quot; so the command line should look like &amp;quot;pioneer.exe -mv myfirstship&amp;quot; this will open it in the modelviewer mode so you can see if everything has worked.&lt;br /&gt;
&lt;br /&gt;
If you run Pioneer as usual then you should be able to go to the ship yard and buy your very own &amp;quot;My First Ship Tutorial&amp;quot; spaceship. Of course it will look like the Wave heavy Hypersonic Fighter, and it will handle like it and be like it in every possible way because it's a perfect copy of it &lt;br /&gt;
 &lt;br /&gt;
If you want to make it look different so it's easier to spot then a quick thing you can do is edit the texture &amp;quot;wave.png&amp;quot; in your &amp;quot;/data/models/ships/myfirstship/&amp;quot; folder, perhaps just paint the white a bright red or something which will make it easy to tell that you've changed it.&lt;br /&gt;
 &lt;br /&gt;
Obviously we're not done turning all this effort into a mod yet, we've just copied a ship definition (&amp;quot;myfirstship.lua&amp;quot;) and the model data for it.&lt;br /&gt;
&lt;br /&gt;
So lets open a new folder window (if on Windows) and:&lt;br /&gt;
* Create a new folder somewhere you won't lose it, &amp;quot;My Documents&amp;quot; for windows users or the home folder on Linux etc, call it &amp;quot;tutorial&amp;quot; this time (to avoid confusion),&lt;br /&gt;
* In this folder create two more called &amp;quot;ships&amp;quot; and &amp;quot;models&amp;quot;,&lt;br /&gt;
* With the &amp;quot;models&amp;quot; folder create another folder called &amp;quot;ships&amp;quot;,&lt;br /&gt;
* by now you should have something that looks like:&lt;br /&gt;
** /tutorial/ships/&lt;br /&gt;
** /tutorial/models/ships/&lt;br /&gt;
* now you're going to _move_ the &amp;quot;myfirstships.lua&amp;quot; file you edited earlier out of the &amp;quot;/pioneer/data/ships/&amp;quot; into the folder &amp;quot;/tutorial/ships/&amp;quot; folder,&lt;br /&gt;
* next you must _move_ the folder &amp;quot;myfirstship&amp;quot; from &amp;quot;/pioneer/data/models/ships/&amp;quot; to &amp;quot;/tutorial/models/ships/&amp;quot;,&lt;br /&gt;
* now create a &amp;quot;readme.txt&amp;quot; file in the &amp;quot;tutorial&amp;quot; folder and add your name to it for now,&lt;br /&gt;
* Add everything inside the &amp;quot;tutorial&amp;quot; folder to a zip archive - to do this with 7-zip just selected everything inside the tutorial folder, right-click, open the 7-zip submenu and choose the &amp;quot;Add to archive&amp;quot;,&lt;br /&gt;
* Make sure that you choose to use &amp;quot;zip&amp;quot; compression and call the archive &amp;quot;myfirstship.zip&amp;quot;.&lt;br /&gt;
* Finally, put the file &amp;quot;myfirstship.zip&amp;quot; into the &amp;quot;mods&amp;quot; directory - this IS NOT usually where you extracted the Pioneer download.&lt;br /&gt;
** On Windows it will be in a folder under your &amp;quot;My Documents&amp;quot;/&amp;quot;Documents&amp;quot; directory for example:&lt;br /&gt;
*** &amp;quot;/My Documents/Pioneer/mods/&amp;quot;&lt;br /&gt;
* Now double check that you really did MOVE the files and folders, i.e; go back to where you first copied the wave files and folders and make sure that there aren't any &amp;quot;myfirsthip&amp;quot; files or folder there because they should now all be inside the zip file (and safely backed up in the &amp;quot;tutorial&amp;quot; folder).&lt;br /&gt;
** The reason this step is important is that you might have made a mistake with the zipping process and the mod is actually now broken, however it will look like it works because the original files are still being found by Pioneer in it's own data directory.&lt;br /&gt;
* Finally, the mod should have been packaged up into a zip, it should be in the &amp;quot;mods&amp;quot; directory, and you've made sure the Pioneer data folder is clean.&lt;br /&gt;
* Run Pioneer and make sure your mod works.&lt;br /&gt;
 &lt;br /&gt;
There are many steps that take just a second or two and lots of bits you can skip once you know what you're doing.&lt;br /&gt;
 &lt;br /&gt;
You can have as many ships within a single mod as you like, this technique just gives you a copy of the Wave to edit and play with.&lt;br /&gt;
&lt;br /&gt;
You can, and should, replace it with your own mesh and edit the parameters it uses for thrust and equipment etc.&lt;br /&gt;
&lt;br /&gt;
=== So how does this get my ships into the Pioneer download? ===&lt;br /&gt;
Easy, you create your mod, test it, try it out, share it via this forums download area so that others can play it, respond to criticisms to improve it and then ask the Core Team to include it in the main distribution.&lt;br /&gt;
&lt;br /&gt;
This is basically the same process that we go through for programmers submissions but without having to understand Git and GitHub.&lt;br /&gt;
&lt;br /&gt;
It's also more direct to players and fans of the game who often use several mods together to make Pioneer play how they want it too.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4741</id>
		<title>Missions and the mission list</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4741"/>
		<updated>2025-04-30T22:38:03Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Registering a mission type */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The player's mission list==&lt;br /&gt;
&lt;br /&gt;
Once the player has negotiated with your form, there might well be a mission in play. It could be a delivery, an assassination, a rush to tell somebody not to leave because so-and-so loves them... the possibilities are limited only by your creativity. The player needs a way to keep track of all the missions that they have agreed to undertake. Pioneer provides this through the player's mission screen, which they can access at any time using the F3 button, and looking at the missions tab. The content of this screen is controlled by some methods on the &amp;lt;code&amp;gt;Player&amp;lt;/code&amp;gt; object, which can always be found at &amp;lt;code&amp;gt;Game.player&amp;lt;/code&amp;gt;, and which inherits from &amp;lt;code&amp;gt;Ship&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Body&amp;lt;/code&amp;gt;. Missions are added to the screen using the &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; method. It takes a table of info, and returns an integer reference to that mission, which should be stored so that it can be updated or removed later. Below follows a typical use case from the [https://codedoc.pioneerspacesim.net/#LuaClass:Mission:New codedoc].&lt;br /&gt;
&lt;br /&gt;
Create a new mission and add it to the player’s mission list while retrieving the reference number:&lt;br /&gt;
&lt;br /&gt;
 ref = Mission.New({&lt;br /&gt;
     'type'      = 'Delivery', -- Must be a translatable token!&lt;br /&gt;
     'client'    = Character.New(),&lt;br /&gt;
     'due'       = Game.time + 3*24*60*60,       -- three days&lt;br /&gt;
     'reward'    = 123.45,&lt;br /&gt;
     'location'  = SystemPath:New(0,0,0,0,16),   -- Mars High, Sol&lt;br /&gt;
     'status'    = 'ACTIVE',&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
In practice, it might look more like this:&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 600, -- ten minutes' time&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
We don't recommend using &amp;lt;code&amp;gt;Game.player.frameBody.path&amp;lt;/code&amp;gt; here. We're only using it because it always returns something, whether docked or not. A real mission would probably use a space station here. For this demonstration we've generated 'Taxi' missions that are already a known mission type, registered by '''Taxi.lua''' who wouldn't know about it because the mission scripts remembers it's own missions and has no way of knowing about missions registered by another module. So far no modules use another modules mission type and we only use it here for the sake of demonstration.&lt;br /&gt;
&lt;br /&gt;
This creates a mission visible on the mission screen:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist3.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Registering a mission type==&lt;br /&gt;
Before a module can create missions that are visible in the player's mission list, it needs to register a mission type. It's a painless task.&lt;br /&gt;
&lt;br /&gt;
'''[https://codedoc.pioneerspacesim.net/#LuaClass:Mission:RegisterType LuaClass:Mission:RegisterType]'''&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType(typeid, display, onClick)&lt;br /&gt;
&lt;br /&gt;
Apart from a unique string and a translatable mission name, you have the option to pass a function that takes care of the mission info presented when you press the '''More info...''' button in the missions list. By convention, this function is named '''buildMissionDescription'''. This would typically happen at the very end of the module. Here is what it looks like at the end of [https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L513 '''DeliverPackage.lua''']&lt;br /&gt;
&lt;br /&gt;
 Event.Register(&amp;quot;onGameEnd&amp;quot;, onGameEnd)&lt;br /&gt;
 Event.Register(&amp;quot;onReputationChanged&amp;quot;, onReputationChanged)&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
 &lt;br /&gt;
 Serializer:Register(&amp;quot;DeliverPackage&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The next example is extended with a much scaled-down version of the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L401-https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L434 onShipDocked]''' function from the '''DeliverPackage''' module. Since these missions are recognized by a module in Pioneer already ('Taxi') they will 'probably' be handled at a later stage if we fail to remove them with this test mission. If you generate a new mission you must also handle removing it or the mission will remain in the game forever. There is no automatic logic, and no automatic removal. Your script must keep track of them. We'll wrap the action in a '''Timer''' function so we don't have to deal with the BBS form this time.&lt;br /&gt;
&lt;br /&gt;
 Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 ...&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Start the game and have a look under the '''Missions''' tab. After 2 seconds two missions are registered. One that should be finished in 10 seconds and another with a due time set 10 seconds after the first one. There is a second timer that checks in with the missions to see how it's going after 15 seconds. One should be completed successfully and the other will fail.&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function (ship, station)&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Missions Created:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist1.png]]&lt;br /&gt;
&lt;br /&gt;
1 Mission failed and 1 mission completed:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist2.png]]&lt;br /&gt;
&lt;br /&gt;
More info? If you look to the right on the mission list there is a button named '''More info...''' which will let you see more specific details of your mission. This will need to be specified in the mission script itself so if you press this button in the example above Pioneer will crash. The common name for the function you need to write in your script is '''buildMissionDescription'''. Here is what it looks like in '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L462-L490 DeliverPackage.lua]'''.&lt;br /&gt;
&lt;br /&gt;
==Mission description==&lt;br /&gt;
If you don't include the '''buildMissionDescription''' argument when you register the mission type, the mission list will not include a '''More info...''' button for the mission type. In the example above we used an existing mission type, '''Taxi''', but we didn't do it from within '''Taxi.lua''' so when '''More info...''' is pressed '''buildMissionDescription''' will be called in '''Taxi.lua''' but it will not now anything about the mission so the function will break. Try and comment out the entire '''buildMissionDescription''' in '''Taxi.lua''' and launch Pioneer once more. The Missions will register but now without the '''More info...''' button. To register a mission type without '''buildMissionDescription''' you simply leave it out.&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI)&lt;br /&gt;
&lt;br /&gt;
You can also declare a minimal '''buildMissionDescription''' that returns nil. This is fine. It will include the button which, if pressed, will just exit immediately and not try and open a new window.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI, buildMissionDescription)&lt;br /&gt;
&lt;br /&gt;
The function responsible for interpreting the mission description is '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/modules/info-view/04-missions.lua#L42-L77 drawMissionDescription()]''' in '''/data/pigui/modules/info-view/04-missions.lua'''. It takes a table '''desc''' as it's argument. A minimal working mission description could look something like the example below. We set '''desc.description''' to an empty string and '''desc.details''' to a table with only one element '''false'''. '''desc.client''' is set to '''mission.client'''. This only works as part of an actual mission script where you already have a registered mission with a client. As you can see the '''Mission Details''' title and the '''Go back''' button are generated automatically.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
Let's fixup the earlier example with it's own mission type and a working '''buildMissionDescription'''.&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Test&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Test&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
  &lt;br /&gt;
 Mission.RegisterType('Test', 'Test', buildMissionDescription)&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
[[File:Minimaldescription.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
That's basically it. Here follows a description of the elements you can add to '''buildMissionDescription'''. Try and expand our latest script on you own.&lt;br /&gt;
&lt;br /&gt;
===desc.description===&lt;br /&gt;
This is a string and will typically be based on the conversation from onChat.&lt;br /&gt;
&lt;br /&gt;
 desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===desc.client===&lt;br /&gt;
Takes a character as it's input and usually this means the character passed via the mission argument.&lt;br /&gt;
&lt;br /&gt;
 desc.client = mission.client&lt;br /&gt;
&lt;br /&gt;
===desc.details===&lt;br /&gt;
This is a table that is passed to '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/libs/text-table.lua#L15-L28 textTable.draw()]''' in '''data/pigui/libs/text-table.lua'''.&lt;br /&gt;
&lt;br /&gt;
 desc.details = {&lt;br /&gt;
     {&amp;quot;data here&amp;quot;, &amp;quot;more data&amp;quot;},&lt;br /&gt;
              ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====string====&lt;br /&gt;
A string will be presented in the form of a header with a delimiter underneath.&lt;br /&gt;
 &amp;quot;Just a string&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====table====&lt;br /&gt;
A table that takes strings and data/numbers in a key/value sort of way. Only one post in the table will cause a crash and any number of entries above the first two will be discarded.&lt;br /&gt;
&lt;br /&gt;
   {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
   {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
 --{&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
   {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;} -- Two entries in the table with one being just an empty string works.&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====spacing====&lt;br /&gt;
The key word '''false''' can be used to insert some padding.&lt;br /&gt;
&lt;br /&gt;
 false,&lt;br /&gt;
&lt;br /&gt;
===desc.location===&lt;br /&gt;
Add a button to set the mission location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.location = mission.location&lt;br /&gt;
&lt;br /&gt;
===desc.returnLocation===&lt;br /&gt;
Add a button to set the return location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.returnLocation = mission.domicile&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     desc.details = {&lt;br /&gt;
         &amp;quot;This will be a 'title' with a delimiter underneath&amp;quot;,&lt;br /&gt;
         {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
         {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
         false, -- some space.&lt;br /&gt;
      -- {&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
         {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
     }&lt;br /&gt;
  &lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Moremissiondetails.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Maintaining immersion==&lt;br /&gt;
&lt;br /&gt;
Fictionally, of course, the bulletin board is visible to any and all ships that dock at the space station, not just the player. It is important that bulletin board missions are not all scaled to the capabilities of the player. Delivery missions with unreasonable deadlines should not be ruled out. Neither should cargo missions requiring much more cargo space than the player's ship has, or combat missions for which the player is completely unqualified.&lt;br /&gt;
&lt;br /&gt;
These missions should deal with the player gracefully; either allowing them to fail, and providing consequences, or preventing them from being given the mission.&lt;br /&gt;
&lt;br /&gt;
It's also important, if your script serves many instances of a mission, to periodically clear away bulletin board adverts and place new ones. Not just those with obvious time constraints, but any others; the assumption that the player should make is that perhaps some other character has taken these missions.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4740</id>
		<title>Missions and the mission list</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4740"/>
		<updated>2025-04-30T07:08:12Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* The player's mission list */ I -&amp;gt; We&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The player's mission list==&lt;br /&gt;
&lt;br /&gt;
Once the player has negotiated with your form, there might well be a mission in play. It could be a delivery, an assassination, a rush to tell somebody not to leave because so-and-so loves them... the possibilities are limited only by your creativity. The player needs a way to keep track of all the missions that they have agreed to undertake. Pioneer provides this through the player's mission screen, which they can access at any time using the F3 button, and looking at the missions tab. The content of this screen is controlled by some methods on the &amp;lt;code&amp;gt;Player&amp;lt;/code&amp;gt; object, which can always be found at &amp;lt;code&amp;gt;Game.player&amp;lt;/code&amp;gt;, and which inherits from &amp;lt;code&amp;gt;Ship&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Body&amp;lt;/code&amp;gt;. Missions are added to the screen using the &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; method. It takes a table of info, and returns an integer reference to that mission, which should be stored so that it can be updated or removed later. Below follows a typical use case from the [https://codedoc.pioneerspacesim.net/#LuaClass:Mission:New codedoc].&lt;br /&gt;
&lt;br /&gt;
Create a new mission and add it to the player’s mission list while retrieving the reference number:&lt;br /&gt;
&lt;br /&gt;
 ref = Mission.New({&lt;br /&gt;
     'type'      = 'Delivery', -- Must be a translatable token!&lt;br /&gt;
     'client'    = Character.New(),&lt;br /&gt;
     'due'       = Game.time + 3*24*60*60,       -- three days&lt;br /&gt;
     'reward'    = 123.45,&lt;br /&gt;
     'location'  = SystemPath:New(0,0,0,0,16),   -- Mars High, Sol&lt;br /&gt;
     'status'    = 'ACTIVE',&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
In practice, it might look more like this:&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 600, -- ten minutes' time&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
We don't recommend using &amp;lt;code&amp;gt;Game.player.frameBody.path&amp;lt;/code&amp;gt; here. We're only using it because it always returns something, whether docked or not. A real mission would probably use a space station here. For this demonstration we've generated 'Taxi' missions that are already a known mission type, registered by '''Taxi.lua''' who wouldn't know about it because the mission scripts remembers it's own missions and has no way of knowing about missions registered by another module. So far no modules use another modules mission type and we only use it here for the sake of demonstration.&lt;br /&gt;
&lt;br /&gt;
This creates a mission visible on the mission screen:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist3.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Registering a mission type==&lt;br /&gt;
Before a module can create missions that are visible in the player's mission list, it needs to register a mission type. It's a painless task.&lt;br /&gt;
&lt;br /&gt;
'''[https://codedoc.pioneerspacesim.net/#LuaClass:Mission:RegisterType LuaClass:Mission:RegisterType]'''&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType(typeid, display, onClick)&lt;br /&gt;
&lt;br /&gt;
Apart from a unique string and a translatable mission name, you have the option to pass a function that takes care of the mission info presented when you press the '''More info...''' button in the missions list. By convention, this function is named '''buildMissionDescription'''. This would typically happen at the very end of the module. Here is what it looks like at the end of [https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L513 '''DeliverPackage.lua''']&lt;br /&gt;
&lt;br /&gt;
 Event.Register(&amp;quot;onGameEnd&amp;quot;, onGameEnd)&lt;br /&gt;
 Event.Register(&amp;quot;onReputationChanged&amp;quot;, onReputationChanged)&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
 &lt;br /&gt;
 Serializer:Register(&amp;quot;DeliverPackage&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The next example is extended with a much scaled-down version of the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L401-https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L434 onShipDocked]''' function from the '''DeliverPackage''' module. Since these missions are recognized by a module in Pioneer already ('Taxi') they will 'probably' be handled at a later stage if we fail to remove them with this test mission. If you generate a new mission type you must also handle removing it or the mission will remain in the game forever. There is no automatic logic, and no automatic removal. Your script must keep track of them. We'll wrap the action in a '''Timer''' function so we don't have to deal with the BBS form this time.&lt;br /&gt;
&lt;br /&gt;
 Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 ...&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Start the game and have a look under the '''Missions''' tab. After 2 seconds two missions are registered. One that should be finished in 10 seconds and another with a due time set 10 seconds after the first one. There is a second timer that checks in with the missions to see how it's going after 15 seconds. One should be completed successfully and the other will fail.&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function (ship, station)&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Missions Created:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist1.png]]&lt;br /&gt;
&lt;br /&gt;
1 Mission failed and 1 mission completed:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist2.png]]&lt;br /&gt;
&lt;br /&gt;
More info? If you look to the right on the mission list there is a button named '''More info...''' which will let you see more specific details of your mission. This will need to be specified in the mission script itself so if you press this button in the example above Pioneer will crash. The common name for the function you need to write in your script is '''buildMissionDescription'''. Here is what it looks like in '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L462-L490 DeliverPackage.lua]'''.&lt;br /&gt;
&lt;br /&gt;
==Mission description==&lt;br /&gt;
If you don't include the '''buildMissionDescription''' argument when you register the mission type, the mission list will not include a '''More info...''' button for the mission type. In the example above we used an existing mission type, '''Taxi''', but we didn't do it from within '''Taxi.lua''' so when '''More info...''' is pressed '''buildMissionDescription''' will be called in '''Taxi.lua''' but it will not now anything about the mission so the function will break. Try and comment out the entire '''buildMissionDescription''' in '''Taxi.lua''' and launch Pioneer once more. The Missions will register but now without the '''More info...''' button. To register a mission type without '''buildMissionDescription''' you simply leave it out.&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI)&lt;br /&gt;
&lt;br /&gt;
You can also declare a minimal '''buildMissionDescription''' that returns nil. This is fine. It will include the button which, if pressed, will just exit immediately and not try and open a new window.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI, buildMissionDescription)&lt;br /&gt;
&lt;br /&gt;
The function responsible for interpreting the mission description is '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/modules/info-view/04-missions.lua#L42-L77 drawMissionDescription()]''' in '''/data/pigui/modules/info-view/04-missions.lua'''. It takes a table '''desc''' as it's argument. A minimal working mission description could look something like the example below. We set '''desc.description''' to an empty string and '''desc.details''' to a table with only one element '''false'''. '''desc.client''' is set to '''mission.client'''. This only works as part of an actual mission script where you already have a registered mission with a client. As you can see the '''Mission Details''' title and the '''Go back''' button are generated automatically.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
Let's fixup the earlier example with it's own mission type and a working '''buildMissionDescription'''.&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Test&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Test&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
  &lt;br /&gt;
 Mission.RegisterType('Test', 'Test', buildMissionDescription)&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
[[File:Minimaldescription.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
That's basically it. Here follows a description of the elements you can add to '''buildMissionDescription'''. Try and expand our latest script on you own.&lt;br /&gt;
&lt;br /&gt;
===desc.description===&lt;br /&gt;
This is a string and will typically be based on the conversation from onChat.&lt;br /&gt;
&lt;br /&gt;
 desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===desc.client===&lt;br /&gt;
Takes a character as it's input and usually this means the character passed via the mission argument.&lt;br /&gt;
&lt;br /&gt;
 desc.client = mission.client&lt;br /&gt;
&lt;br /&gt;
===desc.details===&lt;br /&gt;
This is a table that is passed to '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/libs/text-table.lua#L15-L28 textTable.draw()]''' in '''data/pigui/libs/text-table.lua'''.&lt;br /&gt;
&lt;br /&gt;
 desc.details = {&lt;br /&gt;
     {&amp;quot;data here&amp;quot;, &amp;quot;more data&amp;quot;},&lt;br /&gt;
              ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====string====&lt;br /&gt;
A string will be presented in the form of a header with a delimiter underneath.&lt;br /&gt;
 &amp;quot;Just a string&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====table====&lt;br /&gt;
A table that takes strings and data/numbers in a key/value sort of way. Only one post in the table will cause a crash and any number of entries above the first two will be discarded.&lt;br /&gt;
&lt;br /&gt;
   {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
   {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
 --{&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
   {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;} -- Two entries in the table with one being just an empty string works.&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====spacing====&lt;br /&gt;
The key word '''false''' can be used to insert some padding.&lt;br /&gt;
&lt;br /&gt;
 false,&lt;br /&gt;
&lt;br /&gt;
===desc.location===&lt;br /&gt;
Add a button to set the mission location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.location = mission.location&lt;br /&gt;
&lt;br /&gt;
===desc.returnLocation===&lt;br /&gt;
Add a button to set the return location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.returnLocation = mission.domicile&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     desc.details = {&lt;br /&gt;
         &amp;quot;This will be a 'title' with a delimiter underneath&amp;quot;,&lt;br /&gt;
         {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
         {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
         false, -- some space.&lt;br /&gt;
      -- {&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
         {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
     }&lt;br /&gt;
  &lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Moremissiondetails.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Maintaining immersion==&lt;br /&gt;
&lt;br /&gt;
Fictionally, of course, the bulletin board is visible to any and all ships that dock at the space station, not just the player. It is important that bulletin board missions are not all scaled to the capabilities of the player. Delivery missions with unreasonable deadlines should not be ruled out. Neither should cargo missions requiring much more cargo space than the player's ship has, or combat missions for which the player is completely unqualified.&lt;br /&gt;
&lt;br /&gt;
These missions should deal with the player gracefully; either allowing them to fail, and providing consequences, or preventing them from being given the mission.&lt;br /&gt;
&lt;br /&gt;
It's also important, if your script serves many instances of a mission, to periodically clear away bulletin board adverts and place new ones. Not just those with obvious time constraints, but any others; the assumption that the player should make is that perhaps some other character has taken these missions.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4739</id>
		<title>Strings and translation</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4739"/>
		<updated>2025-04-23T01:17:58Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Lua Standard Library - String manipulation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Internationalization==&lt;br /&gt;
Recommended reading: [https://dev.pioneerspacesim.net/contribute/translations Pioneer translators wikipage]&lt;br /&gt;
&lt;br /&gt;
It is important that scripters are familiar with the translation system. In the previous example '''hello_world.lua''' used strings directly like &amp;lt;code&amp;gt;print(&amp;quot;Hello, World!&amp;quot;)&amp;lt;/code&amp;gt;. In order for Pioneer to be translated we instead use tokens and calls on the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module to substitute it for the string in the chosen language. The translated strings are stored together with their 'key' in json format, one language per file that are kept in the [https://github.com/pioneerspacesim/pioneer/tree/master/data/lang data/lang/] directory. If the script file in which they are used is from the '''data/modules''' directory they have names starting with '''module-''' and ending with the name of the module written in one word, in small caps, and with the '.lua' ending omitted. The final translation from English to other languages is done on Pioneer's project page at [https://www.transifex.com/pioneer/pioneer/ Transifex], a dedicated online translation tool. The translations are committed to the pioneer/master branch on GitHub by a bot and are not to be edited manually apart from the '''en.json''' files.&lt;br /&gt;
&lt;br /&gt;
==Translating strings==&lt;br /&gt;
&lt;br /&gt;
Below is a minimal example with one string added into the translation system. It takes two files, one in '''data/modules''' and one in '''data/lang/module-hello'''. The print statement isn't wrapped in a function so it will be executed when the '''hello.lua''' is being parsed on startup. You will have to scroll through the command-line history to find it interleaved with the other output.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/hello.lua'''.&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(l.HELLO)&lt;br /&gt;
&lt;br /&gt;
And a corresponding file containing the translated string, and a description field shown to the translator, giving the context. &lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-hello/en.json'''&lt;br /&gt;
 {&lt;br /&gt;
     &amp;quot;HELLO&amp;quot;: {&lt;br /&gt;
         &amp;quot;description&amp;quot;: &amp;quot;A classic message.&amp;quot;,&lt;br /&gt;
         &amp;quot;message&amp;quot;: &amp;quot;Hello World!&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In short, any script containing strings that needs translation needs to load the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module:&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
Then load the translation resource they want to use in their module, often just named &amp;lt;code&amp;gt;'l'&amp;lt;/code&amp;gt; as in:&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
You may load more than one translation resource in your script but they will need to have unique names:&lt;br /&gt;
 local lh = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 local lg = Lang.GetResource(&amp;quot;module-goodbye&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Working with strings==&lt;br /&gt;
&lt;br /&gt;
Recommended chapters from [https://www.lua.org/pil/contents.html Programming in Lua (first edition) ]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/2.4.html 2.4 – Strings]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/3.4.html 3.4 – Concatenation]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/20.html 20 – The String Library]&amp;lt;br&amp;gt;&lt;br /&gt;
And from the [https://www.lua.org/manual/5.2/ Lua 5.2 Reference Manual]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/manual/5.2/manual.html#6.4 6.4 – String Manipulation]&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Concatenation===&lt;br /&gt;
&lt;br /&gt;
Instead of serving ready made sentences we can stitch them together with the Lua string concatenation operator &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt;. The following codelines result in the same output.&lt;br /&gt;
 print(&amp;quot;Hello Clarice!&amp;quot;)&lt;br /&gt;
 print(&amp;quot;Hello&amp;quot; .. &amp;quot; &amp;quot; .. &amp;quot;Clarice!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
This could be created by the more generic:&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local NameGen = require 'NameGen'&lt;br /&gt;
 &lt;br /&gt;
 local GREETING = 'Hello'&lt;br /&gt;
 local FBIAGENT = NameGen.Surname()&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship == Game.player then&lt;br /&gt;
         Comms.Message(GREETING .. ' ' .. FBIAGENT .. '!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; operator is used all over the codebase. Try a search for its occurrence. On Linux I would do &amp;lt;code&amp;gt;grep -R &amp;quot; \.\. &amp;quot; data/modules/ data/libs/ data/pigui/&amp;lt;/code&amp;gt; and that will render a lot of output.&lt;br /&gt;
&lt;br /&gt;
===Token concatenation===&lt;br /&gt;
&lt;br /&gt;
You concatenate strings but you also concatenate the string tokens. In the example below from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/modules/DonateToCranks/DonateToCranks.lua#L14-L21 DonateToCranks.lua]''' we see the table &amp;lt;code&amp;gt;flavours&amp;lt;/code&amp;gt; being populated by strings from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/lang/module-donatetocranks/en.json data/lang/module-donatetocranks/en.lua]'''&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 for i = 0,9 do&lt;br /&gt;
 	table.insert(flavours, {&lt;br /&gt;
 		title     = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE&amp;quot;],&lt;br /&gt;
 		desc      = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_DESC&amp;quot;],&lt;br /&gt;
 		message   = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_MESSAGE&amp;quot;],&lt;br /&gt;
 	})&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
The first string in the table is given the value of &amp;lt;code&amp;gt;l[FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE]&amp;lt;/code&amp;gt; is concatenated to &amp;lt;code&amp;gt;l[FLAVOUR_0_ADTITLE]&amp;lt;/code&amp;gt; which corresponds to the first string in the language module which is seen below.&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;FLAVOUR_0_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE!&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_DESC&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;The Church of The Celestial Flying Spaghetti Monster needs YOUR money to spread the word of god.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_MESSAGE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Please select an amount to donate to the Church of the Celestial Flying Spaghetti Monster.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_1_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 &lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
===Lua Standard Library - String manipulation===&lt;br /&gt;
&lt;br /&gt;
I would start to search the '''data/''' register for ''' 'string' '''. to get a feeling for how it's used. Again on Linux i would use the 'grep' command. &amp;lt;code&amp;gt;grep -R &amp;quot;string\.&amp;quot; data/&amp;lt;/code&amp;gt;. You will see that the two most frequently used functions is &amp;lt;code&amp;gt;string.format()&amp;lt;/code&amp;gt;, which is a part of the lua standard library, and &amp;lt;code&amp;gt;string.interp()&amp;lt;/code&amp;gt;, which is a Pioneer global function residing in '''data/libs/autoload.lua''', and is described in the next paragraph about [https://wiki.pioneerspacesim.net/wiki/Strings_and_translation#String_variables String variables].&lt;br /&gt;
&lt;br /&gt;
Some examples from the Lua standard library:&lt;br /&gt;
 Comms.Message(string.lower('wHaT cAsE sHoULd I usE?'))&lt;br /&gt;
&lt;br /&gt;
Maybe not the most easy function to fit into Pioneer, but it's there:&lt;br /&gt;
 Comms.Message('Today is opposite day!')&lt;br /&gt;
 Comms.Message(string.reverse('No, today is not opposite day!'))&lt;br /&gt;
&lt;br /&gt;
A more complex example from '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/pigui/modules/hyperjump-planner.lua#L141 data/pigui/modules/hyperjump-planner.lua]'''&amp;lt;br&amp;gt;&lt;br /&gt;
Here &amp;lt;code&amp;gt;string.format(&amp;quot;%.2f&amp;quot;, distance)&amp;lt;/code&amp;gt; is used to set the number of decimals in the jump to two. The units are translatable but the system name isn't (jump_sys.name). '''lc''' is used as the short name for the '''core''' translation resource.&lt;br /&gt;
&lt;br /&gt;
 textLine = jumpIndex ..&amp;quot;: &amp;quot;.. jump_sys.name .. &amp;quot; (&amp;quot; .. string.format(&amp;quot;%.2f&amp;quot;, distance) .. lc.UNIT_LY .. &amp;quot; - &amp;quot; .. fuel .. lc.UNIT_TONNES..&amp;quot;)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here is what the final formatted text looks like (right side, two selected jump routes):&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:hyperjumpplanner.png]]&lt;br /&gt;
&lt;br /&gt;
===PiGUI - Format===&lt;br /&gt;
&lt;br /&gt;
Pioneer has it's own set of formatting functions accessible through PiGUI, our UI. Some of it is realised on the C++ side and described in codedoc [https://codedoc.pioneerspacesim.net/#CClass:Format here]. Most of the functions, however, are strictly Lua and reside in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/libs/text.lua#L146-L385 data/pigui/libs/text.lua]. You access the functions with '''require''' and make sure that '''PiGUI''' is in the scope.&lt;br /&gt;
&lt;br /&gt;
 local ui = require 'pigui'&lt;br /&gt;
 local Format = require 'Format'&lt;br /&gt;
&lt;br /&gt;
Here is an example from the System overview. The table to the right is relying heavily on the Format functions.&lt;br /&gt;
[[File:Systemoverview.png]]&lt;br /&gt;
&lt;br /&gt;
The code responsible for the table is here in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/modules/system-view-ui.lua#L811-L840 data/pigui/modules/system-view-ui.lua] with an excerpt showed below. The examples below take 1 or two arguments and the lines ending with 'or nil' is what makes the System overview info window omit values that don't apply to the selected system body.&lt;br /&gt;
&lt;br /&gt;
 ...&lt;br /&gt;
 { name = lc.ESCAPE_VELOCITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Speed(body.escapeVelocity , true) or nil },&lt;br /&gt;
 { name = lc.MEAN_DENSITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Mass(body.meanDensity)..&amp;quot;/m³&amp;quot; or nil },&lt;br /&gt;
 { name = lc.ORBITAL_PERIOD, icon = icons.body_orbit_period,&lt;br /&gt;
     value = op and op &amp;gt; 0 and ui.Format.Duration(op, 2) or nil },&lt;br /&gt;
 { name = lc.DAY_LENGTH, icon = icons.body_day_length,&lt;br /&gt;
     value = rp &amp;gt; 0 and ui.Format.Duration(rp, 2) or nil },&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
==String variables==&lt;br /&gt;
&lt;br /&gt;
When you want a string to present variable data such as numbers and names you use the &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; function. &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; accepts a table '{}' and the corresponding keys are then embedded in the translated strings enclosed by curly brackets '{}'.&lt;br /&gt;
&lt;br /&gt;
Using ''''module-stationrefuelling'''' we can do:&lt;br /&gt;
 print(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = 'JFK', fee = 50}))&lt;br /&gt;
&lt;br /&gt;
Example from the actual StationRefuelling module:&lt;br /&gt;
&lt;br /&gt;
'''data/modules/StationRefuelling/'''&lt;br /&gt;
 Comms.Message(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = station.label,fee = Format.Money(fee)}))&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/en.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Welcome to {station}. Your landing fee of {fee} has been deducted.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/it.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Benvenuti a {station}. Vi è stata dedotta una tassa di atterraggio di {fee}.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Mission flavours==&lt;br /&gt;
&lt;br /&gt;
A script can define a mission and then place many instances of it onto many bulletin boards. A script that introduces many instances in the game should provide some variety; it would harm immersion if, for examle, all delivery missions were worded identically. The easiest way to achieve multiple versions, or &amp;quot;flavours&amp;quot;, is by just making a number of different strings and then choosing between them with a simple random function. This is how the [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L18:L48 deny] and [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L282:L320 pirate taunt] messages are done in the DeliverPackage module.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/DeliverPackage/DeliverPackage.lua'''&lt;br /&gt;
 local num_deny = 8&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 if not qualified then&lt;br /&gt;
     local introtext = l[&amp;quot;DENY_&amp;quot;..Engine.rand:Integer(1,num_deny)-1]&lt;br /&gt;
         form:SetMessage(introtext)&lt;br /&gt;
     return&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-deliverpackage/en.json'''&lt;br /&gt;
 &amp;quot;DENY_0&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm sorry, but I don't think I can trust you with this delivery.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;DENY_1&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Come back when you have some qualifications.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;DENY_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm not willing to risk this delivery on someone without any qualifications like you.&amp;quot;&lt;br /&gt;
You don't have to use a string variable that has been passed to the translation file. The pirate taunts, for example, have access to the clients name and the destination of the package but this information is only used in some of the strings.&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;That package isn't going to reach its destination today.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_8&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;You're not getting to {location} today!&amp;quot;&lt;br /&gt;
Another way to add variation is to have a table of strings that are variations of a theme, a flavour. In the example below, again from the DeliverPackage module, you have two of the lines from '''FLAVOUR_0''' AND '''FLAVOUR_1''' compared.&lt;br /&gt;
 &amp;quot;FLAVOUR_0_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;GOING TO the {system} system? Money paid for delivery of a small package.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_0_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Unacceptable! You took forever over that delivery. I'm not willing to pay you.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;FLAVOUR_1_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;WANTED. Delivery of a package to the {system} system.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_1_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm frustrated by the late delivery of my package, and I refuse to pay you.&amp;quot;&lt;br /&gt;
There are ten flavours in DeliverPackage of five strings each, each forming a short story line. They are static in that you only see lines from within the same flavour. You know what lines will follow if you accept the mission and after a short time of playing you know this already from seeing the ad if delivering things is your forte. It's hard to share strings in between the flavours as they would need to be much more similar. It's a trade off. You could build a very complex interaction with the customers but in the end it needs to be translatable.&lt;br /&gt;
&lt;br /&gt;
Generally, the more specific the text is, the stranger it will appear to see it multiple times in the game. For example, consider the following two examples:&lt;br /&gt;
 - &amp;quot;Contract is to move X tonnes of Y to system Z&amp;quot;&lt;br /&gt;
 - &amp;quot;My uncle's cat got sick, so I can't travel right now, so I need you to take my contract for me, to move X tonnes of Y to my home system Z&amp;quot;&lt;br /&gt;
The former might not need any flavours, as it's unpersonal, and very general. Seeing the second string multiple times does break immersion. Solution is to not show it often, either by making the mission rare / single occurance (no script to date does this) or have many flavours, making probability of the same twice low.&lt;br /&gt;
&lt;br /&gt;
==Trailing commas==&lt;br /&gt;
&lt;br /&gt;
The two most common text based files you will come across when you work with module scripting are Lua and JSON. While JSON is strictly used for information, Lua is used most as a scripting language but in some instances it's used for data. See for instance '''data/modules/CargoRun/CargoTypes.lua'''. It is basically just tables of data. In any way, we have used both in this chapter and it's probably a good time to address a &lt;br /&gt;
difference between the two that could otherwise create confusion. Trailing commas after the last element in a table. The short version is: In Lua, you can use trailing commas on the last element in a table. In JSON you cannot.&lt;br /&gt;
&lt;br /&gt;
===Lua===&lt;br /&gt;
&lt;br /&gt;
Here are the last four ladies in the Hawaiian culture file. '''data/culture/haw.lua'''. These are the files we stitch together characters/clients names from. As you see, the last element, ''''Wanaao'''' has a trailing comma after her. This is perfectly fine.&lt;br /&gt;
&lt;br /&gt;
    'Pohaku',&lt;br /&gt;
    'Oke',&lt;br /&gt;
    'Wailani',&lt;br /&gt;
    'Wanaao',         -- Lua. A comma here is fine!&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===JSON===&lt;br /&gt;
&lt;br /&gt;
Here we see the last two elements in the '''data/lang/core/en.json'''. It keeps some of the strings used in the game and every language has it's own version of it. Trailing commas in JSON is explicitly forbidden in the official description of the file format. There are derivative interpreters that are relaxed and allow trailing commas but this is not the case with the one used in Pioneer.&lt;br /&gt;
&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;ZOOM_IN&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Zoom in&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;ZOOM_OUT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Zoom out&amp;quot;&lt;br /&gt;
   }                   -- JSON. A comma here is NOT fine!&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The fastest way to find out what could happen is to simply just put a comma in that place and start Pioneer. Open the '''System oveview''' and click on a planet and look at the data presented in the pop up window. There is now a lot of '''[NO_JSON]''' data presented in there.&lt;br /&gt;
&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Zoom out&amp;quot;&lt;br /&gt;
   },                   -- ;o -onoh!&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Also, another difference between the file formats is that the comment I added above in the Lua file is a real Lua comment and would work and be ignored by the language. The  ;o emoji comment in the JSON file wouldn't work. JSON doesn't have comments in it. You would have to assign a separate element to be used as a comment like the translation file above which has elements beginning with '''&amp;quot;description&amp;quot;:'''.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4737</id>
		<title>Strings and translation</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4737"/>
		<updated>2025-04-07T17:36:16Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Trailing commas */ fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Internationalization==&lt;br /&gt;
Recommended reading: [https://dev.pioneerspacesim.net/contribute/translations Pioneer translators wikipage]&lt;br /&gt;
&lt;br /&gt;
It is important that scripters are familiar with the translation system. In the previous example '''hello_world.lua''' used strings directly like &amp;lt;code&amp;gt;print(&amp;quot;Hello, World!&amp;quot;)&amp;lt;/code&amp;gt;. In order for Pioneer to be translated we instead use tokens and calls on the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module to substitute it for the string in the chosen language. The translated strings are stored together with their 'key' in json format, one language per file that are kept in the [https://github.com/pioneerspacesim/pioneer/tree/master/data/lang data/lang/] directory. If the script file in which they are used is from the '''data/modules''' directory they have names starting with '''module-''' and ending with the name of the module written in one word, in small caps, and with the '.lua' ending omitted. The final translation from English to other languages is done on Pioneer's project page at [https://www.transifex.com/pioneer/pioneer/ Transifex], a dedicated online translation tool. The translations are committed to the pioneer/master branch on GitHub by a bot and are not to be edited manually apart from the '''en.json''' files.&lt;br /&gt;
&lt;br /&gt;
==Translating strings==&lt;br /&gt;
&lt;br /&gt;
Below is a minimal example with one string added into the translation system. It takes two files, one in '''data/modules''' and one in '''data/lang/module-hello'''. The print statement isn't wrapped in a function so it will be executed when the '''hello.lua''' is being parsed on startup. You will have to scroll through the command-line history to find it interleaved with the other output.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/hello.lua'''.&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(l.HELLO)&lt;br /&gt;
&lt;br /&gt;
And a corresponding file containing the translated string, and a description field shown to the translator, giving the context. &lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-hello/en.json'''&lt;br /&gt;
 {&lt;br /&gt;
     &amp;quot;HELLO&amp;quot;: {&lt;br /&gt;
         &amp;quot;description&amp;quot;: &amp;quot;A classic message.&amp;quot;,&lt;br /&gt;
         &amp;quot;message&amp;quot;: &amp;quot;Hello World!&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In short, any script containing strings that needs translation needs to load the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module:&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
Then load the translation resource they want to use in their module, often just named &amp;lt;code&amp;gt;'l'&amp;lt;/code&amp;gt; as in:&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
You may load more than one translation resource in your script but they will need to have unique names:&lt;br /&gt;
 local lh = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 local lg = Lang.GetResource(&amp;quot;module-goodbye&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Working with strings==&lt;br /&gt;
&lt;br /&gt;
Recommended chapters from [https://www.lua.org/pil/contents.html Programming in Lua (first edition) ]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/2.4.html 2.4 – Strings]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/3.4.html 3.4 – Concatenation]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/20.html 20 – The String Library]&amp;lt;br&amp;gt;&lt;br /&gt;
And from the [https://www.lua.org/manual/5.2/ Lua 5.2 Reference Manual]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/manual/5.2/manual.html#6.4 6.4 – String Manipulation]&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Concatenation===&lt;br /&gt;
&lt;br /&gt;
Instead of serving ready made sentences we can stitch them together with the Lua string concatenation operator &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt;. The following codelines result in the same output.&lt;br /&gt;
 print(&amp;quot;Hello Clarice!&amp;quot;)&lt;br /&gt;
 print(&amp;quot;Hello&amp;quot; .. &amp;quot; &amp;quot; .. &amp;quot;Clarice!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
This could be created by the more generic:&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local NameGen = require 'NameGen'&lt;br /&gt;
 &lt;br /&gt;
 local GREETING = 'Hello'&lt;br /&gt;
 local FBIAGENT = NameGen.Surname()&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship == Game.player then&lt;br /&gt;
         Comms.Message(GREETING .. ' ' .. FBIAGENT .. '!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; operator is used all over the codebase. Try a search for its occurrence. On Linux I would do &amp;lt;code&amp;gt;grep -R &amp;quot; \.\. &amp;quot; data/modules/ data/libs/ data/pigui/&amp;lt;/code&amp;gt; and that will render a lot of output.&lt;br /&gt;
&lt;br /&gt;
===Token concatenation===&lt;br /&gt;
&lt;br /&gt;
You concatenate strings but you also concatenate the string tokens. In the example below from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/modules/DonateToCranks/DonateToCranks.lua#L14-L21 DonateToCranks.lua]''' we see the table &amp;lt;code&amp;gt;flavours&amp;lt;/code&amp;gt; being populated by strings from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/lang/module-donatetocranks/en.json data/lang/module-donatetocranks/en.lua]'''&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 for i = 0,9 do&lt;br /&gt;
 	table.insert(flavours, {&lt;br /&gt;
 		title     = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE&amp;quot;],&lt;br /&gt;
 		desc      = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_DESC&amp;quot;],&lt;br /&gt;
 		message   = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_MESSAGE&amp;quot;],&lt;br /&gt;
 	})&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
The first string in the table is given the value of &amp;lt;code&amp;gt;l[FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE]&amp;lt;/code&amp;gt; is concatenated to &amp;lt;code&amp;gt;l[FLAVOUR_0_ADTITLE]&amp;lt;/code&amp;gt; which corresponds to the first string in the language module which is seen below.&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;FLAVOUR_0_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE!&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_DESC&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;The Church of The Celestial Flying Spaghetti Monster needs YOUR money to spread the word of god.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_MESSAGE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Please select an amount to donate to the Church of the Celestial Flying Spaghetti Monster.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_1_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 &lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
===Lua Standard Library - String manipulation===&lt;br /&gt;
&lt;br /&gt;
I would start to search the '''data/''' register for ''' 'string' '''. to get a feeling for how it's used. Again on Linux i would use the 'grep' command. &amp;lt;code&amp;gt;grep -R &amp;quot;string\.&amp;quot; data/&amp;lt;/code&amp;gt;. You will see that the two most frequently used functions is &amp;lt;code&amp;gt;string.format()&amp;lt;/code&amp;gt;, which is a part of the lua standard library, and &amp;lt;code&amp;gt;string.interp()&amp;lt;/code&amp;gt;, which is a Pioneer global function residing in '''data/libs/autoload.lua''', and is described in the next paragraph about [https://wiki.pioneerspacesim.net/wiki/Strings_and_translation#String_variables String variables].&lt;br /&gt;
&lt;br /&gt;
Some examples from the Lua standard library:&lt;br /&gt;
 Comms.Message(string.lower('wHaT cAsE sHoULd I usE?'))&lt;br /&gt;
&lt;br /&gt;
Maybe not the most easy function to fit into Pioneer, but it's there:&lt;br /&gt;
 Comms.Message('Today is opposite day!')&lt;br /&gt;
 Comms.Message(string.reverse('No, today is not opposite day!'))&lt;br /&gt;
&lt;br /&gt;
A more complex example from '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/pigui/modules/hyperjump-planner.lua#L141 data/pigui/modules/hyperjump-planner.lua]'''&amp;lt;br&amp;gt;&lt;br /&gt;
Here &amp;lt;code&amp;gt;string.format(&amp;quot;%.2f&amp;quot;, distance)&amp;lt;/code&amp;gt; is used to set the number of decimals in the jump to two. The units are translatable but the system name off course isn't (jump_sys.name). '''lc''' is used as the short name for the '''core''' translation resource.&lt;br /&gt;
&lt;br /&gt;
 textLine = jumpIndex ..&amp;quot;: &amp;quot;.. jump_sys.name .. &amp;quot; (&amp;quot; .. string.format(&amp;quot;%.2f&amp;quot;, distance) .. lc.UNIT_LY .. &amp;quot; - &amp;quot; .. fuel .. lc.UNIT_TONNES..&amp;quot;)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here is what the final formatted text looks like (right side, two selected jump routes):&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:hyperjumpplanner.png]]&lt;br /&gt;
&lt;br /&gt;
===PiGUI - Format===&lt;br /&gt;
&lt;br /&gt;
Pioneer has it's own set of formatting functions accessible through PiGUI, our UI. Some of it is realised on the C++ side and described in codedoc [https://codedoc.pioneerspacesim.net/#CClass:Format here]. Most of the functions, however, are strictly Lua and reside in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/libs/text.lua#L146-L385 data/pigui/libs/text.lua]. You access the functions with '''require''' and make sure that '''PiGUI''' is in the scope.&lt;br /&gt;
&lt;br /&gt;
 local ui = require 'pigui'&lt;br /&gt;
 local Format = require 'Format'&lt;br /&gt;
&lt;br /&gt;
Here is an example from the System overview. The table to the right is relying heavily on the Format functions.&lt;br /&gt;
[[File:Systemoverview.png]]&lt;br /&gt;
&lt;br /&gt;
The code responsible for the table is here in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/modules/system-view-ui.lua#L811-L840 data/pigui/modules/system-view-ui.lua] with an excerpt showed below. The examples below take 1 or two arguments and the lines ending with 'or nil' is what makes the System overview info window omit values that don't apply to the selected system body.&lt;br /&gt;
&lt;br /&gt;
 ...&lt;br /&gt;
 { name = lc.ESCAPE_VELOCITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Speed(body.escapeVelocity , true) or nil },&lt;br /&gt;
 { name = lc.MEAN_DENSITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Mass(body.meanDensity)..&amp;quot;/m³&amp;quot; or nil },&lt;br /&gt;
 { name = lc.ORBITAL_PERIOD, icon = icons.body_orbit_period,&lt;br /&gt;
     value = op and op &amp;gt; 0 and ui.Format.Duration(op, 2) or nil },&lt;br /&gt;
 { name = lc.DAY_LENGTH, icon = icons.body_day_length,&lt;br /&gt;
     value = rp &amp;gt; 0 and ui.Format.Duration(rp, 2) or nil },&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
==String variables==&lt;br /&gt;
&lt;br /&gt;
When you want a string to present variable data such as numbers and names you use the &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; function. &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; accepts a table '{}' and the corresponding keys are then embedded in the translated strings enclosed by curly brackets '{}'.&lt;br /&gt;
&lt;br /&gt;
Using ''''module-stationrefuelling'''' we can do:&lt;br /&gt;
 print(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = 'JFK', fee = 50}))&lt;br /&gt;
&lt;br /&gt;
Example from the actual StationRefuelling module:&lt;br /&gt;
&lt;br /&gt;
'''data/modules/StationRefuelling/'''&lt;br /&gt;
 Comms.Message(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = station.label,fee = Format.Money(fee)}))&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/en.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Welcome to {station}. Your landing fee of {fee} has been deducted.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/it.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Benvenuti a {station}. Vi è stata dedotta una tassa di atterraggio di {fee}.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Mission flavours==&lt;br /&gt;
&lt;br /&gt;
A script can define a mission and then place many instances of it onto many bulletin boards. A script that introduces many instances in the game should provide some variety; it would harm immersion if, for examle, all delivery missions were worded identically. The easiest way to achieve multiple versions, or &amp;quot;flavours&amp;quot;, is by just making a number of different strings and then choosing between them with a simple random function. This is how the [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L18:L48 deny] and [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L282:L320 pirate taunt] messages are done in the DeliverPackage module.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/DeliverPackage/DeliverPackage.lua'''&lt;br /&gt;
 local num_deny = 8&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 if not qualified then&lt;br /&gt;
     local introtext = l[&amp;quot;DENY_&amp;quot;..Engine.rand:Integer(1,num_deny)-1]&lt;br /&gt;
         form:SetMessage(introtext)&lt;br /&gt;
     return&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-deliverpackage/en.json'''&lt;br /&gt;
 &amp;quot;DENY_0&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm sorry, but I don't think I can trust you with this delivery.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;DENY_1&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Come back when you have some qualifications.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;DENY_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm not willing to risk this delivery on someone without any qualifications like you.&amp;quot;&lt;br /&gt;
You don't have to use a string variable that has been passed to the translation file. The pirate taunts, for example, have access to the clients name and the destination of the package but this information is only used in some of the strings.&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;That package isn't going to reach its destination today.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_8&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;You're not getting to {location} today!&amp;quot;&lt;br /&gt;
Another way to add variation is to have a table of strings that are variations of a theme, a flavour. In the example below, again from the DeliverPackage module, you have two of the lines from '''FLAVOUR_0''' AND '''FLAVOUR_1''' compared.&lt;br /&gt;
 &amp;quot;FLAVOUR_0_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;GOING TO the {system} system? Money paid for delivery of a small package.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_0_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Unacceptable! You took forever over that delivery. I'm not willing to pay you.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;FLAVOUR_1_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;WANTED. Delivery of a package to the {system} system.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_1_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm frustrated by the late delivery of my package, and I refuse to pay you.&amp;quot;&lt;br /&gt;
There are ten flavours in DeliverPackage of five strings each, each forming a short story line. They are static in that you only see lines from within the same flavour. You know what lines will follow if you accept the mission and after a short time of playing you know this already from seeing the ad if delivering things is your forte. It's hard to share strings in between the flavours as they would need to be much more similar. It's a trade off. You could build a very complex interaction with the customers but in the end it needs to be translatable.&lt;br /&gt;
&lt;br /&gt;
Generally, the more specific the text is, the stranger it will appear to see it multiple times in the game. For example, consider the following two examples:&lt;br /&gt;
 - &amp;quot;Contract is to move X tonnes of Y to system Z&amp;quot;&lt;br /&gt;
 - &amp;quot;My uncle's cat got sick, so I can't travel right now, so I need you to take my contract for me, to move X tonnes of Y to my home system Z&amp;quot;&lt;br /&gt;
The former might not need any flavours, as it's unpersonal, and very general. Seeing the second string multiple times does break immersion. Solution is to not show it often, either by making the mission rare / single occurance (no script to date does this) or have many flavours, making probability of the same twice low.&lt;br /&gt;
&lt;br /&gt;
==Trailing commas==&lt;br /&gt;
&lt;br /&gt;
The two most common text based files you will come across when you work with module scripting are Lua and JSON. While JSON is strictly used for information, Lua is used most as a scripting language but in some instances it's used for data. See for instance '''data/modules/CargoRun/CargoTypes.lua'''. It is basically just tables of data. In any way, we have used both in this chapter and it's probably a good time to address a &lt;br /&gt;
difference between the two that could otherwise create confusion. Trailing commas after the last element in a table. The short version is: In Lua, you can use trailing commas on the last element in a table. In JSON you cannot.&lt;br /&gt;
&lt;br /&gt;
===Lua===&lt;br /&gt;
&lt;br /&gt;
Here are the last four ladies in the Hawaiian culture file. '''data/culture/haw.lua'''. These are the files we stitch together characters/clients names from. As you see, the last element, ''''Wanaao'''' has a trailing comma after her. This is perfectly fine.&lt;br /&gt;
&lt;br /&gt;
    'Pohaku',&lt;br /&gt;
    'Oke',&lt;br /&gt;
    'Wailani',&lt;br /&gt;
    'Wanaao',         -- Lua. A comma here is fine!&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===JSON===&lt;br /&gt;
&lt;br /&gt;
Here we see the last two elements in the '''data/lang/core/en.json'''. It keeps some of the strings used in the game and every language has it's own version of it. Trailing commas in JSON is explicitly forbidden in the official description of the file format. There are derivative interpreters that are relaxed and allow trailing commas but this is not the case with the one used in Pioneer.&lt;br /&gt;
&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;ZOOM_IN&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Zoom in&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;ZOOM_OUT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Zoom out&amp;quot;&lt;br /&gt;
   }                   -- JSON. A comma here is NOT fine!&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The fastest way to find out what could happen is to simply just put a comma in that place and start Pioneer. Open the '''System oveview''' and click on a planet and look at the data presented in the pop up window. There is now a lot of '''[NO_JSON]''' data presented in there.&lt;br /&gt;
&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Zoom out&amp;quot;&lt;br /&gt;
   },                   -- ;o -onoh!&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Also, another difference between the file formats is that the comment I added above in the Lua file is a real Lua comment and would work and be ignored by the language. The  ;o emoji comment in the JSON file wouldn't work. JSON doesn't have comments in it. You would have to assign a separate element to be used as a comment like the translation file above which has elements beginning with '''&amp;quot;description&amp;quot;:'''.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4736</id>
		<title>Strings and translation</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4736"/>
		<updated>2025-04-07T17:21:02Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: JSON, Lua and trailing commas&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Internationalization==&lt;br /&gt;
Recommended reading: [https://dev.pioneerspacesim.net/contribute/translations Pioneer translators wikipage]&lt;br /&gt;
&lt;br /&gt;
It is important that scripters are familiar with the translation system. In the previous example '''hello_world.lua''' used strings directly like &amp;lt;code&amp;gt;print(&amp;quot;Hello, World!&amp;quot;)&amp;lt;/code&amp;gt;. In order for Pioneer to be translated we instead use tokens and calls on the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module to substitute it for the string in the chosen language. The translated strings are stored together with their 'key' in json format, one language per file that are kept in the [https://github.com/pioneerspacesim/pioneer/tree/master/data/lang data/lang/] directory. If the script file in which they are used is from the '''data/modules''' directory they have names starting with '''module-''' and ending with the name of the module written in one word, in small caps, and with the '.lua' ending omitted. The final translation from English to other languages is done on Pioneer's project page at [https://www.transifex.com/pioneer/pioneer/ Transifex], a dedicated online translation tool. The translations are committed to the pioneer/master branch on GitHub by a bot and are not to be edited manually apart from the '''en.json''' files.&lt;br /&gt;
&lt;br /&gt;
==Translating strings==&lt;br /&gt;
&lt;br /&gt;
Below is a minimal example with one string added into the translation system. It takes two files, one in '''data/modules''' and one in '''data/lang/module-hello'''. The print statement isn't wrapped in a function so it will be executed when the '''hello.lua''' is being parsed on startup. You will have to scroll through the command-line history to find it interleaved with the other output.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/hello.lua'''.&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(l.HELLO)&lt;br /&gt;
&lt;br /&gt;
And a corresponding file containing the translated string, and a description field shown to the translator, giving the context. &lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-hello/en.json'''&lt;br /&gt;
 {&lt;br /&gt;
     &amp;quot;HELLO&amp;quot;: {&lt;br /&gt;
         &amp;quot;description&amp;quot;: &amp;quot;A classic message.&amp;quot;,&lt;br /&gt;
         &amp;quot;message&amp;quot;: &amp;quot;Hello World!&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In short, any script containing strings that needs translation needs to load the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module:&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
Then load the translation resource they want to use in their module, often just named &amp;lt;code&amp;gt;'l'&amp;lt;/code&amp;gt; as in:&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
You may load more than one translation resource in your script but they will need to have unique names:&lt;br /&gt;
 local lh = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 local lg = Lang.GetResource(&amp;quot;module-goodbye&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Working with strings==&lt;br /&gt;
&lt;br /&gt;
Recommended chapters from [https://www.lua.org/pil/contents.html Programming in Lua (first edition) ]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/2.4.html 2.4 – Strings]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/3.4.html 3.4 – Concatenation]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/20.html 20 – The String Library]&amp;lt;br&amp;gt;&lt;br /&gt;
And from the [https://www.lua.org/manual/5.2/ Lua 5.2 Reference Manual]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/manual/5.2/manual.html#6.4 6.4 – String Manipulation]&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Concatenation===&lt;br /&gt;
&lt;br /&gt;
Instead of serving ready made sentences we can stitch them together with the Lua string concatenation operator &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt;. The following codelines result in the same output.&lt;br /&gt;
 print(&amp;quot;Hello Clarice!&amp;quot;)&lt;br /&gt;
 print(&amp;quot;Hello&amp;quot; .. &amp;quot; &amp;quot; .. &amp;quot;Clarice!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
This could be created by the more generic:&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local NameGen = require 'NameGen'&lt;br /&gt;
 &lt;br /&gt;
 local GREETING = 'Hello'&lt;br /&gt;
 local FBIAGENT = NameGen.Surname()&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship == Game.player then&lt;br /&gt;
         Comms.Message(GREETING .. ' ' .. FBIAGENT .. '!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; operator is used all over the codebase. Try a search for its occurrence. On Linux I would do &amp;lt;code&amp;gt;grep -R &amp;quot; \.\. &amp;quot; data/modules/ data/libs/ data/pigui/&amp;lt;/code&amp;gt; and that will render a lot of output.&lt;br /&gt;
&lt;br /&gt;
===Token concatenation===&lt;br /&gt;
&lt;br /&gt;
You concatenate strings but you also concatenate the string tokens. In the example below from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/modules/DonateToCranks/DonateToCranks.lua#L14-L21 DonateToCranks.lua]''' we see the table &amp;lt;code&amp;gt;flavours&amp;lt;/code&amp;gt; being populated by strings from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/lang/module-donatetocranks/en.json data/lang/module-donatetocranks/en.lua]'''&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 for i = 0,9 do&lt;br /&gt;
 	table.insert(flavours, {&lt;br /&gt;
 		title     = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE&amp;quot;],&lt;br /&gt;
 		desc      = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_DESC&amp;quot;],&lt;br /&gt;
 		message   = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_MESSAGE&amp;quot;],&lt;br /&gt;
 	})&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
The first string in the table is given the value of &amp;lt;code&amp;gt;l[FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE]&amp;lt;/code&amp;gt; is concatenated to &amp;lt;code&amp;gt;l[FLAVOUR_0_ADTITLE]&amp;lt;/code&amp;gt; which corresponds to the first string in the language module which is seen below.&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;FLAVOUR_0_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE!&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_DESC&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;The Church of The Celestial Flying Spaghetti Monster needs YOUR money to spread the word of god.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_MESSAGE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Please select an amount to donate to the Church of the Celestial Flying Spaghetti Monster.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_1_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 &lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
===Lua Standard Library - String manipulation===&lt;br /&gt;
&lt;br /&gt;
I would start to search the '''data/''' register for ''' 'string' '''. to get a feeling for how it's used. Again on Linux i would use the 'grep' command. &amp;lt;code&amp;gt;grep -R &amp;quot;string\.&amp;quot; data/&amp;lt;/code&amp;gt;. You will see that the two most frequently used functions is &amp;lt;code&amp;gt;string.format()&amp;lt;/code&amp;gt;, which is a part of the lua standard library, and &amp;lt;code&amp;gt;string.interp()&amp;lt;/code&amp;gt;, which is a Pioneer global function residing in '''data/libs/autoload.lua''', and is described in the next paragraph about [https://wiki.pioneerspacesim.net/wiki/Strings_and_translation#String_variables String variables].&lt;br /&gt;
&lt;br /&gt;
Some examples from the Lua standard library:&lt;br /&gt;
 Comms.Message(string.lower('wHaT cAsE sHoULd I usE?'))&lt;br /&gt;
&lt;br /&gt;
Maybe not the most easy function to fit into Pioneer, but it's there:&lt;br /&gt;
 Comms.Message('Today is opposite day!')&lt;br /&gt;
 Comms.Message(string.reverse('No, today is not opposite day!'))&lt;br /&gt;
&lt;br /&gt;
A more complex example from '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/pigui/modules/hyperjump-planner.lua#L141 data/pigui/modules/hyperjump-planner.lua]'''&amp;lt;br&amp;gt;&lt;br /&gt;
Here &amp;lt;code&amp;gt;string.format(&amp;quot;%.2f&amp;quot;, distance)&amp;lt;/code&amp;gt; is used to set the number of decimals in the jump to two. The units are translatable but the system name off course isn't (jump_sys.name). '''lc''' is used as the short name for the '''core''' translation resource.&lt;br /&gt;
&lt;br /&gt;
 textLine = jumpIndex ..&amp;quot;: &amp;quot;.. jump_sys.name .. &amp;quot; (&amp;quot; .. string.format(&amp;quot;%.2f&amp;quot;, distance) .. lc.UNIT_LY .. &amp;quot; - &amp;quot; .. fuel .. lc.UNIT_TONNES..&amp;quot;)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here is what the final formatted text looks like (right side, two selected jump routes):&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:hyperjumpplanner.png]]&lt;br /&gt;
&lt;br /&gt;
===PiGUI - Format===&lt;br /&gt;
&lt;br /&gt;
Pioneer has it's own set of formatting functions accessible through PiGUI, our UI. Some of it is realised on the C++ side and described in codedoc [https://codedoc.pioneerspacesim.net/#CClass:Format here]. Most of the functions, however, are strictly Lua and reside in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/libs/text.lua#L146-L385 data/pigui/libs/text.lua]. You access the functions with '''require''' and make sure that '''PiGUI''' is in the scope.&lt;br /&gt;
&lt;br /&gt;
 local ui = require 'pigui'&lt;br /&gt;
 local Format = require 'Format'&lt;br /&gt;
&lt;br /&gt;
Here is an example from the System overview. The table to the right is relying heavily on the Format functions.&lt;br /&gt;
[[File:Systemoverview.png]]&lt;br /&gt;
&lt;br /&gt;
The code responsible for the table is here in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/modules/system-view-ui.lua#L811-L840 data/pigui/modules/system-view-ui.lua] with an excerpt showed below. The examples below take 1 or two arguments and the lines ending with 'or nil' is what makes the System overview info window omit values that don't apply to the selected system body.&lt;br /&gt;
&lt;br /&gt;
 ...&lt;br /&gt;
 { name = lc.ESCAPE_VELOCITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Speed(body.escapeVelocity , true) or nil },&lt;br /&gt;
 { name = lc.MEAN_DENSITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Mass(body.meanDensity)..&amp;quot;/m³&amp;quot; or nil },&lt;br /&gt;
 { name = lc.ORBITAL_PERIOD, icon = icons.body_orbit_period,&lt;br /&gt;
     value = op and op &amp;gt; 0 and ui.Format.Duration(op, 2) or nil },&lt;br /&gt;
 { name = lc.DAY_LENGTH, icon = icons.body_day_length,&lt;br /&gt;
     value = rp &amp;gt; 0 and ui.Format.Duration(rp, 2) or nil },&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
==String variables==&lt;br /&gt;
&lt;br /&gt;
When you want a string to present variable data such as numbers and names you use the &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; function. &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; accepts a table '{}' and the corresponding keys are then embedded in the translated strings enclosed by curly brackets '{}'.&lt;br /&gt;
&lt;br /&gt;
Using ''''module-stationrefuelling'''' we can do:&lt;br /&gt;
 print(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = 'JFK', fee = 50}))&lt;br /&gt;
&lt;br /&gt;
Example from the actual StationRefuelling module:&lt;br /&gt;
&lt;br /&gt;
'''data/modules/StationRefuelling/'''&lt;br /&gt;
 Comms.Message(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = station.label,fee = Format.Money(fee)}))&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/en.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Welcome to {station}. Your landing fee of {fee} has been deducted.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/it.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Benvenuti a {station}. Vi è stata dedotta una tassa di atterraggio di {fee}.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Mission flavours==&lt;br /&gt;
&lt;br /&gt;
A script can define a mission and then place many instances of it onto many bulletin boards. A script that introduces many instances in the game should provide some variety; it would harm immersion if, for examle, all delivery missions were worded identically. The easiest way to achieve multiple versions, or &amp;quot;flavours&amp;quot;, is by just making a number of different strings and then choosing between them with a simple random function. This is how the [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L18:L48 deny] and [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L282:L320 pirate taunt] messages are done in the DeliverPackage module.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/DeliverPackage/DeliverPackage.lua'''&lt;br /&gt;
 local num_deny = 8&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 if not qualified then&lt;br /&gt;
     local introtext = l[&amp;quot;DENY_&amp;quot;..Engine.rand:Integer(1,num_deny)-1]&lt;br /&gt;
         form:SetMessage(introtext)&lt;br /&gt;
     return&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-deliverpackage/en.json'''&lt;br /&gt;
 &amp;quot;DENY_0&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm sorry, but I don't think I can trust you with this delivery.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;DENY_1&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Come back when you have some qualifications.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;DENY_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm not willing to risk this delivery on someone without any qualifications like you.&amp;quot;&lt;br /&gt;
You don't have to use a string variable that has been passed to the translation file. The pirate taunts, for example, have access to the clients name and the destination of the package but this information is only used in some of the strings.&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;That package isn't going to reach its destination today.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_8&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;You're not getting to {location} today!&amp;quot;&lt;br /&gt;
Another way to add variation is to have a table of strings that are variations of a theme, a flavour. In the example below, again from the DeliverPackage module, you have two of the lines from '''FLAVOUR_0''' AND '''FLAVOUR_1''' compared.&lt;br /&gt;
 &amp;quot;FLAVOUR_0_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;GOING TO the {system} system? Money paid for delivery of a small package.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_0_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Unacceptable! You took forever over that delivery. I'm not willing to pay you.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;FLAVOUR_1_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;WANTED. Delivery of a package to the {system} system.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_1_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm frustrated by the late delivery of my package, and I refuse to pay you.&amp;quot;&lt;br /&gt;
There are ten flavours in DeliverPackage of five strings each, each forming a short story line. They are static in that you only see lines from within the same flavour. You know what lines will follow if you accept the mission and after a short time of playing you know this already from seeing the ad if delivering things is your forte. It's hard to share strings in between the flavours as they would need to be much more similar. It's a trade off. You could build a very complex interaction with the customers but in the end it needs to be translatable.&lt;br /&gt;
&lt;br /&gt;
Generally, the more specific the text is, the stranger it will appear to see it multiple times in the game. For example, consider the following two examples:&lt;br /&gt;
 - &amp;quot;Contract is to move X tonnes of Y to system Z&amp;quot;&lt;br /&gt;
 - &amp;quot;My uncle's cat got sick, so I can't travel right now, so I need you to take my contract for me, to move X tonnes of Y to my home system Z&amp;quot;&lt;br /&gt;
The former might not need any flavours, as it's unpersonal, and very general. Seeing the second string multiple times does break immersion. Solution is to not show it often, either by making the mission rare / single occurance (no script to date does this) or have many flavours, making probability of the same twice low.&lt;br /&gt;
&lt;br /&gt;
==Trailing commas==&lt;br /&gt;
&lt;br /&gt;
The two most common text based files you will come across when you work with module scripting are Lua and JSON. While JSON is strictly used for information, Lua is used most as a scripting language but in some instances it's used for data. See for instance '''data/modules/CargoRun/CargoTypes.lua'''. It is basically just tables of data. In any way, we have used both in this chapter and it's probably a good time to address a &lt;br /&gt;
difference between the two that could otherwise create confusion. Trailing commas after the last element in a table.&lt;br /&gt;
&lt;br /&gt;
===Lua===&lt;br /&gt;
&lt;br /&gt;
Here are the last four ladies in the Hawaiian culture file. '''data/culture/haw.lua'''. These are the files we stitch together characters/clients names from. As you see, the last element, ''''Wanaao'''' has a trailing comma after her. This is perfectly fine.&lt;br /&gt;
&lt;br /&gt;
    'Pohaku',&lt;br /&gt;
    'Oke',&lt;br /&gt;
    'Wailani',&lt;br /&gt;
    'Wanaao',         -- Lua. A comma here is fine!&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
===JSON===&lt;br /&gt;
&lt;br /&gt;
Here we see the last two elements in the '''data/lang/core/en.json'''. It keeps some of the strings used in the game and every language has it's own version of it. Trailing commas in JSON is explicitly forbidden in the official description of the file format. There are derivative interpreters that are relaxed and allow trailing commas but this is not the case with the one used in Pioneer.&lt;br /&gt;
&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;ZOOM_IN&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Zoom in&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;ZOOM_OUT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Zoom out&amp;quot;&lt;br /&gt;
   }                   -- JSON. A comma here is NOT fine!&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The fastest way to find out what could happen is to simply just put a comma in that place and start Pioneer. Open the '''System oveview''' and click on a planet and look at the data presented in the pop up window. There is now a lot of '''[NO_JSON]''' data presented in there.&lt;br /&gt;
&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Zoom out&amp;quot;&lt;br /&gt;
   },                   -- ;o -onoh!&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
Also, another difference between the file formats is that the comment I added above in the Lua file is a real Lua comment and would work and be ignored by the language. The  ;o emoji comment in the JSON file wouldn't work. JSON doesn't have comments in it. You would have to assign a separate element to be used as a comment like the translation file above which has elements beginning with '''&amp;quot;description&amp;quot;:'''.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Translations&amp;diff=4735</id>
		<title>Translations</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Translations&amp;diff=4735"/>
		<updated>2025-04-07T14:47:21Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* For translators */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;All text in Pioneer is translatable, and the game ships with several translations. Here's everything you need to know.&lt;br /&gt;
&lt;br /&gt;
== For translators ==&lt;br /&gt;
&lt;br /&gt;
All our translations are managed through [http://transifex.com Transifex], a free web-based translation service. To start writing translations, sign up there, using [https://www.transifex.com/signup/open-source/?join_project=pioneer this link], and then take a look at the [https://www.transifex.com/projects/p/pioneer/ Pioneer project]. You can either use the web-interface, or download the full file, and edit off line, and then re-upload.&lt;br /&gt;
&lt;br /&gt;
Changes to translations are automatically pulled into the master git repo, from transifex, twice per day, and from there to the builds when they're next run.&lt;br /&gt;
&lt;br /&gt;
In Transifex you can subscribe to notifications for the languages or resources you're interested in. When new strings are added or modified, you'll be notified.&lt;br /&gt;
&lt;br /&gt;
If you want to translate Pioneer to an entirely new language, please [https://github.com/pioneerspacesim/pioneer/issues open an issue] on the tracker and someone will create the translation for you.&lt;br /&gt;
&lt;br /&gt;
English is the canonical language for Pioneer. As such, you can't directly modify the english strings through Transifex. If you want to change those, you'll need to make the change in the source code hosted on GitHub and submit a pull request like for a normal code change. However, do note that changes in english text will trigger re-translation of that string in all other languages, thus there should be a good motivation why a string is changes (e.g. spelling/grammar).&lt;br /&gt;
&lt;br /&gt;
Untranslated strings will use the value from the English version.&lt;br /&gt;
&lt;br /&gt;
You can leave comments in Transifex to help out other translators. You'll also see any notes left by the developers to help you translate a particular string.&lt;br /&gt;
&lt;br /&gt;
If you're a new translator and you'd like your name included in AUTHORS.txt, please let us know!&lt;br /&gt;
&lt;br /&gt;
For when you translate: Text within curly brace denote a varialbe name that will be substituted and should not be translated, e.g. &lt;br /&gt;
&lt;br /&gt;
    &amp;quot;Welcome to {system}&amp;quot;&lt;br /&gt;
&lt;br /&gt;
== For mod developers ==&lt;br /&gt;
&lt;br /&gt;
Each module gets its own translation resource, called &amp;lt;tt&amp;gt;module-foo&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
To get at the strings for your module, do something like:&lt;br /&gt;
&lt;br /&gt;
    local Lang = import(&amp;quot;Lang&amp;quot;)&lt;br /&gt;
    local l = Lang.GetResource(&amp;quot;module-foo&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
Then you can get at the string by its token:&lt;br /&gt;
&lt;br /&gt;
    print(l.SOME_TRANSLATED_STRING)&lt;br /&gt;
&lt;br /&gt;
While its possible to load multiple translation resources to share strings, its highly recommended that you don't do that. Stick to your own strings so that you don't have to track changes in other modules. Duplicates across resources are fine. Note that code that uses multiple resources won't be accepted into the main Pioneer repository unless you've checked it with a core developer first and it has a good reason.&lt;br /&gt;
&lt;br /&gt;
Translations are [https://developer.chrome.com/extensions/i18n.html &amp;lt;tt&amp;gt;chrome.i18n&amp;lt;/tt&amp;gt; JSON] files. The format is fairly simple - a JSON object with tokens as keys and values of an object with two keys, &amp;lt;tt&amp;gt;message&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;description&amp;lt;/tt&amp;gt;. &amp;lt;tt&amp;gt;message&amp;lt;/tt&amp;gt; is the text that will appear in the game, while &amp;lt;tt&amp;gt;description&amp;lt;/tt&amp;gt; provides instructions for the translator that will be displayed in Transifex. [https://www.json.org/img/string.png Here] is a useful chart showing how characters are interpreted.&lt;br /&gt;
&lt;br /&gt;
If you're submitting code that requires a new language you should only include &amp;lt;tt&amp;gt;en.json&amp;lt;/tt&amp;gt;. Please tag '''@impaktor''' in your pull request so they can create a new resource in Transifex and make sure a language update is done at merge. If you don't do this then you'll break the game for non-English users.&lt;br /&gt;
&lt;br /&gt;
== For core developers ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;core&amp;lt;/tt&amp;gt; resource is magical. If you add a string to it, you also need to add it to &amp;lt;tt&amp;gt;LangStrings.inc.h&amp;lt;/tt&amp;gt; and recompile. It will then be available as &amp;lt;tt&amp;gt;Lang::SOME_TRANSLATED_STRING&amp;lt;/tt&amp;gt;.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4734</id>
		<title>Strings and translation</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4734"/>
		<updated>2025-04-01T06:52:04Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* PiGUI - Format */ fixup - need PiGUI for Format&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Internationalization==&lt;br /&gt;
Recommended reading: [https://dev.pioneerspacesim.net/contribute/translations Pioneer translators wikipage]&lt;br /&gt;
&lt;br /&gt;
It is important that scripters are familiar with the translation system. In the previous example '''hello_world.lua''' used strings directly like &amp;lt;code&amp;gt;print(&amp;quot;Hello, World!&amp;quot;)&amp;lt;/code&amp;gt;. In order for Pioneer to be translated we instead use tokens and calls on the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module to substitute it for the string in the chosen language. The translated strings are stored together with their 'key' in json format, one language per file that are kept in the [https://github.com/pioneerspacesim/pioneer/tree/master/data/lang data/lang/] directory. If the script file in which they are used is from the '''data/modules''' directory they have names starting with '''module-''' and ending with the name of the module written in one word, in small caps, and with the '.lua' ending omitted. The final translation from English to other languages is done on Pioneer's project page at [https://www.transifex.com/pioneer/pioneer/ Transifex], a dedicated online translation tool. The translations are committed to the pioneer/master branch on GitHub by a bot and are not to be edited manually apart from the '''en.json''' files.&lt;br /&gt;
&lt;br /&gt;
==Translating strings==&lt;br /&gt;
&lt;br /&gt;
Below is a minimal example with one string added into the translation system. It takes two files, one in '''data/modules''' and one in '''data/lang/module-hello'''. The print statement isn't wrapped in a function so it will be executed when the '''hello.lua''' is being parsed on startup. You will have to scroll through the command-line history to find it interleaved with the other output.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/hello.lua'''.&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(l.HELLO)&lt;br /&gt;
&lt;br /&gt;
And a corresponding file containing the translated string, and a description field shown to the translator, giving the context. &lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-hello/en.json'''&lt;br /&gt;
 {&lt;br /&gt;
     &amp;quot;HELLO&amp;quot;: {&lt;br /&gt;
         &amp;quot;description&amp;quot;: &amp;quot;A classic message.&amp;quot;,&lt;br /&gt;
         &amp;quot;message&amp;quot;: &amp;quot;Hello World!&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In short, any script containing strings that needs translation needs to load the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module:&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
Then load the translation resource they want to use in their module, often just named &amp;lt;code&amp;gt;'l'&amp;lt;/code&amp;gt; as in:&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
You may load more than one translation resource in your script but they will need to have unique names:&lt;br /&gt;
 local lh = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 local lg = Lang.GetResource(&amp;quot;module-goodbye&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Working with strings==&lt;br /&gt;
&lt;br /&gt;
Recommended chapters from [https://www.lua.org/pil/contents.html Programming in Lua (first edition) ]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/2.4.html 2.4 – Strings]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/3.4.html 3.4 – Concatenation]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/20.html 20 – The String Library]&amp;lt;br&amp;gt;&lt;br /&gt;
And from the [https://www.lua.org/manual/5.2/ Lua 5.2 Reference Manual]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/manual/5.2/manual.html#6.4 6.4 – String Manipulation]&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Concatenation===&lt;br /&gt;
&lt;br /&gt;
Instead of serving ready made sentences we can stitch them together with the Lua string concatenation operator &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt;. The following codelines result in the same output.&lt;br /&gt;
 print(&amp;quot;Hello Clarice!&amp;quot;)&lt;br /&gt;
 print(&amp;quot;Hello&amp;quot; .. &amp;quot; &amp;quot; .. &amp;quot;Clarice!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
This could be created by the more generic:&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local NameGen = require 'NameGen'&lt;br /&gt;
 &lt;br /&gt;
 local GREETING = 'Hello'&lt;br /&gt;
 local FBIAGENT = NameGen.Surname()&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship == Game.player then&lt;br /&gt;
         Comms.Message(GREETING .. ' ' .. FBIAGENT .. '!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; operator is used all over the codebase. Try a search for its occurrence. On Linux I would do &amp;lt;code&amp;gt;grep -R &amp;quot; \.\. &amp;quot; data/modules/ data/libs/ data/pigui/&amp;lt;/code&amp;gt; and that will render a lot of output.&lt;br /&gt;
&lt;br /&gt;
===Token concatenation===&lt;br /&gt;
&lt;br /&gt;
You concatenate strings but you also concatenate the string tokens. In the example below from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/modules/DonateToCranks/DonateToCranks.lua#L14-L21 DonateToCranks.lua]''' we see the table &amp;lt;code&amp;gt;flavours&amp;lt;/code&amp;gt; being populated by strings from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/lang/module-donatetocranks/en.json data/lang/module-donatetocranks/en.lua]'''&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 for i = 0,9 do&lt;br /&gt;
 	table.insert(flavours, {&lt;br /&gt;
 		title     = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE&amp;quot;],&lt;br /&gt;
 		desc      = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_DESC&amp;quot;],&lt;br /&gt;
 		message   = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_MESSAGE&amp;quot;],&lt;br /&gt;
 	})&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
The first string in the table is given the value of &amp;lt;code&amp;gt;l[FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE]&amp;lt;/code&amp;gt; is concatenated to &amp;lt;code&amp;gt;l[FLAVOUR_0_ADTITLE]&amp;lt;/code&amp;gt; which corresponds to the first string in the language module which is seen below.&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;FLAVOUR_0_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE!&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_DESC&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;The Church of The Celestial Flying Spaghetti Monster needs YOUR money to spread the word of god.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_MESSAGE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Please select an amount to donate to the Church of the Celestial Flying Spaghetti Monster.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_1_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 &lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
===Lua Standard Library - String manipulation===&lt;br /&gt;
&lt;br /&gt;
I would start to search the '''data/''' register for ''' 'string' '''. to get a feeling for how it's used. Again on Linux i would use the 'grep' command. &amp;lt;code&amp;gt;grep -R &amp;quot;string\.&amp;quot; data/&amp;lt;/code&amp;gt;. You will see that the two most frequently used functions is &amp;lt;code&amp;gt;string.format()&amp;lt;/code&amp;gt;, which is a part of the lua standard library, and &amp;lt;code&amp;gt;string.interp()&amp;lt;/code&amp;gt;, which is a Pioneer global function residing in '''data/libs/autoload.lua''', and is described in the next paragraph about [https://wiki.pioneerspacesim.net/wiki/Strings_and_translation#String_variables String variables].&lt;br /&gt;
&lt;br /&gt;
Some examples from the Lua standard library:&lt;br /&gt;
 Comms.Message(string.lower('wHaT cAsE sHoULd I usE?'))&lt;br /&gt;
&lt;br /&gt;
Maybe not the most easy function to fit into Pioneer, but it's there:&lt;br /&gt;
 Comms.Message('Today is opposite day!')&lt;br /&gt;
 Comms.Message(string.reverse('No, today is not opposite day!'))&lt;br /&gt;
&lt;br /&gt;
A more complex example from '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/pigui/modules/hyperjump-planner.lua#L141 data/pigui/modules/hyperjump-planner.lua]'''&amp;lt;br&amp;gt;&lt;br /&gt;
Here &amp;lt;code&amp;gt;string.format(&amp;quot;%.2f&amp;quot;, distance)&amp;lt;/code&amp;gt; is used to set the number of decimals in the jump to two. The units are translatable but the system name off course isn't (jump_sys.name). '''lc''' is used as the short name for the '''core''' translation resource.&lt;br /&gt;
&lt;br /&gt;
 textLine = jumpIndex ..&amp;quot;: &amp;quot;.. jump_sys.name .. &amp;quot; (&amp;quot; .. string.format(&amp;quot;%.2f&amp;quot;, distance) .. lc.UNIT_LY .. &amp;quot; - &amp;quot; .. fuel .. lc.UNIT_TONNES..&amp;quot;)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here is what the final formatted text looks like (right side, two selected jump routes):&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:hyperjumpplanner.png]]&lt;br /&gt;
&lt;br /&gt;
===PiGUI - Format===&lt;br /&gt;
&lt;br /&gt;
Pioneer has it's own set of formatting functions accessible through PiGUI, our UI. Some of it is realised on the C++ side and described in codedoc [https://codedoc.pioneerspacesim.net/#CClass:Format here]. Most of the functions, however, are strictly Lua and reside in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/libs/text.lua#L146-L385 data/pigui/libs/text.lua]. You access the functions with '''require''' and make sure that '''PiGUI''' is in the scope.&lt;br /&gt;
&lt;br /&gt;
 local ui = require 'pigui'&lt;br /&gt;
 local Format = require 'Format'&lt;br /&gt;
&lt;br /&gt;
Here is an example from the System overview. The table to the right is relying heavily on the Format functions.&lt;br /&gt;
[[File:Systemoverview.png]]&lt;br /&gt;
&lt;br /&gt;
The code responsible for the table is here in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/modules/system-view-ui.lua#L811-L840 data/pigui/modules/system-view-ui.lua] with an excerpt showed below. The examples below take 1 or two arguments and the lines ending with 'or nil' is what makes the System overview info window omit values that don't apply to the selected system body.&lt;br /&gt;
&lt;br /&gt;
 ...&lt;br /&gt;
 { name = lc.ESCAPE_VELOCITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Speed(body.escapeVelocity , true) or nil },&lt;br /&gt;
 { name = lc.MEAN_DENSITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Mass(body.meanDensity)..&amp;quot;/m³&amp;quot; or nil },&lt;br /&gt;
 { name = lc.ORBITAL_PERIOD, icon = icons.body_orbit_period,&lt;br /&gt;
     value = op and op &amp;gt; 0 and ui.Format.Duration(op, 2) or nil },&lt;br /&gt;
 { name = lc.DAY_LENGTH, icon = icons.body_day_length,&lt;br /&gt;
     value = rp &amp;gt; 0 and ui.Format.Duration(rp, 2) or nil },&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
==String variables==&lt;br /&gt;
&lt;br /&gt;
When you want a string to present variable data such as numbers and names you use the &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; function. &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; accepts a table '{}' and the corresponding keys are then embedded in the translated strings enclosed by curly brackets '{}'.&lt;br /&gt;
&lt;br /&gt;
Using ''''module-stationrefuelling'''' we can do:&lt;br /&gt;
 print(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = 'JFK', fee = 50}))&lt;br /&gt;
&lt;br /&gt;
Example from the actual StationRefuelling module:&lt;br /&gt;
&lt;br /&gt;
'''data/modules/StationRefuelling/'''&lt;br /&gt;
 Comms.Message(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = station.label,fee = Format.Money(fee)}))&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/en.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Welcome to {station}. Your landing fee of {fee} has been deducted.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/it.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Benvenuti a {station}. Vi è stata dedotta una tassa di atterraggio di {fee}.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Mission flavours==&lt;br /&gt;
&lt;br /&gt;
A script can define a mission and then place many instances of it onto many bulletin boards. A script that introduces many instances in the game should provide some variety; it would harm immersion if, for examle, all delivery missions were worded identically. The easiest way to achieve multiple versions, or &amp;quot;flavours&amp;quot;, is by just making a number of different strings and then choosing between them with a simple random function. This is how the [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L18:L48 deny] and [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L282:L320 pirate taunt] messages are done in the DeliverPackage module.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/DeliverPackage/DeliverPackage.lua'''&lt;br /&gt;
 local num_deny = 8&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 if not qualified then&lt;br /&gt;
     local introtext = l[&amp;quot;DENY_&amp;quot;..Engine.rand:Integer(1,num_deny)-1]&lt;br /&gt;
         form:SetMessage(introtext)&lt;br /&gt;
     return&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-deliverpackage/en.json'''&lt;br /&gt;
 &amp;quot;DENY_0&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm sorry, but I don't think I can trust you with this delivery.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;DENY_1&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Come back when you have some qualifications.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;DENY_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm not willing to risk this delivery on someone without any qualifications like you.&amp;quot;&lt;br /&gt;
You don't have to use a string variable that has been passed to the translation file. The pirate taunts, for example, have access to the clients name and the destination of the package but this information is only used in some of the strings.&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;That package isn't going to reach its destination today.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_8&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;You're not getting to {location} today!&amp;quot;&lt;br /&gt;
Another way to add variation is to have a table of strings that are variations of a theme, a flavour. In the example below, again from the DeliverPackage module, you have two of the lines from '''FLAVOUR_0''' AND '''FLAVOUR_1''' compared.&lt;br /&gt;
 &amp;quot;FLAVOUR_0_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;GOING TO the {system} system? Money paid for delivery of a small package.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_0_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Unacceptable! You took forever over that delivery. I'm not willing to pay you.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;FLAVOUR_1_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;WANTED. Delivery of a package to the {system} system.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_1_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm frustrated by the late delivery of my package, and I refuse to pay you.&amp;quot;&lt;br /&gt;
There are ten flavours in DeliverPackage of five strings each, each forming a short story line. They are static in that you only see lines from within the same flavour. You know what lines will follow if you accept the mission and after a short time of playing you know this already from seeing the ad if delivering things is your forte. It's hard to share strings in between the flavours as they would need to be much more similar. It's a trade off. You could build a very complex interaction with the customers but in the end it needs to be translatable.&lt;br /&gt;
&lt;br /&gt;
Generally, the more specific the text is, the stranger it will appear to see it multiple times in the game. For example, consider the following two examples:&lt;br /&gt;
 - &amp;quot;Contract is to move X tonnes of Y to system Z&amp;quot;&lt;br /&gt;
 - &amp;quot;My uncle's cat got sick, so I can't travel right now, so I need you to take my contract for me, to move X tonnes of Y to my home system Z&amp;quot;&lt;br /&gt;
The former might not need any flavours, as it's unpersonal, and very general. Seeing the second string multiple times does break immersion. Solution is to not show it often, either by making the mission rare / single occurance (no script to date does this) or have many flavours, making probability of the same twice low.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4733</id>
		<title>Strings and translation</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4733"/>
		<updated>2025-03-21T22:01:25Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* PiGUI - String formatting */ Change subtitle&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Internationalization==&lt;br /&gt;
Recommended reading: [https://dev.pioneerspacesim.net/contribute/translations Pioneer translators wikipage]&lt;br /&gt;
&lt;br /&gt;
It is important that scripters are familiar with the translation system. In the previous example '''hello_world.lua''' used strings directly like &amp;lt;code&amp;gt;print(&amp;quot;Hello, World!&amp;quot;)&amp;lt;/code&amp;gt;. In order for Pioneer to be translated we instead use tokens and calls on the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module to substitute it for the string in the chosen language. The translated strings are stored together with their 'key' in json format, one language per file that are kept in the [https://github.com/pioneerspacesim/pioneer/tree/master/data/lang data/lang/] directory. If the script file in which they are used is from the '''data/modules''' directory they have names starting with '''module-''' and ending with the name of the module written in one word, in small caps, and with the '.lua' ending omitted. The final translation from English to other languages is done on Pioneer's project page at [https://www.transifex.com/pioneer/pioneer/ Transifex], a dedicated online translation tool. The translations are committed to the pioneer/master branch on GitHub by a bot and are not to be edited manually apart from the '''en.json''' files.&lt;br /&gt;
&lt;br /&gt;
==Translating strings==&lt;br /&gt;
&lt;br /&gt;
Below is a minimal example with one string added into the translation system. It takes two files, one in '''data/modules''' and one in '''data/lang/module-hello'''. The print statement isn't wrapped in a function so it will be executed when the '''hello.lua''' is being parsed on startup. You will have to scroll through the command-line history to find it interleaved with the other output.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/hello.lua'''.&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(l.HELLO)&lt;br /&gt;
&lt;br /&gt;
And a corresponding file containing the translated string, and a description field shown to the translator, giving the context. &lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-hello/en.json'''&lt;br /&gt;
 {&lt;br /&gt;
     &amp;quot;HELLO&amp;quot;: {&lt;br /&gt;
         &amp;quot;description&amp;quot;: &amp;quot;A classic message.&amp;quot;,&lt;br /&gt;
         &amp;quot;message&amp;quot;: &amp;quot;Hello World!&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In short, any script containing strings that needs translation needs to load the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module:&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
Then load the translation resource they want to use in their module, often just named &amp;lt;code&amp;gt;'l'&amp;lt;/code&amp;gt; as in:&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
You may load more than one translation resource in your script but they will need to have unique names:&lt;br /&gt;
 local lh = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 local lg = Lang.GetResource(&amp;quot;module-goodbye&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Working with strings==&lt;br /&gt;
&lt;br /&gt;
Recommended chapters from [https://www.lua.org/pil/contents.html Programming in Lua (first edition) ]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/2.4.html 2.4 – Strings]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/3.4.html 3.4 – Concatenation]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/20.html 20 – The String Library]&amp;lt;br&amp;gt;&lt;br /&gt;
And from the [https://www.lua.org/manual/5.2/ Lua 5.2 Reference Manual]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/manual/5.2/manual.html#6.4 6.4 – String Manipulation]&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Concatenation===&lt;br /&gt;
&lt;br /&gt;
Instead of serving ready made sentences we can stitch them together with the Lua string concatenation operator &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt;. The following codelines result in the same output.&lt;br /&gt;
 print(&amp;quot;Hello Clarice!&amp;quot;)&lt;br /&gt;
 print(&amp;quot;Hello&amp;quot; .. &amp;quot; &amp;quot; .. &amp;quot;Clarice!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
This could be created by the more generic:&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local NameGen = require 'NameGen'&lt;br /&gt;
 &lt;br /&gt;
 local GREETING = 'Hello'&lt;br /&gt;
 local FBIAGENT = NameGen.Surname()&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship == Game.player then&lt;br /&gt;
         Comms.Message(GREETING .. ' ' .. FBIAGENT .. '!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; operator is used all over the codebase. Try a search for its occurrence. On Linux I would do &amp;lt;code&amp;gt;grep -R &amp;quot; \.\. &amp;quot; data/modules/ data/libs/ data/pigui/&amp;lt;/code&amp;gt; and that will render a lot of output.&lt;br /&gt;
&lt;br /&gt;
===Token concatenation===&lt;br /&gt;
&lt;br /&gt;
You concatenate strings but you also concatenate the string tokens. In the example below from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/modules/DonateToCranks/DonateToCranks.lua#L14-L21 DonateToCranks.lua]''' we see the table &amp;lt;code&amp;gt;flavours&amp;lt;/code&amp;gt; being populated by strings from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/lang/module-donatetocranks/en.json data/lang/module-donatetocranks/en.lua]'''&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 for i = 0,9 do&lt;br /&gt;
 	table.insert(flavours, {&lt;br /&gt;
 		title     = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE&amp;quot;],&lt;br /&gt;
 		desc      = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_DESC&amp;quot;],&lt;br /&gt;
 		message   = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_MESSAGE&amp;quot;],&lt;br /&gt;
 	})&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
The first string in the table is given the value of &amp;lt;code&amp;gt;l[FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE]&amp;lt;/code&amp;gt; is concatenated to &amp;lt;code&amp;gt;l[FLAVOUR_0_ADTITLE]&amp;lt;/code&amp;gt; which corresponds to the first string in the language module which is seen below.&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;FLAVOUR_0_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE!&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_DESC&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;The Church of The Celestial Flying Spaghetti Monster needs YOUR money to spread the word of god.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_MESSAGE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Please select an amount to donate to the Church of the Celestial Flying Spaghetti Monster.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_1_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 &lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
===Lua Standard Library - String manipulation===&lt;br /&gt;
&lt;br /&gt;
I would start to search the '''data/''' register for ''' 'string' '''. to get a feeling for how it's used. Again on Linux i would use the 'grep' command. &amp;lt;code&amp;gt;grep -R &amp;quot;string\.&amp;quot; data/&amp;lt;/code&amp;gt;. You will see that the two most frequently used functions is &amp;lt;code&amp;gt;string.format()&amp;lt;/code&amp;gt;, which is a part of the lua standard library, and &amp;lt;code&amp;gt;string.interp()&amp;lt;/code&amp;gt;, which is a Pioneer global function residing in '''data/libs/autoload.lua''', and is described in the next paragraph about [https://wiki.pioneerspacesim.net/wiki/Strings_and_translation#String_variables String variables].&lt;br /&gt;
&lt;br /&gt;
Some examples from the Lua standard library:&lt;br /&gt;
 Comms.Message(string.lower('wHaT cAsE sHoULd I usE?'))&lt;br /&gt;
&lt;br /&gt;
Maybe not the most easy function to fit into Pioneer, but it's there:&lt;br /&gt;
 Comms.Message('Today is opposite day!')&lt;br /&gt;
 Comms.Message(string.reverse('No, today is not opposite day!'))&lt;br /&gt;
&lt;br /&gt;
A more complex example from '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/pigui/modules/hyperjump-planner.lua#L141 data/pigui/modules/hyperjump-planner.lua]'''&amp;lt;br&amp;gt;&lt;br /&gt;
Here &amp;lt;code&amp;gt;string.format(&amp;quot;%.2f&amp;quot;, distance)&amp;lt;/code&amp;gt; is used to set the number of decimals in the jump to two. The units are translatable but the system name off course isn't (jump_sys.name). '''lc''' is used as the short name for the '''core''' translation resource.&lt;br /&gt;
&lt;br /&gt;
 textLine = jumpIndex ..&amp;quot;: &amp;quot;.. jump_sys.name .. &amp;quot; (&amp;quot; .. string.format(&amp;quot;%.2f&amp;quot;, distance) .. lc.UNIT_LY .. &amp;quot; - &amp;quot; .. fuel .. lc.UNIT_TONNES..&amp;quot;)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here is what the final formatted text looks like (right side, two selected jump routes):&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:hyperjumpplanner.png]]&lt;br /&gt;
&lt;br /&gt;
===PiGUI - Format===&lt;br /&gt;
&lt;br /&gt;
Pioneer has it's own set of formatting functions accessible through PiGUI, our UI. Some of it is realised on the C++ side and described in codedoc [https://codedoc.pioneerspacesim.net/#CClass:Format here]. Most of the functions, however, are strictly Lua and reside in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/libs/text.lua#L146-L385 data/pigui/libs/text.lua]. You access the functions with ''''require''''.&lt;br /&gt;
&lt;br /&gt;
 local Format = require 'Format'&lt;br /&gt;
&lt;br /&gt;
Here is an example from the System overview. The table to the right is relying heavily on the Format functions.&lt;br /&gt;
[[File:Systemoverview.png]]&lt;br /&gt;
&lt;br /&gt;
The code responsible for the table is here in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/modules/system-view-ui.lua#L811-L840 data/pigui/modules/system-view-ui.lua] with an excerpt showed below. The examples below take 1 or two arguments and the lines ending with 'or nil' is what makes the System overview info window omit values that don't apply to the selected system body.&lt;br /&gt;
&lt;br /&gt;
 ...&lt;br /&gt;
 { name = lc.ESCAPE_VELOCITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Speed(body.escapeVelocity , true) or nil },&lt;br /&gt;
 { name = lc.MEAN_DENSITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Mass(body.meanDensity)..&amp;quot;/m³&amp;quot; or nil },&lt;br /&gt;
 { name = lc.ORBITAL_PERIOD, icon = icons.body_orbit_period,&lt;br /&gt;
     value = op and op &amp;gt; 0 and ui.Format.Duration(op, 2) or nil },&lt;br /&gt;
 { name = lc.DAY_LENGTH, icon = icons.body_day_length,&lt;br /&gt;
     value = rp &amp;gt; 0 and ui.Format.Duration(rp, 2) or nil },&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
==String variables==&lt;br /&gt;
&lt;br /&gt;
When you want a string to present variable data such as numbers and names you use the &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; function. &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; accepts a table '{}' and the corresponding keys are then embedded in the translated strings enclosed by curly brackets '{}'.&lt;br /&gt;
&lt;br /&gt;
Using ''''module-stationrefuelling'''' we can do:&lt;br /&gt;
 print(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = 'JFK', fee = 50}))&lt;br /&gt;
&lt;br /&gt;
Example from the actual StationRefuelling module:&lt;br /&gt;
&lt;br /&gt;
'''data/modules/StationRefuelling/'''&lt;br /&gt;
 Comms.Message(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = station.label,fee = Format.Money(fee)}))&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/en.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Welcome to {station}. Your landing fee of {fee} has been deducted.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/it.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Benvenuti a {station}. Vi è stata dedotta una tassa di atterraggio di {fee}.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Mission flavours==&lt;br /&gt;
&lt;br /&gt;
A script can define a mission and then place many instances of it onto many bulletin boards. A script that introduces many instances in the game should provide some variety; it would harm immersion if, for examle, all delivery missions were worded identically. The easiest way to achieve multiple versions, or &amp;quot;flavours&amp;quot;, is by just making a number of different strings and then choosing between them with a simple random function. This is how the [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L18:L48 deny] and [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L282:L320 pirate taunt] messages are done in the DeliverPackage module.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/DeliverPackage/DeliverPackage.lua'''&lt;br /&gt;
 local num_deny = 8&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 if not qualified then&lt;br /&gt;
     local introtext = l[&amp;quot;DENY_&amp;quot;..Engine.rand:Integer(1,num_deny)-1]&lt;br /&gt;
         form:SetMessage(introtext)&lt;br /&gt;
     return&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-deliverpackage/en.json'''&lt;br /&gt;
 &amp;quot;DENY_0&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm sorry, but I don't think I can trust you with this delivery.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;DENY_1&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Come back when you have some qualifications.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;DENY_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm not willing to risk this delivery on someone without any qualifications like you.&amp;quot;&lt;br /&gt;
You don't have to use a string variable that has been passed to the translation file. The pirate taunts, for example, have access to the clients name and the destination of the package but this information is only used in some of the strings.&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;That package isn't going to reach its destination today.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_8&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;You're not getting to {location} today!&amp;quot;&lt;br /&gt;
Another way to add variation is to have a table of strings that are variations of a theme, a flavour. In the example below, again from the DeliverPackage module, you have two of the lines from '''FLAVOUR_0''' AND '''FLAVOUR_1''' compared.&lt;br /&gt;
 &amp;quot;FLAVOUR_0_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;GOING TO the {system} system? Money paid for delivery of a small package.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_0_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Unacceptable! You took forever over that delivery. I'm not willing to pay you.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;FLAVOUR_1_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;WANTED. Delivery of a package to the {system} system.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_1_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm frustrated by the late delivery of my package, and I refuse to pay you.&amp;quot;&lt;br /&gt;
There are ten flavours in DeliverPackage of five strings each, each forming a short story line. They are static in that you only see lines from within the same flavour. You know what lines will follow if you accept the mission and after a short time of playing you know this already from seeing the ad if delivering things is your forte. It's hard to share strings in between the flavours as they would need to be much more similar. It's a trade off. You could build a very complex interaction with the customers but in the end it needs to be translatable.&lt;br /&gt;
&lt;br /&gt;
Generally, the more specific the text is, the stranger it will appear to see it multiple times in the game. For example, consider the following two examples:&lt;br /&gt;
 - &amp;quot;Contract is to move X tonnes of Y to system Z&amp;quot;&lt;br /&gt;
 - &amp;quot;My uncle's cat got sick, so I can't travel right now, so I need you to take my contract for me, to move X tonnes of Y to my home system Z&amp;quot;&lt;br /&gt;
The former might not need any flavours, as it's unpersonal, and very general. Seeing the second string multiple times does break immersion. Solution is to not show it often, either by making the mission rare / single occurance (no script to date does this) or have many flavours, making probability of the same twice low.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4732</id>
		<title>Strings and translation</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4732"/>
		<updated>2025-03-21T20:39:24Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* PiGUI - String formatting */ Code example fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Internationalization==&lt;br /&gt;
Recommended reading: [https://dev.pioneerspacesim.net/contribute/translations Pioneer translators wikipage]&lt;br /&gt;
&lt;br /&gt;
It is important that scripters are familiar with the translation system. In the previous example '''hello_world.lua''' used strings directly like &amp;lt;code&amp;gt;print(&amp;quot;Hello, World!&amp;quot;)&amp;lt;/code&amp;gt;. In order for Pioneer to be translated we instead use tokens and calls on the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module to substitute it for the string in the chosen language. The translated strings are stored together with their 'key' in json format, one language per file that are kept in the [https://github.com/pioneerspacesim/pioneer/tree/master/data/lang data/lang/] directory. If the script file in which they are used is from the '''data/modules''' directory they have names starting with '''module-''' and ending with the name of the module written in one word, in small caps, and with the '.lua' ending omitted. The final translation from English to other languages is done on Pioneer's project page at [https://www.transifex.com/pioneer/pioneer/ Transifex], a dedicated online translation tool. The translations are committed to the pioneer/master branch on GitHub by a bot and are not to be edited manually apart from the '''en.json''' files.&lt;br /&gt;
&lt;br /&gt;
==Translating strings==&lt;br /&gt;
&lt;br /&gt;
Below is a minimal example with one string added into the translation system. It takes two files, one in '''data/modules''' and one in '''data/lang/module-hello'''. The print statement isn't wrapped in a function so it will be executed when the '''hello.lua''' is being parsed on startup. You will have to scroll through the command-line history to find it interleaved with the other output.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/hello.lua'''.&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(l.HELLO)&lt;br /&gt;
&lt;br /&gt;
And a corresponding file containing the translated string, and a description field shown to the translator, giving the context. &lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-hello/en.json'''&lt;br /&gt;
 {&lt;br /&gt;
     &amp;quot;HELLO&amp;quot;: {&lt;br /&gt;
         &amp;quot;description&amp;quot;: &amp;quot;A classic message.&amp;quot;,&lt;br /&gt;
         &amp;quot;message&amp;quot;: &amp;quot;Hello World!&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In short, any script containing strings that needs translation needs to load the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module:&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
Then load the translation resource they want to use in their module, often just named &amp;lt;code&amp;gt;'l'&amp;lt;/code&amp;gt; as in:&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
You may load more than one translation resource in your script but they will need to have unique names:&lt;br /&gt;
 local lh = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 local lg = Lang.GetResource(&amp;quot;module-goodbye&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Working with strings==&lt;br /&gt;
&lt;br /&gt;
Recommended chapters from [https://www.lua.org/pil/contents.html Programming in Lua (first edition) ]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/2.4.html 2.4 – Strings]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/3.4.html 3.4 – Concatenation]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/20.html 20 – The String Library]&amp;lt;br&amp;gt;&lt;br /&gt;
And from the [https://www.lua.org/manual/5.2/ Lua 5.2 Reference Manual]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/manual/5.2/manual.html#6.4 6.4 – String Manipulation]&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Concatenation===&lt;br /&gt;
&lt;br /&gt;
Instead of serving ready made sentences we can stitch them together with the Lua string concatenation operator &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt;. The following codelines result in the same output.&lt;br /&gt;
 print(&amp;quot;Hello Clarice!&amp;quot;)&lt;br /&gt;
 print(&amp;quot;Hello&amp;quot; .. &amp;quot; &amp;quot; .. &amp;quot;Clarice!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
This could be created by the more generic:&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local NameGen = require 'NameGen'&lt;br /&gt;
 &lt;br /&gt;
 local GREETING = 'Hello'&lt;br /&gt;
 local FBIAGENT = NameGen.Surname()&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship == Game.player then&lt;br /&gt;
         Comms.Message(GREETING .. ' ' .. FBIAGENT .. '!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; operator is used all over the codebase. Try a search for its occurrence. On Linux I would do &amp;lt;code&amp;gt;grep -R &amp;quot; \.\. &amp;quot; data/modules/ data/libs/ data/pigui/&amp;lt;/code&amp;gt; and that will render a lot of output.&lt;br /&gt;
&lt;br /&gt;
===Token concatenation===&lt;br /&gt;
&lt;br /&gt;
You concatenate strings but you also concatenate the string tokens. In the example below from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/modules/DonateToCranks/DonateToCranks.lua#L14-L21 DonateToCranks.lua]''' we see the table &amp;lt;code&amp;gt;flavours&amp;lt;/code&amp;gt; being populated by strings from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/lang/module-donatetocranks/en.json data/lang/module-donatetocranks/en.lua]'''&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 for i = 0,9 do&lt;br /&gt;
 	table.insert(flavours, {&lt;br /&gt;
 		title     = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE&amp;quot;],&lt;br /&gt;
 		desc      = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_DESC&amp;quot;],&lt;br /&gt;
 		message   = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_MESSAGE&amp;quot;],&lt;br /&gt;
 	})&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
The first string in the table is given the value of &amp;lt;code&amp;gt;l[FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE]&amp;lt;/code&amp;gt; is concatenated to &amp;lt;code&amp;gt;l[FLAVOUR_0_ADTITLE]&amp;lt;/code&amp;gt; which corresponds to the first string in the language module which is seen below.&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;FLAVOUR_0_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE!&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_DESC&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;The Church of The Celestial Flying Spaghetti Monster needs YOUR money to spread the word of god.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_MESSAGE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Please select an amount to donate to the Church of the Celestial Flying Spaghetti Monster.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_1_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 &lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
===Lua Standard Library - String manipulation===&lt;br /&gt;
&lt;br /&gt;
I would start to search the '''data/''' register for ''' 'string' '''. to get a feeling for how it's used. Again on Linux i would use the 'grep' command. &amp;lt;code&amp;gt;grep -R &amp;quot;string\.&amp;quot; data/&amp;lt;/code&amp;gt;. You will see that the two most frequently used functions is &amp;lt;code&amp;gt;string.format()&amp;lt;/code&amp;gt;, which is a part of the lua standard library, and &amp;lt;code&amp;gt;string.interp()&amp;lt;/code&amp;gt;, which is a Pioneer global function residing in '''data/libs/autoload.lua''', and is described in the next paragraph about [https://wiki.pioneerspacesim.net/wiki/Strings_and_translation#String_variables String variables].&lt;br /&gt;
&lt;br /&gt;
Some examples from the Lua standard library:&lt;br /&gt;
 Comms.Message(string.lower('wHaT cAsE sHoULd I usE?'))&lt;br /&gt;
&lt;br /&gt;
Maybe not the most easy function to fit into Pioneer, but it's there:&lt;br /&gt;
 Comms.Message('Today is opposite day!')&lt;br /&gt;
 Comms.Message(string.reverse('No, today is not opposite day!'))&lt;br /&gt;
&lt;br /&gt;
A more complex example from '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/pigui/modules/hyperjump-planner.lua#L141 data/pigui/modules/hyperjump-planner.lua]'''&amp;lt;br&amp;gt;&lt;br /&gt;
Here &amp;lt;code&amp;gt;string.format(&amp;quot;%.2f&amp;quot;, distance)&amp;lt;/code&amp;gt; is used to set the number of decimals in the jump to two. The units are translatable but the system name off course isn't (jump_sys.name). '''lc''' is used as the short name for the '''core''' translation resource.&lt;br /&gt;
&lt;br /&gt;
 textLine = jumpIndex ..&amp;quot;: &amp;quot;.. jump_sys.name .. &amp;quot; (&amp;quot; .. string.format(&amp;quot;%.2f&amp;quot;, distance) .. lc.UNIT_LY .. &amp;quot; - &amp;quot; .. fuel .. lc.UNIT_TONNES..&amp;quot;)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here is what the final formatted text looks like (right side, two selected jump routes):&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:hyperjumpplanner.png]]&lt;br /&gt;
&lt;br /&gt;
===PiGUI - String formatting===&lt;br /&gt;
&lt;br /&gt;
Pioneer has it's own set of formatting functions accessible through PiGUI, our UI. Some of it is realised on the C++ side and described in codedoc [https://codedoc.pioneerspacesim.net/#CClass:Format here]. Most of the functions, however, are strictly Lua and reside in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/libs/text.lua#L146-L385 data/pigui/libs/text.lua]. You access the functions with ''''require''''.&lt;br /&gt;
&lt;br /&gt;
 local Format = require 'Format'&lt;br /&gt;
&lt;br /&gt;
Here is an example from the System overview. The table to the right is relying heavily on the Format functions.&lt;br /&gt;
[[File:Systemoverview.png]]&lt;br /&gt;
&lt;br /&gt;
The code responsible for the table is here in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/modules/system-view-ui.lua#L811-L840 data/pigui/modules/system-view-ui.lua] with an excerpt showed below. The examples below take 1 or two arguments and the lines ending with 'or nil' is what makes the System overview info window omit values that don't apply to the selected system body.&lt;br /&gt;
&lt;br /&gt;
 ...&lt;br /&gt;
 { name = lc.ESCAPE_VELOCITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Speed(body.escapeVelocity , true) or nil },&lt;br /&gt;
 { name = lc.MEAN_DENSITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Mass(body.meanDensity)..&amp;quot;/m³&amp;quot; or nil },&lt;br /&gt;
 { name = lc.ORBITAL_PERIOD, icon = icons.body_orbit_period,&lt;br /&gt;
     value = op and op &amp;gt; 0 and ui.Format.Duration(op, 2) or nil },&lt;br /&gt;
 { name = lc.DAY_LENGTH, icon = icons.body_day_length,&lt;br /&gt;
     value = rp &amp;gt; 0 and ui.Format.Duration(rp, 2) or nil },&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
==String variables==&lt;br /&gt;
&lt;br /&gt;
When you want a string to present variable data such as numbers and names you use the &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; function. &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; accepts a table '{}' and the corresponding keys are then embedded in the translated strings enclosed by curly brackets '{}'.&lt;br /&gt;
&lt;br /&gt;
Using ''''module-stationrefuelling'''' we can do:&lt;br /&gt;
 print(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = 'JFK', fee = 50}))&lt;br /&gt;
&lt;br /&gt;
Example from the actual StationRefuelling module:&lt;br /&gt;
&lt;br /&gt;
'''data/modules/StationRefuelling/'''&lt;br /&gt;
 Comms.Message(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = station.label,fee = Format.Money(fee)}))&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/en.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Welcome to {station}. Your landing fee of {fee} has been deducted.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/it.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Benvenuti a {station}. Vi è stata dedotta una tassa di atterraggio di {fee}.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Mission flavours==&lt;br /&gt;
&lt;br /&gt;
A script can define a mission and then place many instances of it onto many bulletin boards. A script that introduces many instances in the game should provide some variety; it would harm immersion if, for examle, all delivery missions were worded identically. The easiest way to achieve multiple versions, or &amp;quot;flavours&amp;quot;, is by just making a number of different strings and then choosing between them with a simple random function. This is how the [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L18:L48 deny] and [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L282:L320 pirate taunt] messages are done in the DeliverPackage module.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/DeliverPackage/DeliverPackage.lua'''&lt;br /&gt;
 local num_deny = 8&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 if not qualified then&lt;br /&gt;
     local introtext = l[&amp;quot;DENY_&amp;quot;..Engine.rand:Integer(1,num_deny)-1]&lt;br /&gt;
         form:SetMessage(introtext)&lt;br /&gt;
     return&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-deliverpackage/en.json'''&lt;br /&gt;
 &amp;quot;DENY_0&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm sorry, but I don't think I can trust you with this delivery.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;DENY_1&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Come back when you have some qualifications.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;DENY_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm not willing to risk this delivery on someone without any qualifications like you.&amp;quot;&lt;br /&gt;
You don't have to use a string variable that has been passed to the translation file. The pirate taunts, for example, have access to the clients name and the destination of the package but this information is only used in some of the strings.&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;That package isn't going to reach its destination today.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_8&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;You're not getting to {location} today!&amp;quot;&lt;br /&gt;
Another way to add variation is to have a table of strings that are variations of a theme, a flavour. In the example below, again from the DeliverPackage module, you have two of the lines from '''FLAVOUR_0''' AND '''FLAVOUR_1''' compared.&lt;br /&gt;
 &amp;quot;FLAVOUR_0_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;GOING TO the {system} system? Money paid for delivery of a small package.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_0_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Unacceptable! You took forever over that delivery. I'm not willing to pay you.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;FLAVOUR_1_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;WANTED. Delivery of a package to the {system} system.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_1_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm frustrated by the late delivery of my package, and I refuse to pay you.&amp;quot;&lt;br /&gt;
There are ten flavours in DeliverPackage of five strings each, each forming a short story line. They are static in that you only see lines from within the same flavour. You know what lines will follow if you accept the mission and after a short time of playing you know this already from seeing the ad if delivering things is your forte. It's hard to share strings in between the flavours as they would need to be much more similar. It's a trade off. You could build a very complex interaction with the customers but in the end it needs to be translatable.&lt;br /&gt;
&lt;br /&gt;
Generally, the more specific the text is, the stranger it will appear to see it multiple times in the game. For example, consider the following two examples:&lt;br /&gt;
 - &amp;quot;Contract is to move X tonnes of Y to system Z&amp;quot;&lt;br /&gt;
 - &amp;quot;My uncle's cat got sick, so I can't travel right now, so I need you to take my contract for me, to move X tonnes of Y to my home system Z&amp;quot;&lt;br /&gt;
The former might not need any flavours, as it's unpersonal, and very general. Seeing the second string multiple times does break immersion. Solution is to not show it often, either by making the mission rare / single occurance (no script to date does this) or have many flavours, making probability of the same twice low.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4731</id>
		<title>Strings and translation</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4731"/>
		<updated>2025-03-21T20:12:11Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Lua Standard Library - String manipulation */ Fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Internationalization==&lt;br /&gt;
Recommended reading: [https://dev.pioneerspacesim.net/contribute/translations Pioneer translators wikipage]&lt;br /&gt;
&lt;br /&gt;
It is important that scripters are familiar with the translation system. In the previous example '''hello_world.lua''' used strings directly like &amp;lt;code&amp;gt;print(&amp;quot;Hello, World!&amp;quot;)&amp;lt;/code&amp;gt;. In order for Pioneer to be translated we instead use tokens and calls on the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module to substitute it for the string in the chosen language. The translated strings are stored together with their 'key' in json format, one language per file that are kept in the [https://github.com/pioneerspacesim/pioneer/tree/master/data/lang data/lang/] directory. If the script file in which they are used is from the '''data/modules''' directory they have names starting with '''module-''' and ending with the name of the module written in one word, in small caps, and with the '.lua' ending omitted. The final translation from English to other languages is done on Pioneer's project page at [https://www.transifex.com/pioneer/pioneer/ Transifex], a dedicated online translation tool. The translations are committed to the pioneer/master branch on GitHub by a bot and are not to be edited manually apart from the '''en.json''' files.&lt;br /&gt;
&lt;br /&gt;
==Translating strings==&lt;br /&gt;
&lt;br /&gt;
Below is a minimal example with one string added into the translation system. It takes two files, one in '''data/modules''' and one in '''data/lang/module-hello'''. The print statement isn't wrapped in a function so it will be executed when the '''hello.lua''' is being parsed on startup. You will have to scroll through the command-line history to find it interleaved with the other output.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/hello.lua'''.&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(l.HELLO)&lt;br /&gt;
&lt;br /&gt;
And a corresponding file containing the translated string, and a description field shown to the translator, giving the context. &lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-hello/en.json'''&lt;br /&gt;
 {&lt;br /&gt;
     &amp;quot;HELLO&amp;quot;: {&lt;br /&gt;
         &amp;quot;description&amp;quot;: &amp;quot;A classic message.&amp;quot;,&lt;br /&gt;
         &amp;quot;message&amp;quot;: &amp;quot;Hello World!&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In short, any script containing strings that needs translation needs to load the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module:&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
Then load the translation resource they want to use in their module, often just named &amp;lt;code&amp;gt;'l'&amp;lt;/code&amp;gt; as in:&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
You may load more than one translation resource in your script but they will need to have unique names:&lt;br /&gt;
 local lh = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 local lg = Lang.GetResource(&amp;quot;module-goodbye&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Working with strings==&lt;br /&gt;
&lt;br /&gt;
Recommended chapters from [https://www.lua.org/pil/contents.html Programming in Lua (first edition) ]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/2.4.html 2.4 – Strings]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/3.4.html 3.4 – Concatenation]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/20.html 20 – The String Library]&amp;lt;br&amp;gt;&lt;br /&gt;
And from the [https://www.lua.org/manual/5.2/ Lua 5.2 Reference Manual]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/manual/5.2/manual.html#6.4 6.4 – String Manipulation]&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Concatenation===&lt;br /&gt;
&lt;br /&gt;
Instead of serving ready made sentences we can stitch them together with the Lua string concatenation operator &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt;. The following codelines result in the same output.&lt;br /&gt;
 print(&amp;quot;Hello Clarice!&amp;quot;)&lt;br /&gt;
 print(&amp;quot;Hello&amp;quot; .. &amp;quot; &amp;quot; .. &amp;quot;Clarice!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
This could be created by the more generic:&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local NameGen = require 'NameGen'&lt;br /&gt;
 &lt;br /&gt;
 local GREETING = 'Hello'&lt;br /&gt;
 local FBIAGENT = NameGen.Surname()&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship == Game.player then&lt;br /&gt;
         Comms.Message(GREETING .. ' ' .. FBIAGENT .. '!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; operator is used all over the codebase. Try a search for its occurrence. On Linux I would do &amp;lt;code&amp;gt;grep -R &amp;quot; \.\. &amp;quot; data/modules/ data/libs/ data/pigui/&amp;lt;/code&amp;gt; and that will render a lot of output.&lt;br /&gt;
&lt;br /&gt;
===Token concatenation===&lt;br /&gt;
&lt;br /&gt;
You concatenate strings but you also concatenate the string tokens. In the example below from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/modules/DonateToCranks/DonateToCranks.lua#L14-L21 DonateToCranks.lua]''' we see the table &amp;lt;code&amp;gt;flavours&amp;lt;/code&amp;gt; being populated by strings from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/lang/module-donatetocranks/en.json data/lang/module-donatetocranks/en.lua]'''&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 for i = 0,9 do&lt;br /&gt;
 	table.insert(flavours, {&lt;br /&gt;
 		title     = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE&amp;quot;],&lt;br /&gt;
 		desc      = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_DESC&amp;quot;],&lt;br /&gt;
 		message   = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_MESSAGE&amp;quot;],&lt;br /&gt;
 	})&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
The first string in the table is given the value of &amp;lt;code&amp;gt;l[FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE]&amp;lt;/code&amp;gt; is concatenated to &amp;lt;code&amp;gt;l[FLAVOUR_0_ADTITLE]&amp;lt;/code&amp;gt; which corresponds to the first string in the language module which is seen below.&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;FLAVOUR_0_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE!&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_DESC&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;The Church of The Celestial Flying Spaghetti Monster needs YOUR money to spread the word of god.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_MESSAGE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Please select an amount to donate to the Church of the Celestial Flying Spaghetti Monster.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_1_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 &lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
===Lua Standard Library - String manipulation===&lt;br /&gt;
&lt;br /&gt;
I would start to search the '''data/''' register for ''' 'string' '''. to get a feeling for how it's used. Again on Linux i would use the 'grep' command. &amp;lt;code&amp;gt;grep -R &amp;quot;string\.&amp;quot; data/&amp;lt;/code&amp;gt;. You will see that the two most frequently used functions is &amp;lt;code&amp;gt;string.format()&amp;lt;/code&amp;gt;, which is a part of the lua standard library, and &amp;lt;code&amp;gt;string.interp()&amp;lt;/code&amp;gt;, which is a Pioneer global function residing in '''data/libs/autoload.lua''', and is described in the next paragraph about [https://wiki.pioneerspacesim.net/wiki/Strings_and_translation#String_variables String variables].&lt;br /&gt;
&lt;br /&gt;
Some examples from the Lua standard library:&lt;br /&gt;
 Comms.Message(string.lower('wHaT cAsE sHoULd I usE?'))&lt;br /&gt;
&lt;br /&gt;
Maybe not the most easy function to fit into Pioneer, but it's there:&lt;br /&gt;
 Comms.Message('Today is opposite day!')&lt;br /&gt;
 Comms.Message(string.reverse('No, today is not opposite day!'))&lt;br /&gt;
&lt;br /&gt;
A more complex example from '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/pigui/modules/hyperjump-planner.lua#L141 data/pigui/modules/hyperjump-planner.lua]'''&amp;lt;br&amp;gt;&lt;br /&gt;
Here &amp;lt;code&amp;gt;string.format(&amp;quot;%.2f&amp;quot;, distance)&amp;lt;/code&amp;gt; is used to set the number of decimals in the jump to two. The units are translatable but the system name off course isn't (jump_sys.name). '''lc''' is used as the short name for the '''core''' translation resource.&lt;br /&gt;
&lt;br /&gt;
 textLine = jumpIndex ..&amp;quot;: &amp;quot;.. jump_sys.name .. &amp;quot; (&amp;quot; .. string.format(&amp;quot;%.2f&amp;quot;, distance) .. lc.UNIT_LY .. &amp;quot; - &amp;quot; .. fuel .. lc.UNIT_TONNES..&amp;quot;)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here is what the final formatted text looks like (right side, two selected jump routes):&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:hyperjumpplanner.png]]&lt;br /&gt;
&lt;br /&gt;
===PiGUI - String formatting===&lt;br /&gt;
&lt;br /&gt;
Pioneer has it's own set of formatting functions accessible through PiGUI, our UI. Some of it is realised on the C++ side and described in codedoc [https://codedoc.pioneerspacesim.net/#CClass:Format here]. Most of the functions, however, are strictly Lua and reside in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/libs/text.lua#L146-L385 data/pigui/libs/text.lua]. You access the functions with ''''require''''.&lt;br /&gt;
&lt;br /&gt;
 local Format = require 'Format'&lt;br /&gt;
&lt;br /&gt;
Here is an example from the System overview. The table to the right is relying heavily on the Format functions.&lt;br /&gt;
[[File:Systemoverview.png]]&lt;br /&gt;
&lt;br /&gt;
The code responsible for the table is here in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/modules/system-view-ui.lua#L811-L840 data/pigui/modules/system-view-ui.lua] with an excerpt showed below. The examples below take 1 or two arguments and the lines ending with 'or nil' is what makes the System overview info window omit values that don't apply to the selected system body.&lt;br /&gt;
&lt;br /&gt;
 { name = lc.ESCAPE_VELOCITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Speed(body.escapeVelocity , true) or nil },&lt;br /&gt;
 { name = lc.MEAN_DENSITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Mass(body.meanDensity)..&amp;quot;/m³&amp;quot; or nil },&lt;br /&gt;
 { name = lc.ORBITAL_PERIOD, icon = icons.body_orbit_period,&lt;br /&gt;
     value = op and op &amp;gt; 0 and ui.Format.Duration(op, 2) or nil },&lt;br /&gt;
 { name = lc.DAY_LENGTH, icon = icons.body_day_length,&lt;br /&gt;
     value = rp &amp;gt; 0 and ui.Format.Duration(rp, 2) or nil },&lt;br /&gt;
 { name = luc.ORBIT_APOAPSIS, icon = icons.body_semi_major_axis,&lt;br /&gt;
     value = (parent and not surface) and ui.Format.Distance(body.apoapsis) or nil },&lt;br /&gt;
 { name = luc.ORBIT_PERIAPSIS, icon = icons.body_semi_major_axis,&lt;br /&gt;
     value = (parent and not surface) and ui.Format.Distance(body.periapsis) or nil },&lt;br /&gt;
&lt;br /&gt;
==String variables==&lt;br /&gt;
&lt;br /&gt;
When you want a string to present variable data such as numbers and names you use the &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; function. &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; accepts a table '{}' and the corresponding keys are then embedded in the translated strings enclosed by curly brackets '{}'.&lt;br /&gt;
&lt;br /&gt;
Using ''''module-stationrefuelling'''' we can do:&lt;br /&gt;
 print(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = 'JFK', fee = 50}))&lt;br /&gt;
&lt;br /&gt;
Example from the actual StationRefuelling module:&lt;br /&gt;
&lt;br /&gt;
'''data/modules/StationRefuelling/'''&lt;br /&gt;
 Comms.Message(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = station.label,fee = Format.Money(fee)}))&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/en.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Welcome to {station}. Your landing fee of {fee} has been deducted.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/it.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Benvenuti a {station}. Vi è stata dedotta una tassa di atterraggio di {fee}.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Mission flavours==&lt;br /&gt;
&lt;br /&gt;
A script can define a mission and then place many instances of it onto many bulletin boards. A script that introduces many instances in the game should provide some variety; it would harm immersion if, for examle, all delivery missions were worded identically. The easiest way to achieve multiple versions, or &amp;quot;flavours&amp;quot;, is by just making a number of different strings and then choosing between them with a simple random function. This is how the [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L18:L48 deny] and [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L282:L320 pirate taunt] messages are done in the DeliverPackage module.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/DeliverPackage/DeliverPackage.lua'''&lt;br /&gt;
 local num_deny = 8&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 if not qualified then&lt;br /&gt;
     local introtext = l[&amp;quot;DENY_&amp;quot;..Engine.rand:Integer(1,num_deny)-1]&lt;br /&gt;
         form:SetMessage(introtext)&lt;br /&gt;
     return&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-deliverpackage/en.json'''&lt;br /&gt;
 &amp;quot;DENY_0&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm sorry, but I don't think I can trust you with this delivery.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;DENY_1&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Come back when you have some qualifications.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;DENY_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm not willing to risk this delivery on someone without any qualifications like you.&amp;quot;&lt;br /&gt;
You don't have to use a string variable that has been passed to the translation file. The pirate taunts, for example, have access to the clients name and the destination of the package but this information is only used in some of the strings.&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;That package isn't going to reach its destination today.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_8&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;You're not getting to {location} today!&amp;quot;&lt;br /&gt;
Another way to add variation is to have a table of strings that are variations of a theme, a flavour. In the example below, again from the DeliverPackage module, you have two of the lines from '''FLAVOUR_0''' AND '''FLAVOUR_1''' compared.&lt;br /&gt;
 &amp;quot;FLAVOUR_0_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;GOING TO the {system} system? Money paid for delivery of a small package.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_0_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Unacceptable! You took forever over that delivery. I'm not willing to pay you.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;FLAVOUR_1_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;WANTED. Delivery of a package to the {system} system.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_1_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm frustrated by the late delivery of my package, and I refuse to pay you.&amp;quot;&lt;br /&gt;
There are ten flavours in DeliverPackage of five strings each, each forming a short story line. They are static in that you only see lines from within the same flavour. You know what lines will follow if you accept the mission and after a short time of playing you know this already from seeing the ad if delivering things is your forte. It's hard to share strings in between the flavours as they would need to be much more similar. It's a trade off. You could build a very complex interaction with the customers but in the end it needs to be translatable.&lt;br /&gt;
&lt;br /&gt;
Generally, the more specific the text is, the stranger it will appear to see it multiple times in the game. For example, consider the following two examples:&lt;br /&gt;
 - &amp;quot;Contract is to move X tonnes of Y to system Z&amp;quot;&lt;br /&gt;
 - &amp;quot;My uncle's cat got sick, so I can't travel right now, so I need you to take my contract for me, to move X tonnes of Y to my home system Z&amp;quot;&lt;br /&gt;
The former might not need any flavours, as it's unpersonal, and very general. Seeing the second string multiple times does break immersion. Solution is to not show it often, either by making the mission rare / single occurance (no script to date does this) or have many flavours, making probability of the same twice low.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4730</id>
		<title>Strings and translation</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4730"/>
		<updated>2025-03-21T19:45:19Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* PiGUI - String formatting */ fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Internationalization==&lt;br /&gt;
Recommended reading: [https://dev.pioneerspacesim.net/contribute/translations Pioneer translators wikipage]&lt;br /&gt;
&lt;br /&gt;
It is important that scripters are familiar with the translation system. In the previous example '''hello_world.lua''' used strings directly like &amp;lt;code&amp;gt;print(&amp;quot;Hello, World!&amp;quot;)&amp;lt;/code&amp;gt;. In order for Pioneer to be translated we instead use tokens and calls on the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module to substitute it for the string in the chosen language. The translated strings are stored together with their 'key' in json format, one language per file that are kept in the [https://github.com/pioneerspacesim/pioneer/tree/master/data/lang data/lang/] directory. If the script file in which they are used is from the '''data/modules''' directory they have names starting with '''module-''' and ending with the name of the module written in one word, in small caps, and with the '.lua' ending omitted. The final translation from English to other languages is done on Pioneer's project page at [https://www.transifex.com/pioneer/pioneer/ Transifex], a dedicated online translation tool. The translations are committed to the pioneer/master branch on GitHub by a bot and are not to be edited manually apart from the '''en.json''' files.&lt;br /&gt;
&lt;br /&gt;
==Translating strings==&lt;br /&gt;
&lt;br /&gt;
Below is a minimal example with one string added into the translation system. It takes two files, one in '''data/modules''' and one in '''data/lang/module-hello'''. The print statement isn't wrapped in a function so it will be executed when the '''hello.lua''' is being parsed on startup. You will have to scroll through the command-line history to find it interleaved with the other output.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/hello.lua'''.&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(l.HELLO)&lt;br /&gt;
&lt;br /&gt;
And a corresponding file containing the translated string, and a description field shown to the translator, giving the context. &lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-hello/en.json'''&lt;br /&gt;
 {&lt;br /&gt;
     &amp;quot;HELLO&amp;quot;: {&lt;br /&gt;
         &amp;quot;description&amp;quot;: &amp;quot;A classic message.&amp;quot;,&lt;br /&gt;
         &amp;quot;message&amp;quot;: &amp;quot;Hello World!&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In short, any script containing strings that needs translation needs to load the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module:&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
Then load the translation resource they want to use in their module, often just named &amp;lt;code&amp;gt;'l'&amp;lt;/code&amp;gt; as in:&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
You may load more than one translation resource in your script but they will need to have unique names:&lt;br /&gt;
 local lh = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 local lg = Lang.GetResource(&amp;quot;module-goodbye&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Working with strings==&lt;br /&gt;
&lt;br /&gt;
Recommended chapters from [https://www.lua.org/pil/contents.html Programming in Lua (first edition) ]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/2.4.html 2.4 – Strings]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/3.4.html 3.4 – Concatenation]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/20.html 20 – The String Library]&amp;lt;br&amp;gt;&lt;br /&gt;
And from the [https://www.lua.org/manual/5.2/ Lua 5.2 Reference Manual]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/manual/5.2/manual.html#6.4 6.4 – String Manipulation]&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Concatenation===&lt;br /&gt;
&lt;br /&gt;
Instead of serving ready made sentences we can stitch them together with the Lua string concatenation operator &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt;. The following codelines result in the same output.&lt;br /&gt;
 print(&amp;quot;Hello Clarice!&amp;quot;)&lt;br /&gt;
 print(&amp;quot;Hello&amp;quot; .. &amp;quot; &amp;quot; .. &amp;quot;Clarice!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
This could be created by the more generic:&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local NameGen = require 'NameGen'&lt;br /&gt;
 &lt;br /&gt;
 local GREETING = 'Hello'&lt;br /&gt;
 local FBIAGENT = NameGen.Surname()&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship == Game.player then&lt;br /&gt;
         Comms.Message(GREETING .. ' ' .. FBIAGENT .. '!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; operator is used all over the codebase. Try a search for its occurrence. On Linux I would do &amp;lt;code&amp;gt;grep -R &amp;quot; \.\. &amp;quot; data/modules/ data/libs/ data/pigui/&amp;lt;/code&amp;gt; and that will render a lot of output.&lt;br /&gt;
&lt;br /&gt;
===Token concatenation===&lt;br /&gt;
&lt;br /&gt;
You concatenate strings but you also concatenate the string tokens. In the example below from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/modules/DonateToCranks/DonateToCranks.lua#L14-L21 DonateToCranks.lua]''' we see the table &amp;lt;code&amp;gt;flavours&amp;lt;/code&amp;gt; being populated by strings from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/lang/module-donatetocranks/en.json data/lang/module-donatetocranks/en.lua]'''&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 for i = 0,9 do&lt;br /&gt;
 	table.insert(flavours, {&lt;br /&gt;
 		title     = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE&amp;quot;],&lt;br /&gt;
 		desc      = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_DESC&amp;quot;],&lt;br /&gt;
 		message   = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_MESSAGE&amp;quot;],&lt;br /&gt;
 	})&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
The first string in the table is given the value of &amp;lt;code&amp;gt;l[FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE]&amp;lt;/code&amp;gt; is concatenated to &amp;lt;code&amp;gt;l[FLAVOUR_0_ADTITLE]&amp;lt;/code&amp;gt; which corresponds to the first string in the language module which is seen below.&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;FLAVOUR_0_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE!&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_DESC&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;The Church of The Celestial Flying Spaghetti Monster needs YOUR money to spread the word of god.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_MESSAGE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Please select an amount to donate to the Church of the Celestial Flying Spaghetti Monster.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_1_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 &lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
===Lua Standard Library - String manipulation===&lt;br /&gt;
&lt;br /&gt;
I would start to search the '''data/''' register for ''' 'string' '''. to get a feeling for how it's used. Again on Linux i would use the 'grep' command. &amp;lt;code&amp;gt;grep -R &amp;quot;string\.&amp;quot; data/&amp;lt;/code&amp;gt;. You will see that the two most frequently used functions is &amp;lt;code&amp;gt;string.format()&amp;lt;/code&amp;gt;, which is a part of the lua standard library, and &amp;lt;code&amp;gt;string.interp()&amp;lt;/code&amp;gt;, which is a Pioneer global function residing in '''data/libs/autoload.lua''', and is described in the next paragraph about [https://wiki.pioneerspacesim.net/wiki/Strings_and_translation#String_variables String variables].&lt;br /&gt;
&lt;br /&gt;
Some examples from the Lua standard library:&lt;br /&gt;
 Comms.Message(string.lower('wHaT cAsE sHoULd I usE?'))&lt;br /&gt;
&lt;br /&gt;
Maybe not the most easy function to fit into Pioneer, but it's there:&lt;br /&gt;
 Comms.Message('Today is opposite day!')&lt;br /&gt;
 Comms.Message(string.reverse('No, today is not opposite day!'))&lt;br /&gt;
&lt;br /&gt;
A more complex example from '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/pigui/modules/hyperjump-planner.lua#L141 data/pigui/modules/hyperjump-planner.lua]'''&amp;lt;br&amp;gt;&lt;br /&gt;
Here &amp;lt;code&amp;gt;string.format(&amp;quot;%.2f&amp;quot;, distance)&amp;lt;/code&amp;gt; is used to set the number of decimals in the jump to two. The rest is string variables that are not translated (names) and string concatenation. '''lc''' is used as the short name for the '''core''' translation resource.&amp;lt;br&amp;gt;&lt;br /&gt;
 textLine = jumpIndex ..&amp;quot;: &amp;quot;.. jump_sys.name .. &amp;quot; (&amp;quot; .. string.format(&amp;quot;%.2f&amp;quot;, distance) .. lc.UNIT_LY .. &amp;quot; - &amp;quot; .. fuel .. lc.UNIT_TONNES..&amp;quot;)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here is what the final formatted text looks like (right side, two selected jump routes):&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:hyperjumpplanner.png]]&lt;br /&gt;
&lt;br /&gt;
===PiGUI - String formatting===&lt;br /&gt;
&lt;br /&gt;
Pioneer has it's own set of formatting functions accessible through PiGUI, our UI. Some of it is realised on the C++ side and described in codedoc [https://codedoc.pioneerspacesim.net/#CClass:Format here]. Most of the functions, however, are strictly Lua and reside in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/libs/text.lua#L146-L385 data/pigui/libs/text.lua]. You access the functions with ''''require''''.&lt;br /&gt;
&lt;br /&gt;
 local Format = require 'Format'&lt;br /&gt;
&lt;br /&gt;
Here is an example from the System overview. The table to the right is relying heavily on the Format functions.&lt;br /&gt;
[[File:Systemoverview.png]]&lt;br /&gt;
&lt;br /&gt;
The code responsible for the table is here in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/modules/system-view-ui.lua#L811-L840 data/pigui/modules/system-view-ui.lua] with an excerpt showed below. The examples below take 1 or two arguments and the lines ending with 'or nil' is what makes the System overview info window omit values that don't apply to the selected system body.&lt;br /&gt;
&lt;br /&gt;
 { name = lc.ESCAPE_VELOCITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Speed(body.escapeVelocity , true) or nil },&lt;br /&gt;
 { name = lc.MEAN_DENSITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Mass(body.meanDensity)..&amp;quot;/m³&amp;quot; or nil },&lt;br /&gt;
 { name = lc.ORBITAL_PERIOD, icon = icons.body_orbit_period,&lt;br /&gt;
     value = op and op &amp;gt; 0 and ui.Format.Duration(op, 2) or nil },&lt;br /&gt;
 { name = lc.DAY_LENGTH, icon = icons.body_day_length,&lt;br /&gt;
     value = rp &amp;gt; 0 and ui.Format.Duration(rp, 2) or nil },&lt;br /&gt;
 { name = luc.ORBIT_APOAPSIS, icon = icons.body_semi_major_axis,&lt;br /&gt;
     value = (parent and not surface) and ui.Format.Distance(body.apoapsis) or nil },&lt;br /&gt;
 { name = luc.ORBIT_PERIAPSIS, icon = icons.body_semi_major_axis,&lt;br /&gt;
     value = (parent and not surface) and ui.Format.Distance(body.periapsis) or nil },&lt;br /&gt;
&lt;br /&gt;
==String variables==&lt;br /&gt;
&lt;br /&gt;
When you want a string to present variable data such as numbers and names you use the &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; function. &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; accepts a table '{}' and the corresponding keys are then embedded in the translated strings enclosed by curly brackets '{}'.&lt;br /&gt;
&lt;br /&gt;
Using ''''module-stationrefuelling'''' we can do:&lt;br /&gt;
 print(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = 'JFK', fee = 50}))&lt;br /&gt;
&lt;br /&gt;
Example from the actual StationRefuelling module:&lt;br /&gt;
&lt;br /&gt;
'''data/modules/StationRefuelling/'''&lt;br /&gt;
 Comms.Message(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = station.label,fee = Format.Money(fee)}))&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/en.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Welcome to {station}. Your landing fee of {fee} has been deducted.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/it.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Benvenuti a {station}. Vi è stata dedotta una tassa di atterraggio di {fee}.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Mission flavours==&lt;br /&gt;
&lt;br /&gt;
A script can define a mission and then place many instances of it onto many bulletin boards. A script that introduces many instances in the game should provide some variety; it would harm immersion if, for examle, all delivery missions were worded identically. The easiest way to achieve multiple versions, or &amp;quot;flavours&amp;quot;, is by just making a number of different strings and then choosing between them with a simple random function. This is how the [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L18:L48 deny] and [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L282:L320 pirate taunt] messages are done in the DeliverPackage module.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/DeliverPackage/DeliverPackage.lua'''&lt;br /&gt;
 local num_deny = 8&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 if not qualified then&lt;br /&gt;
     local introtext = l[&amp;quot;DENY_&amp;quot;..Engine.rand:Integer(1,num_deny)-1]&lt;br /&gt;
         form:SetMessage(introtext)&lt;br /&gt;
     return&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-deliverpackage/en.json'''&lt;br /&gt;
 &amp;quot;DENY_0&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm sorry, but I don't think I can trust you with this delivery.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;DENY_1&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Come back when you have some qualifications.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;DENY_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm not willing to risk this delivery on someone without any qualifications like you.&amp;quot;&lt;br /&gt;
You don't have to use a string variable that has been passed to the translation file. The pirate taunts, for example, have access to the clients name and the destination of the package but this information is only used in some of the strings.&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;That package isn't going to reach its destination today.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_8&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;You're not getting to {location} today!&amp;quot;&lt;br /&gt;
Another way to add variation is to have a table of strings that are variations of a theme, a flavour. In the example below, again from the DeliverPackage module, you have two of the lines from '''FLAVOUR_0''' AND '''FLAVOUR_1''' compared.&lt;br /&gt;
 &amp;quot;FLAVOUR_0_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;GOING TO the {system} system? Money paid for delivery of a small package.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_0_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Unacceptable! You took forever over that delivery. I'm not willing to pay you.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;FLAVOUR_1_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;WANTED. Delivery of a package to the {system} system.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_1_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm frustrated by the late delivery of my package, and I refuse to pay you.&amp;quot;&lt;br /&gt;
There are ten flavours in DeliverPackage of five strings each, each forming a short story line. They are static in that you only see lines from within the same flavour. You know what lines will follow if you accept the mission and after a short time of playing you know this already from seeing the ad if delivering things is your forte. It's hard to share strings in between the flavours as they would need to be much more similar. It's a trade off. You could build a very complex interaction with the customers but in the end it needs to be translatable.&lt;br /&gt;
&lt;br /&gt;
Generally, the more specific the text is, the stranger it will appear to see it multiple times in the game. For example, consider the following two examples:&lt;br /&gt;
 - &amp;quot;Contract is to move X tonnes of Y to system Z&amp;quot;&lt;br /&gt;
 - &amp;quot;My uncle's cat got sick, so I can't travel right now, so I need you to take my contract for me, to move X tonnes of Y to my home system Z&amp;quot;&lt;br /&gt;
The former might not need any flavours, as it's unpersonal, and very general. Seeing the second string multiple times does break immersion. Solution is to not show it often, either by making the mission rare / single occurance (no script to date does this) or have many flavours, making probability of the same twice low.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4729</id>
		<title>Strings and translation</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Strings_and_translation&amp;diff=4729"/>
		<updated>2025-03-21T19:43:13Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Working with strings */ Describe PiGUI's Format functions&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==Internationalization==&lt;br /&gt;
Recommended reading: [https://dev.pioneerspacesim.net/contribute/translations Pioneer translators wikipage]&lt;br /&gt;
&lt;br /&gt;
It is important that scripters are familiar with the translation system. In the previous example '''hello_world.lua''' used strings directly like &amp;lt;code&amp;gt;print(&amp;quot;Hello, World!&amp;quot;)&amp;lt;/code&amp;gt;. In order for Pioneer to be translated we instead use tokens and calls on the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module to substitute it for the string in the chosen language. The translated strings are stored together with their 'key' in json format, one language per file that are kept in the [https://github.com/pioneerspacesim/pioneer/tree/master/data/lang data/lang/] directory. If the script file in which they are used is from the '''data/modules''' directory they have names starting with '''module-''' and ending with the name of the module written in one word, in small caps, and with the '.lua' ending omitted. The final translation from English to other languages is done on Pioneer's project page at [https://www.transifex.com/pioneer/pioneer/ Transifex], a dedicated online translation tool. The translations are committed to the pioneer/master branch on GitHub by a bot and are not to be edited manually apart from the '''en.json''' files.&lt;br /&gt;
&lt;br /&gt;
==Translating strings==&lt;br /&gt;
&lt;br /&gt;
Below is a minimal example with one string added into the translation system. It takes two files, one in '''data/modules''' and one in '''data/lang/module-hello'''. The print statement isn't wrapped in a function so it will be executed when the '''hello.lua''' is being parsed on startup. You will have to scroll through the command-line history to find it interleaved with the other output.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/hello.lua'''.&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
 print(l.HELLO)&lt;br /&gt;
&lt;br /&gt;
And a corresponding file containing the translated string, and a description field shown to the translator, giving the context. &lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-hello/en.json'''&lt;br /&gt;
 {&lt;br /&gt;
     &amp;quot;HELLO&amp;quot;: {&lt;br /&gt;
         &amp;quot;description&amp;quot;: &amp;quot;A classic message.&amp;quot;,&lt;br /&gt;
         &amp;quot;message&amp;quot;: &amp;quot;Hello World!&amp;quot;&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In short, any script containing strings that needs translation needs to load the &amp;lt;code&amp;gt;'Lang'&amp;lt;/code&amp;gt; module:&lt;br /&gt;
 local Lang = require 'Lang'&lt;br /&gt;
Then load the translation resource they want to use in their module, often just named &amp;lt;code&amp;gt;'l'&amp;lt;/code&amp;gt; as in:&lt;br /&gt;
 local l = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
You may load more than one translation resource in your script but they will need to have unique names:&lt;br /&gt;
 local lh = Lang.GetResource(&amp;quot;module-hello&amp;quot;)&lt;br /&gt;
 local lg = Lang.GetResource(&amp;quot;module-goodbye&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
==Working with strings==&lt;br /&gt;
&lt;br /&gt;
Recommended chapters from [https://www.lua.org/pil/contents.html Programming in Lua (first edition) ]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/2.4.html 2.4 – Strings]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/3.4.html 3.4 – Concatenation]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/pil/20.html 20 – The String Library]&amp;lt;br&amp;gt;&lt;br /&gt;
And from the [https://www.lua.org/manual/5.2/ Lua 5.2 Reference Manual]&amp;lt;br&amp;gt;&lt;br /&gt;
[https://www.lua.org/manual/5.2/manual.html#6.4 6.4 – String Manipulation]&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Concatenation===&lt;br /&gt;
&lt;br /&gt;
Instead of serving ready made sentences we can stitch them together with the Lua string concatenation operator &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt;. The following codelines result in the same output.&lt;br /&gt;
 print(&amp;quot;Hello Clarice!&amp;quot;)&lt;br /&gt;
 print(&amp;quot;Hello&amp;quot; .. &amp;quot; &amp;quot; .. &amp;quot;Clarice!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
This could be created by the more generic:&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local NameGen = require 'NameGen'&lt;br /&gt;
 &lt;br /&gt;
 local GREETING = 'Hello'&lt;br /&gt;
 local FBIAGENT = NameGen.Surname()&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship == Game.player then&lt;br /&gt;
         Comms.Message(GREETING .. ' ' .. FBIAGENT .. '!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;..&amp;lt;/code&amp;gt; operator is used all over the codebase. Try a search for its occurrence. On Linux I would do &amp;lt;code&amp;gt;grep -R &amp;quot; \.\. &amp;quot; data/modules/ data/libs/ data/pigui/&amp;lt;/code&amp;gt; and that will render a lot of output.&lt;br /&gt;
&lt;br /&gt;
===Token concatenation===&lt;br /&gt;
&lt;br /&gt;
You concatenate strings but you also concatenate the string tokens. In the example below from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/modules/DonateToCranks/DonateToCranks.lua#L14-L21 DonateToCranks.lua]''' we see the table &amp;lt;code&amp;gt;flavours&amp;lt;/code&amp;gt; being populated by strings from '''[https://github.com/pioneerspacesim/pioneer/blob/b7a84a69803e17d9a47fbf9808fd012dfe1ac274/data/lang/module-donatetocranks/en.json data/lang/module-donatetocranks/en.lua]'''&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 for i = 0,9 do&lt;br /&gt;
 	table.insert(flavours, {&lt;br /&gt;
 		title     = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE&amp;quot;],&lt;br /&gt;
 		desc      = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_DESC&amp;quot;],&lt;br /&gt;
 		message   = l[&amp;quot;FLAVOUR_&amp;quot; .. i .. &amp;quot;_MESSAGE&amp;quot;],&lt;br /&gt;
 	})&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
The first string in the table is given the value of &amp;lt;code&amp;gt;l[FLAVOUR_&amp;quot; .. i .. &amp;quot;_ADTITLE]&amp;lt;/code&amp;gt; is concatenated to &amp;lt;code&amp;gt;l[FLAVOUR_0_ADTITLE]&amp;lt;/code&amp;gt; which corresponds to the first string in the language module which is seen below.&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
   &amp;quot;FLAVOUR_0_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE!&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_DESC&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;The Church of The Celestial Flying Spaghetti Monster needs YOUR money to spread the word of god.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_0_MESSAGE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Please select an amount to donate to the Church of the Celestial Flying Spaghetti Monster.&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
   &amp;quot;FLAVOUR_1_ADTITLE&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;DONATE&amp;quot;&lt;br /&gt;
   },&lt;br /&gt;
 &lt;br /&gt;
   ...&lt;br /&gt;
&lt;br /&gt;
===Lua Standard Library - String manipulation===&lt;br /&gt;
&lt;br /&gt;
I would start to search the '''data/''' register for ''' 'string' '''. to get a feeling for how it's used. Again on Linux i would use the 'grep' command. &amp;lt;code&amp;gt;grep -R &amp;quot;string\.&amp;quot; data/&amp;lt;/code&amp;gt;. You will see that the two most frequently used functions is &amp;lt;code&amp;gt;string.format()&amp;lt;/code&amp;gt;, which is a part of the lua standard library, and &amp;lt;code&amp;gt;string.interp()&amp;lt;/code&amp;gt;, which is a Pioneer global function residing in '''data/libs/autoload.lua''', and is described in the next paragraph about [https://wiki.pioneerspacesim.net/wiki/Strings_and_translation#String_variables String variables].&lt;br /&gt;
&lt;br /&gt;
Some examples from the Lua standard library:&lt;br /&gt;
 Comms.Message(string.lower('wHaT cAsE sHoULd I usE?'))&lt;br /&gt;
&lt;br /&gt;
Maybe not the most easy function to fit into Pioneer, but it's there:&lt;br /&gt;
 Comms.Message('Today is opposite day!')&lt;br /&gt;
 Comms.Message(string.reverse('No, today is not opposite day!'))&lt;br /&gt;
&lt;br /&gt;
A more complex example from '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/pigui/modules/hyperjump-planner.lua#L141 data/pigui/modules/hyperjump-planner.lua]'''&amp;lt;br&amp;gt;&lt;br /&gt;
Here &amp;lt;code&amp;gt;string.format(&amp;quot;%.2f&amp;quot;, distance)&amp;lt;/code&amp;gt; is used to set the number of decimals in the jump to two. The rest is string variables that are not translated (names) and string concatenation. '''lc''' is used as the short name for the '''core''' translation resource.&amp;lt;br&amp;gt;&lt;br /&gt;
 textLine = jumpIndex ..&amp;quot;: &amp;quot;.. jump_sys.name .. &amp;quot; (&amp;quot; .. string.format(&amp;quot;%.2f&amp;quot;, distance) .. lc.UNIT_LY .. &amp;quot; - &amp;quot; .. fuel .. lc.UNIT_TONNES..&amp;quot;)&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Here is what the final formatted text looks like (right side, two selected jump routes):&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:hyperjumpplanner.png]]&lt;br /&gt;
&lt;br /&gt;
===PiGUI - String formatting===&lt;br /&gt;
&lt;br /&gt;
Pioneer has it's own set of formatting functions accessible through PiGUI, our UI. Some of it is realised on the C++ side and described in codedoc [https://codedoc.pioneerspacesim.net/#CClass:Format here]. Most of the functions, however, are strictly Lua and reside in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/libs/text.lua#L146-L385 data/pigui/libs/text.lua]. You access the functions with ''''require''''.&lt;br /&gt;
&lt;br /&gt;
 local Format = require 'Format'&lt;br /&gt;
&lt;br /&gt;
Here is an example from the System overview. The table to the right is relying heavily on the Format functions.&lt;br /&gt;
[[File:Systemoverview.png]]&lt;br /&gt;
&lt;br /&gt;
The code responsible for the table is here in [https://github.com/pioneerspacesim/pioneer/blob/5c7b2af87913a7106b69140966578c12e8c9180a/data/pigui/modules/system-view-ui.lua#L811-L840 data/pigui/modules/system-view-ui.lua] with an excerpt showed below. The examples take 1 or two arguments and the constant ending with 'or nil' is what makes the System overview omit values that don't apply to the selected system body.&lt;br /&gt;
&lt;br /&gt;
 { name = lc.ESCAPE_VELOCITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Speed(body.escapeVelocity , true) or nil },&lt;br /&gt;
 { name = lc.MEAN_DENSITY, icon = icons.body_radius,&lt;br /&gt;
     value = (not starport) and ui.Format.Mass(body.meanDensity)..&amp;quot;/m³&amp;quot; or nil },&lt;br /&gt;
 { name = lc.ORBITAL_PERIOD, icon = icons.body_orbit_period,&lt;br /&gt;
     value = op and op &amp;gt; 0 and ui.Format.Duration(op, 2) or nil },&lt;br /&gt;
 { name = lc.DAY_LENGTH, icon = icons.body_day_length,&lt;br /&gt;
     value = rp &amp;gt; 0 and ui.Format.Duration(rp, 2) or nil },&lt;br /&gt;
 { name = luc.ORBIT_APOAPSIS, icon = icons.body_semi_major_axis,&lt;br /&gt;
     value = (parent and not surface) and ui.Format.Distance(body.apoapsis) or nil },&lt;br /&gt;
 { name = luc.ORBIT_PERIAPSIS, icon = icons.body_semi_major_axis,&lt;br /&gt;
     value = (parent and not surface) and ui.Format.Distance(body.periapsis) or nil },&lt;br /&gt;
&lt;br /&gt;
==String variables==&lt;br /&gt;
&lt;br /&gt;
When you want a string to present variable data such as numbers and names you use the &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; function. &amp;lt;code&amp;gt;interp()&amp;lt;/code&amp;gt; accepts a table '{}' and the corresponding keys are then embedded in the translated strings enclosed by curly brackets '{}'.&lt;br /&gt;
&lt;br /&gt;
Using ''''module-stationrefuelling'''' we can do:&lt;br /&gt;
 print(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = 'JFK', fee = 50}))&lt;br /&gt;
&lt;br /&gt;
Example from the actual StationRefuelling module:&lt;br /&gt;
&lt;br /&gt;
'''data/modules/StationRefuelling/'''&lt;br /&gt;
 Comms.Message(l.WELCOME_TO_STATION_FEE_DEDUCTED:interp({station = station.label,fee = Format.Money(fee)}))&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/en.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Welcome to {station}. Your landing fee of {fee} has been deducted.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-stationrefuelling/it.json'''&lt;br /&gt;
 &amp;quot;WELCOME_TO_STATION_FEE_DEDUCTED&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;Station welcome message, ground station landing&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Benvenuti a {station}. Vi è stata dedotta una tassa di atterraggio di {fee}.&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
==Mission flavours==&lt;br /&gt;
&lt;br /&gt;
A script can define a mission and then place many instances of it onto many bulletin boards. A script that introduces many instances in the game should provide some variety; it would harm immersion if, for examle, all delivery missions were worded identically. The easiest way to achieve multiple versions, or &amp;quot;flavours&amp;quot;, is by just making a number of different strings and then choosing between them with a simple random function. This is how the [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L18:L48 deny] and [https://github.com/pioneerspacesim/pioneer/blob/da538b07538962a5473cf02e1da0d9b615698772/data/lang/module-deliverpackage/en.json#L282:L320 pirate taunt] messages are done in the DeliverPackage module.&lt;br /&gt;
&lt;br /&gt;
'''data/modules/DeliverPackage/DeliverPackage.lua'''&lt;br /&gt;
 local num_deny = 8&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 if not qualified then&lt;br /&gt;
     local introtext = l[&amp;quot;DENY_&amp;quot;..Engine.rand:Integer(1,num_deny)-1]&lt;br /&gt;
         form:SetMessage(introtext)&lt;br /&gt;
     return&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
'''data/lang/module-deliverpackage/en.json'''&lt;br /&gt;
 &amp;quot;DENY_0&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm sorry, but I don't think I can trust you with this delivery.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;DENY_1&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Come back when you have some qualifications.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;DENY_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm not willing to risk this delivery on someone without any qualifications like you.&amp;quot;&lt;br /&gt;
You don't have to use a string variable that has been passed to the translation file. The pirate taunts, for example, have access to the clients name and the destination of the package but this information is only used in some of the strings.&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_7&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;That package isn't going to reach its destination today.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;PIRATE_TAUNTS_8&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;You're not getting to {location} today!&amp;quot;&lt;br /&gt;
Another way to add variation is to have a table of strings that are variations of a theme, a flavour. In the example below, again from the DeliverPackage module, you have two of the lines from '''FLAVOUR_0''' AND '''FLAVOUR_1''' compared.&lt;br /&gt;
 &amp;quot;FLAVOUR_0_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;GOING TO the {system} system? Money paid for delivery of a small package.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_0_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;Unacceptable! You took forever over that delivery. I'm not willing to pay you.&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
         ...&lt;br /&gt;
 &lt;br /&gt;
 &amp;quot;FLAVOUR_1_ADTEXT&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;WANTED. Delivery of a package to the {system} system.&amp;quot;&lt;br /&gt;
 },&lt;br /&gt;
 &amp;quot;FLAVOUR_1_FAILUREMSG&amp;quot;: {&lt;br /&gt;
     &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
     &amp;quot;message&amp;quot;: &amp;quot;I'm frustrated by the late delivery of my package, and I refuse to pay you.&amp;quot;&lt;br /&gt;
There are ten flavours in DeliverPackage of five strings each, each forming a short story line. They are static in that you only see lines from within the same flavour. You know what lines will follow if you accept the mission and after a short time of playing you know this already from seeing the ad if delivering things is your forte. It's hard to share strings in between the flavours as they would need to be much more similar. It's a trade off. You could build a very complex interaction with the customers but in the end it needs to be translatable.&lt;br /&gt;
&lt;br /&gt;
Generally, the more specific the text is, the stranger it will appear to see it multiple times in the game. For example, consider the following two examples:&lt;br /&gt;
 - &amp;quot;Contract is to move X tonnes of Y to system Z&amp;quot;&lt;br /&gt;
 - &amp;quot;My uncle's cat got sick, so I can't travel right now, so I need you to take my contract for me, to move X tonnes of Y to my home system Z&amp;quot;&lt;br /&gt;
The former might not need any flavours, as it's unpersonal, and very general. Seeing the second string multiple times does break immersion. Solution is to not show it often, either by making the mission rare / single occurance (no script to date does this) or have many flavours, making probability of the same twice low.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=File:Systemoverview.png&amp;diff=4728</id>
		<title>File:Systemoverview.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=File:Systemoverview.png&amp;diff=4728"/>
		<updated>2025-03-21T19:09:42Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Introduction_to_Mission_Scripting&amp;diff=4727</id>
		<title>Introduction to Mission Scripting</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Introduction_to_Mission_Scripting&amp;diff=4727"/>
		<updated>2025-03-21T16:22:48Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: Add link to online version of 'Programming in Lua'&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Pioneer is extensible through the Lua scripting language. This document is intended to take the reader through the basics of coding up a mission for Pioneer, with working examples. The assumption is made that the reader is already aware of basic programming concepts, and in particular has some knowledge of the Lua programming language.&lt;br /&gt;
&lt;br /&gt;
If you are not familiar with Lua, the [http://lua-users.org/wiki/LearningLua lua-users wiki: Learning Lua] page is a good place to start, or read [http://tylerneylon.com/a/learn-lua/ &amp;quot;learn lua in 15 minutes&amp;quot;]. Also the first edition of Programming in Lua by Roberto Ierusalimschy (the coder behind Lua) is available online [https://lua.org/pil/contents.html here].&lt;br /&gt;
&lt;br /&gt;
==Pioneer's Lua environment==&lt;br /&gt;
Pioneer contains a full Lua interpreter. There are three instances of the interpreter; one for defining models, one for defining the galaxy, and one for writing mission scripts. This document is focused exclusively on the latter of the three.&lt;br /&gt;
&lt;br /&gt;
When Pioneer is started, the universe is generated. After the universe is generated, Pioneer traverses the data/libs directory and all subdirectories, looking for files ending in the &amp;lt;code&amp;gt;.lua&amp;lt;/code&amp;gt; extension. Each of these files is loaded into the interpreter and run. After these are run, Pioneer traverses the data/modules directory and does exactly the same thing. After this, the main menu is shown, and the player can begin a game.&lt;br /&gt;
&lt;br /&gt;
The order in which the Lua scripts are loaded and run is not strictly defined, other than the fact that scripts in the '''data/libs''' directory will be executed before scripts in the '''data/modules''' directory.&lt;br /&gt;
&lt;br /&gt;
Any globally defined functions or variables defined in those scripts will be available to any other scripts. We recommend that any functions or variables not explicitly intended for use by other scripts be declared with the &amp;lt;code&amp;gt;local&amp;lt;/code&amp;gt; keyword, which will make them available only within the scope of the current file.&lt;br /&gt;
&lt;br /&gt;
Chapters:&lt;br /&gt;
* [[Interacting with the game: Event-based programming]]&lt;br /&gt;
* [[Strings and translation]]&lt;br /&gt;
* [[Interacting with the player: BBS forms]]&lt;br /&gt;
* [[Missions and the mission list]]&lt;br /&gt;
* [[Missions and NPC Interaction]]&lt;br /&gt;
* [[Surviving a reload]]&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Creating_Ships_and_Stations&amp;diff=4721</id>
		<title>Creating Ships and Stations</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Creating_Ships_and_Stations&amp;diff=4721"/>
		<updated>2025-02-14T15:34:49Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Ship Properties */ typo&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Before a ship or station can appear in the game, you have to specify its properties.&lt;br /&gt;
&lt;br /&gt;
== Ship Properties ==&lt;br /&gt;
&lt;br /&gt;
'''Note: this is somewhat obsolete now, since ship definitions are in json. The idea is the same, the syntax is a bit different. ''' Ship definitions live in data/ships. Each .lua file in this directory contains one ship definition. The file name (minus the .lua extension) is used as a unique identifier for the ship type (e.g., &amp;quot;wave&amp;quot;), so that code (e.g., mission scripts) can refer to a particular ship type. That identifier is case sensitive -- it is best to stick to the convention used for existing ships (all lower case, using underscores instead of spaces). The already existing kanara.lua is the police ship currently.&lt;br /&gt;
&lt;br /&gt;
The easiest way to create a ship is to copy an existing ship definition and then edit it. The properties you can set (as of 20140411) are:&lt;br /&gt;
&lt;br /&gt;
 define_ship {&lt;br /&gt;
     -- Name of the ship shown to the player (e.g., in the shipyard)&lt;br /&gt;
     name = &amp;quot;&amp;quot;,&lt;br /&gt;
 &lt;br /&gt;
     -- Name of the model used for the ship (minus the &amp;quot;.model&amp;quot; extension. This is case sensitive)&lt;br /&gt;
     model = &amp;quot;&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
     -- Manufacturer of the ship. Only for logo and decal selection currently. &lt;br /&gt;
     -- Valid options are the file names (without extension) from data/icons/manufacturers/&lt;br /&gt;
     manufacturer = &amp;quot;&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
     -- Ship class. Only for icon and text display in the ship market currently.&lt;br /&gt;
     -- Valid options are the file names (without extension) from the data/icons/shipclass/&lt;br /&gt;
     ship_class = &amp;quot;&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
     -- Linear thruster power in Newtons&lt;br /&gt;
     forward_thrust = 65e5,&lt;br /&gt;
     reverse_thrust = 12e5,&lt;br /&gt;
     up_thrust = 18e5,&lt;br /&gt;
     down_thrust = 18e5,&lt;br /&gt;
     left_thrust = 18e5,&lt;br /&gt;
     right_thrust = 18e5,&lt;br /&gt;
 &lt;br /&gt;
     -- Angular thruster power&lt;br /&gt;
     -- This currently uses non-standard units, but is proportional to Newton-metres&lt;br /&gt;
     -- It is best to find the right value by experimenting in-game&lt;br /&gt;
     angular_thrust = 25e5,&lt;br /&gt;
 &lt;br /&gt;
     -- Table of gun mount positions&lt;br /&gt;
     -- note: these values are deprecated fallback values, camera and gun positions are now specified as tag nodes in the model. &lt;br /&gt;
     -- See the article on pioneer wiki about the model system&lt;br /&gt;
     gun_mounts =&lt;br /&gt;
     {&lt;br /&gt;
         { v(0,-2,-46), v(0,0,-1), 5, 'HORIZONTAL' },&lt;br /&gt;
         { v(0,0,0), v(0,0,1), 5, 'HORIZONTAL' },&lt;br /&gt;
     },&lt;br /&gt;
 &lt;br /&gt;
     -- Equipment limits&lt;br /&gt;
     -- Each type of item or equipment has a particular slot that it can fit into;&lt;br /&gt;
     -- equipment can only be fitted if the ship has the right slot available,&lt;br /&gt;
     -- and also has enough capacity to carry the extra mass&lt;br /&gt;
     -- For most types of equipment, the slot limit should be set to&lt;br /&gt;
     -- 0 (to disable that equipment) or 1 (to enable it)&lt;br /&gt;
     -- Some types of equipment could have limits greater than 1 (e.g., cabins, shields)&lt;br /&gt;
 &lt;br /&gt;
     -- note: in the future, these slots will change, because a new Lua-driven equipment&lt;br /&gt;
     -- system is being created (see [https://github.com/pioneerspacesim/pioneer/pull/1719 github issue 1719])&lt;br /&gt;
 &lt;br /&gt;
     max_cargo = 100, -- usually the same as the 'capacity' value, can not be larger than capacity.&lt;br /&gt;
     max_engine = 1, -- set to 0 to prevent fitting a hyperdrive&lt;br /&gt;
     max_laser = 2, -- minimum: 0, maximum 2 currently.&lt;br /&gt;
     max_missile = 4,&lt;br /&gt;
     max_ecm = 1,&lt;br /&gt;
     max_scanner = 1,&lt;br /&gt;
     max_radarmapper = 1,&lt;br /&gt;
     max_hypercloud = 1,&lt;br /&gt;
     max_hullautorepair = 1,&lt;br /&gt;
     max_energybooster = 1,&lt;br /&gt;
     max_atmoshield = 1, -- if it's 0, then the ship can't be bought on surface starports, and NPC ships doesn't try to land on these ports either.&lt;br /&gt;
     max_cabin = 2, -- number of passenger cabins&lt;br /&gt;
     max_shield = 5,&lt;br /&gt;
     max_fuelscoop = 1,&lt;br /&gt;
     max_cargoscoop = 1,&lt;br /&gt;
     max_lasercooler = 1,&lt;br /&gt;
     max_cargolifesupport = 1,&lt;br /&gt;
     max_autopilot = 1,&lt;br /&gt;
 &lt;br /&gt;
     -- crew limits&lt;br /&gt;
     -- note: in the future, the ship will not be able to launch with fewer than min_crew crew-members&lt;br /&gt;
     min_crew = 1,&lt;br /&gt;
     max_crew = 2,&lt;br /&gt;
 &lt;br /&gt;
     -- total cargo and equipment capacity (in tonnes)&lt;br /&gt;
     capacity = 15,&lt;br /&gt;
     -- mass of the empty hull (in tonnes) - health of the ship is determined from this value&lt;br /&gt;
     hull_mass = 10,&lt;br /&gt;
     -- size of the propellant tank (in tonnes)&lt;br /&gt;
     fuel_tank_mass = 15,&lt;br /&gt;
 &lt;br /&gt;
     -- Exhaust velocity Vc [m/s] is equivalent of engine efficiency and depend on used technology.&lt;br /&gt;
     -- Higher Vc means lower fuel consumption. Smaller ships built for speed often mount engines&lt;br /&gt;
     -- with higher Vc. Another way to make faster ship is to increase fuel_tank_mass.&lt;br /&gt;
     effective_exhaust_velocity = 59167e3,&lt;br /&gt;
 &lt;br /&gt;
     -- base price of a ship with just a hyperdrive. If it's 0, then it won't show up in the ship market.&lt;br /&gt;
     price = 70000,&lt;br /&gt;
     -- base hyperdrive fitted as standard (set to 0 if the ship is not hyperspace capable). If it's 0, then it won't be seen flying around as an NC ship.&lt;br /&gt;
     hyperdrive_class = 0,&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
== Station Properties ==&lt;br /&gt;
&lt;br /&gt;
'''To be written...'''&lt;br /&gt;
&lt;br /&gt;
Note: The way that docking bays work is currently being improved. See [https://github.com/pioneerspacesim/pioneer/pull/2058 github issue 2058]&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_game:_Event-based_programming&amp;diff=4719</id>
		<title>Interacting with the game: Event-based programming</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Interacting_with_the_game:_Event-based_programming&amp;diff=4719"/>
		<updated>2025-02-12T18:30:25Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Events */ Codedoc link to Event&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__TOC__&lt;br /&gt;
&lt;br /&gt;
==Events==&lt;br /&gt;
&lt;br /&gt;
The Lua scripts are all executed at startup. If you were to add a single file named &amp;lt;code&amp;gt;hello_world.lua&amp;lt;/code&amp;gt; to the '''data/modules''' directory containing the following:&lt;br /&gt;
&lt;br /&gt;
 print(&amp;quot;Hello, World!&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
you would literally see the words, &amp;quot;''Hello, World!''&amp;quot; appear in Pioneer's output (if running in a terminal) shortly before the main menu appears. You would also see it in the Lua console, if you were to open it.&lt;br /&gt;
&lt;br /&gt;
All file-scoped imperative statements in all Lua files are executed at that time. The way to get Lua code to interact with the game itself, beyond that time, is to write functions and to connect them to event handlers. Many events are triggered by Pioneer during the course of play, all of which will cause any functions which are connected to them, to run. Most will provide those functions with arguments.&lt;br /&gt;
&lt;br /&gt;
Here is a quick list of some of the more commonly used events:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; is triggered when the player clicks on a new game button in the main menu, or when the player loads a game.&lt;br /&gt;
* &amp;lt;code&amp;gt;onEnterSystem&amp;lt;/code&amp;gt; is triggered whenever any ship arrives in the current star system after a hyperspace journey.&lt;br /&gt;
* &amp;lt;code&amp;gt;onLeaveSystem&amp;lt;/code&amp;gt; is triggered whenever any ship leaves the current star system by hyperspacing.&lt;br /&gt;
* &amp;lt;code&amp;gt;onShipDestroyed&amp;lt;/code&amp;gt; is triggered whenever any ship is destroyed.&lt;br /&gt;
* &amp;lt;code&amp;gt;onShipDocked&amp;lt;/code&amp;gt; is triggered whenever any ship docks at a starport.&lt;br /&gt;
&lt;br /&gt;
There are many more. All are fully documented in the [https://codedoc.pioneerspacesim.net/#LuaClass:Event Pioneer Codedoc]. Of the five that I have listed, only &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; does not provide the function with any arguments. The other four provide a reference to the ship in question, and the latter two each also provide an additional argument (a reference to the attacker, and the starport, respectively).&lt;br /&gt;
&lt;br /&gt;
==Writing a function for an event==&lt;br /&gt;
&lt;br /&gt;
An event handling function does not have to return anything. It will be passed any arguments specified in the documentation, which it can either deal with, or ignore. It has access to any variables that are declared in the same file scope, including named functions and tables.&lt;br /&gt;
&lt;br /&gt;
Here is an adaptation of the 'Hello World' message above to be event driven. It's now triggered on game start and will still turn up on the command line, but now much later in the start sequence, pretty much when the game starts and you find yourself docked at a starport.&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local welcome = function ()&lt;br /&gt;
     print(&amp;quot;Hello, World!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, welcome)&lt;br /&gt;
&lt;br /&gt;
We move on. The same function again but now instead the message is presented on the player's ship console and is now welcoming them to Pioneer. We need to add the ''' 'Comms' ''' module to the script and the function name has changed to ''' 'onGameStart' ''', same as the event, which is common practice in Pioneer.&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
The latest code may not work as intended. The reason is that ''' 'onGameStart' ''' is not when the game starts but when it is being launched after pressing the button on the main menu to start on Mars, or whatever location you prefer. Let's see if there is an event that better suits our purpose. ''' 'onShipDocked''''?. This will make the message trigger every time we dock at a space station, on the ground or in orbit. ''' 'onShipDocked' ''' will not trigger on launching a saved game or when we start a new game, docked at a starport. Now the script works just fine and will launch the message the next time you land or dock with a space station.&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function ()&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
If you look at the comms log after docking/landing, you may see the greeting posted more than once. This is because the same function is triggered for all ships in the vicinity, not only the player's. The Pioneer universe is populated by ships and characters and they follow pretty much the same rules as the player. To fix this we need to test if the ship is the player first. Modify the ''' 'onShipDocked' ''' function in the previous example like this:&lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
         Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
An alternative solution to the last code snippet would be to test for if the ship is '''not''' the player:&lt;br /&gt;
&lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if not ship:IsPlayer() then return end&lt;br /&gt;
     Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==Setting a timer==&lt;br /&gt;
Yet another way to avoid the problem with ''' 'onGameStart' ''' missing your function initially is to place a [https://codedoc.pioneerspacesim.net/#CClass:Timer Timer] to delay the event for a short while. Just a second or so to allow the game to load completely. CallAt takes two arguments, the time of action and a function to be carried out. Example form the Timer documentation:&lt;br /&gt;
 Timer:CallAt(Game.time+30, function ()&lt;br /&gt;
     Comms.Message(&amp;quot;Special offer expired, sorry.&amp;quot;)&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
A complete example of the welcome message reworked with a timer. ''' 'Game.time' ''' gives us the game time right now so if we call the timer with ''' 'Game.time + 1' ''' we give it a second to think things through.&lt;br /&gt;
&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     if not Game.player then return end&lt;br /&gt;
 &lt;br /&gt;
     Timer:CallAt(Game.time + 1, function ()&lt;br /&gt;
         Comms.Message ('Welcome to Pioneer!')&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
==Event arguments==&lt;br /&gt;
&lt;br /&gt;
As mentioned before '''[https://codedoc.pioneerspacesim.net/#LuaClass:Event:onShipDocked onShipDocked]''' passes two arguments to the function. A reference to the ship and a reference to the spacestation.&lt;br /&gt;
 local onShipDocked = function (ship, station)&lt;br /&gt;
Through these arguments we also get access to some of the ''' 'ship' ''' and ''' 'station' ''' methods without having to include any modules.&lt;br /&gt;
''' 'ship:IsPlayer()' ''' is for free. Unlimited power is now at your fingertips! ''' 'Comms.Message' ''' takes a second argument for the sender of the message. ''' 'station.label' ''' gives us the name of the space station.&lt;br /&gt;
&lt;br /&gt;
 Comms.Message (&amp;quot;Congratulations! Your ship has been upgraded for free!&amp;quot;, station.label)&lt;br /&gt;
 ship:SetShipType('xylophis')&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=File:Base2.png&amp;diff=4716</id>
		<title>File:Base2.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=File:Base2.png&amp;diff=4716"/>
		<updated>2025-01-18T22:27:52Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=File:Base1.png&amp;diff=4715</id>
		<title>File:Base1.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=File:Base1.png&amp;diff=4715"/>
		<updated>2025-01-18T22:27:17Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=User:Zonkmachine&amp;diff=4714</id>
		<title>User:Zonkmachine</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=User:Zonkmachine&amp;diff=4714"/>
		<updated>2025-01-18T22:19:55Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: zonkmachine - WIP&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Links to wiki test pages and other WIP stuff.&lt;br /&gt;
&lt;br /&gt;
*[[Buildings_and_other_structures|Buildings and stuff]]&lt;br /&gt;
&lt;br /&gt;
Surviving a reload&lt;br /&gt;
*[[Surviving_a_reload_WIP|Surviving a reload WIP]]&lt;br /&gt;
&lt;br /&gt;
Scripting Tutorial TODO:&lt;br /&gt;
*[[Interacting_with_the_player:_BBS_forms|BBS forms]]&lt;br /&gt;
Update BBS images. ''fixed''.&lt;br /&gt;
&lt;br /&gt;
Also mention that the posts are sorted. ''fixed''&lt;br /&gt;
&lt;br /&gt;
Add some info on multiple BBS ads and updating the BBS over time, '''onUpdateBB'''.&lt;br /&gt;
&lt;br /&gt;
Facegen - facemorph:&lt;br /&gt;
*[[Facemorph|Facemorph]]&lt;br /&gt;
&lt;br /&gt;
Facegen - picture cleanup:&lt;br /&gt;
*[[Facegen_picture_cleanup|Facegen - picture cleanup]]&lt;br /&gt;
&lt;br /&gt;
More facegen - hair:&lt;br /&gt;
*[[Facegen_hair|Facegen - more and hair]]&lt;br /&gt;
&lt;br /&gt;
Code example for [[Missions_and_NPC_Interaction|Missions and NPC Interaction]]:&lt;br /&gt;
*[[Missions_and_NPC_Interaction_Code_Example|Missions and NPC Interaction Code Example]]&lt;br /&gt;
&lt;br /&gt;
Snippets for new tutorial:&lt;br /&gt;
*[[Useful_functions|Useful functions]]&lt;br /&gt;
&lt;br /&gt;
Dead links:&lt;br /&gt;
*[[Dead_links|Dead links]]&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
'''Sally. At night a stand up commedian in Cydonia. Daytime, selling parts her cousins strip from ships that does not belong to them:'''&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:Passiveaggressive.png]]&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Surviving_a_reload_WIP&amp;diff=4713</id>
		<title>Surviving a reload WIP</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Surviving_a_reload_WIP&amp;diff=4713"/>
		<updated>2025-01-18T07:07:40Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: Copy Surviving a reload at 15 January 2025‎&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;When a game is saved, a script is responsible for informing Pioneer exactly which of its data needs to be saved. If nothing is specified, nothing will be saved at all. Similarly, after a game is loaded, a script is responsible for restoring all of its saved data; re-creating bulletin board adverts, the player's mission details, and any run-time state.&lt;br /&gt;
&lt;br /&gt;
Here is a simple form. If a new game is started, you will see this on every bulletin board that you visit:&lt;br /&gt;
&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
     form:SetMessage(&amp;quot;Hello!&amp;quot;)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     station:AddAdvert('Click here for a greeting', onChat)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
However, if you save the game, then reload, you will not see it on any bulletin board that had been created in the current system before you saved. onCreateBB will be triggered for any subsequently created bulletin boards, but not for those that already existed. It isn't appropriate to have scripts create new adverts just because a player has loaded; instead, it's the responsibility of the script to tell Pioneer what to save, and to use those saved data to restore all adverts after the game is loaded. The same goes for player missions, and any working data that the script is using.&lt;br /&gt;
&lt;br /&gt;
There are two things we need to do to achieve all of this:&lt;br /&gt;
&lt;br /&gt;
* We need to track everything that the script is doing.&lt;br /&gt;
* We need to be able to pack this away for saving, and bring it back after loading.&lt;br /&gt;
&lt;br /&gt;
==Tracking everything that we are doing==&lt;br /&gt;
&lt;br /&gt;
Any local table that is declared in file scope is visible to the entire script, without affecting any other scripts. Essential data should be kept in such tables.&lt;br /&gt;
&lt;br /&gt;
==Tracking adverts==&lt;br /&gt;
It's important to be able to re-create an advert, so part of the process of making one should be storing that information. Your script will need to make a note of in which station the advert was placed, which flavour was used (if you have flavours), the face data that was used on the form and any unique information that was used, such as specific mission details, reward, etc. The simplest way to keep all of this together is in a table, gathering up the variables by name into keys of the same name. It could look something like this:&lt;br /&gt;
&lt;br /&gt;
 local advert = {&lt;br /&gt;
     station = station,&lt;br /&gt;
     flavour = flavour, -- This will be a table&lt;br /&gt;
     client = client,   -- This will be a table (a character)&lt;br /&gt;
     target = target,&lt;br /&gt;
     destination = destination,&lt;br /&gt;
     reward = reward,&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
In the case of the 'greeting', the first example above, we would just have to save the message ('Hello!'), Advert string ('Click here for a greeting') and the station. It's a very simple example but there are real modules that aren't much more complex than that.&lt;br /&gt;
&lt;br /&gt;
Remember, the &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; method takes three arguments: The advert text, the &amp;lt;code&amp;gt;onChat&amp;lt;/code&amp;gt; function and the &amp;lt;code&amp;gt;onDelete&amp;lt;/code&amp;gt; function. It also returns a unique number, which lends itself nicely to storing the information away. That same unique number is passed to the onChat function, allowing onChat to check that stored information.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;onDelete()&amp;lt;/code&amp;gt; function (again, we call it that by convention only) also accepts this reference as its argument, and is called whenever an advert is destroyed, whether that destruction be explicit (&amp;lt;code&amp;gt;RemoveAdvert()&amp;lt;/code&amp;gt;) or implicit (the player hyperspaces away).&lt;br /&gt;
&lt;br /&gt;
So, we can track adverts that have been created, and stop tracking any that have gone away. We'll do that using the reference returned by &amp;lt;code&amp;gt;AddAdvert()&amp;lt;/code&amp;gt; as the key to a file-scoped local table:&lt;br /&gt;
&lt;br /&gt;
 local flavours = {}&lt;br /&gt;
 local ads = {}&lt;br /&gt;
 &lt;br /&gt;
 local onChat = function (form, ref, option)&lt;br /&gt;
     form:Clear()&lt;br /&gt;
     form:SetFace(ads[ref].character)&lt;br /&gt;
     form:SetMessage(ads[ref].flavour.title)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onDelete = function (ref)&lt;br /&gt;
     ads[ref] = nil&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local onCreateBB = function (station)&lt;br /&gt;
     local flavour = flavours[Engine.rand:Integer(1, #flavours)]&lt;br /&gt;
     ref = station:AddAdvert(flavour.title, onChat, onDelete)&lt;br /&gt;
     ads[ref] = {&lt;br /&gt;
         station = station,&lt;br /&gt;
         flavour = flavour,&lt;br /&gt;
         character = Character.new()&lt;br /&gt;
     }&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
&lt;br /&gt;
==Tracking missions==&lt;br /&gt;
&lt;br /&gt;
This is less of a challenge. Missions created with &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; are stored in the &amp;lt;code&amp;gt;PersistentCharacters.player.missions&amp;lt;/code&amp;gt; table. You also need to register them locally in your script. When &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; is called, it returns a reference to the mission. That reference can be stored in a file-local table that can be iterated through when we need to access a mission. We can wrap these functions up:&lt;br /&gt;
&lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local addMission = function(mission)&lt;br /&gt;
     table.insert(missions,Mission.New(mission))&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local removeMission = function(mref)&lt;br /&gt;
     local ref&lt;br /&gt;
     for i,v in pairs(missions) do&lt;br /&gt;
         if v == mission then&lt;br /&gt;
             ref = i&lt;br /&gt;
             break&lt;br /&gt;
         end&lt;br /&gt;
     end&lt;br /&gt;
     mission:Remove()&lt;br /&gt;
     missions[ref] = nil&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
Now, instead of directly calling &amp;lt;code&amp;gt;Mission.Add()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;mission:Remove()&amp;lt;/code&amp;gt;, we call our local functions, &amp;lt;code&amp;gt;addMission()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;removeMission()&amp;lt;/code&amp;gt;. They will send their argument on to the instance in '''Mission.lua''', and also store or remove the mission ref in the missions table. There is now a record of which missions belong to this script. &amp;lt;code&amp;gt;addMission()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;removeMission()&amp;lt;/code&amp;gt; in the example above is taken from '''SearchRescue.lua'''. Look how &amp;lt;code&amp;gt;removeMission()&amp;lt;/code&amp;gt; is used in the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/SearchRescue/SearchRescue.lua#L241-L252 Search and Rescue module]'''.&lt;br /&gt;
&lt;br /&gt;
==Serializing: Packing away for saving and loading==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;Serializer&amp;lt;/code&amp;gt; object provides one method: &amp;lt;code&amp;gt;Register&amp;lt;/code&amp;gt;. It takes three arguments. The first is a name; a simple string with which to uniquely identify this script. It's probably sensible to re-use the name that was used for the &amp;lt;code&amp;gt;Translate&amp;lt;/code&amp;gt; object's flavour methods. The second argument is the name of a function which will return a single table. The third argument is a function that will accept a single table.&lt;br /&gt;
&lt;br /&gt;
The second argument is your '''serializer''' function. The third is your '''unserializer''' function. By convention, we name these &amp;lt;code&amp;gt;serialize&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;unserialize&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;serialize&amp;lt;/code&amp;gt; must return a table. This table must contain everything that you need to store to get your script working after a reload. It will be run when the player saves the game. The table can contain any pure-lua types, and SystemPath, Body and SceneGraph.ModelSkin core types, anything else will explode, like a ShipDef/EquipDef or a StarSystem.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;unserialize&amp;lt;/code&amp;gt; accepts a table, and does something with it. It will be run by the serializer after the game is loaded, immediately before the &amp;lt;code&amp;gt;onGameStart&amp;lt;/code&amp;gt; event is triggered. It is passed the data that serialize returned at save time.&lt;br /&gt;
&lt;br /&gt;
The most common way to deal with this is as follows (and this is very cut down):&lt;br /&gt;
&lt;br /&gt;
 local table_stuff_this_script_uses = {}&lt;br /&gt;
 local loaded_data&lt;br /&gt;
 &lt;br /&gt;
 -- The rest of the script goes here!&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart&lt;br /&gt;
     if loaded_data then&lt;br /&gt;
         for k,v in loaded_data.table_stuff_this_script_uses do&lt;br /&gt;
             table_stuff_this_script_uses[k] = v&lt;br /&gt;
         end&lt;br /&gt;
         loaded_data = nil&lt;br /&gt;
     else&lt;br /&gt;
         -- New game; do other stuff here perhaps&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local serialize = function ()&lt;br /&gt;
     return {table_stuff_this_script_uses = table_stuff_this_script_uses}&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local unserialize = function (data)&lt;br /&gt;
     loaded_data = data&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
 Serializer:Register(&amp;quot;TestModule&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
==Complete module==&lt;br /&gt;
&lt;br /&gt;
Now we can finally start to put together complete modules that will save, reload, and behave consistently throughout the game. Many of the modules in Pioneer don't register a mission. As an example of this, see '''BreakdownServicing''', '''DonateToCranks''', and '''CrewContracts'''. In the same spirit, let's complete the first example above, 'Click here for a greeting', and make it survive a reload.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local Event = require 'Event'&lt;br /&gt;
local Serializer = require 'Serializer'&lt;br /&gt;
&lt;br /&gt;
local ads = {}&lt;br /&gt;
&lt;br /&gt;
local onChat = function (form, ref, option)&lt;br /&gt;
    local ad = ads[ref]&lt;br /&gt;
    form:Clear()&lt;br /&gt;
    form:SetMessage(ad.bodytext)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local onDelete = function (ref)&lt;br /&gt;
    ads[ref] = nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- when we enter a system the BBS is created and this function is called&lt;br /&gt;
local onCreateBB = function (station)&lt;br /&gt;
&lt;br /&gt;
    local ad = {&lt;br /&gt;
        headline = 'Click here for a greeting',&lt;br /&gt;
        bodytext = &amp;quot;Hello!&amp;quot;,&lt;br /&gt;
        station  = station&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    -- create one per BBS&lt;br /&gt;
    local ref = station:AddAdvert({&lt;br /&gt;
        description = ad.headline,&lt;br /&gt;
        onChat      = onChat,&lt;br /&gt;
        onDelete    = onDelete}&lt;br /&gt;
    )&lt;br /&gt;
    ads[ref] = ad&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local loaded_data&lt;br /&gt;
&lt;br /&gt;
local onGameStart = function ()&lt;br /&gt;
    ads = {}&lt;br /&gt;
&lt;br /&gt;
    if not loaded_data or not loaded_data.ads then return end&lt;br /&gt;
&lt;br /&gt;
    for k,ad in pairs(loaded_data.ads or {}) do&lt;br /&gt;
        local ref = ad.station:AddAdvert({&lt;br /&gt;
            description = ad.headline,&lt;br /&gt;
            onChat      = onChat,&lt;br /&gt;
            onDelete    = onDelete})&lt;br /&gt;
        ads[ref] = ad&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    loaded_data = nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local serialize = function ()&lt;br /&gt;
    return { ads = ads }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local unserialize = function (data)&lt;br /&gt;
    loaded_data = data&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
Serializer:Register(&amp;quot;message&amp;quot;, serialize, unserialize)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The '''Advice''' module generously gave up parts of its code to complete this script.&lt;br /&gt;
&lt;br /&gt;
Although the example is functional, it has been coded with brevity alone in mind. I would not recommend using this as the basis for a mission without at least completely rewriting &amp;lt;code&amp;gt;onChat()&amp;lt;/code&amp;gt;, and possibly tidying up the temporary loop variable names.&lt;br /&gt;
&lt;br /&gt;
==Don't serialize strings==&lt;br /&gt;
&lt;br /&gt;
When we add the strings literally to the ad as we did in the example above, they will be serialized and saved on game end. Considering this is happening on all active stations, this will waste disk space.&lt;br /&gt;
&lt;br /&gt;
 local ad = {&lt;br /&gt;
     headline = 'Click here for a greeting',&lt;br /&gt;
     bodytext = 'Hello!',&lt;br /&gt;
     station  = station&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
What we want to do instead is save the information to recreate the same strings. If for instance we have a set of flavours that are selected with a random number, then generate that number and save it in a variable. The ad example above can then be rewritten as such. We only need the station and the flavour number to recreate the ad.&lt;br /&gt;
&lt;br /&gt;
 local ad = {&lt;br /&gt;
     station = station,&lt;br /&gt;
     n = n&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
The working example in the paragraph above can be updated in this way. When you have the translated strings in a separate module you would start the script by pulling in the strings to the script in an array or set of arrays. In the following example we instead add the strings to an array directly but it shows the general idea.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
local Engine = require 'Engine'&lt;br /&gt;
local Event = require 'Event'&lt;br /&gt;
local Game = require 'Game'&lt;br /&gt;
local Rand = require 'Rand'&lt;br /&gt;
local Serializer = require 'Serializer'&lt;br /&gt;
&lt;br /&gt;
local ads = {}&lt;br /&gt;
&lt;br /&gt;
local flavours = {&lt;br /&gt;
    {flavour = 'flavour 1', title = 'title 1', text = 'text 1'},&lt;br /&gt;
    {flavour = 'flavour 2', title = 'title 2', text = 'text 2'},&lt;br /&gt;
    {flavour = 'flavour 3', title = 'title 3', text = 'text 3'}&lt;br /&gt;
}&lt;br /&gt;
local onChat = function (form, ref, option)&lt;br /&gt;
    local ad = ads[ref]&lt;br /&gt;
    form:Clear()&lt;br /&gt;
    form:SetMessage(flavours[ad.n].text)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local onDelete = function (ref)&lt;br /&gt;
    ads[ref] = nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- when we enter a system the BBS is created and this function is called&lt;br /&gt;
local onCreateBB = function (station)&lt;br /&gt;
&lt;br /&gt;
local rand = Rand.New(Game.time)&lt;br /&gt;
local n = rand:Integer(1, #flavours)&lt;br /&gt;
&lt;br /&gt;
local ad = {&lt;br /&gt;
    station  = station,&lt;br /&gt;
    n = n&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- create one per BBS&lt;br /&gt;
local ref = station:AddAdvert({&lt;br /&gt;
        title       = flavours[n].title,&lt;br /&gt;
        description = flavours[n].flavour,&lt;br /&gt;
        onChat      = onChat,&lt;br /&gt;
        onDelete    = onDelete}&lt;br /&gt;
    )&lt;br /&gt;
    ads[ref] = ad&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local loaded_data&lt;br /&gt;
&lt;br /&gt;
local onGameStart = function ()&lt;br /&gt;
    ads = {}&lt;br /&gt;
&lt;br /&gt;
    if not loaded_data or not loaded_data.ads then return end&lt;br /&gt;
&lt;br /&gt;
    for k,ad in pairs(loaded_data.ads or {}) do&lt;br /&gt;
        local ref = ad.station:AddAdvert({&lt;br /&gt;
            title = flavours[ad.n].title,&lt;br /&gt;
            description = flavours[ad.n].flavour,&lt;br /&gt;
            onChat      = onChat,&lt;br /&gt;
            onDelete    = onDelete})&lt;br /&gt;
            ads[ref]    = ad&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    loaded_data = nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local serialize = function ()&lt;br /&gt;
    return { ads = ads }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local unserialize = function (data)&lt;br /&gt;
    loaded_data = data&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
Event.Register(&amp;quot;onCreateBB&amp;quot;, onCreateBB)&lt;br /&gt;
Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
Serializer:Register(&amp;quot;message&amp;quot;, serialize, unserialize)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The codedoc is complete and accurate, and the scripts provided with Pioneer are good examples themselves. The API is extensive, but if you find that there are additional things you would like to be able to do, or information you would like from Pioneer, the dev team are willing to extend the API to accommodate script writers. Simply create a new issue on the [https://github.com/pioneerspacesim/pioneer/issues Github issue tracker].&lt;br /&gt;
&lt;br /&gt;
Help is also always available on the [https://forum.pioneerspacesim.net/ Pioneer dev forum], and many of the dev team can be found on [[IRC]] at all hours.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=User:Zonkmachine&amp;diff=4712</id>
		<title>User:Zonkmachine</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=User:Zonkmachine&amp;diff=4712"/>
		<updated>2025-01-18T07:05:51Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: WIP - Surviving a reload&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Links to wiki test pages. WIP stuff.&lt;br /&gt;
&lt;br /&gt;
Surviving a reload&lt;br /&gt;
*[[Surviving_a_reload_WIP|Surviving a reload WIP]]&lt;br /&gt;
&lt;br /&gt;
Scripting Tutorial TODO:&lt;br /&gt;
*[[Interacting_with_the_player:_BBS_forms|BBS forms]]&lt;br /&gt;
Update BBS images. ''fixed''.&lt;br /&gt;
&lt;br /&gt;
Also mention that the posts are sorted. ''fixed''&lt;br /&gt;
&lt;br /&gt;
Add some info on multiple BBS ads and updating the BBS over time, '''onUpdateBB'''.&lt;br /&gt;
&lt;br /&gt;
Facegen - facemorph:&lt;br /&gt;
*[[Facemorph|Facemorph]]&lt;br /&gt;
&lt;br /&gt;
Facegen - picture cleanup:&lt;br /&gt;
*[[Facegen_picture_cleanup|Facegen - picture cleanup]]&lt;br /&gt;
&lt;br /&gt;
More facegen - hair:&lt;br /&gt;
*[[Facegen_hair|Facegen - more and hair]]&lt;br /&gt;
&lt;br /&gt;
Code example for [[Missions_and_NPC_Interaction|Missions and NPC Interaction]]:&lt;br /&gt;
*[[Missions_and_NPC_Interaction_Code_Example|Missions and NPC Interaction Code Example]]&lt;br /&gt;
&lt;br /&gt;
Snippets for new tutorial:&lt;br /&gt;
*[[Useful_functions|Useful functions]]&lt;br /&gt;
&lt;br /&gt;
Dead links:&lt;br /&gt;
*[[Dead_links|Dead links]]&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
'''Sally. At night a stand up commedian in Cydonia. Daytime, selling parts her cousins strip from ships that does not belong to them:'''&amp;lt;br&amp;gt;&lt;br /&gt;
[[File:Passiveaggressive.png]]&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4711</id>
		<title>Missions and the mission list</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4711"/>
		<updated>2025-01-16T06:47:50Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Mission description */ Fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The player's mission list==&lt;br /&gt;
&lt;br /&gt;
Once the player has negotiated with your form, there might well be a mission in play. It could be a delivery, an assassination, a rush to tell somebody not to leave because so-and-so loves them... the possibilities are limited only by your creativity. The player needs a way to keep track of all the missions that they have agreed to undertake. Pioneer provides this through the player's mission screen, which they can access at any time using the F3 button, and looking at the missions tab. The content of this screen is controlled by some methods on the &amp;lt;code&amp;gt;Player&amp;lt;/code&amp;gt; object, which can always be found at &amp;lt;code&amp;gt;Game.player&amp;lt;/code&amp;gt;, and which inherits from &amp;lt;code&amp;gt;Ship&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Body&amp;lt;/code&amp;gt;. Missions are added to the screen using the &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; method. It takes a table of info, and returns an integer reference to that mission, which should be stored so that it can be updated or removed later. Below follows a typical use case from the [https://codedoc.pioneerspacesim.net/#LuaClass:Mission:New codedoc].&lt;br /&gt;
&lt;br /&gt;
Create a new mission and add it to the player’s mission list while retrieving the reference number:&lt;br /&gt;
&lt;br /&gt;
 ref = Mission.New({&lt;br /&gt;
     'type'      = 'Delivery', -- Must be a translatable token!&lt;br /&gt;
     'client'    = Character.New(),&lt;br /&gt;
     'due'       = Game.time + 3*24*60*60,       -- three days&lt;br /&gt;
     'reward'    = 123.45,&lt;br /&gt;
     'location'  = SystemPath:New(0,0,0,0,16),   -- Mars High, Sol&lt;br /&gt;
     'status'    = 'ACTIVE',&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
In practice, it might look more like this:&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 600, -- ten minutes' time&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
I don't recommend using &amp;lt;code&amp;gt;Game.player.frameBody.path&amp;lt;/code&amp;gt; here. I'm only using it because it always returns something, whether docked or not. A real mission would probably use a space station here. For this demonstration we've generated 'Taxi' missions that are already a known mission type, registered by '''Taxi.lua''' who wouldn't know about it because the mission scripts remembers it's own missions and has no way of knowing about missions registered by another module. So far no modules use another modules mission type and we only use it here for the sake of demonstration.&lt;br /&gt;
&lt;br /&gt;
This creates a mission visible on the mission screen:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist3.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Registering a mission type==&lt;br /&gt;
Before a module can create missions that are visible in the player's mission list, it needs to register a mission type. It's a painless task.&lt;br /&gt;
&lt;br /&gt;
'''[https://codedoc.pioneerspacesim.net/#LuaClass:Mission:RegisterType LuaClass:Mission:RegisterType]'''&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType(typeid, display, onClick)&lt;br /&gt;
&lt;br /&gt;
Apart from a unique string and a translatable mission name, you have the option to pass a function that takes care of the mission info presented when you press the '''More info...''' button in the missions list. By convention, this function is named '''buildMissionDescription'''. This would typically happen at the very end of the module. Here is what it looks like at the end of [https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L513 '''DeliverPackage.lua''']&lt;br /&gt;
&lt;br /&gt;
 Event.Register(&amp;quot;onGameEnd&amp;quot;, onGameEnd)&lt;br /&gt;
 Event.Register(&amp;quot;onReputationChanged&amp;quot;, onReputationChanged)&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
 &lt;br /&gt;
 Serializer:Register(&amp;quot;DeliverPackage&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The next example is extended with a much scaled-down version of the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L401-https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L434 onShipDocked]''' function from the '''DeliverPackage''' module. Since these missions are recognized by a module in Pioneer already ('Taxi') they will 'probably' be handled at a later stage if we fail to remove them with this test mission. If you generate a new mission type you must also handle removing it or the mission will remain in the game forever. There is no automatic logic, and no automatic removal. Your script must keep track of them. We'll wrap the action in a '''Timer''' function so we don't have to deal with the BBS form this time.&lt;br /&gt;
&lt;br /&gt;
 Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 ...&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Start the game and have a look under the '''Missions''' tab. After 2 seconds two missions are registered. One that should be finished in 10 seconds and another with a due time set 10 seconds after the first one. There is a second timer that checks in with the missions to see how it's going after 15 seconds. One should be completed successfully and the other will fail.&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function (ship, station)&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Missions Created:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist1.png]]&lt;br /&gt;
&lt;br /&gt;
1 Mission failed and 1 mission completed:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist2.png]]&lt;br /&gt;
&lt;br /&gt;
More info? If you look to the right on the mission list there is a button named '''More info...''' which will let you see more specific details of your mission. This will need to be specified in the mission script itself so if you press this button in the example above Pioneer will crash. The common name for the function you need to write in your script is '''buildMissionDescription'''. Here is what it looks like in '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L462-L490 DeliverPackage.lua]'''.&lt;br /&gt;
&lt;br /&gt;
==Mission description==&lt;br /&gt;
If you don't include the '''buildMissionDescription''' argument when you register the mission type, the mission list will not include a '''More info...''' button for the mission type. In the example above we used an existing mission type, '''Taxi''', but we didn't do it from within '''Taxi.lua''' so when '''More info...''' is pressed '''buildMissionDescription''' will be called in '''Taxi.lua''' but it will not now anything about the mission so the function will break. Try and comment out the entire '''buildMissionDescription''' in '''Taxi.lua''' and launch Pioneer once more. The Missions will register but now without the '''More info...''' button. To register a mission type without '''buildMissionDescription''' you simply leave it out.&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI)&lt;br /&gt;
&lt;br /&gt;
You can also declare a minimal '''buildMissionDescription''' that returns nil. This is fine. It will include the button which, if pressed, will just exit immediately and not try and open a new window.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI, buildMissionDescription)&lt;br /&gt;
&lt;br /&gt;
The function responsible for interpreting the mission description is '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/modules/info-view/04-missions.lua#L42-L77 drawMissionDescription()]''' in '''/data/pigui/modules/info-view/04-missions.lua'''. It takes a table '''desc''' as it's argument. A minimal working mission description could look something like the example below. We set '''desc.description''' to an empty string and '''desc.details''' to a table with only one element '''false'''. '''desc.client''' is set to '''mission.client'''. This only works as part of an actual mission script where you already have a registered mission with a client. As you can see the '''Mission Details''' title and the '''Go back''' button are generated automatically.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
Let's fixup the earlier example with it's own mission type and a working '''buildMissionDescription'''.&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Test&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Test&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
  &lt;br /&gt;
 Mission.RegisterType('Test', 'Test', buildMissionDescription)&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
[[File:Minimaldescription.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
That's basically it. Here follows a description of the elements you can add to '''buildMissionDescription'''. Try and expand our latest script on you own.&lt;br /&gt;
&lt;br /&gt;
===desc.description===&lt;br /&gt;
This is a string and will typically be based on the conversation from onChat.&lt;br /&gt;
&lt;br /&gt;
 desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===desc.client===&lt;br /&gt;
Takes a character as it's input and usually this means the character passed via the mission argument.&lt;br /&gt;
&lt;br /&gt;
 desc.client = mission.client&lt;br /&gt;
&lt;br /&gt;
===desc.details===&lt;br /&gt;
This is a table that is passed to '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/libs/text-table.lua#L15-L28 textTable.draw()]''' in '''data/pigui/libs/text-table.lua'''.&lt;br /&gt;
&lt;br /&gt;
 desc.details = {&lt;br /&gt;
     {&amp;quot;data here&amp;quot;, &amp;quot;more data&amp;quot;},&lt;br /&gt;
              ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====string====&lt;br /&gt;
A string will be presented in the form of a header with a delimiter underneath.&lt;br /&gt;
 &amp;quot;Just a string&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====table====&lt;br /&gt;
A table that takes strings and data/numbers in a key/value sort of way. Only one post in the table will cause a crash and any number of entries above the first two will be discarded.&lt;br /&gt;
&lt;br /&gt;
   {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
   {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
 --{&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
   {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;} -- Two entries in the table with one being just an empty string works.&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====spacing====&lt;br /&gt;
The key word '''false''' can be used to insert some padding.&lt;br /&gt;
&lt;br /&gt;
 false,&lt;br /&gt;
&lt;br /&gt;
===desc.location===&lt;br /&gt;
Add a button to set the mission location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.location = mission.location&lt;br /&gt;
&lt;br /&gt;
===desc.returnLocation===&lt;br /&gt;
Add a button to set the return location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.returnLocation = mission.domicile&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     desc.details = {&lt;br /&gt;
         &amp;quot;This will be a 'title' with a delimiter underneath&amp;quot;,&lt;br /&gt;
         {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
         {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
         false, -- some space.&lt;br /&gt;
      -- {&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
         {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
     }&lt;br /&gt;
  &lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Moremissiondetails.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Maintaining immersion==&lt;br /&gt;
&lt;br /&gt;
Fictionally, of course, the bulletin board is visible to any and all ships that dock at the space station, not just the player. It is important that bulletin board missions are not all scaled to the capabilities of the player. Delivery missions with unreasonable deadlines should not be ruled out. Neither should cargo missions requiring much more cargo space than the player's ship has, or combat missions for which the player is completely unqualified.&lt;br /&gt;
&lt;br /&gt;
These missions should deal with the player gracefully; either allowing them to fail, and providing consequences, or preventing them from being given the mission.&lt;br /&gt;
&lt;br /&gt;
It's also important, if your script serves many instances of a mission, to periodically clear away bulletin board adverts and place new ones. Not just those with obvious time constraints, but any others; the assumption that the player should make is that perhaps some other character has taken these missions.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4710</id>
		<title>Missions and the mission list</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4710"/>
		<updated>2025-01-16T06:40:19Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: No longer outdated&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==The player's mission list==&lt;br /&gt;
&lt;br /&gt;
Once the player has negotiated with your form, there might well be a mission in play. It could be a delivery, an assassination, a rush to tell somebody not to leave because so-and-so loves them... the possibilities are limited only by your creativity. The player needs a way to keep track of all the missions that they have agreed to undertake. Pioneer provides this through the player's mission screen, which they can access at any time using the F3 button, and looking at the missions tab. The content of this screen is controlled by some methods on the &amp;lt;code&amp;gt;Player&amp;lt;/code&amp;gt; object, which can always be found at &amp;lt;code&amp;gt;Game.player&amp;lt;/code&amp;gt;, and which inherits from &amp;lt;code&amp;gt;Ship&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Body&amp;lt;/code&amp;gt;. Missions are added to the screen using the &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; method. It takes a table of info, and returns an integer reference to that mission, which should be stored so that it can be updated or removed later. Below follows a typical use case from the [https://codedoc.pioneerspacesim.net/#LuaClass:Mission:New codedoc].&lt;br /&gt;
&lt;br /&gt;
Create a new mission and add it to the player’s mission list while retrieving the reference number:&lt;br /&gt;
&lt;br /&gt;
 ref = Mission.New({&lt;br /&gt;
     'type'      = 'Delivery', -- Must be a translatable token!&lt;br /&gt;
     'client'    = Character.New(),&lt;br /&gt;
     'due'       = Game.time + 3*24*60*60,       -- three days&lt;br /&gt;
     'reward'    = 123.45,&lt;br /&gt;
     'location'  = SystemPath:New(0,0,0,0,16),   -- Mars High, Sol&lt;br /&gt;
     'status'    = 'ACTIVE',&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
In practice, it might look more like this:&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 600, -- ten minutes' time&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
I don't recommend using &amp;lt;code&amp;gt;Game.player.frameBody.path&amp;lt;/code&amp;gt; here. I'm only using it because it always returns something, whether docked or not. A real mission would probably use a space station here. For this demonstration we've generated 'Taxi' missions that are already a known mission type, registered by '''Taxi.lua''' who wouldn't know about it because the mission scripts remembers it's own missions and has no way of knowing about missions registered by another module. So far no modules use another modules mission type and we only use it here for the sake of demonstration.&lt;br /&gt;
&lt;br /&gt;
This creates a mission visible on the mission screen:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist3.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Registering a mission type==&lt;br /&gt;
Before a module can create missions that are visible in the player's mission list, it needs to register a mission type. It's a painless task.&lt;br /&gt;
&lt;br /&gt;
'''[https://codedoc.pioneerspacesim.net/#LuaClass:Mission:RegisterType LuaClass:Mission:RegisterType]'''&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType(typeid, display, onClick)&lt;br /&gt;
&lt;br /&gt;
Apart from a unique string and a translatable mission name, you have the option to pass a function that takes care of the mission info presented when you press the '''More info...''' button in the missions list. By convention, this function is named '''buildMissionDescription'''. This would typically happen at the very end of the module. Here is what it looks like at the end of [https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L513 '''DeliverPackage.lua''']&lt;br /&gt;
&lt;br /&gt;
 Event.Register(&amp;quot;onGameEnd&amp;quot;, onGameEnd)&lt;br /&gt;
 Event.Register(&amp;quot;onReputationChanged&amp;quot;, onReputationChanged)&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
 &lt;br /&gt;
 Serializer:Register(&amp;quot;DeliverPackage&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The next example is extended with a much scaled-down version of the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L401-https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L434 onShipDocked]''' function from the '''DeliverPackage''' module. Since these missions are recognized by a module in Pioneer already ('Taxi') they will 'probably' be handled at a later stage if we fail to remove them with this test mission. If you generate a new mission type you must also handle removing it or the mission will remain in the game forever. There is no automatic logic, and no automatic removal. Your script must keep track of them. We'll wrap the action in a '''Timer''' function so we don't have to deal with the BBS form this time.&lt;br /&gt;
&lt;br /&gt;
 Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 ...&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Start the game and have a look under the '''Missions''' tab. After 2 seconds two missions are registered. One that should be finished in 10 seconds and another with a due time set 10 seconds after the first one. There is a second timer that checks in with the missions to see how it's going after 15 seconds. One should be completed successfully and the other will fail.&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function (ship, station)&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Missions Created:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist1.png]]&lt;br /&gt;
&lt;br /&gt;
1 Mission failed and 1 mission completed:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist2.png]]&lt;br /&gt;
&lt;br /&gt;
More info? If you look to the right on the mission list there is a button named '''More info...''' which will let you see more specific details of your mission. This will need to be specified in the mission script itself so if you press this button in the example above Pioneer will crash. The common name for the function you need to write in your script is '''buildMissionDescription'''. Here is what it looks like in '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L462-L490 DeliverPackage.lua]'''.&lt;br /&gt;
&lt;br /&gt;
==Mission description==&lt;br /&gt;
If you don't include the '''buildMissionDescription''' argument when you register the mission type, the mission list will not include a '''More info...''' button for the mission type. In the example above we used an existing mission type, '''Taxi''', but we didn't do it from within '''Taxi.lua''' so when '''More info...''' is pressed '''buildMissionDescription''' will be called in '''Taxi.lua''' but it will not now anything about the mission so the function will break. Try and comment out the entire '''buildMissionDescription''' in '''Taxi.lua''' and launch Pioneer once more. The Missions will register but now without the '''More info...''' button. To register a mission type without '''buildMissionDescription''' you simply leave it out.&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI)&lt;br /&gt;
&lt;br /&gt;
You can also declare a minimal '''buildMissionDescription''' that returns nil. This is fine. It will include the button which, if pressed, will just exit immediately and not try and open a new window.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI, buildMissionDescription)&lt;br /&gt;
&lt;br /&gt;
The function responsible for interpreting the mission description is '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/modules/info-view/04-missions.lua#L42-L77 drawMissionDescription()]''' in '''/data/pigui/modules/info-view/04-missions.lua'''. It takes a table '''desc''' as it's argument. A minimal working mission description could look something like the example below. We set '''desc.description''' to an empty string and '''desc.details''' to a table with only one element '''false'''. '''desc.client''' is set to '''mission.client'''. This only works as part of an actual mission script where you already have a registered mission with a client. As you can see the '''Mission Details''' title and the '''Go back''' button are generated automatically.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Minimaldescription.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
Let's fixup the earlier example with it's own mission type and a working '''buildMissionDescription'''.&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Test&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Test&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
  &lt;br /&gt;
 Mission.RegisterType('Test', 'Test', buildMissionDescription)&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
That's basically it. Here follows a description of the elements you can add to '''buildMissionDescription'''. Try and expand our latest script on you own.&lt;br /&gt;
&lt;br /&gt;
===desc.description===&lt;br /&gt;
This is a string and will typically be based on the conversation from onChat.&lt;br /&gt;
&lt;br /&gt;
 desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===desc.client===&lt;br /&gt;
Takes a character as it's input and usually this means the character passed via the mission argument.&lt;br /&gt;
&lt;br /&gt;
 desc.client = mission.client&lt;br /&gt;
&lt;br /&gt;
===desc.details===&lt;br /&gt;
This is a table that is passed to '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/libs/text-table.lua#L15-L28 textTable.draw()]''' in '''data/pigui/libs/text-table.lua'''.&lt;br /&gt;
&lt;br /&gt;
 desc.details = {&lt;br /&gt;
     {&amp;quot;data here&amp;quot;, &amp;quot;more data&amp;quot;},&lt;br /&gt;
              ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====string====&lt;br /&gt;
A string will be presented in the form of a header with a delimiter underneath.&lt;br /&gt;
 &amp;quot;Just a string&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====table====&lt;br /&gt;
A table that takes strings and data/numbers in a key/value sort of way. Only one post in the table will cause a crash and any number of entries above the first two will be discarded.&lt;br /&gt;
&lt;br /&gt;
   {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
   {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
 --{&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
   {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;} -- Two entries in the table with one being just an empty string works.&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====spacing====&lt;br /&gt;
The key word '''false''' can be used to insert some padding.&lt;br /&gt;
&lt;br /&gt;
 false,&lt;br /&gt;
&lt;br /&gt;
===desc.location===&lt;br /&gt;
Add a button to set the mission location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.location = mission.location&lt;br /&gt;
&lt;br /&gt;
===desc.returnLocation===&lt;br /&gt;
Add a button to set the return location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.returnLocation = mission.domicile&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     desc.details = {&lt;br /&gt;
         &amp;quot;This will be a 'title' with a delimiter underneath&amp;quot;,&lt;br /&gt;
         {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
         {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
         false, -- some space.&lt;br /&gt;
      -- {&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
         {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
     }&lt;br /&gt;
  &lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Moremissiondetails.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Maintaining immersion==&lt;br /&gt;
&lt;br /&gt;
Fictionally, of course, the bulletin board is visible to any and all ships that dock at the space station, not just the player. It is important that bulletin board missions are not all scaled to the capabilities of the player. Delivery missions with unreasonable deadlines should not be ruled out. Neither should cargo missions requiring much more cargo space than the player's ship has, or combat missions for which the player is completely unqualified.&lt;br /&gt;
&lt;br /&gt;
These missions should deal with the player gracefully; either allowing them to fail, and providing consequences, or preventing them from being given the mission.&lt;br /&gt;
&lt;br /&gt;
It's also important, if your script serves many instances of a mission, to periodically clear away bulletin board adverts and place new ones. Not just those with obvious time constraints, but any others; the assumption that the player should make is that perhaps some other character has taken these missions.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4709</id>
		<title>Missions and the mission list</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4709"/>
		<updated>2025-01-16T06:38:28Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Mission description */ Completed example&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{outdated}}&lt;br /&gt;
&lt;br /&gt;
==The player's mission list==&lt;br /&gt;
&lt;br /&gt;
Once the player has negotiated with your form, there might well be a mission in play. It could be a delivery, an assassination, a rush to tell somebody not to leave because so-and-so loves them... the possibilities are limited only by your creativity. The player needs a way to keep track of all the missions that they have agreed to undertake. Pioneer provides this through the player's mission screen, which they can access at any time using the F3 button, and looking at the missions tab. The content of this screen is controlled by some methods on the &amp;lt;code&amp;gt;Player&amp;lt;/code&amp;gt; object, which can always be found at &amp;lt;code&amp;gt;Game.player&amp;lt;/code&amp;gt;, and which inherits from &amp;lt;code&amp;gt;Ship&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Body&amp;lt;/code&amp;gt;. Missions are added to the screen using the &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; method. It takes a table of info, and returns an integer reference to that mission, which should be stored so that it can be updated or removed later. Below follows a typical use case from the [https://codedoc.pioneerspacesim.net/#LuaClass:Mission:New codedoc].&lt;br /&gt;
&lt;br /&gt;
Create a new mission and add it to the player’s mission list while retrieving the reference number:&lt;br /&gt;
&lt;br /&gt;
 ref = Mission.New({&lt;br /&gt;
     'type'      = 'Delivery', -- Must be a translatable token!&lt;br /&gt;
     'client'    = Character.New(),&lt;br /&gt;
     'due'       = Game.time + 3*24*60*60,       -- three days&lt;br /&gt;
     'reward'    = 123.45,&lt;br /&gt;
     'location'  = SystemPath:New(0,0,0,0,16),   -- Mars High, Sol&lt;br /&gt;
     'status'    = 'ACTIVE',&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
In practice, it might look more like this:&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 600, -- ten minutes' time&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
I don't recommend using &amp;lt;code&amp;gt;Game.player.frameBody.path&amp;lt;/code&amp;gt; here. I'm only using it because it always returns something, whether docked or not. A real mission would probably use a space station here. For this demonstration we've generated 'Taxi' missions that are already a known mission type, registered by '''Taxi.lua''' who wouldn't know about it because the mission scripts remembers it's own missions and has no way of knowing about missions registered by another module. So far no modules use another modules mission type and we only use it here for the sake of demonstration.&lt;br /&gt;
&lt;br /&gt;
This creates a mission visible on the mission screen:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist3.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Registering a mission type==&lt;br /&gt;
Before a module can create missions that are visible in the player's mission list, it needs to register a mission type. It's a painless task.&lt;br /&gt;
&lt;br /&gt;
'''[https://codedoc.pioneerspacesim.net/#LuaClass:Mission:RegisterType LuaClass:Mission:RegisterType]'''&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType(typeid, display, onClick)&lt;br /&gt;
&lt;br /&gt;
Apart from a unique string and a translatable mission name, you have the option to pass a function that takes care of the mission info presented when you press the '''More info...''' button in the missions list. By convention, this function is named '''buildMissionDescription'''. This would typically happen at the very end of the module. Here is what it looks like at the end of [https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L513 '''DeliverPackage.lua''']&lt;br /&gt;
&lt;br /&gt;
 Event.Register(&amp;quot;onGameEnd&amp;quot;, onGameEnd)&lt;br /&gt;
 Event.Register(&amp;quot;onReputationChanged&amp;quot;, onReputationChanged)&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
 &lt;br /&gt;
 Serializer:Register(&amp;quot;DeliverPackage&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The next example is extended with a much scaled-down version of the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L401-https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L434 onShipDocked]''' function from the '''DeliverPackage''' module. Since these missions are recognized by a module in Pioneer already ('Taxi') they will 'probably' be handled at a later stage if we fail to remove them with this test mission. If you generate a new mission type you must also handle removing it or the mission will remain in the game forever. There is no automatic logic, and no automatic removal. Your script must keep track of them. We'll wrap the action in a '''Timer''' function so we don't have to deal with the BBS form this time.&lt;br /&gt;
&lt;br /&gt;
 Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 ...&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Start the game and have a look under the '''Missions''' tab. After 2 seconds two missions are registered. One that should be finished in 10 seconds and another with a due time set 10 seconds after the first one. There is a second timer that checks in with the missions to see how it's going after 15 seconds. One should be completed successfully and the other will fail.&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function (ship, station)&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Missions Created:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist1.png]]&lt;br /&gt;
&lt;br /&gt;
1 Mission failed and 1 mission completed:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist2.png]]&lt;br /&gt;
&lt;br /&gt;
More info? If you look to the right on the mission list there is a button named '''More info...''' which will let you see more specific details of your mission. This will need to be specified in the mission script itself so if you press this button in the example above Pioneer will crash. The common name for the function you need to write in your script is '''buildMissionDescription'''. Here is what it looks like in '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L462-L490 DeliverPackage.lua]'''.&lt;br /&gt;
&lt;br /&gt;
==Mission description==&lt;br /&gt;
If you don't include the '''buildMissionDescription''' argument when you register the mission type, the mission list will not include a '''More info...''' button for the mission type. In the example above we used an existing mission type, '''Taxi''', but we didn't do it from within '''Taxi.lua''' so when '''More info...''' is pressed '''buildMissionDescription''' will be called in '''Taxi.lua''' but it will not now anything about the mission so the function will break. Try and comment out the entire '''buildMissionDescription''' in '''Taxi.lua''' and launch Pioneer once more. The Missions will register but now without the '''More info...''' button. To register a mission type without '''buildMissionDescription''' you simply leave it out.&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI)&lt;br /&gt;
&lt;br /&gt;
You can also declare a minimal '''buildMissionDescription''' that returns nil. This is fine. It will include the button which, if pressed, will just exit immediately and not try and open a new window.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI, buildMissionDescription)&lt;br /&gt;
&lt;br /&gt;
The function responsible for interpreting the mission description is '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/modules/info-view/04-missions.lua#L42-L77 drawMissionDescription()]''' in '''/data/pigui/modules/info-view/04-missions.lua'''. It takes a table '''desc''' as it's argument. A minimal working mission description could look something like the example below. We set '''desc.description''' to an empty string and '''desc.details''' to a table with only one element '''false'''. '''desc.client''' is set to '''mission.client'''. This only works as part of an actual mission script where you already have a registered mission with a client. As you can see the '''Mission Details''' title and the '''Go back''' button are generated automatically.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Minimaldescription.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
Let's fixup the earlier example with it's own mission type and a working '''buildMissionDescription'''.&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function ()&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Test&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Test&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
  &lt;br /&gt;
 Mission.RegisterType('Test', 'Test', buildMissionDescription)&lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
That's basically it. Here follows a description of the elements you can add to '''buildMissionDescription'''. Try and expand our latest script on you own.&lt;br /&gt;
&lt;br /&gt;
===desc.description===&lt;br /&gt;
This is a string and will typically be based on the conversation from onChat.&lt;br /&gt;
&lt;br /&gt;
 desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===desc.client===&lt;br /&gt;
Takes a character as it's input and usually this means the character passed via the mission argument.&lt;br /&gt;
&lt;br /&gt;
 desc.client = mission.client&lt;br /&gt;
&lt;br /&gt;
===desc.details===&lt;br /&gt;
This is a table that is passed to '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/libs/text-table.lua#L15-L28 textTable.draw()]''' in '''data/pigui/libs/text-table.lua'''.&lt;br /&gt;
&lt;br /&gt;
 desc.details = {&lt;br /&gt;
     {&amp;quot;data here&amp;quot;, &amp;quot;more data&amp;quot;},&lt;br /&gt;
              ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====string====&lt;br /&gt;
A string will be presented in the form of a header with a delimiter underneath.&lt;br /&gt;
 &amp;quot;Just a string&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====table====&lt;br /&gt;
A table that takes strings and data/numbers in a key/value sort of way. Only one post in the table will cause a crash and any number of entries above the first two will be discarded.&lt;br /&gt;
&lt;br /&gt;
   {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
   {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
 --{&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
   {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;} -- Two entries in the table with one being just an empty string works.&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====spacing====&lt;br /&gt;
The key word '''false''' can be used to insert some padding.&lt;br /&gt;
&lt;br /&gt;
 false,&lt;br /&gt;
&lt;br /&gt;
===desc.location===&lt;br /&gt;
Add a button to set the mission location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.location = mission.location&lt;br /&gt;
&lt;br /&gt;
===desc.returnLocation===&lt;br /&gt;
Add a button to set the return location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.returnLocation = mission.domicile&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     desc.details = {&lt;br /&gt;
         &amp;quot;This will be a 'title' with a delimiter underneath&amp;quot;,&lt;br /&gt;
         {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
         {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
         false, -- some space.&lt;br /&gt;
      -- {&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
         {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
     }&lt;br /&gt;
  &lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Moremissiondetails.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Maintaining immersion==&lt;br /&gt;
&lt;br /&gt;
Fictionally, of course, the bulletin board is visible to any and all ships that dock at the space station, not just the player. It is important that bulletin board missions are not all scaled to the capabilities of the player. Delivery missions with unreasonable deadlines should not be ruled out. Neither should cargo missions requiring much more cargo space than the player's ship has, or combat missions for which the player is completely unqualified.&lt;br /&gt;
&lt;br /&gt;
These missions should deal with the player gracefully; either allowing them to fail, and providing consequences, or preventing them from being given the mission.&lt;br /&gt;
&lt;br /&gt;
It's also important, if your script serves many instances of a mission, to periodically clear away bulletin board adverts and place new ones. Not just those with obvious time constraints, but any others; the assumption that the player should make is that perhaps some other character has taken these missions.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4708</id>
		<title>Missions and the mission list</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4708"/>
		<updated>2025-01-16T06:28:57Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Registering a mission type */ Fixup example&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{outdated}}&lt;br /&gt;
&lt;br /&gt;
==The player's mission list==&lt;br /&gt;
&lt;br /&gt;
Once the player has negotiated with your form, there might well be a mission in play. It could be a delivery, an assassination, a rush to tell somebody not to leave because so-and-so loves them... the possibilities are limited only by your creativity. The player needs a way to keep track of all the missions that they have agreed to undertake. Pioneer provides this through the player's mission screen, which they can access at any time using the F3 button, and looking at the missions tab. The content of this screen is controlled by some methods on the &amp;lt;code&amp;gt;Player&amp;lt;/code&amp;gt; object, which can always be found at &amp;lt;code&amp;gt;Game.player&amp;lt;/code&amp;gt;, and which inherits from &amp;lt;code&amp;gt;Ship&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Body&amp;lt;/code&amp;gt;. Missions are added to the screen using the &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; method. It takes a table of info, and returns an integer reference to that mission, which should be stored so that it can be updated or removed later. Below follows a typical use case from the [https://codedoc.pioneerspacesim.net/#LuaClass:Mission:New codedoc].&lt;br /&gt;
&lt;br /&gt;
Create a new mission and add it to the player’s mission list while retrieving the reference number:&lt;br /&gt;
&lt;br /&gt;
 ref = Mission.New({&lt;br /&gt;
     'type'      = 'Delivery', -- Must be a translatable token!&lt;br /&gt;
     'client'    = Character.New(),&lt;br /&gt;
     'due'       = Game.time + 3*24*60*60,       -- three days&lt;br /&gt;
     'reward'    = 123.45,&lt;br /&gt;
     'location'  = SystemPath:New(0,0,0,0,16),   -- Mars High, Sol&lt;br /&gt;
     'status'    = 'ACTIVE',&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
In practice, it might look more like this:&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 600, -- ten minutes' time&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
I don't recommend using &amp;lt;code&amp;gt;Game.player.frameBody.path&amp;lt;/code&amp;gt; here. I'm only using it because it always returns something, whether docked or not. A real mission would probably use a space station here. For this demonstration we've generated 'Taxi' missions that are already a known mission type, registered by '''Taxi.lua''' who wouldn't know about it because the mission scripts remembers it's own missions and has no way of knowing about missions registered by another module. So far no modules use another modules mission type and we only use it here for the sake of demonstration.&lt;br /&gt;
&lt;br /&gt;
This creates a mission visible on the mission screen:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist3.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Registering a mission type==&lt;br /&gt;
Before a module can create missions that are visible in the player's mission list, it needs to register a mission type. It's a painless task.&lt;br /&gt;
&lt;br /&gt;
'''[https://codedoc.pioneerspacesim.net/#LuaClass:Mission:RegisterType LuaClass:Mission:RegisterType]'''&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType(typeid, display, onClick)&lt;br /&gt;
&lt;br /&gt;
Apart from a unique string and a translatable mission name, you have the option to pass a function that takes care of the mission info presented when you press the '''More info...''' button in the missions list. By convention, this function is named '''buildMissionDescription'''. This would typically happen at the very end of the module. Here is what it looks like at the end of [https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L513 '''DeliverPackage.lua''']&lt;br /&gt;
&lt;br /&gt;
 Event.Register(&amp;quot;onGameEnd&amp;quot;, onGameEnd)&lt;br /&gt;
 Event.Register(&amp;quot;onReputationChanged&amp;quot;, onReputationChanged)&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
 &lt;br /&gt;
 Serializer:Register(&amp;quot;DeliverPackage&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The next example is extended with a much scaled-down version of the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L401-https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L434 onShipDocked]''' function from the '''DeliverPackage''' module. Since these missions are recognized by a module in Pioneer already ('Taxi') they will 'probably' be handled at a later stage if we fail to remove them with this test mission. If you generate a new mission type you must also handle removing it or the mission will remain in the game forever. There is no automatic logic, and no automatic removal. Your script must keep track of them. We'll wrap the action in a '''Timer''' function so we don't have to deal with the BBS form this time.&lt;br /&gt;
&lt;br /&gt;
 Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 ...&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Start the game and have a look under the '''Missions''' tab. After 2 seconds two missions are registered. One that should be finished in 10 seconds and another with a due time set 10 seconds after the first one. There is a second timer that checks in with the missions to see how it's going after 15 seconds. One should be completed successfully and the other will fail.&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onGameStart = function (ship, station)&lt;br /&gt;
     Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 &lt;br /&gt;
         -- On docking, starting a new game, we create&lt;br /&gt;
         -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically &lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
 &lt;br /&gt;
         -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
         -- after 15 seconds and check in with our passengers.&lt;br /&gt;
         Timer:CallAt(Game.time+15, function ()  -- 15 seconds timer&lt;br /&gt;
             for ref,mission in pairs(missions) do&lt;br /&gt;
                 if Game.time &amp;gt; mission.due then&lt;br /&gt;
                     mission.status = 'FAILED'&lt;br /&gt;
                     Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
                 else&lt;br /&gt;
                     Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                     mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                     Game.player:AddMoney(mission.reward)&lt;br /&gt;
                 end&lt;br /&gt;
                 mission:Remove()&lt;br /&gt;
                 missions[ref] = nil&lt;br /&gt;
             end&lt;br /&gt;
         end)&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onGameStart&amp;quot;, onGameStart)&lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
Missions Created:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist1.png]]&lt;br /&gt;
&lt;br /&gt;
1 Mission failed and 1 mission completed:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist2.png]]&lt;br /&gt;
&lt;br /&gt;
More info? If you look to the right on the mission list there is a button named '''More info...''' which will let you see more specific details of your mission. This will need to be specified in the mission script itself so if you press this button in the example above Pioneer will crash. The common name for the function you need to write in your script is '''buildMissionDescription'''. Here is what it looks like in '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L462-L490 DeliverPackage.lua]'''.&lt;br /&gt;
&lt;br /&gt;
==Mission description==&lt;br /&gt;
If you don't include the '''buildMissionDescription''' argument when you register the mission type, the mission list will not include a '''More info...''' button for the mission type. In the example above we used an existing mission type, '''Taxi''', but we didn't do it from within '''Taxi.lua''' so when '''More info...''' is pressed '''buildMissionDescription''' will be called in '''Taxi.lua''' but it will not now anything about the mission so the function will break. Try and comment out the entire '''buildMissionDescription''' in '''Taxi.lua''' and launch Pioneer once more. The Missions will register but now without the '''More info...''' button. To register a mission type without '''buildMissionDescription''' you simply leave it out.&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI)&lt;br /&gt;
&lt;br /&gt;
You can also declare a minimal '''buildMissionDescription''' that returns nil. This is fine. It will include the button which, if pressed, will just exit immediately and not try and open a new window.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI, buildMissionDescription)&lt;br /&gt;
&lt;br /&gt;
The function responsible for interpreting the mission description is '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/modules/info-view/04-missions.lua#L42-L77 drawMissionDescription()]''' in '''/data/pigui/modules/info-view/04-missions.lua'''. It takes a table '''desc''' as it's argument. A minimal working mission description could look something like the example below. We set '''desc.description''' to an empty string and '''desc.details''' to a table with only one element '''false'''. '''desc.client''' is set to '''mission.client'''. This only works as part of an actual mission script where you already have a registered mission with a client. As you can see the '''Mission Details''' title and the '''Go back''' button are generated automatically.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Minimaldescription.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
===desc.description===&lt;br /&gt;
This is a string and will typically be based on the conversation from onChat.&lt;br /&gt;
&lt;br /&gt;
 desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===desc.client===&lt;br /&gt;
Takes a character as it's input and usually this means the character passed via the mission argument.&lt;br /&gt;
&lt;br /&gt;
 desc.client = mission.client&lt;br /&gt;
&lt;br /&gt;
===desc.details===&lt;br /&gt;
This is a table that is passed to '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/libs/text-table.lua#L15-L28 textTable.draw()]''' in '''data/pigui/libs/text-table.lua'''.&lt;br /&gt;
&lt;br /&gt;
 desc.details = {&lt;br /&gt;
     {&amp;quot;data here&amp;quot;, &amp;quot;more data&amp;quot;},&lt;br /&gt;
              ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====string====&lt;br /&gt;
A string will be presented in the form of a header with a delimiter underneath.&lt;br /&gt;
 &amp;quot;Just a string&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====table====&lt;br /&gt;
A table that takes strings and data/numbers in a key/value sort of way. Only one post in the table will cause a crash and any number of entries above the first two will be discarded.&lt;br /&gt;
&lt;br /&gt;
   {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
   {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
 --{&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
   {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;} -- Two entries in the table with one being just an empty string works.&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====spacing====&lt;br /&gt;
The key word '''false''' can be used to insert some padding.&lt;br /&gt;
&lt;br /&gt;
 false,&lt;br /&gt;
&lt;br /&gt;
===desc.location===&lt;br /&gt;
Add a button to set the mission location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.location = mission.location&lt;br /&gt;
&lt;br /&gt;
===desc.returnLocation===&lt;br /&gt;
Add a button to set the return location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.returnLocation = mission.domicile&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     desc.details = {&lt;br /&gt;
         &amp;quot;This will be a 'title' with a delimiter underneath&amp;quot;,&lt;br /&gt;
         {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
         {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
         false, -- some space.&lt;br /&gt;
      -- {&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
         {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
     }&lt;br /&gt;
  &lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Moremissiondetails.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Maintaining immersion==&lt;br /&gt;
&lt;br /&gt;
Fictionally, of course, the bulletin board is visible to any and all ships that dock at the space station, not just the player. It is important that bulletin board missions are not all scaled to the capabilities of the player. Delivery missions with unreasonable deadlines should not be ruled out. Neither should cargo missions requiring much more cargo space than the player's ship has, or combat missions for which the player is completely unqualified.&lt;br /&gt;
&lt;br /&gt;
These missions should deal with the player gracefully; either allowing them to fail, and providing consequences, or preventing them from being given the mission.&lt;br /&gt;
&lt;br /&gt;
It's also important, if your script serves many instances of a mission, to periodically clear away bulletin board adverts and place new ones. Not just those with obvious time constraints, but any others; the assumption that the player should make is that perhaps some other character has taken these missions.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4707</id>
		<title>Missions and the mission list</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4707"/>
		<updated>2025-01-16T05:54:03Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Registering a mission type */ fixup - escape apostrophe&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{outdated}}&lt;br /&gt;
&lt;br /&gt;
==The player's mission list==&lt;br /&gt;
&lt;br /&gt;
Once the player has negotiated with your form, there might well be a mission in play. It could be a delivery, an assassination, a rush to tell somebody not to leave because so-and-so loves them... the possibilities are limited only by your creativity. The player needs a way to keep track of all the missions that they have agreed to undertake. Pioneer provides this through the player's mission screen, which they can access at any time using the F3 button, and looking at the missions tab. The content of this screen is controlled by some methods on the &amp;lt;code&amp;gt;Player&amp;lt;/code&amp;gt; object, which can always be found at &amp;lt;code&amp;gt;Game.player&amp;lt;/code&amp;gt;, and which inherits from &amp;lt;code&amp;gt;Ship&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Body&amp;lt;/code&amp;gt;. Missions are added to the screen using the &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; method. It takes a table of info, and returns an integer reference to that mission, which should be stored so that it can be updated or removed later. Below follows a typical use case from the [https://codedoc.pioneerspacesim.net/#LuaClass:Mission:New codedoc].&lt;br /&gt;
&lt;br /&gt;
Create a new mission and add it to the player’s mission list while retrieving the reference number:&lt;br /&gt;
&lt;br /&gt;
 ref = Mission.New({&lt;br /&gt;
     'type'      = 'Delivery', -- Must be a translatable token!&lt;br /&gt;
     'client'    = Character.New(),&lt;br /&gt;
     'due'       = Game.time + 3*24*60*60,       -- three days&lt;br /&gt;
     'reward'    = 123.45,&lt;br /&gt;
     'location'  = SystemPath:New(0,0,0,0,16),   -- Mars High, Sol&lt;br /&gt;
     'status'    = 'ACTIVE',&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
In practice, it might look more like this:&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 600, -- ten minutes' time&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
I don't recommend using &amp;lt;code&amp;gt;Game.player.frameBody.path&amp;lt;/code&amp;gt; here. I'm only using it because it always returns something, whether docked or not. A real mission would probably use a space station here. For this demonstration we've generated 'Taxi' missions that are already a known mission type, registered by '''Taxi.lua''' who wouldn't know about it because the mission scripts remembers it's own missions and has no way of knowing about missions registered by another module. So far no modules use another modules mission type and we only use it here for the sake of demonstration.&lt;br /&gt;
&lt;br /&gt;
This creates a mission visible on the mission screen:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist3.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Registering a mission type==&lt;br /&gt;
Before a module can create missions that are visible in the player's mission list, it needs to register a mission type. It's a painless task.&lt;br /&gt;
&lt;br /&gt;
'''[https://codedoc.pioneerspacesim.net/#LuaClass:Mission:RegisterType LuaClass:Mission:RegisterType]'''&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType(typeid, display, onClick)&lt;br /&gt;
&lt;br /&gt;
Apart from a unique string and a translatable mission name, you have the option to pass a function that takes care of the mission info presented when you press the '''More info...''' button in the missions list. By convention, this function is named '''buildMissionDescription'''. This would typically happen at the very end of the module. Here is what it looks like at the end of [https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L513 '''DeliverPackage.lua''']&lt;br /&gt;
&lt;br /&gt;
 Event.Register(&amp;quot;onGameEnd&amp;quot;, onGameEnd)&lt;br /&gt;
 Event.Register(&amp;quot;onReputationChanged&amp;quot;, onReputationChanged)&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
 &lt;br /&gt;
 Serializer:Register(&amp;quot;DeliverPackage&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The next example is extended with a much scaled-down version of the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L401-https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L434 onShipDocked]''' function from the '''DeliverPackage''' module. Since these missions are recognized by a module in Pioneer already ('Taxi') they will 'probably' be handled at a later stage if we fail to remove them with this test mission. If you generate a new mission type you must also handle removing it or the mission will remain in the game forever. There is no automatic logic, and no automatic removal. Your script must keep track of them. We'll wrap the action in a '''Timer''' function so we don't have to deal with the BBS form this time.&lt;br /&gt;
&lt;br /&gt;
 Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 ...&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Start the game and have a look under the '''Missions''' tab. After 2 seconds two missions are registered. One that should be finished in 10 seconds and another with a due time set 10 seconds after the first one. There is a second timer that checks in with the missions to see how it's going after 15 seconds. One should be completed successfully and the other will fail.&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship, station)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
     -- On docking, starting a new game, we create&lt;br /&gt;
     -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
     -- after 15 seconds and check in with our passengers.&lt;br /&gt;
     Timer:CallAt(Game.time + 15, function ()  -- 15 seconds timer&lt;br /&gt;
         for ref,mission in pairs(missions) do&lt;br /&gt;
             if Game.time &amp;gt; mission.due then&lt;br /&gt;
                 mission.status = 'FAILED'&lt;br /&gt;
                 Comms.ImportantMessage('We are five seconds late and I\'m at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
             else&lt;br /&gt;
                 Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                 mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                 Game.player:AddMoney(mission.reward)&lt;br /&gt;
             end&lt;br /&gt;
             mission:Remove()&lt;br /&gt;
             missions[ref] = nil&lt;br /&gt;
         end&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
Missions Created:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist1.png]]&lt;br /&gt;
&lt;br /&gt;
1 Mission failed and 1 mission completed:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist2.png]]&lt;br /&gt;
&lt;br /&gt;
More info? If you look to the right on the mission list there is a button named '''More info...''' which will let you see more specific details of your mission. This will need to be specified in the mission script itself so if you press this button in the example above Pioneer will crash. The common name for the function you need to write in your script is '''buildMissionDescription'''. Here is what it looks like in '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L462-L490 DeliverPackage.lua]'''.&lt;br /&gt;
&lt;br /&gt;
==Mission description==&lt;br /&gt;
If you don't include the '''buildMissionDescription''' argument when you register the mission type, the mission list will not include a '''More info...''' button for the mission type. In the example above we used an existing mission type, '''Taxi''', but we didn't do it from within '''Taxi.lua''' so when '''More info...''' is pressed '''buildMissionDescription''' will be called in '''Taxi.lua''' but it will not now anything about the mission so the function will break. Try and comment out the entire '''buildMissionDescription''' in '''Taxi.lua''' and launch Pioneer once more. The Missions will register but now without the '''More info...''' button. To register a mission type without '''buildMissionDescription''' you simply leave it out.&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI)&lt;br /&gt;
&lt;br /&gt;
You can also declare a minimal '''buildMissionDescription''' that returns nil. This is fine. It will include the button which, if pressed, will just exit immediately and not try and open a new window.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI, buildMissionDescription)&lt;br /&gt;
&lt;br /&gt;
The function responsible for interpreting the mission description is '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/modules/info-view/04-missions.lua#L42-L77 drawMissionDescription()]''' in '''/data/pigui/modules/info-view/04-missions.lua'''. It takes a table '''desc''' as it's argument. A minimal working mission description could look something like the example below. We set '''desc.description''' to an empty string and '''desc.details''' to a table with only one element '''false'''. '''desc.client''' is set to '''mission.client'''. This only works as part of an actual mission script where you already have a registered mission with a client. As you can see the '''Mission Details''' title and the '''Go back''' button are generated automatically.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Minimaldescription.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
===desc.description===&lt;br /&gt;
This is a string and will typically be based on the conversation from onChat.&lt;br /&gt;
&lt;br /&gt;
 desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===desc.client===&lt;br /&gt;
Takes a character as it's input and usually this means the character passed via the mission argument.&lt;br /&gt;
&lt;br /&gt;
 desc.client = mission.client&lt;br /&gt;
&lt;br /&gt;
===desc.details===&lt;br /&gt;
This is a table that is passed to '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/libs/text-table.lua#L15-L28 textTable.draw()]''' in '''data/pigui/libs/text-table.lua'''.&lt;br /&gt;
&lt;br /&gt;
 desc.details = {&lt;br /&gt;
     {&amp;quot;data here&amp;quot;, &amp;quot;more data&amp;quot;},&lt;br /&gt;
              ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====string====&lt;br /&gt;
A string will be presented in the form of a header with a delimiter underneath.&lt;br /&gt;
 &amp;quot;Just a string&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====table====&lt;br /&gt;
A table that takes strings and data/numbers in a key/value sort of way. Only one post in the table will cause a crash and any number of entries above the first two will be discarded.&lt;br /&gt;
&lt;br /&gt;
   {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
   {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
 --{&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
   {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;} -- Two entries in the table with one being just an empty string works.&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====spacing====&lt;br /&gt;
The key word '''false''' can be used to insert some padding.&lt;br /&gt;
&lt;br /&gt;
 false,&lt;br /&gt;
&lt;br /&gt;
===desc.location===&lt;br /&gt;
Add a button to set the mission location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.location = mission.location&lt;br /&gt;
&lt;br /&gt;
===desc.returnLocation===&lt;br /&gt;
Add a button to set the return location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.returnLocation = mission.domicile&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     desc.details = {&lt;br /&gt;
         &amp;quot;This will be a 'title' with a delimiter underneath&amp;quot;,&lt;br /&gt;
         {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
         {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
         false, -- some space.&lt;br /&gt;
      -- {&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
         {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
     }&lt;br /&gt;
  &lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Moremissiondetails.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Maintaining immersion==&lt;br /&gt;
&lt;br /&gt;
Fictionally, of course, the bulletin board is visible to any and all ships that dock at the space station, not just the player. It is important that bulletin board missions are not all scaled to the capabilities of the player. Delivery missions with unreasonable deadlines should not be ruled out. Neither should cargo missions requiring much more cargo space than the player's ship has, or combat missions for which the player is completely unqualified.&lt;br /&gt;
&lt;br /&gt;
These missions should deal with the player gracefully; either allowing them to fail, and providing consequences, or preventing them from being given the mission.&lt;br /&gt;
&lt;br /&gt;
It's also important, if your script serves many instances of a mission, to periodically clear away bulletin board adverts and place new ones. Not just those with obvious time constraints, but any others; the assumption that the player should make is that perhaps some other character has taken these missions.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4706</id>
		<title>Missions and the mission list</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4706"/>
		<updated>2025-01-16T05:41:53Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Mission description */ Fixup - Taxi example&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{outdated}}&lt;br /&gt;
&lt;br /&gt;
==The player's mission list==&lt;br /&gt;
&lt;br /&gt;
Once the player has negotiated with your form, there might well be a mission in play. It could be a delivery, an assassination, a rush to tell somebody not to leave because so-and-so loves them... the possibilities are limited only by your creativity. The player needs a way to keep track of all the missions that they have agreed to undertake. Pioneer provides this through the player's mission screen, which they can access at any time using the F3 button, and looking at the missions tab. The content of this screen is controlled by some methods on the &amp;lt;code&amp;gt;Player&amp;lt;/code&amp;gt; object, which can always be found at &amp;lt;code&amp;gt;Game.player&amp;lt;/code&amp;gt;, and which inherits from &amp;lt;code&amp;gt;Ship&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Body&amp;lt;/code&amp;gt;. Missions are added to the screen using the &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; method. It takes a table of info, and returns an integer reference to that mission, which should be stored so that it can be updated or removed later. Below follows a typical use case from the [https://codedoc.pioneerspacesim.net/#LuaClass:Mission:New codedoc].&lt;br /&gt;
&lt;br /&gt;
Create a new mission and add it to the player’s mission list while retrieving the reference number:&lt;br /&gt;
&lt;br /&gt;
 ref = Mission.New({&lt;br /&gt;
     'type'      = 'Delivery', -- Must be a translatable token!&lt;br /&gt;
     'client'    = Character.New(),&lt;br /&gt;
     'due'       = Game.time + 3*24*60*60,       -- three days&lt;br /&gt;
     'reward'    = 123.45,&lt;br /&gt;
     'location'  = SystemPath:New(0,0,0,0,16),   -- Mars High, Sol&lt;br /&gt;
     'status'    = 'ACTIVE',&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
In practice, it might look more like this:&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 600, -- ten minutes' time&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
I don't recommend using &amp;lt;code&amp;gt;Game.player.frameBody.path&amp;lt;/code&amp;gt; here. I'm only using it because it always returns something, whether docked or not. A real mission would probably use a space station here. For this demonstration we've generated 'Taxi' missions that are already a known mission type, registered by '''Taxi.lua''' who wouldn't know about it because the mission scripts remembers it's own missions and has no way of knowing about missions registered by another module. So far no modules use another modules mission type and we only use it here for the sake of demonstration.&lt;br /&gt;
&lt;br /&gt;
This creates a mission visible on the mission screen:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist3.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Registering a mission type==&lt;br /&gt;
Before a module can create missions that are visible in the player's mission list, it needs to register a mission type. It's a painless task.&lt;br /&gt;
&lt;br /&gt;
'''[https://codedoc.pioneerspacesim.net/#LuaClass:Mission:RegisterType LuaClass:Mission:RegisterType]'''&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType(typeid, display, onClick)&lt;br /&gt;
&lt;br /&gt;
Apart from a unique string and a translatable mission name, you have the option to pass a function that takes care of the mission info presented when you press the '''More info...''' button in the missions list. By convention, this function is named '''buildMissionDescription'''. This would typically happen at the very end of the module. Here is what it looks like at the end of [https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L513 '''DeliverPackage.lua''']&lt;br /&gt;
&lt;br /&gt;
 Event.Register(&amp;quot;onGameEnd&amp;quot;, onGameEnd)&lt;br /&gt;
 Event.Register(&amp;quot;onReputationChanged&amp;quot;, onReputationChanged)&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
 &lt;br /&gt;
 Serializer:Register(&amp;quot;DeliverPackage&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The next example is extended with a much scaled-down version of the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L401-https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L434 onShipDocked]''' function from the '''DeliverPackage''' module. Since these missions are recognized by a module in Pioneer already ('Taxi') they will 'probably' be handled at a later stage if we fail to remove them with this test mission. If you generate a new mission type you must also handle removing it or the mission will remain in the game forever. There is no automatic logic, and no automatic removal. Your script must keep track of them. We'll wrap the action in a '''Timer''' function so we don't have to deal with the BBS form this time.&lt;br /&gt;
&lt;br /&gt;
 Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 ...&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Start the game and have a look under the '''Missions''' tab. After 2 seconds two missions are registered. One that should be finished in 10 seconds and another with a due time set 10 seconds after the first one. There is a second timer that checks in with the missions to see how it's going after 15 seconds. One should be completed successfully and the other will fail.&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship, station)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
     -- On docking, starting a new game, we create&lt;br /&gt;
     -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
     -- after 15 seconds and check in with our passengers.&lt;br /&gt;
     Timer:CallAt(Game.time + 15, function ()  -- 15 seconds timer&lt;br /&gt;
         for ref,mission in pairs(missions) do&lt;br /&gt;
             if Game.time &amp;gt; mission.due then&lt;br /&gt;
                 mission.status = 'FAILED'&lt;br /&gt;
                 Comms.ImportantMessage('We are five seconds late and I'm at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
             else&lt;br /&gt;
                 Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                 mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                 Game.player:AddMoney(mission.reward)&lt;br /&gt;
             end&lt;br /&gt;
             mission:Remove()&lt;br /&gt;
             missions[ref] = nil&lt;br /&gt;
         end&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
Missions Created:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist1.png]]&lt;br /&gt;
&lt;br /&gt;
1 Mission failed and 1 mission completed:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist2.png]]&lt;br /&gt;
&lt;br /&gt;
More info? If you look to the right on the mission list there is a button named '''More info...''' which will let you see more specific details of your mission. This will need to be specified in the mission script itself so if you press this button in the example above Pioneer will crash. The common name for the function you need to write in your script is '''buildMissionDescription'''. Here is what it looks like in '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L462-L490 DeliverPackage.lua]'''.&lt;br /&gt;
&lt;br /&gt;
==Mission description==&lt;br /&gt;
If you don't include the '''buildMissionDescription''' argument when you register the mission type, the mission list will not include a '''More info...''' button for the mission type. In the example above we used an existing mission type, '''Taxi''', but we didn't do it from within '''Taxi.lua''' so when '''More info...''' is pressed '''buildMissionDescription''' will be called in '''Taxi.lua''' but it will not now anything about the mission so the function will break. Try and comment out the entire '''buildMissionDescription''' in '''Taxi.lua''' and launch Pioneer once more. The Missions will register but now without the '''More info...''' button. To register a mission type without '''buildMissionDescription''' you simply leave it out.&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI)&lt;br /&gt;
&lt;br /&gt;
You can also declare a minimal '''buildMissionDescription''' that returns nil. This is fine. It will include the button which, if pressed, will just exit immediately and not try and open a new window.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Taxi', l.TAXI, buildMissionDescription)&lt;br /&gt;
&lt;br /&gt;
The function responsible for interpreting the mission description is '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/modules/info-view/04-missions.lua#L42-L77 drawMissionDescription()]''' in '''/data/pigui/modules/info-view/04-missions.lua'''. It takes a table '''desc''' as it's argument. A minimal working mission description could look something like the example below. We set '''desc.description''' to an empty string and '''desc.details''' to a table with only one element '''false'''. '''desc.client''' is set to '''mission.client'''. This only works as part of an actual mission script where you already have a registered mission with a client. As you can see the '''Mission Details''' title and the '''Go back''' button are generated automatically.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Minimaldescription.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
===desc.description===&lt;br /&gt;
This is a string and will typically be based on the conversation from onChat.&lt;br /&gt;
&lt;br /&gt;
 desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===desc.client===&lt;br /&gt;
Takes a character as it's input and usually this means the character passed via the mission argument.&lt;br /&gt;
&lt;br /&gt;
 desc.client = mission.client&lt;br /&gt;
&lt;br /&gt;
===desc.details===&lt;br /&gt;
This is a table that is passed to '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/libs/text-table.lua#L15-L28 textTable.draw()]''' in '''data/pigui/libs/text-table.lua'''.&lt;br /&gt;
&lt;br /&gt;
 desc.details = {&lt;br /&gt;
     {&amp;quot;data here&amp;quot;, &amp;quot;more data&amp;quot;},&lt;br /&gt;
              ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====string====&lt;br /&gt;
A string will be presented in the form of a header with a delimiter underneath.&lt;br /&gt;
 &amp;quot;Just a string&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====table====&lt;br /&gt;
A table that takes strings and data/numbers in a key/value sort of way. Only one post in the table will cause a crash and any number of entries above the first two will be discarded.&lt;br /&gt;
&lt;br /&gt;
   {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
   {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
 --{&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
   {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;} -- Two entries in the table with one being just an empty string works.&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====spacing====&lt;br /&gt;
The key word '''false''' can be used to insert some padding.&lt;br /&gt;
&lt;br /&gt;
 false,&lt;br /&gt;
&lt;br /&gt;
===desc.location===&lt;br /&gt;
Add a button to set the mission location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.location = mission.location&lt;br /&gt;
&lt;br /&gt;
===desc.returnLocation===&lt;br /&gt;
Add a button to set the return location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.returnLocation = mission.domicile&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     desc.details = {&lt;br /&gt;
         &amp;quot;This will be a 'title' with a delimiter underneath&amp;quot;,&lt;br /&gt;
         {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
         {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
         false, -- some space.&lt;br /&gt;
      -- {&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
         {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
     }&lt;br /&gt;
  &lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Moremissiondetails.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Maintaining immersion==&lt;br /&gt;
&lt;br /&gt;
Fictionally, of course, the bulletin board is visible to any and all ships that dock at the space station, not just the player. It is important that bulletin board missions are not all scaled to the capabilities of the player. Delivery missions with unreasonable deadlines should not be ruled out. Neither should cargo missions requiring much more cargo space than the player's ship has, or combat missions for which the player is completely unqualified.&lt;br /&gt;
&lt;br /&gt;
These missions should deal with the player gracefully; either allowing them to fail, and providing consequences, or preventing them from being given the mission.&lt;br /&gt;
&lt;br /&gt;
It's also important, if your script serves many instances of a mission, to periodically clear away bulletin board adverts and place new ones. Not just those with obvious time constraints, but any others; the assumption that the player should make is that perhaps some other character has taken these missions.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4705</id>
		<title>Missions and the mission list</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4705"/>
		<updated>2025-01-16T05:39:01Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Mission description */ Minimal buildMissionDescription fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{outdated}}&lt;br /&gt;
&lt;br /&gt;
==The player's mission list==&lt;br /&gt;
&lt;br /&gt;
Once the player has negotiated with your form, there might well be a mission in play. It could be a delivery, an assassination, a rush to tell somebody not to leave because so-and-so loves them... the possibilities are limited only by your creativity. The player needs a way to keep track of all the missions that they have agreed to undertake. Pioneer provides this through the player's mission screen, which they can access at any time using the F3 button, and looking at the missions tab. The content of this screen is controlled by some methods on the &amp;lt;code&amp;gt;Player&amp;lt;/code&amp;gt; object, which can always be found at &amp;lt;code&amp;gt;Game.player&amp;lt;/code&amp;gt;, and which inherits from &amp;lt;code&amp;gt;Ship&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Body&amp;lt;/code&amp;gt;. Missions are added to the screen using the &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; method. It takes a table of info, and returns an integer reference to that mission, which should be stored so that it can be updated or removed later. Below follows a typical use case from the [https://codedoc.pioneerspacesim.net/#LuaClass:Mission:New codedoc].&lt;br /&gt;
&lt;br /&gt;
Create a new mission and add it to the player’s mission list while retrieving the reference number:&lt;br /&gt;
&lt;br /&gt;
 ref = Mission.New({&lt;br /&gt;
     'type'      = 'Delivery', -- Must be a translatable token!&lt;br /&gt;
     'client'    = Character.New(),&lt;br /&gt;
     'due'       = Game.time + 3*24*60*60,       -- three days&lt;br /&gt;
     'reward'    = 123.45,&lt;br /&gt;
     'location'  = SystemPath:New(0,0,0,0,16),   -- Mars High, Sol&lt;br /&gt;
     'status'    = 'ACTIVE',&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
In practice, it might look more like this:&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 600, -- ten minutes' time&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
I don't recommend using &amp;lt;code&amp;gt;Game.player.frameBody.path&amp;lt;/code&amp;gt; here. I'm only using it because it always returns something, whether docked or not. A real mission would probably use a space station here. For this demonstration we've generated 'Taxi' missions that are already a known mission type, registered by '''Taxi.lua''' who wouldn't know about it because the mission scripts remembers it's own missions and has no way of knowing about missions registered by another module. So far no modules use another modules mission type and we only use it here for the sake of demonstration.&lt;br /&gt;
&lt;br /&gt;
This creates a mission visible on the mission screen:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist3.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Registering a mission type==&lt;br /&gt;
Before a module can create missions that are visible in the player's mission list, it needs to register a mission type. It's a painless task.&lt;br /&gt;
&lt;br /&gt;
'''[https://codedoc.pioneerspacesim.net/#LuaClass:Mission:RegisterType LuaClass:Mission:RegisterType]'''&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType(typeid, display, onClick)&lt;br /&gt;
&lt;br /&gt;
Apart from a unique string and a translatable mission name, you have the option to pass a function that takes care of the mission info presented when you press the '''More info...''' button in the missions list. By convention, this function is named '''buildMissionDescription'''. This would typically happen at the very end of the module. Here is what it looks like at the end of [https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L513 '''DeliverPackage.lua''']&lt;br /&gt;
&lt;br /&gt;
 Event.Register(&amp;quot;onGameEnd&amp;quot;, onGameEnd)&lt;br /&gt;
 Event.Register(&amp;quot;onReputationChanged&amp;quot;, onReputationChanged)&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
 &lt;br /&gt;
 Serializer:Register(&amp;quot;DeliverPackage&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The next example is extended with a much scaled-down version of the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L401-https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L434 onShipDocked]''' function from the '''DeliverPackage''' module. Since these missions are recognized by a module in Pioneer already ('Taxi') they will 'probably' be handled at a later stage if we fail to remove them with this test mission. If you generate a new mission type you must also handle removing it or the mission will remain in the game forever. There is no automatic logic, and no automatic removal. Your script must keep track of them. We'll wrap the action in a '''Timer''' function so we don't have to deal with the BBS form this time.&lt;br /&gt;
&lt;br /&gt;
 Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 ...&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Start the game and have a look under the '''Missions''' tab. After 2 seconds two missions are registered. One that should be finished in 10 seconds and another with a due time set 10 seconds after the first one. There is a second timer that checks in with the missions to see how it's going after 15 seconds. One should be completed successfully and the other will fail.&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship, station)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
     -- On docking, starting a new game, we create&lt;br /&gt;
     -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
     -- after 15 seconds and check in with our passengers.&lt;br /&gt;
     Timer:CallAt(Game.time + 15, function ()  -- 15 seconds timer&lt;br /&gt;
         for ref,mission in pairs(missions) do&lt;br /&gt;
             if Game.time &amp;gt; mission.due then&lt;br /&gt;
                 mission.status = 'FAILED'&lt;br /&gt;
                 Comms.ImportantMessage('We are five seconds late and I'm at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
             else&lt;br /&gt;
                 Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                 mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                 Game.player:AddMoney(mission.reward)&lt;br /&gt;
             end&lt;br /&gt;
             mission:Remove()&lt;br /&gt;
             missions[ref] = nil&lt;br /&gt;
         end&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
Missions Created:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist1.png]]&lt;br /&gt;
&lt;br /&gt;
1 Mission failed and 1 mission completed:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist2.png]]&lt;br /&gt;
&lt;br /&gt;
More info? If you look to the right on the mission list there is a button named '''More info...''' which will let you see more specific details of your mission. This will need to be specified in the mission script itself so if you press this button in the example above Pioneer will crash. The common name for the function you need to write in your script is '''buildMissionDescription'''. Here is what it looks like in '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L462-L490 DeliverPackage.lua]'''.&lt;br /&gt;
&lt;br /&gt;
==Mission description==&lt;br /&gt;
If you don't include the '''buildMissionDescription''' argument when you register the mission type, the mission list will not include a '''More info...''' button for the mission type. In the example above we used an existing mission type, '''Taxi''', but we didn't do it from within '''Taxi.lua''' so when '''More info...''' is pressed '''buildMissionDescription''' will be called in '''Taxi.lua''' but it will not now anything about the mission so the function will break. Try and comment out the entire '''buildMissionDescription''' in '''Taxi.lua''' and launch Pioneer once more. The Missions will register but now without the '''More info...''' button. To register a mission type without '''buildMissionDescription''' you simply leave it out.&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY)&lt;br /&gt;
&lt;br /&gt;
You can also declare a minimal '''buildMissionDescription''' that returns nil. This is fine. It will include the button which, if pressed, will just exit immediately and not try and open a new window.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
&lt;br /&gt;
The function responsible for interpreting the mission description is '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/modules/info-view/04-missions.lua#L42-L77 drawMissionDescription()]''' in '''/data/pigui/modules/info-view/04-missions.lua'''. It takes a table '''desc''' as it's argument. A minimal working mission description could look something like the example below. We set '''desc.description''' to an empty string and '''desc.details''' to a table with only one element '''false'''. '''desc.client''' is set to '''mission.client'''. This only works as part of an actual mission script where you already have a registered mission with a client. As you can see the '''Mission Details''' title and the '''Go back''' button are generated automatically.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Minimaldescription.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
===desc.description===&lt;br /&gt;
This is a string and will typically be based on the conversation from onChat.&lt;br /&gt;
&lt;br /&gt;
 desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===desc.client===&lt;br /&gt;
Takes a character as it's input and usually this means the character passed via the mission argument.&lt;br /&gt;
&lt;br /&gt;
 desc.client = mission.client&lt;br /&gt;
&lt;br /&gt;
===desc.details===&lt;br /&gt;
This is a table that is passed to '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/libs/text-table.lua#L15-L28 textTable.draw()]''' in '''data/pigui/libs/text-table.lua'''.&lt;br /&gt;
&lt;br /&gt;
 desc.details = {&lt;br /&gt;
     {&amp;quot;data here&amp;quot;, &amp;quot;more data&amp;quot;},&lt;br /&gt;
              ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====string====&lt;br /&gt;
A string will be presented in the form of a header with a delimiter underneath.&lt;br /&gt;
 &amp;quot;Just a string&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====table====&lt;br /&gt;
A table that takes strings and data/numbers in a key/value sort of way. Only one post in the table will cause a crash and any number of entries above the first two will be discarded.&lt;br /&gt;
&lt;br /&gt;
   {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
   {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
 --{&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
   {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;} -- Two entries in the table with one being just an empty string works.&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====spacing====&lt;br /&gt;
The key word '''false''' can be used to insert some padding.&lt;br /&gt;
&lt;br /&gt;
 false,&lt;br /&gt;
&lt;br /&gt;
===desc.location===&lt;br /&gt;
Add a button to set the mission location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.location = mission.location&lt;br /&gt;
&lt;br /&gt;
===desc.returnLocation===&lt;br /&gt;
Add a button to set the return location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.returnLocation = mission.domicile&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     desc.details = {&lt;br /&gt;
         &amp;quot;This will be a 'title' with a delimiter underneath&amp;quot;,&lt;br /&gt;
         {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
         {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
         false, -- some space.&lt;br /&gt;
      -- {&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
         {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
     }&lt;br /&gt;
  &lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Moremissiondetails.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Maintaining immersion==&lt;br /&gt;
&lt;br /&gt;
Fictionally, of course, the bulletin board is visible to any and all ships that dock at the space station, not just the player. It is important that bulletin board missions are not all scaled to the capabilities of the player. Delivery missions with unreasonable deadlines should not be ruled out. Neither should cargo missions requiring much more cargo space than the player's ship has, or combat missions for which the player is completely unqualified.&lt;br /&gt;
&lt;br /&gt;
These missions should deal with the player gracefully; either allowing them to fail, and providing consequences, or preventing them from being given the mission.&lt;br /&gt;
&lt;br /&gt;
It's also important, if your script serves many instances of a mission, to periodically clear away bulletin board adverts and place new ones. Not just those with obvious time constraints, but any others; the assumption that the player should make is that perhaps some other character has taken these missions.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4704</id>
		<title>Missions and the mission list</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4704"/>
		<updated>2025-01-16T05:34:22Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Mission description */ Fix link&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{outdated}}&lt;br /&gt;
&lt;br /&gt;
==The player's mission list==&lt;br /&gt;
&lt;br /&gt;
Once the player has negotiated with your form, there might well be a mission in play. It could be a delivery, an assassination, a rush to tell somebody not to leave because so-and-so loves them... the possibilities are limited only by your creativity. The player needs a way to keep track of all the missions that they have agreed to undertake. Pioneer provides this through the player's mission screen, which they can access at any time using the F3 button, and looking at the missions tab. The content of this screen is controlled by some methods on the &amp;lt;code&amp;gt;Player&amp;lt;/code&amp;gt; object, which can always be found at &amp;lt;code&amp;gt;Game.player&amp;lt;/code&amp;gt;, and which inherits from &amp;lt;code&amp;gt;Ship&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Body&amp;lt;/code&amp;gt;. Missions are added to the screen using the &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; method. It takes a table of info, and returns an integer reference to that mission, which should be stored so that it can be updated or removed later. Below follows a typical use case from the [https://codedoc.pioneerspacesim.net/#LuaClass:Mission:New codedoc].&lt;br /&gt;
&lt;br /&gt;
Create a new mission and add it to the player’s mission list while retrieving the reference number:&lt;br /&gt;
&lt;br /&gt;
 ref = Mission.New({&lt;br /&gt;
     'type'      = 'Delivery', -- Must be a translatable token!&lt;br /&gt;
     'client'    = Character.New(),&lt;br /&gt;
     'due'       = Game.time + 3*24*60*60,       -- three days&lt;br /&gt;
     'reward'    = 123.45,&lt;br /&gt;
     'location'  = SystemPath:New(0,0,0,0,16),   -- Mars High, Sol&lt;br /&gt;
     'status'    = 'ACTIVE',&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
In practice, it might look more like this:&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 600, -- ten minutes' time&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
I don't recommend using &amp;lt;code&amp;gt;Game.player.frameBody.path&amp;lt;/code&amp;gt; here. I'm only using it because it always returns something, whether docked or not. A real mission would probably use a space station here. For this demonstration we've generated 'Taxi' missions that are already a known mission type, registered by '''Taxi.lua''' who wouldn't know about it because the mission scripts remembers it's own missions and has no way of knowing about missions registered by another module. So far no modules use another modules mission type and we only use it here for the sake of demonstration.&lt;br /&gt;
&lt;br /&gt;
This creates a mission visible on the mission screen:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist3.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Registering a mission type==&lt;br /&gt;
Before a module can create missions that are visible in the player's mission list, it needs to register a mission type. It's a painless task.&lt;br /&gt;
&lt;br /&gt;
'''[https://codedoc.pioneerspacesim.net/#LuaClass:Mission:RegisterType LuaClass:Mission:RegisterType]'''&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType(typeid, display, onClick)&lt;br /&gt;
&lt;br /&gt;
Apart from a unique string and a translatable mission name, you have the option to pass a function that takes care of the mission info presented when you press the '''More info...''' button in the missions list. By convention, this function is named '''buildMissionDescription'''. This would typically happen at the very end of the module. Here is what it looks like at the end of [https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L513 '''DeliverPackage.lua''']&lt;br /&gt;
&lt;br /&gt;
 Event.Register(&amp;quot;onGameEnd&amp;quot;, onGameEnd)&lt;br /&gt;
 Event.Register(&amp;quot;onReputationChanged&amp;quot;, onReputationChanged)&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
 &lt;br /&gt;
 Serializer:Register(&amp;quot;DeliverPackage&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The next example is extended with a much scaled-down version of the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L401-https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L434 onShipDocked]''' function from the '''DeliverPackage''' module. Since these missions are recognized by a module in Pioneer already ('Taxi') they will 'probably' be handled at a later stage if we fail to remove them with this test mission. If you generate a new mission type you must also handle removing it or the mission will remain in the game forever. There is no automatic logic, and no automatic removal. Your script must keep track of them. We'll wrap the action in a '''Timer''' function so we don't have to deal with the BBS form this time.&lt;br /&gt;
&lt;br /&gt;
 Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 ...&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Start the game and have a look under the '''Missions''' tab. After 2 seconds two missions are registered. One that should be finished in 10 seconds and another with a due time set 10 seconds after the first one. There is a second timer that checks in with the missions to see how it's going after 15 seconds. One should be completed successfully and the other will fail.&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship, station)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
     -- On docking, starting a new game, we create&lt;br /&gt;
     -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
     -- after 15 seconds and check in with our passengers.&lt;br /&gt;
     Timer:CallAt(Game.time + 15, function ()  -- 15 seconds timer&lt;br /&gt;
         for ref,mission in pairs(missions) do&lt;br /&gt;
             if Game.time &amp;gt; mission.due then&lt;br /&gt;
                 mission.status = 'FAILED'&lt;br /&gt;
                 Comms.ImportantMessage('We are five seconds late and I'm at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
             else&lt;br /&gt;
                 Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                 mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                 Game.player:AddMoney(mission.reward)&lt;br /&gt;
             end&lt;br /&gt;
             mission:Remove()&lt;br /&gt;
             missions[ref] = nil&lt;br /&gt;
         end&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
Missions Created:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist1.png]]&lt;br /&gt;
&lt;br /&gt;
1 Mission failed and 1 mission completed:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist2.png]]&lt;br /&gt;
&lt;br /&gt;
More info? If you look to the right on the mission list there is a button named '''More info...''' which will let you see more specific details of your mission. This will need to be specified in the mission script itself so if you press this button in the example above Pioneer will crash. The common name for the function you need to write in your script is '''buildMissionDescription'''. Here is what it looks like in '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L462-L490 DeliverPackage.lua]'''.&lt;br /&gt;
&lt;br /&gt;
==Mission description==&lt;br /&gt;
If you don't include the '''buildMissionDescription''' argument when you register the mission type, the mission list will not include a '''More info...''' button for the mission type. In the example above we used an existing mission type, '''Taxi''', but we didn't do it from within '''Taxi.lua''' so when '''More info...''' is pressed '''buildMissionDescription''' will be called in '''Taxi.lua''' but it will not now anything about the mission so the function will break. Try and comment out the entire '''buildMissionDescription''' in '''Taxi.lua''' and launch Pioneer once more. The Missions will register but now without the '''More info...''' button. To register a mission type without '''buildMissionDescription''' you simply leave it out.&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY)&lt;br /&gt;
&lt;br /&gt;
You can also declare a minimal '''buildMissionDescription''' that returns nil. This is fine. It will include the button which, if pressed, will launch a minimal info window with just the basics. A title, The customer image, and a '''Go back''' button.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
&lt;br /&gt;
The function responsible for interpreting the mission description is '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/modules/info-view/04-missions.lua#L42-L77 drawMissionDescription()]''' in '''/data/pigui/modules/info-view/04-missions.lua'''. It takes a table '''desc''' as it's argument. A minimal working mission description could look something like the example below. We set '''desc.description''' to an empty string and '''desc.details''' to a table with only one element '''false'''. '''desc.client''' is set to '''mission.client'''. This only works as part of an actual mission script where you already have a registered mission with a client. As you can see the '''Mission Details''' title and the '''Go back''' button are generated automatically.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Minimaldescription.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
===desc.description===&lt;br /&gt;
This is a string and will typically be based on the conversation from onChat.&lt;br /&gt;
&lt;br /&gt;
 desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===desc.client===&lt;br /&gt;
Takes a character as it's input and usually this means the character passed via the mission argument.&lt;br /&gt;
&lt;br /&gt;
 desc.client = mission.client&lt;br /&gt;
&lt;br /&gt;
===desc.details===&lt;br /&gt;
This is a table that is passed to '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/libs/text-table.lua#L15-L28 textTable.draw()]''' in '''data/pigui/libs/text-table.lua'''.&lt;br /&gt;
&lt;br /&gt;
 desc.details = {&lt;br /&gt;
     {&amp;quot;data here&amp;quot;, &amp;quot;more data&amp;quot;},&lt;br /&gt;
              ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====string====&lt;br /&gt;
A string will be presented in the form of a header with a delimiter underneath.&lt;br /&gt;
 &amp;quot;Just a string&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====table====&lt;br /&gt;
A table that takes strings and data/numbers in a key/value sort of way. Only one post in the table will cause a crash and any number of entries above the first two will be discarded.&lt;br /&gt;
&lt;br /&gt;
   {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
   {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
 --{&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
   {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;} -- Two entries in the table with one being just an empty string works.&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====spacing====&lt;br /&gt;
The key word '''false''' can be used to insert some padding.&lt;br /&gt;
&lt;br /&gt;
 false,&lt;br /&gt;
&lt;br /&gt;
===desc.location===&lt;br /&gt;
Add a button to set the mission location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.location = mission.location&lt;br /&gt;
&lt;br /&gt;
===desc.returnLocation===&lt;br /&gt;
Add a button to set the return location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.returnLocation = mission.domicile&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     desc.details = {&lt;br /&gt;
         &amp;quot;This will be a 'title' with a delimiter underneath&amp;quot;,&lt;br /&gt;
         {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
         {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
         false, -- some space.&lt;br /&gt;
      -- {&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
         {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
     }&lt;br /&gt;
  &lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Moremissiondetails.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Maintaining immersion==&lt;br /&gt;
&lt;br /&gt;
Fictionally, of course, the bulletin board is visible to any and all ships that dock at the space station, not just the player. It is important that bulletin board missions are not all scaled to the capabilities of the player. Delivery missions with unreasonable deadlines should not be ruled out. Neither should cargo missions requiring much more cargo space than the player's ship has, or combat missions for which the player is completely unqualified.&lt;br /&gt;
&lt;br /&gt;
These missions should deal with the player gracefully; either allowing them to fail, and providing consequences, or preventing them from being given the mission.&lt;br /&gt;
&lt;br /&gt;
It's also important, if your script serves many instances of a mission, to periodically clear away bulletin board adverts and place new ones. Not just those with obvious time constraints, but any others; the assumption that the player should make is that perhaps some other character has taken these missions.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
	<entry>
		<id>https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4703</id>
		<title>Missions and the mission list</title>
		<link rel="alternate" type="text/html" href="https://wiki.pioneerspacesim.net/index.php?title=Missions_and_the_mission_list&amp;diff=4703"/>
		<updated>2025-01-16T05:32:24Z</updated>

		<summary type="html">&lt;p&gt;Zonkmachine: /* Mission description */ Fixup&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{outdated}}&lt;br /&gt;
&lt;br /&gt;
==The player's mission list==&lt;br /&gt;
&lt;br /&gt;
Once the player has negotiated with your form, there might well be a mission in play. It could be a delivery, an assassination, a rush to tell somebody not to leave because so-and-so loves them... the possibilities are limited only by your creativity. The player needs a way to keep track of all the missions that they have agreed to undertake. Pioneer provides this through the player's mission screen, which they can access at any time using the F3 button, and looking at the missions tab. The content of this screen is controlled by some methods on the &amp;lt;code&amp;gt;Player&amp;lt;/code&amp;gt; object, which can always be found at &amp;lt;code&amp;gt;Game.player&amp;lt;/code&amp;gt;, and which inherits from &amp;lt;code&amp;gt;Ship&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Body&amp;lt;/code&amp;gt;. Missions are added to the screen using the &amp;lt;code&amp;gt;Mission.New()&amp;lt;/code&amp;gt; method. It takes a table of info, and returns an integer reference to that mission, which should be stored so that it can be updated or removed later. Below follows a typical use case from the [https://codedoc.pioneerspacesim.net/#LuaClass:Mission:New codedoc].&lt;br /&gt;
&lt;br /&gt;
Create a new mission and add it to the player’s mission list while retrieving the reference number:&lt;br /&gt;
&lt;br /&gt;
 ref = Mission.New({&lt;br /&gt;
     'type'      = 'Delivery', -- Must be a translatable token!&lt;br /&gt;
     'client'    = Character.New(),&lt;br /&gt;
     'due'       = Game.time + 3*24*60*60,       -- three days&lt;br /&gt;
     'reward'    = 123.45,&lt;br /&gt;
     'location'  = SystemPath:New(0,0,0,0,16),   -- Mars High, Sol&lt;br /&gt;
     'status'    = 'ACTIVE',&lt;br /&gt;
 })&lt;br /&gt;
&lt;br /&gt;
In practice, it might look more like this:&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 600, -- ten minutes' time&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
I don't recommend using &amp;lt;code&amp;gt;Game.player.frameBody.path&amp;lt;/code&amp;gt; here. I'm only using it because it always returns something, whether docked or not. A real mission would probably use a space station here. For this demonstration we've generated 'Taxi' missions that are already a known mission type, registered by '''Taxi.lua''' who wouldn't know about it because the mission scripts remembers it's own missions and has no way of knowing about missions registered by another module. So far no modules use another modules mission type and we only use it here for the sake of demonstration.&lt;br /&gt;
&lt;br /&gt;
This creates a mission visible on the mission screen:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist3.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Registering a mission type==&lt;br /&gt;
Before a module can create missions that are visible in the player's mission list, it needs to register a mission type. It's a painless task.&lt;br /&gt;
&lt;br /&gt;
'''[https://codedoc.pioneerspacesim.net/#LuaClass:Mission:RegisterType LuaClass:Mission:RegisterType]'''&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType(typeid, display, onClick)&lt;br /&gt;
&lt;br /&gt;
Apart from a unique string and a translatable mission name, you have the option to pass a function that takes care of the mission info presented when you press the '''More info...''' button in the missions list. By convention, this function is named '''buildMissionDescription'''. This would typically happen at the very end of the module. Here is what it looks like at the end of [https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L513 '''DeliverPackage.lua''']&lt;br /&gt;
&lt;br /&gt;
 Event.Register(&amp;quot;onGameEnd&amp;quot;, onGameEnd)&lt;br /&gt;
 Event.Register(&amp;quot;onReputationChanged&amp;quot;, onReputationChanged)&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
 &lt;br /&gt;
 Serializer:Register(&amp;quot;DeliverPackage&amp;quot;, serialize, unserialize)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The next example is extended with a much scaled-down version of the '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L401-https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L434 onShipDocked]''' function from the '''DeliverPackage''' module. Since these missions are recognized by a module in Pioneer already ('Taxi') they will 'probably' be handled at a later stage if we fail to remove them with this test mission. If you generate a new mission type you must also handle removing it or the mission will remain in the game forever. There is no automatic logic, and no automatic removal. Your script must keep track of them. We'll wrap the action in a '''Timer''' function so we don't have to deal with the BBS form this time.&lt;br /&gt;
&lt;br /&gt;
 Timer:CallAt(Game.time + 2, function ()&lt;br /&gt;
 ...&lt;br /&gt;
 end)&lt;br /&gt;
&lt;br /&gt;
Start the game and have a look under the '''Missions''' tab. After 2 seconds two missions are registered. One that should be finished in 10 seconds and another with a due time set 10 seconds after the first one. There is a second timer that checks in with the missions to see how it's going after 15 seconds. One should be completed successfully and the other will fail.&lt;br /&gt;
&lt;br /&gt;
 local Character = require 'Character'&lt;br /&gt;
 local Comms = require 'Comms'&lt;br /&gt;
 local Event = require 'Event'&lt;br /&gt;
 local Game = require 'Game'&lt;br /&gt;
 local Mission = require 'Mission'&lt;br /&gt;
 local Player = require 'Player'&lt;br /&gt;
 local Timer = require 'Timer'&lt;br /&gt;
 &lt;br /&gt;
 local missions = {}&lt;br /&gt;
 &lt;br /&gt;
 local onShipDocked = function (ship, station)&lt;br /&gt;
     if ship:IsPlayer() then&lt;br /&gt;
 &lt;br /&gt;
     -- On docking, starting a new game, we create&lt;br /&gt;
     -- two npc's and book them on a taxi mission. &lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 10, -- ten seconds&lt;br /&gt;
             reward = 10,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
         table.insert(missions, Mission.New({&lt;br /&gt;
             type = &amp;quot;Taxi&amp;quot;,&lt;br /&gt;
             client = Character.New(),&lt;br /&gt;
             due = Game.time + 20, -- 20 seconds&lt;br /&gt;
             reward = 5000,&lt;br /&gt;
             location = Game.player.frameBody.path, -- here, basically&lt;br /&gt;
             status = 'ACTIVE'&lt;br /&gt;
         }))&lt;br /&gt;
     end&lt;br /&gt;
 &lt;br /&gt;
     -- Magically, without moving, we've arrived at the destination&lt;br /&gt;
     -- after 15 seconds and check in with our passengers.&lt;br /&gt;
     Timer:CallAt(Game.time + 15, function ()  -- 15 seconds timer&lt;br /&gt;
         for ref,mission in pairs(missions) do&lt;br /&gt;
             if Game.time &amp;gt; mission.due then&lt;br /&gt;
                 mission.status = 'FAILED'&lt;br /&gt;
                 Comms.ImportantMessage('We are five seconds late and I'm at a loss of words. I had been looking forward to this ride and now my day is simply destroyed!', mission.client.name)&lt;br /&gt;
             else&lt;br /&gt;
                 Comms.ImportantMessage('Thanks for the ride!', mission.client.name)&lt;br /&gt;
                 mission.status = &amp;quot;COMPLETED&amp;quot;&lt;br /&gt;
                 Game.player:AddMoney(mission.reward)&lt;br /&gt;
             end&lt;br /&gt;
             mission:Remove()&lt;br /&gt;
             missions[ref] = nil&lt;br /&gt;
         end&lt;br /&gt;
     end)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Event.Register(&amp;quot;onShipDocked&amp;quot;, onShipDocked)&lt;br /&gt;
&lt;br /&gt;
Missions Created:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist1.png]]&lt;br /&gt;
&lt;br /&gt;
1 Mission failed and 1 mission completed:&lt;br /&gt;
&lt;br /&gt;
[[File:Missionlist2.png]]&lt;br /&gt;
&lt;br /&gt;
More info? If you look to the right on the mission list there is a button named '''More info...''' which will let you see more specific details of your mission. This will need to be specified in the mission script itself so if you press this button in the example above Pioneer will crash. The common name for the function you need to write in your script is '''buildMissionDescription'''. Here is what it looks like in '''[https://github.com/pioneerspacesim/pioneer/blob/646f13acb76ab6ff3e407258c598e9e4012f5008/data/modules/DeliverPackage/DeliverPackage.lua#L462-L490 DeliverPackage.lua]'''.&lt;br /&gt;
&lt;br /&gt;
==Mission description==&lt;br /&gt;
If you don't include the '''buildMissionDescription''' argument when you register the mission type, the mission list will not include a '''More info...''' button for the mission type. In the example above we used an existing mission type, '''Taxi''', but we didn't do it from within '''Taxi.lua''' so when '''More info...''' is pressed '''buildMissionDescription''' will be called in '''Taxi.lua''' but it will not now anything about the mission so the function will break. Try and comment out the entire '''buildMissionDescription''' in '''Taxi.lua''' and launch Pioneer once more. The Missions will register but now without the '''More info...''' button. To register a mission type without '''buildMissionDescription''' you simply leave it out.&lt;br /&gt;
&lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY)&lt;br /&gt;
&lt;br /&gt;
You can also declare a minimal '''buildMissionDescription''' that returns nil. This is fine. It will include the button which, if pressed, will launch a minimal info window with just the basics. A title, The customer image, and a '''Go back''' button.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 Mission.RegisterType('Delivery', l.DELIVERY, buildMissionDescription)&lt;br /&gt;
&lt;br /&gt;
The function responsible for interpreting the mission description is '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/modules/info-view/04-missions.lua#L49-L76 drawMissionDescription()]''' in '''/data/pigui/modules/info-view/04-missions.lua'''. It takes a table '''desc''' as it's argument. A minimal working mission description could look something like the example below. We set '''desc.description''' to an empty string and '''desc.details''' to a table with only one element '''false'''. '''desc.client''' is set to '''mission.client'''. This only works as part of an actual mission script where you already have a registered mission with a client. As you can see the '''Mission Details''' title and the '''Go back''' button are generated automatically.&lt;br /&gt;
&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;&amp;quot;&lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
     desc.details = {false}&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Minimaldescription.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
===desc.description===&lt;br /&gt;
This is a string and will typically be based on the conversation from onChat.&lt;br /&gt;
&lt;br /&gt;
 desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===desc.client===&lt;br /&gt;
Takes a character as it's input and usually this means the character passed via the mission argument.&lt;br /&gt;
&lt;br /&gt;
 desc.client = mission.client&lt;br /&gt;
&lt;br /&gt;
===desc.details===&lt;br /&gt;
This is a table that is passed to '''[https://github.com/pioneerspacesim/pioneer/blob/8a84b93e361d462baf6e0fc688679c0bc1ba7078/data/pigui/libs/text-table.lua#L15-L28 textTable.draw()]''' in '''data/pigui/libs/text-table.lua'''.&lt;br /&gt;
&lt;br /&gt;
 desc.details = {&lt;br /&gt;
     {&amp;quot;data here&amp;quot;, &amp;quot;more data&amp;quot;},&lt;br /&gt;
              ...&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====string====&lt;br /&gt;
A string will be presented in the form of a header with a delimiter underneath.&lt;br /&gt;
 &amp;quot;Just a string&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
====table====&lt;br /&gt;
A table that takes strings and data/numbers in a key/value sort of way. Only one post in the table will cause a crash and any number of entries above the first two will be discarded.&lt;br /&gt;
&lt;br /&gt;
   {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
   {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
 --{&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
   {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;} -- Two entries in the table with one being just an empty string works.&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
====spacing====&lt;br /&gt;
The key word '''false''' can be used to insert some padding.&lt;br /&gt;
&lt;br /&gt;
 false,&lt;br /&gt;
&lt;br /&gt;
===desc.location===&lt;br /&gt;
Add a button to set the mission location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.location = mission.location&lt;br /&gt;
&lt;br /&gt;
===desc.returnLocation===&lt;br /&gt;
Add a button to set the return location as navigation target.&lt;br /&gt;
&lt;br /&gt;
From the CargoRun module:&lt;br /&gt;
 desc.returnLocation = mission.domicile&lt;br /&gt;
&lt;br /&gt;
===Example===&lt;br /&gt;
 local buildMissionDescription = function (mission)&lt;br /&gt;
     local ui = require 'pigui'&lt;br /&gt;
     local desc = {}&lt;br /&gt;
 &lt;br /&gt;
     desc.description = &amp;quot;String to be shown on top of the mission description goes in here!&amp;quot;&lt;br /&gt;
 &lt;br /&gt;
     desc.details = {&lt;br /&gt;
         &amp;quot;This will be a 'title' with a delimiter underneath&amp;quot;,&lt;br /&gt;
         {&amp;quot;Left&amp;quot;,&amp;quot;Right&amp;quot;},&lt;br /&gt;
         {&amp;quot;Data&amp;quot;, 42},&lt;br /&gt;
         false, -- some space.&lt;br /&gt;
      -- {&amp;quot;This entry would cause a crash&amp;quot;},&lt;br /&gt;
         {&amp;quot;No crash&amp;quot;, &amp;quot;&amp;quot;}&lt;br /&gt;
     }&lt;br /&gt;
  &lt;br /&gt;
     desc.client = mission.client&lt;br /&gt;
 &lt;br /&gt;
     return desc;&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
[[File:Moremissiondetails.png|1024px]]&lt;br /&gt;
&lt;br /&gt;
==Maintaining immersion==&lt;br /&gt;
&lt;br /&gt;
Fictionally, of course, the bulletin board is visible to any and all ships that dock at the space station, not just the player. It is important that bulletin board missions are not all scaled to the capabilities of the player. Delivery missions with unreasonable deadlines should not be ruled out. Neither should cargo missions requiring much more cargo space than the player's ship has, or combat missions for which the player is completely unqualified.&lt;br /&gt;
&lt;br /&gt;
These missions should deal with the player gracefully; either allowing them to fail, and providing consequences, or preventing them from being given the mission.&lt;br /&gt;
&lt;br /&gt;
It's also important, if your script serves many instances of a mission, to periodically clear away bulletin board adverts and place new ones. Not just those with obvious time constraints, but any others; the assumption that the player should make is that perhaps some other character has taken these missions.&lt;/div&gt;</summary>
		<author><name>Zonkmachine</name></author>
		
	</entry>
</feed>