|
DefineClass.CombatPath = { |
|
__parents = { "InitDone" }, |
|
unit = false, |
|
start_pos = false, |
|
stance = false, |
|
ap = 0, |
|
move_modifier = 0, |
|
restrict_area = false, |
|
|
|
destinations = empty_table, |
|
paths_ap = empty_table, |
|
paths_prev_pos = empty_table, |
|
closest_free_pos = false, |
|
destination_stances = false, |
|
} |
|
|
|
function CombatPath:RebuildPaths(unit, ap, pos, stance, ignore_occupied, move_through_occupied, action_id) |
|
stance = stance or unit and unit.stance or "Standing" |
|
ap = ap or unit and unit.ActionPoints or 0 |
|
|
|
self.unit = unit |
|
self.stance = stance |
|
self.ap = ap |
|
self.start_pos = pos or unit and unit:GetPos() |
|
if ap < 0 then |
|
self.destinations = nil |
|
self.paths_ap = nil |
|
self.paths_prev_pos = nil |
|
self.closest_free_pos = nil |
|
return |
|
end |
|
local consts = Presets.ConstDef["Action Point Costs"] |
|
local side = unit and unit.team and unit.team.side |
|
local player_controlled = (side == "player1" or side == "player2") |
|
|
|
local stance_modifier = 0 |
|
if stance == "Crouch" then |
|
stance_modifier = consts["CrouchModifier"].value |
|
elseif stance == "Prone" then |
|
stance_modifier = consts["ProneModifier"].value |
|
end |
|
|
|
local tunnel_mask |
|
if side and side ~= "neutral" and unit.body_type == "Human" then |
|
if stance == "Prone" then |
|
tunnel_mask = player_controlled and const.TunnelMaskPlayerProne or const.TunnelMaskAIProne |
|
else |
|
tunnel_mask = player_controlled and const.TunnelMaskPlayerStanding or const.TunnelMaskAIStanding |
|
end |
|
end |
|
|
|
self.move_modifier = unit and unit:GetMoveModifier(stance, action_id) or 0 |
|
local move_modifier = Max(-100, self.move_modifier) |
|
local walk_modifier = stance_modifier + move_modifier |
|
local vertical_move_modifier = 0 |
|
if HasPerk(unit, "DeathFromAbove") then |
|
vertical_move_modifier = CharacterEffectDefs.DeathFromAbove:ResolveValue("vertical_cost_modifier") |
|
end |
|
local tunnel_param = { |
|
unit = unit, |
|
player_controlled = player_controlled, |
|
move_modifier = move_modifier, |
|
walk_modifier = walk_modifier, |
|
walk_stairs_modifier = Max(-100, walk_modifier + vertical_move_modifier), |
|
ladder_modifier = Max(-100, move_modifier + vertical_move_modifier), |
|
drop_down_modifier = Max(-100, move_modifier + vertical_move_modifier), |
|
climb_up_modifier = Max(-100, move_modifier + vertical_move_modifier), |
|
} |
|
local walk_cost = consts["Walk"].value * (100 + walk_modifier) / 100 |
|
local avoid_mines = unit and not player_controlled |
|
|
|
self.destinations, self.paths_ap, self.paths_prev_pos, self.closest_free_pos = GetCombatPathPositions(unit, self.start_pos, ap, walk_cost, tunnel_param, tunnel_mask, self.restrict_area, ignore_occupied, move_through_occupied, avoid_mines) |
|
end |
|
|
|
function CombatPath:GetAP(pos, endStance) |
|
if not pos then return end |
|
local pos_type = type(pos) |
|
local ap, stance |
|
if pos_type == "number" then |
|
ap = self.paths_ap[pos] |
|
stance = self.stance_at_end or (self.destination_stances and self.destination_stances[pos]) |
|
if endStance and stance and stance ~= endStance then |
|
return ap + GetStanceToStanceAP(stance, endStance) |
|
end |
|
return ap |
|
elseif pos_type == "table" then |
|
local min_cost |
|
for i, p in ipairs(pos) do |
|
local c = self:GetAP(p) |
|
if c and (not min_cost or c < min_cost) then |
|
min_cost = c |
|
end |
|
end |
|
return min_cost |
|
end |
|
|
|
pos = point_pack(pos) |
|
ap = self.paths_ap[pos] |
|
stance = self.stance_at_end or (self.destination_stances and self.destination_stances[pos]) |
|
if ap and endStance and stance and stance ~= endStance then |
|
return ap + GetStanceToStanceAP(stance, endStance) |
|
end |
|
return ap |
|
end |
|
|
|
function CombatPath:GetCombatPathFromPos(pos) |
|
if not pos then return end |
|
local p = type(pos) == "number" and pos or point_pack(pos) |
|
local paths_prev_pos = self.paths_prev_pos |
|
if not paths_prev_pos[p] then |
|
return |
|
end |
|
local path = {} |
|
while p do |
|
table.insert(path, p) |
|
p = paths_prev_pos[p] |
|
end |
|
return path |
|
end |
|
|
|
function CombatPath:GetReachableMeleeRangePositions(target, check_occupied, min_ap) |
|
local list = GetMeleeRangePositions(self.unit, target, nil, check_occupied) |
|
local paths_ap = self.paths_ap |
|
for i = list and #list or 0, 1, -1 do |
|
local ap = paths_ap[list[i]] |
|
if not ap or min_ap and ap < min_ap or not self.destinations[list[i]] then |
|
table.remove(list, i) |
|
end |
|
end |
|
return list |
|
end |
|
|
|
function CombatPath:GetClosestMeleeRangePos(target, target_pos, check_free, interaction) |
|
local list = GetMeleeRangePositions(self.unit, target, target_pos, check_free) |
|
local paths_ap = self.paths_ap |
|
local closest, min_ap |
|
for i, packed_pos in ipairs(list) do |
|
local ap = paths_ap[packed_pos] |
|
if ap and (not min_ap or ap < min_ap) and (not check_free or self.destinations[packed_pos]) then |
|
if interaction or IsMeleeRangeTarget(self.unit, packed_pos, nil, target, target_pos) then |
|
closest = packed_pos |
|
min_ap = ap |
|
end |
|
end |
|
end |
|
if closest then |
|
return point(point_unpack(closest)) |
|
end |
|
end |
|
|
|
function GetCombatPathLen(path, obj) |
|
local len = 0 |
|
if #path > 0 then |
|
local x1, y1, z1 = point_unpack(path[1]) |
|
for i = 2, #path do |
|
local x2, y2, z2 = point_unpack(path[i]) |
|
len = len + GetLen(x2 - x1, y2 - y1, (z1 or z2) and (z2 or terrain.GetHeight(x2, y2)) - (z1 or terrain.GetHeight(x1, y1)) or 0) |
|
x1, y1, z1 = x2, y2, z2 |
|
end |
|
if IsValid(obj) and obj:IsValidPos() then |
|
len = len + obj:GetVisualDist(x1, y1, z1) |
|
end |
|
end |
|
return len |
|
end |
|
|