Compare commits

...

49 Commits

Author SHA1 Message Date
33a427bb8a add country flag and name to profile 2021-07-01 21:58:09 -05:00
c9d7a957dc add reset anticheat metric (!rsa) for issue #177 2021-07-01 13:12:19 -05:00
9c6ff6f353 use right game for estimated score 2021-07-01 13:06:31 -05:00
7444cb6472 actually fix steam id parsing 2021-07-01 10:14:58 -05:00
c7e5c9c8dd parse steam id properly for source games 2021-07-01 09:10:56 -05:00
0256fc35d2 add login/logout events to change tracker
default guest profile to minimum permissions
2021-06-30 21:13:25 -05:00
0019ed8dde fix run as command config not being honored properly 2021-06-30 18:10:45 -05:00
56aec53e72 fix bad key lookup in manager 2021-06-30 14:01:41 -05:00
1b773f21c6 fix alignment for long server names 2021-06-30 10:44:43 -05:00
bccbcce3c1 add lobby rating to home
add gametype (WIP) to home
misc UI tweaks
2021-06-30 09:57:07 -05:00
fc0bed2405 show "out of" ranked players for stats command 2021-06-29 17:14:25 -05:00
16cfb33109 improvements and consistencies to the top stats, most played and top players commands 2021-06-29 15:35:56 -05:00
42979dc5ae Use string for AC snapshot weapon and hit location
Add webfront logging
2021-06-29 15:02:01 -05:00
95cbc85144 fix issue with selecting wrong parser during setup
add minimum name length option
fix issue with stats spm
2021-06-27 20:31:39 -05:00
9cbca390fe Merge branch 'release/pre' of https://github.com/RaidMax/IW4M-Admin into release/pre 2021-06-16 08:55:56 -05:00
38c0c81451 Added CSGO maps (#210)
Added all current default CSGO maps (Competitive, Wingman, Casual, War Games, Retakes, Danger Zone)
2021-06-16 08:54:49 -05:00
af4630ecb9 Additional CSGO compatibility improvements 2021-06-16 08:53:50 -05:00
dbceb23823 fix issue with custom event registration 2021-06-16 08:51:22 -05:00
e628ac0e9e improve CS:GO compatibility 2021-06-11 11:52:30 -05:00
3a1e8359c2 add one log indicator for games (Pluto IW5) that don't log to mods folder even when fs_game is specified. 2021-06-07 16:58:36 -05:00
c397fd5479 update pluto iw5 parser for new version
fix issue with finding players with color codes in name
2021-06-06 13:40:58 -05:00
16e1bbb1b5 fix bug with additional group mapping key 2021-06-03 13:21:34 -05:00
eff1fe237d Fix null pointer exception (#207) 2021-06-03 10:52:27 -05:00
b09ce46ff9 Merge branch 'release/pre' of https://github.com/RaidMax/IW4M-Admin into release/pre 2021-06-03 10:51:19 -05:00
be08d49f0a add initial CS:GO support 2021-06-03 10:51:03 -05:00
b9fb274db6 Update ParserPT6.js (#206) 2021-05-15 09:22:34 -05:00
9488f754d4 Fix stupid idiot things 2021-05-15 09:20:49 -05:00
1595c1fa99 Initial implementation of configuration support for script plugins 2021-05-14 21:52:55 -05:00
4d21680d59 small issue fix with api and more checks for welcome tags 2021-05-04 19:01:09 -05:00
127af98b00 fix issue with help and dynamically loaded plugins with commands 2021-04-30 12:37:55 -05:00
21a9eb8716 Update DefaultSettings.json (T4, IW5, S1x) (#202)
* Update DefaultSettings.json
2021-04-30 12:35:38 -05:00
f1593e2f99 fix issue with chat message search 2021-04-18 09:17:01 -05:00
74dbc3572f Added WaW bot guid (#200)
may be PlutoniumT4 only.
2021-04-16 13:48:52 -05:00
e6d149736a Added T4 weapon names. (#198) 2021-04-16 13:47:58 -05:00
a034394610 Merge branch 'release/pre' of https://github.com/RaidMax/IW4M-Admin into release/pre 2021-04-16 13:38:34 -05:00
34e7d69110 Add RCon support for S1x 2021-04-16 13:35:51 -05:00
4b686e5fdd Update Plutonium T4 Parser [v0.2]
Static version string
2021-04-08 09:36:32 -05:00
0428453426 Update Pluto T4 Parser
Uses new static version string.
2021-04-08 09:36:32 -05:00
e80e5d6a70 remove test code 2021-04-07 09:53:32 -05:00
22cf3081e1 update parser for Plutonium T4 2021-04-07 09:50:41 -05:00
76a18d9797 add parser support for Plutonium T4 2021-04-07 09:33:49 -05:00
fc13363c9c add user agent header for vpn detection issue #195 2021-04-07 08:47:42 -05:00
f916c51bc0 fix issue with iw5 weapon prefix not being removed properly 2021-04-01 13:12:47 -05:00
21087d6c25 remove whitespace on alias display and client name search 2021-03-31 11:20:32 -05:00
c84e374274 fix issue with client api for issue #191 2021-03-27 19:01:27 -05:00
e777a68105 properly pass game name to game string config finder.
add weapon prefix to weapon name parser for (iw5).
add some iw3 game strings
2021-03-23 21:42:26 -05:00
1f9c80e23b strip colors from header penalty on profile 2021-03-23 21:42:26 -05:00
33371a6d28 Added iw6 aliases (#184) 2021-03-23 21:42:26 -05:00
Edo
d164ef2eab Removed tempbanclient (#187)
Removed tempbanclient because Tekno has "weird" internal DB that manages temp bans it it would interfere with iw4m
2021-03-23 10:36:33 -05:00
125 changed files with 14528 additions and 1914 deletions

4
.gitignore vendored
View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -3,7 +3,13 @@
"Using": [
"Serilog.Sinks.File"
],
"MinimumLevel": "Information",
"MinimumLevel": {
"Default": "Information",
"Override": {
"System": "Warning",
"Microsoft": "Warning"
}
},
"WriteTo": [
{
"Name": "File",

View 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();
}
}
}

View File

@ -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"
}
}
}

View File

@ -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()

View File

@ -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();
}
}
}

View File

@ -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>>());
}
}
}
}

View File

@ -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}'")
};
}
}
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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; }
}
}

View File

@ -168,6 +168,7 @@ namespace IW4MAdmin.Application.Misc
}
}
_scriptEngine.SetValue("_configHandler", new ScriptPluginConfigurationWrapper(Name, _scriptEngine));
await OnLoadAsync(manager);
try

View 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);
}
}
}

View File

@ -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);
}
}

View File

@ -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)
{

View File

@ -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();
}
}
}

View 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; }
}
}

View File

@ -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">

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}
}

View File

@ -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 =>

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}
}

View File

@ -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 =>

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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)
{
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}
}

View File

@ -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 =>

View File

@ -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; }

View File

@ -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 ?? "--";
}
}

View File

@ -13,7 +13,9 @@
T4 = 5,
T5 = 6,
T6 = 7,
T7 = 8
T7 = 8,
SHG1 = 9,
CSGO = 10
}
}
}

View File

@ -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;
}
}
}

View File

@ -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}

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}
}

View 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>

View File

@ -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;
}
}
}

View 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();
}
}
}

View 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>

View File

@ -0,0 +1,9 @@
using RconSharp;
namespace Integrations.Source.Interfaces
{
public interface IRConClientFactory
{
RconClient CreateClient(string hostname, int port);
}
}

View 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);
}
}
}

View 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)
{
}
}
}

View File

@ -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">

View File

@ -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">

View File

@ -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">

View File

@ -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">

View File

@ -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">

View 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) {
}
};

View 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) {
}
};

View File

@ -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';

View File

@ -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) {
}
};
};

View 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) {
}
};

View 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) {}
};

View File

@ -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');

View File

@ -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) {
}
};
};

View File

@ -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 () {

View File

@ -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);
}
},

View File

@ -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
};

View File

@ -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

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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
}
}
}
}
}

View 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;
}
}
}
}

View File

@ -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);

View File

@ -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)))

View File

@ -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",

View File

@ -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; }
}
}

View File

@ -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

View File

@ -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:

View File

@ -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">

View File

@ -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
}
}
}
}
}

View File

@ -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>

View File

@ -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" }
};
}

View File

@ -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");

View File

@ -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()

View File

@ -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; }

View File

@ -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];
}
}

View File

@ -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"]);

View File

@ -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; }
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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>

View File

@ -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
}

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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

View File

@ -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