Knights Lua overview

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.

Modules

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.

Module basics: init.lua, RegisterMod, GetRegisteredMod

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:

The "kts" table

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.

Example of a Lua module

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:

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:

And of course, you can check out the Knights Lua reference documentation for more helpful information.