Introduction to Lua
Contents
About
this page is a work-in progress
Stendhal supports Lua scripting via the LuaJ library.
Lua scripts end with the .lua extension & are stored in the data/script directory.
Lua Basics
For more detailed information, see the Lua reference manual.
Comments
Lua uses double dashes (--) for single line comments & double dashes followed by double square
brackets (--[[) & closed with double square brackets (]]) for multi-line comments:
-- a single line comment
--[[
a multi-line comment
]]
Variables
By default, variables are set in global scope
(meaning it is exposed to the entire Lua engine). To create a variable in
local scope, the local keyword must be used:
-- a global variable
var1 = "Hello world!"
-- a local variable
local var2 = "Hello world!"
Data Types
Some of the common data types are nil, string, number,
boolean, & table. Lua is a dynamically typed language, so
declaring the type is not required when setting variables.
Examples:
-- string variable
local var_s = "Hello world!"
-- number variable
local var_n = 11
-- the number type also encompasses floating-point or double-precision values
local var_f = 5.112
-- boolean variable
local var_b = true
-- table variable
local var_t = {}
By default all variables are nil. This means that a variable can be accessed before it is declared
without throwing an error.
-- print value of undeclared variable
print(foo) -- output: nil
-- print value of declared variable without value
foo = nil
print(foo) -- output: nil
-- print value of declared variable with value
foo = "bar"
print(foo) -- output: bar
Strings
String Concatenation
String concatenation is simple, much like Java uses a plus operator (+) to join strings, Lua uses
two periods (..).
Example:
-- create a string variable
local var = "Hello"
-- append another string
var = var .. " world!"
print(var) -- output: "Hello world!"
Tables
A Lua table is a data type similar to a Java list or map. Tables can be indexed or use key=value pairs.
Lua table indexes begin at 1, not 0
Creating Tables
An empty table is initialized with a pair of curly braces ({}):
local mytable = {}
You can add values to indexed tables at initialization or with the table.insert method:
-- create a table with values
local mytable = {"foo"}
-- add value
table.insert(mytable, "bar")
To create a key=value table, any of the following methods can be used to add values:
-- all of these do the same thing, that is, assigning "bar" to mytable.foo
local mytable {
foo = "bar",
["foo"] = "bar",
}
mytable.foo = "bar"
mytable["foo"] = "bar"
Accessing Table Values
Square brackets ([]) enclosing an index number are used to access values in indexed tables
(remember that Lua table indexes start at "1" not "0"):
local mytable = {"foo", "bar"}
print(mytable[1]) -- output: "foo"
print(mytable[2]) -- output: "bar"
In a key=value table, values can be accessed by either enclosing the key string in square brackets or concatenating the key member using a dot (.):
local mytable = {foo="bar"}
-- using square brackets
print(mytable["foo"]) -- output: "bar"
-- using concatenated member
print(mytable.foo) -- output: "bar"
Iterating Tables
Tables can be iterated in a for loop using the pairs or ipairs iterators. Loops are terminated
with the end keyword:
local mytable = {"foo", "bar"}
print("indexes:")
for idx in pairs(mytable) do
print(idx)
end
print("\nvalues:")
for idx, value in pairs(mytable) do
print(value)
end
Output:
indexes:
1
2
values:
foo
bar
Using a key=value table:
local mytable = {
["foo"] = "hello",
["bar"] = " world!",
}
print("keys:")
for key in pairs(mytable) do
print(key)
end
print("\nvalues:")
for key, value in pairs(mytable) do
print(value)
end
Output:
keys:
foo
bar
values:
hello
world!
See also: Lua Tables Tutorial
Functions
Like normal variables, functions can be declared as global or local & must be terminated
with the end keyword.
There are two ways to define functions with the function keyword:
local function myFunction()
print("Hello world!")
end
or
local myFunction = function()
print("Hello world!")
end
Functions can also be members of a table:
local myTable = {}
function myTable.myFunction()
print("Hello world!")
end
or
local myTable = {}
myTable.myFunction = function()
print("Hello world!")
end
or
local myTable = {
myFunction = function()
print("Hello world!")
end,
}
-- execute with
myTable.myFunction()
Userdata
Userdata is a special type that allows Java data to be stored in Lua variables.
This allows access to Java objects & methods. Userdata methods are accessed using a colon (:).
One example is Stendhal's grammar parser class. It is exposed to Lua as the grammar
global variable.
print(type(grammar)) -- ouput: "userdata"
print(grammar:itthem(5)) -- output: "them"
A dot (.) can be used just like accessing table values, but the userdata object itself must then be
passed as the first argument. In other words grammar:itthem(5) is the same as
grammar.itthem(grammar, 5).
Comparison Operators
Logical Operators
| Operator | Description | Java Equivalent |
|---|---|---|
| and | logical and | && |
| or | logical or | || |
| not | logical inverse | ! |
Relational Operators
| Operator | Description | Java Equivalent |
|---|---|---|
| < | less than | < |
| > | greater than | > |
| <= | less than or equal to | <= |
| >= | greater than or equal to | >= |
| == | equal to | == |
| ~= | not equal to | != |
Stendhal Application
Zones
Setting Zone
To set a zone to work with, use the game:setZone method:
game:setZone("0_semos_city")
Create New Zone
For creating a permanent zone it is recommended to use the XML configurations in data/conf/zones.
Currently creating new zones via Lua is not supported.
Add Zone Music
Music can be added to zones with the game:setMusic method.
Example:
if game:setZone("0_semos_plains_n") then
game:setMusic("pleasant_creek_loop", {volume=85, radius=100})
end
Adding Entities
Entities can be added to the game using the entities object.
Adding Signs
Signs can be created with the "Sign", "Reader", & "ShopSign" types:
local zone = "0_semos_city"
if game:setZone(zone) then
-- create the sign instance
local sign = entities:create({
type = "Sign",
class = "signpost",
pos = {12, 55},
text = "Meet Lua!"
})
-- add to the world
game:add(sign)
else
logger:error("Could not set zone: " .. zone)
end
Adding NPCs
Use the "SpeakerNPC" type to create an interactive NPC:
local zone = "0_semos_city"
if game:setZone(zone) then
-- create the NPC instance
local npc = entities:create({
type = "SpeakerNPC",
name = "Lua",
class = "littlegirlnpc",
pos = {10, 55},
path = {
nodes = {
{10, 55},
{11, 55},
{11, 56},
{10, 56},
},
collisionAction = CollisionAction.STOP
},
speed = 0.1
})
-- dialogue
npc:addJob("Actually, I am jobless.")
npc:addGoodbye();
-- add to the world
game:add(npc)
else
logger:error("Could not set zone: " .. zone)
end
Adding Transitions
A simple example of adding a chat transition can be done without any special functionality:
local frank = entities:create({
type = "SpeakerNPC",
name = "Frank"
})
frank:add(ConversationStates.IDLE,
ConversationPhrases.GREETING_MESSAGES,
nil,
ConversationStates.ATTENDING,
"Hello.",
nil)
This simply adds a response to saying "hello" & sets the NPC to attend to the player (equivalent
of frank:addGreeting("Hello")).
For more advanced behavior, we need to use some helper methods. If we want to check a condition we use the conditions:create method. The first parameter is the string name of the ChatCondition we want to instantiate. The second parameter is a table that contains the values that should be passed to the ChatCondition constructor.
Example:
frank:add(ConversationStates.IDLE,
ConversationPhrases.GREETING_MESSAGES,
conditions:create("PlayerHasItemWithHimCondition", {"money"}),
ConversationStates.ATTENDING,
"Hello.",
nil)
In this scenario, the NPC will only respond if the player is carrying money.
A NotCondition instance can be created with the actions:notCondition method:
Example usage:
local condition = conditions.notCondition(conditions:create("PlayerHasItemWithHimCondition", {"money"})
To add a ChatAction, we use the actions:create method. Its usage is identical to conditions:create.
Example:
frank:add(ConversationStates.IDLE,
ConversationPhrases.GREETING_MESSAGES,
conditions:create("PlayerHasItemWithHimCondition", {"money"}),
ConversationStates.ATTENDING,
"Hello.",
actions:create("NPCEmoteAction", {"looks greedily at your pouch of money.", false}))
Lua tables can be used to add multiple conditions or actions (this only works for NPCs created from Lua or instances of LuaSpeakerNPC):
add global helper methods for managing SpeakerNPC instances not created in Lua
frank:add(ConversationStates.IDLE,
ConversationPhrases.GREETING_MESSAGES,
{
conditions:create("PlayerHasItemWithHimCondition", {"money"}),
conditions:notCondition(conditions:create("NakedCondition")),
},
ConversationStates.ATTENDING,
nil,
{
actions:create("SayTextAction", {"Hello."}),
actions:create("NPCEmoteAction", {"looks greedily at your pouch of money.", false}),
})
In this scenario, the NPC will respond if the player has money & is not naked.
Nested tables are supported as well:
local conditions = {
conditions:create("PlayerHasItemWithHimCondition", {"money"}),
{
conditions:notCondition(conditions:create("NakedCondition")),
},
}
frank:add(ConversationStates.IDLE,
ConversationPhrases.GREETING_MESSAGES,
conditions,
ConversationStates.ATTENDING,
nil,
{
actions:create("SayTextAction", {"Hello."}),
actions:create("NPCEmoteAction", {"looks greedily at your pouch of money.", false}),
})
Adding Merchant Behavior
The merchants object is used for adding merchant behavior (buying/selling) to an NPC.
should use ShopType enum
Example of adding seller behavior to an NPC:
if game:setZone("0_semos_city") then
local frank = entities.create({
type = "SpeakerNPC",
name = "Frank"
})
merchants:addSeller(frank, merchants.shops:get("shopname"), true)
game:add(frank)
end
To create a custom shop list, you can use a Lua table:
local priceList = {
meat = 50,
ham = 70,
}
The helper methods have special handling for underscore characters as well (the following are all the same):
local priceList = {
smoked_ham = 100,
["smoked ham"] = 100,
}
priceList.smoked_ham = 100
priceList["smoked ham"] = 100
Then add the seller behavior using the custom list:
merchants:addSeller(frank, priceList, true)
System Properties
Java's system properties are exposed to Lua with the properties object.
Examples:
-- property state
if properties:enabled("stendhal.testserver") then
print("Test server enabled")
if properties:equals("stendhal.testserver", "junk") then
print("Junk enabled")
else
print("Junk disabled")
end
else
print("Test server disabled")
end
-- property value
local prop = properties:getValue("stendhal.testserver")
if prop ~= nil then
print("Test server enabled")
if prop == "junk" then
print("Junk enabled")
else
print("Junk disabled")
end
else
print("Test server disabled")
end
Misc
Typecasting
Lua does not support typecasting (as far as I know), but if the class you want to cast to has a copy constructor, achieving the same functionality possible.
-- "entities:getItem" returns an instance of Item
local bestiary = entities:getItem("bestiary")
-- in order to use the bestiary's "setOwner" method, we must convert it to an "OwnedItem" instance
-- by calling its copy constructor
bestiary = luajava.newInstance("games.stendhal.server.entity.item.OwnedItem", bestiary)
bestiary:setOwner("Ted")