Feasibility of customising return guardian micro ai
Moderator: Forum Moderators
Re: Feasibility of customising return guardian micro ai
It looks like the return value ofSpannerbag wrote: September 3rd, 2026, 4:26 pm Only other minor query: I've not been able to find the internal structure oflocso guessed at how to assignreturn_loc_uvthe relevant x,y values - is there a better way than what I did (line 6 of code below)? Just curious.
Code: Select all
local function get_guardian_uv_xy(cfg, guardian_uv) local return_loc_uv = AH.get_named_loc_xy('return', cfg) -- if guardian_uv.variables.return_guardian.return_x and guardian_uv.variables.return_guardian.return_y then if guardian_uv.variables.return_guardian then if wesnoth.current.map:on_board(guardian_uv.variables.return_guardian.return_x, guardian_uv.variables.return_guardian.return_y) then return_loc_uv[1], return_loc_uv[2] = guardian_uv.variables.return_guardian.return_x, guardian_uv.variables.return_guardian.return_y end end return return_loc_uv end
get_named_loc_xy is just an ordinary Lua array with two elements.See the example in https://www.lua.org/pil/3.6.html:
Code: Select all
days = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"}
days[1] is "Sunday", days[2] is "Monday", etc.So instead of writing this:
Code: Select all
return_loc_uv[1], return_loc_uv[2] = guardian_uv.variables.return_guardian.return_x, guardian_uv.variables.return_guardian.return_y
Code: Select all
return_loc_uv = { guardian_uv.variables.return_guardian.return_x, guardian_uv.variables.return_guardian.return_y }
- Spannerbag
- Posts: 979
- Joined: December 18th, 2016, 6:14 pm
- Location: Yes
Re: Feasibility of customising return guardian micro ai
Thanks for the clarifications, useful to know.gnombat wrote: September 3rd, 2026, 4:49 pm ...So instead of writing this:
You could write this:Code: Select all
return_loc_uv[1], return_loc_uv[2] = guardian_uv.variables.return_guardian.return_x, guardian_uv.variables.return_guardian.return_y
I'm not sure if that's any better, though. It might actually be slower (since it creates a new table object instead of reusing an existing one).Code: Select all
return_loc_uv = { guardian_uv.variables.return_guardian.return_x, guardian_uv.variables.return_guardian.return_y }
Have moved code from test to dev and will playtest there in due course.
Again, many thanks for your help.
Cheers!
- Spannerbag
- Posts: 979
- Joined: December 18th, 2016, 6:14 pm
- Location: Yes
Re: Feasibility of customising return guardian micro ai
Baffled again. 
Plan was to simplify working version of return_guardian_uv to only use unit vars, partly as a lua exercise.
However I'm stuck.
Edit: using v1.19.27 on windows.
Here's the working code:
I simply tried to remove the return_x,return_y elements (code posted further below).
However I've broken something and can't work out what/how.
Nothing in lua console but onscreen and in log get these errors:
I guess this is complaining that line 12:
If I reinstate the original
Here's the corresponding new versions:
WML
As I say, despite all this verbiage I'm sure it's something stupid, probably a parameter mis-match caused by removing
and/or hacking
to
However I've spent a lot of time on this and I think a fresh pair of eyes would help.
Cheers!
Plan was to simplify working version of return_guardian_uv to only use unit vars, partly as a lua exercise.
However I'm stuck.
Edit: using v1.19.27 on windows.
Here's the working code:
mai_return_guardian_uv.lua
Code: Select all
--[[ mai_return_guardian_uv.lua
Modified return guardian mai with unit specific return co-ordinates
Many thanks to gnombat for advice and assistance
https://forums.wesnoth.org/viewtopic.php?t=60912
https://wiki.wesnoth.org/LuaAPI/types/unit#Unit_sub-tables
https://github.com/wesnoth/wesnoth/blob/1.18.8/data/ai/lua/ai_helper.lua#L776
If specified, unit variables can override return location specified in [micro_ai] tag.
These are: unit.variables.return_guardian_uv.return_x and unit.variables.return_guardian_uv.return_y
Usage example:
[micro_ai]
side=3
[filter]
[/filter]
ai_type=return_guardian_uv
return_x,return_y=8,1
action=add
[/micro_ai]
# This unit will use return_x,return_y=8,1 above
{GENERIC_UNIT 3 (Wose Sapling) 8 1}
# This unit will use return_x,return_y=7,1
{GENERIC_UNIT 3 (Wose Sapling) 7 1}
[modify_unit]
[filter]
x,y=7,1
[/filter]
{VARIABLE return_guardian.return_x 7}
{VARIABLE return_guardian.return_y 1}
[/modify_unit]
]]
local AH = wesnoth.require "ai/lua/ai_helper.lua"
function wesnoth.micro_ais.return_guardian_uv(cfg)
if (cfg.action ~= 'delete') then
if (not cfg.id) and (not wml.get_child(cfg, "filter")) then
wml.error("Return Guardian UV [micro_ai] tag requires either id= key or [filter] tag")
end
AH.get_named_loc_xy('return', cfg, 'Return guardian UV [micro_ai] tag')
end
local required_keys = {}
local optional_keys = { id = 'string', filter = 'tag',
return_loc = 'string', return_x = 'integer', return_y = 'integer' -- REQUIRED but may be overridden by unit variables
}
local CA_parms = {
ai_id = 'mai_return_guardian_uv',
{ ca_id = 'move', location = '~add-ons/stub19/ai/ca_return_guardian_uv.lua', score = cfg.ca_score or 100100 }
}
return required_keys, optional_keys, CA_parms
endca_return_guardian_uv.lua
Code: Select all
-- ca_return_guardian_uv.lua
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_guardian_uv(cfg)
local filter = wml.get_child(cfg, "filter") or { id = cfg.id }
local guardian_uv = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", filter }
}[1]
return guardian_uv
end
local function get_guardian_uv_xy(cfg, guardian_uv)
local return_loc_uv = AH.get_named_loc_xy('return', cfg)
-- if guardian_uv.variables.return_guardian.return_x and guardian_uv.variables.return_guardian.return_y then
if guardian_uv.variables.return_guardian then
if wesnoth.current.map:on_board(guardian_uv.variables.return_guardian.return_x, guardian_uv.variables.return_guardian.return_y) then
return_loc_uv[1], return_loc_uv[2] = guardian_uv.variables.return_guardian.return_x, guardian_uv.variables.return_guardian.return_y
end
end
return return_loc_uv
end
local ca_return_guardian_uv = {}
function ca_return_guardian_uv:evaluation(cfg)
local guardian_uv = get_guardian_uv(cfg)
if guardian_uv then
-- local return_loc = AH.get_named_loc_xy('return', cfg)
local return_loc = get_guardian_uv_xy(cfg, guardian_uv)
if (guardian_uv.x == return_loc[1]) and (guardian_uv.y == return_loc[2]) then
return cfg.ca_score - 200
else
return cfg.ca_score
end
end
return 0
end
function ca_return_guardian_uv:execution(cfg)
local guardian_uv = get_guardian_uv(cfg)
-- local return_loc = AH.get_named_loc_xy('return', cfg)
local return_loc = get_guardian_uv_xy(cfg, guardian_uv)
-- In case the return hex is occupied:
local x, y = return_loc[1], return_loc[2]
if (guardian_uv.x ~= x) or (guardian_uv.y ~= y) then
x, y = wesnoth.paths.find_vacant_hex(x, y, guardian_uv)
end
local nh = AH.next_hop(guardian_uv, x, y)
if (not nh) then nh = { guardian_uv.x, guardian_uv.y } end
AH.movefull_stopunit(ai, guardian_uv, nh)
end
return ca_return_guardian_uvHowever I've broken something and can't work out what/how.
Nothing in lua console but onscreen and in log get these errors:
error scripting/lua: ~add-ons/stub19/ai/ca_return_guardian_u.lua:12: attempt to index a nil value (local 'guardian_u')
stack traceback:
~add-ons/stub19/ai/ca_return_guardian_u.lua:12: in upvalue 'get_guardian_u'
~add-ons/stub19/ai/ca_return_guardian_u.lua:28: in function <~add-ons/stub19/ai/ca_return_guardian_u.lua:27>
(...tail calls...)
error scripting/lua: ~add-ons/stub19/ai/ca_return_guardian_u.lua:12: attempt to index a nil value (local 'guardian_u')
stack traceback:
~add-ons/stub19/ai/ca_return_guardian_u.lua:12: in upvalue 'get_guardian_u'
~add-ons/stub19/ai/ca_return_guardian_u.lua:28: in function <~add-ons/stub19/ai/ca_return_guardian_u.lua:27>
(...tail calls...)
It may be coincidence but one sapling doesn't seem to move and the other moves to the specified return location (6,1).I guess this is complaining that line 12:
if guardian_u.variables.return_guardian then references an empty guardian_u?If I reinstate the original
wml.error then the error changes to:error scripting/lua: ~add-ons/stub19/ai/ca_return_guardian_u.lua:20: ')' expected near 'guardian_u'
stack traceback:
[C]: in field 'dofile'
lua/package.lua:53: in field 'require'
[string "local self, params, data, filter_own = ......"]:2: in main chunk
error scripting/lua: ~add-ons/stub19/ai/ca_return_guardian_u.lua:20: ')' expected near 'guardian_u'
stack traceback:
[C]: in field 'dofile'
lua/package.lua:53: in field 'require'
[string "local self, params, data, filter_own = ......"]:2: in main chunk
error scripting/lua: ~add-ons/stub19/ai/ca_return_guardian_u.lua:20: ')' expected near 'guardian_u'
stack traceback:
[C]: in field 'dofile'
lua/package.lua:53: in field 'require'
[string "local self, params, data, filter_own = ......"]:2: in main chunk
error scripting/lua: ~add-ons/stub19/ai/ca_return_guardian_u.lua:20: ')' expected near 'guardian_u'
stack traceback:
[C]: in field 'dofile'
lua/package.lua:53: in field 'require'
[string "local self, params, data, filter_own = ......"]:2: in main chunk
Here both saplings seem to behave as if under RCA AI?Here's the corresponding new versions:
mai_return_guardian_u.lua
Code: Select all
--[[ mai_return_guardian_u.lua
Modified return guardian mai with unit specific return co-ordinates
Many thanks to gnombat for advice and assistance
https://forums.wesnoth.org/viewtopic.php?t=60912
https://wiki.wesnoth.org/LuaAPI/types/unit#Unit_sub-tables
https://github.com/wesnoth/wesnoth/blob/1.18.8/data/ai/lua/ai_helper.lua#L776
Return location specified in unit.variables.return_guardian_u.return_x and unit.variables.return_guardian_u.return_y
Usage example:
[micro_ai]
side=3
[filter]
[/filter]
ai_type=return_guardian_u
action=add
[/micro_ai]
# This unit will use return_x,return_y=6,1
{GENERIC_UNIT 3 (Wose Sapling) 8 1}
[modify_unit]
[filter]
x,y=8,1
[/filter]
{VARIABLE return_guardian.return_x 6}
{VARIABLE return_guardian.return_y 1}
[/modify_unit]
# This unit will use it's current position: return_x,return_y=7,1
{GENERIC_UNIT 3 (Wose Sapling) 7 1}
[modify_unit]
[filter]
x,y=7,1
[/filter]
{VARIABLE return_guardian.return_x 7}
{VARIABLE return_guardian.return_y 1}
[/modify_unit]
]]
local AH = wesnoth.require "ai/lua/ai_helper.lua"
function wesnoth.micro_ais.return_guardian_u(cfg)
if (cfg.action ~= 'delete') then
if (not cfg.id) and (not wml.get_child(cfg, "filter")) then
wml.error("Return Guardian U [micro_ai] tag requires either id= key or [filter] tag")
end
-- AH.get_named_loc_xy('return', cfg, 'Return guardian UV [micro_ai] tag') -- REMOVED
end
local required_keys = {}
local optional_keys = { id = 'string', filter = 'tag'} -- REMOVED "," added "}"
-- return_loc = 'string', return_x = 'integer', return_y = 'integer' -- REMOVED
-- } -- REMOVED
local CA_parms = {
ai_id = 'mai_return_guardian_u',
{ ca_id = 'move', location = '~add-ons/stub19/ai/ca_return_guardian_u.lua', score = cfg.ca_score or 100100 }
}
return required_keys, optional_keys, CA_parms
endca_return_guardian_u.lua
Code: Select all
-- ca_return_guardian_u.lua
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local function get_guardian_u(cfg)
local filter = wml.get_child(cfg, "filter") or { id = cfg.id }
local guardian_u = AH.get_units_with_moves {
side = wesnoth.current.side,
{ "and", filter }
}[1]
local onmap = false
if guardian_u.variables.return_guardian then
if wesnoth.current.map:on_board(guardian_u.variables.return_guardian.return_x, guardian_u.variables.return_guardian.return_y) then
onmap = true
end
end
if onmap then
return guardian_u
else
-- wml.error("Micro AI get_guardian_u unit id= " .. guardian_u.id .. " currently at x,y= " .. guardian_u.x .. "," guardian_u.y .. " has invalid return_x,return_y= " .. guardian_u.variables.return_guardian.return_x .. "," .. guardian_u.variables.return_guardian.return_y)
wml.error("Micro AI get_guardian_u error msg")
end
end
local ca_return_guardian_u = {}
function ca_return_guardian_u:evaluation(cfg)
local guardian_u = get_guardian_u(cfg)
if guardian_u then
if (guardian_u.x == guardian_u.variables.return_guardian.return_x) and (guardian_u.y == guardian_u.variables.return_guardian.return_y) then
return cfg.ca_score - 200
else
return cfg.ca_score
end
end
return 0
end
function ca_return_guardian_u:execution(cfg)
local guardian_u = get_guardian_u(cfg)
-- In case the return hex is occupied:
local x, y = guardian_u.variables.return_guardian.return_x, guardian_u.variables.return_guardian.return_y
if (guardian_u.x ~= x) or (guardian_u.y ~= y) then
x, y = wesnoth.paths.find_vacant_hex(x, y, guardian_u)
end
local nh = AH.next_hop(guardian_u, x, y)
if (not nh) then nh = { guardian_u.x, guardian_u.y } end
AH.movefull_stopunit(ai, guardian_u, nh)
end
return ca_return_guardian_uWML
Code: Select all
[event]
name=side 1 turn 1
[micro_ai]
side=3
[filter]
[/filter]
ai_type=return_guardian_u
action=add
[/micro_ai]
# This unit will use return_x,return_y=6,1
{GENERIC_UNIT 3 (Wose Sapling) 8 1}
[modify_unit]
[filter]
x,y=8,1
[/filter]
{VARIABLE return_guardian.return_x 6}
{VARIABLE return_guardian.return_y 1}
[/modify_unit]
# This unit will use it's current position: return_x,return_y=7,1
{GENERIC_UNIT 3 (Wose Sapling) 7 1}
[modify_unit]
[filter]
x,y=7,1
[/filter]
{VARIABLE return_guardian.return_x 7}
{VARIABLE return_guardian.return_y 1}
[/modify_unit]
...AH.get_named_loc_xy('return', cfg, 'Return guardian UV [micro_ai] tag')and/or hacking
Code: Select all
local optional_keys = { id = 'string', filter = 'tag',
return_loc = 'string', return_x = 'integer', return_y = 'integer' -- REQUIRED but may be overridden by unit variables local optional_keys = { id = 'string', filter = 'tag'}However I've spent a lot of time on this and I think a fresh pair of eyes would help.
Cheers!
Re: Feasibility of customising return guardian micro ai
Yes,Spannerbag wrote: September 8th, 2026, 4:07 pm I guess this is complaining that line 12:if guardian_u.variables.return_guardian thenreferences an emptyguardian_u?
AH.get_units_with_moves() might not return any units. In that case guardian_u will be nil. You need to check for this before trying to do anything with guardian_u.- Spannerbag
- Posts: 979
- Joined: December 18th, 2016, 6:14 pm
- Location: Yes
Re: Feasibility of customising return guardian micro ai
Hmm, pretty sure it should find both the test units as they would both have full moves (created side 1 turn 1 but in side 3 so would have a turn refresh before being controlled by micro ai).gnombat wrote: September 8th, 2026, 9:38 pmYes,Spannerbag wrote: September 8th, 2026, 4:07 pm I guess this is complaining that line 12:if guardian_u.variables.return_guardian thenreferences an emptyguardian_u?AH.get_units_with_moves()might not return any units. In that caseguardian_uwill benil. You need to check for this before trying to do anything withguardian_u.
I think I'll have to sprinkle some comments in the code and see what's going on.
Oh well, that's a job for another day, can't face more lua today!
Thanks for your help.
Cheers!
Re: Feasibility of customising return guardian micro ai
What about if they don't have any moves remaining?Spannerbag wrote: September 8th, 2026, 11:03 pm Hmm, pretty sure it should find both the test units as they would both have full moves (created side 1 turn 1 but in side 3 so would have a turn refresh before being controlled by micro ai).
- Spannerbag
- Posts: 979
- Joined: December 18th, 2016, 6:14 pm
- Location: Yes
Re: Feasibility of customising return guardian micro ai
Is it possible for the same unit to be processed multiple times by a micro ai?
If so then yes I suppose it could happen.
Bit doubtful tho' TBH because this issue only arises when I use the version that doesn't have
return_x, return_y keys inside [micro_ai]; the versions that do work fine.Will have to systematically test all this however some time in the next few days when I get chance.
Thanks again for your help!
Cheers!
Re: Feasibility of customising return guardian micro ai
In general, the evaluation function will keep getting called over and over again until it returns a score of zero.Spannerbag wrote: September 8th, 2026, 11:25 pm Is it possible for the same unit to be processed multiple times by a micro ai?
If so then yes I suppose it could happen.
For the original ca_return_guardian, the evaluation function returns zero whenSpannerbag wrote: September 8th, 2026, 11:25 pm Bit doubtful tho' TBH because this issue only arises when I use the version that doesn't havereturn_x, return_ykeys inside[micro_ai]; the versions that do work fine.
get_guardian() returns a nil value, which happens when get_units_with_moves() returns no units. So this is always going to happen eventually.- Spannerbag
- Posts: 979
- Joined: December 18th, 2016, 6:14 pm
- Location: Yes
Re: Feasibility of customising return guardian micro ai
Thanks for that last info-nugget, it helped.
Based on somewhat sparse testing I think I have two working return_guardian variants.
return_guardian_u, only uses unit variables for return co-ordinates.return_guardian_uras above but falls back toreturn_x,treturn_yspecified in[micro_ai]if unit variables absent or invalid.
Happy to share this code if anyone's interested (I'm also fairly sure some of my code is rather klunky so would also appreciate any improvements).
Thanks again for your help, really much appreciated.
Cheers!