Proposed micro_ai logic sanity check

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

Moderator: Forum Moderators

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

Proposed micro_ai logic sanity check

Post by Spannerbag »

Hello,
As a project I've decided to try and create a new micro_ai.
I did have quite a bit of the logic below working using WML but my implementation was messy and inelegant, hence an attempt at a purely lua implementation.

Below is a breakdown of the proposed logic.

All thoughts and comments (including "don't do this") greatly appreciated.
I already have a version of forest animals that allows units to wander randomly (ignores enemies) and if a child (tusklet) is attacked makes all units hostile (i.e. may attack on their turn). Me being me I decided to push the envelope but thought I'd best check here first to ensure the logic will work as I expect.

Here's an outline of the proposed behaviours:

Code: Select all

-- Ugly hack of forest animals mai to create wanderers mai (wmai).
--
-- Ignores enemies, if any, when moving units.
-- Units: parents and children.
--
-- Is really 3 micro-ais in one; parents only, children only or both.
-- Each have various behaviours as detailed below.
-- All subtags and filters detailed below are optional.
--
-- [filter_parent]
-- Unit filter for parent units.
-- If absent or no match: no parents.
-- As with forest animals mai parents will unconditionally attack any enemy adjacent to a child.
--
-- [filter_child]
-- Unit filter for child units.
-- If absent or no match: no children.
-- Child units never attack (so can only die if attacked).
--
-- [filter_location]
-- If present restricts units to specified locations.
-- If absent or no match: move over whole map.
--
-- Key "parent_attack" defines % chance (default 0%) of parent attacking if:
--   a) enemy unit adjacent to parent (but not also to child) at turn start, or
--   b) if parent moves adjacent to enemy unit.
--
-- If enemy attacks and hits:
--   Parent: attacked unit no longer controlled by mai but by default RCA ai
--     (next wmai turn only).
--   Child:  *all* units controlled by default RCA ai
--     (all subsequent turns, i.e. wmai disabled).
--
-- Controlling ai is mediated by unit variable "maiw_status" with values:
--   i)   =0 unit(s) controlled by mai (default/fallback value).
--   ii)  =1 unit(s) controlled by RCA ai current/next turn only
--           (parents only, maiw_status cleared at turn end).
--   iii) =2 unit(s) always controlled by default RCA ai
--           (maiw_status not cleared at turn end).
--
-- Events (provisional)
-- 1: new side turn
-- If any parents begin with 1+ enemie(s) adjacent *and* no enemies adjacent to a child
--   then if maiw_status < 1 set maiw_status = 1 for this unit, prior to
--   checking which unit(s) should be controlled by wmai on current turn.
--
-- 2: moveto (enter_hex?)
-- If any unit moves adjacent to an enemy set moves=0.
-- If parent, set maiw_status=1 and attacks_left=1 (might not be necessary?).
--
-- 3: attacker hits
-- If second_unit.side = wmai side set maiw_status accordingly (depending on whether parent or child hit).
--
-- Proposed WML structure:
--
-- [micro_ai]
--  side=...
--  ai_type=wanderers
--  action=add / change / delete
--  [filter_parent]
--    Includes micro_ai side number above
--    Standard unit filter sub-tags and keys
--  [/filter_parent]
--  [filter_child]
--    Includes micro_ai side number above
--    Standard unit filter sub-tags and keys
--  [/filter_child]
--  [filter_location]
--    Standard location filter sub-tags and keys
--  [/filter_location]
-- parent_attack=%
Initial questions:
  • if a unit does blunder into an enemy, if I simply ensure the unit can attack will control automatically pass to the RCA ai when the micro_ai has finished or do I need to explicitly "deregister" it with the micro ai before the RCA ai can subsequently assume control of this unit?
  • Is is preferable to implement the events above in lua or WML?
    I would prefer lua as I hope to write the entire micro_ai as a single lua file.
    However given the need to trap events on other side turns (attacker hits) I'm not certain this is possible?
  • Is it OK to invent/create arbitrary tags such as [filter_parent] or are only recognised WML tags allowed/recognised?
    (If not I could fallback to using the existing type specification logic.)
  • Not sure whether to check that parents and children are distinct (and issue error if not) or trust the campaign designer.
    (I.e. would such a sanity check be useful?)
  • Need to split moveto/enter_hex logic:
    • Set moves=0 immediately (all units)
    • Parent only: defer attack enemy until all moves completed then only attack adjacent enemy if no enemies adjacent to children within range (and parent_attack chance → attack).
      Is this awkward to implement in lua?
  • Would "nuisances" (or something else) be a better name than "wanderers"?
  • Is my logic sane/optimal or are there better ways of doing this?
All comments gratefully received,
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: Proposed micro_ai logic sanity check

Post by Celtic_Minstrel »

Spannerbag wrote: May 10th, 2025, 12:23 pm if a unit does blunder into an enemy, if I simply ensure the unit can attack will control automatically pass to the RCA ai when the micro_ai has finished or do I need to explicitly "deregister" it with the micro ai before the RCA ai can subsequently assume control of this unit?
If your MicroAI does not move the unit nor clear its moves, other candidate actions will be given a chance to control it.
Spannerbag wrote: May 10th, 2025, 12:23 pm Is is preferable to implement the events above in lua or WML?
I would prefer lua as I hope to write the entire micro_ai as a single lua file.
However given the need to trap events on other side turns (attacker hits) I'm not certain this is possible?
It doesn't really make sense to have events in a MicroAI at all. Your events 1 and 2 probably don't need to be events – you can just check the current state of the map when the MicroAI runs. As for event 3, it doesn't make sense to me to put that logic in the MicroAI. I'd just define that logic in WML, separate from the MicroAI, and make the event actually disable the MicroAI. And, when it comes to detecting an attack on a parent, all you really need to do is store the parent's hitpoints in a unit variable on every turn. Then when the MicroAI runs, check if the variable is different from its current hitpoints and there is an adjacent enemy. If there is, skip that parent unit entirely.
Spannerbag wrote: May 10th, 2025, 12:23 pmIs it OK to invent/create arbitrary tags such as [filter_parent] or are only recognised WML tags allowed/recognised?
It's perfectly fine to make up tags. You can define the format of the [micro_ai] tag contents however you want.
Spannerbag wrote: May 10th, 2025, 12:23 pmNot sure whether to check that parents and children are distinct (and issue error if not) or trust the campaign designer.
(I.e. would such a sanity check be useful?)
I'm not sure if that would be useful… I think I'd skip it for now.
Spannerbag wrote: May 10th, 2025, 12:23 pm Need to split moveto/enter_hex logic:
  • Set moves=0 immediately (all units)
  • Parent only: defer attack enemy until all moves completed then only attack adjacent enemy if no enemies adjacent to children within range (and parent_attack chance → attack).
    Is this awkward to implement in lua?
Setting moves=0 seems wrong, pretty sure you said you want other candidate actions to take control in this case? The "defer attack" thing makes it even more clear that this probably shouldn't be an event, as you only care about the final state once it is the MicroAI's turn.
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: Proposed micro_ai logic sanity check

Post by Spannerbag »

Hi Celtic_Minstrel,
Thanks for the as ever useful and enlightening reply.

A couple of follow-ups (sorry, to keep the post size within tl;dr tolerances I omitted a few background items that I perhaps should've included :doh: ).
Celtic_Minstrel wrote: May 10th, 2025, 3:07 pm
Spannerbag wrote: May 10th, 2025, 12:23 pm if a unit does blunder into an enemy...
If your MicroAI does not move the unit nor clear its moves, other candidate actions will be given a chance to control it.
Ah, that might be a problem. The wmai side will have allies and enemies as usual and will move its units randomly and may run into an enemy (on the wmai turn, obviously) so, I assume, that will set moves=0 for the unit anyway (explicitly setting moves=0 is just paranoia plus a remnant of earlier versions).
So the unit will have moved (and have moves=0 I assume) after encountering an enemy.
This is fine as I want to replicate normal behaviour so entering an enemy ZoC then stopping is fine (unless the designer chooses skirmishers... hadn't thought of that :doh: ).
Thus the unit can (unless a skirmisher) only attack adjacent enemies (and not move) which is also fine.
So what I should have asked is Once the wmai has finished, can the RCA mount an attack with these units (regardless of moves remaining)?
Also, for skirmishers with moves remaining, can the RCA also move these again?

Regarding a second move my guess is no, because "move" candidate action has already been used up by the wmai.

Celtic_Minstrel wrote: May 10th, 2025, 3:07 pm [It doesn't really make sense to have events in a MicroAI at all. Your events 1 and 2 probably don't need to be events – you can just check the current state of the map when the MicroAI runs. As for event 3, it doesn't make sense to me to put that logic in the MicroAI. I'd just define that logic in WML, separate from the MicroAI, and make the event actually disable the MicroAI. And, when it comes to detecting an attack on a parent, all you really need to do is store the parent's hitpoints in a unit variable on every turn. Then when the MicroAI runs, check if the variable is different from its current hitpoints and there is an adjacent enemy. If there is, skip that parent unit entirely.
Excellent suggestions: I'll rethink my logic - and maybe simplify.

Celtic_Minstrel wrote: May 10th, 2025, 3:07 pm
Spannerbag wrote: May 10th, 2025, 12:23 pmIs it OK to invent/create arbitrary tags such as [filter_parent] or are only recognised WML tags allowed/recognised?
It's perfectly fine to make up tags. You can define the format of the [micro_ai] tag contents however you want.
Great - hoped that'd be the case. :D

Celtic_Minstrel wrote: May 10th, 2025, 3:07 pm
Spannerbag wrote: May 10th, 2025, 12:23 pmNot sure whether to check that parents and children are distinct (and issue error if not) or trust the campaign designer.
(I.e. would such a sanity check be useful?)
I'm not sure if that would be useful… I think I'd skip it for now.
Thanks. Less work for me. :)

Celtic_Minstrel wrote: May 10th, 2025, 3:07 pm
Spannerbag wrote: May 10th, 2025, 12:23 pm Need to split moveto/enter_hex logic:...
Setting moves=0 seems wrong, pretty sure you said you want other candidate actions to take control in this case? The "defer attack" thing makes it even more clear that this probably shouldn't be an event, as you only care about the final state once it is the MicroAI's turn.
Yeah, need to rethink this.

Many thanks for your reply it's given me a far better idea of what kind of logic suits/works inside a micro_ai and what doesn't (i.e. events).
I think before coding in earnest I'll rethink some wmai behaviours and probably simplify.
Will also try and ditch events entirely or, if I can't, embed the lua inside inside a WML wrapper so the micro_ai is still all contained inside a single file.

Thanks again, much appreciated! :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
Celtic_Minstrel
Developer
Posts: 2371
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: Proposed micro_ai logic sanity check

Post by Celtic_Minstrel »

Spannerbag wrote: May 11th, 2025, 10:22 amz
Thus the unit can (unless a skirmisher) only attack adjacent enemies (and not move) which is also fine.
So what I should have asked is Once the wmai has finished, can the RCA mount an attack with these units (regardless of moves remaining)?
Also, for skirmishers with moves remaining, can the RCA also move these again?

Regarding a second move my guess is no, because "move" candidate action has already been used up by the wmai.
I'm afraid you're wrong – that's not what you should have asked. The problem with your question is that "Once the wmai is finished" appears to demonstrate an assumption of linearity: "The game will run my MicroAI, and then it will run the RCA." That's not even close to the truth. It's entirely possible that actions from your MicroAI and actions from the default AI will end up interleaved. The MicroAI simply inserts additional candidate actions into the RCA, which the RCA will then evaluate on each loop just like the default candidate actions.

I will nevertheless try to answer the question you wanted to ask.

If a unit has moves remaining, the default move candidate action in the RCA will move it, although "move it" can include the possibility of deciding that there are no good moves and setting its moves to 0.

If a unit has attacks remaining and is in a position to attack, the default attack candidate action in the RCA will attack with it if it deems it to be worthwhile. I believe it will set the unit's attacks to 0 if it decides it's not worth attacking even though it can.

The candidate actions of your custom MicroAI would normally be set up so that the RCA prefers to choose them instead of the default move or attack candidate action for the units you want the MicroAI to control. The evaluation phase of the custom candidate action needs to inform the RCA when it decides there is nothing for the MicroAI to do.

Since the MicroAI will therefore run before the default move and attack candidate actions (but not necessarily before the entire default AI), assuming everything is set up correctly, that means the MicroAI gets the first chance to decide what those units will do. It also gets the opportunity to prevent further candidate actions from controlling the unit, by setting its moves and/or attacks to 0.

In short: if the MicroAI leaves a unit with unused moves or attacks, then the default CAs can then make it move and attack however they want. If on the other hand it leaves a unit with 0 moves or attacks, then the default CAs cannot make the unit do anything else.
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: Proposed micro_ai logic sanity check

Post by Spannerbag »

Wow, a really helpful reply that explains how unit moves/attacks were set to zero even when I was sure the micro_ai hadn't done anything!
My understanding of what's going on is now much clearer - many thanks! :D

I've still got a fair amount of research to do, e.g. not currently sure how to implement:
Celtic_Minstrel wrote: May 11th, 2025, 11:01 pm ...The evaluation phase of the custom candidate action needs to inform the RCA when it decides there is nothing for the MicroAI to do...
However with a better grasp of the fundamentals I'm sure when I look for examples I'll be able to work out how to do that. :fingers_crossed:
E.g. at first glance my understanding of the code below is:
If units subject to this micro_ai with moves>0 exist, movement will be considered to have taken place; return ca_score as specified in custom micro_ai otherwise return 0 (thereby reducing this CA's priority below the default CA score?).

Code: Select all

function ca_forest_animals_move:evaluation(cfg)
    if get_forest_animals(cfg)[1] then return cfg.ca_score end
    return 0
end
Again, thanks for the detailed reply, 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: Proposed micro_ai logic sanity check

Post by Celtic_Minstrel »

Spannerbag wrote: May 12th, 2025, 9:49 am I've still got a fair amount of research to do, e.g. not currently sure how to implement:
Celtic_Minstrel wrote: May 11th, 2025, 11:01 pm ...The evaluation phase of the custom candidate action needs to inform the RCA when it decides there is nothing for the MicroAI to do...
[/code]
It informs the RCA by returning a score of 0. This also allows it to be called again if, after a move from the default move CA, there is something new that the MicroAI can do. If it returns a non-zero score but then doesn't do anything, the RCA won't call it again until the next turn.
Spannerbag wrote: May 12th, 2025, 9:49 am However with a better grasp of the fundamentals I'm sure when I look for examples I'll be able to work out how to do that. :fingers_crossed:
E.g. at first glance my understanding of the code below is:
If units subject to this micro_ai with moves>0 exist, movement will be considered to have taken place; return ca_score as specified in custom micro_ai otherwise return 0 (thereby reducing this CA's priority below the default CA score?).
Pretty much, except that If units subject to this micro_ai with moves>0 exist can be whatever condition you want. If you want the units to only ever be controlled by the MicroAI, then that condition is fine. If you want the units to be controlled by the default AI in certain circumstances, your condition would need to be such that it returns 0 in that circumstance.
Spannerbag wrote: May 12th, 2025, 9:49 am

Code: Select all

function ca_forest_animals_move:evaluation(cfg)
    if get_forest_animals(cfg)[1] then return cfg.ca_score end
    return 0
end
This works, assuming get_forest_animals filters out units that cannot move.
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: Proposed micro_ai logic sanity check

Post by Spannerbag »

Hi Celtic_Minstrel,
As always, many thanks for your help.

Not had chance to look at the code any further (rl... tsk, always getting in the way), hopefully I won't have fogotten it all by the time I revisit this!
Am mulling over behavioural changes to my custom micro_ai to make implementation easier.

Your time and patience are very much appreciated; you've made me slightly less clueless (quite an achievement). :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: Proposed micro_ai logic sanity check - syntax query

Post by Spannerbag »

Hi,

Bit puzzled about how lua actually gets to execute a chunk of code?

Assigning ca values seems to be:

Code: Select all

function wesnoth.micro_ais.forest_animals(cfg)
	local optional_keys = { rabbit_type = 'string', rabbit_number = 'integer',
		rabbit_enemy_distance = 'integer', rabbit_hole_img = 'string', tusker_type = 'string',
		tusklet_type = 'string', deer_type = 'string', filter_location = 'tag'
	}
	local score = cfg.ca_score or 300000
	local CA_parms = {
		ai_id = 'mai_forest_animals',
		{ ca_id = "new_rabbit", location = 'ca_forest_animals_new_rabbit.lua', score = score },
		{ ca_id = "tusker_attack", location = 'ca_forest_animals_tusker_attack.lua', score = score - 1 },
		{ ca_id = "move", location = 'ca_forest_animals_move.lua', score = score - 2 },
		{ ca_id = "tusklet_move", location = 'ca_forest_animals_tusklet_move.lua', score = score - 3 }
	}
...
Looking at one of these:
{ ca_id = "move", location = 'ca_forest_animals_move.lua', score = score - 2 },

I understand that location defines the filepath to the relevant code however I don't seem able to find where this code is actually called/executed?
I notice that the lua file name always matches a table creation, here (within the file ca_forest_animals_move.lua) in this case:

local ca_forest_animals_move = {}

As I'm writing a custom mai I'd like to have everything in a single file, so will remove location = and simply embed the same code within the mai definition file that calls the various ca assignments.
However, do I need to put a function call/table definition/something else in place of location?

E.g. would this simply suffice?
  1. Add line local ca_forest_animals_move = {} before the code below.
  2. Replace location { ca_id = "move", ca_forest_animals_move, score = score - 2 },
Or is it better/more efficient to change the entire

Code: Select all

	local CA_parms = {
		ai_id = 'mai_forest_animals',
		{ ca_id = "new_rabbit", location = 'ca_forest_animals_new_rabbit.lua', score = score },
		{ ca_id = "tusker_attack", location = 'ca_forest_animals_tusker_attack.lua', score = score - 1 },
		{ ca_id = "move", location = 'ca_forest_animals_move.lua', score = score - 2 },
		{ ca_id = "tusklet_move", location = 'ca_forest_animals_tusklet_move.lua', score = score - 3 }
	}
	...
structure?

I haven't found anything helpful in online references so suspect I'm asking the wrong question but could spend a lot of time getting nowhere fast hence this post.

I'd also appreciate an explanation of how
{ ca_id = "move", location = 'ca_forest_animals_move.lua', score = score - 2 },
knows what to do with ca_forest_animals_move.lua - or is the code executed elsewhere and I'd missed it?

Any help 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...
gnombat
Posts: 892
Joined: June 10th, 2010, 8:49 pm

Re: Proposed micro_ai logic sanity check

Post by gnombat »

Spannerbag wrote: May 18th, 2025, 9:33 am As I'm writing a custom mai I'd like to have everything in a single file, so will remove location = and simply embed the same code within the mai definition file that calls the various ca assignments.
I don't think that's really possible? location is a required field, and it is defined in the Micro AIs documentation as the "Path to the Lua file that defines the candidate action". So I think it has to be there and it has to be a file.

Keep in mind, if you really don't like the way Micro AIs are structured, you don't actually have to use them. There are other ways to customize the AI's behavior - i.e., you could use the [ai] tag instead of the [micro_ai] tag. Note that you would still need to have a separate .lua file for each CA. But if you have only one CA, you would just need that one .lua file.
User avatar
Spannerbag
Posts: 759
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Proposed micro_ai logic sanity check

Post by Spannerbag »

gnombat wrote: May 18th, 2025, 4:13 pm
Spannerbag wrote: May 18th, 2025, 9:33 am .. will remove location = ...
I don't think that's really possible? location is a required field...
Thanks for pointing that out.
Oh well, multiple files it is...

Thanks again for the advice, 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: Proposed micro_ai logic sanity check - syntax query

Post by Celtic_Minstrel »

Spannerbag wrote: May 18th, 2025, 9:33 am I understand that location defines the filepath to the relevant code however I don't seem able to find where this code is actually called/executed?
I notice that the lua file name always matches a table creation, here (within the file ca_forest_animals_move.lua) in this case:
It's called from inside the engine, so you won't find "the place it's called" anywhere in WML or Lua.
Spannerbag wrote: May 18th, 2025, 9:33 am As I'm writing a custom mai I'd like to have everything in a single file, so will remove location = and simply embed the same code within the mai definition file that calls the various ca assignments.
However, do I need to put a function call/table definition/something else in place of location?
Hmm. There might actually be a way to make it work if there's only one CA – you might be able to put the MicroAI definition in the CA file and have the location refer to the same file. I don't know if it would work, but if you really want to keep things in one file, it might be worth a try.
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: Proposed micro_ai logic sanity check

Post by Spannerbag »

Gnombat kindly pointed out this link:
gnombat wrote: May 18th, 2025, 4:13 pm ...There are other ways to customize the AI's behavior...
This page has hardly changed for years but I'm hoping it's still valid as it further dispelled my ignorance.
Unfortunately as gnombat said, it still expects a location and I think I'll need 3 CAs (stolen/adapted from forest animals mai):
  • Move units randomly ignoring enemies.
  • "Protective parents" attack (defend child).
  • Child move (follow parent).
So multiple lua files will be created, preferably in a wmai sub-folder of lua, assuming the add-on server can create sub-folders.
Otherwise they'll have to clutter up lua.

I've reworked the logic a little to be more amenable to (my present understanding of) wmai implementation.
I'd hoped to begin incremental test/refine this weekend but stuff happens... :x



Re.:
Celtic_Minstrel wrote: May 10th, 2025, 3:07 pm ...when it comes to detecting an attack on a parent, all you really need to do is store the parent's hitpoints in a unit variable on every turn. Then when the MicroAI runs, check if the variable is different from its current hitpoints and there is an adjacent enemy. If there is, skip that parent unit entirely.
I'm assuming that - if implemented as part of a mai - the code to record hitpoints would execute every mai turn start?
This wouldn't be an issue because only the first successful attack needs to be detected so I can simply compare current hitpoints vs max_hitpoints *.
* Of course this assumes that units are added with full hitpoints.
Given that the mai doesn't have to be running when the scenario starts and that units can be added any time (and not necessarily on full hitpoints) I'm not clear if it is possible to "capture starting hitpoints" whenever a unit is added to the mai in the general case
.
Sanity check: is my understanding correct or - as is highly likely - have I missed or misunderstood something again?

As always, thanks for your time and patience.

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: Proposed micro_ai logic sanity check

Post by Celtic_Minstrel »

There is a way to make CAs without a location= parameter. You would instead provide evaluation= and execution= parameters, which contain the content of the respective functions in the CA file. You would need to also add a line at the top to convert ... to the proper arguments (just take the actual argument list of the function declared in the file, without parentheses, put local in front, and add = ... to the end).

You can find more info on this alternate (and older) method here. That page says you need an [engine] tag in the AI definition to use the alternate form, but I'm not sure if that's actually true. Personally, I don't really recommend trying to consolidate the whole thing into one file like this, but it is at least theoretically possible to achieve.
Spannerbag wrote: May 24th, 2025, 12:07 pm So multiple lua files will be created, preferably in a wmai sub-folder of lua, assuming the add-on server can create sub-folders.
Otherwise they'll have to clutter up lua.
What? The add-on server doesn't create folders. It just takes something like a zip file and stores it.
Spannerbag wrote: May 24th, 2025, 12:07 pm
Celtic_Minstrel wrote: May 10th, 2025, 3:07 pm ...when it comes to detecting an attack on a parent, all you really need to do is store the parent's hitpoints in a unit variable on every turn. Then when the MicroAI runs, check if the variable is different from its current hitpoints and there is an adjacent enemy. If there is, skip that parent unit entirely.
I'm assuming that - if implemented as part of a mai - the code to record hitpoints would execute every mai turn start?
This wouldn't be an issue because only the first successful attack needs to be detected so I can simply compare current hitpoints vs max_hitpoints *.
* Of course this assumes that units are added with full hitpoints.
Given that the mai doesn't have to be running when the scenario starts and that units can be added any time (and not necessarily on full hitpoints) I'm not clear if it is possible to "capture starting hitpoints" whenever a unit is added to the mai in the general case
.
Sanity check: is my understanding correct or - as is highly likely - have I missed or misunderstood something again?
I don't quite get what you're saying.

If you want the MicroAI to only control undamaged parent units, then just add formula="hitpoints < max_hitpoints" to the filter for parent units. But I thought you were asking for something that directly reacts to being damaged. That would probably require some events.
Last edited by Celtic_Minstrel on May 27th, 2025, 5:54 am, edited 1 time in total.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
gnombat
Posts: 892
Joined: June 10th, 2010, 8:49 pm

Re: Proposed micro_ai logic sanity check

Post by gnombat »

Spannerbag wrote: May 24th, 2025, 12:07 pm So multiple lua files will be created, preferably in a wmai sub-folder of lua, assuming the add-on server can create sub-folders.
Otherwise they'll have to clutter up lua.
The usual convention appears to be to put these in a top-level folder named ai, not in the lua folder at all.

Eastern_Invasion/ai
Son_Of_The_Black_Eye/ai
The_Rise_Of_Wesnoth/ai
Two_Brothers/ai

(I believe this is just a convention, though - I don't think it is required or enforced.)
User avatar
Spannerbag
Posts: 759
Joined: December 18th, 2016, 6:14 pm
Location: Yes

Re: Proposed micro_ai logic sanity check

Post by Spannerbag »

Celtic_Minstrel wrote: May 25th, 2025, 9:31 pm There is a way to make CAs without a location= parameter...
Personally, I don't really recommend trying to consolidate the whole thing into one file like this, but it is at least theoretically possible to achieve.
gnombat wrote: May 26th, 2025, 3:32 am The usual convention appears to be to put these in a top-level folder named ai, not in the lua folder at all.
... (I believe this is just a convention, though - I don't think it is required or enforced.)
Thanks for the insights, useful to know. :thumbsup:
On reflection will use standard (multiple file) implementation with all lua files in ai folder.
As I learn more my ideas keeps changing - hopefully this is a good thing and will lead somewhere productive... :fingers_crossed:


Celtic_Minstrel wrote: May 25th, 2025, 9:31 pm ...The add-on server doesn't create folders...
Well that's saved me an experiment, thanks! :)

Celtic_Minstrel wrote: May 25th, 2025, 9:31 pm
Spannerbag wrote: May 24th, 2025, 12:07 pm
Celtic_Minstrel wrote: May 10th, 2025, 3:07 pm ...when it comes to detecting an attack on a parent, all you really need to do is store the parent's hitpoints in a unit variable on every turn. Then when the MicroAI runs, check if the variable is different from its current hitpoints and there is an adjacent enemy. If there is, skip that parent unit entirely.
I'm assuming that - if implemented as part of a mai - the code to record hitpoints would execute every mai turn start?
This wouldn't be an issue because only the first successful attack needs to be detected so I can simply compare current hitpoints vs max_hitpoints *.
* Of course this assumes that units are added with full hitpoints.
Given that the mai doesn't have to be running when the scenario starts and that units can be added any time (and not necessarily on full hitpoints) I'm not clear if it is possible to "capture starting hitpoints" whenever a unit is added to the mai in the general case
.
Sanity check: is my understanding correct or - as is highly likely - have I missed or misunderstood something again?
I don't quite get what you're saying.
Heh, I'm not surprised since I'd changed my idea of how the mai would work but didn't mention the changes here. :doh:

But don't worry about my witterings about starting hitpoints - my ideas have moved on.
FWIW my concern was over how to detect the first successful attack upon a unit (i.e. has taken damage) via hitpoint reduction given that in the general case it's possible to put an already wounded unit under mai control.
In such cases initial hitpoints seen by the mai (when compared to [max_hitpoints]) would indicate the unit had been previously attacked, which may or may not be so.
Further, (possibly wounded) units can be added to the mai at any time so "was attacked?" would need to be tested every time the mai executes
.
I'm probably trying to do this the wrong way or using the wrong tools.
More of this later in this post.



Not reached a firm conclusion yet but on reflection I think as little as possible needs to be put into the mai but allow scenario designers great flexibility in how to deploy or change the mai's behaviour via WML?
  • Using a dynamic unit selection/inclusion method (e.g. [filter]) rather than a static one (e.g. type=) would hopefully allow units to be selectively placed under mai control every turn by using unit specific markers such as role or a unit variable.
    (This is actually the same underlying logic as my very first attempt which involved tortuous WML because that version of my custom mai wasn't suited to doing this.)
  • Use a few boolean keys to switch some inbuilt behaviours on or off.
    (Here I'm thinking primarily of the "protective parents*" behaviour.)
  • Other behaviours would be always enforced because removing them disables the behaviours that define this mai, leaving the designer with an mai that doesn't do anything special.
    (These include always ignore enemies when moving and children follow parents*.)
  • Presently dithering about [filter_location] and/or [avoid] for the mai.
* Both stolen from forest animals mai.

Other considerations such as whether or not to allow parents to attack adjacent enemies on their turn can be implemented in WML.
E.g. if the designer wants to allow, say, a difficulty dependent chance that parents might attack an adjacent enemy on their turn, this could be done with WML.
I haven't throught this through yet but something like:
  • For units starting their turn adjacent to an enemy unit, adding a [filter_adjacent]→is_enemy→count=0 clause to the parent filter should exclude those units from mai control, allowing the RCA ai to determine what to do with these units depending on aggression/caution etc. and/or specific WML?
    (Moves could also be set to 0 to force attacks vs adjacent units only if desired.)
  • For units that blunder into enemies, enter_hex could trap that so that hopefully simply setting attacks_left=1 (if needed) would suffice.
    Current thinking: mai always sets moves and attacks =0 (ai.stopunit_all) before finishing with a unit regardless of whether it moves or not and this mai never attacks offensively - but this might change...
    Also please see end of post for question regarding this point.
Similarly, if behaviour needs to change if a unit is attacked/damaged then attack/attacker hits (as previously advised) would do the job.

As I say my ideas and thoughts are still developing as time and energy permit.


Question:

It would he helpful to know for certain about the relative timings of events and mai execution, as mentioned previously in this post, i.e.:
for all units, do all (RCA and/or micro) ai processes concerned with movement always complete before movement related events (WML) can fire?

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