Introduction to Lua
Lua is a scripting language and is used in the WoW User Interface and addons.
Contents
Getting started
- It's recommended to use a simple text editor like VS Code or Notepad++
- You will need an AddOn to test your code in, or the WowLua addon.
You can also use the Lua Demo or get Lua on your machine. Note that a slightly modified version of Lua 5.1 is used in WoW.
This guide will follow the Learn X in Y minutes format.
Variables
- Reference: PIL: Types and Values, Reference Manual
-- Lua is dynamically typed. variables do not have types; only values
apple = 4 -- number
apple = true -- boolean
apple = nil
banana = "yellow" -- string
cherry = 'red' -- single quotes are also fine
print(banana) -- prints "yellow"
print(type(banana)) -- prints "string"
d, e, f = "hi", false, 123 -- multiple assignment
i = 10 -- global variable
local j = 1 -- local variable
local k = 42; -- semicolons are mostly optional
--[[
This is a
multi-line comment
--]]
-- variable scope https://www.lua.org/pil/4.2.html
if true then
local x = 4
print(x) -- 4
end
print(x) -- nil (out of scope)
local y = 10
local z
-- a do-end block can be useful for explicitly scoping variables
do
print(y) -- 10
local y = 3
print(y) -- 3
z = true
end
print(y) -- 10
print(z) -- true
Boolean Logic
- Reference: PIL: Relational Operators, Logical Operators
-- relational operators
local apple, berry = 3, 5
print(apple == berry) -- false
print(apple ~= berry) -- true
print(apple <= berry) -- true
-- be careful when comparing values with different types
print(0 == "0") -- false
print(2 == tonumber("2")) -- true
print("2" == tostring(2)) -- true
-- only nil and false are falsy, anything else is truthy
local a = 123
if a then
print("This is truthy") -- prints this
else
print("This is falsy")
end
print(not nil) -- true
print(not false) -- true
print(not true) -- false
print(not 0) -- false
print(not "") -- false
-- logical operators
-- and operator: if the first operand is truthy, then the second operand will be returned, otherwise returns the first operand
-- or operator: if the first operand is truthy, then the first operand will be returned, otherwise returns the second operand
print(true and 7) -- first op is truthy -> returns the second op: 7
print(4 and 5) -- first op is truthy -> returns the second op: 5
print(false and 13) -- first op is falsy -> returns the first op: false
print(nil and 9) -- first op is falsy -> returns the first op: nil
print(true or 7) -- first op is truthy -> returns the first op: true
print(4 or 5) -- first op is truthy -> returns the first op: 4
print(false or 13) -- first op is falsy -> returns the second op: 13
print(nil or 9) -- first op is falsy -> returns the second op: 9
print(true and true and false) -- false (not all operands are true)
print(false or false or true) -- true (at least a single operand is true)
-- both "and" and "or" use short-circuit evaluation, that is, they evaluate their second operand only when necessary
local function greet() print("hello") end
local a = true and greet() -- hello
local b = false and greet() -- prints nothing
local c = true or greet() -- prints nothing
local d = false or greet() -- hello
-- toggles between true/false
local x = true
x = not x
print(x) -- false
x = not x
print(x) -- true
-- combining the "and" and "or" operators is similar to the ternary operator
-- http://lua-users.org/wiki/TernaryOperator
local b = true and "yes" or "no"
local c = false and "yes" or "no"
print(b) -- yes
print(c) -- no
Math
- Reference: MathLibraryTutorial, Reference Manual
-- the normal order of operations is followed
print(2 + 3 * 4) -- 14
print((2 + 3) * 4) -- 20
-- Lua can automatically convert between strings and numbers (coercion)
-- https://www.lua.org/manual/5.1/manual.html#2.2.1
local a = 3
local b = "2"
print(a + b) -- 5
print(math.max(23, 17)) -- 23
print(math.floor(2.9)) -- 2
print(math.cos(2 * math.pi)) -- 1
print(math.pow(2, 10)) -- 1024
print(2^10) -- 1024
print(math.fmod(14, 3)) -- 2
print(14%3) -- 2
-- WoW includes a bitwise library
-- https://wow.gamepedia.com/Lua_functions#Bit_Functions
print(bit.band(0x10A48, 0x800)) -- 2048 (0x800)
print(bit.bor(1, 2)) -- 3
print(bit.lshift(1, 10)) -- 1024
Strings
- Reference: String Manipulation, String Library Tutorial
-- string concatenation https://www.lua.org/pil/3.4.html
print("Storm".."wind") -- Stormwind
print(40 .." yards") -- 40 yards
print(0 .. 1) -- 01
local a, b = "Where", "wife"
print(a.." is Mankrik's "..b.."?") -- Where is Mankrik's wife?
-- string length https://www.lua.org/manual/5.1/manual.html#2.5.5
print(#a) -- 5
-- format strings https://www.lua.org/manual/5.1/manual.html#pdf-string.format
print(string.format("Hello %s", "Bob")) -- Hello Bob
-- object-oriented style
local fs = "WTS %d Shiny %s"
print(fs:format(6, "Red Apples")) -- WTS 6 Shiny Red Apples
local s = "Hello my friend"
print(string.sub(s, 7)) -- my friend
print(string.gsub(s, "l", "w")) -- Hewwo my friend, 2
print( s:upper() ) -- HELLO MY FRIEND
Patterns
- Reference: PatternsTutorial, PIL: Patterns, Reference Manual
-- Lua uses "patterns" instead of regular expressions
print(string.match("abcdefg", "b..")) -- bcd (. matches all characters)
print(string.find("abcdefg", "b..")) -- 2, 4
print(string.match("foo 123 bar", "%d%d%d")) -- 123 (%d matches a digit)
print(string.match("hello World", "%u")) -- W (%u matches an uppercase letter)
-- a pattern can contain sub-patterns enclosed in parentheses, also known as captures
local m, d, y = string.match("04/19/64", "(%d+)/(%d+)/(%d+)")
print(m, d, y) -- 04, 19, 64
Control Flow
- Reference: PIL: Control Structures
-- if-then block
local x = math.random(1, 6)
if x == 3 then
print(x, "is three")
elseif x < 3 then
print(x, "is less than three")
else
print(x, "is greater than three")
end
-- for loop
local sum = 0
for i = 1, 10 do
sum = sum + i
end
print(sum) -- 55
-- while loop
local sum, i = 0, 0
while true do
i = i + 1
sum = sum + i
if i == 10 then
break
end
end
print(sum) -- 55
Functions
- Reference: PIL: Functions
function square(v)
return v * v
end
print(square(4)) -- 16
-- varargs can hold a variable number of arguments
-- https://www.lua.org/pil/5.2.html
local function add(...)
print(...) -- 3, 5, 4
local a, b, c = ...
return a + b + c
end
-- returns the sum of 3 values
print(add(3, 5, 4)) -- 12
-- the select() function returns all parameters after the nth index
-- https://www.lua.org/manual/5.1/manual.html#pdf-select
local function foo(...)
print(select(2, ...)) -- green, blue
end
foo("red", "green", "blue")
local function bar()
return "hello", "world", "bye"
end
print(select(2, bar())) -- world, bye
print(select(2, "a", "b", "c", "d")) -- b, c, d
print(select(3, "a", "b", "c", "d")) -- c, d
print(select(4, "a", "b", "c", "d")) -- d
-- an extra pair of brackets captures the first value
print((select(2, "a", "b", "c", "d"))) -- b
Syntactic sugar
- References: WowAce coding tips, Difference between . and : in Lua
Alpha = function() end
-- is the same as
function Alpha() end
local Object = {}
Object.Bravo = function(Object) print(Object) end
-- is the same as
function Object.Bravo(Object) print(Object) end
-- is the same as
function Object:Bravo() print(self) end
-- and for calling
Object.Bravo(Object)
-- is the same as
Object:Bravo()
Tables
- Reference: PIL: Tables, Tables Tutorial, Arrays
-- table constructors can contain a comma separated list of objects to create an "array"
-- table indices start at one instead of zero
local t = {"a", "b", "c"}
print(t[1]) -- a
print(t[3]) -- c
-- this is syntactic sugar for
local t = {[1]="a", [2]="b", [3]="c"}
print(t[1]) -- a
print(t[3]) -- c
-- tables can be nested
local t = {"test", {17, 43}}
print(t[2][1]) -- 17
print(t[2][2]) -- 43
-- table with key-value pairs
local t = {["fruit"] = "peach", foo = true}
print(t.fruit) -- peach
print(t["foo"]) -- true
print(t.foo) -- true (syntactic sugar)
-- table used as a list / sequentially ordered array
local t = {8, 5, 7}
print(t[1]) -- 8
t[2] = 14 -- changes the value at index 2
table.insert(t, "banana")
-- iterates through the table with a for loop
for i = 1, #t do
print(i, t[i])
end
-- 1, 8
-- 2, 14
-- 3, 7
-- 4, banana
-- note this is table.unpack() in Lua 5.2
print(unpack(t)) -- 8, 14, 7, banana
-- table used as a dictionary / associative array
local t = {
foo = 2,
bar = 5,
baz = 7,
}
print(t.bar) -- 5
t.fruit = "banana"
-- iterates through the table with a generic for loop (random order)
-- https://www.lua.org/pil/4.3.5.html
for k, v in pairs(t) do
print(k, v)
end
-- baz, 7
-- bar, 5
-- fruit, banana
-- foo, 2
-- tables are passed as a reference https://stackoverflow.com/a/6128322
local a = {"strawberry", 12, true}
print(a) -- table: 0000022C0973C2A0
local b = a -- points b to the table at a
print(b == a) -- true
-- changing a key/value in b also changes it in a since they point to the same table
b[1] = "banana"
print(a[1]) -- banana
a = nil -- a no longer points to the table
print(a) -- nil
print(b) -- table: 0000022C0973C2A0 (b still points to the table)
local c = {}
for key, value in pairs(b) do -- shallow copies a table
c[key] = value
end
-- the contents of c equals b but they both are different tables in memory
print(c == b) -- false
-- tip: you can e.g. concatenate variables when indexing a table
local names = {
party1 = "Bob",
party2 = "Alice",
party3 = "Elroy",
party4 = "Flintlocke",
}
for i = 1, 4 do
print(names["party"..i])
end
-- Bob
-- Alice
-- Elroy
-- Flintlocke
-- tip: use a lookup table if you want to check if a (really big) table contains a specific value
local mounts = {352, 435, 464, 522}
local function contains(t, id)
-- we use the _ single underscore as a dummy variable when we're not interested in it
-- https://stackoverflow.com/questions/5893163
for _, v in pairs(t) do
if v == id then
return true
end
end
end
print(contains(mounts, 464))
local mounts = { [352] = true, [435] = true, [464] = true, [522] = true, } print(mounts[464])
Sorting
-- sequential tables can be sorted
-- http://lua-users.org/wiki/TableLibraryTutorial
local t = {2, 14, 5, 9}
table.sort(t)
print(unpack(t)) -- 2, 5, 9, 14
local mounts = {
{id = 352, name = "Big Love Rocket"},
{id = 435, name = "Mountain Horse"},
{id = 464, name = "Azure Cloud Serpent"},
{id = 522, name = "Sky Golem"},
}
-- sorts alphabetically by the name field
table.sort(mounts, function(a, b)
return a.name < b.name
end)
for _, tbl in pairs(mounts) do
print(tbl.id, tbl.name)
end
-- 464, Azure Cloud Serpent
-- 352, Big Love Rocket
-- 435, Mountain Horse
-- 522, Sky Golem
Details
Global Environment
- References: PIL: The Environment, Reference Manual
-- _G holds the global environment
-- you can explicitly access global variables from there
apple = "fresh"
local apple = "ripe"
print(apple) -- ripe
print(_G.apple) -- fresh
-- prints all global functions in the environment
for k, v in pairs(_G) do
if type(v) == "function" then
print(k, v)
end
end
ipairs vs pairs
-- ipairs() only iterates over index-value pairs sequentially
-- non numeric keys (key-value pairs) are ignored, as are tables with "holes"
local t = {
[1] = "a",
[2] = "b",
[3] = "c",
[27] = 123,
fruit = "strawberry",
[33] = "hello",
}
for i, v in ipairs(t) do
print(i, v)
end
-- 1, a
-- 2, b
-- 3, c
for k, v in pairs(t) do
print(k, v)
end
-- 1, a
-- 2, b
-- 3, c
-- fruit, strawberry
-- 33, hello
-- 27, 123
Follow-up: Create a WoW AddOn in under 15 Minutes
Resources
API Reference
- Lua functions - The Lua 5.1 API as implemented in WoW
- https://www.lua.org/manual/5.1/ - Reference Manual (5.1)
Tutorials
- https://www.mmo-champion.com/threads/817817-Creating-Your-Own-WoW-Addon - MMO-Champion tutorial
- https://www.lua.org/start.html - Getting started with Lua
- https://www.lua.org/pil/contents.html - Programming in Lua (online version)
- https://en.wikibooks.org/wiki/Lua_Programming - Wikibooks tutorial
- https://wiki.facepunch.com/gmod/Beginner_Tutorial_Intro - Garry's Mod tutorial
- https://developer.roblox.com/en-us/learn-roblox/coding-scripts - Roblox tutorial
Style Guides
These may be helpful for seeing what coding conventions other projects follow. See also Lua Coding Tips for best practices.