@@ -2717,17 +2717,27 @@ function AIRBOSS:DeleteRecoveryWindow( Window, Delay )
27172717 self:ScheduleOnce( Delay, self.DeleteRecoveryWindow, self, Window )
27182718 else
27192719
2720- for i, _recovery in pairs( self.recoverytimes ) do
2721- local recovery = _recovery -- #AIRBOSS.Recovery
2720+ if not Window then
2721+ return
2722+ end
27222723
2723- if Window and Window.ID == recovery.ID then
2724- if Window.OPEN then
2725- -- Window is currently open.
2726- self:RecoveryStop()
2727- else
2728- table.remove( self.recoverytimes, i )
2729- end
2724+ -- If this window is currently open, stop recovery first. Mark it OVER so the
2725+ -- recovery time check cannot re-open it before/while we remove it.
2726+ if Window.OPEN then
2727+ Window.OPEN = false
2728+ Window.OVER = true
2729+ if self:IsRecovering() then
2730+ self:RecoveryStop()
2731+ end
2732+ end
27302733
2734+ -- Remove the window from the queue by its unique ID. Iterate over a numerically
2735+ -- indexed copy of the keys and remove via ipairs-safe reverse loop so that the
2736+ -- removal does not corrupt traversal (the window may appear once).
2737+ for i = #self.recoverytimes, 1, -1 do
2738+ local recovery = self.recoverytimes[i] -- #AIRBOSS.Recovery
2739+ if recovery and recovery.ID == Window.ID then
2740+ table.remove( self.recoverytimes, i )
27312741 end
27322742 end
27332743 end
@@ -4078,15 +4088,23 @@ function AIRBOSS:_CheckRecoveryTimes()
40784088 if self:IsRecovering() then
40794089 -- Carrier is already recovering.
40804090 state = "in progress"
4081- else
4082- -- Start recovery.
4091+ elseif not recovery.OVER then
4092+ -- Start recovery. Only if the window has not already been closed/cancelled.
4093+ -- The OVER guard prevents a window that was stopped manually (e.g. via the
4094+ -- Skipper "Stop Recovery" menu) from being immediately re-opened on the next
4095+ -- status tick while its [START,STOP) range is still active.
40834096 self:RecoveryStart( recovery.CASE, recovery.OFFSET )
40844097 state = "starting now"
40854098 recovery.OPEN = true
4099+ else
4100+ -- Window was already closed/cancelled within its active time range.
4101+ state = "cancelled"
40864102 end
40874103
4088- -- Set current recovery window.
4089- currwindow = recovery
4104+ -- Set current recovery window (unless this window has been cancelled).
4105+ if not recovery.OVER then
4106+ currwindow = recovery
4107+ end
40904108
40914109 else -- Stop time HAS passed.
40924110
@@ -4361,11 +4379,20 @@ function AIRBOSS:onafterRecoveryStop( From, Event, To )
43614379 self:CarrierResumeRoute( coord )
43624380 end
43634381
4364- -- Delete current recovery window if open.
4365- if self.recoverywindow and self.recoverywindow.OPEN == true then
4382+ -- Mark the current recovery window closed and cancelled, then remove it from the
4383+ -- queue. We do NOT gate this on Window.OPEN: that flag is only set by
4384+ -- _CheckRecoveryTimes (not by RecoveryStart), and the recovery time check nils and
4385+ -- rebuilds self.recoverywindow every status tick, so OPEN is not a reliable signal
4386+ -- here. Setting OVER=true is what actually prevents the window from being re-opened
4387+ -- on the next tick while its [START,STOP) range is still active.
4388+ --
4389+ -- The removal is deferred by one tick (Delay>0) so it does not mutate the
4390+ -- recoverytimes table while _CheckRecoveryTimes may be iterating over it (the natural
4391+ -- close path calls RecoveryStop() from inside that loop).
4392+ if self.recoverywindow then
43664393 self.recoverywindow.OPEN = false
43674394 self.recoverywindow.OVER = true
4368- self:DeleteRecoveryWindow( self.recoverywindow )
4395+ self:DeleteRecoveryWindow( self.recoverywindow, 1 )
43694396 end
43704397
43714398 -- Check recovery windows. This sets self.recoverywindow to the next window.
0 commit comments