Merge branch 'master' of github.com:wesnoth/wesnoth

This commit is contained in:
Alexander van Gessel 2014-04-01 03:14:31 +02:00
commit 116c6a6e97
13 changed files with 362 additions and 180 deletions

View file

@ -1,6 +1,7 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local LS = wesnoth.require "lua/location_set.lua"
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
local ca_big_animals = {}
@ -33,15 +34,18 @@ function ca_big_animals:execution(ai, cfg)
--AH.put_labels(avoid)
for i,unit in ipairs(units) do
local goal = MAIUV.get_mai_unit_variables(unit, cfg.ai_id)
-- Unit gets a new goal if none exist or on any move with 10% random chance
local r = math.random(10)
if (not unit.variables.goal_x) or (r == 1) then
if (not goal.goal_x) or (r == 1) then
local locs = AH.get_passable_locations(cfg.filter_location or {})
local rand = math.random(#locs)
--print(type, ': #locs', #locs, rand)
unit.variables.goal_x, unit.variables.goal_y = locs[rand][1], locs[rand][2]
goal.goal_x, goal.goal_y = locs[rand][1], locs[rand][2]
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, goal)
end
--print('Big animal goto: ', type, unit.variables.goal_x, unit.variables.goal_y, r)
--print('Big animal goto: ', unit.id, goal.goal_x, goal.goal_y, r)
-- hexes the unit can reach
local reach_map = AH.get_reachable_unocc(unit)
@ -57,7 +61,7 @@ function ca_big_animals:execution(ai, cfg)
local max_rating, best_hex = -9e99, {}
reach_map:iter( function(x, y, v)
-- Distance from goal is first rating
local rating = - H.distance_between(x, y, unit.variables.goal_x, unit.variables.goal_y)
local rating = - H.distance_between(x, y, goal.goal_x, goal.goal_y)
-- Proximity to an enemy unit is a plus
local enemy_hp = 500
@ -87,14 +91,12 @@ function ca_big_animals:execution(ai, cfg)
else -- If animal did not move, we need to stop it (also delete the goal)
AH.checked_stopunit_moves(ai, unit)
if (not unit) or (not unit.valid) then return end
unit.variables.goal_x = nil
unit.variables.goal_y = nil
MAIUV.delete_mai_unit_variables(unit, cfg.ai_id)
end
-- Or if this gets the unit to the goal, we also delete the goal
if (unit.x == unit.variables.goal_x) and (unit.y == unit.variables.goal_y) then
unit.variables.goal_x = nil
unit.variables.goal_y = nil
if (unit.x == goal.goal_x) and (unit.y == goal.goal_y) then
MAIUV.delete_mai_unit_variables(unit, cfg.ai_id)
end
-- Finally, if the unit ended up next to enemies, attack the weakest of those

View file

@ -22,7 +22,7 @@ function ca_goto:evaluation(ai, cfg, self)
-- which case we do not do anything
if cfg.release_all_units_at_goal then
for rel in H.child_range(self.data, "goto_release_all") do
if (rel.id == cfg.ca_id) then
if (rel.id == cfg.ai_id) then
return 0
end
end
@ -65,7 +65,7 @@ function ca_goto:evaluation(ai, cfg, self)
if cfg.release_unit_at_goal then
for i_unit=#units,1,-1 do
for rel in H.child_range(self.data, "goto_release_unit") do
if (rel.id == cfg.ca_id .. '_' .. units[i_unit].id) then
if (rel.id == cfg.ai_id .. '_' .. units[i_unit].id) then
table.remove(units, i_unit)
break
end
@ -227,12 +227,12 @@ function ca_goto:execution(ai, cfg, self)
-- 2. Keys cannot contain certain characters -> everything potentially user-defined needs to be in values
if unit_at_goal then
if cfg.release_unit_at_goal then
table.insert(self.data, { "goto_release_unit" , { id = cfg.ca_id .. '_' .. best_unit.id } } )
table.insert(self.data, { "goto_release_unit" , { id = cfg.ai_id .. '_' .. best_unit.id } } )
end
if cfg.release_all_units_at_goal then
--print("Releasing all units")
table.insert(self.data, { "goto_release_all", { id = cfg.ca_id } } )
table.insert(self.data, { "goto_release_all", { id = cfg.ai_id } } )
end
end
end

View file

@ -1,6 +1,7 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local LS = wesnoth.require "lua/location_set.lua"
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
local ca_hang_out = {}
@ -8,8 +9,8 @@ function ca_hang_out:evaluation(ai, cfg, self)
cfg = cfg or {}
-- Return 0 if the mobilize condition has previously been met
for mobilze in H.child_range(self.data, "hangout_mobilize_units") do
if (mobilze.id == cfg.ca_id) then
for mobilize in H.child_range(self.data, "hangout_mobilize_units") do
if (mobilize.id == cfg.ai_id) then
return 0
end
end
@ -18,12 +19,12 @@ function ca_hang_out:evaluation(ai, cfg, self)
if (cfg.mobilize_condition and wesnoth.eval_conditional(cfg.mobilize_condition))
or (cfg.mobilize_on_gold_less_than and (wesnoth.sides[wesnoth.current.side].gold < cfg.mobilize_on_gold_less_than))
then
table.insert(self.data, { "hangout_mobilize_units" , { id = cfg.ca_id } } )
table.insert(self.data, { "hangout_mobilize_units" , { id = cfg.ai_id } } )
-- Need to unmark all units also
local units = wesnoth.get_units { side = wesnoth.current.side, { "and", cfg.filter } }
for i,u in ipairs(units) do
u.variables.mai_hangout_moved = nil
MAIUV.delete_mai_unit_variables(u, cfg.ai_id)
end
return 0
@ -85,7 +86,7 @@ function ca_hang_out:execution(ai, cfg, self)
local best_hex, best_unit, max_rating = {}, {}, -9e99
for i,u in ipairs(units) do
-- Only consider units that have not been marked yet
if (not u.variables.mai_hangout_moved) then
if (not MAIUV.get_mai_unit_variables(u, cfg.ai_id, "moved")) then
local best_hex_unit, max_rating_unit = {}, -9e99
-- Check out all unoccupied hexes the unit can reach
@ -128,12 +129,14 @@ function ca_hang_out:execution(ai, cfg, self)
for i,u in ipairs(units) do
AH.checked_stopunit_moves(ai, u)
-- Also remove the markers
if u and u.valid then u.variables.mai_hangout_moved = nil end
if u and u.valid then MAIUV.delete_mai_unit_variables(u, cfg.ai_id) end
end
else
-- Otherwise move unit and mark as having been used
AH.checked_move(ai, best_unit, best_hex[1], best_hex[2])
if best_unit and best_unit.valid then best_unit.variables.mai_hangout_moved = true end
if best_unit and best_unit.valid then
MAIUV.set_mai_unit_variables(best_unit, cfg.ai_id, { moved = true })
end
end
end

View file

@ -1,6 +1,7 @@
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
local ca_hunter = {}
@ -64,17 +65,19 @@ function ca_hunter:execution(ai, cfg)
)[1]
-- If hunting_status is not set for the unit -> default behavior -> hunting
if (not unit.variables.hunting_status) then
local hunter_vars = MAIUV.get_mai_unit_variables(unit, cfg.ai_id)
if (not hunter_vars.hunting_status) then
-- Unit gets a new goal if none exist or on any move with 10% random chance
local r = math.random(10)
if (not unit.variables.goal_x) or (r <= 1) then
if (not hunter_vars.goal_x) or (r <= 1) then
-- 'locs' includes border hexes, but that does not matter here
locs = AH.get_passable_locations((cfg.filter_location or {}), unit)
local rand = math.random(#locs)
--print('#locs', #locs, rand)
unit.variables.goal_x, unit.variables.goal_y = locs[rand][1], locs[rand][2]
hunter_vars.goal_x, hunter_vars.goal_y = locs[rand][1], locs[rand][2]
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
end
--print('Hunter goto: ', unit.variables.goal_x, unit.variables.goal_y, r)
--print('Hunter goto: ', hunter_vars.goal_x, hunter_vars.goal_y, r)
-- Hexes the unit can reach
local reach_map = AH.get_reachable_unocc(unit)
@ -83,7 +86,7 @@ function ca_hunter:execution(ai, cfg)
local max_rating, best_hex = -9e99, {}
reach_map:iter( function(x, y, v)
-- Distance from goal is first rating
local rating = - H.distance_between(x, y, unit.variables.goal_x, unit.variables.goal_y)
local rating = - H.distance_between(x, y, hunter_vars.goal_x, hunter_vars.goal_y)
-- Proximity to an enemy unit is a plus
local enemy_hp = 500
@ -109,12 +112,14 @@ function ca_hunter:execution(ai, cfg)
else -- If hunter did not move, we need to stop it (also delete the goal)
AH.checked_stopunit_moves(ai, unit)
if (not unit) or (not unit.valid) then return end
unit.variables.goal_x, unit.variables.goal_y = nil, nil
hunter_vars.goal_x, hunter_vars.goal_y = nil, nil
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
end
-- Or if this gets the unit to the goal, we also delete the goal
if (unit.x == unit.variables.goal_x) and (unit.y == unit.variables.goal_y) then
unit.variables.goal_x, unit.variables.goal_y = nil, nil
if (unit.x == hunter_vars.goal_x) and (unit.y == hunter_vars.goal_y) then
hunter_vars.goal_x, hunter_vars.goal_y = nil, nil
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
end
-- Finally, if the unit ended up next to enemies, attack the weakest of those
@ -122,8 +127,9 @@ function ca_hunter:execution(ai, cfg)
-- If the enemy was killed, hunter returns home
if unit.valid and (attack_status == 'killed') then
unit.variables.goal_x, unit.variables.goal_y = nil, nil
unit.variables.hunting_status = 'return'
hunter_vars.goal_x, hunter_vars.goal_y = nil, nil
hunter_vars.hunting_status = 'return'
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
if cfg.show_messages then
W.message { speaker = unit.id, message = 'Now that I have eaten, I will go back home.' }
end
@ -134,7 +140,7 @@ function ca_hunter:execution(ai, cfg)
end
-- If we got here, this means the unit is either returning, or resting
if (unit.variables.hunting_status == 'return') then
if (hunter_vars.hunting_status == 'return') then
goto_x, goto_y = wesnoth.find_vacant_tile(cfg.home_x, cfg.home_y)
--print('Go home:', home_x, home_y, goto_x, goto_y)
@ -163,10 +169,11 @@ function ca_hunter:execution(ai, cfg)
-- If the unit got home, start the resting counter
if (unit.x == cfg.home_x) and (unit.y == cfg.home_y) then
unit.variables.hunting_status = 'resting'
unit.variables.resting_until = wesnoth.current.turn + (cfg.rest_turns or 3)
hunter_vars.hunting_status = 'resting'
hunter_vars.resting_until = wesnoth.current.turn + (cfg.rest_turns or 3)
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
if cfg.show_messages then
W.message { speaker = unit.id, message = 'I made it home - resting now until the end of Turn ' .. unit.variables.resting_until .. ' or until fully healed.' }
W.message { speaker = unit.id, message = 'I made it home - resting now until the end of Turn ' .. hunter_vars.resting_until .. ' or until fully healed.' }
end
end
@ -175,7 +182,7 @@ function ca_hunter:execution(ai, cfg)
end
-- If we got here, the only remaining action is resting
if (unit.variables.hunting_status == 'resting') then
if (hunter_vars.hunting_status == 'resting') then
-- So all we need to do is take moves away from the unit
AH.checked_stopunit_moves(ai, unit)
if (not unit) or (not unit.valid) then return end
@ -185,9 +192,10 @@ function ca_hunter:execution(ai, cfg)
if (not unit) or (not unit.valid) then return end
-- If this is the last turn of resting, we also remove the status and turn variable
if (unit.hitpoints >= unit.max_hitpoints) and (unit.variables.resting_until <= wesnoth.current.turn) then
unit.variables.hunting_status = nil
unit.variables.resting_until = nil
if (unit.hitpoints >= unit.max_hitpoints) and (hunter_vars.resting_until <= wesnoth.current.turn) then
hunter_vars.hunting_status = nil
hunter_vars.resting_until = nil
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
if cfg.show_messages then
W.message { speaker = unit.id, message = 'I am done resting. It is time to go hunting again next turn.' }
end
@ -196,7 +204,8 @@ function ca_hunter:execution(ai, cfg)
end
-- In principle we should never get here, but just in case: reset variable, so that unit goes hunting on next turn
unit.variables.hunting_status = nil
hunter_vars.hunting_status = nil
MAIUV.set_mai_unit_variables(unit, cfg.ai_id, hunter_vars)
end
return ca_hunter

View file

@ -1,6 +1,7 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local LS = wesnoth.require "lua/location_set.lua"
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
local ca_messenger_escort_move = {}
@ -58,7 +59,8 @@ function ca_messenger_escort_move:execution(ai, cfg)
local max_messenger_rating = -9e99
for _,m in ipairs(messengers) do
local messenger_rating = 1. / (H.distance_between(x, y, m.x, m.y) + 2.)
messenger_rating = messenger_rating * 10. * (1. + m.variables.wp_rating * 2.)
local wp_rating = MAIUV.get_mai_unit_variables(m, cfg.ai_id, "wp_rating")
messenger_rating = messenger_rating * 10. * (1. + wp_rating * 2.)
if (messenger_rating > max_messenger_rating) then
max_messenger_rating = messenger_rating

View file

@ -1,5 +1,6 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
return function(cfg)
-- Calculate next waypoint and rating for all messengers
@ -26,7 +27,7 @@ return function(cfg)
for i, m in ipairs(messengers) do
-- To avoid code duplication and ensure consistency, we store some pieces of
-- information in the messenger units, even though it could be calculated each time it is needed
local wp_i = m.variables.wp_i or 1
local wp_i = MAIUV.get_mai_unit_variables(m, cfg.ai_id, "wp_i") or 1
local wp_x, wp_y = waypoint_x[wp_i], waypoint_y[wp_i]
-- If this messenger is within 3 hexes of the next waypoint, we go on to the one after that
@ -34,8 +35,6 @@ return function(cfg)
local dist_wp = H.distance_between(m.x, m.y, wp_x, wp_y)
if (dist_wp <= 3) and (wp_i < #waypoint_x) then wp_i = wp_i + 1 end
m.variables.wp_i, m.variables.wp_x, m.variables.wp_y = wp_i, wp_x, wp_y
-- Also store the rating for each messenger
-- For now, this is simply a "forward rating"
local rating = wp_i - dist_wp / 1000.
@ -47,7 +46,7 @@ return function(cfg)
rating = #waypoint_x - rating
end
m.variables.wp_rating = rating
MAIUV.set_mai_unit_variables(m, cfg.ai_id, { wp_i = wp_i, wp_x = wp_x, wp_y = wp_y, wp_rating = rating })
-- Find the messenger with the highest rating that has MP left
if (m.moves > 0) and (rating > max_rating) then

View file

@ -1,5 +1,6 @@
local H = wesnoth.require "lua/helper.lua"
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
local ca_patrol = {}
@ -28,10 +29,12 @@ function ca_patrol:execution(ai, cfg)
local n_wp = #cfg.waypoint_x -- just for convenience
local patrol_vars = MAIUV.get_mai_unit_variables(patrol, cfg.ai_id)
-- Set up waypoints, taking into account whether 'reverse' is set
-- This works even the first time, when patrol.variables.patrol_reverse is not set yet
-- This works even the first time, when patrol_vars.patrol_reverse is not set yet
local waypoints = {}
if patrol.variables.patrol_reverse then
if patrol_vars.patrol_reverse then
for i = 1,n_wp do
waypoints[i] = { tonumber(cfg.waypoint_x[n_wp-i+1]), tonumber(cfg.waypoint_y[n_wp-i+1]) }
end
@ -43,10 +46,11 @@ function ca_patrol:execution(ai, cfg)
-- If not set, set next location (first move)
-- This needs to be in WML format, so that it persists over save/load cycles
if (not patrol.variables.patrol_x) then
patrol.variables.patrol_x = waypoints[1][1]
patrol.variables.patrol_y = waypoints[1][2]
patrol.variables.patrol_reverse = false
if (not patrol_vars.patrol_x) then
patrol_vars.patrol_x = waypoints[1][1]
patrol_vars.patrol_y = waypoints[1][2]
patrol_vars.patrol_reverse = false
MAIUV.set_mai_unit_variables(patrol, cfg.ai_id, patrol_vars)
end
while patrol.moves > 0 do
@ -61,8 +65,8 @@ function ca_patrol:execution(ai, cfg)
-- Also check whether we're next to any unit (enemy or ally) which is on the next waypoint
local unit_on_wp = wesnoth.get_units {
x = patrol.variables.patrol_x,
y = patrol.variables.patrol_y,
x = patrol_vars.patrol_x,
y = patrol_vars.patrol_y,
{ "filter_adjacent", { id = patrol.id } }
}[1]
@ -75,28 +79,32 @@ function ca_patrol:execution(ai, cfg)
-- Move him to the first one (or reverse route), if he's on the last waypoint
-- Unless cfg.one_time_only is set
if cfg.one_time_only then
patrol.variables.patrol_x = waypoints[n_wp][1]
patrol.variables.patrol_y = waypoints[n_wp][2]
patrol_vars.patrol_x = waypoints[n_wp][1]
patrol_vars.patrol_y = waypoints[n_wp][2]
MAIUV.set_mai_unit_variables(patrol, cfg.ai_id, patrol_vars)
else
-- Go back to first WP or reverse direction
if cfg.out_and_back then
patrol.variables.patrol_x = waypoints[n_wp-1][1]
patrol.variables.patrol_y = waypoints[n_wp-1][2]
patrol_vars.patrol_x = waypoints[n_wp-1][1]
patrol_vars.patrol_y = waypoints[n_wp-1][2]
-- We also need to reverse the waypoints right here, as this might not be the end of the move
patrol.variables.patrol_reverse = not patrol.variables.patrol_reverse
patrol_vars.patrol_reverse = not patrol_vars.patrol_reverse
MAIUV.set_mai_unit_variables(patrol, cfg.ai_id, patrol_vars)
local tmp_wp = {}
for i,wp in ipairs(waypoints) do tmp_wp[n_wp-i+1] = wp end
waypoints = tmp_wp
else
patrol.variables.patrol_x = waypoints[1][1]
patrol.variables.patrol_y = waypoints[1][2]
patrol_vars.patrol_x = waypoints[1][1]
patrol_vars.patrol_y = waypoints[1][2]
MAIUV.set_mai_unit_variables(patrol, cfg.ai_id, patrol_vars)
end
end
else
-- ... else move him on the next waypoint
patrol.variables.patrol_x = waypoints[i+1][1]
patrol.variables.patrol_y = waypoints[i+1][2]
patrol_vars.patrol_x = waypoints[i+1][1]
patrol_vars.patrol_y = waypoints[i+1][2]
MAIUV.set_mai_unit_variables(patrol, cfg.ai_id, patrol_vars)
end
end
end
@ -107,7 +115,7 @@ function ca_patrol:execution(ai, cfg)
then
AH.checked_stopunit_moves(ai, patrol)
else -- otherwise move toward next WP
local x, y = wesnoth.find_vacant_tile(patrol.variables.patrol_x, patrol.variables.patrol_y, patrol)
local x, y = wesnoth.find_vacant_tile(patrol_vars.patrol_x, patrol_vars.patrol_y, patrol)
local nh = AH.next_hop(patrol, x, y)
if nh and ((nh[1] ~= patrol.x) or (nh[2] ~= patrol.y)) then
AH.checked_move(ai, patrol, nh[1], nh[2])

View file

@ -1,6 +1,7 @@
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
local LS = wesnoth.require "lua/location_set.lua"
local WMPF = wesnoth.require "ai/micro_ais/cas/ca_wolves_multipacks_functions.lua"
@ -8,7 +9,6 @@ local ca_wolves_multipacks_attack = {}
function ca_wolves_multipacks_attack:evaluation(ai, cfg)
local unit_type = cfg.type or "Wolf"
-- If wolves have attacks left, call this CA
-- It will generally be disabled by being black-listed, so as to avoid
-- having to do the full attack evaluation for every single move
@ -26,7 +26,6 @@ function ca_wolves_multipacks_attack:execution(ai, cfg)
-- and I want all wolves in a pack to move first, before going on to the next pack
-- which makes this slightly more complicated than it would be otherwise
for pack_number,pack in pairs(packs) do
local keep_attacking_this_pack = true -- whether there might be attacks left
local pack_attacked = false -- whether an attack by the pack has happened
@ -97,10 +96,13 @@ function ca_wolves_multipacks_attack:execution(ai, cfg)
-- the same target for all wolves of the pack)
for x, y in H.adjacent_tiles(target.x, target.y) do
local adj_unit = wesnoth.get_unit(x, y)
if adj_unit and (adj_unit.variables.pack == pack_number)
and (adj_unit.side == wesnoth.current.side) and (adj_unit.attacks_left == 0)
then
rating = rating + 10 -- very strongly favors this target
if adj_unit then
local pack = MAIUV.get_mai_unit_variables(adj_unit, cfg.ai_id, "pack")
if (pack == pack_number) and (adj_unit.side == wesnoth.current.side)
and (adj_unit.attacks_left == 0)
then
rating = rating + 10 -- very strongly favors this target
end
end
end

View file

@ -1,5 +1,6 @@
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
local wolves_multipacks_functions = {}
@ -25,9 +26,10 @@ function wolves_multipacks_functions.assign_packs(cfg)
local packs = {}
-- Find wolves that already have a pack number assigned
for i,w in ipairs(wolves) do
if w.variables.pack then
if (not packs[w.variables.pack]) then packs[w.variables.pack] = {} end
table.insert(packs[w.variables.pack], { x = w.x, y = w.y, id = w.id })
local pack = MAIUV.get_mai_unit_variables(w, cfg.ai_id, "pack")
if pack then
if (not packs[pack]) then packs[pack] = {} end
table.insert(packs[pack], { x = w.x, y = w.y, id = w.id })
end
end
@ -37,7 +39,7 @@ function wolves_multipacks_functions.assign_packs(cfg)
--print(' have pack:', k, ' #members:', #p)
if (#p == 1) then
local wolf = wesnoth.get_unit(p[1].x, p[1].y)
wolf.variables.pack, wolf.variables.goal_x, wolf.variables.goal_y = nil, nil, nil
MAIUV.delete_mai_unit_variables(wolf, cfg.ai_id)
packs[k] = nil
end
end
@ -47,10 +49,11 @@ function wolves_multipacks_functions.assign_packs(cfg)
-- Wolves that are not in a pack (new ones or those removed above)
local nopack_wolves = {}
for i,w in ipairs(wolves) do
if (not w.variables.pack) then
local pack = MAIUV.get_mai_unit_variables(w, cfg.ai_id, "pack")
if (not pack) then
table.insert(nopack_wolves, w)
-- Also erase any goal one of these might have
w.variables.pack, w.variables.goal_x, w.variables.goal_y = nil, nil, nil
MAIUV.delete_mai_unit_variables(w, cfg.ai_id)
end
end
--print('#nopack_wolves:', #nopack_wolves)
@ -70,7 +73,7 @@ function wolves_multipacks_functions.assign_packs(cfg)
end
if (min_dist < 9e99) then
table.insert(packs[k], { x = best_wolf.x, y = best_wolf.y, id = best_wolf.id })
best_wolf.variables.pack = k
MAIUV.set_mai_unit_variables(best_wolf, cfg.ai_id, { pack = k })
table.remove(nopack_wolves, best_ind)
end
end
@ -93,7 +96,7 @@ function wolves_multipacks_functions.assign_packs(cfg)
packs[new_pack] = {}
for i,w in ipairs(nopack_wolves) do
table.insert(packs[new_pack], { x = w.x, y = w.y, id = w.id })
w.variables.pack = new_pack
MAIUV.set_mai_unit_variables(w, cfg.ai_id, { pack = new_pack })
end
break
end
@ -121,7 +124,7 @@ function wolves_multipacks_functions.assign_packs(cfg)
-- Need to count down for table.remove to work correctly
for i = pack_size,1,-1 do
table.insert(packs[new_pack], { x = best_wolves[i].x, y = best_wolves[i].y, id = best_wolves[i].id })
best_wolves[i].variables.pack = new_pack
MAIUV.set_mai_unit_variables(best_wolves[i], cfg.ai_id, { pack = new_pack })
end
end
--print('After grouping remaining single wolves')

View file

@ -1,6 +1,7 @@
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local AH = wesnoth.require "ai/lua/ai_helper.lua"
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
local LS = wesnoth.require "lua/location_set.lua"
local WMPF = wesnoth.require "ai/micro_ais/cas/ca_wolves_multipacks_functions.lua"
@ -27,9 +28,11 @@ function ca_wolves_multipacks_wander:execution(ai, cfg)
local wolf = wesnoth.get_unit(loc.x, loc.y)
--print(k, i, wolf.id)
table.insert(wolves, wolf)
-- If any of the wolves in the pack has a goal set, we use that one
if wolf.variables.goal_x then
goal = { wolf.variables.goal_x, wolf.variables.goal_y }
local wolf_goal = MAIUV.get_mai_unit_variables(wolf, cfg.ai_id)
if wolf_goal.goal_x then
goal = { wolf_goal.goal_x, wolf_goal.goal_y }
end
end
@ -61,7 +64,7 @@ function ca_wolves_multipacks_wander:execution(ai, cfg)
-- This goal is saved with every wolf of the pack
for i,w in ipairs(wolves) do
w.variables.goal_x, w.variables.goal_y = goal[1], goal[2]
MAIUV.insert_mai_unit_variables(w, cfg.ai_id, { goal_x = goal[1], goal_y = goal[2] })
end
-- The pack wanders with only 2 considerations

View file

@ -1,44 +1,53 @@
local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local AH = wesnoth.require("ai/lua/ai_helper.lua")
local MAIUV = wesnoth.dofile "ai/micro_ais/micro_ai_unit_variables.lua"
local micro_ai_helper = {}
function micro_ai_helper.add_CAs(side, CA_parms, CA_cfg)
-- Add the candidate actions defined in 'CA_parms' to the AI of 'side'
-- CA_parms is an array of tables, one for each CA to be added (CA setup parameters)
-- CA_cfg is a table with the parameters passed to the eval/exec functions
-- Add the candidate actions defined in @CA_parms to the AI of @side
-- @CA_parms is an array of tables, one for each CA to be added (CA setup parameters)
-- and also contains one key: ai_id
-- @CA_cfg is a table with the parameters passed to the eval/exec functions
--
-- Required keys for CA_parms:
-- - ca_id: is used for CA id/name and the eval/exec function names
-- Required keys for each table of @CA_parms:
-- - ca_id: is used for CA id/name
-- - location: the path+file name for the external CA file
-- - score: the evaluation score
for i,parms in ipairs(CA_parms) do
-- Make sure the id/name of each CA are unique.
-- We do this by seeing if a CA by that name exists already.
-- If not, we use the passed id in parms.ca_id
-- If yes, we add a number to the end of parms.ca_id until we find an id that does not exist yet
local ca_id, id_found = parms.ca_id, true
-- We need to make sure that the id/name of each CA are unique.
-- We do this by checking if CAs starting with ai_id exist already
-- If yes, we add numbers to the end of ai_id until we find an id that does not exist yet
local n = 1
while id_found do -- This is really just a precaution
id_found = false
local ai_id, id_found = CA_parms.ai_id, true
for ai_tag in H.child_range(wesnoth.sides[side].__cfg, 'ai') do
for stage in H.child_range(ai_tag, 'stage') do
for ca in H.child_range(stage, 'candidate_action') do
if (ca.name == ca_id) then id_found = true end
--print('---> found CA:', ca.name, id_found)
local n = 1
while id_found do -- This is really just a precaution
id_found = false
for ai_tag in H.child_range(wesnoth.sides[side].__cfg, 'ai') do
for stage in H.child_range(ai_tag, 'stage') do
for ca in H.child_range(stage, 'candidate_action') do
if string.find(ca.name, ai_id .. '_') then
id_found = true
--print('---> found CA:', ca.name, ai_id, id_found, string.find(ca.name, ai_id))
break
end
end
end
if (id_found) then ca_id = parms.ca_id .. n end
n = n+1
end
-- Always pass the ca_id and ca_score to the eval/exec functions
CA_cfg.ca_id = ca_id
if (id_found) then ai_id = CA_parms.ai_id .. n end
n = n + 1
end
-- Now add the CAs
for i,parms in ipairs(CA_parms) do
local ca_id = ai_id .. '_' .. parms.ca_id
-- Always pass the ai_id and ca_score to the eval/exec functions
CA_cfg.ai_id = ai_id
CA_cfg.ca_score = parms.score
local CA = {
@ -63,27 +72,33 @@ function micro_ai_helper.add_CAs(side, CA_parms, CA_cfg)
end
function micro_ai_helper.delete_CAs(side, CA_parms)
-- Delete the candidate actions defined in 'CA_parms' from the AI of 'side'
-- CA_parms is an array of tables, one for each CA to be removed
-- Delete the candidate actions defined in @CA_parms from the AI of @side
-- @CA_parms is an array of tables, one for each CA to be removed
-- We can simply pass the one used for add_CAs(), although only the
-- CA_parms.ca_id field is needed
for i,parms in ipairs(CA_parms) do
local ca_id = parms.ca_id
local ca_id = CA_parms.ai_id .. '_' .. parms.ca_id
W.modify_ai {
side = side,
action = "try_delete",
path = "stage[main_loop].candidate_action[" .. ca_id .. "]"
}
-- Also need to delete variable stored in all units of the side, so that later MAIs can use these units
local units = wesnoth.get_units { side = side }
for i,u in ipairs(units) do
MAIUV.delete_mai_unit_variables(u, CA_parms.ai_id)
end
end
end
function micro_ai_helper.add_aspects(side, aspect_parms)
-- Add the aspects defined in 'aspect_parms' to the AI of 'side'
-- aspect_parms is an array of tables, one for each aspect to be added
-- Add the aspects defined in @aspect_parms to the AI of @side
-- @aspect_parms is an array of tables, one for each aspect to be added
--
-- Required keys for aspect_parms:
-- Required keys for @aspect_parms:
-- - aspect: the aspect name (e.g. 'attacks' or 'aggression')
-- - facet: A table describing the facet to be added
--
@ -111,8 +126,8 @@ function micro_ai_helper.add_aspects(side, aspect_parms)
end
function micro_ai_helper.delete_aspects(side, aspect_parms)
-- Delete the aspects defined in 'aspect_parms' from the AI of 'side'
-- aspect_parms is an array of tables, one for each CA to be removed
-- Delete the aspects defined in @aspect_parms from the AI of @side
-- @aspect_parms is an array of tables, one for each aspect to be removed
-- We can simply pass the one used for add_aspects(), although only the
-- aspect_parms.aspect_id field is needed
@ -126,15 +141,12 @@ function micro_ai_helper.delete_aspects(side, aspect_parms)
end
function micro_ai_helper.micro_ai_setup(cfg, CA_parms, required_keys, optional_keys)
-- If cfg.ca_id is set, it gets added to the ca_id= key of all CAs
-- If cfg.ca_id is set, it gets used as the ai_id= key
-- This allows for selective removal of CAs
if cfg.ca_id then
for i,parms in ipairs(CA_parms) do
-- Need to save eval_id first though
parms.eval_id = parms.ca_id
parms.ca_id = parms.ca_id .. '_' .. cfg.ca_id
end
end
-- Note: the ca_id key of the [micro_ai] tag should really be renamed to ai_id,
-- but that would mean breaking backward compatibility, so we'll just deal with it internally instead
CA_parms.ai_id = cfg.ca_id or CA_parms.ai_id
-- If action=delete, we do that and are done
if (cfg.action == 'delete') then

View file

@ -0,0 +1,89 @@
-- This is a set of functions provides a consistent way of storing Micro AI
-- variables in units. They need to be stored inside a [micro_ai] tag in a
-- unit's [variables] tag together with an ai_id= key, so that they can be
-- removed when the Micro AI gets deleted. Otherwise subsequent Micro AIs used
-- in the same scenario (or using the same units in later scenarios) might work
-- incorrectly or not at all.
-- Note that with this method, there can only ever be one of these tags for each
-- ai_ca in each unit (but of course several when there are several Micro AIs
-- with different ai_CA values affecting the same unit)
-- For the time being, we only allow key=value style variables.
local H = wesnoth.require "lua/helper.lua"
local micro_ai_unit_variables = {}
function micro_ai_unit_variables.modify_mai_unit_variables(unit, ai_id, action, vars_table)
-- Modify [unit][variables][micro_ai] tags
-- @ai_id (string): the id of the Micro AI
-- @action (string): "delete", "set" or "insert"
-- @vars_table: table of key=value pairs with the variables to be set or inserted (not needed for @action="delete")
local unit_cfg = unit.__cfg
local variables, ind = H.get_child(unit_cfg, "variables")
-- Always delete the respective [variables][micro_ai] tag, if it exists
local existing_table
for i,mai in ipairs(variables, "micro_ai") do
if (mai[1] == "micro_ai") and (mai[2].ai_id == ai_id) then
existing_table = mai[2]
table.remove(variables, i)
break
end
end
-- Then replace it, if the "set" action is selected
-- or add the new keys to it, overwriting old ones with the same name, if action == "insert"
if (action == "set") or (action == "insert") then
local tag = { "micro_ai" }
if (not existing_table) or (action == "set") then
tag[2] = vars_table
tag[2].ai_id = ai_id
else
for k,v in pairs(vars_table) do existing_table[k] = v end
tag[2] = existing_table
end
table.insert(unit_cfg[ind][2], tag)
end
-- All of this so far was only on the table dump -> apply to unit
wesnoth.put_unit(unit_cfg)
end
function micro_ai_unit_variables.delete_mai_unit_variables(unit, ai_id)
micro_ai_unit_variables.modify_mai_unit_variables(unit, ai_id, "delete")
end
function micro_ai_unit_variables.insert_mai_unit_variables(unit, ai_id, vars_table)
micro_ai_unit_variables.modify_mai_unit_variables(unit, ai_id, "insert", vars_table)
end
function micro_ai_unit_variables.set_mai_unit_variables(unit, ai_id, vars_table)
micro_ai_unit_variables.modify_mai_unit_variables(unit, ai_id, "set", vars_table)
end
function micro_ai_unit_variables.get_mai_unit_variables(unit, ai_id, key)
-- Get the content of [unit][variables][micro_ai] tag for the given @ai_id
-- Return value:
-- - If tag is found: value of key if @key parameter is given, otherwise
-- table of key=value pairs (including the ai_id key)
-- - If no such tag is found: nil (if @key is set), otherwise empty table
for mai in H.child_range(unit.variables.__cfg, "micro_ai") do
if (mai.ai_id == ai_id) then
if key then
return mai[key]
else
return mai
end
end
end
-- If we got here, no corresponding tag was found
-- Return empty table; or nil if @key was set
if (not key) then return {} end
end
return micro_ai_unit_variables

View file

@ -3,7 +3,8 @@ local W = H.set_wml_action_metatable {}
local MAIH = wesnoth.require("ai/micro_ais/micro_ai_helper.lua")
function wesnoth.wml_actions.micro_ai(cfg)
-- Set up the [micro_ai] tag functionality for each Micro AI
local CA_path = 'ai/micro_ais/cas/'
cfg = cfg.__parsed
-- Add translation for old-syntax animal MAIs to new syntax plus deprecation message
@ -57,21 +58,21 @@ function wesnoth.wml_actions.micro_ai(cfg)
-- Set up the configuration tables for the different Micro AIs
local required_keys, optional_keys, CA_parms = {}, {}, {}
local CA_path = 'ai/micro_ais/cas/'
--------- Healer Support Micro AI ------------------------------------
if (cfg.ai_type == 'healer_support') then
optional_keys = { "aggression", "injured_units_only", "max_threats", "filter", "filter_second" }
-- Scores for this AI need to be hard-coded, it does not work otherwise
CA_parms = {
{ ca_id = 'mai_healer_initialize', location = CA_path .. 'ca_healer_initialize.lua', score = 999990 },
{ ca_id = 'mai_healer_move', location = CA_path .. 'ca_healer_move.lua', score = 105000 },
ai_id = 'mai_healer',
{ ca_id = 'initialize', location = CA_path .. 'ca_healer_initialize.lua', score = 999990 },
{ ca_id = 'move', location = CA_path .. 'ca_healer_move.lua', score = 105000 },
}
-- The healers_can_attack CA is only added to the table if aggression ~= 0
-- But: make sure we always try removal
if (cfg.action == 'delete') or (tonumber(cfg.aggression) ~= 0) then
table.insert(CA_parms, { ca_id = 'mai_healer_may_attack', location = CA_path .. 'ca_healer_may_attack.lua', score = 99990 })
table.insert(CA_parms, { ca_id = 'may_attack', location = CA_path .. 'ca_healer_may_attack.lua', score = 99990 })
end
--------- Bottleneck Defense Micro AI -----------------------------------
@ -80,38 +81,44 @@ function wesnoth.wml_actions.micro_ai(cfg)
optional_keys = { "healer_x", "healer_y", "leadership_x", "leadership_y", "active_side_leader" }
local score = cfg.ca_score or 300000
CA_parms = {
{ ca_id = 'mai_bottleneck_move', location = CA_path .. 'ca_bottleneck_move.lua', score = score },
{ ca_id = 'mai_bottleneck_attack', location = CA_path .. 'ca_bottleneck_attack.lua', score = score - 1 }
ai_id = 'mai_bottleneck',
{ ca_id = 'move', location = CA_path .. 'ca_bottleneck_move.lua', score = score },
{ ca_id = 'attack', location = CA_path .. 'ca_bottleneck_attack.lua', score = score - 1 }
}
--------- Messenger Escort Micro AI ------------------------------------
elseif (cfg.ai_type == 'messenger_escort') then
if (not cfg.id) and (not H.get_child(cfg, "filter")) then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Messenger [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "waypoint_x", "waypoint_y" }
optional_keys = { "id", "enemy_death_chance", "filter", "filter_second", "invert_order", "messenger_death_chance" }
local score = cfg.ca_score or 300000
CA_parms = {
{ ca_id = 'mai_messenger_attack', location = CA_path .. 'ca_messenger_attack.lua', score = score },
{ ca_id = 'mai_messenger_move', location = CA_path .. 'ca_messenger_move.lua', score = score - 1 },
{ ca_id = 'mai_messenger_escort_move', location = CA_path .. 'ca_messenger_escort_move.lua', score = score - 2 }
ai_id = 'mai_messenger',
{ ca_id = 'attack', location = CA_path .. 'ca_messenger_attack.lua', score = score },
{ ca_id = 'move', location = CA_path .. 'ca_messenger_move.lua', score = score - 1 },
{ ca_id = 'escort_move', location = CA_path .. 'ca_messenger_escort_move.lua', score = score - 2 }
}
--------- Lurkers Micro AI ------------------------------------
elseif (cfg.ai_type == 'lurkers') then
required_keys = { "filter", "filter_location" }
optional_keys = { "stationary", "filter_location_wander" }
CA_parms = { { ca_id = 'mai_lurkers', location = CA_path .. 'ca_lurkers.lua', score = cfg.ca_score or 300000 } }
CA_parms = {
ai_id = 'mai_lurkers',
{ ca_id = 'move', location = CA_path .. 'ca_lurkers.lua', score = cfg.ca_score or 300000 }
}
--------- Protect Unit Micro AI ------------------------------------
elseif (cfg.ai_type == 'protect_unit') then
required_keys = { "id", "goal_x", "goal_y" }
-- Scores for this AI need to be hard-coded, it does not work otherwise
CA_parms = {
{ ca_id = 'mai_protect_unit_finish', location = CA_path .. 'ca_protect_unit_finish.lua', score = 300000 },
{ ca_id = 'mai_protect_unit_attack', location = CA_path .. 'ca_protect_unit_attack.lua', score = 95000 },
{ ca_id = 'mai_protect_unit_move', location = CA_path .. 'ca_protect_unit_move.lua', score = 94999 }
ai_id = 'mai_protect_unit',
{ ca_id = 'finish', location = CA_path .. 'ca_protect_unit_finish.lua', score = 300000 },
{ ca_id = 'attack', location = CA_path .. 'ca_protect_unit_attack.lua', score = 95000 },
{ ca_id = 'move', location = CA_path .. 'ca_protect_unit_move.lua', score = 94999 }
}
-- [unit] tags need to be dealt with separately
@ -190,50 +197,66 @@ function wesnoth.wml_actions.micro_ai(cfg)
--------- Micro AI Guardian -----------------------------------
elseif (cfg.ai_type == 'stationed_guardian') then
if (not cfg.id) and (not H.get_child(cfg, "filter")) then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Stationed Guardian [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "distance", "station_x", "station_y", "guard_x", "guard_y" }
optional_keys = { "id", "filter" }
CA_parms = { { ca_id = 'mai_stationed_guardian', location = CA_path .. 'ca_stationed_guardian.lua', score = cfg.ca_score or 300000 } }
CA_parms = {
ai_id = 'mai_stationed_guardian',
{ ca_id = 'move', location = CA_path .. 'ca_stationed_guardian.lua', score = cfg.ca_score or 300000 }
}
elseif (cfg.ai_type == 'zone_guardian') then
if (not cfg.id) and (not H.get_child(cfg, "filter")) then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Zone Guardian [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "filter_location" }
optional_keys = { "id", "filter", "filter_location_enemy", "station_x", "station_y" }
CA_parms = { { ca_id = 'mai_zone_guardian', location = CA_path .. 'ca_zone_guardian.lua', score = cfg.ca_score or 300000 } }
CA_parms = {
ai_id = 'mai_zone_guardian',
{ ca_id = 'move', location = CA_path .. 'ca_zone_guardian.lua', score = cfg.ca_score or 300000 }
}
elseif (cfg.ai_type == 'return_guardian') then
if (not cfg.id) and (not H.get_child(cfg, "filter")) then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Return Guardian [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "return_x", "return_y" }
optional_keys = { "id", "filter" }
CA_parms = { { ca_id = 'mai_return_guardian', location = CA_path .. 'ca_return_guardian.lua', score = cfg.ca_score or 100010 } }
CA_parms = {
ai_id = 'mai_return_guardian',
{ ca_id = 'move', location = CA_path .. 'ca_return_guardian.lua', score = cfg.ca_score or 100010 }
}
elseif (cfg.ai_type == 'coward') then
if (not cfg.id) and (not H.get_child(cfg, "filter")) then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Coward [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "distance" }
optional_keys = { "id", "filter", "filter_second", "seek_x", "seek_y","avoid_x","avoid_y" }
CA_parms = { { ca_id = 'mai_coward', location = CA_path .. 'ca_coward.lua', score = cfg.ca_score or 300000 } }
CA_parms = {
ai_id = 'mai_coward',
{ ca_id = 'move', location = CA_path .. 'ca_coward.lua', score = cfg.ca_score or 300000 }
}
--------- Micro AI Animals ------------------------------------
elseif (cfg.ai_type == 'big_animals') then
required_keys = { "filter"}
optional_keys = { "avoid_unit", "filter_location", "filter_location_wander" }
CA_parms = { { ca_id = "mai_big_animals", location = CA_path .. 'ca_big_animals.lua', score = cfg.ca_score or 300000 } }
CA_parms = {
ai_id = 'mai_big_animals',
{ ca_id = "move", location = CA_path .. 'ca_big_animals.lua', score = cfg.ca_score or 300000 }
}
elseif (cfg.ai_type == 'wolves') then
required_keys = { "filter", "filter_second" }
optional_keys = { "attack_only_prey", "avoid_type" }
local score = cfg.ca_score or 90000
CA_parms = {
{ ca_id = "mai_wolves_move", location = CA_path .. 'ca_wolves_move.lua', score = score },
{ ca_id = "mai_wolves_wander", location = CA_path .. 'ca_wolves_wander.lua', score = score - 1 }
ai_id = 'mai_wolves',
{ ca_id = "move", location = CA_path .. 'ca_wolves_move.lua', score = score },
{ ca_id = "wander", location = CA_path .. 'ca_wolves_wander.lua', score = score - 1 }
}
if cfg.attack_only_prey then
@ -283,12 +306,13 @@ function wesnoth.wml_actions.micro_ai(cfg)
optional_keys = { "attention_distance", "attack_distance" }
local score = cfg.ca_score or 300000
CA_parms = {
{ ca_id = "mai_herding_attack_close_enemy", location = CA_path .. 'ca_herding_attack_close_enemy.lua', score = score },
{ ca_id = "mai_herding_sheep_runs_enemy", location = CA_path .. 'ca_herding_sheep_runs_enemy.lua', score = score - 1 },
{ ca_id = "mai_herding_sheep_runs_dog", location = CA_path .. 'ca_herding_sheep_runs_dog.lua', score = score - 2 },
{ ca_id = "mai_herding_herd_sheep", location = CA_path .. 'ca_herding_herd_sheep.lua', score = score - 3 },
{ ca_id = "mai_herding_sheep_move", location = CA_path .. 'ca_herding_sheep_move.lua', score = score - 4 },
{ ca_id = "mai_herding_dog_move", location = CA_path .. 'ca_herding_dog_move.lua', score = score - 5 }
ai_id = 'mai_herding',
{ ca_id = "attack_close_enemy", location = CA_path .. 'ca_herding_attack_close_enemy.lua', score = score },
{ ca_id = "sheep_runs_enemy", location = CA_path .. 'ca_herding_sheep_runs_enemy.lua', score = score - 1 },
{ ca_id = "sheep_runs_dog", location = CA_path .. 'ca_herding_sheep_runs_dog.lua', score = score - 2 },
{ ca_id = "herd_sheep", location = CA_path .. 'ca_herding_herd_sheep.lua', score = score - 3 },
{ ca_id = "sheep_move", location = CA_path .. 'ca_herding_sheep_move.lua', score = score - 4 },
{ ca_id = "dog_move", location = CA_path .. 'ca_herding_dog_move.lua', score = score - 5 }
}
elseif (cfg.ai_type == 'forest_animals') then
@ -297,66 +321,83 @@ function wesnoth.wml_actions.micro_ai(cfg)
}
local score = cfg.ca_score or 300000
CA_parms = {
{ ca_id = "mai_forest_animals_new_rabbit", location = CA_path .. 'ca_forest_animals_new_rabbit.lua', score = score },
{ ca_id = "mai_forest_animals_tusker_attack", location = CA_path .. 'ca_forest_animals_tusker_attack.lua', score = score - 1 },
{ ca_id = "mai_forest_animals_move", location = CA_path .. 'ca_forest_animals_move.lua', score = score - 2 },
{ ca_id = "mai_forest_animals_tusklet_move", location = CA_path .. 'ca_forest_animals_tusklet_move.lua', score = score - 3 }
ai_id = 'mai_forest_animals',
{ ca_id = "new_rabbit", location = CA_path .. 'ca_forest_animals_new_rabbit.lua', score = score },
{ ca_id = "tusker_attack", location = CA_path .. 'ca_forest_animals_tusker_attack.lua', score = score - 1 },
{ ca_id = "move", location = CA_path .. 'ca_forest_animals_move.lua', score = score - 2 },
{ ca_id = "tusklet_move", location = CA_path .. 'ca_forest_animals_tusklet_move.lua', score = score - 3 }
}
elseif (cfg.ai_type == 'swarm') then
optional_keys = { "scatter_distance", "vision_distance", "enemy_distance" }
local score = cfg.ca_score or 300000
CA_parms = {
{ ca_id = "mai_swarm_scatter", location = CA_path .. 'ca_swarm_scatter.lua', score = score },
{ ca_id = "mai_swarm_move", location = CA_path .. 'ca_swarm_move.lua', score = score - 1 }
ai_id = 'mai_swarm',
{ ca_id = "scatter", location = CA_path .. 'ca_swarm_scatter.lua', score = score },
{ ca_id = "move", location = CA_path .. 'ca_swarm_move.lua', score = score - 1 }
}
elseif (cfg.ai_type == 'wolves_multipacks') then
optional_keys = { "type", "pack_size", "show_pack_number" }
local score = cfg.ca_score or 300000
CA_parms = {
{ ca_id = "mai_wolves_multipacks_attack", location = CA_path .. 'ca_wolves_multipacks_attack.lua', score = score },
{ ca_id = "mai_wolves_multipacks_wander", location = CA_path .. 'ca_wolves_multipacks_wander.lua', score = score - 1 }
ai_id = 'mai_wolves_multipacks',
{ ca_id = "attack", location = CA_path .. 'ca_wolves_multipacks_attack.lua', score = score },
{ ca_id = "wander", location = CA_path .. 'ca_wolves_multipacks_wander.lua', score = score - 1 }
}
elseif (cfg.ai_type == 'hunter') then
if (not cfg.id) and (not H.get_child(cfg, "filter")) then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Hunter [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "home_x", "home_y" }
optional_keys = { "id", "filter", "filter_location", "rest_turns", "show_messages" }
CA_parms = { { ca_id = "mai_hunter", location = CA_path .. 'ca_hunter.lua', score = cfg.ca_score or 300000 } }
CA_parms = {
ai_id = 'mai_hunter',
{ ca_id = "move", location = CA_path .. 'ca_hunter.lua', score = cfg.ca_score or 300000 }
}
--------- Patrol Micro AI ------------------------------------
elseif (cfg.ai_type == 'patrol') then
if (not cfg.id) and (not H.get_child(cfg, "filter")) then
if (cfg.action ~= 'delete') and (not cfg.id) and (not H.get_child(cfg, "filter")) then
H.wml_error("Patrol [micro_ai] tag requires either id= key or [filter] tag")
end
required_keys = { "waypoint_x", "waypoint_y" }
optional_keys = { "id", "filter", "attack", "one_time_only", "out_and_back" }
CA_parms = { { ca_id = "mai_patrol", location = CA_path .. 'ca_patrol.lua', score = cfg.ca_score or 300000 } }
CA_parms = {
ai_id = 'mai_patrol',
{ ca_id = "move", location = CA_path .. 'ca_patrol.lua', score = cfg.ca_score or 300000 }
}
--------- Recruiting Micro AI ------------------------------------
elseif (cfg.ai_type == 'recruit_rushers') or (cfg.ai_type == 'recruit_random')then
if (cfg.ai_type == 'recruit_rushers') then
optional_keys = { "randomness" }
CA_parms = { { ca_id = "mai_rusher_recruit", location = CA_path .. 'ca_recruit_rushers.lua', score = cfg.ca_score or 180000 } }
CA_parms = {
ai_id = 'mai_rusher_recruit',
{ ca_id = "move", location = CA_path .. 'ca_recruit_rushers.lua', score = cfg.ca_score or 180000 }
}
else
optional_keys = { "skip_low_gold_recruiting", "type", "prob" }
CA_parms = { { ca_id = "mai_random_recruit", location = CA_path .. 'ca_recruit_random.lua', score = cfg.ca_score or 180000 } }
CA_parms = {
ai_id = 'mai_random_recruit',
{ ca_id = "move", location = CA_path .. 'ca_recruit_random.lua', score = cfg.ca_score or 180000 }
}
-- The 'probability' tags need to be handled separately here
cfg.type, cfg.prob = {}, {}
for p in H.child_range(cfg, "probability") do
if (not p.type) then
H.wml_error("Random Recruiting Micro AI [probability] tag is missing required type= key")
if (cfg.action ~= 'delete') then
-- The 'probability' tags need to be handled separately here
cfg.type, cfg.prob = {}, {}
for p in H.child_range(cfg, "probability") do
if (not p.type) then
H.wml_error("Random Recruiting Micro AI [probability] tag is missing required type= key")
end
if (not p.probability) then
H.wml_error("Random Recruiting Micro AI [probability] tag is missing required probability= key")
end
table.insert(cfg.type, p.type)
table.insert(cfg.prob, p.probability)
end
if (not p.probability) then
H.wml_error("Random Recruiting Micro AI [probability] tag is missing required probability= key")
end
table.insert(cfg.type, p.type)
table.insert(cfg.prob, p.probability)
end
end
@ -391,17 +432,26 @@ function wesnoth.wml_actions.micro_ai(cfg)
"avoid_enemies", "filter", "ignore_units", "ignore_enemy_at_goal",
"release_all_units_at_goal", "release_unit_at_goal", "unique_goals", "use_straight_line"
}
CA_parms = { { ca_id = 'mai_goto', location = CA_path .. 'ca_goto.lua', score = cfg.ca_score or 300000 } }
CA_parms = {
ai_id = 'mai_goto',
{ ca_id = 'move', location = CA_path .. 'ca_goto.lua', score = cfg.ca_score or 300000 }
}
--------- Hang Out Micro AI ------------------------------------
elseif (cfg.ai_type == 'hang_out') then
optional_keys = { "filter", "filter_location", "avoid", "mobilize_condition", "mobilize_on_gold_less_than" }
CA_parms = { { ca_id = 'mai_hang_out', location = CA_path .. 'ca_hang_out.lua', score = cfg.ca_score or 170000 } }
CA_parms = {
ai_id = 'mai_hang_out',
{ ca_id = 'move', location = CA_path .. 'ca_hang_out.lua', score = cfg.ca_score or 170000 }
}
--------- Simple Attack Micro AI ---------------------------
elseif (cfg.ai_type == 'simple_attack') then
optional_keys = { "filter", "filter_second", "weapon" }
CA_parms = { { ca_id = 'mai_simple_attack', location = CA_path .. 'ca_simple_attack.lua', score = cfg.ca_score or 110000 } }
CA_parms = {
ai_id = 'mai_simple_attack',
{ ca_id = 'move', location = CA_path .. 'ca_simple_attack.lua', score = cfg.ca_score or 110000 }
}
-- If we got here, none of the valid ai_types was specified
else