New experimental CAs to improve village capturing and retreat when injured
This commit is contained in:
parent
668445cb6b
commit
f5a4a83def
4 changed files with 224 additions and 0 deletions
|
@ -12,6 +12,8 @@ Version 1.11.1+svn:
|
|||
* Fix bug handling regeneration
|
||||
* Minor improvements in switching between castles
|
||||
* Add healer support micro AI to improve healer use
|
||||
* Improved village capturing
|
||||
* Retreat badly injured units more effectively
|
||||
* New [micro_ai] tag, 18 different Micro AIs, and 14 test scenarios
|
||||
* This includes AIs for 7 different animal behaviors, bottleneck defense,
|
||||
2 different guardians and a coward, healer support, lurkers,
|
||||
|
|
|
@ -10,6 +10,7 @@ return {
|
|||
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
|
||||
local LS = wesnoth.require "lua/location_set.lua"
|
||||
local HS = wesnoth.require("ai/micro_ais/ais/mai_healer_support_engine.lua").init(ai)
|
||||
local R = wesnoth.require "ai/lua/retreat.lua"
|
||||
|
||||
------ Stats at beginning of turn -----------
|
||||
|
||||
|
@ -467,6 +468,8 @@ return {
|
|||
self.data.attack = nil
|
||||
end
|
||||
|
||||
------- Place Healers CA --------------
|
||||
|
||||
generic_rush.healer_support_eval = HS.healer_support_eval
|
||||
|
||||
function generic_rush:place_healers_eval()
|
||||
|
@ -478,6 +481,98 @@ return {
|
|||
|
||||
generic_rush.place_healers_exec = HS.healer_support_exec
|
||||
|
||||
------- Retreat CA --------------
|
||||
|
||||
function generic_rush:retreat_injured_units_eval()
|
||||
local units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
local unit, loc = R.retreat_injured_units(units)
|
||||
if unit then
|
||||
self.data.retreat_unit = unit
|
||||
self.data.retreat_loc = loc
|
||||
|
||||
-- First check if attacks are possible for any unit
|
||||
-- If one with > 50% chance of kill is possible, set return_value to lower than combat CA
|
||||
local attacks = ai.get_attacks()
|
||||
for i,a in ipairs(attacks) do
|
||||
if (#a.movements == 1) and (a.chance_to_kill > 0.5) then
|
||||
return 95000
|
||||
end
|
||||
end
|
||||
return 205000
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function generic_rush:retreat_injured_units_exec()
|
||||
AH.movefull_outofway_stopunit(ai, self.data.retreat_unit, self.data.retreat_loc)
|
||||
self.data.retreat_unit = nil
|
||||
self.data.retreat_loc = nil
|
||||
end
|
||||
|
||||
------- Village Hunt CA --------------
|
||||
-- Give extra priority to seeking villages if we have less than our share
|
||||
-- our share is defined as being slightly more than the total/the number of sides
|
||||
|
||||
function generic_rush:village_hunt_eval()
|
||||
local villages = wesnoth.get_villages()
|
||||
|
||||
if not villages[1] then
|
||||
return 0
|
||||
end
|
||||
|
||||
local my_villages = wesnoth.get_villages { owner_side = wesnoth.current.side }
|
||||
|
||||
if #my_villages > #villages / #wesnoth.sides then
|
||||
return 0
|
||||
end
|
||||
|
||||
local allied_villages = wesnoth.get_villages { {"filter_owner", { {"ally_of", { side = wesnoth.current.side }} }} }
|
||||
if #allied_villages == #villages then
|
||||
return 0
|
||||
end
|
||||
|
||||
local units = wesnoth.get_units {
|
||||
side = wesnoth.current.side,
|
||||
canrecruit = false,
|
||||
formula = '$this_unit.moves > 0'
|
||||
}
|
||||
|
||||
if not units[1] then
|
||||
return 0
|
||||
end
|
||||
|
||||
return 30000
|
||||
end
|
||||
|
||||
function generic_rush:village_hunt_exec()
|
||||
local unit = wesnoth.get_units({
|
||||
side = wesnoth.current.side,
|
||||
canrecruit = false,
|
||||
formula = '$this_unit.moves > 0'
|
||||
})[1]
|
||||
|
||||
local villages = wesnoth.get_villages()
|
||||
local target, best_cost = nil, AH.no_path
|
||||
for i,v in ipairs(villages) do
|
||||
if not wesnoth.match_location(v[1], v[2], { {"filter_owner", { {"ally_of", { side = wesnoth.current.side }} }} }) then
|
||||
local path, cost = wesnoth.find_path(unit, v[1], v[2], { ignore_units = true, max_cost = best_cost })
|
||||
if cost < best_cost then
|
||||
target = v
|
||||
best_cost = cost
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if target then
|
||||
local x, y = wesnoth.find_vacant_tile(target[1], target[2], unit)
|
||||
local dest = AH.next_hop(unit, x, y)
|
||||
ai.move(unit, dest[1], dest[2])
|
||||
end
|
||||
end
|
||||
|
||||
return generic_rush
|
||||
end
|
||||
}
|
||||
|
|
126
data/ai/lua/retreat.lua
Normal file
126
data/ai/lua/retreat.lua
Normal file
|
@ -0,0 +1,126 @@
|
|||
--[=[
|
||||
Functions to support the retreat of injured units
|
||||
]=]
|
||||
|
||||
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
||||
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
|
||||
|
||||
local retreat_functions = {}
|
||||
|
||||
-- Given a set of units, return one from the set that should retreat and the location to retreat to
|
||||
-- Return nil if no unit needs to retreat
|
||||
function retreat_functions.retreat_injured_units(units)
|
||||
local min_hp = function(unit)
|
||||
-- The minimum hp to retreat is a function of level and terrain defense
|
||||
-- We want to stay longer on good terrain and leave early on very bad terrain
|
||||
local hp_per_level = wesnoth.unit_defense(unit, wesnoth.get_terrain(unit.x, unit.y))/15
|
||||
local level = wesnoth.unit_types[unit.type].level
|
||||
|
||||
-- Leaders are considered to be higher level because of their value
|
||||
if unit.canrecruit then level = level+2 end
|
||||
|
||||
local min_hp = hp_per_level*(level+2)
|
||||
|
||||
-- Account for poison damage on next turn
|
||||
if unit.status.poisoned then min_hp = min_hp + 8 end
|
||||
|
||||
-- Make sure that units are actually injured
|
||||
if min_hp > unit.max_hitpoints - 4 then
|
||||
min_hp = unit.max_hitpoints - 4
|
||||
end
|
||||
|
||||
return min_hp
|
||||
end
|
||||
|
||||
-- Split units into those that regenerate and those that do not
|
||||
local regen, non_regen = {}, {}
|
||||
for i,u in ipairs(units) do
|
||||
if u.hitpoints < min_hp(u) then
|
||||
if wesnoth.unit_ability(u, 'regenerate') then
|
||||
table.insert(regen, u)
|
||||
else
|
||||
table.insert(non_regen, u)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- First we retreat non-regenerating units to healing terrain
|
||||
if non_regen[1] then
|
||||
local unit, loc = retreat_functions.get_retreat_injured_units(non_regen, true)
|
||||
if unit then
|
||||
return unit, loc
|
||||
end
|
||||
end
|
||||
|
||||
-- Then we retreat regenerating units to terrain with high defense
|
||||
if regen[1] then
|
||||
local action = retreat_functions.get_retreat_injured_units(regen, false)
|
||||
if unit then
|
||||
return unit, loc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function retreat_functions.get_retreat_injured_units(healees, healing_terrain_only)
|
||||
-- Only retreat to safe locations
|
||||
local enemies = AH.get_live_units {
|
||||
{ "filter_side", {{"enemy_of", {side = wesnoth.current.side} }} }
|
||||
}
|
||||
local enemy_attack_map = BC.get_attack_map(enemies)
|
||||
|
||||
local max_rating, best_loc, best_unit = -9e99, nil, nil
|
||||
for i,u in ipairs(healees) do
|
||||
local possible_locations = wesnoth.find_reach(u)
|
||||
-- TODO: avoid ally's villages (may be preferable to lower rating so they will
|
||||
-- be used if unit is very injured)
|
||||
if healing_terrain_only then
|
||||
-- Unit cannot self heal, make the terrain do it for us if possible
|
||||
-- TODO: add hexes adjacent to healers (only those that will not move)
|
||||
local location_subset = {}
|
||||
for j,loc in ipairs(possible_locations) do
|
||||
if wesnoth.get_terrain_info(wesnoth.get_terrain(loc[1], loc[2])).healing > 0 then
|
||||
table.insert(location_subset, loc)
|
||||
end
|
||||
end
|
||||
if location_subset[1] then
|
||||
-- If healing terrain is available, restrict retreat locations to it
|
||||
possible_locations = location_subset
|
||||
end
|
||||
end
|
||||
|
||||
local base_rating = - u.hitpoints + u.max_hitpoints / 2.
|
||||
if u.status.poisoned then base_rating = base_rating + 8 end
|
||||
if u.status.slowed then base_rating = base_rating + 4 end
|
||||
base_rating = base_rating * 1000
|
||||
|
||||
for j,loc in ipairs(possible_locations) do
|
||||
local unit_in_way = wesnoth.get_unit(loc[1], loc[2])
|
||||
if (not unit_in_way) or ((unit_in_way.moves > 0) and (unit_in_way.side == wesnoth.current.side)) then
|
||||
local rating = base_rating
|
||||
|
||||
-- Penalty for each enemy that can reach location
|
||||
rating = rating - (enemy_attack_map.units:get(loc[1], loc[2]) or 0) * 10
|
||||
|
||||
-- Penalty based on terrain defense for unit
|
||||
rating = rating - wesnoth.unit_defense(u, wesnoth.get_terrain(loc[1], loc[2]))/10
|
||||
|
||||
if (loc[1] == u.x) and (loc[2] == u.y) then
|
||||
-- Bonus if we don't have to move (might get to rest heal)
|
||||
rating = rating + 2
|
||||
elseif unit_in_way then
|
||||
-- Penalty if a unit has to move out of the way
|
||||
-- (based on hp of moving unit)
|
||||
rating = rating + unit_in_way.hitpoints - unit_in_way.max_hitpoints
|
||||
end
|
||||
|
||||
if (rating > max_rating) then
|
||||
max_rating, best_loc, best_unit = rating, loc, u
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return best_unit, best_loc
|
||||
end
|
||||
|
||||
return retreat_functions
|
|
@ -16,6 +16,7 @@ Version 1.11.1+svn:
|
|||
* Fix bug handling regeneration.
|
||||
* Minor improvements in switching between castles.
|
||||
* Add healer support micro AI to improve healer use.
|
||||
* Retreat badly injured units more effectively.
|
||||
* New [micro_ai] tag, 18 different Micro AIs, and 14 test scenarios
|
||||
* This includes AIs for 7 different animal behaviors, bottleneck defense,
|
||||
2 different guardians and a coward, healer support, lurkers,
|
||||
|
|
Loading…
Add table
Reference in a new issue