Map scripting - UberWaffe/OpenRA GitHub Wiki
The map scripting feature of OpenRA allows you to create custom shellmaps, minigames, single player/co-op missions and more. See New-Lua-API.
The Lua scripting language is used in OpenRA for scripting maps. There is no need to download Lua from the website; a custom build of Lua is included with recent versions of the game.
You can use any programmers text editor or Lua supporting IDE (integrated development environment). A convenient way is to also use MonoDevelop for both the C# editing and Lua scripting. Go to Tools - Add-In-Manager and install the MonoDevelop Lua bindings.
You will then get syntax high-lighting and basic code completion for the .lua files referenced in the OpenRA.sln main project file.
This guide assumes you have knowledge of creating and editing OpenRA maps (See Mapping).
Lua script code is to be contained in one or more .lua
files within the map archive, next to map.yaml
and map.bin
. All script files for a particular map must be within the map's archive.
To actually have this Lua code executed, you must add a trait to the World actor in map.yaml
like so:
World:
LuaScriptInterface:
LuaScripts: <filename>, ...
LuaScripts
is a list of Lua script filenames. These script files will be loaded when the map is loaded.
In one of the script files you may have a function named WorldLoaded
. This function will be called when the game world is loaded. You can place script initialization code that depends on the game state inside this function.
You may also have a function named Tick
, called 25 times per second during gameplay if you have implemented it. Try to not have long-running code in this function, because it will hold up the game.
The following code will display "Hello world" in the game once per second:
tick = 0
Tick = function()
tick = tick + 1
if tick % 25 == 0 then
print("Hello world")
end
end
You can now go ahead and implement your scripts. You should make use of the standard library in this process, which is explained below.
OpenRA comes with a suite of standard Lua files that are usable from all scripted maps. It is strongly recommended to use these functions over rolling your own, or your mission may stop working in later OpenRA versions if there are breaking changes.
The standard library is located in mods/common/lua. A short description of each file follows:
- actor.lua - For creating and manipulating game actors.
- map.lua - For working with maps.
- media.lua - For media playback.
- mission.lua - Mission-specific functionality.
- openra.lua - General game engine functionality.
- reinforcements.lua - For working with reinforcements.
- rules.lua - Game rule lookups.
- supportpowers.lua - For working with support powers.
- team.lua - Utilities for collectively manipulating teams of actors.
- utils.lua - Miscellaneous Lua utilities.
TODO: Talk about the functions/tables themselves
You can easily interact with placed actors in your map by giving them a name in map.yaml
and then referring to them using their names. For example:
map.yaml
:
FootSoldier: e1
Location: 49,59
Owner: Soviets
...
World:
LuaScriptInterface:
LuaScripts: map.lua
map.lua
:
WorldLoaded = function()
Actor.Scatter(FootSoldier)
end
This will make the map actor named FootSoldier
scatter to an adjacent cell immediately after the map is loaded.
It is also possible to attach triggers from the actors.lua
standard library. Add the following code to mission.lua
to have the player lose the mission once his special soldier is dead:
MissionFailed = function()
Mission.MissionOver(nil, { player }, false)
Media.PlayMovieFullscreen("gameover.vqa")
end
...
WorldLoaded = function()
Actor.OnKilled(FootSoldier, MissionFailed)
end
The simplest one that is similar to the ConquestVictoryConditions
in multiplayer is checking if all actors with the MustBeDestroyed
trait (default is all buildings) have been eliminated. If you want all infantry plus all structures gone to trigger a win or loss, add this to your map.yaml
:
^Infantry:
MustBeDestroyed:
and add the following checks to mission.lua
:
WorldLoaded = function()
player = OpenRA.GetPlayer("GoodGuy")
enemy = OpenRA.GetPlayer("BadGuy")
end
Tick = function()
if Mission.RequiredUnitsAreDestroyed(player) then
MissionFailed()
end
if Mission.RequiredUnitsAreDestroyed(enemy) then
MissionAccomplished()
end
end
You can also add this trait to a self-defined actor you placed somewhere in a map for assassination missions:
GNRL:
Tooltip:
Name: General Archenemy
MustBeDestroyed:
You can use pre-defined functions from the reinforcements.lua standard library which are controlled by waypoint locations set in map.yaml
:
Actors:
ReinforcementsEntryPoint: waypoint
Location: 90,44
Owner: Neutral
McvDeployPoint: waypoint
Location: 89,51
Owner: Neutral
with the following code in mission.lua
to send a mobile construction vehicle:
SendMcvReinforcements = function()
Media.PlaySpeechNotification("ReinforcementsArrived")
local mcv = Actor.Create("mcv", { Owner = player, Location = ReinforcementsEntryPoint.Location })
Actor.Move(mcv, McvDeployPoint.Location)
Actor.DeployTransform(mcv)
end
If you want an action like the above reinforcements to get executed after a set time played, try the following code to send them after 30 seconds.
WorldLoaded = function()
OpenRA.RunAfterDelay(25 * 30, SendMcvReinforcements)
end
Define a standalone support power in map.yaml
, e.g.:
PowerProxy.AirSupport:
AirstrikePower:
Icon: airstrike
ChargeTime: 30
SquadSize: 3
QuantizedFacings: 8
Description: Air Strike
LongDesc: Deploy an aerial napalm strike.
EndChargeSound: airredy1.aud
SelectTargetSound: select1.aud
IncomingSound: enemya.aud
UnitType: a10
Then by creating the actor and setting the owner in mission.lua
Actor.Create("PowerProxy.AirSupport", { Owner = player })
the player will get access to it. You can wrap this into a triggered function to make it conditional.