------- 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