Horrible lua hack almost working
Moderator: Forum Moderators
- Spannerbag
- Posts: 759
- Joined: December 18th, 2016, 6:14 pm
- Location: Yes
Horrible lua hack almost working
Hi,
I so often need to extract the x,y of a unit that I decided to write a quick WML inline hack to do this.
It works as expected except for units on recall.
Here's the relevant code:
This all works fine (
Ideally I'd like to:
[sanity_check]
Is this actually any quicker/more efficient than using
[/sanity_check]
Any help, thoughts, suggestions etc. gratefully received!
Cheers!
-- Spannerbag
I so often need to extract the x,y of a unit that I decided to write a quick WML inline hack to do this.
It works as expected except for units on recall.
Here's the relevant code:
Code: Select all
# unit_xy template
#
# lua: takes a filter and returns first matching unit's x,y
#
# WML uses [get_unit_xy] tag to find (first) matching unit on map
#
# [get_unit_xy]
# ... filter (=cfg in lua) ...
# [/get_unit_xy]
#
# Sets variables $gux and $guy
# These are 0,0 if unit not found, no error is issued
...
[lua]
code = <<
function wesnoth.wml_actions.get_unit_xy(cfg)
local guxy = wesnoth.units.find(cfg)
if not guxy[1] then
wml.variables.gux = 0
wml.variables.guy = 0
else
wml.variables.gux = guxy[1].x
wml.variables.guy = guxy[1].y
end
end
>>
[/lua]
# Our heroes
[side]
team_name=Goodies
user_team_name= _ "Goodies"
side=1
x,y=1,1
id=Goody
name=_"Goody"
type=Spearman
controller=human
canrecruit=yes
recruit=Bowman,Spearman
gold=100
[/side]
# Enemy side
[side]
side=2
x,y=44,33
id=Baddy
type=Goblin Rouser
name= _ "Baddy"
canrecruit=yes
controller=ai
team_name=Baddies
user_team_name= _ "Baddies"
random_traits=yes
recruit=Goblin Spearman,Goblin Impaler
gold=100
[/side]
# --- Events ---
[event]
name=side 1 turn 1
{UNIT 1 Mage recall recall (id=testmage
generate_name=yes)}
[/event]
[event]
name=side 1 turn 2
{DEBUG_MSG (_"About to test [get_unit_xy]")}
[get_unit_xy]
# side=2
# [filter_location]
# terrain=*^V*
# [/filter_location]
id=testmage
[/get_unit_xy]
{DEBUG_MSG (_"Done [get_unit_xy], x=$gux, y=$guy")}
[/event]
$gux
and $guy
return values expected) except for recall.Ideally I'd like to:
- Allow the coder to specify a particular WML array variable or default to
gu
if no array specified.
I tried setting$gu.x
and$gu.y
in lua but couldn't get the syntax quite right.
Iswml.array_variables
the right command to do this?
Will keep experimenting when I next get time. - Return
x,y=recall,recall
for units on recall.
E.g. if I recall the mage withid=testmage
on turn 1, it's co-ordinates are correctly reported on turn 2.
However if I leave it on recall I don't getx,y=recall,recall
as expected.
I thought wesnoth.units.find included recall but for me it doesn't seem to work so what am I doing wrong?
(I'm guessing I need to useunstore_unit
or similar to do this but I'm getting out of my depth.)
I can't spend any more time on this now as I have other stuff to do hence this post, hopefully it's a trivial mistake I've made somewhere.

[sanity_check]
Is this actually any quicker/more efficient than using
[store_unit]
to extract the x,y values?[/sanity_check]
Any help, thoughts, suggestions etc. gratefully received!
Cheers!
-- Spannerbag
Re: Horrible lua hack almost working
https://wiki.wesnoth.org/LuaAPI/types/u ... t_validity lets you determine recall.
- Spannerbag
- Posts: 759
- Joined: December 18th, 2016, 6:14 pm
- Location: Yes
Re: Horrible lua hack almost working
Got an unexpected extra 30 mins and managed (by sheer trial and error) to get the array specification right so that now works. 
Many thanks for your advice, will have a play with unit.valid when I next get chance, much appreciated.
Cheers!
-- Spannerbag

Many thanks for your advice, will have a play with unit.valid when I next get chance, much appreciated.
Cheers!
-- Spannerbag
- Spannerbag
- Posts: 759
- Joined: December 18th, 2016, 6:14 pm
- Location: Yes
Re: Horrible lua hack almost working
Hi again,
Making progress, thought I'd post my latest effort/progress for comment.
The logic can produce one of 3 outputs (if multiple matches, only first unit processed):
This was because if the unit was found on map it created a
However if the unit was found on recall an error was reported: basically my guess is that there was no
Thus I could not distinguish between "no match found" and "unit(s) found in recall".
This puzzled me because wesnoth.units.find seemed to behave differently whether the unit was found on map or in recall.
I assume I'm doing something stupid hence this post.
I have a workaround but it's ugly and I'm sure there's a better, more elegant way of doing this.
Here's the relevant code:
Note that without an explicit test for recall my logic fails and units on recall are flagged as not found (x,y=0,0).
I.e. this code is required:
Also, collapsing the logic fails so "or"-ing both find operations doesn't work (again units on recall retuirn x,y=0,0):
Any thoughts/comments on how to properly test for the various outputs would be greatly apreciated as I'm sure my logic isn't the best!
Cheers!
-- Spannerbag
Making progress, thought I'd post my latest effort/progress for comment.
The logic can produce one of 3 outputs (if multiple matches, only first unit processed):
- Unit found on map.
- Unit found in recall.
- No match.
This was because if the unit was found on map it created a
unit
object/dataset and unit.valid existed.However if the unit was found on recall an error was reported: basically my guess is that there was no
.valid
operation available which was the same (to my logic) as no unit being found.Thus I could not distinguish between "no match found" and "unit(s) found in recall".
This puzzled me because wesnoth.units.find seemed to behave differently whether the unit was found on map or in recall.
I assume I'm doing something stupid hence this post.
I have a workaround but it's ugly and I'm sure there's a better, more elegant way of doing this.
Here's the relevant code:
_main.cfg
Code: Select all
# get_unit_xy
#
# Takes a filter and if:
# a) unit(s) found on map returns gu.x,gu.y = first matching unit's x,y co-ordinates
# b) unit(s) found in recall returns gu.x,gu.y = recall,recall
# c) no units match filter returns gu.x,gu.y = 0,0
#
# WML usage:
#
# [get_unit_xy]
# ... filter ... # Do not use a [filter] tag
# [/get_unit_xy]
#
# Returns WML variable $gu with elements .x and .y
# This needs to be cleared
# These are 0,0 if unit not found, no error is issued
[lua]
code="wesnoth.require '~add-ons/LSB/lua/get_unit_xy.lua'"
[/lua]
get_unit_xy.lua
Code: Select all
function wesnoth.wml_actions.get_unit_xy(cfg)
local guxy = wesnoth.units.find(cfg)
if not guxy[1] then
local guxy = wesnoth.units.find_on_recall(cfg)
end
if not guxy[1] then
wml.array_variables['gu'] = { {x=0,y=0} }
elseif guxy[1].valid == "map" then
wml.array_variables['gu'] = { {x=guxy[1].x,y=guxy[1].y} }
elseif guxy[1].valid == "recall" then
wml.array_variables['gu'] = { {x="recall",y="recall"} }
else
wml.array_variables['gu'] = { {x=0,y=0} } -- Should never happen
end
end
I.e. this code is required:
Code: Select all
if not guxy[1] then
local guxy = wesnoth.units.find_on_recall(cfg)
end
Code: Select all
elseif guxy[1].valid == "map" or guxy[1].valid == "recall" then
wml.array_variables['gu'] = { {x=guxy[1].x,y=guxy[1].y} }
Cheers!
-- Spannerbag
Re: Horrible lua hack almost working
I tried removing the above code and the function still seems to work without it.Spannerbag wrote: ↑March 22nd, 2025, 9:58 am this code is required:Code: Select all
if not guxy[1] then local guxy = wesnoth.units.find_on_recall(cfg) end
This is what I tested it with:
Code: Select all
[get_unit_xy]
side=1
type=Spearman
[/get_unit_xy]
[message]
speaker=harry
message="It looks like the spearman is at coordinates $gu.x $gu.y"
[/message]
find_on_recall
I don't see how that could work - according to the unit documentation "x and y values ... have no meaning", so if you want x and y to be set toAlso, collapsing the logic fails so "or"-ing both find operations doesn't work (again units on recall retuirn x,y=0,0):Code: Select all
elseif guxy[1].valid == "map" or guxy[1].valid == "recall" then wml.array_variables['gu'] = { {x=guxy[1].x,y=guxy[1].y} }
recall
you will have to do that in your own code.- Spannerbag
- Posts: 759
- Joined: December 18th, 2016, 6:14 pm
- Location: Yes
Re: Horrible lua hack almost working
Weird, just repeated tests myself and as you say that code isn't needed so no idea what I'd done wrong before.gnombat wrote: ↑March 22nd, 2025, 2:32 pmI tried removing the above code and the function still seems to work without it.Spannerbag wrote: ↑March 22nd, 2025, 9:58 am this code is required:Code: Select all
if not guxy[1] then local guxy = wesnoth.units.find_on_recall(cfg) end
Mind you I was rushing (as ever, am under time pressure now

Anyway, tested it with and without the recall test and both results seem identical so will remove it.
Yeah, some time later when I had a bit of time to reflect I dimly realised something similar...gnombat wrote: ↑March 22nd, 2025, 2:32 pmI don't see how that could work - according to the unit documentation "x and y values ... have no meaning", so if you want x and y to be set toAlso, collapsing the logic fails...recall
you will have to do that in your own code.

Thanks for sanity checking my code - it needed it!

Cheers!
-- Spannerbag
- Spannerbag
- Posts: 759
- Joined: December 18th, 2016, 6:14 pm
- Location: Yes
Re: Horrible lua hack working!
Just thought I'd post the latest version as there's been several changes:
Any suggestions etc. much appreciated (especially 1. above).
WML used in testing:
Cheers!
-- Spannerbag
- Changed tag to
[get_uxyid]
- but if anyone has a better name I'd appreciate it.
Returns x,y,id in thegu
array. - Returns an array of units found and length indicates number found (so length=0 means no matches).
- Decided not to bother adding a variable name specifier, I want this to be succinct and use minimal WML.
- Be neat to embed a
scenario end
event in the lua that clears the WMLgu
array.
Then coder would not have to clear it manually - but that's probably overkill and confusing...
[store_unit_lite]
... but more importantly I've learned a bit more lua! 
Any suggestions etc. much appreciated (especially 1. above).
Code: Select all
--- As above but returns all x,y,id values
--- 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
Code: Select all
{DEBUG_MSG (_"About to test [get_uxyid] id=Baddy : unit on map")}
[get_uxyid]
id=Baddy
[/get_uxyid]
{DEBUG_MSG (_"Done Baddy: units found = $gu.length, gu.x=$gu.x, gu.y=$gu.y, gu.id=$gu.id
About to test [get_unit_xyid] id=testmage : unit in recall")}
[get_uxyid]
id=testmage
[/get_uxyid]
{DEBUG_MSG (_"Done testmage: units found = $gu.length, gu.x=$gu.x, gu.y=$gu.y, gu.id=$gu.id
About to test [get_unit_xyid] with nonexistent unit id=doesnotexist")}
[get_uxyid]
id=doesnotexist
[/get_uxyid]
{DEBUG_MSG (_"Done doesnotexist: units found = $gu.length, gu.x=$gu.x, gu.y=$gu.y, gu.id=$gu.id
About to test [get_uxyid] side=1")}
[get_uxyid]
side=1
[/get_uxyid]
{DEBUG_MSG (_"side=1: units found = $gu.length")}
[foreach]
array=gu
[do]
{DEBUG_MSG(_"$i: x,y,id=$gu[$i].x,$gu[$i].y,$gu[$i].id")}
[/do]
[/foreach]
{DEBUG_MSG(_"About to test [get_uxyid] side=3 : no units match")}
[get_uxyid]
side=3
[/get_uxyid]
{DEBUG_MSG (_"side=3: units found = $gu.length")}
[foreach]
array=gu
[do]
{DEBUG_MSG(_"$i: x,y,id=$gu[$i].x,$gu[$i].y,$gu[$i].id")}
[/do]
[/foreach]
{DEBUG_MSG (_"Done side=3")}
-- Spannerbag