Compare commits
49 Commits
2021.03.23
...
2021.07.02
Author | SHA1 | Date | |
---|---|---|---|
33a427bb8a | |||
c9d7a957dc | |||
9c6ff6f353 | |||
7444cb6472 | |||
c7e5c9c8dd | |||
0256fc35d2 | |||
0019ed8dde | |||
56aec53e72 | |||
1b773f21c6 | |||
bccbcce3c1 | |||
fc0bed2405 | |||
16cfb33109 | |||
42979dc5ae | |||
95cbc85144 | |||
9cbca390fe | |||
38c0c81451 | |||
af4630ecb9 | |||
dbceb23823 | |||
e628ac0e9e | |||
3a1e8359c2 | |||
c397fd5479 | |||
16e1bbb1b5 | |||
eff1fe237d | |||
b09ce46ff9 | |||
be08d49f0a | |||
b9fb274db6 | |||
9488f754d4 | |||
1595c1fa99 | |||
4d21680d59 | |||
127af98b00 | |||
21a9eb8716 | |||
f1593e2f99 | |||
74dbc3572f | |||
e6d149736a | |||
a034394610 | |||
34e7d69110 | |||
4b686e5fdd | |||
0428453426 | |||
e80e5d6a70 | |||
22cf3081e1 | |||
76a18d9797 | |||
fc13363c9c | |||
f916c51bc0 | |||
21087d6c25 | |||
c84e374274 | |||
e777a68105 | |||
1f9c80e23b | |||
33371a6d28 | |||
d164ef2eab |
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
|
||||
|
@ -48,6 +48,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Integrations\Cod\Integrations.Cod.csproj" />
|
||||
<ProjectReference Include="..\Integrations\Source\Integrations.Source.csproj" />
|
||||
<ProjectReference Include="..\SharedLibraryCore\SharedLibraryCore.csproj">
|
||||
<Private>true</Private>
|
||||
</ProjectReference>
|
||||
|
@ -1,7 +1,7 @@
|
||||
using IW4MAdmin.Application.EventParsers;
|
||||
using IW4MAdmin.Application.Extensions;
|
||||
using IW4MAdmin.Application.Misc;
|
||||
using IW4MAdmin.Application.RconParsers;
|
||||
using IW4MAdmin.Application.RConParsers;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
@ -220,15 +220,15 @@ namespace IW4MAdmin.Application
|
||||
public async Task UpdateServerStates()
|
||||
{
|
||||
// store the server hash code and task for it
|
||||
var runningUpdateTasks = new Dictionary<long, Task>();
|
||||
var runningUpdateTasks = new Dictionary<long, (Task task, CancellationTokenSource tokenSource, DateTime startTime)>();
|
||||
|
||||
while (!_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
// select the server ids that have completed the update task
|
||||
var serverTasksToRemove = runningUpdateTasks
|
||||
.Where(ut => ut.Value.Status == TaskStatus.RanToCompletion ||
|
||||
ut.Value.Status == TaskStatus.Canceled ||
|
||||
ut.Value.Status == TaskStatus.Faulted)
|
||||
.Where(ut => ut.Value.task.Status == TaskStatus.RanToCompletion ||
|
||||
ut.Value.task.Status == TaskStatus.Canceled || // we want to cancel if a task takes longer than 5 minutes
|
||||
ut.Value.task.Status == TaskStatus.Faulted || DateTime.Now - ut.Value.startTime > TimeSpan.FromMinutes(5))
|
||||
.Select(ut => ut.Key)
|
||||
.ToList();
|
||||
|
||||
@ -239,9 +239,14 @@ namespace IW4MAdmin.Application
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
// remove the update tasks as they have completd
|
||||
foreach (long serverId in serverTasksToRemove)
|
||||
// remove the update tasks as they have completed
|
||||
foreach (var serverId in serverTasksToRemove.Where(serverId => runningUpdateTasks.ContainsKey(serverId)))
|
||||
{
|
||||
if (!runningUpdateTasks[serverId].tokenSource.Token.IsCancellationRequested)
|
||||
{
|
||||
runningUpdateTasks[serverId].tokenSource.Cancel();
|
||||
}
|
||||
|
||||
runningUpdateTasks.Remove(serverId);
|
||||
}
|
||||
|
||||
@ -249,11 +254,12 @@ namespace IW4MAdmin.Application
|
||||
var serverIds = Servers.Select(s => s.EndPoint).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
|
||||
foreach (var server in Servers.Where(s => serverIds.Contains(s.EndPoint)))
|
||||
{
|
||||
runningUpdateTasks.Add(server.EndPoint, Task.Run(async () =>
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
runningUpdateTasks.Add(server.EndPoint, (Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await server.ProcessUpdatesAsync(_tokenSource.Token);
|
||||
await server.ProcessUpdatesAsync(_tokenSource.Token).WithWaitCancellation(runningUpdateTasks[server.EndPoint].tokenSource.Token);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
@ -268,7 +274,7 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
server.IsInitialized = true;
|
||||
}
|
||||
}));
|
||||
}, tokenSource.Token), tokenSource, DateTime.Now));
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -3,7 +3,13 @@
|
||||
"Using": [
|
||||
"Serilog.Sinks.File"
|
||||
],
|
||||
"MinimumLevel": "Information",
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"System": "Warning",
|
||||
"Microsoft": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "File",
|
||||
|
15
Application/Configuration/ScriptPluginConfiguration.cs
Normal file
15
Application/Configuration/ScriptPluginConfiguration.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Configuration
|
||||
{
|
||||
public class ScriptPluginConfiguration : Dictionary<string, Dictionary<string, object>>, IBaseConfiguration
|
||||
{
|
||||
public string Name() => nameof(ScriptPluginConfiguration);
|
||||
|
||||
public IBaseConfiguration Generate()
|
||||
{
|
||||
return new ScriptPluginConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"AutoMessagePeriod": 60,
|
||||
"AutoMessages": [
|
||||
"This server uses ^5IW4M Admin v{{VERSION}} ^7get it at ^5raidmax.org/IW4MAdmin",
|
||||
@ -169,6 +169,18 @@
|
||||
"Alias": "Asylum",
|
||||
"Name": "mp_asylum"
|
||||
},
|
||||
{
|
||||
"Alias": "Banzai",
|
||||
"Name": "mp_kwai"
|
||||
},
|
||||
{
|
||||
"Alias": "Battery",
|
||||
"Name": "mp_drum"
|
||||
},
|
||||
{
|
||||
"Alias": "Breach",
|
||||
"Name": "mp_bgate"
|
||||
},
|
||||
{
|
||||
"Alias": "Castle",
|
||||
"Name": "mp_castle"
|
||||
@ -177,6 +189,10 @@
|
||||
"Alias": "Cliffside",
|
||||
"Name": "mp_shrine"
|
||||
},
|
||||
{
|
||||
"Alias": "Corrosion",
|
||||
"Name": "mp_stalingrad"
|
||||
},
|
||||
{
|
||||
"Alias": "Courtyard",
|
||||
"Name": "mp_courtyard"
|
||||
@ -190,60 +206,52 @@
|
||||
"Name": "mp_downfall"
|
||||
},
|
||||
{
|
||||
"Alias": "Hanger",
|
||||
"Alias": "Hangar",
|
||||
"Name": "mp_hangar"
|
||||
},
|
||||
{
|
||||
"Alias": "Makin",
|
||||
"Name": "mp_makin"
|
||||
},
|
||||
{
|
||||
"Alias": "Outskirts",
|
||||
"Name": "mp_outskirts"
|
||||
},
|
||||
{
|
||||
"Alias": "Roundhouse",
|
||||
"Name": "mp_roundhouse"
|
||||
},
|
||||
{
|
||||
"Alias": "Upheaval",
|
||||
"Name": "mp_suburban"
|
||||
},
|
||||
{
|
||||
"Alias": "Knee Deep",
|
||||
"Name": "mp_kneedeep"
|
||||
},
|
||||
{
|
||||
"Alias": "Makin",
|
||||
"Name": "mp_makin"
|
||||
},
|
||||
{
|
||||
"Alias": "Makin Day",
|
||||
"Name": "mp_makin_day"
|
||||
},
|
||||
{
|
||||
"Alias": "Nightfire",
|
||||
"Name": "mp_nachtfeuer"
|
||||
},
|
||||
{
|
||||
"Alias": "Outskirts",
|
||||
"Name": "mp_outskirts"
|
||||
},
|
||||
{
|
||||
"Alias": "Revolution",
|
||||
"Name": "mp_vodka"
|
||||
},
|
||||
{
|
||||
"Alias": "Roundhouse",
|
||||
"Name": "mp_roundhouse"
|
||||
},
|
||||
{
|
||||
"Alias": "Seelow",
|
||||
"Name": "mp_seelow"
|
||||
},
|
||||
{
|
||||
"Alias": "Station",
|
||||
"Name": "mp_subway"
|
||||
},
|
||||
{
|
||||
"Alias": "Banzai",
|
||||
"Name": "mp_kwai"
|
||||
},
|
||||
{
|
||||
"Alias": "Corrosion",
|
||||
"Name": "mp_stalingrad"
|
||||
},
|
||||
{
|
||||
"Alias": "Sub Pens",
|
||||
"Name": "mp_docks"
|
||||
},
|
||||
{
|
||||
"Alias": "Battery",
|
||||
"Name": "mp_drum"
|
||||
},
|
||||
{
|
||||
"Alias": "Breach",
|
||||
"Name": "mp_bgate"
|
||||
},
|
||||
{
|
||||
"Alias": "Revolution",
|
||||
"Name": "mp_vodka"
|
||||
"Alias": "Upheaval",
|
||||
"Name": "mp_suburban"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -687,6 +695,10 @@
|
||||
{
|
||||
"Alias": "Terminal",
|
||||
"Name": "mp_terminal_cls"
|
||||
},
|
||||
{
|
||||
"Alias": "Rust",
|
||||
"Name": "mp_rust"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -846,6 +858,425 @@
|
||||
"Name": "zm_transit"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "IW6",
|
||||
"Maps": [
|
||||
{
|
||||
"Alias": "Prision Break",
|
||||
"Name": "mp_prisonbreak"
|
||||
},
|
||||
{
|
||||
"Alias": "Octane",
|
||||
"Name": "mp_dart"
|
||||
},
|
||||
{
|
||||
"Alias": "Tremor",
|
||||
"Name": "mp_lonestar"
|
||||
},
|
||||
{
|
||||
"Alias": "Freight",
|
||||
"Name": "mp_frag"
|
||||
},
|
||||
{
|
||||
"Alias": "Whiteout",
|
||||
"Name": "mp_snow"
|
||||
},
|
||||
{
|
||||
"Alias": "Stormfront",
|
||||
"Name": "mp_fahrenheit"
|
||||
},
|
||||
{
|
||||
"Alias": "Siege",
|
||||
"Name": "mp_hashima"
|
||||
},
|
||||
{
|
||||
"Alias": "Warhawk",
|
||||
"Name": "mp_warhawk"
|
||||
},
|
||||
{
|
||||
"Alias": "Sovereign",
|
||||
"Name": "mp_sovereign"
|
||||
},
|
||||
{
|
||||
"Alias": "Overload",
|
||||
"Name": "mp_zebra"
|
||||
},
|
||||
{
|
||||
"Alias": "Stonehaven",
|
||||
"Name": "mp_skeleton"
|
||||
},
|
||||
{
|
||||
"Alias": "Chasm",
|
||||
"Name": "mp_chasm"
|
||||
},
|
||||
{
|
||||
"Alias": "Flooded",
|
||||
"Name": "mp_flooded"
|
||||
},
|
||||
{
|
||||
"Alias": "Strikezone",
|
||||
"Name": "mp_strikezone"
|
||||
},
|
||||
{
|
||||
"Alias": "Free Fall",
|
||||
"Name": "mp_descent_new"
|
||||
},
|
||||
{
|
||||
"Alias": "Unearthed",
|
||||
"Name": "mp_dome_ns"
|
||||
},
|
||||
{
|
||||
"Alias": "Collision",
|
||||
"Name": "mp_ca_impact"
|
||||
},
|
||||
{
|
||||
"Alias": "Behemoth",
|
||||
"Name": "mp_ca_behemoth"
|
||||
},
|
||||
{
|
||||
"Alias": "Ruins",
|
||||
"Name": "mp_battery3"
|
||||
},
|
||||
{
|
||||
"Alias": "Pharaoh",
|
||||
"Name": "mp_dig"
|
||||
},
|
||||
{
|
||||
"Alias": "Favela",
|
||||
"Name": "mp_favela_iw6"
|
||||
},
|
||||
{
|
||||
"Alias": "Mutiny",
|
||||
"Name": "mp_pirate"
|
||||
},
|
||||
{
|
||||
"Alias": "Departed",
|
||||
"Name": "mp_zulu"
|
||||
},
|
||||
{
|
||||
"Alias": "Dynasty",
|
||||
"Name": "mp_conflict"
|
||||
},
|
||||
{
|
||||
"Alias": "Goldrush",
|
||||
"Name": "mp_mine"
|
||||
},
|
||||
{
|
||||
"Alias": "Showtime",
|
||||
"Name": "mp_shipment_ns"
|
||||
},
|
||||
{
|
||||
"Alias": "Subzero",
|
||||
"Name": "mp_zerosub"
|
||||
},
|
||||
{
|
||||
"Alias": "Ignition",
|
||||
"Name": "mp_boneyard_ns"
|
||||
},
|
||||
{
|
||||
"Alias": "Containment",
|
||||
"Name": "mp_ca_red_river"
|
||||
},
|
||||
{
|
||||
"Alias": "Bayview",
|
||||
"Name": "mp_ca_rumble"
|
||||
},
|
||||
{
|
||||
"Alias": "Fog",
|
||||
"Name": "mp_swamp"
|
||||
},
|
||||
{
|
||||
"Alias": "Point of Contact",
|
||||
"Name": "mp_alien_town"
|
||||
},
|
||||
{
|
||||
"Alias": "Nightfall",
|
||||
"Name": "mp_alien_armory"
|
||||
},
|
||||
{
|
||||
"Alias": "Mayday",
|
||||
"Name": "mp_alien_beacon"
|
||||
},
|
||||
{
|
||||
"Alias": "Awakening",
|
||||
"Name": "mp_alien_dlc3"
|
||||
},
|
||||
{
|
||||
"Alias": "Exodus",
|
||||
"Name": "mp_alien_last"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Game": "SHG1",
|
||||
"Maps": [
|
||||
{
|
||||
"Alias": "Ascend",
|
||||
"Name": "mp_refraction"
|
||||
},
|
||||
{
|
||||
"Alias": "Bio Lab",
|
||||
"Name": "mp_lab2"
|
||||
},
|
||||
{
|
||||
"Alias": "Comeback",
|
||||
"Name": "mp_comeback"
|
||||
},
|
||||
{
|
||||
"Alias": "Defender",
|
||||
"Name": "mp_laser2"
|
||||
},
|
||||
{
|
||||
"Alias": "Detroit",
|
||||
"Name": "mp_detroit"
|
||||
},
|
||||
{
|
||||
"Alias": "Greenband",
|
||||
"Name": "mp_greenband"
|
||||
},
|
||||
{
|
||||
"Alias": "Horizon",
|
||||
"Name": "mp_levity"
|
||||
},
|
||||
{
|
||||
"Alias": "Instinct",
|
||||
"Name": "mp_instinct"
|
||||
},
|
||||
{
|
||||
"Alias": "Recovery",
|
||||
"Name": "mp_recovery"
|
||||
},
|
||||
{
|
||||
"Alias": "Retreat",
|
||||
"Name": "mp_venus"
|
||||
},
|
||||
{
|
||||
"Alias": "Riot",
|
||||
"Name": "mp_prison"
|
||||
},
|
||||
{
|
||||
"Alias": "Solar",
|
||||
"Name": "mp_solar"
|
||||
},
|
||||
{
|
||||
"Alias": "Terrace",
|
||||
"Name": "mp_terrace"
|
||||
},
|
||||
{
|
||||
"Alias": "Atlas Gorge",
|
||||
"Name": "mp_dam"
|
||||
},
|
||||
{
|
||||
"Alias": "Chop Shop",
|
||||
"Name": "mp_spark"
|
||||
},
|
||||
{
|
||||
"Alias": "Climate",
|
||||
"Name": "mp_climate_3"
|
||||
},
|
||||
{
|
||||
"Alias": "Compound",
|
||||
"Name": "mp_sector17"
|
||||
},
|
||||
{
|
||||
"Alias": "Core",
|
||||
"Name": "mp_lost"
|
||||
},
|
||||
{
|
||||
"Alias": "Drift",
|
||||
"Name": "mp_torqued"
|
||||
},
|
||||
{
|
||||
"Alias": "Fracture",
|
||||
"Name": "mp_fracture"
|
||||
},
|
||||
{
|
||||
"Alias": "Kremlin",
|
||||
"Name": "mp_kremlin"
|
||||
},
|
||||
{
|
||||
"Alias": "Overload",
|
||||
"Name": "mp_lair"
|
||||
},
|
||||
{
|
||||
"Alias": "Parliament",
|
||||
"Name": "mp_bigben2"
|
||||
},
|
||||
{
|
||||
"Alias": "Perplex",
|
||||
"Name": "mp_perplex_1"
|
||||
},
|
||||
{
|
||||
"Alias": "Quarantine",
|
||||
"Name": "mp_liberty"
|
||||
},
|
||||
{
|
||||
"Alias": "Sideshow",
|
||||
"Name": "mp_clowntown3"
|
||||
},
|
||||
{
|
||||
"Alias": "Site 244",
|
||||
"Name": "mp_blackbox"
|
||||
},
|
||||
{
|
||||
"Alias": "Skyrise",
|
||||
"Name": "mp_highrise2"
|
||||
},
|
||||
{
|
||||
"Alias": "Swarn",
|
||||
"Name": "mp_seoul2"
|
||||
},
|
||||
{
|
||||
"Alias": "Urban",
|
||||
"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": {
|
||||
@ -920,6 +1351,103 @@
|
||||
"artillery": "Precision Airstrike",
|
||||
"player": "",
|
||||
"attach": ""
|
||||
}
|
||||
},
|
||||
"IW3": {
|
||||
"torso_upper": "Upper Torso",
|
||||
"torso_lower": "Lower Torso",
|
||||
"right_leg_upper": "Upper Right Leg",
|
||||
"right_leg_lower": "Lower Right Leg",
|
||||
"right_hand": "Right Hand",
|
||||
"right_foot": "Right Foot",
|
||||
"right_arm_upper": "Upper Right Arm",
|
||||
"right_arm_lower": "Lower Right Arm",
|
||||
"left_leg_upper": "Upper Left Leg",
|
||||
"left_leg_lower": "Lower Left Leg",
|
||||
"left_hand": "Left Hand",
|
||||
"left_foot": "Left Foot",
|
||||
"left_arm_upper": "Upper Left Arm",
|
||||
"left_arm_lower": "Lower Left Arm",
|
||||
"acog": "ACOG Sight",
|
||||
"gl": "Grenade Launcher",
|
||||
"reflex": "Red Dot Sight",
|
||||
"grip": "Grip",
|
||||
"m4": "M4 Carbine",
|
||||
"m40a3": "M40A3",
|
||||
"ak47": "AK-47",
|
||||
"ak74u": "AK-74u",
|
||||
"rpg": "RPG-7",
|
||||
"deserteagle": "Desert Eagle",
|
||||
"deserteaglegold": "Desert Eagle Gold",
|
||||
"m16": "M16A4",
|
||||
"g36c": "G36C",
|
||||
"uzi": "Mini-Uzi",
|
||||
"m60e4": "M60E4",
|
||||
"mp5": "MP5",
|
||||
"barrett": "Barrett .50cal",
|
||||
"mp44": "MP44",
|
||||
"remington700": "R700",
|
||||
"rpd": "RDP",
|
||||
"saw": " M249 SAW",
|
||||
"usp": "USP .45",
|
||||
"winchester1200": "W1200",
|
||||
"concussion": "Stun",
|
||||
"melee": "Knife",
|
||||
"Frag" : "Grenade",
|
||||
"airstrike": "Airstrike",
|
||||
"helicopter": "Attack Helicopter",
|
||||
"player": "",
|
||||
"attach": ""
|
||||
},
|
||||
"T4": {
|
||||
"torso_upper": "Upper Torso",
|
||||
"torso_lower": "Lower Torso",
|
||||
"right_leg_upper": "Upper Right Leg",
|
||||
"right_leg_lower": "Lower Right Leg",
|
||||
"right_hand": "Right Hand",
|
||||
"right_foot": "Right Foot",
|
||||
"right_arm_upper": "Upper Right Arm",
|
||||
"right_arm_lower": "Lower Right Arm",
|
||||
"left_leg_upper": "Upper Left Leg",
|
||||
"left_leg_lower": "Lower Left Leg",
|
||||
"left_hand": "Left Hand",
|
||||
"left_foot": "Left Foot",
|
||||
"left_arm_upper": "Upper Left Arm",
|
||||
"left_arm_lower": "Lower Left Arm",
|
||||
"gl": "Rifle Grenade",
|
||||
"bigammo": "Round Drum",
|
||||
"scoped": "Sniper Scope",
|
||||
"telescopic": "Telescopic Sight",
|
||||
"aperture": "Aperture Sight",
|
||||
"flash": "Flash Hider",
|
||||
"silenced": "Silencer",
|
||||
"molotov": "Molotov Cocktail",
|
||||
"sticky": "N° 74 ST",
|
||||
"m2": "M2 Flamethrower",
|
||||
"artillery": "Artillery Strike",
|
||||
"dog": "Attack Dogs",
|
||||
"colt": "Colt M1911",
|
||||
"357magnum": ".357 Magnum",
|
||||
"walther": "Walther P38",
|
||||
"tokarev": "Tokarev TT-33",
|
||||
"shotgun": "M1897 Trench Gun",
|
||||
"doublebarreledshotgun": "Double-Barreled Shotgun",
|
||||
"mp40": "MP40",
|
||||
"type100smg": "Type 100",
|
||||
"ppsh": "PPSh-41",
|
||||
"svt40": "SVT-40",
|
||||
"gewehr43": "Gewehr 43",
|
||||
"m1garand": "M1 Garand",
|
||||
"stg44": "STG-44",
|
||||
"m1carbine": "M1A1 Carbine",
|
||||
"type99lmg": "Type 99",
|
||||
"bar": "BAR",
|
||||
"dp28": "DP-28",
|
||||
"mg42": "MG42",
|
||||
"fg42": "FG42",
|
||||
"30cal": "Browning M1919",
|
||||
"type99rifle": "Arisaka",
|
||||
"mosinrifle": "Mosin-Nagant",
|
||||
"ptrs41":"PTRS-41"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
private readonly Dictionary<string, (string, Func<string, IEventParserConfiguration, GameEvent, GameEvent>)> _customEventRegistrations;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ApplicationConfiguration _appConfig;
|
||||
private readonly Dictionary<ParserRegex, GameEvent.EventType> _regexMap;
|
||||
private readonly Dictionary<string, GameEvent.EventType> _eventTypeMap;
|
||||
|
||||
public BaseEventParser(IParserRegexFactory parserRegexFactory, ILogger logger, ApplicationConfiguration appConfig)
|
||||
{
|
||||
@ -78,7 +80,28 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
Configuration.Kill.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12);
|
||||
Configuration.Kill.AddMapping(ParserRegex.GroupType.HitLocation, 13);
|
||||
|
||||
Configuration.MapChange.Pattern = @".*InitGame.*";
|
||||
Configuration.MapEnd.Pattern = @".*(?:ExitLevel|ShutdownGame).*";
|
||||
|
||||
Configuration.Time.Pattern = @"^ *(([0-9]+):([0-9]+) |^[0-9]+ )";
|
||||
|
||||
_regexMap = new Dictionary<ParserRegex, GameEvent.EventType>
|
||||
{
|
||||
{Configuration.Say, GameEvent.EventType.Say},
|
||||
{Configuration.Kill, GameEvent.EventType.Kill},
|
||||
{Configuration.MapChange, GameEvent.EventType.MapChange},
|
||||
{Configuration.MapEnd, GameEvent.EventType.MapEnd}
|
||||
};
|
||||
|
||||
_eventTypeMap = new Dictionary<string, GameEvent.EventType>
|
||||
{
|
||||
{"say", GameEvent.EventType.Say},
|
||||
{"sayteam", GameEvent.EventType.Say},
|
||||
{"K", GameEvent.EventType.Kill},
|
||||
{"D", GameEvent.EventType.Damage},
|
||||
{"J", GameEvent.EventType.PreConnect},
|
||||
{"Q", GameEvent.EventType.PreDisconnect},
|
||||
};
|
||||
}
|
||||
|
||||
public IEventParserConfiguration Configuration { get; set; }
|
||||
@ -91,6 +114,28 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
|
||||
public string Name { get; set; } = "Call of Duty";
|
||||
|
||||
private (GameEvent.EventType type, string eventKey) GetEventTypeFromLine(string logLine)
|
||||
{
|
||||
var lineSplit = logLine.Split(';');
|
||||
if (lineSplit.Length > 1)
|
||||
{
|
||||
var type = lineSplit[0];
|
||||
return _eventTypeMap.ContainsKey(type) ? (_eventTypeMap[type], type): (GameEvent.EventType.Unknown, lineSplit[0]);
|
||||
}
|
||||
|
||||
foreach (var (key, value) in _regexMap)
|
||||
{
|
||||
var result = key.PatternMatcher.Match(logLine);
|
||||
if (result.Success)
|
||||
{
|
||||
return (value, null);
|
||||
}
|
||||
}
|
||||
|
||||
return (GameEvent.EventType.Unknown, null);
|
||||
}
|
||||
|
||||
|
||||
public virtual GameEvent GenerateGameEvent(string logLine)
|
||||
{
|
||||
var timeMatch = Configuration.Time.PatternMatcher.Match(logLine);
|
||||
@ -104,7 +149,7 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
.Values
|
||||
.Skip(2)
|
||||
// this converts the timestamp into seconds passed
|
||||
.Select((_value, index) => long.Parse(_value.ToString()) * (index == 0 ? 60 : 1))
|
||||
.Select((value, index) => long.Parse(value.ToString()) * (index == 0 ? 60 : 1))
|
||||
.Sum();
|
||||
|
||||
}
|
||||
@ -114,33 +159,34 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
|
||||
// we want to strip the time from the log line
|
||||
logLine = logLine.Substring(timeMatch.Values.First().Length);
|
||||
logLine = logLine.Substring(timeMatch.Values.First().Length).Trim();
|
||||
}
|
||||
|
||||
string[] lineSplit = logLine.Split(';');
|
||||
string eventType = lineSplit[0];
|
||||
var eventParseResult = GetEventTypeFromLine(logLine);
|
||||
var eventType = eventParseResult.type;
|
||||
|
||||
_logger.LogDebug(logLine);
|
||||
|
||||
if (eventType == "say" || eventType == "sayteam")
|
||||
if (eventType == GameEvent.EventType.Say)
|
||||
{
|
||||
var matchResult = Configuration.Say.PatternMatcher.Match(logLine);
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
string message = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.Message]]
|
||||
.ToString()
|
||||
var message = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.Message]]
|
||||
.Replace("\x15", "")
|
||||
.Trim();
|
||||
|
||||
if (message.Length > 0)
|
||||
{
|
||||
string originIdString = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
|
||||
string originName = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
|
||||
var originIdString = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginNetworkId]];
|
||||
var originName = matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginName]];
|
||||
|
||||
long originId = originIdString.IsBotGuid() ?
|
||||
var originId = originIdString.IsBotGuid() ?
|
||||
originName.GenerateGuidFromString() :
|
||||
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
||||
|
||||
int clientNumber = int.Parse(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
var clientNumber = int.Parse(matchResult.Values[Configuration.Say.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
|
||||
if (message.StartsWith(_appConfig.CommandPrefix) || message.StartsWith(_appConfig.BroadcastCommandPrefix))
|
||||
{
|
||||
@ -172,26 +218,26 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType == "K")
|
||||
if (eventType == GameEvent.EventType.Kill)
|
||||
{
|
||||
var match = Configuration.Kill.PatternMatcher.Match(logLine);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
string originIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
|
||||
string targetIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString();
|
||||
string originName = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
|
||||
string targetName = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetName]].ToString();
|
||||
var originIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]];
|
||||
var targetIdString = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]];
|
||||
var originName = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginName]];
|
||||
var targetName = match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetName]];
|
||||
|
||||
long originId = originIdString.IsBotGuid() ?
|
||||
var originId = originIdString.IsBotGuid() ?
|
||||
originName.GenerateGuidFromString() :
|
||||
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
|
||||
long targetId = targetIdString.IsBotGuid() ?
|
||||
var targetId = targetIdString.IsBotGuid() ?
|
||||
targetName.GenerateGuidFromString() :
|
||||
targetIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
|
||||
|
||||
int originClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
int targetClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
|
||||
var originClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
var targetClientNumber = int.Parse(match.Values[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -206,26 +252,26 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType == "D")
|
||||
if (eventType == GameEvent.EventType.Damage)
|
||||
{
|
||||
var match = Configuration.Damage.PatternMatcher.Match(logLine);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
string originIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
|
||||
string targetIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString();
|
||||
string originName = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
|
||||
string targetName = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetName]].ToString();
|
||||
var originIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]];
|
||||
var targetIdString = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]];
|
||||
var originName = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginName]];
|
||||
var targetName = match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetName]];
|
||||
|
||||
long originId = originIdString.IsBotGuid() ?
|
||||
var originId = originIdString.IsBotGuid() ?
|
||||
originName.GenerateGuidFromString() :
|
||||
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
|
||||
long targetId = targetIdString.IsBotGuid() ?
|
||||
var targetId = targetIdString.IsBotGuid() ?
|
||||
targetName.GenerateGuidFromString() :
|
||||
targetIdString.ConvertGuidToLong(Configuration.GuidNumberStyle, Utilities.WORLD_ID);
|
||||
|
||||
int originClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
int targetClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
|
||||
var originClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]);
|
||||
var targetClientNumber = int.Parse(match.Values[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetClientNumber]]);
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -240,16 +286,16 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType == "J")
|
||||
if (eventType == GameEvent.EventType.PreConnect)
|
||||
{
|
||||
var match = Configuration.Join.PatternMatcher.Match(logLine);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
string originIdString = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
|
||||
string originName = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
|
||||
var originIdString = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]];
|
||||
var originName = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]];
|
||||
|
||||
long networkId = originIdString.IsBotGuid() ?
|
||||
var networkId = originIdString.IsBotGuid() ?
|
||||
originName.GenerateGuidFromString() :
|
||||
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
||||
|
||||
@ -261,10 +307,10 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Name = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine(),
|
||||
Name = match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].TrimNewLine(),
|
||||
},
|
||||
NetworkId = networkId,
|
||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]),
|
||||
State = EFClient.ClientState.Connecting,
|
||||
},
|
||||
Extra = originIdString,
|
||||
@ -276,16 +322,16 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType == "Q")
|
||||
if (eventType == GameEvent.EventType.PreDisconnect)
|
||||
{
|
||||
var match = Configuration.Quit.PatternMatcher.Match(logLine);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
string originIdString = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
|
||||
string originName = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString();
|
||||
var originIdString = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]];
|
||||
var originName = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]];
|
||||
|
||||
long networkId = originIdString.IsBotGuid() ?
|
||||
var networkId = originIdString.IsBotGuid() ?
|
||||
originName.GenerateGuidFromString() :
|
||||
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
|
||||
|
||||
@ -297,10 +343,10 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Name = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().TrimNewLine()
|
||||
Name = match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].TrimNewLine()
|
||||
},
|
||||
NetworkId = networkId,
|
||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]].ToString()),
|
||||
ClientNumber = Convert.ToInt32(match.Values[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]),
|
||||
State = EFClient.ClientState.Disconnecting
|
||||
},
|
||||
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
||||
@ -311,7 +357,7 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType.Contains("ExitLevel") || eventType.Contains("ShutdownGame"))
|
||||
if (eventType == GameEvent.EventType.MapEnd)
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -325,9 +371,9 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
};
|
||||
}
|
||||
|
||||
if (eventType.Contains("InitGame"))
|
||||
if (eventType == GameEvent.EventType.MapChange)
|
||||
{
|
||||
string dump = eventType.Replace("InitGame: ", "");
|
||||
var dump = logLine.Replace("InitGame: ", "");
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
@ -342,26 +388,37 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
};
|
||||
}
|
||||
|
||||
if (_customEventRegistrations.ContainsKey(eventType))
|
||||
if (eventParseResult.eventKey == null || !_customEventRegistrations.ContainsKey(eventParseResult.eventKey))
|
||||
{
|
||||
var eventModifier = _customEventRegistrations[eventType];
|
||||
|
||||
try
|
||||
return new GameEvent()
|
||||
{
|
||||
return eventModifier.Item2(logLine, Configuration, new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Other,
|
||||
Data = logLine,
|
||||
Subtype = eventModifier.Item1,
|
||||
GameTime = gameTime,
|
||||
Source = GameEvent.EventSource.Log
|
||||
});
|
||||
}
|
||||
Type = GameEvent.EventType.Unknown,
|
||||
Data = logLine,
|
||||
Origin = Utilities.IW4MAdminClient(),
|
||||
Target = Utilities.IW4MAdminClient(),
|
||||
RequiredEntity = GameEvent.EventRequiredEntity.None,
|
||||
GameTime = gameTime,
|
||||
Source = GameEvent.EventSource.Log
|
||||
};
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
var eventModifier = _customEventRegistrations[eventParseResult.eventKey];
|
||||
|
||||
try
|
||||
{
|
||||
return eventModifier.Item2(logLine, Configuration, new GameEvent()
|
||||
{
|
||||
_logger.LogError(e, $"Could not handle custom event generation");
|
||||
}
|
||||
Type = GameEvent.EventType.Other,
|
||||
Data = logLine,
|
||||
Subtype = eventModifier.Item1,
|
||||
GameTime = gameTime,
|
||||
Source = GameEvent.EventSource.Log
|
||||
});
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Could not handle custom event generation");
|
||||
}
|
||||
|
||||
return new GameEvent()
|
||||
|
@ -1,5 +1,6 @@
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Globalization;
|
||||
using SharedLibraryCore;
|
||||
|
||||
namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
@ -17,6 +18,8 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
public ParserRegex Damage { get; set; }
|
||||
public ParserRegex Action { get; set; }
|
||||
public ParserRegex Time { get; set; }
|
||||
public ParserRegex MapChange { get; set; }
|
||||
public ParserRegex MapEnd { get; set; }
|
||||
public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber;
|
||||
|
||||
public DynamicEventParserConfiguration(IParserRegexFactory parserRegexFactory)
|
||||
@ -28,6 +31,8 @@ namespace IW4MAdmin.Application.EventParsers
|
||||
Damage = parserRegexFactory.CreateParserRegex();
|
||||
Action = parserRegexFactory.CreateParserRegex();
|
||||
Time = parserRegexFactory.CreateParserRegex();
|
||||
MapChange = parserRegexFactory.CreateParserRegex();
|
||||
MapEnd = parserRegexFactory.CreateParserRegex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
using IW4MAdmin.Application.RCon;
|
||||
using System;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Text;
|
||||
using Integrations.Cod;
|
||||
using Integrations.Source;
|
||||
using Integrations.Source.Interfaces;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace IW4MAdmin.Application.Factories
|
||||
@ -10,16 +14,16 @@ namespace IW4MAdmin.Application.Factories
|
||||
/// </summary>
|
||||
internal class RConConnectionFactory : IRConConnectionFactory
|
||||
{
|
||||
private static readonly Encoding gameEncoding = Encoding.GetEncoding("windows-1252");
|
||||
private readonly ILogger<RConConnection> _logger;
|
||||
|
||||
private static readonly Encoding GameEncoding = Encoding.GetEncoding("windows-1252");
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Base constructor
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
public RConConnectionFactory(ILogger<RConConnection> logger)
|
||||
public RConConnectionFactory(IServiceProvider serviceProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -29,9 +33,16 @@ namespace IW4MAdmin.Application.Factories
|
||||
/// <param name="port">port of the server</param>
|
||||
/// <param name="password">rcon password of the server</param>
|
||||
/// <returns></returns>
|
||||
public IRConConnection CreateConnection(string ipAddress, int port, string password)
|
||||
public IRConConnection CreateConnection(string ipAddress, int port, string password, string rconEngine)
|
||||
{
|
||||
return new RConConnection(ipAddress, port, password, _logger, gameEncoding);
|
||||
return rconEngine switch
|
||||
{
|
||||
"COD" => new CodRConConnection(ipAddress, port, password,
|
||||
_serviceProvider.GetRequiredService<ILogger<CodRConConnection>>(), GameEncoding),
|
||||
"Source" => new SourceRConConnection(_serviceProvider.GetRequiredService<ILogger<SourceRConConnection>>(),
|
||||
_serviceProvider.GetRequiredService<IRConClientFactory>(), ipAddress, port, password),
|
||||
_ => throw new ArgumentException($"No supported RCon engine available for '{rconEngine}'")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
@ -74,6 +77,8 @@ namespace IW4MAdmin
|
||||
client = await Manager.GetClientService().Create(clientFromLog);
|
||||
}
|
||||
|
||||
client.CopyAdditionalProperties(clientFromLog);
|
||||
|
||||
// this is only a temporary version until the IPAddress is transmitted
|
||||
client.CurrentAlias = new EFAlias()
|
||||
{
|
||||
@ -156,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)
|
||||
@ -739,11 +744,11 @@ namespace IW4MAdmin
|
||||
/// array index 2 = updated clients
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
async Task<IList<EFClient>[]> PollPlayersAsync()
|
||||
async Task<List<EFClient>[]> PollPlayersAsync()
|
||||
{
|
||||
var currentClients = GetClientsAsList();
|
||||
var statusResponse = (await this.GetStatusAsync());
|
||||
var polledClients = statusResponse.Item1.AsEnumerable();
|
||||
var polledClients = statusResponse.Clients.AsEnumerable();
|
||||
|
||||
if (Manager.GetApplicationSettings().Configuration().IgnoreBots)
|
||||
{
|
||||
@ -753,10 +758,12 @@ namespace IW4MAdmin
|
||||
var connectingClients = polledClients.Except(currentClients);
|
||||
var updatedClients = polledClients.Except(connectingClients).Except(disconnectingClients);
|
||||
|
||||
UpdateMap(statusResponse.Item2);
|
||||
UpdateGametype(statusResponse.Item3);
|
||||
UpdateMap(statusResponse.Map);
|
||||
UpdateGametype(statusResponse.GameType);
|
||||
UpdateHostname(statusResponse.Hostname);
|
||||
UpdateMaxPlayers(statusResponse.MaxClients);
|
||||
|
||||
return new List<EFClient>[]
|
||||
return new []
|
||||
{
|
||||
connectingClients.ToList(),
|
||||
disconnectingClients.ToList(),
|
||||
@ -803,6 +810,36 @@ namespace IW4MAdmin
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHostname(string hostname)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hostname) || Hostname == hostname)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using(LogContext.PushProperty("Server", ToString()))
|
||||
{
|
||||
ServerLogger.LogDebug("Updating hostname to {HostName}", hostname);
|
||||
}
|
||||
|
||||
Hostname = hostname;
|
||||
}
|
||||
|
||||
private void UpdateMaxPlayers(int? maxPlayers)
|
||||
{
|
||||
if (maxPlayers == null || maxPlayers == MaxClients)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using(LogContext.PushProperty("Server", ToString()))
|
||||
{
|
||||
ServerLogger.LogDebug("Updating max clients to {MaxPlayers}", maxPlayers);
|
||||
}
|
||||
|
||||
MaxClients = maxPlayers.Value;
|
||||
}
|
||||
|
||||
private async Task ShutdownInternal()
|
||||
{
|
||||
foreach (var client in GetClientsAsList())
|
||||
@ -1008,14 +1045,15 @@ 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);
|
||||
|
||||
var version = await this.GetMappedDvarValueOrDefaultAsync<string>("version");
|
||||
Version = version.Value;
|
||||
GameName = Utilities.GetGame(version?.Value ?? RconParser.Version);
|
||||
GameName = Utilities.GetGame(version.Value ?? RconParser.Version);
|
||||
|
||||
if (GameName == Game.UKN)
|
||||
{
|
||||
@ -1142,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));
|
||||
@ -1172,7 +1210,7 @@ namespace IW4MAdmin
|
||||
|
||||
public Uri[] GenerateUriForLog(string logPath, string gameLogServerUrl)
|
||||
{
|
||||
var logUri = new Uri(logPath);
|
||||
var logUri = new Uri(logPath, UriKind.Absolute);
|
||||
|
||||
if (string.IsNullOrEmpty(gameLogServerUrl))
|
||||
{
|
||||
@ -1188,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
|
||||
@ -1202,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);
|
||||
}
|
||||
@ -1287,8 +1325,12 @@ namespace IW4MAdmin
|
||||
|
||||
Manager.AddEvent(e);
|
||||
|
||||
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
||||
var parsedClientId = string.IsNullOrEmpty(temporalClientId) ? (int?)null : int.Parse(temporalClientId);
|
||||
var clientNumber = parsedClientId ?? targetClient.ClientNumber;
|
||||
|
||||
var formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||
targetClient.ClientNumber,
|
||||
clientNumber,
|
||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration,
|
||||
newPenalty,
|
||||
previousPenalty));
|
||||
@ -1319,8 +1361,12 @@ namespace IW4MAdmin
|
||||
|
||||
if (targetClient.IsIngame)
|
||||
{
|
||||
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
||||
var parsedClientId = string.IsNullOrEmpty(temporalClientId) ? (int?)null : int.Parse(temporalClientId);
|
||||
var clientNumber = parsedClientId ?? targetClient.ClientNumber;
|
||||
|
||||
var formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||
targetClient.ClientNumber,
|
||||
clientNumber,
|
||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
|
||||
ServerLogger.LogDebug("Executing tempban kick command for {targetClient}", targetClient.ToString());
|
||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||
@ -1353,8 +1399,13 @@ namespace IW4MAdmin
|
||||
if (targetClient.IsIngame)
|
||||
{
|
||||
ServerLogger.LogDebug("Attempting to kicking newly banned client {targetClient}", targetClient.ToString());
|
||||
|
||||
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
||||
var parsedClientId = string.IsNullOrEmpty(temporalClientId) ? (int?)null : int.Parse(temporalClientId);
|
||||
var clientNumber = parsedClientId ?? targetClient.ClientNumber;
|
||||
|
||||
var formattedString = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||
targetClient.ClientNumber,
|
||||
clientNumber,
|
||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
|
||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedString);
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Abstractions;
|
||||
using Data.Helpers;
|
||||
using Integrations.Source.Extensions;
|
||||
using IW4MAdmin.Application.Extensions;
|
||||
using IW4MAdmin.Application.Localization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -417,6 +418,8 @@ namespace IW4MAdmin.Application
|
||||
serviceCollection.AddSingleton<IEventHandler, GameEventHandler>();
|
||||
}
|
||||
|
||||
serviceCollection.AddSource();
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +168,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
}
|
||||
}
|
||||
|
||||
_scriptEngine.SetValue("_configHandler", new ScriptPluginConfigurationWrapper(Name, _scriptEngine));
|
||||
await OnLoadAsync(manager);
|
||||
|
||||
try
|
||||
|
90
Application/Misc/ScriptPluginConfigurationWrapper.cs
Normal file
90
Application/Misc/ScriptPluginConfigurationWrapper.cs
Normal file
@ -0,0 +1,90 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using IW4MAdmin.Application.Configuration;
|
||||
using Jint;
|
||||
using Jint.Native;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
public class ScriptPluginConfigurationWrapper
|
||||
{
|
||||
private readonly BaseConfigurationHandler<ScriptPluginConfiguration> _handler;
|
||||
private readonly ScriptPluginConfiguration _config;
|
||||
private readonly string _pluginName;
|
||||
private readonly Engine _scriptEngine;
|
||||
|
||||
public ScriptPluginConfigurationWrapper(string pluginName, Engine scriptEngine)
|
||||
{
|
||||
_handler = new BaseConfigurationHandler<ScriptPluginConfiguration>("ScriptPluginSettings");
|
||||
_config = _handler.Configuration() ??
|
||||
(ScriptPluginConfiguration) new ScriptPluginConfiguration().Generate();
|
||||
_pluginName = pluginName;
|
||||
_scriptEngine = scriptEngine;
|
||||
}
|
||||
|
||||
private static int? AsInteger(double d)
|
||||
{
|
||||
return int.TryParse(d.ToString(CultureInfo.InvariantCulture), out var parsed) ? parsed : (int?) null;
|
||||
}
|
||||
|
||||
public async Task SetValue(string key, object value)
|
||||
{
|
||||
var castValue = value;
|
||||
|
||||
if (value is double d)
|
||||
{
|
||||
castValue = AsInteger(d) ?? value;
|
||||
}
|
||||
|
||||
if (value is object[] array && array.All(item => item is double d && AsInteger(d) != null))
|
||||
{
|
||||
castValue = array.Select(item => AsInteger((double)item)).ToArray();
|
||||
}
|
||||
|
||||
if (!_config.ContainsKey(_pluginName))
|
||||
{
|
||||
_config.Add(_pluginName, new Dictionary<string, object>());
|
||||
}
|
||||
|
||||
var plugin = _config[_pluginName];
|
||||
|
||||
if (plugin.ContainsKey(key))
|
||||
{
|
||||
plugin[key] = castValue;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
plugin.Add(key, castValue);
|
||||
}
|
||||
|
||||
_handler.Set(_config);
|
||||
await _handler.Save();
|
||||
}
|
||||
|
||||
public JsValue GetValue(string key)
|
||||
{
|
||||
if (!_config.ContainsKey(_pluginName))
|
||||
{
|
||||
return JsValue.Undefined;
|
||||
}
|
||||
|
||||
if (!_config[_pluginName].ContainsKey(key))
|
||||
{
|
||||
return JsValue.Undefined;
|
||||
}
|
||||
|
||||
var item = _config[_pluginName][key];
|
||||
|
||||
if (item is JArray array)
|
||||
{
|
||||
item = array.ToObject<List<dynamic>>();
|
||||
}
|
||||
|
||||
return JsValue.FromObject(_scriptEngine, item);
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ using Microsoft.Extensions.Logging;
|
||||
using static SharedLibraryCore.Server;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
namespace IW4MAdmin.Application.RConParsers
|
||||
{
|
||||
public class BaseRConParser : IRConParser
|
||||
{
|
||||
@ -56,6 +56,7 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDefaultValue, 3);
|
||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarLatchedValue, 4);
|
||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.RConDvarDomain, 5);
|
||||
Configuration.Dvar.AddMapping(ParserRegex.GroupType.AdditionalGroup, int.MaxValue);
|
||||
|
||||
Configuration.StatusHeader.Pattern = "num +score +ping +guid +name +lastmsg +address +qport +rate *";
|
||||
Configuration.GametypeStatus.Pattern = "";
|
||||
@ -73,11 +74,13 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
public Game GameName { get; set; } = Game.COD;
|
||||
public bool CanGenerateLogPath { get; set; } = true;
|
||||
public string Name { get; set; } = "Call of Duty";
|
||||
public string RConEngine { get; set; } = "COD";
|
||||
public 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)
|
||||
@ -128,7 +131,7 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
|
||||
return new Dvar<T>()
|
||||
{
|
||||
Name = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarName]].Value,
|
||||
Name = dvarName,
|
||||
Value = string.IsNullOrEmpty(value) ? default : (T)Convert.ChangeType(value, typeof(T)),
|
||||
DefaultValue = string.IsNullOrEmpty(defaultValue) ? default : (T)Convert.ChangeType(defaultValue, typeof(T)),
|
||||
LatchedValue = string.IsNullOrEmpty(latchedValue) ? default : (T)Convert.ChangeType(latchedValue, typeof(T)),
|
||||
@ -136,53 +139,55 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
};
|
||||
}
|
||||
|
||||
public virtual async Task<(List<EFClient>, string, string)> GetStatusAsync(IRConConnection connection)
|
||||
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection)
|
||||
{
|
||||
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS);
|
||||
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS);
|
||||
_logger.LogDebug("Status Response {response}", string.Join(Environment.NewLine, response));
|
||||
return (ClientsFromStatus(response), MapFromStatus(response), GameTypeFromStatus(response));
|
||||
return new StatusResponse
|
||||
{
|
||||
Clients = ClientsFromStatus(response).ToArray(),
|
||||
Map = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusMap, Configuration.MapStatus.Pattern),
|
||||
GameType = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusGametype, Configuration.GametypeStatus.Pattern),
|
||||
Hostname = GetValueFromStatus<string>(response, ParserRegex.GroupType.RConStatusHostname, Configuration.HostnameStatus.Pattern),
|
||||
MaxClients = GetValueFromStatus<int?>(response, ParserRegex.GroupType.RConStatusMaxPlayers, Configuration.MaxPlayersStatus.Pattern)
|
||||
};
|
||||
}
|
||||
|
||||
private string MapFromStatus(string[] response)
|
||||
private T GetValueFromStatus<T>(IEnumerable<string> response, ParserRegex.GroupType groupType, string groupPattern)
|
||||
{
|
||||
string map = null;
|
||||
if (string.IsNullOrEmpty(groupPattern))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
string value = null;
|
||||
foreach (var line in response)
|
||||
{
|
||||
var regex = Regex.Match(line, Configuration.MapStatus.Pattern);
|
||||
var regex = Regex.Match(line, groupPattern);
|
||||
if (regex.Success)
|
||||
{
|
||||
map = regex.Groups[Configuration.MapStatus.GroupMapping[ParserRegex.GroupType.RConStatusMap]].ToString();
|
||||
value = regex.Groups[Configuration.MapStatus.GroupMapping[groupType]].ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private string GameTypeFromStatus(string[] response)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Configuration.GametypeStatus.Pattern))
|
||||
if (value == null)
|
||||
{
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
|
||||
string gametype = null;
|
||||
foreach (var line in response)
|
||||
if (typeof(T) == typeof(int?))
|
||||
{
|
||||
var regex = Regex.Match(line, Configuration.GametypeStatus.Pattern);
|
||||
if (regex.Success)
|
||||
{
|
||||
gametype = regex.Groups[Configuration.GametypeStatus.GroupMapping[ParserRegex.GroupType.RConStatusGametype]].ToString();
|
||||
}
|
||||
return (T)Convert.ChangeType(int.Parse(value), Nullable.GetUnderlyingType(typeof(T)));
|
||||
}
|
||||
|
||||
return gametype;
|
||||
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
|
||||
public async Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue)
|
||||
{
|
||||
string dvarString = (dvarValue is string str)
|
||||
? $"{dvarName} \"{str}\""
|
||||
: $"{dvarName} {dvarValue.ToString()}";
|
||||
: $"{dvarName} {dvarValue}";
|
||||
|
||||
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString)).Length > 0;
|
||||
}
|
||||
@ -212,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)
|
||||
@ -224,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();
|
||||
|
||||
@ -258,6 +268,17 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
|
||||
client.SetAdditionalProperty("BotGuid", networkIdString);
|
||||
|
||||
if (Configuration.Status.GroupMapping.ContainsKey(ParserRegex.GroupType.AdditionalGroup))
|
||||
{
|
||||
var additionalGroupIndex =
|
||||
Configuration.Status.GroupMapping[ParserRegex.GroupType.AdditionalGroup];
|
||||
|
||||
if (match.Values.Length > additionalGroupIndex)
|
||||
{
|
||||
client.SetAdditionalProperty("ConnectionClientId", match.Values[additionalGroupIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
StatusPlayers.Add(client);
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
namespace IW4MAdmin.Application.RConParsers
|
||||
{
|
||||
/// <summary>
|
||||
/// empty implementation of the IW4RConParser
|
||||
/// allows script plugins to generate dynamic RCon parsers
|
||||
/// </summary>
|
||||
sealed internal class DynamicRConParser : BaseRConParser
|
||||
internal sealed class DynamicRConParser : BaseRConParser
|
||||
{
|
||||
public DynamicRConParser(ILogger<BaseRConParser> logger, IParserRegexFactory parserRegexFactory) : base(logger, parserRegexFactory)
|
||||
{
|
@ -4,7 +4,7 @@ using SharedLibraryCore.RCon;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
namespace IW4MAdmin.Application.RConParsers
|
||||
{
|
||||
/// <summary>
|
||||
/// generic implementation of the IRConParserConfiguration
|
||||
@ -16,6 +16,8 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
public ParserRegex Status { get; set; }
|
||||
public ParserRegex MapStatus { get; set; }
|
||||
public ParserRegex GametypeStatus { get; set; }
|
||||
public ParserRegex HostnameStatus { get; set; }
|
||||
public ParserRegex MaxPlayersStatus { get; set; }
|
||||
public ParserRegex Dvar { get; set; }
|
||||
public ParserRegex StatusHeader { get; set; }
|
||||
public string ServerNotRunningResponse { get; set; }
|
||||
@ -34,6 +36,8 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
GametypeStatus = parserRegexFactory.CreateParserRegex();
|
||||
Dvar = parserRegexFactory.CreateParserRegex();
|
||||
StatusHeader = parserRegexFactory.CreateParserRegex();
|
||||
HostnameStatus = parserRegexFactory.CreateParserRegex();
|
||||
MaxPlayersStatus = parserRegexFactory.CreateParserRegex();
|
||||
}
|
||||
}
|
||||
}
|
15
Application/RConParsers/StatusResponse.cs
Normal file
15
Application/RConParsers/StatusResponse.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.RConParsers
|
||||
{
|
||||
/// <inheritdoc cref="IStatusResponse"/>
|
||||
public class StatusResponse : IStatusResponse
|
||||
{
|
||||
public string Map { get; set; }
|
||||
public string GameType { get; set; }
|
||||
public string Hostname { get; set; }
|
||||
public int? MaxClients { get; set; }
|
||||
public EFClient[] Clients { get; set; }
|
||||
}
|
||||
}
|
@ -8,61 +8,9 @@
|
||||
<PackageId>RaidMax.IW4MAdmin.Data</PackageId>
|
||||
<Title>RaidMax.IW4MAdmin.Data</Title>
|
||||
<Authors />
|
||||
<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 ?? "--";
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,9 @@
|
||||
T4 = 5,
|
||||
T5 = 6,
|
||||
T6 = 7,
|
||||
T7 = 8
|
||||
T7 = 8,
|
||||
SHG1 = 9,
|
||||
CSGO = 10
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ namespace Data.Models
|
||||
{
|
||||
public class SharedEntity : IPropertyExtender
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, object> _additionalProperties;
|
||||
private ConcurrentDictionary<string, object> _additionalProperties;
|
||||
|
||||
/// <summary>
|
||||
/// indicates if the entity is active
|
||||
@ -33,5 +33,10 @@ namespace Data.Models
|
||||
_additionalProperties.TryAdd(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyAdditionalProperties(SharedEntity source)
|
||||
{
|
||||
_additionalProperties = source._additionalProperties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
||||
Plugins\ScriptPlugins\SampleScriptPluginCommand.js = Plugins\ScriptPlugins\SampleScriptPluginCommand.js
|
||||
Plugins\ScriptPlugins\SharedGUIDKick.js = Plugins\ScriptPlugins\SharedGUIDKick.js
|
||||
Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js
|
||||
Plugins\ScriptPlugins\ParserPlutoniumT4.js = Plugins\ScriptPlugins\ParserPlutoniumT4.js
|
||||
Plugins\ScriptPlugins\ParserS1x.js = Plugins\ScriptPlugins\ParserS1x.js
|
||||
Plugins\ScriptPlugins\ParserCSGO.js = Plugins\ScriptPlugins\ParserCSGO.js
|
||||
Plugins\ScriptPlugins\ParserCSGOSM.js = Plugins\ScriptPlugins\ParserCSGOSM.js
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutomessageFeed", "Plugins\AutomessageFeed\AutomessageFeed.csproj", "{F5815359-CFC7-44B4-9A3B-C04BACAD5836}"
|
||||
@ -57,6 +61,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationTests", "Tests\A
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Data", "Data\Data.csproj", "{81689023-E55E-48ED-B7A8-53F4E21BBF2D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Integrations", "Integrations", "{A2AE33B4-0830-426A-9E11-951DAB12BE5B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integrations.Cod", "Integrations\Cod\Integrations.Cod.csproj", "{A9348433-58C1-4B9C-8BB7-088B02529D9D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Integrations.Source", "Integrations\Source\Integrations.Source.csproj", "{9512295B-3045-40E0-9B7E-2409F2173E9D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -360,6 +370,54 @@ Global
|
||||
{81689023-E55E-48ED-B7A8-53F4E21BBF2D}.Release|x86.Build.0 = Release|Any CPU
|
||||
{81689023-E55E-48ED-B7A8-53F4E21BBF2D}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
|
||||
{81689023-E55E-48ED-B7A8-53F4E21BBF2D}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|x64.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|x64.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|x86.ActiveCfg = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Prerelease|x86.Build.0 = Debug|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D}.Release|x86.Build.0 = Release|Any CPU
|
||||
{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
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|x64.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|x64.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|x86.ActiveCfg = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Prerelease|x86.Build.0 = Debug|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D}.Release|x86.Build.0 = Release|Any CPU
|
||||
{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
|
||||
@ -374,6 +432,8 @@ Global
|
||||
{F5815359-CFC7-44B4-9A3B-C04BACAD5836} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{00A1FED2-2254-4AF7-A5DB-2357FA7C88CD} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{581FA7AF-FEF6-483C-A7D0-2D13EF50801B} = {3065279E-17F0-4CE0-AF5B-014E04263D77}
|
||||
{A9348433-58C1-4B9C-8BB7-088B02529D9D} = {A2AE33B4-0830-426A-9E11-951DAB12BE5B}
|
||||
{9512295B-3045-40E0-9B7E-2409F2173E9D} = {A2AE33B4-0830-426A-9E11-951DAB12BE5B}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}
|
||||
|
@ -1,8 +1,4 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.RCon;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -14,25 +10,29 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog.Context;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.RCon;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace IW4MAdmin.Application.RCon
|
||||
namespace Integrations.Cod
|
||||
{
|
||||
/// <summary>
|
||||
/// implementation of IRConConnection
|
||||
/// </summary>
|
||||
public class RConConnection : IRConConnection
|
||||
public class CodRConConnection : IRConConnection
|
||||
{
|
||||
static readonly ConcurrentDictionary<EndPoint, ConnectionState> ActiveQueries = new ConcurrentDictionary<EndPoint, ConnectionState>();
|
||||
public IPEndPoint Endpoint { get; private set; }
|
||||
public string RConPassword { get; private set; }
|
||||
public IPEndPoint Endpoint { get; }
|
||||
public string RConPassword { get; }
|
||||
|
||||
private IRConParser parser;
|
||||
private IRConParserConfiguration config;
|
||||
private readonly ILogger _log;
|
||||
private readonly Encoding _gameEncoding;
|
||||
|
||||
public RConConnection(string ipAddress, int port, string password, ILogger<RConConnection> log, Encoding gameEncoding)
|
||||
public CodRConConnection(string ipAddress, int port, string password, ILogger<CodRConConnection> log, Encoding gameEncoding)
|
||||
{
|
||||
Endpoint = new IPEndPoint(IPAddress.Parse(ipAddress), port);
|
||||
_gameEncoding = gameEncoding;
|
||||
@ -468,4 +468,4 @@ namespace IW4MAdmin.Application.RCon
|
||||
ActiveQueries[this.Endpoint].OnSentData.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace IW4MAdmin.Application.RCon
|
||||
namespace Integrations.Cod
|
||||
{
|
||||
/// <summary>
|
||||
/// used to keep track of the udp connection state
|
||||
@ -28,4 +28,4 @@ namespace IW4MAdmin.Application.RCon
|
||||
public SocketAsyncEventArgs ReceiveEventArgs { get; set; } = new SocketAsyncEventArgs();
|
||||
public DateTime LastQuery { get; set; } = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
19
Integrations/Cod/Integrations.Cod.csproj
Normal file
19
Integrations/Cod/Integrations.Cod.csproj
Normal file
@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<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>
|
||||
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,15 @@
|
||||
using Integrations.Source.Interfaces;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Integrations.Source.Extensions
|
||||
{
|
||||
public static class IntegrationServicesExtensions
|
||||
{
|
||||
public static IServiceCollection AddSource(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<IRConClientFactory, RConClientFactory>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
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();
|
||||
}
|
||||
}
|
||||
}
|
23
Integrations/Source/Integrations.Source.csproj
Normal file
23
Integrations/Source/Integrations.Source.csproj
Normal file
@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<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>
|
||||
<PackageReference Include="RconSharp" Version="2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
9
Integrations/Source/Interfaces/IRConClientFactory.cs
Normal file
9
Integrations/Source/Interfaces/IRConClientFactory.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using RconSharp;
|
||||
|
||||
namespace Integrations.Source.Interfaces
|
||||
{
|
||||
public interface IRConClientFactory
|
||||
{
|
||||
RconClient CreateClient(string hostname, int port);
|
||||
}
|
||||
}
|
13
Integrations/Source/RConClientFactory.cs
Normal file
13
Integrations/Source/RConClientFactory.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Integrations.Source.Interfaces;
|
||||
using RconSharp;
|
||||
|
||||
namespace Integrations.Source
|
||||
{
|
||||
public class RConClientFactory : IRConClientFactory
|
||||
{
|
||||
public RconClient CreateClient(string hostname, int port)
|
||||
{
|
||||
return RconClient.Create(hostname, port);
|
||||
}
|
||||
}
|
||||
}
|
187
Integrations/Source/SourceRConConnection.cs
Normal file
187
Integrations/Source/SourceRConConnection.cs
Normal file
@ -0,0 +1,187 @@
|
||||
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;
|
||||
using Serilog.Context;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.RCon;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace Integrations.Source
|
||||
{
|
||||
public class SourceRConConnection : IRConConnection
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly string _password;
|
||||
private readonly string _hostname;
|
||||
private readonly int _port;
|
||||
private readonly IRConClientFactory _rconClientFactory;
|
||||
private 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)
|
||||
{
|
||||
_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 = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
await _activeQuery.WaitAsync();
|
||||
await WaitForAvailable();
|
||||
|
||||
if (_needNewSocket)
|
||||
{
|
||||
try
|
||||
{
|
||||
_rconClient?.Disconnect();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
_rconClient = _rconClientFactory.CreateClient(_hostname, _port);
|
||||
_authenticated = false;
|
||||
_needNewSocket = false;
|
||||
}
|
||||
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogDebug("Connecting to RCon socket");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
_needNewSocket = true;
|
||||
throw new NetworkException("Timeout while attempting to communicate with server");
|
||||
}
|
||||
|
||||
catch (SocketException ex)
|
||||
{
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogError(ex, "Socket exception encountered while attempting to communicate with server");
|
||||
}
|
||||
|
||||
_needNewSocket = true;
|
||||
|
||||
throw new NetworkException("Socket exception encountered while attempting to communicate with server");
|
||||
}
|
||||
|
||||
catch (Exception ex) when (ex.GetType() != typeof(NetworkException) &&
|
||||
ex.GetType() != typeof(ServerException))
|
||||
{
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogError(ex, "Could not execute RCon query {Parameters}", parameters);
|
||||
}
|
||||
|
||||
throw new NetworkException("Unable to communicate with server");
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
if (_activeQuery.CurrentCount == 0)
|
||||
{
|
||||
_activeQuery.Release();
|
||||
}
|
||||
|
||||
_lastQuery = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WaitForAvailable()
|
||||
{
|
||||
var diff = DateTime.Now - _lastQuery;
|
||||
if (diff < FloodDelay)
|
||||
{
|
||||
await Task.Delay(FloodDelay - diff);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TryConnectAndAuthenticate()
|
||||
{
|
||||
if (!_authenticated)
|
||||
{
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogDebug("Authenticating to RCon socket");
|
||||
}
|
||||
|
||||
await _rconClient.ConnectAsync().WithTimeout(ConnectionTimeout);
|
||||
_authenticated = await _rconClient.AuthenticateAsync(_password).WithTimeout(ConnectionTimeout);
|
||||
|
||||
if (!_authenticated)
|
||||
{
|
||||
using (LogContext.PushProperty("Server", $"{_hostname}:{_port}"))
|
||||
{
|
||||
_logger.LogError("Could not login to server");
|
||||
}
|
||||
|
||||
throw new ServerException("Could not authenticate to server with provided password");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetConfiguration(IRConParser config)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -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">
|
||||
|
101
Plugins/ScriptPlugins/ParserCSGO.js
Normal file
101
Plugins/ScriptPlugins/ParserCSGO.js
Normal file
@ -0,0 +1,101 @@
|
||||
let rconParser;
|
||||
let eventParser;
|
||||
|
||||
const plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 0.2,
|
||||
name: 'CS:GO Parser',
|
||||
engine: 'Source',
|
||||
isParser: true,
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
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';
|
||||
|
||||
rconParser.Configuration.MapStatus.Pattern = '^map *: +(.+)$';
|
||||
rconParser.Configuration.MapStatus.AddMapping(111, 1);
|
||||
|
||||
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
|
||||
rconParser.Configuration.MapStatus.AddMapping(113, 1);
|
||||
|
||||
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d+ humans, \\d+ bots \\((\\d+).+';
|
||||
rconParser.Configuration.MapStatus.AddMapping(114, 1);
|
||||
|
||||
rconParser.Configuration.Dvar.Pattern = '^"(.+)" = "(.+)" (?:\\( def. "(.*)" \\))?(?: |\\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+)?) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
|
||||
rconParser.Configuration.Status.AddMapping(100, 2);
|
||||
rconParser.Configuration.Status.AddMapping(101, -1);
|
||||
rconParser.Configuration.Status.AddMapping(102, 6);
|
||||
rconParser.Configuration.Status.AddMapping(103, 4)
|
||||
rconParser.Configuration.Status.AddMapping(104, 3);
|
||||
rconParser.Configuration.Status.AddMapping(105, 10);
|
||||
rconParser.Configuration.Status.AddMapping(200, 1);
|
||||
|
||||
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('version', this.engine);
|
||||
rconParser.Configuration.DefaultDvarValues.Add('fs_basepath', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('fs_basegame', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('g_log', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('net_ip', 'localhost');
|
||||
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_hostname', 'hostname');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('mapname', 'host_map');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_maxclients', 'maxplayers');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('g_gametype', 'game_type'); // todo: will need gamemode too
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('fs_game', 'game_mode');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('g_password', 'sv_password');
|
||||
|
||||
rconParser.Configuration.NoticeLineSeparator = '. ';
|
||||
rconParser.CanGenerateLogPath = false;
|
||||
|
||||
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
|
||||
rconParser.Configuration.CommandPrefixes.Kick = 'kickid {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.Ban = 'kickid {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.TempBan = 'kickid {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
|
||||
rconParser.Configuration.CommandPrefixes.Tell = 'say [{0}] {1}'; // no tell exists in vanilla
|
||||
|
||||
eventParser.Configuration.Say.Pattern = '^"(.+)<(\\d+)><(.+)><(.*?)>" say "(.*)"$';
|
||||
eventParser.Configuration.Say.AddMapping(5, 1);
|
||||
eventParser.Configuration.Say.AddMapping(3, 2);
|
||||
eventParser.Configuration.Say.AddMapping(1, 3);
|
||||
eventParser.Configuration.Say.AddMapping(7, 4);
|
||||
eventParser.Configuration.Say.AddMapping(13, 5);
|
||||
|
||||
eventParser.Configuration.Kill.Pattern = '"(.+)<(\\d+)><(.+)><(.*)>" \\[-?\\d+ -?\\d+ -?\\d+\\] killed "(.+)<(\\d+)><(.+)><(.*)>" \\[-?\\d+ -?\\d+ -?\\d+\\] with "(\\S*)"(.*)$';
|
||||
eventParser.Configuration.Kill.AddMapping(5, 1);
|
||||
eventParser.Configuration.Kill.AddMapping(3, 2);
|
||||
eventParser.Configuration.Kill.AddMapping(1, 3);
|
||||
eventParser.Configuration.Kill.AddMapping(7, 4);
|
||||
eventParser.Configuration.Kill.AddMapping(6, 5);
|
||||
eventParser.Configuration.Kill.AddMapping(4, 6);
|
||||
eventParser.Configuration.Kill.AddMapping(2, 7);
|
||||
eventParser.Configuration.Kill.AddMapping(8, 8);
|
||||
eventParser.Configuration.Kill.AddMapping(9, 9);
|
||||
|
||||
eventParser.Configuration.Time.Pattern = '^L [01]\\d/[0-3]\\d/\\d+ - [0-2]\\d:[0-5]\\d:[0-5]\\d:';
|
||||
|
||||
rconParser.Version = 'CSGO';
|
||||
rconParser.GameName = 10; // CSGO
|
||||
eventParser.Version = 'CSGO';
|
||||
eventParser.GameName = 10; // CSGO
|
||||
eventParser.URLProtocolFormat = 'steam://connect/{{ip}}:{{port}}';
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
101
Plugins/ScriptPlugins/ParserCSGOSM.js
Normal file
101
Plugins/ScriptPlugins/ParserCSGOSM.js
Normal file
@ -0,0 +1,101 @@
|
||||
let rconParser;
|
||||
let eventParser;
|
||||
|
||||
const plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 0.2,
|
||||
name: 'CS:GO (SourceMod) Parser',
|
||||
engine: 'Source',
|
||||
isParser: true,
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
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';
|
||||
|
||||
rconParser.Configuration.MapStatus.Pattern = '^map *: +(.+)$';
|
||||
rconParser.Configuration.MapStatus.AddMapping(111, 1);
|
||||
|
||||
rconParser.Configuration.HostnameStatus.Pattern = '^hostname: +(.+)$';
|
||||
rconParser.Configuration.MapStatus.AddMapping(113, 1);
|
||||
|
||||
rconParser.Configuration.MaxPlayersStatus.Pattern = '^players *: +\\d+ humans, \\d+ bots \\((\\d+).+';
|
||||
rconParser.Configuration.MapStatus.AddMapping(114, 1);
|
||||
|
||||
rconParser.Configuration.Dvar.Pattern = '^"(.+)" = "(.+)" (?:\\( def. "(.*)" \\))?(?: |\\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+)?) (\\d+) (\\S+) (\\S+) (\\d+) (\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+)$';
|
||||
rconParser.Configuration.Status.AddMapping(100, 2);
|
||||
rconParser.Configuration.Status.AddMapping(101, -1);
|
||||
rconParser.Configuration.Status.AddMapping(102, 6);
|
||||
rconParser.Configuration.Status.AddMapping(103, 4)
|
||||
rconParser.Configuration.Status.AddMapping(104, 3);
|
||||
rconParser.Configuration.Status.AddMapping(105, 10);
|
||||
rconParser.Configuration.Status.AddMapping(200, 1);
|
||||
|
||||
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('version', this.engine);
|
||||
rconParser.Configuration.DefaultDvarValues.Add('fs_basepath', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('fs_basegame', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('g_log', '');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('net_ip', 'localhost');
|
||||
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_hostname', 'hostname');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('mapname', 'host_map');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_maxclients', 'maxplayers');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('g_gametype', 'game_type');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('fs_game', 'game_mode');
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('g_password', 'sv_password');
|
||||
|
||||
rconParser.Configuration.NoticeLineSeparator = '. ';
|
||||
rconParser.CanGenerateLogPath = false;
|
||||
|
||||
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
|
||||
rconParser.Configuration.CommandPrefixes.Kick = 'sm_kick #{0} {1}';
|
||||
rconParser.Configuration.CommandPrefixes.Ban = 'sm_kick #{0} {1}';
|
||||
rconParser.Configuration.CommandPrefixes.TempBan = 'sm_kick #{0} {1}';
|
||||
rconParser.Configuration.CommandPrefixes.Say = 'sm_say {0}';
|
||||
rconParser.Configuration.CommandPrefixes.Tell = 'sm_psay #{0} "{1}"';
|
||||
|
||||
eventParser.Configuration.Say.Pattern = '^"(.+)<(\\d+)><(.+)><(.*?)>" say "(.*)"$';
|
||||
eventParser.Configuration.Say.AddMapping(5, 1);
|
||||
eventParser.Configuration.Say.AddMapping(3, 2);
|
||||
eventParser.Configuration.Say.AddMapping(1, 3);
|
||||
eventParser.Configuration.Say.AddMapping(7, 4);
|
||||
eventParser.Configuration.Say.AddMapping(13, 5);
|
||||
|
||||
eventParser.Configuration.Kill.Pattern = '"(.+)<(\\d+)><(.+)><(.*)>" \\[-?\\d+ -?\\d+ -?\\d+\\] killed "(.+)<(\\d+)><(.+)><(.*)>" \\[-?\\d+ -?\\d+ -?\\d+\\] with "(\\S*)"(.*)$';
|
||||
eventParser.Configuration.Kill.AddMapping(5, 1);
|
||||
eventParser.Configuration.Kill.AddMapping(3, 2);
|
||||
eventParser.Configuration.Kill.AddMapping(1, 3);
|
||||
eventParser.Configuration.Kill.AddMapping(7, 4);
|
||||
eventParser.Configuration.Kill.AddMapping(6, 5);
|
||||
eventParser.Configuration.Kill.AddMapping(4, 6);
|
||||
eventParser.Configuration.Kill.AddMapping(2, 7);
|
||||
eventParser.Configuration.Kill.AddMapping(8, 8);
|
||||
eventParser.Configuration.Kill.AddMapping(9, 9);
|
||||
|
||||
eventParser.Configuration.Time.Pattern = '^L [01]\\d/[0-3]\\d/\\d+ - [0-2]\\d:[0-5]\\d:[0-5]\\d:';
|
||||
|
||||
rconParser.Version = 'CSGOSM';
|
||||
rconParser.GameName = 10; // CSGO
|
||||
eventParser.Version = 'CSGOSM';
|
||||
eventParser.GameName = 10; // CSGO
|
||||
eventParser.URLProtocolFormat = 'steam://connect/{{ip}}:{{port}}';
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
@ -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';
|
||||
|
@ -3,7 +3,7 @@ var eventParser;
|
||||
|
||||
var plugin = {
|
||||
author: 'RaidMax, Xerxes',
|
||||
version: 0.9,
|
||||
version: 0.10,
|
||||
name: 'Plutonium T6 Parser',
|
||||
isParser: true,
|
||||
|
||||
@ -37,7 +37,7 @@ var plugin = {
|
||||
rconParser.Configuration.Status.AddMapping(104, 5);
|
||||
rconParser.Configuration.Status.AddMapping(105, 6);
|
||||
|
||||
eventParser.Configuration.GameDirectory = 't6r\\data';
|
||||
eventParser.Configuration.GameDirectory = 't6';
|
||||
eventParser.Configuration.GuidNumberStyle = 7; // Integer
|
||||
|
||||
rconParser.Version = 'Call of Duty Multiplayer - Ship COD_T6_S MP build 1.0.44 CL(1759941) CODPCAB2 CEG Fri May 9 19:19:19 2014 win-x86 813e66d5';
|
||||
@ -51,4 +51,4 @@ var plugin = {
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
37
Plugins/ScriptPlugins/ParserPlutoniumT4.js
Normal file
37
Plugins/ScriptPlugins/ParserPlutoniumT4.js
Normal file
@ -0,0 +1,37 @@
|
||||
var rconParser;
|
||||
var eventParser;
|
||||
|
||||
var plugin = {
|
||||
author: 'RaidMax, Chase',
|
||||
version: 0.2,
|
||||
name: 'Plutonium T4 Parser',
|
||||
isParser: true,
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
rconParser = manager.GenerateDynamicRConParser(this.name);
|
||||
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||
|
||||
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0}';
|
||||
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0}';
|
||||
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick {0}';
|
||||
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
|
||||
rconParser.Configuration.GuidNumberStyle = 7; // Integer
|
||||
|
||||
rconParser.Version = 'Plutonium T4';
|
||||
rconParser.GameName = 5; // T4
|
||||
|
||||
eventParser.Configuration.GuidNumberStyle = 7; // Integer
|
||||
eventParser.Configuration.GameDirectory = 'raw';
|
||||
|
||||
eventParser.Version = 'Plutonium T4';
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
39
Plugins/ScriptPlugins/ParserS1x.js
Normal file
39
Plugins/ScriptPlugins/ParserS1x.js
Normal file
@ -0,0 +1,39 @@
|
||||
var rconParser;
|
||||
var eventParser;
|
||||
|
||||
var plugin = {
|
||||
author: 'Diavolo, RaidMax',
|
||||
version: 0.1,
|
||||
name: 'S1x Parser',
|
||||
isParser: true,
|
||||
|
||||
onEventAsync: function(gameEvent, server) {},
|
||||
|
||||
onLoadAsync: function(manager) {
|
||||
rconParser = manager.GenerateDynamicRConParser(this.name);
|
||||
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||
|
||||
rconParser.Configuration.CommandPrefixes.Kick = 'kickClient {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.Ban = 'kickClient {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.TempBan = 'kickClient {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint';
|
||||
rconParser.Configuration.Dvar.Pattern = '^ *\\"(.+)\\" is: \\"(.+)?\\" default: \\"(.+)?\\"\\n(?:latched: \\"(.+)?\\"\\n)? *(.+)$';
|
||||
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +(Yes|No) +((?:[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.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +address +qport *';
|
||||
rconParser.Configuration.Status.AddMapping(102, 4);
|
||||
rconParser.Configuration.Status.AddMapping(103, 5);
|
||||
rconParser.Configuration.Status.AddMapping(104, 6);
|
||||
rconParser.Configuration.WaitForResponse = false;
|
||||
|
||||
eventParser.Configuration.GameDirectory = '';
|
||||
|
||||
rconParser.Version = 'S1 MP 1.22 build 2195988 Wed Apr 18 11:26:14 2018 win64';
|
||||
rconParser.GameName = 9; // SHG1
|
||||
eventParser.Version = 'S1 MP 1.22 build 2195988 Wed Apr 18 11:26:14 2018 win64';
|
||||
eventParser.GameName = 9; // SHG1
|
||||
},
|
||||
|
||||
onUnloadAsync: function() {},
|
||||
|
||||
onTickAsync: function(server) {}
|
||||
};
|
@ -26,7 +26,7 @@ var plugin = {
|
||||
rconParser.Configuration.GametypeStatus.Pattern = 'Gametype: (.+)';
|
||||
rconParser.Configuration.MapStatus.Pattern = 'Map: (.+)';
|
||||
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined; // disables this, because it's useless on T7
|
||||
rconParser.Configuration.ServerNotRunningResponse = 'this is here to prevent a hiberating server from being detected as not running';
|
||||
rconParser.Configuration.ServerNotRunningResponse = 'this is here to prevent a hibernating server from being detected as not running';
|
||||
|
||||
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_hostname', 'live_steam_server_name');
|
||||
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');
|
||||
|
@ -24,7 +24,7 @@ var plugin = {
|
||||
rconParser.Configuration.CommandPrefixes.Say = 'say {0}';
|
||||
rconParser.Configuration.CommandPrefixes.Kick = 'dropclient {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.Ban = 'dropclient {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0} "{1}"';
|
||||
rconParser.Configuration.CommandPrefixes.TempBan = 'dropclient {0} "{1}"';
|
||||
rconParser.Configuration.Dvar.AddMapping(107, 1); // RCon DvarValue
|
||||
rconParser.Configuration.Dvar.Pattern = '^(.*)$';
|
||||
rconParser.Configuration.NoticeLineSeparator = '. ';
|
||||
@ -46,4 +46,4 @@ var plugin = {
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -39,7 +39,7 @@ let commands = [{
|
||||
|
||||
let plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 1.0,
|
||||
version: 1.1,
|
||||
name: 'Ping Pong Sample Command Plugin',
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
@ -48,6 +48,38 @@ let plugin = {
|
||||
onLoadAsync: function (manager) {
|
||||
this.logger = _serviceResolver.ResolveService("ILogger");
|
||||
this.logger.WriteDebug("sample plugin loaded");
|
||||
|
||||
const intArray = [
|
||||
1337,
|
||||
1505,
|
||||
999
|
||||
];
|
||||
|
||||
const stringArray = [
|
||||
"ping",
|
||||
"pong",
|
||||
"hello"
|
||||
];
|
||||
|
||||
this.configHandler = _configHandler;
|
||||
|
||||
this.configHandler.SetValue("SampleIntegerValue", 123);
|
||||
this.configHandler.SetValue("SampleStringValue", this.author);
|
||||
this.configHandler.SetValue("SampleFloatValue", this.version);
|
||||
this.configHandler.SetValue("SampleNumericalArray", intArray);
|
||||
this.configHandler.SetValue("SampleStringArray", stringArray);
|
||||
|
||||
this.logger.WriteDebug(this.configHandler.GetValue("SampleIntegerValue"));
|
||||
this.logger.WriteDebug(this.configHandler.GetValue("SampleStringValue"));
|
||||
this.logger.WriteDebug(this.configHandler.GetValue("SampleFloatValue"));
|
||||
|
||||
this.configHandler.GetValue("SampleNumericalArray").forEach((element) => {
|
||||
this.logger.WriteDebug(element);
|
||||
});
|
||||
|
||||
this.configHandler.GetValue("SampleStringArray").forEach((element) => {
|
||||
this.logger.WriteDebug(element);
|
||||
});
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
|
@ -26,6 +26,8 @@ var plugin = {
|
||||
try {
|
||||
var cl = new System.Net.Http.HttpClient();
|
||||
var re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result;
|
||||
var userAgent = 'IW4MAdmin-' + this.manager.GetApplicationSettings().Configuration().Id;
|
||||
cl.DefaultRequestHeaders.Add('User-Agent', userAgent);
|
||||
var co = re.Content;
|
||||
var parsedJSON = JSON.parse(co.ReadAsStringAsync().Result);
|
||||
co.Dispose();
|
||||
@ -38,7 +40,12 @@ var plugin = {
|
||||
|
||||
if (usingVPN) {
|
||||
this.logger.WriteInfo(origin + ' is using a VPN (' + origin.IPAddressString + ')');
|
||||
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], _IW4MAdminClient);
|
||||
var contactUrl = this.manager.GetApplicationSettings().Configuration().ContactUri;
|
||||
var additionalInfo = '';
|
||||
if (contactUrl) {
|
||||
additionalInfo = _localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED_INFO"] + ' ' + contactUrl;
|
||||
}
|
||||
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"] + ' ' + additionalInfo, _IW4MAdminClient);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,6 +177,12 @@ namespace IW4MAdmin.Plugins.Stats.Client
|
||||
|
||||
foreach (var hitInfo in new[] {attackerHitInfo, victimHitInfo})
|
||||
{
|
||||
if (hitInfo.MeansOfDeath == null || hitInfo.Location == null || hitInfo.Weapon == null)
|
||||
{
|
||||
_logger.LogDebug("Skipping hit because it does not contain the required data");
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _onTransaction.WaitAsync();
|
||||
@ -584,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;
|
||||
}
|
||||
|
||||
@ -594,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
|
||||
|
@ -15,13 +15,13 @@ namespace Stats.Client
|
||||
private readonly IWeaponNameParser _weaponNameParser;
|
||||
private readonly ILogger _logger;
|
||||
private const int MaximumDamage = 1000;
|
||||
|
||||
|
||||
public HitInfoBuilder(ILogger<HitInfoBuilder> logger, IWeaponNameParser weaponNameParser)
|
||||
{
|
||||
_weaponNameParser = weaponNameParser;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public HitInfo Build(string[] log, int entityId, bool isSelf, bool isVictim, Server.Game gameName)
|
||||
{
|
||||
var eventType = log[(uint) ParserRegex.GroupType.EventType].First();
|
||||
@ -50,11 +50,19 @@ namespace Stats.Client
|
||||
EntityId = entityId,
|
||||
IsVictim = isVictim,
|
||||
HitType = hitType,
|
||||
Damage = Math.Min(MaximumDamage, int.Parse(log[(uint) ParserRegex.GroupType.Damage])),
|
||||
Location = log[(uint) ParserRegex.GroupType.HitLocation],
|
||||
Weapon = _weaponNameParser.Parse(log[(uint) ParserRegex.GroupType.Weapon], gameName),
|
||||
MeansOfDeath = log[(uint)ParserRegex.GroupType.MeansOfDeath],
|
||||
Game = (Reference.Game)gameName
|
||||
Damage = Math.Min(MaximumDamage,
|
||||
log.Length > (uint) ParserRegex.GroupType.Damage
|
||||
? int.Parse(log[(uint) ParserRegex.GroupType.Damage])
|
||||
: 0),
|
||||
Location = log.Length > (uint) ParserRegex.GroupType.HitLocation
|
||||
? log[(uint) ParserRegex.GroupType.HitLocation]
|
||||
: "Unknown",
|
||||
Weapon = log.Length == 10 ? _weaponNameParser.Parse(log[8], gameName)
|
||||
: _weaponNameParser.Parse(log[(uint) ParserRegex.GroupType.Weapon], gameName),
|
||||
MeansOfDeath = log.Length > (uint) ParserRegex.GroupType.MeansOfDeath
|
||||
? log[(uint) ParserRegex.GroupType.MeansOfDeath]
|
||||
: "Unknown",
|
||||
Game = (Reference.Game) gameName
|
||||
};
|
||||
|
||||
//_logger.LogDebug("Generated new hitInfo {@hitInfo}", hitInfo);
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using IW4MAdmin.Plugins.Stats.Config;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using Stats.Config;
|
||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace Stats.Client
|
||||
@ -24,22 +25,16 @@ namespace Stats.Client
|
||||
public WeaponInfo Parse(string weaponName, Server.Game gameName)
|
||||
{
|
||||
var configForGame = _config.WeaponNameParserConfigurations
|
||||
?.FirstOrDefault(config => config.Game == gameName);
|
||||
|
||||
if (configForGame == null)
|
||||
?.FirstOrDefault(config => config.Game == gameName) ?? new WeaponNameParserConfiguration()
|
||||
{
|
||||
_logger.LogWarning("No weapon parser config available for game {game}", gameName);
|
||||
return new WeaponInfo()
|
||||
{
|
||||
Name = "Unknown"
|
||||
};
|
||||
}
|
||||
|
||||
Game = gameName
|
||||
};
|
||||
|
||||
var splitWeaponName = weaponName.Split(configForGame.Delimiters);
|
||||
|
||||
if (!splitWeaponName.Any())
|
||||
{
|
||||
_logger.LogError("Could not parse weapon name {weapon}", weaponName);
|
||||
_logger.LogError("Could not parse weapon name {Weapon}", weaponName);
|
||||
|
||||
return new WeaponInfo()
|
||||
{
|
||||
@ -48,8 +43,10 @@ namespace Stats.Client
|
||||
}
|
||||
|
||||
// remove the _mp suffix
|
||||
var filtered = splitWeaponName.Where(part => part != configForGame.WeaponSuffix);
|
||||
var baseName = splitWeaponName.First();
|
||||
var filtered = splitWeaponName
|
||||
.Where(part => part != configForGame.WeaponSuffix && part != configForGame.WeaponPrefix)
|
||||
.ToList();
|
||||
var baseName = filtered.First();
|
||||
var attachments = new List<string>();
|
||||
|
||||
if (filtered.Count() > 1)
|
||||
@ -67,8 +64,6 @@ namespace Stats.Client
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
// _logger.LogDebug("Parsed weapon info {@info}", weaponInfo);
|
||||
|
||||
return weaponInfo;
|
||||
}
|
||||
}
|
||||
|
@ -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)))
|
||||
|
@ -20,6 +20,12 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
||||
|
||||
public WeaponNameParserConfiguration[] WeaponNameParserConfigurations { get; set; } = new[]
|
||||
{
|
||||
new WeaponNameParserConfiguration()
|
||||
{
|
||||
Game = Server.Game.IW3,
|
||||
WeaponSuffix = "mp",
|
||||
Delimiters = new[] {'_'}
|
||||
},
|
||||
new WeaponNameParserConfiguration()
|
||||
{
|
||||
Game = Server.Game.IW4,
|
||||
@ -27,6 +33,13 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
||||
Delimiters = new[] {'_'}
|
||||
},
|
||||
new WeaponNameParserConfiguration()
|
||||
{
|
||||
Game = Server.Game.IW5,
|
||||
WeaponSuffix = "mp",
|
||||
WeaponPrefix = "iw5",
|
||||
Delimiters = new[] {'_'}
|
||||
},
|
||||
new WeaponNameParserConfiguration()
|
||||
{
|
||||
Game = Server.Game.T6,
|
||||
WeaponSuffix = "mp",
|
||||
|
@ -7,5 +7,6 @@ namespace Stats.Config
|
||||
public Server.Game Game { get; set; }
|
||||
public char[] Delimiters { get; set; }
|
||||
public string WeaponSuffix { get; set; }
|
||||
public string WeaponPrefix { get; set; }
|
||||
}
|
||||
}
|
@ -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">
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
@ -28,7 +27,8 @@ namespace IW4MAdmin.Plugins.Welcome
|
||||
|
||||
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory, IDatabaseContextFactory contextFactory)
|
||||
{
|
||||
_configHandler = configurationHandlerFactory.GetConfigurationHandler<WelcomeConfiguration>("WelcomePluginSettings");
|
||||
_configHandler =
|
||||
configurationHandlerFactory.GetConfigurationHandler<WelcomeConfiguration>("WelcomePluginSettings");
|
||||
_contextFactory = contextFactory;
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ namespace IW4MAdmin.Plugins.Welcome
|
||||
{
|
||||
if (_configHandler.Configuration() == null)
|
||||
{
|
||||
_configHandler.Set((WelcomeConfiguration)new WelcomeConfiguration().Generate());
|
||||
_configHandler.Set((WelcomeConfiguration) new WelcomeConfiguration().Generate());
|
||||
await _configHandler.Save();
|
||||
}
|
||||
}
|
||||
@ -49,9 +49,14 @@ namespace IW4MAdmin.Plugins.Welcome
|
||||
{
|
||||
if (E.Type == GameEvent.EventType.Join)
|
||||
{
|
||||
EFClient newPlayer = E.Origin;
|
||||
if (newPlayer.Level >= Permission.Trusted && !E.Origin.Masked || !string.IsNullOrEmpty(newPlayer.GetAdditionalProperty<string>("ClientTag")))
|
||||
E.Owner.Broadcast(await ProcessAnnouncement(_configHandler.Configuration().PrivilegedAnnouncementMessage, newPlayer));
|
||||
var newPlayer = E.Origin;
|
||||
if ((newPlayer.Level >= Permission.Trusted && !E.Origin.Masked) ||
|
||||
(!string.IsNullOrEmpty(newPlayer.GetAdditionalProperty<string>("ClientTag")) &&
|
||||
newPlayer.Level != Permission.Flagged && newPlayer.Level != Permission.Banned &&
|
||||
!newPlayer.Masked))
|
||||
E.Owner.Broadcast(
|
||||
await ProcessAnnouncement(_configHandler.Configuration().PrivilegedAnnouncementMessage,
|
||||
newPlayer));
|
||||
|
||||
newPlayer.Tell(await ProcessAnnouncement(_configHandler.Configuration().UserWelcomeMessage, newPlayer));
|
||||
|
||||
@ -71,19 +76,22 @@ namespace IW4MAdmin.Plugins.Welcome
|
||||
E.Owner.ToAdmins($"^1NOTICE: ^7Flagged player ^5{newPlayer.Name} ^7({penaltyReason}) has joined!");
|
||||
}
|
||||
else
|
||||
E.Owner.Broadcast(await ProcessAnnouncement(_configHandler.Configuration().UserAnnouncementMessage, newPlayer));
|
||||
E.Owner.Broadcast(await ProcessAnnouncement(_configHandler.Configuration().UserAnnouncementMessage,
|
||||
newPlayer));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> ProcessAnnouncement(string msg, EFClient joining)
|
||||
{
|
||||
msg = msg.Replace("{{ClientName}}", joining.Name);
|
||||
msg = msg.Replace("{{ClientLevel}}", $"{Utilities.ConvertLevelToColor(joining.Level, joining.ClientPermission.Name)}{(string.IsNullOrEmpty(joining.GetAdditionalProperty<string>("ClientTag")) ? "" : $" ^7({joining.GetAdditionalProperty<string>("ClientTag")}^7)")}");
|
||||
msg = msg.Replace("{{ClientLevel}}",
|
||||
$"{Utilities.ConvertLevelToColor(joining.Level, joining.ClientPermission.Name)}{(string.IsNullOrEmpty(joining.GetAdditionalProperty<string>("ClientTag")) ? "" : $" ^7({joining.GetAdditionalProperty<string>("ClientTag")}^7)")}");
|
||||
// this prevents it from trying to evaluate it every message
|
||||
if (msg.Contains("{{ClientLocation}}"))
|
||||
{
|
||||
msg = msg.Replace("{{ClientLocation}}", await GetCountryName(joining.IPAddressString));
|
||||
}
|
||||
|
||||
msg = msg.Replace("{{TimesConnected}}", joining.Connections.Ordinalize());
|
||||
|
||||
return msg;
|
||||
@ -100,11 +108,14 @@ namespace IW4MAdmin.Plugins.Welcome
|
||||
{
|
||||
try
|
||||
{
|
||||
string response = await wc.DownloadStringTaskAsync(new Uri($"http://extreme-ip-lookup.com/json/{ip}"));
|
||||
var responseObj = JObject.Parse(response);
|
||||
string response =
|
||||
await wc.DownloadStringTaskAsync(new Uri($"http://extreme-ip-lookup.com/json/{ip}"));
|
||||
var responseObj = JObject.Parse(response);
|
||||
response = responseObj["country"].ToString();
|
||||
|
||||
return string.IsNullOrEmpty(response) ? Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_UNKNOWN_COUNTRY"] : response;
|
||||
return string.IsNullOrEmpty(response)
|
||||
? Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_UNKNOWN_COUNTRY"]
|
||||
: response;
|
||||
}
|
||||
|
||||
catch
|
||||
@ -114,4 +125,4 @@ namespace IW4MAdmin.Plugins.Welcome
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
@ -22,7 +22,8 @@ namespace SharedLibraryCore.Commands
|
||||
E.Message = E.Data;
|
||||
|
||||
Command C = null;
|
||||
foreach (Command cmd in Manager.GetCommands())
|
||||
foreach (Command cmd in Manager.GetCommands()
|
||||
.Where(c => c.Name != null))
|
||||
{
|
||||
if (cmd.Name.Equals(CommandString, StringComparison.OrdinalIgnoreCase) ||
|
||||
(cmd.Alias ?? "").Equals(CommandString, StringComparison.OrdinalIgnoreCase))
|
||||
@ -39,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"]);
|
||||
}
|
||||
@ -1435,6 +1435,7 @@ namespace SharedLibraryCore.Commands
|
||||
Alias = "pa";
|
||||
Permission = Permission.Owner;
|
||||
RequiresTarget = false;
|
||||
_contextFactory = contextFactory;
|
||||
Arguments = new[]
|
||||
{
|
||||
new CommandArgument()
|
||||
|
@ -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; }
|
||||
|
@ -1,23 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using Data.Models;
|
||||
using Humanizer;
|
||||
|
||||
namespace SharedLibraryCore.Configuration
|
||||
{
|
||||
public class GameStringConfiguration : Dictionary<Server.Game, Dictionary<string, string>>
|
||||
public class GameStringConfiguration : Dictionary<Reference.Game, Dictionary<string, string>>
|
||||
{
|
||||
public string GetStringForGame(string key, Server.Game game = Server.Game.IW4)
|
||||
public string GetStringForGame(string key, Reference.Game? game = Reference.Game.IW4)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ContainsKey(game))
|
||||
if (!ContainsKey(game.Value))
|
||||
{
|
||||
return key.Transform(To.TitleCase);
|
||||
}
|
||||
|
||||
var strings = this[game];
|
||||
var strings = this[game.Value];
|
||||
return !strings.ContainsKey(key) ? key.Transform(To.TitleCase) : strings[key];
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -11,20 +11,20 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// </summary>
|
||||
public enum GroupType
|
||||
{
|
||||
EventType,
|
||||
OriginNetworkId,
|
||||
TargetNetworkId,
|
||||
OriginClientNumber,
|
||||
TargetClientNumber,
|
||||
OriginName,
|
||||
TargetName,
|
||||
OriginTeam,
|
||||
TargetTeam,
|
||||
Weapon,
|
||||
Damage,
|
||||
MeansOfDeath,
|
||||
HitLocation,
|
||||
Message,
|
||||
EventType = 0,
|
||||
OriginNetworkId = 1,
|
||||
TargetNetworkId = 2,
|
||||
OriginClientNumber = 3,
|
||||
TargetClientNumber = 4,
|
||||
OriginName = 5,
|
||||
TargetName = 6,
|
||||
OriginTeam = 7,
|
||||
TargetTeam = 8,
|
||||
Weapon = 9,
|
||||
Damage = 10,
|
||||
MeansOfDeath = 11,
|
||||
HitLocation = 12,
|
||||
Message = 13,
|
||||
RConClientNumber = 100,
|
||||
RConScore = 101,
|
||||
RConPing = 102,
|
||||
@ -38,6 +38,8 @@ namespace SharedLibraryCore.Interfaces
|
||||
RConDvarDomain = 110,
|
||||
RConStatusMap = 111,
|
||||
RConStatusGametype = 112,
|
||||
RConStatusHostname = 113,
|
||||
RConStatusMaxPlayers = 114,
|
||||
AdditionalGroup = 200
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,16 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// </summary>
|
||||
ParserRegex Time { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex information for the map change game log
|
||||
/// </summary>
|
||||
ParserRegex MapChange { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex information for the map end game log
|
||||
/// </summary>
|
||||
ParserRegex MapEnd { get; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates the format expected for parsed guids
|
||||
/// </summary>
|
||||
|
@ -11,7 +11,8 @@
|
||||
/// <param name="ipAddress">ip address of the server</param>
|
||||
/// <param name="port">port of the server</param>
|
||||
/// <param name="password"> password of the server</param>
|
||||
/// <param name="rconEngine">engine to create the rcon connection to</param>
|
||||
/// <returns>instance of rcon connection</returns>
|
||||
IRConConnection CreateConnection(string ipAddress, int port, string password);
|
||||
IRConConnection CreateConnection(string ipAddress, int port, string password, string rconEngine);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using static SharedLibraryCore.Server;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
@ -39,8 +37,8 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// get the list of connected clients from status response
|
||||
/// </summary>
|
||||
/// <param name="connection">RCon connection to use</param>
|
||||
/// <returns>list of clients, current map, and current gametype</returns>
|
||||
Task<(List<EFClient>, string, string)> GetStatusAsync(IRConConnection connection);
|
||||
/// <returns><see cref="IStatusResponse"/></returns>
|
||||
Task<IStatusResponse> GetStatusAsync(IRConConnection connection);
|
||||
|
||||
/// <summary>
|
||||
/// stores the RCon configuration
|
||||
@ -50,23 +48,35 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <summary>
|
||||
/// stores the game/client specific version (usually the value of the "version" DVAR)
|
||||
/// </summary>
|
||||
string Version { get; set; }
|
||||
string Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the game name (usually the internal studio iteration ie: IW4, T5 etc...)
|
||||
/// </summary>
|
||||
Game GameName { get; set; }
|
||||
Game GameName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates if the game supports generating a log path from DVAR retrieval
|
||||
/// of fs_game, fs_basepath, g_log
|
||||
/// </summary>
|
||||
bool CanGenerateLogPath { get; set; }
|
||||
bool CanGenerateLogPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the name of the parser
|
||||
/// </summary>
|
||||
string Name { get; set; }
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the type of rcon engine
|
||||
/// eg: COD, Source
|
||||
/// </summary>
|
||||
string RConEngine { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
|
@ -9,59 +9,69 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <summary>
|
||||
/// stores the command format for console commands
|
||||
/// </summary>
|
||||
CommandPrefix CommandPrefixes { get; set; }
|
||||
CommandPrefix CommandPrefixes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex info for parsing get status response
|
||||
/// </summary>
|
||||
ParserRegex Status { get; set; }
|
||||
ParserRegex Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores regex info for parsing the map line from rcon status response
|
||||
/// </summary>
|
||||
ParserRegex MapStatus { get; set; }
|
||||
ParserRegex MapStatus { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores regex info for parsing the gametype line from rcon status response
|
||||
/// </summary>
|
||||
ParserRegex GametypeStatus { get; set; }
|
||||
ParserRegex GametypeStatus { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores regex info for parsing hostname line from rcon status response
|
||||
/// </summary>
|
||||
ParserRegex HostnameStatus { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores regex info for parsing max players line from rcon status response
|
||||
/// </summary>
|
||||
ParserRegex MaxPlayersStatus { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex info for parsing get DVAR responses
|
||||
/// </summary>
|
||||
ParserRegex Dvar { get; set; }
|
||||
ParserRegex Dvar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// stores the regex info for parsing the header of a status response
|
||||
/// </summary>
|
||||
ParserRegex StatusHeader { get; set; }
|
||||
ParserRegex StatusHeader { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the expected response message from rcon when the server is not running
|
||||
/// </summary>
|
||||
string ServerNotRunningResponse { get; set; }
|
||||
string ServerNotRunningResponse { get; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates if the application should wait for response from server
|
||||
/// when executing a command
|
||||
/// </summary>
|
||||
bool WaitForResponse { get; set; }
|
||||
bool WaitForResponse { get; }
|
||||
|
||||
/// <summary>
|
||||
/// indicates the format expected for parsed guids
|
||||
/// </summary>
|
||||
NumberStyles GuidNumberStyle { get; set; }
|
||||
NumberStyles GuidNumberStyle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies simple mappings for dvar names in scenarios where the needed
|
||||
/// information is not stored in a traditional dvar name
|
||||
/// </summary>
|
||||
IDictionary<string, string> OverrideDvarNameMapping { get; set; }
|
||||
IDictionary<string, string> OverrideDvarNameMapping { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the default dvar values for games that don't support certain dvars
|
||||
/// </summary>
|
||||
IDictionary<string, string> DefaultDvarValues { get; set; }
|
||||
IDictionary<string, string> DefaultDvarValues { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies how many lines can be used for ingame notice
|
||||
@ -71,11 +81,11 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// <summary>
|
||||
/// specifies how many characters can be displayed per notice line
|
||||
/// </summary>
|
||||
int NoticeMaxCharactersPerLine { get; set; }
|
||||
int NoticeMaxCharactersPerLine { get; }
|
||||
|
||||
/// <summary>
|
||||
/// specifies the characters used to split a line
|
||||
/// </summary>
|
||||
string NoticeLineSeparator { get; set; }
|
||||
string NoticeLineSeparator { get; }
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user