Skip to content
BlockFrontBlockFront DocsOfficial Documentation for BlockFront

Scripting

Missions are written in Lua, using threads, waits and events.

mission.lua runs when the mission loads. It sets things up and registers sections, then stops. The mission itself runs inside the sections.

mission.api(1)
mission.section("landing", { start = "spawn" }, function()
objective.add("regroup", "Find your squad", { waypoint = "shed" })
trigger.wait("shed_area")
objective.complete("regroup")
mission.gotoSection("barn")
end)
mission.section("barn", { start = "shed" }, function()
sayAndWait("spencer", "Good to see you.")
mission.complete()
end)

Every mission.lua must call mission.api(1) at the top and register at least one section.

A section is one stretch of the mission, and the first one registered runs first. opts.start names the marker the squad begins it at.

Sections also make testing quick, because you can jump straight to any of them. See Debugging.

A thread runs alongside the rest of the script, and waiting in one doesn’t hold up anything else.

thread(function()
while true do
wait(30)
say("spencer", "Keep moving.")
end
end)

delay(seconds, fn) runs a function later on its own thread.

notify raises an event and waittill waits for one. An event has a key and a name.

thread(function()
waittill("farm", "alarm")
music.set("combat")
end)
-- elsewhere
notify("farm", "alarm")

The game raises events too:

Key Event Raised when
actor:<id> interact A player interacts with the actor
actor:<id> death The actor dies
stealth detected An enemy spots the squad
zone left, returned A player crosses the mission area’s edge
document collected A player picks up a document
wave:<name> spawned A man in a wave spawns
sequence:<name> finished A sequence finishes
charge:<id> planted, detonated A charge is planted or goes off

endon(key, event) ends the calling thread when an event fires. runUntil(key, event, fn) runs a function that an event can cut short, such as dialogue interrupted by an alarm:

if not level.alarm and runUntil("farm", "alarm", briefing) then
objective.complete("listen")
end

runUntil only sees events that fire after it starts, so check the state first, as above.

A flag is a value that stays set, so a thread that checks late doesn’t miss it.

flag.set("bridge_blown")
flag.wait("bridge_blown")

Keep state that must last across sections in a global table named level:

level = {
farmAlerted = false,
}

The top of the script runs again on a restart. Plain data in level is saved at each checkpoint and put back when the player resumes, but health, ammo and picked-up items are not saved. Hand out kit again at the top of each section.

include runs another Lua file from the mission folder. Its locals stay private, so share through globals or its return value.

local village = include("village.lua")

Text can be a plain string or a translation:

say("spencer", tr("my_campaign.spencer.hello", character("hollis")))

tr takes a key from your pack’s lang files, and further arguments fill %1$s, %2$s in the line. character(id) passes a character’s display name.

Scripts run in a sandbox. There’s no file or system access, and load and loadstring are removed. A thread that runs too long without waiting is stopped, so give long loops a wait.