Skip to content

Commit

Permalink
Fix segfaults and a few other issues in wesnoth.find_path
Browse files Browse the repository at this point in the history
Co-authored-by: mattsc
  • Loading branch information
CelticMinstrel committed Feb 19, 2021
1 parent 05b2ea2 commit 56bdd42
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 113 deletions.
161 changes: 98 additions & 63 deletions data/ai/lua/ai_helper.lua

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion data/ai/lua/ca_castle_switch.lua
Expand Up @@ -20,7 +20,7 @@ local function get_reachable_enemy_leaders(unit, avoid_map)
for _,e in ipairs(potential_enemy_leaders) do
-- Cannot use AH.find_path_with_avoid() here as there might be enemies all around the enemy leader
if (not avoid_map:get(e.x, e.y)) then
local path, cost = wesnoth.find_path(unit, e.x, e.y, { ignore_units = true, viewing_side = 0 })
local path, cost = wesnoth.find_path(unit, e.x, e.y, { ignore_units = true, ignore_visibility = true })
if cost < AH.no_path then
table.insert(enemy_leaders, e)
end
Expand Down
10 changes: 5 additions & 5 deletions data/ai/lua/generic_recruit_engine.lua
Expand Up @@ -587,7 +587,7 @@ return {
-- If the recruited unit cannot reach the target hex, return it to the pool of targets
if recruit_data.recruit.target_hex and recruit_data.recruit.target_hex[1] then
local unit = wesnoth.units.get(recruit_data.recruit.best_hex[1], recruit_data.recruit.best_hex[2])
local path, cost = wesnoth.find_path(unit, recruit_data.recruit.target_hex[1], recruit_data.recruit.target_hex[2], {viewing_side=0, max_cost=unit.max_moves+1})
local path, cost = wesnoth.find_path(unit, recruit_data.recruit.target_hex[1], recruit_data.recruit.target_hex[2], {ignore_visibility=true, max_cost=unit.max_moves+1})
if cost > unit.max_moves then
-- The last village added to the list should be the one we tried to aim for, check anyway
local last = #recruit_data.castle.assigned_villages_x
Expand Down Expand Up @@ -676,7 +676,7 @@ return {
local target_hex = recruit_data.recruit.target_hex

local reference_hex = target_hex[1] and target_hex or best_hex
local enemy_location, distance_to_enemy = AH.get_closest_enemy(reference_hex, wesnoth.current.side, { viewing_side = 0 })
local enemy_location, distance_to_enemy = AH.get_closest_enemy(reference_hex, wesnoth.current.side, { ignore_visibility = true })

-- If no enemy is on the map, then we first use closest enemy start hex,
-- and if that does not exist either, a location mirrored w.r.t the center of the map
Expand Down Expand Up @@ -731,7 +731,7 @@ return {
random_gender = false
}
if target_hex[1] then
local path, cost = wesnoth.find_path(recruit_unit, target_hex[1], target_hex[2], {viewing_side=0, max_cost=wesnoth.unit_types[recruit_id].max_moves+1})
local path, cost = wesnoth.find_path(recruit_unit, target_hex[1], target_hex[2], {ignore_visibility=true, max_cost=wesnoth.unit_types[recruit_id].max_moves+1})
if cost > wesnoth.unit_types[recruit_id].max_moves then
-- Unit cost is effectively higher if cannot reach the village
efficiency_index = 2
Expand Down Expand Up @@ -854,7 +854,7 @@ return {
if target_hex[1] then
recruitable_units[recruit_id].x = best_hex[1]
recruitable_units[recruit_id].y = best_hex[2]
local path, cost = wesnoth.find_path(recruitable_units[recruit_id], target_hex[1], target_hex[2], {viewing_side=0, max_cost=wesnoth.unit_types[recruit_id].max_moves+1})
local path, cost = wesnoth.find_path(recruitable_units[recruit_id], target_hex[1], target_hex[2], {ignore_visibility=true, max_cost=wesnoth.unit_types[recruit_id].max_moves+1})
if cost > wesnoth.unit_types[recruit_id].max_moves then
-- penalty if the unit can't reach the target village
bonus = bonus - 0.2
Expand Down Expand Up @@ -993,7 +993,7 @@ return {
local key = unit.type .. '_' .. v[1] .. '-' .. v[2] .. '_' .. c[1] .. '-' .. c[2]
local path, unit_distance
if (not recruit_data.unit_distances[key]) then
path, unit_distance = wesnoth.find_path(unit, c[1], c[2], {viewing_side=0, max_cost=fastest_unit_speed+1})
path, unit_distance = wesnoth.find_path(unit, c[1], c[2], {ignore_visibility=true, max_cost=fastest_unit_speed+1})
recruit_data.unit_distances[key] = unit_distance
else
unit_distance = recruit_data.unit_distances[key]
Expand Down
6 changes: 3 additions & 3 deletions data/ai/micro_ais/cas/ca_fast_combat.lua
Expand Up @@ -58,7 +58,7 @@ function ca_fast_combat:evaluation(cfg, data)
end

-- Exclude hidden enemies, except if attack_hidden_enemies=yes is set in [micro_ai] tag
local viewing_side = wesnoth.current.side
local viewing_side, ignore_visibility = wesnoth.current.side, false
if (not cfg.attack_hidden_enemies) then
local hidden_enemies = AH.get_live_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
Expand All @@ -69,7 +69,7 @@ function ca_fast_combat:evaluation(cfg, data)
enemy_map:remove(e.x, e.y)
end
else
viewing_side = 0
ignore_visibility = true
end

local aggression = ai.aspects.aggression
Expand All @@ -84,7 +84,7 @@ function ca_fast_combat:evaluation(cfg, data)
if unit and unit.valid and (unit.attacks_left > 0) and (#unit.attacks > 0) then
local unit_info = FAU.get_unit_info(unit, gamedata)
local unit_copy = FAU.get_unit_copy(unit.id, gamedata)
local attacks = AH.get_attacks({ unit }, { include_occupied = cfg.include_occupied_attack_hexes, viewing_side = viewing_side })
local attacks = AH.get_attacks({ unit }, { include_occupied = cfg.include_occupied_attack_hexes, viewing_side = viewing_side, ignore_visibility = ignore_visibility })

if (#attacks > 0) then
local max_rating, best_target, best_dst = - math.huge
Expand Down
6 changes: 3 additions & 3 deletions data/ai/micro_ais/cas/ca_fast_combat_leader.lua
Expand Up @@ -51,7 +51,7 @@ function ca_fast_combat_leader:evaluation(cfg, data)
end

-- Exclude hidden enemies, except if attack_hidden_enemies=yes is set in [micro_ai] tag
local viewing_side = wesnoth.current.side
local viewing_side, ignore_visibility = wesnoth.current.side, false
if (not cfg.attack_hidden_enemies) then
local hidden_enemies = AH.get_live_units {
{ "filter_side", { { "enemy_of", { side = wesnoth.current.side } } } },
Expand All @@ -62,7 +62,7 @@ function ca_fast_combat_leader:evaluation(cfg, data)
enemy_map:remove(e.x, e.y)
end
else
viewing_side = 0
ignore_visibility = true
end

local aggression = ai.aspects.aggression
Expand Down Expand Up @@ -119,7 +119,7 @@ function ca_fast_combat_leader:evaluation(cfg, data)
end
end

local attacks = AH.get_attacks({ leader }, { include_occupied = cfg.include_occupied_attack_hexes, viewing_side = viewing_side })
local attacks = AH.get_attacks({ leader }, { include_occupied = cfg.include_occupied_attack_hexes, viewing_side = viewing_side, ignore_visibility = ignore_visibility })

if (#attacks > 0) then
local max_rating, best_target, best_dst = - math.huge
Expand Down
Expand Up @@ -717,7 +717,7 @@
local ai_helper = wesnoth.require "ai/lua/ai_helper.lua"
local delf = wesnoth.units.find_on_map { id = 'Delfador' }[1]
local sceptre_loc= wesnoth.special_locations.sceptre
local path = wesnoth.find_path(delf, sceptre_loc[1], sceptre_loc[2], {ignore_units = true, viewing_side = 0}) -- # wmllint: noconvert
local path = wesnoth.find_path(delf, sceptre_loc[1], sceptre_loc[2], {ignore_units = true, ignore_visibility = true}) -- # wmllint: noconvert
_ = wesnoth.textdomain 'wesnoth-httt'

local dirs = { _"I sense the path to the Sceptre is to the east of me.",
Expand Down
7 changes: 6 additions & 1 deletion data/lua/wml-tags.lua
Expand Up @@ -437,7 +437,12 @@ function wml_actions.store_reachable_locations(cfg)
local range = cfg.range or "movement"
local moves = cfg.moves or "current"
local variable = cfg.variable or wml.error "[store_reachable_locations] missing required variable= key"
local reach_param = { viewing_side = cfg.viewing_side or 0 }
local reach_param = { viewing_side = cfg.viewing_side }
if cfg.viewing_side == 0 then
wml.error "[store_reachable_locations] invalid viewing_side"
elseif cfg.viewing_side == nil then
reach_param.ignore_visibility = true
end
if range == "vision" then
moves = "max"
reach_param.ignore_units = true
Expand Down
15 changes: 9 additions & 6 deletions data/lua/wml/find_path.lua
Expand Up @@ -23,7 +23,7 @@ function wesnoth.wml_actions.find_path(cfg)
end

local allow_multiple_turns = cfg.allow_multiple_turns
local viewing_side
local ignore_visibility = not cfg.check_visibility

local nearest_by_cost = true
local nearest_by_distance = false
Expand All @@ -38,8 +38,6 @@ function wesnoth.wml_actions.find_path(cfg)
nearest_by_steps = true
end

if not cfg.check_visibility then viewing_side = 0 end -- if check_visiblity then shroud is taken in account

-- only the first location with the lowest distance and lowest movement cost will match.
local locations = wesnoth.get_locations(filter_location)

Expand All @@ -57,7 +55,12 @@ function wesnoth.wml_actions.find_path(cfg)
else
local distance = wesnoth.map.distance_between ( unit.x, unit.y, location[1], location[2] )
-- if we pass an unreachable location then an empty path and high value cost will be returned
local path, cost = wesnoth.find_path( unit, location[1], location[2], { max_cost = max_cost, ignore_units = ignore_units, ignore_teleport = ignore_teleport, viewing_side = viewing_side } )
local path, cost = wesnoth.find_path( unit, location[1], location[2], {
max_cost = max_cost,
ignore_units = ignore_units,
ignore_teleport = ignore_teleport,
ignore_visibility = ignore_visibility
} )

if #path == 0 or cost >= 42424241 then
-- it's not a reachable hex. 42424242 is the high value returned for unwalkable or busy terrains
Expand Down Expand Up @@ -104,7 +107,7 @@ function wesnoth.wml_actions.find_path(cfg)
max_cost = max_cost,
ignore_units = ignore_units,
ignore_teleport = ignore_teleport,
viewing_side = viewing_side
ignore_visibility = ignore_visibility
})
local turns

Expand Down Expand Up @@ -142,7 +145,7 @@ function wesnoth.wml_actions.find_path(cfg)
max_cost = max_cost,
ignore_units = ignore_units,
ignore_teleport = ignore_teleport,
viewing_side = viewing_side
ignore_visibility = ignore_visibility
} )
local sub_turns

Expand Down
62 changes: 32 additions & 30 deletions src/scripting/game_lua_kernel.cpp
Expand Up @@ -1695,11 +1695,13 @@ int game_lua_kernel::intf_find_path(lua_State *L)
int arg = 1;
map_location src, dst;
const unit* u = nullptr;
int viewing_side = 0;

if (lua_isuserdata(L, arg))
{
u = &luaW_checkunit(L, arg);
src = u->get_location();
viewing_side = u->side();
++arg;
}
else
Expand All @@ -1708,6 +1710,7 @@ int game_lua_kernel::intf_find_path(lua_State *L)
unit_map::const_unit_iterator ui = units().find(src);
if (ui.valid()) {
u = ui.get_shared_ptr().get();
viewing_side = u->side();
}
++arg;
}
Expand All @@ -1721,26 +1724,30 @@ int game_lua_kernel::intf_find_path(lua_State *L)
return luaL_argerror(L, arg - 2, "invalid location");

const gamemap &map = board().map();
int viewing_side = 0;
bool ignore_units = false, see_all = false, ignore_teleport = false;
double stop_at = 10000;
std::unique_ptr<pathfind::cost_calculator> calc;

if (lua_istable(L, arg))
{
ignore_units = luaW_table_get_def<bool>(L, arg, "ignore_units", false);

see_all = luaW_table_get_def<bool>(L, arg, "ignore_visibility", false);
ignore_teleport = luaW_table_get_def<bool>(L, arg, "ignore_teleport", false);

stop_at = luaW_table_get_def<double>(L, arg, "stop_at", stop_at);
stop_at = luaW_table_get_def<double>(L, arg, "max_cost", stop_at);


lua_pushstring(L, "viewing_side");
lua_rawget(L, arg);
if (!lua_isnil(L, -1)) {
int i = luaL_checkinteger(L, -1);
if (i >= 1 && i <= static_cast<int>(teams().size())) viewing_side = i;
else see_all = true;
else {
// If there's a unit, we have a valid side, so fall back to legacy behaviour.
// If we don't have a unit, legacy behaviour would be a crash, so let's not.
if(u) see_all = true;
deprecated_message("wesnoth.find_path with viewing_side=0 (or an invalid side)", DEP_LEVEL::FOR_REMOVAL, {1, 17, 0}, "To consider fogged and hidden units, use ignore_visibility=true instead.");
}
}
lua_pop(L, 1);

Expand All @@ -1757,20 +1764,22 @@ int game_lua_kernel::intf_find_path(lua_State *L)
calc.reset(new lua_pathfind_cost_calculator(L, arg));
}

const team& viewing_team = viewing_side
? board().get_team(viewing_side)
: board().get_team(u->side());

pathfind::teleport_map teleport_locations;

if(!ignore_teleport) {
teleport_locations = pathfind::get_teleport_locations(*u, viewing_team, see_all, ignore_units);
if(viewing_side == 0) {
return luaL_error(L, "wesnoth.find_path: ignore_teleport=false requires a valid viewing_side");
} else {
teleport_locations = pathfind::get_teleport_locations(*u, board().get_team(viewing_side), see_all, ignore_units);
}
}

if (!calc) {
if (!u) return luaL_argerror(L, 1, "unit not found");
if(!u) {
return luaL_argerror(L, 1, "unit not found OR custom cost function not provided");
}

calc.reset(new pathfind::shortest_path_calculator(*u, viewing_team,
calc.reset(new pathfind::shortest_path_calculator(*u, board().get_team(viewing_side),
teams(), map, ignore_units, false, see_all));
}

Expand Down Expand Up @@ -1819,40 +1828,33 @@ int game_lua_kernel::intf_find_reach(lua_State *L)
++arg;
}

int viewing_side = 0;
int viewing_side = u->side();
bool ignore_units = false, see_all = false, ignore_teleport = false;
int additional_turns = 0;

if (lua_istable(L, arg))
{
lua_pushstring(L, "ignore_units");
lua_rawget(L, arg);
ignore_units = luaW_toboolean(L, -1);
lua_pop(L, 1);

lua_pushstring(L, "ignore_teleport");
lua_rawget(L, arg);
ignore_teleport = luaW_toboolean(L, -1);
lua_pop(L, 1);

lua_pushstring(L, "additional_turns");
lua_rawget(L, arg);
additional_turns = lua_tointeger(L, -1);
lua_pop(L, 1);
ignore_units = luaW_table_get_def<bool>(L, arg, "ignore_units", false);
see_all = luaW_table_get_def<bool>(L, arg, "ignore_visibility", false);
ignore_teleport = luaW_table_get_def<bool>(L, arg, "ignore_teleport", false);
additional_turns = luaW_table_get_def<int>(L, arg, "max_cost", additional_turns);

lua_pushstring(L, "viewing_side");
lua_rawget(L, arg);
if (!lua_isnil(L, -1)) {
int i = luaL_checkinteger(L, -1);
if (i >= 1 && i <= static_cast<int>(teams().size())) viewing_side = i;
else see_all = true;
else {
// If there's a unit, we have a valid side, so fall back to legacy behaviour.
// If we don't have a unit, legacy behaviour would be a crash, so let's not.
if(u) see_all = true;
deprecated_message("wesnoth.find_reach with viewing_side=0 (or an invalid side)", DEP_LEVEL::FOR_REMOVAL, {1, 17, 0}, "To consider fogged and hidden units, use ignore_visibility=true instead.");
}
}
lua_pop(L, 1);
}

const team& viewing_team = viewing_side
? board().get_team(viewing_side)
: board().get_team(u->side());
const team& viewing_team = board().get_team(viewing_side);

pathfind::paths res(*u, ignore_units, !ignore_teleport,
viewing_team, additional_turns, see_all, ignore_units);
Expand Down

0 comments on commit 56bdd42

Please sign in to comment.