diff --git a/Application/Commands/MapAndGameTypeCommand.cs b/Application/Commands/MapAndGameTypeCommand.cs new file mode 100644 index 000000000..2d97e4ee8 --- /dev/null +++ b/Application/Commands/MapAndGameTypeCommand.cs @@ -0,0 +1,126 @@ +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Data.Models.Client; +using IW4MAdmin.Application.Extensions; +using Microsoft.Extensions.Logging; +using SharedLibraryCore; +using SharedLibraryCore.Commands; +using SharedLibraryCore.Configuration; +using SharedLibraryCore.Interfaces; +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace IW4MAdmin.Application.Commands +{ + public class MapAndGameTypeCommand : Command + { + private const string ArgumentRegexPattern = "(?:\"([^\"]+)\"|([^\\s]+)) (?:\"([^\"]+)\"|([^\\s]+))"; + private readonly ILogger _logger; + private readonly DefaultSettings _defaultSettings; + + public MapAndGameTypeCommand(ILogger logger, CommandConfiguration config, + DefaultSettings defaultSettings, ITranslationLookup layout) : base(config, layout) + { + Name = "mapandgametype"; + Description = _translationLookup["COMMANDS_MAG_DESCRIPTION"]; + Alias = "mag"; + Permission = EFClient.Permission.Administrator; + RequiresTarget = false; + Arguments = new[] + { + new CommandArgument + { + Name = _translationLookup["COMMADS_MAG_ARG_1"], + Required = true + }, + new CommandArgument + { + Name = _translationLookup["COMMADS_MAG_ARG_2"], + Required = true + } + }; + _logger = logger; + _defaultSettings = defaultSettings; + } + + public override async Task ExecuteAsync(GameEvent gameEvent) + { + var match = Regex.Match(gameEvent.Data.Trim(), ArgumentRegexPattern, + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + if (!match.Success) + { + gameEvent.Origin.Tell(Syntax); + return; + } + + string map; + string gametype; + + if (match.Groups.Count > 3) + { + map = match.Groups[2].ToString(); + gametype = match.Groups[4].ToString(); + } + + else + { + map = match.Groups[1].ToString(); + gametype = match.Groups[3].ToString(); + } + + var matchingMaps = gameEvent.Owner.FindMap(map); + var matchingGametypes = _defaultSettings.FindGametype(gametype, gameEvent.Owner.GameName); + + if (matchingMaps.Count > 1) + { + gameEvent.Origin.Tell(_translationLookup["COMMANDS_MAG_MULTIPLE_MAPS"]); + + foreach (var matchingMap in matchingMaps) + { + gameEvent.Origin.Tell( + $"[(Color::Yellow){matchingMap.Alias}(Color::White)] [(Color::Yellow){matchingMap.Name}(Color::White)]"); + } + + return; + } + + if (matchingGametypes.Count > 1) + { + gameEvent.Origin.Tell(_translationLookup["COMMANDS_MAG_MULTIPLE_GAMETYPES"]); + + foreach (var matchingGametype in matchingGametypes) + { + gameEvent.Origin.Tell( + $"[(Color::Yellow){matchingGametype.Alias}(Color::White)] [(Color::Yellow){matchingGametype.Name}(Color::White)]"); + } + + return; + } + + map = matchingMaps.FirstOrDefault()?.Name ?? map; + gametype = matchingGametypes.FirstOrDefault()?.Name ?? gametype; + var hasMatchingGametype = matchingGametypes.Any(); + + _logger.LogDebug("Changing map to {Map} and gametype {Gametype}", map, gametype); + + await gameEvent.Owner.SetDvarAsync("g_gametype", gametype); + gameEvent.Owner.Broadcast(_translationLookup["COMMANDS_MAP_SUCCESS"].FormatExt(map)); + await Task.Delay(gameEvent.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds); + + switch (gameEvent.Owner.GameName) + { + case Server.Game.IW5: + await gameEvent.Owner.ExecuteCommandAsync( + $"load_dsr {(hasMatchingGametype ? gametype.ToUpper() + "_default" : gametype)};map {map}"); + break; + case Server.Game.T6: + await gameEvent.Owner.ExecuteCommandAsync($"exec {gametype}.cfg;map {map}"); + break; + default: + await gameEvent.Owner.ExecuteCommandAsync($"map {map}"); + break; + } + } + } +} diff --git a/Application/DefaultSettings.json b/Application/DefaultSettings.json index 54673aef4..3c2a7f61b 100644 --- a/Application/DefaultSettings.json +++ b/Application/DefaultSettings.json @@ -68,6 +68,504 @@ } } ], + "Gametypes": [ + { + "Game": "IW4", + "Gametypes": [ + { + "Name": "arena", + "Alias": "Arena" + }, + { + "Name": "ctf", + "Alias": "Capture The Flag" + }, + { + "Name": "dd", + "Alias": "Demolition" + }, + { + "Name": "dm", + "Alias": "Free For All" + }, + { + "Name": "dom", + "Alias": "Domination" + }, + { + "Name": "gtnw", + "Alias": "Global Thermo-Nuclear War" + }, + { + "Name": "koth", + "Alias": "Headquarters" + }, + { + "Name": "oneflag", + "Alias": "One-Flag CTF" + }, + { + "Name": "sab", + "Alias": "Sabotage" + }, + { + "Name": "sd", + "Alias": "Search & Destroy" + }, + { + "Name": "war", + "Alias": "Team Deathmatch" + } + ] + }, + { + "Game": "T4", + "Gametypes": [ + { + "Name": "ctf", + "Alias": "Capture The Flag" + }, + { + "Name": "dom", + "Alias": "Domination" + }, + { + "Name": "dm", + "Alias": "Free For All" + }, + { + "Name": "koth", + "Alias": "Headquarters" + }, + { + "Name": "tdm", + "Alias": "Team Deathmatch" + }, + { + "Name": "sab", + "Alias": "Sabotage" + }, + { + "Name": "sd", + "Alias": "Search & Destroy" + }, + { + "Name": "twar", + "Alias": "War" + } + ] + }, + { + "Game": "IW5", + "Gametypes": [ + { + "Name": "tdm", + "Alias": "Team Deathmatch" + }, + { + "Name": "dom", + "Alias": "Domination" + }, + { + "Name": "ctf", + "Alias": "Capture The Flag" + }, + { + "Name": "dd", + "Alias": "Demolition" + }, + { + "Name": "dz", + "Alias": "Drop Zone" + }, + { + "Name": "ffa", + "Alias": "Free For All" + }, + { + "Name": "gg", + "Alias": "Gun Game" + }, + { + "Name": "hq", + "Alias": "Headquarters" + }, + { + "Name": "koth", + "Alias": "Headquarters" + }, + { + "Name": "inf", + "Alias": "Infected" + }, + { + "Name": "jug", + "Alias": "Juggernaut" + }, + { + "Name": "kc", + "Alias": "Kill Confirmed" + }, + { + "Name": "oic", + "Alias": "One In The Chamber" + }, + { + "Name": "sab", + "Alias": "Sabotage" + }, + { + "Name": "sd", + "Alias": "Search & Destroy" + }, + { + "Name": "tdef", + "Alias": "Team Defender" + }, + { + "Name": "tj", + "Alias": "Team Juggernaut" + } + ] + }, + { + "Game": "T5", + "Gametypes": [ + { + "Name": "ctf", + "Alias": "Capture The Flag" + }, + { + "Name": "dem", + "Alias": "Demolition" + }, + { + "Name": "dom", + "Alias": "Domination" + }, + { + "Name": "dm", + "Alias": "Free For All" + }, + { + "Name": "gun", + "Alias": "Gun Game" + }, + { + "Name": "hlnd", + "Alias": "Sticks & Stones" + }, + { + "Name": "koth", + "Alias": "Headquarters" + }, + { + "Name": "oic", + "Alias": "One In The Chamber" + }, + { + "Name": "sab", + "Alias": "Sabotage" + }, + { + "Name": "sd", + "Alias": "Search & Destroy" + }, + { + "Name": "shrp", + "Alias": "Sharpshooter" + }, + { + "Name": "tdm", + "Alias": "Team Deathmatch" + } + ] + }, + { + "Game": "IW6", + "Gametypes": [ + { + "Name": "blitz", + "Alias": "Blitz" + }, + { + "Name": "conf", + "Alias": "Kill Confirmed" + }, + { + "Name": "cranked", + "Alias": "Cranked" + }, + { + "Name": "dm", + "Alias": "Free For All" + }, + { + "Name": "dom", + "Alias": "Domination" + }, + { + "Name": "grind", + "Alias": "Grind" + }, + { + "Name": "grnd", + "Alias": "Drop Zone" + }, + { + "Name": "gun", + "Alias": "Gun Game" + }, + { + "Name": "horde", + "Alias": "Safeguard" + }, + { + "Name": "infect", + "Alias": "Infected" + }, + { + "Name": "sd", + "Alias": "Search & Destroy" + }, + { + "Name": "siege", + "Alias": "Reinforce" + }, + { + "Name": "sotf", + "Alias": "Hunted" + }, + { + "Name": "sotf_ffa", + "Alias": "Hunted FFA" + }, + { + "Name": "sr", + "Alias": "Search & Rescue" + }, + { + "Name": "war", + "Alias": "Team Deathmatch" + } + ] + }, + { + "Game": "T6", + "Gametypes": [ + { + "Name": "conf", + "Alias": "Kill Confirmed" + }, + { + "Name": "ctf", + "Alias": "Capture The Flag" + }, + { + "Name": "dem", + "Alias": "Demolition" + }, + { + "Name": "dm", + "Alias": "Free For All" + }, + { + "Name": "dom", + "Alias": "Domination" + }, + { + "Name": "gun", + "Alias": "Gun Game" + }, + { + "Name": "hq", + "Alias": "Headquarters" + }, + { + "Name": "koth", + "Alias": "Hardpoint" + }, + { + "Name": "oic", + "Alias": "One In The Chamber" + }, + { + "Name": "oneflag", + "Alias": "One-Flag CTF" + }, + { + "Name": "sas", + "Alias": "Sticks & Stones" + }, + { + "Name": "sd", + "Alias": "Search & Destroy" + }, + { + "Name": "shrp", + "Alias": "Sharpshooter" + }, + { + "Name": "tdm", + "Alias": "Team Deathmatch" + } + ] + }, + { + "Game": "T7", + "Gametypes": [ + { + "Name": "ball", + "Alias": "Uplink" + }, + { + "Name": "clean", + "Alias": "Fracture" + }, + { + "Name": "conf", + "Alias": "Kill Confirmed" + }, + { + "Name": "ctf", + "Alias": "Capture The Flag" + }, + { + "Name": "dom", + "Alias": "Domination" + }, + { + "Name": "dem", + "Alias": "Demolition" + }, + { + "Name": "dm", + "Alias": "Free For All" + }, + { + "Name": "escort", + "Alias": "Safeguard" + }, + { + "Name": "gun", + "Alias": "Gun Game" + }, + { + "Name": "koth", + "Alias": "Hardpoint" + }, + { + "Name": "sd", + "Alias": "Search & Destroy" + }, + { + "Name": "tdm", + "Alias": "Team Deathmatch" + }, + { + "Name": "hc_ball", + "Alias": "Hardcore Uplink" + }, + { + "Name": "hc_clean", + "Alias": "Hardcore Fracture" + }, + { + "Name": "hc_conf", + "Alias": "Hardcore Kill Confirmed" + }, + { + "Name": "hc_ctf", + "Alias": "Hardcore Capture The Flag" + }, + { + "Name": "hc_dom", + "Alias": "Hardcore Domination" + }, + { + "Name": "hc_dem", + "Alias": "Hardcore Demolition" + }, + { + "Name": "hc_dm", + "Alias": "Hardcore Free For All" + }, + { + "Name": "hc_escort", + "Alias": "Hardcore Safeguard" + }, + { + "Name": "hc_gun", + "Alias": "Hardcore Gun Game" + }, + { + "Name": "hc_koth", + "Alias": "Hardcore Hardpoint" + }, + { + "Name": "hc_sd", + "Alias": "Hardcore Search & Destroy" + }, + { + "Name": "hc_tdm", + "Alias": "Hardcore Team Deathmatch" + } + ] + }, + { + "Game": "SHG1", + "Gametypes": [ + { + "Name": "ball", + "Alias": "Uplink" + }, + { + "Name": "conf", + "Alias": "Kill Confirmed" + }, + { + "Name": "ctf", + "Alias": "Capture The Flag" + }, + { + "Name": "dom", + "Alias": "Domination" + }, + { + "Name": "dm", + "Alias": "Free For All" + }, + { + "Name": "gun", + "Alias": "Gun Game" + }, + { + "Name": "hp", + "Alias": "Hardpoint" + }, + { + "Name": "infect", + "Alias": "Infected" + }, + { + "Name": "sd", + "Alias": "Search & Destroy" + }, + { + "Name": "sr", + "Alias": "Search & Rescue" + }, + { + "Name": "war", + "Alias": "Team Deathmatch" + }, + { + "Name": "twar", + "Alias": "Momentum" + } + ] + } + ], "Maps": [ { "Game": "IW3", @@ -1529,62 +2027,62 @@ "winchester1200": "W1200", "concussion": "Stun", "melee": "Knife", - "Frag" : "Grenade", + "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" - } + "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" + } } } diff --git a/Application/Extensions/CommandExtensions.cs b/Application/Extensions/CommandExtensions.cs index ca27dfb30..8004baf3c 100644 --- a/Application/Extensions/CommandExtensions.cs +++ b/Application/Extensions/CommandExtensions.cs @@ -1,6 +1,10 @@ -using IW4MAdmin.Application.Misc; +using System; +using System.Collections.Generic; +using IW4MAdmin.Application.Misc; using SharedLibraryCore.Interfaces; using System.Linq; +using SharedLibraryCore; +using SharedLibraryCore.Configuration; namespace IW4MAdmin.Application.Extensions { @@ -13,9 +17,19 @@ namespace IW4MAdmin.Application.Extensions /// public static string CommandConfigNameForType(this IManagerCommand command) { - return command.GetType() == typeof(ScriptCommand) ? - $"{char.ToUpper(command.Name[0])}{command.Name.Substring(1)}Command" : - command.GetType().Name; + return command.GetType() == typeof(ScriptCommand) + ? $"{char.ToUpper(command.Name[0])}{command.Name.Substring(1)}Command" + : command.GetType().Name; } + + public static IList FindMap(this Server server, string mapName) => server.Maps.Where(map => + map.Name.Equals(mapName, StringComparison.InvariantCultureIgnoreCase) || + map.Alias.Equals(mapName, StringComparison.InvariantCultureIgnoreCase)).ToList(); + + public static IList FindGametype(this DefaultSettings settings, string gameType, Server.Game? game = null) => + settings.Gametypes?.Where(gt => game == null || gt.Game == game) + .SelectMany(gt => gt.Gametypes).Where(gt => + gt.Alias.Contains(gameType, StringComparison.CurrentCultureIgnoreCase) || + gt.Name.Contains(gameType, StringComparison.CurrentCultureIgnoreCase)).ToList(); } } diff --git a/SharedLibraryCore/Configuration/DefaultSettings.cs b/SharedLibraryCore/Configuration/DefaultSettings.cs index d9ebd0a6a..5f0e6bdda 100644 --- a/SharedLibraryCore/Configuration/DefaultSettings.cs +++ b/SharedLibraryCore/Configuration/DefaultSettings.cs @@ -7,6 +7,7 @@ namespace SharedLibraryCore.Configuration public string[] AutoMessages { get; set; } public string[] GlobalRules { get; set; } public MapConfiguration[] Maps { get; set; } + public GametypeConfiguration[] Gametypes { get; set; } public QuickMessageConfiguration[] QuickMessages {get; set;} public string[] DisallowedClientNames { get; set; } public GameStringConfiguration GameStrings { get; set; } diff --git a/SharedLibraryCore/Configuration/GametypeConfiguration.cs b/SharedLibraryCore/Configuration/GametypeConfiguration.cs new file mode 100644 index 000000000..84f75edab --- /dev/null +++ b/SharedLibraryCore/Configuration/GametypeConfiguration.cs @@ -0,0 +1,8 @@ +namespace SharedLibraryCore.Configuration +{ + public class GametypeConfiguration + { + public Server.Game Game { get; set; } + public Gametype[] Gametypes { get; set; } + } +} diff --git a/SharedLibraryCore/Configuration/MapConfiguration.cs b/SharedLibraryCore/Configuration/MapConfiguration.cs index 54bdbbe70..34b91aed1 100644 --- a/SharedLibraryCore/Configuration/MapConfiguration.cs +++ b/SharedLibraryCore/Configuration/MapConfiguration.cs @@ -1,16 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using static SharedLibraryCore.Server; - -namespace SharedLibraryCore.Configuration +namespace SharedLibraryCore.Configuration { public class MapConfiguration { - public Game Game { get; set; } - public List Maps { get; set; } + public Server.Game Game { get; set; } + public Map[] Maps { get; set; } } } diff --git a/SharedLibraryCore/Game/Gametype.cs b/SharedLibraryCore/Game/Gametype.cs new file mode 100644 index 000000000..f6f40f688 --- /dev/null +++ b/SharedLibraryCore/Game/Gametype.cs @@ -0,0 +1,8 @@ +namespace SharedLibraryCore +{ + public class Gametype + { + public string Name { get; set; } + public string Alias { get; set; } + } +} diff --git a/SharedLibraryCore/Game/Map.cs b/SharedLibraryCore/Game/Map.cs new file mode 100644 index 000000000..679cab797 --- /dev/null +++ b/SharedLibraryCore/Game/Map.cs @@ -0,0 +1,10 @@ +namespace SharedLibraryCore +{ + public class Map + { + public string Name { get; set; } + public string Alias { get; set; } + + public override string ToString() => Alias; + } +} diff --git a/SharedLibraryCore/Map.cs b/SharedLibraryCore/Map.cs deleted file mode 100644 index b4c2705d3..000000000 --- a/SharedLibraryCore/Map.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Reflection; - -namespace SharedLibraryCore -{ - public class Map - { - public String Name { get; set; } - public String Alias { get; set; } - - public override string ToString() => Alias; - } -}