add initial CS:GO support
This commit is contained in:
parent
9488f754d4
commit
be08d49f0a
@ -48,6 +48,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Integrations\Cod\Integrations.Cod.csproj" />
|
||||
<ProjectReference Include="..\Integrations\Source\Integrations.Source.csproj" />
|
||||
<ProjectReference Include="..\SharedLibraryCore\SharedLibraryCore.csproj">
|
||||
<Private>true</Private>
|
||||
</ProjectReference>
|
||||
|
@ -1,7 +1,7 @@
|
||||
using IW4MAdmin.Application.EventParsers;
|
||||
using IW4MAdmin.Application.Extensions;
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using IW4MAdmin.Application.RconParsers;
|
||||
using IW4MAdmin.Application.RConParsers;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
@ -220,15 +220,15 @@ namespace IW4MAdmin.Application
|
||||
public async Task UpdateServerStates()
|
||||
{
|
||||
// store the server hash code and task for it
|
||||
var runningUpdateTasks = new Dictionary<long, Task>();
|
||||
var runningUpdateTasks = new Dictionary<long, (Task task, CancellationTokenSource tokenSource, DateTime startTime)>();
|
||||
|
||||
while (!_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
// select the server ids that have completed the update task
|
||||
var serverTasksToRemove = runningUpdateTasks
|
||||
.Where(ut => ut.Value.Status == TaskStatus.RanToCompletion ||
|
||||
ut.Value.Status == TaskStatus.Canceled ||
|
||||
ut.Value.Status == TaskStatus.Faulted)
|
||||
.Where(ut => ut.Value.task.Status == TaskStatus.RanToCompletion ||
|
||||
ut.Value.task.Status == TaskStatus.Canceled || // we want to cancel if a task takes longer than 5 minutes
|
||||
ut.Value.task.Status == TaskStatus.Faulted || DateTime.Now - ut.Value.startTime > TimeSpan.FromMinutes(5))
|
||||
.Select(ut => ut.Key)
|
||||
.ToList();
|
||||
|
||||
@ -239,9 +239,14 @@ namespace IW4MAdmin.Application
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
// remove the update tasks as they have completd
|
||||
foreach (long serverId in serverTasksToRemove)
|
||||
// remove the update tasks as they have completed
|
||||
foreach (var serverId in serverTasksToRemove)
|
||||
{
|
||||
if (!runningUpdateTasks[serverId].tokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
runningUpdateTasks[serverId].tokenSource.Cancel();
|
||||
}
|
||||
|
||||
runningUpdateTasks.Remove(serverId);
|
||||
}
|
||||
|
||||
@ -249,11 +254,12 @@ namespace IW4MAdmin.Application
|
||||
var serverIds = Servers.Select(s => s.EndPoint).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
|
||||
foreach (var server in Servers.Where(s => serverIds.Contains(s.EndPoint)))
|
||||
{
|
||||
runningUpdateTasks.Add(server.EndPoint, Task.Run(async () =>
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
runningUpdateTasks.Add(server.EndPoint, (Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await server.ProcessUpdatesAsync(_tokenSource.Token);
|
||||
await server.ProcessUpdatesAsync(_tokenSource.Token).WithWaitCancellation(runningUpdateTasks[server.EndPoint].tokenSource.Token);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
@ -268,7 +274,7 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
server.IsInitialized = true;
|
||||
}
|
||||
}));
|
||||
}, tokenSource.Token), tokenSource, DateTime.Now));
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -17,6 +17,8 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
private readonly Dictionary<string, (string, Func<string, IEventParserConfiguration, GameEvent, GameEvent>)> _customEventRegistrations;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ApplicationConfiguration _appConfig;
|
||||
private readonly Dictionary<ParserRegex, GameEvent.EventType> _regexMap;
|
||||
private readonly Dictionary<string, GameEvent.EventType> _eventTypeMap;
|
||||
|
||||
public BaseEventParser(IParserRegexFactory parserRegexFactory, ILogger logger, ApplicationConfiguration appConfig)
|
||||
{
|
||||
@ -78,7 +80,28 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
Configuration.Kill.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12);
|
||||
Configuration.Kill.AddMapping(ParserRegex.GroupType.HitLocation, 13);
|
||||
|
||||
Configuration.MapChange.Pattern = @".*InitGame.*";
|
||||
Configuration.MapEnd.Pattern = @".*(?:ExitLevel|ShutdownGame).*";
|
||||
|
||||
Configuration.Time.Pattern = @"^ *(([0-9]+):([0-9]+) |^[0-9]+ )";
|
||||
|
||||
_regexMap = new Dictionary<ParserRegex, GameEvent.EventType>
|
||||
{
|
||||
{Configuration.Say, GameEvent.EventType.Say},
|
||||
{Configuration.Kill, GameEvent.EventType.Kill},
|
||||
{Configuration.MapChange, GameEvent.EventType.MapChange},
|
||||
{Configuration.MapEnd, GameEvent.EventType.MapEnd}
|
||||
};
|
||||
|
||||
_eventTypeMap = new Dictionary<string, GameEvent.EventType>
|
||||
{
|
||||
{"say", GameEvent.EventType.Say},
|
||||
{"sayteam", GameEvent.EventType.Say},
|
||||
{"K", GameEvent.EventType.Kill},
|
||||
{"D", GameEvent.EventType.Damage},
|
||||
{"J", GameEvent.EventType.PreConnect},
|
||||
{"Q", GameEvent.EventType.PreDisconnect},
|
||||
};
|
||||
}
|
||||
|
||||
public IEventParserConfiguration Configuration { get; set; }
|
||||
@ -91,6 +114,28 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
|
||||
public string Name { get; set; } = "Call of Duty";
|
||||
|
||||
private (GameEvent.EventType type, string eventKey) GetEventTypeFromLine(string logLine)
|
||||
{
|
||||
var lineSplit = logLine.Split(';');
|
||||
if (lineSplit.Length > 1)
|
||||
{
|
||||
var type = lineSplit[0];
|
||||
return _eventTypeMap.ContainsKey(type) ? (_eventTypeMap[type], type): (GameEvent.EventType.Unknown, null);
|
||||
}
|
||||
|
||||
foreach (var (key, value) in _regexMap)
|
||||
{
|
||||
var result = key.PatternMatcher.Match(logLine);
|
||||
if (result.Success)
|
||||
{
|
||||
return (value, null);
|
||||
}
|
||||
}
|
||||
|
||||
return (GameEvent.EventType.Unknown, null);
|
||||
}
|
||||
|
||||
|
||||
public virtual GameEvent GenerateGameEvent(string logLine)
|
||||
{
|
||||
var timeMatch = Configuration.Time.PatternMatcher.Match(logLine);
|
||||
@ -104,7 +149,7 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
.Values
|
||||
.Skip(2)
|
||||
// this converts the timestamp into seconds passed
|
||||
.Select((_value, index) => long.Parse(_value.ToString()) * (index == 0 ? 60 : 1))
|
||||
.Select((value, index) => long.Parse(value.ToString()) * (index == 0 ? 60 : 1))
|
||||
.Sum();
|
||||
|
||||
}
|
||||
@ -114,33 +159,34 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
|
||||
// we want to strip the time from the log line
|
||||
logLine = logLine.Substring(timeMatch.Values.First().Length);
|
||||
logLine = logLine.Substring(timeMatch.Values.First().Length).Trim();
|
||||
}
|
||||
|
||||
string[] lineSplit = logLine.Split(';');
|
||||
string eventType = lineSplit[0];
|
||||
var eventParseResult = GetEventTypeFromLine(logLine);
|
||||
var eventType = eventParseResult.type;
|
||||
|
||||
_logger.LogDebug(logLine);
|
||||
|
||||
if (eventType == "say" || eventType == "sayteam")
|
||||
if (eventType == GameEvent.EventType.Say)
|
||||
{
|
||||
var matchResult = Configuration.Say.PatternMatcher.Match(logLine);
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
string message = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.Message]]
|
||||
.ToString()
|
||||
var message = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.Message]]
|
||||
.Replace("\x15", "")
|
||||
.Trim();
|
||||
|
||||
if (message.Length > 0)
|
||||
{
|
||||
string originIdString = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
|
||||
string originName = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
|
||||
var originIdString = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]];
|
||||
var originName = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginName]];
|
||||
|
||||
long originId = originIdString.IsBotGuid() ?
|
||||
var originId = originIdString.IsBotGuid() ?
|
||||
originName.GenerateGuidFromString() :
|
||||
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
||||
|
||||
int clientNumber = int.Parse(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
var clientNumber = int.Parse(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
|
||||
if (message.StartsWith(_appConfig.CommandPrefix) || message.StartsWith(_appConfig.BroadcastCommandPrefix))
|
||||
{
|
||||
@ -172,26 +218,26 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType == "K")
|
||||
if (eventType == GameEvent.EventType.Kill)
|
||||
{
|
||||
var match = Configuration.Kill.PatternMatcher.Match(logLine);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
string originIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
|
||||
string targetIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString();
|
||||
string originName = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
|
||||
string targetName = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetName]].ToString();
|
||||
var originIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]];
|
||||
var targetIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]];
|
||||
var originName = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginName]];
|
||||
var targetName = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetName]];
|
||||
|
||||
long originId = originIdString.IsBotGuid() ?
|
||||
var originId = originIdString.IsBotGuid() ?
|
||||
originName.GenerateGuidFromString() :
|
||||
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
|
||||
long targetId = targetIdString.IsBotGuid() ?
|
||||
var targetId = targetIdString.IsBotGuid() ?
|
||||
targetName.GenerateGuidFromString() :
|
||||
targetIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
|
||||
|
||||
int originClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
int targetClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
|
||||
var originClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
var targetClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -206,26 +252,26 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType == "D")
|
||||
if (eventType == GameEvent.EventType.Damage)
|
||||
{
|
||||
var match = Configuration.Damage.PatternMatcher.Match(logLine);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
string originIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
|
||||
string targetIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString();
|
||||
string originName = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
|
||||
string targetName = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetName]].ToString();
|
||||
var originIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]];
|
||||
var targetIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]];
|
||||
var originName = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginName]];
|
||||
var targetName = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetName]];
|
||||
|
||||
long originId = originIdString.IsBotGuid() ?
|
||||
var originId = originIdString.IsBotGuid() ?
|
||||
originName.GenerateGuidFromString() :
|
||||
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
|
||||
long targetId = targetIdString.IsBotGuid() ?
|
||||
var targetId = targetIdString.IsBotGuid() ?
|
||||
targetName.GenerateGuidFromString() :
|
||||
targetIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
|
||||
|
||||
int originClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
int targetClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
|
||||
var originClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
var targetClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -240,16 +286,16 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType == "J")
|
||||
if (eventType == GameEvent.EventType.PreConnect)
|
||||
{
|
||||
var match = Configuration.Join.PatternMatcher.Match(logLine);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
string originIdString = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
|
||||
string originName = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
|
||||
var originIdString = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]];
|
||||
var originName = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]];
|
||||
|
||||
long networkId = originIdString.IsBotGuid() ?
|
||||
var networkId = originIdString.IsBotGuid() ?
|
||||
originName.GenerateGuidFromString() :
|
||||
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
||||
|
||||
@ -261,10 +307,10 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Name = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine(),
|
||||
Name = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].TrimNewLine(),
|
||||
},
|
||||
NetworkId = networkId,
|
||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]),
|
||||
State = EFClient.ClientState.Connecting,
|
||||
},
|
||||
Extra = originIdString,
|
||||
@ -276,16 +322,16 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType == "Q")
|
||||
if (eventType == GameEvent.EventType.PreDisconnect)
|
||||
{
|
||||
var match = Configuration.Quit.PatternMatcher.Match(logLine);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
string originIdString = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
|
||||
string originName = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
|
||||
var originIdString = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]];
|
||||
var originName = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]];
|
||||
|
||||
long networkId = originIdString.IsBotGuid() ?
|
||||
var networkId = originIdString.IsBotGuid() ?
|
||||
originName.GenerateGuidFromString() :
|
||||
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
||||
|
||||
@ -297,10 +343,10 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Name = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine()
|
||||
Name = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].TrimNewLine()
|
||||
},
|
||||
NetworkId = networkId,
|
||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]),
|
||||
State = EFClient.ClientState.Disconnecting
|
||||
},
|
||||
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
||||
@ -311,7 +357,7 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType.Contains("ExitLevel") || eventType.Contains("ShutdownGame"))
|
||||
if (eventType == GameEvent.EventType.MapEnd)
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -325,9 +371,9 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
};
|
||||
}
|
||||
|
||||
if (eventType.Contains("InitGame"))
|
||||
if (eventType == GameEvent.EventType.MapChange)
|
||||
{
|
||||
string dump = eventType.Replace("InitGame: ", "");
|
||||
var dump = logLine.Replace("InitGame: ", "");
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -342,26 +388,37 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
};
|
||||
}
|
||||
|
||||
if (_customEventRegistrations.ContainsKey(eventType))
|
||||
if (eventParseResult.eventKey == null || !_customEventRegistrations.ContainsKey(eventParseResult.eventKey))
|
||||
{
|
||||
var eventModifier = _customEventRegistrations[eventType];
|
||||
|
||||
try
|
||||
return new GameEvent()
|
||||
{
|
||||
return eventModifier.Item2(logLine, Configuration, new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Other,
|
||||
Data = logLine,
|
||||
Subtype = eventModifier.Item1,
|
||||
GameTime = gameTime,
|
||||
Source = GameEvent.EventSource.Log
|
||||
});
|
||||
}
|
||||
Type = GameEvent.EventType.Unknown,
|
||||
Data = logLine,
|
||||
Origin = Utilities.IW4MAdminClient(),
|
||||
Target = Utilities.IW4MAdminClient(),
|
||||
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
||||
GameTime = gameTime,
|
||||
Source = GameEvent.EventSource.Log
|
||||
};
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
var eventModifier = _customEventRegistrations[eventParseResult.eventKey];
|
||||
|
||||
try
|
||||
{
|
||||
return eventModifier.Item2(logLine, Configuration, new GameEvent()
|
||||
{
|
||||
_logger.LogError(e, $"Could not handle custom event generation");
|
||||
}
|
||||
Type = GameEvent.EventType.Other,
|
||||
Data = logLine,
|
||||
Subtype = eventModifier.Item1,
|
||||
GameTime = gameTime,
|
||||
Source = GameEvent.EventSource.Log
|
||||
});
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Could not handle custom event generation");
|
||||
}
|
||||
|
||||
return new GameEvent()
|
||||
|
@ -1,5 +1,6 @@
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Globalization;
|
||||
using SharedLibraryCore;
|
||||
|
||||
namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
@ -17,6 +18,8 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
public ParserRegex Damage { get; set; }
|
||||
public ParserRegex Action { get; set; }
|
||||
public ParserRegex Time { get; set; }
|
||||
public ParserRegex MapChange { get; set; }
|
||||
public ParserRegex MapEnd { get; set; }
|
||||
public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber;
|
||||
|
||||
public DynamicEventParserConfiguration(IParserRegexFactory parserRegexFactory)
|
||||
@ -28,6 +31,8 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
Damage = parserRegexFactory.CreateParserRegex();
|
||||
Action = parserRegexFactory.CreateParserRegex();
|
||||
Time = parserRegexFactory.CreateParserRegex();
|
||||
MapChange = parserRegexFactory.CreateParserRegex();
|
||||
MapEnd = parserRegexFactory.CreateParserRegex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
using IW4MAdmin.Application.RCon;
|
||||
using System;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Text;
|
||||
using Integrations.Cod;
|
||||
using Integrations.Source;
|
||||
using Integrations.Source.Interfaces;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace IW4MAdmin.Application.Factories
|
||||
@ -10,16 +14,16 @@ namespace IW4MAdmin.Application.Factories
|
||||
/// </summary>
|
||||
internal class RConConnectionFactory : IRConConnectionFactory
|
||||
{
|
||||
private static readonly Encoding gameEncoding = Encoding.GetEncoding("windows-1252");
|
||||
private readonly ILogger<RConConnection> _logger;
|
||||
|
||||
private static readonly Encoding GameEncoding = Encoding.GetEncoding("windows-1252");
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Base constructor
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
public RConConnectionFactory(ILogger<RConConnection> logger)
|
||||
public RConConnectionFactory(IServiceProvider serviceProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -29,9 +33,16 @@ namespace IW4MAdmin.Application.Factories
|
||||
/// <param name="port">port of the server</param>
|
||||
/// <param name="password">rcon password of the server</param>
|
||||
/// <returns></returns>
|
||||
public IRConConnection CreateConnection(string ipAddress, int port, string password)
|
||||
public IRConConnection CreateConnection(string ipAddress, int port, string password, string rconEngine)
|
||||
{
|
||||
return new RConConnection(ipAddress, port, password, _logger, gameEncoding);
|
||||
return rconEngine switch
|
||||
{
|
||||
"COD" => new CodRConConnection(ipAddress, port, password,
|
||||
_serviceProvider.GetRequiredService<ILogger<CodRConConnection>>(), GameEncoding),
|
||||
"Source" => new SourceRConConnection(_serviceProvider.GetRequiredService<ILogger<SourceRConConnection>>(),
|
||||
_serviceProvider.GetRequiredService<IRConClientFactory>(), ipAddress, port, password),
|
||||
_ => throw new ArgumentException($"No supported RCon engine available for '{rconEngine}'")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -74,6 +74,8 @@ namespace IW4MAdmin
|
||||
client = await Manager.GetClientService().Create(clientFromLog);
|
||||
}
|
||||
|
||||
client.CopyAdditionalProperties(clientFromLog);
|
||||
|
||||
// this is only a temporary version until the IPAddress is transmitted
|
||||
client.CurrentAlias = new EFAlias()
|
||||
{
|
||||
@ -739,11 +741,11 @@ namespace IW4MAdmin
|
||||
/// array index 2 = updated clients
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
async Task<IList<EFClient>[]> PollPlayersAsync()
|
||||
async Task<List<EFClient>[]> PollPlayersAsync()
|
||||
{
|
||||
var currentClients = GetClientsAsList();
|
||||
var statusResponse = (await this.GetStatusAsync());
|
||||
var polledClients = statusResponse.Item1.AsEnumerable();
|
||||
var polledClients = statusResponse.Clients.AsEnumerable();
|
||||
|
||||
if (Manager.GetApplicationSettings().Configuration().IgnoreBots)
|
||||
{
|
||||
@ -753,10 +755,12 @@ namespace IW4MAdmin
|
||||
var connectingClients = polledClients.Except(currentClients);
|
||||
var updatedClients = polledClients.Except(connectingClients).Except(disconnectingClients);
|
||||
|
||||
UpdateMap(statusResponse.Item2);
|
||||
UpdateGametype(statusResponse.Item3);
|
||||
UpdateMap(statusResponse.Map);
|
||||
UpdateGametype(statusResponse.GameType);
|
||||
UpdateHostname(statusResponse.Hostname);
|
||||
UpdateMaxPlayers(statusResponse.MaxClients);
|
||||
|
||||
return new List<EFClient>[]
|
||||
return new []
|
||||
{
|
||||
connectingClients.ToList(),
|
||||
disconnectingClients.ToList(),
|
||||
@ -803,6 +807,36 @@ namespace IW4MAdmin
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHostname(string hostname)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hostname) || Hostname == hostname)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using(LogContext.PushProperty("Server", ToString()))
|
||||
{
|
||||
ServerLogger.LogDebug("Updating hostname to {HostName}", hostname);
|
||||
}
|
||||
|
||||
Hostname = hostname;
|
||||
}
|
||||
|
||||
private void UpdateMaxPlayers(int? maxPlayers)
|
||||
{
|
||||
if (maxPlayers == null || maxPlayers == MaxClients)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using(LogContext.PushProperty("Server", ToString()))
|
||||
{
|
||||
ServerLogger.LogDebug("Updating max clients to {MaxPlayers}", maxPlayers);
|
||||
}
|
||||
|
||||
MaxClients = maxPlayers.Value;
|
||||
}
|
||||
|
||||
private async Task ShutdownInternal()
|
||||
{
|
||||
foreach (var client in GetClientsAsList())
|
||||
@ -1011,6 +1045,7 @@ namespace IW4MAdmin
|
||||
RconParser = RconParser ?? Manager.AdditionalRConParsers[0];
|
||||
EventParser = EventParser ?? Manager.AdditionalEventParsers[0];
|
||||
|
||||
RemoteConnection = RConConnectionFactory.CreateConnection(IP, Port, Password, RconParser.RConEngine);
|
||||
RemoteConnection.SetConfiguration(RconParser);
|
||||
|
||||
var version = await this.GetMappedDvarValueOrDefaultAsync<string>("version");
|
||||
@ -1172,7 +1207,7 @@ namespace IW4MAdmin
|
||||
|
||||
public Uri[] GenerateUriForLog(string logPath, string gameLogServerUrl)
|
||||
{
|
||||
var logUri = new Uri(logPath);
|
||||
var logUri = new Uri(logPath, UriKind.Absolute);
|
||||
|
||||
if (string.IsNullOrEmpty(gameLogServerUrl))
|
||||
{
|
||||
@ -1287,8 +1322,12 @@ namespace IW4MAdmin
|
||||
|
||||
Manager.AddEvent(e);
|
||||
|
||||
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
||||
var parsedClientId = string.IsNullOrEmpty(temporalClientId) ? (int?)null : int.Parse(temporalClientId);
|
||||
var clientNumber = parsedClientId ?? targetClient.ClientNumber;
|
||||
|
||||
var formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||
targetClient.ClientNumber,
|
||||
clientNumber,
|
||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration,
|
||||
newPenalty,
|
||||
previousPenalty));
|
||||
@ -1319,8 +1358,12 @@ namespace IW4MAdmin
|
||||
|
||||
if (targetClient.IsIngame)
|
||||
{
|
||||
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
||||
var parsedClientId = string.IsNullOrEmpty(temporalClientId) ? (int?)null : int.Parse(temporalClientId);
|
||||
var clientNumber = parsedClientId ?? targetClient.ClientNumber;
|
||||
|
||||
var formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||
targetClient.ClientNumber,
|
||||
clientNumber,
|
||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
|
||||
ServerLogger.LogDebug("Executing tempban kick command for {targetClient}", targetClient.ToString());
|
||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||
@ -1353,8 +1396,13 @@ namespace IW4MAdmin
|
||||
if (targetClient.IsIngame)
|
||||
{
|
||||
ServerLogger.LogDebug("Attempting to kicking newly banned client {targetClient}", targetClient.ToString());
|
||||
|
||||
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
||||
var parsedClientId = string.IsNullOrEmpty(temporalClientId) ? (int?)null : int.Parse(temporalClientId);
|
||||
var clientNumber = parsedClientId ?? targetClient.ClientNumber;
|
||||
|
||||
var formattedString = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||
targetClient.ClientNumber,
|
||||
clientNumber,
|
||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
|
||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedString);
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Data.Helpers;
|
||||
using Integrations.Source.Extensions;
|
||||
using IW4MAdmin.Application.Extensions;
|
||||
using IW4MAdmin.Application.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -417,6 +418,8 @@ namespace IW4MAdmin.Application
|
||||
serviceCollection.AddSingleton<IEventHandler, GameEventHandler>();
|
||||
}
|
||||
|
||||
serviceCollection.AddSource();
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
|
||||
using static SharedLibraryCore.Server;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
namespace IW4MAdmin.Application.RConParsers
|
||||
{
|
||||
public class BaseRConParser : IRConParser
|
||||
{
|
||||
@ -56,6 +56,7 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDefaultValue, 3);
|
||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarLatchedValue, 4);
|
||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDomain, 5);
|
||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.AdditionalGroup, int.MaxValue);
|
||||
|
||||
Configuration.StatusHeader.Pattern = "num +score +ping +guid +name +lastmsg +address +qport +rate *";
|
||||
Configuration.GametypeStatus.Pattern = "";
|
||||
@ -73,6 +74,7 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
public Game GameName { get; set; } = Game.COD;
|
||||
public bool CanGenerateLogPath { get; set; } = true;
|
||||
public string Name { get; set; } = "Call of Duty";
|
||||
public string RConEngine { get; set; } = "COD";
|
||||
|
||||
public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command)
|
||||
{
|
||||
@ -128,7 +130,7 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
|
||||
return new Dvar<T>()
|
||||
{
|
||||
Name = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarName]].Value,
|
||||
Name = dvarName,
|
||||
Value = string.IsNullOrEmpty(value) ? default : (T)Convert.ChangeType(value, typeof(T)),
|
||||
DefaultValue = string.IsNullOrEmpty(defaultValue) ? default : (T)Convert.ChangeType(defaultValue, typeof(T)),
|
||||
LatchedValue = string.IsNullOrEmpty(latchedValue) ? default : (T)Convert.ChangeType(latchedValue, typeof(T)),
|
||||
@ -136,53 +138,55 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
};
|
||||
}
|
||||
|
||||
public virtual async Task<(List<EFClient>, string, string)> GetStatusAsync(IRConConnection connection)
|
||||
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection)
|
||||
{
|
||||
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS);
|
||||
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS);
|
||||
_logger.LogDebug("Status Response {response}", string.Join(Environment.NewLine, response));
|
||||
return (ClientsFromStatus(response), MapFromStatus(response), GameTypeFromStatus(response));
|
||||
return new StatusResponse
|
||||
{
|
||||
Clients = ClientsFromStatus(response).ToArray(),
|
||||
Map = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusMap, Configuration.MapStatus.Pattern),
|
||||
GameType = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusGametype, Configuration.GametypeStatus.Pattern),
|
||||
Hostname = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusHostname, Configuration.HostnameStatus.Pattern),
|
||||
MaxClients = GetValueFromStatus<int?>(response, ParserRegex.GroupType.RConStatusMaxPlayers, Configuration.MaxPlayersStatus.Pattern)
|
||||
};
|
||||
}
|
||||
|
||||
private string MapFromStatus(string[] response)
|
||||
private T GetValueFromStatus<T>(IEnumerable<string> response, ParserRegex.GroupType groupType, string groupPattern)
|
||||
{
|
||||
string map = null;
|
||||
if (string.IsNullOrEmpty(groupPattern))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
string value = null;
|
||||
foreach (var line in response)
|
||||
{
|
||||
var regex = Regex.Match(line, Configuration.MapStatus.Pattern);
|
||||
var regex = Regex.Match(line, groupPattern);
|
||||
if (regex.Success)
|
||||
{
|
||||
map = regex.Groups[Configuration.MapStatus.GroupMapping[ParserRegex.GroupType.RConStatusMap]].ToString();
|
||||
value = regex.Groups[Configuration.MapStatus.GroupMapping[groupType]].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private string GameTypeFromStatus(string[] response)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Configuration.GametypeStatus.Pattern))
|
||||
if (value == null)
|
||||
{
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
|
||||
string gametype = null;
|
||||
foreach (var line in response)
|
||||
if (typeof(T) == typeof(int?))
|
||||
{
|
||||
var regex = Regex.Match(line, Configuration.GametypeStatus.Pattern);
|
||||
if (regex.Success)
|
||||
{
|
||||
gametype = regex.Groups[Configuration.GametypeStatus.GroupMapping[ParserRegex.GroupType.RConStatusGametype]].ToString();
|
||||
}
|
||||
return (T)Convert.ChangeType(int.Parse(value), Nullable.GetUnderlyingType(typeof(T)));
|
||||
}
|
||||
|
||||
return gametype;
|
||||
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
|
||||
public async Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue)
|
||||
{
|
||||
string dvarString = (dvarValue is string str)
|
||||
? $"{dvarName} \"{str}\""
|
||||
: $"{dvarName} {dvarValue.ToString()}";
|
||||
: $"{dvarName} {dvarValue}";
|
||||
|
||||
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString)).Length > 0;
|
||||
}
|
||||
@ -257,6 +261,11 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
};
|
||||
|
||||
client.SetAdditionalProperty("BotGuid", networkIdString);
|
||||
var additionalGroupIndex = Configuration.Status.GroupMapping[ParserRegex.GroupType.AdditionalGroup];
|
||||
if (match.Values.Length > additionalGroupIndex)
|
||||
{
|
||||
client.SetAdditionalProperty("ConnectionClientId", match.Values[additionalGroupIndex]);
|
||||
}
|
||||
|
||||
StatusPlayers.Add(client);
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
namespace IW4MAdmin.Application.RConParsers
|
||||
{
|
||||
/// <summary>
|
||||
/// empty implementation of the IW4RConParser
|
||||
/// allows script plugins to generate dynamic RCon parsers
|
||||
/// </summary>
|
||||
sealed internal class DynamicRConParser : BaseRConParser
|
||||
internal sealed class DynamicRConParser : BaseRConParser
|
||||
{
|
||||
public DynamicRConParser(ILogger<BaseRConParser> logger, IParserRegexFactory parserRegexFactory) : base(logger, parserRegexFactory)
|
||||
{
|
@ -4,7 +4,7 @@ using SharedLibraryCore.RCon;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
namespace IW4MAdmin.Application.RConParsers
|
||||
{
|
||||
/// <summary>
|
||||
/// generic implementation of the IRConParserConfiguration
|
||||
@ -16,6 +16,8 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
public ParserRegex Status { get; set; }
|
||||
public ParserRegex MapStatus { get; set; }
|
||||
public ParserRegex GametypeStatus { get; set; }
|
||||
public ParserRegex HostnameStatus { get; set; }
|
||||
public ParserRegex MaxPlayersStatus { get; set; }
|
||||
public ParserRegex Dvar { get; set; }
|
||||
public ParserRegex StatusHeader { get; set; }
|
||||
public string ServerNotRunningResponse { get; set; }
|
||||
@ -34,6 +36,8 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
GametypeStatus = parserRegexFactory.CreateParserRegex();
|
||||
Dvar = parserRegexFactory.CreateParserRegex();
|
||||
StatusHeader = parserRegexFactory.CreateParserRegex();
|
||||
HostnameStatus = parserRegexFactory.CreateParserRegex();
|
||||
MaxPlayersStatus = parserRegexFactory.CreateParserRegex();
|
||||
}
|
||||
}
|
||||
}
|
15
Application/RConParsers/StatusResponse.cs
Normal file
15
Application/RConParsers/StatusResponse.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.RConParsers
|
||||
{
|
||||
/// <inheritdoc cref="IStatusResponse"/>
|
||||
public class StatusResponse : IStatusResponse
|
||||
{
|
||||
public string Map { get; set; }
|
||||
public string GameType { get; set; }
|
||||
public string Hostname { get; set; }
|
||||
public int? MaxClients { get; set; }
|
||||
public EFClient[] Clients { get; set; }
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@
|
||||
<PackageId>RaidMax.IW4MAdmin.Data</PackageId>
|
||||
<Title>RaidMax.IW4MAdmin.Data</Title>
|
||||
<Authors />
|
||||
<PackageVersion>1.0.1</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -14,7 +14,8 @@
|
||||
T5 = 6,
|
||||
T6 = 7,
|
||||
T7 = 8,
|
||||
SHG1 = 9
|
||||
SHG1 = 9,
|
||||
CSGO = 10
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ namespace Data.Models
|
||||
{
|
||||
public class SharedEntity : IPropertyExtender
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, object> _additionalProperties;
|
||||
private ConcurrentDictionary<string, object> _additionalProperties;
|
||||
|
||||
/// <summary>
|
||||
/// indicates if the entity is active
|
||||
@ -33,5 +33,10 @@ namespace Data.Models
|
||||
_additionalProperties.TryAdd(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyAdditionalProperties(SharedEntity source)
|
||||
{
|
||||
_additionalProperties = source._additionalProperties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
||||
Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js
|
||||
Plugins\ScriptPlugins\ParserPlutoniumT4.js = Plugins\ScriptPlugins\ParserPlutoniumT4.js
|
||||
Plugins\ScriptPlugins\ParserS1x.js = Plugins\ScriptPlugins\ParserS1x.js
|
||||
Plugins\ScriptPlugins\ParserCSGO.js = Plugins\ScriptPlugins\ParserCSGO.js
|
||||
Plugins\ScriptPlugins\ParserCSGOSM.js = Plugins\ScriptPlugins\ParserCSGOSM.js
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutomessageFeed", "Plugins\AutomessageFeed\AutomessageFeed.csproj", "{F5815359-CFC7-44B4-9A3B-C04BACAD5836}"
|
||||
@ -59,6 +61,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationTests", "Tests\A
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Data", "Data\Data.csproj", "{81689023-E55E-48ED-B7A8-53F4E21BBF2D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Integrations", "Integrations", "{A2AE33B4-0830-426A-9E11-951DAB12BE5B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integrations.Cod", "Integrations\Cod\Integrations.Cod.csproj", "{A9348433-58C1-4B9C-8BB7-088B02529D9D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integrations.Source", "Integrations\Source\Integrations.Source.csproj", "{9512295B-3045-40E0-9B7E-2409F2173E9D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -362,6 +370,54 @@ Global
|
||||
{81689023-E55E-48ED-B7A8-53F4E21BBF2D}.Release|x86.Build.0 = Release|Any CPU
|
||||
{81689023-E55E-48ED-B7A8-53F4E21BBF2D}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
|
||||
{81689023-E55E-48ED-B7A8-53F4E21BBF2D}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|x64.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|x64.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|x86.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|x86.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|x86.Build.0 = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|x64.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|x64.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|x86.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|x86.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -376,6 +432,8 @@ Global
|
||||
{F5815359-CFC7-44B4-9A3B-C04BACAD5836} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{00A1FED2-2254-4AF7-A5DB-2357FA7C88CD} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B} = {3065279E-17F0-4CE0-AF5B-014E04263D77}
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D} = {A2AE33B4-0830-426A-9E11-951DAB12BE5B}
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D} = {A2AE33B4-0830-426A-9E11-951DAB12BE5B}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}
|
||||
|
@ -1,8 +1,4 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.RCon;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -14,25 +10,29 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.RCon;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.RCon
|
||||
namespace Integrations.Cod
|
||||
{
|
||||
/// <summary>
|
||||
/// implementation of IRConConnection
|
||||
/// </summary>
|
||||
public class RConConnection : IRConConnection
|
||||
public class CodRConConnection : IRConConnection
|
||||
{
|
||||
static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new ConcurrentDictionary<EndPoint, ConnectionState>();
|
||||
public IPEndPoint Endpoint { get; private set; }
|
||||
public string RConPassword { get; private set; }
|
||||
public IPEndPoint Endpoint { get; }
|
||||
public string RConPassword { get; }
|
||||
|
||||
private IRConParser parser;
|
||||
private IRConParserConfiguration config;
|
||||
private readonly ILogger _log;
|
||||
private readonly Encoding _gameEncoding;
|
||||
|
||||
public RConConnection(string ipAddress, int port, string password, ILogger<RConConnection> log, Encoding gameEncoding)
|
||||
public CodRConConnection(string ipAddress, int port, string password, ILogger<CodRConConnection> log, Encoding gameEncoding)
|
||||
{
|
||||
Endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
|
||||
_gameEncoding = gameEncoding;
|
||||
@ -468,4 +468,4 @@ namespace IW4MAdmin.Application.RCon
|
||||
ActiveQueries[this.Endpoint].OnSentData.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace IW4MAdmin.Application.RCon
|
||||
namespace Integrations.Cod
|
||||
{
|
||||
/// <summary>
|
||||
/// used to keep track of the udp connection state
|
||||
@ -28,4 +28,4 @@ namespace IW4MAdmin.Application.RCon
|
||||
public SocketAsyncEventArgs ReceiveEventArgs { get; set; } = new SocketAsyncEventArgs();
|
||||
public DateTime LastQuery { get; set; } = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
13
Integrations/Cod/Integrations.Cod.csproj
Normal file
13
Integrations/Cod/Integrations.Cod.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AssemblyName>Integrations.Cod</AssemblyName>
|
||||
<RootNamespace>Integrations.Cod</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,15 @@
|
||||
using Integrations.Source.Interfaces;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Integrations.Source.Extensions
|
||||
{
|
||||
public static class IntegrationServicesExtensions
|
||||
{
|
||||
public static IServiceCollection AddSource(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IRConClientFactory, RConClientFactory>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
17
Integrations/Source/Integrations.Source.csproj
Normal file
17
Integrations/Source/Integrations.Source.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AssemblyName>Integrations.Source</AssemblyName>
|
||||
<RootNamespace>Integrations.Source</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RconSharp" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
9
Integrations/Source/Interfaces/IRConClientFactory.cs
Normal file
9
Integrations/Source/Interfaces/IRConClientFactory.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using RconSharp;
|
||||
|
||||
namespace Integrations.Source.Interfaces
|
||||
{
|
||||
public interface IRConClientFactory
|
||||
{
|
||||
RconClient CreateClient(string hostname, int port);
|
||||
}
|
||||
}
|
13
Integrations/Source/RConClientFactory.cs
Normal file
13
Integrations/Source/RConClientFactory.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Integrations.Source.Interfaces;
|
||||
using RconSharp;
|
||||
|
||||
namespace Integrations.Source
|
||||
{
|
||||
public class RConClientFactory : IRConClientFactory
|
||||
{
|
||||
public RconClient CreateClient(string hostname, int port)
|
||||
{
|
||||
return RconClient.Create(hostname, port);
|
||||
}
|
||||
}
|
||||
}
|
105
Integrations/Source/SourceRConConnection.cs
Normal file
105
Integrations/Source/SourceRConConnection.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Integrations.Source.Interfaces;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RconSharp;
|
||||
using Serilog.Context;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.RCon;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace Integrations.Source
|
||||
{
|
||||
public class SourceRConConnection : IRConConnection
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly string _password;
|
||||
private readonly string _hostname;
|
||||
private readonly int _port;
|
||||
private readonly IRConClientFactory _rconClientFactory;
|
||||
|
||||
private RconClient _rconClient;
|
||||
|
||||
public SourceRConConnection(ILogger<SourceRConConnection> logger, IRConClientFactory rconClientFactory,
|
||||
string hostname, int port, string password)
|
||||
{
|
||||
_rconClient = rconClientFactory.CreateClient(hostname, port);
|
||||
_rconClientFactory = rconClientFactory;
|
||||
_password = password;
|
||||
_hostname = hostname;
|
||||
_port = port;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "")
|
||||
{
|
||||
await _rconClient.ConnectAsync();
|
||||
|
||||
bool authenticated;
|
||||
|
||||
try
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
_rconClient = _rconClientFactory.CreateClient(_hostname, _port);
|
||||
}
|
||||
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogError("Could not login to server");
|
||||
}
|
||||
|
||||
throw new NetworkException("Could not authenticate 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)
|
||||
{
|
||||
parameters = "status";
|
||||
}
|
||||
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogDebug("Sending query {Type} with parameters {Parameters}", type, parameters);
|
||||
}
|
||||
|
||||
var response = await _rconClient.ExecuteCommandAsync(parameters.StripColors(), true);
|
||||
|
||||
using (LogContext.PushProperty("Server", $"{_rconClient.Host}:{_rconClient.Port}"))
|
||||
{
|
||||
_logger.LogDebug("Received RCon response {Response}", response);
|
||||
}
|
||||
|
||||
var split = response.TrimEnd('\n').Split('\n');
|
||||
return split.Take(split.Length - 1).ToArray();
|
||||
}
|
||||
|
||||
public void SetConfiguration(IRConParser config)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
100
Plugins/ScriptPlugins/ParserCSGO.js
Normal file
100
Plugins/ScriptPlugins/ParserCSGO.js
Normal file
@ -0,0 +1,100 @@
|
||||
let rconParser;
|
||||
let eventParser;
|
||||
|
||||
const plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 0.1,
|
||||
name: 'CS:GO Parser',
|
||||
engine: 'Source',
|
||||
isParser: true,
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
rconParser = manager.GenerateDynamicRConParser(this.engine);
|
||||
eventParser = manager.GenerateDynamicEventParser(this.engine);
|
||||
rconParser.RConEngine = this.engine;
|
||||
|
||||
rconParser.Configuration.StatusHeader.Pattern = 'userid +name +uniqueid +connected +ping +loss +state +rate +adr';
|
||||
|
||||
rconParser.Configuration.MapStatus.Pattern = '^map *: +(.+)$';
|
||||
rconParser.Configuration.MapStatus.AddMapping(111, 1);
|
||||
|
||||
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
|
||||
rconParser.Configuration.MapStatus.AddMapping(113, 1);
|
||||
|
||||
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d humans, \\d bots \\((\\d+).+';
|
||||
rconParser.Configuration.MapStatus.AddMapping(114, 1);
|
||||
|
||||
rconParser.Configuration.Dvar.Pattern = '^"(.+)" = (?:"(.+)" (?:\\( def\\. "(.*)" \\))|"(.+)" +(.+)) +- (.*)$';
|
||||
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
||||
rconParser.Configuration.Dvar.AddMapping(107, 2);
|
||||
rconParser.Configuration.Dvar.AddMapping(108, 3);
|
||||
rconParser.Configuration.Dvar.AddMapping(109, 3);
|
||||
|
||||
rconParser.Configuration.Status.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) (\\d+:\\d+) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
|
||||
rconParser.Configuration.Status.AddMapping(100, 2);
|
||||
rconParser.Configuration.Status.AddMapping(101, 7);
|
||||
rconParser.Configuration.Status.AddMapping(102, 6);
|
||||
rconParser.Configuration.Status.AddMapping(103, 4)
|
||||
rconParser.Configuration.Status.AddMapping(104, 3);
|
||||
rconParser.Configuration.Status.AddMapping(105, 10);
|
||||
rconParser.Configuration.Status.AddMapping(200, 1);
|
||||
|
||||
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('version', this.engine);
|
||||
rconParser.Configuration.DefaultDvarValues.Add('fs_basepath', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('fs_basegame', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('g_log', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('net_ip', 'localhost');
|
||||
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_hostname', 'hostname');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('mapname', 'host_map');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_maxclients', 'maxplayers');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('g_gametype', 'game_type'); // todo: will need gamemode too
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('fs_game', 'game_mode');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('g_password', 'sv_password');
|
||||
|
||||
rconParser.Configuration.NoticeLineSeparator = '. ';
|
||||
rconParser.CanGenerateLogPath = false;
|
||||
|
||||
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
|
||||
rconParser.Configuration.CommandPrefixes.Kick = 'kickid {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.Ban = 'kickid {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.TempBan = 'kickid {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
|
||||
rconParser.Configuration.CommandPrefixes.Tell = 'say [{0}] {1}'; // no tell exists in vanilla
|
||||
|
||||
eventParser.Configuration.Say.Pattern = '^"(.+)<(\\d+)><(.+)><(.*?)>" say "(.*)"$';
|
||||
eventParser.Configuration.Say.AddMapping(5, 1);
|
||||
eventParser.Configuration.Say.AddMapping(3, 2);
|
||||
eventParser.Configuration.Say.AddMapping(1, 3);
|
||||
eventParser.Configuration.Say.AddMapping(7, 4);
|
||||
eventParser.Configuration.Say.AddMapping(13, 5);
|
||||
|
||||
eventParser.Configuration.Kill.Pattern = '"(.+)<(\\d+)><(.+)><(.*)>" \\[-?\\d+ -?\\d+ -?\\d+\\] killed "(.+)<(\\d+)><(.+)><(.*)>" \\[-?\\d+ -?\\d+ -?\\d+\\] with "(\\S*)"(.*)$';
|
||||
eventParser.Configuration.Kill.AddMapping(5, 1);
|
||||
eventParser.Configuration.Kill.AddMapping(3, 2);
|
||||
eventParser.Configuration.Kill.AddMapping(1, 3);
|
||||
eventParser.Configuration.Kill.AddMapping(7, 4);
|
||||
eventParser.Configuration.Kill.AddMapping(6, 5);
|
||||
eventParser.Configuration.Kill.AddMapping(4, 6);
|
||||
eventParser.Configuration.Kill.AddMapping(2, 7);
|
||||
eventParser.Configuration.Kill.AddMapping(8, 8);
|
||||
eventParser.Configuration.Kill.AddMapping(9, 9);
|
||||
|
||||
eventParser.Configuration.Time.Pattern = '^L [01]\\d/[0-3]\\d/\\d+ - [0-2]\\d:[0-5]\\d:[0-5]\\d:';
|
||||
|
||||
rconParser.Version = 'CSGO';
|
||||
rconParser.GameName = 10; // CSGO
|
||||
eventParser.Version = 'CSGO';
|
||||
eventParser.GameName = 10; // CSGO
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
100
Plugins/ScriptPlugins/ParserCSGOSM.js
Normal file
100
Plugins/ScriptPlugins/ParserCSGOSM.js
Normal file
@ -0,0 +1,100 @@
|
||||
let rconParser;
|
||||
let eventParser;
|
||||
|
||||
const plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 0.1,
|
||||
name: 'CS:GO (SourceMod) Parser',
|
||||
engine: 'Source',
|
||||
isParser: true,
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
rconParser = manager.GenerateDynamicRConParser(this.engine);
|
||||
eventParser = manager.GenerateDynamicEventParser(this.engine);
|
||||
rconParser.RConEngine = this.engine;
|
||||
|
||||
rconParser.Configuration.StatusHeader.Pattern = 'userid +name +uniqueid +connected +ping +loss +state +rate +adr';
|
||||
|
||||
rconParser.Configuration.MapStatus.Pattern = '^map *: +(.+)$';
|
||||
rconParser.Configuration.MapStatus.AddMapping(111, 1);
|
||||
|
||||
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
|
||||
rconParser.Configuration.MapStatus.AddMapping(113, 1);
|
||||
|
||||
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d humans, \\d bots \\((\\d+).+';
|
||||
rconParser.Configuration.MapStatus.AddMapping(114, 1);
|
||||
|
||||
rconParser.Configuration.Dvar.Pattern = '^"(.+)" = (?:"(.+)" (?:\\( def\\. "(.*)" \\))|"(.+)" +(.+)) +- (.*)$';
|
||||
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
||||
rconParser.Configuration.Dvar.AddMapping(107, 2);
|
||||
rconParser.Configuration.Dvar.AddMapping(108, 3);
|
||||
rconParser.Configuration.Dvar.AddMapping(109, 3);
|
||||
|
||||
rconParser.Configuration.Status.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) (\\d+:\\d+) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
|
||||
rconParser.Configuration.Status.AddMapping(100, 2);
|
||||
rconParser.Configuration.Status.AddMapping(101, 7);
|
||||
rconParser.Configuration.Status.AddMapping(102, 6);
|
||||
rconParser.Configuration.Status.AddMapping(103, 4)
|
||||
rconParser.Configuration.Status.AddMapping(104, 3);
|
||||
rconParser.Configuration.Status.AddMapping(105, 10);
|
||||
rconParser.Configuration.Status.AddMapping(200, 1);
|
||||
|
||||
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('version', this.engine);
|
||||
rconParser.Configuration.DefaultDvarValues.Add('fs_basepath', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('fs_basegame', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('g_log', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('net_ip', 'localhost');
|
||||
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_hostname', 'hostname');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('mapname', 'host_map');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_maxclients', 'maxplayers');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('g_gametype', 'game_type');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('fs_game', 'game_mode');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('g_password', 'sv_password');
|
||||
|
||||
rconParser.Configuration.NoticeLineSeparator = '. ';
|
||||
rconParser.CanGenerateLogPath = false;
|
||||
|
||||
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
|
||||
rconParser.Configuration.CommandPrefixes.Kick = 'sm_kick #{0} {1}';
|
||||
rconParser.Configuration.CommandPrefixes.Ban = 'sm_kick #{0} {1}';
|
||||
rconParser.Configuration.CommandPrefixes.TempBan = 'sm_kick #{0} {1}';
|
||||
rconParser.Configuration.CommandPrefixes.Say = 'sm_say {0}';
|
||||
rconParser.Configuration.CommandPrefixes.Tell = 'sm_psay #{0} {1}';
|
||||
|
||||
eventParser.Configuration.Say.Pattern = '^"(.+)<(\\d+)><(.+)><(.*?)>" say "(.*)"$';
|
||||
eventParser.Configuration.Say.AddMapping(5, 1);
|
||||
eventParser.Configuration.Say.AddMapping(3, 2);
|
||||
eventParser.Configuration.Say.AddMapping(1, 3);
|
||||
eventParser.Configuration.Say.AddMapping(7, 4);
|
||||
eventParser.Configuration.Say.AddMapping(13, 5);
|
||||
|
||||
eventParser.Configuration.Kill.Pattern = '"(.+)<(\\d+)><(.+)><(.*)>" \\[-?\\d+ -?\\d+ -?\\d+\\] killed "(.+)<(\\d+)><(.+)><(.*)>" \\[-?\\d+ -?\\d+ -?\\d+\\] with "(\\S*)"(.*)$';
|
||||
eventParser.Configuration.Kill.AddMapping(5, 1);
|
||||
eventParser.Configuration.Kill.AddMapping(3, 2);
|
||||
eventParser.Configuration.Kill.AddMapping(1, 3);
|
||||
eventParser.Configuration.Kill.AddMapping(7, 4);
|
||||
eventParser.Configuration.Kill.AddMapping(6, 5);
|
||||
eventParser.Configuration.Kill.AddMapping(4, 6);
|
||||
eventParser.Configuration.Kill.AddMapping(2, 7);
|
||||
eventParser.Configuration.Kill.AddMapping(8, 8);
|
||||
eventParser.Configuration.Kill.AddMapping(9, 9);
|
||||
|
||||
eventParser.Configuration.Time.Pattern = '^L [01]\\d/[0-3]\\d/\\d+ - [0-2]\\d:[0-5]\\d:[0-5]\\d:';
|
||||
|
||||
rconParser.Version = 'CSGOSM';
|
||||
rconParser.GameName = 10; // CSGO
|
||||
eventParser.Version = 'CSGOSM';
|
||||
eventParser.GameName = 10; // CSGO
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
@ -176,6 +176,12 @@ namespace IW4MAdmin.Plugins.Stats.Client
|
||||
|
||||
foreach (var hitInfo in new[] {attackerHitInfo, victimHitInfo})
|
||||
{
|
||||
if (hitInfo.MeansOfDeath == null || hitInfo.Location == null || hitInfo.Weapon == null)
|
||||
{
|
||||
_logger.LogDebug("Skipping hit because it does not contain the required data");
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _onTransaction.WaitAsync();
|
||||
|
@ -15,13 +15,13 @@ namespace Stats.Client
|
||||
private readonly IWeaponNameParser _weaponNameParser;
|
||||
private readonly ILogger _logger;
|
||||
private const int MaximumDamage = 1000;
|
||||
|
||||
|
||||
public HitInfoBuilder(ILogger<HitInfoBuilder> logger, IWeaponNameParser weaponNameParser)
|
||||
{
|
||||
_weaponNameParser = weaponNameParser;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public HitInfo Build(string[] log, int entityId, bool isSelf, bool isVictim, Server.Game gameName)
|
||||
{
|
||||
var eventType = log[(uint) ParserRegex.GroupType.EventType].First();
|
||||
@ -50,11 +50,19 @@ namespace Stats.Client
|
||||
EntityId = entityId,
|
||||
IsVictim = isVictim,
|
||||
HitType = hitType,
|
||||
Damage = Math.Min(MaximumDamage, int.Parse(log[(uint) ParserRegex.GroupType.Damage])),
|
||||
Location = log[(uint) ParserRegex.GroupType.HitLocation],
|
||||
Weapon = _weaponNameParser.Parse(log[(uint) ParserRegex.GroupType.Weapon], gameName),
|
||||
MeansOfDeath = log[(uint)ParserRegex.GroupType.MeansOfDeath],
|
||||
Game = (Reference.Game)gameName
|
||||
Damage = Math.Min(MaximumDamage,
|
||||
log.Length > (uint) ParserRegex.GroupType.Damage
|
||||
? int.Parse(log[(uint) ParserRegex.GroupType.Damage])
|
||||
: 0),
|
||||
Location = log.Length > (uint) ParserRegex.GroupType.HitLocation
|
||||
? log[(uint) ParserRegex.GroupType.HitLocation]
|
||||
: "Unknown",
|
||||
Weapon = log.Length == 10 ? _weaponNameParser.Parse(log[8], gameName)
|
||||
: _weaponNameParser.Parse(log[(uint) ParserRegex.GroupType.Weapon], gameName),
|
||||
MeansOfDeath = log.Length > (uint) ParserRegex.GroupType.MeansOfDeath
|
||||
? log[(uint) ParserRegex.GroupType.MeansOfDeath]
|
||||
: "Unknown",
|
||||
Game = (Reference.Game) gameName
|
||||
};
|
||||
|
||||
//_logger.LogDebug("Generated new hitInfo {@hitInfo}", hitInfo);
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using IW4MAdmin.Plugins.Stats.Config;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using Stats.Config;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace Stats.Client
|
||||
@ -24,22 +25,16 @@ namespace Stats.Client
|
||||
public WeaponInfo Parse(string weaponName, Server.Game gameName)
|
||||
{
|
||||
var configForGame = _config.WeaponNameParserConfigurations
|
||||
?.FirstOrDefault(config => config.Game == gameName);
|
||||
|
||||
if (configForGame == null)
|
||||
?.FirstOrDefault(config => config.Game == gameName) ?? new WeaponNameParserConfiguration()
|
||||
{
|
||||
_logger.LogWarning("No weapon parser config available for game {game}", gameName);
|
||||
return new WeaponInfo()
|
||||
{
|
||||
Name = "Unknown"
|
||||
};
|
||||
}
|
||||
|
||||
Game = gameName
|
||||
};
|
||||
|
||||
var splitWeaponName = weaponName.Split(configForGame.Delimiters);
|
||||
|
||||
if (!splitWeaponName.Any())
|
||||
{
|
||||
_logger.LogError("Could not parse weapon name {weapon}", weaponName);
|
||||
_logger.LogError("Could not parse weapon name {Weapon}", weaponName);
|
||||
|
||||
return new WeaponInfo()
|
||||
{
|
||||
|
@ -11,20 +11,20 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// </summary>
|
||||
public enum GroupType
|
||||
{
|
||||
EventType,
|
||||
OriginNetworkId,
|
||||
TargetNetworkId,
|
||||
OriginClientNumber,
|
||||
TargetClientNumber,
|
||||
OriginName,
|
||||
TargetName,
|
||||
OriginTeam,
|
||||
TargetTeam,
|
||||
Weapon,
|
||||
Damage,
|
||||
MeansOfDeath,
|
||||
HitLocation,
|
||||
Message,
|
||||
EventType = 0,
|
||||
OriginNetworkId = 1,
|
||||
TargetNetworkId = 2,
|
||||
OriginClientNumber = 3,
|
||||
TargetClientNumber = 4,
|
||||
OriginName = 5,
|
||||
TargetName = 6,
|
||||
OriginTeam = 7,
|
||||
TargetTeam = 8,
|
||||
Weapon = 9,
|
||||
Damage = 10,
|
||||
MeansOfDeath = 11,
|
||||
HitLocation = 12,
|
||||
Message = 13,
|
||||
RConClientNumber = 100,
|
||||
RConScore = 101,
|
||||
RConPing = 102,
|
||||
@ -38,6 +38,8 @@ namespace SharedLibraryCore.Interfaces
|
||||
RConDvarDomain = 110,
|
||||
RConStatusMap = 111,
|
||||
RConStatusGametype = 112,
|
||||
RConStatusHostname = 113,
|
||||
RConStatusMaxPlayers = 114,
|
||||
AdditionalGroup = 200
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,16 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// </summary>
|
||||
ParserRegex Time { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex information for the map change game log
|
||||
/// </summary>
|
||||
ParserRegex MapChange { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex information for the map end game log
|
||||
/// </summary>
|
||||
ParserRegex MapEnd { get; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates the format expected for parsed guids
|
||||
/// </summary>
|
||||
|
@ -11,7 +11,8 @@
|
||||
/// <param name="ipAddress">ip address of the server</param>
|
||||
/// <param name="port">port of the server</param>
|
||||
/// <param name="password"> password of the server</param>
|
||||
/// <param name="rconEngine">engine to create the rcon connection to</param>
|
||||
/// <returns>instance of rcon connection</returns>
|
||||
IRConConnection CreateConnection(string ipAddress, int port, string password);
|
||||
IRConConnection CreateConnection(string ipAddress, int port, string password, string rconEngine);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using static SharedLibraryCore.Server;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
@ -39,8 +37,8 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// get the list of connected clients from status response
|
||||
/// </summary>
|
||||
/// <param name="connection">RCon connection to use</param>
|
||||
/// <returns>list of clients, current map, and current gametype</returns>
|
||||
Task<(List<EFClient>, string, string)> GetStatusAsync(IRConConnection connection);
|
||||
/// <returns><see cref="IStatusResponse"/></returns>
|
||||
Task<IStatusResponse> GetStatusAsync(IRConConnection connection);
|
||||
|
||||
/// <summary>
|
||||
/// stores the RCon configuration
|
||||
@ -50,23 +48,29 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <summary>
|
||||
/// stores the game/client specific version (usually the value of the "version" DVAR)
|
||||
/// </summary>
|
||||
string Version { get; set; }
|
||||
string Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the game name (usually the internal studio iteration ie: IW4, T5 etc...)
|
||||
/// </summary>
|
||||
Game GameName { get; set; }
|
||||
Game GameName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates if the game supports generating a log path from DVAR retrieval
|
||||
/// of fs_game, fs_basepath, g_log
|
||||
/// </summary>
|
||||
bool CanGenerateLogPath { get; set; }
|
||||
bool CanGenerateLogPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the name of the parser
|
||||
/// </summary>
|
||||
string Name { get; set; }
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the type of rcon engine
|
||||
/// eg: COD, Source
|
||||
/// </summary>
|
||||
string RConEngine { get; }
|
||||
|
||||
/// <summary>
|
||||
/// retrieves the value of given dvar key if it exists in the override dict
|
||||
|
@ -9,59 +9,69 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <summary>
|
||||
/// stores the command format for console commands
|
||||
/// </summary>
|
||||
CommandPrefix CommandPrefixes { get; set; }
|
||||
CommandPrefix CommandPrefixes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex info for parsing get status response
|
||||
/// </summary>
|
||||
ParserRegex Status { get; set; }
|
||||
ParserRegex Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores regex info for parsing the map line from rcon status response
|
||||
/// </summary>
|
||||
ParserRegex MapStatus { get; set; }
|
||||
ParserRegex MapStatus { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores regex info for parsing the gametype line from rcon status response
|
||||
/// </summary>
|
||||
ParserRegex GametypeStatus { get; set; }
|
||||
ParserRegex GametypeStatus { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores regex info for parsing hostname line from rcon status response
|
||||
/// </summary>
|
||||
ParserRegex HostnameStatus { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores regex info for parsing max players line from rcon status response
|
||||
/// </summary>
|
||||
ParserRegex MaxPlayersStatus { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex info for parsing get DVAR responses
|
||||
/// </summary>
|
||||
ParserRegex Dvar { get; set; }
|
||||
ParserRegex Dvar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex info for parsing the header of a status response
|
||||
/// </summary>
|
||||
ParserRegex StatusHeader { get; set; }
|
||||
ParserRegex StatusHeader { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the expected response message from rcon when the server is not running
|
||||
/// </summary>
|
||||
string ServerNotRunningResponse { get; set; }
|
||||
string ServerNotRunningResponse { get; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates if the application should wait for response from server
|
||||
/// when executing a command
|
||||
/// </summary>
|
||||
bool WaitForResponse { get; set; }
|
||||
bool WaitForResponse { get; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates the format expected for parsed guids
|
||||
/// </summary>
|
||||
NumberStyles GuidNumberStyle { get; set; }
|
||||
NumberStyles GuidNumberStyle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies simple mappings for dvar names in scenarios where the needed
|
||||
/// information is not stored in a traditional dvar name
|
||||
/// </summary>
|
||||
IDictionary<string, string> OverrideDvarNameMapping { get; set; }
|
||||
IDictionary<string, string> OverrideDvarNameMapping { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the default dvar values for games that don't support certain dvars
|
||||
/// </summary>
|
||||
IDictionary<string, string> DefaultDvarValues { get; set; }
|
||||
IDictionary<string, string> DefaultDvarValues { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies how many lines can be used for ingame notice
|
||||
@ -71,11 +81,11 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <summary>
|
||||
/// specifies how many characters can be displayed per notice line
|
||||
/// </summary>
|
||||
int NoticeMaxCharactersPerLine { get; set; }
|
||||
int NoticeMaxCharactersPerLine { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the characters used to split a line
|
||||
/// </summary>
|
||||
string NoticeLineSeparator { get; set; }
|
||||
string NoticeLineSeparator { get; }
|
||||
}
|
||||
}
|
||||
|
35
SharedLibraryCore/Interfaces/IStatusResponse.cs
Normal file
35
SharedLibraryCore/Interfaces/IStatusResponse.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using SharedLibraryCore.Database.Models;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// describes the collection of data returned from a status query
|
||||
/// </summary>
|
||||
public interface IStatusResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// name of the map
|
||||
/// </summary>
|
||||
string Map { get; }
|
||||
|
||||
/// <summary>
|
||||
/// gametype/mode
|
||||
/// </summary>
|
||||
string GameType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// server name
|
||||
/// </summary>
|
||||
string Hostname { get; }
|
||||
|
||||
/// <summary>
|
||||
/// max number of players
|
||||
/// </summary>
|
||||
int? MaxClients { get; }
|
||||
|
||||
/// <summary>
|
||||
/// active clients
|
||||
/// </summary>
|
||||
EFClient[] Clients { get; }
|
||||
}
|
||||
}
|
@ -29,7 +29,8 @@ namespace SharedLibraryCore
|
||||
T5 = 6,
|
||||
T6 = 7,
|
||||
T7 = 8,
|
||||
SHG1 = 9
|
||||
SHG1 = 9,
|
||||
CSGO = 10
|
||||
}
|
||||
|
||||
public Server(ILogger<Server> logger, SharedLibraryCore.Interfaces.ILogger deprecatedLogger,
|
||||
@ -42,7 +43,6 @@ namespace SharedLibraryCore
|
||||
Manager = mgr;
|
||||
Logger = deprecatedLogger;
|
||||
ServerConfig = config;
|
||||
RemoteConnection = rconConnectionFactory.CreateConnection(IP, Port, Password);
|
||||
EventProcessing = new SemaphoreSlim(1, 1);
|
||||
Clients = new List<EFClient>(new EFClient[64]);
|
||||
Reports = new List<Report>();
|
||||
@ -52,6 +52,7 @@ namespace SharedLibraryCore
|
||||
CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName;
|
||||
CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName;
|
||||
this.gameLogReaderFactory = gameLogReaderFactory;
|
||||
RConConnectionFactory = rconConnectionFactory;
|
||||
ServerLogger = logger;
|
||||
InitializeTokens();
|
||||
InitializeAutoMessages();
|
||||
@ -158,24 +159,28 @@ namespace SharedLibraryCore
|
||||
/// Send a message to a particular players
|
||||
/// </summary>
|
||||
/// <param name="message">Message to send</param>
|
||||
/// <param name="target">EFClient to send message to</param>
|
||||
protected async Task Tell(string message, EFClient target)
|
||||
/// <param name="targetClient">EFClient to send message to</param>
|
||||
protected async Task Tell(string message, EFClient targetClient)
|
||||
{
|
||||
if (!Utilities.IsDevelopment)
|
||||
{
|
||||
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
||||
var parsedClientId = string.IsNullOrEmpty(temporalClientId) ? (int?)null : int.Parse(temporalClientId);
|
||||
var clientNumber = parsedClientId ?? targetClient.ClientNumber;
|
||||
|
||||
var formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Tell,
|
||||
target.ClientNumber,
|
||||
clientNumber,
|
||||
$"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{message.FixIW4ForwardSlash()}");
|
||||
if (target.ClientNumber > -1 && message.Length > 0 && target.Level != EFClient.Permission.Console)
|
||||
if (targetClient.ClientNumber > -1 && message.Length > 0 && targetClient.Level != EFClient.Permission.Console)
|
||||
await this.ExecuteCommandAsync(formattedMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerLogger.LogDebug("Tell[{clientNumber}]->{message}", target.ClientNumber, message.StripColors());
|
||||
ServerLogger.LogDebug("Tell[{clientNumber}]->{message}", targetClient.ClientNumber, message.StripColors());
|
||||
}
|
||||
|
||||
|
||||
if (target.Level == EFClient.Permission.Console)
|
||||
if (targetClient.Level == EFClient.Permission.Console)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
using (LogContext.PushProperty("Server", ToString()))
|
||||
@ -340,6 +345,7 @@ namespace SharedLibraryCore
|
||||
protected DateTime LastPoll;
|
||||
protected ManualResetEventSlim OnRemoteCommandResponse;
|
||||
protected IGameLogReaderFactory gameLogReaderFactory;
|
||||
protected IRConConnectionFactory RConConnectionFactory;
|
||||
|
||||
// only here for performance
|
||||
private readonly bool CustomSayEnabled;
|
||||
|
@ -44,7 +44,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.10" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.Data" Version="1.0.0" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.Data" Version="1.0.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
@ -322,6 +322,8 @@ namespace SharedLibraryCore
|
||||
/// <returns></returns>
|
||||
public static long ConvertGuidToLong(this string str, NumberStyles numberStyle, long? fallback = null)
|
||||
{
|
||||
// added for source games that provide the steam ID
|
||||
str = str.Replace("STEAM_1", "").Replace(":", "");
|
||||
str = str.Substring(0, Math.Min(str.Length, 19));
|
||||
var parsableAsNumber = Regex.Match(str, @"([A-F]|[a-f]|[0-9])+").Value;
|
||||
|
||||
@ -732,7 +734,7 @@ namespace SharedLibraryCore
|
||||
return await server.RconParser.ExecuteCommandAsync(server.RemoteConnection, commandName);
|
||||
}
|
||||
|
||||
public static Task<(List<EFClient>, string, string)> GetStatusAsync(this Server server)
|
||||
public static Task<IStatusResponse> GetStatusAsync(this Server server)
|
||||
{
|
||||
return server.RconParser.GetStatusAsync(server.RemoteConnection);
|
||||
}
|
||||
|
@ -56,7 +56,7 @@
|
||||
}
|
||||
|
||||
var weapons = Model.ByWeapon
|
||||
.Where(hit => hit.DamageInflicted > 0)
|
||||
.Where(hit => hit.DamageInflicted > 0 || (hit.DamageInflicted == 0 && hit.HitCount > 0))
|
||||
.GroupBy(hit => new {hit.WeaponId})
|
||||
.Select(group =>
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user