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.

IDE support

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.

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.

MonoDevelop OpenRA Lua

Setting up Lua for your map

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.

Standard library

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

Map actor interaction

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

Victory conditions

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:

Reinforcements

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

Timers

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

Grant a superweapon

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.

Examples

⚠️ **GitHub.com Fallback** ⚠️