Healer Support Micro AI: code cleanup
This commit is contained in:
parent
8cb1e4f989
commit
d4917d534e
3 changed files with 49 additions and 85 deletions
|
@ -3,26 +3,21 @@ local W = H.set_wml_action_metatable {}
|
|||
|
||||
local ca_healer_initialize = {}
|
||||
|
||||
------ Initialize healer support at beginning of turn -----------
|
||||
-- Set variables and aspects correctly at the beginning of the turn
|
||||
-- This will be blacklisted after first execution each turn
|
||||
function ca_healer_initialize:evaluation(ai)
|
||||
-- Set variables and aspects so that healers are excluded from attacks at beginning of turn
|
||||
-- This will be blacklisted after first execution each turn
|
||||
|
||||
local score = 999990
|
||||
return score
|
||||
end
|
||||
|
||||
function ca_healer_initialize:execution(ai, cfg, self)
|
||||
--print(' Initializing healer_support at beginning of Turn ' .. wesnoth.current.turn)
|
||||
|
||||
-- First, modify the attacks aspect to exclude healers
|
||||
-- Always delete the attacks aspect first, so that we do not end up with 100 copies of the facet
|
||||
W.modify_ai {
|
||||
side = wesnoth.current.side,
|
||||
action = "try_delete",
|
||||
path = "aspect[attacks].facet[no_healers_attack]"
|
||||
}
|
||||
|
||||
-- Then set the aspect to exclude healers
|
||||
W.modify_ai {
|
||||
side = wesnoth.current.side,
|
||||
action = "add",
|
||||
|
@ -37,8 +32,9 @@ function ca_healer_initialize:execution(ai, cfg, self)
|
|||
} }
|
||||
}
|
||||
|
||||
-- We also need to set the return score of healer moves to happen _after_ combat at beginning of turn
|
||||
self.data.HS_return_score = 95000
|
||||
-- We also need to set the score of healer moves to happen after
|
||||
-- combat (of other units) at beginning of turn
|
||||
self.data.HS_healer_move_score = 95000
|
||||
end
|
||||
|
||||
return ca_healer_initialize
|
||||
|
|
|
@ -3,30 +3,24 @@ local W = H.set_wml_action_metatable {}
|
|||
|
||||
local ca_healer_may_attack = {}
|
||||
|
||||
-- After attacks by all other units are done, reset things so that healers can attack, if desired
|
||||
-- This will be blacklisted after first execution each turn
|
||||
function ca_healer_may_attack:evaluation(ai)
|
||||
-- After attacks by all other units are done, reset things so that healers can attack, if desired
|
||||
-- This will be blacklisted after first execution each turn
|
||||
|
||||
local score = 99990
|
||||
return score
|
||||
end
|
||||
|
||||
function ca_healer_may_attack:execution(ai, cfg, self)
|
||||
--print(' Letting healers participate in attacks from now on')
|
||||
|
||||
--local leader = wesnoth.get_units { side = wesnoth.current.side, canrecruit = 'yes' }[1]
|
||||
--W.message { speaker = leader.id, message = "I'm done with the RCA AI combat CA for all other units, letting healers participate now (if they cannot find a support position)." }
|
||||
|
||||
-- Delete the attacks aspect
|
||||
--print("Deleting attacks aspect")
|
||||
W.modify_ai {
|
||||
side = wesnoth.current.side,
|
||||
action = "try_delete",
|
||||
path = "aspect[attacks].facet[no_healers_attack]"
|
||||
}
|
||||
|
||||
-- We also reset the variable containing the return score of the healers CA
|
||||
-- This will make it use its default value
|
||||
self.data.HS_return_score = nil
|
||||
-- Once combat (by other units) is done, set the healer move score so that it
|
||||
-- now happens before combat (of the healers which were so far excluded from combat)
|
||||
self.data.HS_healer_move_score = nil
|
||||
end
|
||||
|
||||
return ca_healer_may_attack
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
local H = wesnoth.require "lua/helper.lua"
|
||||
local W = H.set_wml_action_metatable {}
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
|
||||
|
@ -8,14 +7,10 @@ local ca_healer_move = {}
|
|||
|
||||
function ca_healer_move:evaluation(ai, cfg, self)
|
||||
-- Should happen with higher priority than attacks, except at beginning of turn,
|
||||
-- when we want attacks done first
|
||||
-- when we want attacks (by other units) done first
|
||||
-- This is done so that it is possible for healers to attack, if they do not
|
||||
-- find an appropriate hex to back up other units
|
||||
local score = 105000
|
||||
if self.data.HS_return_score then score = self.data.HS_return_score end
|
||||
--print('healer_support score:', score)
|
||||
|
||||
cfg = cfg or {}
|
||||
local score = self.data.HS_healer_move_score or 105000
|
||||
|
||||
local all_healers = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
|
@ -24,87 +19,73 @@ function ca_healer_move:evaluation(ai, cfg, self)
|
|||
}
|
||||
|
||||
local healers, healers_noMP = {}, {}
|
||||
for _, healer in ipairs(all_healers) do
|
||||
for _,healer in ipairs(all_healers) do
|
||||
if (healer.moves > 0) then
|
||||
table.insert(healers, healer)
|
||||
else
|
||||
table.insert(healers_noMP, healer)
|
||||
end
|
||||
end
|
||||
|
||||
if (not healers[1]) then return 0 end
|
||||
|
||||
local all_units = wesnoth.get_units {
|
||||
local all_healees = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
{ "and", cfg.filter_second }
|
||||
}
|
||||
|
||||
local healees, units_MP = {}, {}
|
||||
for i,u in ipairs(all_units) do
|
||||
local healees, healees_MP = {}, {}
|
||||
for _,healee in ipairs(all_healees) do
|
||||
-- Potential healees are units without MP that don't already have a healer (also without MP) next to them
|
||||
-- Also, they cannot be on a village or regenerate
|
||||
if (u.moves == 0) then
|
||||
if (not wesnoth.match_unit(u, {ability = "regenerates"})) then
|
||||
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(u.x, u.y)).village
|
||||
if (healee.moves == 0) then
|
||||
if (not wesnoth.match_unit(healee, { ability = "regenerates" })) then
|
||||
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(healee.x, healee.y)).village
|
||||
if (not is_village) then
|
||||
local healee = true
|
||||
for j,h in ipairs(healers_noMP) do
|
||||
if (H.distance_between(u.x, u.y, h.x, h.y) == 1) then
|
||||
--print('Already next to healer:', u.x, u.y, h.x, h.y)
|
||||
healee = false
|
||||
local is_healee = true
|
||||
for _,healer in ipairs(healers_noMP) do
|
||||
if (H.distance_between(healee.x, healee.y, healer.x, healer.y) == 1) then
|
||||
is_healee = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if healee then table.insert(healees, u) end
|
||||
if is_healee then table.insert(healees, healee) end
|
||||
end
|
||||
end
|
||||
else
|
||||
table.insert(units_MP,u)
|
||||
table.insert(healees_MP, healee)
|
||||
end
|
||||
end
|
||||
--print('#healees, #units_MP', #healees, #units_MP)
|
||||
|
||||
-- Take all units with moves left off the map, for enemy path finding
|
||||
for i,u in ipairs(units_MP) do wesnoth.extract_unit(u) end
|
||||
|
||||
-- Enemy attack map
|
||||
local enemies = wesnoth.get_units {
|
||||
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
|
||||
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } }
|
||||
}
|
||||
for _,healee in ipairs(healees_MP) do wesnoth.extract_unit(healee) end
|
||||
local enemy_attack_map = BC.get_attack_map(enemies)
|
||||
--AH.put_labels(enemy_attack_map.units)
|
||||
for _,healee in ipairs(healees_MP) do wesnoth.put_unit(healee) end
|
||||
|
||||
local avoid_map = LS.of_pairs(ai.get_avoid())
|
||||
|
||||
-- Put units back out there
|
||||
for i,u in ipairs(units_MP) do wesnoth.put_unit(u) end
|
||||
|
||||
-- Now find the best healer move
|
||||
local max_rating, best_hex = -9e99, {}
|
||||
for i,h in ipairs(healers) do
|
||||
--local rating_map = LS.create()
|
||||
|
||||
local reach = wesnoth.find_reach(h)
|
||||
for j,r in ipairs(reach) do
|
||||
|
||||
local rating, adjacent_healer = 0
|
||||
local max_rating, best_hex = -9e99
|
||||
for _,healer in ipairs(healers) do
|
||||
local reach = wesnoth.find_reach(healer)
|
||||
|
||||
for _,loc in ipairs(reach) do
|
||||
-- Only consider hexes that are next to at least one noMP unit that
|
||||
-- - either can be attacked by an enemy (15 points per enemy)
|
||||
-- - or has non-perfect HP (1 point per missing HP)
|
||||
|
||||
-- Also, hex must be unoccupied by another unit, of course
|
||||
local unit_in_way = wesnoth.get_unit(r[1], r[2])
|
||||
if (not avoid_map:get(r[1], r[2])) then
|
||||
if (not unit_in_way) or (unit_in_way == h) then
|
||||
for k,u in ipairs(healees) do
|
||||
if (H.distance_between(u.x, u.y, r[1], r[2]) == 1) then
|
||||
-- !!!!!!! These ratings have to be positive or the method doesn't work !!!!!!!!!
|
||||
rating = rating + u.max_hitpoints - u.hitpoints
|
||||
local rating, adjacent_healer = 0
|
||||
if (not avoid_map:get(loc[1], loc[2])) then
|
||||
local unit_in_way = wesnoth.get_unit(loc[1], loc[2])
|
||||
if (not unit_in_way) or (unit_in_way == healer) then
|
||||
for _,healee in ipairs(healees) do
|
||||
if (H.distance_between(healee.x, healee.y, loc[1], loc[2]) == 1) then
|
||||
-- Note: These ratings have to be positive or the method doesn't work
|
||||
rating = rating + healee.max_hitpoints - healee.hitpoints
|
||||
|
||||
-- If injured_units_only = true then don't count units with full HP
|
||||
if (u.max_hitpoints - u.hitpoints > 0) or (not cfg.injured_units_only) then
|
||||
rating = rating + 15 * (enemy_attack_map.units:get(u.x, u.y) or 0)
|
||||
if (healee.max_hitpoints - healee.hitpoints > 0) or (not cfg.injured_units_only) then
|
||||
rating = rating + 15 * (enemy_attack_map.units:get(healee.x, healee.y) or 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -113,7 +94,7 @@ function ca_healer_move:evaluation(ai, cfg, self)
|
|||
|
||||
-- Number of enemies that can threaten the healer at that position
|
||||
-- This has to be no larger than cfg.max_threats for hex to be considered
|
||||
local enemies_in_reach = enemy_attack_map.units:get(r[1], r[2]) or 0
|
||||
local enemies_in_reach = enemy_attack_map.units:get(loc[1], loc[2]) or 0
|
||||
|
||||
-- If this hex fulfills those requirements, 'rating' is now greater than 0
|
||||
-- and we do the rest of the rating, otherwise set rating to below max_rating
|
||||
|
@ -124,26 +105,19 @@ function ca_healer_move:evaluation(ai, cfg, self)
|
|||
rating = rating - enemies_in_reach * 1000
|
||||
|
||||
-- All else being more or less equal, prefer villages and strong terrain
|
||||
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(r[1], r[2])).village
|
||||
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(loc[1], loc[2])).village
|
||||
if is_village then rating = rating + 2 end
|
||||
|
||||
local defense = 100 - wesnoth.unit_defense(h, wesnoth.get_terrain(r[1], r[2]))
|
||||
local defense = 100 - wesnoth.unit_defense(healer, wesnoth.get_terrain(loc[1], loc[2]))
|
||||
rating = rating + defense / 10.
|
||||
|
||||
--rating_map:insert(r[1], r[2], rating)
|
||||
end
|
||||
|
||||
if (rating > max_rating) then
|
||||
max_rating, best_healer, best_hex = rating, h, {r[1], r[2]}
|
||||
max_rating, best_healer, best_hex = rating, healer, { loc[1], loc[2] }
|
||||
end
|
||||
end
|
||||
--AH.put_labels(rating_map)
|
||||
--W.message { speaker = h.id, message = 'Healer rating map for me' }
|
||||
end
|
||||
--print('best unit move', best_hex[1], best_hex[2], max_rating)
|
||||
|
||||
-- Only move healer if a good move as found
|
||||
-- Be aware that this means that other CAs will move the healers if not
|
||||
if (max_rating > -9e99) then
|
||||
self.data.HS_unit, self.data.HS_hex = best_healer, best_hex
|
||||
return score
|
||||
|
@ -154,7 +128,7 @@ end
|
|||
|
||||
function ca_healer_move:execution(ai, cfg, self)
|
||||
AH.movefull_outofway_stopunit(ai, self.data.HS_unit, self.data.HS_hex)
|
||||
self.data.HS_unit, self.data.HS_hex = nil, nil
|
||||
self.data.HS_unit, self.data.HS_hex = nil, nil
|
||||
end
|
||||
|
||||
return ca_healer_move
|
||||
|
|
Loading…
Add table
Reference in a new issue