@@ -24,6 +24,7 @@ public class PlanetWarsMatchMaker : PlanetWarsMatchMakerState
2424 private int missedDefenseCount = 0 ;
2525 private int missedDefenseFactionID = 0 ;
2626 private ZkLobbyServer . ZkLobbyServer server ;
27+ private DateTime ? defendersFullTime ; // set when total defenders >= total attacker slots
2728
2829 private Timer timer ;
2930
@@ -73,7 +74,7 @@ public PlanetWarsMatchMaker(ZkLobbyServer.ZkLobbyServer server)
7374 AttackerSideCounter = gal . AttackerSideCounter ;
7475 AttackerSideChangeTime = gal . AttackerSideChangeTime ?? DateTime . UtcNow ;
7576 Phase = PwPhase . AttackCollect ;
76- PhaseStartTime = GetNextTurnBoundary ( ) ;
77+ PhaseStartTime = DateTime . UtcNow ;
7778 }
7879
7980 timer = new Timer ( 1045 ) ;
@@ -143,8 +144,28 @@ private async void TimerOnElapsed(object sender, ElapsedEventArgs elapsedEventAr
143144 break ;
144145
145146 case PwPhase . DefendCollect :
146- if ( DateTime . UtcNow > GetDefendDeadline ( ) )
147+ // check if enough defenders volunteered — start 30s countdown
148+ var totalSlots = FormedSquads . Sum ( s => s . TeamSize ) ;
149+ var totalDefenders = DefenderVotes . Values . Sum ( v => v . Count ) ;
150+ if ( totalDefenders >= totalSlots )
147151 {
152+ if ( defendersFullTime == null ) defendersFullTime = DateTime . UtcNow ;
153+ }
154+ else
155+ {
156+ defendersFullTime = null ;
157+ }
158+
159+ var deadline = GetDefendDeadline ( ) ;
160+ if ( defendersFullTime != null )
161+ {
162+ var earlyDeadline = defendersFullTime . Value . AddSeconds ( 30 ) ;
163+ if ( earlyDeadline < deadline ) deadline = earlyDeadline ;
164+ }
165+
166+ if ( DateTime . UtcNow > deadline )
167+ {
168+ defendersFullTime = null ;
148169 RunDefenderAssignment ( ) ;
149170 await LaunchAllBattles ( ) ;
150171 RunGalaxyTick ( ) ;
@@ -687,36 +708,14 @@ public void AddAttackOption(Planet planet)
687708 }
688709 }
689710
690- /// <summary>
691- /// Total turn duration in minutes (attack + defend). Turns snap to this boundary on the wall clock.
692- /// </summary>
693- private static readonly int TurnIntervalMinutes = GlobalConst . PlanetWarsMinutesToAttack + GlobalConst . PlanetWarsMinutesToAccept ;
694-
695- /// <summary>
696- /// Returns the current or next wall-clock turn boundary (e.g. :00, :15, :30, :45 for 15-min turns).
697- /// If exactly on a boundary, returns that time. Otherwise returns the next one.
698- /// </summary>
699- private static DateTime GetNextTurnBoundary ( )
700- {
701- var now = DateTime . UtcNow ;
702- // truncate to whole minutes to avoid floating point issues
703- var nowTrunc = new DateTime ( now . Year , now . Month , now . Day , now . Hour , now . Minute , 0 , DateTimeKind . Utc ) ;
704- var midnight = nowTrunc . Date ;
705- var totalMinutes = ( int ) ( nowTrunc - midnight ) . TotalMinutes ;
706- var remainder = totalMinutes % TurnIntervalMinutes ;
707- if ( remainder == 0 )
708- return nowTrunc ; // already on a boundary
709- return nowTrunc . AddMinutes ( TurnIntervalMinutes - remainder ) ;
710- }
711-
712711 private void ResetAttackOptions ( )
713712 {
714713 AttackOptions . Clear ( ) ;
715714 FormedSquads . Clear ( ) ;
716715 DefenderVotes . Clear ( ) ;
717716 Phase = PwPhase . AttackCollect ;
718- PhaseStartTime = GetNextTurnBoundary ( ) ;
719- AttackerSideChangeTime = PhaseStartTime ;
717+ PhaseStartTime = DateTime . UtcNow ;
718+ AttackerSideChangeTime = DateTime . UtcNow ;
720719
721720 var contestedPlanetIds = RunningBattles . Values . Select ( x => x . PlanetID ) . ToHashSet ( ) ;
722721
0 commit comments