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