Overview
This page shows how to make new missions in Powerplay using LUA. The whole campaign and the skirmish behavior is made using this same system that is available to you as a player.
Getting Started
Every script is a plain .lua text file. The engine injects different global objects and calls different lifecycle
functions on it — see Structure below for the
full breakdown.
To attach a script to a mission, in the map editor:
Click the script icon in the map editor's toolbar, then Create New File (or Browse File to link an existing .lua file).
The panel now shows the linked file. Edit it in your favorite IDE. The engine picks up the changes when you press play in the editor. Press save to persist the changes into the map.
A minimal script that adds one objective:
function start()
game:show_map_pointer({ x = 40, y = 65 }, "BUILD A MINE HERE")
game:add_objective({
title = "BUILD A MINE",
condition = function()
return count(game:query_buildings({ type = "mine", player = 1 })) > 0
end,
on_completed = function()
game:hide_pointer()
game:set_mission_complete()
end,
})
end Structure
There is one top-level mission script per map. Drives objectives, dialog, scripted events, and overall mission flow.
Flow 2
start()
Runs once at the start. Typical place to define mission objectives.
update()
Runs once per second. You should avoid it and try to use the objective system for most things.
State 2
game:set_value(key, value)
Stores a string, number, boolean, or table value under key, persisted for the duration of the mission and across save games.
-
keystring -
valuestring | number | boolean | table
game:get_value(key, default_value)
Reads a value previously stored with set_value.
-
keystring -
default_valueany — optional, returned when key has never been set
Returns: value or default_value
Objective System
The Objective System is the intended way to write missions: a declarative system,
continuously run by the engine every second, instead of procedural code and loops. Every
field in an objective is optional except condition, which represents
completion. Once an objective is completed, it is removed. An objective with no
title is never shown in the UI, which is a common trick for using the
objective system purely to sequence dialog or watch a fail-condition in the background
(see mission1.lua for several examples of this).
Objectives are typically added from the script's top-level start() function.
function start()
game:add_objective({
-- Shown in the objectives UI. Omit title to make a "silent" objective —
-- useful for gating dialog or watching a fail-condition without cluttering the UI.
title = "BUILD MINE",
-- Only becomes active once this returns true (checked every tick until it does).
-- Omit to activate immediately.
prerequisite = function() return game:get_value("intro_completed") end,
-- Runs once, the tick the objective becomes active.
on_activated = function()
game:show_map_pointer(mine_pos, "BUILD MINE")
end,
-- Checked every tick once active. Objective completes the first time this is true.
condition = function()
return count(game:query_buildings({ type = "mine", player = 1 })) > 0
end,
-- Optional: live status text shown next to the objective, e.g. "3/5".
status = function() return tostring(some_progress) .. "/5" end,
-- Runs once, the tick condition() first returns true.
on_completed = function()
game:hide_pointer()
end,
-- Optional alternate failure path, checked alongside condition().
failure_condition = function() return player_has_no_base() end,
on_failed = function() game:set_mission_failed() end,
-- Optional: played automatically when the objective activates.
dialog = {
{ character = "faction01_lieutenant", message = "Let's get that mine built." },
},
})
end Game API
This is the API available through the game keyword. It
covers spawning, querying units/buildings, objectives, dialog, UI, camera control, and
mission state.
Every function here runs synchronously, except game:delayed_call, which
schedules a function to run later without blocking.
Units 5
game:spawn_unit(props)
Spawns a unit and returns it.
-
propstable{ type, pos = { x, y }, player = 1, -- optional, defaults to the script's player tag, -- optional string }typeis a Unit Types
Returns: unit
local trooper = game:spawn_unit({ type = "trooper", pos = { x = 40, y = 65 }, player = 2, tag = "wave_1" }) game:get_unit(unit_id)
game:query_units(filter)
Returns every unit matching filter.
-
filtertable{ player = 1, -- optional, defaults to script's player global = true, -- optional, search all players tag = nil, -- optional type = 'trooper', -- optional is_selected = false, -- optional is_moving = false, -- optional }typeis a Unit Types
Returns: array of units
local troopers = game:query_units({ type = "trooper", player = 1 })
print(count(troopers) .. " troopers") game:move_units(units, coords)
Moves a group of units to coords, automatically spacing them out so they don't stack.
-
unitsarray of units -
coords{x, y}
game:move_units_along_path(units, path, move_type)
Moves a group of units along a sequence of waypoints, keeping formation spacing.
-
unitsarray of units -
patharray of {x, y} -
move_typestring — "move" (default) or "attack_move"
Buildings 3
game:spawn_building(props)
Spawns a building and returns it.
-
propstable{ type, pos = { x, y }, player = 1, -- optional tag, -- optional string }typeis a Building Types
Returns: building
game:get_building(building_id)
game:query_buildings(filter)
Returns every building matching filter.
-
filtertable{ player = 1, -- optional global = false, tag = nil, type = nil, output = nil, -- filter by current production output }typeis a Building Types
Returns: array of buildings
World & Map 11
game:get_time()
Seconds elapsed in the current mission.
Returns: number
game:get_real_time()
Seconds elapsed in real time (not affected by game speed).
Returns: number
game:get_cell_type(coords)
Type of whatever occupies a map cell (a building or resource type string), or nil if empty.
-
coords{x, y}
Returns: string or nil
game:world_to_map(world_pos)
Converts a 3D world position into map cell coordinates.
-
world_pos{x, y, z}
Returns: {x, y}
game:map_to_world(map_coords)
Converts map cell coordinates into a 3D world position.
-
map_coords{x, y}
Returns: {x, y, z}
game:get_sector_width()
Width (and height) of a sector, in cells. The map is divided into a grid of sectors for spatial queries like scan_map.
Returns: number
game:get_sector_coords(map_coords)
Sector coordinates containing the given map cell.
-
map_coords{x, y}
Returns: {x, y}
game:get_map_resolution()
Number of sectors per dimension (e.g. 8 for an 8x8 grid of sectors).
Returns: number
game:is_sector_valid(sector_coords)
Whether a sector is within the map bounds.
-
sector_coords{x, y}
Returns: boolean
game:scan_map(origin, filter)
Spirals outward sector by sector from origin, calling filter(sector_coords) until it returns true. Returns the matching sector, or nil if none match.
-
origin{x, y} — sector coords to start from -
filterfunction(sector_coords) -> boolean
Returns: {x, y} or nil
local nearest = game:scan_map(origin, function(sector_coords)
if not game:is_sector_valid(sector_coords) then return false end
return not state.explored[sector_key(sector_coords)]
end) game:is_map_explored(coords)
Whether the fog of war has been lifted at the given map cell.
-
coords{x, y}
Returns: boolean
Economy 4
game:get_costs(item_type)
The resource cost to produce a unit or building type.
Returns: array of { resource, amount }
game:add_resource(item_type, amount)
Adds resources to the current script's player inventory.
-
item_typestring -
amountnumber
game:get_resource(item_type)
Current amount of a resource in the current player's inventory.
-
item_typestring
Returns: number
game:remove_resource(item_type, amount)
Removes resources from the current script's player inventory.
-
item_typestring -
amountnumber
Players & Factions 6
game:set_player_team(player, team)
Assigns a player to a team.
-
playernumber — 1-based player index -
teamnumber — 1-based team index
game:set_player_color(player, color)
Recolors a player's units and buildings, and adopts the team of any other player already using that color (so same-colored players don't fight each other).
-
playernumber — 1-based -
colornumber — 1-based
game:set_available_units(units)
Sets the full list of unit types the player is allowed to build.
game:add_available_unit(unit_type)
Unlocks a single additional buildable unit type.
game:set_available_buildings(buildings)
Sets the full list of building types the player is allowed to build.
game:add_available_building(building_type)
Unlocks a single additional buildable building type.
Objectives 5
game:add_objective(objective)
Registers a single objective. See the Objectives section below for worked examples.
-
objectivetable{ title = nil, -- optional, omit for a silent objective prerequisite = function() end, -- optional, checked every tick until true — omit to activate immediately on_activated = function() end, -- optional, runs once when the objective activates condition = function() end, -- required, objective completes the first time this returns true status = function() end, -- optional, live status text shown next to the objective on_completed = function() end, -- optional, runs once the tick condition first passes failure_condition = function() end, -- optional, checked alongside condition for an alternate fail path on_failed = function() end, -- optional, runs once the tick failure_condition first passes dialog = nil, -- optional, array of dialog line tables — played automatically on activation, see Dialog lines below }
game:add_objectives(objectives)
Registers a list of objectives at once (calls add_objective for each).
-
objectivesarray of objective tables
game:are_objectives_completed()
Whether every currently active objective has been completed.
Returns: boolean
game:set_mission_complete()
Marks the campaign mission as won.
game:set_mission_failed()
Marks the campaign mission as lost.
Fog of War 1
game:fog_reveal(props)
Temporarily (or permanently) reveals an area of the map, independent of any unit's vision.
-
propstable{ pos = { x, y }, radius, duration = 0, -- 0 or omitted = permanent }
game:fog_reveal({ pos = enemy_base:get_pos(), radius = 10, duration = 5 }) Camera 5
game:set_camera_zoom(zoom, duration)
Sets the camera zoom level, optionally animated.
-
zoomnumber -
durationnumber — seconds, default 0 (instant)
game:set_camera_pos(coords, immediate)
Moves the camera to look at a map position.
-
coords{x, y} -
immediateboolean — skip the pan animation, default false
game:camera_follow_unit(unit_id)
Locks the camera to follow a unit. Pass nil (or an empty string) to stop following.
-
unit_idstring or nil
game:enable_camera_pan(enabled)
Enables/disables player camera panning.
-
enabledboolean
game:enable_camera_zoom(enabled)
Enables/disables player camera zoom.
-
enabledboolean
UI 16
game:show_map_pointer(coords, data)
Shows an on-screen pointer/arrow toward a map location, guiding the player's attention.
-
coords{x, y} -
datastring or table — a label string, or { label, icon = (optional) }
game:show_map_pointer(iron_mine_pos, "BUILD MINE") game:show_map_pointers(data)
Shows several map pointers at once.
-
dataarray of { pos = {x, y}, label, icon = (optional) }
game:hide_pointer(index)
Hides a single map/UI pointer.
-
indexnumber — default 0
game:hide_pointers()
Hides every active pointer.
game:set_ui_highlight(ui_id, highlighted)
Highlights (or un-highlights) a UI element, e.g. a build button, to draw attention to it.
-
ui_idstring -
highlightedboolean
game:show_ui_pointer(ui_id, label, icon)
Shows a pointer aimed at a UI element instead of a map location.
-
ui_idstring -
labelstring -
iconstring — optional
game:set_building_panel_section(section)
Switches the build menu to a category tab, e.g. "industry" or "military".
-
sectionstring
game:trigger_ui_click(ui_id)
Programmatically triggers a click on a UI element.
-
ui_idstring
game:enable_ui_section(ui_id, enabled)
Shows/hides a UI section.
-
ui_idstring -
enabledboolean
game:enable_ui_crafting(item_type, enabled)
Enables/disables crafting UI for an item type.
-
item_typestring -
enabledboolean
game:enable_ui_output(item_type, enabled)
Enables/disables the output selector UI for an item type.
-
item_typestring -
enabledboolean
game:enable_building_panel(enabled)
Shows/hides the entire building panel.
-
enabledboolean
game:show_error_message(message)
Shows a global on-screen error/toast message.
-
messagestring
game:show_ghost_building(building_type, coords)
Shows a translucent preview of a building at a map location (no gameplay effect, purely visual).
-
coords{x, y}
game:hide_ghost_building(coords)
Hides a single ghost building preview.
-
coords{x, y}
game:hide_ghost_buildings()
Hides every ghost building preview.
Input & Selection 10
game:enable_input(enabled)
Enables/disables all player input to the map (used to lock the player during cutscenes).
-
enabledboolean
game:enable_multi_selection(enabled)
Enables/disables box/drag multi-selection.
-
enabledboolean
game:enable_deselection(allow)
Enables/disables the ability to deselect the current selection.
-
allowboolean
game:get_selection_type()
What kind of thing is currently selected.
Returns: string: "none" | "unit" | "building"
game:set_right_click_filter(filter)
Restricts what the player is allowed to right-click on. Pass nil to clear the filter.
-
filterfunction(target_type) -> boolean, or nil
game:set_selection_filter(filter)
Restricts what the player is allowed to select. Pass nil to clear the filter.
-
filterfunction(target_type) -> boolean, or nil
game:deselect()
Clears the current selection.
game:set_build_filter(filter)
Restricts where buildings can be placed. Pass nil to clear the filter.
-
filterfunction(coords) -> boolean, or nil
game:set_required_building_connection(building_type, connection_type)
Forces a specific connection type to be required when placing a building type (used to steer tutorial-style connection prompts).
-
connection_typestring
game:clear_required_building_connection()
Clears any forced connection requirement set by set_required_building_connection.
Dialog & Events 3
game:radio_dialog(dialog)
Plays a sequence of radio dialog lines. See the Objectives & Dialog section for the dialog line schema.
-
dialogarray of dialog line tables
game:hide_radio()
Hides the radio dialog UI immediately.
game:delayed_call(seconds, fn)
Calls fn() once, after a delay. Unlike wait(), this works from anywhere (not just inside start()'s coroutine).
-
secondsnumber -
fnfunction
State Storage 2
game:set_value(key, value)
Stores a string, number, boolean, or table value under key, persisted for the duration of the mission and across save games.
-
keystring -
valuestring | number | boolean | table
game:get_value(key, default_value)
Reads a value previously stored with set_value.
-
keystring -
default_valueany — optional, returned when key has never been set
Returns: value or default_value
Units API
Methods available on any unit object — returned by game:spawn_unit(),
game:get_unit(), game:query_units().
State 17
unit:get_id()
The unit's unique id.
Returns: string
unit:get_type()
The unit's type, e.g. "trooper".
Returns: unit type
unit:is_player()
Whether this unit belongs to the human player (Player 1).
Returns: boolean
unit:is_moving()
Whether the unit currently has a move order.
Returns: boolean
unit:is_selected()
Whether the unit is currently selected.
Returns: boolean
unit:is_alive()
Whether the unit is still alive.
Returns: boolean
unit:can_attack()
Whether this unit type is capable of attacking.
Returns: boolean
unit:is_attacking()
Whether the unit is currently engaged in an attack.
Returns: boolean
unit:get_spawn_time()
game:get_time() value at the moment this unit was spawned.
Returns: number
unit:get_hit_points()
Current hit points.
Returns: number
unit:get_speed()
Current max movement speed.
Returns: number
unit:get_pos()
Current map cell coordinates.
Returns: {x, y}
unit:get_dest()
Current pathfinding destination, in map cell coordinates.
Returns: {x, y}
unit:get_extractor()
Truck only — the resource building it's currently hauling from.
Returns: building or nil
unit:get_processor()
Truck only — the building it's currently delivering to.
Returns: building or nil
unit:has_units()
Chinook only — whether it's currently carrying units.
Returns: boolean
unit:is_ejecting_units()
Chinook only — whether it's currently in the middle of unloading its carried units.
Returns: boolean
Modifiers 6
unit:set_hit_points(hp)
Sets current hit points.
-
hpnumber
unit:enable_collision(enabled)
Enables/disables collision for this unit.
-
enabledboolean
unit:set_speed(speed)
Overrides max movement speed.
-
speednumber
unit:set_vision(enabled)
Enables/disables this unit's ongoing vision radius.
-
enabledboolean
unit:set_invincible(enabled)
Makes the unit immune to damage.
-
enabledboolean
unit:set_units(units)
Chinook only — loads a manifest of unit types to transport.
-
unitsarray of { type }
Actions 14
unit:select()
Selects this unit (as if the player clicked it).
unit:move(coords)
Issues a move order to a map position.
-
coords{x, y}
unit:stop()
Clears the unit's current action/order.
unit:move_along_path(path)
Moves along a sequence of waypoints.
-
patharray of {x, y}
unit:attack_move(coords)
Moves toward a map position, engaging any enemies encountered on the way.
-
coords{x, y}
unit:attack_move_along_path(path)
Attack-moves along a sequence of waypoints.
-
patharray of {x, y}
unit:attack(target_id)
Orders the unit to attack a specific unit or building by id.
-
target_idstring
unit:fog_reveal()
Reveals fog of war around this unit for the local player, once.
unit:kill()
Kills the unit (plays death effects).
unit:remove()
Silently removes the unit — no death effects, no on_killed callback noise.
unit:fill_cargo(resource_type)
Truck only — fills its cargo with a resource type.
-
resource_typestring
unit:eject_units()
Chinook only — unloads/drops its carried units at its current position.
unit:unpack()
BEV only — deploys the vehicle into an Exphub building.
Returns: building or nil
unit:explore()
LRV only — starts the vehicle's built-in autonomous exploration behavior.
Buildings API
Methods available on any building object — returned by game:spawn_building(),
game:get_building(), game:query_buildings().
State 22
building:get_id()
The building's unique id.
Returns: string
building:get_type()
The building's type, e.g. "foundry".
Returns: building type
building:is_selected()
Whether the building is currently selected.
Returns: boolean
building:is_active()
Whether the building is currently active/operating.
Returns: boolean
building:is_ready()
Whether the building has finished spawning/emerging and its GameObject is active.
Returns: boolean
building:get_output()
The resource type this building currently outputs (mines/wells/etc).
Returns: string
building:get_spawn_time()
game:get_time() value at the moment this building was spawned.
Returns: number
building:get_hit_points()
Current hit points.
Returns: number
building:is_extractor()
Whether this building type extracts a raw resource (e.g. a mine).
Returns: boolean
building:is_research_building()
Whether this building type is a research lab.
Returns: boolean
building:is_defense_building()
Whether this building type is a defensive structure (e.g. a turret).
Returns: boolean
building:is_unit_building()
Whether this building type trains/produces units.
Returns: boolean
building:is_base()
Whether this building type is a construction base (HQ/Exphub) that other buildings attach to.
Returns: boolean
building:is_starved()
Factory only — whether production is currently blocked due to missing input resources.
Returns: boolean
building:is_depleted()
Resource-extracting building only — whether the resource patch it's built on has run dry.
Returns: boolean
building:is_alive()
Whether the building is still standing.
Returns: boolean
building:is_enemy()
Whether the building is hostile to the local player.
Returns: boolean
building:get_pos()
Current map cell coordinates.
Returns: {x, y}
building:get_buildable_area()
HQ/Exphub only — the placement bounds within which the player can construct new buildings.
Returns: { min_x, min_y, max_x, max_y } or nil
building:get_base()
Finds the nearest owned HQ or Exphub this building is attached to.
Returns: building or nil
building:get_connected_buildings()
Resource-extracting building only — ids of the buildings connected downstream of it (e.g. a mine's connected factories).
Returns: array of building ids
building:get_queued_units()
Unit-producing building only — its current production queue.
Returns: table of { [unit_type] = amount }
Modifiers 4
building:set_hit_points(hp)
Sets current hit points.
-
hpnumber
building:set_can_salvage(can_salvage)
Enables/disables whether the player is allowed to salvage/sell this building.
-
can_salvageboolean
Used heavily in campaign scripts to lock scripted starting buildings so players can't sell their tutorial base.
building:set_disconnected(disconnected)
Forces this building's supply-chain connection state.
-
disconnectedboolean
Returns: boolean
building:set_invincible(enabled)
Makes the building immune to damage.
-
enabledboolean
Actions 6
building:select()
Selects this building.
building:salvage()
Sells/salvages this building immediately, as if the player triggered it.
building:connect(building_id)
Attempts to connect this building to another (e.g. a mine to a factory via conveyor).
-
building_idstring
Returns: boolean (success)
building:queue_unit(unit_type)
Queues a unit for production at this specific building.
Returns: boolean
building:pack()
Exphub only — packs it back into a mobile BEV.
building:upgrade()
Exphub only — upgrades it.
Returns: boolean
Global Callbacks
Callbacks don't share local variables — to pass data between them (or from start()),
store it with game:set_value and read it back with game:get_value.
Global Callbacks 15
on_ui_clicked(ui_id)
Fired when the player clicks a tracked UI element.
on_built(building_id)
Fired when a building finishes construction.
on_building_selected(building_id)
Fired when the player selects a single building.
on_unit_selected(unit_id)
Fired when the player selects a single unit.
on_units_selected(unit_ids)
Fired when the player selects multiple units.
on_unit_killed(unit_id)
Fired when any unit dies.
on_building_destroyed(building, connections)
Fired when any building is destroyed.
-
buildingBuilding -
connections— the list of building ids it was connected to
on_selection_changed(selection_type)
Fired on every selection change, with "none" | "unit" | "building".
on_inventory_item_added(item_type, amount)
Fired when a resource is added to the player's inventory.
on_command(unit_id, command_name, target_type, map_coords)
Fired when the player issues a raw command to a unit.
on_buildings_connected(building_id_1, building_id_2)
Fired when two buildings become connected.
on_connection_start(building_id)
Fired when the player starts dragging a connection from a building.
on_building_revealed(building_id)
Fired when fog of war first reveals a building.
on_building_failed(building_type, reason)
Fired when a building placement attempt fails.
on_resource_revealed(resource_type)
Fired when fog of war first reveals a resource patch.
Glossary
Unit and building type strings used throughout this reference — e.g. the type
field in game:spawn_unit(), game:query_units(), and similar
functions.
Unit Types 22
aircraft_carrier
Aircraft Carrier — Massive capital ship that can carry and refuel aircraft.
apc
Armored Personnel Carrier — Armored infantry carrier with a cannon that engages aircraft and light vehicles.
chopper
Attack Helicopter — Fast attack helicopter effective against infantry and light vehicles.
submarine
Attack Submarine — Stealthy attack submarine that ambushes enemy vessels with long-range torpedoes.
bev
Base Expansion Vehicle — Armored vehicle that deploys into a Base Expansion Hub, extending your construction radius.
truck
Cargo Truck — Heavy military truck for transporting resources across the battlefield.
destroyer
Destroyer — Heavily armored surface warship delivering devastating railgun fire against sea and coastal targets.
engineer
Engineer — Army engineer capable of repairing vehicles, infantry, and structures.
fighter
Fighter Jet — Next-gen stealth fighter jet for air superiority and ground strikes.
joint_strike_ship
Joint Strike Ship — Long-range missile warship that can strike both ground and sea targets from a distance.
patrol_boat
Light Combat Ship — Fast combat ship armed with an autocannon for naval combat and coastal interdiction.
lrv
Light Recon Vehicle — Fast scout vehicle armed with a dual remote-controlled machinegun.
tank
Main Battle Tank — Powerful main battle tank that excels at destroying enemy vehicles.
mlrs
MLRS — Long-range rocket artillery that saturates an area with multiple rockets.
artillery
Mobile Artillery — Long-range artillery vehicle that engages targets from a safe distance.
frigate
Multirole Frigate — Versatile escort frigate armed with anti-air missiles and anti-naval torpedoes.
rocket_trooper
Rocket Trooper — Anti-armor infantry armed with compact homing rocket launchers.
sniper
Sniper — Long-range infantry specialist that picks off enemy personnel from extreme distances.
bomber
Stealth Bomber — Fast stealth bomber that delivers heavy explosive payloads.
strike_drone
Strike Drone — Autonomous UCAV that delivers precision explosive ground strikes.
chinook
Transport Helicopter — Transport helicopter that carries up to 10 soldiers over any terrain.
trooper
Trooper — Standard infantry armed with an advanced assault rifle and next-gen armor.
Building Types 21
airfield
Airfield — Airfield that builds, houses, and rearms up to 6 air units.
armory
Armory — Field armory that refines weaponry and unlocks advanced military upgrades.
exphub
Base Expansion Hub — Lightweight forward construction hub that enables basic building in remote locations.
hq
Construction Center — Permanent construction center that builds and supports advanced structures.
electronics_fab
Electronics Fab — Miniaturized factory that produces microchips and electronics for military hardware.
foundry
Foundry — Smelts raw ore into steel for construction and unit production.
gate
Gate — Reinforced gate that automatically opens for allied units and blocks enemies.
quarters
Infantry Quarters — Combined barracks and training facility for producing infantry units.
logibay
Logibay — Logistics hub where Cargo Trucks dock to load and unload resources.
mine
Mine — Miniaturized mining facility that extracts iron, rare earths, and uranium.
rocket_pod
Missile Turret — Anti-air defense turret that targets all kinds of aircraft.
reactor
Nuclear Enrichment Plant — Advanced plant that enriches uranium for nuclear weapons production.
silo
Nuclear Silo — Subterranean bunker that constructs and launches tactical nuclear ICBMs.
oil_derrick
Oil Derrick — Modular oil extraction unit that pumps crude oil for fuel production.
radar
Radar — Sensor tower that reveals the fog of war and detects cloaked units.
refinery
Refinery — Miniaturized refinery that converts crude oil into vehicle and aircraft fuel.
research_lab
Research Center — AI-powered facility that develops and deploys upgrades and new technologies.
shipyard
Shipyard — Massive coastal facility for building and deploying naval units.
turret
Turret — Automated defense turret that independently targets and engages ground enemies.
assembly
Vehicle Assembly — Advanced facility for producing a wide array of military vehicles.
wall
Wall — Reinforced barrier that blocks enemy movement into your base.
Global Helpers
Injected into every script's global scope.
Global Helpers 16
loop(table, action)
Iterates an array-like table, calling action(element, index) for each entry.
-
tabletable — array-like table -
actionfunction(element, index) — called once per element
Shorthand for ipairs(). Used throughout the campaign scripts instead of writing for loops.
count(table)
Returns the number of elements in an array-like table (#table).
-
tabletable
Returns: number
to_string(value)
Like tostring(), but returns the string "nil" instead of erroring/returning nothing when value is nil.
-
valueany
Returns: string
contains(list, value)
Returns true if value is present anywhere in list.
-
listtable -
valueany
Returns: boolean
find_index(list, value)
Returns the 1-based index of value in list, or -1 if not found.
-
listtable -
valueany
Returns: number or nil
distance2D(pos1, pos2)
Euclidean distance between two {x, y} points.
-
pos1{x, y} -
pos2{x, y}
Returns: number
distance3D(pos1, pos2)
Euclidean distance between two {x, y, z} points.
-
pos1{x, y, z} -
pos2{x, y, z}
Returns: number
normalize2D(vector)
Returns vector scaled to unit length.
-
vector{x, y}
Returns: {x, y}
normalize3D(vector)
Returns vector scaled to unit length.
-
vector{x, y, z}
Returns: {x, y, z}
add2D(pos1, pos2)
Component-wise addition.
-
pos1{x, y} -
pos2{x, y}
Returns: {x, y}
add3D(pos1, pos2)
Component-wise addition.
-
pos1{x, y, z} -
pos2{x, y, z}
Returns: {x, y, z}
sub2D(pos1, pos2)
Component-wise subtraction (pos1 - pos2).
-
pos1{x, y} -
pos2{x, y}
Returns: {x, y}
sub3D(pos1, pos2)
Component-wise subtraction (pos1 - pos2).
-
pos1{x, y, z} -
pos2{x, y, z}
Returns: {x, y, z}
mul2D(pos, scalar)
Component-wise scale.
-
pos{x, y} -
scalarnumber
Returns: {x, y}
mul3D(pos, scalar)
Component-wise scale.
-
pos{x, y, z} -
scalarnumber
Returns: {x, y, z}
round(value)
Rounds to the nearest integer.
-
valuenumber
Returns: number
Examples
Full, unedited scripts from the GDA campaign.
Mission 1
mission1.lua
local iron_mine_pos = { x = 40, y = 65 }
local enemy_spawn_pos = { x = 51, y = 119 }
function count_produced_troopers()
local troopers = game:query_units({ type = "trooper", player = 1 })
local trooper_count = count(troopers)
local produced = trooper_count - game:get_value("initial_trooper_count", 0)
return produced
end
local exphub = nil
local foundry = nil
function remaining_time(time_key, duration)
local time_value = game:get_value(time_key)
local delta_time = duration - (game:get_time() - time_value)
if delta_time < 0 then
return 0
end
return delta_time
end
function has_time_passed(time_key, duration)
local remaining = remaining_time(time_key, duration)
return remaining <= 0
end
function lock_enemy_walls(lock)
local enemy_walls = game:query_buildings({ type = "wall", player = 2 })
loop(enemy_walls, function(wall)
wall:set_invincible(lock)
end)
local enemy_gates = game:query_buildings({ type = "gate", player = 2 })
loop(enemy_gates, function(gate)
gate:set_invincible(lock)
end)
end
function spawn_enemies(type, pos, count)
for i = 1, count do
local unit = game:spawn_unit({ type = type, pos = pos, player = 2 })
unit.attack_move(exphub:get_pos())
end
end
function send_major_wave()
local draft = {
{ type = "trooper", count = 5 },
{ type = "rocket_trooper", count = 3 },
{ type = "lrv", count = 3 }
}
for _, unit in ipairs(draft) do
spawn_enemies(unit.type, enemy_spawn_pos, unit.count)
end
end
local transport = nil
function start()
foundry = game:query_buildings({ type = "factory", player = 1 })[1]
foundry:set_can_salvage(false)
exphub = game:query_buildings({ type = "exphub", player = 1 })[1]
exphub:set_can_salvage(false)
lock_enemy_walls(true)
local transports = game:query_units({ type = "chinook", player = 1 })
if (count(transports) > 0) then
transport = transports[1]
transport:enable_collision(false)
end
game:add_objectives({
{
prerequisite = function() return transport ~= nil end,
on_activated = function()
transport:move({ x = 48, y = 55 })
game:camera_follow_unit(transport:get_id())
end,
condition = function()
if not transport:is_moving() then
game:camera_follow_unit(nil)
game:set_value("ejecting_units", true)
return true
end
return false
end
},
{
prerequisite = function() return game:get_value("ejecting_units") end,
on_activated = function()
transport:eject_units()
end,
condition = function()
return not transport:is_ejecting_units()
end,
on_completed = function()
game:set_value("transport_exiting", true)
end
},
{
prerequisite = function() return game:get_value("transport_exiting") end,
on_activated = function()
transport:move({ x = 0, y = 0 })
end,
condition = function() return not transport:is_moving() end,
on_completed = function()
transport:remove()
transport = nil
end
},
{
dialog = {
{
character = "faction01_general",
message =
"Welcome back, Commander."
},
{
character = "faction01_general",
message = "I assume you went through basic training already, so we will get right to the point. Lieutenant Gonzalez?"
},
{
character = "faction01_lieutenant",
message =
"Gonzalez reporting. Commander, let's pick up where we left off.",
on_spoken = function()
game:set_value("intro_completed", true)
end
}
},
condition = function() return game:get_value("intro_completed") end
},
{
dialog = {
{
character = "faction01_lieutenant",
message = "Fortunately, this training area has a common ore deposit. Let's setup a mine to harvest it."
}
},
prerequisite = function() return game:get_value("intro_completed") end,
title = "BUILD MINE",
on_activated = function()
game:show_map_pointer(iron_mine_pos, "BUILD MINE")
game:set_building_panel_section("industry")
game:set_ui_highlight("building_mine", true)
end,
condition = function()
local mines = game:query_buildings({ type = "mine", player = 1 });
if count(mines) > 0 then
local mine = mines[1]
mine:set_can_salvage(false)
print("mine_id " .. tostring(mine:get_id()))
game:set_value("mine_id", mine:get_id())
return true
else
return false
end
end,
on_completed = function()
game:hide_pointer()
end,
},
{
title = "CONNECT FOUNDRY",
dialog = {
{
character = "faction01_lieutenant",
message = "Excellent. Select the mine, then right-click on the foundry, or use the connect command, to connect them."
}
},
on_activated = function()
game:set_value("expect_foundry_connection", true)
local mine = game:get_building(game:get_value("mine_id"))
if not mine:is_selected() then
game:show_map_pointer(mine:get_pos(), "SELECT MINE")
game:set_camera_pos(mine:get_pos())
else
game:show_map_pointer(foundry:get_pos(), "CONNECT FOUNDRY")
game:set_camera_pos(foundry:get_pos())
game:set_ui_highlight("action_connect", true)
end
end,
prerequisite = function() return game:get_value("mine_id") ~= nil end,
condition = function() return game:get_value("foundry_connected") end,
on_completed = function()
game:hide_pointer()
end
},
{
prerequisite = function() return game:get_value("foundry_connected") end,
title = "BUILD QUARTERS",
dialog = {
{
character = "faction01_lieutenant",
message = "Nice job Commander. It's time to build a quarters to train some troops."
}
},
on_activated = function()
game:set_building_panel_section("military")
game:set_ui_highlight("building_quarters", true)
end,
condition = function()
local quarters = game:query_buildings({ type = "quarters", player = 1 })
if (count(quarters) > 0) then
local quarter = quarters[1]
quarter:set_can_salvage(false)
game:set_value("quarter_id", quarter:get_id())
return true
else
return false
end
end
},
{
title = "TRAIN TROOPERS",
dialog = {
{
character = "faction01_lieutenant",
message = "You got it. We need at least 5 troopers to repel the incoming attackers."
}
},
prerequisite = function() return game:get_value("quarter_id") ~= nil end,
on_activated = function()
local troopers = game:query_units({ type = "trooper", player = 1 })
local initial_trooper_count = count(troopers)
game:set_value("initial_trooper_count", initial_trooper_count)
local quarter = game:get_building(game:get_value("quarter_id"))
game:show_map_pointer(quarter.get_pos(), "TRAIN TROOPERS")
game:set_camera_pos(quarter:get_pos())
end,
status = function()
local produced = count_produced_troopers()
if (produced > 1 and not game:get_value("attack_warning_shown", false)) then
game:set_value("attack_warning_shown", true)
local enemy_helicopter = game:query_units({ type = "chinook", player = 2 })[1]
enemy_helicopter:eject_units()
game:set_camera_pos(enemy_helicopter:get_pos())
game:fog_reveal({ pos = enemy_helicopter:get_pos(), radius = 10, duration = 5 })
end
return tostring(produced) .. "/5"
end,
condition = function() return count_produced_troopers() >= 5 end,
on_completed = function()
local enemy_helicopter = game:query_units({ type = "chinook", player = 2 })[1]
enemy_helicopter:remove()
game:set_value("troopers_trained", true)
game:hide_pointer()
end
},
{
prerequisite = function () return game:get_value("attack_warning_shown") end,
dialog = {
{
character = "faction01_major",
message = "Lieutenant, Major Williams here. We have received intel of incoming Rebel troops on the way to your location. They bear the insigna of the Los Víboras Cartel."
},
{
character = "faction01_lieutenant",
message = "The Los Víboras Cartel? They're being led by that ruthless viper, Valencia. We've never been able to put a face to her, but know that she's not to be messed with."
},
{
character = "faction01_lieutenant",
message = "Wait, how'd they even cross the border? Crap...We don't have time to figure this out."
},
{
character = "faction01_lieutenant",
message = "Well, this is it, Commander, this is your moment to shine. Let's get those troops ready.",
on_activated = function()
game:set_camera_pos(exphub:get_pos())
end
}
}
},
{
failure_condition = function()
local base_count = count(game:query_buildings({ player = 1, type = "exphub" }))
local bev_count = count(game:query_units({ player = 1, type = "bev" }))
return base_count == 0 and bev_count == 0
end,
on_failed = function()
game:set_mission_failed()
end,
},
{
prerequisite = function() return game:get_value("troopers_trained") end,
title = "REPEL THE ATTACKERS",
condition = function()
local attackers = game:query_units({ player = 2 })
return count(attackers) == 0
end,
on_completed = function()
game:set_value("attackers_repelled", true)
end,
dialog = {
{
character = "faction01_major",
message = "Commander, Valencia's Rebel cell is at the outskirts of your base - get ready for a brawl!",
can_skip = false
},
{
character = "faction02_cartelboss_obfuscated",
message = "Attention, GDA weaklings! This land belongs to the Víboras Cartel now. Surrender now or we will open fire upon your pitiful base and bury your men...Or perhaps I'll just enjoy hunting you all murderers down.",
on_activated = function()
local attackers = game:query_units({ player = 2 })
loop(attackers, function(attacker)
attacker:attack_move(exphub:get_pos())
end)
game:set_camera_pos(attackers[1]:get_pos())
game:fog_reveal({ pos = attackers[1]:get_pos(), radius = 10, duration = 5 })
end
},
{
character = "faction01_lieutenant",
message = "All your troops are ready. It's time to show those rebels who's boss.",
on_activated = function()
game:set_camera_pos(exphub:get_pos())
end
}
}
},
{
prerequisite = function() return game:get_value("attackers_repelled") end,
dialog = {
{
character = "faction01_general",
message = "Well done, Commander."
},
{
character = "faction01_general",
message = "This could have turned out much worse. We're are lucky to.. Lieutenant, do you have something to say?"
},
{
character = "faction01_lieutenant",
message = "I am sorry General, but this was just a diversion. New satellite imagery revealed enemy presence in the North.",
on_activated = function()
local _enemy_quarters = game:query_buildings({ type = "quarters", player = 2 })
if (count(_enemy_quarters) > 0) then
local enemy_quarters = _enemy_quarters[1]
game:fog_reveal({ pos = enemy_quarters:get_pos(), radius = 10, duration = 5 })
game:set_camera_pos(enemy_quarters:get_pos())
end
end
},
{
character = "faction01_general",
message = "This is why I hate celebrating things. Lieutenant, you know what to do.",
},
{
character = "faction01_lieutenant",
message = "Yes, General, I will take care of it. Commander, we need to rally and preemptively strike them before it's too late."
},
{
character = "faction01_lieutenant",
message = "Destroy the enemy quarters before they can attack us.",
on_activated = function()
game:set_value("preemptive_strike", true)
game:set_value("major_attack_warning_time", 60*3)
end
}
}
},
{
prerequisite = function() return game:get_value("preemptive_strike") end,
title = "PREEMPTIVE STRIKE",
on_activated = function()
lock_enemy_walls(false)
local _enemy_quarters = game:query_buildings({ type = "quarters", player = 2 })
if (count(_enemy_quarters) > 0) then
local enemy_quarters = _enemy_quarters[1]
game:show_map_pointer(enemy_quarters:get_pos(), "DESTROY ENEMY QUARTERS")
end
end,
condition = function() return count(game:query_buildings({ type = "quarters", player = 2 })) == 0 end,
on_completed = function()
game:set_mission_complete()
end
},
{
title = "MAJOR ATTACK INCOMING",
prerequisite = function() return game:get_value("preemptive_strike") end,
status = function(delta_time)
local remaining = game:get_value("major_attack_warning_time")
remaining = remaining - delta_time;
if remaining < 0 then
remaining = 0
end
game:set_value("major_attack_warning_time", remaining)
return tostring(math.floor(remaining + 0.5)) .. "s"
end,
condition = function() return game:get_value("major_attack_warning_time") == 0 end,
on_completed = function()
send_major_wave()
end
}
})
end
function on_selection_changed(selection_type)
if game:get_value("expect_foundry_connection") then
local mine = game:get_building(game:get_value("mine_id"))
if mine ~= nil then
if selection_type == "building" then
if mine:is_selected() then
game:delayed_call(.3, function() game:set_ui_highlight("action_connect", true) end)
game:show_map_pointer(foundry:get_pos(), "CONNECT FOUNDRY")
game:set_camera_pos(foundry:get_pos())
else
game:show_map_pointer(mine:get_pos(), "SELECT MINE")
game:set_camera_pos(mine:get_pos())
end
else
game:show_map_pointer(mine:get_pos(), "SELECT MINE")
game:set_camera_pos(mine:get_pos())
end
end
end
end
function on_buildings_connected(building1_id, building2_id)
local building1 = game:get_building(building1_id)
local building2 = game:get_building(building2_id)
print("on_buildings_connected " .. building1.get_type() .. " " .. building2.get_type())
if building1.get_type() == "mine" and building2.get_type() == "factory" then
game:set_value("foundry_connected", true)
game:set_value("expect_foundry_connection", false)
game:hide_pointer()
end
end
Mission 2
mission2.lua
local iron_mine1_pos = { x = 56, y = 245 }
local reinforcements1_pos = { x = 250, y = 137 }
local reinforcements1_move_to = { x = 229, y = 138 }
local reinforcements2_pos = { x = 171, y = 8 }
local reinforcements2_move_to = { x = 171, y = 28 }
local reinforcements3_pos = { x = 7, y = 48 }
local reinforcements3_move_to = { x = 27, y = 53 }
local truck = nil
local foundry = nil
local quarters = nil
local mine = nil
local enemy_base = nil
function count_produced_troopers()
local troopers = game:query_units({ type = "trooper", player = 1 })
local rocket_troopers = game:query_units({ type = "rocket_trooper", player = 1 })
local snipers = game:query_units({ type = "sniper", player = 1 })
local trooper_count = count(troopers) + count(rocket_troopers) + count(snipers)
local produced = trooper_count - game:get_value("initial_trooper_count", 0)
return produced
end
function count_units_with_tag(tag)
local units = game:query_units({ player = 2, tag = tag })
return count(units)
end
function count_buildings_with_tag(tag)
local buildings = game:query_buildings({ player = 2, tag = tag })
return count(buildings)
end
function get_average_pos(playerIndex, tag)
local units = game:query_units({ player = playerIndex, tag = tag })
local total_x = 0
local total_y = 0
for i, unit in ipairs(units) do
total_x = total_x + unit:get_pos().x
total_y = total_y + unit:get_pos().y
end
return { x = total_x / count(units), y = total_y / count(units) }
end
function spawn_reinforcements(pos, units)
local spawned_units = {}
for i, unit in ipairs(units) do
for j = 1, unit.count do
local instance = game:spawn_unit({ type = unit.type, pos = pos, player = 1 })
table.insert(spawned_units, instance)
end
end
return spawned_units
end
function reinforce(pos, move_to, type)
local draft = nil
if type == "heavy" then
draft = {
{ type = "chopper", count = 6 },
{ type = "artillery", count = 2 },
{ type = "tank", count = 2 }
}
else
draft = {
{ type = "trooper", count = 5 },
{ type = "rocket_trooper", count = 3 },
{ type = "tank", count = 2 }
}
end
local spawned_units = spawn_reinforcements(pos, draft)
game:move_units(spawned_units, move_to)
game:delayed_call(.5, function()
game:set_camera_pos(pos)
end)
end
function start()
truck = game:query_units({ type = "truck", player = 1 })[1]
truck:set_invincible(true)
foundry = game:query_buildings({ type = "factory", player = 1 })[1]
foundry:set_can_salvage(false)
quarters = game:query_buildings({ type = "quarters", player = 1 })[1]
quarters:set_can_salvage(false)
enemy_base = game:query_buildings({ type = "hq", player = 2 })[1]
game:add_objectives({
{
dialog = {
{
character = "faction01_general",
message = "Commander, we've arrived at the outskirts of Valencia's compound. Let's cut to the chase."
},
{
character = "faction01_general",
message =
"The Cartel has gotten its hands on some serious firepower. They have their own private standing army. Lieutenant, get the Commander briefed on base operations."
},
{
character = "faction01_lieutenant",
message = "Yes, sir!"
},
{
character = "faction01_lieutenant",
message = "There is a nearby common ore deposit, that we need to secure to set up military production.",
on_activated = function()
game:set_value("intro_completed", true)
end
}
},
},
{
title = "SECURE COMMON ORE DEPOSIT",
prerequisite = function() return game:get_value("intro_completed") end,
on_activated = function()
game:show_map_pointer(iron_mine1_pos, "SECURE COMMON ORE DEPOSIT")
end,
condition = function()
local enemies = game:query_units({ player = 2, tag = "1" })
return count(enemies) == 0
end,
on_completed = function()
game:hide_pointer()
game:set_value("iron_mine1_secured", true)
end
},
{
title = "BUILD MINE",
dialog = {
{
character = "faction01_lieutenant",
message = "Perfect. You know the drill, Commander. I mean, let's build a mine."
}
},
prerequisite = function() return game:get_value("iron_mine1_secured") end,
on_activated = function()
game:show_map_pointer(iron_mine1_pos, "BUILD MINE")
end,
on_completed = function()
game:hide_pointer()
game:set_value("mine_built", true)
end,
condition = function()
local mines = game:query_buildings({ type = "mine", player = 1 })
if (count(mines) > 0) then
mine = mines[1]
mine:set_can_salvage(false)
return true
else
return false
end
end
},
{
prerequisite = function() return game:get_value("mine_built") end,
on_activated = function()
game:set_camera_pos(truck:get_pos())
truck:set_invincible(false)
end,
dialog = {
{
character = "faction01_lieutenant",
message = "Excellent. This deposit will be enough for primary production. Whenever you are ready, use these trucks to bring common ore to the foundry."
},
{
character = "faction01_lieutenant",
message = "Time to start troop production. You will need it!",
on_activated = function()
game:set_value("truck_hint_given", true)
end
}
}
},
{
title = "TRAIN TROOPERS",
-- prerequisite = function() return game:get_value("mine_connected") end,
prerequisite = function() return game:get_value("truck_hint_given") end,
on_activated = function()
local troopers = game:query_units({ type = "trooper", player = 1 })
local rocket_troopers = game:query_units({ type = "rocket_trooper", player = 1 })
local snipers = game:query_units({ type = "sniper", player = 1 })
local initial_trooper_count = count(troopers) + count(rocket_troopers) + count(snipers)
game:set_value("initial_trooper_count", initial_trooper_count)
game:show_map_pointer(quarters:get_pos(), "TRAIN TROOPERS")
game:set_camera_pos(quarters:get_pos())
end,
condition = function()
return count_produced_troopers() >= 5
end,
status = function() return tostring(count_produced_troopers()) .. "/5" end,
on_completed = function()
game:hide_pointer()
game:set_value("troopers_trained", true)
end,
dialog = {
{
character = "faction01_lieutenant",
message = "Great work, commander! Time to start troop production. You will need it!"
}
}
},
{
dialog = {
{
character = "faction01_general",
message = "Commander, it's go time. Secure all nearby common ore deposits. Beware, the rebels are around and they are not exactly thrilled to see you."
}
},
title = "SECURE COMMON ORE DEPOSIT",
prerequisite = function() return game:get_value("troopers_trained") end,
on_activated = function()
game:show_map_pointer(get_average_pos(2, "2"), "SECURE COMMON ORE")
end,
condition = function()
return count_units_with_tag("2") == 0
end,
on_completed = function()
game:hide_pointer()
game:set_value("common_ore_deposit_secured", true)
end
},
{
prerequisite = function() return game:get_value("common_ore_deposit_secured") end,
dialog = {
{
character = "faction01_lieutenant",
message = "You are doing great, commander!"
},
{
character = "faction01_lieutenant",
message = "The general has formally approved sending you additional reinforcements."
},
{
character = "faction01_lieutenant",
message = "But you will need to secure more common ore deposits to degrade the rebels industrial capacity.",
on_activated = function()
game:set_value("intro2_completed", true)
end
}
}
},
{
title = "SECURE COMMON ORE DEPOSIT",
prerequisite = function() return game:get_value("intro2_completed") end,
on_activated = function()
game:show_map_pointer(get_average_pos(2, "3"), "SECURE COMMON ORE DEPOSIT")
end,
condition = function()
return count_units_with_tag("3") == 0
end,
on_completed = function()
game:hide_pointer()
game:set_value("common_ore_deposit_secured2", true)
end
},
{
prerequisite = function() return game:get_value("common_ore_deposit_secured2") end,
dialog = {
{
character = "faction01_general",
message = "Lieutenant, please coordinate the arrival of reinforcements to a secure location."
},
{
character = "faction01_lieutenant",
message = "Roger that, sir. Reinforcements are on the way.",
on_activated = function()
reinforce(reinforcements1_pos, reinforcements1_move_to)
game:set_value("reinforcements1_sent", true)
end
}
},
},
{
dialog = {
{
character = "faction01_lieutenant",
message = "Commander, the rebels are still holding more common ore deposits, according to intelligence reports."
},
{
character = "faction01_lieutenant",
message = "We need to take a shot at their last potential source of common ore."
}
},
title = "SECURE COMMON ORE DEPOSIT",
prerequisite = function() return game:get_value("reinforcements1_sent") end,
on_activated = function()
game:show_map_pointer(get_average_pos(2, "4"), "SECURE COMMON ORE DEPOSIT")
end,
condition = function()
local units = count_units_with_tag("4")
local buildings = count_buildings_with_tag("4")
return units == 0 and buildings == 0
end,
on_completed = function()
game:hide_pointer()
game:set_value("common_ore_deposit_secured3", true)
end
},
{
prerequisite = function() return game:get_value("common_ore_deposit_secured3") end,
dialog = {
{
character = "faction01_lieutenant",
message = "That was a close one, commander. The rebels have a lot of firepower."
},
{
character = "faction01_general",
message = "This mission is unfortunately taking a heavier toll than expected. But we need to push through."
},
{
character = "faction01_lieutenant",
message = "Roger that, sir. Additional reinforcements are on the way.",
on_activated = function()
reinforce(reinforcements2_pos, reinforcements2_move_to)
game:set_value("reinforcements2_sent", true)
end
}
},
},
{
prerequisite = function() return game:get_value("reinforcements2_sent") end,
dialog = {
{
character = "faction01_lieutenant",
message = "Wait a minute. Reports are coming in that there may be air defense structures in the area."
},
{
character = "faction01_general",
message = "We can't send you advanced, mission critical reinforcements until you take down their air defense.",
on_activated = function()
game:set_value("intro3_completed", true)
end
}
}
},
{
title = "DESTROY MISSILE TURRETS",
prerequisite = function() return game:get_value("intro3_completed") end,
on_activated = function()
local rocket_pods = game:query_buildings({ type = "rocket_pod", player = 2 })
local remaining = count(rocket_pods)
game:set_value("initial_missile_turret_count", remaining)
game:set_value("previous_missile_turret_count", remaining)
game:show_map_pointer(rocket_pods[1]:get_pos(), "DESTROY MISSILE TURRET")
end,
condition = function()
local rocket_pods = game:query_buildings({ type = "rocket_pod", player = 2 })
return count(rocket_pods) == 0
end,
status = function()
local rocket_pods = game:query_buildings({ type = "rocket_pod", player = 2 })
local remaining = count(rocket_pods)
local previous_count = game:get_value("previous_missile_turret_count")
if remaining ~= previous_count then
if remaining > 0 then
game:show_map_pointer(rocket_pods[1]:get_pos(), "DESTROY MISSILE TURRET")
end
game:set_value("previous_missile_turret_count", remaining)
end
local initial_count = game:get_value("initial_missile_turret_count")
local destroyed_count = initial_count - remaining
return destroyed_count .. "/" .. initial_count
end,
on_completed = function()
game:hide_pointers()
game:set_value("missile_turrets_destroyed", true)
end
},
{
prerequisite = function() return game:get_value("missile_turrets_destroyed") end,
dialog = {
{
character = "faction01_general",
message = "Fantastic work, commander."
},
{
character = "faction01_general",
message = "The enemy base is in sight. We are sending additional reinforcements to finish this off.",
on_activated = function()
reinforce(reinforcements3_pos, reinforcements3_move_to, "heavy")
game:set_value("reinforcements3_sent", true)
end
}
},
},
{
title = "DESTROY ENEMY BASE",
prerequisite = function() return game:get_value("reinforcements3_sent") end,
on_activated = function()
game:show_map_pointer(enemy_base:get_pos(), "DESTROY ENEMY BASE")
end,
condition = function()
return not enemy_base:is_alive()
end,
on_completed = function()
game:hide_pointer()
game:set_value("enemy_base_destroyed", true)
game:set_mission_complete()
end
}
})
end Mission 3
mission3.lua
local mine1_pos = { x = 56, y = 138 }
local oilrig1_pos = { x = 40, y = 95 }
local area_to_scout = { x = 157, y = 101 }
local area_reveal = { x = 179, y = 104 }
function get_units_average_pos()
local units = game:query_units({ player = 1 })
if count(units) == 0 then
return nil
end
local total_x = 0
local total_y = 0
for i, unit in ipairs(units) do
total_x = total_x + unit:get_pos().x
total_y = total_y + unit:get_pos().y
end
return { x = total_x / count(units), y = total_y / count(units) }
end
function is_close(pos1, pos2, threshold)
local distance = distance2D(pos1, pos2)
return distance < threshold
end
function target_lab(lab)
game:show_map_pointer(lab:get_pos(), "DESTROY RESEARCH LAB")
game:fog_reveal({ pos = lab:get_pos(), radius = 10, duration = 5 })
end
function start()
local exphubs = game:query_buildings({ type = "exphub", player = 1 })
local exphub = exphubs[1]
local playerBuildings = game:query_buildings({ player = 1 })
loop(playerBuildings, function(building)
building:set_disconnected(true)
end)
game:enable_building_panel(false)
game:add_objectives({
{
dialog = {
{
character = "faction01_lieutenant",
message = "Welcome back, Commander! Your troops are landing here, near the beachhead. This is about as close as we can get you by air. Let's hope someone's still alive out there..."
},
{
duration = 2,
on_activated = function()
game:set_camera_pos(exphub:get_pos())
end
},
{
character = "faction01_lieutenant",
message = "Copperhead Actual, respond, this GDA Military Command aboard the GDS Endeavour! Copperhead Actual, please respond..."
},
{
character = "faction01_lieutenant",
message = "Well, it seems like we might be too late. Commander, clear the area of hostiles and see if we can't get that base up and running again, at the very least.",
on_activated = function()
game:set_value("intro_completed", true)
end
}
}
},
{
title = "SECURE THE BASE",
prerequisite = function() return game:get_value("intro_completed") end,
on_activated = function()
game:show_map_pointer(exphub:get_pos(), "SECURE THE BASE")
game:delayed_call(2, function()
local average_pos = get_units_average_pos()
if average_pos ~= nil then
game:set_camera_pos(average_pos)
end
end)
end,
condition = function()
local enemies = game:query_units({ player = 2, tag = "1" })
return count(enemies) == 0
end,
failure_condition = function()
local units = game:query_units({ player = 1 })
return count(units) == 0
end,
on_completed = function()
game:hide_pointer()
game:set_value("base_secured", true)
end,
on_failed = function()
game:set_mission_failed()
end
},
{
prerequisite = function() return game:get_value("base_secured") end,
on_activated = function()
loop(playerBuildings, function(building)
building:set_disconnected(false)
end)
game:enable_building_panel(true)
end,
dialog = {
{
character = "faction01_lieutenant",
message = "Excellent work. I was able to get back control of the Base's systems. It appears that Copperhead's platoon is gone, and the servers mention Rebel Research Labs as well as a new weapons technology called 'Lightbringer'"
},
{
character = "faction01_lieutenant",
message = "You'll have to clear those labs out."
},
{
character = "faction01_major",
message = "Sir, I'll get to finding where those labs are located while you set up your base."
},
{
character = "faction01_lieutenant",
message = "Thanks, Major. Let's set up our resource and unit production, in the meantime. Build up a Mine first, Commander.",
on_activated = function()
game:set_value("intro2_completed", true)
end
}
}
},
{
title = "BUILD A MINE",
prerequisite = function() return game:get_value("intro2_completed") end,
on_activated = function()
game:show_map_pointer(mine1_pos, "BUILD A MINE")
game:set_building_panel_section("industry")
game:set_ui_highlight("building_mine", true)
end,
condition = function()
local mines = game:query_buildings({ type = "mine", player = 1 })
return count(mines) > 0
end,
on_completed = function()
game:hide_pointer()
game:set_value("mine_built", true)
end
},
{
dialog = {
{
character = "faction01_lieutenant",
message = "Now build a Foundry."
}
},
title = "BUILD A FOUNDRY",
prerequisite = function() return game:get_value("mine_built") end,
on_activated = function()
game:set_building_panel_section("industry")
game:set_ui_highlight("building_factory", true)
end,
condition = function()
local foundries = game:query_buildings({ type = "factory", player = 1 })
return count(foundries) > 0
end,
on_completed = function()
game:set_value("foundry_built", true)
end
},
{
dialog = {
{
character = "faction01_lieutenant",
message = "Now that that's taken care of, let's build an Oil Derrick to provide oil to our Refinery."
}
},
title = "BUILD AN OIL DERRICK",
prerequisite = function() return game:get_value("foundry_built") end,
on_activated = function()
game:show_map_pointer(oilrig1_pos, "BUILD AN OIL DERRICK")
game:set_building_panel_section("industry")
game:set_ui_highlight("building_oil_derrick", true)
end,
condition = function()
local oilrigs = game:query_buildings({ type = "oil_derrick", player = 1 })
return count(oilrigs) > 0
end,
on_completed = function()
game:hide_pointer()
game:set_value("oil_derrick_built", true)
end
},
{
dialog = {
{
character = "faction01_lieutenant",
message = "Excellent! Now select the Oil Derrick, click the 'Connect' button in the BCI interface, then click on the Refinery."
}
},
title = "CONNECT REFINERY",
on_activated = function()
game:set_value("expect_refinery_connection", true)
end,
prerequisite = function() return game:get_value("oil_derrick_built") end,
condition = function()
return game:get_value("refinery_connected")
end,
on_help = function()
game:deselect();
local oilrigs = game:query_buildings({ type = "oil_derrick", player = 1 })
if (count(oilrigs) > 0) then
oilrigs[1].select();
end
end
},
{
dialog = {
{
character = "faction01_lieutenant",
message = "Perfect. With fuel, we'll now be able to build vehicles. Our next order of business is to do some scouting to find more resources. Let's build ourselves a Vehicle Assembly. You can find it in the Military Production Tab on your BCI."
}
},
title = "BUILD A VEHICLE ASSEMBLY",
prerequisite = function() return game:get_value("refinery_connected") end,
on_activated = function()
game:set_building_panel_section("military")
game:set_ui_highlight("building_assembly", true)
end,
condition = function()
local assemblies = game:query_buildings({ type = "assembly", player = 1 })
return count(assemblies) > 0
end,
on_completed = function()
game:set_value("vehicle_assembly_built", true)
end
},
{
prerequisite = function() return game:get_value("vehicle_assembly_built") end,
dialog = {
{
character = "faction01_lieutenant",
message = "Great! Light Recon Vehicles, or LRVs for short, are good for scouting thanks to their speed and vision."
},
{
character = "faction01_lieutenant",
message = "Let's build an LRV to scout the area."
}
},
title = "BUILD AN LRV",
on_activated = function()
game:deselect();
local assemblies = game:query_buildings({ type = "assembly", player = 1 })
if (count(assemblies) > 0) then
assemblies[1].select();
game:delayed_call(.3, function() game:set_ui_highlight("unit_lrv", true) end)
end
end,
condition = function()
local lrvs = game:query_units({ type = "lrv", player = 1 })
return count(lrvs) > 0
end,
on_completed = function()
game:set_value("lrv_built", true)
end
},
{
title = "SCOUT THE AREA",
prerequisite = function() return game:get_value("lrv_built") end,
on_activated = function()
game:show_map_pointer(area_to_scout, "SCOUT THE AREA")
game:set_camera_pos(area_to_scout)
end,
condition = function()
local lrvs = game:query_units({ type = "lrv", player = 1 })
if count(lrvs) > 0 and not lrvs[1]:is_moving() then
if (is_close(lrvs[1]:get_pos(), area_to_scout, 20)) then
return true
end
end
return false
end,
on_completed = function()
game:hide_pointer()
game:set_value("area_scouted", true)
end,
dialog = {
{
character = "faction01_lieutenant",
message = "Now that we have an LRV, let's scout out this area. Intel reports that there is a Rebel presence up ahead and that they've seemingly dug something out there."
}
}
},
{
prerequisite = function() return game:get_value("area_scouted") end,
on_activated = function()
game:set_camera_pos(area_reveal)
game:fog_reveal({ pos = area_reveal, radius = 20, duration = 5 })
end,
dialog = {
{
character = "faction01_lieutenant",
message = "It looks like the rebels have drilled up another Oil Field. Hold on, I'm receiving a transmission from the Major. Patching it through."
},
{
character = "faction01_major",
message = "Commander, I've been able to track the labs' locations. I'll ping them on your map and through the Fog of War."
},
{
character = "faction01_lieutenant",
message = "Build up your army and get to crushing those labs, Commander!",
on_activated = function()
game:set_value("intro3_completed", true)
end
}
}
},
{
title = "DESTROY ENEMY RESEARCH LABS",
prerequisite = function() return game:get_value("intro3_completed") end,
on_activated = function()
local labs = game:query_buildings({ type = "research_lab", player = 2 })
local current_time = game:get_time()
game:set_value("lab_destruction_start_time", current_time)
local lab_count = count(labs)
game:set_value("previous_lab_count", lab_count)
game:set_value("initial_lab_count", lab_count)
if lab_count > 0 then
local first_lab = labs[1]
target_lab(first_lab)
game:set_camera_pos(first_lab:get_pos())
end
end,
status = function()
local abbot_warning_sent = game:get_value("abbot_warning_sent")
if not abbot_warning_sent then
local current_time = game:get_time()
local start_time = game:get_value("lab_destruction_start_time")
local elapsed_time = current_time - start_time
if elapsed_time > 60 then
game:set_value("abbot_warning_sent", true)
end
end
local labs = game:query_buildings({ type = "research_lab", player = 2 })
local lab_count = count(labs)
local initial_lab_count = game:get_value("initial_lab_count")
local destroyed = initial_lab_count - lab_count
local previous_lab_count = game:get_value("previous_lab_count")
if previous_lab_count ~= lab_count then
if lab_count > 0 then
local first_lab = labs[1]
target_lab(first_lab)
end
game:set_value("previous_lab_count", lab_count)
end
return tostring(destroyed) .. " / " .. initial_lab_count
end,
condition = function()
local labs = game:query_buildings({ type = "research_lab", player = 2 })
return count(labs) == 0
end,
on_completed = function()
game:set_value("lab_destruction_completed", true)
end
},
{
prerequisite = function() return game:get_value("abbot_warning_sent") end,
dialog = {
{
character = "faction02_intelofficer",
message = "GDA is sending recruits to command troops in the field of battle? Good, I will make short work of this runt."
},
{
character = "faction01_major",
message = "I can't believe it, that's Ethan Abbott! I suppose leaking national state secrets and putting England in danger wasn't enough for him...He's been on Interpol's list since, Commander. I don't like this..."
},
{
character = "faction01_lieutenant",
message = "He'll have a lot to answer for when we get him, that much I can assure you, Major. Let's focus on destroying those labs, for now.",
on_activated = function()
game:set_value("begin_enemy_waves", true)
end
},
{
character = "faction01_major",
message = "Commander, be sure to leave some troops to defend your base. I'm sure the Rebels will send forces to attack you soon, as well."
}
}
},
{
prerequisite = function() return game:get_value("begin_enemy_waves") end,
on_activated = function()
game:set_value("enemy_waves_started", true)
end,
status = function()
-- todo send waves ?
return false
end
},
{
prerequisite = function() return game:get_value("lab_destruction_completed") end,
dialog = {
{
character = "faction01_major",
message = "Sir, we've cleaned out their base, there's no sign of the enemy Commanding Officer here. We'll gather what intel we can find from these wrecked Labs and be on our way."
},
{
character = "faction01_lieutenant",
message = "Abbott has always had a thing for evading capture. But we'll catch him."
},
{
character = "faction01_general",
message = "Good job on destroying those labs, Commander! We're waiting back at HQ for you.",
on_activated = function()
game:set_mission_complete()
end
}
}
}
})
end
function on_buildings_connected(building1_id, building2_id)
local building1 = game:get_building(building1_id)
local building2 = game:get_building(building2_id)
if building1.get_type() == "oil_derrick" and building2.get_type() == "refinery" then
game:set_value("refinery_connected", true)
game:set_value("expect_refinery_connection", false)
game:hide_pointer()
end
end
function on_selection_changed(selection_type)
if game:get_value("expect_refinery_connection") then
if selection_type == "building" then
local oilrigs = game:query_buildings({ type = "oil_derrick", player = 1 })
if count(oilrigs) > 0 and oilrigs[1]:is_selected() then
game:delayed_call(.3, function() game:set_ui_highlight("action_connect", true) end)
end
end
end
end
function on_ui_clicked(ui_id)
print("on_ui_clicked " .. ui_id)
if game:get_value("expect_refinery_connection") then
if ui_id == "action_connect" then
local refineries = game:query_buildings({ type = "refinery", player = 1 })
if count(refineries) > 0 then
game:show_map_pointer(refineries[1]:get_pos(), "CONNECT REFINERY")
end
end
end
end