|
MapVar("g_TacticalMap", false) |
|
|
|
function AITacticCalcPathDistances(unit, context, disable_bias) |
|
context.apply_bias = not disable_bias |
|
|
|
|
|
AICalcPathDistances(context) |
|
local cur_dest = GetPackedPosAndStance(unit) |
|
for _, dest in ipairs(context.destinations) do |
|
context.dest_dist[dest] = stance_pos_dist(context.best_dest, dest) |
|
end |
|
|
|
context.total_dist = stance_pos_dist(cur_dest, context.best_dest) |
|
end |
|
|
|
function AITacticSelectEndTurnPolicies(unit, context) |
|
local archetype = unit:GetCurrentArchetype() |
|
if not archetype then return end |
|
for _, behavior in ipairs(archetype.Behaviors) do |
|
if behavior.Fallback and #(behavior.EndTurnPolicies or empty_table) > 0 then |
|
return behavior.EndTurnPolicies |
|
end |
|
end |
|
if archetype and #(archetype.EndTurnPolicies or empty_table) > 0 then |
|
return archetype.EndTurnPolicies |
|
end |
|
end |
|
|
|
function AITacticSelectSignatureActions(unit, context) |
|
local archetype = unit:GetCurrentArchetype() |
|
if archetype and #(archetype.SignatureActions or empty_table) > 0 then |
|
return archetype.SignatureActions |
|
end |
|
end |
|
|
|
function tac_area_ids(areas) |
|
if not g_TacticalMap then return empty_func end |
|
local bit = 0 |
|
areas = areas or 0 |
|
return function() |
|
while bit < 64 do |
|
local mask = shift(1, bit) |
|
bit = bit + 1 |
|
if band(areas, mask) ~= 0 then |
|
return g_TacticalMap.individual_areas[bit] |
|
end |
|
end |
|
end |
|
end |
|
|
|
DefineClass.TacticalMap = { |
|
__parents = { "GameDynamicSpawnObject" }, |
|
|
|
area_to_marker = false, |
|
area_to_positions = false, |
|
ppos_to_individual_area = false, |
|
id_to_individual_area = false, |
|
individual_areas = false, |
|
|
|
|
|
assigned_individual_areas = false, |
|
|
|
assigned_area_priority = false, |
|
|
|
PriorityHigh = 1, |
|
PriorityMedium = 2, |
|
PriorityLow = 3, |
|
} |
|
|
|
function TacticalMap:Init() |
|
self.area_to_marker = {} |
|
self.area_to_positions = {} |
|
self.ppos_to_individual_area = {} |
|
self.individual_areas = {} |
|
self.id_to_individual_area = {} |
|
self.assigned_individual_areas = {} |
|
self.assigned_area_priority = {} |
|
|
|
self:SetPos(point(terrain.GetMapSize()) / 2) |
|
|
|
local markers = MapGetMarkers() |
|
local bit = 0 |
|
local seen = {} |
|
for _, marker in ipairs(markers) do |
|
if marker.FightAreaId ~= "" then |
|
if seen[marker.FightAreaId] then |
|
StoreErrorSource(marker, string.format("Marker Fight Area Id '%s' already in use, ignored", marker.FightAreaId)) |
|
else |
|
seen[marker.FightAreaId] = true |
|
if bit >= 64 then |
|
StoreErrorSource(marker, string.format("Trying to register more than 64 Individual Fight Areas, Fight Area '%s' ignored", marker.FightAreaId)) |
|
else |
|
local flag = shift(1, bit) |
|
self.id_to_individual_area[marker.FightAreaId] = flag |
|
self.individual_areas[bit + 1] = marker.FightAreaId |
|
self:RegisterIndividualArea(marker, marker.FightAreaId, marker.FightArea3d) |
|
bit = bit + 1 |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
function TacticalMap:AssignUnit(unit, areas, reset, priority) |
|
if not areas then |
|
self.id_to_individual_area[unit] = nil |
|
self.assigned_area_priority[unit] = nil |
|
return |
|
end |
|
if type(areas) == "table" then |
|
for _, area in ipairs(areas) do |
|
self:AssignUnit(unit, area, reset, priority) |
|
end |
|
return |
|
end |
|
local flag = self.id_to_individual_area[areas] or 0 |
|
self.assigned_individual_areas[unit] = bor(reset and 0 or self.assigned_individual_areas[unit] or 0, flag) |
|
if type(priority) == "number" then |
|
priority = Clamp(priority, self.PriorityHigh, self.PriorityLow) |
|
self.assigned_area_priority[unit] = self.assigned_area_priority[unit] or {} |
|
self.assigned_area_priority[unit][priority] = bor(reset and 0 or self.assigned_area_priority[unit][priority] or 0, flag) |
|
end |
|
end |
|
|
|
function TacticalMap:GetAssignedAreas(unit, priority) |
|
local areas = self.assigned_area_priority[unit] |
|
if areas and priority then |
|
areas = areas[priority] |
|
end |
|
return areas |
|
end |
|
|
|
function TacticalMap:ShowAssignedArea(unit, time) |
|
time = time or 2000 |
|
CreateRealTimeThread(function() |
|
local areas = self.assigned_individual_areas[unit] |
|
for id in tac_area_ids(areas) do |
|
local marker = self.area_to_marker[id] |
|
marker.ground_visuals = true |
|
marker:ShowArea() |
|
end |
|
Sleep(time) |
|
for id in tac_area_ids(areas) do |
|
local marker = self.area_to_marker[id] |
|
marker.ground_visuals = false |
|
marker:HideArea() |
|
end |
|
end) |
|
end |
|
|
|
function TacticalMap:GetUnitAreas(unit) |
|
local x, y, z = unit:GetPosXYZ() |
|
local pos = point_pack(x, y, z) |
|
return self.ppos_to_individual_area[pos] or 0 |
|
end |
|
|
|
function TacticalMap:GetUnitPrimaryArea(unit) |
|
local x, y, z = unit:GetPosXYZ() |
|
local pos = point_pack(x, y, z) |
|
local areas = self.ppos_to_individual_area[pos] or 0 |
|
local area, mindist |
|
for id in tac_area_ids(areas) do |
|
local marker = self.area_to_marker[id] |
|
local dist = unit:GetDist(marker) |
|
if not area or dist < mindist then |
|
area, mindist = id, dist |
|
end |
|
end |
|
return area |
|
end |
|
|
|
function TacticalMap:GetUnitsInArea(area_id) |
|
local units = {} |
|
for _, unit in ipairs(g_Units) do |
|
local x, y, z = unit:GetPosXYZ() |
|
local pos = point_pack(x, y, z) |
|
local areas = self.ppos_to_individual_area[pos] or 0 |
|
for id in tac_area_ids(areas) do |
|
if id == area_id then |
|
units[#units + 1] = unit |
|
break |
|
end |
|
end |
|
end |
|
return units |
|
end |
|
|
|
function TacticalMap:GetNearestArea(unit, ...) |
|
local n = select("#", ...) |
|
local area, mindist |
|
for i = 1, n do |
|
local area_id = select(i, ...) |
|
local marker = self.area_to_marker[area_id] |
|
if marker then |
|
local dist = unit:GetDist(marker) |
|
if not area or dist < mindist then |
|
area, mindist = area_id, dist |
|
end |
|
end |
|
end |
|
return area |
|
end |
|
|
|
function TacticalMap:CountUnitsInAreas() |
|
local player_units_in_area = {} |
|
local enemy_units_in_area = {} |
|
for _, unit in ipairs(g_Units) do |
|
local area = self:GetUnitPrimaryArea(unit) |
|
if area then |
|
if unit.team.player_team then |
|
player_units_in_area[area] = (player_units_in_area[area] or 0) + 1 |
|
elseif unit.team.player_enemy then |
|
enemy_units_in_area[area] = (enemy_units_in_area[area] or 0) + 1 |
|
end |
|
end |
|
end |
|
return player_units_in_area, enemy_units_in_area |
|
end |
|
|
|
function TacticalMap:FindOptimalLocationInAssignedArea(unit, context) |
|
local positions |
|
local assigned_area = self.assigned_individual_areas[unit] or 0 |
|
local cur_pos = point_pack(unit:GetPos()) |
|
|
|
if assigned_area ~= 0 then |
|
local area = self.ppos_to_individual_area[cur_pos] |
|
if band(area, assigned_area) ~= 0 then |
|
|
|
context.best_dest = GetPackedPosAndStance(unit) |
|
return true |
|
end |
|
positions = {} |
|
if self.assigned_area_priority[unit] then |
|
for p = self.PriorityHigh, self.PriorityLow do |
|
for id in tac_area_ids(self.assigned_area_priority[unit][p]) do |
|
local flag = self.id_to_individual_area[id] |
|
if band(assigned_area, flag) ~= 0 then |
|
positions = table.union(positions, self.area_to_positions[id]) |
|
end |
|
end |
|
if #positions > 0 then break end |
|
end |
|
end |
|
if #positions == 0 then |
|
for id, flag in pairs(self.id_to_individual_area) do |
|
if band(assigned_area, flag) ~= 0 then |
|
positions = table.union(positions, self.area_to_positions[id]) |
|
end |
|
end |
|
end |
|
end |
|
|
|
positions = positions or empty_table |
|
if #positions > 0 then |
|
local goto_pos = table.interaction_rand(positions, "Behavior") |
|
local x, y, z = point_unpack(goto_pos) |
|
context.best_dest = stance_pos_pack(x, y, z, StancesList.Standing) |
|
context.optimal_dest_in_assigned_area = true |
|
return true |
|
end |
|
end |
|
|
|
function TacticalMap:EnumDestsInAssignedArea(unit, context) |
|
AIFindDestinations(unit, context) |
|
|
|
local marker_area = self.assigned_individual_areas[unit] or 0 |
|
if marker_area ~= 0 then |
|
local function dest_in_marker_filter(idx, dest, area) |
|
local x, y, z = stance_pos_unpack(dest) |
|
local ppos = point_pack(x, y, z) |
|
return band(self.ppos_to_individual_area[ppos] or 0, area) ~= 0 |
|
end |
|
|
|
if self.assigned_area_priority[unit] then |
|
for p = self.PriorityHigh, self.PriorityLow do |
|
local marker_areas = self.assigned_area_priority[unit][p] or 0 |
|
if marker_areas ~= 0 then |
|
local dests = table.ifilter(context.destinations, dest_in_marker_filter, marker_areas) |
|
local all_dests = table.ifilter(context.all_destinations, dest_in_marker_filter, marker_areas) |
|
if #dests > 0 and #all_dests > 0 then |
|
context.destinations = dests |
|
context.all_destinations = all_dests |
|
return true |
|
end |
|
end |
|
end |
|
end |
|
|
|
local dests = table.ifilter(context.destinations, dest_in_marker_filter, marker_area) |
|
local all_dests = table.ifilter(context.all_destinations, dest_in_marker_filter, marker_area) |
|
if #dests > 0 and #all_dests > 0 then |
|
context.destinations = dests |
|
context.all_destinations = all_dests |
|
return true |
|
else |
|
context.assigned_area_unreachable = true |
|
end |
|
end |
|
|
|
return true |
|
end |
|
|
|
function TacticalMap:GetDynamicData(data) |
|
data.assigned_individual_areas = {} |
|
for unit, area in pairs(self.assigned_individual_areas) do |
|
if unit then |
|
data.assigned_individual_areas[unit:GetHandle()] = area |
|
end |
|
end |
|
|
|
data.assigned_area_priority = {} |
|
for unit, priorities in pairs(self.assigned_area_priority) do |
|
if unit then |
|
local handle = unit:GetHandle() |
|
for p = self.PriorityHigh, self.PriorityLow do |
|
local areas = priorities[p] or 0 |
|
if areas ~= 0 then |
|
data.assigned_area_priority[handle] = data.assigned_area_priority[handle] or {} |
|
data.assigned_area_priority[handle][p] = areas |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
function TacticalMap:SetDynamicData(data) |
|
self.assigned_individual_areas = {} |
|
for handle, area in pairs(data.assigned_individual_areas) do |
|
local unit = HandleToObject[handle] or false |
|
self.assigned_individual_areas[unit] = area |
|
end |
|
|
|
self.assigned_area_priority = {} |
|
for handle, priorities in pairs(data.assigned_area_priority) do |
|
local unit = HandleToObject[handle] or false |
|
for p = self.PriorityHigh, self.PriorityLow do |
|
local areas = priorities[p] or 0 |
|
if areas ~= 0 then |
|
self.assigned_area_priority[unit] = self.assigned_area_priority[unit] or {} |
|
self.assigned_area_priority[unit][p] = areas |
|
end |
|
end |
|
end |
|
end |
|
|
|
function TacticalMap:RegisterIndividualArea(marker, area, mode_3d) |
|
local positions = marker:GetAreaPositions(true) |
|
local area_flag = self.id_to_individual_area[area] |
|
|
|
self.area_to_marker[area] = marker |
|
self.area_to_positions[area] = positions |
|
for _, ppos in ipairs(positions) do |
|
local x, y, z = SnapToPassSlabXYZ(point_unpack(ppos)) |
|
if x then |
|
ppos = point_pack(x, y, z) |
|
end |
|
self.ppos_to_individual_area[ppos] = bor(self.ppos_to_individual_area[ppos] or 0, area_flag) |
|
end |
|
|
|
if mode_3d then |
|
local bbox = marker:GetBBox() |
|
local min, max = bbox:min():SetZ(0), bbox:max():SetZ(1000*guim) |
|
bbox = Extend(Extend(bbox, min), max) |
|
|
|
ForEachPassSlab(bbox, function(x, y, z, positions, ppos_to_individual_area, area_flag) |
|
local ppos = point_pack(x, y, z) |
|
ppos_to_individual_area[ppos] = bor(ppos_to_individual_area[ppos] or 0, area_flag) |
|
end, self.area_to_positions[area], self.ppos_to_individual_area, area_flag) |
|
end |
|
end |
|
|
|
local function InitTacticalMap() |
|
g_TacticalMap = g_TacticalMap or TacticalMap:new() |
|
end |
|
|
|
function OnMsg.EnterSector(game_start, load_game) |
|
if not load_game then |
|
InitTacticalMap() |
|
end |
|
end |
|
|
|
function OnMsg.CombatStart(dynamic_data) |
|
if not dynamic_data then |
|
InitTacticalMap() |
|
end |
|
end |