Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Recoil Game Tutorial

From Fightorder

Making A Basic Recoil Game

This guide walks you through creating a minimal but complete game with the Recoil engine. By the end you will have a working game archive that loads in the engine, with a playable Commander unit, a simple weapon, and a basic Lua gadget.

No prior Spring or Recoil experience is assumed, though basic familiarity with Lua will help when you reach the scripting sections.

Recoil is descended from the Spring engine and is highly compatible with Spring 105 games and mods. If you are migrating an existing Spring game, the bulk of your content will work as-is — see Recoil:Migrating From Spring for the specific breaking changes.

1. What is Recoil?

Recoil is an open-source, cross-platform real-time strategy game engine descended from Spring (which itself descended from Total Annihilation). The engine handles low-level simulation, networking, and rendering. Your game is a Lua + asset package that rides on top of the engine: you control the rules, the units, the UI, and everything else.

Out of the box the engine provides:

  • Physics simulation — projectile trajectories, terrain collision, unit movement and pathfinding
  • Resource system — metal and energy by default, fully customisable
  • Networking — authoritative server, deterministic lock-step simulation, automatic replay recording
  • Lua scripting — every mechanic is overridable via Lua gadgets and widgets
  • VFS — a virtual file system that merges your game archive, maps, and basecontent into one namespace

Key terminology

Term Meaning
Game Your content archive (units, Lua scripts, textures, …) loaded by the engine.
Map A separate terrain archive (.smf height data + textures). Distributed independently of your game.
Gadget A Lua script that runs game logic (synced or unsynced). Lives in luarules/gadgets/.
Widget A Lua script that runs UI logic. Lives in luaui/widgets/.
Wupget Informal collective term for widgets and gadgets.
Callin A function in your Lua code that the engine calls on events (e.g. gadget:UnitCreated).
Callout A function the engine exposes to Lua (e.g. Spring.GetUnitPosition).
VFS Virtual File System — unified read-only view of the game archive, map archive, and basecontent.
Basecontent An archive always loaded by the engine that provides default wupget handlers, water textures, and other bare necessities.
.sdd A game folder whose name ends in .sdd. Treated as an uncompressed archive during development.
.sdz / .sd7 Production game archives (zip and 7-zip respectively). Faster to distribute and load than .sdd.
Elmo The internal distance unit. Roughly 8 height units of map height. Used in unit defs for speed, range, etc.

2. Prerequisites

Before starting you need:

  1. The Recoil engine binary. Download a pre-built release from GitHub Releases or build from source following the build documentation. The main executable is spring on Linux or spring.exe on Windows.
  2. A map archive (.smf / .sd7) placed in the maps/ subfolder of your Recoil data directory. Any map from an existing Recoil game works for early testing.
  3. A text editor. Any editor works. For the best experience, configure the Recoil:Lua Language Server with Recoil type stubs so you get autocomplete on all Spring.* functions.

Your Recoil data directory typically looks like:

recoil-data/
├── spring            ← engine binary (Linux) or spring.exe (Windows)
├── spring_headless   ← headless binary (no rendering; useful for automated testing)
├── spring_dedicated  ← dedicated server binary
├── games/            ← your game archives / development folders go here
└── maps/             ← map archives go here

3. Virtual File System

The engine loads content from several sources, merged into one Virtual File System (VFS):

  • Game archive — your .sdd folder or .sdz/.sd7 file. Files here have the highest priority.
  • Map archive — the terrain and map-specific data. Maps can override some of your game's properties via MapOptions.lua.
  • Basecontent — always loaded by the engine. Provides the default wupget handler framework, stock water textures, and other bare essentials.

Game archives can declare dependencies on other archives in modinfo.lua. The engine merges all archives into the VFS, with your game taking priority over its dependencies. This lets you build on top of an existing game without copying its entire asset tree.

Files in a dependency are completely overridden if your game provides a file at the same path. There is currently no way to partially merge files.

See Recoil:VFS Basics for the full list of VFS modes available to Lua scripts.


4. Game Archive Structure

During development, use an .sdd folder. Create it inside your games/ directory:

games/
└── mygame.sdd/
    ├── modinfo.lua           ← required: identifies your game to the engine
    ├── gamedata/             ← def pre/post processing, game options, rules
    │   ├── modrules.lua
    │   ├── unitdefs_post.lua
    │   └── ModOptions.lua
    ├── units/                ← unit definition files (one per unit by convention)
    │   └── commander.lua
    ├── weapons/              ← weapon definition files
    │   └── laser.lua
    ├── features/             ← feature (prop/wreck/tree) definitions
    │   └── trees.lua
    ├── luarules/
    │   └── gadgets/          ← synced and unsynced game logic
    │       └── game_end.lua
    ├── luaui/
    │   └── widgets/          ← UI scripts
    │       └── resources_display.lua
    ├── objects3d/            ← unit models (.dae / .obj / .s3o)
    └── unittextures/         ← unit icon textures (unitname.png)
<translate> Warning</translate> <translate> Warning:</translate> The .sdd extension on the folder name is mandatory. Without it the engine will not recognise it as a game archive.

For production, compress the contents into a .sdz or .sd7 archive. modinfo.lua must end up at the archive root — not inside a subfolder.


5. modinfo.lua

modinfo.lua is the most important file in your archive. Without it the engine will not recognise it as a game.

-- games/mygame.sdd/modinfo.lua

local modinfo = {
    -- Displayed in lobbies and the engine title bar
    name        = "My Recoil Game",
    -- Short unique identifier; no spaces, keep it stable across versions
    shortName   = "MRG",
    description = "A minimal Recoil game for learning purposes.",
    version     = "0.1.0",
    -- Leave empty for base games (not mods layered on top of another game)
    mutator     = "",
    -- Other archive names this game depends on; basecontent is always loaded
    depend      = {},
    -- 1 = game/mod (visible in lobbies), 0 = hidden
    modtype     = 1,
    -- Faction names available in start scripts and team configuration
    sides       = { "MyFaction" },
    -- Per-side starting unit def name
    startscreens = {
        MyFaction = "commander",
    },
}

return modinfo
<translate> Warning</translate> <translate> Warning:</translate> shortName must be unique across all games the engine knows about. Changing it later breaks lobby integrations and replay compatibility.

ModOptions.lua

To expose configurable options in lobbies (sliders, checkboxes, dropdowns), add gamedata/ModOptions.lua:

-- gamedata/ModOptions.lua

return {
    {
        key  = "startmetal",
        name = "Starting Metal",
        desc = "Metal each player starts with.",
        type = "number",
        def  = 1000,
        min  = 0,
        max  = 10000,
        step = 100,
    },
    {
        key  = "commanderdie",
        name = "Commander Death Ends Game",
        desc = "Lose when your Commander is destroyed.",
        type = "bool",
        def  = true,
    },
}

Read options at runtime in gadgets with Spring.GetModOptions().


6. Unit Definitions

Unit defs are Lua files in units/ that return a table of unit type definitions. The convention is one unit per file, where the filename matches the unit def name.

-- units/commander.lua

return {
    commander = {
        -- ── Display ──────────────────────────────────────────────────
        name        = "Commander",
        description = "The starting unit.",
        buildPic    = "commander.png",   -- icon shown in unit HUD

        -- ── Model ────────────────────────────────────────────────────
        objectName  = "commander.dae",   -- path inside objects3d/

        -- ── Health ───────────────────────────────────────────────────
        health      = 3000,
        armorType   = "commander",       -- armour class name

        -- ── Footprint ────────────────────────────────────────────────
        footprintX  = 2,                 -- width in map squares (8 elmos each)
        footprintZ  = 2,
        collisionVolumeScales = "30 50 30",
        collisionVolumeType   = "ellipsoid",

        -- ── Movement ─────────────────────────────────────────────────
        moveDef = {
            family   = "tank",
            maxSlope = 36,     -- steepest traversable slope in degrees
            turnRate = 1024,
        },
        maxVelocity = 42,      -- elmos/second
        maxAcc      = 0.5,     -- elmos/second²
        maxDec      = 1.5,

        -- ── Construction ─────────────────────────────────────────────
        workerTime      = 200, -- build power provided when assisting
        buildCostMetal  = 0,   -- Commanders are spawned free by the engine
        buildCostEnergy = 0,
        buildTime       = 1,

        -- ── Economy ──────────────────────────────────────────────────
        metalMake  = 1,        -- passive metal income per second
        energyMake = 20,       -- passive energy income per second

        -- ── Weapons ──────────────────────────────────────────────────
        weapons = {
            { def = "COMMANDER_LASER" },
        },

        -- ── Misc ─────────────────────────────────────────────────────
        commander     = true,  -- marks this as the starting Commander unit
        sightDistance = 660,
    },
}
<translate> Warning</translate> <translate> Warning:</translate> The UnitDefs table available inside gadgets and widgets is not the same table as the one in def files. After loading, the engine parses definitions into internal structures and re-exposes them with different key names (e.g. buildCostMetal becomes metalCost) and different value scales. Never copy-paste keys between def files and wupget code.

Notable unit def keys

Key Type Notes
name string Display name shown in the UI. Not the same as the def name (the Lua table key).
health number Maximum hit points.
armorType string Armour class name. Controls how much damage weapons of each type deal.
moveDef table Inline movement class. family can be tank, kbot, hover, ship, boat, amphibious, submarine, or air.
maxVelocity number Top speed in elmos/second.
buildCostMetal number Metal cost. Constructor must have enough free income to begin building.
buildTime number Build time in seconds at 1 build power. Actual time = buildTime / workerTime.
workerTime number Build power this unit contributes when constructing or assisting.
weapons table Up to 3 entries; each a table with a def key naming a WeaponDef.
commander bool Marks this unit as a Commander for lobby/engine purposes.
extractsMetal number > 0 means this unit extracts metal when placed over a metal spot.
customParams table Arbitrary string/number key-value pairs preserved by the engine for gadget use.

For the full list of unit def keys and their effects see the Recoil Lua API documentation.

Unit types overview

Recoil games traditionally group units into several broad categories. Understanding these helps when designing your roster:

Ground vehicles (family
tank)
Typically heavy armour, strong weapons, poor slope climbing. Sluggish turning, struggles on hills. Best for open terrain and armour-versus-armour engagements.
K-bots / bipeds (family
kbot)
Lighter and cheaper than vehicles. Excellent slope climbing — can go where tanks cannot. Smaller profile, so they slip through narrow gaps in terrain or walls.
Aircraft (family
air)
Ignores terrain completely. Fastest units. Roles: bombers, gunships, scouts, air superiority fighters, torpedo bombers, transports. Countered by anti-air (AA) units and structures. Expensive and fragile compared to ground units.
Ships (family
ship / boat)
Powerful weapons and heavy armour, but confined to water. Long effective range. Very expensive. Vulnerable to submarines and aircraft with torpedoes.
Hovercraft (family
hover)
Amphibious — traverse both land and water. Weaker than dedicated land vehicles and ships, but unique in their flexibility. Poor slope climbing.
Amphibious units (family
amphibious)
Can operate both underwater and on the surface. Slow. Excellent for surprise attacks and on maps with mixed terrain.
Structures / towers
Fixed-position buildings. Includes factories, resource extractors, power generators, defence turrets, radar towers, and storage. Cannot move.

Def pre- and post-processing

The engine reads gamedata/defs.lua (provided by basecontent) to orchestrate def loading. It runs gamedata/unitdefs_pre.lua first (populates a Shared table visible to all def files), then loads every units/*.lua, then runs gamedata/unitdefs_post.lua.

-- gamedata/unitdefs_post.lua

-- Normalise key casing so post-processing code is consistent
for _, unitDef in pairs(UnitDefs) do
    lowerkeys(unitDef)   -- lowerkeys() is provided by basecontent
end

-- Example: give every unit 10% bonus health
for _, unitDef in pairs(UnitDefs) do
    if unitDef.health then
        unitDef.health = unitDef.health * 1.1
    end
end
lowerkeys() converts all keys to lowercase in-place. If your def files are consistent in their casing you don't need it, but it prevents subtle bugs when multiple contributors write defs in different styles.

7. Weapon Definitions

Weapon defs follow the same pattern as unit defs — Lua files in weapons/ returning tables.

-- weapons/laser.lua

return {
    COMMANDER_LASER = {
        name        = "Commander Laser",
        description = "Short-range direct-fire laser.",

        -- Weapon physics model
        -- Options: Cannon, LaserCannon, MissileLauncher, AircraftBomb,
        --          BeamLaser, LightningCannon, Flame, TorpedoLauncher, ...
        weaponType  = "LaserCannon",

        -- Only engage targets in these categories
        onlyTargetCategory = "SURFACE",

        -- ── Visuals ──────────────────────────────────────────────────
        rgbColor        = "1 0.2 0.2",    -- beam colour (R G B, 0-1 each)
        rgbColor2       = "1 0.9 0.9",    -- colour at the far end of the beam
        beamtime        = 1,              -- visual beam length in elmos
        thickness       = 3,
        corethickness   = 0.3,
        laserflaresize  = 8,

        -- ── Ballistics ───────────────────────────────────────────────
        projectilespeed = 800,  -- elmos/second
        range           = 350,  -- elmos
        reloadtime      = 1.5,  -- seconds between shots
        burst           = 1,    -- shots per fire command
        accuracy        = 0,    -- spread in degrees (0 = perfect accuracy)

        -- ── Damage ───────────────────────────────────────────────────
        damage = {
            default = 50,       -- applies to armour types not explicitly listed
        },
        areaOfEffect = 8,   -- splash radius in elmos (0 = single target)
        impulse      = 0,   -- knockback force
        firestarter  = 0,   -- chance to ignite (0 = no fire)
    },
}

Like unit defs, you can post-process weapon defs in gamedata/weapondefs_post.lua.

WeaponDefs inside wupgets is 0-indexed (unlike UnitDefs and FeatureDefs which are 1-indexed). Beware of for i = 1, #WeaponDefs do — this will skip the first weapon def.

8. Feature Definitions

Features are non-commandable objects on the map: trees, rocks, wreckage, and any other static props. They can block pathfinding, provide reclaim value, and leave behind other features when destroyed.

-- features/trees.lua

return {
    tree = {
        description = "A tree.",
        object      = "tree.dae",   -- model in objects3d/
        damage      = 50,           -- HP before the feature is destroyed
        metal       = 0,            -- metal gained when reclaimed
        energy      = 10,           -- energy gained when reclaimed
        blocking    = true,         -- blocks ground unit pathfinding
        reclaimable = true,
        deathFeature = "",          -- feature name to spawn on death (blank = nothing)
    },
}
Tip

9. Game Rules (modrules.lua)

gamedata/modrules.lua configures engine-level behaviour not driven by unit or weapon defs.

-- gamedata/modrules.lua

return {
    system = {
        -- Starting resources (also overridable via ModOptions or start script)
        startMetal  = 1000,
        startEnergy = 1000,

        -- Maximum units per team (0 = unlimited)
        maxUnits    = 1000,

        -- 0 = game continues on Commander death
        -- 1 = team loses when Commander dies
        -- 2 = lineage mode (Commander transferred to surviving ally)
        gameMode    = 1,

        -- Repair speed cap (% max health per second; 0 = unlimited)
        repairSpeed = 0,

        -- Whether resources are recovered when resurrecting a unit
        resurrectHealth = false,

        -- Metal recovered when reclaiming a living unit (1 = full cost)
        reclaimUnitMethod = 1,

        -- Whether destroyed units leave wreck features
        featureDeathResurrect = true,
    },
}

10. Lua Scripting

Lua is the scripting language for everything above the engine's C++ layer. The engine provides several isolated Lua environments:

Environment Entry point Purpose
LuaRules (synced) luarules/main.lua Authoritative game logic. All clients run the same code to stay in sync.
LuaRules (unsynced) luarules/draw.lua Effects and UI driven by game logic but not part of the simulation.
LuaUI luaui/main.lua Player-facing UI: HUD, minimap overlays, build menus, etc.
LuaGaia luagaia/main.lua Logic for the neutral Gaia team (wrecks, map features, world events).
LuaIntro luaintro/main.lua Loading screen and pre-game briefings.
LuaMenu luamenu/main.lua Main menu (pre-game). Requires a menu archive.

Basecontent provides default main.lua entry points and the wupget handler boilerplate so you can start writing gadgets and widgets without building a handler yourself.

Synced vs unsynced

  • Synced code participates in the game simulation. All clients run exactly the same instructions in exactly the same order — this is what keeps the game deterministic. Only synced code can affect units, resources, or any other simulation state.
  • Unsynced code runs on each client independently. It can read simulation state for display purposes, but cannot modify it. UI, rendering, and sound belong here.
<translate> Warning</translate> <translate> Warning:</translate> Calling unsynced functions from synced code causes a desync error. When writing gadgets, guard appropriately with gadgetHandler:IsSyncedCode().

Your first gadget

A gadget is a Lua file loaded by the gadget handler from luarules/gadgets/.

-- luarules/gadgets/game_end.lua
-- Ends the game when a team's Commander is destroyed.

local gadget = gadget ---@type Gadget

function gadget:GetInfo()
    return {
        name    = "Game End Conditions",
        desc    = "Ends the game on Commander death.",
        author  = "YourName",
        date    = "2025",
        license = "GNU GPL, v2 or later",
        layer   = 0,
        enabled = true,
    }
end

-- Only run the logic in synced mode
if not gadgetHandler:IsSyncedCode() then return end

-- Track alive Commanders by team
local commanderByTeam = {}

function gadget:UnitFinished(unitID, unitDefID, teamID)
    local def = UnitDefs[unitDefID]
    if def and def.isCommander then
        commanderByTeam[teamID] = unitID
    end
end

function gadget:UnitDestroyed(unitID, unitDefID, teamID)
    if commanderByTeam[teamID] == unitID then
        commanderByTeam[teamID] = nil
        local allyTeamID = Spring.GetTeamAllyTeamID(teamID)
        local hasCommander = false
        for _, ally in ipairs(Spring.GetTeamList(allyTeamID)) do
            if commanderByTeam[ally] then
                hasCommander = true
                break
            end
        end
        if not hasCommander then
            Spring.GameOver({ allyTeamID })
        end
    end
end

Your first widget

A widget is a Lua file loaded by the widget handler from luaui/widgets/.

-- luaui/widgets/resources_display.lua
-- Draws current metal and energy in the corner of the screen.

local widget = widget ---@type Widget

function widget:GetInfo()
    return {
        name    = "Resources Display",
        desc    = "Shows current resource totals.",
        author  = "YourName",
        date    = "2025",
        license = "GNU GPL, v2 or later",
        layer   = 0,
        enabled = true,
    }
end

function widget:DrawScreen()
    local metal, _, metalIncome = Spring.GetTeamResources(Spring.GetMyTeamID(), "metal")
    local energy, _, energyIncome = Spring.GetTeamResources(Spring.GetMyTeamID(), "energy")
    if not metal then return end

    gl.Color(1, 1, 1, 1)
    gl.Text(string.format("Metal: %.0f (+%.1f/s)", metal, metalIncome), 10, 60, 14, "s")
    gl.Text(string.format("Energy: %.0f (+%.1f/s)", energy, energyIncome), 10, 40, 14, "s")
end

Callins and callouts

Callins are functions in your code the engine invokes at the right moment: UnitCreated, UnitDestroyed, GameOver, DrawScreen, Update, and many more. You can list all available callins at runtime with Script.GetCallInList().

Callouts are Spring.* functions you call into the engine: Spring.GetTeamResources, Spring.GiveOrderToUnit, Spring.Echo, etc. The full reference is at beyond-all-reason.github.io/spring/ldoc/.

Communicating between environments

The Lua environments are isolated from each other by design. Common bridges:

Method Direction Notes
WG / GG table Within one environment Widgets share the global WG table; gadgets share GG.
SendToUnsynced / RecvFromSynced Synced → Unsynced Pass data from game logic to rendering or UI without exposing it as a callout.
Spring.SendLuaRulesMsg / gadget:RecvLuaMsg LuaUI → LuaRules UI triggers a game action beyond normal unit orders.
Spring.SendLuaUIMsg / widget:RecvLuaMsg LuaUI ↔ all LuaUIs Broadcast UI state to other players' widgets.
Rules params (Spring.SetRulesParam) LuaRules → all Expose synced values as read-only key/value pairs readable by any environment.

See Recoil:Wupget Communication for detailed examples and best practices.

Creating user interfaces

There are two main approaches to UI in Recoil:

  • OpenGL drawing — use gl.* callouts directly (as in the widget example above) or a higher-level framework like FlowUI or Chili.
  • RmlUi — Recoil has built-in support for an HTML/CSS-style reactive UI framework. Recommended for complex interfaces. See Recoil:RmlUI Starter Guide.

11. Running Your Game

The engine is launched with a start script — a plain text file (conventionally .sdf) describing who is playing, which game, and which map.

Minimal start script

[GAME]
{
    GameType = My Recoil Game;   -- must match modinfo.lua name or shortName
    MapName  = my_map.smf;       -- the .smf file inside the map archive

    IsHost   = 1;
    MyPlayerName = Dev;

    [PLAYER0]
    {
        Name      = Dev;
        Password  = ;
        Spectator = 0;
        Team      = 0;
    }

    [TEAM0]
    {
        TeamLeader = 0;
        AllyTeam   = 0;
        RgbColor   = 0.1 0.4 1.0;
        Side       = MyFaction;   -- must match a side declared in modinfo.lua
        StartPosX  = 4000;
        StartPosZ  = 4000;
    }

    [ALLYTEAM0]
    {
        NumAllies = 0;
    }
}

Save as game.sdf in your Recoil data directory and launch:

./spring game.sdf          # Linux
spring.exe game.sdf        # Windows
<translate> Warning</translate> <translate> Warning:</translate> If the engine cannot find your game, check that the folder name ends in exactly .sdd (case-sensitive on Linux) and that modinfo.lua is at the root of that folder, not in a subfolder.

Headless mode

For automated testing or AI-vs-AI matches without graphics:

./spring_headless game.sdf

Widgets still run in headless mode, making it useful for data-gathering scripts and CI testing.

Hot-reloading Lua scripts

While a game is running you can reload Lua environments without restarting:

/luarules reload    ← reload LuaRules gadgets
/luaui reload      ← reload LuaUI widgets

This dramatically speeds up iteration. Keep /cheat toggled on during development so these commands are available.


12. Useful Debug Commands

In-game chat commands prefixed with / control engine and game state. Enable cheats first with /cheat:

Command Effect
/cheat Toggle cheats on/off. Required for most debug commands.
/give [unitname] Spawn a unit at the cursor position.
/destroy Destroy all selected units immediately.
/invincible Toggle invincibility for selected units.
/godmode Toggle god mode — all units obey all players.
/speed [x] Set game speed multiplier (e.g. /speed 5).
/luarules reload Reload all LuaRules gadgets.
/luaui reload Reload all LuaUI widgets.
/editdefs Enter def-editing mode. Lets you change unit defs live via Lua assignment (development only, desyncs in multiplayer).
Spring.Echo("msg") Print a message to the in-game console from any Lua environment.
Tip

13. Resources and Economy

Recoil's economy follows the TA tradition: resources are a flow rate, not a stockpile of discrete units.

Metal

Metal is the primary construction resource, gathered by:

  • Metal extractors (mexes) — units with extractsMetal > 0 placed over metal spots defined in the map.
  • Metal converters / makers — units that convert energy into metal at an exchange rate.
  • Reclaiming — construction units can reclaim wreckage, trees, and living units/structures to recover metal.

Metal spots are defined in the map archive (as an SMF metal intensity map or a Lua table) and read with Spring.GetMetalMapSize() and related callouts.

Energy

Energy is produced by:

  • Solar collectors — cheap, stable, small output.
  • Geothermal plants — placed on geothermal vents for high, free output.
  • Fusion reactors — high output, high cost, large explosion on death.
  • Wind generators — cheap, output depends on map wind setting.

Most level-2 weapons and some movement types consume energy to fire or operate. An energy deficit stalls construction and disables energy-hungry weapons.

Tip

Storage

By default teams have a small resource cap. Build storage structures to increase it. metalStorage and energyStorage are unit def keys you set on storage buildings.


14. Maps and Map Integration

Maps are separate archives placed in maps/. They are not bundled with your game — players download them independently.

The map defines:

  • Height terrain data (the .smf file)
  • Sky, water, atmosphere settings
  • Metal spot positions
  • Feature placement (trees, rocks)
  • Optional mapinfo.lua with gravity, max wind, and other overrides

Reading map data from Lua

-- Inside a gadget (synced):
local mapX, mapZ = Game.mapSizeX, Game.mapSizeZ
local metalX, metalZ = Spring.GetMetalMapSize()

-- Place a unit at the start position of team 0:
local x, y, z = Spring.GetTeamStartPosition(0)
Spring.CreateUnit("commander", x, y, z, "n", 0)

Restricting map compatibility

Check map properties at startup and call Spring.GameOver if requirements are not met:

function gadget:GamePreload()
    local mapOptions = Spring.GetMapOptions()
    if mapOptions.myRequiredKey ~= "expectedValue" then
        Spring.Echo("This map is not compatible with MyGame!")
        Spring.GameOver({})
    end
end

15. Packaging and Distribution

When ready to distribute, compress your .sdd folder contents:

# Linux — create .sdz (zip)
cd games/mygame.sdd
zip -r ../mygame-0.1.0.sdz .

# Linux — create .sd7 (7-zip; better compression)
cd games/mygame.sdd
7z a ../mygame-0.1.0.sd7 .
<translate> Warning</translate> <translate> Warning:</translate> Make sure modinfo.lua ends up at the root of the archive, not inside a subfolder. Some zip tools create an extra folder level automatically — check the archive structure before distributing.

Rapid

Rapid is the standard package distribution system for Recoil games. It allows lobby clients such as BAR Lobby or Chobby to download and update games automatically. See the Rapid documentation for how to host and tag releases.


16. Next Steps

With a working minimal game, the usual next steps are:

More unit types
Add builders, resource extractors, power generators, turrets, and mobile combat units following the same unit def pattern. Give each its own file in units/.
MoveClasses
Define movement types in gamedata/movedefs.lua to control how units traverse terrain: hover, boat, amphibious, kbot, tank, aircraft. Each family has different slope tolerance and water behaviour.
Armour classes
Define armour types in gamedata/armorclasses.lua and reference them in unit defs. Then use the damage table in weapon defs to give different damage values against different armour classes.
Resource extractors and converters
A unit with extractsMetal > 0 placed over a metal spot extracts metal. Add an energy converter unit to turn metal into energy and vice versa. Add storage buildings with metalStorage / energyStorage.
Tech levels and factories
A factory unit has a buildOptions list of unit def names it can produce. Level-2 factories can be unlocked by a prerequisite gadget once a level-2 lab is built.
AI support
The Skirmish AI interface lets players add bot opponents without any gadget work. For custom AI behaviour, write a [Lua AI] inside your game's Lua scripting.
Full UI
The basecontent widget handler gives you a functional HUD for free. For a polished interface, use RmlUI for HTML/CSS-style reactive widgets, or raw gl.* OpenGL callouts for maximum control.
Map creation
Creating your own map requires MapConv (height and texture compilation) and a height editor such as SpringMapEdit. See the Recoil map documentation for the full pipeline.
Multiplayer and dedicated servers
Replays are recorded automatically when RecordDemo=1 is set in the start script. For large-scale multiplayer, use the dedicated binary (spring_dedicated) with an autohost such as SPADS.

17. Further Reading

On this wiki

External resources