local H = wesnoth.require "lua/helper.lua" local W = H.set_wml_action_metatable {} local AH = wesnoth.require "ai/lua/ai_helper.lua" local LS = wesnoth.require "lua/location_set.lua" local function get_forest_animals(cfg) -- We want the deer/rabbits to move first, tuskers afterward local deer_type = cfg.deer_type or "no_unit_of_this_type" local rabbit_type = cfg.rabbit_type or "no_unit_of_this_type" local forest_animals = AH.get_units_with_moves { side = wesnoth.current.side, type = deer_type .. ',' .. rabbit_type } local tusker_type = cfg.tusker_type or "no_unit_of_this_type" local all_tuskers = wesnoth.get_units { side = wesnoth.current.side, type = tusker_type } for _,tusker in ipairs(all_tuskers) do if (tusker.moves > 0) then table.insert(forest_animals, tusker) end end -- Tusklets get moved by this CA if there are no tuskers left if not all_tuskers[1] then local tusklet_type = cfg.tusklet_type or "no_unit_of_this_type" local tusklets = wesnoth.get_units { side = wesnoth.current.side, type = tusklet_type } for _,tusklet in ipairs(tusklets) do if (tusklet.moves > 0) then table.insert(forest_animals, tusklet) end end end return forest_animals end local ca_forest_animals_move = {} function ca_forest_animals_move:evaluation(ai, cfg) if get_forest_animals(cfg)[1] then return cfg.ca_score end return 0 end function ca_forest_animals_move:execution(ai, cfg) -- These animals run from any enemy local forest_animals = get_forest_animals(cfg) local enemies = wesnoth.get_units { { "filter_side", { { "enemy_of", {side = wesnoth.current.side } } } } } -- Get the locations of all the rabbit holes W.store_items { variable = 'holes_wml' } local all_items = H.get_variable_array('holes_wml') W.clear_variable { name = 'holes_wml' } -- If cfg.rabbit_hole_img is set, only items with that image or halo count as holes local holes if cfg.rabbit_hole_img then for _,item in ipairs(all_items) do if (item.image == cfg.rabbit_hole_img) or (item.halo == cfg.rabbit_hole_img) then table.insert(holes, item) end end else holes = all_items end local hole_map = LS.create() for _,hole in ipairs(holes) do hole_map:insert(hole.x, hole.y, 1) end -- Each unit moves independently for _,unit in ipairs(forest_animals) do -- Behavior is different depending on whether a predator is close or not local close_enemies = {} for _,enemy in ipairs(enemies) do if (H.distance_between(unit.x, unit.y, enemy.x, enemy.y) <= unit.max_moves+1) then table.insert(close_enemies, enemy) end end -- If no close enemies, do a random move local wander_terrain = cfg.filter_location or {} if (not close_enemies[1]) then local reach = AH.get_reachable_unocc(unit) local width, height = wesnoth.get_map_size() local wander_locs = wesnoth.get_locations { x = '1-' .. width, y = '1-' .. height, { "and", wander_terrain } } local locs_map = LS.of_pairs(wander_locs) local reachable_wander_terrain = {} reach:iter( function(x, y, v) if locs_map:get(x,y) then table.insert(reachable_wander_terrain, {x, y}) end end) -- Choose one of the possible locations at random if reachable_wander_terrain[1] then local rand = math.random(#reachable_wander_terrain) -- This is not a full move, as running away might happen next if (unit.x ~= reachable_wander_terrain[rand][1]) or (unit.y ~= reachable_wander_terrain[rand][2]) then AH.checked_move(ai, unit, reachable_wander_terrain[rand][1], reachable_wander_terrain[rand][2]) end else -- Or if no close reachable terrain was found, move toward the closest local min_dist, best_hex = 9e99 for _,loc in ipairs(wander_locs) do local dist = H.distance_between(loc[1], loc[2], unit.x, unit.y) if dist < min_dist then best_hex, min_dist = loc, dist end end if (best_hex) then local x,y = wesnoth.find_vacant_tile(best_hex[1], best_hex[2], unit) local next_hop = AH.next_hop(unit, x, y) if (unit.x ~= next_hop[1]) or (unit.y ~= next_hop[2]) then AH.checked_move(ai, unit, next_hop[1], next_hop[2]) end end end end -- Now we check for close enemies again, as we might just have moved within reach of some local close_enemies = {} if unit and unit.valid then for _,enemy in ipairs(enemies) do if (H.distance_between(unit.x, unit.y, enemy.x, enemy.y) <= unit.max_moves+1) then table.insert(close_enemies, enemy) end end end -- If there are close enemies, run away (and rabbits disappear into holes) local rabbit_type = cfg.rabbit_type or "no_unit_of_this_type" if close_enemies[1] then -- Calculate the hex that maximizes distance of unit from enemies -- Returns nil if the only hex that can be reached is the one the unit is on local farthest_hex = AH.find_best_move(unit, function(x, y) local rating = 0 for _,enemy in ipairs(close_enemies) do local dist = H.distance_between(enemy.x, enemy.y, x, y) rating = rating - 1 / dist^2 end -- If this is a rabbit, try to go for holes if (unit.type == rabbit_type) and hole_map:get(x, y) then rating = rating + 1000 -- But if possible, go to another hole if unit is on one if (x == unit.x) and (y == unit.y) then rating = rating - 10 end end return rating end) AH.movefull_stopunit(ai, unit, farthest_hex) -- If this is a rabbit ending on a hole -> disappears if unit and unit.valid and (unit.type == rabbit_type) and hole_map:get(farthest_hex[1], farthest_hex[2]) then local command = "wesnoth.put_unit(x1, y1)" ai.synced_command(command, farthest_hex[1], farthest_hex[2]) end end -- Finally, take moves away, as only partial move might have been done -- Also take attacks away, as these units never attack if unit and unit.valid then AH.checked_stopunit_all(ai, unit) end end end return ca_forest_animals_move