NAV
Lua

Introduction

Illarion game content like items, NPCs, quests, etc. is defined in scripts written in Lua 5.2 with Illarion specific extensions. This document describes these extensions. Those scripts can be found in the official repository, which has two branches. The master branch reflects the scripts used by the live game, while the develop branch is used to prepare the next release. You can fork that repository on GitHub and clone it to your hard drive for development or testing using the local development server.

The recommended editor for Illarion scripting is Visual Studio Code, which is available for Linux, Windows and macOS. You can use the extension rog2.luacheck to automatically perform some code checks ahead of pull requests. If you are using Visual Studio Code and open the repository directory as a folder, the required ISO 8859-1 encoding will be used automatically. A more detailed description of setting up and using a development environment is available in the tutorial.

Learning Lua

If you are new to programming in general or new to Lua, you will need to learn Lua 5.2 if you want to make changes beyond searching for and fixing text errors. Luckily Lua is a fairly simple language and I would suggest beginning by studying Programming in Lua chapters 1 to 5.1. You can skip 1.4, since we won't be using a stand-alone interpreter, but rather our local Illarion server. While this book is geared towards Lua 5.0, the basics did not change. Before you get started, make sure to follow the tutorial to set up a development environment.

Minimal script for testing Lua code

local M = {}

function M.UseItem(user, item, actionState)
    -- your code here
end

return M

While learning, you will want to test your code snippets. To do that, you can go to an item script of your choosing, e.g. item/id_9_saw.lua and delete all of its contents. Then paste the example on the right and write your code inside the UseItem entry point. Make sure to reload (!fr in the client) the server after saving your script to load any changes. Now you can run your code by simply using that item. To create an item with e.g. id 9, use !create 9 in your client (the item might be created in your bag).

Since print does not exist here (where would we print output anyway), you need to use another means of producing output. You can either use log("some output text") to send output to the server log, use debug("some output text") to send output to the server log with a call stack, or use user:inform("some output text") to send output to your client.

If you struggle or are not sure about something, you are always welcome to ask for help in Discord. Developers will be notified if you use the channel Development > beginners.

Structure

item/apple.lua

-- mandatory license header omitted for the sake of brevity

local M = {}

function M.UseItem(user, item)
    -- code handling items being used
end

function M.LookAtItem(user, item)
    -- code handling items being looked at
end

-- more entry points can follow

return M

Every time a certain event happens (e.g someone using an item, a monster dying, someone logging into the game, etc.) a script function is called. The name of that script is usually defined in the database, except for server scripts which have a fixed name and implement very specific server behaviour not tied to a particular game object.

The executed function is called an entry point, since it is run directly by the server. Scripts defined in the database, as well as server scripts, return a table containing expected entry points. A script does not need to include all possible entry points. For example, if an item has no UseItem entry point, nothing happens when a player uses the item.

Database Changes

INSERT INTO triggerfields VALUES (330, 536, -24, 'triggerfield.irundarmirror');
INSERT INTO npc VALUES (2, 61, 334, 538, -26, 0, false, 'Neil Peter Caldori', 'npc.caldori');
UPDATE items SET itm_script = 'item.food' WHERE itm_id = 15;

In the second example everything after npc_script will get a default value. You probably do not want an NPC with white skin, though.

You need to track all changes you make to the database. This will allow you to

To apply your changes easily, you need to write them down in SQL. Typically these will be INSERT statements for new database entries (new NPCs, quests, trigger fields, ...) and UPDATE statements for modifying existing entries (e.g. adding a script to an item). The syntax is pretty simple:

INSERT INTO <table name> VALUES (<list of all required values>);

UPDATE <table name> SET <field name> = <value> WHERE <id field> = <entry id>;

Notation

Function definitions in this document are given in the following format:

<return values> <function name>(<parameters>)

Example:

weight, value example(Item item [, number amount = 1])

The function example given here returns weight and value. It takes an Item called item as well as an optional number called amount as parameters. If amount is not passed into the function it will be set to 1.

Tutorial

This tutorial shows how to get started with Illarion content development. It gives a basic introduction into our development process and policies, i.e. what you need to know to contribute to Illarion development. We will go through the steps to set up a development environment and show how projects (even small ones, like fixing spelling errors) are started and shared.

Should you encounter any problems with this tutorial, please do not hesitate to ask for help on Discord. Feedback and suggestions on how to improve it are also welcome!

Initial Setup

  1. Create a free GitHub account if you don't have one and verify your email address.
  2. Fork the Illarion game content to your GitHub account.
  3. Fork the Illarion maps to your GitHub account.
  4. Install and run Visual Studio Code.
  5. Select Source Control on the left tool bar, then Clone Repository and Clone from GitHub.
  6. Authorize Visual Studio Code to access GitHub.
  7. In Visual Studio Code, select the Illarion-Map repository and afterwards a location to save it to. Make sure the folder is open in Visual Studio Code.
  8. Select Source Control again. Select ... > Remote > Add Remote..., insert https://github.com/Illarion-eV/Illarion-Map and name it upstream. If asked, you can choose to periodically run git fetch.
  9. On the bottom blue bar, click the current master branch and select origin/develop.
  10. Select File > Close Folder and repeat steps 5, 7, 8 and 9 for the Illarion-Content repository, using https://github.com/Illarion-eV/Illarion-Content as remote URL. Name the remote upstream as well.
  11. Close the Illarion-Content folder and re-open it. You will be asked to install recommended extensions. Do so. These extensions will offer better support for Lua, Git and point out problems in your Lua code.
  12. Every change you make needs a name and an email. Use a name Illarion devs know you by and an email you can be reached at:
    1. From the menu select View > Terminal.
    2. Enter: git config --global user.name "your name here"
    3. Enter: git config --global user.email your@email.here
  13. Setup the local Illarion development server. Use your Illarion-Map and Illarion-Content directories under 2. Setup.

Starting a Project

  1. Make sure the Illarion-Content folder is open and the bottom blue bar shows develop.
  2. Select Source Control on the left tool bar and select ... > Pull, Push > Pull from... > upstream > upstream/develop.
  3. Select develop > Create new branch... and name it feature/hello-illarion. Notice that the blue bar shows the new branch.
  4. Select Explorer on the left bar and open server > login.lua.
  5. Find the line function M.onLogin(player) and under it enter player:inform("Hello Illarion Development!")
  6. Save the file.
  7. Start your local server and connect with your Illarion client to check for the new login message. To reload scripts while the server is running, use the !fr command within the client.
  8. Under Source Control you can now see login.lua as a modified file. Click + to stage the changes. login.lua will now show under Staged Changes.
  9. Enter a message title "Say hello on login", insert a blank line and write a more detailed message below. Press CTRL + Enter to submit the commit of all staged changes.
  10. Under Source Control select ... > Push to send your changes to your GitHub repository.

Sharing your Work

  1. On GitHub go to your Illarion-Content repository and in the left-hand combo box select the new feature/hello-illarion branch.
  2. Either click Compare & pull request or Contribute > Open pull request.
  3. Switch the base branch from master to develop.
  4. Enter a suitable title and description before not clicking Create pull request, as this would notify a lot of developers and this is not a real project.

Entry Points

As described in the introduction, entry points are the interface between server and scripts. The server calls entry points on a variety of events to perform the implemented game mechanics. This allows the server to remain unchanged and running, while scripts are updated at run-time.

Entry points are expected to be functions with pre-defined names, which the server will call. For the server to be able to find the script these functions are in, the script is named in the db entry of the appropriate game entity. The only exception are server scripts, which just have one exact name.

The server expects scripts to return a table, which contains all implemented entry points. In the following definitions only the function signature is given for clarity.

Those scripts which are entered into the database, need to be named in the following format:

E.g. a script item/apple.lua would be entered as item.apple.

Some scripts provide a special global service for the server. These scripts are not attached to a specific game object (e.g. item, monster or field), but define general or in some cases default behaviour. All of these scripts are located in the server directory. Server scripts should be edited with great caution, since breaking one of those scripts would break the related behaviour for the server.

Combat

Combat script with all available entry points

local M = {}

function M.onAttack(attacker, defender)
end

return M

Script file: server/standardfighting.lua

onAttack(Character attacker, Character defender)

Is called every time attacker tries to hit defender in physical combat.

Death

Death script with all available entry points

local M = {}

function M.playerDeath(user)
end

return M

Script file: server/playerdeath.lua

playerDeath(Character user)

Is called when user dies. Here you can handle e.g. penalties for dying.

Depot

Depot script with all available entry points

local M = {}

function M.onOpenDepot(user, depot)
    return true
end

return M

Script file: server/depot.lua

boolean onOpenDepot(Character user, Item depot)

Is called when user tries to open a depot. Has to return true if user is allowed to open this depot and false otherwise.

Fields

These entry points are bound to a single field (x, y, z) on the map.

SQL

INSERT INTO triggerfields VALUES (X, Y, Z, 'SCRIPT_NAME');

Field script with all available entry points

local M = {}

function M.ItemRotsOnField(oldItem, newItem)
end

function M.MoveFromField(user)
end

function M.MoveToField(user)
end

function M.PutItemOnField(item, user)
end

function M.TakeItemFromField(item, user)
end

return M

Database table: triggerfields

ItemRotsOnField(Item oldItem, Item newItem)

oldItem had its wear reduced to zero on this field and rots into newItem.

MoveFromField(Character user)

user moves away from this field.

MoveToField(Character user)

user moves onto this field.

PutItemOnField(Item item, Character user)

user puts item on this field.

TakeItemFromField(Item item, Character user)

user takes item from this field.

Item Inspection

Item inspection script with all available entry points

local M = {}

function M.lookAtItem(user, item)
    local lookAt = ItemLookAt()
    return lookAt
end

return M

Script file: server/itemlookat.lua

ItemLookAt lookAtItem(Character user, Item item)

Handles basic item inspection if item does not have a script with LookAtItem entry point. Needs to return an ItemLookAt.

Items

SQL

UPDATE items SET itm_script = 'SCRIPT_NAME' WHERE itm_id = ITEM_ID;

Item script with all available entry points

local M = {}

function M.actionDisturbed(user, disturber)
    return false
end

function M.CharacterOnField(user)
end

function M.LookAtItem(user, item)
    local lookAt = ItemLookAt()
    return lookAt
end

function M.MoveItemAfterMove(user, source, target)
end

function M.MoveItemBeforeMove(user, source, target)
    return true
end

function M.UseItem(user, item, actionState)
end

return M

Database table: items

boolean actionDisturbed(Character user, Character attacker)

Is called when user is attacked while an action is running. This action had to be started by user:startAction(...) in UseItem. If this entry point does not exist or returns false the action is not aborted. If it returns true the action is aborted. See section Actions in Character for more details on starting actions.

CharacterOnField(Character user)

Is invoked if someone steps on an item. For this entry point to be called, the corresponding item needs to have an entry in the database table tilesmodificators with tim_specialitem set to true.

ItemLookAt LookAtItem(Character user, Item item)

A user looks at an item. Needs to return an ItemLookAt.

boolean MoveItemBeforeMove(Character user, Item source, Item target)

A user tries to move the item source. After the move that item would be target, having a new location. If this returns true or if this entry point does not exist, the move is carried out. If false is returned, the move is prevented.

MoveItemAfterMove(Character user, Item source, Item target)

A user has moved the item source. It is now the item target.

UseItem(Character user, Item item, number actionState)

A user uses an item. actionState is one of

See section Actions in Character for more details on starting actions.

Learning

Learning script with all available entry points

local M = {}

function M.learn(user, skill, actionPoints, learnLimit)
end

function M.reduceMC(user)
end

return M

Script file: server/learn.lua

learn(Character user, number skill, number actionPoints, number learnLimit)

Called as a result of user:learn being invoked. skill IDs are defined in the database table skills. A certain number of actionPoints were spent on an action, resulting in learning. The learned skill cannot exceed both learnLimit and 100.

reduceMC(Character user)

Called every 10s for every online player. Use this to reduce mental capacity with user:increaseMentalCapacity. A higher mental capacity value makes learning more difficult.

Login

Login script with all available entry points

local M = {}

function M.onLogin(user)
end

return M

Script file: server/login.lua

onLogin(Character user)

Invoked when user logs in. Here you can display login information, tax players, etc.

Logout

Logout script with all available entry points

local M = {}

function M.onLogout(user)
end

return M

Script file: server/logout.lua

onLogout(Character user)

Invoked when user logs out. Here you can e.g. substitute player faction leaders with their NPC equivalent.

Long Time Effects

Long time effects (LTE) allow you to influence a character over a period of time. Their state is saved whenever the character is saved. They consist of:

After adding an LTE to the database, you can add it to a character, e.g. inside an item script. How the effect works has to be defined in its script. Every time the LTE is called, the entry point callEffect is invoked. There you can change the character's attributes etc. Note that you should always save any changes of fixed attributes in LTE variables, so you can restore everything when the LTE ends. When a character logs out, all LTE variables are saved. When it logs in again, the entry point loadEffect is invoked. Any temporary changes of attributes will be lost on logout, so here you can read the values you have saved and perform those changes again.

SQL

INSERT INTO longtimeeffects VALUES (ID, 'EFFECT_NAME', 'SCRIPT_NAME');

LTE script with all available entry points

local M = {}

function M.addEffect(effect, user)
    user:inform("added " .. effect.effectName)
end

function M.callEffect(effect, user)
    user:inform("called " .. effect.effectName .. ", next: " .. effect.nextCalled .. ", called before: " .. effect.numberCalled)
    return true
end

function M.doubleEffect(effect, user)
    user:inform("doubled " .. effect.effectName)
end

function M.loadEffect(effect, user)
    user:inform("loaded " .. effect.effectName)
end

function M.removeEffect(effect, user)
    user:inform("removed " .. effect.effectName)
end

return M

Database table: longtimeeffects

addEffect(LongTimeEffect effect, Character user)

A new effect has been added to user.

bool callEffect(LongTimeEffect effect, Character user)

An effect activates for user. If true is returned, the effect will be called again in effect.nextCalled deciseconds. If false is returned, the effect will be removed.

doubleEffect(LongTimeEffect effect, Character user)

A script tried to add an effect to user, but effect which has the same id, was already present. This can be used e.g. to refresh the overall duration of effect, by modifying a variable which stores the number of times the effect needs to be called.

loadEffect(LongTimeEffect effect, Character user)

A user has logged into the game afflicted by effect. Temporary changes need to be recreated here.

removeEffect(LongTimeEffect effect, Character user)

An effect was removed from user either by returning false in callEffect or by calling user.effects:removeEffect(id).

Monsters

Monster entry points are only run when a player is within a range of 60 fields, two levels up and down, or if the monster is on a route.

abortRoute(Character monster)

Is called if monster has reached the destination of its route or if items block the way and the destination cannot be reached. See section Waypoints in Character for details on routes.

boolean actionDisturbed(Character user, Character disturber)

Is called when user is attacked while an action is running. This action had to be started by user:startAction(...) in useMonster. If this entry point does not exist or returns false the action is not aborted. If it returns true the action is aborted. See section Actions in Character for more details on starting actions.

boolean enemyNear(Character monster, Character enemy)

Invoked when monster is on a field next to enemy. If setTarget returns 0 ("don't attack anyone"), then enemyNear has to return false.

boolean enemyOnSight(Character monster, Character enemy)

Invoked whenever a monster sees an enemy. Must return true or false.

It is not invoked when the monster stands on a field next to the enemy.

lookAtMonster(Character user, Character monster, number mode)

Invoked when user looks at monster with mode being one of:

onAttack(Character monster, Character enemy)

Invoked every time monster would hit enemy.

onAttacked(Character monster, Character attacker)

Invoked when monster is hit by attacker.

onCasted(Character monster, Character caster)

Invoked when a spell is cast on monster by caster.

onDeath(Character monster)

Invoked as monster dies.

onSpawn(Character monster)

Invoked after monster has spawned.

receiveText(Character monster, number textType, string text, Character speaker)

Invoked when monster receives text spoken by speaker. textType is Character.say, Character.whisper or Character.yell.

number setTarget(Character monster, table candidateList)

If setTarget exists, it is called whenever monster needs to decide what it should attack. The candidateList is a table of players who are possible targets. To set a target, its index in that table has to be returned. The first enemy in the table has the index 1. If the entry point returns 0, monster will ignore all enemies. If setTarget does not exist, the player with lowest health is chosen by default.

useMonster(Character monster, Character user, number actionState)

A user uses a monster. actionState is one of

See section Actions in Character for more details on starting actions.

NPCs

local M = {}

function M.nextCycle (npc)
end

function M.receiveText(npc)
end
function M.useNPC(npc, user)
    -- What should happen when you use the npc
end
lookAtNpc(npc, player, mode)
    -- The lookat for the npc (Not in use? Found no existing examples)
return M

NPC entry points are only run when a player is within a range of 60 fields, two levels up and down, or if the NPC is on a route.

lookAtNpc(npc, user, mode)

Invoked when user looks at npc with mode being one of:

nextCycle(npc)

Invoked every decisecond if a player is nearby or npc is on a route.

receiveText(npc, talkType, message, talker)

Invoked anytime the NPC hears someone speaking.

useNPC(npc, user, actionState)

Invoked when the user shift clicks the NPC.

Player Inspection

Player inspection script with all available entry points

local M = {}

function M.lookAtPlayer(user, targetPlayer, mode)
end

return M

Script file: server/playerlookat.lua

lookAtPlayer(Character user, Character targetPlayer, number mode)

Is invoked when user looks at targetPlayer. For normal inspection mode is 0, for close examination mode is 1.

Quests

INSERT INTO quests VALUES (QUEST_ID, 'QUEST_SCRIPT');
local common = require("base.common")
local M = {}

local GERMAN = Player.german
local ENGLISH = Player.english

local title = {}
title[GERMAN] = "Deutscher Questtitel"
title[ENGLISH] = "English Quest Title"

local description = {}
description[GERMAN] = {}
description[ENGLISH] = {}
description[GERMAN][1] = "Tolle lange Beschreibung die angezeigt wird wenn man Queststatus 1 erreicht, \z
                          auch was man wo machen muss ..."
description[ENGLISH][1] = "Cool long description which is displayed when you reach quest status 1, \z
                           also mention what to do where ..."

local start = position(1, 2, 3)

local questTarget = {}
questTarget[1] = {position(1, 2, 3), position(4, 5, 6)}

local FINAL_QUEST_STATUS = 0

function M.QuestTitle(user)
    return common.GetNLS(user, title[GERMAN], title[ENGLISH])
end

function M.QuestDescription(user, status)
    local german = description[GERMAN][status] or ""
    local english = description[ENGLISH][status] or ""

    return common.GetNLS(user, german, english)
end

function M.QuestAvailability(user, status)
    if status == 0 then
        return Player.questAvailable
    else
        return Player.questNotAvailable
    end
end

function M.QuestStart()
    return start
end

function M.QuestTargets(user, status)
    return questTarget[status]
end

function M.QuestFinalStatus()
    return FINAL_QUEST_STATUS
end

return M

A more extensive template is available in quest/quest.template

While you can create quests with setQuestProgress and getQuestProgress alone, it is nice to have an actual log of your quest progress in your client. This way you cannot forget about your quests and you always know what to do and where to do it. The client might even point you in the right direction. The entrypoints in this section implement that behaviour.

Database Table: quests

Quest scripts must be entered into the database for the functions to be applied to the associated quest.

qst_id qst_script
603 quest.nobarg_603

The example above is for a quest associated with the npc nobarg, with the quest id 603. In the qst_id entry is the id assigned to the quest. In the qst_script entry is the folder the script is in (quest) and the name of the .lua file (nobarg_603).

string QuestTitle(Character user)

You should return the quest title here, depending on user language.

string QuestDescription(Character user, number status)

You should return the quest description here, depending on user language and quest status. You can be as extensive as you want, but make sure to cover the most important points. A good description should serve as a reminder where to go to complete the next step of the quest. Let the player know how to get there and what to do there. Imagine a player continuing a quest after some time, they still need to know where they are at and how to go on.

position QuestStart()

Return the position where the quest begins. This will probably be the position of an NPC or item.

number QuestAvailability(Character user, number status)

You can return three possible values here, depending on the user and the current quest status:

If the quest could be available soon (e.g. missing skill) return Player.questWillBeAvailable and if the player tries to accept the quest, inform them what exactly is missing to do so. If the quest will not be available (wrong town, too much skill already, etc.) return Player.questNotAvailable.

If this entry point is not defined, the server will assume Player.questAvailable for status == 0 and Player.questNotAvailable otherwise.

position|table QuestTargets(Character user, number status)

Here you should return the position where the quest continues, i.e. where a new quest status can be obtained. The client will receive this position and direct the player towards it. You can also return nil or an empty table if no such positions should be displayed in the client. Omit positions with care. Even if you think it would be cool to let players search on their own, most of the time it is just annoying. You can also return a table of positions here if the quest can continue in more than one location.

number QuestFinalStatus()

Return the final status of your quest here. This is used by the server to determine whether the end of a quest has been reached. If so, the client is notified to display the quest as completed.

Reloading Scripts

Reload script with all available entry points

local M = {}

function M.onReload()
    return true
end

return M

Script file: server/reload.lua

boolean onReload()

Invoked after server start as well as after the !fr command has been issued in the client. Needs to return true for the reload to succeed. If false is returned, the reload will fail. You can use this for initialising code that would otherwise not run on reload.

Most of the time any initialisation should be performed in a related script, though. The server will run all scripts entered into the database as well as all server scripts on every reload and server start. Keep in mind that you can put code in a file outside of any function and it will be run at those times.

Scheduled

INSERT INTO scheduledscripts VALUES ('SCRIPT_NAME', MIN_TIME, MAX_TIME, 'myScheduledFunction');
local M = {}

function M.myScheduledFunction()
    -- What I want my scheduled function to do
end

return M

Database Table: scheduledscripts

Scripts can be scheduled to be executed at defined intervals by entering them into the database.

You need the following information:

Script name

The name of the script file.

Minimum Cycle Time

The minimum time between each function call in deciseconds.

Maximum Cycle Time

The maximum time between each function call in deciseconds. Can be set to the same value as minimum time to have no randomness.

Function Name

The name of the function you want to run.

Database entry example:

local M = {}

function M.informUsers()
    world:broadcast("German Text","English Text")
end

return M
sc_scriptname sc_mincycletime sc_maxcycletime sc_functionname
scheduled.myScript 100 150 informUsers

The above database entry will make it so that the function informUsers found in myScheduledScript.lua in the scheduled directory is run every 10 to 15 seconds. In this case it is broadcasting the given text to all players.

Talk

Talk script with all available entry points

local M = {}

function M.talk(user, talkType, text)
    return text
end

return M

Script file: server/playertalk.lua

boolean actionDisturbed(Character user, Character attacker)

Is called when user is attacked while an action is running. This action had to be started by user:startAction(...) in talk. If this entry point does not exist or returns false the action is not aborted. If it returns true the action is aborted. See section Actions in Character for more details on starting actions.

string talk(Character user, number talkType, string text, number actionState)

Is called when player user tries to speak text. talkType is one of Character.say, Character.whisper and Character.yell. Needs to return the text the player will actually speak.

This can be used for speaking passwords, reciting spells, making players sound drunk, etc.

actionState is one of

See section Actions in Character for more details on starting actions.

Classes

Character

Players, NPCs and monsters are the three types of characters in Illarion.

directions

Character - Variables

Name Type Writable Description
id number no character id
effects userdata no contains methods to control the character's long time effects, see section on long time effects.
name text no character name
pos position no character position on world map
lastSpokenText string no character's last spoken line of text
attackmode boolean no true if character currently attacks, false otherwise
activeLanguage number yes 0: common, 1: human, 2: dwarf, 3: elf, 4: lizard, 5: orc, 6: halfling, 7: fairy, 8: gnome, 9: goblin, 10: ancient
movepoints number yes A character has a maximum of 21 movepoints. Every action like talking, fighting, using etc. needs at least 7 movepoints. The reduction of movepoints depends on the character's agility. A character gains one movepoint per decisecond.

Character - Actions

local M = {}

function M.UseItem(user, item, actionState)
    local gfxRain = 16

    if actionState == Action.none then
        user:startAction(50, gfxRain, 20, 0, 0)
        user:inform("start")
        item.number = item.number + 1
        world:changeItem(item)
        user:changeSource(item)
    elseif actionState == Action.abort then
        user:inform("abort: " .. item.number)
    elseif actionState == Action.success then
        user:inform("success: " .. item.number)
    end
end

function M.actionDisturbed(user, attacker)
    user:inform("disturbed by " .. attacker.name)
    return true
end

return M

All Use* entry points (e.g. UseItem) which have an actionState parameter and the talk entry point, allow for starting actions. An action can be seen as a state of concentration of the user, which lasts for a specified time. After that time or when the action is interrupted, the entry point is called again with the same parameters, but with actionState set to Action.abort or Action.success accordingly. Usually actions are aborted by the user doing something else, like using something, moving, etc. However, actions can also be aborted by the user being attacked if an actionDisturbed entry point is defined and returns true.

startAction(number duration, number gfxId, number gfxInterval, number sfxId, number sfxInterval)

Starts an action lasting duration deciseconds. At the start of the action and every gfxInterval deciseconds after that graphic effect gfxId is played. Similarly, at the start of the action and every sfxInterval deciseconds after that sound effect sfxId is played. If gfxId or sfxId are set to 0, no effect of that type will be played.

changeSource(Item item)

If you modify the item associated with an action started in UseItem, this functions ensures the next time UseItem is called by the action (when it succeeds or is aborted) the updated item will be provided as a parameter. This needs to be called in addition to world:changeItem.

Long Time Effects

See class LongTimeEffect for details.

effects:addEffect(LongTimeEffect effect)

local effect = LongTimeEffect(10, 50)
user.effects:addEffect(effect)

Tries to add effect to the character. Entry point addEffect is called immediately if the effect is not yet present on the character. Otherwise entry point doubleEffect is called.

boolean, LongTimeEffect effects.find(number id)

boolean, LongTimeEffect effects.find(string name)

local found, effect = user.effects:find("some effect")

Searches for a long time effect by id or name and returns true and that effect if it exists on this character. Returns false if that effect does not exist on this character.

boolean effects:removeEffect(number id)

boolean effects:removeEffect(string name)

boolean effects:removeEffect(LongTimeEffect effect)

local removed = user.effects:removeEffect(10)

Removes an effect from this character, either by id, by name or by passing the effect itself. Returns true if an effect was removed and false otherwise.

Character - Text/Speech Functions

talk(number mode, string message)

talk(number mode, string textInGerman, string textInEnglish)

character:talk(Character.say, "Hello World!")
character:talk(Character.say, "Hallo Welt!", "Hello World!")

Lets a character say/whisper/yell a message. mode can be Character.say, Character.whisper or Character.yell.

Character - Movement/Position functions

setClippingActive(boolean status)

character:setClippingActive(True)

Enables the character to walk on fields where he usualy can’t walk (water, walls, ...).

boolean getClippingActive()

character:getClippingActive()

Returns true if clipping is enabled for the current character, otherwise false.

move(number direction, boolean activeMove=true)

character:move(0, true)

Moves the character one step in the direction, is the movement performed actively activeMove should be true otherwise false.

number getFaceTo()

character:getFaceTo()

Returns an integer between 0 and 7 inclusively that indicates the direction the character is facing to.

turn(number direction) \ turn(posStruct position)

character:turn(0)
character:turn(position(1,1,0))

Turns the character either towards a direction or towards a position.

warp(posStruct position) \ forceWarp(posStruct position)

character:warp(position(1,1,1))
character:forceWarp(position(1,1,1))

Warps the character to a certain position. While the warp function is going to protect the character to be spawned on a non-moveable position, forceWarp ignores this aspect.

boolean isInRage(character secondCharacter, number distance)`

character:isInRage(secondCharacter, 5)

Returns true if secondCharacter is within distance of the character, else false.

boolean isInRangeToPosition(posStruct position, number distance)

Returns true if the character is within the distance of position, else false.

number distanceMetric(character secondCharacter)

number distanceMetricToPosition(posStruct position)

character:distanceMetric(secondCharacter)
character:distanceMetricToPosition(position(1,1,1))

Returns distance between the character and a secondCharacter or between the character and a position.

boolean getOnRoute()

character:getOnRoute()

Returns if the character is currently following a route(waypoints).

setOnRoute(boolean isOnRoute)

character:setOnRoute(true)

Makes the character following/stopping a route.

Character - Admin

boolean pageGM(string text)

character:pageGM("Please marry me!")

Returns true if ticket is successfully logged as message to the GM team.

logAdmin(string message)

character:logAdmin("Set GM castle on fire.")

Writes message for the given user into the server log using the admin facility.

Character - Information

sendCharDescription(number id, string text)

character:sendCharDescription(10, "An old man.")

Shows the text as character description of the character with ID id only to the current character.

getPlayerLanguage()

character:getPlayerLanguage()

Returns the player’s language: Player.german or Player.english.

boolean isNewPlayer()

character:isNewPlayer()

Returns whether character is a new player or not.

boolean isAdmin()

character:isAdmin()

Returns true if that character is admin (GM) and false otherwise.

number idleTime()

character:idleTime()

If the character is a player, returns the number of seconds they are idle. Returns 0 otherwise.

boolean isValidChar(character Char)

isValidChar(char)

Returns true if char is still valid and safe to use. Validity has to be checked if char is used in another entrypoint call than the one where it was originally obtained, since a player might have logged out, an NPC might have been deleted and a monster might have been killed in the meantime.

number getMonsterType()

character:getMonsterType()

Returns the monster-ID of a monster.

number getType()

character:getType()

Returns Character.player for players, Character.monster for monsters and Character.npc for NPCs.

Character Appearance

setSkinColour(colour skinColour)

character:setSkinColour(colour(0,0,0))

Sets the skin colour.

setHairColour(colour hariColour)

character:setHairColour(colour(255,255,255))

Sets hair and beard colour.

setHair(number hairID)

character:setHair(1)

Changes the character hair to given one.

setBeard(number beardID)

character:setBeard(3)

Changes the beard of the character to the given one.

setRace(number race)

character:setRace(4)

Temporarily sets the race of a character.

colour getSkinColour()

character:getSkinColour()

Returns the skin colour of the character.

colour getHairColour()

character:getHairColour()

Returns the hair color of the character.

number getHair()

character:getHair()

Returns the ID of the present hair, 0 for no hair.

number getBeard()

character:getBeard()

Returns the ID of the present beard, 0 for no beard.

number getRace()

character:getRace()

Returns the race of the character.

Character - Constitution

increasePoisonValue(number amount)

character:increasePoisonValue(5)

Increases the toxication with the given amount.

number getPoisonValue()

character:getPoisonValue()

Returns the character's toxication value.

setPoisonValue(number value)

character:setPoisonValue(100)

Sets the toxication to the given value.

number getMentalCapacity()

character:getMentalCapacity()

Returns the mental capacity of the character which influences the ability to skill.

setMentalCapacity(number value)

character:setMentalCapacity(100)

Sets the mental capacity of the character to the given value

increaseMentalCapacity(number amount)

character:increaseMentalCapacity(20)

Increases the mental capacity of the character with the given amount.

Character - Modifying attributes

number increaseAttrib(string attribute, number value)

character:increaseAttrib("intelligence",100)

Increases the attribute given (see below) and returns the new attribute value. Use value=0 to read the attribute’s value. Note that this command also sends a player update to all characters in range if necessary.

setAttrib(string attribute, number value)

character:setAttrib("hitpoints", 1)

Sets the given value for the given attribute. Be aware that any attribute change to fixed attributes like "strength" will only last for the current session and will be reset to the database value upon the next login.

boolean isBaseAttributeValid(string attribute, number value)

character:isBaseAttributeValid("strength",0)

Returns whether value is acceptable for the given attribute and the race of the character, respecting limits given in table.

number getBaseAttributeSum()

character:getBaseAttributeSum()

Returns the current sum of the eight primary attributes.

number getMaxAttributePoints()

character:getMaxAttributePoints()

Returns the maximum value which the sum of primary attributes should have.

number getBaseAttribute(string attribute)

Returns the base value of the given attribute, that is the value that this attribute normally has, when no special effects are active.

boolean setBaseAttribute(string attribute, number value)`

Sets the base value of the given attribute and returns true if isBaseAttributeValid(...) would return true. Otherwise is a no-op and returns false.

boolean increaseBaseAttribute(string attribute, number amount)

character:increaseBaseAttribute("strength",20)

If isBaseAttributeValid(...) would return true, increases or decreases the base value of the given attribute and returns true. Otherwise is a no-op and returns false.

boolean saveBaseAttributes()

character:saveBaseAttributes()

Saves the eight primary base attributes to the database, iff getBaseAttributeSum() == getMaxAttributePoints(). On failure resets primary attribute values to database values. Returns whether the operation was successful or not.

Character - Magic

Illarion distinguishes between four magic types: Mages(0), Priests(1), Bards(2) and Druids(3).

number getMagicType()

character:getMagicType()

Returns the magic type of the character.

setMagicType(number magicType)

-- Be a druid
character:setMagicType(3)

Changes the magic type of the character to the given magicType.

number getMagicFlags(number magicType)

character:getMagicFlags(0)

Returns the number of magicFlags of the given magicType.

teachMagic(number magicType,number magicFlag)

-- Teach rune with ID 1
character:teachMagic(0,2^(1))

Can be used to teach a character runes or other flags, based on the given magicTypeand magicFlag.

Character - Skills

string getSkillName(skill targetSkill)

character:getSkillName(Character.ancientLanguage)

Returns the name of the given skill in the player’s language.

number getSkill(skill targetSkill)`

character:getSkill(Character.ancientLanguage)

Returns the major skill value for the character.

number getSkillValue(skill targetSkill)`

character:getSkillValue(Character.ancientLanguage)

Returns a table with two fields, major and minor, representing the skill and the minor skill.

setSkill(skill targetSkill, number major, number minor)`

character:setSkill(Character.ancientLanguage, 15, 10)

Directly sets major and minor skill. Returns the new major skill. If the skill does not exist in the database, nothing is set and 0 is returned.

increaseSkill(skill targetSkill, number value)

increaseMinorSkill(skill targetSkill, number value)

character:increaseSkill(Character.ancientLanguage, 10)
character:increaseMinorSkill(Character.slashingWeapons, 10)

Increase major and minor skill, respectively. Returns the new major skill. If the skill does not exist in the database, nothing is increased and 0 is returned.

learn(skill targetSkill, number actionPoints, number learnLimit)

character:learn(Character.slashingWeapons, 10,100)

Number of actionPoints used up for the action which resulted in learning, but the skill will not be advanced beyond this learnLimit and never beyond 100.

Character - Quests

setQuestProgress(number questID, number progress)

character:setQuestProgress(100,1)

A quest progress can be set for a specific quest.

number getQuestProgress(number questID)

character:getQuestProgress(100)

Returns the progress for a specific quest. Optionally also returns the time when this progress was last set as Unix timestamp.

Character - Item/Inventory handling

Relevant class sections for the following section : Item, ItemStruct. A full list of all item IDs can be found here.

enum value
Character.backpack 0
Character.head 1
Character.neck 2
Character.breast 3
Character.hands 4
Character.left_tool 5
Character.right_tool 6
Character.finger_left_hand 7
Character.finger_right_hand 8
Character.legs 9
Character.feet 10
Character.coat 11
Character.belt_pos_1 12
Character.belt_pos_2 13
Character.belt_pos_3 14
Character.belt_pos_4 15
Character.belt_pos_5 16
Character.belt_pos_6 17

increaseAtPos(number bodyPosition, number count)

character:increaseAtPos(Character.belt_pos_1,1)

Increases the amount of items at bodyPositionby count.

swapAtPos(number bodyPosition, number itemID, number quality)

-- 2763 == pickaxe
character:swapAtPos(Character.belt_pos_1, 2763, 999)

Changes the item at the given bodyPosition to the given itemID and quality. A full list of all item IDs can be found here.

table getItemList(number itemID)

character:getItemList(2763)

Returns a list with all items of this itemID.

Character - Depot/Containers

conStruct getBackPack()

character:getBackPack()

Returns a container-item, which can contain other items.

conStruct getDepot(number depotID)

character:getDepot(0)

Returns a container-item (the depot of that Character).

table takeItemNr(number bodyPosition,number amount).

character:takeItemNr(Character.belt_pos_2,2)

Takes the amount of items from the bodyPosition and returns the taken items.

Character - Items

number createItem(number itemID, number count, int number quality, dataTable data)

character:createItem(2764, 1, 999, {})

Item is created in the belt or backpack of character. If that is not possible, the items will not be created. The function returns an integer that gives the number of items that cannot be created.

createAtPos(number bodyPosition, number itemID, number count)

character:createAtPos(Character.hands, 2763, 1)

Creates an item at a special body position.

changeQualityAt(number bodyPosition, number amount)

character:changeQualityAt(hands, 333)

Changes the quality by the amount at the body position.

number eraseItem(number itemID, number count)

number eraseItem(number itemID, number count, table data)

character:eraseItem(2763, 1, {})
character:eraseItem(2763, 1, {glyphEffect:0})

The given count of items with the itemID are erased from the inventory. You have no influence on which items are deleted, you can just determine ID and number. The return value contains the amount of items that could not be deleted. In case the optional data parameter is set, only items that include these data values are deleted. If the data table is empty however, only items without data are erased.

number countItem(number itemID)

character:count(2763)

Returns the amount of items with type itemID in the character's inventory.

number countItemAt(string slots, number itemID)

number countItemAt(string slots, number itemID, table data)

character:countItemAt("all", 2763)
character:countItemAt("all", 2763, {"madeBy": "Vilarion"})

Counts only at the specified slots Valid slots values are "all", "belt", "body", "backpack". The variant with data does only count items that include these data values. If the data table is empty however, only items without data are counted.

table getLoot()

monster:getLoot()

Returns the loot for a monster, otherwise throws a NoLootFound error.

Character - Ambient

startMusic(number track)

character:startMusic(10)

Starts music track for the current character, with 0 meaning silence.

defaultMusic()

character:defaultMusic()

Plays the default music for the current character as defined by the map.

colour

Colours are used to represent a characters skin or hair colour.

Variables

Name Type Writable Description
red number yes From 0 (no red) to 255 (full red)
green number yes From 0 (no green) to 255 (full green)
blue number yes From 0 (no blue) to 255 (full blue)
alpha number yes From 0 (transparent) to 255 (opaque)

Functions

colour(number red, number green, number blue [, number alpha = 255])

local black = colour(0, 0, 0)

Creates colour for given RGBA values.

Container

Containers are items that can contain other items. Examples of these are a character's backpack and depot.

local M = {}

Functions

number getSlotCount()

local slots = myContainer:getSlotCount()

Returns the number of slots available in the given container.

boolean, Item, Container viewItemNr(number itempos)


local function viewItemNr(user, theDepot)

    local success, theItem, theContainer = theDepot:viewItemNr(1)

    local text

    if success then
        if theContainer then
            text = "The item is a container with an item-ID of "..theItem.id
        else
            text = "The item is not a container. Its item-ID is "..theItem.id
        end
        user:inform(text)
    end
end

Returns a boolean for success, the item found at the given position if any, as well as the container if the item found is a container item. The "itempos" can be anything from 0-99, signifying the corresponding slot in the container from left to right, top to bottom. Most containers have 100 slots in total, however some cases such as the "basket" item have less.

boolean, Item, Container takeItemNr(number itempos, number count)

local function takeItemNr(user, theDepot)

    local success, theItem, theContainer = theDepot:takeItemNr(1, 1)

    local text

    if success then
        if theContainer then
            text = "A container with an item-ID of "..theItem.id.." was removed from the container."
        else
            text = "An item with an ID of "..theItem.id.." was removed from the container."
        end
    end

    user:inform(text)
end

Returns the same values as the above viewItemNr function, but it also deletes an amount of the item. How many it deletes is determined by the count integer.

changeQualityAt(number itempos, number amount)

local function changeQualityAt(user, theDepot)

    local success, theItem = theDepot:viewItemNr(1)

    if success then
        local oldQuality = theItem.quality
        local qualityChanged = theDepot:changeQualityAt(1, 333)
        success, theItem = theDepot:viewItemNr(1)
        if qualityChanged and success then
            local newQuality = theItem.quality
            user:inform("An item with an ID of "..theItem.id.." had it's quality changed from "..oldQuality.. " to "..newQuality..".")
        end
    end

end

This function is supposed to change the quality of the item at the given position inside the container to whatever the amount integer is set to. It returns as true if it worked. The quality value of an item is a three digit integer, where the first digit corresponds to quality (0-9) and the latter digits correspond to durability (00-99). For some reason, this function does not work currently.

boolean insertContainer(Item item, Container container, number itempos)

local function insertContainer(user, theDepot)
    local success, theItem, theContainer = theDepot:viewItemNr(1)

    if success then
        if theContainer and theItem then
            success, theItem, theContainer = theDepot:takeItemNr(1, 1)
            if success then
                local containerInserted = theDepot:insertContainer(theItem, theContainer, 2)
                if containerInserted then
                    user:inform("Container has been inserted into the slot.")
                end
            end
        end
    end
end

If itempos has been provided, tries to insert a container at that position. If not, or if that position is not free, inserst the container at the first free position. Returns true if successful.

insertItem(Item item, boolean merge)

local function insertItem(user, theDepot)

    local success, theItem = theDepot:viewItemNr(1)

    if success then
        theDepot:insertItem(theItem, true)
    end

end

Inserts an item into the container. If merge is false, it inserts the item in the first available slot of that container. If merge is true, it will insert into an existing stack of said item if it exists.

number countItem(number itemid, Table data)

local function countItem(user, theDepot)

    local numberOfRegularScissors = theDepot:countItem(6)
    local numberOfSpecialScissors = theDepot:countItem(6, {nameEn = "Special Scissor"})

    user:inform("There are "..numberOfRegularScissors.." regular scissors and "..numberOfSpecialScissors.. " special scissors in the container.")

end

Counts the number of items in a container of the given itemid, including items that may be inside containers inside said container. The data parameter is optional. If the data parameter is set, only items that include these data values are counted. If the data table is empty, only items without data are counted.

number eraseItem(number itemid, number count, Table data)

local function eraseItems(user, theDepot)

    local amountToDelete = 10

    local amountFailedToDelete = theDepot:eraseItem(6, amountToDelete)

    if amountFailedToDelete == 0 then
        user:inform("All "..amountToDelete.." scissors were erased from the container.")
    else
        local deletedScissors = amountToDelete-amountFailedToDelete
        if deletedScissors > 0 then
            user:inform("Only "..deletedScissors.." scissors were erased from the container.")
        else
            user:inform("No scissors were erased from the container.")
        end
    end

end

Erases an amount of the specified itemid that is the same as the count integer from the container. If a Table is specified, it will only erase items with data that matches that. If no Table is specified, it will only erase items with no special data. The returned integer is how many items could not be deleted.

increaseAtPos(number itempos, number value)

local function increaseAtPos(user, theDepot)

    theDepot:increaseAtPos(1, 40)

end

Increase the number of items at the given itempos by value. It should return the number of items increase, but currently it does not.

boolean swapAtPos(number itempos, number newId, number newQuality)

local function swapAtPos(user, theDepot)

    local success, theItem = theDepot:viewItemNr(0)

    if success then
        if theItem.number == 1 then
            local swapItem = theDepot:swapAtPos(0, 9, 999)
            if swapItem then
                user:inform("The item in slot 0 was replaced with a saw and the quality was set to 999")
            end
        end
    end
end

Changes the ID of the item at itempos to newId and quality of the item to newQuality. Returns true on success. Here it's also important to make sure the item stack number is correct. You don't want to suddenly have a stack of normally unstackable items.

number weight()

local function getWeight(user, theDepot)

    local weight = theDepot:weight()

    user:inform("The container weighs a total of "..weight.. " weight units.")

end

Returns the total weight of that container.

function M.mainFunction(user, sourceItem, ltstate)

    local theDepot = user:getDepot(101) --Runewick's depot

    if theDepot then

        --[[It's important to check if the container exists. 
        For instance, a depot that has never been accessed does not exist for that character.]]

        myFunction(user, theDepot)

    end
end

return M

Dialogs

Dialogs are a more sophisticated approach to aquire user input than e.g. user.lastSpokenText. Each dialog serves a specific purpose like displaying bulk text, interfacing with a merchant or with the crafting system. Dialogs should be the preferred method for handling user input. If necessary new types should be implemented rather than abusing old variants or falling back to lastSpokenText. Please note, that as with many functions dialogs work with all types of characters (players, monsters, npcs) but only make sense with players. Using the other two types will work but do nothing at all. Using a dialog always consists of three descrete steps:

  1. Create a callback function to be triggered automatically when the user closes the dialog. This function has to have a single parameter to which the dialog will be passed with obtained results.
  2. Invoke the constructor of a specific dialog to create a dialog instance, passing required parameters and callback.
  3. Have a player object request the dialog in the user's client. Script execution does not stop after this request. The callback with results is called whenever the user closes the dialog.

Usually, creating an object would be described before talking about results. However, here we chose a different order to follow the three steps mentioned above, which reflect the order in which you would write a dialog, and to review the most interesting details first, namely what each dialog actually contributes to a particular script.

MessageDialog

Use this dialog to display bulk text in an on-screen window. The user can close the window at any time.

local callback = function(dialog)
    user:inform("Dialog closed")
end

local lyrics = [[
O Fortuna
Velut luna
Statu variabilis
...
]]

local dialog = MessageDialog("O Fortuna", lyrics, callback)
user:requestMessageDialog(dialog)

Results

This type of dialog by it's very nature has no results to be accessed. Still a callback makes sense, because you might want to react to the dialog being closed.

Construction

MessageDialog(string title, string message, function callback)

Creates a MessageDialog with a specific title and message.

Request

user:requestMessageDialog(MessageDialog dialog)

InputDialog

This dialog requests text input from the user. If you want to further restrict the input, make that clear to the user using the description and enforce it inside the callback. For different kinds of input (e.g. items) use or request development of a different type of dialog.

local callback = function(dialog)
    if not dialog:getSuccess() then
        user:inform("You canceled! How dare you?")
    else
        user:inform("You wrote: " .. dialog:getInput())
    end
end

local dialog = InputDialog("Insert some text!", false, 255, callback)
user:requestInputDialog(dialog)

Results

boolean getSuccess()

The result is true if the dialog was confirmed and false if it was aborted.

string getInput()

Returns the user's input if the dialog was successful. The return value is undefined if the dialog was aborted.

Construction

InputDialog(string title, string description, boolean multiline, number maxChars, function callback)

Creates an InputDialog with title and description. It allows line breaks iff multiline is set to true. The input can be up to maxChars characters long.

Request

user:requestInputDialog(InputDialog dialog)

Field

Fields make up the game map. Characters stand on fields, items are on fields, warps make players jump from one field to another. Each field is on a different logical position.

Functions

Most of the time these functions will be used on a field received from world:getField(position).

local doorPosition = position(261, 631, 0)
local doorField = world:getField(doorPosition)
local groundId = doorField:tile()

number tile()

Returns the tile id of that field, meaning the number for "water", "grass", etc.

local itemsOnField = field:countItems()

number countItems()

Returns the number of items on top of that field.

local topItem = field:getStackItem(itemsOnField - 1)

Item getStackItem(number stackPosition)

Returns the item at stackPosition on a given field. stackPosition == 0 will return the bottom item. If stackPosition is equal or greater than the number of items on that field, an item with id 0 will be returned.

local dropPossible = field:isPassable()

boolean isPassable()

Determines whether or not a field allows a character to pass over it or having items dropped on it.

local hasWarp, warp = field:isWarp()

boolean, position isWarp()

Returns whether or not a field is a warp as well as the warp destination.

local exit = position (146, -381, 0)
field:setWarp(exit)

setWarp(position destination)

Enables the field's warp and sets its destination.

field:removeWarp()

removeWarp()

Removes the field's warp.

Item

item.quality = 284
item.setData("name", "John's Item")
world:changeItem(item)

Represents an actual item on the map or in a character's pockets. If you change an item, you need to propagate your changes to the server via world:changeItem(item).

Constants

local itemStats = world:getItemStatsFromId(Item.apple)

If an item has an entry someUniqueItemName in itm_name in the item's database entry, you can get its id with Item.someUniqueItemName. This is more descriptive than using the actual number.

Variables

Name Type Writable Description
id number yes item id
inside Container no if the item is in a container, this is that container
itempos number no if the item is in a container or in a character's inventory, this is its position there
number number yes number of identical items in the stack
owner Character no if the item is on a character, this is that character
pos position no if the item is on the map, this is the position of the field it is on
quality number yes item quality from 0 to 999, where the first digit is quality and the last two digits are durability
wear number yes if an item can decay it loses one wear point every three minutes

Functions

string getData(string key)

local prayerLevel = tonumber(item:getData("prayerLevel"))

Returns the item's data value for the given key if that key exists and an empty string otherwise.

number getType()

if item:getType() == scriptItem.field then
    debug("item with id " .. item.id .. " is on the ground")
end

Depending on where the item is located, will return one of

boolean isLarge()

local blocksView = item:isLarge()

Returns true if and only if the item is large enough to block a character's view. This means in the database it has a volume of at least 5000.

setData(string key, number value)

setData(string key, string value)

item:setData("prayerLevel", 12)
item:setData("prayerLevel", "")
item:setData("prefix", "very strong ")

The second line removes the data key prayerLevel from the item.

Sets the data value for a given key unless value is an empty string, in which case the entry for key is removed. If given a number for value, that number is stored as the corresponding string.

ItemLookAt

Holds information about an item which is used by the client to display item inspection pop-ups. It is mainly used in the server entry point lookAtItem and the Item entry point LookAtItem.

Variables

local itemLookAt = ItemLookAt()
itemLookAt.name = "Sword"
itemLookAt.rareness = ItemLookAt.uncommonItem

All variables are writable.

Name Type Default Description
name string "" item name, must be set
rareness number ItemLookAt.commonItem has to be one of ItemLookAt.commonItem, ItemLookAt.uncommonItem, ItemLookAt.rareItem, ItemLookAt.epicItem
description string "" item description
craftedBy string "" who crafted the item
type string "" states what kind of item this is
level number 0 item level from 0 to 100
usable boolean true can the item be used by players?
weight number 0 item weight
worth number 0 selling price (to NPCs) in copper coins
qualityText string "" describing item quality
durabilityText string "" describing item durability
durabilityValue number 0 from 0 to 100 in percent
diamondLevel number 0 magic gem level from 0 to 10
emeraldLevel number 0 magic gem level from 0 to 10
rubyLevel number 0 magic gem level from 0 to 10
sapphireLevel number 0 magic gem level from 0 to 10
amethystLevel number 0 magic gem level from 0 to 10
obsidianLevel number 0 magic gem level from 0 to 10
topazLevel number 0 magic gem level from 0 to 10
bonus number 0 gem bonus from 0 to 255

Functions

ItemLookAt()

Returns new ItemLookAt with default values.

ItemStruct

local itemStats = world:getItemStats(item)
local item.wear = itemStats.AgeingSpeed
world:changeItem(item)

Reset item wear to its default

local itemStats = world:getItemStatsFromId(Item.apple)
local vendorSellPrice = itemStats.Worth
local nameEnglish = itemStats.English
local nameGerman = itemStats.German

We don't need an actual item to get this data, the id (Item.apple) is enough.

Represents item definitions obtained from the database. Does not represent an item on the map or in a player's inventory, but general information about an item with a specific id.

Variables

All of these variables are read-only.

Name Type Description
AgeingSpeed number item does not decay for 3 * AgeingSpeed minutes, by default
Brightness number from 0 to 9, used for light sources
BuyStack number default stack size on vendors
English string English item name
EnglishDescription string English item description
German string German item name
GermanDescription string German item description
id number item id
Level number item level from 0 to 100
MaxStack number maximum stack size
ObjectAfterRot number item id the item changes into after decaying
Rareness number ItemLookAt.commonItem, ItemLookAt.uncommonItem, ItemLookAt.rareItem or ItemLookAt.epicItem
rotsInInventory boolean if set, item decays in a player's inventory, useful for e.g. lit torches
Weight number item weight, item can only be moved if its weight is below 30000
Worth number sold by vendors for this amount of copper coins

Long Time Effect

Stored in a character's effects variable. See section "Long Time Effects" for Character on how to access it. For a description on how to use long time effects, see their entry point section.

Variables

Name Type Writable Description
effectId number no effect id
effectName string no effect name
nextCalled number yes number of deciseconds until the effect should be called
numberCalled number no number of times the effect has been called before, meaning on the first call this will be zero, etc.

Functions

local effect = LongTimeEffect(10, 50)
effect:addValue("totalCalls", 10)
local exists, value = effect:findValue("totalCalls")

if exists then
    effect:addValue("totalCalls", value + 10)
else
    effect:addValue("totalCalls", 10)
end

effect:removeValue("totalCalls")

LongTimeEffect(number id, number nextCalled)

Returns a new LongTimeEffect with given id and nextCalled being the number of deciseconds until the effect is first called via entry point callEffect after being added to a character.

addValue(string key, number value)

Adds or overwrites variable key with value value. value is non-negative and less than 2^32.

boolean, number findValue(string key)

Returns false if the effect has no variable with name key. Otherwise returns true and the value of that variable.

removeValue(string key)

Removes variable with name key from the effect.

position

Positions represent a location on the world map.

Variables

Name Type Writable Description
x number yes x coordinate, increases from west to east (lower left to upper right)
y number yes y coordinate, increases from north to south (upper left to lower right)
z number yes z coordinate, increases as levels go up

Functions

position(number x, number y, number z)

local pos = position(10, 11, 0)

Returns position for map location (x, y, z).

position p1 == position p2

local equal = position(4, 5, 0) == position(4, 5, 0) -- equal == true

Compares two positions p1 and p2. Returns

Free Functions

tostring(position p)

local text = tostring(position(3, 4, 0)) -- text == "(3, 4, 0)"

Returns "(" .. p.x .. ", " .. p.y .. ", " .. p.z .. ")".

user:forceWarp(position(0, 0, 0))
local pos = user.pos              -- pos is (0, 0, 0)
user:forceWarp(position(1, 1, 0)) -- now user.pos AND pos are (1, 1, 0)

Avoid this by creating a new position from individual coordinates.

local pos = position(user.pos.x, user.pos.y, user.pos.z)

Random

Most of the time the random number generator supplied by Lua should be enough. However, if you are looking for a statistically sound implementation of a random number generator, or if you need another distribution besides the uniform one, this is the class you are looking for.

Functions

These functions belong to the class Random and not to variables of type Random. As such they use the . delimeter instead of :. It is not possible to create variables of type Random.

number uniform()

local chance = Random.uniform()

Returns a random floating-point value uniformly distributed in the range [0, 1).

number uniform(number min, number max)

local die = Random.uniform(1, 6)

Returns a random integer value uniformly distributed in the integer range [min, max]

number normal(number mean, number standard_deviation)

local chance = Random.normal(10.75, 1.2)

Returns a random floating-point value normally distributed with given meanand standard_deviation.

Script Variables

ScriptVars is a global variable, allowing us to save values in the database and to access them with an identifier string. Use with caution as these values have global effect. Everything in ScriptVars is automatically saved upon server shutdown.

Functions

ScriptVars:set("welcome", "Welcome to Illarion")
local found, message = ScriptVars:find("welcome")
ScriptVars:remove("something")
ScriptVars:save()

boolean, string find(string key)

Returns false if a ScriptVar key does not exist. Otherwise returns true and the value of ScriptVar key.

boolean remove(string key)

Removes ScriptVar key and returns true if there was such an entry. Returns false otherwise.

save()

Saves all ScriptVars immediately. Use with caution, this might cause lag.

set(string key, number value)

set(string key, string value)

Writes value into ScriptVar key. Values are always stored as strings.

Logging

Sometimes you want to log information e.g. for game masters. You can do this with the log function.

With the local server you can view this log in the console you started the server in.

The Illarion development server and the Illarion game server provide a summary of this log. In these logs messages will start with the number of occurences of a specific entry, followed by a time stamp. Be aware of the fact that access to the game server log is restricted to game masters.

Functions

log(string message)

log("some information")
game-server_1  | Script (info): some information

Sends a log message to the server log.

Debugging

After setting up your editor as described in the tutorial, it will tell you about Lua syntax errors and other problems, like the use of global variables. However, some issues will only surface at run-time. If a script throws an error while it is running, that error will appear in the server log.

With the local server you can view this log in the console you started the server in.

The Illarion development server and the Illarion game server provide a summary of script errors. In these logs error messages will start with the number of occurences of a specific error, followed by a time stamp.

Functions

Crude mechanisms like sending debug or error messages to players and prompting them to contact a developer are not very helpful, since you rely on the player to give a hopefully accurate report, if the report occurs at all. Instead there are two functions you can use to send information to the server log.

debug(string message)

debug("some debug info")
game-server_1  | Script (notice): Debug Message: some debug info
game-server_1  | #1 called by: /usr/share/illarion/scripts/test.lua:11

Sends a debug message to the server log, including a call stack which will show where the message came from. This is disabled on the Illarion game server.

Notice how the server log will show your files in the server's script directory /usr/share/illarion/scripts/ instead of your local script directory. After the file name, seperated by a : you can see the line number.

error(string message)

error("some error message")
game-server_1  | Script (err): /usr/share/illarion/scripts/test.lua:16: some error message
game-server_1  | #1 called by: [C]:-1(global error)
game-server_1  | #2 called by: /usr/share/illarion/scripts/test.lua:16

Sends an error message to the server log and aborts the current script. Use this to avoid running into undefined states or to prevent potential loss of data. Just as with debug you will get a call stack, which will show the error's origin.

Resources

These lists contain game content stored in the client. They can be handy during content development, to help you find a suitable resource. Every day they are automatically generated.

Common Bugs

Changelog

Version Date Changes
6.0 xx/xx/202x Illarion Lua is now documented with Slate
5.24 12/09/2020 Last version in LaTex format
Lua