Another basic lua query

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

Moderator: Forum Moderators

User avatar
Spannerbag
Posts: 759
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Another basic lua query

Post by Spannerbag »

Hi,
Still playing with the forest animals micro ai and am rather lost after some progress.
The following test setup was merely to check I was capturing correct data and it sent me down a rabbit hole.

Yes, it is basically [store_unit] and I have a cutdown version that works. :shock:
Here though I have a couple of issues.

First, the code:
WML:

Code: Select all

  [event]
    name=side 1 turn 1
    {GENERIC_UNIT 1 Mage 4 4}
    [ftest]
      [filter]
        side=1
        canrecruit=yes
      [/filter]
    [/ftest]
lua:

Code: Select all

--- Parse filter and return units in array "units"
function wesnoth.wml_actions.ftest(cfg)
   wml.array_variables.ftest = {}
  local units = {}
  local unitsm = {}

  wesnoth.interface.add_chat_message(string.format("Length 'units' is %d.", #units))

  local units = wesnoth.units.find_on_map(cfg.filter)
  local unitsm = wesnoth.units.find(cfg.filter)

  wesnoth.interface.add_chat_message(string.format("Length 'units' is %d and 'unitsm' is %d.", #units, #unitsm))
  wesnoth.interface.add_chat_message(string.format("'units[1,2,3].id'  = %s,%s,%s.", units[1].id, units[2].id, units[3].id))
  wesnoth.interface.add_chat_message(string.format("'unitsm[1,2,3].id' = %s,%s,%s.", unitsm[1].id, unitsm[2].id, unitsm[3].id))

--  wml.array_variables.ftest = units
end
Using two types of units.find just to make this code use the same logic as the working code example below.

Output:
luamessage.jpg
luamessage.jpg (20.04 KiB) Viewed 1390 times
- - -

Issue #1
The [filter] contents are ignored?
(Every unit on the map is returned.)
I've read various code examples but I've struggled to follow the logic as it flows through various files.
When an entire tag contains just a filter things work, as in:

Code: Select all

--- If unit(s) found, assumes all are valid
function wesnoth.wml_actions.get_uxyid(cfg)
  local guxyid = wesnoth.units.find(cfg)
  local gu = {}
  wml.array_variables.gu = {}
  if guxyid then
    for index, unit in ipairs(guxyid) do
      if unit.valid == "map" then
        gu[index] = {x=unit.x,y=unit.y,id=unit.id}
      elseif unit.valid == "recall" then
        gu[index] = {x="recall",y="recall",id=unit.id}
      end
      wml.array_variables.gu = gu
    end
  end
end
So I'm guessing there's something wrong with my using cfg.filter?

Issue #2
If I enable the WML array assignment wml.array_variables.ftest = units I get an error:
luamessage2.jpg
That last line assigning a WML variable is disabled, it complains and I'm guessing the lua table isn't compatible with this assignment.
(Especially as in the working code I built a simple array from scratch.)

I have various speculations on what might be wrong but I'd rather ask someone who actually knows something about lua. :)

I did look for the code for [store_unit] but couldn't find it - if I've missed it, please let me know where it's located.

Any pointers on either or both these issues would be greatly appreciated.

Cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
User avatar
Celtic_Minstrel
Developer
Posts: 2371
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: Another basic lua query

Post by Celtic_Minstrel »

--1--

When you write config.filter, you're expecting WML like the following:

Code: Select all

[ftest]
	filter=something
[/ftest]
But the WML you've written is like the following:

Code: Select all

[ftest]
	[filter]
		id=whatever
	[/filter]
[/ftest]
So to get that in Lua, you use wml.get_child('filter'). See here for more info.

--2--

You seem to have left out the actual error message, but it's fairly obvious what's wrong here. When assigning to wml.array_variables.ftest, the right-hand side must be an array of WML tables. However, you've assigned an array of units instead. You can convert an array of units to an array of WML tables representing the units with the following code:

Code: Select all

for i = 1, #units do
	units[i] = units[i].__cfg
end
Spannerbag wrote: May 2nd, 2025, 11:57 am I did look for the code for [store_unit] but couldn't find it - if I've missed it, please let me know where it's located.
The code for most ActionWML tags is located either in data/lua/wml-tags.lua or one of the files in data/lua/wml/. A few are located in other places – data/lua/wml-flow.lua or even in the C++ source code in game_events/action_wml.cpp.

Assuming the tag is implemented in Lua, you should be able to find a function called wml_actions.tag_name in one of the above files. I believe [store_unit] is in wml-tags.lua.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
User avatar
Spannerbag
Posts: 759
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query

Post by Spannerbag »

Celtic_Minstrel wrote: May 2nd, 2025, 1:02 pm --1--
...
--2--
...
Thanks ever so much for the comprehensive explanation, really really helpful.
I might get fairly good at lua - in a few hundred years :augh: ... or not! :doh:
Thanks again,
cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
User avatar
Spannerbag
Posts: 759
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query

Post by Spannerbag »

Hi again,
Got everything working, no problems.
Just curious about a couple of things?
  1. Code: Select all

      for i = 1, #units do
        units[i] = units[i].__cfg
      end
    I'm assuming __cfg is a metamethod?
    Just intested in what changes this makes?
    Don't need detailed breakdown (but happy to read if you do provide this :) ) just a summary of the nature of the changes will be fine.
  2. Re. filter assignment:
    local filter = wml.get_child(cfg,"filter") or wml.error "[ftest] missing required [filter] tag"
    During debug wanted to check filter.
    Type was vconfig not string?
    Again, just out of curiosity, I see there is a wml.tovconfig.
    Is there a converse method to extract a string from (whatever flavour of) vconfig filter contained?
    (Assuming this is possible.)
I now am potentially able to tweak the custom ai a bit more and add another behaviour (never thought I'd get this far :D ).

Hope this all makes sense!

Cheers!
-- Spanner bag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
User avatar
Ravana
Forum Moderator
Posts: 3313
Joined: January 29th, 2012, 12:49 am
Location: Estonia
Contact:

Re: Another basic lua query

Post by Ravana »

1. It replaces each element in variable units with __cfg property of its original value.
2. For string format you have wml.tostring.
User avatar
Celtic_Minstrel
Developer
Posts: 2371
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: Another basic lua query

Post by Celtic_Minstrel »

The word "metamethod" is not a correct descriptor for __cfg. As for what the code does, Ravana summarized the logic of the assignment, and the __cfg call is basically equivalent to [store_unit] – that is, it converts the unit to WML.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
User avatar
Spannerbag
Posts: 759
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query

Post by Spannerbag »

Ravana wrote: May 3rd, 2025, 10:39 am 1. It replaces each element in variable units with __cfg property of its original value.
2. For string format you have wml.tostring.
Many thanks for replying, will remember wml.tostring!


Celtic_Minstrel wrote: May 3rd, 2025, 4:46 pm The word "metamethod" is not a correct descriptor for __cfg. As for what the code does, Ravana summarized the logic of the assignment, and the __cfg call is basically equivalent to [store_unit] – that is, it converts the unit to WML.
Thanks for clarifying what "__cfg" means/does, if it isn't a metamethod, what is the correct term out of interest please?

I didn't express my lack of understanding very well. :(
I am unfamiliar with the "__" notation; I gather that "__literal" means (I believe) "interpret this entity literally without processing special characters"?
So by implication "__cfg" presumably means "reformat entity data to align with WML format"?

When I first saw your code I kinda gathered that __cgf reformatted data (from ??? to WML) but had no idea what the original format was but that doesn't matter; I was just curious.

Again, thanks to both of you for your help and time, very much appreciated.

Cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
User avatar
Celtic_Minstrel
Developer
Posts: 2371
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: Another basic lua query

Post by Celtic_Minstrel »

The "__" isn't really a notation, it's just part of the name of something. I think we usually add "__" to the beginning of names that represent something other than "something that is part of this object". So, for example, if you have a vconfig, most names (like cfg.x or cfg.id) will be a key in that vconfig, but "__literal" is not a key – it's a special value. Similarly, if you have a unit, most names (like unit.name or unit.hitpoints) will be some attribute of that unit, but "__cfg" is not an attribute of the unit – it's a conversion. I don't think we have a general term to refer to such things, though. You could use the word "property", but that would describe any name found in the object, not just the special ones beginning with "__".
Spannerbag wrote: May 4th, 2025, 9:06 am When I first saw your code I kinda gathered that __cgf reformatted data (from ??? to WML) but had no idea what the original format was but that doesn't matter; I was just curious.
The original format is basically just "a unit", or if you want, "a unit userdata". It's essentially an "opaque" format that we define in the engine to represent a unit. (Opaque means you aren't able to manually inspect it in code, which also means you can't do things like writing it to a file or reading it to a file.)
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
User avatar
Spannerbag
Posts: 759
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query

Post by Spannerbag »

Celtic_Minstrel wrote: May 4th, 2025, 9:00 pm The "__" isn't really a notation...
Ooops , forgot to thank you for your post. :doh:

Belated thanks for your informative reply. :D

Cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
User avatar
Spannerbag
Posts: 759
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query: location set

Post by Spannerbag »

Hi,
Finally got a bit of time to work on this a bit further, made some progress but still loads to do.
I'm wanting to generate a random move but am unclear as to whether or not this can be done directly from a location set rather than iterating through it first to generate (I think) an array (or table) of x,y values.

Also changed the names of the units from parent and child to wanderer and follower to avoid function name clashes. :doh:

Here's the setup code:

Code: Select all

function ca_wanderers_move:execution(cfg)
    local unit = get_wanderers(cfg)[1] or ((not all_wanderers[1]) and get_followers(cfg)[1])		-- Move wanderers randomly or (if no wanderers then move followers if any)
    local avoid_locs = AH.get_avoid_map(ai, nil, true)							-- [avoid]ed locations, if any
    local reach_locs = AH.get_reachmap(unit, { avoid_map = avoid_locs, exclude_occupied = true })	-- Unoccupied hexes this unit can reach, honouring [avoid] if set

Not able to actually test this yet but hopefully should work.

Can I (easily) extract a random location from reach_locs or do I need to:
  • Strip out border hexes (doubt it but thought I'd best ask)?
  • Use :iter to build an x,y table?
Also, what's the internal structure of a locations set?
(I couldn't find anything definitive in the wiki, apologies if I missed it.)

Many thanks in advance for your time and trouble.

Cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
gnombat
Posts: 892
Joined: June 10th, 2010, 8:49 pm

Re: Another basic lua query: location set

Post by gnombat »

Spannerbag wrote: June 9th, 2025, 2:39 pm Can I (easily) extract a random location from reach_locs
Did you try location_set.random ?
User avatar
Spannerbag
Posts: 759
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Another basic lua query: location set

Post by Spannerbag »

gnombat wrote: June 9th, 2025, 2:52 pm
Spannerbag wrote: June 9th, 2025, 2:39 pm Can I (easily) extract a random location from reach_locs
Did you try location_set.random ?
Aaarghh... looked through that page... guess that as it was the very last entry I missed it. :doh:
Thanks!

I guess the actual code to generate a random location from a location set named reach_locs would be something like:

local rand_x,rand_y = location_set.random(reach_locs)

The wiki doesn't give an example so I'd like to check syntax.
(Doesn't seem to be used in any .lua files the BfW directory tree nor did I find an example in local UMC on my PC hence this question.)

Also, would still - out of curiosity - appreciate any insight into the internal structure of a location set if anyone knows...?

Thanks again gnombat for pointing this useful function out to me, saved me a lot of hassle.

Cheers!
-- Spannerbag
SP Campaigns: After EI (v1.14) Leafsea Burning (v1.18, v1.16)
I suspect the universe is simpler than we think and stranger than we can know.
Also, I fear that beyond a certain point more intelligence does not necessarily benefit a species...
User avatar
Ravana
Forum Moderator
Posts: 3313
Joined: January 29th, 2012, 12:49 am
Location: Estonia
Contact:

Re: Another basic lua query

Post by Ravana »

gnombat
Posts: 892
Joined: June 10th, 2010, 8:49 pm

Re: Another basic lua query: location set

Post by gnombat »

Spannerbag wrote: June 10th, 2025, 8:09 am I guess the actual code to generate a random location from a location set named reach_locs would be something like:

local rand_x,rand_y = location_set.random(reach_locs)

The wiki doesn't give an example so I'd like to check syntax.
(Doesn't seem to be used in any .lua files the BfW directory tree nor did I find an example in local UMC on my PC hence this question.)
There is an example in data/ai/micro_ais/cas/ca_herding_sheep_move.lua
User avatar
Celtic_Minstrel
Developer
Posts: 2371
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: Another basic lua query: location set

Post by Celtic_Minstrel »

Spannerbag wrote: June 10th, 2025, 8:09 am local rand_x,rand_y = location_set.random(reach_locs)
That should work, but you can shorten it a bit t local rand_x,rand_y = reach_locs:random().
Spannerbag wrote: June 10th, 2025, 8:09 amAlso, would still - out of curiosity - appreciate any insight into the internal structure of a location set if anyone knows...?
It's not all that complicated. It maps from locations to values, by converting the location to a numeric key in a Lua table.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
Post Reply