From 9fe80430c683be7c55784d7f75e2a2e0dcd6f6fa Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Sun, 21 Jun 2026 22:06:10 -0700 Subject: [PATCH 1/3] Add AbandonedArmyGracePeriod Fixes online replays being cut off at the point where someone disconnected due to the disconnected player reporting a game over state. --- lua/SimUtils.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lua/SimUtils.lua b/lua/SimUtils.lua index 399ff1236d..8f3f6fda9a 100644 --- a/lua/SimUtils.lua +++ b/lua/SimUtils.lua @@ -994,6 +994,13 @@ end -- would have blown up. EndGameGracePeriod = 10 +-- Seconds to wait after an army has been abandoned (the player disconnected) +-- before processing its units. This gives a small opportunity to avoid the ACU +-- explosion, and prevents disconnecting players from providing the server with +-- a cut-off replay due to the sim on their client instantly killing all other ACUs +-- and reporting a game over state. +AbandonedArmyGracePeriod = 3 + -- Set to true in `AbstractVictoryCondition.EndGame` to prevent killing units after a -- team is victorious but before the sim is stopped. GameIsEnding = false @@ -1345,8 +1352,8 @@ function KillAbandonedArmy(self, shareOption, shareAcuOption, victoryOption) shareOption = ScenarioInfo.Options.Share end - -- Don't apply instant-effect disconnect rules for players/ACUs that might be defeated soon, - -- and might have intentionally disconnected. + WaitSeconds(AbandonedArmyGracePeriod) + if shareAcuOption == 'Explode' or shareAcuOption == 'Recall' then local safeCommanders local commanders = self:GetListOfUnits(categories.COMMAND, false) From 773b12aa7b57fccd89ade61d6e636268a0c450ad Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Mon, 22 Jun 2026 02:03:22 -0700 Subject: [PATCH 2/3] Move extraneous delays into threads to make KillArmy's timing consistent across different scenarios --- lua/SimUtils.lua | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/lua/SimUtils.lua b/lua/SimUtils.lua index 8f3f6fda9a..3e97b7f8b4 100644 --- a/lua/SimUtils.lua +++ b/lua/SimUtils.lua @@ -992,6 +992,8 @@ end -- shortly thereafter due to the army being defeated (as is common), we then -- have an opportunity to see the final game state as observers before everything -- would have blown up. +-- +-- This should be longer than `AbandonedArmyGracePeriod` EndGameGracePeriod = 10 -- Seconds to wait after an army has been abandoned (the player disconnected) @@ -999,6 +1001,8 @@ EndGameGracePeriod = 10 -- explosion, and prevents disconnecting players from providing the server with -- a cut-off replay due to the sim on their client instantly killing all other ACUs -- and reporting a game over state. +-- +-- This should be shorter than `EndGameGracePeriod` AbandonedArmyGracePeriod = 3 -- Set to true in `AbstractVictoryCondition.EndGame` to prevent killing units after a @@ -1017,6 +1021,16 @@ local function KillUnits(toKill) end end +--- Kills all given units after a delay, if not already dead. +---@param toKill Entity[] +---@param delaySec number +local function DelayedKillUnits(toKill, delaySec) + ForkThread(function () + WaitSeconds(delaySec) + KillUnits(toKill) + end) +end + ---@param self AIBrain local function KillWalls(self) KillUnits(self:GetListOfUnits(categories.WALL, false)) @@ -1258,13 +1272,17 @@ MinimumShareTime = 5 * 60 * 10 -- 5 minutes function KillUnsafeCommanders(commanders, tick) tick = tick or GetGameTick() local safeCommanders = {} + local unsafeCommanders = {} for _, com in commanders do if com.LastTickDamaged and com.LastTickDamaged + CommanderSafeTime > tick then - com:Kill() + table.insert(unsafeCommanders, com) else table.insert(safeCommanders, com) end end + + DelayedKillUnits(unsafeCommanders, AbandonedArmyGracePeriod) + return safeCommanders end @@ -1312,8 +1330,7 @@ function KillArmyOnDelayedRecall(self, shareOption, shareTime) local safeCommanders = KillUnsafeCommanders(sharedCommanders) if not table.empty(safeCommanders) then - -- note: this adds 3 seconds to the grace period - FakeTeleportUnits(safeCommanders, true) + ForkThread(FakeTeleportUnits, safeCommanders, true) end end end @@ -1352,8 +1369,6 @@ function KillAbandonedArmy(self, shareOption, shareAcuOption, victoryOption) shareOption = ScenarioInfo.Options.Share end - WaitSeconds(AbandonedArmyGracePeriod) - if shareAcuOption == 'Explode' or shareAcuOption == 'Recall' then local safeCommanders local commanders = self:GetListOfUnits(categories.COMMAND, false) @@ -1361,7 +1376,7 @@ function KillAbandonedArmy(self, shareOption, shareAcuOption, victoryOption) safeCommanders = KillUnsafeCommanders(commanders) else -- explode all the ACUs so they don't get shared - KillUnits(commanders) + DelayedKillUnits(commanders, AbandonedArmyGracePeriod) end -- Only handle Assassination victory, as in other settings the player is unlikely to be defeated soon @@ -1371,8 +1386,7 @@ function KillAbandonedArmy(self, shareOption, shareAcuOption, victoryOption) -- non-assassination modes can have armies abandon without commanders if shareAcuOption == 'Recall' and not table.empty(safeCommanders) then - -- note: this adds 3 seconds to the grace period - FakeTeleportUnits(safeCommanders, true) + ForkThread(FakeTeleportUnits, safeCommanders, true) end KillArmy(self, shareOption) From b0b5ebce9b2dde9ba233a95b5c87e1bcd0854fe4 Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Mon, 22 Jun 2026 02:13:49 -0700 Subject: [PATCH 3/3] Create fix.7153.md --- changelog/snippets/fix.7153.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/snippets/fix.7153.md diff --git a/changelog/snippets/fix.7153.md b/changelog/snippets/fix.7153.md new file mode 100644 index 0000000000..c9ac1e031f --- /dev/null +++ b/changelog/snippets/fix.7153.md @@ -0,0 +1 @@ +- (#7153) Add a delay before ACUs explode when their player disconnects. This should fix replays cuttting off at the first disconnected player. \ No newline at end of file