Lua support in KeeperFX is currently in early stages. 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.
Example maps can be found here.
The recommended IDE for writing Lua scripts in KeeperFX is VS Code with the sumneko Lua extension.
Note:
Ctrl + ,).Lua scripts in KeeperFX are event-driven, meaning they respond to in-game events (triggers). All available triggers are defined in fxdata/lua/triggers/Events.lua.
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.---@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
--- Called when a spell is cast on a unit
--- @param pwkind power_kind
--- @param caster Player
--- @param target_thing Creature
--- @param stl_x integer
--- @param stl_y integer
--- @param splevel integer
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
💡 Not all spells target a unit or a position. Some fields may be
nil.
All native bindings to the game can be found in fxdata/lua/bindings folder
This folder serves two purposes:
KeeperFX Lua supports most commands from the original DKScript language. While many are duplicated in native.lua, the DKScript documentation is also useful. Be aware:
NEXT_COMMAND_REUSABLE is not needed — all Lua commands are reusable.if, while, for, etc.).OnGameStart() for initialization logic.Player variables:
PLAYER0.MONEY
Action Point trigger:
RegisterOnActionPointEvent(function()
AddPartyToLevel(PLAYER_GOOD, "PARTY2", 1, 1)
end, 2, PLAYER0)
Checks:
PLAYER0:available("WORKSHOP")
PLAYER0:controls("IMP")
Slab properties:
local slab = GetSlab(x, y)
slab.owner
slab.type
slab.style
Allied check:
PLAYER0:allied_with(PLAYER2)
Regular flags:
PLAYER0.FLAG0 = 1
Campaign flags:
PLAYER0.CAMPAIGN_FLAG2 = 1
Math operations:
PLAYER0.CAMPAIGN_FLAG2 = PLAYER0.MONEY + 75 * PLAYER0.IMP
Assigning to an invalid or read-only variable will throw an error.
Note on naming: Avoid using generic flags like
PLAYER0.FLAG0unless necessary. Prefer descriptive names:
Game.roundCounter = roundCounter
--multiple ways to get them, the GetCreatureByCriterion functions similar to how they were fetched in dkscript
local imp = GetCreatureByCriterion (PLAYER0, "IMP", "MOST_EXPERIENCED")
-- then interact with them
imp:level_up(2)
imp:kill()
SetGenerateSpeed(300)
StartMoney(PLAYER0, 7500)
CreatureAvailable(PLAYER1, "TROLL", true, 2)
--when spawning a creature you could store it in a variable, this var can then later be used to do things with said unit
local dragon = AddCreatureToLevel(PLAYER0, "DRAGON", 1, 3, 200)
CreateParty("RAIDERS")
AddToParty("RAIDERS", "TUNNELLER", 5, 0, "ATTACK_DUNGEON_HEART", 0)
AddTunnellerPartyToLevel(PLAYER0, "RAIDERS", 2, "DUNGEON", 1, 5, 0)
--when one of PLAYER0's units enters actionpoint 3, show message 42
RegisterOnActionPointEvent(function() DisplayInformation(42, PLAYER0) end, 3, PLAYER0)
| Field Name | Description |
|---|---|
PLAYER0.MONEY |
Amount of gold the player currently has |
PLAYER0.IMP |
Number of imps controlled |
PLAYER0.FLAG0 |
General-purpose flag (avoid if not needed) |
PLAYER0.CAMPAIGN_FLAG2 |
A campaign-wide persistent flag |
these are the same as Dkscript, so find full list in dkscript document
example custom keeperpower
in the cfg some functions
[power7]
Name = POWER_TELEPORT_ORC
...
UseFunction = magic_use_power_teleport_orc_to_heart
in templates.lua you'll find the possible fields that take functions, these will be structured as cfgname_block_field
so in this case
cfgname magic
block [powerX]
field UseFunction
so
Magic_power_UseFunction_template
-- -first check if the power is castable, then call the Pay_for_power fuction to pay,
--- if the player doesn't havve the gold said function will return false, implement it in a way that makes it return early in that case
---@param player Player player who cast the power
---@param power_kind power_kind what power was cast
---@param power_level integer how much the power was charged up
---@param stl_x integer|nil the x coordinate of the tile where the power was cast, will be nil for powers that are cast on the entire level
---@param stl_y integer|nil the y coordinate of the tile where the power was cast, will be nil for powers that are cast on the entire level
---@param thing Thing|nil the thing that was hit by the power, will be nil for powers that are cast on the entire level or on a tile
---@param is_free boolean if the power should be cast for free (no gold cost)
---@return -1|0|1 if the power was cast successfully 1, if it failed -1, if it was cast but the power didn't do anything 0
function Magic_power_UseFunction_template(player,power_kind,power_level,stl_x,stl_y,thing,is_free) return 0 end
to implement this function make a function that uses the same parameters
---this will teleport orcs to the heart of the caster
function magic_use_power_teleport_orc_to_heart(player,power_kind,power_level,stl_x,stl_y,thing,is_free)
if (thing.model ~= "ORC") then
-- 0 means power simply doesn't have a valid target, so so simply does nothing
return 0
end
--pay for the power, the function will return false if the player doesn't have enough gold
if (not PayForPower(player, power_kind, power_level, is_free)) then
-- -1 means it failed, and will make a reject sound
return -1
end
--player here is used as a location, so it will teleport the player to the heart
thing:teleport(player, "EFFECT_EXPLOSION_4")
-- 1 means it was cast successfully
return 1
end
Thanks to annotations in native.lua, the Lua language server supports:
This improves the experience in VS Code significantly.
All persistent data should be stored in the global Game table:
Game.hasTriggeredEvent = true
Game.playerScore = 1234