diff --git a/data/ai/lua/ca_castle_switch.lua b/data/ai/lua/ca_castle_switch.lua index 9a103213ce4..dc90ed1f03e 100644 --- a/data/ai/lua/ca_castle_switch.lua +++ b/data/ai/lua/ca_castle_switch.lua @@ -4,7 +4,7 @@ local AH = wesnoth.require "ai/lua/ai_helper.lua" local M = wesnoth.map local CS_leader_score --- Note that leader_target is also needed by the recruiting CA, so it must be stored in 'data' +-- Note that CS_leader and CS_leader_target are also needed by the recruiting CA, so they must be stored in 'data' local function get_reachable_enemy_leaders(unit, avoid_map) -- We're cheating a little here and also find hidden enemy leaders. That's @@ -38,15 +38,16 @@ function ca_castle_switch:evaluation(cfg, data, filter_own) return 0 end - local leader = AH.get_units_with_moves({ + local leaders = AH.get_units_with_moves({ side = wesnoth.current.side, canrecruit = 'yes', formula = '(movement_left = total_movement) and (hitpoints = max_hitpoints)', { "and", filter_own } - }, true)[1] - if not leader then + }, true) + + if (not leaders[1]) then -- CA is irrelevant if no leader or the leader may have moved from another CA - data.leader_target = nil + data.CS_leader, data.CS_leader_target = nil, nil if AH.print_eval() then AH.done_eval_messages(start_time, ca_name) end return 0 end @@ -55,142 +56,156 @@ function ca_castle_switch:evaluation(cfg, data, filter_own) local avoid_map = AH.get_avoid_map(ai, nil, true) - if data.leader_target and wesnoth.sides[wesnoth.current.side].gold >= cheapest_unit_cost then + if data.CS_leader and wesnoth.sides[wesnoth.current.side].gold >= cheapest_unit_cost then -- make sure move is still valid - local path, cost = AH.find_path_with_avoid(leader, data.leader_target[1], data.leader_target[2], avoid_map) - local next_hop = AH.next_hop(leader, nil, nil, { path = path, avoid_map = avoid_map }) - if next_hop and next_hop[1] == data.leader_target[1] - and next_hop[2] == data.leader_target[2] then + local path, cost = AH.find_path_with_avoid(data.CS_leader, data.CS_leader_target[1], data.CS_leader_target[2], avoid_map) + local next_hop = AH.next_hop(data.CS_leader, nil, nil, { path = path, avoid_map = avoid_map }) + if next_hop and next_hop[1] == data.CS_leader_target[1] + and next_hop[2] == data.CS_leader_target[2] + then return CS_leader_score else - data.leader_target = nil + data.CS_leader, data.CS_leader_target = nil, nil end end - local keeps = AH.get_locations_no_borders { - terrain = 'K*,K*^*,*^K*', -- Keeps - { "not", { {"filter", {}} }}, -- That have no unit - { "not", { radius = 6, {"filter", { canrecruit = 'yes', - { "filter_side", { { "enemy_of", {side = wesnoth.current.side} } } } - }} }}, -- That are not too close to an enemy leader - { "not", { - x = leader.x, y = leader.y, terrain = 'K*,K*^*,*^K*', - radius = 3, - { "filter_radius", { terrain = 'C*,K*,C*^*,K*^*,*^K*,*^C*' } } - }}, -- That are not close and connected to a keep the leader is on - { "filter_adjacent_location", { - terrain = 'C*,K*,C*^*,K*^*,*^K*,*^C*' - }} -- That are not one-hex keeps - } - if #keeps < 1 then - -- Skip if there aren't extra keeps to evaluate - -- In this situation we'd only switch keeps if we were running away - if AH.print_eval() then AH.done_eval_messages(start_time, ca_name) end - return 0 - end - - local enemy_leaders = get_reachable_enemy_leaders(leader, avoid_map) - -- Look for the best keep - local best_score, best_loc, best_turns, best_path = 0, {}, 3 - for i,loc in ipairs(keeps) do - -- Only consider keeps within 2 turns movement - local path, cost = AH.find_path_with_avoid(leader, loc[1], loc[2], avoid_map) - local score = 0 - -- Prefer closer keeps to enemy - local turns = math.ceil(cost/leader.max_moves) - if turns <= 2 then - score = 1/turns - for j,e in ipairs(enemy_leaders) do - score = score + 1 / M.distance_between(loc[1], loc[2], e.x, e.y) - end + local overall_best_score = 0 + for _,leader in ipairs(leaders) do + local best_score, best_loc, best_turns, best_path = 0, {}, 3 + local keeps = AH.get_locations_no_borders { + terrain = 'K*,K*^*,*^K*', -- Keeps + { "not", { {"filter", {}} }}, -- That have no unit + { "not", { radius = 6, {"filter", { canrecruit = 'yes', + { "filter_side", { { "enemy_of", {side = wesnoth.current.side} } } } + }} }}, -- That are not too close to an enemy leader + { "not", { + x = leader.x, y = leader.y, terrain = 'K*,K*^*,*^K*', + radius = 3, + { "filter_radius", { terrain = 'C*,K*,C*^*,K*^*,*^K*,*^C*' } } + }}, -- That are not close and connected to a keep the leader is on + { "filter_adjacent_location", { + terrain = 'C*,K*,C*^*,K*^*,*^K*,*^C*' + }} -- That are not one-hex keeps + } + if #keeps < 1 then + -- Skip if there aren't extra keeps to evaluate + -- In this situation we'd only switch keeps if we were running away + if AH.print_eval() then AH.done_eval_messages(start_time, ca_name) end + return 0 + end - if score > best_score then - best_score = score - best_loc = loc - best_turns = turns - best_path = path + local enemy_leaders = get_reachable_enemy_leaders(leader, avoid_map) + + for i,loc in ipairs(keeps) do + -- Only consider keeps within 2 turns movement + local path, cost = AH.find_path_with_avoid(leader, loc[1], loc[2], avoid_map) + local score = 0 + -- Prefer closer keeps to enemy + local turns = math.ceil(cost/leader.max_moves) + if turns <= 2 then + score = 1/turns + for j,e in ipairs(enemy_leaders) do + score = score + 1 / M.distance_between(loc[1], loc[2], e.x, e.y) + end + + if score > best_score then + best_score = score + best_loc = loc + best_turns = turns + best_path = path + end end end - end - -- If we're on a keep, - -- don't move to another keep unless it's much better when uncaptured villages are present - if best_score > 0 and wesnoth.get_terrain_info(wesnoth.get_terrain(leader.x, leader.y)).keep then - local close_unowned_village = (wesnoth.get_villages { - { "and", { - x = leader.x, - y = leader.y, - radius = leader.max_moves - }}, - owner_side = 0 - })[1] - if close_unowned_village then - local score = 1/best_turns - for j,e in ipairs(enemy_leaders) do - -- count all distances as three less than they actually are - score = score + 1 / (M.distance_between(leader.x, leader.y, e.x, e.y) - 3) - end + -- If we're on a keep, + -- don't move to another keep unless it's much better when uncaptured villages are present + if best_score > 0 and wesnoth.get_terrain_info(wesnoth.get_terrain(leader.x, leader.y)).keep then + local close_unowned_village = (wesnoth.get_villages { + { "and", { + x = leader.x, + y = leader.y, + radius = leader.max_moves + }}, + owner_side = 0 + })[1] + if close_unowned_village then + local score = 1/best_turns + for j,e in ipairs(enemy_leaders) do + -- count all distances as three less than they actually are + score = score + 1 / (M.distance_between(leader.x, leader.y, e.x, e.y) - 3) + end - if score > best_score then - best_score = 0 + if score > best_score then + best_score = 0 + end end end - end - if best_score > 0 then - local next_hop = AH.next_hop(leader, nil, nil, { path = best_path, avoid_map = avoid_map }) + if best_score > 0 then + local next_hop = AH.next_hop(leader, nil, nil, { path = best_path, avoid_map = avoid_map }) - if next_hop and ((next_hop[1] ~= leader.x) or (next_hop[2] ~= leader.y)) then - -- See if there is a nearby village that can be captured without delaying progress - local close_villages = wesnoth.get_villages( { - { "and", { x = next_hop[1], y = next_hop[2], radius = leader.max_moves }}, - owner_side = 0 }) - for i,loc in ipairs(close_villages) do - local path_village, cost_village = AH.find_path_with_avoid(leader, loc[1], loc[2], avoid_map) - if cost_village <= leader.moves then - local dummy_leader = leader:clone() - dummy_leader.x = loc[1] - dummy_leader.y = loc[2] - local path_keep, cost_keep = wesnoth.find_path(dummy_leader, best_loc[1], best_loc[2], avoid_map) - local turns_from_keep = math.ceil(cost_keep/leader.max_moves) - if turns_from_keep < best_turns - or (turns_from_keep == 1 and wesnoth.sides[wesnoth.current.side].gold < cheapest_unit_cost) - then - -- There is, go there instead - next_hop = loc - break + if next_hop and ((next_hop[1] ~= leader.x) or (next_hop[2] ~= leader.y)) then + -- See if there is a nearby village that can be captured without delaying progress + local close_villages = wesnoth.get_villages( { + { "and", { x = next_hop[1], y = next_hop[2], radius = leader.max_moves }}, + owner_side = 0 }) + for i,loc in ipairs(close_villages) do + local path_village, cost_village = AH.find_path_with_avoid(leader, loc[1], loc[2], avoid_map) + if cost_village <= leader.moves then + local dummy_leader = leader:clone() + dummy_leader.x = loc[1] + dummy_leader.y = loc[2] + local path_keep, cost_keep = wesnoth.find_path(dummy_leader, best_loc[1], best_loc[2], avoid_map) + local turns_from_keep = math.ceil(cost_keep/leader.max_moves) + if turns_from_keep < best_turns + or (turns_from_keep == 1 and wesnoth.sides[wesnoth.current.side].gold < cheapest_unit_cost) + then + -- There is, go there instead + next_hop = loc + break + end end end end - end - data.leader_target = next_hop - -- if we're on a keep, wait until there are no movable units on the castle before moving off - CS_leader_score = 195000 - if wesnoth.get_terrain_info(wesnoth.get_terrain(leader.x, leader.y)).keep then - local castle = AH.get_locations_no_borders { - { "and", { - x = leader.x, y = leader.y, radius = 200, - { "filter_radius", { terrain = 'C*,K*,C*^*,K*^*,*^K*,*^C*' } } - }} - } - local should_wait = false - for i,loc in ipairs(castle) do - local unit = wesnoth.units.get(loc[1], loc[2]) - if (not AH.is_visible_unit(wesnoth.current.side, unit)) then - should_wait = false - elseif unit.moves > 0 then - should_wait = true - break + -- if we're on a keep, wait until there are no movable units on the castle before moving off + local leader_score = 195000 + if wesnoth.get_terrain_info(wesnoth.get_terrain(leader.x, leader.y)).keep then + local castle = AH.get_locations_no_borders { + { "and", { + x = leader.x, y = leader.y, radius = 200, + { "filter_radius", { terrain = 'C*,K*,C*^*,K*^*,*^K*,*^C*' } } + }} + } + local should_wait = false + for i,loc in ipairs(castle) do + local unit = wesnoth.units.get(loc[1], loc[2]) + if (not AH.is_visible_unit(wesnoth.current.side, unit)) then + should_wait = false + elseif unit.moves > 0 then + should_wait = true + break + end + end + if should_wait then + leader_score = 15000 end end - if should_wait then - CS_leader_score = 15000 + + best_score = best_score + leader_score + + if (best_score > overall_best_score) then + overall_best_score = best_score + CS_leader_score = leader_score + data.CS_leader = leader + data.CS_leader_target = next_hop end end + end + if (overall_best_score > 0) then if AH.print_eval() then AH.done_eval_messages(start_time, ca_name) end return CS_leader_score end @@ -200,17 +215,11 @@ function ca_castle_switch:evaluation(cfg, data, filter_own) end function ca_castle_switch:execution(cfg, data, filter_own) - local leader = AH.get_units_with_moves({ - side = wesnoth.current.side, - canrecruit = 'yes', - { "and", filter_own } - }, true)[1] - if AH.print_exec() then AH.print_ts(' Executing castle_switch CA') end if AH.show_messages() then wesnoth.wml_actions.message { speaker = leader.id, message = 'Switching castles' } end - AH.checked_move(ai, leader, data.leader_target[1], data.leader_target[2]) - data.leader_target = nil + AH.checked_move(ai, data.CS_leader, data.CS_leader_target[1], data.CS_leader_target[2]) + data.CS_leader, data.CS_leader_target = nil end return ca_castle_switch diff --git a/data/ai/lua/ca_recruit_rushers.lua b/data/ai/lua/ca_recruit_rushers.lua index ec2a5244a92..3a0d695ad3c 100644 --- a/data/ai/lua/ca_recruit_rushers.lua +++ b/data/ai/lua/ca_recruit_rushers.lua @@ -20,8 +20,8 @@ if ca_castle_switch then params.leader_takes_village = (function() if ca_castle_switch:evaluation({}, dummy_engine.data) > 0 then local take_village = #(wesnoth.get_villages { - x = dummy_engine.data.leader_target[1], - y = dummy_engine.data.leader_target[2] + x = dummy_engine.data.CS_leader_target[1], + y = dummy_engine.data.CS_leader_target[2] }) > 0 return take_village end