diff --git a/AdminStealth.cs b/AdminStealth.cs index 317484c..f4ee8fb 100644 --- a/AdminStealth.cs +++ b/AdminStealth.cs @@ -23,7 +23,7 @@ namespace AdminStealth; public class AdminStealth : BasePlugin { public override string ModuleName => "AdminStealth"; - public override string ModuleVersion => "0.3.13"; + public override string ModuleVersion => "0.3.14"; public override string ModuleAuthor => "astra"; public override string ModuleDescription => "Makes CS2-SimpleAdmin's css_hide look like leaving, and keeps it"; @@ -80,6 +80,7 @@ public class AdminStealth : BasePlugin _hideTyped.Clear(); _restoreScheduled.Clear(); _restoring.Clear(); + _closingTeamScreen.Clear(); }); } @@ -136,20 +137,42 @@ public class AdminStealth : BasePlugin player!.PrintToChat($" You're hidden, so you can't join a team. Type {ChatAlias()} to show yourself."); - // M opens the team screen on the client, where the server can't block it, and a player with - // no team (as hidden admins are) can't close it without picking. Answer the pick by telling - // only their client they joined spectators, with the team screen disabled around it, so - // nobody else ever sees them on a team. + CloseTeamScreen(player); + return HookResult.Stop; + } + + // M opens the team screen on the client, where the server can't block it, and a player with no + // team (as hidden admins are) can't close it without picking. Telling only their client they + // joined spectators doesn't close it (tried live), so do what css_hide does: really move them + // to spectators and back to no team, with the team screen disabled around it. Their team + // events aren't broadcast while hidden (OnPlayerTeam), and our SimpleAdmin fork doesn't unhide + // for spectators. + private readonly HashSet _closingTeamScreen = []; + + private void CloseTeamScreen(CCSPlayerController player) + { var slot = player.Slot; + if (!_closingTeamScreen.Add(slot)) + return; + Server.ExecuteCommand("sv_disable_teamselect_menu 1"); - AddTimer(0.3f, () => + Server.NextFrame(() => { var p = Utilities.GetPlayerFromSlot(slot); - if (p is { IsValid: true } && IsHidden(p)) - SendTeamEvent(p, (int)CsTeam.Spectator, p.TeamNum); + if (p is { IsValid: true } && IsHidden(p) && p.TeamNum == (byte)CsTeam.None) + p.ChangeTeam(CsTeam.Spectator); + }); + AddTimer(0.15f, () => + { + var p = Utilities.GetPlayerFromSlot(slot); + if (p is { IsValid: true } && IsHidden(p) && p.TeamNum == (byte)CsTeam.Spectator) + p.ChangeTeam(CsTeam.None); + }, TimerFlags.STOP_ON_MAPCHANGE); + AddTimer(0.5f, () => + { + _closingTeamScreen.Remove(slot); + Server.ExecuteCommand("sv_disable_teamselect_menu 0"); }, TimerFlags.STOP_ON_MAPCHANGE); - AddTimer(0.6f, () => Server.ExecuteCommand("sv_disable_teamselect_menu 0"), TimerFlags.STOP_ON_MAPCHANGE); - return HookResult.Stop; } private HookResult OnTeamMenu(CCSPlayerController? player, CommandInfo command) diff --git a/README.md b/README.md index 5f82ae0..19b1e81 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,9 @@ admin off the scoreboard. This plugin makes it look like they left the server: the team select screen doesn't open, until the restore hides them again. - The team select screen: the server's "team" menu message (`CS_UM_VGUIMenu`) is never sent to a hidden admin. M opens it on the client regardless, and a player with no team can't close it - without picking, so a pick is answered by telling only that client it joined spectators - (with `sv_disable_teamselect_menu` on around it); nobody else sees a team change. + without picking, so a pick moves them to spectators and straight back to no team, as `css_hide` + does (with `sv_disable_teamselect_menu` on around it), which closes it. The team events aren't + broadcast; for about 0.15 s they're on the spectator list of anyone looking at the scoreboard. - Restoring runs `css_hide` at most once: it toggles, so a second run could unhide them. Needs our SimpleAdmin fork (1.8.2b-zio2 or later), where moving to spectators doesn't unhide a