Skip to content

Commit

Permalink
Lua AIs: distinguish between healing locations and villages
Browse files Browse the repository at this point in the history
There are rating contributions for hexes which heal (not all of which are villages) and others for hexes that provide income (villages). Previously only villages were considered for both types of hexes.
  • Loading branch information
mattsc committed Nov 7, 2018
1 parent 8bca955 commit 1856377
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 27 deletions.
11 changes: 5 additions & 6 deletions data/ai/lua/battle_calcs.lua
Expand Up @@ -809,10 +809,10 @@ function battle_calcs.attack_rating(attacker, defender, dst, cfg, cache)
damage = damage + 6 * (att_stats.slowed - att_stats.hp_chance[0])
end

-- If attack is from a village (or other healing location), count that as slightly more than the healing amount
-- If attack is from a healing location, count that as slightly more than the healing amount
damage = damage - 1.25 * wesnoth.get_terrain_info(wesnoth.get_terrain(dst[1], dst[2])).healing

-- Equivalently, if attack is adjacent to an unoccupied village, that's bad
-- Equivalently, if attack is adjacent to an unoccupied healing location, that's bad
for xa,ya in H.adjacent_tiles(dst[1], dst[2]) do
local healing = wesnoth.get_terrain_info(wesnoth.get_terrain(xa, ya)).healing
if (healing > 0) and (not wesnoth.get_unit(xa, ya)) then
Expand Down Expand Up @@ -863,7 +863,7 @@ function battle_calcs.attack_rating(attacker, defender, dst, cfg, cache)
damage = damage + 6 * (def_stats.slowed - def_stats.hp_chance[0])
end

-- If defender is on a village (or other healing location), count that as slightly more than the healing amount
-- If defender is on a healing location, count that as slightly more than the healing amount
damage = damage - 1.25 * wesnoth.get_terrain_info(wesnoth.get_terrain(defender.x, defender.y)).healing

if (damage < 0) then damage = 0. end
Expand Down Expand Up @@ -905,9 +905,8 @@ function battle_calcs.attack_rating(attacker, defender, dst, cfg, cache)

-- If defender is on a village, add a bonus rating (we want to get rid of those preferentially)
-- So yes, this is positive, even though it's a plus for the defender
-- Defenders on villages also got a negative damage rating above (these don't exactly cancel each other though)
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(defender.x, defender.y)).village
if is_village then
-- Note: defenders on healing locations also got a negative damage rating above (these don't exactly cancel each other though)
if wesnoth.get_terrain_info(wesnoth.get_terrain(defender.x, defender.y)).village then
defender_value = defender_value * (1. + 10. / attacker.max_hitpoints)
end

Expand Down
6 changes: 3 additions & 3 deletions data/ai/lua/ca_spread_poison.lua
Expand Up @@ -42,14 +42,14 @@ function ca_spread_poison:evaluation(cfg, data)
-- Don't try to poison a unit that cannot be poisoned
local cant_poison = defender.status.poisoned or defender.status.unpoisonable

-- For now, we also simply don't poison units on villages (unless standard combat CA does it)
-- For now, we also simply don't poison units on healing locations (unless standard combat CA does it)
local defender_terrain = wesnoth.get_terrain(defender.x, defender.y)
local on_village = wesnoth.get_terrain_info(defender_terrain).village
local healing = wesnoth.get_terrain_info(defender_terrain).healing

-- Also, poisoning units that would level up through the attack or could level on their turn as a result is very bad
local about_to_level = defender.max_experience - defender.experience <= (attacker.level * 2 * wesnoth.game_config.combat_experience)

if (not cant_poison) and (not on_village) and (not about_to_level) then
if (not cant_poison) and (healing == 0) and (not about_to_level) then
-- Strongest enemy gets poisoned first
local rating = defender.hitpoints

Expand Down
38 changes: 23 additions & 15 deletions data/ai/micro_ais/cas/ca_fast_attack_utils.lua
Expand Up @@ -49,13 +49,17 @@ function ca_fast_attack_utils.gamedata_setup()
local village_map = {}
for _,village in ipairs(wesnoth.get_villages()) do
if (not village_map[village[1]]) then village_map[village[1]] = {} end
village_map[village[1]][village[2]] = {
owner = wesnoth.get_village_owner(village[1], village[2]),
healing = wesnoth.get_terrain_info(wesnoth.get_terrain(village[1], village[2])).healing
}
village_map[village[1]][village[2]] = true
end
gamedata.village_map = village_map

local healing_map = {}
for _,loc in ipairs(AH.get_healing_locations {}) do
if (not healing_map[loc[1]]) then healing_map[loc[1]] = {} end
healing_map[loc[1]][loc[2]] = wesnoth.get_terrain_info(wesnoth.get_terrain(loc[1], loc[2])).healing
end
gamedata.healing_map = healing_map

-- Only uses one leader per side right now, but only used for finding direction
-- of move -> sufficient for this.
gamedata.leaders = {}
Expand Down Expand Up @@ -181,7 +185,7 @@ function ca_fast_attack_utils.is_acceptable_attack(damage_taken, damage_done, ag
return (damage_done / damage_taken) >= (1 - aggression)
end

function ca_fast_attack_utils.damage_rating_unit(attacker_info, defender_info, att_stat, def_stat, healing, cfg)
function ca_fast_attack_utils.damage_rating_unit(attacker_info, defender_info, att_stat, def_stat, is_village, healing, cfg)
-- Calculate the rating for the damage received by a single attacker on a defender.
-- The attack att_stat both for the attacker and the defender need to be precalculated for this.
-- Unit information is passed in unit_infos format, rather than as unit proxy tables for speed reasons.
Expand All @@ -191,8 +195,10 @@ function ca_fast_attack_utils.damage_rating_unit(attacker_info, defender_info, a
-- Input parameters:
-- @attacker_info, @defender_info: unit_info tables produced by ca_fast_attack_utils.get_unit_info()
-- @att_stat, @def_stat: attack statistics for the two units
-- @healing: the healing given by a village
-- @is_village: whether the hex from which the attacker attacks is a village
-- Set to nil or false if not, to anything if it is a village (does not have to be a boolean)
-- @healing: the amount of healing given by the hex from which the attacker attacks
-- Set to nil or false if there is no healing
--
-- Optional parameters:
-- @cfg: the optional weights listed right below (currently only leader_weight)
Expand All @@ -210,7 +216,7 @@ function ca_fast_attack_utils.damage_rating_unit(attacker_info, defender_info, a
damage = damage + 4 * (att_stat.slowed - att_stat.hp_chance[0])
end

-- If attack is from a village, count its healing_value
-- If attack is from a healing location, count its healing_value
if healing then
damage = damage - healing
-- Otherwise only: if attacker can regenerate, add the regeneration bonus
Expand Down Expand Up @@ -290,22 +296,24 @@ function ca_fast_attack_utils.attack_rating(attacker_infos, defender_info, dsts,

local attacker_rating = 0
for i,attacker_info in ipairs(attacker_infos) do
local attacker_healing = gamedata.village_map[dsts[i][1]]
local attacker_on_village = gamedata.village_map[dsts[i][1]]
and gamedata.village_map[dsts[i][1]][dsts[i][2]]
and gamedata.village_map[dsts[i][1]][dsts[i][2]].healing
local attacker_healing = gamedata.healing_map[dsts[i][1]]
and gamedata.healing_map[dsts[i][1]][dsts[i][2]]
attacker_rating = attacker_rating + ca_fast_attack_utils.damage_rating_unit(
attacker_info, defender_info, att_stats[i], def_stat, attacker_healing, cfg
attacker_info, defender_info, att_stats[i], def_stat, attacker_on_village, attacker_healing, cfg
)
end

-- attacker_info is passed only to figure out whether the attacker might level up
-- TODO: This is only works for the first attacker in a combo at the moment
local defender_x, defender_y = defender_info.x, defender_info.y
local defender_healing = gamedata.village_map[defender_x]
local defender_on_village = gamedata.village_map[defender_x]
and gamedata.village_map[defender_x][defender_y]
and gamedata.village_map[defender_x][defender_y].healing
local defender_healing = gamedata.healing_map[defender_x]
and gamedata.healing_map[defender_x][defender_y]
local defender_rating = ca_fast_attack_utils.damage_rating_unit(
defender_info, attacker_infos[1], def_stat, att_stats[1], defender_healing, cfg
defender_info, attacker_infos[1], def_stat, att_stats[1], defender_on_village, defender_healing, cfg
)

-- Now we add some extra ratings. They are positive for attacks that should be preferred
Expand All @@ -319,8 +327,8 @@ function ca_fast_attack_utils.attack_rating(attacker_infos, defender_info, dsts,
extra_rating = extra_rating + defender_starting_damage_fraction * 0.33

-- If defender is on a village, add a bonus rating (we want to get rid of those preferentially)
-- This is in addition to the damage bonus already included above (but as an extra rating)
if defender_healing then
-- This is in addition to the healing bonus already included above (but as an extra rating)
if defender_on_village then
extra_rating = extra_rating + 10.
end

Expand Down
6 changes: 3 additions & 3 deletions data/ai/micro_ais/cas/ca_healer_move.lua
Expand Up @@ -35,11 +35,11 @@ function ca_healer_move:evaluation(cfg, data)
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
-- Also, they cannot be on a healing location or regenerate
if (healee.moves == 0) then
if (not healee:matches { ability = "regenerates" }) then
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(healee.x, healee.y)).village
if (not is_village) then
local healing = wesnoth.get_terrain_info(wesnoth.get_terrain(healee.x, healee.y)).healing
if (healing == 0) then
local is_healee = true
for _,healer in ipairs(healers_noMP) do
if (M.distance_between(healee.x, healee.y, healer.x, healer.y) == 1) then
Expand Down

0 comments on commit 1856377

Please sign in to comment.