File size: 11,372 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
DefineClass.BossfightPierre = {
	__parents = { "Encounter" },	
	
	default_assigned_area = false,	
	boss = false,
	guards_melee = false,
	guard_heavy = false,
	guard_rpg = false,
	
	enrage_turn = 4, -- timer to make sure Pierre and his melee guards go hand-to-hand
	fallback_turn = false,
	area_tactics = true,
}

g_SectorEncounters.H4 = "BossfightPierre"

function BossfightPierre:ShouldStart()
	return not GetQuestVar("ErnieSideQuests", "FortressFirstCapture")
end

function BossfightPierre:Setup()	
	for _, obj in ipairs(Groups.LegionRocketeer_SlowReloader) do
		if IsKindOf(obj, "Unit") then
			self.guard_rpg = obj
			break
		end
	end
	for _, obj in ipairs(Groups.PierreGuard_Ordnance) do
		if IsKindOf(obj, "Unit") then
			self.guard_heavy = obj
			break
		end
	end
	self.guards_melee = {}
	for _, obj in ipairs(Groups.PierreGuard_Stormer) do
		if IsKindOf(obj, "Unit") then
			table.insert(self.guards_melee, obj)
		end
	end
	
	for _, obj in ipairs(Groups.Pierre) do
		if IsKindOf(obj, "Unit") then
			self.boss = obj
			break
		end
	end	
end

function BossfightPierre:GetDynamicData(data)
	data.boss = IsValid(self.boss) and self.boss:GetHandle() or nil
	data.guards_melee = {}
	for i, unit in ipairs(self.guards_melee) do
		local handle = IsValid(unit) and not unit:IsDead() and unit:GetHandle()
		if handle then
			table.insert(data.guards_melee, handle)
		end
	end
	data.guard_heavy = IsValid(self.guard_heavy) and self.guard_heavy:GetHandle() or nil
	data.guard_rpg = IsValid(self.guard_rpg) and self.guard_rpg:GetHandle() or nil
	data.enrage_turn = self.enrage_turn
	data.area_tactics = self.area_tactics
	data.fallback_turn = self.fallback_turn
end

function BossfightPierre:SetDynamicData(data)
	self.boss = data.boss and HandleToObject[data.boss]
	self.guards_melee = {}
	for _, handle in ipairs(data.guards_melee) do
		table.insert(self.guards_melee, HandleToObject[handle])
	end
	self.guard_heavy = data.guard_heavy and HandleToObject[data.guard_heavy]
	self.guard_rpg = data.guard_rpg and HandleToObject[data.guard_rpg]
	
	self.enrage_turn = data.enrage_turn
	self.area_tactics = data.area_tactics
	self.fallback_turn = data.fallback_turn
end

function BossfightPierre:ShouldHoldPosition(unit)
	return table.find(unit.AIKeywords, "Sniper") or table.find(unit.AIKeywords, "Ordnance")
end

function BossfightPierre:OnTurnStart()		
	local player_units_in_area, enemy_units_in_area = g_TacticalMap:CountUnitsInAreas()
	local plrunits = 0
	plrunits = plrunits + (player_units_in_area.PierreFight_DetectAssault_Yard_1 or 0)
	plrunits = plrunits + (player_units_in_area.PierreFight_DetectAssault_Yard_2 or 0)
	plrunits = plrunits + (player_units_in_area.PierreFight_DetectFlanking_BackPass or 0)
	plrunits = plrunits + (player_units_in_area.PierreFight_DetectFlanking_MainBuilding or 0)
	plrunits = plrunits + (player_units_in_area.PierreFight_Yard_Transition or 0)
	plrunits = plrunits + (player_units_in_area.PierreFight_InnerPerimeter_MainBuilding or 0)

	local area_tactics = self.area_tactics and (g_Combat.current_turn < self.enrage_turn)

	if area_tactics and plrunits < 2 then
		self.boss.script_archetype = "GuardArea"
		for _, guard in ipairs(self.guards_melee) do
			guard.script_archetype = "GuardArea"
		end
		
		local mg_detect = (player_units_in_area.PierreFight_DetectFlanking_MG or 0) > 1
		if g_Combat.current_turn == 1 or mg_detect then 
			g_TacticalMap:AssignUnit(self.boss, nil)
			g_TacticalMap:AssignUnit(self.boss, "PierreFight_Yard_Hangar", nil, g_TacticalMap.PriorityHigh)
			g_TacticalMap:AssignUnit(self.boss, "PierreFight_Yard_Containers", nil, g_TacticalMap.PriorityMedium)
			if mg_detect then
				g_TacticalMap:AssignUnit(self.boss, "PierreFight_FlankingDefense_Yard", nil, g_TacticalMap.PriorityMedium)
			else
				g_TacticalMap:AssignUnit(self.boss, "PierreFight_Yard_SideHangar", nil, g_TacticalMap.PriorityMedium)
			end
			for _, guard in ipairs(self.guards_melee) do
				g_TacticalMap:AssignUnit(guard, nil)
				g_TacticalMap:AssignUnit(guard, "PierreFight_Yard_Hangar", nil, g_TacticalMap.PriorityHigh)
				g_TacticalMap:AssignUnit(guard, "PierreFight_Yard_Containers", nil, g_TacticalMap.PriorityMedium)
				if mg_detect then
					g_TacticalMap:AssignUnit(guard, "PierreFight_FlankingDefense_Yard", nil, g_TacticalMap.PriorityMedium)
				else
					g_TacticalMap:AssignUnit(guard, "PierreFight_Yard_SideHangar", nil, g_TacticalMap.PriorityMedium)
				end
			end
		end
		
		if self.fallback_turn and g_Combat.current_turn > self.fallback_turn then
			-- "Fallback!" continuation
			-- assign units from PierreFight_Yard_Transition and PierreFight_FlankingDefense_Yard (3.2) to PierreFight_InnerPerimeter_MainBuilding
			local units = table.union(g_TacticalMap:GetUnitsInArea("PierreFight_Yard_Transition"), g_TacticalMap:GetUnitsInArea("PierreFight_FlankingDefense_Yard"))
			for _, unit in ipairs(units) do
				if not self:ShouldHoldPosition(unit) then
					unit.script_archetype = "GuardArea"
					g_TacticalMap:AssignUnit(unit, "PierreFight_InnerPerimeter_MainBuilding", "reset")
				end
			end
		elseif (self.fallback_turn and self.fallback_turn == g_Combat.current_turn) or g_Combat.current_turn >= 3 then
			-- "Fallback!"
			
			-- assign units from PierreFight_Yard_SideHangar to PierreFight_InnerPerimeter_Cannon
			local units = g_TacticalMap:GetUnitsInArea("PierreFight_Yard_SideHangar")
			for _, unit in ipairs(units) do
				if not self:ShouldHoldPosition(unit) then
					unit.script_archetype = "GuardArea"
					g_TacticalMap:AssignUnit(unit, "PierreFight_InnerPerimeter_Cannon", "reset")
				end
			end
			
			-- assign units from PierreFight_Yard_Hangar and PierreFight_Yard_Containers to PierreFight_InnerPerimeter_MainBuilding, sub PierreFight_Yard_Transition, sub PierreFight_FlankingDefense_Yard
			units = table.union(g_TacticalMap:GetUnitsInArea("PierreFight_Yard_Hangar"), g_TacticalMap:GetUnitsInArea("PierreFight_Yard_Containers"))
			for _, unit in ipairs(units) do
				if not self:ShouldHoldPosition(unit) then
					unit.script_archetype = "GuardArea"
					g_TacticalMap:AssignUnit(unit, nil)
					g_TacticalMap:AssignUnit(unit, "PierreFight_InnerPerimeter_MainBuilding", nil, g_TacticalMap.PriorityHigh)
					g_TacticalMap:AssignUnit(unit, "PierreFight_Yard_Transition", nil, g_TacticalMap.PriorityMedium)
					g_TacticalMap:AssignUnit(unit, "PierreFight_FlankingDefense_Yard", nil, g_TacticalMap.PriorityLow)
				end
			end
		end		
	else
		if plrunits >= 2 then
			self.area_tactics = false
		end
		for _, unit in ipairs(self.boss.team.units) do
			g_TacticalMap:AssignUnit(unit, nil)
			unit.script_archetype = nil
		end

		self.guard_heavy.AIKeywords = table.copy(self.guard_heavy.AIKeywords)
		if self.guard_rpg then
			self.guard_rpg.AIKeywords = table.copy(self.guard_rpg.AIKeywords)
		end
		
		if g_Combat.current_turn >= self.enrage_turn then
			self.boss.script_archetype = "Brute"
			for _, guard in ipairs(self.guards_melee) do
				guard.script_archetype = "Brute"
			end
			
			table.insert_unique(self.guard_heavy.AIKeywords, "Ordnance")		
		else
			-- check Pierre for charge attack, go melee or ranged based on that
			local boss = self.boss
			local enemies = GetEnemies(boss)
			local can_charge 
			local ap = CombatActions.GloryHog:ResolveValue("move_ap") * const.Scale.AP
			for _, enemy in ipairs(enemies) do
				if GetChargeAttackPosition(boss, enemy, ap, "GloryHog") then
					can_charge = true
					break
				end
			end
			if can_charge then
				self.boss.script_archetype = "Brute"
				for _, guard in ipairs(self.guards_melee) do
					guard.script_archetype = "Brute"
				end
			else
				self.boss.script_archetype = "Soldier"
				for _, guard in ipairs(self.guards_melee) do
					guard.script_archetype = "Soldier"
				end
			end
			
			table.remove_value(self.guard_heavy.AIKeywords, "Ordnance")
		end
		
		-- RPG Guard
		if self.guard_rpg then
			if g_Combat.current_turn > 3 then
				table.insert_unique(self.guard_rpg.AIKeywords, "Ordnance")
			else
				table.remove_value(self.guard_rpg.AIKeywords, "Ordnance")
			end
		end
	end

	-- GuardArea assignment (fallback)
	for _, unit in ipairs(self.boss.team.units) do
		if unit.script_archetype == "GuardArea" and not g_TacticalMap:GetAssignedAreas(unit) then
			g_TacticalMap:AssignUnit(unit, nil)
			g_TacticalMap:AssignUnit(unit, "PierreFight_InnerPerimeter_MainBuilding", nil, g_TacticalMap.PriorityHigh)
			g_TacticalMap:AssignUnit(unit, "PierreFight_Yard_Transition", nil, g_TacticalMap.PriorityMedium)
			g_TacticalMap:AssignUnit(unit, "PierreFight_FlankingDefense_Yard", nil, g_TacticalMap.PriorityLow)
		end
	end	
	
	-- make sure Pierre and his guards are on the correct weapon set for their archetypes
	self:EquipProperWeapon(self.boss)
	for _, guard in ipairs(self.guards_melee) do
		self:EquipProperWeapon(guard)
	end
	self:EquipProperWeapon(self.guard_rpg)
end

local function FindWeaponByClass(unit, class, slot)
	if not slot then
		slot = unit.current_weapon
	elseif slot == "alt" then
		slot = (unit.current_weapon == "Handheld A") and "Handheld B" or "Handheld A"
	end
	
	local weapons = unit:GetEquippedWeapons(slot)

	for _, weapon in ipairs(weapons) do
		if IsKindOf(weapon, class) and (class ~= "Firearm" or not IsKindOf(weapon, "HeavyWeapon")) then
			return weapon
		end
		if IsKindOf(weapon, "Firearm") then		
			for slot, sub in sorted_pairs(weapon.subweapons) do
				if IsKindOf(sub, class) and (class ~= "Firearm" or not IsKindOf(sub, "HeavyWeapon")) then
					return sub
				end			
			end
		end
	end
end


function BossfightPierre:EquipProperWeapon(unit)
	if not IsValidTarget(unit) then return end
	
	if table.find(unit.AIKeywords, "Ordnance") then
		-- make sure we have a heavy weapon
		if not FindWeaponByClass(unit, "HeavyWeapon") and FindWeaponByClass(unit, "HeavyWeapon", "alt") then
			unit:SwapActiveWeapon()
		end
	elseif unit:GetArchetype().id == "Brute" then
		-- prefer to have melee weapon or shotgun
		if not FindWeaponByClass(unit, "MeleeWeapon") then
			if FindWeaponByClass(unit, "MeleeWeapon", "alt") then
				unit:SwapActiveWeapon()
			elseif not FindWeaponByClass(unit, "Shotgun") and FindWeaponByClass(unit, "Shotgun", "alt") then
				unit:SwapActiveWeapon()
			end
		end
	else
		-- prefer to have AR -> SMG -> Firearm
		if not FindWeaponByClass(unit, "AssaultRifle") then
			if FindWeaponByClass(unit, "AssaultRifle", "alt") then
				unit:SwapActiveWeapon()
			elseif not FindWeaponByClass(unit, "SubmachineGun") then
				if FindWeaponByClass(unit, "SubmachineGun", "alt") then
					unit:SwapActiveWeapon()
				elseif not FindWeaponByClass(unit, "Firearm") and FindWeaponByClass(unit, "Firearm", "alt") then
					unit:SwapActiveWeapon()
				end
			end
		end
	end
end

function BossfightPierre:OnEnemyFall(unit)
	if g_Combat and IsValid(self.boss) and not self.boss:IsDead() and self.boss:GetDist(unit) < 5 * const.SlabSizeX then
		self.enrage_turn = g_Combat.current_turn + 3
	end
end

function BossfightPierre:OnUnitDied(unit)
	if not g_Combat or not self.boss then return end
	if unit.team == self.boss.team and not table.find(unit.AIKeywords, "Sniper") then
		self.fallback_turn = g_Combat.current_turn
		if g_Teams[g_CurrentTeam] == self.boss.team then
			self.fallback_turn = self.fallback_turn + 1
		end
	elseif unit:IsOnEnemySide(self.boss) then
		self:OnEnemyFall(unit)
	end
end

function BossfightPierre:OnUnitDowned(unit)
	return self:OnUnitDied(unit)
end