mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-02 20:15:58 +00:00

This is done for consistency with similar functions in mainline, and so that the function actual returns what it name says. This breaks backward compatibility, but note that the only practical difference for that is the order in which the arguments are returned, as both 'closest_enemy' and 'location' contain the enemy location in .x/.y format.
141 lines
5.5 KiB
Lua
141 lines
5.5 KiB
Lua
------- Grab Villages CA --------------
|
|
|
|
local AH = wesnoth.require "ai/lua/ai_helper.lua"
|
|
local BC = wesnoth.require "ai/lua/battle_calcs.lua"
|
|
local M = wesnoth.map
|
|
|
|
local GV_unit, GV_village
|
|
|
|
local ca_grab_villages = {}
|
|
|
|
function ca_grab_villages:evaluation(cfg, data)
|
|
local start_time, ca_name = wesnoth.get_time_stamp() / 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' }
|
|
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 villages = wesnoth.get_villages()
|
|
-- Just in case:
|
|
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
|
|
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.get_unit(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.get_village_owner(v[1], v[2])
|
|
if (not owner) then
|
|
village_rating = village_rating + 10000
|
|
else
|
|
if wesnoth.is_enemy(owner, wesnoth.current.side) then village_rating = village_rating + 20000 end
|
|
end
|
|
|
|
local _, enemy_distance_from_village = AH.get_closest_enemy(v)
|
|
|
|
-- 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
|
|
local path, cost = wesnoth.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 _, enemy_distance_from_unit = AH.get_closest_enemy({u.x, u.y})
|
|
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
|