Exercises in Formula and Lua AI and AI-demos add-on feedback
Moderator: Forum Moderators
-
- Inactive Developer
- Posts: 2461
- Joined: August 15th, 2008, 8:46 pm
- Location: Germany
Re: Exercises in Formula and Lua AI
Well it seems the global lua variable ai doesn't get redefined upon reload. It makes sense since you have the assignment ai = ... in [side] which is only read at scenario start. The lua state is destroyed upon scenario ending (advancing to the next scenario, loading a saved game, "back to turn x" etc) and it takes all variables with it if they haven't been put into wml variables. Whether this is a bug or intended behavior I dunno in this case. You get a similar effect when defining some function in a preload event with first_time_only=yes.
A way to solve it could probably be to store the complete ai table into a wml variable on saving (wesnoth.game_events.on_save) and to restore it when loading (wesnoth.game_events.on_load) in case that the ai table is still empty. In case that it is a bug this could be a workaround.
Do you code anything for this ai table or is it pre-created by the game ? In the ladder case I vote for a bug.
A way to solve it could probably be to store the complete ai table into a wml variable on saving (wesnoth.game_events.on_save) and to restore it when loading (wesnoth.game_events.on_load) in case that the ai table is still empty. In case that it is a bug this could be a workaround.
Do you code anything for this ai table or is it pre-created by the game ? In the ladder case I vote for a bug.
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml starters • Plan Your Advancements: mp mod
The Earth's Gut: sp campaign • Settlers of Wesnoth: mp scenario • Wesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
A Simple Campaign: campaign draft for wml starters • Plan Your Advancements: mp mod
The Earth's Gut: sp campaign • Settlers of Wesnoth: mp scenario • Wesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign
Re: Exercises in Formula and Lua AI
I'm not sure that that's it. It's my understanding that the ai table is created by the engine at the beginning of the AI's turn (that's what the '...' is about, I think). It contains all the Lua AI functions/actions and only stopunit seems to be causing problems. There's an ai.move() 2 lines before the second stopunit line that doesn't make any trouble, and I've use ai.attack() without trouble before also.
... and since I wrote that I did some more tests and figured out what is going on, although I do not know why. The AI got stuck in an infinite loop. From what I understand, there are 3 reasons why a candidate action is not called any more:
1. Its evaluation returns a value <=0: that's not the case here since I had it hard coded to return 300,000.
2. The CA tries to perform an invalid move (e.g. moving a unit with no moves left): it gets black listed for the remainder of the turn
3. If a CA gets called too many times without changing the game state, the loop gets broken after a certain number of iterations (not sure about this one one, I think I read that somewhere, but don't remember where).
So, 1. and 2. don't apply here, I think. 1. for the mentioned reasons, 2. because (apparently?) a stopunit action does not count as an invalid move even if the unit has no actions left (which makes sense). If that's true, that means that 3, for some reason, kicks in before a reload of the scenario, but not after. That sounds like a bug to me (assuming I have it right), I just don't know if the before or the after is the bug... I'll put that on my list of things to talk to Crab about.
Anyways, I have now changed the evaluation routine to return a valid score only when the unit has moves left, and that takes care of the problem. It's a cleaner way of doing it anyway. I want to do a bit more testing, then I'll update the code posted earlier.
As for your other question, I used to store my helper functions inside the ai variable, but since that holds all the default engine functions, I decided that that was probably bad practice and am not doing it any more. That's what I am using ai_helper for now (or I am defining them directly in the engine, as in the posted examples).
... and since I wrote that I did some more tests and figured out what is going on, although I do not know why. The AI got stuck in an infinite loop. From what I understand, there are 3 reasons why a candidate action is not called any more:
1. Its evaluation returns a value <=0: that's not the case here since I had it hard coded to return 300,000.
2. The CA tries to perform an invalid move (e.g. moving a unit with no moves left): it gets black listed for the remainder of the turn
3. If a CA gets called too many times without changing the game state, the loop gets broken after a certain number of iterations (not sure about this one one, I think I read that somewhere, but don't remember where).
So, 1. and 2. don't apply here, I think. 1. for the mentioned reasons, 2. because (apparently?) a stopunit action does not count as an invalid move even if the unit has no actions left (which makes sense). If that's true, that means that 3, for some reason, kicks in before a reload of the scenario, but not after. That sounds like a bug to me (assuming I have it right), I just don't know if the before or the after is the bug... I'll put that on my list of things to talk to Crab about.
Anyways, I have now changed the evaluation routine to return a valid score only when the unit has moves left, and that takes care of the problem. It's a cleaner way of doing it anyway. I want to do a bit more testing, then I'll update the code posted earlier.
As for your other question, I used to store my helper functions inside the ai variable, but since that holds all the default engine functions, I decided that that was probably bad practice and am not doing it any more. That's what I am using ai_helper for now (or I am defining them directly in the engine, as in the posted examples).
SP campaigns: Galuldur's First Journey (1.12 & 1.14) & Grnk the Mighty (1.10 & 1.12)
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
Re: Exercises in Formula and Lua AI
Further on that topic: the global variable ca_counter (candidate action counter?) defined in the preload event has value 2 inside coward_ai:coward_execution() when the scenario is started from the beginning. It has value 0 after a reload. It seems that the ai function table gets set correctly again after a reload (at the beginning of the AI turn), but that the counter variable is not set. I don't know if that is related to the above problem, but it's currently my best guess.
[I updated the code posted earlier. It now works after reloading too.]
EDIT: The only place where I can find ca_counter in either the source or the lua code is in the definition of
[I updated the code posted earlier. It now works after reloading too.]
EDIT: The only place where I can find ca_counter in either the source or the lua code is in the definition of
[add_ai_behavior]
, and there it is only used to set name and id of the new behavior candidate action (BCA). So I thought it might cause problems if adding BCA's both before and after a reload, as we then might get different CA's with the same name/id, but that doesn't seem to cause any problems, at least not in the tests I did. So, I don't think that this is the cause of the problem -- and I am wondering if there really is a need for having to define ca_counter in the first place in a preload event (it just seems "unelegant") or whether this shouldn't be handled differently in the add_ai_behavior code (which is written in Lua and easily modified).SP campaigns: Galuldur's First Journey (1.12 & 1.14) & Grnk the Mighty (1.10 & 1.12)
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
Re: Exercises in Formula and Lua AI
Simons: Below are the macros for the stationed guardian. The macro used for a guardian unit is defined like this:
'SIDE' is the side of the guardian unit (needed so that the behavior can be applied to the correct side's AI), 'ID' its id. 'RADIUS' is the distance over which the actions take effect (as we discussed, I implemented that as distance in hexes, rather than movement cost). Then come the cordinates of the station and the guarded location.
The behavior is as follows:
- If no enemy is within radius of the guard's current position: do nothing
- Otherwise: If an enemy is within 'radius' of the guard, but not also within the same distance of the guarded location and the station (all of this simultaneously), move the guard in the direction of the station
- Otherwise:
- Pick the enemy that is closest to the guarded location
- If we can reach him, pick the adjacent hex with the highest defense rating and attack from there
- If not in reach, move toward this unit
I think this is pretty much as you described, but if not, it's easy to change any of that. As for the choice of unit to be attacked, I figured that the highest priority target for our guard would be the unit that is closest to the position he guards. Note, however, that this means that the guard always moves toward that closest unit (even if it is out of his current MP reach, because of terrain or enemy ZoC), and might skip a unit that he can reach but that is not as close to the guarded location. Still, I think that makes sense given the priorities I set above. If, on this move, the guard ends up at a position next to an enemy, he'll still attack that enemy, even if it was not the original target.
You can also get somewhat strange behavior if you choose radius much smaller or larger than the units MP and the distance between the two locations, but that's up to the scenario designer to set up in a sensible way. Also, those strange behaviors might be desirable in some cases.
One more thing: you mentioned shroud before, but as the standard Wesnoth AI behavior is to ignore shroud and fog, it is also not taken into account here. If you want me to add that, I'm sure that can be done. I'll have to look up how exactly, but the functionality is there.
A couple technical notes for those interested in the code:
- The attack code can be simplified quite a bit once ai.get_attacks() has been implemented in Lua AI.
- The evaluation value is set so that this action happens just before the normal combat candidate action
- If a the guard moves toward the station, or an enemy without getting there, but ends up next to another enemy, it will still attack this through the standard RCA AI actions. This is done by only using ai.stopunit_moves() at the end, not ai.stopunit_all()
Finally, here's the code. It has the same 3 macros as the previous examples:
EDIT: renamed the preload macro STATIONED_GUARDIAN_PRELOAD, to make it consistent with the other examples. The default LUA_AI_PRLEOAD can also be used, but might contain parts not necessary for the stationed guardian.
EDIT 2: This is the original version of the code. A newer version might be available here.
#define STATIONED_GUARDIAN_UNIT SIDE ID RADIUS STATION_X STATION_Y GUARD_X GUARD_Y
'SIDE' is the side of the guardian unit (needed so that the behavior can be applied to the correct side's AI), 'ID' its id. 'RADIUS' is the distance over which the actions take effect (as we discussed, I implemented that as distance in hexes, rather than movement cost). Then come the cordinates of the station and the guarded location.
The behavior is as follows:
- If no enemy is within radius of the guard's current position: do nothing
- Otherwise: If an enemy is within 'radius' of the guard, but not also within the same distance of the guarded location and the station (all of this simultaneously), move the guard in the direction of the station
- Otherwise:
- Pick the enemy that is closest to the guarded location
- If we can reach him, pick the adjacent hex with the highest defense rating and attack from there
- If not in reach, move toward this unit
I think this is pretty much as you described, but if not, it's easy to change any of that. As for the choice of unit to be attacked, I figured that the highest priority target for our guard would be the unit that is closest to the position he guards. Note, however, that this means that the guard always moves toward that closest unit (even if it is out of his current MP reach, because of terrain or enemy ZoC), and might skip a unit that he can reach but that is not as close to the guarded location. Still, I think that makes sense given the priorities I set above. If, on this move, the guard ends up at a position next to an enemy, he'll still attack that enemy, even if it was not the original target.
You can also get somewhat strange behavior if you choose radius much smaller or larger than the units MP and the distance between the two locations, but that's up to the scenario designer to set up in a sensible way. Also, those strange behaviors might be desirable in some cases.
One more thing: you mentioned shroud before, but as the standard Wesnoth AI behavior is to ignore shroud and fog, it is also not taken into account here. If you want me to add that, I'm sure that can be done. I'll have to look up how exactly, but the functionality is there.
A couple technical notes for those interested in the code:
- The attack code can be simplified quite a bit once ai.get_attacks() has been implemented in Lua AI.
- The evaluation value is set so that this action happens just before the normal combat candidate action
- If a the guard moves toward the station, or an enemy without getting there, but ends up next to another enemy, it will still attack this through the standard RCA AI actions. This is done by only using ai.stopunit_moves() at the end, not ai.stopunit_all()
Finally, here's the code. It has the same 3 macros as the previous examples:
{STATIONED_GUARDIAN_PRELOAD}
(put inside scenario tags), {STATIONED_GUARDIAN_ENGINE}
(inside the side definition) and {STATIONED_GUARDIAN_UNIT SIDE ID RADIUS STATION_X STATION_Y GUARD_X GUARD_Y}
(inside action WML after the unit has been created). Other units of the side will behave as usual.EDIT: renamed the preload macro STATIONED_GUARDIAN_PRELOAD, to make it consistent with the other examples. The default LUA_AI_PRLEOAD can also be used, but might contain parts not necessary for the stationed guardian.
EDIT 2: This is the original version of the code. A newer version might be available here.
Code: Select all
#define STATIONED_GUARDIAN_PRELOAD
[event]
name=preload
first_time_only=no
[lua]
code = <<
H = wesnoth.require "lua/helper.lua"
W = H.set_wml_action_metatable {}
_ = wesnoth.textdomain "my-campaign"
-- Define your global constants here.
-- ai = {}
ca_counter = 0
H.set_wml_var_metatable(_G)
>>
[/lua]
[/event]
#enddef
#define STATIONED_GUARDIAN_ENGINE
{ai/aliases/stable_singleplayer.cfg}
[ai]
[engine]
name="lua"
code= <<
--! ==============================================================
local ai = ...
local stationed_guardian_ai = {}
function stationed_guardian_ai:next_hop(unit, x, y)
-- Finds the next "hop" of 'unit' on its way to (x,y)
-- Returns coordinates of the endpoint of the hop, and movement cost to get there
local path, cost = wesnoth.find_path(unit, x, y)
-- If unit cannot get there:
if cost >= 42424242 then return nil, cost end
-- If unit can get there in one move:
if cost <= unit.moves then return {x, y}, cost end
-- If it takes more than one move:
local next_hop, nh_cost = {x,y}, 0
for index, path_loc in ipairs(path) do
local sub_path, sub_cost = wesnoth.find_path( unit, path_loc[1], path_loc[2])
if sub_cost <= unit.moves
then next_hop, nh_cost = path_loc, sub_cost
else return next_hop, nh_cost
end
end
end
function stationed_guardian_ai:guardian_evaluation(id)
local unit = wesnoth.get_units { id=id }[1]
if (unit.moves > 0) then
value = 100010
else
value = 0
end
--print("Eval:", value)
return value
end
function stationed_guardian_ai:guardian_execution(id, radius, s_x, s_y, g_x, g_y)
-- (s_x,s_y): coordinates where unit is stationed; tries to move here if there is nobody to attack
-- (g_x,g_y): location that the unit guards
local unit = wesnoth.get_units { id=id }[1]
-- find if there are enemies within 'radius'
local enemies = wesnoth.get_units {
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} },
{ "filter_location", {x = unit.x, y = unit.y, radius = radius} }
}
-- if no enemies are within 'radius': keep unit from doing anything and exit
if not enemies[1] then
--print("No enemies close -> sleeping:",unit.id)
ai.stopunit_moves(unit)
return
end
-- Otherwise, unit will either attack or move toward station
--print("Guardian unit waking up",unit.id)
-- enemies must be within 'radius' of guard, (s_x,s_y) *and* (g_x,g_y)
-- simultaneous for guard to attack
local target = {}
local min_dist = 9999
for i,e in ipairs(enemies) do
local ds = H.distance_between(s_x, s_y, e.x, e.y)
local dg = H.distance_between(g_x, g_y, e.x, e.y)
-- If valid target found, save the one with the shortest distance from (g_x,g_y)
if (ds <= radius) and (dg <= radius) and (dg < min_dist) then
--print("target:", e.id, ds, dg)
target = e
min_dist = dg
end
end
-- If a valid target was found, unit attacks this target, or moves toward it
if (min_dist ~= 9999) then
--print ("Go for enemy unit:", target.id)
-- Find tiles adjacent to the target, and save the one that our unit
-- can reach with the highest defense rating
local best_defense = -9999
local attack_loc = 0
for x,y in H.adjacent_tiles(target.x, target.y) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=x, y=y, { "not", { id = unit.id } } }[1]
if not occ_hex then
-- defense rating of the hex
local defense = 100 - wesnoth.unit_defense(unit, wesnoth.get_terrain(x, y))
--print(x,y,defense)
local nh = self:next_hop(unit, x, y)
-- if this is best defense rating and unit can reach it, save this location
if (nh[1] == x) and (nh[2] == y) and (defense > best_defense) then
attack_loc = {x, y}
best_defense = defense
end
end
end
-- If a valid hex was found: move there and attack
if (best_defense ~= -9999) then
--print("Attack at:",attack_loc[1],attack_loc[2],best_defense)
ai.move(unit, attack_loc[1],attack_loc[2])
ai.attack(unit, target)
else -- otherwise move toward that enemy
--print("Cannot reach target, moving toward it")
local reach = wesnoth.find_reach(unit)
-- Go through all hexes the unit can reach, find closest to target
local nh = {} -- cannot use next_hop here since target hex is occupied by enemy
local min_dist = 9999
for i,r in ipairs(reach) do
-- only consider unoccupied hexes
local occ_hex = wesnoth.get_units { x=r[1], y=r[2], { "not", { id = unit.id } } }[1]
if not occ_hex then
local d = H.distance_between(r[1], r[2], target.x, target.y)
if d < min_dist then
min_dist = d
nh = {r[1], r[2]}
end
end
end
-- Finally, execute the move toward the target
if (nh[1] ~= unit.x) or (nh[2] ~= unit.y) then
ai.move_full(unit, nh[1], nh[2])
end
end
-- If no enemy within the target zone, move toward station position
else
--print "Move toward station"
local nh = self:next_hop(unit, s_x, s_y)
if (nh[1] ~= unit.x) or (nh[2] ~= unit.y) then
ai.move_full(unit, nh[1], nh[2])
end
end
ai.stopunit_moves(unit) -- just in case we did not use up all MP
-- If there are attacks left and unit ended up next to an enemy, we'll leave this to RCA AI
end
return stationed_guardian_ai
--! ==============================================================
>>
[/engine]
[/ai]
#enddef
#define STATIONED_GUARDIAN_UNIT SIDE ID RADIUS STATION_X STATION_Y GUARD_X GUARD_Y
[add_ai_behavior]
side={SIDE}
[filter]
id="{ID}"
[/filter]
sticky=yes
loop_id=main_loop
evaluation="return (...):guardian_evaluation('{ID}')"
execution="(...):guardian_execution('{ID}', {RADIUS}, {STATION_X}, {STATION_Y}, {GUARD_X}, {GUARD_Y})"
[/add_ai_behavior]
#enddef
Last edited by mattsc on November 27th, 2011, 11:33 pm, edited 2 times in total.
SP campaigns: Galuldur's First Journey (1.12 & 1.14) & Grnk the Mighty (1.10 & 1.12)
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
- Simons Mith
- Posts: 821
- Joined: January 27th, 2005, 10:46 pm
- Location: Twickenham
- Contact:
Re: Exercises in Formula and Lua AI
Well, I plumbed in a couple of stationed guards into my current working scenario, and the work fine. My first ever guards who return to their stations!
/me color="#impressed"
(Which is just black, apparently.)
/me color="#impressed"
(Which is just black, apparently.)
Re: Exercises in Formula and Lua AI
I have a suggestion for changing the code of the
In brief:
I am pretty sure that I know how to make the needed change to the code.
[add_ai_behavior]
tag, which is written in Lua (in 'lua/wml-tags.lua'). How do I go about making that an official request? It's neither really a bug nor a feature request, although it could be considered the latter, I guess. (And yes, I know that we're in a feature freeze right now, so there's plenty of time for this.)In brief:
[add_ai_behavior]
requires the global variable 'ca_counter' to be set, which is, I believe, unnecessary with a minor(ish) change to the code. If it is not set, Wesnoth crashes when the tag is called. Furthermore, I believe that even if an engine other than Lua AI is used, the variable needs to be set as a global Lua variable [untested]. Also, as I have mentioned before, 'ca_counter' is not preserved after reload, which doesn't matter as long as we only add candidate actions, but can cause trouble if we want to remove them manually later.I am pretty sure that I know how to make the needed change to the code.
SP campaigns: Galuldur's First Journey (1.12 & 1.14) & Grnk the Mighty (1.10 & 1.12)
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
Re: Exercises in Formula and Lua AI
I have set up a few wiki pages describing how to modify the Wesnoth AI using both Formula AI and Lua AI. They are meant to complement the information on the existing wiki pages, not replace it. I wrote them in a howto-ish, tutorial-kind-of-style, with a specific emphasis on setting up an efficient development and testing environment. The front page can be found here: Practical Guide to Modifying AI Behavior.
If this is found to be useful, I could link to it from the existing pages, probably from the beginning of Customizing AI in Wesnoth 1.8.
The 'Example Code' pages are not done yet, containing only placeholder lists that are not yet complete (mostly they are just the examples from this thread). Also, while I was putting these pages together, I found a number of inconsistencies on the existing wiki pages. My guess is that they mostly stem from earlier incarnations of the AIs that have by now been changed. I assume it is ok if I go ahead and fix those?
Anyways, hope this is useful to at least one or two of you!
If this is found to be useful, I could link to it from the existing pages, probably from the beginning of Customizing AI in Wesnoth 1.8.
The 'Example Code' pages are not done yet, containing only placeholder lists that are not yet complete (mostly they are just the examples from this thread). Also, while I was putting these pages together, I found a number of inconsistencies on the existing wiki pages. My guess is that they mostly stem from earlier incarnations of the AIs that have by now been changed. I assume it is ok if I go ahead and fix those?
Anyways, hope this is useful to at least one or two of you!
SP campaigns: Galuldur's First Journey (1.12 & 1.14) & Grnk the Mighty (1.10 & 1.12)
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
Re: Exercises in Formula and Lua AI
wow - that's a really valuable contribution, thanks!
Re: Exercises in Formula and Lua AI
Thanks for setting up the Wiki page mattsc. I have a bunch of example FAIs that might be applicable that I've been working on recently.
Also, I've been using [modify_ai] tags in [side] WML areas for my campaign. This might be useful to add that you can just add to the RCA AI without specifying a version number or engine.
I really like the idea of getting a page up that shows how to use FAI and LUA AI easily with examples. I'd be glad to help you with this.
Also, I've been using [modify_ai] tags in [side] WML areas for my campaign. This might be useful to add that you can just add to the RCA AI without specifying a version number or engine.
I really like the idea of getting a page up that shows how to use FAI and LUA AI easily with examples. I'd be glad to help you with this.
Re: Exercises in Formula and Lua AI
Coffee:
That would be great, especially since I have mostly LAI and very few FAI examples. I set up the beginning of the FAI example page, with the one example I have (I also have a bunch of working code on alternative attack strategies etc., but none of them are ready to post). If you could add to that, that would be great. Or if you prefer me to put them up, send them to me and I'll do it.Coffee wrote:Thanks for setting up the Wiki page mattsc. I have a bunch of example FAIs that might be applicable that I've been working on recently.
...
I really like the idea of getting a page up that shows how to use FAI and LUA AI easily with examples. I'd be glad to help you with this.
Sounds good. I haven't tried that, all the examples I tried need the version number. I will see where to add this in the pages. Just to confirm first though, you are doing that without having anCoffee wrote:Also, I've been using [modify_ai] tags in [side] WML areas for my campaign. This might be useful to add that you can just add to the RCA AI without specifying a version number or engine.
{ai/aliases/stable_singleplayer.cfg}
(which contains the version number line) or equivalent in your scenario somewhere?SP campaigns: Galuldur's First Journey (1.12 & 1.14) & Grnk the Mighty (1.10 & 1.12)
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
Re: Exercises in Formula and Lua AI
Yeah, the 2 examples for Formula AI from my campaign demonstrate how to do it with just [Modify_AI] WML macros from AI.cfg. I think this makes the most sense to do since the version numbers change.mattsc wrote:Just to confirm first though, you are doing that without having an{ai/aliases/stable_singleplayer.cfg}
(which contains the version number line) or equivalent in your scenario somewhere?
Just wondering if this is possible to do in LUA in a similar way?
Re: Exercises in Formula and Lua AI
I saw that. Thanks for posting the examples! I will add that method to the wiki page.Coffee wrote:Yeah, the 2 examples for Formula AI from my campaign demonstrate how to do it with just [Modify_AI] WML macros from AI.cfg. I think this makes the most sense to do since the version numbers change.
To be honest, I don't know. For all I know you always need to define an engine for Lua AI, but I have not tried only adding a self-contained candidate action. I will test that and report back (and change the wiki if it works).Coffee wrote:Just wondering if this is possible to do in LUA in a similar way?
SP campaigns: Galuldur's First Journey (1.12 & 1.14) & Grnk the Mighty (1.10 & 1.12)
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
Re: Exercises in Formula and Lua AI
I confirmed that this works in Formula AI, but not in Lua AI. In fact, in Lua not only do we need to set up the engine, but the main_loop stage needs to be defined explicitly also, even if it does not contain any CAs.Coffee wrote:Yeah, the 2 examples for Formula AI from my campaign demonstrate how to do it with just [Modify_AI] WML macros from AI.cfg. I think this makes the most sense to do since the version numbers change.
Just wondering if this is possible to do in LUA in a similar way?
I added a section to the FAI Howto page on how to do this, and a comment to the LAI Howto page that it doesn't work there.
SP campaigns: Galuldur's First Journey (1.12 & 1.14) & Grnk the Mighty (1.10 & 1.12)
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
- Simons Mith
- Posts: 821
- Joined: January 27th, 2005, 10:46 pm
- Location: Twickenham
- Contact:
Re: Exercises in Formula and Lua AI
I've plugged in the coward AI now, and I am pleased to announce I now have hordes -or herds- of cowardly horses.
But this seems to have uncovered a bug. My horses have defined flee_to locations, as you might expect, and when they reach that hex, or ones near to it, they are killed to remove them. This upsets the lua_ai which sometimes seems to want to move them further. Result is a lua error:
followed by a stacktrace
One trivial change I made, which I don't think should have upset anything, was to tidy the macro names slightly. I now have STATIONED_GUARDIAN_PRELOAD, STATIONED_GUARDIAN_ENGINE, STATIONED_GUARDIAN, COWARD_PRELOAD, COWARD_ENGINE and COWARD as my macro names. That alternation's harmless, isn't it?
But this seems to have uncovered a bug. My horses have defined flee_to locations, as you might expect, and when they reach that hex, or ones near to it, they are killed to remove them. This upsets the lua_ai which sometimes seems to want to move them further. Result is a lua error:
Code: Select all
131: bad argument to stopunit_all
location (unit/integer) expected, got userdata
One trivial change I made, which I don't think should have upset anything, was to tidy the macro names slightly. I now have STATIONED_GUARDIAN_PRELOAD, STATIONED_GUARDIAN_ENGINE, STATIONED_GUARDIAN, COWARD_PRELOAD, COWARD_ENGINE and COWARD as my macro names. That alternation's harmless, isn't it?
Re: Exercises in Formula and Lua AI
Simons: Yes, that change is harmless. Here's what's happening: the coward move execution has two separate 'AI moves' in it, the actual move away from the enemies, and then an 'ai.stopunit_all()' that takes all moves and attacks away from the unit at the very end of the code. The latter is supposed to kick in whether the unit moved away from somebody or not, and makes sure that it does not participate in any of the other (default) AI moves.
What happens in your case is that the unit gets killed after the move (through a 'moveto' event) and then isn't there any more for the stopunit command, which causes the error. There are 3 ways (at least) to take care of that:
- Program the code so that it only ever executes one action. That might not be possible in all cases, as sometimes we want a unit to do a move and an attack; or to do a move and disable all attacks; etc.
- Every AI move should probably be preceded by a 'ai.check_whatevermove_action()' call, just so that one is certain that one doesn't overlook something. Those functions aren't implemented in Lua AI yet, AFAIK, so that's not possible for now.
- For now, I simply check whether the unit still exists before the stopunit command. I tested it and I think it works.
I updated the macros here. The only change are 2 lines at the end of the engine macro. Could you test that that works now without producing error messages? Thanks for pointing this out!
I will go through the other examples and make sure similar things cannot happen for those also.
What happens in your case is that the unit gets killed after the move (through a 'moveto' event) and then isn't there any more for the stopunit command, which causes the error. There are 3 ways (at least) to take care of that:
- Program the code so that it only ever executes one action. That might not be possible in all cases, as sometimes we want a unit to do a move and an attack; or to do a move and disable all attacks; etc.
- Every AI move should probably be preceded by a 'ai.check_whatevermove_action()' call, just so that one is certain that one doesn't overlook something. Those functions aren't implemented in Lua AI yet, AFAIK, so that's not possible for now.
- For now, I simply check whether the unit still exists before the stopunit command. I tested it and I think it works.
I updated the macros here. The only change are 2 lines at the end of the engine macro. Could you test that that works now without producing error messages? Thanks for pointing this out!
I will go through the other examples and make sure similar things cannot happen for those also.
SP campaigns: Galuldur's First Journey (1.12 & 1.14) & Grnk the Mighty (1.10 & 1.12)
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on
AI experiments: Micro AIs (wiki, forum thread, known/fixed bugs), Fred, AI-demos add-on