Compare commits
22 Commits
2021.06.03
...
2021.07.02
Author | SHA1 | Date | |
---|---|---|---|
8b06da5783 | |||
33a427bb8a | |||
c9d7a957dc | |||
9c6ff6f353 | |||
7444cb6472 | |||
c7e5c9c8dd | |||
0256fc35d2 | |||
0019ed8dde | |||
56aec53e72 | |||
1b773f21c6 | |||
bccbcce3c1 | |||
fc0bed2405 | |||
16cfb33109 | |||
42979dc5ae | |||
95cbc85144 | |||
9cbca390fe | |||
38c0c81451 | |||
af4630ecb9 | |||
dbceb23823 | |||
e628ac0e9e | |||
3a1e8359c2 | |||
c397fd5479 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -245,4 +245,6 @@ launchSettings.json
|
||||
/Tests/ApplicationTests/Files/replay.json
|
||||
/GameLogServer/game_log_server_env
|
||||
.idea/*
|
||||
*.db
|
||||
*.db
|
||||
/Data/IW4MAdmin_Migration.db-shm
|
||||
/Data/IW4MAdmin_Migration.db-wal
|
||||
|
@ -240,7 +240,7 @@ namespace IW4MAdmin.Application
|
||||
}
|
||||
|
||||
// remove the update tasks as they have completed
|
||||
foreach (var serverId in serverTasksToRemove)
|
||||
foreach (var serverId in serverTasksToRemove.Where(serverId => runningUpdateTasks.ContainsKey(serverId)))
|
||||
{
|
||||
if (!runningUpdateTasks[serverId].tokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
|
@ -3,7 +3,13 @@
|
||||
"Using": [
|
||||
"Serilog.Sinks.File"
|
||||
],
|
||||
"MinimumLevel": "Information",
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"System": "Warning",
|
||||
"Microsoft": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "File",
|
||||
|
@ -1008,7 +1008,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
"Game": "SHG1",
|
||||
"Maps": [
|
||||
{
|
||||
@ -1132,6 +1132,151 @@
|
||||
"Name": "mp_urban"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "CSGO",
|
||||
"Maps": [
|
||||
{
|
||||
"Name": "ar_baggage",
|
||||
"Alias": "Baggage"
|
||||
},
|
||||
{
|
||||
"Name": "ar_dizzy",
|
||||
"Alias": "Dizzy"
|
||||
},
|
||||
{
|
||||
"Name": "ar_lunacy",
|
||||
"Alias": "Lunacy"
|
||||
},
|
||||
{
|
||||
"Name": "ar_monastery",
|
||||
"Alias": "Monastery"
|
||||
},
|
||||
{
|
||||
"Name": "ar_shoots",
|
||||
"Alias": "Shoots"
|
||||
},
|
||||
{
|
||||
"Name": "cs_agency",
|
||||
"Alias": "Agency"
|
||||
},
|
||||
{
|
||||
"Name": "cs_assault",
|
||||
"Alias": "Assault"
|
||||
},
|
||||
{
|
||||
"Name": "cs_italy",
|
||||
"Alias": "Italy"
|
||||
},
|
||||
{
|
||||
"Name": "cs_militia",
|
||||
"Alias": "Militia"
|
||||
},
|
||||
{
|
||||
"Name": "cs_office",
|
||||
"Alias": "Office"
|
||||
},
|
||||
{
|
||||
"Name": "de_ancient",
|
||||
"Alias": "Ancient"
|
||||
},
|
||||
{
|
||||
"Name": "de_bank",
|
||||
"Alias": "Bank"
|
||||
},
|
||||
{
|
||||
"Name": "de_cache",
|
||||
"Alias": "Cache"
|
||||
},
|
||||
{
|
||||
"Name": "de_calavera",
|
||||
"Alias": "Calavera"
|
||||
},
|
||||
{
|
||||
"Name": "de_canals",
|
||||
"Alias": "Canals"
|
||||
},
|
||||
{
|
||||
"Name": "de_cbble",
|
||||
"Alias": "Cobblestone"
|
||||
},
|
||||
{
|
||||
"Name": "de_dust2",
|
||||
"Alias": "Dust II"
|
||||
},
|
||||
{
|
||||
"Name": "de_grind",
|
||||
"Alias": "Grind"
|
||||
},
|
||||
{
|
||||
"Name": "de_inferno",
|
||||
"Alias": "Inferno"
|
||||
},
|
||||
{
|
||||
"Name": "de_lake",
|
||||
"Alias": "Lake"
|
||||
},
|
||||
{
|
||||
"Name": "de_mirage",
|
||||
"Alias": "Mirage"
|
||||
},
|
||||
{
|
||||
"Name": "de_mocha",
|
||||
"Alias": "Mocha"
|
||||
},
|
||||
{
|
||||
"Name": "de_nuke",
|
||||
"Alias": "Nuke"
|
||||
},
|
||||
{
|
||||
"Name": "de_overpass",
|
||||
"Alias": "Overpass"
|
||||
},
|
||||
{
|
||||
"Name": "de_pitstop",
|
||||
"Alias": "Pitstop"
|
||||
},
|
||||
{
|
||||
"Name": "de_safehouse",
|
||||
"Alias": "Safehouse"
|
||||
},
|
||||
{
|
||||
"Name": "de_shortdust",
|
||||
"Alias": "Shortdust"
|
||||
},
|
||||
{
|
||||
"Name": "de_shortnuke",
|
||||
"Alias": "Shortnuke"
|
||||
},
|
||||
{
|
||||
"Name": "de_stmarc",
|
||||
"Alias": "St. Marc"
|
||||
},
|
||||
{
|
||||
"Name": "de_sugarcane",
|
||||
"Alias": "Sugarcane"
|
||||
},
|
||||
{
|
||||
"Name": "de_train",
|
||||
"Alias": "Train"
|
||||
},
|
||||
{
|
||||
"Name": "de_vertigo",
|
||||
"Alias": "Vertigo"
|
||||
},
|
||||
{
|
||||
"Name": "dz_blacksite",
|
||||
"Alias": "Blacksite"
|
||||
},
|
||||
{
|
||||
"Name": "dz_frostbite",
|
||||
"Alias": "Frostbite"
|
||||
},
|
||||
{
|
||||
"Name": "dz_sirocco",
|
||||
"Alias": "Sirocco"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"GameStrings": {
|
||||
|
@ -120,7 +120,7 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
if (lineSplit.Length > 1)
|
||||
{
|
||||
var type = lineSplit[0];
|
||||
return _eventTypeMap.ContainsKey(type) ? (_eventTypeMap[type], type): (GameEvent.EventType.Unknown, null);
|
||||
return _eventTypeMap.ContainsKey(type) ? (_eventTypeMap[type], type): (GameEvent.EventType.Unknown, lineSplit[0]);
|
||||
}
|
||||
|
||||
foreach (var (key, value) in _regexMap)
|
||||
|
@ -22,7 +22,7 @@ namespace IW4MAdmin.Application.Factories
|
||||
/// </summary>
|
||||
/// <param name="translationLookup"></param>
|
||||
/// <param name="rconConnectionFactory"></param>
|
||||
public GameServerInstanceFactory(ITranslationLookup translationLookup,
|
||||
public GameServerInstanceFactory(ITranslationLookup translationLookup,
|
||||
IMetaService metaService,
|
||||
IServiceProvider serviceProvider)
|
||||
{
|
||||
@ -39,7 +39,10 @@ namespace IW4MAdmin.Application.Factories
|
||||
/// <returns></returns>
|
||||
public Server CreateServer(ServerConfiguration config, IManager manager)
|
||||
{
|
||||
return new IW4MServer(config, _translationLookup, _metaService, _serviceProvider, _serviceProvider.GetRequiredService<IClientNoticeMessageFormatter>(), _serviceProvider.GetRequiredService<ILookupCache<EFServer>>());
|
||||
return new IW4MServer(config,
|
||||
_serviceProvider.GetRequiredService<CommandConfiguration>(), _translationLookup, _metaService,
|
||||
_serviceProvider, _serviceProvider.GetRequiredService<IClientNoticeMessageFormatter>(),
|
||||
_serviceProvider.GetRequiredService<ILookupCache<EFServer>>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -39,9 +39,11 @@ namespace IW4MAdmin
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IClientNoticeMessageFormatter _messageFormatter;
|
||||
private readonly ILookupCache<EFServer> _serverCache;
|
||||
private readonly CommandConfiguration _commandConfiguration;
|
||||
|
||||
public IW4MServer(
|
||||
ServerConfiguration serverConfiguration,
|
||||
CommandConfiguration commandConfiguration,
|
||||
ITranslationLookup lookup,
|
||||
IMetaService metaService,
|
||||
IServiceProvider serviceProvider,
|
||||
@ -58,6 +60,7 @@ namespace IW4MAdmin
|
||||
_serviceProvider = serviceProvider;
|
||||
_messageFormatter = messageFormatter;
|
||||
_serverCache = serverCache;
|
||||
_commandConfiguration = commandConfiguration;
|
||||
}
|
||||
|
||||
public override async Task<EFClient> OnClientConnected(EFClient clientFromLog)
|
||||
@ -158,7 +161,7 @@ namespace IW4MAdmin
|
||||
{
|
||||
try
|
||||
{
|
||||
C = await SharedLibraryCore.Commands.CommandProcessing.ValidateCommand(E, Manager.GetApplicationSettings().Configuration());
|
||||
C = await SharedLibraryCore.Commands.CommandProcessing.ValidateCommand(E, Manager.GetApplicationSettings().Configuration(), _commandConfiguration);
|
||||
}
|
||||
|
||||
catch (CommandException e)
|
||||
@ -1042,8 +1045,8 @@ namespace IW4MAdmin
|
||||
EventParser = Manager.AdditionalEventParsers
|
||||
.FirstOrDefault(_parser => _parser.Version == ServerConfig.EventParserVersion);
|
||||
|
||||
RconParser = RconParser ?? Manager.AdditionalRConParsers[0];
|
||||
EventParser = EventParser ?? Manager.AdditionalEventParsers[0];
|
||||
RconParser ??= Manager.AdditionalRConParsers[0];
|
||||
EventParser ??= Manager.AdditionalEventParsers[0];
|
||||
|
||||
RemoteConnection = RConConnectionFactory.CreateConnection(IP, Port, Password, RconParser.RConEngine);
|
||||
RemoteConnection.SetConfiguration(RconParser);
|
||||
@ -1177,12 +1180,12 @@ namespace IW4MAdmin
|
||||
GameDirectory = EventParser.Configuration.GameDirectory ?? "",
|
||||
ModDirectory = game.Value ?? "",
|
||||
LogFile = logfile.Value,
|
||||
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows),
|
||||
IsOneLog = RconParser.IsOneLog
|
||||
};
|
||||
LogPath = GenerateLogPath(logInfo);
|
||||
ServerLogger.LogInformation("Game log information {@logInfo}", logInfo);
|
||||
|
||||
|
||||
if (!File.Exists(LogPath) && ServerConfig.GameLogServerUrl == null)
|
||||
{
|
||||
Console.WriteLine(loc["SERVER_ERROR_DNE"].FormatExt(LogPath));
|
||||
@ -1223,12 +1226,12 @@ namespace IW4MAdmin
|
||||
public static string GenerateLogPath(LogPathGeneratorInfo logInfo)
|
||||
{
|
||||
string logPath;
|
||||
string workingDirectory = logInfo.BasePathDirectory;
|
||||
var workingDirectory = logInfo.BasePathDirectory;
|
||||
|
||||
bool baseGameIsDirectory = !string.IsNullOrWhiteSpace(logInfo.BaseGameDirectory) &&
|
||||
var baseGameIsDirectory = !string.IsNullOrWhiteSpace(logInfo.BaseGameDirectory) &&
|
||||
logInfo.BaseGameDirectory.IndexOfAny(Utilities.DirectorySeparatorChars) != -1;
|
||||
|
||||
bool baseGameIsRelative = logInfo.BaseGameDirectory.FixDirectoryCharacters()
|
||||
var baseGameIsRelative = logInfo.BaseGameDirectory.FixDirectoryCharacters()
|
||||
.Equals(logInfo.GameDirectory.FixDirectoryCharacters(), StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
// we want to see if base game is provided and it 'looks' like a directory
|
||||
@ -1237,7 +1240,7 @@ namespace IW4MAdmin
|
||||
workingDirectory = logInfo.BaseGameDirectory;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(logInfo.ModDirectory))
|
||||
if (string.IsNullOrWhiteSpace(logInfo.ModDirectory) || logInfo.IsOneLog)
|
||||
{
|
||||
logPath = Path.Combine(workingDirectory, logInfo.GameDirectory, logInfo.LogFile);
|
||||
}
|
||||
|
@ -41,5 +41,11 @@ namespace IW4MAdmin.Application.Misc
|
||||
/// indicates if running on windows
|
||||
/// </summary>
|
||||
public bool IsWindows { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// indicates that the game does not log to the mods folder (when mod is loaded),
|
||||
/// but rather always to the fs_basegame directory
|
||||
/// </summary>
|
||||
public bool IsOneLog { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -75,11 +75,12 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
public bool CanGenerateLogPath { get; set; } = true;
|
||||
public string Name { get; set; } = "Call of Duty";
|
||||
public string RConEngine { get; set; } = "COD";
|
||||
public bool IsOneLog { get; set; }
|
||||
|
||||
public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command)
|
||||
{
|
||||
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
|
||||
return response.Skip(1).ToArray();
|
||||
return response.Where(item => item != Configuration.CommandPrefixes.RConResponse).ToArray();
|
||||
}
|
||||
|
||||
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default)
|
||||
@ -216,10 +217,15 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
continue;
|
||||
}
|
||||
|
||||
int clientNumber = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]]);
|
||||
int score = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]]);
|
||||
var clientNumber = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConClientNumber]]);
|
||||
var score = 0;
|
||||
|
||||
if (Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore] > 0)
|
||||
{
|
||||
score = int.Parse(match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConScore]]);
|
||||
}
|
||||
|
||||
int ping = 999;
|
||||
var ping = 999;
|
||||
|
||||
// their state can be CNCT, ZMBI etc
|
||||
if (match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConPing]].Length <= 3)
|
||||
@ -228,7 +234,7 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
}
|
||||
|
||||
long networkId;
|
||||
string name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
|
||||
var name = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConName]].TrimNewLine();
|
||||
string networkIdString;
|
||||
var ip = match.Values[Configuration.Status.GroupMapping[ParserRegex.GroupType.RConIpAddress]].Split(':')[0].ConvertToIP();
|
||||
|
||||
|
@ -8,62 +8,9 @@
|
||||
<PackageId>RaidMax.IW4MAdmin.Data</PackageId>
|
||||
<Title>RaidMax.IW4MAdmin.Data</Title>
|
||||
<Authors />
|
||||
<PackageVersion>1.0.1</PackageVersion>
|
||||
<PackageVersion>1.0.3</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Migrations\MySql\20210210221342_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\MySql\20210210221342_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210224014503_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210224014503_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210224030227_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210224030227_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210224031245_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210224031245_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210227041237_AddPerformancePercentileToClientStats.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210227041237_AddPerformancePercentileToClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210227161333_AddPerformancePercentileToClientStats.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210227161333_AddPerformancePercentileToClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210307163752_AddRankingHistory.cs" />
|
||||
<Compile Remove="Migrations\Postgresql\20210307163752_AddRankingHistory.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209205243_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209205243_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209205948_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209205948_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209211745_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209211745_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209212725_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210209212725_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210020314_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210020314_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210140835_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210140835_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210154738_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210154738_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210163803_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210163803_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210193852_AddAdditionaClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210210193852_AddAdditionaClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210211033835_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210211033835_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210219013429_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210219013429_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210220171950_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210220171950_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210223163022_AddAdditionalClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210223163022_AddAdditionalClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210226215929_AddPerformancePercentileToClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210226215929_AddPerformancePercentileToClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210227160800_AddPerformancePercentileToClientStats.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210227160800_AddPerformancePercentileToClientStats.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210305033616_AddRankingHistory.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210305033616_AddRankingHistory.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210305033846_AddRankingHistory.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210305033846_AddRankingHistory.Designer.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210306223712_AddRankingHistory.cs" />
|
||||
<Compile Remove="Migrations\Sqlite\20210306223712_AddRankingHistory.Designer.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.10">
|
||||
|
1283
Data/Migrations/MySql/20210628153649_AddWeaponReferenceToEFClientKill.Designer.cs
generated
Normal file
1283
Data/Migrations/MySql/20210628153649_AddWeaponReferenceToEFClientKill.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class AddWeaponReferenceToEFClientKill : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WeaponReference",
|
||||
table: "EFClientKills",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WeaponReference",
|
||||
table: "EFClientKills");
|
||||
}
|
||||
}
|
||||
}
|
1295
Data/Migrations/MySql/20210628160144_AddWeaponReferenceAndServerIdToEFACSnapshot.Designer.cs
generated
Normal file
1295
Data/Migrations/MySql/20210628160144_AddWeaponReferenceAndServerIdToEFACSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,52 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class AddWeaponReferenceAndServerIdToEFACSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "ServerId",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WeaponReference",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFACSnapshot_ServerId",
|
||||
table: "EFACSnapshot",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_EFACSnapshot_EFServers_ServerId",
|
||||
table: "EFACSnapshot",
|
||||
column: "ServerId",
|
||||
principalTable: "EFServers",
|
||||
principalColumn: "ServerId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_EFACSnapshot_EFServers_ServerId",
|
||||
table: "EFACSnapshot");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_EFACSnapshot_ServerId",
|
||||
table: "EFACSnapshot");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ServerId",
|
||||
table: "EFACSnapshot");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WeaponReference",
|
||||
table: "EFACSnapshot");
|
||||
}
|
||||
}
|
||||
}
|
1298
Data/Migrations/MySql/20210629022028_AddHitLocationReferenceToEFACSnapshot.Designer.cs
generated
Normal file
1298
Data/Migrations/MySql/20210629022028_AddHitLocationReferenceToEFACSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.MySql
|
||||
{
|
||||
public partial class AddHitLocationReferenceToEFACSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "HitLocationReference",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "HitLocationReference",
|
||||
table: "EFACSnapshot");
|
||||
}
|
||||
}
|
||||
}
|
@ -146,6 +146,9 @@ namespace Data.Migrations.MySql
|
||||
b.Property<int>("Weapon")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("WeaponReference")
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
|
||||
b.Property<DateTime>("When")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@ -237,6 +240,9 @@ namespace Data.Migrations.MySql
|
||||
b.Property<int>("HitLocation")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("HitLocationReference")
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
|
||||
b.Property<int>("HitOriginId")
|
||||
.HasColumnType("int");
|
||||
|
||||
@ -255,6 +261,9 @@ namespace Data.Migrations.MySql
|
||||
b.Property<double>("RecoilOffset")
|
||||
.HasColumnType("double");
|
||||
|
||||
b.Property<long?>("ServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<double>("SessionAngleOffset")
|
||||
.HasColumnType("double");
|
||||
|
||||
@ -279,6 +288,9 @@ namespace Data.Migrations.MySql
|
||||
b.Property<int>("WeaponId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("WeaponReference")
|
||||
.HasColumnType("longtext CHARACTER SET utf8mb4");
|
||||
|
||||
b.Property<DateTime>("When")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
@ -294,6 +306,8 @@ namespace Data.Migrations.MySql
|
||||
|
||||
b.HasIndex("LastStrainAngleId");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("EFACSnapshot");
|
||||
});
|
||||
|
||||
@ -1103,6 +1117,10 @@ namespace Data.Migrations.MySql
|
||||
.HasForeignKey("LastStrainAngleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Data.Models.Server.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
|
||||
|
1308
Data/Migrations/Postgresql/20210628153932_AddWeaponReferenceToEFClientKill.Designer.cs
generated
Normal file
1308
Data/Migrations/Postgresql/20210628153932_AddWeaponReferenceToEFClientKill.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.Postgresql
|
||||
{
|
||||
public partial class AddWeaponReferenceToEFClientKill : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WeaponReference",
|
||||
table: "EFClientKills",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WeaponReference",
|
||||
table: "EFClientKills");
|
||||
}
|
||||
}
|
||||
}
|
1320
Data/Migrations/Postgresql/20210628160226_AddWeaponReferenceAndServerIdToEFACSnapshot.Designer.cs
generated
Normal file
1320
Data/Migrations/Postgresql/20210628160226_AddWeaponReferenceAndServerIdToEFACSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,52 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.Postgresql
|
||||
{
|
||||
public partial class AddWeaponReferenceAndServerIdToEFACSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "ServerId",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WeaponReference",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_EFACSnapshot_ServerId",
|
||||
table: "EFACSnapshot",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_EFACSnapshot_EFServers_ServerId",
|
||||
table: "EFACSnapshot",
|
||||
column: "ServerId",
|
||||
principalTable: "EFServers",
|
||||
principalColumn: "ServerId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_EFACSnapshot_EFServers_ServerId",
|
||||
table: "EFACSnapshot");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_EFACSnapshot_ServerId",
|
||||
table: "EFACSnapshot");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ServerId",
|
||||
table: "EFACSnapshot");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WeaponReference",
|
||||
table: "EFACSnapshot");
|
||||
}
|
||||
}
|
||||
}
|
1323
Data/Migrations/Postgresql/20210629022117_AddHitLocationReferenceToEFACSnapshot.Designer.cs
generated
Normal file
1323
Data/Migrations/Postgresql/20210629022117_AddHitLocationReferenceToEFACSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.Postgresql
|
||||
{
|
||||
public partial class AddHitLocationReferenceToEFACSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "HitLocationReference",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "HitLocationReference",
|
||||
table: "EFACSnapshot");
|
||||
}
|
||||
}
|
||||
}
|
@ -151,6 +151,9 @@ namespace Data.Migrations.Postgresql
|
||||
b.Property<int>("Weapon")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("WeaponReference")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("When")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
@ -244,6 +247,9 @@ namespace Data.Migrations.Postgresql
|
||||
b.Property<int>("HitLocation")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("HitLocationReference")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("HitOriginId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
@ -262,6 +268,9 @@ namespace Data.Migrations.Postgresql
|
||||
b.Property<double>("RecoilOffset")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<long?>("ServerId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<double>("SessionAngleOffset")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
@ -286,6 +295,9 @@ namespace Data.Migrations.Postgresql
|
||||
b.Property<int>("WeaponId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("WeaponReference")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("When")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
@ -301,6 +313,8 @@ namespace Data.Migrations.Postgresql
|
||||
|
||||
b.HasIndex("LastStrainAngleId");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("EFACSnapshot");
|
||||
});
|
||||
|
||||
@ -1128,6 +1142,10 @@ namespace Data.Migrations.Postgresql
|
||||
.HasForeignKey("LastStrainAngleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Data.Models.Server.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
|
||||
|
1282
Data/Migrations/Sqlite/20210628144550_AddWeaponReferenceToEFClientKill.Designer.cs
generated
Normal file
1282
Data/Migrations/Sqlite/20210628144550_AddWeaponReferenceToEFClientKill.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.Sqlite
|
||||
{
|
||||
public partial class AddWeaponReferenceToEFClientKill : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "WeaponReference",
|
||||
table: "EFClientKills",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "WeaponReference",
|
||||
table: "EFClientKills");
|
||||
}
|
||||
}
|
||||
}
|
1294
Data/Migrations/Sqlite/20210628154945_AddWeaponReferenceAndServerIdToEFACSnapshot.Designer.cs
generated
Normal file
1294
Data/Migrations/Sqlite/20210628154945_AddWeaponReferenceAndServerIdToEFACSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,162 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.Sqlite
|
||||
{
|
||||
public partial class AddWeaponReferenceAndServerIdToEFACSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql(@"PRAGMA foreign_keys = 0;
|
||||
|
||||
CREATE TABLE sqlitestudio_temp_table AS SELECT *
|
||||
FROM EFACSnapshot;
|
||||
|
||||
DROP TABLE EFACSnapshot;
|
||||
|
||||
CREATE TABLE EFACSnapshot (
|
||||
Active INTEGER NOT NULL,
|
||||
TimeSinceLastEvent INTEGER NOT NULL,
|
||||
SnapshotId INTEGER NOT NULL
|
||||
CONSTRAINT PK_EFACSnapshot PRIMARY KEY AUTOINCREMENT,
|
||||
ClientId INTEGER NOT NULL,
|
||||
ServerId INTEGER CONSTRAINT FK_EFACSnapshot_EFServers_ServerId REFERENCES EFServers (ServerId) ON DELETE RESTRICT,
|
||||
[When] TEXT NOT NULL,
|
||||
CurrentSessionLength INTEGER NOT NULL,
|
||||
EloRating REAL NOT NULL,
|
||||
SessionScore INTEGER NOT NULL,
|
||||
SessionSPM REAL NOT NULL,
|
||||
Hits INTEGER NOT NULL,
|
||||
Kills INTEGER NOT NULL,
|
||||
Deaths INTEGER NOT NULL,
|
||||
CurrentStrain REAL NOT NULL,
|
||||
StrainAngleBetween REAL NOT NULL,
|
||||
SessionAngleOffset REAL NOT NULL,
|
||||
LastStrainAngleId INTEGER NOT NULL,
|
||||
HitOriginId INTEGER NOT NULL,
|
||||
HitDestinationId INTEGER NOT NULL,
|
||||
Distance REAL NOT NULL,
|
||||
CurrentViewAngleId INTEGER,
|
||||
WeaponId INTEGER NOT NULL,
|
||||
WeaponReference TEXT,
|
||||
HitLocation INTEGER NOT NULL,
|
||||
HitType INTEGER NOT NULL,
|
||||
RecoilOffset REAL NOT NULL
|
||||
DEFAULT 0.0,
|
||||
SessionAverageSnapValue REAL NOT NULL
|
||||
DEFAULT 0.0,
|
||||
SessionSnapHits INTEGER NOT NULL
|
||||
DEFAULT 0,
|
||||
CONSTRAINT FK_EFACSnapshot_EFClients_ClientId FOREIGN KEY (
|
||||
ClientId
|
||||
)
|
||||
REFERENCES EFClients (ClientId) ON DELETE CASCADE,
|
||||
CONSTRAINT FK_EFACSnapshot_Vector3_CurrentViewAngleId FOREIGN KEY (
|
||||
CurrentViewAngleId
|
||||
)
|
||||
REFERENCES Vector3 (Vector3Id) ON DELETE RESTRICT,
|
||||
CONSTRAINT FK_EFACSnapshot_Vector3_HitDestinationId FOREIGN KEY (
|
||||
HitDestinationId
|
||||
)
|
||||
REFERENCES Vector3 (Vector3Id) ON DELETE CASCADE,
|
||||
CONSTRAINT FK_EFACSnapshot_Vector3_HitOriginId FOREIGN KEY (
|
||||
HitOriginId
|
||||
)
|
||||
REFERENCES Vector3 (Vector3Id) ON DELETE CASCADE,
|
||||
CONSTRAINT FK_EFACSnapshot_Vector3_LastStrainAngleId FOREIGN KEY (
|
||||
LastStrainAngleId
|
||||
)
|
||||
REFERENCES Vector3 (Vector3Id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO EFACSnapshot (
|
||||
Active,
|
||||
TimeSinceLastEvent,
|
||||
SnapshotId,
|
||||
ClientId,
|
||||
[When],
|
||||
CurrentSessionLength,
|
||||
EloRating,
|
||||
SessionScore,
|
||||
SessionSPM,
|
||||
Hits,
|
||||
Kills,
|
||||
Deaths,
|
||||
CurrentStrain,
|
||||
StrainAngleBetween,
|
||||
SessionAngleOffset,
|
||||
LastStrainAngleId,
|
||||
HitOriginId,
|
||||
HitDestinationId,
|
||||
Distance,
|
||||
CurrentViewAngleId,
|
||||
WeaponId,
|
||||
HitLocation,
|
||||
HitType,
|
||||
RecoilOffset,
|
||||
SessionAverageSnapValue,
|
||||
SessionSnapHits
|
||||
)
|
||||
SELECT Active,
|
||||
TimeSinceLastEvent,
|
||||
SnapshotId,
|
||||
ClientId,
|
||||
""When"",
|
||||
CurrentSessionLength,
|
||||
EloRating,
|
||||
SessionScore,
|
||||
SessionSPM,
|
||||
Hits,
|
||||
Kills,
|
||||
Deaths,
|
||||
CurrentStrain,
|
||||
StrainAngleBetween,
|
||||
SessionAngleOffset,
|
||||
LastStrainAngleId,
|
||||
HitOriginId,
|
||||
HitDestinationId,
|
||||
Distance,
|
||||
CurrentViewAngleId,
|
||||
WeaponId,
|
||||
HitLocation,
|
||||
HitType,
|
||||
RecoilOffset,
|
||||
SessionAverageSnapValue,
|
||||
SessionSnapHits
|
||||
FROM sqlitestudio_temp_table;
|
||||
|
||||
DROP TABLE sqlitestudio_temp_table;
|
||||
|
||||
CREATE INDEX IX_EFACSnapshot_ClientId ON EFACSnapshot (
|
||||
""ClientId""
|
||||
);
|
||||
|
||||
CREATE INDEX IX_EFACSnapshot_CurrentViewAngleId ON EFACSnapshot (
|
||||
""CurrentViewAngleId""
|
||||
);
|
||||
|
||||
CREATE INDEX IX_EFACSnapshot_HitDestinationId ON EFACSnapshot (
|
||||
""HitDestinationId""
|
||||
);
|
||||
|
||||
CREATE INDEX IX_EFACSnapshot_HitOriginId ON EFACSnapshot (
|
||||
""HitOriginId""
|
||||
);
|
||||
|
||||
CREATE INDEX IX_EFACSnapshot_LastStrainAngleId ON EFACSnapshot (
|
||||
""LastStrainAngleId""
|
||||
);
|
||||
|
||||
CREATE INDEX IX_EFACSnapshot_ServerId ON EFACSnapshot (
|
||||
""_ServerId""
|
||||
);
|
||||
|
||||
|
||||
PRAGMA foreign_keys = 1;
|
||||
");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
1297
Data/Migrations/Sqlite/20210629021801_AddHitLocationReferenceToEFACSnapshot.Designer.cs
generated
Normal file
1297
Data/Migrations/Sqlite/20210629021801_AddHitLocationReferenceToEFACSnapshot.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Data.Migrations.Sqlite
|
||||
{
|
||||
public partial class AddHitLocationReferenceToEFACSnapshot : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "HitLocationReference",
|
||||
table: "EFACSnapshot",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "HitLocationReference",
|
||||
table: "EFACSnapshot");
|
||||
}
|
||||
}
|
||||
}
|
@ -145,6 +145,9 @@ namespace Data.Migrations.Sqlite
|
||||
b.Property<int>("Weapon")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("WeaponReference")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("When")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -236,6 +239,9 @@ namespace Data.Migrations.Sqlite
|
||||
b.Property<int>("HitLocation")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("HitLocationReference")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("HitOriginId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -254,6 +260,9 @@ namespace Data.Migrations.Sqlite
|
||||
b.Property<double>("RecoilOffset")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<long?>("ServerId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("SessionAngleOffset")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
@ -278,6 +287,9 @@ namespace Data.Migrations.Sqlite
|
||||
b.Property<int>("WeaponId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("WeaponReference")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("When")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -293,6 +305,8 @@ namespace Data.Migrations.Sqlite
|
||||
|
||||
b.HasIndex("LastStrainAngleId");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("EFACSnapshot");
|
||||
});
|
||||
|
||||
@ -1102,6 +1116,10 @@ namespace Data.Migrations.Sqlite
|
||||
.HasForeignKey("LastStrainAngleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Data.Models.Server.EFServer", "Server")
|
||||
.WithMany()
|
||||
.HasForeignKey("ServerId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
|
||||
|
@ -18,7 +18,9 @@ namespace Data.Models.Client
|
||||
public int HitLoc { get; set; }
|
||||
public int DeathType { get; set; }
|
||||
public int Damage { get; set; }
|
||||
[Obsolete]
|
||||
public int Weapon { get; set; }
|
||||
public string WeaponReference { get; set; }
|
||||
public Vector3 KillOrigin { get; set; }
|
||||
public Vector3 DeathOrigin { get; set; }
|
||||
public Vector3 ViewAngles { get; set; }
|
||||
|
@ -4,6 +4,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Data.Models.Server;
|
||||
|
||||
namespace Data.Models.Client.Stats
|
||||
{
|
||||
@ -17,7 +18,9 @@ namespace Data.Models.Client.Stats
|
||||
public int ClientId { get; set; }
|
||||
[ForeignKey("ClientId")]
|
||||
public EFClient Client { get; set; }
|
||||
|
||||
public long? ServerId { get; set; }
|
||||
[ForeignKey(nameof(ServerId))]
|
||||
public EFServer Server { get; set; }
|
||||
public DateTime When { get; set; }
|
||||
public int CurrentSessionLength { get; set; }
|
||||
public int TimeSinceLastEvent { get; set; }
|
||||
@ -46,8 +49,11 @@ namespace Data.Models.Client.Stats
|
||||
public int CurrentViewAngleId { get; set; }
|
||||
[ForeignKey("CurrentViewAngleId")]
|
||||
public Vector3 CurrentViewAngle { get; set; }
|
||||
[Obsolete]
|
||||
public int WeaponId { get; set; }
|
||||
public string WeaponReference { get; set; }
|
||||
public int HitLocation { get; set; }
|
||||
public string HitLocationReference { get; set; }
|
||||
public int HitType { get; set; }
|
||||
public virtual ICollection<EFACSnapshotVector3> PredictedViewAngles { get; set; }
|
||||
|
||||
@ -55,5 +61,7 @@ namespace Data.Models.Client.Stats
|
||||
public string CapturedViewAngles => PredictedViewAngles?.Count > 0 ?
|
||||
string.Join(", ", PredictedViewAngles.OrderBy(_angle => _angle.ACSnapshotVector3Id).Select(_angle => _angle.Vector.ToString())) :
|
||||
"";
|
||||
|
||||
[NotMapped] public string ServerName => Server?.HostName ?? "--";
|
||||
}
|
||||
}
|
||||
|
@ -378,8 +378,6 @@ Global
|
||||
{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
|
||||
@ -394,6 +392,8 @@ Global
|
||||
{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
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Any CPU.Build.0 = Prerelease|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
|
||||
@ -402,8 +402,6 @@ Global
|
||||
{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
|
||||
@ -418,6 +416,8 @@ Global
|
||||
{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
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -4,6 +4,12 @@
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AssemblyName>Integrations.Cod</AssemblyName>
|
||||
<RootNamespace>Integrations.Cod</RootNamespace>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
48
Integrations/Source/Extensions/SourceExtensions.cs
Normal file
48
Integrations/Source/Extensions/SourceExtensions.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Integrations.Source.Extensions
|
||||
{
|
||||
public static class SourceExtensions
|
||||
{
|
||||
public static string ReplaceUnfriendlyCharacters(this string source)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
var quoteStart = false;
|
||||
var quoteIndex = 0;
|
||||
var index = 0;
|
||||
|
||||
foreach (var character in source)
|
||||
{
|
||||
if (character == '%')
|
||||
{
|
||||
result.Append('‰');
|
||||
}
|
||||
|
||||
else if ((character == '"' || character == '\'') && index + 1 != source.Length)
|
||||
{
|
||||
if (quoteIndex > 0)
|
||||
{
|
||||
result.Append(!quoteStart ? "«" : "»");
|
||||
quoteStart = !quoteStart;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
result.Append('"');
|
||||
}
|
||||
|
||||
quoteIndex++;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
result.Append(character);
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,12 @@
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AssemblyName>Integrations.Source</AssemblyName>
|
||||
<RootNamespace>Integrations.Source</RootNamespace>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,6 +1,9 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Integrations.Source.Extensions;
|
||||
using Integrations.Source.Interfaces;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RconSharp;
|
||||
@ -20,82 +23,161 @@ namespace Integrations.Source
|
||||
private readonly string _hostname;
|
||||
private readonly int _port;
|
||||
private readonly IRConClientFactory _rconClientFactory;
|
||||
private readonly SemaphoreSlim _activeQuery;
|
||||
|
||||
private static readonly TimeSpan FloodDelay = TimeSpan.FromMilliseconds(250);
|
||||
private static readonly TimeSpan ConnectionTimeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
private DateTime _lastQuery = DateTime.Now;
|
||||
private RconClient _rconClient;
|
||||
private bool _authenticated;
|
||||
private bool _needNewSocket = true;
|
||||
|
||||
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;
|
||||
_activeQuery = new SemaphoreSlim(1, 1);
|
||||
}
|
||||
|
||||
~SourceRConConnection()
|
||||
{
|
||||
_activeQuery.Dispose();
|
||||
}
|
||||
|
||||
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)
|
||||
await _activeQuery.WaitAsync();
|
||||
await WaitForAvailable();
|
||||
|
||||
if (_needNewSocket)
|
||||
{
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
try
|
||||
{
|
||||
_logger.LogWarning(ex,
|
||||
"Server appears to resumed from hibernation, so we are using a new socket");
|
||||
_rconClient?.Disconnect();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
_rconClient = _rconClientFactory.CreateClient(_hostname, _port);
|
||||
_authenticated = false;
|
||||
_needNewSocket = false;
|
||||
}
|
||||
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogError("Could not login to server");
|
||||
_logger.LogDebug("Connecting to RCon socket");
|
||||
}
|
||||
|
||||
throw new NetworkException("Could not authenticate with server");
|
||||
await TryConnectAndAuthenticate().WithTimeout(ConnectionTimeout);
|
||||
|
||||
var multiPacket = false;
|
||||
|
||||
if (type == StaticHelpers.QueryType.COMMAND_STATUS)
|
||||
{
|
||||
parameters = "status";
|
||||
multiPacket = true;
|
||||
}
|
||||
|
||||
parameters = parameters.ReplaceUnfriendlyCharacters();
|
||||
parameters = parameters.StripColors();
|
||||
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogDebug("Sending query {Type} with parameters \"{Parameters}\"", type, parameters);
|
||||
}
|
||||
|
||||
var response = await _rconClient.ExecuteCommandAsync(parameters, multiPacket)
|
||||
.WithTimeout(ConnectionTimeout);
|
||||
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogDebug("Received RCon response {Response}", response);
|
||||
}
|
||||
|
||||
var split = response.TrimEnd('\n').Split('\n');
|
||||
return split.Take(split.Length - 1).ToArray();
|
||||
}
|
||||
|
||||
if (!authenticated)
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
_needNewSocket = true;
|
||||
throw new NetworkException("Timeout while attempting to communicate with server");
|
||||
}
|
||||
|
||||
catch (SocketException ex)
|
||||
{
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogError("Could not login to server");
|
||||
_logger.LogError(ex, "Socket exception encountered while attempting to communicate with server");
|
||||
}
|
||||
|
||||
throw new ServerException("Could not authenticate to server with provided password");
|
||||
_needNewSocket = true;
|
||||
|
||||
throw new NetworkException("Socket exception encountered while attempting to communicate with server");
|
||||
}
|
||||
|
||||
if (type == StaticHelpers.QueryType.COMMAND_STATUS)
|
||||
catch (Exception ex) when (ex.GetType() != typeof(NetworkException) &&
|
||||
ex.GetType() != typeof(ServerException))
|
||||
{
|
||||
parameters = "status";
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogError(ex, "Could not execute RCon query {Parameters}", parameters);
|
||||
}
|
||||
|
||||
throw new NetworkException("Unable to communicate with server");
|
||||
}
|
||||
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
finally
|
||||
{
|
||||
_logger.LogDebug("Sending query {Type} with parameters {Parameters}", type, parameters);
|
||||
if (_activeQuery.CurrentCount == 0)
|
||||
{
|
||||
_activeQuery.Release();
|
||||
}
|
||||
|
||||
_lastQuery = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
var response = await _rconClient.ExecuteCommandAsync(parameters.StripColors(), true);
|
||||
|
||||
using (LogContext.PushProperty("Server", $"{_rconClient.Host}:{_rconClient.Port}"))
|
||||
private async Task WaitForAvailable()
|
||||
{
|
||||
var diff = DateTime.Now - _lastQuery;
|
||||
if (diff < FloodDelay)
|
||||
{
|
||||
_logger.LogDebug("Received RCon response {Response}", response);
|
||||
await Task.Delay(FloodDelay - diff);
|
||||
}
|
||||
}
|
||||
|
||||
var split = response.TrimEnd('\n').Split('\n');
|
||||
return split.Take(split.Length - 1).ToArray();
|
||||
private async Task TryConnectAndAuthenticate()
|
||||
{
|
||||
if (!_authenticated)
|
||||
{
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogDebug("Authenticating to RCon socket");
|
||||
}
|
||||
|
||||
await _rconClient.ConnectAsync().WithTimeout(ConnectionTimeout);
|
||||
_authenticated = await _rconClient.AuthenticateAsync(_password).WithTimeout(ConnectionTimeout);
|
||||
|
||||
if (!_authenticated)
|
||||
{
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogError("Could not login to server");
|
||||
}
|
||||
|
||||
throw new ServerException("Could not authenticate to server with provided password");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetConfiguration(IRConParser config)
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.3.19.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.6.29.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.3.19.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.6.29.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -23,7 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.3.19.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.6.29.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -19,7 +19,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.3.19.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.6.29.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -16,7 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.3.19.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.6.29.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -3,7 +3,7 @@ let eventParser;
|
||||
|
||||
const plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 0.1,
|
||||
version: 0.2,
|
||||
name: 'CS:GO Parser',
|
||||
engine: 'Source',
|
||||
isParser: true,
|
||||
@ -12,8 +12,8 @@ const plugin = {
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
rconParser = manager.GenerateDynamicRConParser(this.engine);
|
||||
eventParser = manager.GenerateDynamicEventParser(this.engine);
|
||||
rconParser = manager.GenerateDynamicRConParser(this.name);
|
||||
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||
rconParser.RConEngine = this.engine;
|
||||
|
||||
rconParser.Configuration.StatusHeader.Pattern = 'userid +name +uniqueid +connected +ping +loss +state +rate +adr';
|
||||
@ -24,18 +24,18 @@ const plugin = {
|
||||
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
|
||||
rconParser.Configuration.MapStatus.AddMapping(113, 1);
|
||||
|
||||
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d humans, \\d bots \\((\\d+).+';
|
||||
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d+ humans, \\d+ bots \\((\\d+).+';
|
||||
rconParser.Configuration.MapStatus.AddMapping(114, 1);
|
||||
|
||||
rconParser.Configuration.Dvar.Pattern = '^"(.+)" = (?:"(.+)" (?:\\( def\\. "(.*)" \\))|"(.+)" +(.+)) +- (.*)$';
|
||||
rconParser.Configuration.Dvar.Pattern = '^"(.+)" = "(.+)" (?:\\( def. "(.*)" \\))?(?: |\\w)+- (.+)$';
|
||||
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.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) +(\\d+:\\d+(?::\\d+)?) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
|
||||
rconParser.Configuration.Status.AddMapping(100, 2);
|
||||
rconParser.Configuration.Status.AddMapping(101, 7);
|
||||
rconParser.Configuration.Status.AddMapping(101, -1);
|
||||
rconParser.Configuration.Status.AddMapping(102, 6);
|
||||
rconParser.Configuration.Status.AddMapping(103, 4)
|
||||
rconParser.Configuration.Status.AddMapping(104, 3);
|
||||
@ -90,6 +90,7 @@ const plugin = {
|
||||
rconParser.GameName = 10; // CSGO
|
||||
eventParser.Version = 'CSGO';
|
||||
eventParser.GameName = 10; // CSGO
|
||||
eventParser.URLProtocolFormat = 'steam://connect/{{ip}}:{{port}}';
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
|
@ -3,7 +3,7 @@ let eventParser;
|
||||
|
||||
const plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 0.1,
|
||||
version: 0.2,
|
||||
name: 'CS:GO (SourceMod) Parser',
|
||||
engine: 'Source',
|
||||
isParser: true,
|
||||
@ -12,8 +12,8 @@ const plugin = {
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
rconParser = manager.GenerateDynamicRConParser(this.engine);
|
||||
eventParser = manager.GenerateDynamicEventParser(this.engine);
|
||||
rconParser = manager.GenerateDynamicRConParser(this.name);
|
||||
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||
rconParser.RConEngine = this.engine;
|
||||
|
||||
rconParser.Configuration.StatusHeader.Pattern = 'userid +name +uniqueid +connected +ping +loss +state +rate +adr';
|
||||
@ -24,18 +24,18 @@ const plugin = {
|
||||
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
|
||||
rconParser.Configuration.MapStatus.AddMapping(113, 1);
|
||||
|
||||
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d humans, \\d bots \\((\\d+).+';
|
||||
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d+ humans, \\d+ bots \\((\\d+).+';
|
||||
rconParser.Configuration.MapStatus.AddMapping(114, 1);
|
||||
|
||||
rconParser.Configuration.Dvar.Pattern = '^"(.+)" = (?:"(.+)" (?:\\( def\\. "(.*)" \\))|"(.+)" +(.+)) +- (.*)$';
|
||||
rconParser.Configuration.Dvar.Pattern = '^"(.+)" = "(.+)" (?:\\( def. "(.*)" \\))?(?: |\\w)+- (.+)$';
|
||||
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.Pattern = '^#\\s*(\\d+) (\\d+) "(.+)" (\\S+) +(\\d+:\\d+(?::\\d+)?) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
|
||||
rconParser.Configuration.Status.AddMapping(100, 2);
|
||||
rconParser.Configuration.Status.AddMapping(101, 7);
|
||||
rconParser.Configuration.Status.AddMapping(101, -1);
|
||||
rconParser.Configuration.Status.AddMapping(102, 6);
|
||||
rconParser.Configuration.Status.AddMapping(103, 4)
|
||||
rconParser.Configuration.Status.AddMapping(104, 3);
|
||||
@ -64,7 +64,7 @@ const plugin = {
|
||||
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}';
|
||||
rconParser.Configuration.CommandPrefixes.Tell = 'sm_psay #{0} "{1}"';
|
||||
|
||||
eventParser.Configuration.Say.Pattern = '^"(.+)<(\\d+)><(.+)><(.*?)>" say "(.*)"$';
|
||||
eventParser.Configuration.Say.AddMapping(5, 1);
|
||||
@ -90,6 +90,7 @@ const plugin = {
|
||||
rconParser.GameName = 10; // CSGO
|
||||
eventParser.Version = 'CSGOSM';
|
||||
eventParser.GameName = 10; // CSGO
|
||||
eventParser.URLProtocolFormat = 'steam://connect/{{ip}}:{{port}}';
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
|
@ -3,7 +3,7 @@ var eventParser;
|
||||
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 0.6,
|
||||
version: 0.8,
|
||||
name: 'Plutonium IW5 Parser',
|
||||
isParser: true,
|
||||
|
||||
@ -20,6 +20,7 @@ var plugin = {
|
||||
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.RConGetDvar = '\xff\xff\xff\xffrcon {0} get {1}';
|
||||
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
|
||||
|
||||
rconParser.Configuration.Dvar.Pattern = '^(.+) is "(.+)?"';
|
||||
rconParser.Configuration.Dvar.AddMapping(106, 1);
|
||||
@ -27,16 +28,14 @@ var plugin = {
|
||||
rconParser.Configuration.WaitForResponse = true;
|
||||
rconParser.Configuration.CanGenerateLogPath = true;
|
||||
rconParser.Configuration.NoticeLineSeparator = '. ';
|
||||
|
||||
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +address +qport *';
|
||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +(0|1) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|unknown|bot) +(-*[0-9]+) *$';
|
||||
rconParser.Configuration.Status.AddMapping(102, 4);
|
||||
rconParser.Configuration.Status.AddMapping(103, 5);
|
||||
rconParser.Configuration.Status.AddMapping(104, 6);
|
||||
|
||||
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +lastmsg +address +qport +rate *';
|
||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +(?:[0-1]{1}) +([0-9]{1,4}|[A-Z]{4}) +([a-f|A-F|0-9]{16}) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$';
|
||||
rconParser.Configuration.Status.AddMapping(100, 1);
|
||||
rconParser.Configuration.Status.AddMapping(101, 2);
|
||||
rconParser.Configuration.Status.AddMapping(102, 3);
|
||||
rconParser.Configuration.Status.AddMapping(103, 4);
|
||||
rconParser.Configuration.Status.AddMapping(104, 5);
|
||||
rconParser.Configuration.Status.AddMapping(105, 6);
|
||||
|
||||
rconParser.IsOneLog = true;
|
||||
rconParser.Version = 'IW5 MP 1.9 build 388110 Fri Sep 14 00:04:28 2012 win-x86';
|
||||
rconParser.GameName = 3; // IW5
|
||||
eventParser.Version = 'IW5 MP 1.9 build 388110 Fri Sep 14 00:04:28 2012 win-x86';
|
||||
|
@ -38,7 +38,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
double AngleDifferenceAverage;
|
||||
EFClientStatistics ClientStats;
|
||||
long LastOffset;
|
||||
IW4Info.WeaponName LastWeapon;
|
||||
string LastWeapon;
|
||||
ILogger Log;
|
||||
Strain Strain;
|
||||
readonly DateTime ConnectionTime = DateTime.UtcNow;
|
||||
@ -111,7 +111,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
hit.DeathType != (int)IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
|
||||
hit.HitLoc == (int)IW4Info.HitLocation.none || hit.TimeOffset - LastOffset < 0 ||
|
||||
// hack: prevents false positives
|
||||
((int)LastWeapon != hit.Weapon && (hit.TimeOffset - LastOffset) == 50))
|
||||
(LastWeapon != hit.WeaponReference && (hit.TimeOffset - LastOffset) == 50))
|
||||
{
|
||||
return new[] {new DetectionPenaltyResult()
|
||||
{
|
||||
@ -119,7 +119,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
}};
|
||||
}
|
||||
|
||||
LastWeapon = (IW4Info.WeaponName)(hit.Weapon);
|
||||
LastWeapon = hit.WeaponReference;
|
||||
|
||||
HitLocationCount[(IW4Info.HitLocation)hit.HitLoc].Count++;
|
||||
HitCount++;
|
||||
@ -309,7 +309,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
try
|
||||
{
|
||||
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[(Server.Game)hit.GameName][DetectionType.Recoil]
|
||||
.Any(_weaponRegex => Regex.IsMatch(((IW4Info.WeaponName)(hit.Weapon)).ToString(), _weaponRegex));
|
||||
.Any(_weaponRegex => Regex.IsMatch(hit.WeaponReference, _weaponRegex));
|
||||
}
|
||||
|
||||
catch (KeyNotFoundException)
|
||||
@ -341,7 +341,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
{
|
||||
shouldIgnoreDetection = false;
|
||||
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[(Server.Game)hit.GameName][DetectionType.Button]
|
||||
.Any(_weaponRegex => Regex.IsMatch(((IW4Info.WeaponName)(hit.Weapon)).ToString(), _weaponRegex));
|
||||
.Any(_weaponRegex => Regex.IsMatch(hit.WeaponReference, _weaponRegex));
|
||||
}
|
||||
|
||||
catch (KeyNotFoundException)
|
||||
@ -454,7 +454,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
{
|
||||
shouldIgnoreDetection = false; // reset previous value
|
||||
shouldIgnoreDetection = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredDetectionSpecification[(Server.Game)hit.GameName][DetectionType.Chest]
|
||||
.Any(_weaponRegex => Regex.IsMatch(((IW4Info.WeaponName)(hit.Weapon)).ToString(), _weaponRegex));
|
||||
.Any(_weaponRegex => Regex.IsMatch(hit.WeaponReference, _weaponRegex));
|
||||
}
|
||||
|
||||
catch (KeyNotFoundException)
|
||||
@ -506,6 +506,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
{
|
||||
When = hit.When,
|
||||
ClientId = ClientStats.ClientId,
|
||||
ServerId = ClientStats.ServerId,
|
||||
SessionAngleOffset = AngleDifferenceAverage,
|
||||
RecoilOffset = hitRecoilAverage,
|
||||
CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalMinutes,
|
||||
@ -527,7 +528,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
SessionSPM = Math.Round(ClientStats.SessionSPM, 0),
|
||||
StrainAngleBetween = Strain.LastDistance,
|
||||
TimeSinceLastEvent = (int)Strain.LastDeltaTime,
|
||||
WeaponId = hit.Weapon,
|
||||
WeaponReference = hit.WeaponReference,
|
||||
SessionSnapHits = sessionSnapHits,
|
||||
SessionAverageSnapValue = sessionAverageSnapAmount
|
||||
};
|
||||
|
@ -11,6 +11,7 @@ using Data.Models.Client.Stats.Reference;
|
||||
using Data.Models.Server;
|
||||
using IW4MAdmin.Plugins.Stats.Client.Abstractions;
|
||||
using IW4MAdmin.Plugins.Stats.Client.Game;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
@ -147,7 +148,7 @@ namespace IW4MAdmin.Plugins.Stats.Client
|
||||
foreach (var client in gameEvent.Owner.GetClientsAsList())
|
||||
{
|
||||
var scores = client.GetAdditionalProperty<List<(int, DateTime)>>(SessionScores);
|
||||
scores?.Add((client.Score, DateTime.Now));
|
||||
scores?.Add((client.GetAdditionalProperty<int?>(StatManager.ESTIMATED_SCORE) ?? client.Score, DateTime.Now));
|
||||
}
|
||||
}
|
||||
|
||||
@ -590,7 +591,7 @@ namespace IW4MAdmin.Plugins.Stats.Client
|
||||
|
||||
if (sessionScores == null)
|
||||
{
|
||||
_logger.LogWarning($"No session scores available for {client}");
|
||||
_logger.LogWarning("No session scores available for {Client}", client.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -600,7 +601,7 @@ namespace IW4MAdmin.Plugins.Stats.Client
|
||||
|
||||
if (sessionScores.Count == 0)
|
||||
{
|
||||
stat.Score += client.Score;
|
||||
stat.Score += client.Score > 0 ? client.Score : client.GetAdditionalProperty<int?>(Helpers.StatManager.ESTIMATED_SCORE) ?? 0 * 50;
|
||||
}
|
||||
|
||||
else
|
||||
|
@ -2,7 +2,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using SharedLibraryCore;
|
||||
using System.Collections.Generic;
|
||||
using Data.Abstractions;
|
||||
@ -16,11 +15,12 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
class MostPlayedCommand : Command
|
||||
{
|
||||
public static async Task<List<string>> GetMostPlayed(Server s, ITranslationLookup translationLookup, IDatabaseContextFactory contextFactory)
|
||||
public static async Task<List<string>> GetMostPlayed(Server s, ITranslationLookup translationLookup,
|
||||
IDatabaseContextFactory contextFactory)
|
||||
{
|
||||
long serverId = StatManager.GetIdForServer(s);
|
||||
var serverId = StatManager.GetIdForServer(s);
|
||||
|
||||
List<string> mostPlayed = new List<string>()
|
||||
var mostPlayed = new List<string>()
|
||||
{
|
||||
$"^5--{translationLookup["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
|
||||
};
|
||||
@ -29,25 +29,28 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1);
|
||||
|
||||
var iqStats = (from stats in context.Set<EFClientStatistics>()
|
||||
join client in context.Clients
|
||||
on stats.ClientId equals client.ClientId
|
||||
join alias in context.Aliases
|
||||
on client.CurrentAliasId equals alias.AliasId
|
||||
where stats.ServerId == serverId
|
||||
where client.Level != EFClient.Permission.Banned
|
||||
where client.LastConnection >= thirtyDaysAgo
|
||||
orderby stats.TimePlayed descending
|
||||
select new
|
||||
{
|
||||
alias.Name,
|
||||
client.TotalConnectionTime,
|
||||
stats.Kills
|
||||
})
|
||||
.Take(5);
|
||||
join client in context.Clients
|
||||
on stats.ClientId equals client.ClientId
|
||||
join alias in context.Aliases
|
||||
on client.CurrentAliasId equals alias.AliasId
|
||||
where stats.ServerId == serverId
|
||||
where client.Level != EFClient.Permission.Banned
|
||||
where client.LastConnection >= thirtyDaysAgo
|
||||
orderby stats.TimePlayed descending
|
||||
select new
|
||||
{
|
||||
alias.Name,
|
||||
stats.TimePlayed,
|
||||
stats.Kills
|
||||
})
|
||||
.Take(5);
|
||||
|
||||
var iqList = await iqStats.ToListAsync();
|
||||
|
||||
mostPlayed.AddRange(iqList.Select(stats => translationLookup["COMMANDS_MOST_PLAYED_FORMAT"].FormatExt(stats.Name, stats.Kills, (DateTime.UtcNow - DateTime.UtcNow.AddSeconds(-stats.TotalConnectionTime)).HumanizeForCurrentCulture())));
|
||||
mostPlayed.AddRange(iqList.Select((stats, index) =>
|
||||
$"#{index + 1} " + translationLookup["COMMANDS_MOST_PLAYED_FORMAT"].FormatExt(stats.Name, stats.Kills,
|
||||
(DateTime.UtcNow - DateTime.UtcNow.AddSeconds(-stats.TimePlayed))
|
||||
.HumanizeForCurrentCulture())));
|
||||
|
||||
|
||||
return mostPlayed;
|
||||
@ -57,7 +60,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
|
||||
public MostPlayedCommand(CommandConfiguration config, ITranslationLookup translationLookup,
|
||||
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
||||
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
||||
{
|
||||
Name = "mostplayed";
|
||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC"];
|
||||
@ -88,4 +91,4 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
94
Plugins/Stats/Commands/ResetAnticheatMetricsCommand.cs
Normal file
94
Plugins/Stats/Commands/ResetAnticheatMetricsCommand.cs
Normal file
@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Data.Models.Client;
|
||||
using Data.Models.Client.Stats;
|
||||
using IW4MAdmin.Plugins.Stats.Cheat;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace Stats.Commands
|
||||
{
|
||||
public class ResetAnticheatMetricsCommand : Command
|
||||
{
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public ResetAnticheatMetricsCommand(ILogger<ResetAnticheatMetricsCommand> logger, CommandConfiguration config,
|
||||
ITranslationLookup translationLookup,
|
||||
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
|
||||
{
|
||||
Name = "resetanticheat";
|
||||
Description = translationLookup["PLUGINS_STATS_COMMANDS_RESETAC_DESC"];
|
||||
Alias = "rsa";
|
||||
Permission = EFClient.Permission.Owner;
|
||||
RequiresTarget = true;
|
||||
|
||||
_contextFactory = contextFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent gameEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
var clientDetection =
|
||||
gameEvent.Target.GetAdditionalProperty<Detection>(IW4MAdmin.Plugins.Stats.Helpers.StatManager
|
||||
.CLIENT_DETECTIONS_KEY);
|
||||
var clientStats =
|
||||
gameEvent.Target.GetAdditionalProperty<EFClientStatistics>(IW4MAdmin.Plugins.Stats.Helpers
|
||||
.StatManager.CLIENT_STATS_KEY);
|
||||
|
||||
if (clientStats != null)
|
||||
{
|
||||
clientStats.MaxStrain = 0;
|
||||
clientStats.AverageSnapValue = 0;
|
||||
clientStats.SnapHitCount = 0;
|
||||
clientStats.HitLocations.Clear();
|
||||
}
|
||||
|
||||
clientDetection?.TrackedHits.Clear();
|
||||
|
||||
await using var context = _contextFactory.CreateContext();
|
||||
|
||||
var hitLocationCounts = await context.Set<EFHitLocationCount>()
|
||||
.Where(loc => loc.EFClientStatisticsClientId == gameEvent.Target.ClientId)
|
||||
.Select(loc => new EFHitLocationCount()
|
||||
{
|
||||
HitLocationCountId = loc.HitLocationCountId
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
context.RemoveRange(hitLocationCounts);
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var stats = await context.Set<EFClientStatistics>()
|
||||
.Where(stat => stat.ClientId == gameEvent.Target.ClientId)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var stat in stats)
|
||||
{
|
||||
stat.MaxStrain = 0;
|
||||
stat.AverageSnapValue = 0;
|
||||
stat.SnapHitCount = 0;
|
||||
}
|
||||
|
||||
context.UpdateRange(stats);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
gameEvent.Origin.Tell(_translationLookup["PLUGINS_STATS_COMMANDS_RESETAC_SUCCESS"]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not reset anticheat metrics for {Target}", gameEvent.Target);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
};
|
||||
|
||||
var stats = await Plugin.Manager.GetTopStats(0, 5, serverId);
|
||||
var statsList = stats.Select(stats => $"^3{stats.Name}^7 - ^5{stats.KDR} ^7{translationLookup["PLUGINS_STATS_TEXT_KDR"]} | ^5{stats.Performance} ^7{translationLookup["PLUGINS_STATS_COMMANDS_PERFORMANCE"]}");
|
||||
var statsList = stats.Select((stats, index) => $"#{index + 1} ^3{stats.Name}^7 - ^5{stats.KDR} ^7{translationLookup["PLUGINS_STATS_TEXT_KDR"]} | ^5{stats.Performance} ^7{translationLookup["PLUGINS_STATS_COMMANDS_PERFORMANCE"]}");
|
||||
|
||||
topStatsText.AddRange(statsList);
|
||||
|
||||
|
@ -53,13 +53,15 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
|
||||
var serverId = StatManager.GetIdForServer(E.Owner);
|
||||
|
||||
var totalRankedPlayers = await Plugin.Manager.GetTotalRankedPlayers(serverId);
|
||||
|
||||
// getting stats for a particular client
|
||||
if (E.Target != null)
|
||||
{
|
||||
var performanceRanking = await Plugin.Manager.GetClientOverallRanking(E.Target.ClientId, serverId);
|
||||
var performanceRankingString = performanceRanking == 0
|
||||
? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"]
|
||||
: $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
|
||||
: $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}/{totalRankedPlayers}";
|
||||
|
||||
// target is currently connected so we want their cached stats if they exist
|
||||
if (E.Owner.GetClientsAsList().Any(client => client.Equals(E.Target)))
|
||||
@ -87,7 +89,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
var performanceRanking = await Plugin.Manager.GetClientOverallRanking(E.Origin.ClientId, serverId);
|
||||
var performanceRankingString = performanceRanking == 0
|
||||
? _translationLookup["WEBFRONT_STATS_INDEX_UNRANKED"]
|
||||
: $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
|
||||
: $"{_translationLookup["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}/{totalRankedPlayers}";
|
||||
|
||||
// check if current client is connected to the server
|
||||
if (E.Owner.GetClientsAsList().Any(client => client.Equals(E.Origin)))
|
||||
|
@ -38,6 +38,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
private static List<EFServer> serverModels;
|
||||
public static string CLIENT_STATS_KEY = "ClientStats";
|
||||
public static string CLIENT_DETECTIONS_KEY = "ClientDetections";
|
||||
public static string ESTIMATED_SCORE = "EstimatedScore";
|
||||
private readonly SemaphoreSlim _addPlayerWaiter = new SemaphoreSlim(1, 1);
|
||||
private readonly IServerDistributionCalculator _serverDistributionCalculator;
|
||||
|
||||
@ -112,19 +113,33 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
return 0;
|
||||
}
|
||||
|
||||
public Expression<Func<EFClientRankingHistory, bool>> GetNewRankingFunc(int? clientId = null, long? serverId = null)
|
||||
{
|
||||
return (ranking) => ranking.ServerId == serverId
|
||||
&& ranking.Client.Level != Data.Models.Client.EFClient.Permission.Banned
|
||||
&& ranking.Client.LastConnection >= Extensions.FifteenDaysAgo()
|
||||
&& ranking.ZScore != null
|
||||
&& ranking.PerformanceMetric != null
|
||||
&& ranking.Newest
|
||||
&& ranking.Client.TotalConnectionTime >=
|
||||
_configHandler.Configuration().TopPlayersMinPlayTime;
|
||||
}
|
||||
|
||||
public async Task<int> GetTotalRankedPlayers(long serverId)
|
||||
{
|
||||
await using var context = _contextFactory.CreateContext(enableTracking: false);
|
||||
|
||||
return await context.Set<EFClientRankingHistory>()
|
||||
.Where(GetNewRankingFunc(serverId: serverId))
|
||||
.CountAsync();
|
||||
}
|
||||
|
||||
public async Task<List<TopStatsInfo>> GetNewTopStats(int start, int count, long? serverId = null)
|
||||
{
|
||||
await using var context = _contextFactory.CreateContext(false);
|
||||
|
||||
var clientIdsList = await context.Set<EFClientRankingHistory>()
|
||||
.Where(ranking => ranking.ServerId == serverId)
|
||||
.Where(ranking => ranking.Client.Level != Data.Models.Client.EFClient.Permission.Banned)
|
||||
.Where(ranking => ranking.Client.LastConnection >= Extensions.FifteenDaysAgo())
|
||||
.Where(ranking => ranking.ZScore != null)
|
||||
.Where(ranking => ranking.PerformanceMetric != null)
|
||||
.Where(ranking => ranking.Newest)
|
||||
.Where(ranking =>
|
||||
ranking.Client.TotalConnectionTime >= _configHandler.Configuration().TopPlayersMinPlayTime)
|
||||
.Where(GetNewRankingFunc(serverId: serverId))
|
||||
.OrderByDescending(ranking => ranking.PerformanceMetric)
|
||||
.Select(ranking => ranking.ClientId)
|
||||
.Skip(start)
|
||||
@ -533,7 +548,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
// sync their stats before they leave
|
||||
if (clientStats != null)
|
||||
{
|
||||
clientStats = UpdateStats(clientStats);
|
||||
clientStats = UpdateStats(clientStats, pl);
|
||||
await SaveClientStats(clientStats);
|
||||
if (_configHandler.Configuration().EnableAdvancedMetrics)
|
||||
{
|
||||
@ -609,7 +624,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
DeathType = (int) ParseEnum<IW4Info.MeansOfDeath>.Get(type, typeof(IW4Info.MeansOfDeath)),
|
||||
Damage = int.Parse(damage),
|
||||
HitLoc = (int) ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)),
|
||||
Weapon = (int) ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName)),
|
||||
WeaponReference = weapon,
|
||||
ViewAngles = vViewAngles,
|
||||
TimeOffset = long.Parse(offset),
|
||||
When = time,
|
||||
@ -859,7 +874,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
|
||||
// update the total stats
|
||||
_servers[serverId].ServerStatistics.TotalKills += 1;
|
||||
|
||||
|
||||
// this happens when the round has changed
|
||||
if (attackerStats.SessionScore == 0)
|
||||
{
|
||||
@ -871,18 +886,28 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
victimStats.LastScore = 0;
|
||||
}
|
||||
|
||||
attackerStats.SessionScore = attacker.Score;
|
||||
victimStats.SessionScore = victim.Score;
|
||||
var estimatedAttackerScore = attacker.CurrentServer.GameName != Server.Game.CSGO
|
||||
? attacker.Score
|
||||
: attackerStats.SessionKills * 50;
|
||||
var estimatedVictimScore = attacker.CurrentServer.GameName != Server.Game.CSGO
|
||||
? victim.Score
|
||||
: victimStats.SessionKills * 50;
|
||||
|
||||
attackerStats.SessionScore = estimatedAttackerScore;
|
||||
victimStats.SessionScore = estimatedVictimScore;
|
||||
|
||||
attacker.SetAdditionalProperty(ESTIMATED_SCORE, estimatedAttackerScore);
|
||||
victim.SetAdditionalProperty(ESTIMATED_SCORE, estimatedVictimScore);
|
||||
|
||||
// calculate for the clients
|
||||
CalculateKill(attackerStats, victimStats);
|
||||
CalculateKill(attackerStats, victimStats, attacker, victim);
|
||||
// this should fix the negative SPM
|
||||
// updates their last score after being calculated
|
||||
attackerStats.LastScore = attacker.Score;
|
||||
victimStats.LastScore = victim.Score;
|
||||
attackerStats.LastScore = estimatedAttackerScore;
|
||||
victimStats.LastScore = estimatedVictimScore;
|
||||
|
||||
// show encouragement/discouragement
|
||||
string streakMessage = (attackerStats.ClientId != victimStats.ClientId)
|
||||
var streakMessage = (attackerStats.ClientId != victimStats.ClientId)
|
||||
? StreakMessage.MessageOnStreak(attackerStats.KillStreak, attackerStats.DeathStreak)
|
||||
: StreakMessage.MessageOnStreak(-1, -1);
|
||||
|
||||
@ -1227,7 +1252,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
/// </summary>
|
||||
/// <param name="attackerStats">Stats of the attacker</param>
|
||||
/// <param name="victimStats">Stats of the victim</param>
|
||||
public void CalculateKill(EFClientStatistics attackerStats, EFClientStatistics victimStats)
|
||||
public void CalculateKill(EFClientStatistics attackerStats, EFClientStatistics victimStats,
|
||||
EFClient attacker, EFClient victim)
|
||||
{
|
||||
bool suicide = attackerStats.ClientId == victimStats.ClientId;
|
||||
|
||||
@ -1246,43 +1272,12 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
victimStats.KillStreak = 0;
|
||||
|
||||
// process the attacker's stats after the kills
|
||||
attackerStats = UpdateStats(attackerStats);
|
||||
|
||||
#region DEPRECATED
|
||||
|
||||
/* var validAttackerLobbyRatings = Servers[attackerStats.ServerId].PlayerStats
|
||||
.Where(cs => cs.Value.ClientId != attackerStats.ClientId)
|
||||
.Where(cs =>
|
||||
Servers[attackerStats.ServerId].IsTeamBased ?
|
||||
cs.Value.Team != attackerStats.Team :
|
||||
cs.Value.Team != IW4Info.Team.Spectator)
|
||||
.Where(cs => cs.Value.Team != IW4Info.Team.Spectator);
|
||||
|
||||
double attackerLobbyRating = validAttackerLobbyRatings.Count() > 0 ?
|
||||
validAttackerLobbyRatings.Average(cs => cs.Value.EloRating) :
|
||||
attackerStats.EloRating;
|
||||
|
||||
var validVictimLobbyRatings = Servers[victimStats.ServerId].PlayerStats
|
||||
.Where(cs => cs.Value.ClientId != victimStats.ClientId)
|
||||
.Where(cs =>
|
||||
Servers[attackerStats.ServerId].IsTeamBased ?
|
||||
cs.Value.Team != victimStats.Team :
|
||||
cs.Value.Team != IW4Info.Team.Spectator)
|
||||
.Where(cs => cs.Value.Team != IW4Info.Team.Spectator);
|
||||
|
||||
double victimLobbyRating = validVictimLobbyRatings.Count() > 0 ?
|
||||
validVictimLobbyRatings.Average(cs => cs.Value.EloRating) :
|
||||
victimStats.EloRating;*/
|
||||
|
||||
#endregion
|
||||
attackerStats = UpdateStats(attackerStats, attacker);
|
||||
|
||||
// calculate elo
|
||||
double attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) -
|
||||
var attackerEloDifference = Math.Log(Math.Max(1, victimStats.EloRating)) -
|
||||
Math.Log(Math.Max(1, attackerStats.EloRating));
|
||||
double winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E));
|
||||
|
||||
// double victimEloDifference = Math.Log(Math.Max(1, attackerStats.EloRating)) - Math.Log(Math.Max(1, victimStats.EloRating));
|
||||
// double lossPercentage = 1.0 / (1 + Math.Pow(10, victimEloDifference/ Math.E));
|
||||
var winPercentage = 1.0 / (1 + Math.Pow(10, attackerEloDifference / Math.E));
|
||||
|
||||
attackerStats.EloRating += 6.0 * (1 - winPercentage);
|
||||
victimStats.EloRating -= 6.0 * (1 - winPercentage);
|
||||
@ -1302,7 +1297,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
/// </summary>
|
||||
/// <param name="clientStats">Client statistics</param>
|
||||
/// <returns></returns>
|
||||
private EFClientStatistics UpdateStats(EFClientStatistics clientStats)
|
||||
private EFClientStatistics UpdateStats(EFClientStatistics clientStats, EFClient client)
|
||||
{
|
||||
// prevent NaN or inactive time lowering SPM
|
||||
if ((DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0 < 0.01 ||
|
||||
@ -1314,10 +1309,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
return clientStats;
|
||||
}
|
||||
|
||||
double timeSinceLastCalc = (DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0;
|
||||
double timeSinceLastActive = (DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0;
|
||||
var timeSinceLastCalc = (DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0;
|
||||
|
||||
int scoreDifference = 0;
|
||||
var scoreDifference = 0;
|
||||
// this means they've been tking or suicide and is the only time they can have a negative SPM
|
||||
if (clientStats.RoundScore < 0)
|
||||
{
|
||||
@ -1329,17 +1323,17 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
scoreDifference = clientStats.RoundScore - clientStats.LastScore;
|
||||
}
|
||||
|
||||
double killSPM = scoreDifference / timeSinceLastCalc;
|
||||
double spmMultiplier = 2.934 *
|
||||
var killSpm = scoreDifference / timeSinceLastCalc;
|
||||
var spmMultiplier = 2.934 *
|
||||
Math.Pow(
|
||||
_servers[clientStats.ServerId]
|
||||
.TeamCount((IW4Info.Team) clientStats.Team == IW4Info.Team.Allies
|
||||
? IW4Info.Team.Axis
|
||||
: IW4Info.Team.Allies), -0.454);
|
||||
killSPM *= Math.Max(1, spmMultiplier);
|
||||
killSpm *= Math.Max(1, spmMultiplier);
|
||||
|
||||
// update this for ac tracking
|
||||
clientStats.SessionSPM = killSPM;
|
||||
clientStats.SessionSPM = clientStats.SessionScore / Math.Max(1, client.ConnectionLength / 60.0);
|
||||
|
||||
// calculate how much the KDR should weigh
|
||||
// 1.637 is a Eddie-Generated number that weights the KDR nicely
|
||||
@ -1358,7 +1352,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
double SPMAgainstPlayWeight = timeSinceLastCalc / Math.Min(600, (totalPlayTime / 60.0));
|
||||
|
||||
// calculate the new weight against average times the weight against play time
|
||||
clientStats.SPM = (killSPM * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight));
|
||||
clientStats.SPM = (killSpm * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight));
|
||||
|
||||
if (clientStats.SPM < 0)
|
||||
{
|
||||
@ -1373,7 +1367,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
if (double.IsNaN(clientStats.SPM) || double.IsNaN(clientStats.Skill))
|
||||
{
|
||||
_log.LogWarning("clientStats SPM/Skill NaN {@killInfo}",
|
||||
new {killSPM, KDRWeight, totalPlayTime, SPMAgainstPlayWeight, clientStats, scoreDifference});
|
||||
new {killSPM = killSpm, KDRWeight, totalPlayTime, SPMAgainstPlayWeight, clientStats, scoreDifference});
|
||||
clientStats.SPM = 0;
|
||||
clientStats.Skill = 0;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -83,6 +83,7 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
await Manager.Sync(S);
|
||||
break;
|
||||
case GameEvent.EventType.MapEnd:
|
||||
Manager.ResetKillstreaks(S);
|
||||
await Manager.Sync(S);
|
||||
break;
|
||||
case GameEvent.EventType.Command:
|
||||
|
@ -17,7 +17,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.3.19.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.6.29.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -20,7 +20,7 @@
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.3.19.1" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.6.29.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -61,7 +61,7 @@ namespace SharedLibraryCore
|
||||
Client ??= new EFClient()
|
||||
{
|
||||
ClientId = -1,
|
||||
Level = EFClient.Permission.User,
|
||||
Level = EFClient.Permission.Banned,
|
||||
CurrentAlias = new EFAlias() { Name = "Webfront Guest" }
|
||||
};
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace SharedLibraryCore.Commands
|
||||
{
|
||||
public class CommandProcessing
|
||||
{
|
||||
public static async Task<Command> ValidateCommand(GameEvent E, ApplicationConfiguration appConfig)
|
||||
public static async Task<Command> ValidateCommand(GameEvent E, ApplicationConfiguration appConfig, CommandConfiguration commandConfig)
|
||||
{
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
var Manager = E.Owner.Manager;
|
||||
@ -40,7 +40,11 @@ namespace SharedLibraryCore.Commands
|
||||
|
||||
C.IsBroadcast = isBroadcast;
|
||||
|
||||
if (!C.AllowImpersonation && E.ImpersonationOrigin != null)
|
||||
var allowImpersonation = commandConfig?.Commands?.ContainsKey(C.GetType().Name) ?? false
|
||||
? commandConfig.Commands[C.GetType().Name].AllowImpersonation
|
||||
: C.AllowImpersonation;
|
||||
|
||||
if (!allowImpersonation && E.ImpersonationOrigin != null)
|
||||
{
|
||||
E.ImpersonationOrigin.Tell(loc["COMMANDS_RUN_AS_FAIL"]);
|
||||
throw new CommandException($"Command {C.Name} cannot be run as another client");
|
||||
|
@ -1359,13 +1359,13 @@ namespace SharedLibraryCore.Commands
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
var Response = await E.Owner.ExecuteCommandAsync(E.Data.Trim());
|
||||
foreach (string S in Response)
|
||||
var response = await E.Owner.ExecuteCommandAsync(E.Data.Trim());
|
||||
foreach (var item in response)
|
||||
{
|
||||
E.Origin.Tell(S);
|
||||
E.Origin.Tell(item);
|
||||
}
|
||||
|
||||
if (Response.Length == 0)
|
||||
if (response.Length == 0)
|
||||
{
|
||||
E.Origin.Tell(_translationLookup["COMMANDS_RCON_SUCCESS"]);
|
||||
}
|
||||
|
@ -146,6 +146,7 @@ namespace SharedLibraryCore.Configuration
|
||||
[UIHint("ServerConfiguration")]
|
||||
public ServerConfiguration[] Servers { get; set; }
|
||||
|
||||
[ConfigurationIgnore] public int MinimumNameLength { get; set; } = 3;
|
||||
[ConfigurationIgnore] public string Id { get; set; }
|
||||
[ConfigurationIgnore] public string SubscriptionId { get; set; }
|
||||
[ConfigurationIgnore] public MapConfiguration[] Maps { get; set; }
|
||||
|
@ -65,7 +65,7 @@ namespace SharedLibraryCore.Configuration
|
||||
{
|
||||
RConParserVersion = rconParsers.FirstOrDefault(_parser => _parser.Name == selection.Item2)?.Version;
|
||||
|
||||
if (selection.Item1 > 0 && !rconParsers[selection.Item1 - 1].CanGenerateLogPath)
|
||||
if (selection.Item1 > 0 && !rconParsers[selection.Item1].CanGenerateLogPath)
|
||||
{
|
||||
Console.WriteLine(loc["SETUP_SERVER_NO_LOG"]);
|
||||
ManualLogPath = Utilities.PromptString(loc["SETUP_SERVER_LOG_PATH"]);
|
||||
|
@ -28,5 +28,6 @@ namespace SharedLibraryCore.Dtos
|
||||
public string LastConnectionText => (DateTime.UtcNow - LastConnection).HumanizeForCurrentCulture();
|
||||
public IDictionary<int, long> LinkedAccounts { get; set; }
|
||||
public MetaType? MetaFilterType { get; set; }
|
||||
public double? ZScore { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -23,5 +23,21 @@ namespace SharedLibraryCore.Dtos
|
||||
public string IPAddress { get; set; }
|
||||
public bool IsPasswordProtected { get; set; }
|
||||
public string Endpoint => $"{IPAddress}:{Port}";
|
||||
|
||||
public double? LobbyZScore
|
||||
{
|
||||
get
|
||||
{
|
||||
var valid = Players.Where(player => player.ZScore != null && player.ZScore != 0)
|
||||
.ToList();
|
||||
|
||||
if (!valid.Any())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Math.Round(valid.Select(player => player.ZScore.Value).Average(), 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -151,6 +151,14 @@ namespace SharedLibraryCore
|
||||
/// a client's permission was changed
|
||||
/// </summary>
|
||||
ChangePermission = 111,
|
||||
/// <summary>
|
||||
/// client logged in to webfront
|
||||
/// </summary>
|
||||
Login = 112,
|
||||
/// <summary>
|
||||
/// client logged out of webfront
|
||||
/// </summary>
|
||||
Logout = 113,
|
||||
|
||||
// events "generated" by IW4MAdmin
|
||||
/// <summary>
|
||||
|
@ -71,6 +71,12 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// eg: COD, Source
|
||||
/// </summary>
|
||||
string RConEngine { get; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates that the game does not log to the mods folder (when mod is loaded),
|
||||
/// but rather always to the fs_basegame directory
|
||||
/// </summary>
|
||||
bool IsOneLog { get; }
|
||||
|
||||
/// <summary>
|
||||
/// retrieves the value of given dvar key if it exists in the override dict
|
||||
|
@ -457,9 +457,10 @@ namespace SharedLibraryCore.Database.Models
|
||||
|
||||
using (LogContext.PushProperty("Server", CurrentServer?.ToString()))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Name) || CleanedName.Replace(" ", "").Length < 3)
|
||||
if (string.IsNullOrWhiteSpace(Name) || CleanedName.Replace(" ", "").Length <
|
||||
(CurrentServer?.Manager?.GetApplicationSettings()?.Configuration()?.MinimumNameLength ?? 3))
|
||||
{
|
||||
Utilities.DefaultLogger.LogInformation("Kicking {client} because their name is too short", ToString());
|
||||
Utilities.DefaultLogger.LogInformation("Kicking {Client} because their name is too short", ToString());
|
||||
Kick(loc["SERVER_KICK_MINNAME"], Utilities.IW4MAdminClient(CurrentServer));
|
||||
return false;
|
||||
}
|
||||
@ -468,14 +469,14 @@ namespace SharedLibraryCore.Database.Models
|
||||
.DisallowedClientNames
|
||||
?.Any(_name => Regex.IsMatch(Name, _name)) ?? false)
|
||||
{
|
||||
Utilities.DefaultLogger.LogInformation("Kicking {client} because their name is not allowed", ToString());
|
||||
Utilities.DefaultLogger.LogInformation("Kicking {Client} because their name is not allowed", ToString());
|
||||
Kick(loc["SERVER_KICK_GENERICNAME"], Utilities.IW4MAdminClient(CurrentServer));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Name.Where(c => char.IsControl(c)).Count() > 0)
|
||||
{
|
||||
Utilities.DefaultLogger.LogInformation("Kicking {client} because their name contains control characters", ToString());
|
||||
Utilities.DefaultLogger.LogInformation("Kicking {Client} because their name contains control characters", ToString());
|
||||
Kick(loc["SERVER_KICK_CONTROLCHARS"], Utilities.IW4MAdminClient(CurrentServer));
|
||||
return false;
|
||||
}
|
||||
@ -487,7 +488,7 @@ namespace SharedLibraryCore.Database.Models
|
||||
CurrentServer.GetClientsAsList().Count <= CurrentServer.MaxClients &&
|
||||
CurrentServer.MaxClients != 0)
|
||||
{
|
||||
Utilities.DefaultLogger.LogInformation("Kicking {client} their spot is reserved", ToString());
|
||||
Utilities.DefaultLogger.LogInformation("Kicking {Client} their spot is reserved", ToString());
|
||||
Kick(loc["SERVER_KICK_SLOT_IS_RESERVED"], Utilities.IW4MAdminClient(CurrentServer));
|
||||
return false;
|
||||
}
|
||||
|
@ -107,10 +107,10 @@ namespace SharedLibraryCore
|
||||
}
|
||||
if (literal)
|
||||
{
|
||||
return GetClientsAsList().Where(p => p.Name?.ToLower() == pName.ToLower()).ToList();
|
||||
return GetClientsAsList().Where(p => p.Name?.StripColors()?.ToLower() == pName.ToLower()).ToList();
|
||||
}
|
||||
|
||||
return GetClientsAsList().Where(p => (p.Name?.ToLower() ?? "").Contains(pName.ToLower())).ToList();
|
||||
return GetClientsAsList().Where(p => (p.Name?.StripColors()?.ToLower() ?? "").Contains(pName.ToLower())).ToList();
|
||||
}
|
||||
|
||||
virtual public Task<bool> ProcessUpdatesAsync(CancellationToken cts) => (Task<bool>)Task.CompletedTask;
|
||||
|
@ -66,7 +66,23 @@ namespace SharedLibraryCore.Services
|
||||
CurrentValue = ((EFClient.Permission)e.Extra).ToString()
|
||||
};
|
||||
break;
|
||||
default:
|
||||
case GameEvent.EventType.Login:
|
||||
change = new EFChangeHistory()
|
||||
{
|
||||
OriginEntityId = e.Origin.ClientId,
|
||||
Comment = "Logged In To Webfront",
|
||||
TypeOfChange = EFChangeHistory.ChangeType.Command,
|
||||
CurrentValue = e.Data
|
||||
};
|
||||
break;
|
||||
case GameEvent.EventType.Logout:
|
||||
change = new EFChangeHistory()
|
||||
{
|
||||
OriginEntityId = e.Origin.ClientId,
|
||||
Comment = "Logged Out of Webfront",
|
||||
TypeOfChange = EFChangeHistory.ChangeType.Command,
|
||||
CurrentValue = e.Data
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1,66 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||
<Version>2020.11.18.1</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
<PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<PackageTags>IW4MAdmin</PackageTags>
|
||||
<RepositoryUrl>https://github.com/RaidMax/IW4M-Admin/</RepositoryUrl>
|
||||
<PackageProjectUrl>https://www.raidmax.org/IW4MAdmin/</PackageProjectUrl>
|
||||
<Copyright>2020</Copyright>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Description>Shared Library for IW4MAdmin</Description>
|
||||
<PackageVersion>2020.11.18.1</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="9.1.3" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
|
||||
<PackageReference Include="Humanizer.Core.ru" Version="2.8.26" />
|
||||
<PackageReference Include="Humanizer.Core.de" Version="2.8.26" />
|
||||
<PackageReference Include="Humanizer.Core.es" Version="2.8.26" />
|
||||
<PackageReference Include="Humanizer.Core.pt" Version="2.8.26" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.7">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.7" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Npgsql" Version="4.1.4" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.2" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="if not exist "$(ProjectDir)..\BUILD" (
if $(ConfigurationName) == Debug (
md "$(ProjectDir)..\BUILD"
)
)
if not exist "$(ProjectDir)..\BUILD\Plugins" (
if $(ConfigurationName) == Debug (
md "$(ProjectDir)..\BUILD\Plugins"
)
)" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
@ -4,7 +4,7 @@
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||
<Version>2021.3.5.1</Version>
|
||||
<Version>2021.6.29.1</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
@ -19,7 +19,7 @@
|
||||
<IsPackable>true</IsPackable>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Description>Shared Library for IW4MAdmin</Description>
|
||||
<PackageVersion>2021.3.19.1</PackageVersion>
|
||||
<PackageVersion>2021.6.29.1</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
||||
@ -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.1" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.Data" Version="1.0.3" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
@ -1,5 +1,4 @@
|
||||
|
||||
using Humanizer;
|
||||
using Humanizer;
|
||||
using Humanizer.Localisation;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Dtos.Meta;
|
||||
@ -323,7 +322,16 @@ namespace SharedLibraryCore
|
||||
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(":", "");
|
||||
var match = Regex.Match(str, @"^STEAM_(\d):(\d):(\d+)$");
|
||||
if (match.Success)
|
||||
{
|
||||
var x = int.Parse(match.Groups[1].ToString());
|
||||
var y = int.Parse(match.Groups[2].ToString());
|
||||
var z = long.Parse(match.Groups[3].ToString());
|
||||
|
||||
return z * 2 + 0x0110000100000000 + y;
|
||||
}
|
||||
|
||||
str = str.Substring(0, Math.Min(str.Length, 19));
|
||||
var parsableAsNumber = Regex.Match(str, @"([A-F]|[a-f]|[0-9])+").Value;
|
||||
|
||||
@ -917,6 +925,18 @@ namespace SharedLibraryCore
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<T> WithTimeout<T>(this Task<T> task, TimeSpan timeout)
|
||||
{
|
||||
await Task.WhenAny(task, Task.Delay(timeout));
|
||||
return await task;
|
||||
}
|
||||
|
||||
public static async Task WithTimeout(this Task task, TimeSpan timeout)
|
||||
{
|
||||
await Task.WhenAny(task, Task.Delay(timeout));
|
||||
}
|
||||
|
||||
|
||||
public static bool ShouldHideLevel(this Permission perm) => perm == Permission.Flagged;
|
||||
|
||||
/// <summary>
|
||||
|
@ -119,6 +119,14 @@ namespace WebfrontCore.Controllers.API
|
||||
var claimsIdentity = new ClaimsIdentity(claims, "login");
|
||||
var claimsPrinciple = new ClaimsPrincipal(claimsIdentity);
|
||||
await SignInAsync(claimsPrinciple);
|
||||
|
||||
Manager.AddEvent(new GameEvent()
|
||||
{
|
||||
Origin = privilegedClient,
|
||||
Type = GameEvent.EventType.Login,
|
||||
Owner = Manager.GetServers().First(),
|
||||
Data = HttpContext.Connection.RemoteIpAddress.ToString()
|
||||
});
|
||||
|
||||
return Ok();
|
||||
}
|
||||
@ -137,6 +145,17 @@ namespace WebfrontCore.Controllers.API
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> LogoutAsync()
|
||||
{
|
||||
if (Authorized)
|
||||
{
|
||||
Manager.AddEvent(new GameEvent()
|
||||
{
|
||||
Origin = Client,
|
||||
Type = GameEvent.EventType.Logout,
|
||||
Owner = Manager.GetServers().First(),
|
||||
Data = HttpContext.Connection.RemoteIpAddress.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
return Ok();
|
||||
|
@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -50,6 +51,14 @@ namespace WebfrontCore.Controllers
|
||||
var claimsIdentity = new ClaimsIdentity(claims, "login");
|
||||
var claimsPrinciple = new ClaimsPrincipal(claimsIdentity);
|
||||
await SignInAsync(claimsPrinciple);
|
||||
|
||||
Manager.AddEvent(new GameEvent()
|
||||
{
|
||||
Origin = privilegedClient,
|
||||
Type = GameEvent.EventType.Login,
|
||||
Owner = Manager.GetServers().First(),
|
||||
Data = HttpContext.Connection.RemoteIpAddress.ToString()
|
||||
});
|
||||
|
||||
return Ok();
|
||||
}
|
||||
@ -66,6 +75,17 @@ namespace WebfrontCore.Controllers
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> LogoutAsync()
|
||||
{
|
||||
if (Authorized)
|
||||
{
|
||||
Manager.AddEvent(new GameEvent()
|
||||
{
|
||||
Origin = Client,
|
||||
Type = GameEvent.EventType.Logout,
|
||||
Owner = Manager.GetServers().First(),
|
||||
Data = HttpContext.Connection.RemoteIpAddress.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return RedirectToAction("Index", "Home");
|
||||
}
|
||||
|
@ -183,6 +183,7 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
||||
.Include(s => s.HitOrigin)
|
||||
.Include(s => s.HitDestination)
|
||||
.Include(s => s.CurrentViewAngle)
|
||||
.Include(s => s.Server)
|
||||
.Include(s => s.PredictedViewAngles)
|
||||
.ThenInclude(_angles => _angles.Vector)
|
||||
.OrderBy(s => s.When)
|
||||
|
@ -3,6 +3,7 @@ using SharedLibraryCore;
|
||||
using SharedLibraryCore.Dtos;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Linq;
|
||||
using Data.Models.Client.Stats;
|
||||
|
||||
namespace WebfrontCore.Controllers
|
||||
{
|
||||
@ -39,7 +40,8 @@ namespace WebfrontCore.Controllers
|
||||
Name = p.Name,
|
||||
ClientId = p.ClientId,
|
||||
Level = p.Level.ToLocalizedLevelName(),
|
||||
LevelInt = (int)p.Level
|
||||
LevelInt = (int)p.Level,
|
||||
ZScore = p.GetAdditionalProperty<EFClientStatistics>(IW4MAdmin.Plugins.Stats.Helpers.StatManager.CLIENT_STATS_KEY)?.ZScore
|
||||
}).ToList(),
|
||||
ChatHistory = s.ChatHistory.ToList(),
|
||||
PlayerHistory = s.ClientHistory.ToArray(),
|
||||
|
@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using WebfrontCore.Middleware;
|
||||
|
||||
|
@ -113,6 +113,7 @@ namespace WebfrontCore
|
||||
services.AddSingleton<IResourceQueryHelper<StatsInfoRequest, AdvancedStatsInfo>, AdvancedClientStatsResourceQueryHelper>();
|
||||
services.AddSingleton(typeof(IDataValueCache<,>), typeof(DataValueCache<,>));
|
||||
// todo: this needs to be handled more gracefully
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ILoggerFactory>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IConfigurationHandlerFactory>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IDatabaseContextFactory>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IAuditInformationRepository>());
|
||||
|
@ -3,6 +3,7 @@ using SharedLibraryCore;
|
||||
using SharedLibraryCore.Dtos;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Data.Models.Client.Stats;
|
||||
using static SharedLibraryCore.Server;
|
||||
|
||||
namespace WebfrontCore.ViewComponents
|
||||
@ -30,7 +31,8 @@ namespace WebfrontCore.ViewComponents
|
||||
ClientId = p.ClientId,
|
||||
Level = p.Level.ToLocalizedLevelName(),
|
||||
LevelInt = (int)p.Level,
|
||||
Tag = p.Tag
|
||||
Tag = p.Tag,
|
||||
ZScore = p.GetAdditionalProperty<EFClientStatistics>(IW4MAdmin.Plugins.Stats.Helpers.StatManager.CLIENT_STATS_KEY)?.ZScore
|
||||
}).ToList(),
|
||||
ChatHistory = s.ChatHistory.ToList(),
|
||||
Online = !s.Throttled,
|
||||
|
@ -22,8 +22,8 @@
|
||||
}
|
||||
</div>
|
||||
<!-- Name/Level Column -->
|
||||
<div class="w-75 d-block d-lg-inline-flex flex-column flex-fill text-center text-lg-left pb-3 pb-lg-0 pt-3 pt-lg-0 pl-3 pr-3 ml-auto mr-auto" style="overflow-wrap: anywhere">
|
||||
<div class="mt-n2 flex-fill d-block d-lg-inline-flex">
|
||||
<div class="w-50 d-block d-lg-inline-flex flex-column flex-fill text-center text-lg-left pb-3 pb-lg-0 pt-3 pt-lg-0 pl-3 pr-3 ml-auto mr-auto" style="overflow-wrap: anywhere">
|
||||
<div class="mt-n2 d-block d-lg-inline-flex @(ViewBag.Authorized ? "" : "flex-fill")">
|
||||
<div id="profile_name" class="client-name h1 mb-0">
|
||||
<color-code value="@Model.Name" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||
</div>
|
||||
@ -35,6 +35,11 @@
|
||||
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
<div class="d-flex flex-row justify-content-start flex-fill flex-column flex-lg-row mr-lg-2 mb-2 mb-md-0">
|
||||
<div class="ip-lookup-profile align-self-center mr-0 mr-lg-2 ml-lg-n1" data-ip="@Model.IPAddress"></div>
|
||||
<div id="ip_lookup_country" class="h4 mb-2 mb-lg-0 align-self-center text-muted"></div>
|
||||
</div>
|
||||
|
||||
<div id="profile_aliases" class="text-muted pt-0 pt-lg-2 pb-2">
|
||||
@foreach (var linked in Model.LinkedAccounts)
|
||||
{
|
||||
|
@ -7,15 +7,16 @@
|
||||
@foreach (var snapshot in Model)
|
||||
{
|
||||
<!-- this is not ideal, but I didn't want to manually write out all the properties-->
|
||||
var snapProperties = Model.First().GetType().GetProperties();
|
||||
var snapProperties = Model.First().GetType().GetProperties().OrderBy(prop => prop.Name);
|
||||
foreach (var prop in snapProperties)
|
||||
{
|
||||
@if ((prop.Name.EndsWith("Id") && prop.Name != "WeaponId") || new[] { "Active", "Client", "PredictedViewAngles" }.Contains(prop.Name))
|
||||
@if ((prop.Name.EndsWith("Id") && prop.Name != "WeaponId" || prop.Name == "Server") || new[] {"Active", "Client", "PredictedViewAngles"}.Contains(prop.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
<span class="text-white">@prop.Name </span> <span>— @prop.GetValue(snapshot).ToString()</span><br />
|
||||
<span class="text-white">@prop.Name </span>
|
||||
<span>— @prop.GetValue(snapshot)?.ToString()?.StripColors()</span><br/>
|
||||
}
|
||||
<div class="w-100 mt-1 mb-1 border-bottom"></div>
|
||||
}
|
||||
|
@ -92,7 +92,7 @@
|
||||
</a>
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
<span class="oi oi-circle-x ml-1 profile-action align-baseline action-kick-button flex-column" data-action="kick" data-action-id="@Model.Players[i].ClientId" aria-hidden="true"></span>
|
||||
<span class="oi oi-circle-x profile-action align-baseline action-kick-button flex-column mt-0" data-action="kick" data-action-id="@Model.Players[i].ClientId" aria-hidden="true"></span>
|
||||
}
|
||||
<br />
|
||||
</div>
|
||||
|
@ -16,8 +16,26 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="text-center col-md-4">@Model.Map</div>
|
||||
<div class="text-center text-md-right col-md-4"><span class="server-clientcount">@Model.ClientCount</span>/<span class="server-maxclients">@Model.MaxClients</span></div>
|
||||
<div class="text-center col-md-4 align-self-center">
|
||||
<span>@Model.Map</span>
|
||||
@if (!string.IsNullOrEmpty(Model.GameType) && Model.GameType.Length > 1)
|
||||
{
|
||||
<span>–</span>
|
||||
<span>@Model.GameType.ToUpper()</span>
|
||||
}
|
||||
</div>
|
||||
<div class="text-center text-md-right col-md-4 d-flex align-self-center justify-content-center justify-content-md-end flex-column-reverse flex-sm-row">
|
||||
@if (Model.LobbyZScore != null)
|
||||
{
|
||||
<div title="@ViewBag.Localization["WEBFRONT_HOME_RATING_DESC"]" class="cursor-help d-flex flex-row-reverse flex-md-row justify-content-center">
|
||||
<span>@(Model.LobbyZScore ?? 0)</span>
|
||||
<span class="oi oi-bolt align-self-center mr-1 ml-1"></span>
|
||||
</div>
|
||||
}
|
||||
<div>
|
||||
<span class="server-clientcount">@Model.ClientCount</span>/<span class="server-maxclients">@Model.MaxClients</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
@ -27,10 +45,10 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<div id="server_clientactivity_@Model.ID" class="bg-dark row server-activity pt-2 pb-2">
|
||||
<div id="server_clientactivity_@Model.ID" class="bg-dark row server-activity @(Model.ClientCount > 0 ? "pt-2 pb-2" : "")">
|
||||
@await Html.PartialAsync("../Server/_ClientActivity", Model)
|
||||
</div>
|
||||
|
||||
<div class="row server-history mb-4">
|
||||
<div class="server-history-row" id="server_history_@Model.ID" data-serverid="@Model.ID" data-clienthistory='@Html.Raw(Json.Serialize(Model.PlayerHistory))' data-online="@Model.Online"></div>
|
||||
</div>
|
||||
</div>
|
@ -437,3 +437,7 @@ div.card {
|
||||
#hitlocation_container {
|
||||
background-color: #141414;
|
||||
}
|
||||
|
||||
.cursor-help {
|
||||
cursor: help;
|
||||
}
|
||||
|
@ -201,3 +201,11 @@
|
||||
background-color: $dark;
|
||||
color: $white !important;
|
||||
}
|
||||
|
||||
.ip-lookup-profile {
|
||||
height: 2.5rem;
|
||||
min-width: 3.0rem;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
$(document).ready(function () {
|
||||
/*
|
||||
Expand alias tab if they have any
|
||||
*/
|
||||
/*
|
||||
Expand alias tab if they have any
|
||||
*/
|
||||
$('#profile_aliases_btn').click(function (e) {
|
||||
const aliases = $('#profile_aliases').text().trim();
|
||||
if (aliases && aliases.length !== 0) {
|
||||
@ -10,6 +10,27 @@
|
||||
}
|
||||
});
|
||||
|
||||
const ipAddresses = $('.ip-lookup-profile');
|
||||
$.each(ipAddresses, function (index, address) {
|
||||
let ip = $(address).data('ip');
|
||||
if (ip.length === 0) {
|
||||
return;
|
||||
}
|
||||
$.get('https://ip2c.org/' + ip, function (result) {
|
||||
const countryCode = result.split(';')[1].toLowerCase();
|
||||
const country = result.split(';')[3];
|
||||
|
||||
if (country === 'Unknown') {
|
||||
return;
|
||||
}
|
||||
|
||||
$('#ip_lookup_country').text(country);
|
||||
if (countryCode !== 'zz' && countryCode !== '') {
|
||||
$(address).css('background-image', `url(https://www.countryflags.io/${countryCode}/flat/64.png)`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* set the end time for initial event query */
|
||||
startAt = $('.loader-data-time').last().data('time');
|
||||
|
||||
@ -35,7 +56,7 @@
|
||||
|
||||
$(this).children().filter('.client-message-prefix').removeClass('oi-chevron-right');
|
||||
$(this).children().filter('.client-message-prefix').addClass('oi-chevron-bottom');
|
||||
|
||||
|
||||
$.get('/Stats/GetMessageAsync', {
|
||||
'serverId': $(this).data('serverid'),
|
||||
'when': $(this).data('when')
|
||||
@ -102,7 +123,7 @@
|
||||
$('#mainModal .modal-body').append(response.city);
|
||||
}
|
||||
if (response.region.length > 0) {
|
||||
$('#mainModal .modal-body').append((response.city.length > 0 ? ', ' : '') + response.region);
|
||||
$('#mainModal .modal-body').append((response.city.length > 0 ? ', ' : '') + response.region);
|
||||
}
|
||||
if (response.country.length > 0) {
|
||||
$('#mainModal .modal-body').append((response.country.length > 0 ? ', ' : '') + response.country);
|
||||
|
Reference in New Issue
Block a user