Skip to content
Pieter Vandecandelaere edited this page Mar 6, 2025 · 22 revisions

Lua Documentation (WIP)

Introduction

Lua support in KeeperFX is currently incomplete and not yet functional. The work-in-progress implementation is available in this PR. This documentation is also a work in progress and subject to change.

KeeperFX uses LuaJIT, which is based on Lua 5.1. For general Lua language reference, see the official Lua 5.1 manual. This document focuses specifically on KeeperFX's Lua implementation.

Setup IDE

The recommended IDE for writing Lua scripts in KeeperFX is VS Code with the sumneko Lua extension.

Note:

  • This is the second one when searching for lua extension in vscode, the first one isn't as comprehensive as the sumneko one

Steps to Set Up:

  1. Install the Lua extension in VS Code.
  2. Set the workspace folder and path. (TODO: more detail)
  3. Change the Lua runtime to LuaJIT:
    • Open VS Code Settings (Ctrl + ,).
    • Search for "Lua › Runtime › Version."
    • Set it to LuaJIT.

Triggers

Lua scripts in KeeperFX are event-driven, meaning they respond to in-game events (triggers). All available triggers are defined in fxdata/lua/triggers.lua(TODO: Make link to final file in github).

Example: Detecting a Creature Being Slapped

The following example listens for when a creature is slapped and executes an action at the slap's location:

function ThingToDoWhenSlapIsCast(eventData, triggerData)
  ThingToDoAtSlapPosition(eventData.stl_x, eventData.stl_y)
end

function OnGameStart()
  RegisterPowerCastEvent(ThingToDoWhenSlapIsCast, "POWER_SLAP")
end
  • OnGameStart is a fixed function that runs at level start. It is commonly used to set up triggers.
  • RegisterPowerCastEvent binds an event listener for when a spell is cast.

Registering an Event Listener

The function below registers an event trigger for a spell being cast:

---@param action function|string Function to call when the event occurs
---@param powerKind? power_kind Optional: Specific spell type that triggers the event
---@return Trigger
function RegisterPowerCastEvent(action, powerKind)
    local trigData = {PowerKind = powerKind}
    local trigger = CreateTrigger("PowerCast", action, trigData)
    if powerKind then
        TriggerAddCondition(trigger, function(eventData, triggerData)
            return eventData.PowerKind == triggerData.PowerKind
        end)
    end
    return trigger
end
  • If powerKind is specified, the event will only trigger for that spell type.
  • The event type is "PowerCast".

the OnPowerCast has more details on all the data that is accessible related to the event

--- Called when a spell is cast on a unit
--- @param pwkind power_kind Spell type
--- @param caster Player Caster of the spell
--- @param target_thing Creature Target creature (if applicable)
--- @param stl_x integer X-coordinate (if applicable)
--- @param stl_y integer Y-coordinate (if applicable)
--- @param splevel integer Spell level
function OnPowerCast(pwkind, caster, target_thing, stl_x, stl_y, splevel)
    local eventData = {
        Thing = target_thing,
        PowerKind = pwkind,
        Player = caster,
        stl_x = stl_x,
        stl_y = stl_y,
        splevel = splevel
    }
    ProcessEvent("PowerCast", eventData)
end

Note:

  • Not all spells target specific units or locations, those values would then just be nil

commands

all native functions can be found in this file, https://github.com/PieterVdc/keeperfx/blob/lua/config/fxdata/lua/native.lua the file makes the IDE aware of the functions DK provides, and doubles as documentation if you want to find something

the lua has support for most of the commands in the DKscript language, while also repeated in the native.lua file, the dkscript documentation could also be referenced for the overlapping ones, some commands might be changed, and some might only exist in 1 of the 2 or have an alternative way of doing so Level-Script-Commands

the flow control is also different NEXT_COMMAND_REUSABLE is not a thing in lua, all commands are always reusable in lua you have to control flow in other ways, GameStart for example will generally only be run once, so all initialization code goes there

the special IF's in dkscript are also replaced with different syntax you just compare variables which you can fetch like this

  • PLAYER0.MONEY
  • IF_ACTION_POINT todo
  • IF_AVAILABLE PLAYER0.AVAILABLE.WORKSHOP
  • IF_CONTROLS PLAYER0.CONTROLS.IMP
  • IF_SLAB_OWNER and IF_SLAB_TYPE slb = GET_SLAB(x,y) slb.owner slb.type slb.style
  • IF_ALLIED PLAYER0.ALLIED_WITH(PLAYER2)

the commands related to flags are removed to assign to a flag simply assign PLAYER0.FLAG0 = 1 calculations can also be done in lua, so no need for COMPUTE_FLAG either PLAYER0.CAMPAIGN_FLAG2 = PLAYER0.MONEY + 75 * PLAYER0.IMP

the assignable variables, are regular flags eg. PLAYER0.FLAG0 = 1

campaign flags eg. PLAYER0.CAMPAIGN_FLAG2 = 1´

sacrificed creatures eg. PLAYER0.SACRIFICED.IMP = 1

and temple rewarded creatures eg. PLAYER0.REWARDED.IMP = 1

attempting to assign to a variable that's not assignable will throw an error

commands that select a creature by criteria in DKScript now take a Creature directly, to still fetch them by criteria you can use getCreatureByCriterion

savegames

all globals should be the global Game table, so it gets serialized