Difference between revisions of "Mission Scripting"

From Off Grid Wiki
Jump to navigation Jump to search
(Fix Device Scripting Link)
 
(38 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{OffGridDocStart}}
 
 
 
== Intro ==
 
== Intro ==
  
Line 21: Line 19:
 
|-
 
|-
 
|}
 
|}
 +
 +
You might also want to get our [[Atom Snippets]] code and copy&paste it into Atom's Snippets file to add nice autocompletion for Off Grid API.
  
 
=== LevelKit & Game ===
 
=== LevelKit & Game ===
Line 32: Line 32:
 
Enough talking, lets jump into an example! Rather than posting an entire example script, we'll go from top to bottom exploring what each element does.
 
Enough talking, lets jump into an example! Rather than posting an entire example script, we'll go from top to bottom exploring what each element does.
  
=== Initial Mission State ===
+
=== Mission table ===
 +
 
 +
==== Initial Mission State ====
 
<syntaxhighlight source lang="lua" line>
 
<syntaxhighlight source lang="lua" line>
mission = {
+
name = "MyNewMission",
 
+
description = "The best mod ever",
-- Mission info
+
startTime = "27/04/2009/21:30",
startTime = {2009, 04, 27, 21, 13, 00},
 
  
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 46: Line 47:
 
! Name !! Description
 
! Name !! Description
 
|-
 
|-
| ''startTime'' || The start time of the mission in the game world (Year, Month, Day, Hour, Mins, Secs)
+
| ''startTime'' || The start time of the mission in the game world (Day/Month/Year/Hour:Mins)
 
|}
 
|}
  
startTime doesn't have to be the current date, you can set your mission in any time you desire.
+
startTime doesn't have to (and probaly shouldn't) be the current date, you can set your mission in any time you desire.
 +
 
 +
==== State ====
 +
 
 +
State includes anything you'd like to be saved & loaded. So for instance, you might want to set a bool on the first time a player enters a building, or performs an action, or completes a particular objective. Currently we only support numbers, bools and strings, and these need to have string keys (rather than indices).
 +
 
 +
<syntaxhighlight source lang="lua" line start=5>
 +
state = {
 +
hasEnteredBuilding = true,
 +
sideObjectivesComplete = 17,
 +
mostAnnoyedGuard = "Brian",
 +
},
 +
</syntaxhighlight>
  
=== Characters ===
+
==== Characters ====
  
 
<syntaxhighlight source lang="lua" line start=5>
 
<syntaxhighlight source lang="lua" line start=5>
 
-- Character definitions:
 
-- Character definitions:
 
characters = {
 
characters = {
joe = {
+
player = {
 
displayName = "Joe Harman",
 
displayName = "Joe Harman",
internalName = "Joe",
 
 
characterType = "player",
 
characterType = "player",
 
prefab = "player",
 
prefab = "player",
Line 66: Line 78:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
This is the first example of us creating a sub table in the ''mission'' table, the ''characters'' table contains all the information about the characters in your mission!
+
This is the first example of us creating a sub table in the ''mission'' table, the ''characters'' table contains all the information about the characters in your mission! Different types of characters might need different values, and there are some optional settings as well for different purposes. Different character types and creating background profiles for the characters is explained in more detail in [[Character Profiles]].
 +
 
 
In this example we've added Joe, the protagonist of Off Grid. Here's a break down of what the value of Joe's table means:
 
In this example we've added Joe, the protagonist of Off Grid. Here's a break down of what the value of Joe's table means:
 
{| class="wikitable"
 
{| class="wikitable"
Line 74: Line 87:
 
|-
 
|-
 
| ''displayName'' || The name that will be used any game UI referencing the character  
 
| ''displayName'' || The name that will be used any game UI referencing the character  
|-
 
| ''internalName'' || The internalName can be thought of as the id for the character, you'll reference the character by this value in a few places including conversations (Must be unique!)
 
 
|-  
 
|-  
 
| ''characterType'' || The 'type' of the character, see [[Character Types and Prefabs]] for more information on possible values
 
| ''characterType'' || The 'type' of the character, see [[Character Types and Prefabs]] for more information on possible values
Line 86: Line 97:
 
The ''characters'' table can hold information on as many characters as you'd like to fill the mission with, they won't be spawned into the mission until you decide (more on that later)
 
The ''characters'' table can hold information on as many characters as you'd like to fill the mission with, they won't be spawned into the mission until you decide (more on that later)
  
=== Physical Items ===
+
==== Physical Items ====
  
 
<syntaxhighlight source lang="lua" line start=15>
 
<syntaxhighlight source lang="lua" line start=15>
Line 92: Line 103:
 
items = {
 
items = {
 
usbkey = {
 
usbkey = {
internalName = "USBDongle",
 
 
displayName = "USB dongle",
 
displayName = "USB dongle",
 
description = "Modified USB Bluetooth dongle given to you by the hackers",
 
description = "Modified USB Bluetooth dongle given to you by the hackers",
 
uiSpriteName = "usbdongle.png",
 
uiSpriteName = "usbdongle.png",
 +
                        onItemUse = function()
 +
                            // Do stuff.
 +
                        end,
 
},
 
},
 
},
 
},
Line 105: Line 118:
 
|-     
 
|-     
 
! Name !! Description
 
! Name !! Description
|-
 
| ''internalName'' || The internalName we'll use to identify this item (Must be unique!)
 
 
|-
 
|-
 
| ''displayName'' || The display name of the item, this is what will be displayed in the games UI
 
| ''displayName'' || The display name of the item, this is what will be displayed in the games UI
Line 112: Line 123:
 
| ''description'' || The description of the item, this will be displayed to users in the players inventory UI
 
| ''description'' || The description of the item, this will be displayed to users in the players inventory UI
 
|-
 
|-
| ''uiSpriteName'' || The path to the items sprite, this is the image that will be used in the games UI
+
| ''uiSpriteName'' || The path to the image file to be used for the item in game menus.
 +
|-
 +
| ''onItemUse'' || Optional. Function that's ran when the player selects to use the item in inventory menu.
 
|}
 
|}
  
=== Data ===
+
==== Data ====
  
 
<syntaxhighlight source lang="lua" line start=24>
 
<syntaxhighlight source lang="lua" line start=24>
Line 123: Line 136:
 
data = {
 
data = {
 
PlayerPGPKey = {
 
PlayerPGPKey = {
internalName = "PlayerPGPKey",
 
 
name = "Personal PGP encryption key",
 
name = "Personal PGP encryption key",
 
immutable = true,
 
immutable = true,
dataType = 7,
+
dataType = DataType.Key,
 
creatorName = "Player",
 
creatorName = "Player",
 
dataString = "PGP Fingerprint: 1d7d ef54 7a63 5756 63a7 cf14 fbd8 775c c39d 4e51",
 
dataString = "PGP Fingerprint: 1d7d ef54 7a63 5756 63a7 cf14 fbd8 775c c39d 4e51",
Line 142: Line 154:
 
|-     
 
|-     
 
! Name !! Description
 
! Name !! Description
|-
 
| ''internalName'' || The internalName we'll use to identify this data (Must be unique!)
 
 
|-
 
|-
 
| ''name'' || The display name of the data, this is what will be displayed in the games UI
 
| ''name'' || The display name of the data, this is what will be displayed in the games UI
 
|-
 
|-
| ''immutable'' || Is the data immutable? If true the player won't be able to delete it, this is useful for data items that are part of mission objectives
+
| ''immutable'' || Optional. Is the data immutable? If true the player won't be able to delete it, this is useful for data items that are part of mission objectives
 
|-  
 
|-  
| ''dataType'' || The type of the data, this is used for displaying the data correctly  
+
| ''dataType'' || The type of the data, this is used for displaying the data correctly. Available DataTypes are listed in [[Constants_Lua_API#DataType|Constants page]].
 
|-  
 
|-  
 
| ''creatorName'' || The name of the data's creator (displayed in the UI)
 
| ''creatorName'' || The name of the data's creator (displayed in the UI)
Line 158: Line 168:
 
|-
 
|-
 
| ''dataColor'' || RGBA value that's used as the colour of the data point when visible in the players data view
 
| ''dataColor'' || RGBA value that's used as the colour of the data point when visible in the players data view
 +
|-
 +
| ''script'' || The path (relative to the mission folder) to the optional Lua script for this data. See [[Data Scripting]] for more information.
 
|}
 
|}
  
=== Networks ===
+
==== Networks ====
  
 
<syntaxhighlight source lang="lua" line start=40>
 
<syntaxhighlight source lang="lua" line start=40>
 
-- Networks:
 
-- Networks:
--[[ types: 0 = mobile, 1 = WiFi, 2 = mesh ]]--
 
 
networks = {
 
networks = {
Semaeopus4G = {
+
semaeopus4G = {
name = "Semaeopus4G",
+
name = "Semaeopus 4G",
networkType = 0,
+
networkType = NetworkType.mobile,
 
allowPlayerDisconnect = false,
 
allowPlayerDisconnect = false,
userAccessKey = "user",
 
adminAccessKey = "admin",
 
rootAccessKey = "root",
 
 
},
 
},
 
},
 
},
Line 185: Line 193:
 
| ''name'' || The name of the network (Must be unique!)
 
| ''name'' || The name of the network (Must be unique!)
 
|-
 
|-
| ''networkType'' || The type of the network (mobile - 0, WiFi - 1, mesh - 2)
+
| ''networkType'' || The type of the network.
 
|-
 
|-
 
| ''allowPlayerDisconnect'' || Is the player allowed to disconnect from the network?
 
| ''allowPlayerDisconnect'' || Is the player allowed to disconnect from the network?
|-
 
| ''userAccessKey'' || The password for user access to the network
 
|-
 
| ''adminAccessKey'' || The password for admin access to the network
 
|-
 
| ''rootAccessKey'' || The password for root access to the network
 
 
|}
 
|}
  
 
Data sent at higher access levels across a network won't be visible to devices that are connected at a lower access level.
 
Data sent at higher access levels across a network won't be visible to devices that are connected at a lower access level.
  
=== Objectives ===
+
==== Devices ====
 +
 
 +
<syntaxhighlight source lang="lua" line start=66>
 +
devices = {
 +
laptop = {
 +
owner = "ownerInternalName",
 +
dataColor = Color.Orange,
 +
script = "Scripts/Devices/laptop.lua",
 +
},
 +
},
 +
</syntaxhighlight>
 +
 
 +
{| class="wikitable"
 +
!colspan="2" | Device Table
 +
|- 
 +
! Name !! Description
 +
|-
 +
| ''dataColor'' || The color used when displaying data from this device.
 +
|-
 +
| ''owner'' || Optional. The internalName of the device's owner. Use this if you want too restrict device's access based on owner's metadata or something.
 +
|-
 +
| ''script'' || The script that defines the GUI and the behaviour of the device ( See [[Device Scripting | device scripts]] for more information )
 +
|}
 +
 
 +
==== Objectives ====
  
 
<syntaxhighlight source lang="lua" line start=52>
 
<syntaxhighlight source lang="lua" line start=52>
Line 205: Line 231:
 
enterTheBuilding = {
 
enterTheBuilding = {
 
name = "Enter the building",
 
name = "Enter the building",
 +
description = "Short description of the objective",
 +
active = false,
 +
visible = true,
 +
keyObjective = false,
 +
 
onStart = function()
 
onStart = function()
 
print("Player must now enter the building")
 
print("Player must now enter the building")
Line 222: Line 253:
 
! Name !! Description
 
! Name !! Description
 
|-
 
|-
| ''name'' || The name of the objective (should be unique)
+
| ''name'' || The name of the objective, displayed to the player.
 +
|-
 +
| ''description'' || Short description displayed to the player in the Tasks menu.
 
|-
 
|-
| ''onStart'' || A callback function that's triggered when the objective is started
+
| ''active'' || Is the objective active at the moment (as opposed to already completed, or not even started yet)? (Optional. This is handled automatically when you call Mission.StartObjective() and Mission.CompleteObjective())
 
|-
 
|-
| ''onCompleted'' || A callback function that's triggered when the objective is completed
+
| ''visible'' || Should this objective be displayed to player? Hidden objectives can help organizing your mission scripts. (Optional, defaults to visible)
 +
|-
 +
| ''keyObjective'' || Key objectives are ones the player knows from the start of the level, they are visible even before the actual objective becomes active. (Optional)
 +
|-
 +
| ''onStart'' ||A callback function that's triggered when the objective is started. (Optional)
 +
|-
 +
| ''onCompleted'' || A callback function that's triggered when the objective is completed (Optional)
 
|}
 
|}
  
=== Devices ===
+
=== Starting the mission ===
 
 
<syntaxhighlight source lang="lua" line start=66>
 
devices = {
 
laptop = {
 
internalName = "laptop",
 
script = "Scripts/Devices/laptop.lua",
 
},
 
},
 
</syntaxhighlight>
 
  
{| class="wikitable"
+
In addition to the big mission table, you'll need two more things to get a mission up and running; MissionSetup() and MissionStart() functions.
!colspan="2" | Device Table
 
|- 
 
! Name !! Description
 
|-
 
| ''internalName'' || The name of the device. Must be unique and match the MissionObject in the scene
 
|-
 
| ''script'' || The script that defines the gui and the behaviour of the device ( See [[Mission Scripting|Device Scripting]] for more information )
 
|}
 
  
=== Mission Setup ===
+
==== Mission Setup ====
 +
SetupMission() is executed every time the mission is started, be it a fresh start, or when loading a saved game.
  
 
<syntaxhighlight source lang="lua" line start=73>
 
<syntaxhighlight source lang="lua" line start=73>
 
function SetupMission()
 
function SetupMission()
 +
 
-- add player and guards:
 
-- add player and guards:
Mission.AddCharacter(mission.characters.joe)
+
Mission.SpawnCharacter("player")
-- these two below add in the guards which you might not have created yet!
+
-- these two below add in the guards which you might not have created yet!
Mission.AddCharacter(mission.characters.jack)
+
Mission.SpawnCharacter("jack")
Mission.AddCharacter(mission.characters.john)
+
Mission.SpawnCharacter("john")
  
-- set up hackable devices
+
-- Doors:
Mission.AddHackableDevice(mission.devices.laptop)
+
Doors.SetNetwork("ApostleWiFi")
  
-- set up networks:
+
Doors.SetZoneKeys("Maintenance", {"Maintenance", "Staff", "Security", "Admin"})
Mission.AddNetwork(mission.networks.Semaeopus4G)
+
Doors.SetZoneKeys("Staff", {"Staff", "Security", "Admin"})
Mission.ConnectToNetwork(mission.devices.laptop, mission.networks.Semaeopus4G.name, mission.networks.Semaeopus4G.userAccessKey)
+
Doors.SetZoneKeys("Security", {"Security", "Admin"})
Doors.SetNetwork(mission.networks.Semaeopus4G)
+
Doors.SetZoneKeys("Admin", {"Admin"})
Doors.SetZoneKeys("admin", {"adminkey"})
 
  
-- misc.
+
Mission.MissionStarted()
-- Player.SetInvisible(true)
 
 
 
Mission.MissionStarted()
 
 
end
 
end
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 278: Line 298:
 
This will add in all of your created elements into your level, below you will see what the function of each part of this code will do.
 
This will add in all of your created elements into your level, below you will see what the function of each part of this code will do.
  
==== Adding in Characters ====
+
===== Adding in Characters =====
  
 
<syntaxhighlight source lang="lua" line start=74>
 
<syntaxhighlight source lang="lua" line start=74>
 
-- add player and guards:
 
-- add player and guards:
Mission.AddCharacter(mission.characters.joe)
+
Mission.SpawnCharacter("player")
-- these two below add in the guards which you might not have created yet!
+
-- these two below add in the guards which you might not have created yet!
Mission.AddCharacter(mission.characters.jack)
+
Mission.SpawnCharacter("jack")
Mission.AddCharacter(mission.characters.john)
+
Mission.SpawnCharacter("john")
 
</syntaxhighlight>
 
</syntaxhighlight>
  
This part of the code shows the format of adding in characters - this includes the player and any NPCs like the guards, which you will have created further up in your Mission Script. If the title of your character table for your player was called <code>jenson</code>, you would simply change <code>Mission.AddCharacter(mission.characters.joe)</code> to <code>Mission.AddCharacter(mission.characters.jenson)</code>, this would now let your player spawn in.
+
This part of the code shows the format of spawning in characters - this includes the player and any NPCs like the guards, which you will have created further up in your Mission Script. If the title of your character table for your player was called <code>jenson</code>, you would simply change <code>Mission.SpawnCharacter("player")</code> to <code>Mission.SpawnCharacter("jenson")</code>, this would now let your new character spawn in.
  
==== Adding in Hackable Devices ====
 
  
<syntaxhighlight source lang="lua" line start=80>
+
===== Connect Doors to the Network and set Zones =====
-- set up hackable devices
+
Once you have [[Setting up Doors|created doors]] you can then also add them to a network. Network-connected doors arre controlled by the electronic lock systems in the buildings and opened by specific access keys. Firstly you add the door to a network, then you can set the door's zone to require a specific key to open it. For example where if you consider the following command to set keys: <code>Doors.SetZoneKeys("Offices", {"admins", "janitors"})</code>, <code>"Offices"</code> is the name of the zone the door is assigned to, and <code>{"admins", "janitors"}</code> telsl that all doors belonging to this zone should be possible to open using either "admin" or "janitors" keys.
Mission.AddHackableDevice(mission.devices.laptop)
+
 
 +
==== Mission Start ====
 +
 
 +
StartMission() is ran when the mission is started form the beginning. When loading a save or checkpoint, the same things would be covered by data loaded from the save instead. Use this to set initial state of things in your level.
 +
 
 +
<syntaxhighlight source lang="lua" line start=0>
 +
function StartMission()
 +
 
 +
-- Add items to player inventory:
 +
Player.ClearInventory()
 +
Player.AddItemToInventory("usbkey")
 +
Player.AddItemToInventory("meshnode")
 +
 
 +
--  Add files to data inventory:
 +
Player.ClearDataFiles()
 +
Player.AddDataFile("PlayerPGPKey", mission.data.PlayerPGPKey)
 +
Player.AddDataFile("CoffeeOffer", mission.data.CoffeeOffer)
 +
 
 +
--mobile
 +
Network.ConnectToNetwork(
 +
{
 +
"player",
 +
"DevLaptop",
 +
"Smedley",
 +
"Terrence",
 +
"securityChief",
 +
"apostleItIntern",
 +
"guard1","guard2","guardF1a","guardF1b","guardF1c","guard4","guard5",
 +
"drone1",
 +
"davesEPhone",
 +
"BasementSmartTV",
 +
"BasementLaptop",
 +
"iDeskLampTEST",
 +
"testRadio",
 +
"SmartFridge",
 +
"sodamachine",
 +
},
 +
"Semaeopus4G",
 +
"user"
 +
)
 +
 
 +
-- WIFI
 +
Network.ConnectToNetwork("player", "ApostleWiFi", "none" )
 +
Network.ConnectToNetwork(
 +
{
 +
"guard1","guard2","guardF1a","guardF1b","guardF1c","guard4","guard5",
 +
},
 +
"ApostleWiFi",
 +
"user"
 +
)
 +
 
 +
-- MESH
 +
Network.ConnectToNetwork( {"Smedley","MeshleaksSecureDrop"}, "MESH", "admin" )
 +
 
 +
Mission.DevicesConnected()
 +
 
 +
-- Doors
 +
Doors.SetKeyOnDevice("Maintenance", "IDcard01")
 +
Doors.SetKeyOnDevice("Admin", "IDcard02")
 +
 
 +
Doors.AssignKeyToCharacter("Security", "guard1")
 +
Doors.AssignKeyToCharacter("Security", "guard2")
 +
 
 +
 
 +
-- Set up music (we'll set it to silent in start, and set gameplay state from a trigger when entering lobby)
 +
Sound.TriggerEvent("Set_MX_Mission_1")
 +
Sound.TriggerEvent("Set_State_MX_Mission_Silent")
 +
 
 +
---[[ Things to enable when testing & debugging:
 +
Apps.RequestAppState("DevTools", AppState.off)
 +
--]]
 +
 
 +
end
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Once you have set up some hackable devices, by adding this to your SetupMission() the hackable devices will now be added into your level. Similarly to adding in the characters as seen above, to customise this for your own devices, you only need to change the last part inside the brackets. So if the title of one of your devices tables was <code>vendingmachine</code>, you could just change <code>Mission.AddHackableDevice(mission.devices.laptop)</code> to <code>Mission.AddHackableDevice(mission.devices.vendingmachine)</code>.
+
== Including other files ==
  
==== Adding in Networks ====
+
We know how long these mission scripts can become. As a result, we came up with a method of splitting up your mission into smaller, bite-size pieces. You're more than welcome to keep your mission in a single file, but if you want to manage them differently, read on...
 +
 
 +
Let's say you want to keep all your device related shenanigans in a separate file for clarity. We'll call this 'mission_devices.lua', and put it in the same folder as the mission.
 +
 
 +
<syntaxhighlight source lang="lua" line start=0>
 +
MyDevices = {
 +
    somedevice = {
 +
      internalName = "Device Z",
 +
      script = "device.lua"
 +
    },
 +
    someotherdevice = {
 +
      internalName = "Another Device",
 +
      script = "device.lua"
 +
    },
 +
}
 +
</syntaxhighlight>
 +
 
 +
Now, this obviously seems rather pointless as there are only two devices. But if there were many, many more, it could prove quite useful.
 +
 
 +
To use this in the mission.lua file, we need to utilise Lua's dofile() function.
 +
 
 +
<syntaxhighlight source lang="lua" line start=0>
 +
dofile("mission_devices.lua")
 +
 
 +
mission = {
 +
    -- some tables!
 +
    devices = MyDevices
 +
    -- lots of other tables!
 +
}
  
<syntaxhighlight source lang="lua" line start=83>
 
-- set up networks:
 
Mission.AddNetwork(mission.networks.Semaeopus4G)
 
Mission.ConnectToNetwork(mission.devices.laptop, mission.networks.Semaeopus4G.name, mission.networks.Semaeopus4G.userAccessKey)
 
Doors.SetNetwork(mission.networks.Semaeopus4G)
 
Doors.SetZoneKeys("admin", {"adminkey"})
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
You will have already created a network in your networks table. Now to add it into the game you need to just add in <code>Mission.AddNetwork(mission.networks.NETWORKNAME)</code> where <code>NETWORKNAME</code> is the title of the table you created in your networks table, in this tutorial it was defined as <code>Semaeopus4G</code>. To then connect your hackable devices to your network you just need to add in what you see on line 86, where <code>laptop</code> can be replaced with the hackable devices table name, though in our example the table of our hackable device is called <code>laptop</code>, so we'll keep it as it is.
+
The key bit here is that the devices table is now defined to be the table we wrote in mission_devices.lua. If we took this to its natural conclusion, we end up with the following:
  
Once you have [[Setting up Doors|created doors]] you can then also add them to a network. By adding them to a network you can make it so that the player cannot open the door unless they have a key. Firstly you add the door to a network, then you can set the door's zone to require a specific key to open it. In our example where we set the zone's required key: <code>Doors.SetZoneKeys("admin", {"adminkey"})</code>, <code>"admin"</code> is the name of the zone the door is assigned to, and <code>"adminkey"</code> is the name of the key that will allow the player to unlock the doors of the zone <code>"admin"</code>.
+
<syntaxhighlight source lang="lua" line start=0>
 +
dofile("mission_characters.lua")
 +
dofile("mission_devices.lua")
 +
dofile("mission_data.lua")
 +
dofile("mission_items.lua")
 +
dofile("mission_networks.lua")
  
==== Adding in Other Changes====
+
mission = {
 +
    character = MyCharacters,
 +
    devices = MyDevices,
 +
    data = MyData,
 +
    items = MyItems,
 +
    networks = MyNetworks,
 +
    objectives = {
 +
        -- objectives here...
 +
    },
 +
}
  
<syntaxhighlight source lang="lua" line start=89>
 
-- misc.
 
-- Player.SetInvisible(true)
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Whilst modding you might get to a point in development where you need to test features like the patrol routes of guards, so a useful line of code you can add to your MissionSetup() is <code>Player.SetInvisible(true)</code>, this will make your player invisible to the Guards which can certainly be handy for testing your level without being spotted!
+
We hope this proves useful in organising and managing your creations!
 +
 
 +
[[Category:Modding]]

Latest revision as of 14:21, 3 August 2020

Intro

This article will describe everything you need to know in order to understand how to write and modify missions scripts in Off Grid! Mission scripts can get a little bit large but don't let that overwhelm you, they're deceptively simple, we promise!

Pre-requisites

All modding scripts for Off Grid are written in Lua, it's a lovely little language which is simple ( don't tell it I said that ) and extensible thanks to the marvellous MoonSharp.

Lua is quite easy to pick up, even people with no programming experience should be able to get hacking in next to no time!

You'll need the following tools for editing and writing new mission scripts:

IDE

Firstly you'll need an IDE, we recommend the Atom with the following extensions:

language-lua Syntax highlighting for Lua
autocomplete-lua Auto-complete support for Lua, we recommend enabling the "Override lower priority providers" in the plugin settings

You might also want to get our Atom Snippets code and copy&paste it into Atom's Snippets file to add nice autocompletion for Off Grid API.

LevelKit & Game

Both of these are needed in order to run and test and scripts you create

TO DO

The Mission Script Structure

As mentioned above, mission scripts can get rather large so this article will attempt to break down all the little sub elements, it can be helpful to try and think of the mission script in the same way you might think of a film script. The vast majority of the script is simply defining what is in the world of the mission you've created. Enough talking, lets jump into an example! Rather than posting an entire example script, we'll go from top to bottom exploring what each element does.

Mission table

Initial Mission State

name = "MyNewMission",
	description = "The best mod ever",
	startTime = "27/04/2009/21:30",

In this first code block we create the mission Lua table and define its first value.

Name Description
startTime The start time of the mission in the game world (Day/Month/Year/Hour:Mins)

startTime doesn't have to (and probaly shouldn't) be the current date, you can set your mission in any time you desire.

State

State includes anything you'd like to be saved & loaded. So for instance, you might want to set a bool on the first time a player enters a building, or performs an action, or completes a particular objective. Currently we only support numbers, bools and strings, and these need to have string keys (rather than indices).

state = {
		hasEnteredBuilding = true,
		sideObjectivesComplete = 17,
		mostAnnoyedGuard = "Brian",
	},

Characters

-- Character definitions:
	characters = {
		player = {
			displayName = "Joe Harman",
			characterType = "player",
			prefab = "player",
			spawnpoint = "PlayerSpawn",
		},
	},

This is the first example of us creating a sub table in the mission table, the characters table contains all the information about the characters in your mission! Different types of characters might need different values, and there are some optional settings as well for different purposes. Different character types and creating background profiles for the characters is explained in more detail in Character Profiles.

In this example we've added Joe, the protagonist of Off Grid. Here's a break down of what the value of Joe's table means:

Character Table
Name Description
displayName The name that will be used any game UI referencing the character
characterType The 'type' of the character, see Character Types and Prefabs for more information on possible values
'prefab' The prefab for the character, see Character Types and Prefabs for more information on possible values
spawnpoint The name of the spawn mission object, the position of this object will be where the character is spawned

The characters table can hold information on as many characters as you'd like to fill the mission with, they won't be spawned into the mission until you decide (more on that later)

Physical Items

-- Inventory items:
	items = {
		usbkey = {
			displayName = "USB dongle",
			description = "Modified USB Bluetooth dongle given to you by the hackers",
			uiSpriteName = "usbdongle.png",
                        onItemUse = function()
                            // Do stuff.
                        end,
		},
	},

The items table contains information about physical items that the player will be able to add to their inventory over the course of the mission, in this example we're marking up a USB dongle that the player can plug into computers

Item Table
Name Description
displayName The display name of the item, this is what will be displayed in the games UI
description The description of the item, this will be displayed to users in the players inventory UI
uiSpriteName The path to the image file to be used for the item in game menus.
onItemUse Optional. Function that's ran when the player selects to use the item in inventory menu.

Data

--[[ Data files:
Available data types: generic, text, SMS, encrypted, audio, video, location, key, UUID
]]--
	data = {
		PlayerPGPKey = {
			name = "Personal PGP encryption key",
			immutable = true,
			dataType = DataType.Key,
			creatorName = "Player",
			dataString = "PGP Fingerprint: 1d7d ef54 7a63 5756 63a7 cf14 fbd8 775c c39d 4e51",
			description = "AES 256-bit",
			dataColor = {0.0, 0.6, 1.0, 0.3},
		},
		
	},

The data table contains data that's specific to your mission, in this example we're defining the players PGP encryption key

Data Table
Name Description
name The display name of the data, this is what will be displayed in the games UI
immutable Optional. Is the data immutable? If true the player won't be able to delete it, this is useful for data items that are part of mission objectives
dataType The type of the data, this is used for displaying the data correctly. Available DataTypes are listed in Constants page.
creatorName The name of the data's creator (displayed in the UI)
dataString The contents of the data file
description A description of the data file
dataColor RGBA value that's used as the colour of the data point when visible in the players data view
script The path (relative to the mission folder) to the optional Lua script for this data. See Data Scripting for more information.

Networks

-- Networks:
	networks = {
		semaeopus4G = {
			name = "Semaeopus 4G",
			networkType = NetworkType.mobile,
			allowPlayerDisconnect = false,
		},
	},

The networks table contains all the information about the possible networks in your game, networks can be of different types such as mobile networks, WiFi, and mesh

Network Table
Name Description
name The name of the network (Must be unique!)
networkType The type of the network.
allowPlayerDisconnect Is the player allowed to disconnect from the network?

Data sent at higher access levels across a network won't be visible to devices that are connected at a lower access level.

Devices

devices = {
		laptop = {
			owner = "ownerInternalName",
			dataColor = Color.Orange,
			script = "Scripts/Devices/laptop.lua",
		},
	},
Device Table
Name Description
dataColor The color used when displaying data from this device.
owner Optional. The internalName of the device's owner. Use this if you want too restrict device's access based on owner's metadata or something.
script The script that defines the GUI and the behaviour of the device ( See device scripts for more information )

Objectives

-- Mission objectives:
	objectives = {
		enterTheBuilding = {
			name = "Enter the building",
			description = "Short description of the objective",
			active = false,
			visible = true,
			keyObjective = false,
			
			onStart = function()
				print("Player must now enter the building")
			end,
			onCompleted = function()
				print("The player is now in the building!")
			end,
		},
	},

The objectives table contains all the tasks that the player should complete before your mission is finished.

Objective Table
Name Description
name The name of the objective, displayed to the player.
description Short description displayed to the player in the Tasks menu.
active Is the objective active at the moment (as opposed to already completed, or not even started yet)? (Optional. This is handled automatically when you call Mission.StartObjective() and Mission.CompleteObjective())
visible Should this objective be displayed to player? Hidden objectives can help organizing your mission scripts. (Optional, defaults to visible)
keyObjective Key objectives are ones the player knows from the start of the level, they are visible even before the actual objective becomes active. (Optional)
onStart A callback function that's triggered when the objective is started. (Optional)
onCompleted A callback function that's triggered when the objective is completed (Optional)

Starting the mission

In addition to the big mission table, you'll need two more things to get a mission up and running; MissionSetup() and MissionStart() functions.

Mission Setup

SetupMission() is executed every time the mission is started, be it a fresh start, or when loading a saved game.

function SetupMission()

	-- add player and guards:
	Mission.SpawnCharacter("player")
	-- these two below add in the guards which you might not have created yet!
	Mission.SpawnCharacter("jack")
	Mission.SpawnCharacter("john")

	-- Doors:
	Doors.SetNetwork("ApostleWiFi")

	Doors.SetZoneKeys("Maintenance", {"Maintenance", "Staff", "Security", "Admin"})
	Doors.SetZoneKeys("Staff", {"Staff", "Security", "Admin"})
	Doors.SetZoneKeys("Security", {"Security", "Admin"})
	Doors.SetZoneKeys("Admin", {"Admin"})

	Mission.MissionStarted()
	end

This will add in all of your created elements into your level, below you will see what the function of each part of this code will do.

Adding in Characters
-- add player and guards:
	Mission.SpawnCharacter("player")
	-- these two below add in the guards which you might not have created yet!
	Mission.SpawnCharacter("jack")
	Mission.SpawnCharacter("john")

This part of the code shows the format of spawning in characters - this includes the player and any NPCs like the guards, which you will have created further up in your Mission Script. If the title of your character table for your player was called jenson, you would simply change Mission.SpawnCharacter("player") to Mission.SpawnCharacter("jenson"), this would now let your new character spawn in.


Connect Doors to the Network and set Zones

Once you have created doors you can then also add them to a network. Network-connected doors arre controlled by the electronic lock systems in the buildings and opened by specific access keys. Firstly you add the door to a network, then you can set the door's zone to require a specific key to open it. For example where if you consider the following command to set keys: Doors.SetZoneKeys("Offices", {"admins", "janitors"}), "Offices" is the name of the zone the door is assigned to, and {"admins", "janitors"} telsl that all doors belonging to this zone should be possible to open using either "admin" or "janitors" keys.

Mission Start

StartMission() is ran when the mission is started form the beginning. When loading a save or checkpoint, the same things would be covered by data loaded from the save instead. Use this to set initial state of things in your level.

function StartMission()

	-- Add items to player inventory:
	Player.ClearInventory()
	Player.AddItemToInventory("usbkey")
	Player.AddItemToInventory("meshnode")

	--  Add files to data inventory:
	Player.ClearDataFiles()
	Player.AddDataFile("PlayerPGPKey", mission.data.PlayerPGPKey)
	Player.AddDataFile("CoffeeOffer", mission.data.CoffeeOffer)

	--mobile
	Network.ConnectToNetwork(
		{
			"player",
			"DevLaptop",
			"Smedley",
			"Terrence",
			"securityChief",
			"apostleItIntern",
			"guard1","guard2","guardF1a","guardF1b","guardF1c","guard4","guard5",
			"drone1",
			"davesEPhone",
			"BasementSmartTV",
			"BasementLaptop",
			"iDeskLampTEST",
			"testRadio",
			"SmartFridge",
			"sodamachine",
		},
		"Semaeopus4G",
		"user"
	)

	-- WIFI
	Network.ConnectToNetwork("player", "ApostleWiFi", "none" )
	Network.ConnectToNetwork(
		{
			"guard1","guard2","guardF1a","guardF1b","guardF1c","guard4","guard5",
		},
		"ApostleWiFi",
		"user"
	)

	-- MESH
	Network.ConnectToNetwork(	{"Smedley","MeshleaksSecureDrop"},	"MESH", "admin"	)

	Mission.DevicesConnected()

	-- Doors
	Doors.SetKeyOnDevice("Maintenance", "IDcard01")
	Doors.SetKeyOnDevice("Admin", "IDcard02")

	Doors.AssignKeyToCharacter("Security", "guard1")
	Doors.AssignKeyToCharacter("Security", "guard2")


	-- Set up music (we'll set it to silent in start, and set gameplay state from a trigger when entering lobby)
	Sound.TriggerEvent("Set_MX_Mission_1")
	Sound.TriggerEvent("Set_State_MX_Mission_Silent")

	---[[ Things to enable when testing & debugging:
	Apps.RequestAppState("DevTools", AppState.off)
	--]]

end

Including other files

We know how long these mission scripts can become. As a result, we came up with a method of splitting up your mission into smaller, bite-size pieces. You're more than welcome to keep your mission in a single file, but if you want to manage them differently, read on...

Let's say you want to keep all your device related shenanigans in a separate file for clarity. We'll call this 'mission_devices.lua', and put it in the same folder as the mission.

MyDevices = {
    somedevice = {
      internalName = "Device Z",
      script = "device.lua"
    },
    someotherdevice = {
      internalName = "Another Device",
      script = "device.lua"
    },
}

Now, this obviously seems rather pointless as there are only two devices. But if there were many, many more, it could prove quite useful.

To use this in the mission.lua file, we need to utilise Lua's dofile() function.

dofile("mission_devices.lua")

mission = {
    -- some tables!
    devices = MyDevices
    -- lots of other tables!
}

The key bit here is that the devices table is now defined to be the table we wrote in mission_devices.lua. If we took this to its natural conclusion, we end up with the following:

dofile("mission_characters.lua")
dofile("mission_devices.lua")
dofile("mission_data.lua")
dofile("mission_items.lua")
dofile("mission_networks.lua")

mission = {
    character = MyCharacters,
    devices = MyDevices,
    data = MyData,
    items = MyItems,
    networks = MyNetworks,
    objectives = {
        -- objectives here...
    },
}

We hope this proves useful in organising and managing your creations!