monochromatic's lua thread

Discussion of Lua and LuaWML support, development, and ideas.

Moderator: Forum Moderators

monochromatic
Posts: 1549
Joined: June 18th, 2009, 1:45 am

monochromatic's lua thread

Post by monochromatic »

:doh: 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 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.
Luther
Posts: 128
Joined: July 28th, 2007, 5:19 pm
Location: USA

Re: elvish_soveriegn and Lua

Post by Luther »

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.
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.
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?
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.
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?
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:
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. :mrgreen:

EDITED for clarification and corrected my code.
Last edited by Luther on January 10th, 2011, 11:06 pm, edited 3 times in total.
User avatar
Dixie
Posts: 1757
Joined: February 10th, 2010, 1:06 am
Location: $x1,$y1

Re: elvish_soveriegn and Lua

Post by Dixie »

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:

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
The return part would allow you to do the following:

Code: Select all

local foo = sow_random(1, 20)
3. You have to use the "for" iterator. The two most common instances being:

Code: Select all

for i,v in ipairs(table) do ... end
for k,v in pairs(table) do ... end
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 :)
Jazz is not dead, it just smells funny - Frank Zappa
Current projects: Internet meme Era, The Settlers of Wesnoth
User avatar
Elvish_Hunter
Posts: 1575
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: elvish_soveriegn and Lua

Post by Elvish_Hunter »

From an elf to another: welcome! :wink:
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.
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.
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.
It was very useful also for me, so it's worth a look.
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:
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).
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)
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: elvish_sovereign and Lua

Post by Anonymissimus »

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:

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()
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.
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
monochromatic
Posts: 1549
Joined: June 18th, 2009, 1:45 am

Re: elvish_soveriegn and Lua

Post by monochromatic »

Wow, such a warm response from the Lua crowd! Thanks for explaining, a lot of things make more sense now.
@Luther Thanks a bunch :)
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.
Mine too. :) I found it more clear and less confusing than either documentation on the Lua website.
@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]
It would be a little like this (untested)?

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]
:oops: And yes, I may be making up code.
Last edited by monochromatic on January 14th, 2011, 8:29 pm, edited 1 time in total.
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: elvish_soveriegn and Lua

Post by silene »

elvish_sovereign wrote:

Code: Select all

        affected_units = wesnoth.get_units { side = "$side_number" , { "filter_location" , { terrain = "Ce" } } }
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

        for value in pairs(affected_units) do
Table-based loops work on two values: the table key and the table value. So in your case, it would be

Code: Select all

for key,value in ipairs(affected_units) do
In this particular case, the "key" variable will be the unit number in the table, while "value" will be the actual unit (a so-called unit proxy). 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.
elvish_sovereign wrote:

Code: Select all

                    wesnoth.set_variable( "affected_units.status.slowed" , yes )
"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".
"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
elvish_sovereign wrote:

Code: Select all

                    wesnoth.fire( "unstore_unit" , { variable = "affected_units" , find_vacant = "no" , text = _ "slowed" , red = 255 }
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 do

Code: Select all

wesnoth.put_unit(value)
So the only thing left is the label:

Code: Select all

wesnoth.float_label(value.x, value.y, _"<span color='red'>slowed</span>")
By the way, the "_" is not a special symbol, it is just a plain variable (which is supposed to contain a function), and your code doesn't define it. So you would need the following line somewhere:

Code: Select all

_ = wesnoth.textdomain "yourdomain"
User avatar
Elvish_Hunter
Posts: 1575
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: elvish_sovereign and Lua

Post by Elvish_Hunter »

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? :hmm: 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
and you'll be able to use it like any other WML tag

Code: Select all

[slow]
	id = $unit.id # SUF, don't use a [filter] tag
[/slow]
The 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.
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)
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: elvish_sovereign and Lua

Post by Anonymissimus »

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.
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 ;)).
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] )
wesnoth.wml_actions is only in 1.9, and whether wesnoth.fire will vanish isn't decided yet I think.
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: elvish_sovereign and Lua

Post by silene »

Anonymissimus wrote:
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.
Uh, what ? So pairs is a candidate for causing OOS errors ?
Once you know what "pairs" actually does, it becomes clearer.

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.
monochromatic
Posts: 1549
Joined: June 18th, 2009, 1:45 am

Re: elvish_sovereign and Lua

Post by monochromatic »

@silene Cool! So it should look a little like this:

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
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).
silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: elvish_sovereign and Lua

Post by silene »

elvish_sovereign wrote:@silene Cool! So it should look a little like this:
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.
monochromatic
Posts: 1549
Joined: June 18th, 2009, 1:45 am

Re: elvish_sovereign and Lua

Post by monochromatic »

:doh: Trying to understand what I'm doing wrong.

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
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?
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: elvish_sovereign and Lua

Post by Anonymissimus »

-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
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
monochromatic
Posts: 1549
Joined: June 18th, 2009, 1:45 am

Re: elvish_sovereign and Lua

Post by monochromatic »

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?
Post Reply