|
|
|
local reachedPlayer = 1 |
|
local reachedBoss = 2 |
|
|
|
DefineClass.BossfightFaucheaux = { |
|
__parents = { "Encounter" }, |
|
|
|
default_assigned_area = false, |
|
|
|
|
|
run_away = false, |
|
chosen_path = 1, |
|
boss = false, |
|
escape_turn = false, |
|
escaped = false, |
|
enemies_aware = false, |
|
|
|
reached_areas = false, |
|
released_areas = false, |
|
} |
|
|
|
g_SectorEncounters.K16 = "BossfightFaucheaux" |
|
|
|
function BossfightFaucheaux:ShouldStart() |
|
return not GetQuestVar("05_TakeDownFaucheux", "Completed") |
|
end |
|
|
|
function BossfightFaucheaux:Init() |
|
self.reached_areas = {} |
|
self.released_areas = {} |
|
end |
|
|
|
function BossfightFaucheaux:Setup() |
|
self:InitAssignedArea() |
|
end |
|
|
|
function BossfightFaucheaux:GetDynamicData(data) |
|
data.run_away = self.run_away or nil |
|
data.chosen_path = (self.chosen_path ~= 1) and self.chosen_path or nil |
|
data.escape_turn = self.escape_turn or nil |
|
data.escaped = self.escaped or nil |
|
data.reached_areas = table.copy(self.reached_areas) |
|
data.released_areas = table.copy(self.released_areas) |
|
data.boss = IsValid(self.boss) and self.boss:GetHandle() or nil |
|
data.enemies_aware = self.enemies_aware or nil |
|
end |
|
|
|
function BossfightFaucheaux:SetDynamicData(data) |
|
self.run_away = data.run_away |
|
self.chosen_path = data.chosen_path |
|
self.escape_turn = data.escape_turn |
|
self.escaped = data.escaped |
|
self.reached_areas = table.copy(data.reached_areas) |
|
self.released_areas = table.copy(data.released_areas) |
|
self.boss = data.boss and HandleToObject[data.boss] |
|
self.enemies_aware = data.enemies_aware |
|
end |
|
|
|
local path1areas = { |
|
"Control_Zone_HQ", |
|
"Control_Zone_HQFront", |
|
"Control_Zone_Containers1", |
|
"Control_Zone_Containers2", |
|
"ControlZone_OpenCorridor2", |
|
"Control_Zone_FinalDest", |
|
} |
|
|
|
local path2areas = { |
|
"Control_Zone_HQ", |
|
"Control_Zone_MessHall", |
|
"Control_Zone_Tents", |
|
"Control_Zone_Armory", |
|
"Control_Zone_Passage", |
|
"Control_Zone_OfficerRoom", |
|
"ControlZone_OpenCorridor2", |
|
"Control_Zone_FinalDest", |
|
} |
|
|
|
local detection_areas = { |
|
["Control_Zone_MessHall"] = true, |
|
["Control_Zone_HQFront"] = true, |
|
["Control_Zone_FaucheauxCabinet"] = true, |
|
["Control_Zone_HQ"] = true, |
|
["Control_Zone_Tents"] = true, |
|
} |
|
|
|
function BossfightFaucheaux:TriggerRetreat() |
|
if self.run_away then |
|
return |
|
end |
|
self.run_away = true |
|
|
|
local path1alive, path1total = 0, 0 |
|
local path2alive, path2total = 0, 0 |
|
|
|
for _, unit in ipairs(g_Units) do |
|
local x, y, z = unit:GetPosXYZ() |
|
local unit_pos = point_pack(x, y, z) |
|
local area = g_TacticalMap:GetUnitPrimaryArea(unit) |
|
local original_area = self.original_area[unit] |
|
|
|
if unit.team.player_enemy and table.find(path1areas, original_area) then |
|
if not unit:IsDead() then |
|
path1alive = path1alive + 1 |
|
end |
|
path1total = path1total + 1 |
|
end |
|
|
|
if unit.team.player_enemy and table.find(path2areas, original_area) then |
|
if not unit:IsDead() then |
|
path2alive = path2alive + 1 |
|
end |
|
path2total = path2total + 1 |
|
end |
|
end |
|
|
|
local chance1 = MulDivRound(path1alive, 100, Max(1, path1total)) |
|
local chance2 = MulDivRound(path2alive, 100, Max(1, path2total)) |
|
|
|
if chance1 + chance2 > 0 then |
|
local roll = InteractionRand(chance1 + chance2, "Bossfight") |
|
self.chosen_path = (roll <= chance1) and 1 or 2 |
|
end |
|
self.boss.script_archetype = "Faucheaux_BossRetreating" |
|
end |
|
|
|
function BossfightFaucheaux:OnDamageDone(attacker, target, dmg, hit_descr) |
|
if target == self.boss then |
|
self:TriggerRetreat() |
|
end |
|
end |
|
|
|
function BossfightFaucheaux:OnAreaReached(area, flag) |
|
if not self.run_away then |
|
if flag == reachedPlayer and self.enemies_aware and detection_areas[area] then |
|
self:TriggerRetreat() |
|
end |
|
return |
|
end |
|
|
|
if flag == reachedPlayer then |
|
if area == "Control_Zone_KillingField1" or area == "Control_Zone_KillingField2" or area == "Control_Zone_Containers1" or area == "Control_Zone_Armory" then |
|
self.released_areas.Control_Zone_EastWall = true |
|
end |
|
if area == "Control_Zone_KillingField1" or area == "Control_Zone_KillingField2" or area == "Control_Zone_Containers1" or area == "Control_Zone_MessHall" then |
|
self.released_areas.Control_Zone_BlastEntrance = true |
|
self.released_areas.Control_Zone_WestPicket = true |
|
self.released_areas.Control_Zone_WestTower = true |
|
end |
|
if area == "Control_Zone_OpenCorridor1" or area == "Control_Zone_Containers1" or area == "Control_Zone_Tents" then |
|
self.released_areas.Control_Zone_WallWest = true |
|
self.released_areas.Control_Zone_RadioTower = true |
|
end |
|
elseif flag == reachedBoss then |
|
if area == "Control_Zone_Tents" then |
|
self.released_areas.Control_Zone_EastWall = true |
|
end |
|
if area == "Control_Zone_Containers1" or area == "Control_Zone_Armory" then |
|
self.released_areas.Control_Zone_BlastEntrance = true |
|
self.released_areas.Control_Zone_WestPicket = true |
|
self.released_areas.Control_Zone_WestTower = true |
|
self.released_areas.Control_Zone_WallWest = true |
|
self.released_areas.Control_Zone_RadioTower = true |
|
end |
|
if area == "Control_Zone_FinalDest" then |
|
self.escape_turn = g_Combat.current_turn + 1 |
|
end |
|
end |
|
end |
|
|
|
function BossfightFaucheaux:UpdateAwareness() |
|
if not self.boss then return end |
|
|
|
local aware = self.enemies_aware |
|
if not aware then |
|
for _, unit in ipairs(g_Units) do |
|
if unit.team == self.boss.team then |
|
aware = aware or unit:IsAware() |
|
end |
|
end |
|
end |
|
if aware then |
|
for _, unit in ipairs(g_Units) do |
|
if unit.team == self.boss.team then |
|
unit:RemoveStatusEffect("Unaware") |
|
end |
|
end |
|
self.enemies_aware = true |
|
end |
|
end |
|
|
|
function BossfightFaucheaux:UpdateAreaProgress() |
|
assert(g_TacticalMap) |
|
if not self.run_away then |
|
return |
|
end |
|
for _, unit in ipairs(g_Units) do |
|
local areas = g_TacticalMap:GetUnitAreas(unit) |
|
if unit.team.player_team and not unit:IsDead() then |
|
for area_id in tac_area_ids(areas) do |
|
if band(self.reached_areas[area_id] or 0, reachedPlayer) == 0 then |
|
self.reached_areas[area_id] = bor(self.reached_areas[area_id] or 0, reachedPlayer) |
|
self:OnAreaReached(area_id, reachedPlayer) |
|
end |
|
end |
|
elseif unit == self.boss and not unit:IsDead() then |
|
for area_id in tac_area_ids(areas) do |
|
if band(self.reached_areas[area_id] or 0, reachedBoss) == 0 then |
|
self.reached_areas[area_id] = bor(self.reached_areas[area_id] or 0, reachedBoss) |
|
self:OnAreaReached(area_id, reachedBoss) |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
function BossfightFaucheaux:AssignToNextArea(path, cur_area) |
|
local area_idx = table.find(path, cur_area) |
|
if area_idx then |
|
|
|
local next_area_idx = Min(area_idx + 1, #path) |
|
local area = path[next_area_idx] |
|
g_TacticalMap:AssignUnit(self.boss, area, "reset", g_TacticalMap.PriorityHigh) |
|
if area == "ControlZone_OpenCorridor2" then |
|
g_TacticalMap:AssignUnit(self.boss, "ControlZone_OpenCorridor1", nil, g_TacticalMap.PriorityMedium) |
|
end |
|
g_TacticalMap:AssignUnit(self.boss, cur_area, nil, g_TacticalMap.PriorityLow) |
|
else |
|
|
|
local area, min_dist |
|
local markers = MapGetMarkers() |
|
for _, id in ipairs(path) do |
|
local idx = table.find(markers, "ID", id) |
|
if idx and id ~= "Control_Zone_OpenCorridor2" then |
|
local dist = self.boss:GetDist(markers[idx]) |
|
if dist < (min_dist or dist + 1) then |
|
area, min_dist = id, dist |
|
end |
|
end |
|
end |
|
if area then |
|
g_TacticalMap:AssignUnit(self.boss, area, "reset") |
|
else |
|
assert(false, string.format("unable to find fallback area to assign Faucheaux following path %d; current area: %s", self.chosen_path, tostring(cur_area))) |
|
end |
|
end |
|
end |
|
|
|
function BossfightFaucheaux:UpdateUnitArchetypes() |
|
assert(g_TacticalMap) |
|
for _, unit in ipairs(g_Units) do |
|
if unit.team.player_enemy and not unit:IsDead() then |
|
local def_id = unit.unitdatadef_id or false |
|
local classdef = g_Classes[def_id] |
|
local base_archetype = classdef and classdef.archetype |
|
if self.run_away and unit ~= self.boss then |
|
local orig_area = self.original_area[unit] |
|
unit.archetype = base_archetype |
|
if not self.released_areas[orig_area] then |
|
unit.scrpit_archetype = "GuardArea" |
|
else |
|
unit.scrpit_archetype = nil |
|
end |
|
else |
|
unit.scrpit_archetype = "GuardArea" |
|
end |
|
end |
|
end |
|
|
|
|
|
if self.run_away then |
|
self.boss.scrpit_archetype = "Faucheaux_BossRetreating" |
|
|
|
local cur_area = g_TacticalMap:GetUnitPrimaryArea(self.boss) |
|
if cur_area == "Control_Zone_FinalDest" then |
|
g_TacticalMap:AssignUnit(self.boss, "Control_Zone_FinalDest", "reset") |
|
elseif self.chosen_path == 1 then |
|
self:AssignToNextArea(path1areas, cur_area) |
|
else |
|
self:AssignToNextArea(path2areas, cur_area) |
|
end |
|
else |
|
g_TacticalMap:AssignUnit(self.boss, "Control_Zone_FaucheauxCabinet", "reset") |
|
end |
|
end |
|
|
|
function BossfightFaucheaux:OnTurnStart() |
|
if g_Combat and self.escape_turn and g_Combat.current_turn >= self.escape_turn then |
|
|
|
if not self.escaped then |
|
self.escaped = true |
|
|
|
CreateGameTimeThread(function() |
|
|
|
while not IsSetpiecePlaying() do |
|
WaitMsg("SetpieceStarted", 10) |
|
end |
|
|
|
|
|
while IsSetpiecePlaying() do |
|
WaitMsg("SetpieceEnded", 10) |
|
end |
|
|
|
|
|
self:OnTurnStart() |
|
end) |
|
return |
|
end |
|
end |
|
|
|
if self.escaped then return end |
|
|
|
for _, obj in ipairs(Groups.Faucheux) do |
|
if IsKindOf(obj, "Unit") then |
|
self.boss = obj |
|
break |
|
end |
|
end |
|
|
|
g_Encounter:UpdateAwareness() |
|
g_Encounter:UpdateAreaProgress() |
|
g_Encounter:UpdateUnitArchetypes() |
|
end |