Compare commits
12 Commits
2022.02.23
...
2022.03.02
Author | SHA1 | Date | |
---|---|---|---|
e6e56d8d14 | |||
55b0caf900 | |||
a4c3f9c2d1 | |||
241aa0a5f6 | |||
ec0f59cdb1 | |||
59d69bd22b | |||
e9c8ead829 | |||
58d48a211e | |||
edf8e03b04 | |||
de2e804b84 | |||
b087d4c8de | |||
bd6c0dd5be |
@ -92,7 +92,7 @@ namespace IW4MAdmin.Application.Commands
|
|||||||
|
|
||||||
_logger.LogDebug("Changing map to {Map} and gametype {Gametype}", map, gametype);
|
_logger.LogDebug("Changing map to {Map} and gametype {Gametype}", map, gametype);
|
||||||
|
|
||||||
await gameEvent.Owner.SetDvarAsync("g_gametype", gametype);
|
await gameEvent.Owner.SetDvarAsync("g_gametype", gametype, gameEvent.Owner.Manager.CancellationToken);
|
||||||
gameEvent.Owner.Broadcast(_translationLookup["COMMANDS_MAP_SUCCESS"].FormatExt(map));
|
gameEvent.Owner.Broadcast(_translationLookup["COMMANDS_MAP_SUCCESS"].FormatExt(map));
|
||||||
await Task.Delay(gameEvent.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds);
|
await Task.Delay(gameEvent.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds);
|
||||||
|
|
||||||
|
@ -66,8 +66,8 @@ namespace IW4MAdmin.Application.IO
|
|||||||
}
|
}
|
||||||
return Task.FromResult(Enumerable.Empty<GameEvent>());
|
return Task.FromResult(Enumerable.Empty<GameEvent>());
|
||||||
}
|
}
|
||||||
|
|
||||||
new Thread(() => ReadNetworkData(client)).Start();
|
Task.Run(async () => await ReadNetworkData(client, _token), _token);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -111,9 +111,9 @@ namespace IW4MAdmin.Application.IO
|
|||||||
return Task.FromResult((IEnumerable<GameEvent>)events);
|
return Task.FromResult((IEnumerable<GameEvent>)events);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadNetworkData(UdpClient client)
|
private async Task ReadNetworkData(UdpClient client, CancellationToken token)
|
||||||
{
|
{
|
||||||
while (!_token.IsCancellationRequested)
|
while (!token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
// get more data
|
// get more data
|
||||||
IPEndPoint remoteEndpoint = null;
|
IPEndPoint remoteEndpoint = null;
|
||||||
@ -127,7 +127,13 @@ namespace IW4MAdmin.Application.IO
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bufferedData = client.Receive(ref remoteEndpoint);
|
var result = await client.ReceiveAsync(_token);
|
||||||
|
remoteEndpoint = result.RemoteEndPoint;
|
||||||
|
bufferedData = result.Buffer;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Stopping network log receive");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -321,7 +321,7 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(CustomSayName))
|
if (!string.IsNullOrEmpty(CustomSayName))
|
||||||
{
|
{
|
||||||
await this.SetDvarAsync("sv_sayname", CustomSayName);
|
await this.SetDvarAsync("sv_sayname", CustomSayName, Manager.CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
Throttled = false;
|
Throttled = false;
|
||||||
@ -783,7 +783,7 @@ namespace IW4MAdmin
|
|||||||
async Task<List<EFClient>[]> PollPlayersAsync()
|
async Task<List<EFClient>[]> PollPlayersAsync()
|
||||||
{
|
{
|
||||||
var currentClients = GetClientsAsList();
|
var currentClients = GetClientsAsList();
|
||||||
var statusResponse = (await this.GetStatusAsync());
|
var statusResponse = await this.GetStatusAsync(Manager.CancellationToken);
|
||||||
var polledClients = statusResponse.Clients.AsEnumerable();
|
var polledClients = statusResponse.Clients.AsEnumerable();
|
||||||
|
|
||||||
if (Manager.GetApplicationSettings().Configuration().IgnoreBots)
|
if (Manager.GetApplicationSettings().Configuration().IgnoreBots)
|
||||||
@ -1109,7 +1109,7 @@ namespace IW4MAdmin
|
|||||||
RemoteConnection = RConConnectionFactory.CreateConnection(ResolvedIpEndPoint, Password, RconParser.RConEngine);
|
RemoteConnection = RConConnectionFactory.CreateConnection(ResolvedIpEndPoint, Password, RconParser.RConEngine);
|
||||||
RemoteConnection.SetConfiguration(RconParser);
|
RemoteConnection.SetConfiguration(RconParser);
|
||||||
|
|
||||||
var version = await this.GetMappedDvarValueOrDefaultAsync<string>("version");
|
var version = await this.GetMappedDvarValueOrDefaultAsync<string>("version", token: Manager.CancellationToken);
|
||||||
Version = version.Value;
|
Version = version.Value;
|
||||||
GameName = Utilities.GetGame(version.Value ?? RconParser.Version);
|
GameName = Utilities.GetGame(version.Value ?? RconParser.Version);
|
||||||
|
|
||||||
@ -1126,7 +1126,7 @@ namespace IW4MAdmin
|
|||||||
Version = RconParser.Version;
|
Version = RconParser.Version;
|
||||||
}
|
}
|
||||||
|
|
||||||
var svRunning = await this.GetMappedDvarValueOrDefaultAsync<string>("sv_running");
|
var svRunning = await this.GetMappedDvarValueOrDefaultAsync<string>("sv_running", token: Manager.CancellationToken);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
|
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
|
||||||
{
|
{
|
||||||
@ -1135,27 +1135,28 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null;
|
var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null;
|
||||||
|
|
||||||
string hostname = (await this.GetMappedDvarValueOrDefaultAsync<string>("sv_hostname", "hostname", infoResponse)).Value;
|
var hostname = (await this.GetMappedDvarValueOrDefaultAsync<string>("sv_hostname", "hostname", infoResponse, token: Manager.CancellationToken)).Value;
|
||||||
string mapname = (await this.GetMappedDvarValueOrDefaultAsync<string>("mapname", infoResponse: infoResponse)).Value;
|
var mapname = (await this.GetMappedDvarValueOrDefaultAsync<string>("mapname", infoResponse: infoResponse, token: Manager.CancellationToken)).Value;
|
||||||
int maxplayers = (await this.GetMappedDvarValueOrDefaultAsync<int>("sv_maxclients", infoResponse: infoResponse)).Value;
|
var maxplayers = (await this.GetMappedDvarValueOrDefaultAsync<int>("sv_maxclients", infoResponse: infoResponse, token: Manager.CancellationToken)).Value;
|
||||||
string gametype = (await this.GetMappedDvarValueOrDefaultAsync<string>("g_gametype", "gametype", infoResponse)).Value;
|
var gametype = (await this.GetMappedDvarValueOrDefaultAsync<string>("g_gametype", "gametype", infoResponse, token: Manager.CancellationToken)).Value;
|
||||||
var basepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basepath");
|
var basepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basepath", token: Manager.CancellationToken);
|
||||||
var basegame = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basegame");
|
var basegame = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basegame", token: Manager.CancellationToken);
|
||||||
var homepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_homepath");
|
var homepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_homepath", token: Manager.CancellationToken);
|
||||||
var game = (await this.GetMappedDvarValueOrDefaultAsync<string>("fs_game", infoResponse: infoResponse));
|
var game = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_game", infoResponse: infoResponse, token: Manager.CancellationToken);
|
||||||
var logfile = await this.GetMappedDvarValueOrDefaultAsync<string>("g_log");
|
var logfile = await this.GetMappedDvarValueOrDefaultAsync<string>("g_log", token: Manager.CancellationToken);
|
||||||
var logsync = await this.GetMappedDvarValueOrDefaultAsync<int>("g_logsync");
|
var logsync = await this.GetMappedDvarValueOrDefaultAsync<int>("g_logsync", token: Manager.CancellationToken);
|
||||||
var ip = await this.GetMappedDvarValueOrDefaultAsync<string>("net_ip");
|
var ip = await this.GetMappedDvarValueOrDefaultAsync<string>("net_ip", token: Manager.CancellationToken);
|
||||||
var gamePassword = await this.GetMappedDvarValueOrDefaultAsync("g_password", overrideDefault: "");
|
var gamePassword = await this.GetMappedDvarValueOrDefaultAsync("g_password", overrideDefault: "", token: Manager.CancellationToken);
|
||||||
|
|
||||||
if (Manager.GetApplicationSettings().Configuration().EnableCustomSayName)
|
if (Manager.GetApplicationSettings().Configuration().EnableCustomSayName)
|
||||||
{
|
{
|
||||||
await this.SetDvarAsync("sv_sayname", Manager.GetApplicationSettings().Configuration().CustomSayName);
|
await this.SetDvarAsync("sv_sayname", Manager.GetApplicationSettings().Configuration().CustomSayName,
|
||||||
|
Manager.CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var website = await this.GetMappedDvarValueOrDefaultAsync<string>("_website");
|
var website = await this.GetMappedDvarValueOrDefaultAsync<string>("_website", token: Manager.CancellationToken);
|
||||||
|
|
||||||
// this occurs for games that don't give us anything back when
|
// this occurs for games that don't give us anything back when
|
||||||
// the dvar is not set
|
// the dvar is not set
|
||||||
@ -1201,14 +1202,14 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
if (logsync.Value == 0)
|
if (logsync.Value == 0)
|
||||||
{
|
{
|
||||||
await this.SetDvarAsync("g_logsync", 2); // set to 2 for continous in other games, clamps to 1 for IW4
|
await this.SetDvarAsync("g_logsync", 2, Manager.CancellationToken); // set to 2 for continous in other games, clamps to 1 for IW4
|
||||||
needsRestart = true;
|
needsRestart = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(logfile.Value))
|
if (string.IsNullOrWhiteSpace(logfile.Value))
|
||||||
{
|
{
|
||||||
logfile.Value = "games_mp.log";
|
logfile.Value = "games_mp.log";
|
||||||
await this.SetDvarAsync("g_log", logfile.Value);
|
await this.SetDvarAsync("g_log", logfile.Value, Manager.CancellationToken);
|
||||||
needsRestart = true;
|
needsRestart = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1220,7 +1221,7 @@ namespace IW4MAdmin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this DVAR isn't set until the a map is loaded
|
// this DVAR isn't set until the a map is loaded
|
||||||
await this.SetDvarAsync("logfile", 2);
|
await this.SetDvarAsync("logfile", 2, Manager.CancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomCallback = await ScriptLoaded();
|
CustomCallback = await ScriptLoaded();
|
||||||
|
@ -153,6 +153,8 @@ namespace IW4MAdmin.Application
|
|||||||
{
|
{
|
||||||
Console.WriteLine(e.Message);
|
Console.WriteLine(e.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_serverManager?.Stop();
|
||||||
|
|
||||||
Console.WriteLine(exitMessage);
|
Console.WriteLine(exitMessage);
|
||||||
await Console.In.ReadAsync(new char[1], 0, 1);
|
await Console.In.ReadAsync(new char[1], 0, 1);
|
||||||
|
@ -88,7 +88,7 @@ namespace IW4MAdmin.Application.Migration
|
|||||||
|
|
||||||
public static void RemoveObsoletePlugins20210322()
|
public static void RemoveObsoletePlugins20210322()
|
||||||
{
|
{
|
||||||
var files = new[] {"StatsWeb.dll", "StatsWeb.Views.dll"};
|
var files = new[] {"StatsWeb.dll", "StatsWeb.Views.dll", "IW4ScriptCommands.dll"};
|
||||||
|
|
||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
{
|
||||||
|
@ -27,6 +27,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
_serializerOptions = new JsonSerializerOptions
|
_serializerOptions = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
};
|
};
|
||||||
_serializerOptions.Converters.Add(new JsonStringEnumConverter());
|
_serializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||||
_onSaving = new SemaphoreSlim(1, 1);
|
_onSaving = new SemaphoreSlim(1, 1);
|
||||||
|
@ -7,6 +7,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Models;
|
using Data.Models;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -77,19 +78,19 @@ namespace IW4MAdmin.Application.RConParsers
|
|||||||
public string RConEngine { get; set; } = "COD";
|
public string RConEngine { get; set; } = "COD";
|
||||||
public bool IsOneLog { get; set; }
|
public bool IsOneLog { get; set; }
|
||||||
|
|
||||||
public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command)
|
public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
|
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, token);
|
||||||
return response.Where(item => item != Configuration.CommandPrefixes.RConResponse).ToArray();
|
return response.Where(item => item != Configuration.CommandPrefixes.RConResponse).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default)
|
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
string[] lineSplit;
|
string[] lineSplit;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
|
lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName, token);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -98,10 +99,10 @@ namespace IW4MAdmin.Application.RConParsers
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
lineSplit = new string[0];
|
lineSplit = Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
string response = string.Join('\n', lineSplit).TrimEnd('\0');
|
var response = string.Join('\n', lineSplit).TrimEnd('\0');
|
||||||
var match = Regex.Match(response, Configuration.Dvar.Pattern);
|
var match = Regex.Match(response, Configuration.Dvar.Pattern);
|
||||||
|
|
||||||
if (response.Contains("Unknown command") ||
|
if (response.Contains("Unknown command") ||
|
||||||
@ -109,7 +110,7 @@ namespace IW4MAdmin.Application.RConParsers
|
|||||||
{
|
{
|
||||||
if (fallbackValue != null)
|
if (fallbackValue != null)
|
||||||
{
|
{
|
||||||
return new Dvar<T>()
|
return new Dvar<T>
|
||||||
{
|
{
|
||||||
Name = dvarName,
|
Name = dvarName,
|
||||||
Value = fallbackValue
|
Value = fallbackValue
|
||||||
@ -119,17 +120,17 @@ namespace IW4MAdmin.Application.RConParsers
|
|||||||
throw new DvarException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"].FormatExt(dvarName));
|
throw new DvarException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"].FormatExt(dvarName));
|
||||||
}
|
}
|
||||||
|
|
||||||
string value = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarValue]].Value;
|
var value = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarValue]].Value;
|
||||||
string defaultValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDefaultValue]].Value;
|
var defaultValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDefaultValue]].Value;
|
||||||
string latchedValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarLatchedValue]].Value;
|
var latchedValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarLatchedValue]].Value;
|
||||||
|
|
||||||
string removeTrailingColorCode(string input) => Regex.Replace(input, @"\^7$", "");
|
string RemoveTrailingColorCode(string input) => Regex.Replace(input, @"\^7$", "");
|
||||||
|
|
||||||
value = removeTrailingColorCode(value);
|
value = RemoveTrailingColorCode(value);
|
||||||
defaultValue = removeTrailingColorCode(defaultValue);
|
defaultValue = RemoveTrailingColorCode(defaultValue);
|
||||||
latchedValue = removeTrailingColorCode(latchedValue);
|
latchedValue = RemoveTrailingColorCode(latchedValue);
|
||||||
|
|
||||||
return new Dvar<T>()
|
return new Dvar<T>
|
||||||
{
|
{
|
||||||
Name = dvarName,
|
Name = dvarName,
|
||||||
Value = string.IsNullOrEmpty(value) ? default : (T)Convert.ChangeType(value, typeof(T)),
|
Value = string.IsNullOrEmpty(value) ? default : (T)Convert.ChangeType(value, typeof(T)),
|
||||||
@ -139,10 +140,12 @@ namespace IW4MAdmin.Application.RConParsers
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection)
|
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS);
|
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS, "status", token);
|
||||||
_logger.LogDebug("Status Response {response}", string.Join(Environment.NewLine, response));
|
|
||||||
|
_logger.LogDebug("Status Response {Response}", string.Join(Environment.NewLine, response));
|
||||||
|
|
||||||
return new StatusResponse
|
return new StatusResponse
|
||||||
{
|
{
|
||||||
Clients = ClientsFromStatus(response).ToArray(),
|
Clients = ClientsFromStatus(response).ToArray(),
|
||||||
@ -183,13 +186,13 @@ namespace IW4MAdmin.Application.RConParsers
|
|||||||
return (T)Convert.ChangeType(value, typeof(T));
|
return (T)Convert.ChangeType(value, typeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue)
|
public async Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
string dvarString = (dvarValue is string str)
|
var dvarString = (dvarValue is string str)
|
||||||
? $"{dvarName} \"{str}\""
|
? $"{dvarName} \"{str}\""
|
||||||
: $"{dvarName} {dvarValue}";
|
: $"{dvarName} {dvarValue}";
|
||||||
|
|
||||||
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString)).Length > 0;
|
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString, token)).Length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<EFClient> ClientsFromStatus(string[] Status)
|
private List<EFClient> ClientsFromStatus(string[] Status)
|
||||||
|
@ -66,7 +66,12 @@ OnPlayerConnect()
|
|||||||
|
|
||||||
player thread OnPlayerSpawned();
|
player thread OnPlayerSpawned();
|
||||||
player thread PlayerTrackingOnInterval();
|
player thread PlayerTrackingOnInterval();
|
||||||
player ToggleNightMode();
|
|
||||||
|
// only toggle if it's enabled
|
||||||
|
if ( IsDefined( level.nightModeEnabled ) && level.nightModeEnabled )
|
||||||
|
{
|
||||||
|
player ToggleNightMode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -496,7 +501,7 @@ OnExecuteCommand( event )
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
response = self GotoImpl( event.data );
|
response = self GotoImpl( data );
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Kill":
|
case "Kill":
|
||||||
@ -505,6 +510,9 @@ OnExecuteCommand( event )
|
|||||||
case "NightMode":
|
case "NightMode":
|
||||||
NightModeImpl();
|
NightModeImpl();
|
||||||
break;
|
break;
|
||||||
|
case "SetSpectator":
|
||||||
|
response = event.target SetSpectatorImpl();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// send back the response to the origin, but only if they're not the target
|
// send back the response to the origin, but only if they're not the target
|
||||||
@ -590,8 +598,8 @@ HideImpl()
|
|||||||
}
|
}
|
||||||
|
|
||||||
self SetClientDvar( "sv_cheats", 1 );
|
self SetClientDvar( "sv_cheats", 1 );
|
||||||
self SetClientDvar( "cg_thirdperson", 1 );
|
self SetClientDvar( "cg_thirdperson", 1 );
|
||||||
self SetClientDvar( "sv_cheats", 0 );
|
self SetClientDvar( "sv_cheats", 0 );
|
||||||
|
|
||||||
self.savedHealth = self.health;
|
self.savedHealth = self.health;
|
||||||
self.health = 9999;
|
self.health = 9999;
|
||||||
@ -610,9 +618,9 @@ UnhideImpl()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self SetClientDvar( "sv_cheats", 1 );
|
self SetClientDvar( "sv_cheats", 1 );
|
||||||
self SetClientDvar( "cg_thirdperson", 0 );
|
self SetClientDvar( "cg_thirdperson", 0 );
|
||||||
self SetClientDvar( "sv_cheats", 0 );
|
self SetClientDvar( "sv_cheats", 0 );
|
||||||
|
|
||||||
self.health = self.savedHealth;
|
self.health = self.savedHealth;
|
||||||
self.isHidden = false;
|
self.isHidden = false;
|
||||||
@ -707,3 +715,16 @@ ToggleNightMode()
|
|||||||
self SetClientDvar( "fx_draw", fxDraw );
|
self SetClientDvar( "fx_draw", fxDraw );
|
||||||
self SetClientDvar( "sv_cheats", 0 );
|
self SetClientDvar( "sv_cheats", 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetSpectatorImpl()
|
||||||
|
{
|
||||||
|
if ( self.pers["team"] == "spectator" )
|
||||||
|
{
|
||||||
|
return self.name + " is already spectating";
|
||||||
|
}
|
||||||
|
|
||||||
|
self [[level.spectator]]();
|
||||||
|
self IPrintLnBold( "You have been moved to spectator" );
|
||||||
|
|
||||||
|
return self.name + " has been moved to spectator";
|
||||||
|
}
|
||||||
|
@ -23,7 +23,7 @@ namespace Integrations.Cod
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CodRConConnection : IRConConnection
|
public class CodRConConnection : IRConConnection
|
||||||
{
|
{
|
||||||
static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new ConcurrentDictionary<EndPoint, ConnectionState>();
|
private static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new();
|
||||||
public IPEndPoint Endpoint { get; }
|
public IPEndPoint Endpoint { get; }
|
||||||
public string RConPassword { get; }
|
public string RConPassword { get; }
|
||||||
|
|
||||||
@ -48,7 +48,40 @@ namespace Integrations.Cod
|
|||||||
config = parser.Configuration;
|
config = parser.Configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "")
|
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "",
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await SendQueryAsyncInternal(type, parameters, token);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_log.LogWarning("Timed out waiting for RCon response");
|
||||||
|
throw new RConException("Did not received RCon response in allocated time frame");
|
||||||
|
}
|
||||||
|
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
var state = ActiveQueries[Endpoint];
|
||||||
|
if (state.OnComplete.CurrentCount == 0)
|
||||||
|
{
|
||||||
|
state.OnComplete.Release(1);
|
||||||
|
state.ConnectionAttempts = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.OnReceivedData.CurrentCount == 0)
|
||||||
|
{
|
||||||
|
state.OnReceivedData.Release(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.OnSentData.CurrentCount == 0)
|
||||||
|
{
|
||||||
|
state.OnSentData.Release(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private async Task<string[]> SendQueryAsyncInternal(StaticHelpers.QueryType type, string parameters = "", CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (!ActiveQueries.ContainsKey(this.Endpoint))
|
if (!ActiveQueries.ContainsKey(this.Endpoint))
|
||||||
{
|
{
|
||||||
@ -57,36 +90,36 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
var connectionState = ActiveQueries[this.Endpoint];
|
var connectionState = ActiveQueries[this.Endpoint];
|
||||||
|
|
||||||
_log.LogDebug("Waiting for semaphore to be released [{endpoint}]", Endpoint);
|
_log.LogDebug("Waiting for semaphore to be released [{Endpoint}]", Endpoint);
|
||||||
|
|
||||||
// enter the semaphore so only one query is sent at a time per server.
|
// enter the semaphore so only one query is sent at a time per server.
|
||||||
await connectionState.OnComplete.WaitAsync();
|
await connectionState.OnComplete.WaitAsync(token);
|
||||||
|
|
||||||
var timeSinceLastQuery = (DateTime.Now - connectionState.LastQuery).TotalMilliseconds;
|
var timeSinceLastQuery = (DateTime.Now - connectionState.LastQuery).TotalMilliseconds;
|
||||||
|
|
||||||
if (timeSinceLastQuery < config.FloodProtectInterval)
|
if (timeSinceLastQuery < config.FloodProtectInterval)
|
||||||
{
|
{
|
||||||
await Task.Delay(config.FloodProtectInterval - (int)timeSinceLastQuery);
|
await Task.Delay(config.FloodProtectInterval - (int)timeSinceLastQuery, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionState.LastQuery = DateTime.Now;
|
connectionState.LastQuery = DateTime.Now;
|
||||||
|
|
||||||
_log.LogDebug("Semaphore has been released [{endpoint}]", Endpoint);
|
_log.LogDebug("Semaphore has been released [{Endpoint}]", Endpoint);
|
||||||
_log.LogDebug("Query {@queryInfo}", new { endpoint=Endpoint.ToString(), type, parameters });
|
_log.LogDebug("Query {@QueryInfo}", new { endpoint=Endpoint.ToString(), type, parameters });
|
||||||
|
|
||||||
byte[] payload = null;
|
byte[] payload = null;
|
||||||
bool waitForResponse = config.WaitForResponse;
|
var waitForResponse = config.WaitForResponse;
|
||||||
|
|
||||||
string convertEncoding(string text)
|
string ConvertEncoding(string text)
|
||||||
{
|
{
|
||||||
byte[] convertedBytes = Utilities.EncodingType.GetBytes(text);
|
var convertedBytes = Utilities.EncodingType.GetBytes(text);
|
||||||
return _gameEncoding.GetString(convertedBytes);
|
return _gameEncoding.GetString(convertedBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string convertedRConPassword = convertEncoding(RConPassword);
|
var convertedRConPassword = ConvertEncoding(RConPassword);
|
||||||
string convertedParameters = convertEncoding(parameters);
|
var convertedParameters = ConvertEncoding(parameters);
|
||||||
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
@ -137,7 +170,7 @@ namespace Integrations.Cod
|
|||||||
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
{
|
{
|
||||||
_log.LogInformation(
|
_log.LogInformation(
|
||||||
"Retrying RCon message ({connectionAttempts}/{allowedConnectionFailures} attempts) with parameters {payload}",
|
"Retrying RCon message ({ConnectionAttempts}/{AllowedConnectionFailures} attempts) with parameters {payload}",
|
||||||
connectionState.ConnectionAttempts,
|
connectionState.ConnectionAttempts,
|
||||||
_retryAttempts, parameters);
|
_retryAttempts, parameters);
|
||||||
}
|
}
|
||||||
@ -151,20 +184,27 @@ namespace Integrations.Cod
|
|||||||
{
|
{
|
||||||
connectionState.SendEventArgs.UserToken = socket;
|
connectionState.SendEventArgs.UserToken = socket;
|
||||||
connectionState.ConnectionAttempts++;
|
connectionState.ConnectionAttempts++;
|
||||||
await connectionState.OnSentData.WaitAsync();
|
await connectionState.OnSentData.WaitAsync(token);
|
||||||
await connectionState.OnReceivedData.WaitAsync();
|
await connectionState.OnReceivedData.WaitAsync(token);
|
||||||
connectionState.BytesReadPerSegment.Clear();
|
connectionState.BytesReadPerSegment.Clear();
|
||||||
bool exceptionCaught = false;
|
var exceptionCaught = false;
|
||||||
|
|
||||||
_log.LogDebug("Sending {payloadLength} bytes to [{endpoint}] ({connectionAttempts}/{allowedConnectionFailures})",
|
_log.LogDebug("Sending {PayloadLength} bytes to [{Endpoint}] ({ConnectionAttempts}/{AllowedConnectionFailures})",
|
||||||
payload.Length, Endpoint, connectionState.ConnectionAttempts, _retryAttempts);
|
payload.Length, Endpoint, connectionState.ConnectionAttempts, _retryAttempts);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
try
|
||||||
response = await SendPayloadAsync(payload, waitForResponse, parser.OverrideTimeoutForCommand(parameters));
|
{
|
||||||
|
response = await SendPayloadAsync(payload, waitForResponse,
|
||||||
if ((response.Length == 0 || response[0].Length == 0) && waitForResponse)
|
parser.OverrideTimeoutForCommand(parameters), token);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((response?.Length == 0 || response[0].Length == 0) && waitForResponse)
|
||||||
{
|
{
|
||||||
throw new RConException("Expected response but got 0 bytes back");
|
throw new RConException("Expected response but got 0 bytes back");
|
||||||
}
|
}
|
||||||
@ -178,14 +218,14 @@ namespace Integrations.Cod
|
|||||||
if (connectionState.ConnectionAttempts < _retryAttempts)
|
if (connectionState.ConnectionAttempts < _retryAttempts)
|
||||||
{
|
{
|
||||||
exceptionCaught = true;
|
exceptionCaught = true;
|
||||||
await Task.Delay(StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts));
|
await Task.Delay(StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts), token);
|
||||||
goto retrySend;
|
goto retrySend;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
{
|
{
|
||||||
_log.LogWarning(
|
_log.LogWarning(
|
||||||
"Made {connectionAttempts} attempts to send RCon data to server, but received no response",
|
"Made {ConnectionAttempts} attempts to send RCon data to server, but received no response",
|
||||||
connectionState.ConnectionAttempts);
|
connectionState.ConnectionAttempts);
|
||||||
}
|
}
|
||||||
connectionState.ConnectionAttempts = 0;
|
connectionState.ConnectionAttempts = 0;
|
||||||
@ -214,14 +254,15 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
if (response.Length == 0)
|
if (response.Length == 0)
|
||||||
{
|
{
|
||||||
_log.LogDebug("Received empty response for RCon request {@query}", new { endpoint=Endpoint.ToString(), type, parameters });
|
_log.LogDebug("Received empty response for RCon request {@Query}",
|
||||||
return new string[0];
|
new { endpoint = Endpoint.ToString(), type, parameters });
|
||||||
|
return Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
string responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
|
var responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
|
||||||
ReassembleSegmentedStatus(response) : RecombineMessages(response);
|
ReassembleSegmentedStatus(response) : RecombineMessages(response);
|
||||||
|
|
||||||
// note: not all games respond if the pasword is wrong or not set
|
// note: not all games respond if the password is wrong or not set
|
||||||
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
|
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
|
||||||
{
|
{
|
||||||
throw new RConException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]);
|
throw new RConException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]);
|
||||||
@ -237,21 +278,21 @@ namespace Integrations.Cod
|
|||||||
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString()));
|
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
string responseHeaderMatch = Regex.Match(responseString, config.CommandPrefixes.RConResponse).Value;
|
var responseHeaderMatch = Regex.Match(responseString, config.CommandPrefixes.RConResponse).Value;
|
||||||
string[] headerSplit = responseString.Split(type == StaticHelpers.QueryType.GET_INFO ? config.CommandPrefixes.RconGetInfoResponseHeader : responseHeaderMatch);
|
var headerSplit = responseString.Split(type == StaticHelpers.QueryType.GET_INFO ? config.CommandPrefixes.RconGetInfoResponseHeader : responseHeaderMatch);
|
||||||
|
|
||||||
if (headerSplit.Length != 2)
|
if (headerSplit.Length != 2)
|
||||||
{
|
{
|
||||||
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
{
|
{
|
||||||
_log.LogWarning("Invalid response header from server. Expected {expected}, but got {response}",
|
_log.LogWarning("Invalid response header from server. Expected {Expected}, but got {Response}",
|
||||||
config.CommandPrefixes.RConResponse, headerSplit.FirstOrDefault());
|
config.CommandPrefixes.RConResponse, headerSplit.FirstOrDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new RConException("Unexpected response header from server");
|
throw new RConException("Unexpected response header from server");
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] splitResponse = headerSplit.Last().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
var splitResponse = headerSplit.Last().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
return splitResponse;
|
return splitResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,7 +353,7 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<byte[][]> SendPayloadAsync(byte[] payload, bool waitForResponse, TimeSpan overrideTimeout)
|
private async Task<byte[][]> SendPayloadAsync(byte[] payload, bool waitForResponse, TimeSpan overrideTimeout, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var connectionState = ActiveQueries[this.Endpoint];
|
var connectionState = ActiveQueries[this.Endpoint];
|
||||||
var rconSocket = (Socket)connectionState.SendEventArgs.UserToken;
|
var rconSocket = (Socket)connectionState.SendEventArgs.UserToken;
|
||||||
@ -332,18 +373,27 @@ namespace Integrations.Cod
|
|||||||
connectionState.SendEventArgs.SetBuffer(payload);
|
connectionState.SendEventArgs.SetBuffer(payload);
|
||||||
|
|
||||||
// send the data to the server
|
// send the data to the server
|
||||||
bool sendDataPending = rconSocket.SendToAsync(connectionState.SendEventArgs);
|
var sendDataPending = rconSocket.SendToAsync(connectionState.SendEventArgs);
|
||||||
|
|
||||||
if (sendDataPending)
|
if (sendDataPending)
|
||||||
{
|
{
|
||||||
// the send has not been completed asynchronously
|
// the send has not been completed asynchronously
|
||||||
// this really shouldn't ever happen because it's UDP
|
// this really shouldn't ever happen because it's UDP
|
||||||
|
var complete = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
complete = await connectionState.OnSentData.WaitAsync(StaticHelpers.SocketTimeout(1), token);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
if(!await connectionState.OnSentData.WaitAsync(StaticHelpers.SocketTimeout(1)))
|
if(!complete)
|
||||||
{
|
{
|
||||||
using(LogContext.PushProperty("Server", Endpoint.ToString()))
|
using(LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
{
|
{
|
||||||
_log.LogWarning("Socket timed out while sending RCon data on attempt {attempt}",
|
_log.LogWarning("Socket timed out while sending RCon data on attempt {Attempt}",
|
||||||
connectionState.ConnectionAttempts);
|
connectionState.ConnectionAttempts);
|
||||||
}
|
}
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
@ -359,17 +409,29 @@ namespace Integrations.Cod
|
|||||||
connectionState.ReceiveEventArgs.SetBuffer(connectionState.ReceiveBuffer);
|
connectionState.ReceiveEventArgs.SetBuffer(connectionState.ReceiveBuffer);
|
||||||
|
|
||||||
// get our response back
|
// get our response back
|
||||||
bool receiveDataPending = rconSocket.ReceiveFromAsync(connectionState.ReceiveEventArgs);
|
var receiveDataPending = rconSocket.ReceiveFromAsync(connectionState.ReceiveEventArgs);
|
||||||
|
|
||||||
if (receiveDataPending)
|
if (receiveDataPending)
|
||||||
{
|
{
|
||||||
_log.LogDebug("Waiting to asynchronously receive data on attempt #{connectionAttempts}", connectionState.ConnectionAttempts);
|
_log.LogDebug("Waiting to asynchronously receive data on attempt #{ConnectionAttempts}", connectionState.ConnectionAttempts);
|
||||||
if (!await connectionState.OnReceivedData.WaitAsync(
|
|
||||||
new[]
|
var completed = false;
|
||||||
{
|
|
||||||
StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts),
|
try
|
||||||
overrideTimeout
|
{
|
||||||
}.Max()))
|
completed = await connectionState.OnReceivedData.WaitAsync(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
StaticHelpers.SocketTimeout(connectionState.ConnectionAttempts),
|
||||||
|
overrideTimeout
|
||||||
|
}.Max(), token);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!completed)
|
||||||
{
|
{
|
||||||
if (connectionState.ConnectionAttempts > 1) // this reduces some spam for unstable connections
|
if (connectionState.ConnectionAttempts > 1) // this reduces some spam for unstable connections
|
||||||
{
|
{
|
||||||
@ -388,16 +450,15 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
|
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
|
|
||||||
return GetResponseData(connectionState);
|
return GetResponseData(connectionState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[][] GetResponseData(ConnectionState connectionState)
|
private byte[][] GetResponseData(ConnectionState connectionState)
|
||||||
{
|
{
|
||||||
var responseList = new List<byte[]>();
|
var responseList = new List<byte[]>();
|
||||||
int totalBytesRead = 0;
|
var totalBytesRead = 0;
|
||||||
|
|
||||||
foreach (int bytesRead in connectionState.BytesReadPerSegment)
|
foreach (var bytesRead in connectionState.BytesReadPerSegment)
|
||||||
{
|
{
|
||||||
responseList.Add(connectionState.ReceiveBuffer
|
responseList.Add(connectionState.ReceiveBuffer
|
||||||
.Skip(totalBytesRead)
|
.Skip(totalBytesRead)
|
||||||
|
@ -48,12 +48,12 @@ namespace Integrations.Source
|
|||||||
_activeQuery.Dispose();
|
_activeQuery.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "")
|
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "", CancellationToken token = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _activeQuery.WaitAsync();
|
await _activeQuery.WaitAsync(token);
|
||||||
await WaitForAvailable();
|
await WaitForAvailable(token);
|
||||||
|
|
||||||
if (_needNewSocket)
|
if (_needNewSocket)
|
||||||
{
|
{
|
||||||
@ -66,7 +66,7 @@ namespace Integrations.Source
|
|||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(ConnectionTimeout);
|
await Task.Delay(ConnectionTimeout, token);
|
||||||
_rconClient = _rconClientFactory.CreateClient(_ipEndPoint);
|
_rconClient = _rconClientFactory.CreateClient(_ipEndPoint);
|
||||||
_authenticated = false;
|
_authenticated = false;
|
||||||
_needNewSocket = false;
|
_needNewSocket = false;
|
||||||
@ -147,12 +147,12 @@ namespace Integrations.Source
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task WaitForAvailable()
|
private async Task WaitForAvailable(CancellationToken token)
|
||||||
{
|
{
|
||||||
var diff = DateTime.Now - _lastQuery;
|
var diff = DateTime.Now - _lastQuery;
|
||||||
if (diff < FloodDelay)
|
if (diff < FloodDelay)
|
||||||
{
|
{
|
||||||
await Task.Delay(FloodDelay - diff);
|
await Task.Delay(FloodDelay - diff, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.2.22.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.2.28.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.2.22.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.2.28.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.2.22.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.2.28.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.2.22.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.2.28.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -271,6 +271,24 @@ let commands = [{
|
|||||||
}
|
}
|
||||||
sendScriptCommand(gameEvent.Owner, 'NightMode', gameEvent.Origin, undefined, undefined);
|
sendScriptCommand(gameEvent.Owner, 'NightMode', gameEvent.Origin, undefined, undefined);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'setspectator',
|
||||||
|
description: 'sets a player as spectator',
|
||||||
|
alias: 'spec',
|
||||||
|
permission: 'Administrator',
|
||||||
|
targetRequired: true,
|
||||||
|
arguments: [{
|
||||||
|
name: 'player',
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
supportedGames: ['IW4'],
|
||||||
|
execute: (gameEvent) => {
|
||||||
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendScriptCommand(gameEvent.Owner, 'SetSpectator', gameEvent.Origin, gameEvent.Target, undefined);
|
||||||
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
const sendScriptCommand = (server, command, origin, target, data) => {
|
const sendScriptCommand = (server, command, origin, target, data) => {
|
||||||
@ -289,6 +307,11 @@ const sendEvent = (server, responseExpected, event, subtype, origin, target, dat
|
|||||||
const start = new Date();
|
const start = new Date();
|
||||||
|
|
||||||
while (pendingOut && pendingCheckCount <= 10) {
|
while (pendingOut && pendingCheckCount <= 10) {
|
||||||
|
if (server.Throttled) {
|
||||||
|
logger.WriteWarning('Server is throttled, so we are not attempting to send data');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const out = server.GetServerDvar(outDvar);
|
const out = server.GetServerDvar(outDvar);
|
||||||
pendingOut = !(out == null || out === '' || out === 'null');
|
pendingOut = !(out == null || out === '' || out === 'null');
|
||||||
@ -300,6 +323,7 @@ const sendEvent = (server, responseExpected, event, subtype, origin, target, dat
|
|||||||
logger.WriteDebug('Waiting for event bus to be cleared');
|
logger.WriteDebug('Waiting for event bus to be cleared');
|
||||||
System.Threading.Tasks.Task.Delay(1000).Wait();
|
System.Threading.Tasks.Task.Delay(1000).Wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingCheckCount++;
|
pendingCheckCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,6 +403,10 @@ const initialize = (server) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const pollForEvents = server => {
|
const pollForEvents = server => {
|
||||||
|
if (server.Throttled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const logger = _serviceResolver.ResolveService('ILogger');
|
const logger = _serviceResolver.ResolveService('ILogger');
|
||||||
|
|
||||||
let input;
|
let input;
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.2.22.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.2.28.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.2.22.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.2.28.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1190,7 +1190,7 @@ namespace SharedLibraryCore.Commands
|
|||||||
|
|
||||||
public static async Task<string> GetNextMap(Server s, ITranslationLookup lookup)
|
public static async Task<string> GetNextMap(Server s, ITranslationLookup lookup)
|
||||||
{
|
{
|
||||||
var mapRotation = (await s.GetDvarAsync<string>("sv_mapRotation")).Value?.ToLower() ?? "";
|
var mapRotation = (await s.GetDvarAsync<string>("sv_mapRotation", token: s.Manager.CancellationToken)).Value?.ToLower() ?? "";
|
||||||
var regexMatches = Regex.Matches(mapRotation,
|
var regexMatches = Regex.Matches(mapRotation,
|
||||||
@"((?:gametype|exec) +(?:([a-z]{1,4})(?:.cfg)?))? *map ([a-z|_|\d]+)", RegexOptions.IgnoreCase)
|
@"((?:gametype|exec) +(?:([a-z]{1,4})(?:.cfg)?))? *map ([a-z|_|\d]+)", RegexOptions.IgnoreCase)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
@ -20,6 +20,7 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
|
|
||||||
public enum MetaType
|
public enum MetaType
|
||||||
{
|
{
|
||||||
|
All = -1,
|
||||||
Other,
|
Other,
|
||||||
Information,
|
Information,
|
||||||
AliasUpdate,
|
AliasUpdate,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using SharedLibraryCore.RCon;
|
using SharedLibraryCore.RCon;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Interfaces
|
namespace SharedLibraryCore.Interfaces
|
||||||
@ -14,7 +15,7 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="type">type of RCon query to perform</param>
|
/// <param name="type">type of RCon query to perform</param>
|
||||||
/// <param name="parameters">optional parameter list</param>
|
/// <param name="parameters">optional parameter list</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "");
|
Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "", CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// sets the rcon parser
|
/// sets the rcon parser
|
||||||
@ -22,4 +23,4 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="config">parser</param>
|
/// <param name="config">parser</param>
|
||||||
void SetConfiguration(IRConParser config);
|
void SetConfiguration(IRConParser config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using static SharedLibraryCore.Server;
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="dvarName">name of DVAR</param>
|
/// <param name="dvarName">name of DVAR</param>
|
||||||
/// <param name="fallbackValue">default value to return if dvar retrieval fails</param>
|
/// <param name="fallbackValue">default value to return if dvar retrieval fails</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default);
|
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// set value of DVAR by name
|
/// set value of DVAR by name
|
||||||
@ -61,7 +62,7 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="dvarName">name of DVAR to set</param>
|
/// <param name="dvarName">name of DVAR to set</param>
|
||||||
/// <param name="dvarValue">value to set DVAR to</param>
|
/// <param name="dvarValue">value to set DVAR to</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue);
|
Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// executes a console command on the server
|
/// executes a console command on the server
|
||||||
@ -69,8 +70,8 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="connection">RCon connection to use</param>
|
/// <param name="connection">RCon connection to use</param>
|
||||||
/// <param name="command">console command to execute</param>
|
/// <param name="command">console command to execute</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command);
|
Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// get the list of connected clients from status response
|
/// get the list of connected clients from status response
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -78,7 +79,7 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <returns>
|
/// <returns>
|
||||||
/// <see cref="IStatusResponse" />
|
/// <see cref="IStatusResponse" />
|
||||||
/// </returns>
|
/// </returns>
|
||||||
Task<IStatusResponse> GetStatusAsync(IRConConnection connection);
|
Task<IStatusResponse> GetStatusAsync(IRConConnection connection, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// retrieves the value of given dvar key if it exists in the override dict
|
/// retrieves the value of given dvar key if it exists in the override dict
|
||||||
@ -103,4 +104,4 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
TimeSpan OverrideTimeoutForCommand(string command);
|
TimeSpan OverrideTimeoutForCommand(string command);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -377,7 +377,7 @@ namespace SharedLibraryCore
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (await this.GetDvarAsync("sv_customcallbacks", "0")).Value == "1";
|
return (await this.GetDvarAsync("sv_customcallbacks", "0", Manager.CancellationToken)).Value == "1";
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (DvarException)
|
catch (DvarException)
|
||||||
@ -391,11 +391,11 @@ namespace SharedLibraryCore
|
|||||||
public string[] ExecuteServerCommand(string command)
|
public string[] ExecuteServerCommand(string command)
|
||||||
{
|
{
|
||||||
var tokenSource = new CancellationTokenSource();
|
var tokenSource = new CancellationTokenSource();
|
||||||
tokenSource.CancelAfter(TimeSpan.FromMilliseconds(400));
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(0.5));
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return this.ExecuteCommandAsync(command).WithWaitCancellation(tokenSource.Token).GetAwaiter().GetResult();
|
return this.ExecuteCommandAsync(command, tokenSource.Token).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -406,11 +406,10 @@ namespace SharedLibraryCore
|
|||||||
public string GetServerDvar(string dvarName)
|
public string GetServerDvar(string dvarName)
|
||||||
{
|
{
|
||||||
var tokenSource = new CancellationTokenSource();
|
var tokenSource = new CancellationTokenSource();
|
||||||
tokenSource.CancelAfter(TimeSpan.FromMilliseconds(400));
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(0.5));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return this.GetDvarAsync<string>(dvarName).WithWaitCancellation(tokenSource.Token).GetAwaiter()
|
return this.GetDvarAsync<string>(dvarName, token: tokenSource.Token).GetAwaiter().GetResult().Value;
|
||||||
.GetResult()?.Value;
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -421,12 +420,11 @@ namespace SharedLibraryCore
|
|||||||
public bool SetServerDvar(string dvarName, string dvarValue)
|
public bool SetServerDvar(string dvarName, string dvarValue)
|
||||||
{
|
{
|
||||||
var tokenSource = new CancellationTokenSource();
|
var tokenSource = new CancellationTokenSource();
|
||||||
tokenSource.CancelAfter(TimeSpan.FromMilliseconds(400));
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(0.5));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
this.SetDvarAsync(dvarName, dvarValue).WithWaitCancellation(tokenSource.Token).GetAwaiter().GetResult();
|
this.SetDvarAsync(dvarName, dvarValue, tokenSource.Token).GetAwaiter().GetResult();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -597,51 +597,16 @@ namespace SharedLibraryCore.Services
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public virtual async Task UpdateLevel(Permission newPermission, EFClient temporalClient, EFClient origin)
|
public virtual async Task UpdateLevel(Permission newPermission, EFClient temporalClient, EFClient origin)
|
||||||
{
|
{
|
||||||
await using var ctx = _contextFactory.CreateContext();
|
await using var context = _contextFactory.CreateContext();
|
||||||
var entity = await ctx.Clients
|
var entity = await context.Clients
|
||||||
.Where(_client => _client.ClientId == temporalClient.ClientId)
|
.Where(client => client.ClientId == temporalClient.ClientId)
|
||||||
.FirstAsync();
|
.FirstAsync();
|
||||||
|
|
||||||
var oldPermission = entity.Level;
|
_logger.LogInformation("Updating {ClientId} from {OldPermission} to {NewPermission} ",
|
||||||
|
temporalClient.ClientId, entity.Level, newPermission);
|
||||||
|
|
||||||
entity.Level = newPermission;
|
entity.Level = newPermission;
|
||||||
await ctx.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
using (LogContext.PushProperty("Server", temporalClient?.CurrentServer?.ToString()))
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Updated {clientId} to {newPermission}", temporalClient.ClientId, newPermission);
|
|
||||||
|
|
||||||
var linkedPermissionSet = new[] { Permission.Banned, Permission.Flagged };
|
|
||||||
// if their permission level has been changed to level that needs to be updated on all accounts
|
|
||||||
if (linkedPermissionSet.Contains(newPermission) || linkedPermissionSet.Contains(oldPermission))
|
|
||||||
{
|
|
||||||
//get all clients that have the same linkId
|
|
||||||
var iqMatchingClients = ctx.Clients
|
|
||||||
.Where(_client => _client.AliasLinkId == entity.AliasLinkId);
|
|
||||||
|
|
||||||
var iqLinkClients = new List<Data.Models.Client.EFClient>().AsQueryable();
|
|
||||||
if (!_appConfig.EnableImplicitAccountLinking)
|
|
||||||
{
|
|
||||||
var linkIds = await ctx.Aliases.Where(alias =>
|
|
||||||
alias.IPAddress != null && alias.IPAddress == temporalClient.IPAddress)
|
|
||||||
.Select(alias => alias.LinkId)
|
|
||||||
.ToListAsync();
|
|
||||||
iqLinkClients = ctx.Clients.Where(client => linkIds.Contains(client.AliasLinkId));
|
|
||||||
}
|
|
||||||
|
|
||||||
// this updates the level for all the clients with the same LinkId
|
|
||||||
// only if their new level is flagged or banned
|
|
||||||
await iqMatchingClients.Union(iqLinkClients).ForEachAsync(_client =>
|
|
||||||
{
|
|
||||||
_client.Level = newPermission;
|
|
||||||
_logger.LogInformation("Updated linked {clientId} to {newPermission}", _client.ClientId,
|
|
||||||
newPermission);
|
|
||||||
});
|
|
||||||
|
|
||||||
await ctx.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
temporalClient.Level = newPermission;
|
temporalClient.Level = newPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ namespace SharedLibraryCore.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static readonly EFPenalty.PenaltyType[] LinkedPenalties =
|
private static readonly EFPenalty.PenaltyType[] LinkedPenalties =
|
||||||
{ EFPenalty.PenaltyType.Ban, EFPenalty.PenaltyType.Flag };
|
{ EFPenalty.PenaltyType.Ban, EFPenalty.PenaltyType.Flag, EFPenalty.PenaltyType.TempBan };
|
||||||
|
|
||||||
private static readonly Expression<Func<EFPenalty, bool>> Filter = p =>
|
private static readonly Expression<Func<EFPenalty, bool>> Filter = p =>
|
||||||
LinkedPenalties.Contains(p.Type) && p.Active && (p.Expires == null || p.Expires > DateTime.UtcNow);
|
LinkedPenalties.Contains(p.Type) && p.Active && (p.Expires == null || p.Expires > DateTime.UtcNow);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||||
<Version>2022.2.22.1</Version>
|
<Version>2022.2.28.1</Version>
|
||||||
<Authors>RaidMax</Authors>
|
<Authors>RaidMax</Authors>
|
||||||
<Company>Forever None</Company>
|
<Company>Forever None</Company>
|
||||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<Description>Shared Library for IW4MAdmin</Description>
|
<Description>Shared Library for IW4MAdmin</Description>
|
||||||
<PackageVersion>2022.2.22.1</PackageVersion>
|
<PackageVersion>2022.2.28.1</PackageVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
||||||
|
@ -723,14 +723,21 @@ namespace SharedLibraryCore
|
|||||||
.Replace('/', '_');
|
.Replace('/', '_');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName, T fallbackValue = default)
|
public static async Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName,
|
||||||
|
T fallbackValue = default, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue);
|
return await server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName,
|
||||||
|
T fallbackValue = default)
|
||||||
|
{
|
||||||
|
return await GetDvarAsync(server, dvarName, fallbackValue, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Dvar<T>> GetMappedDvarValueOrDefaultAsync<T>(this Server server, string dvarName,
|
public static async Task<Dvar<T>> GetMappedDvarValueOrDefaultAsync<T>(this Server server, string dvarName,
|
||||||
string infoResponseName = null, IDictionary<string, string> infoResponse = null,
|
string infoResponseName = null, IDictionary<string, string> infoResponse = null,
|
||||||
T overrideDefault = default)
|
T overrideDefault = default, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
// todo: unit test this
|
// todo: unit test this
|
||||||
var mappedKey = server.RconParser.GetOverrideDvarName(dvarName);
|
var mappedKey = server.RconParser.GetOverrideDvarName(dvarName);
|
||||||
@ -749,22 +756,32 @@ namespace SharedLibraryCore
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return await server.GetDvarAsync(mappedKey, defaultValue);
|
return await server.GetDvarAsync(mappedKey, defaultValue, token: token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
|
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
return server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue);
|
await server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
|
||||||
|
{
|
||||||
|
await SetDvarAsync(server, dvarName, dvarValue, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return await server.RconParser.ExecuteCommandAsync(server.RemoteConnection, commandName, token);
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName)
|
public static async Task<string[]> ExecuteCommandAsync(this Server server, string commandName)
|
||||||
{
|
{
|
||||||
return await server.RconParser.ExecuteCommandAsync(server.RemoteConnection, commandName);
|
return await ExecuteCommandAsync(server, commandName, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<IStatusResponse> GetStatusAsync(this Server server)
|
public static Task<IStatusResponse> GetStatusAsync(this Server server, CancellationToken token)
|
||||||
{
|
{
|
||||||
return server.RconParser.GetStatusAsync(server.RemoteConnection);
|
return server.RconParser.GetStatusAsync(server.RemoteConnection, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -53,6 +53,14 @@ namespace WebfrontCore.Controllers
|
|||||||
client.SetAdditionalProperty(EFMeta.ClientTag, tag.LinkedMeta.Value);
|
client.SetAdditionalProperty(EFMeta.ClientTag, tag.LinkedMeta.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// even though we haven't set their level to "banned" yet
|
||||||
|
// (ie they haven't reconnected with the infringing player identifier)
|
||||||
|
// we want to show them as banned as to not confuse people.
|
||||||
|
if (activePenalties.Any(penalty => penalty.Type == EFPenalty.PenaltyType.Ban))
|
||||||
|
{
|
||||||
|
client.Level = Data.Models.Client.EFClient.Permission.Banned;
|
||||||
|
}
|
||||||
|
|
||||||
var displayLevelInt = (int)client.Level;
|
var displayLevelInt = (int)client.Level;
|
||||||
var displayLevel = client.Level.ToLocalizedLevelName();
|
var displayLevel = client.Level.ToLocalizedLevelName();
|
||||||
|
|
||||||
|
@ -38,11 +38,12 @@ namespace WebfrontCore.ViewComponents
|
|||||||
return View("_List", meta);
|
return View("_List", meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IEnumerable<IClientMeta>> GetClientMeta(IMetaService metaService, MetaType? metaType, EFClient.Permission level, ClientPaginationRequest request)
|
public static async Task<IEnumerable<IClientMeta>> GetClientMeta(IMetaService metaService, MetaType? metaType,
|
||||||
|
EFClient.Permission level, ClientPaginationRequest request)
|
||||||
{
|
{
|
||||||
IEnumerable<IClientMeta> meta = null;
|
IEnumerable<IClientMeta> meta = null;
|
||||||
|
|
||||||
if (metaType == null) // all types
|
if (metaType is null or MetaType.All)
|
||||||
{
|
{
|
||||||
meta = await metaService.GetRuntimeMeta(request);
|
meta = await metaService.GetRuntimeMeta(request);
|
||||||
}
|
}
|
||||||
|
@ -146,23 +146,23 @@
|
|||||||
<partial name="Meta/_Information.cshtml" model="@Model.Meta"/>
|
<partial name="Meta/_Information.cshtml" model="@Model.Meta"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row border-bottom bg-dark">
|
<div class="row border-bottom">
|
||||||
<div class="d-md-flex flex-fill align-items-center bg-dark">
|
<div class="d-md-flex flex-fill">
|
||||||
<div class="text-center bg-dark p-2 pl-3 pr-4 text-muted" id="filter_meta_container_button">
|
<div class="bg-dark p-2 pl-3 pr-3 text-center text-muted border-0 align-self-stretch align-middle" id="filter_meta_container_button">
|
||||||
<span class="oi oi-sort-ascending"></span>
|
<span class="text-primary" id="meta_filter_dropdown_icon">▼</span>
|
||||||
<a>@ViewBag.Localization["WEBFRONT_CLIENT_META_FILTER"]</a>
|
<a>@ViewBag.Localization["WEBFRONT_CLIENT_META_FILTER"]</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="filter_meta_container" class="d-none d-md-flex flex-md-fill flex-md-wrap">
|
<div id="filter_meta_container" class="d-none d-md-flex flex-md-fill flex-md-wrap">
|
||||||
<a asp-action="ProfileAsync" asp-controller="Client"
|
@{
|
||||||
class="nav-link p-2 pl-3 pr-3 text-center col-12 col-md-auto text-md-left @(!Model.MetaFilterType.HasValue ? "btn-primary text-white" : "text-muted")"
|
const int defaultTabCount = 5;
|
||||||
asp-route-id="@Model.ClientId">
|
var metaTypes = Enum.GetValues(typeof(MetaType))
|
||||||
@ViewBag.Localization["META_TYPE_ALL_NAME"]
|
|
||||||
</a>
|
|
||||||
@{ var metaTypes = Enum.GetValues(typeof(MetaType))
|
|
||||||
.Cast<MetaType>()
|
.Cast<MetaType>()
|
||||||
.Where(type => !ignoredMetaTypes.Contains(type))
|
.Where(type => !ignoredMetaTypes.Contains(type))
|
||||||
.ToList(); }
|
.OrderByDescending(type => type == MetaType.All)
|
||||||
@foreach (var type in metaTypes.Take(4))
|
.ToList();
|
||||||
|
var selectedMeta = metaTypes.FirstOrDefault(meta => metaTypes.IndexOf(Model.MetaFilterType ?? MetaType.All) >= defaultTabCount && meta != MetaType.All && meta == Model.MetaFilterType);
|
||||||
|
}
|
||||||
|
@foreach (var type in metaTypes.Take(defaultTabCount - 1).Append(selectedMeta == MetaType.Other ? metaTypes[defaultTabCount - 1] : selectedMeta))
|
||||||
{
|
{
|
||||||
<a asp-action="ProfileAsync" asp-controller="Client"
|
<a asp-action="ProfileAsync" asp-controller="Client"
|
||||||
class="meta-filter nav-link p-2 pl-3 pr-3 text-center @(Model.MetaFilterType.HasValue && Model.MetaFilterType.Value.ToString() == type.ToString() ? "btn-primary text-white" : "text-muted")"
|
class="meta-filter nav-link p-2 pl-3 pr-3 text-center @(Model.MetaFilterType.HasValue && Model.MetaFilterType.Value.ToString() == type.ToString() ? "btn-primary text-white" : "text-muted")"
|
||||||
@ -172,9 +172,8 @@
|
|||||||
@type.ToTranslatedName()
|
@type.ToTranslatedName()
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
<a href="#" class="nav-link p-2 pl-3 pr-3 text-center text-muted d-none d-md-block" id="expand-meta-filters">...</a>
|
<div class="d-md-none" id="additional_meta_filter">
|
||||||
<div class="d-block d-md-none" id="additional-meta-filter">
|
@foreach (var type in (selectedMeta == MetaType.Other ? metaTypes.Skip(defaultTabCount) : metaTypes.Skip(defaultTabCount).Append(metaTypes[defaultTabCount - 1])).Where(meta => selectedMeta == MetaType.Other || meta != selectedMeta))
|
||||||
@foreach (var type in metaTypes.Skip(4))
|
|
||||||
{
|
{
|
||||||
<a asp-action="ProfileAsync" asp-controller="Client"
|
<a asp-action="ProfileAsync" asp-controller="Client"
|
||||||
class="meta-filter nav-link p-2 pl-3 pr-3 text-center @(Model.MetaFilterType.HasValue && Model.MetaFilterType.Value.ToString() == type.ToString() ? "btn-primary text-white" : "text-muted")"
|
class="meta-filter nav-link p-2 pl-3 pr-3 text-center @(Model.MetaFilterType.HasValue && Model.MetaFilterType.Value.ToString() == type.ToString() ? "btn-primary text-white" : "text-muted")"
|
||||||
|
@ -35,15 +35,9 @@
|
|||||||
startAt = $('.loader-data-time').last().data('time');
|
startAt = $('.loader-data-time').last().data('time');
|
||||||
|
|
||||||
$('#filter_meta_container_button').click(function () {
|
$('#filter_meta_container_button').click(function () {
|
||||||
$('#filter_meta_container').hide().removeClass('d-none').addClass('d-block').slideDown();
|
$('#filter_meta_container').removeClass('d-none').addClass('flex-md-column');
|
||||||
$('#additional-meta-filter').removeClass('d-md-none').addClass('d-flex').slideDown();
|
$('#additional_meta_filter').removeClass('d-md-none');
|
||||||
$('#expand-meta-filters').removeClass('d-md-block');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#expand-meta-filters').click(function () {
|
|
||||||
$('#additional-meta-filter').removeClass('d-md-none').addClass('d-flex').slideDown();
|
|
||||||
$('#expand-meta-filters').removeClass('d-md-block');
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* load context of chat
|
* load context of chat
|
||||||
|
@ -15,6 +15,8 @@ $(document).ready(() => {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInterval(refreshScoreboard, 5000);
|
||||||
|
|
||||||
$(window.location.hash).tab('show');
|
$(window.location.hash).tab('show');
|
||||||
$(`${window.location.hash}_nav`).addClass('active');
|
$(`${window.location.hash}_nav`).addClass('active');
|
||||||
|
|
||||||
@ -32,5 +34,3 @@ function setupDataSorting() {
|
|||||||
refreshScoreboard();
|
refreshScoreboard();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval(refreshScoreboard, 5000);
|
|
||||||
|
Reference in New Issue
Block a user