StendhalScripting/Lua: Difference between revisions
Jump to navigation
Jump to search
Content deleted Content added
imported>AntumDeluge →Objects and Functions: some instructions on using the "luajava" object |
imported>AntumDeluge add categories |
||
| (23 intermediate revisions by the same user not shown) | |||
| Line 7: | Line 7: | ||
Lua scripts end in the <code>.lua</code> extension & are stored in the <code>data/script</code> directory. |
Lua scripts end in the <code>.lua</code> extension & are stored in the <code>data/script</code> directory. |
||
= Lua Basics = |
|||
== Objects and Functions == |
|||
For more detailed information, see the [https://www.lua.org/docs.html Lua reference manual]. |
|||
The following objects & functions are exposed to the Lua engine: |
|||
== Comments == |
|||
Lua uses double dashes (<code>--</code>) for single line comments & double dashes followed by double square brackets (<code>[[</code>) & closed with double square brackets (<code>]]</code>) for multi-line comments: |
|||
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. |
|||
<pre> |
|||
-- a single line comment |
|||
--[[ |
|||
a multi-line comment |
|||
]] |
|||
</pre> |
|||
== Variables == |
|||
By default, Lua variables are set in [https://en.wikipedia.org/wiki/Global_variable '''global''' scope] (meaning it is exposed to the entire Lua engine). To create a variable in [https://en.wikipedia.org/wiki/Local_variable '''local''' scope], the <code>local</code> keyword must be used: |
|||
Example of exposing a static object & enums to Lua: |
|||
<pre> |
<pre> |
||
-- |
-- a global variable |
||
var1 = "Hello world!" |
|||
ConversationStates = luajava.bindClass("games.stendhal.server.entity.npc.ConversationStates") |
|||
-- a local variable |
|||
-- access the enum values like so |
|||
local var2 = "Hello world!" |
|||
ConversationStates.IDLE |
|||
</pre> |
</pre> |
||
== Data Types == |
|||
Example of creating an object instance: |
|||
Some common data types in Lua are ''string'', ''integer'', ''boolean'', & ''table''. Type names do not need to be declared when setting variables. |
|||
Examples: |
|||
<pre> |
<pre> |
||
-- |
-- string variable |
||
local var1 = "Hello world!" |
|||
local dog = luajava.newInstance("games.stendhal.server.entity.npc.SilentNPC") |
|||
-- access object methods like so |
|||
dog:setEntityClass("animal/puppy") |
|||
dog:setPosition(2, 5) |
|||
-- integer variable |
|||
-- class with constructor using parameters |
|||
local var2 = 11 |
|||
local speaker = luajava.newInstance("games.stendhal.server.entity.npc.SpeakerNPC", "Frank") |
|||
speaker:setOutfit("body=0,head=0,eyes=0,hair=5,dress=5") |
|||
-- boolean variable |
|||
speaker:setPosition(2, 6) |
|||
local var3 = true |
|||
-- table variable |
|||
local var4 = {} |
|||
</pre> |
</pre> |
||
=== Strings === |
|||
To make scripting easier, Stendhal employs a {{StendhalFile|master|src/games/stendhal/server/core/scripting/lua/init.lua|master script}} & some helper objects & methods to handle the functionality mentioned above. An explanation of these objects & methods follows. |
|||
=== |
==== String Concatenation ==== |
||
String concatenation is simple, much like Java uses a plus operator (<code>+</code>) to join strings, Lua uses two periods (<code>..</code>). |
|||
The main object that handles setting zone & adding entities to game. |
|||
Example: |
|||
Methods: |
|||
<pre> |
|||
* <code>game:add(object)</code> - Adds an object to the current zone. |
|||
-- create a string variable |
|||
* <code>game:setZone(name)</code> - Sets the current zone. |
|||
local var = "Hello" |
|||
* <code>game:createSign(visible)</code> - Creates a new {{StendhalFile|master|src/games/stendhal/server/entity/mapstuff/sign/Sign.java|Sign}} instance. |
|||
* <code>game:createShopSign(name, title, caption, seller)</code> - Creates a new {{StendhalFile|master|src/games/stendhal/server/entity/mapstuff/sign/ShopSign.java|ShopSign}} instance. |
|||
-- append another string |
|||
=== npcHelper === |
|||
var = var .. " world!" |
|||
print(var) -- prints "Hello world!" |
|||
This object helps to create instances of {{StendhalFile|master|src/games/stendhal/server/entity/npc/SpeakerNPC.java|SpeakerNPC}} & {{StendhalFile|master|src/games/stendhal/server/entity/npc/SilentNPC.java|SilentNPC}} classes. |
|||
</pre> |
|||
=== Tables === |
|||
Methods: |
|||
* <code>npcHelper:createSpeakerNPC(name)</code> - Creates a new SpeakerNPC. |
|||
* <code>npcHelper:createSilentNPC()</code> - Creates a new SilentNPC. |
|||
* <code>npcHelper:setPath(npc, path, loop)</code> - Sets the path for the specified NPC. |
|||
* <code>npcHelper:setPathAndPosition(npc, path, loop)</code> - Sets the path & starting position of the specified NPC. |
|||
* <code>npcHelper:addMerchant(merchantType, npc, items, offer)</code> - Adds merchant behavior to <code>npc</code> of either a buyer or seller defined by <code>merchantType</code>. |
|||
* <code>npcHelper:addSeller(npc, items, offer)</code> - Adds seller merchant behavior to <code>npc</code>. |
|||
* <code>npcHelper:addBuyer(npc, items, offer)</code> - Adds buyer merchant behavior to <code>npc</code>. |
|||
A Lua table is a data type similar to a Java list or map. Tables can be indexed or use key=value pairs. |
|||
== Setting Zone == |
|||
''(<span style="color:red;">IMPORTANT NOTE: Lua table indexes begin at 1, not 0</span>)'' |
|||
==== Creating Tables ==== |
|||
An empty table is initialized with a pair of curly braces (<code>{}</code>): |
|||
<pre> |
|||
local mytable = {} |
|||
</pre> |
|||
You can add values to indexed tables at initialization or with the <code>table.insert</code> method: |
|||
<pre> |
|||
-- create a table with values |
|||
local mytable = {"foo"} |
|||
-- add value |
|||
table.insert(mytable, "bar") |
|||
</pre> |
|||
To create a key=value table, any of the following methods can be used to add values: |
|||
<pre> |
|||
-- 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" |
|||
</pre> |
|||
==== Accessing Table Values ==== |
|||
Square brackets (<code>[]</code>) enclosing an index number are used to access values in indexed tables (''remember that Lua table indexes start at "1" not "0"''): |
|||
<pre> |
|||
local mytable = {"foo", "bar"} |
|||
print(mytable[1]) -- prints "foo" |
|||
print(mytable[2]) -- prints "bar" |
|||
</pre> |
|||
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 <code>.</code>: |
|||
<pre> |
|||
local mytable = {foo="bar"} |
|||
-- using square brackets |
|||
print(mytable["foo"]) -- prints "bar" |
|||
-- using concatenated member |
|||
print(mytable.foo) -- prints "bar" |
|||
</pre> |
|||
==== Iterating Tables ==== |
|||
Tables can be iterated in a <code>for</code> loop using the <code>pairs</code> or <code>ipairs</code> iterators. Loops are terminated with the <code>end</code> keyword: |
|||
<pre> |
|||
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 |
|||
</pre> |
|||
Output: |
|||
<pre> |
|||
indexes: |
|||
1 |
|||
2 |
|||
values: |
|||
foo |
|||
bar |
|||
</pre> |
|||
Using a key=value table: |
|||
<pre> |
|||
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 |
|||
</pre> |
|||
Output: |
|||
<pre> |
|||
keys: |
|||
foo |
|||
bar |
|||
values: |
|||
hello |
|||
world! |
|||
</pre> |
|||
See also: [http://lua-users.org/wiki/TablesTutorial Lua Tables Tutorial] |
|||
=== Functions === |
|||
Like normal variables, functions can be declared as '''global''' or '''local''' & must be terminated with the <code>end</code> keyword. |
|||
There are two ways to define functions with the <code>function</code> keyword: |
|||
<pre> |
|||
local function myFunction() |
|||
print("Hello world!") |
|||
end |
|||
</pre> |
|||
or |
|||
<pre> |
|||
local myFunction = function() |
|||
print("Hello world!") |
|||
end |
|||
</pre> |
|||
Functions can also be members of a table: |
|||
<pre> |
|||
local myTable = {} |
|||
function myTable.myFunction() |
|||
print("Hello world!") |
|||
end |
|||
</pre> |
|||
or |
|||
<pre> |
|||
local myTable = {} |
|||
myTable.myFunction = function() |
|||
print("Hello world!") |
|||
end |
|||
</pre> |
|||
or |
|||
<pre> |
|||
local myTable = { |
|||
myFunction = function() |
|||
print("Hello world!") |
|||
end, |
|||
} |
|||
-- execute with |
|||
myTable.myFunction() |
|||
</pre> |
|||
== Comparison Operators == |
|||
{| class="wikitable" |
|||
|+ Logical Operators |
|||
! Operator !! Description !! Java Equivalent |
|||
|- |
|||
| and || logical ''and'' || && |
|||
|- |
|||
| or || logical ''or'' || <nowiki>||</nowiki> |
|||
|- |
|||
| not || logical ''opposite'' || ! |
|||
|} |
|||
{| class="wikitable" |
|||
|+ 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 the zone to work with, use the <code>game</code> object: |
To set the zone to work with, use the <code>game</code> object: |
||
| Line 71: | Line 270: | ||
</pre> |
</pre> |
||
=== Create New Zone === |
|||
The logger is exposed to Lua via the <code>logger</code> object: |
|||
It is recommended to create new zones in the XML configurations in {{StendhalFile|master|data/conf/zones|data/conf/zones}}. |
|||
Currently creating new zones via Lua is not supported. |
|||
=== Add Zone Music === |
|||
Music can be added to zones with the <code>game:setMusic</code> function. It supports the following arguments: |
|||
* <span style="color:darkgreen; font-style:italic;>filename:</span> Basename of the OGG audio file to use stored in {{StendhalFile|master|data/music|data/music}}. |
|||
* <span style="color:darkgreen; font-style:italic;>args:</span> A table of key=value integers. |
|||
* Valid keys: |
|||
** <span style="color:darkblue; font-style:italic;">volume:</span> Volume level (default: 100). |
|||
** <span style="color:darkblue; font-style:italic;">x:</span> The horizontal point for the source of the music (default: 1). |
|||
** <span style="color:darkblue; font-style:italic;">y:</span> The vertical point for the source of the music (default: 1). |
|||
** <span style="color:darkblue; font-style:italic;">radius:</span> The radial range at which the music can be heard (default: 10000). |
|||
Example: |
|||
<pre> |
<pre> |
||
if game:setZone("0_semos_plains_n") then |
|||
local zone = "0_semos_city" |
|||
game:setMusic("pleasant_creek_loop", {volume=85, radius=100}) |
|||
if game:setZone(zone) then |
|||
-- do something |
|||
else |
|||
logger:error("Could not set zone: " .. zone) |
|||
end |
end |
||
</pre> |
</pre> |
||
| Line 86: | Line 298: | ||
=== Signs === |
=== Signs === |
||
Signs can be created with <code> |
Signs can be created with <code>entities:createSign</code> and <code>entities:createShopSign</code>: |
||
<pre> |
<pre> |
||
| Line 92: | Line 304: | ||
if game:setZone(zone) then |
if game:setZone(zone) then |
||
-- create the sign instance |
-- create the sign instance |
||
local sign = |
local sign = entities:createSign() |
||
sign:setEntityClass("signpost") |
sign:setEntityClass("signpost") |
||
sign:setPosition(12, 55) |
sign:setPosition(12, 55) |
||
| Line 106: | Line 318: | ||
=== NPCs === |
=== NPCs === |
||
Use the <code> |
Use the <code>entities:createSpeakerNPC</code> method to create an interactive NPC: |
||
<pre> |
<pre> |
||
| Line 112: | Line 324: | ||
if game:setZone(zone) then |
if game:setZone(zone) then |
||
-- Use helper object to create a new NPC |
-- Use helper object to create a new NPC |
||
local npc = |
local npc = entities:createSpeakerNPC("Lua") |
||
npc:setEntityClass("littlegirlnpc") |
npc:setEntityClass("littlegirlnpc") |
||
npc:setPosition(10, 55) |
npc:setPosition(10, 55) |
||
| Line 125: | Line 337: | ||
} |
} |
||
npc:setPath(nodes) |
|||
-- Use helper object to create NPC path |
|||
npcHelper:setPath(npc, nodes) |
|||
-- Dialogue |
-- Dialogue |
||
| Line 137: | Line 348: | ||
logger:error("Could not set zone: " .. zone) |
logger:error("Could not set zone: " .. zone) |
||
end |
end |
||
</pre> |
|||
==== Adding Transitions ==== |
|||
A simple example of adding a chat transition can be done without any special functionality: |
|||
<pre> |
|||
local frank = entities:createSpeakerNPC("Frank") |
|||
frank:add(ConversationStates.IDLE, |
|||
ConversationPhrases.GREETING_MESSAGES, |
|||
nil, |
|||
ConversationStates.ATTENDING, |
|||
"Hello.", |
|||
nil) |
|||
</pre> |
|||
This simply adds a response to saying "hello" & sets the NPC to attend to the player (equivalent of <code>frank:addGreeting("Hello")</code>). |
|||
For more complicated behavior, we need to use some helper methods. If we want to check a condition we use the <code>conditions:create</code> 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: |
|||
<pre> |
|||
frank:add(ConversationStates.IDLE, |
|||
ConversationPhrases.GREETING_MESSAGES, |
|||
conditions:create("PlayerHasItemWithHimCondition", {"money"}), |
|||
ConversationStates.ATTENDING, |
|||
"Hello.", |
|||
nil) |
|||
</pre> |
|||
In this scenario, the NPC will only respond if the player is carrying <item>money</item>. |
|||
A NotCondition instance can be created with the <code>actions:notCondition</code> method: |
|||
Example usage: |
|||
<pre> |
|||
local condition = conditions.notCondition(conditions:create("PlayerHasItemWithHimCondition", {"money"}) |
|||
</pre> |
|||
To add a ChatAction, we use the <code>actions:create</code> method. Its usage is identical to <code>conditions:create</code>. |
|||
Example: |
|||
<pre> |
|||
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})) |
|||
</pre> |
|||
Lua tables can be used to add multiple conditions or actions: |
|||
<pre> |
|||
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}), |
|||
}) |
|||
</pre> |
|||
In this scenario, the NPC will respond if the player has money & is not naked. |
|||
Nested tables are supported as well: |
|||
<pre> |
|||
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}), |
|||
}) |
|||
</pre> |
</pre> |
||
==== Adding Merchant Behavior ==== |
==== Adding Merchant Behavior ==== |
||
The <code>merchants</code> object is used for adding merchant behavior (buying/selling) to an NPC. |
|||
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 <code>true</code>, will add default replies for "offer". |
|||
Example of adding seller behavior to an NPC: |
Example of adding seller behavior to an NPC: |
||
<pre> |
<pre> |
||
if game:setZone("0_semos_city") then |
if game:setZone("0_semos_city") then |
||
local frank = |
local frank = entities.createSpeakerNPC("Frank") |
||
merchants:addSeller(frank, merchants.shops:get("shopname"), true) |
|||
game:add(frank) |
game:add(frank) |
||
| Line 190: | Line 479: | ||
Then add the seller behavior using the custom list: |
Then add the seller behavior using the custom list: |
||
<pre> |
<pre> |
||
merchants:addSeller(frank, priceList, true) |
|||
</pre> |
</pre> |
||
== System Properties == |
|||
Java's system properties are exposed to Lua with the <code>properties</code> object. |
|||
Examples: |
|||
<pre> |
|||
-- 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 |
|||
</pre> |
|||
== 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 is quite simple. |
|||
<pre> |
|||
-- "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") |
|||
</pre> |
|||
= See Also = |
|||
* [[StendhalScripting/LuaAPI|Lua API]] |
|||
[[Category:Stendhal]] |
|||
[[Category:Documentation]] |
|||
[[Category:API]] |
|||
[[Category:Scripting]] |
|||
[[Category:Lua]] |
|||