This page provides an introduction to working with the Knights Lua system. This will be useful to anyone wanting to create mods (modifications) for the game. (A basic knowledge of Lua programming is assumed.)
It is planned to add Steam Workshop support for Lua mods soon. When that is ready, this page will be updated. For now, mods must be installed manually by copying files to the game's data directory.
All Knights game data files are organised into "modules". A module is basically a collection of Lua scripts, graphics, sounds, map data, and other files, all stored together in a folder on disk. Modules can also import data or code from other modules.
Knights itself provides a module called base which contains all the base data required to recreate the classic Amiga Knights game from 1994. Other, user-provided modules will typically import base and make changes to it, e.g. adding new menu options, changing the graphics or sounds, adding new monsters and items, or whatever else the module wants to do. Alternatively, some modules (such as "toolbox" by ImpassIve_rus) do not directly modify the base game, but instead provide tools for mod-makers to use.
Modules are installed by copying the module's folder directly into the knights_data/modules folder of a Knights installation. Once installed, a module needs to be "activated" by editing knights_data/modules/modules.txt and adding the module's folder name. For example, if you copied spiders_v3 to the modules directory, you would add spiders_v3 to the modules.txt file. Note that modules are loaded in the order listed – this is important if there are dependencies between modules.
Knights itself comes with two modules pre-installed:
Additional modules have been provided by the community, e.g. there is a list on the old Knights Forum here. Note: these old mods will need updating before they can work with the new Knights module system.
All modules need to contain, at minimum, a file called init.lua (at the root of the module's folder). This file is expected to call mod.RegisterMod immediately when starting up, passing the module name and a version string, e.g.:
mod.RegisterMod{ name = "my_module", version = "1.0" }
Note that setting a version is optional, but it's recommended as it will help keep track of different versions of your mod.
Note also that the name passed to mod.RegisterMod doesn't have to be the same as the folder name on disk.
After calling mod.RegisterMod, the global environment (_ENV) is changed to point to a new table which is created specifically for your module. Any global variables you create will become part of your module's namespace.
To access global variables created by other modules, you can use require or mod.GetRegisteredMod. For example, this code:
local B = require("base")
will make B point to a table consisting of all the global names exported by the base module. You could then, for instance, use base.i_hammer to refer to the Hammer item type, as defined in base/items.lua.
The difference between require and mod.GetRegisteredMod is that the former throws an error if the module isn't found, while the latter only returns nil in that case. Also, mod.GetRegisteredMod allows you to specify a minimum required version, if you wish. See the individual pages for those functions for more details.
A few other functions are provided for your convenience:
Knights itself makes available a large API (i.e. a set of functions and other variables provided by the game) for Lua modules to use. Almost all of these are defined within a special table named kts. This table is always available (it is defined as part of the Knights Lua environment itself) and you don't have to do anything special to import it.
For example, Lua code is free to call kts functions such as kts.Tile, kts.AddMonsters, kts.PlaySound, or many others, whenever it makes sense to do so.
The main page of the Knights Lua docs contains an alphabetical list of all Knights API functions and variables.
Please note that most kts functions have restrictions on when they can be called. For example, calling kts.AddMissile outside of active gameplay (e.g. calling it directly from your init.lua code) does not make sense, and will result in an error message. Similarly, attempting to create new graphics, tiles etc. (by calling functions such as kts.Graphic or kts.Tile) during gameplay does not work – all such objects must be created during initialization, before the quest selection screen is entered. Unfortunately, there is not currently any documentation explaining when it is or is not legal to call any given kts function – but in most cases, it should be possible to figure this out by common sense.
We will now give a small example of a Lua module, to help explain in more detail how modules can be created.
For this example, we will use a simplified version of the "Spiders" module by Worst. The original "Spiders" module can still be found in the old Knights forum. The version found there includes many more features than the one we are about to describe, including multiple spider types, spiders hiding in barrels, and so on, so if you actually want to play with the Spiders mod, then check out that forum thread.
With that out of the way, here is how to create the code and data for the simplified "Spiders" module.
First of all, we need to create a folder named spiders in the knights_data/modules folder.
Next, we create a sub-folder named gfx containing the art assets for the spider mod. This consists of files spid1e.bmp, spid2n.bmp, etc. which contain the various animation frames for the spiders, together with gfx/dead_spider.bmp for the dead spider image. (If you are following along yourself, you can get these files from the original spiders zip file in the above forum thread.)
(As a side note, all graphics loaded into Knights must currently be provided in the BMP format. Unfortunately the Knights codebase is quite old by now, and has not yet been updated to be able to handle more modern image formats such as PNG.)
Next, we create the Lua code. In this case, we will split the code between two files, spiders.lua and menus.lua. First of all, we create spiders/init.lua which just loads our two Lua files in sequence:
mod.RegisterMod{name = "spiders", version = "0.1"}
dofile("spiders.lua")
dofile("menus.lua")
Next, in spiders/spiders.lua we add the following code:
-- Highly simplified version of "Spiders" by Worst,
-- for teaching purposes.
-- This file just defines the new monster type, and also defines
-- a function "add_spiders" to actually add some spiders to the
-- dungeon.
-- Load the "base" module
local B = require("base")
-- Resource helper functions
function gfx(name, ofx, ofy, s1, s2)
return kts.Graphic("gfx/" .. name .. ".bmp", 255,255,0, ofx,ofy, s1,s2)
end
function anim(name, of, s1, s2)
return kts.Anim {
gfx(name .. "1n", of, of, s1,s2), -- Normal
gfx(name .. "1e", of, of, s1,s2),
gfx(name .. "1s", of, of, s1,s2),
gfx(name .. "1w", of, of, s1,s2),
gfx(name .. "2n", of, of, s1,s2), -- Melee backswing
gfx(name .. "2e", of, of, s1,s2),
gfx(name .. "2s", of, of, s1,s2),
gfx(name .. "2w", of, of, s1,s2),
gfx(name .. "3n", of, 16+of, s1,s2), -- Melee downswing
gfx(name .. "3e", of, of, s1,s2),
gfx(name .. "3s", of, of, s1,s2),
gfx(name .. "3w", 16+of, of, s1,s2),
gfx(name .. "4n", of, of, s1,s2), -- Just Hit.
gfx(name .. "4e", of, of, s1,s2),
gfx(name .. "4s", of, of, s1,s2),
gfx(name .. "4w", of, of, s1,s2)
}
end
function tile(name, of, s1, s2)
local g_tile = gfx(name, of,of, s1,s2)
return kts.Tile(kts.table_merge(B.floor, { graphic=g_tile, depth = -1 }))
end
-- Define Anim and Tile for a spider
a_spider1 = anim("spid", -3, 4,3) -- Animation
t_spider1 = tile("dead_spider", -3, 4,3) -- Death tile
-- Define a spider's weapon (item type)
spider_weapon = kts.ItemType {
-- Something beetween sword and axe;
-- Barely any tile damage.
type = "held",
melee_backswing_time = 2*B.ts,
melee_downswing_time = 3*B.ts,
melee_damage = B.rng_range(1,2),
melee_stun_time = B.rng_time_range(2,3),
melee_tile_damage = 1
}
-- Define the spider monster type
spider_monster = kts.MonsterType {
type = "walking",
-- general properties:
health = B.rng_range(1, 4), -- somewhere between zombies and bats
speed = 76, -- faster than zombies, but slower than bats
anim = a_spider1,
corpse_tiles = { t_spider1 },
-- properties specific to walking monsters:
weapon = spider_weapon,
-- list of tiles that spiders don't want to walk onto:
ai_avoid = B.all_open_pit_tiles
}
-- Function to add some spiders to the dungeon initially
function add_spiders(num)
B.add_initial_monsters(spider_monster, num)
B.add_monster_limit(spider_monster, num)
end
This code:
base module (so that we can re-use some of the code from it, like the B.add_initial_monsters function, the B.all_open_pit_tiles list, and similar things). gfx and anim, to create the required graphics. add_spiders which will be called by menus.lua. (Note: This function could potentially also be used by other modules that want to build on top of the Spiders module, if desired. Those other modules could do something like local S = require("spiders"), then they could call S.add_spiders as needed.) We can now write the code to add a "Spiders" item to the quest selection menu. The following code goes in spiders/menus.lua:
-- Continuation of the simplified "spiders" example based on the original
-- Spiders mod by Worst.
-- This file adds the "Number of Spiders" item to the menu.
-- Menu helper functions
function menu_item_exists(id)
for k,v in ipairs(kts.MENU.items) do
if v.id == id then
return true
end
end
return false
end
function add_menu_item_after(item_id, old_id)
if menu_item_exists(old_id) then
local pos
for k,v in ipairs(kts.MENU.items) do
if v.id == old_id then
pos = k
break
end
end
table.insert(kts.MENU.items, pos+1, item_id) -- Add after the specified choice
else
table.insert(kts.MENU.items, item_id) -- Add it at the end.
end
end
-- Menu item for spiders
spiders_menu_item = {
id = "spiders",
text_key = "spiders.spiders",
choice_min = 0,
choice_max = 5,
features = function(S)
-- Add spiders to the dungeon
if S.spiders > 0 then
local num_spiders = 2 * S.spiders + 1
add_spiders(num_spiders)
end
end
}
-- Add the new item to the menu
add_menu_item_after(spiders_menu_item, "bats")
This code:
Note that the "Spiders" menu item uses text_key = "spiders.spiders". This is an example of a localization key (see Localization). To set the corresponding localized string, we must add a file called spiders/localization_english.txt with the following contents:
spiders.spiders Spiders
This sets the name of the menu item in English to "Spiders". (You can also add localization files for other languages, if you wish.)
Notice that for the localization key, I used the mod name (spiders), followed by a dot, and then the actual key name (again spiders in this example). Following this convention helps to avoid naming conflicts with other modules (localization keys all go into a shared namespace, unfortunately).
The mod is now complete!
The final step is to edit knights_data/modules/modules.txt to add the line
spiders
at the end of the file. (Note that spiders must be added after base, otherwise it will not work – the line require("base") would fail.)
You should then be able to boot up the game and observe the new "Spiders" option in the menu.
If you want to see more examples of Knights Lua mods, then you can:
tutorial module from the game, which shows a few more examples of things you can do. base module code, to understand how the base game works. And of course, you can check out the Knights Lua reference documentation for more helpful information.