StendhalScripting/Lua: Difference between revisions
imported>AntumDeluge →NPCs: adding transitions |
imported>AntumDeluge →Adding Transitions: nested tables |
||
| Line 367: | Line 367: | ||
In this scenario, the NPC will respond if the player has money & is not naked. |
In this scenario, the NPC will respond if the player has money & is not naked. |
||
Nested tables are supported as well: |
|||
<pre> |
|||
local conditions = { |
|||
newCondition("PlayerHasItemWithHimCondition", "money"), |
|||
{ |
|||
newNotCondition(newCondition("NakedCondition")), |
|||
}, |
|||
} |
|||
frank:add(ConversationStates.IDLE, |
|||
ConversationPhrases.GREETING_MESSAGES, |
|||
conditions, |
|||
ConversationStates.ATTENDING, |
|||
nil, |
|||
{ |
|||
newAction("SayTextAction", "Hello."), |
|||
newAction("NPCEmoteAction", "looks greedily at your pouch of money.", false), |
|||
}) |
|||
</pre> |
|||
==== Adding Merchant Behavior ==== |
==== Adding Merchant Behavior ==== |
||
Revision as of 14:22, 2 April 2020
this page is a work-in progress
Stendhal supports Lua scripting via the LuaJ library.
Lua scripts end in 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
Be default, Lua 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!"
Tables
A Lua table is a data type similar to a list. Tables can be indexed or use key=value pairs.
(IMPORTANT NOTE: Lua table indexes begin at 1, not 0)
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:
local mytable {
foo = "bar",
["foo"] = "bar",
}
mytable.foo = "bar"
mytable["foo"] = "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 declare functions:
local function myFunction()
print("Hello world!")
end
or
local myFunction = function()
print("Hello world!")
end
Functions can also be values in 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()
Stendhal Application
Objects and Functions
The following objects & functions are exposed to the Lua engine:
luajava
This is an object of the LuajavaLib library. It can be used to coerce Java static objects to Lua or create new Java object instances.
Example of exposing a static object & enums to Lua:
-- store a Java enum in a Lua global variable
ConversationStates = luajava.bindClass("games.stendhal.server.entity.npc.ConversationStates")
-- access the enum values like so
ConversationStates.IDLE
Example of creating an object instance:
-- store instance in local variable
local dog = luajava.newInstance("games.stendhal.server.entity.npc.SilentNPC")
-- access object methods like so
dog:setEntityClass("animal/puppy")
dog:setPosition(2, 5)
-- class with constructor using parameters
local speaker = luajava.newInstance("games.stendhal.server.entity.npc.SpeakerNPC", "Frank")
speaker:setOutfit("body=0,head=0,eyes=0,hair=5,dress=5")
speaker:setPosition(2, 6)
To make scripting easier, Stendhal employs a master script & some helper objects & methods to handle the functionality mentioned above. An explanation of these objects & methods follows.
game
The main object that handles setting zone & adding entities to game.
Methods:
game:add(object)- Adds an object to the current zone.game:setZone(name)- Sets the current zone.game:createSign(visible)- Creates a new Sign instance.game:createShopSign(name, title, caption, seller)- Creates a new ShopSign instance.
npcHelper
This object helps to create instances of SpeakerNPC & SilentNPC classes.
Methods:
npcHelper:createSpeakerNPC(name)- Creates a new SpeakerNPC.npcHelper:createSilentNPC()- Creates a new SilentNPC.npcHelper:setPath(npc, path, loop)- Sets the path for the specified NPC.npcHelper:setPathAndPosition(npc, path, loop)- Sets the path & starting position of the specified NPC.npcHelper:addMerchant(merchantType, npc, items, offer)- Adds merchant behavior tonpcof either a buyer or seller defined bymerchantType.npcHelper:addSeller(npc, items, offer)- Adds seller merchant behavior tonpc.npcHelper:addBuyer(npc, items, offer)- Adds buyer merchant behavior tonpc.
Setting Zone
To set the zone to work with, use the game object:
game:setZone("0_semos_city")
The logger is exposed to Lua via the logger object:
local zone = "0_semos_city"
if game:setZone(zone) then
-- do something
else
logger:error("Could not set zone: " .. zone)
end
Adding Entities
Signs
Signs can be created with game:createSign and game:createShopSign:
local zone = "0_semos_city"
if game:setZone(zone) then
-- create the sign instance
local sign = game:createSign()
sign:setEntityClass("signpost")
sign:setPosition(12, 55)
sign:setText("Meet Lua!")
-- Add it to the world
game:add(sign)
else
logger:error("Could not set zone: " .. zone)
end
NPCs
Use the game:createSpeakerNPC method to create an interactive NPC:
local zone = "0_semos_city"
if game:setZone(zone) then
-- Use helper object to create a new NPC
local npc = npcHelper:createSpeakerNPC("Lua")
npc:setEntityClass("littlegirlnpc")
npc:setPosition(10, 55)
npc:setBaseSpeed(0.1)
npc:setCollisionAction(CollisionAction.STOP)
local nodes = {
{10, 55},
{11, 55},
{11, 56},
{10, 56},
}
-- Use helper object to create NPC path
npcHelper:setPath(npc, nodes)
-- 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 = npcHelper:createSpeakerNPC("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.
For more complicated behavior, we need to use some helper methods. If we want to check a condition we use the newCondition global function:
frank:add(ConversationStates.IDLE,
ConversationPhrases.GREETING_MESSAGES,
newCondition("PlayerHasItemWithHimCondition", "money"),
ConversationStates.ATTENDING,
"Hello.",
nil)
In this scenario, the NPC will only respond if the player is carrying <item>money</item>.
A NotCondition instance can be created with the newNotCondition global function:
newNotCondition(newCondition("PlayerHasItemWithHimCondition", "money"))
To add a ChatAction, we use the newAction global function:
frank:add(ConversationStates.IDLE,
ConversationPhrases.GREETING_MESSAGES,
newCondition("PlayerHasItemWithHimCondition", "money"),
ConversationStates.ATTENDING,
"Hello.",
newAction("NPCEmoteAction", "looks greedily at your pouch of money.", false))
Lua tables can be used to add multiple conditions or actions:
frank:add(ConversationStates.IDLE,
ConversationPhrases.GREETING_MESSAGES,
{
newCondition("PlayerHasItemWithHimCondition", "money"),
newNotCondition(newCondition("NakedCondition")),
},
ConversationStates.ATTENDING,
nil,
{
newAction("SayTextAction", "Hello."),
newAction("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 = {
newCondition("PlayerHasItemWithHimCondition", "money"),
{
newNotCondition(newCondition("NakedCondition")),
},
}
frank:add(ConversationStates.IDLE,
ConversationPhrases.GREETING_MESSAGES,
conditions,
ConversationStates.ATTENDING,
nil,
{
newAction("SayTextAction", "Hello."),
newAction("NPCEmoteAction", "looks greedily at your pouch of money.", false),
})
Adding Merchant Behavior
Merchant behavior (buying/selling) can be set with one of the following helper functions:
- npcHelper:addMerchant(merchantType, npc, prices, addOffer)
- npcHelper:addBuyer(npc, prices, addOffer)
- npcHelper:addSeller(npc, prices, addOffer)
- Arguments:
- merchantType: (string) If set to "buyer", will add buyer behavior, otherwise will be "seller" (may change type to boolean in future).
- npc: (SpeakerNPC) The NPC to add the behavior to.
- prices: (Map<String, Integer> or LuaTable) List of items & their prices.
- addOffer: (boolean) If
true, will add default replies for "offer".
- Arguments:
Example of adding seller behavior to an NPC:
if game:setZone("0_semos_city") then
local frank = npcHelper.createSpeakerNPC("Frank")
npcHelper:addSeller(frank, shops:get("shopname"), true)
game:add(frank)
end
To create a custom shop list, you can use a Lua table (there are multiple ways to add elements to a Lua table):
Method 1:
local priceList = {
meat = 50,
["ham"] = 70,
}
Method 2:
local priceList = {}
priceList.meat = 50
priceList["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:
npcHelper:addSeller(frank, priceList, true)