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.)

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 single folder on disk. Modules can also "import" other modules.

Knights itself provides a module called classic which contains all the base data required to recreate the classic Amiga Knights game from 1994. Other, user-provided modules will typically import classic 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/server folder of a Knights installation. Once installed, a module needs to be "activated" by editing knights_data/server/main.lua and adding a suitable require line. For example, after installing a module named "spiders", the user would add require("spiders") to the bottom of main.lua.

Knights itself comes with three modules pre-installed:

Additional modules have been provided by the community, e.g. there is a list on the old Knights Forum here.

init.lua, module, require

All modules need to contain, at minimum, a file called init.lua. This Lua script should return a table containing any functions or other values that the module wishes to make available to other modules. A straightforward example of this can be seen in the init.lua file from the "menu" module, which defines a table consisting of four Lua functions and then returns it.

An alternative way to handle this is to write module(...) (on a single line) at the top of init.lua. Then, any "global" variables defined by your module will automatically be packaged up into a table and made available for other modules to use, and there is no need to write an explicit return statement at the bottom of your init.lua file. An example of this way of doing things is found in the "classic" module's init.lua file.

To "import" functions or data from another module, you can use the require Lua function. For example, writing the code:

local M = require("menu")

will import the menu module. The four functions defined by that module will now be available for you to use, under the names M.get_menu_item, M.get_menu_choice, and so on. A similar procedure will import the classic module:

local C = require("classic")

Your module can now refer to any (global) functions or variables defined in the classic module by adding the C. prefix. For example, C.floor allows you to access the floor table defined in tiles.lua.

Your module can also import other Lua files (within the same module) by using dofile, if desired.

Note: For those familiar with the module function introduced in Lua 5.1: the Knights module function is actually a custom implementation of this function (or a simplified version of it). It does not use the "official" implementation from Lua 5.1. Thus, the deprecation of this function in Lua 5.2 does not affect Knights.

The "kts" table

Another thing to be aware of is that 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, we will explain how a simplified, minimalistic version of the "Spiders" mod could be implemented from scratch.

First of all, we need to create a folder named spiders in the knights_data/server 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 at this time, 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:

module(...)

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 "classic" module
local C = require("classic")

-- 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(C.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*C.ts,
    melee_downswing_time = 3*C.ts,
    melee_damage         = C.rng_range(1,2),
    melee_stun_time      = C.rng_time_range(2,3),
    melee_tile_damage    = 1
}

-- Define the spider monster type

spider_monster = kts.MonsterType {
    type = "walking",

    -- general properties:
    health = C.rng_range(1, 4),    -- somewhere beetween 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 = C.all_open_pit_tiles
}

-- Function to add some spiders to the dungeon initially

function add_spiders(num)
    C.add_initial_monsters(spider_monster, num)
    C.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 = "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:

The mod is now complete!

The final step is to edit knights_data/server/main.lua to add the line

require("spiders")

at the end of the file. (Put simply, main.lua is the main Lua file which is executed by the Knights code when it starts up. If nothing in this file refers to the spiders module, then the module will not be loaded.)

You should then be able to boot up the game and observe the new "Spiders" option in the menu.

Once again, the "real" Spiders mod (from the Knights forum) is better than this one, so you should use that if you actually want to play a game with spiders. The above was just a simplified version to help teach how a Knights module can be built, without getting bogged down in too many details. Readers are advised to read the code for the "real" Spiders mod, as well as the other mods available from the Knights forum, if they want to see more examples of the Knights module system being used in practice. Also, of course, the Knights Lua reference documentation provides lots of helpful info.