mirror of
https://github.com/wesnoth/wesnoth
synced 2025-04-29 10:08:34 +00:00

Insert these calls in loops that do expensive calculations for the CAs of the default and experimental AIs.
156 lines
6.1 KiB
Lua
156 lines
6.1 KiB
Lua
------- Grab Villages CA --------------
|
|
|
|
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
|
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
|
|
local LS = wesnoth.require "location_set"
|
|
local M = wesnoth.map
|
|
|
|
local GV_unit, GV_village
|
|
|
|
local ca_grab_villages = {}
|
|
|
|
function ca_grab_villages:evaluation(cfg, data, filter_own)
|
|
local start_time, ca_name = wesnoth.ms_since_init() / 1000., 'grab_villages'
|
|
if AH.print_eval() then AH.print_ts(' - Evaluating grab_villages CA:') end
|
|
|
|
-- Check if there are units with moves left
|
|
local units = AH.get_units_with_moves({
|
|
side = wesnoth.current.side,
|
|
canrecruit = 'no',
|
|
{ "and", filter_own }
|
|
}, true)
|
|
if (not units[1]) then
|
|
if AH.print_eval() then AH.done_eval_messages(start_time, ca_name) end
|
|
return 0
|
|
end
|
|
|
|
local enemies = AH.get_attackable_enemies()
|
|
|
|
local avoid_map = LS.of_pairs(ai.aspects.avoid)
|
|
|
|
local all_villages, villages = wesnoth.map.find{gives_income = true}, {}
|
|
for _,village in ipairs(all_villages) do
|
|
if (not avoid_map:get(village[1], village[2])) then
|
|
table.insert(villages, village)
|
|
end
|
|
end
|
|
|
|
if (not villages[1]) then
|
|
if AH.print_eval() then AH.done_eval_messages(start_time, ca_name) end
|
|
return 0
|
|
end
|
|
|
|
-- First check if attacks are possible for any unit
|
|
local return_value = 191000
|
|
-- 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_value = 90000
|
|
break
|
|
end
|
|
end
|
|
|
|
-- Also find which locations can be attacked by enemies
|
|
local enemy_attack_map = BC.get_attack_map(enemies).units
|
|
|
|
-- Now we go through the villages and units
|
|
local max_rating, best_village, best_unit = - math.huge, nil, nil
|
|
local village_ratings = {}
|
|
for j,v in ipairs(villages) do
|
|
-- First collect all information that only depends on the village
|
|
local village_rating = 0 -- This is the unit independent rating
|
|
|
|
local unit_in_way = wesnoth.units.get(v[1], v[2])
|
|
|
|
-- If an enemy can get within one move of the village, we want to hold it
|
|
if enemy_attack_map:get(v[1], v[2]) then
|
|
village_rating = village_rating + 100
|
|
end
|
|
|
|
-- Unowned and enemy-owned villages get a large bonus
|
|
local owner = wesnoth.map.get_owner(v)
|
|
if (not owner) then
|
|
village_rating = village_rating + 10000
|
|
else
|
|
if wesnoth.sides.is_enemy(owner, wesnoth.current.side) then village_rating = village_rating + 20000 end
|
|
end
|
|
|
|
local village_closest_enemy, enemy_distance_from_village = AH.get_closest_enemy(v)
|
|
if (not village_closest_enemy) then enemy_distance_from_village = 0 end
|
|
|
|
-- Now we go on to the unit-dependent rating
|
|
local best_unit_rating = - math.huge
|
|
local reachable = false
|
|
for i,u in ipairs(units) do
|
|
-- Skip villages that have units other than 'u' itself on them
|
|
local village_occupied = false
|
|
if AH.is_visible_unit(wesnoth.current.side, unit_in_way) and ((unit_in_way ~= u)) then
|
|
village_occupied = true
|
|
end
|
|
|
|
-- Rate all villages that can be reached and are unoccupied by other units
|
|
if (not village_occupied) then
|
|
-- Path finding is expensive, so we do a first cut simply by distance
|
|
-- There is no way a unit can get to the village if the distance is greater than its moves
|
|
local dist = M.distance_between(u.x, u.y, v[1], v[2])
|
|
if (dist <= u.moves) then
|
|
wesnoth.interface.handle_user_interact()
|
|
local path, cost = wesnoth.paths.find_path(u, v[1], v[2])
|
|
if (cost <= u.moves) then
|
|
village_rating = village_rating - 1
|
|
reachable = true
|
|
local rating = 0
|
|
|
|
-- Prefer strong units if enemies can reach the village, injured units otherwise
|
|
if enemy_attack_map:get(v[1], v[2]) then
|
|
rating = rating + u.hitpoints
|
|
else
|
|
rating = rating + u.max_hitpoints - u.hitpoints
|
|
end
|
|
|
|
-- Prefer not backtracking and moving more distant units to capture villages
|
|
local unit_closest_enemy, enemy_distance_from_unit = AH.get_closest_enemy({u.x, u.y})
|
|
if (not unit_closest_enemy) then enemy_distance_from_unit = 0 end
|
|
rating = rating - (enemy_distance_from_village + enemy_distance_from_unit)/5
|
|
|
|
if (rating > best_unit_rating) then
|
|
best_unit_rating, best_unit = rating, u
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
village_ratings[v] = {village_rating, best_unit, reachable}
|
|
end
|
|
for j,v in ipairs(villages) do
|
|
local rating = village_ratings[v][1]
|
|
if village_ratings[v][3] and rating > max_rating then
|
|
max_rating, best_village, best_unit = rating, v, village_ratings[v][2]
|
|
end
|
|
end
|
|
|
|
if best_village then
|
|
GV_unit, GV_village = best_unit, best_village
|
|
if (max_rating >= 1000) then
|
|
if AH.print_eval() then AH.done_eval_messages(start_time, ca_name) end
|
|
return return_value
|
|
else
|
|
if AH.print_eval() then AH.done_eval_messages(start_time, ca_name) end
|
|
return 0
|
|
end
|
|
end
|
|
if AH.print_eval() then AH.done_eval_messages(start_time, ca_name) end
|
|
return 0
|
|
end
|
|
|
|
function ca_grab_villages:execution(cfg, data)
|
|
if AH.print_exec() then AH.print_ts(' Executing grab_villages CA') end
|
|
if AH.show_messages() then wesnoth.wml_actions.message { speaker = GV_unit.id, message = 'Grab villages' } end
|
|
|
|
AH.movefull_stopunit(ai, GV_unit, GV_village)
|
|
GV_unit, GV_village = nil, nil
|
|
end
|
|
|
|
return ca_grab_villages
|