File size: 11,601 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
MapVar("g_TacticalMap", false)

function AITacticCalcPathDistances(unit, context, disable_bias)
	context.apply_bias = not disable_bias

	-- make sure we have a distance to optimal - if there's no path use raw distance
	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() -- take from "base" archetype ignoring the current (script) one
	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() -- take from "base" archetype ignoring the current (script) one
	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] -- the list is base 1 so the +1 above matches the needs
			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, -- [id] = flag
	individual_areas = false,

	-- area assignment: [unit] = area(s)
	assigned_individual_areas = false,
	-- area assignment priority: [unit] = { [priority] = area(s) }
	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
			-- already in one of the areas
			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