Additional CSGO compatibility improvements

This commit is contained in:
RaidMax 2021-06-16 08:53:50 -05:00
parent dbceb23823
commit af4630ecb9
8 changed files with 129 additions and 117 deletions

View File

@ -217,10 +217,15 @@ namespace IW4MAdmin.Application.RConParsers
continue; continue;
} }
int clientNumber = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]]); var clientNumber = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]]);
int score = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]]); var score = 0;
if (Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore] > 0)
{
score = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]]);
}
int ping = 999; var ping = 999;
// their state can be CNCT, ZMBI etc // their state can be CNCT, ZMBI etc
if (match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Length <= 3) if (match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Length <= 3)
@ -229,7 +234,7 @@ namespace IW4MAdmin.Application.RConParsers
} }
long networkId; long networkId;
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine(); var name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
string networkIdString; string networkIdString;
var ip = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Split(':')[0].ConvertToIP(); var ip = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Split(':')[0].ConvertToIP();

View File

@ -26,9 +26,12 @@ namespace Integrations.Source
private readonly SemaphoreSlim _activeQuery; private readonly SemaphoreSlim _activeQuery;
private static readonly TimeSpan FloodDelay = TimeSpan.FromMilliseconds(250); private static readonly TimeSpan FloodDelay = TimeSpan.FromMilliseconds(250);
private static readonly TimeSpan ConnectionTimeout = TimeSpan.FromSeconds(30);
private DateTime _lastQuery = DateTime.Now; private DateTime _lastQuery = DateTime.Now;
private RconClient _rconClient; private RconClient _rconClient;
private bool _authenticated;
private bool _needNewSocket = true;
public SourceRConConnection(ILogger<SourceRConConnection> logger, IRConClientFactory rconClientFactory, public SourceRConConnection(ILogger<SourceRConConnection> logger, IRConClientFactory rconClientFactory,
string hostname, int port, string password) string hostname, int port, string password)
@ -38,7 +41,6 @@ namespace Integrations.Source
_hostname = hostname; _hostname = hostname;
_port = port; _port = port;
_logger = logger; _logger = logger;
_rconClient = _rconClientFactory.CreateClient(_hostname, _port);
_activeQuery = new SemaphoreSlim(1, 1); _activeQuery = new SemaphoreSlim(1, 1);
} }
@ -52,10 +54,22 @@ namespace Integrations.Source
try try
{ {
await _activeQuery.WaitAsync(); await _activeQuery.WaitAsync();
var diff = DateTime.Now - _lastQuery; await WaitForAvailable();
if (diff < FloodDelay)
if (_needNewSocket)
{ {
await Task.Delay(FloodDelay - diff); try
{
_rconClient?.Disconnect();
}
catch
{
// ignored
}
_rconClient = _rconClientFactory.CreateClient(_hostname, _port);
_authenticated = false;
_needNewSocket = false;
} }
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}")) using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
@ -63,64 +77,14 @@ namespace Integrations.Source
_logger.LogDebug("Connecting to RCon socket"); _logger.LogDebug("Connecting to RCon socket");
} }
await _rconClient.ConnectAsync(); await TryConnectAndAuthenticate().WithTimeout(ConnectionTimeout);
bool authenticated; var multiPacket = false;
try
{
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
{
_logger.LogDebug("Authenticating to RCon socket");
}
authenticated = await _rconClient.AuthenticateAsync(_password);
}
catch (SocketException ex)
{
// occurs when the server comes back from hibernation
// this is probably a bug in the library
if (ex.ErrorCode == 10053 || ex.ErrorCode == 10054)
{
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
{
_logger.LogWarning(ex,
"Server appears to resumed from hibernation, so we are using a new socket");
}
try
{
_rconClient.Disconnect();
}
catch
{
// ignored
}
_rconClient = _rconClientFactory.CreateClient(_hostname, _port);
}
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
{
_logger.LogError(ex, "Error occurred authenticating with server");
}
throw new NetworkException("Error occurred authenticating with server");
}
if (!authenticated)
{
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
{
_logger.LogError("Could not login to server");
}
throw new ServerException("Could not authenticate to server with provided password");
}
if (type == StaticHelpers.QueryType.COMMAND_STATUS) if (type == StaticHelpers.QueryType.COMMAND_STATUS)
{ {
parameters = "status"; parameters = "status";
multiPacket = true;
} }
parameters = parameters.ReplaceUnfriendlyCharacters(); parameters = parameters.ReplaceUnfriendlyCharacters();
@ -131,9 +95,10 @@ namespace Integrations.Source
_logger.LogDebug("Sending query {Type} with parameters \"{Parameters}\"", type, parameters); _logger.LogDebug("Sending query {Type} with parameters \"{Parameters}\"", type, parameters);
} }
var response = await _rconClient.ExecuteCommandAsync(parameters, true); var response = await _rconClient.ExecuteCommandAsync(parameters, multiPacket)
.WithTimeout(ConnectionTimeout);
using (LogContext.PushProperty("Server", $"{_rconClient.Host}:{_rconClient.Port}")) using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
{ {
_logger.LogDebug("Received RCon response {Response}", response); _logger.LogDebug("Received RCon response {Response}", response);
} }
@ -142,6 +107,24 @@ namespace Integrations.Source
return split.Take(split.Length - 1).ToArray(); return split.Take(split.Length - 1).ToArray();
} }
catch (TaskCanceledException)
{
_needNewSocket = true;
throw new NetworkException("Timeout while attempting to communicate with server");
}
catch (SocketException ex)
{
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
{
_logger.LogError(ex, "Socket exception encountered while attempting to communicate with server");
}
_needNewSocket = true;
throw new NetworkException("Socket exception encountered while attempting to communicate with server");
}
catch (Exception ex) when (ex.GetType() != typeof(NetworkException) && catch (Exception ex) when (ex.GetType() != typeof(NetworkException) &&
ex.GetType() != typeof(ServerException)) ex.GetType() != typeof(ServerException))
{ {
@ -164,6 +147,39 @@ namespace Integrations.Source
} }
} }
private async Task WaitForAvailable()
{
var diff = DateTime.Now - _lastQuery;
if (diff < FloodDelay)
{
await Task.Delay(FloodDelay - diff);
}
}
private async Task TryConnectAndAuthenticate()
{
if (!_authenticated)
{
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
{
_logger.LogDebug("Authenticating to RCon socket");
}
await _rconClient.ConnectAsync().WithTimeout(ConnectionTimeout);
_authenticated = await _rconClient.AuthenticateAsync(_password).WithTimeout(ConnectionTimeout);
if (!_authenticated)
{
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
{
_logger.LogError("Could not login to server");
}
throw new ServerException("Could not authenticate to server with provided password");
}
}
}
public void SetConfiguration(IRConParser config) public void SetConfiguration(IRConParser config)
{ {
} }

View File

@ -35,7 +35,7 @@ const plugin = {
rconParser.Configuration.Status.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) +(\\d+:\\d+(?::\\d+)?) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$'; rconParser.Configuration.Status.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) +(\\d+:\\d+(?::\\d+)?) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
rconParser.Configuration.Status.AddMapping(100, 2); rconParser.Configuration.Status.AddMapping(100, 2);
rconParser.Configuration.Status.AddMapping(101, 7); rconParser.Configuration.Status.AddMapping(101, -1);
rconParser.Configuration.Status.AddMapping(102, 6); rconParser.Configuration.Status.AddMapping(102, 6);
rconParser.Configuration.Status.AddMapping(103, 4) rconParser.Configuration.Status.AddMapping(103, 4)
rconParser.Configuration.Status.AddMapping(104, 3); rconParser.Configuration.Status.AddMapping(104, 3);
@ -90,6 +90,7 @@ const plugin = {
rconParser.GameName = 10; // CSGO rconParser.GameName = 10; // CSGO
eventParser.Version = 'CSGO'; eventParser.Version = 'CSGO';
eventParser.GameName = 10; // CSGO eventParser.GameName = 10; // CSGO
eventParser.URLProtocolFormat = 'steam://connect/{{ip}}:{{port}}';
}, },
onUnloadAsync: function () { onUnloadAsync: function () {

View File

@ -35,7 +35,7 @@ const plugin = {
rconParser.Configuration.Status.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) +(\\d+:\\d+(?::\\d+)?) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$'; rconParser.Configuration.Status.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) +(\\d+:\\d+(?::\\d+)?) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
rconParser.Configuration.Status.AddMapping(100, 2); rconParser.Configuration.Status.AddMapping(100, 2);
rconParser.Configuration.Status.AddMapping(101, 7); rconParser.Configuration.Status.AddMapping(101, -1);
rconParser.Configuration.Status.AddMapping(102, 6); rconParser.Configuration.Status.AddMapping(102, 6);
rconParser.Configuration.Status.AddMapping(103, 4) rconParser.Configuration.Status.AddMapping(103, 4)
rconParser.Configuration.Status.AddMapping(104, 3); rconParser.Configuration.Status.AddMapping(104, 3);
@ -90,6 +90,7 @@ const plugin = {
rconParser.GameName = 10; // CSGO rconParser.GameName = 10; // CSGO
eventParser.Version = 'CSGOSM'; eventParser.Version = 'CSGOSM';
eventParser.GameName = 10; // CSGO eventParser.GameName = 10; // CSGO
eventParser.URLProtocolFormat = 'steam://connect/{{ip}}:{{port}}';
}, },
onUnloadAsync: function () { onUnloadAsync: function () {

View File

@ -11,6 +11,7 @@ using Data.Models.Client.Stats.Reference;
using Data.Models.Server; using Data.Models.Server;
using IW4MAdmin.Plugins.Stats.Client.Abstractions; using IW4MAdmin.Plugins.Stats.Client.Abstractions;
using IW4MAdmin.Plugins.Stats.Client.Game; using IW4MAdmin.Plugins.Stats.Client.Game;
using IW4MAdmin.Plugins.Stats.Helpers;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SharedLibraryCore; using SharedLibraryCore;
@ -147,7 +148,7 @@ namespace IW4MAdmin.Plugins.Stats.Client
foreach (var client in gameEvent.Owner.GetClientsAsList()) foreach (var client in gameEvent.Owner.GetClientsAsList())
{ {
var scores = client.GetAdditionalProperty<List<(int, DateTime)>>(SessionScores); var scores = client.GetAdditionalProperty<List<(int, DateTime)>>(SessionScores);
scores?.Add((client.Score, DateTime.Now)); scores?.Add((client.GetAdditionalProperty<int?>(StatManager.ESTIMATED_SCORE) ?? client.Score, DateTime.Now));
} }
} }
@ -590,7 +591,7 @@ namespace IW4MAdmin.Plugins.Stats.Client
if (sessionScores == null) if (sessionScores == null)
{ {
_logger.LogWarning($"No session scores available for {client}"); _logger.LogWarning("No session scores available for {Client}", client.ToString());
return; return;
} }
@ -600,7 +601,7 @@ namespace IW4MAdmin.Plugins.Stats.Client
if (sessionScores.Count == 0) if (sessionScores.Count == 0)
{ {
stat.Score += client.Score; stat.Score += client.Score > 0 ? client.Score : client.GetAdditionalProperty<int?>(Helpers.StatManager.ESTIMATED_SCORE) ?? 0 * 50;
} }
else else

View File

@ -38,6 +38,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
private static List<EFServer> serverModels; private static List<EFServer> serverModels;
public static string CLIENT_STATS_KEY = "ClientStats"; public static string CLIENT_STATS_KEY = "ClientStats";
public static string CLIENT_DETECTIONS_KEY = "ClientDetections"; public static string CLIENT_DETECTIONS_KEY = "ClientDetections";
public static string ESTIMATED_SCORE = "EstimatedScore";
private readonly SemaphoreSlim _addPlayerWaiter = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _addPlayerWaiter = new SemaphoreSlim(1, 1);
private readonly IServerDistributionCalculator _serverDistributionCalculator; private readonly IServerDistributionCalculator _serverDistributionCalculator;
@ -859,7 +860,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// update the total stats // update the total stats
_servers[serverId].ServerStatistics.TotalKills += 1; _servers[serverId].ServerStatistics.TotalKills += 1;
// this happens when the round has changed // this happens when the round has changed
if (attackerStats.SessionScore == 0) if (attackerStats.SessionScore == 0)
{ {
@ -871,18 +872,24 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
victimStats.LastScore = 0; victimStats.LastScore = 0;
} }
attackerStats.SessionScore = attacker.Score; var estimatedAttackerScore = attacker.Score > 0 ? attacker.Score : attackerStats.SessionKills * 50;
victimStats.SessionScore = victim.Score; var estimatedVictimScore = victim.Score > 0 ? victim.Score : victimStats.SessionKills * 50;
attackerStats.SessionScore = estimatedAttackerScore;
victimStats.SessionScore = estimatedVictimScore;
attacker.SetAdditionalProperty(ESTIMATED_SCORE, estimatedAttackerScore);
victim.SetAdditionalProperty(ESTIMATED_SCORE, estimatedVictimScore);
// calculate for the clients // calculate for the clients
CalculateKill(attackerStats, victimStats); CalculateKill(attackerStats, victimStats);
// this should fix the negative SPM // this should fix the negative SPM
// updates their last score after being calculated // updates their last score after being calculated
attackerStats.LastScore = attacker.Score; attackerStats.LastScore = estimatedAttackerScore;
victimStats.LastScore = victim.Score; victimStats.LastScore = estimatedVictimScore;
// show encouragement/discouragement // show encouragement/discouragement
string streakMessage = (attackerStats.ClientId != victimStats.ClientId) var streakMessage = (attackerStats.ClientId != victimStats.ClientId)
? StreakMessage.MessageOnStreak(attackerStats.KillStreak, attackerStats.DeathStreak) ? StreakMessage.MessageOnStreak(attackerStats.KillStreak, attackerStats.DeathStreak)
: StreakMessage.MessageOnStreak(-1, -1); : StreakMessage.MessageOnStreak(-1, -1);
@ -1248,41 +1255,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// process the attacker's stats after the kills // process the attacker's stats after the kills
attackerStats = UpdateStats(attackerStats); attackerStats = UpdateStats(attackerStats);
#region DEPRECATED
/* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats
.Where(cs => cs.Value.ClientId != attackerStats.ClientId)
.Where(cs =>
Servers[attackerStats.ServerId].IsTeamBased ?
cs.Value.Team != attackerStats.Team :
cs.Value.Team != IW4Info.Team.Spectator)
.Where(cs => cs.Value.Team != IW4Info.Team.Spectator);
double attackerLobbyRating = validAttackerLobbyRatings.Count() > 0 ?
validAttackerLobbyRatings.Average(cs => cs.Value.EloRating) :
attackerStats.EloRating;
var validVictimLobbyRatings = Servers[victimStats.ServerId].PlayerStats
.Where(cs => cs.Value.ClientId != victimStats.ClientId)
.Where(cs =>
Servers[attackerStats.ServerId].IsTeamBased ?
cs.Value.Team != victimStats.Team :
cs.Value.Team != IW4Info.Team.Spectator)
.Where(cs => cs.Value.Team != IW4Info.Team.Spectator);
double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ?
validVictimLobbyRatings.Average(cs => cs.Value.EloRating) :
victimStats.EloRating;*/
#endregion
// calculate elo // calculate elo
double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) - var attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) -
Math.Log(Math.Max(1, attackerStats.EloRating)); Math.Log(Math.Max(1, attackerStats.EloRating));
double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E)); var winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E));
// double victimEloDifference = Math.Log(Math.Max(1, attackerStats.EloRating)) - Math.Log(Math.Max(1, victimStats.EloRating));
// double lossPercentage = 1.0 / (1 + Math.Pow(10, victimEloDifference/ Math.E));
attackerStats.EloRating += 6.0 * (1 - winPercentage); attackerStats.EloRating += 6.0 * (1 - winPercentage);
victimStats.EloRating -= 6.0 * (1 - winPercentage); victimStats.EloRating -= 6.0 * (1 - winPercentage);
@ -1314,10 +1290,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return clientStats; return clientStats;
} }
double timeSinceLastCalc = (DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0; var timeSinceLastCalc = (DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0;
double timeSinceLastActive = (DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0;
int scoreDifference = 0; var scoreDifference = 0;
// this means they've been tking or suicide and is the only time they can have a negative SPM // this means they've been tking or suicide and is the only time they can have a negative SPM
if (clientStats.RoundScore < 0) if (clientStats.RoundScore < 0)
{ {
@ -1329,17 +1304,17 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
scoreDifference = clientStats.RoundScore - clientStats.LastScore; scoreDifference = clientStats.RoundScore - clientStats.LastScore;
} }
double killSPM = scoreDifference / timeSinceLastCalc; var killSpm = scoreDifference / timeSinceLastCalc;
double spmMultiplier = 2.934 * var spmMultiplier = 2.934 *
Math.Pow( Math.Pow(
_servers[clientStats.ServerId] _servers[clientStats.ServerId]
.TeamCount((IW4Info.Team) clientStats.Team == IW4Info.Team.Allies .TeamCount((IW4Info.Team) clientStats.Team == IW4Info.Team.Allies
? IW4Info.Team.Axis ? IW4Info.Team.Axis
: IW4Info.Team.Allies), -0.454); : IW4Info.Team.Allies), -0.454);
killSPM *= Math.Max(1, spmMultiplier); killSpm *= Math.Max(1, spmMultiplier);
// update this for ac tracking // update this for ac tracking
clientStats.SessionSPM = killSPM; clientStats.SessionSPM = killSpm;
// calculate how much the KDR should weigh // calculate how much the KDR should weigh
// 1.637 is a Eddie-Generated number that weights the KDR nicely // 1.637 is a Eddie-Generated number that weights the KDR nicely
@ -1358,7 +1333,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
double SPMAgainstPlayWeight = timeSinceLastCalc / Math.Min(600, (totalPlayTime / 60.0)); double SPMAgainstPlayWeight = timeSinceLastCalc / Math.Min(600, (totalPlayTime / 60.0));
// calculate the new weight against average times the weight against play time // calculate the new weight against average times the weight against play time
clientStats.SPM = (killSPM * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight)); clientStats.SPM = (killSpm * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight));
if (clientStats.SPM < 0) if (clientStats.SPM < 0)
{ {
@ -1373,7 +1348,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill)) if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill))
{ {
_log.LogWarning("clientStats SPM/Skill NaN {@killInfo}", _log.LogWarning("clientStats SPM/Skill NaN {@killInfo}",
new {killSPM, KDRWeight, totalPlayTime, SPMAgainstPlayWeight, clientStats, scoreDifference}); new {killSPM = killSpm, KDRWeight, totalPlayTime, SPMAgainstPlayWeight, clientStats, scoreDifference});
clientStats.SPM = 0; clientStats.SPM = 0;
clientStats.Skill = 0; clientStats.Skill = 0;
} }

View File

@ -83,6 +83,7 @@ namespace IW4MAdmin.Plugins.Stats
await Manager.Sync(S); await Manager.Sync(S);
break; break;
case GameEvent.EventType.MapEnd: case GameEvent.EventType.MapEnd:
Manager.ResetKillstreaks(S);
await Manager.Sync(S); await Manager.Sync(S);
break; break;
case GameEvent.EventType.Command: case GameEvent.EventType.Command:

View File

@ -917,6 +917,18 @@ namespace SharedLibraryCore
} }
} }
public static async Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeout)
{
await Task.WhenAny(task, Task.Delay(timeout));
return await task;
}
public static async Task WithTimeout(this Task task, TimeSpan timeout)
{
await Task.WhenAny(task, Task.Delay(timeout));
}
public static bool ShouldHideLevel(this Permission perm) => perm == Permission.Flagged; public static bool ShouldHideLevel(this Permission perm) => perm == Permission.Flagged;
/// <summary> /// <summary>