monochromatic's lua thread
Moderator: Forum Moderators
-
- Posts: 1549
- Joined: June 18th, 2009, 1:45 am
monochromatic's lua thread
Okay, I'm going to risk looking stupid.
Anyways, I've been looking at a couple online tutorials for Lua and the documentation in LuaWML, and I think I have a general grasp for the syntax and stuff. I've looked at a number or examples (especially the ones in core), and I've been able to understand what it does. But I still have a number of questions.
1. What exactly is the difference between a normal global variable
2. What does
3. What is the Lua equivalent of
Please bear in mind that I'm still relatively inexperienced at coding an such, and this is my real first language besides WML and some basic HTML.
Anyways, I've been looking at a couple online tutorials for Lua and the documentation in LuaWML, and I think I have a general grasp for the syntax and stuff. I've looked at a number or examples (especially the ones in core), and I've been able to understand what it does. But I still have a number of questions.
1. What exactly is the difference between a normal global variable
local
global variable? (I know that if the local
is within a block, it will just be applicable to that block.) I've read local
ones are faster, since they are indexed. I'll sometimes see in the code that some global variables are localized and some are not.2. What does
return
do exactly? I know break
stops the action in a do
block, but what exactly is the difference between break
and return
?3. What is the Lua equivalent of
{FOREACH}
? I didn't find anything in the wiki (but I may very well be blind too). I read table.foreach
is deprecated for Lua 5.1, and one should use for ... in pairs(...)
. Is that it?Please bear in mind that I'm still relatively inexperienced at coding an such, and this is my real first language besides WML and some basic HTML.
Last edited by monochromatic on March 31st, 2011, 11:12 pm, edited 3 times in total.
Re: elvish_soveriegn and Lua
In Lua, every file or code snippit inside a [lua] tag is called a "chunk". Every chunk has its own scope, just like a function. If you declare a variable local, but outside any function, it will be visible only to that chunk. A truly global variable is visible anywhere in the environment.elvish_sovereign wrote: 1. What exactly is the difference between a normal global variablelocal
global variable? (I know that if thelocal
is within a block, it will just be applicable to that block.) I've readlocal
ones are faster, since they are indexed. I'll sometimes see in the code that some global variables are localized and some are not.
2. What doesreturn
do exactly? I knowbreak
stops the action in ado
block, but what exactly is the difference betweenbreak
andreturn
?
return
designates what a function or chunk is supposed to evaluate to. It also exits the function immediately. Example:
Code: Select all
function getNumber()
return 42
--Can't execute this code
if y>0 then
doSomething()
end
end
x = getNumber()
x
will equal 42. return
without a value might be useful to let you break out of a function from inside a loop.Pretty much, yes. But there's a few different ways to use for loops, so it gets kind of complicated. (Also, I've never used the3. What is the Lua equivalent of{FOREACH}
? I didn't find anything in the wiki (but I may very well be blind too). I readtable.foreach
is deprecated for Lua 5.1, and one should usefor ... in pairs(...)
. Is that it?
{FOREACH}
macro.) The Lua reference might help:http://www.lua.org/manual/5.1/manual.html#2.4.5
http://www.lua.org/manual/5.1/manual.html#pdf-ipairs
http://www.lua.org/manual/5.1/manual.html#pdf-pairs
That stuff is pretty dense, though, so let us know if you have more questions.
EDITED for clarification and corrected my code.
Last edited by Luther on January 10th, 2011, 11:06 pm, edited 3 times in total.
Re: elvish_soveriegn and Lua
I don't know if you found that site while looking on the net - it was my no1 reference for learning what I know of Lua, and I still widely refer to it when I encounter problems.
As for your questions, I'll try to answer what I can:
1. No idea
2. Well, simply said, it returns stuff. Imagine you have a function. Sometimes, you want a funtion to just execute a bunch of stuff when it is called, but sometimes you want it to return a value. Take this random function, for instance:
The return part would allow you to do the following:
3. You have to use the "for" iterator. The two most common instances being:
The difference is that for ipairs, only numerical indexes will be returned (in order). The table must be an array (or at least contain some numerical indexes). For pairs, all indexes will be returned (if your table was a dictionary, for example), including the numerical indexes if there are any, but in no particular order.
Edit: ninja'd, but still
As for your questions, I'll try to answer what I can:
1. No idea
2. Well, simply said, it returns stuff. Imagine you have a function. Sometimes, you want a funtion to just execute a bunch of stuff when it is called, but sometimes you want it to return a value. Take this random function, for instance:
Code: Select all
function sow_random(min, max)
if not max then min, max = 1, min end
wesnoth.fire("set_variable", { name = "LUA_random", rand = string.format("%d..%d", min, max) })
local res = wesnoth.get_variable "LUA_random"
wesnoth.set_variable "LUA_random"
return wesnoth.synchronize_choice(function() return { value = res } end).value
end
Code: Select all
local foo = sow_random(1, 20)
Code: Select all
for i,v in ipairs(table) do ... end
for k,v in pairs(table) do ... end
Edit: ninja'd, but still
Jazz is not dead, it just smells funny - Frank Zappa
Current projects: Internet meme Era, The Settlers of Wesnoth
Current projects: Internet meme Era, The Settlers of Wesnoth
- Elvish_Hunter
- Posts: 1576
- Joined: September 4th, 2009, 2:39 pm
- Location: Lintanir Forest...
Re: elvish_soveriegn and Lua
From an elf to another: welcome!
Another good reason is that, by using local variables, you don't risk overwriting global ones. If you have an "a" variable in your function, and you have an "a" variable elsewhere in the same program, you'll end up overwriting each other - unless one of them is local.elvish_sovereign wrote:1. What exactly is the difference between a normal global variable local global variable? (I know that if the local is within a block, it will just be applicable to that block.) I've read local ones are faster, since they are indexed. I'll sometimes see in the code that some global variables are localized and some are not.
It was very useful also for me, so it's worth a look.Dixie wrote:I don't know if you found that site while looking on the net - it was my no1 reference for learning what I know of Lua, and I still widely refer to it when I encounter problems.
Another difference is that WML needs the {NEXT} macro to make {FOREACH} working. Lua doesn't need this to do the cycle for (Lua has a next() function, but has another purpose). Don't forget that Lua, unlike WML and almost all other programming languages, starts counting from 1 instead of 0 (this applies to table indexes, for example).Luther wrote:Pretty much, yes. But there's a few different ways to use for loops, so it gets kind of complicated. (Also, I've never used the {FOREACH} macro.) The Lua reference might help:
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
-
- Inactive Developer
- Posts: 2461
- Joined: August 15th, 2008, 8:46 pm
- Location: Germany
Re: elvish_sovereign and Lua
To elaborate more on 1). In programming languages things have a scope where they are valid. Such scope can in lua be a file, function, if-block-section, loop and probably more which I don't know about atm. If a variable from an outer scope is redeclared in an inner one the outer variable is still valid in the inner but no longer visible. When leaving the inner scope, the variable is considered the one from the next outer scope (thats why it was still valid in the inner but not visible since the value of the inner was taken). Here is some code snippet which you can put into a file and load it with wesnoth.dofile to explain it:
If we query a now at the place the file was loaded it is 0.
This is how I learned it for java however and it appears to be the same in C++ and lua. It also applies for functions in lua btw, not only variables.
Code: Select all
a = 0
local a = 1
wesnoth.message(a)--1
local function fun()
local a = 2
for i = 1, 2 do
local a = a
if i ==1 then
a = 3
end
wesnoth.message(a)--3, 2
end
wesnoth.message(a)--2
end
fun()
local function other_fun()
wesnoth.message(a)--1
end
other_fun()
This is how I learned it for java however and it appears to be the same in C++ and lua. It also applies for functions in lua btw, not only variables.
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml starters • Plan Your Advancements: mp mod
The Earth's Gut: sp campaign • Settlers of Wesnoth: mp scenario • Wesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
A Simple Campaign: campaign draft for wml starters • Plan Your Advancements: mp mod
The Earth's Gut: sp campaign • Settlers of Wesnoth: mp scenario • Wesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
-
- Posts: 1549
- Joined: June 18th, 2009, 1:45 am
Re: elvish_soveriegn and Lua
Wow, such a warm response from the Lua crowd! Thanks for explaining, a lot of things make more sense now.
@Luther Thanks a bunch
@Elvish_Hunter Awesome!
@Anonymissimus Got it!
So for example, if I'm trying to re-write this section of WML in Lua (a simplified version from a WIP project):
It would be a little like this (untested)?
And yes, I may be making up code.
@Luther Thanks a bunch
Mine too. I found it more clear and less confusing than either documentation on the Lua website.Dixie wrote:I don't know if you found that site while looking on the net - it was my no1 reference for learning what I know of Lua, and I still widely refer to it when I encounter problems.
@Elvish_Hunter Awesome!
@Anonymissimus Got it!
So for example, if I'm trying to re-write this section of WML in Lua (a simplified version from a WIP project):
Code: Select all
[event]
name = side turn
first_time_only = no
[store_unit]
[filter]
side = $side_number
[filter_location]
terrain = Ce
[/filter_location]
[/filter]
variable = affected_units
kill = no
[/store_unit]
{FOREACH affected_units i}
{VARIABLE affected_units[$i].status.slowed yes}
[unstore_unit]
variable = affected_units[$i]
find_vacant = no
text = _ "slowed"
{COLOR_HARM}
[/unstore_unit]
{NEXT i}
[/event]
Code: Select all
[event]
name = side turn
first_time_only = no
[lua]
code = <<
affected_units = wesnoth.get_units { side = "$side_number" , { "filter_location" , { terrain = "Ce" } } }
for value in pairs(affected_units) do
wesnoth.set_variable( "affected_units.status.slowed" , yes )
wesnoth.fire( "unstore_unit" , { variable = "affected_units" , find_vacant = "no" , text = _ "slowed" , red = 255 }
end >>
[/lua]
[/event]
Last edited by monochromatic on January 14th, 2011, 8:29 pm, edited 1 time in total.
Re: elvish_soveriegn and Lua
The "affected_units" variable won't be used outside this block, so you may just as well declare it "local".elvish_sovereign wrote:Code: Select all
affected_units = wesnoth.get_units { side = "$side_number" , { "filter_location" , { terrain = "Ce" } } }
Table-based loops work on two values: the table key and the table value. So in your case, it would beelvish_sovereign wrote:Code: Select all
for value in pairs(affected_units) do
Code: Select all
for key,value in ipairs(affected_units) do
"yes" is the name of a global variable, presumably missing. Never use it, it won't work. Instead, use either "true" or "'yes'" (notice the extra quotes). I suggest "true".elvish_sovereign wrote:Code: Select all
wesnoth.set_variable( "affected_units.status.slowed" , yes )
"wesnoth.set_variable" modifies a WML variable, not a Lua one, and "affected_unit" is a Lua variable. You want to modify the status of the currently iterated unit, so
Code: Select all
value.status.slowed = true
Don't use the [unstore_unit] tag unless you are unstoring the content of a WML variable. In your particular case, you don't need to unstore anything, since the previous line has already modified the "slowed" status of the unit on the map. If you wanted to unstore a Lua variable (again, it's superfluous here, since the unit never left the map), you would doelvish_sovereign wrote:Code: Select all
wesnoth.fire( "unstore_unit" , { variable = "affected_units" , find_vacant = "no" , text = _ "slowed" , red = 255 }
Code: Select all
wesnoth.put_unit(value)
Code: Select all
wesnoth.float_label(value.x, value.y, _"<span color='red'>slowed</span>")
Code: Select all
_ = wesnoth.textdomain "yourdomain"
- Elvish_Hunter
- Posts: 1576
- Joined: September 4th, 2009, 2:39 pm
- Location: Lintanir Forest...
Re: elvish_sovereign and Lua
Just to expand a little what silene said:
- wesnoth.fire is deprecated; should you need to fire a WML action tag, you can call it using wesnoth.wml_actions.kill (for example, this calls [kill] )
- mixing WML and Lua? Several text editors offer syntax higlighting, that really helps you in coding. So, it's better to place your Lua code in a separate file, and call it in a preload event, or in the _main.cfg, using wesnoth.dofile
- and, if you have things in a separate file, the simplest way to use it is creating a new WML tag! Your code to slow units can become a [slow] tag (1.9 syntax):and you'll be able to use it like any other WML tagThe if not check serves the purpose to avoid slowing and already slowed unit. However, I didn't yet inserted a check for changing the floating label depending on the unit's gender, but it can easily be done.
- wesnoth.fire is deprecated; should you need to fire a WML action tag, you can call it using wesnoth.wml_actions.kill (for example, this calls [kill] )
- mixing WML and Lua? Several text editors offer syntax higlighting, that really helps you in coding. So, it's better to place your Lua code in a separate file, and call it in a preload event, or in the _main.cfg, using wesnoth.dofile
- and, if you have things in a separate file, the simplest way to use it is creating a new WML tag! Your code to slow units can become a [slow] tag (1.9 syntax):
Code: Select all
local _ = wesnoth.textdomain "wesnoth"
-- #textdomain wesnoth
function wml_actions.slow(cfg)
for index, unit in ipairs(wesnoth.get_units(cfg)) do
if not unit.status.slowed then
unit.status.slowed = true
wesnoth.float_label( unit.x, unit.y, string.format("<span color='red'>%s</span>", tostring( _"slowed" ) ) )
end
end
end
Code: Select all
[slow]
id = $unit.id # SUF, don't use a [filter] tag
[/slow]
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
-
- Inactive Developer
- Posts: 2461
- Joined: August 15th, 2008, 8:46 pm
- Location: Germany
Re: elvish_sovereign and Lua
Uh, what ? So pairs is a candidate for causing OOS errors ? This IS a problem...I should have noticed In SoW though...(Actually, I found little engine bugs through that project so far ).silene wrote:By the way, I suggest using "ipairs" instead of "pairs" when writing LuaWML; it makes clear that the same iteration order is chosen for all the hosts and replays.
wesnoth.wml_actions is only in 1.9, and whether wesnoth.fire will vanish isn't decided yet I think.Elvish_Hunter wrote: - wesnoth.fire is deprecated; should you need to fire a WML action tag, you can call it using wesnoth.wml_actions.kill (for example, this calls [kill] )
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml starters • Plan Your Advancements: mp mod
The Earth's Gut: sp campaign • Settlers of Wesnoth: mp scenario • Wesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
A Simple Campaign: campaign draft for wml starters • Plan Your Advancements: mp mod
The Earth's Gut: sp campaign • Settlers of Wesnoth: mp scenario • Wesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
Re: elvish_sovereign and Lua
Once you know what "pairs" actually does, it becomes clearer.Anonymissimus wrote:Uh, what ? So pairs is a candidate for causing OOS errors ?silene wrote:By the way, I suggest using "ipairs" instead of "pairs" when writing LuaWML; it makes clear that the same iteration order is chosen for all the hosts and replays.
Tables can be stored two ways in Lua internally: if all the keys are consecutive integers from 1 to n, then the table might be stored as a compact array, otherwise it is stored as hashtable. (I say "might be stored", because the internal representation depends not only on the key values but also on the way the table was built.) If the table is stored as an array, then "pairs" and "ipairs" will behave exactly the same. Otherwise, "pairs" iterates with respect to the hashtable buckets, which is is completely random but tremendously faster than "ipairs" in that generic case.
I'm almost certain that the tables returned by get_units and get_locations will always be stored as arrays. That's why it is impossible to notice a discrepancy between "pairs" and "ipairs" for these tables. Also, if the actions of your loop body do not depend on the actual iteration order (e.g. slowing all the units of a table), then you won't be able to notice a behavior change either. So usually you will never notice a synchronization issue caused by these functions, but it is still worth mentioning the issue so that people take good habits.
By the way, the "pairs" issue is the reason for the "location_set" library to provide "stable" versions of some of its functions. They are a lot costlier than the unstable ones, but they guarantee a reproducible behavior on all the clients.
-
- Posts: 1549
- Joined: June 18th, 2009, 1:45 am
Re: elvish_sovereign and Lua
@silene Cool! So it should look a little like this:
right? Awesome!
@Elvish_Hunter I wrote that from scratch here, so yeah. My lua snippets are in another file and are called with wesnoth.dofile
Oh and yes, I have trunk, so it's good to know wesnoth.fire is deprecated.
Will post again if I have more troubles (probably will).
Code: Select all
_ = wesnoth.textdomain "wesnoth"
local affected_units = wesnoth.get_units { side = "$side_number" , { "filter_location" , { terrain = "Ce" } } }
for key,value in ipairs(affected_units) do
value.status.slowed = true
wesnoth.float_label(value.x, value.y, _"<span color='red'>slowed</span>")
end
@Elvish_Hunter I wrote that from scratch here, so yeah. My lua snippets are in another file and are called with wesnoth.dofile
Oh and yes, I have trunk, so it's good to know wesnoth.fire is deprecated.
Will post again if I have more troubles (probably will).
Re: elvish_sovereign and Lua
Yes. But note that the string you are trying to translate does not exist in the "wesnoth" textdomain, so you have to modify the code a bit. Elvish_hunter has taken care of that issue in his snippet; he also avoids printing the label if the unit is already slowed. So it is worth a look.elvish_sovereign wrote:@silene Cool! So it should look a little like this:
-
- Posts: 1549
- Joined: June 18th, 2009, 1:45 am
Re: elvish_sovereign and Lua
Trying to understand what I'm doing wrong.
Trying to transform SCATTER_UNITS into Lua, as a practice project (and for use in my campaign). I guess the main question for me is, if I store the table index in a variable, how do I call it?
Code: Select all
helper = wesnoth.require "lua/helper.lua"
function wesnoth.wml_actions.scatter_units(cfg)
-- replacement for macro SCATTER_UNITS
local filter = ( helper.get_child( cfg , "filter" ) ) or helper.wml_error("Missing required [filter] attribute in [scatter_units]")
local side = cfg.side or helper.wml_error("Missing required 'side' attribute in [scatter_units]")
local type = cfg.type or helper.wml_error("Missing required 'type' attribute in [scatter_units]")
local number = cfg.number or helper.wml_error("Missing required 'number' attribute in [scatter_units]")
-- radius separating units, default 1
local padding_radius = cfg.padding_radius or "1"
local placed_units = 0
local possible_locations = wesnoth.get_locations(filter)
while placed_units < number do
local number_of_locs = # possible_locations
local rand_position = math.random(number_of_locs)
wesnoth.put_unit( possible_locations[rand_position].x , possible_locations[rand_position].y , { type = helper.rand(cfg.type) , side = cfg.side })
local possible_locations = wesnoth.get_locations( filter_location , { "not" , { possible_locations[rand_position].x , possible_locations[rand_position].y , radius = padding_radius } })
placed_units = placed_units + 1
end
end
-
- Inactive Developer
- Posts: 2461
- Joined: August 15th, 2008, 8:46 pm
- Location: Germany
Re: elvish_sovereign and Lua
-array entries returned by wesnoth.get_locations are accessed ...[1] and ...[2] (not .x and .y)
-dont' use math.random here since it'll break replays
-you can use table.remove instead of re-calling the locations filter
-dont' use math.random here since it'll break replays
-you can use table.remove instead of re-calling the locations filter
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml starters • Plan Your Advancements: mp mod
The Earth's Gut: sp campaign • Settlers of Wesnoth: mp scenario • Wesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
A Simple Campaign: campaign draft for wml starters • Plan Your Advancements: mp mod
The Earth's Gut: sp campaign • Settlers of Wesnoth: mp scenario • Wesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
-
- Posts: 1549
- Joined: June 18th, 2009, 1:45 am
Re: elvish_sovereign and Lua
Anonymissimus wrote:-array entries returned by wesnoth.get_locations are accessed ...[1] and ...[2] (not .x and .y)
-dont' use math.random here since it'll break replays
-you can use table.remove instead of re-calling the locations filter
1. So it should be
possible_locations[rand_position][1] , possible_locations[rand_position][2]
, right?2. Would I use
helper.rand
then?3. I thought about that, but then how would you filter out the padding_radius as well?