vultraz's lua questions

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

Moderator: Forum Moderators

Post Reply
User avatar
Elvish_Hunter
Posts: 1575
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: vultraz's lua questions

Post by Elvish_Hunter »

The helper.get_child() function returns data in form of a userdata object. This kind of objects cannot be handled by ordinary Lua means, and for example attempting to use pairs on it yields a nice error:

Code: Select all

bad argument #1 to 'pairs' (table expected, got userdata)
However, userdata objects have four special fields: __parsed, __literal, __shallow_parsed and __shallow_literal.
Their purpose is to allow accessing userdata stuff as if they were ordinary WML tables, and since I suppose that you still want to perform variable subtitution, then you need to use the __parsed field:

Code: Select all

local filter = helper.get_child(cfg, "filter").__parsed
If you want a faster code, and you don't need accessing sub-tags that may be contained in the filter, you can try using __shallow_parsed:

Code: Select all

local filter = helper.get_child(cfg, "filter").__shallow_parsed
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)
User avatar
vultraz
Developer
Posts: 960
Joined: February 7th, 2011, 12:51 pm
Location: Dodging Daleks

Re: vultraz's lua questions

Post by vultraz »

Hmm... Crendgrim gave me a different solution:

Code: Select all

	local filter = helper.literal(helper.get_child(cfg, "filter"))
	if not filter then filter = wesnoth.tovconfig({id = "$unit.id"}) end
	filter.role = "hero"
	local units = wesnoth.get_units(filter)
Or do they do the same thing?
Also, am I doing the second line there right? We (me and Crend) weren't sure.
Thanks :)
Creator of Shadows of Deception (for 1.12) and co-creator of the Era of Chaos (for 1.12/1.13).
SurvivalXtreme rocks!!!
What happens when you get scared half to death...twice?
User avatar
Elvish_Hunter
Posts: 1575
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: vultraz's lua questions

Post by Elvish_Hunter »

The problem is that the __literal, __shallow_literal fields and helper.literal() function do not perform variable substitution, so you'll retrieve $unit.id exactly as a string stating "$unit.id". I'm not sure if get_units() will still work correctly also with __literal, as so far I ever needed to use the __parsed stuff. Since I think that you'll want to use $unit.id, I'd stay on the safe side and use __parsed.

Code: Select all

if not filter then filter = wesnoth.tovconfig({id = "$unit.id"}) end
So, the idea is to make a tag that requires a [filter] sub-tag, but if such sub-tag is missing it falls back on the auto-stored unit, instead of raising the usual error? That will be a non-standard behaviour, and as such a potential source of errors and bugs. Some days ago I insisted in typing "elif" (instead of "elseif") in Lua, then I remembered that "elif" is a Python command. It's so easy to do mistakes... :mrgreen:
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: vultraz's lua questions

Post by Anonymissimus »

Note that I haven't read everything here carefully, but...
Elvish_Hunter wrote:The problem is that the __literal, __shallow_literal fields and helper.literal() function do not perform variable substitution, so you'll retrieve $unit.id exactly as a string stating "$unit.id". I'm not sure if get_units() will still work correctly also with __literal, as so far I ever needed to use the __parsed stuff. Since I think that you'll want to use $unit.id, I'd stay on the safe side and use __parsed.
The safer thing is .__literal, since .__parsed may substitute a variable at a time already when it is not yet defined. In particular, the filter passed may contain a $this_unit and this automatically stored variable is only set once .get_units is called.
And yes, get_units substitutes variables fine. You can see that it converts the argument into a vconfig object in case that it isn't already a vconfig userdata:
vconfig filter = luaW_checkvconfig(L, 1, true);
(and that call path)
vultraz wrote:

Code: Select all

	local filter = helper.literal(helper.get_child(cfg, "filter"))
	if not filter then filter = wesnoth.tovconfig({id = "$unit.id"}) end
	filter.role = "hero"
	local units = wesnoth.get_units(filter)
Looking at this I would expect an error in case that that filter is nil, since writing into a vconfig userdata shouldn't work. helper.literal() probably returns always at least an empty table so it's never nil anyway.

EDIT
Here's your code from above copy-pasted and corrected (untested):

Code: Select all

function wml_actions.check_inventory(cfg)
	local filter = helper.get_child(cfg, "filter") or
		helper.wml_error "[check_inventory] missing required [filter] tag"
	filter = helper.shallow_literal(filter)--just inserting this line here should be all that's needed
	filter.role = "hero"
	local units = wesnoth.get_units(filter)

	for i, u in ipairs(units) do
		local u_id = units[i].id
		local items_carried = helper.get_variable_array(string.format("%s.%s", u_id, "inventory"))

		for ii,k in ipairs(items_carried) do
			if items_carried[ii].id == cfg.item then
				local stuff_to_do = helper.get_child(cfg, "then") or
					helper.wml_error "[check_inventory] missing required [then] tag"
				wesnoth.fire(stuff_to_do.__literal)
				return
			end
		end
	end
end
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
User avatar
vultraz
Developer
Posts: 960
Joined: February 7th, 2011, 12:51 pm
Location: Dodging Daleks

Re: vultraz's lua questions

Post by vultraz »

Haven't had to use this in a long time, but a quick question: How would you execute some WML, stored in a WML variable, from lua, in a callback function?
Creator of Shadows of Deception (for 1.12) and co-creator of the Era of Chaos (for 1.12/1.13).
SurvivalXtreme rocks!!!
What happens when you get scared half to death...twice?
Luther
Posts: 128
Joined: July 28th, 2007, 5:19 pm
Location: USA

Re: vultraz's lua questions

Post by Luther »

This is untested, but I think it's pretty simple once you understand it:

Code: Select all

-- You need get_variable because you can't iterate over something you get
-- through set_wml_var_metatable.
wml = wesnoth.get_variable(var_name)

for _, tag in ipairs(wml) do
  wesnoth.wml_actions[tag[1]](tag[2])
end
EDIT: Fixed the indexes.
User avatar
Elvish_Hunter
Posts: 1575
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: vultraz's lua questions

Post by Elvish_Hunter »

vultraz wrote:How would you execute some WML, stored in a WML variable, from lua, in a callback function?
I'd try using wml_actions.command, like I did here:

Code: Select all

local do_actions = wesnoth.get_variable( 'my_var' )
wesnoth.wml_actions.command( do_actions )
Of course, you may need to replace get_variable with helper.get_variable_array, depending on the structure of your variable.
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)
User avatar
vultraz
Developer
Posts: 960
Joined: February 7th, 2011, 12:51 pm
Location: Dodging Daleks

Re: vultraz's lua questions

Post by vultraz »

The structure could possible multiple tags, subtags, and conditionals ([if],etc), or maybe just [object].
Creator of Shadows of Deception (for 1.12) and co-creator of the Era of Chaos (for 1.12/1.13).
SurvivalXtreme rocks!!!
What happens when you get scared half to death...twice?
User avatar
vultraz
Developer
Posts: 960
Joined: February 7th, 2011, 12:51 pm
Location: Dodging Daleks

Re: vultraz's lua questions

Post by vultraz »

Nextt question, for $100! :D

I'm trying to find a way to quickly insert a variable into a unit's [variables] node via Lua. Would this work?

Code: Select all

		local class_var = wesnoth.get_variable(string.format("character_development.class[%d]", wesnoth.get_variable("class_index")))
	
		local unit_being_developed = wesnoth.get_unit(id = u_id)
		
		rawset("unit_being_developed.variables", "class", class_var.id)

		wesnoth.put_unit(unit_being_developed.x,unit_being_developed.y, unit_being_developed)
Creator of Shadows of Deception (for 1.12) and co-creator of the Era of Chaos (for 1.12/1.13).
SurvivalXtreme rocks!!!
What happens when you get scared half to death...twice?
User avatar
8680
Moderator Emeritus
Posts: 742
Joined: March 20th, 2011, 11:45 pm
Location: The past

Re: vultraz's lua questions

Post by 8680 »

No it wouldn’t. Try this:

Code: Select all

local class_id = wesnoth.get_variable(("character_development.class[%d].id"):format(wesnoth.get_variable("class_index")))
local unit_being_developed = wesnoth.get_units {id = u_id} [1] -- get_unit takes either (underlying_id) or (x, y).
unit_being_developed.variables.class = class_id                -- rawset would bypass the metamethod that tells the engine to set the variable.
wesnoth.put_unit(unit_being_developed)
User avatar
vultraz
Developer
Posts: 960
Joined: February 7th, 2011, 12:51 pm
Location: Dodging Daleks

Re: vultraz's lua questions

Post by vultraz »

Been forever since I've had to post something here, but I'm really stuck.

So basically, it seem that variables get substituted in the cfg userdata in a custom tag. So if you have, say [tag] name=$foo [/tag], using cfg.name in wml_actions.tag will give it the value of foo. But in my case, I have a problem. It parses the WHOLE thing, even if you do cfg = helper.shallow_parsed(cfg) before doing anything. I want to toplevel keys parsed, so they're written as variable value in the config, BUT, the keys in any subtags to remain as "$foo" in the config, and substituted when accessed. This is because later in the tag, I write the whole cfg field to a a unit's [variables] table, to be accessed later. If the values of the subtags' keys contains a variable, and they get substituted when [tag] is encountered, and if the variable doesn't exist, they will end up with an empty string ("") as a value.

I COULD simply write the entire config object as literal, and then pass it through wesnoth.tovconfig when I access it, but that would rely on any external variables to remain in existence, so I'd rather not do that unless I have to.

So, to sum it up, is there a way to write variable substitutions for specific parts of a config object, to a config object, and leave the rest alone to be substituted by whatever accesses them?

EDIT: I seem to have explained myself badly. What I want to do is shallow_parse cfg and return the rest as literal.

EDIT 2: 8680 gave me a solution that involved looping through subtags using a function from his lua pack and passing each through helper.literal. It works.

Code: Select all

	-- Parse the toplevel to substitute variables, then write the rest
	-- of the config object as literal to preserve variables for later
	-- access in the inventory
	cfg = helper.shallow_parsed(cfg)
	for subtag in lp8.subtags(cfg) do
		subtag[2] = helper.literal(subtag[2])
	end
Creator of Shadows of Deception (for 1.12) and co-creator of the Era of Chaos (for 1.12/1.13).
SurvivalXtreme rocks!!!
What happens when you get scared half to death...twice?
Post Reply