Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/FiveStack.Events/PlayerDisconnected.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public HookResult OnPlayerDisconnect(EventPlayerDisconnect @event, GameEventInfo
match.captainSystem.RemoveCaptain(@event.Userid);
}

_surrenderSystem.surrenderingVote?.RemovePlayerVote(player.SteamID);
_timeoutSystem.pauseVote?.RemovePlayerVote(player.SteamID);
_timeoutSystem.resumeVote?.RemovePlayerVote(player.SteamID);
_gameBackupRounds.restoreRoundVote?.RemovePlayerVote(player.SteamID);

if (match.IsInProgress())
{
if (match.IsFreezePeriod())
Expand Down
2 changes: 1 addition & 1 deletion src/FiveStack.Services/CaptainSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public void RemoveCaptain(CCSPlayerController player)

if (
match == null
|| !match.IsWarmup()
|| !(match.IsWarmup() || match.IsKnife())
|| team == CsTeam.None
|| team == CsTeam.Spectator
|| _captains[team] == null
Expand Down
92 changes: 92 additions & 0 deletions src/FiveStack.Services/MatchManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,86 @@ namespace FiveStack;

public class MatchManager
{
private static readonly Dictionary<eMapStatus, HashSet<eMapStatus>> _allowedTransitions =
new()
{
{
eMapStatus.Unknown,
new HashSet<eMapStatus>
{
eMapStatus.Scheduled,
eMapStatus.Warmup,
eMapStatus.Knife,
eMapStatus.Live,
eMapStatus.Paused,
eMapStatus.Overtime,
eMapStatus.Finished,
eMapStatus.Surrendered,
eMapStatus.UploadingDemo,
}
},
{ eMapStatus.Scheduled, new HashSet<eMapStatus> { eMapStatus.Warmup } },
{
eMapStatus.Warmup,
new HashSet<eMapStatus>
{
eMapStatus.Knife,
eMapStatus.Live,
eMapStatus.Paused,
}
},
{
eMapStatus.Knife,
new HashSet<eMapStatus>
{
eMapStatus.Warmup,
eMapStatus.Live,
eMapStatus.Paused,
}
},
{
eMapStatus.Live,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sometimes we may need to restart go bk to warmup / restart knife round.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Added restart transitions in abbd6d3:

  • LiveWarmup / Knife (restart from live)
  • OvertimeWarmup / Knife / Live (full or knife restart during OT, or roll OT back to regulation)
  • KnifeWarmup (go back to warmup from knife)

The downstream handlers (StartWarmup, StartKnife with mp_restartgame 1) already cover the game-side reset, so no other changes needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heads-up on a related concern the reviewer flagged (pre-existing, not introduced here but worth your call):

When going Live/Overtime/KnifeWarmup (or Knife), StartWarmup()/StartKnife() do not stop the demo or reset backup state:

  • _matchDemos.Start() (GameDemos.cs:53) bails early if the lock file exists → restarting Live won't re-initialize recording, producing a continuous demo across the restart
  • _backUpManagement.Setup() cvar (mp_backup_round_file) is not reset

This gap already exists for the Paused → Warmup path, so this PR doesn't regress anything, but the new transitions widen the surface. Options:

  1. Leave as-is — admin restart keeps one continuous demo (current behavior)
  2. Stop demo on Warmup/Knife entry from active states — produces a fresh demo file per restart
  3. Handle at a different layer (server-side tv_stoprecord + file rotation)

Happy to implement (2) in this PR or a follow-up if that's what you want.

new HashSet<eMapStatus>
{
eMapStatus.Warmup,
eMapStatus.Knife,
eMapStatus.Paused,
eMapStatus.Finished,
eMapStatus.Overtime,
eMapStatus.Surrendered,
eMapStatus.UploadingDemo,
}
},
{
eMapStatus.Overtime,
new HashSet<eMapStatus>
{
eMapStatus.Warmup,
eMapStatus.Knife,
eMapStatus.Live,
eMapStatus.Paused,
eMapStatus.Finished,
eMapStatus.Surrendered,
eMapStatus.UploadingDemo,
}
},
{
eMapStatus.Paused,
new HashSet<eMapStatus>
{
eMapStatus.Live,
eMapStatus.Warmup,
eMapStatus.Overtime,
eMapStatus.Knife,
eMapStatus.Finished,
eMapStatus.Surrendered,
}
},
{ eMapStatus.UploadingDemo, new HashSet<eMapStatus> { eMapStatus.Finished } },
{ eMapStatus.Finished, new HashSet<eMapStatus>() },
{ eMapStatus.Surrendered, new HashSet<eMapStatus> { eMapStatus.Finished } },
};

private MatchData? _matchData;
private eMapStatus _currentMapStatus = eMapStatus.Unknown;
private Timer? _resumeMessageTimer;
Expand Down Expand Up @@ -265,6 +345,18 @@ public void UpdateMapStatus(eMapStatus status, Guid? winningLineupId = null)

_logger.LogInformation($"Update Map Status {_currentMapStatus} -> {status}");

if (
_currentMapStatus != eMapStatus.Unknown
&& _allowedTransitions.TryGetValue(_currentMapStatus, out var allowed)
&& !allowed.Contains(status)
)
{
_logger.LogWarning(
$"Illegal map status transition {_currentMapStatus} -> {status}, ignoring"
);
return;
}

if (_currentMapStatus == eMapStatus.Unknown)
{
_backUpManagement.CheckForBackupRestore();
Expand Down
5 changes: 2 additions & 3 deletions src/FiveStack.Services/VoteSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ private void CheckVotes(bool fail = false)

if (IsCaptainVoteOnly())
{
if (_votes.Count < 2)
if (_votes.Count < expectedVoteCount)
{
if (fail)
{
Expand All @@ -328,7 +328,7 @@ private void CheckVotes(bool fail = false)
return;
}

if (totalYesVotes >= 2)
if (totalYesVotes >= Math.Ceiling(expectedVoteCount / 2.0))
{
VoteSuccess();
return;
Expand Down Expand Up @@ -367,7 +367,6 @@ private int GetExpectedVoteCount()
.Where(player =>
{
return CanVote(player);
;
})
.ToList();

Expand Down
Loading