Lua AIs: do not use hard-coded values for modifiable parameters
This commit is contained in:
parent
e3b2cbfc25
commit
0c57ae16e4
7 changed files with 77 additions and 61 deletions
|
@ -800,26 +800,23 @@ function battle_calcs.attack_rating(attacker, defender, dst, cfg, cache)
|
|||
|
||||
-- Average damage to unit is negative rating
|
||||
local damage = attacker.hitpoints - att_stats.average_hp
|
||||
-- Count poisoned as additional 8 HP damage times probability of being poisoned
|
||||
-- Count poisoned as additional damage done by poison times probability of being poisoned
|
||||
if (att_stats.poisoned ~= 0) then
|
||||
damage = damage + 8 * (att_stats.poisoned - att_stats.hp_chance[0])
|
||||
damage = damage + wesnoth.game_config.poison_amount * (att_stats.poisoned - att_stats.hp_chance[0])
|
||||
end
|
||||
-- Count slowed as additional 6 HP damage times probability of being slowed
|
||||
if (att_stats.slowed ~= 0) then
|
||||
damage = damage + 6 * (att_stats.slowed - att_stats.hp_chance[0])
|
||||
end
|
||||
|
||||
-- If attack is from a village, we count that as a 10 HP bonus
|
||||
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(dst[1], dst[2])).village
|
||||
if is_village then
|
||||
damage = damage - 10.
|
||||
end
|
||||
-- If attack is from a village (or other 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
|
||||
|
||||
-- If attack is adjacent to an unoccupied village, that's bad
|
||||
-- Equivalently, if attack is adjacent to an unoccupied village, that's bad
|
||||
for xa,ya in H.adjacent_tiles(dst[1], dst[2]) do
|
||||
local is_adjacent_village = wesnoth.get_terrain_info(wesnoth.get_terrain(xa, ya)).village
|
||||
if is_adjacent_village and (not wesnoth.get_unit(xa, ya)) then
|
||||
damage = damage + 10
|
||||
local healing = wesnoth.get_terrain_info(wesnoth.get_terrain(xa, ya)).healing
|
||||
if (healing > 0) and (not wesnoth.get_unit(xa, ya)) then
|
||||
damage = damage + 1.25 * healing
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -835,10 +832,10 @@ function battle_calcs.attack_rating(attacker, defender, dst, cfg, cache)
|
|||
-- proportional to the chance of it happening and the chance of not dying itself
|
||||
local level_bonus = 0.
|
||||
local defender_level = defender.level
|
||||
if (attacker.max_experience - attacker.experience <= defender_level) then
|
||||
if (attacker.max_experience - attacker.experience <= defender_level * wesnoth.game_config.combat_experience) then
|
||||
level_bonus = 1. - att_stats.hp_chance[0]
|
||||
else
|
||||
if (attacker.max_experience - attacker.experience <= defender_level * 8) then
|
||||
if (attacker.max_experience - attacker.experience <= defender_level * wesnoth.game_config.kill_experience) then
|
||||
level_bonus = (1. - att_stats.hp_chance[0]) * def_stats.hp_chance[0]
|
||||
end
|
||||
end
|
||||
|
@ -857,20 +854,17 @@ function battle_calcs.attack_rating(attacker, defender, dst, cfg, cache)
|
|||
------ Now (most of) the same for the defender ------
|
||||
-- Average damage to defender is positive rating
|
||||
local damage = defender.hitpoints - def_stats.average_hp
|
||||
-- Count poisoned as additional 8 HP damage times probability of being poisoned
|
||||
-- Count poisoned as additional damage done by poison times probability of being poisoned
|
||||
if (def_stats.poisoned ~= 0) then
|
||||
damage = damage + 8 * (def_stats.poisoned - def_stats.hp_chance[0])
|
||||
damage = damage + wesnoth.game_config.poison_amount * (def_stats.poisoned - def_stats.hp_chance[0])
|
||||
end
|
||||
-- Count slowed as additional 6 HP damage times probability of being slowed
|
||||
if (def_stats.slowed ~= 0) then
|
||||
damage = damage + 6 * (def_stats.slowed - def_stats.hp_chance[0])
|
||||
end
|
||||
|
||||
-- If defender is on a village, we count that as a 10 HP bonus
|
||||
local is_village = wesnoth.get_terrain_info(wesnoth.get_terrain(defender.x, defender.y)).village
|
||||
if is_village then
|
||||
damage = damage - 10.
|
||||
end
|
||||
-- If defender is on a village (or other 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
|
||||
|
||||
|
@ -884,10 +878,10 @@ function battle_calcs.attack_rating(attacker, defender, dst, cfg, cache)
|
|||
-- proportional to the chance of it happening and the chance of not dying itself
|
||||
local defender_level_penalty = 0.
|
||||
local attacker_level = attacker.level
|
||||
if (defender.max_experience - defender.experience <= attacker_level) then
|
||||
if (defender.max_experience - defender.experience <= attacker_level * wesnoth.game_config.combat_experience) then
|
||||
defender_level_penalty = 1. - def_stats.hp_chance[0]
|
||||
else
|
||||
if (defender.max_experience - defender.experience <= attacker_level * 8) then
|
||||
if (defender.max_experience - defender.experience <= attacker_level * wesnoth.game_config.kill_experience) then
|
||||
defender_level_penalty = (1. - def_stats.hp_chance[0]) * att_stats.hp_chance[0]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,12 +56,12 @@ function ca_attack_highxp:evaluation(cfg, data)
|
|||
for i_t,enemy in ipairs(attacks_aspect.enemy) do
|
||||
if AH.is_attackable_enemy(enemy) then
|
||||
local XP_to_levelup = enemy.max_experience - enemy.experience
|
||||
if (max_unit_level >= XP_to_levelup) then
|
||||
if (max_unit_level * wesnoth.game_config.combat_experience >= XP_to_levelup) then
|
||||
local potential_target = false
|
||||
local ind_attackers, ind_other_units = {}, {}
|
||||
for i_u,unit in ipairs(units) do
|
||||
if (M.distance_between(enemy.x, enemy.y, unit.x, unit.y) <= unit.moves + 1) then
|
||||
if (unit.level >= XP_to_levelup) then
|
||||
if (unit.level * wesnoth.game_config.combat_experience >= XP_to_levelup) then
|
||||
potential_target = true
|
||||
table.insert(ind_attackers, i_u)
|
||||
else
|
||||
|
|
|
@ -47,7 +47,7 @@ function ca_spread_poison:evaluation(cfg, data)
|
|||
local on_village = wesnoth.get_terrain_info(defender_terrain).village
|
||||
|
||||
-- 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)
|
||||
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
|
||||
-- Strongest enemy gets poisoned first
|
||||
|
|
|
@ -223,7 +223,7 @@ return {
|
|||
local poison_damage = 0
|
||||
if poison then
|
||||
-- Add poison damage * probability of poisoning
|
||||
poison_damage = 8*(1-((1-defense)^attack.number))
|
||||
poison_damage = wesnoth.game_config.poison_amount*(1-((1-defense)^attack.number))
|
||||
end
|
||||
|
||||
if (not best_attack) or (attack_damage+poison_damage > best_damage+best_poison_damage) then
|
||||
|
|
|
@ -21,7 +21,7 @@ function retreat_functions.min_hp(unit)
|
|||
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
|
||||
if unit.status.poisoned then min_hp = min_hp + wesnoth.game_config.poison_amount end
|
||||
|
||||
-- Make sure that units are actually injured
|
||||
if min_hp > unit.max_hitpoints - 4 then
|
||||
|
@ -35,11 +35,22 @@ end
|
|||
-- Return nil if no unit needs to retreat
|
||||
function retreat_functions.retreat_injured_units(units)
|
||||
-- Split units into those that regenerate and those that do not
|
||||
local regen, non_regen = {}, {}
|
||||
local regen, regen_amounts, non_regen = {}, {}, {}
|
||||
for i,u in ipairs(units) do
|
||||
if u.hitpoints < retreat_functions.min_hp(u) then
|
||||
if u:ability('regenerate') then
|
||||
-- Find the best regeneration ability and use it to estimate hp regained by regeneration
|
||||
local abilities = wml.get_child(u.__cfg, "abilities")
|
||||
local regen_amount = 0
|
||||
if abilities then
|
||||
for regen in wml.child_range(abilities, "regenerate") do
|
||||
if regen.value > regen_amount then
|
||||
regen_amount = regen.value
|
||||
end
|
||||
end
|
||||
end
|
||||
table.insert(regen, u)
|
||||
table.insert(regen_amounts, regen_amount)
|
||||
else
|
||||
table.insert(non_regen, u)
|
||||
end
|
||||
|
@ -49,7 +60,7 @@ function retreat_functions.retreat_injured_units(units)
|
|||
-- First we retreat non-regenerating units to healing terrain, if they can get to a safe location
|
||||
local unit_nr, loc_nr, threat_nr
|
||||
if non_regen[1] then
|
||||
unit_nr, loc_nr, threat_nr = retreat_functions.get_retreat_injured_units(non_regen, false)
|
||||
unit_nr, loc_nr, threat_nr = retreat_functions.get_retreat_injured_units(non_regen, {})
|
||||
if unit_nr and (threat_nr == 0) then
|
||||
return unit_nr, loc_nr, threat_nr
|
||||
end
|
||||
|
@ -58,7 +69,7 @@ function retreat_functions.retreat_injured_units(units)
|
|||
-- Then we retreat regenerating units to terrain with high defense, if they can get to a safe location
|
||||
local unit_r, loc_r, threat_r
|
||||
if regen[1] then
|
||||
unit_r, loc_r, threat_r = retreat_functions.get_retreat_injured_units(regen, true)
|
||||
unit_r, loc_r, threat_r = retreat_functions.get_retreat_injured_units(regen, regen_amounts)
|
||||
if unit_r and (threat_r == 0) then
|
||||
return unit_r, loc_r, threat_r
|
||||
end
|
||||
|
@ -107,7 +118,7 @@ function retreat_functions.get_healing_locations()
|
|||
return healing_locs
|
||||
end
|
||||
|
||||
function retreat_functions.get_retreat_injured_units(healees, regenerates)
|
||||
function retreat_functions.get_retreat_injured_units(healees, regen_amounts)
|
||||
-- Only retreat to safe locations
|
||||
local enemies = AH.get_attackable_enemies()
|
||||
local enemy_attack_map = BC.get_attack_map(enemies)
|
||||
|
@ -119,7 +130,7 @@ function retreat_functions.get_retreat_injured_units(healees, regenerates)
|
|||
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 not regenerates then
|
||||
if (not regen_amounts[i]) then
|
||||
-- Unit cannot self heal, make the terrain do it for us if possible
|
||||
local location_subset = {}
|
||||
for j,loc in ipairs(possible_locations) do
|
||||
|
@ -151,7 +162,7 @@ function retreat_functions.get_retreat_injured_units(healees, regenerates)
|
|||
end
|
||||
|
||||
local base_rating = - u.hitpoints + u.max_hitpoints / 2.
|
||||
if u.status.poisoned then base_rating = base_rating + 8 end
|
||||
if u.status.poisoned then base_rating = base_rating + wesnoth.game_config.poison_amount end
|
||||
if u.status.slowed then base_rating = base_rating + 4 end
|
||||
base_rating = base_rating * 1000
|
||||
|
||||
|
@ -162,12 +173,12 @@ function retreat_functions.get_retreat_injured_units(healees, regenerates)
|
|||
then
|
||||
local rating = base_rating
|
||||
local heal_score = 0
|
||||
if regenerates then
|
||||
heal_score = math.min(8, u.max_hitpoints - u.hitpoints)
|
||||
if regen_amounts[i] then
|
||||
heal_score = math.min(regen_amounts[i], u.max_hitpoints - u.hitpoints)
|
||||
else
|
||||
if u.status.poisoned then
|
||||
if loc[4] > 0 then
|
||||
heal_score = math.min(8, u.hitpoints - 1)
|
||||
heal_score = math.min(wesnoth.game_config.poison_amount, u.hitpoints - 1)
|
||||
if loc[4] == 2 then
|
||||
-- This value is arbitrary, it just represents the ability to heal on the turn after
|
||||
heal_score = heal_score + 1
|
||||
|
@ -189,7 +200,7 @@ function retreat_functions.get_retreat_injured_units(healees, regenerates)
|
|||
if (loc[1] == u.x) and (loc[2] == u.y) and (not u.status.poisoned) then
|
||||
if is_healthy or enemy_count == 0 then
|
||||
-- Bonus if we can rest heal
|
||||
heal_score = heal_score + 2
|
||||
heal_score = heal_score + wesnoth.game_config.rest_heal_amount
|
||||
end
|
||||
elseif unit_in_way then
|
||||
-- Penalty if a unit has to move out of the way
|
||||
|
|
|
@ -380,24 +380,24 @@ function ca_bottleneck_move:evaluation(cfg, data)
|
|||
for _,attack in ipairs(attacks) do
|
||||
-- Only do calc. if there's a theoretical chance for leveling up (speeds things up a lot)
|
||||
if (attack.x == loc[1]) and (attack.y == loc[2]) and
|
||||
(unit.max_experience - unit.experience <= 8 * attack.defender_level)
|
||||
(unit.max_experience - unit.experience <= wesnoth.game_config.kill_experience * attack.defender_level)
|
||||
then
|
||||
for n_weapon,weapon in ipairs(unit.attacks) do
|
||||
local att_stats, def_stats = BC.simulate_combat_loc(unit, { attack.x, attack.y }, attack.defender, n_weapon)
|
||||
|
||||
-- Execute level-up attack when:
|
||||
-- 1. max_experience-experience <= target.level and chance to die = 0
|
||||
-- 2. max_experience-experience <= target.level*8 and chance to die = 0
|
||||
-- 1. max_experience-experience <= target.level*combat_experience and chance to die = 0
|
||||
-- 2. max_experience-experience <= target.level*kill_experience and chance to die = 0
|
||||
-- and chance to kill > 66% and remaining av hitpoints > 20
|
||||
-- #1 is a definite level up, #2 is not, so #1 gets priority
|
||||
local level_up_rating = 0
|
||||
if (unit.max_experience - unit.experience <= attack.defender_level) then
|
||||
if (unit.max_experience - unit.experience <= wesnoth.game_config.combat_experience * attack.defender_level) then
|
||||
if (att_stats.hp_chance[0] == 0) then
|
||||
-- Weakest enemy is best (favors stronger weapon)
|
||||
level_up_rating = 15000 - def_stats.average_hp
|
||||
end
|
||||
else
|
||||
if (unit.max_experience - unit.experience <= 8 * attack.defender_level)
|
||||
if (unit.max_experience - unit.experience <= wesnoth.game_config.kill_experience * attack.defender_level)
|
||||
and (att_stats.hp_chance[0] == 0)
|
||||
and (def_stats.hp_chance[0] >= 0.66)
|
||||
and (att_stats.average_hp >= 20)
|
||||
|
|
|
@ -49,7 +49,10 @@ 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]) }
|
||||
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
|
||||
}
|
||||
end
|
||||
gamedata.village_map = village_map
|
||||
|
||||
|
@ -100,7 +103,11 @@ function ca_fast_attack_utils.single_unit_info(unit_proxy)
|
|||
local abilities = wml.get_child(unit_proxy.__cfg, "abilities")
|
||||
if abilities then
|
||||
for _,ability in ipairs(abilities) do
|
||||
single_unit_info[ability[1]] = true
|
||||
if ability[1] == 'regenerate' then
|
||||
single_unit_info[ability[1]] = ability[2].value
|
||||
else
|
||||
single_unit_info[ability[1]] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -174,7 +181,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, is_village, cfg)
|
||||
function ca_fast_attack_utils.damage_rating_unit(attacker_info, defender_info, att_stat, def_stat, 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.
|
||||
|
@ -184,7 +191,7 @@ 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
|
||||
-- @is_village: whether the hex from which the attacker attacks is a village
|
||||
-- @healing: the healing given by a village
|
||||
-- Set to nil or false if not, to anything if it is a village (does not have to be a boolean)
|
||||
--
|
||||
-- Optional parameters:
|
||||
|
@ -194,21 +201,21 @@ function ca_fast_attack_utils.damage_rating_unit(attacker_info, defender_info, a
|
|||
|
||||
local damage = attacker_info.hitpoints - att_stat.average_hp
|
||||
|
||||
-- Count poisoned as additional 8 HP damage times probability of being poisoned
|
||||
-- Count poisoned as additional damage done by poison times probability of being poisoned
|
||||
if (att_stat.poisoned ~= 0) then
|
||||
damage = damage + 8 * (att_stat.poisoned - att_stat.hp_chance[0])
|
||||
damage = damage + wesnoth.game_config.poison_amount * (att_stat.poisoned - att_stat.hp_chance[0])
|
||||
end
|
||||
-- Count slowed as additional 4 HP damage times probability of being slowed
|
||||
if (att_stat.slowed ~= 0) then
|
||||
damage = damage + 4 * (att_stat.slowed - att_stat.hp_chance[0])
|
||||
end
|
||||
|
||||
-- If attack is from a village, we count that as an 8 HP bonus
|
||||
if is_village then
|
||||
damage = damage - 8.
|
||||
-- Otherwise only: if attacker can regenerate, this is an 8 HP bonus
|
||||
elseif attacker_info.regenerate then
|
||||
damage = damage - 8.
|
||||
-- If attack is from a village, count its healing_value
|
||||
if healing then
|
||||
damage = damage - healing
|
||||
-- Otherwise only: if attacker can regenerate, add the regeneration bonus
|
||||
else
|
||||
damage = damage - (attacker_info.regenerate or 0)
|
||||
end
|
||||
|
||||
if (damage < 0) then damage = 0 end
|
||||
|
@ -227,9 +234,9 @@ function ca_fast_attack_utils.damage_rating_unit(attacker_info, defender_info, a
|
|||
if (defender_level == 0) then defender_level = 0.5 end -- L0 units
|
||||
|
||||
local level_bonus = 0.
|
||||
if (attacker_info.max_experience - attacker_info.experience <= defender_level) then
|
||||
if (attacker_info.max_experience - attacker_info.experience <= defender_level * wesnoth.game_config.combat_experience) then
|
||||
level_bonus = 1. - att_stat.hp_chance[0]
|
||||
elseif (attacker_info.max_experience - attacker_info.experience <= defender_level * 8) then
|
||||
elseif (attacker_info.max_experience - attacker_info.experience <= defender_level * wesnoth.game_config.kill_experience) then
|
||||
level_bonus = (1. - att_stat.hp_chance[0]) * def_stat.hp_chance[0]
|
||||
end
|
||||
|
||||
|
@ -283,18 +290,22 @@ 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_on_village = gamedata.village_map[dsts[i][1]] and gamedata.village_map[dsts[i][1]][dsts[i][2]]
|
||||
local attacker_healing = 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
|
||||
attacker_rating = attacker_rating + ca_fast_attack_utils.damage_rating_unit(
|
||||
attacker_info, defender_info, att_stats[i], def_stat, attacker_on_village, cfg
|
||||
attacker_info, defender_info, att_stats[i], def_stat, 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_on_village = gamedata.village_map[defender_x] and gamedata.village_map[defender_x][defender_y]
|
||||
local defender_healing = gamedata.village_map[defender_x]
|
||||
and gamedata.village_map[defender_x][defender_y]
|
||||
and gamedata.village_map[defender_x][defender_y].healing
|
||||
local defender_rating = ca_fast_attack_utils.damage_rating_unit(
|
||||
defender_info, attacker_infos[1], def_stat, att_stats[1], defender_on_village, cfg
|
||||
defender_info, attacker_infos[1], def_stat, att_stats[1], defender_healing, cfg
|
||||
)
|
||||
|
||||
-- Now we add some extra ratings. They are positive for attacks that should be preferred
|
||||
|
@ -309,7 +320,7 @@ function ca_fast_attack_utils.attack_rating(attacker_infos, defender_info, dsts,
|
|||
|
||||
-- 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_on_village then
|
||||
if defender_healing then
|
||||
extra_rating = extra_rating + 10.
|
||||
end
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue