diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..a7d0fc7b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "esbonio.sphinx.confDir": "" +} \ No newline at end of file diff --git a/Docs/source/admin/commands.rst b/Docs/source/admin/commands.rst index 04283d38..90ce59bd 100644 --- a/Docs/source/admin/commands.rst +++ b/Docs/source/admin/commands.rst @@ -46,3 +46,11 @@ These commands are available through rcon or to users with the required permissi +----------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ | ``!ps_dumpmatch`` | Dumps the current matchstate and config to console | +----------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ``!ps_creatematch`` | Creates a new match without preloaded configuration. | ++----------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ``!ps_startmatch`` | After match configuration is done with ``!ps_creatematch`` the match can be started. | ++----------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ``!ps_addmap`` | Add a map to the map pool during match creation. | ++----------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ +| ``!ps_removemap`` | Remove a map from the map pool during match creation. | ++----------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------+ diff --git a/Docs/source/admin/configuration.rst b/Docs/source/admin/configuration.rst index 1bf87cdf..750b775a 100644 --- a/Docs/source/admin/configuration.rst +++ b/Docs/source/admin/configuration.rst @@ -59,7 +59,9 @@ Matchconfig Fields +--------------------------+-----------------+-------------------------------------------------------------------------------------------+ | server_locale | en | This is the language that will be used for the messages that are printed to the users | +--------------------------+-----------------+-------------------------------------------------------------------------------------------+ - +| team_mode | 0 | Change how teams are defined. 0: Default (Teams are fix defined) 1: Scramble (Teams are scrambled when all players are ready) | ++--------------------------+-----------------+-------------------------------------------------------------------------------------------+ + Matchconfig Example ''''''''''''''''''''' diff --git a/PugSharp.Api.Contract/PugSharp.Api.Contract.csproj b/PugSharp.Api.Contract/PugSharp.Api.Contract.csproj index c99a6078..cc4ce739 100644 --- a/PugSharp.Api.Contract/PugSharp.Api.Contract.csproj +++ b/PugSharp.Api.Contract/PugSharp.Api.Contract.csproj @@ -6,6 +6,13 @@ enable + + + + + + + all diff --git a/PugSharp.Api.G5Api/PugSharp.Api.G5Api.csproj b/PugSharp.Api.G5Api/PugSharp.Api.G5Api.csproj index 2e7bedcf..014d3b59 100644 --- a/PugSharp.Api.G5Api/PugSharp.Api.G5Api.csproj +++ b/PugSharp.Api.G5Api/PugSharp.Api.G5Api.csproj @@ -7,7 +7,8 @@ - + + diff --git a/PugSharp.Api.Json/PugSharp.Api.Json.csproj b/PugSharp.Api.Json/PugSharp.Api.Json.csproj index 6fa8d935..23126c15 100644 --- a/PugSharp.Api.Json/PugSharp.Api.Json.csproj +++ b/PugSharp.Api.Json/PugSharp.Api.Json.csproj @@ -7,7 +7,10 @@ - + + + + diff --git a/PugSharp.ApiStats/BaseApi.cs b/PugSharp.ApiStats/BaseApi.cs index 7321afa4..85d78cd1 100644 --- a/PugSharp.ApiStats/BaseApi.cs +++ b/PugSharp.ApiStats/BaseApi.cs @@ -33,6 +33,8 @@ protected void InitializeBase(string? baseUrl, string? authKey) HttpClient.BaseAddress = new Uri(baseUrl); + HttpClient.DefaultRequestHeaders.Remove(HeaderNames.Authorization); + if (!string.IsNullOrEmpty(authKey)) { HttpClient.DefaultRequestHeaders.Add(HeaderNames.Authorization, authKey); diff --git a/PugSharp.ApiStats/PugSharp.ApiStats.csproj b/PugSharp.ApiStats/PugSharp.ApiStats.csproj index 3c1145c3..f0f2ed41 100644 --- a/PugSharp.ApiStats/PugSharp.ApiStats.csproj +++ b/PugSharp.ApiStats/PugSharp.ApiStats.csproj @@ -13,7 +13,10 @@ - + + + + all runtime; build; native; contentfiles; analyzers diff --git a/PugSharp.Config/ConfigCreator.cs b/PugSharp.Config/ConfigCreator.cs new file mode 100644 index 00000000..21c7befa --- /dev/null +++ b/PugSharp.Config/ConfigCreator.cs @@ -0,0 +1,24 @@ +namespace PugSharp.Config; + +public class ConfigCreator +{ + public ConfigCreator() + { + Config = new MatchConfig + { + // TODO Maybe use guid for demo, ... + MatchId = "CustomMatch", + Team1 = new Team + { + Name = "Team 1", + }, + Team2 = new Team + { + Name = "Team 2", + }, + TeamMode = TeamMode.Scramble, + }; + } + + public MatchConfig Config { get; } +} diff --git a/PugSharp.Config/MatchConfig.cs b/PugSharp.Config/MatchConfig.cs index 948615d5..6add3b9e 100644 --- a/PugSharp.Config/MatchConfig.cs +++ b/PugSharp.Config/MatchConfig.cs @@ -5,7 +5,7 @@ namespace PugSharp.Config; public class MatchConfig { [JsonPropertyName("maplist")] - public required string[] Maplist { get; init; } + public IList Maplist { get; init; } = new List(); [JsonPropertyName("team1")] public required Team Team1 { get; init; } @@ -20,16 +20,16 @@ public class MatchConfig public int NumMaps { get; init; } = 1; [JsonPropertyName("players_per_team")] - public int PlayersPerTeam { get; init; } = 5; + public int PlayersPerTeam { get; set; } = 5; [JsonPropertyName("min_players_to_ready")] - public int MinPlayersToReady { get; init; } = 5; + public int MinPlayersToReady { get; set; } = 5; [JsonPropertyName("max_rounds")] - public int MaxRounds { get; init; } = 24; + public int MaxRounds { get; set; } = 24; [JsonPropertyName("max_overtime_rounds")] - public int MaxOvertimeRounds { get; init; } = 6; + public int MaxOvertimeRounds { get; set; } = 6; [JsonPropertyName("vote_timeout")] public long VoteTimeout { get; init; } = 60000; @@ -60,4 +60,7 @@ public class MatchConfig [JsonPropertyName("server_locale")] public string ServerLocale { get; init; } = "en"; + + [JsonPropertyName("team_mode")] + public TeamMode TeamMode { get; set; } } diff --git a/PugSharp.Config/PugSharp.Config.csproj b/PugSharp.Config/PugSharp.Config.csproj index c78198b9..158f71eb 100644 --- a/PugSharp.Config/PugSharp.Config.csproj +++ b/PugSharp.Config/PugSharp.Config.csproj @@ -7,7 +7,10 @@ - + + + + diff --git a/PugSharp.Config/Team.cs b/PugSharp.Config/Team.cs index b9c3ec5a..c0c5396c 100644 --- a/PugSharp.Config/Team.cs +++ b/PugSharp.Config/Team.cs @@ -5,7 +5,7 @@ namespace PugSharp.Config; public class Team { [JsonPropertyName("name")] - public required string Name { get; init; } + public required string Name { get; set; } [JsonPropertyName("tag")] public string Tag { get; init; } = string.Empty; @@ -14,5 +14,5 @@ public class Team public string Flag { get; init; } = string.Empty; [JsonPropertyName("players")] - public required IDictionary Players { get; init; } + public IDictionary Players { get; init; } = new Dictionary(); } diff --git a/PugSharp.Config/TeamMode.cs b/PugSharp.Config/TeamMode.cs new file mode 100644 index 00000000..06e9873e --- /dev/null +++ b/PugSharp.Config/TeamMode.cs @@ -0,0 +1,10 @@ +namespace PugSharp.Config; + +public enum TeamMode +{ + // Take Teams as they are + Default, + + // Scramble Teams afte all players are joined + Scramble, +} diff --git a/PugSharp.DebugDummy/PugSharp.DebugDummy.csproj b/PugSharp.DebugDummy/PugSharp.DebugDummy.csproj index da56d824..003e1bdb 100644 --- a/PugSharp.DebugDummy/PugSharp.DebugDummy.csproj +++ b/PugSharp.DebugDummy/PugSharp.DebugDummy.csproj @@ -12,6 +12,11 @@ ..\cs2\game\csgo\addons\counterstrikesharp\plugins\PugSharp\ + + + + + diff --git a/PugSharp.Match.Contract/MatchCommand.cs b/PugSharp.Match.Contract/MatchCommand.cs index 11e30fc0..f09935e9 100644 --- a/PugSharp.Match.Contract/MatchCommand.cs +++ b/PugSharp.Match.Contract/MatchCommand.cs @@ -14,4 +14,5 @@ public enum MatchCommand CompleteMap, Pause, Unpause, + TeamsDefined, } diff --git a/PugSharp.Match.Contract/MatchState.cs b/PugSharp.Match.Contract/MatchState.cs index 2944c019..7def2ec9 100644 --- a/PugSharp.Match.Contract/MatchState.cs +++ b/PugSharp.Match.Contract/MatchState.cs @@ -14,4 +14,5 @@ public enum MatchState MapCompleted, MatchCompleted, RestoreMatch, + DefineTeams, } diff --git a/PugSharp.Match.Contract/PugSharp.Match.Contract.csproj b/PugSharp.Match.Contract/PugSharp.Match.Contract.csproj index e0e653fa..23126c15 100644 --- a/PugSharp.Match.Contract/PugSharp.Match.Contract.csproj +++ b/PugSharp.Match.Contract/PugSharp.Match.Contract.csproj @@ -6,6 +6,13 @@ enable + + + + + + + diff --git a/PugSharp.Match.Tests/MatchTests.cs b/PugSharp.Match.Tests/MatchTests.cs index deef5fa5..61a1f403 100644 --- a/PugSharp.Match.Tests/MatchTests.cs +++ b/PugSharp.Match.Tests/MatchTests.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using NSubstitute; @@ -23,7 +22,7 @@ private static IServiceProvider CreateTestProvider() services.AddSingleton(Substitute.For()); services.AddLogging(options => { - options.AddConsole(); + //options.AddConsole(); }); services.AddSingleton(); @@ -151,16 +150,16 @@ private static async Task VoteTeam(ICsServer csServer, MatchConfig config, Match csServer.Received().SwitchMap(config.Maplist[^1]); Assert.Equal(MatchState.WaitingForPlayersReady, match.CurrentState); - await match.TogglePlayerIsReadyAsync(player1).ConfigureAwait(false); + match.TogglePlayerIsReady(player1); Assert.Equal(MatchState.WaitingForPlayersReady, match.CurrentState); - await match.TogglePlayerIsReadyAsync(player2).ConfigureAwait(false); + match.TogglePlayerIsReady(player2); Assert.Equal(MatchState.MatchRunning, match.CurrentState); } private static IPlayer VoteForMap(MatchConfig config, Match match, IPlayer player1, IPlayer player2) { - var matchCount = config.Maplist.Length; + var matchCount = config.Maplist.Count; var votePlayer = player1; Assert.False(match.BanMap(votePlayer, matchCount)); @@ -184,9 +183,9 @@ private static IPlayer VoteForMap(MatchConfig config, Match match, IPlayer playe private static async Task SetPlayersReady(Match match, IPlayer player1, IPlayer player2, MatchState expectedMatchStateAfterReady) { - await match.TogglePlayerIsReadyAsync(player1).ConfigureAwait(false); + match.TogglePlayerIsReady(player1); Assert.Equal(MatchState.WaitingForPlayersConnectedReady, match.CurrentState); - await match.TogglePlayerIsReadyAsync(player2).ConfigureAwait(false); + match.TogglePlayerIsReady(player2); Assert.Equal(expectedMatchStateAfterReady, match.CurrentState); } @@ -210,7 +209,7 @@ private static MatchConfig CreateExampleConfig(IEnumerable? mapList = nu mapListInternal = mapList; } - return new MatchConfig + var matchConfig = new MatchConfig { MatchId = "1337", PlayersPerTeam = 1, @@ -232,8 +231,14 @@ private static MatchConfig CreateExampleConfig(IEnumerable? mapList = nu { 1,"Def" }, }, }, - Maplist = mapListInternal.ToArray(), }; + + foreach (var map in mapListInternal) + { + matchConfig.Maplist.Add(map); + } + + return matchConfig; } private static IPlayer CreatePlayerSub(ulong steamId, int playerId) diff --git a/PugSharp.Match.Tests/PugSharp.Match.Tests.csproj b/PugSharp.Match.Tests/PugSharp.Match.Tests.csproj index ca55b5c8..7e8cb7fa 100644 --- a/PugSharp.Match.Tests/PugSharp.Match.Tests.csproj +++ b/PugSharp.Match.Tests/PugSharp.Match.Tests.csproj @@ -10,6 +10,8 @@ + + diff --git a/PugSharp.Match/Match.cs b/PugSharp.Match/Match.cs index bc2bcaea..26e1e923 100644 --- a/PugSharp.Match/Match.cs +++ b/PugSharp.Match/Match.cs @@ -7,6 +7,7 @@ using PugSharp.ApiStats; using PugSharp.Match.Contract; using PugSharp.Server.Contract; +using PugSharp.Shared; using PugSharp.Translation; using PugSharp.Translation.Properties; @@ -82,10 +83,10 @@ private void Initialize(MatchInfo matchInfo) { if (MatchInfo != null) { - throw new NotSupportedException("Initialize can onyl be called once!"); + throw new NotSupportedException("Initialize can only be called once!"); } - if (matchInfo.Config.Maplist.Length < matchInfo.Config.NumMaps) + if (matchInfo.Config.Maplist.Count < matchInfo.Config.NumMaps) { throw new NotSupportedException($"Can not create Match without the required number of maps! At lease {matchInfo.Config.NumMaps} are required!"); } @@ -129,12 +130,17 @@ private void InitializeStateMachine() .PermitDynamicIf(MatchCommand.LoadMatch, () => HasRestoredMatch() ? MatchState.RestoreMatch : MatchState.WaitingForPlayersConnectedReady); _MatchStateMachine.Configure(MatchState.WaitingForPlayersConnectedReady) - .PermitDynamicIf(MatchCommand.PlayerReady, () => HasRestoredMatch() ? MatchState.MatchRunning : MatchState.MapVote, AllPlayersAreReady) + .PermitDynamicIf(MatchCommand.PlayerReady, () => HasRestoredMatch() ? MatchState.MatchRunning : MatchState.DefineTeams, AllPlayersAreReady) .OnEntry(StartWarmup) .OnEntry(SetAllPlayersNotReady) .OnEntry(StartReadyReminder) .OnExit(StopReadyReminder); + _MatchStateMachine.Configure(MatchState.DefineTeams) + .Permit(MatchCommand.TeamsDefined, MatchState.MapVote) + .OnEntry(ContinueIfDefault) + .OnEntry(ScrambleTeams); + _MatchStateMachine.Configure(MatchState.MapVote) .PermitReentryIf(MatchCommand.VoteMap, MapIsNotSelected) .PermitIf(MatchCommand.VoteMap, MatchState.TeamVote, MapIsSelected) @@ -200,6 +206,30 @@ private void InitializeStateMachine() _MatchStateMachine.Fire(MatchCommand.LoadMatch); } + private void ScrambleTeams() + { + if (MatchInfo.Config.TeamMode == Config.TeamMode.Scramble) + { + var randomizedPlayers = AllMatchPlayers.Randomize().ToList(); + + MatchInfo.MatchTeam1.Players.Clear(); + MatchInfo.MatchTeam1.Players.AddRange(randomizedPlayers.Take(randomizedPlayers.Count.Half())); + + MatchInfo.MatchTeam2.Players.Clear(); + MatchInfo.MatchTeam2.Players.AddRange(randomizedPlayers.Skip(randomizedPlayers.Count.Half())); + + TryFireState(MatchCommand.TeamsDefined); + } + } + + private void ContinueIfDefault() + { + if (MatchInfo.Config.TeamMode == Config.TeamMode.Default) + { + TryFireState(MatchCommand.TeamsDefined); + } + } + private void StartWarmup() { _CsServer.LoadAndExecuteConfig("warmup.cfg"); @@ -525,21 +555,15 @@ private void TryCompleteMatch() _ = TryFireStateAsync(MatchCommand.CompleteMatch); } + + private async Task CompleteMatchAsync() { try { _CsServer.StopDemoRecording(); - var delay = 15; - - if (_CsServer.GetConvar("tv_enable") || _CsServer.GetConvar("tv_enable1")) - { - // TV Delay in s - var tvDelaySeconds = Math.Max(_CsServer.GetConvar("tv_delay"), _CsServer.GetConvar("tv_delay1")); - _Logger.LogInformation("Waiting for sourceTV. Delay: {delay}s + 15s", tvDelaySeconds); - delay += tvDelaySeconds; - } + int delay = GetSourceTvDelay(); var seriesResultParams = new SeriesResultParams(MatchInfo.Config.MatchId, MatchInfo.MatchMaps.GroupBy(x => x.Winner).MaxBy(x => x.Count())!.Key!.TeamConfig.Name, Forfeit: true, (uint)delay * 1100, MatchInfo.MatchMaps.Count(x => x.Team1Points > x.Team2Points), MatchInfo.MatchMaps.Count(x => x.Team2Points > x.Team1Points)); var finalize = _ApiProvider.FinalizeAsync(seriesResultParams, CancellationToken.None); @@ -574,6 +598,21 @@ private async Task CompleteMatchAsync() } } + private int GetSourceTvDelay() + { + var delay = 15; + + if (_CsServer.GetConvar("tv_enable") || _CsServer.GetConvar("tv_enable1")) + { + // TV Delay in s + var tvDelaySeconds = Math.Max(_CsServer.GetConvar("tv_delay"), _CsServer.GetConvar("tv_delay1")); + _Logger.LogInformation("Waiting for sourceTV. Delay: {delay}s + 15s", tvDelaySeconds); + delay += tvDelaySeconds; + } + + return delay; + } + private void MatchLive() { _ = Task.Run(async () => @@ -648,7 +687,7 @@ public string CreateDotGraph() private void InitializeMapsToVote(StateMachine.Transition transition) { - if (transition.Source == MatchState.WaitingForPlayersConnectedReady) + if (transition.Source == MatchState.DefineTeams) { var playedMaps = MatchInfo.MatchMaps.Select(x => x.MapName).Where(x => !string.IsNullOrEmpty(x)); _MapsToSelect = MatchInfo.Config.Maplist.Except(playedMaps!, StringComparer.Ordinal).Select(x => new Vote(x)).ToList(); @@ -664,7 +703,7 @@ private void SendRemainingMapsToVotingTeam() } // If only one map is configured - if (MatchInfo.Config.Maplist.Length == 1) + if (MatchInfo.Config.Maplist.Count == 1) { _MapsToSelect = MatchInfo.Config.Maplist.Select(x => new Vote(x)).ToList(); TryFireState(MatchCommand.VoteMap); @@ -684,8 +723,11 @@ private void SendRemainingMapsToVotingTeam() mapOptions.Add(new MenuOption(map, (opt, player) => BanMap(player, mapNumber))); } + ShowMenuToTeam(_CurrentMatchTeamToVote!, _TextHelper.GetText(nameof(Resources.PugSharp_Match_VoteMapMenuHeader)), mapOptions); - GetOtherTeam(_CurrentMatchTeamToVote!).PrintToChat(_TextHelper.GetText(nameof(Resources.PugSharp_Match_WaitForOtherTeam))); + + //GetOtherTeam(_CurrentMatchTeamToVote!).PrintToChat(_TextHelper.GetText(nameof(Resources.PugSharp_Match_WaitForOtherTeam))); + _VoteTimer.Start(); } @@ -729,7 +771,7 @@ private void SendTeamVoteToVotingteam() }; ShowMenuToTeam(_CurrentMatchTeamToVote!, _TextHelper.GetText(nameof(Resources.PugSharp_Match_VoteTeamMenuHeader)), mapOptions); - GetOtherTeam(_CurrentMatchTeamToVote!).PrintToChat(_TextHelper.GetText(nameof(Resources.PugSharp_Match_WaitForOtherTeam))); + //GetOtherTeam(_CurrentMatchTeamToVote!).PrintToChat(_TextHelper.GetText(nameof(Resources.PugSharp_Match_WaitForOtherTeam))); _VoteTimer.Start(); } @@ -763,7 +805,7 @@ private MatchTeam GetOtherTeam(MatchTeam team) private void ShowMenuToTeam(MatchTeam team, string title, IEnumerable options) { - DoForAll(team.Players.Select(p => p.Player).ToList(), p => p.ShowMenu(title, options)); + DoForAll(team.Players.Select(x => x.Player), p => p.ShowMenu(title, options)); } private void SwitchVotingTeam() @@ -796,7 +838,7 @@ private bool AllPlayersAreReady() var readyPlayers = AllMatchPlayers.Where(p => p.IsReady); var requiredPlayers = MatchInfo.Config.PlayersPerTeam * 2; - _Logger.LogInformation("Match has {readyPlayers} of {rquiredPlayers} ready players: {readyPlayers}", readyPlayers.Count(), requiredPlayers, string.Join("; ", readyPlayers.Select(a => $"{a.Player.PlayerName}[{a.IsReady}]"))); + //_Logger.LogInformation("Match has {readyPlayers} of {requiredPlayers} ready players: {readyPlayers}", readyPlayers.Count(), requiredPlayers, string.Join("; ", readyPlayers.Select(a => $"{a.Player.PlayerName}[{a.IsReady}]").ToList())); return readyPlayers.Take(requiredPlayers + 1).Count() == requiredPlayers; } @@ -900,14 +942,36 @@ private MatchPlayer GetMatchPlayer(ulong steamID) public bool TryAddPlayer(IPlayer player) { - var isTeam1 = MatchInfo.Config.Team1.Players.ContainsKey(player.SteamID); - var isTeam2 = !isTeam1 && MatchInfo.Config.Team2.Players.ContainsKey(player.SteamID); - if (!isTeam1 && !isTeam2) + if (!PlayerBelongsToMatch(player.SteamID)) { _Logger.LogInformation("Player with steam id {steamId} is no member of this match!", player.SteamID); return false; } + if (MatchInfo.MatchTeam1.Players.Any(x => x.Player.SteamID == player.SteamID) + || MatchInfo.MatchTeam2.Players.Any(x => x.Player.SteamID == player.SteamID)) + { + // Player is already part of this match + _ = TryFireStateAsync(MatchCommand.ConnectPlayer); + return true; + } + + var isTeam1 = MatchInfo.Config.Team1.Players.ContainsKey(player.SteamID); + var isTeam2 = !isTeam1 && MatchInfo.Config.Team2.Players.ContainsKey(player.SteamID); + if (MatchInfo.RandomPlayersAllowed && !isTeam1 && !isTeam2) + { + // if no team is configured add player to team with less players + isTeam1 = MatchInfo.MatchTeam1.Players.Count <= MatchInfo.MatchTeam2.Players.Count; + if (isTeam1) + { + MatchInfo.Config.Team1.Players.Add(player.SteamID, player.PlayerName); + } + else + { + MatchInfo.Config.Team2.Players.Add(player.SteamID, player.PlayerName); + } + } + var team = isTeam1 ? MatchInfo.MatchTeam1 : MatchInfo.MatchTeam2; var startSite = team.CurrentTeamSite; if (startSite == Team.None) @@ -924,12 +988,6 @@ public bool TryAddPlayer(IPlayer player) player.SwitchTeam(startSite); } - var existingPlayer = team.Players.FirstOrDefault(x => x.Player.SteamID.Equals(player.SteamID)); - if (existingPlayer != null) - { - team.Players.Remove(existingPlayer); - } - team.Players.Add(new MatchPlayer(player)); _ = TryFireStateAsync(MatchCommand.ConnectPlayer); @@ -970,7 +1028,7 @@ public void SetPlayerDisconnected(IPlayer player) } } - public async Task TogglePlayerIsReadyAsync(IPlayer player) + public void TogglePlayerIsReady(IPlayer player) { if (CurrentState != MatchState.WaitingForPlayersConnectedReady && CurrentState != MatchState.WaitingForPlayersReady) { @@ -989,7 +1047,7 @@ public async Task TogglePlayerIsReadyAsync(IPlayer player) if (matchPlayer.IsReady) { _CsServer.PrintToChatAll(_TextHelper.GetText(nameof(Resources.PugSharp_Match_Info_Ready), player.PlayerName, readyPlayers, requiredPlayers)); - await TryFireStateAsync(MatchCommand.PlayerReady).ConfigureAwait(false); + TryFireState(MatchCommand.PlayerReady); } else { @@ -1022,6 +1080,11 @@ private Team GetConfigTeam(ulong steamID) return Team.CounterTerrorist; } + if (MatchInfo.Config.Team1.Players.Count == 0 && MatchInfo.Config.Team2.Players.Count == 0) + { + return MatchInfo.MatchTeam1.Players.Count < MatchInfo.MatchTeam2.Players.Count ? Team.Terrorist : Team.CounterTerrorist; + } + return Team.None; } @@ -1180,6 +1243,9 @@ public void Dispose() public void CompleteMap(int tPoints, int ctPoints) { + int delay = GetSourceTvDelay(); + _CsServer.UpdateConvar("mp_win_panel_display_time", delay); + var winner = tPoints > ctPoints ? Team.Terrorist : Team.CounterTerrorist; var winnerTeam = GetMatchTeam(winner) ?? throw new NotSupportedException("Winner Team could not be found!"); @@ -1205,6 +1271,12 @@ public void CompleteMap(int tPoints, int ctPoints) public bool PlayerBelongsToMatch(ulong steamId) { + if (MatchInfo.RandomPlayersAllowed) + { + // Allow matches without player configuration wait for the first 10 players + return true; + } + return MatchInfo.Config.Team1.Players.Any(x => x.Key.Equals(steamId)) || MatchInfo.Config.Team2.Players.Any(x => x.Key.Equals(steamId)); } diff --git a/PugSharp.Match/MatchInfo.cs b/PugSharp.Match/MatchInfo.cs index c54cfe54..7d100568 100644 --- a/PugSharp.Match/MatchInfo.cs +++ b/PugSharp.Match/MatchInfo.cs @@ -14,8 +14,11 @@ public MatchInfo(MatchConfig config) MatchTeam1 = new MatchTeam(Config.Team1) { CurrentTeamSite = Contract.Team.Terrorist }; MatchTeam2 = new MatchTeam(Config.Team2) { CurrentTeamSite = Contract.Team.CounterTerrorist }; + RandomPlayersAllowed = Config.Team1.Players.Count == 0 && Config.Team2.Players.Count == 0; } + public bool RandomPlayersAllowed { get; } + [JsonIgnore] public MatchMap CurrentMap { get; set; } diff --git a/PugSharp.Match/PugSharp.Match.csproj b/PugSharp.Match/PugSharp.Match.csproj index d5422914..9dc9309b 100644 --- a/PugSharp.Match/PugSharp.Match.csproj +++ b/PugSharp.Match/PugSharp.Match.csproj @@ -7,6 +7,10 @@ + + + + @@ -15,6 +19,7 @@ + diff --git a/PugSharp.Server.Contract/ICsServer.cs b/PugSharp.Server.Contract/ICsServer.cs index c2d0db22..721ac95b 100644 --- a/PugSharp.Server.Contract/ICsServer.cs +++ b/PugSharp.Server.Contract/ICsServer.cs @@ -6,6 +6,7 @@ namespace PugSharp.Server.Contract; public interface ICsServer { string GameDirectory { get; } + string CurrentMap { get; } void DisableCheats(); void EndWarmup(); diff --git a/PugSharp.Server.Contract/PugSharp.Server.Contract.csproj b/PugSharp.Server.Contract/PugSharp.Server.Contract.csproj index 7bac2bbf..d57b244b 100644 --- a/PugSharp.Server.Contract/PugSharp.Server.Contract.csproj +++ b/PugSharp.Server.Contract/PugSharp.Server.Contract.csproj @@ -6,6 +6,13 @@ enable + + + + + + + diff --git a/PugSharp.Shared/EnumerableExtensions.cs b/PugSharp.Shared/EnumerableExtensions.cs new file mode 100644 index 00000000..ed94d591 --- /dev/null +++ b/PugSharp.Shared/EnumerableExtensions.cs @@ -0,0 +1,33 @@ +namespace PugSharp.Shared +{ + public static class EnumerableExtensions + { + public static IEnumerable Randomize(this IEnumerable source) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + + return source.RandomizeInternal(); + } + + private static IEnumerable RandomizeInternal( + this IEnumerable source) + { + var buffer = source.ToList(); + for (int i = 0; i < buffer.Count; i++) + { + int j = Random.Shared.Next(i, buffer.Count); + yield return buffer[j]; + + buffer[j] = buffer[i]; + } + } + + public static void AddRange(this IList items, IEnumerable itemsToAdd) + { + foreach (var item in itemsToAdd) + { + items.Add(item); + } + } + } +} diff --git a/PugSharp/NumericExtensions.cs b/PugSharp.Shared/NumericExtensions.cs similarity index 71% rename from PugSharp/NumericExtensions.cs rename to PugSharp.Shared/NumericExtensions.cs index 476fa20e..99031c9f 100644 --- a/PugSharp/NumericExtensions.cs +++ b/PugSharp.Shared/NumericExtensions.cs @@ -1,6 +1,6 @@ -namespace PugSharp +namespace PugSharp.Shared { - internal static class NumericExtensions + public static class NumericExtensions { private const double _HalfFactor = 0.5; public static int Half(this int value) diff --git a/PugSharp.Shared/PugSharp.Shared.csproj b/PugSharp.Shared/PugSharp.Shared.csproj index c99a6078..cc4ce739 100644 --- a/PugSharp.Shared/PugSharp.Shared.csproj +++ b/PugSharp.Shared/PugSharp.Shared.csproj @@ -6,6 +6,13 @@ enable + + + + + + + all diff --git a/PugSharp.Translation/PugSharp.Translation.csproj b/PugSharp.Translation/PugSharp.Translation.csproj index 9d861a71..be6d9f8f 100644 --- a/PugSharp.Translation/PugSharp.Translation.csproj +++ b/PugSharp.Translation/PugSharp.Translation.csproj @@ -7,6 +7,13 @@ en + + + + + + + True @@ -27,7 +34,6 @@ all runtime; build; native; contentfiles; analyzers - diff --git a/PugSharp/Application.cs b/PugSharp/Application.cs index 2ffa6bd3..1fff4403 100644 --- a/PugSharp/Application.cs +++ b/PugSharp/Application.cs @@ -7,7 +7,6 @@ using CounterStrikeSharp.API.Core.Attributes.Registration; using CounterStrikeSharp.API.Modules.Admin; using CounterStrikeSharp.API.Modules.Commands; -using CounterStrikeSharp.API.Modules.Cvars; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -26,6 +25,7 @@ namespace PugSharp; public class Application : IApplication { + private const int _SwitchPlayerDelay = 1000; private readonly ILogger _Logger; private readonly IBasePlugin _Plugin; private readonly ICsServer _CsServer; @@ -41,6 +41,7 @@ public class Application : IApplication private Match.Match? _Match; private bool _DisposedValue; + private ConfigCreator _ConfigCreator; private readonly CurrentRoundState _CurrentRountState = new(); /// @@ -123,7 +124,6 @@ private void RegisterEventHandlers() _Plugin.RegisterEventHandler(OnBombDefused); _Plugin.RegisterEventHandler(OnBombPlanted); - _Plugin.AddCommandListener("jointeam", OnClientCommandJoinTeam); _Logger.LogInformation("End RegisterEventHandlers"); @@ -163,6 +163,11 @@ private HookResult OnPlayerConnectFull(EventPlayerConnectFull eventPlayerConnect // // Userid will give you a reference to a CCSPlayerController class _Logger.LogInformation("Player {playerName} has connected!", userId.PlayerName); + if (userId.IsHLTV) + { + return HookResult.Continue; + } + if (_Match != null) { if (!_Match.PlayerBelongsToMatch(eventPlayerConnectFull.Userid.SteamID)) @@ -191,8 +196,7 @@ private HookResult OnPlayerConnectFull(EventPlayerConnectFull eventPlayerConnect } else { - _Logger.LogInformation("No match is loaded. Kick Player {player}!", userId.PlayerName); - userId.Kick(); + // Do nothign if no match is loaded } } else @@ -219,16 +223,15 @@ public HookResult OnPlayerTeam(EventPlayerTeam eventPlayerTeam, GameEventInfo in } else { - _Logger.LogInformation("No match is loaded. Kick Player {player}!", eventPlayerTeam.Userid.PlayerName); - eventPlayerTeam.Userid.Kick(); + // Do nothing } return HookResult.Continue; } - private void CheckMatchPlayerTeam(CCSPlayerController playerController, int team) + private async void CheckMatchPlayerTeam(CCSPlayerController playerController, int team) { - if (_Match == null) + if (_Match == null || !playerController.IsValid) { return; } @@ -236,17 +239,17 @@ private void CheckMatchPlayerTeam(CCSPlayerController playerController, int team if (_Match.CurrentState == MatchState.WaitingForPlayersConnectedReady || _Match.CurrentState == MatchState.WaitingForPlayersReady) { var configTeam = _Match.GetPlayerTeam(playerController.SteamID); + var localPlayer = playerController; if ((int)configTeam != team) { - _Logger.LogInformation("Player {playerName} tried to join {team} but shoudl be in {configTeam}!", playerController.PlayerName, team, configTeam); - var player = new Player(playerController); + await Task.Delay(_SwitchPlayerDelay, _CancellationTokenSource.Token).ConfigureAwait(false); - _CsServer.NextFrame(() => - { - _Logger.LogInformation("Switch {playerName} to team {team}!", player.PlayerName, configTeam); - player.SwitchTeam(configTeam); - }); + _Logger.LogInformation("Player {playerName} tried to join {team} but should be in {configTeam}!", localPlayer.PlayerName, team, configTeam); + var player = new Player(localPlayer); + + _Logger.LogInformation("Switch {playerName} to team {team}!", player.PlayerName, configTeam); + player.SwitchTeam(configTeam); } } } @@ -282,34 +285,6 @@ private HookResult OnPlayerSpawn(EventPlayerSpawn eventPlayerSpawn, GameEventInf _CsServer.NextFrame(() => { CheckMatchPlayerTeam(userId, userId.TeamNum); - - try - { - _Logger.LogInformation("Update Money on PlayerSpawn"); - var player = new Player(userId); - - int maxMoneyValue = 16000; - try - { - // Use value from server if possible - var maxMoneyCvar = ConVar.Find("mp_maxmoney"); - if (maxMoneyCvar != null) - { - - maxMoneyValue = maxMoneyCvar.GetPrimitiveValue(); - } - } - catch (Exception e) - { - _Logger.LogError(e, "Error loading mp_maxmoney!"); - } - - player.Money = maxMoneyValue; - } - catch (Exception ex) - { - _Logger.LogError(ex, "Error updating money!"); - } }); } @@ -559,6 +534,30 @@ private HookResult OnPlayerDeath(EventPlayerDeath eventPlayerDeath, GameEventInf return HookResult.Continue; } + if (_Match.CurrentState <= MatchState.WaitingForPlayersReady) + { + if (eventPlayerDeath.Userid.InGameMoneyServices != null) + { + int maxMoneyValue = 16000; + //try + //{ + // // Use value from server if possible + // var maxMoneyCvar = ConVar.Find("mp_maxmoney"); + // if (maxMoneyCvar != null) + // { + + // maxMoneyValue = maxMoneyCvar.GetPrimitiveValue(); + // } + //} + //catch (Exception e) + //{ + // _Logger.LogError(e, "Error loading mp_maxmoney!"); + //} + + eventPlayerDeath.Userid.InGameMoneyServices.Account = maxMoneyValue; + } + } + if (_Match.CurrentState == MatchState.MatchRunning) { var victim = eventPlayerDeath.Userid; @@ -801,6 +800,11 @@ private HookResult OnBombDefused(EventBombDefused eventBombDefused, GameEventInf private HookResult OnClientCommandJoinTeam(CCSPlayerController? player, CommandInfo commandInfo) { + if (_Match == null) + { + return HookResult.Continue; + } + _Logger.LogInformation("OnClientCommandJoinTeam was called!"); if (player != null && player.IsValid) { @@ -968,7 +972,6 @@ public async void OnCommandRestoreMatch(CCSPlayerController? player, CommandInfo await HandleCommandAsync(async () => { - if (_Match != null) { command.ReplyToCommand("Currently Match {match} is running. To stop it call ps_stopmatch"); @@ -1050,51 +1053,334 @@ await HandleCommandAsync(async () => player).ConfigureAwait(false); } - [ConsoleCommand("css_dumpmatch", "Serialize match to JSON on console")] - [ConsoleCommand("ps_dumpmatch", "Load a match config")] + [ConsoleCommand("css_creatematch", "Create a match without predefined config")] + [ConsoleCommand("ps_creatematch", "Create a match without predefined config")] [RequiresPermissions("@pugsharp/matchadmin")] - public void OnCommandDumpMatch(CCSPlayerController? player, CommandInfo command) + public void OnCommandCreateMatch(CCSPlayerController? player, CommandInfo command) { HandleCommand(() => { - _Logger.LogInformation("################ dump match ################"); - _Logger.LogInformation("{matchJson}", JsonSerializer.Serialize(_Match)); - _Logger.LogInformation("################ dump match ################"); + if (_Match != null) + { + command.ReplyToCommand("Currently Match {match} is running. To stop it call ps_stopmatch"); + return; + } + + _ConfigCreator = new ConfigCreator(); + _ConfigCreator.Config.Maplist.Add(_CsServer.CurrentMap); + + command.ReplyToCommand("Creating a match started!"); + command.ReplyToCommand("!addmap to add a map!"); + command.ReplyToCommand("!removemap to remove a map!"); + command.ReplyToCommand("!startmatch to start the match!"); + command.ReplyToCommand("!maxrounds to set max match rounds!"); + command.ReplyToCommand("!maxovertimerounds to set max overtime rounds!"); + command.ReplyToCommand("!playersperteam to set players per team!"); + command.ReplyToCommand("!matchinfo to show current match configuration!"); }, command, player); } - [ConsoleCommand("css_ready", "Mark player as ready")] - public void OnCommandReady(CCSPlayerController? player, CommandInfo command) + [ConsoleCommand("css_startmatch", "Create a match without predefined config")] + [ConsoleCommand("ps_startmatch", "Create a match without predefined config")] + [RequiresPermissions("@pugsharp/matchadmin")] + public void OnCommandStartMatch(CCSPlayerController? player, CommandInfo command) { - _ = HandleCommandAsync(async () => + HandleCommand(() => { - if (player == null) + if (_Match != null) { - _Logger.LogInformation("Command Start has been called by the server. Player is required to be marked as ready"); + command.ReplyToCommand("Currently Match {match} is running. To stop it call ps_stopmatch"); return; } - if (_Match == null) + var matchConfig = _ConfigCreator.Config; + if (matchConfig.Maplist.Count == 0) { + command.ReplyToCommand("Can not start match. At least one map is required!"); return; } - var matchPlayer = new Player(player); - if (!_Match.TryAddPlayer(matchPlayer)) + InitializeMatch(matchConfig); + }, + command, + player); + } + + [ConsoleCommand("css_addmap", "Adds a map to the map pool")] + [ConsoleCommand("ps_addmap", "Adds a map to the map pool")] + [RequiresPermissions("@pugsharp/matchadmin")] + public void OnCommandAddMap(CCSPlayerController? player, CommandInfo command) + { + const int requiredArgCount = 2; + HandleCommand(() => + { + if (!IsConfigCreatorAvailable(command)) { - _Logger.LogError("Can not toggle ready state. Player is not part of this match!"); - player.Kick(); return; } - await _Match.TogglePlayerIsReadyAsync(matchPlayer).ConfigureAwait(false); + if (command.ArgCount != requiredArgCount) + { + command.ReplyToCommand("A map name is required to be added!"); + return; + } + + var mapName = command.ArgByIndex(1); + if (!_ConfigCreator.Config.Maplist.Contains(mapName, StringComparer.OrdinalIgnoreCase)) + { + _ConfigCreator.Config.Maplist.Add(mapName); + } + + command.ReplyToCommand($"Added {mapName}. Current maps are {string.Join(", ", _ConfigCreator.Config.Maplist)}"); + }, + command, + player); + } + + [ConsoleCommand("css_removemap", "Removes a map to the map pool")] + [ConsoleCommand("ps_removemap", "Removes a map to the map pool")] + [RequiresPermissions("@pugsharp/matchadmin")] + public void OnCommandRemoveMap(CCSPlayerController? player, CommandInfo command) + { + const int requiredArgCount = 2; + HandleCommand(() => + { + if (!IsConfigCreatorAvailable(command)) + { + return; + } + + if (command.ArgCount != requiredArgCount) + { + command.ReplyToCommand("A map name is required to be removed!"); + return; + } + + var mapName = command.ArgByIndex(1); + if (_ConfigCreator.Config.Maplist.Remove(mapName)) + { + command.ReplyToCommand($"Removed {mapName}. Current maps are {string.Join(", ", _ConfigCreator.Config.Maplist)}"); + } + else + { + command.ReplyToCommand($"Could not remove {mapName}. Current maps are {string.Join(", ", _ConfigCreator.Config.Maplist)}"); + } }, command, player); } + [ConsoleCommand("css_maxrounds", "Sets max rounds for the match")] + [ConsoleCommand("ps_maxrounds", "Sets max rounds for the match")] + [RequiresPermissions("@pugsharp/matchadmin")] + public void OnCommandMaxRounds(CCSPlayerController? player, CommandInfo command) + { + const int requiredArgCount = 2; + HandleCommand(() => + { + if (!IsConfigCreatorAvailable(command)) + { + return; + } + + if (command.ArgCount != requiredArgCount) + { + command.ReplyToCommand("A number of rounds is required!"); + return; + } + + if (!int.TryParse(command.ArgByIndex(1), CultureInfo.InvariantCulture, out var maxRounds)) + { + command.ReplyToCommand("Max rounds have to be an number!"); + return; + } + + if (maxRounds <= 0) + { + command.ReplyToCommand("Max rounds have to be greater than 0!"); + return; + } + + var oldMaxRounds = _ConfigCreator.Config.MaxRounds; + _ConfigCreator.Config.MaxRounds = maxRounds; + command.ReplyToCommand($"Changed max rounds from {oldMaxRounds} to {maxRounds}"); + }, + command, + player); + } + + [ConsoleCommand("css_maxovertimerounds", "Sets max overtime rounds for the match")] + [ConsoleCommand("ps_maxovertimerounds", "Sets max overtime rounds for the match")] + [RequiresPermissions("@pugsharp/matchadmin")] + public void OnCommandMaxOvertimeRounds(CCSPlayerController? player, CommandInfo command) + { + const int requiredArgCount = 2; + HandleCommand(() => + { + if (!IsConfigCreatorAvailable(command)) + { + return; + } + + if (command.ArgCount != requiredArgCount) + { + command.ReplyToCommand("A number of rounds is required!"); + return; + } + + if (!int.TryParse(command.ArgByIndex(1), CultureInfo.InvariantCulture, out var maxOvertimeRounds)) + { + command.ReplyToCommand("Max overtime rounds have to be an number!"); + return; + } + + if (maxOvertimeRounds <= 0) + { + command.ReplyToCommand("Max overtime rounds have to be greater than 0!"); + return; + } + + var oldMaxRounds = _ConfigCreator.Config.MaxOvertimeRounds; + _ConfigCreator.Config.MaxOvertimeRounds = maxOvertimeRounds; + command.ReplyToCommand($"Changed max overtime rounds from {oldMaxRounds} to {maxOvertimeRounds}"); + }, + command, + player); + } + + [ConsoleCommand("css_playersperteam", "Sets number of players per team for the match")] + [ConsoleCommand("ps_playersperteam", "Sets number of players per team for the match")] + [RequiresPermissions("@pugsharp/matchadmin")] + public void OnCommandPlayersPerTeam(CCSPlayerController? player, CommandInfo command) + { + const int requiredArgCount = 2; + HandleCommand(() => + { + if (!IsConfigCreatorAvailable(command)) + { + return; + } + + if (command.ArgCount != requiredArgCount) + { + command.ReplyToCommand("Players per team is required!"); + return; + } + + if (!int.TryParse(command.ArgByIndex(1), CultureInfo.InvariantCulture, out var playersPerTeam)) + { + command.ReplyToCommand("Players per team have to be an number!"); + return; + } + + if (playersPerTeam <= 0) + { + command.ReplyToCommand("Players per team have to be greater than 0!"); + return; + } + + var oldPlayersPerTeam = _ConfigCreator.Config.PlayersPerTeam; + _ConfigCreator.Config.PlayersPerTeam = playersPerTeam; + _ConfigCreator.Config.MinPlayersToReady = playersPerTeam; + command.ReplyToCommand($"Changed players per team from {oldPlayersPerTeam} to {playersPerTeam}"); + }, + command, + player); + } + + private bool IsConfigCreatorAvailable(CommandInfo command) + { + if (_Match != null) + { + command.ReplyToCommand("Currently Match {match} is running. To stop it call ps_stopmatch"); + return false; + } + + if (_ConfigCreator == null) + { + command.ReplyToCommand("To Configure a new match you have to call ps_creatematch first"); + return false; + } + + return true; + } + + [ConsoleCommand("css_matchinfo", "Serialize match to JSON on console")] + [ConsoleCommand("ps_matchinfo", "Serialize match to JSON on console")] + [RequiresPermissions("@pugsharp/matchadmin")] + public void OnCommandMatchInfo(CCSPlayerController? player, CommandInfo command) + { + HandleCommand(() => + { + if (_Match == null && _ConfigCreator == null) + { + command.ReplyToCommand("Currently no match is running. Matchinfo is unavailable!"); + return; + } + + var config = _Match?.MatchInfo?.Config ?? _ConfigCreator.Config; + + command.ReplyToCommand($"Info Match {config.MatchId}"); + command.ReplyToCommand($"Maplist: {string.Join(", ", config.Maplist)}"); + command.ReplyToCommand($"Number of Maps: {config.NumMaps}"); + command.ReplyToCommand($"Players per Team: {config.NumMaps}"); + command.ReplyToCommand($"Max rounds: {config.MaxRounds}"); + command.ReplyToCommand($"Max overtime rounds: {config.MaxOvertimeRounds}"); + command.ReplyToCommand($"Vote timeout: {config.MaxOvertimeRounds}"); + command.ReplyToCommand($"Allow suicide: {config.AllowSuicide}"); + command.ReplyToCommand($"Team mode: {config.TeamMode}"); + }, + command, + player); + } + + [ConsoleCommand("css_dumpmatch", "Serialize match to JSON on console")] + [ConsoleCommand("ps_dumpmatch", "Serialize match to JSON on console")] + [RequiresPermissions("@pugsharp/matchadmin")] + public void OnCommandDumpMatch(CCSPlayerController? player, CommandInfo command) + { + HandleCommand(() => + { + _Logger.LogInformation("################ dump match ################"); + _Logger.LogInformation("{matchJson}", JsonSerializer.Serialize(_Match)); + _Logger.LogInformation("################ dump match ################"); + }, + command, + player); + } + + [ConsoleCommand("css_ready", "Mark player as ready")] + public void OnCommandReady(CCSPlayerController? player, CommandInfo command) + { + HandleCommand(() => + { + if (player == null) + { + _Logger.LogInformation("Command Start has been called by the server. Player is required to be marked as ready"); + return; + } + + if (_Match == null) + { + return; + } + + var matchPlayer = new Player(player); + if (!_Match.TryAddPlayer(matchPlayer)) + { + _Logger.LogError("Can not toggle ready state. Player is not part of this match!"); + player.Kick(); + return; + } + + _Match.TogglePlayerIsReady(matchPlayer); + }, + command, + player); + } + [ConsoleCommand("css_unpause", "Starts a match")] public void OnCommandUnpause(CCSPlayerController? player, CommandInfo command) { @@ -1199,7 +1485,6 @@ private async Task HandleCommandAsync(Func commandAction, CommandInfo comm catch (Exception e) { _Logger.LogError(e, "Error executing command {command}", commandName); - command.ReplyToCommand($"Error executing command \"{commandName}\"!"); } } @@ -1210,7 +1495,7 @@ private async Task ConfigLoaderTask() { while (await _ConfigTimer.WaitForNextTickAsync(_CancellationTokenSource.Token).ConfigureAwait(false)) { - if (_Match != null && (_Match.CurrentState == MatchState.WaitingForPlayersConnectedReady || _Match.CurrentState == MatchState.WaitingForPlayersReady) && Utilities.GetPlayers().Count(x => !x.IsBot) == 0) + if ((_Match == null || _Match.CurrentState == MatchState.WaitingForPlayersConnectedReady || _Match.CurrentState == MatchState.WaitingForPlayersReady) && Utilities.GetPlayers().Count(x => !x.IsBot) == 0) { // TODO Besseren Platz suchen! _CsServer.LoadAndExecuteConfig("warmup.cfg"); @@ -1279,6 +1564,7 @@ private void InitializeMatch(MatchConfig matchConfig) var matchFactory = _ServiceProvider.GetRequiredService(); _Match = matchFactory.CreateMatch(matchConfig); _Match.MatchFinalized += OnMatchFinalized; + KickNonMatchPlayers(); } private void InitializeMatch(MatchInfo matchInfo, string roundBackupFile) @@ -1287,6 +1573,7 @@ private void InitializeMatch(MatchInfo matchInfo, string roundBackupFile) var matchFactory = _ServiceProvider.GetRequiredService(); _Match = matchFactory.CreateMatch(matchInfo, roundBackupFile); _Match.MatchFinalized += OnMatchFinalized; + KickNonMatchPlayers(); } private void OnMatchFinalized(object? sender, MatchFinalizedEventArgs e) @@ -1322,16 +1609,24 @@ private void ResetForMatch(MatchConfig matchConfig) SetMatchVariable(matchConfig); - var players = _CsServer.LoadAllPlayers(); - foreach (var player in players.Where(x => x.UserId.HasValue && x.UserId >= 0)) + _Match?.Dispose(); + } + + private void KickNonMatchPlayers() + { + if (_Match == null) + { + return; + } + + var players = Utilities.GetPlayers().Where(x => x.IsValid && !x.IsHLTV); + foreach (var player in players) { - if (player.UserId != null) + if (!_Match.TryAddPlayer(new Player(player))) { player.Kick(); } } - - _Match?.Dispose(); } private void ResetServer(string map) diff --git a/PugSharp/CsServer.cs b/PugSharp/CsServer.cs index 30ff3f22..22d636b2 100644 --- a/PugSharp/CsServer.cs +++ b/PugSharp/CsServer.cs @@ -218,6 +218,6 @@ public IReadOnlyList LoadAllPlayers() { return Utilities.GetPlayers().Where(x => x.PlayerState() == PlayerConnectedState.PlayerConnected).Select(p => new Player(p)).ToList(); } -} - + public string CurrentMap => CounterStrikeSharp.API.Server.MapName; +} diff --git a/PugSharp/Models/Player.cs b/PugSharp/Models/Player.cs index 47a8676f..8e67c44d 100644 --- a/PugSharp/Models/Player.cs +++ b/PugSharp/Models/Player.cs @@ -35,6 +35,7 @@ private T DefaultIfInvalid(Func loadValue, T defaultValue) private void ReloadPlayerController() { + _PlayerController = Utilities.GetPlayers().Find(x => x.SteamID == SteamID) ?? _PlayerController; if (_PlayerController == null || !_PlayerController.IsValid) { _PlayerController = Utilities.GetPlayerFromUserid(_UserId); @@ -53,6 +54,7 @@ public int? Money { get { + ReloadPlayerController(); if (!_PlayerController.IsValid) { return null; @@ -63,25 +65,26 @@ public int? Money set { - if (_PlayerController.IsValid) + ReloadPlayerController(); + if (_PlayerController.IsValid && _PlayerController.InGameMoneyServices != null && value != null) { -#pragma warning disable S1854 // Unused assignments should be removed -#pragma warning disable IDE0059 // Unnecessary assignment of a value - var money = _PlayerController.InGameMoneyServices?.Account; - money = value; -#pragma warning restore IDE0059 // Unnecessary assignment of a value -#pragma warning restore S1854 // Unused assignments should be removed + _PlayerController.InGameMoneyServices.Account = value.Value; } } } public void PrintToChat(string message) { - _PlayerController.PrintToChat(message); + ReloadPlayerController(); + if (_PlayerController.IsValid) + { + _PlayerController.PrintToChat(message); + } } public void ShowMenu(string title, IEnumerable menuOptions) { + ReloadPlayerController(); var menu = new ChatMenu(title); foreach (var menuOption in menuOptions) @@ -89,19 +92,18 @@ public void ShowMenu(string title, IEnumerable menuOptions) menu.AddMenuOption(menuOption.DisplayName, (player, opt) => menuOption.Action.Invoke(menuOption, this)); } - ChatMenus.OpenMenu(_PlayerController, menu); + if (_PlayerController.IsValid) + { + ChatMenus.OpenMenu(_PlayerController, menu); + } } public void SwitchTeam(Team team) { + ReloadPlayerController(); if (_PlayerController.IsValid) { - _PlayerController.SwitchTeam((CounterStrikeSharp.API.Modules.Utils.CsTeam)(int)team); - CounterStrikeSharp.API.Server.NextFrame(() => - { - _PlayerController.PlayerPawn.Value.CommitSuicide(explode: true, force: true); - ResetScoreboard(); - }); + _PlayerController.ChangeTeam((CounterStrikeSharp.API.Modules.Utils.CsTeam)(int)team); } } @@ -109,15 +111,4 @@ public void Kick() { CounterStrikeSharp.API.Server.ExecuteCommand(string.Create(CultureInfo.InvariantCulture, $"kickid {UserId!.Value} \"You are not part of the current match!\"")); } - - private void ResetScoreboard() - { - var matchStats = _PlayerController.ActionTrackingServices?.MatchStats; - - if (matchStats != null) - { - matchStats.Kills = 0; - matchStats.Deaths = 0; - } - } } diff --git a/PugSharp/PugSharp.cs b/PugSharp/PugSharp.cs index 8f74594e..f3920df4 100644 --- a/PugSharp/PugSharp.cs +++ b/PugSharp/PugSharp.cs @@ -39,11 +39,21 @@ public override void Load(bool hotReload) // Create DI container var services = new ServiceCollection(); + services.AddLogging(options => { - options.AddConsole(); + //options.AddConsole(); }); + var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(ILoggerFactory)); + if (serviceDescriptor != null) + { + services.Remove(serviceDescriptor); + } + + services.AddSingleton(CounterStrikeSharp.API.Core.Logging.CoreLogging.Factory); + + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(s => s.GetRequiredService()); diff --git a/PugSharp/PugSharp.csproj b/PugSharp/PugSharp.csproj index 728d02bf..18b68823 100644 --- a/PugSharp/PugSharp.csproj +++ b/PugSharp/PugSharp.csproj @@ -8,15 +8,19 @@ - + none runtime compile; build; native; contentfiles; analyzers; buildtransitive + + - - + + + diff --git a/resources/cfg/PugSharp/warmup.cfg b/resources/cfg/PugSharp/warmup.cfg index d5f225cf..633791e5 100644 --- a/resources/cfg/PugSharp/warmup.cfg +++ b/resources/cfg/PugSharp/warmup.cfg @@ -3,14 +3,13 @@ bot_quota 0 mp_autokick 0 mp_autoteambalance 0 mp_buy_anywhere 0 -mp_buytime 15 +mp_buytime 1500 mp_death_drop_gun 0 mp_free_armor 0 mp_ignore_round_win_conditions 0 mp_limitteams 0 mp_respawn_on_death_ct 0 mp_respawn_on_death_t 0 -mp_solid_teammates 0 mp_spectators_max 20 mp_maxmoney 16000 mp_startmoney 16000 @@ -26,16 +25,10 @@ sv_human_autojoin_team 2 sv_showimpacts 0 sv_voiceenable 1 sv_mute_players_with_social_penalties 0 -tv_relayvoice 1 -mp_ct_default_melee weapon_knife -mp_ct_default_secondary weapon_hkp2000 -mp_ct_default_primary "" -mp_t_default_melee weapon_knife -mp_t_default_secondary weapon_glock -mp_t_default_primary +cash_team_bonus_shorthanded 0 +tv_relayvoice 0 +tv_autorecord 0 +mp_weapons_allow_typecount -1 mp_warmup_start mp_warmup_pausetimer 1 mp_warmuptime 9999 -cash_team_bonus_shorthanded 0 -tv_relayvoice 0 -tv_autorecord 0 \ No newline at end of file