Compare commits

...

15 Commits

Author SHA1 Message Date
f32ac3f45e abstract engine color codes to use (Color::<Color>) format to make codes more.
see pt6 parser and configs for example usages
2021-11-23 17:26:33 -06:00
3716255740 fix issue with caching implementation 2021-11-21 15:33:44 -06:00
d36e19a077 try renable FTP publish 2021-11-20 20:37:11 -06:00
12cc5f1820 update help command to use per game commands 2021-11-20 20:32:15 -06:00
61a131be9d fix plugin error formatting 2021-11-20 19:01:25 -06:00
d93bfc11d0 update caching to use automatic timer instead of request based to prevent task cancellation 2021-11-15 10:25:55 -06:00
21f290ca58 add default port and rcon password hint during setup 2021-11-14 21:38:00 -06:00
d3df9623aa add console log sink for critical errors 2021-11-13 21:24:51 -06:00
1b9ca676dc update plugin error message format 2021-11-13 21:19:44 -06:00
58616e18fe Merge branch 'release/pre' of https://github.com/RaidMax/IW4M-Admin into release/pre 2021-11-04 19:10:37 -05:00
0dcbafd0f2 update webfront ip lookup to bypass api key restriction 2021-11-04 19:10:10 -05:00
f8723e6a8c Add Pluto IW5 Maps from r2385 (#220) 2021-11-03 18:53:23 -05:00
3ebdbde33d fix issue with assigning correct server when processing command 2021-11-03 18:51:52 -05:00
769faaa31b update country flag api 2021-11-02 18:12:47 -05:00
2734a3f138 remove javascript error log trying to load hljs from non config pages 2021-11-02 18:02:04 -05:00
78 changed files with 1233 additions and 734 deletions

View File

@ -15,6 +15,7 @@ using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Text;
@ -217,6 +218,8 @@ namespace IW4MAdmin.Application
return _commands;
}
public IReadOnlyList<IManagerCommand> Commands => _commands.ToImmutableList();
public async Task UpdateServerStates()
{
// store the server hash code and task for it

View File

@ -0,0 +1,59 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Finds player by name
/// </summary>
public class FindPlayerCommand : Command
{
public FindPlayerCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "find";
Description = _translationLookup["COMMANDS_FIND_DESC"];
Alias = "f";
Permission = EFClient.Permission.Administrator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true
}
};
}
public override async Task ExecuteAsync(GameEvent gameEvent)
{
if (gameEvent.Data.Length < 3)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_FIND_MIN"]);
return;
}
var players = await gameEvent.Owner.Manager.GetClientService().FindClientsByIdentifier(gameEvent.Data);
if (!players.Any())
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_FIND_EMPTY"]);
return;
}
foreach (var client in players)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_FIND_FORMAT_V2"].FormatExt(client.Name,
client.ClientId, Utilities.ConvertLevelToColor((EFClient.Permission) client.LevelInt, client.Level),
client.IPAddress, (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture()));
}
}
}
}

View File

@ -0,0 +1,93 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Prints help information
/// </summary>
public class HelpCommand : Command
{
public HelpCommand(CommandConfiguration config, ITranslationLookup translationLookup) :
base(config, translationLookup)
{
Name = "help";
Description = translationLookup["COMMANDS_HELP_DESC"];
Alias = "h";
Permission = EFClient.Permission.User;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument
{
Name = translationLookup["COMMANDS_ARGS_COMMANDS"],
Required = false
}
};
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
var searchTerm = gameEvent.Data.Trim();
var availableCommands = gameEvent.Owner.Manager.Commands.Distinct().Where(command =>
command.SupportedGames == null || !command.SupportedGames.Any() ||
command.SupportedGames.Contains(gameEvent.Owner.GameName))
.Where(command => gameEvent.Origin.Level >= command.Permission);
if (searchTerm.Length > 2)
{
var matchingCommand = availableCommands.FirstOrDefault(command =>
command.Name.Equals(searchTerm, StringComparison.InvariantCultureIgnoreCase) ||
command.Alias.Equals(searchTerm, StringComparison.InvariantCultureIgnoreCase));
if (matchingCommand != null)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_HELP_SEARCH_RESULT"]
.FormatExt(matchingCommand.Name, matchingCommand.Alias));
gameEvent.Origin.Tell(matchingCommand.Syntax);
}
else
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_HELP_NOTFOUND"]);
}
}
else
{
var commandStrings = availableCommands.Select((command, index) =>
new
{
response = $" {_translationLookup["COMMANDS_HELP_LIST_FORMAT"].FormatExt(command.Name)} ",
index
});
var helpResponse = new StringBuilder();
foreach (var item in commandStrings)
{
helpResponse.Append(item.response);
if (item.index == 0 || item.index % 4 != 0)
{
continue;
}
gameEvent.Origin.Tell(helpResponse.ToString());
helpResponse = new StringBuilder();
}
gameEvent.Origin.Tell(helpResponse.ToString());
gameEvent.Origin.Tell(_translationLookup["COMMANDS_HELP_MOREINFO"]);
}
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Lists all unmasked admins
/// </summary>
public class ListAdminsCommand : Command
{
public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "admins";
Description = _translationLookup["COMMANDS_ADMINS_DESC"];
Alias = "a";
Permission = EFClient.Permission.User;
RequiresTarget = false;
}
public static string OnlineAdmins(Server server, ITranslationLookup lookup)
{
var onlineAdmins = server.GetClientsAsList()
.Where(p => p.Level > EFClient.Permission.Flagged)
.Where(p => !p.Masked)
.Select(p =>
$"[(Color::Yellow){Utilities.ConvertLevelToColor(p.Level, p.ClientPermission.Name)}(Color::White)] {p.Name}")
.ToList();
return onlineAdmins.Any() ? string.Join(Environment.NewLine, onlineAdmins) : lookup["COMMANDS_ADMINS_NONE"];
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
foreach (var line in OnlineAdmins(gameEvent.Owner, _translationLookup).Split(Environment.NewLine))
{
var _ = gameEvent.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix)
? gameEvent.Owner.Broadcast(line)
: gameEvent.Origin.Tell(line);
}
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Lists alises of specified client
/// </summary>
public class ListAliasesCommand : Command
{
public ListAliasesCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "alias";
Description = _translationLookup["COMMANDS_ALIAS_DESC"];
Alias = "known";
Permission = EFClient.Permission.Moderator;
RequiresTarget = true;
Arguments = new[]
{
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true,
}
};
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
var message = new StringBuilder();
var names = new List<string>(gameEvent.Target.AliasLink.Children.Select(a => a.Name));
var ips = new List<string>(gameEvent.Target.AliasLink.Children.Select(a => a.IPAddress.ConvertIPtoString())
.Distinct());
gameEvent.Origin.Tell($"[(Color::Accent){gameEvent.Target}(Color::White)]");
message.Append($"{_translationLookup["COMMANDS_ALIAS_ALIASES"]}: ");
message.Append(string.Join(" | ", names));
gameEvent.Origin.Tell(message.ToString());
message.Clear();
message.Append($"{_translationLookup["COMMANDS_ALIAS_IPS"]}: ");
message.Append(string.Join(" | ", ips));
gameEvent.Origin.Tell(message.ToString());
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,37 @@
using System.Linq;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// List online clients
/// </summary>
public class ListClientsCommand : Command
{
public ListClientsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "list";
Description = _translationLookup["COMMANDS_LIST_DESC"];
Alias = "l";
Permission = EFClient.Permission.Moderator;
RequiresTarget = false;
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
var clientList = gameEvent.Owner.GetClientsAsList()
.Select(client =>
$"[(Color::Accent){client.ClientPermission.Name}(Color::White){(string.IsNullOrEmpty(client.Tag) ? "" : $" {client.Tag}")}(Color::White)][(Color::Yellow)#{client.ClientNumber}(Color::White)] {client.Name}")
.ToArray();
gameEvent.Origin.Tell(clientList);
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Lists the loaded plugins
/// </summary>
public class ListPluginsCommand : Command
{
private readonly IEnumerable<IPlugin> _plugins;
public ListPluginsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
IEnumerable<IPlugin> plugins) : base(config, translationLookup)
{
Name = "plugins";
Description = _translationLookup["COMMANDS_PLUGINS_DESC"];
Alias = "p";
Permission = EFClient.Permission.Administrator;
RequiresTarget = false;
_plugins = plugins;
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_PLUGINS_LOADED"]);
foreach (var plugin in _plugins.Where(plugin => !plugin.IsParser))
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_LIST_PLUGINS_FORMAT"]
.FormatExt(plugin.Name, plugin.Version, plugin.Author));
}
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// List all reports on the server
/// </summary>
public class ListReportsCommand : Command
{
public ListReportsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "reports";
Description = _translationLookup["COMMANDS_REPORTS_DESC"];
Alias = "reps";
Permission = EFClient.Permission.Moderator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_CLEAR"],
Required = false
}
};
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
if (gameEvent.Data != null && gameEvent.Data.ToLower().Contains(_translationLookup["COMMANDS_ARGS_CLEAR"]))
{
gameEvent.Owner.Reports = new List<Report>();
gameEvent.Origin.Tell(_translationLookup["COMMANDS_REPORTS_CLEAR_SUCCESS"]);
return Task.CompletedTask;
}
if (gameEvent.Owner.Reports.Count < 1)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_REPORTS_NONE"]);
return Task.CompletedTask;
}
foreach (var report in gameEvent.Owner.Reports)
{
gameEvent.Origin.Tell(
$"(Color::Accent){report.Origin.Name}(Color::White) -> (Color::Red){report.Target.Name}(Color::White): {report.Reason}");
}
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,45 @@
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Sends a private message to another player
/// </summary>
public class PrivateMessageCommand : Command
{
public PrivateMessageCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "privatemessage";
Description = _translationLookup["COMMANDS_PM_DESC"];
Alias = "pm";
Permission = EFClient.Permission.User;
RequiresTarget = true;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true
},
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
Required = true
}
};
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
gameEvent.Target.Tell(_translationLookup["COMMANDS_PRIVATE_MESSAGE_FORMAT"].FormatExt(gameEvent.Origin.Name, gameEvent.Data));
gameEvent.Origin.Tell(_translationLookup["COMMANDS_PRIVATE_MESSAGE_RESULT"]
.FormatExt(gameEvent.Target.Name, gameEvent.Data));
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,77 @@
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Report client for given reason
/// </summary>
public class ReportClientCommand : Command
{
public ReportClientCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "report";
Description = _translationLookup["COMMANDS_REPORT_DESC"];
Alias = "rep";
Permission = EFClient.Permission.User;
RequiresTarget = true;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true
},
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_REASON"],
Required = true
}
};
}
public override async Task ExecuteAsync(GameEvent commandEvent)
{
if (commandEvent.Data.ToLower().Contains("camp"))
{
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_CAMP"]);
return;
}
var success = false;
switch ((await commandEvent.Target.Report(commandEvent.Data, commandEvent.Origin)
.WaitAsync(Utilities.DefaultCommandTimeout, commandEvent.Owner.Manager.CancellationToken)).FailReason)
{
case GameEvent.EventFailReason.None:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_SUCCESS"]);
success = true;
break;
case GameEvent.EventFailReason.Exception:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_DUPLICATE"]);
break;
case GameEvent.EventFailReason.Permission:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL"]
.FormatExt(commandEvent.Target.Name));
break;
case GameEvent.EventFailReason.Invalid:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_SELF"]);
break;
case GameEvent.EventFailReason.Throttle:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_TOOMANY"]);
break;
}
if (success)
{
commandEvent.Owner.ToAdmins(
$"(Color::Accent){commandEvent.Origin.Name}(Color::White) -> (Color::Red){commandEvent.Target.Name}(Color::White): {commandEvent.Data}");
}
}
}
}

View File

@ -0,0 +1,46 @@
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Prints out a message to all clients on all servers
/// </summary>
public class SayAllCommand : Command
{
public SayAllCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "sayall";
Description = _translationLookup["COMMANDS_SAY_ALL_DESC"];
Alias = "sa";
Permission = EFClient.Permission.Moderator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
Required = true
}
};
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
var message = $"(Color::Accent){gameEvent.Origin.Name}(Color::White) - (Color::Red){gameEvent.Data}";
foreach (var server in gameEvent.Owner.Manager.GetServers())
{
server.Broadcast(message, gameEvent.Origin);
}
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SAY_SUCCESS"]);
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,42 @@
using System.Threading.Tasks;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
using EFClient = Data.Models.Client.EFClient;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Prints out a message to all clients on the server
/// </summary>
public class SayCommand : Command
{
public SayCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "say";
Description = _translationLookup["COMMANDS_SAY_DESC"];
Alias = "s";
Permission = EFClient.Permission.Moderator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
Required = true
}
};
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
gameEvent.Owner.Broadcast(
_translationLookup["COMMANDS_SAY_FORMAT"].FormatExt(gameEvent.Origin.Name, gameEvent.Data),
gameEvent.Origin);
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SAY_SUCCESS"]);
return Task.CompletedTask;
}
}
}

View File

@ -0,0 +1,38 @@
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Prints client information
/// </summary>
public class WhoAmICommand : Command
{
public WhoAmICommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "whoami";
Description = _translationLookup["COMMANDS_WHO_DESC"];
Alias = "who";
Permission = EFClient.Permission.User;
RequiresTarget = false;
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
var you =
"[(Color::Yellow)#{{clientNumber}}(Color::White)] [(Color::Yellow)@{{clientId}}(Color::White)] [{{networkId}}] [{{ip}}] [(Color::Cyan){{level}}(Color::White){{tag}}(Color::White)] {{name}}"
.FormatExt(gameEvent.Origin.ClientNumber,
gameEvent.Origin.ClientId, gameEvent.Origin.GuidString,
gameEvent.Origin.IPAddressString, gameEvent.Origin.ClientPermission.Name,
string.IsNullOrEmpty(gameEvent.Origin.Tag) ? "" : $" {gameEvent.Origin.Tag}",
gameEvent.Origin.Name);
gameEvent.Origin.Tell(you);
return Task.CompletedTask;
}
}
}

View File

@ -18,6 +18,13 @@
"rollingInterval": "Day",
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}",
"RestrictedToMinimumLevel": "Fatal"
}
}
],
"Enrich": [

View File

@ -1,11 +1,11 @@
{
"AutoMessagePeriod": 60,
"AutoMessages": [
"This server uses ^5IW4M Admin v{{VERSION}} ^7get it at ^5raidmax.org/IW4MAdmin",
"^5IW4M Admin ^7sees ^5YOU!",
"This server uses (Color::Accent)IW4M Admin v{{VERSION}} (Color::White)get it at (Color::Accent)raidmax.org/IW4MAdmin",
"(Color::Accent)IW4M Admin (Color::White)sees (Color::Accent)YOU!",
"{{TOPSTATS}}",
"This server has seen a total of ^5{{TOTALPLAYERS}} ^7players!",
"Cheaters are ^1unwelcome ^7 on this server",
"This server has seen a total of (Color::Accent){{TOTALPLAYERS}} (Color::White)players!",
"Cheaters are (Color::Red)unwelcome (Color::White)on this server",
"Did you know 8/10 people agree with unverified statistics?"
],
"GlobalRules": [
@ -703,6 +703,18 @@
{
"Alias": "Highrise",
"Name": "mp_highrise"
},
{
"Alias": "Favela",
"Name": "mp_favela"
},
{
"Alias": "Nuketown",
"Name": "mp_nuked"
},
{
"Alias": "Skidrow",
"Name": "mp_nightshift"
}
]
},

View File

@ -23,7 +23,9 @@ using Serilog.Context;
using static SharedLibraryCore.Database.Models.EFClient;
using Data.Models;
using Data.Models.Server;
using IW4MAdmin.Application.Commands;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Formatting;
using static Data.Models.Client.EFClient;
namespace IW4MAdmin
@ -242,11 +244,11 @@ namespace IW4MAdmin
try
{
await (plugin.OnEventAsync(gameEvent, this)).WithWaitCancellation(tokenSource.Token);
await plugin.OnEventAsync(gameEvent, this).WithWaitCancellation(tokenSource.Token);
}
catch (Exception ex)
{
Console.WriteLine(loc["SERVER_PLUGIN_ERROR"]);
Console.WriteLine(loc["SERVER_PLUGIN_ERROR"].FormatExt(plugin.Name, ex.GetType().Name));
ServerLogger.LogError(ex, "Could not execute {methodName} for plugin {plugin}",
nameof(plugin.OnEventAsync), plugin.Name);
}
@ -433,7 +435,7 @@ namespace IW4MAdmin
if (E.Origin.Level > Permission.Moderator)
{
E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count));
E.Origin.Tell(loc["SERVER_REPORT_COUNT_V2"].FormatExt(E.Owner.Reports.Count));
}
}
@ -1109,6 +1111,15 @@ namespace IW4MAdmin
Version = RconParser.Version;
}
if (!RconParser.Configuration.ColorCodeMapping.ContainsKey(ColorCodes.Accent.ToString()))
{
var accentKey = Manager.GetApplicationSettings().Configuration().IngameAccentColorKey;
RconParser.Configuration.ColorCodeMapping.Add(ColorCodes.Accent.ToString(),
RconParser.Configuration.ColorCodeMapping.TryGetValue(accentKey, out var colorCode)
? colorCode
: "");
}
var svRunning = await this.GetMappedDvarValueOrDefaultAsync<string>("sv_running");
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
@ -1344,7 +1355,7 @@ namespace IW4MAdmin
return;
}
var message = loc["COMMANDS_WARNING_FORMAT"]
var message = loc["COMMANDS_WARNING_FORMAT_V2"]
.FormatExt(activeClient.Warnings, activeClient.Name, reason);
activeClient.CurrentServer.Broadcast(message);
}
@ -1472,7 +1483,7 @@ namespace IW4MAdmin
Manager.GetMessageTokens().Add(new MessageToken("TOTALPLAYERS", (Server s) => Task.Run(async () => (await Manager.GetClientService().GetTotalClientsAsync()).ToString())));
Manager.GetMessageTokens().Add(new MessageToken("VERSION", (Server s) => Task.FromResult(Application.Program.Version.ToString())));
Manager.GetMessageTokens().Add(new MessageToken("NEXTMAP", (Server s) => SharedLibraryCore.Commands.NextMapCommand.GetNextMap(s, _translationLookup)));
Manager.GetMessageTokens().Add(new MessageToken("ADMINS", (Server s) => Task.FromResult(SharedLibraryCore.Commands.ListAdminsCommand.OnlineAdmins(s, _translationLookup))));
Manager.GetMessageTokens().Add(new MessageToken("ADMINS", (Server s) => Task.FromResult(ListAdminsCommand.OnlineAdmins(s, _translationLookup))));
}
}
}

View File

@ -24,7 +24,7 @@ namespace IW4MAdmin.Application.Misc
private readonly IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> _clientHistoryCache;
private readonly TimeSpan? _cacheTimeSpan =
Utilities.IsDevelopment ? TimeSpan.FromSeconds(1) : (TimeSpan?) TimeSpan.FromMinutes(1);
Utilities.IsDevelopment ? TimeSpan.FromSeconds(30) : (TimeSpan?) TimeSpan.FromMinutes(10);
public ServerDataViewer(ILogger<ServerDataViewer> logger, IDataValueCache<EFServerSnapshot, (int?, DateTime?)> snapshotCache,
IDataValueCache<EFClient, (int, int)> serverStatsCache,
@ -36,7 +36,8 @@ namespace IW4MAdmin.Application.Misc
_clientHistoryCache = clientHistoryCache;
}
public async Task<(int?, DateTime?)> MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null,
public async Task<(int?, DateTime?)>
MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null,
CancellationToken token = default)
{
_snapshotCache.SetCacheItem(async (snapshots, cancellationToken) =>
@ -83,7 +84,7 @@ namespace IW4MAdmin.Application.Misc
_logger.LogDebug("Max concurrent clients since {Start} is {Clients}", oldestEntry, maxClients);
return (maxClients, maxClientsTime);
}, nameof(MaxConcurrentClientsAsync), _cacheTimeSpan);
}, nameof(MaxConcurrentClientsAsync), _cacheTimeSpan, true);
try
{
@ -107,7 +108,7 @@ namespace IW4MAdmin.Application.Misc
cancellationToken);
return (count, recentCount);
}, nameof(_serverStatsCache), _cacheTimeSpan);
}, nameof(_serverStatsCache), _cacheTimeSpan, true);
try
{

View File

@ -3,6 +3,7 @@ using SharedLibraryCore.Interfaces;
using SharedLibraryCore.RCon;
using System.Collections.Generic;
using System.Globalization;
using SharedLibraryCore.Formatting;
namespace IW4MAdmin.Application.RConParsers
{
@ -28,6 +29,24 @@ namespace IW4MAdmin.Application.RConParsers
public int NoticeMaximumLines { get; set; } = 8;
public int NoticeMaxCharactersPerLine { get; set; } = 50;
public string NoticeLineSeparator { get; set; } = Environment.NewLine;
public int? DefaultRConPort { get; set; }
public string DefaultInstallationDirectoryHint { get; set; }
public ColorCodeMapping ColorCodeMapping { get; set; } = new ColorCodeMapping
{
// this is the default mapping (IW4), but can be overridden as needed in the parsers
{ColorCodes.Black.ToString(), "^0"},
{ColorCodes.Red.ToString(), "^1"},
{ColorCodes.Green.ToString(), "^2"},
{ColorCodes.Yellow.ToString(), "^3"},
{ColorCodes.Blue.ToString(), "^4"},
{ColorCodes.Cyan.ToString(), "^5"},
{ColorCodes.Pink.ToString(), "^6"},
{ColorCodes.White.ToString(), "^7"},
{ColorCodes.Map.ToString(), "^8"},
{ColorCodes.Grey.ToString(), "^9"},
{ColorCodes.Wildcard.ToString(), ":^"},
};
public DynamicRConParserConfiguration(IParserRegexFactory parserRegexFactory)
{
@ -40,4 +59,4 @@ namespace IW4MAdmin.Application.RConParsers
MaxPlayersStatus = parserRegexFactory.CreateParserRegex();
}
}
}
}

View File

@ -5,9 +5,10 @@ using Microsoft.EntityFrameworkCore;
namespace Data.Abstractions
{
public interface IDataValueCache<T, V> where T : class
public interface IDataValueCache<TEntityType, TReturnType> where TEntityType : class
{
void SetCacheItem(Func<DbSet<T>, CancellationToken, Task<V>> itemGetter, string keyName, TimeSpan? expirationTime = null);
Task<V> GetCacheItem(string keyName, CancellationToken token = default);
void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> itemGetter, string keyName,
TimeSpan? expirationTime = null, bool autoRefresh = false);
Task<TReturnType> GetCacheItem(string keyName, CancellationToken token = default);
}
}

View File

@ -8,7 +8,7 @@
<PackageId>RaidMax.IW4MAdmin.Data</PackageId>
<Title>RaidMax.IW4MAdmin.Data</Title>
<Authors />
<PackageVersion>1.0.7</PackageVersion>
<PackageVersion>1.0.9</PackageVersion>
</PropertyGroup>
<ItemGroup>

View File

@ -1,58 +1,84 @@
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Data.Abstractions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Timer = System.Timers.Timer;
namespace Data.Helpers
{
public class DataValueCache<T, V> : IDataValueCache<T, V> where T : class
public class DataValueCache<TEntityType, TReturnType> : IDataValueCache<TEntityType, TReturnType>
where TEntityType : class
{
private readonly ILogger _logger;
private readonly IDatabaseContextFactory _contextFactory;
private readonly Dictionary<string, CacheState> _cacheStates = new Dictionary<string, CacheState>();
private const int DefaultExpireMinutes = 15;
private class CacheState
private readonly ConcurrentDictionary<string, CacheState<TReturnType>> _cacheStates =
new ConcurrentDictionary<string, CacheState<TReturnType>>();
private bool _autoRefresh;
private const int DefaultExpireMinutes = 15;
private Timer _timer;
private class CacheState<TCacheType>
{
public string Key { get; set; }
public DateTime LastRetrieval { get; set; }
public TimeSpan ExpirationTime { get; set; }
public Func<DbSet<T>, CancellationToken, Task<V>> Getter { get; set; }
public V Value { get; set; }
public Func<DbSet<TEntityType>, CancellationToken, Task<TCacheType>> Getter { get; set; }
public TCacheType Value { get; set; }
public bool IsSet { get; set; }
public bool IsExpired => ExpirationTime != TimeSpan.MaxValue &&
(DateTime.Now - LastRetrieval.Add(ExpirationTime)).TotalSeconds > 0;
}
public DataValueCache(ILogger<DataValueCache<T, V>> logger, IDatabaseContextFactory contextFactory)
public DataValueCache(ILogger<DataValueCache<TEntityType, TReturnType>> logger,
IDatabaseContextFactory contextFactory)
{
_logger = logger;
_contextFactory = contextFactory;
}
public void SetCacheItem(Func<DbSet<T>, CancellationToken, Task<V>> getter, string key,
TimeSpan? expirationTime = null)
~DataValueCache()
{
_timer?.Stop();
_timer?.Dispose();
}
public void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> getter, string key,
TimeSpan? expirationTime = null, bool autoRefresh = false)
{
if (_cacheStates.ContainsKey(key))
{
_logger.LogDebug("Cache key {key} is already added", key);
_logger.LogDebug("Cache key {Key} is already added", key);
return;
}
var state = new CacheState()
var state = new CacheState<TReturnType>
{
Key = key,
Getter = getter,
ExpirationTime = expirationTime ?? TimeSpan.FromMinutes(DefaultExpireMinutes)
};
_cacheStates.Add(key, state);
_autoRefresh = autoRefresh;
_cacheStates.TryAdd(key, state);
if (!_autoRefresh || expirationTime == TimeSpan.MaxValue)
{
return;
}
_timer = new Timer(state.ExpirationTime.TotalMilliseconds);
_timer.Elapsed += async (sender, args) => await RunCacheUpdate(state, CancellationToken.None);
_timer.Start();
}
public async Task<V> GetCacheItem(string keyName, CancellationToken cancellationToken = default)
public async Task<TReturnType> GetCacheItem(string keyName, CancellationToken cancellationToken = default)
{
if (!_cacheStates.ContainsKey(keyName))
{
@ -61,7 +87,9 @@ namespace Data.Helpers
var state = _cacheStates[keyName];
if (state.IsExpired || state.Value == null)
// when auto refresh is off we want to check the expiration and value
// when auto refresh is on, we want to only check the value, because it'll be refreshed automatically
if ((state.IsExpired || !state.IsSet) && !_autoRefresh || _autoRefresh && !state.IsSet)
{
await RunCacheUpdate(state, cancellationToken);
}
@ -69,19 +97,21 @@ namespace Data.Helpers
return state.Value;
}
private async Task RunCacheUpdate(CacheState state, CancellationToken token)
private async Task RunCacheUpdate(CacheState<TReturnType> state, CancellationToken token)
{
try
{
_logger.LogDebug("Running update for {ClassName} {@State}", GetType().Name, state);
await using var context = _contextFactory.CreateContext(false);
var set = context.Set<T>();
var set = context.Set<TEntityType>();
var value = await state.Getter(set, token);
state.Value = value;
state.IsSet = true;
state.LastRetrieval = DateTime.Now;
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not get cached value for {key}", state.Key);
_logger.LogError(ex, "Could not get cached value for {Key}", state.Key);
}
}
}

View File

@ -139,35 +139,35 @@ steps:
archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
replaceExistingArchive: true
#- task: FtpUpload@2
# displayName: 'Upload zip file to website'
# inputs:
# credentialsOption: 'inputs'
# serverUrl: '$(FTPUrl)'
# username: '$(FTPUsername)'
# password: '$(FTPPassword)'
# rootDirectory: '$(Build.ArtifactStagingDirectory)'
# filePatterns: '*.zip'
# remoteDirectory: 'IW4MAdmin/Download'
# clean: false
# cleanContents: false
# preservePaths: false
# trustSSL: false
- task: FtpUpload@2
displayName: 'Upload zip file to website'
inputs:
credentialsOption: 'inputs'
serverUrl: '$(FTPUrl)'
username: '$(FTPUsername)'
password: '$(FTPPassword)'
rootDirectory: '$(Build.ArtifactStagingDirectory)'
filePatterns: '*.zip'
remoteDirectory: 'IW4MAdmin/Download'
clean: false
cleanContents: false
preservePaths: false
trustSSL: false
#- task: FtpUpload@2
# displayName: 'Upload version info to website'
# inputs:
# credentialsOption: 'inputs'
# serverUrl: '$(FTPUrl)'
# username: '$(FTPUsername)'
# password: '$(FTPPassword)'
# rootDirectory: '$(Build.ArtifactStagingDirectory)'
# filePatterns: 'version_$(releaseType).txt'
# remoteDirectory: 'IW4MAdmin'
# clean: false
# cleanContents: false
# preservePaths: false
# trustSSL: false
- task: FtpUpload@2
displayName: 'Upload version info to website'
inputs:
credentialsOption: 'inputs'
serverUrl: '$(FTPUrl)'
username: '$(FTPUsername)'
password: '$(FTPPassword)'
rootDirectory: '$(Build.ArtifactStagingDirectory)'
filePatterns: 'version_$(releaseType).txt'
remoteDirectory: 'IW4MAdmin'
clean: false
cleanContents: false
preservePaths: false
trustSSL: false
- task: GitHubRelease@1
displayName: 'Make GitHub release'

View File

@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.21.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.21.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -23,7 +23,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.21.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -19,7 +19,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.21.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.21.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -3,7 +3,7 @@ let eventParser;
const plugin = {
author: 'RaidMax',
version: 0.3,
version: 0.4,
name: 'CS:GO Parser',
engine: 'Source',
isParser: true,
@ -58,6 +58,7 @@ const plugin = {
rconParser.Configuration.OverrideDvarNameMapping.Add('g_password', 'sv_password');
rconParser.Configuration.NoticeLineSeparator = '. ';
rconParser.Configuration.DefaultRConPort = 27015;
rconParser.CanGenerateLogPath = false;
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;

View File

@ -3,7 +3,7 @@ let eventParser;
const plugin = {
author: 'RaidMax',
version: 0.3,
version: 0.4,
name: 'CS:GO (SourceMod) Parser',
engine: 'Source',
isParser: true,
@ -58,6 +58,7 @@ const plugin = {
rconParser.Configuration.OverrideDvarNameMapping.Add('g_password', 'sv_password');
rconParser.Configuration.NoticeLineSeparator = '. ';
rconParser.Configuration.DefaultRConPort = 27015;
rconParser.CanGenerateLogPath = false;
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'FrenchFry, RaidMax',
version: 0.7,
version: 0.8,
name: 'CoD4x Parser',
isParser: true,
@ -25,6 +25,7 @@ var plugin = {
rconParser.Configuration.Dvar.AddMapping(110, 4); // dvar info
rconParser.Configuration.GuidNumberStyle = 7; // Integer
rconParser.Configuration.NoticeLineSeparator = '. '; // CoD4x does not support \n in the client notice
rconParser.Configuration.DefaultRConPort = 28960;
rconParser.Version = 'CoD4 X - win_mingw-x86 build 1056 Dec 12 2020';
rconParser.GameName = 1; // IW3

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.4,
version: 0.5,
name: 'IW4x Parser',
isParser: true,
@ -19,6 +19,10 @@ var plugin = {
rconParser.Configuration.CommandPrefixes.Kick = 'clientkick {0} "{1}"';
rconParser.Configuration.CommandPrefixes.Ban = 'clientkick {0} "{1}"';
rconParser.Configuration.CommandPrefixes.TempBan = 'tempbanclient {0} "{1}"';
rconParser.Configuration.DefaultRConPort = 28960;
rconParser.Configuration.DefaultInstallationDirectoryHint = 'HKEY_CURRENT_USER\\Software\\Classes\\iw4x\\shell\\open\\command';
eventParser.Configuration.GameDirectory = 'userraw';
rconParser.Version = 'IW4x (v0.6.0)';

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'Xerxes, RaidMax, st0rm',
version: 0.3,
version: 0.4,
name: 'IW6x Parser',
isParser: true,
@ -24,6 +24,7 @@ var plugin = {
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.WaitForResponse = false;
rconParser.Configuration.DefaultRConPort = 28960;
rconParser.Configuration.Status.AddMapping(102, 4);
rconParser.Configuration.Status.AddMapping(103, 5);
rconParser.Configuration.Status.AddMapping(104, 6);

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.9,
version: 1.0,
name: 'Plutonium IW5 Parser',
isParser: true,
@ -28,6 +28,9 @@ var plugin = {
rconParser.Configuration.WaitForResponse = true;
rconParser.Configuration.CanGenerateLogPath = true;
rconParser.Configuration.NoticeLineSeparator = '. ';
rconParser.Configuration.DefaultRConPort = 27016;
rconParser.Configuration.DefaultInstallationDirectoryHint = '{LocalAppData}/Plutonium/storage/iw5';
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]+) *$';

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax, Xerxes',
version: 1.0,
version: 1.2,
name: 'Plutonium T6 Parser',
isParser: true,
@ -27,6 +27,8 @@ var plugin = {
rconParser.Configuration.Dvar.AddMapping(107, 2);
rconParser.Configuration.WaitForResponse = false;
rconParser.Configuration.NoticeLineSeparator = '. ';
rconParser.Configuration.DefaultRConPort = 4976;
rconParser.Configuration.DefaultInstallationDirectoryHint = '{LocalAppData}/Plutonium/storage/t6';
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]+) +([A-F0-9]+|0) +(.+?) +(?:[0-9]+) +(\\d+\\.\\d+\\.\\d+\\.\\d+\\:-?\\d{1,5}|0+\\.0+:-?\\d{1,5}|loopback) +(?:-?[0-9]+) +(?:[0-9]+) *$';
@ -36,6 +38,21 @@ var plugin = {
rconParser.Configuration.Status.AddMapping(103, 4);
rconParser.Configuration.Status.AddMapping(104, 5);
rconParser.Configuration.Status.AddMapping(105, 6);
// this is mostly default but just an example on how to map
rconParser.Configuration.ColorCodeMapping.Clear();
rconParser.Configuration.ColorCodeMapping.Add('Black', '^0');
rconParser.Configuration.ColorCodeMapping.Add('Red', '^1');
rconParser.Configuration.ColorCodeMapping.Add('Green', '^2');
rconParser.Configuration.ColorCodeMapping.Add('Yellow', '^3');
rconParser.Configuration.ColorCodeMapping.Add('Blue', '^4');
rconParser.Configuration.ColorCodeMapping.Add('Cyan', '^5');
rconParser.Configuration.ColorCodeMapping.Add('Pink', '^6');
rconParser.Configuration.ColorCodeMapping.Add('White', '^7');
rconParser.Configuration.ColorCodeMapping.Add('Map', '^8');
rconParser.Configuration.ColorCodeMapping.Add('Grey', '^9');
rconParser.Configuration.ColorCodeMapping.Add('LightBlue', '^;');
rconParser.Configuration.ColorCodeMapping.Add('LightYellow', '^:');
eventParser.Configuration.GameDirectory = '';
eventParser.Configuration.GuidNumberStyle = 7; // Integer

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax, Chase',
version: 0.2,
version: 0.3,
name: 'Plutonium T4 Parser',
isParser: true,
@ -19,6 +19,9 @@ var plugin = {
rconParser.Configuration.CommandPrefixes.TempBan = 'clientkick {0}';
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
rconParser.Configuration.GuidNumberStyle = 7; // Integer
rconParser.Configuration.DefaultRConPort = 28960;
rconParser.Configuration.DefaultInstallationDirectoryHint = '{LocalAppData}/Plutonium/storage/t4';
rconParser.Version = 'Plutonium T4';
rconParser.GameName = 5; // T4

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.3,
version: 0.4,
name: 'RektT5m Parser',
isParser: true,
@ -19,6 +19,7 @@ var plugin = {
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff\x01print\n';
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
rconParser.Configuration.DefaultRConPort = 28960;
rconParser.Version = 'Call of Duty Multiplayer - Ship COD_T5_S MP build 7.0.189 CL(1022875) CODPCAB-V64 CEG Wed Nov 02 18:02:23 2011 win-x86';
rconParser.GameName = 6; // T5

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'Diavolo, RaidMax',
version: 0.1,
version: 0.2,
name: 'S1x Parser',
isParser: true,
@ -24,6 +24,7 @@ var plugin = {
rconParser.Configuration.Status.AddMapping(103, 5);
rconParser.Configuration.Status.AddMapping(104, 6);
rconParser.Configuration.WaitForResponse = false;
rconParser.Configuration.DefaultRConPort = 27016;
eventParser.Configuration.GameDirectory = '';

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.1,
version: 0.2,
name: 'Call of Duty 5: World at War Parser',
isParser: true,
@ -15,6 +15,7 @@ var plugin = {
eventParser = manager.GenerateDynamicEventParser(this.name);
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint\n';
rconParser.Configuration.GuidNumberStyle = 7; // Integer
rconParser.Configuration.DefaultRConPort = 28960;
rconParser.Version = 'Call of Duty Multiplayer COD_WaW MP build 1.7.1263 CL(350073) JADAMS2 Thu Oct 29 15:43:55 2009 win-x86';
eventParser.Configuration.GuidNumberStyle = 7; // Integer

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.3,
version: 0.4,
name: 'Black Ops 3 Parser',
isParser: true,
@ -27,6 +27,7 @@ var plugin = {
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 hibernating server from being detected as not running';
rconParser.Configuration.DefaultRConPort = 27016;
rconParser.Configuration.OverrideDvarNameMapping.Add('sv_hostname', 'live_steam_server_name');
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.8,
version: 0.9,
name: 'Tekno MW3 Parser',
isParser: true,
@ -28,6 +28,7 @@ var plugin = {
rconParser.Configuration.Dvar.AddMapping(107, 1); // RCon DvarValue
rconParser.Configuration.Dvar.Pattern = '^(.*)$';
rconParser.Configuration.NoticeLineSeparator = '. ';
rconParser.Configuration.DefaultRConPort = 8766;
rconParser.Configuration.DefaultDvarValues.Add('sv_running', '1');
rconParser.Configuration.OverrideDvarNameMapping.Add('_website', 'sv_clanWebsite');

View File

@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using Stats.Client.Abstractions;
using Stats.Config;
using Stats.Helpers;
namespace Stats.Client

View File

@ -9,8 +9,8 @@ using Data.Models.Client.Stats;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
using IW4MAdmin.Plugins.Stats.Config;
using IW4MAdmin.Plugins.Stats.Helpers;
using Stats.Config;
namespace IW4MAdmin.Plugins.Stats.Commands
{
@ -76,7 +76,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
var iqList = await iqStats.ToListAsync();
return iqList.Select((stats, index) => translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_FORMAT"]
return iqList.Select((stats, index) => translationLookup["PLUGINS_STATS_COMMANDS_MOSTKILLS_FORMAT_V2"]
.FormatExt(index + 1, stats.Name, stats.Kills))
.Prepend(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTKILLS_HEADER"]);
}

View File

@ -20,9 +20,9 @@ namespace IW4MAdmin.Plugins.Stats.Commands
{
var serverId = StatManager.GetIdForServer(s);
var mostPlayed = new List<string>()
var mostPlayed = new List<string>
{
$"^5--{translationLookup["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
$"(Color::Accent)--{translationLookup["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
};
await using var context = contextFactory.CreateContext(false);
@ -48,7 +48,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
var iqList = await iqStats.ToListAsync();
mostPlayed.AddRange(iqList.Select((stats, index) =>
$"#{index + 1} " + translationLookup["COMMANDS_MOST_PLAYED_FORMAT"].FormatExt(stats.Name, stats.Kills,
$"#{index + 1} " + translationLookup["COMMANDS_MOST_PLAYED_FORMAT_V2"].FormatExt(stats.Name, stats.Kills,
(DateTime.UtcNow - DateTime.UtcNow.AddSeconds(-stats.TimePlayed))
.HumanizeForCurrentCulture())));

View File

@ -1,35 +1,32 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Linq;
using System.Threading.Tasks;
using SharedLibraryCore;
using System.Collections.Generic;
using Data.Abstractions;
using Data.Models.Client.Stats;
using SharedLibraryCore.Database.Models;
using IW4MAdmin.Plugins.Stats.Helpers;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
using EFClient = Data.Models.Client.EFClient;
namespace IW4MAdmin.Plugins.Stats.Commands
{
class TopStats : Command
public class TopStats : Command
{
public static async Task<List<string>> GetTopStats(Server s, ITranslationLookup translationLookup)
{
long serverId = StatManager.GetIdForServer(s);
var serverId = StatManager.GetIdForServer(s);
var topStatsText = new List<string>()
{
$"^5--{translationLookup["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--"
$"(Color::Accent)--{translationLookup["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--"
};
var stats = await Plugin.Manager.GetTopStats(0, 5, serverId);
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"]}");
var statsList = stats.Select((stats, index) =>
translationLookup["COMMANDS_TOPSTATS_RESULT"]
.FormatExt(index + 1, stats.Name, stats.KDR, stats.Performance));
topStatsText.AddRange(statsList);
// no one qualified
// no one qualified
if (topStatsText.Count == 1)
{
topStatsText = new List<string>()
@ -41,11 +38,10 @@ namespace IW4MAdmin.Plugins.Stats.Commands
return topStatsText;
}
private readonly CommandConfiguration _config;
private readonly IDatabaseContextFactory _contextFactory;
private new readonly CommandConfiguration _config;
public TopStats(CommandConfiguration config, ITranslationLookup translationLookup,
IDatabaseContextFactory contextFactory) : base(config, translationLookup)
public TopStats(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "topstats";
Description = translationLookup["PLUGINS_STATS_COMMANDS_TOP_DESC"];
@ -54,7 +50,6 @@ namespace IW4MAdmin.Plugins.Stats.Commands
RequiresTarget = false;
_config = config;
_contextFactory = contextFactory;
}
public override async Task ExecuteAsync(GameEvent E)
@ -76,4 +71,4 @@ namespace IW4MAdmin.Plugins.Stats.Commands
}
}
}
}
}

View File

@ -24,9 +24,9 @@ namespace IW4MAdmin.Plugins.Stats.Commands
Alias = "xlrstats";
Permission = EFClient.Permission.User;
RequiresTarget = false;
Arguments = new CommandArgument[]
Arguments = new []
{
new CommandArgument()
new CommandArgument
{
Name = "player",
Required = false
@ -73,14 +73,15 @@ namespace IW4MAdmin.Plugins.Stats.Commands
if (pStats == null)
{
await using var context = _contextFactory.CreateContext(false);
pStats = (await context.Set<EFClientStatistics>()
.FirstOrDefaultAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId));
pStats = await context.Set<EFClientStatistics>()
.FirstOrDefaultAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId);
}
// if it's still null then they've not gotten a kill or death yet
statLine = pStats == null
? _translationLookup["PLUGINS_STATS_COMMANDS_NOTAVAILABLE"]
: $"^5{pStats.Kills} ^7{_translationLookup["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{_translationLookup["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{_translationLookup["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
: _translationLookup["COMMANDS_VIEW_STATS_RESULT"].FormatExt(pStats.Kills, pStats.Deaths,
pStats.KDR, pStats.Performance, performanceRankingString);
}
// getting self stats
@ -108,7 +109,8 @@ namespace IW4MAdmin.Plugins.Stats.Commands
// if it's still null then they've not gotten a kill or death yet
statLine = pStats == null
? _translationLookup["PLUGINS_STATS_COMMANDS_NOTAVAILABLE"]
: $"^5{pStats.Kills} ^7{_translationLookup["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{_translationLookup["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{_translationLookup["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
: _translationLookup["COMMANDS_VIEW_STATS_RESULT"].FormatExt(pStats.Kills, pStats.Deaths,
pStats.KDR, pStats.Performance, performanceRankingString);
}
if (E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))

View File

@ -1,11 +1,11 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using Stats.Config;
using System;
using System;
using System.Collections.Generic;
using IW4MAdmin.Plugins.Stats.Config;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
namespace IW4MAdmin.Plugins.Stats.Config
namespace Stats.Config
{
public class StatsConfiguration : IBaseConfiguration
{
@ -74,28 +74,28 @@ namespace IW4MAdmin.Plugins.Stats.Config
public IBaseConfiguration Generate()
{
AnticheatConfiguration.Enable =
Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_SETUP_ENABLEAC"]);
KillstreakMessages = new List<StreakMessageConfiguration>()
Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_SETUP_ENABLEAC"].PromptBool();
KillstreakMessages = new List<StreakMessageConfiguration>
{
new StreakMessageConfiguration()
new StreakMessageConfiguration
{
Count = -1,
Message = "Try not to kill yourself anymore"
Message = Utilities.CurrentLocalization.LocalizationIndex["STATS_STREAK_MESSAGE_SUICIDE"]
},
new StreakMessageConfiguration()
new StreakMessageConfiguration
{
Count = 5,
Message = "Great job! You're on a ^55 killstreak!"
Message = Utilities.CurrentLocalization.LocalizationIndex["STATS_STREAK_MESSAGE_5"]
},
new StreakMessageConfiguration()
new StreakMessageConfiguration
{
Count = 10,
Message = "Amazing! ^510 kills ^7without dying!"
Message = Utilities.CurrentLocalization.LocalizationIndex["STATS_STREAK_MESSAGE_10"]
},
new StreakMessageConfiguration()
new StreakMessageConfiguration
{
Count = 25,
Message = "You better call in that nuke, ^525 killstreak^7!"
Message = Utilities.CurrentLocalization.LocalizationIndex["STATS_STREAK_MESSAGE_25"]
}
};
@ -104,12 +104,12 @@ namespace IW4MAdmin.Plugins.Stats.Config
new StreakMessageConfiguration()
{
Count = 5,
Message = "Pick it up soldier, you've died ^55 times ^7in a row..."
Message = Utilities.CurrentLocalization.LocalizationIndex["STATS_DEATH_STREAK_MESSAGE_5"]
},
new StreakMessageConfiguration()
{
Count = 10,
Message = "Seriously? ^510 deaths ^7without getting a kill?"
Message = Utilities.CurrentLocalization.LocalizationIndex["STATS_DEATH_STREAK_MESSAGE_10"]
},
};

View File

@ -21,6 +21,7 @@ using Data.Models.Server;
using Humanizer.Localisation;
using Microsoft.Extensions.Logging;
using Stats.Client.Abstractions;
using Stats.Config;
using Stats.Helpers;
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
using EFClient = SharedLibraryCore.Database.Models.EFClient;

View File

@ -20,6 +20,7 @@ using Microsoft.Extensions.Logging;
using SharedLibraryCore.Commands;
using IW4MAdmin.Plugins.Stats.Client.Abstractions;
using Stats.Client.Abstractions;
using Stats.Config;
using EFClient = SharedLibraryCore.Database.Models.EFClient;
namespace IW4MAdmin.Plugins.Stats
@ -88,7 +89,7 @@ namespace IW4MAdmin.Plugins.Stats
break;
case GameEvent.EventType.Command:
var shouldPersist = !string.IsNullOrEmpty(E.Data) &&
E.Extra is SayCommand;
E.Extra?.GetType().Name == "SayCommand";
if (shouldPersist)
{
await Manager.AddMessageAsync(E.Origin.ClientId, StatManager.GetIdForServer(S), false, E.Data);

View File

@ -17,7 +17,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.21.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -45,16 +45,16 @@ namespace IW4MAdmin.Plugins.Welcome
public Task OnTickAsync(Server S) => Task.CompletedTask;
public async Task OnEventAsync(GameEvent E, Server S)
public async Task OnEventAsync(GameEvent gameEvent, Server server)
{
if (E.Type == GameEvent.EventType.Join)
if (gameEvent.Type == GameEvent.EventType.Join)
{
var newPlayer = E.Origin;
if ((newPlayer.Level >= Permission.Trusted && !E.Origin.Masked) ||
(!string.IsNullOrEmpty(newPlayer.GetAdditionalProperty<string>("ClientTag")) &&
var newPlayer = gameEvent.Origin;
if (newPlayer.Level >= Permission.Trusted && !gameEvent.Origin.Masked||
!string.IsNullOrEmpty(newPlayer.GetAdditionalProperty<string>("ClientTag")) &&
newPlayer.Level != Permission.Flagged && newPlayer.Level != Permission.Banned &&
!newPlayer.Masked))
E.Owner.Broadcast(
!newPlayer.Masked)
gameEvent.Owner.Broadcast(
await ProcessAnnouncement(_configHandler.Configuration().PrivilegedAnnouncementMessage,
newPlayer));
@ -73,10 +73,11 @@ namespace IW4MAdmin.Plugins.Welcome
.FirstOrDefaultAsync();
}
E.Owner.ToAdmins($"^1NOTICE: ^7Flagged player ^5{newPlayer.Name} ^7({penaltyReason}) has joined!");
gameEvent.Owner.ToAdmins(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_FLAG_MESSAGE"]
.FormatExt(newPlayer.Name, penaltyReason));
}
else
E.Owner.Broadcast(await ProcessAnnouncement(_configHandler.Configuration().UserAnnouncementMessage,
gameEvent.Owner.Broadcast(await ProcessAnnouncement(_configHandler.Configuration().UserAnnouncementMessage,
newPlayer));
}
}
@ -85,7 +86,7 @@ namespace IW4MAdmin.Plugins.Welcome
{
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)")}");
$"{Utilities.ConvertLevelToColor(joining.Level, joining.ClientPermission.Name)}{(string.IsNullOrEmpty(joining.GetAdditionalProperty<string>("ClientTag")) ? "" : $" (Color::White)({joining.GetAdditionalProperty<string>("ClientTag")}(Color::White))")}");
// this prevents it from trying to evaluate it every message
if (msg.Contains("{{ClientLocation}}"))
{

View File

@ -20,7 +20,7 @@
</Target>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.11.21.1" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -11,9 +11,9 @@ namespace IW4MAdmin.Plugins.Welcome
public IBaseConfiguration Generate()
{
UserAnnouncementMessage = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_USERANNOUNCE"];
UserWelcomeMessage = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_USERWELCOME"];
PrivilegedAnnouncementMessage = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_PRIVANNOUNCE"];
UserAnnouncementMessage = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_USERANNOUNCE_V2"];
UserWelcomeMessage = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_USERWELCOME_V2"];
PrivilegedAnnouncementMessage = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_WELCOME_PRIVANNOUNCE_V2"];
return this;
}

View File

@ -23,6 +23,7 @@ namespace SharedLibraryCore
{
_config = config;
_translationLookup = layout;
SupportedGames = new Game[0];
}
/// <summary>

View File

@ -85,8 +85,8 @@ namespace SharedLibraryCore.Commands
{
found = Manager.FindActiveClient(found);
E.Target = found;
E.Target.CurrentServer = E.Owner;
E.Data = String.Join(" ", Args.Skip(1));
E.Target.CurrentServer = found.CurrentServer ?? E.Owner;
E.Data = string.Join(" ", Args.Skip(1));
}
}
@ -136,7 +136,7 @@ namespace SharedLibraryCore.Commands
E.Origin.Tell(loc["COMMAND_TARGET_MULTI"]);
foreach (var p in matchingPlayers)
{
E.Origin.Tell($"[^3{p.ClientNumber}^7] {p.Name}");
E.Origin.Tell($"[(Color::Yellow){p.ClientNumber}(Color::White)] {p.Name}");
}
throw new CommandException($"{E.Origin} had multiple players found for {C.Name}");
}

View File

@ -219,72 +219,6 @@ namespace SharedLibraryCore.Commands
}
}
/// <summary>
/// Prints out a message to all clients on the server
/// </summary>
public class SayCommand : Command
{
public SayCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "say";
Description = _translationLookup["COMMANDS_SAY_DESC"];
Alias = "s";
Permission = Permission.Moderator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
Required = true
}
};
}
public override Task ExecuteAsync(GameEvent E)
{
E.Owner.Broadcast($"{(E.Owner.GameName == Server.Game.IW4 ? "^:" : "")}{E.Origin.Name} - ^6{E.Data}^7", E.Origin);
E.Origin.Tell(_translationLookup["COMMANDS_SAY_SUCCESS"]);
return Task.CompletedTask;
}
}
/// <summary>
/// Prints out a message to all clients on all servers
/// </summary>
public class SayAllCommand : Command
{
public SayAllCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "sayall";
Description = _translationLookup["COMMANDS_SAY_ALL_DESC"];
Alias = "sa";
Permission = Permission.Moderator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
Required = true
}
};
}
public override Task ExecuteAsync(GameEvent E)
{
string message = _translationLookup["COMMANDS_SAY_ALL_MESSAGE_FORMAT"].FormatExt(E.Origin.Name, E.Data);
foreach (var server in E.Owner.Manager.GetServers())
{
server.Broadcast(message, E.Origin);
}
E.Origin.Tell(_translationLookup["COMMANDS_SAY_SUCCESS"]);
return Task.CompletedTask;
}
}
/// <summary>
/// Temporarily bans a client
/// </summary>
@ -452,131 +386,6 @@ namespace SharedLibraryCore.Commands
}
}
/// <summary>
/// Prints client information
/// </summary>
public class WhoAmICommand : Command
{
public WhoAmICommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "whoami";
Description = _translationLookup["COMMANDS_WHO_DESC"];
Alias = "who";
Permission = Permission.User;
RequiresTarget = false;
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
var you = _translationLookup["COMMANDS_WHOAMI_FORMAT"].FormatExt(gameEvent.Origin.ClientNumber, gameEvent.Origin.ClientId, gameEvent.Origin.GuidString,
gameEvent.Origin.IPAddressString, gameEvent.Origin.ClientPermission.Name, string.IsNullOrEmpty(gameEvent.Origin.Tag) ? "" : $" {gameEvent.Origin.Tag}^7", gameEvent.Origin.Name);
gameEvent.Origin.Tell(you);
return Task.CompletedTask;
}
}
/// <summary>
/// List online clients
/// </summary>
public class ListClientsCommand : Command
{
public ListClientsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "list";
Description = _translationLookup["COMMANDS_LIST_DESC"];
Alias = "l";
Permission = Permission.Moderator;
RequiresTarget = false;
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
var clientList = gameEvent.Owner.GetClientsAsList()
.Select(client => _translationLookup["COMMANDS_LIST_FORMAT"]
.FormatExt(client.ClientPermission.Name, string.IsNullOrEmpty(client.Tag) ? "" : $" {client.Tag}^7", client.ClientNumber, client.Name))
.ToArray();
gameEvent.Origin.Tell(clientList);
return Task.CompletedTask;
}
}
/// <summary>
/// Prints help information
/// </summary>
public class HelpCommand : Command
{
public HelpCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "help";
Description = _translationLookup["COMMANDS_HELP_DESC"];
Alias = "h";
Permission = Permission.User;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_COMMANDS"],
Required = false
}
};
}
public override Task ExecuteAsync(GameEvent E)
{
string cmd = E.Data.Trim();
if (cmd.Length > 2)
{
bool found = false;
foreach (var command in E.Owner.Manager.GetCommands())
{
if (command.Name == cmd.ToLower() ||
command.Alias == cmd.ToLower())
{
E.Origin.Tell($"[^3{command.Name}^7] {command.Description}");
E.Origin.Tell(command.Syntax);
found = true;
}
}
if (!found)
{
E.Origin.Tell(_translationLookup["COMMANDS_HELP_NOTFOUND"]);
}
}
else
{
int count = 0;
StringBuilder helpResponse = new StringBuilder();
var CommandList = E.Owner.Manager.GetCommands();
foreach (Command C in CommandList)
{
if (E.Origin.Level >= C.Permission)
{
helpResponse.Append(" [^3" + C.Name + "^7] ");
if (count >= 4)
{
E.Origin.Tell(helpResponse.ToString());
helpResponse = new StringBuilder();
count = 0;
}
count++;
}
}
E.Origin.Tell(helpResponse.ToString());
E.Origin.Tell(_translationLookup["COMMANDS_HELP_MOREINFO"]);
}
return Task.CompletedTask;
}
}
/// <summary>
/// Fast restarts the map
/// </summary>
@ -596,7 +405,7 @@ namespace SharedLibraryCore.Commands
await E.Owner.ExecuteCommandAsync("fast_restart");
var _ = !E.Origin.Masked ?
E.Owner.Broadcast($"^5{E.Origin.Name} ^7{_translationLookup["COMMANDS_FASTRESTART_UNMASKED"]}") :
E.Owner.Broadcast($"(Color::Accent){E.Origin.Name} (Color::White){_translationLookup["COMMANDS_FASTRESTART_UNMASKED"]}") :
E.Owner.Broadcast(_translationLookup["COMMANDS_FASTRESTART_MASKED"]);
}
}
@ -618,7 +427,7 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent E)
{
_ = !E.Origin.Masked ?
E.Owner.Broadcast($"{_translationLookup["COMMANDS_MAPROTATE"]} [^5{E.Origin.Name}^7]", E.Origin) :
E.Owner.Broadcast($"{_translationLookup["COMMANDS_MAPROTATE"]} [(Color::Accent){E.Origin.Name}(Color::White)]", E.Origin) :
E.Owner.Broadcast(_translationLookup["COMMANDS_MAPROTATE"], E.Origin);
await Task.Delay(E.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds * 1000);
@ -692,13 +501,13 @@ namespace SharedLibraryCore.Commands
else if (gameEvent.Origin.Level < Permission.Owner && !steppedPrivileges)
{
// only the owner is allowed to set levels
gameEvent.Origin.Tell($"{_translationLookup["COMMANDS_SETLEVEL_STEPPEDDISABLED"]} ^5{gameEvent.Target.Name}");
gameEvent.Origin.Tell($"{_translationLookup["COMMANDS_SETLEVEL_STEPPEDDISABLED"]} (Color::White){gameEvent.Target.Name}");
return;
}
else if (gameEvent.Target.Level == Permission.Flagged)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_FLAGGED"].FormatExt(gameEvent.Target.Name));
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_FLAGGED"].FormatExt(gameEvent.Target.Name + "(Color::White)"));
return;
}
@ -707,8 +516,8 @@ namespace SharedLibraryCore.Commands
{
// can't promote a client to higher than your current perms
// or your peer
gameEvent.Origin.Tell(string.Format(_translationLookup["COMMANDS_SETLEVEL_LEVELTOOHIGH"], gameEvent.Target.Name, (gameEvent.Origin.Level - 1).ToLocalizedLevelName()));
return;
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_LEVELTOOHIGH_V2"]
.FormatExt(gameEvent.Target.Name, (gameEvent.Origin.Level - 1).ToLocalizedLevelName()));
}
// valid
@ -728,7 +537,7 @@ namespace SharedLibraryCore.Commands
if (result.FailReason == GameEvent.EventFailReason.Invalid)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_INVALID"]
.FormatExt(gameEvent.Target.Name, newPerm.ToString()));
.FormatExt(gameEvent.Target.Name + "(Color::White)", newPerm.ToString()));
return;
}
@ -807,42 +616,6 @@ namespace SharedLibraryCore.Commands
}
}
/// <summary>
/// Lists all unmasked admins
/// </summary>
public class ListAdminsCommand : Command
{
public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "admins";
Description = _translationLookup["COMMANDS_ADMINS_DESC"];
Alias = "a";
Permission = Permission.User;
RequiresTarget = false;
}
public static string OnlineAdmins(Server S, ITranslationLookup lookup)
{
var onlineAdmins = S.GetClientsAsList()
.Where(p => p.Level > Permission.Flagged)
.Where(p => !p.Masked)
.Select(p => $"[^3{Utilities.ConvertLevelToColor(p.Level, p.ClientPermission.Name)}^7] {p.Name}");
return onlineAdmins.Count() > 0 ?
string.Join(Environment.NewLine, onlineAdmins) :
lookup["COMMANDS_ADMINS_NONE"];
}
public override Task ExecuteAsync(GameEvent E)
{
foreach (string line in OnlineAdmins(E.Owner, _translationLookup).Split(Environment.NewLine))
{
var _ = E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix) ? E.Owner.Broadcast(line) : E.Origin.Tell(line);
}
return Task.CompletedTask;
}
}
/// <summary>
/// Attempts to load the specified map
@ -883,50 +656,6 @@ namespace SharedLibraryCore.Commands
}
}
/// <summary>
/// Finds player by name
/// </summary>
public class FindPlayerCommand : Command
{
public FindPlayerCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "find";
Description = _translationLookup["COMMANDS_FIND_DESC"];
Alias = "f";
Permission = Permission.Administrator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true
}
};
}
public override async Task ExecuteAsync(GameEvent E)
{
if (E.Data.Length < 3)
{
E.Origin.Tell(_translationLookup["COMMANDS_FIND_MIN"]);
return;
}
var db_players = (await (E.Owner.Manager.GetClientService() as ClientService).FindClientsByIdentifier(E.Data));
if (db_players.Count == 0)
{
E.Origin.Tell(_translationLookup["COMMANDS_FIND_EMPTY"]);
return;
}
foreach (var client in db_players)
{
E.Origin.Tell(_translationLookup["COMMANDS_FIND_FORMAT"].FormatExt(client.Name, client.ClientId, Utilities.ConvertLevelToColor((Permission)client.LevelInt, client.Level), client.IPAddress, client.LastConnectionText));
}
}
}
/// <summary>
/// Lists server and global rules
@ -976,40 +705,7 @@ namespace SharedLibraryCore.Commands
}
}
/// <summary>
/// Sends a private message to another player
/// </summary>
public class PrivateMessageCommand : Command
{
public PrivateMessageCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "privatemessage";
Description = _translationLookup["COMMANDS_PM_DESC"];
Alias = "pm";
Permission = Permission.User;
RequiresTarget = true;
Arguments = new[]
{
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true
},
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
Required = true
}
};
}
public override Task ExecuteAsync(GameEvent E)
{
E.Target.Tell($"^1{E.Origin.Name} ^3[PM]^7 - {E.Data}");
E.Origin.Tell($"To ^3{E.Target.Name} ^7-> {E.Data}");
return Task.CompletedTask;
}
}
/// <summary>
/// Flag given client for specified reason
@ -1105,116 +801,6 @@ namespace SharedLibraryCore.Commands
}
}
/// <summary>
/// Report client for given reason
/// </summary>
public class ReportClientCommand : Command
{
public ReportClientCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "report";
Description = _translationLookup["COMMANDS_REPORT_DESC"];
Alias = "rep";
Permission = Permission.User;
RequiresTarget = true;
Arguments = new[]
{
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true
},
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_REASON"],
Required = true
}
};
}
public override async Task ExecuteAsync(GameEvent commandEvent)
{
if (commandEvent.Data.ToLower().Contains("camp"))
{
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_CAMP"]);
return;
}
bool success = false;
switch ((await commandEvent.Target.Report(commandEvent.Data, commandEvent.Origin).WaitAsync(Utilities.DefaultCommandTimeout, commandEvent.Owner.Manager.CancellationToken)).FailReason)
{
case GameEvent.EventFailReason.None:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_SUCCESS"]);
success = true;
break;
case GameEvent.EventFailReason.Exception:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_DUPLICATE"]);
break;
case GameEvent.EventFailReason.Permission:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL"].FormatExt(commandEvent.Target.Name));
break;
case GameEvent.EventFailReason.Invalid:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_SELF"]);
break;
case GameEvent.EventFailReason.Throttle:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_TOOMANY"]);
break;
}
if (success)
{
commandEvent.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", commandEvent.Origin.Name, commandEvent.Target.Name, commandEvent.Data));
}
}
}
/// <summary>
/// List all reports on the server
/// </summary>
public class ListReportsCommand : Command
{
public ListReportsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "reports";
Description = _translationLookup["COMMANDS_REPORTS_DESC"];
Alias = "reps";
Permission = Permission.Moderator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_CLEAR"],
Required = false
}
};
}
public override Task ExecuteAsync(GameEvent E)
{
if (E.Data != null && E.Data.ToLower().Contains(_translationLookup["COMMANDS_ARGS_CLEAR"]))
{
E.Owner.Reports = new List<Report>();
E.Origin.Tell(_translationLookup["COMMANDS_REPORTS_CLEAR_SUCCESS"]);
return Task.CompletedTask;
}
if (E.Owner.Reports.Count < 1)
{
E.Origin.Tell(_translationLookup["COMMANDS_REPORTS_NONE"]);
return Task.CompletedTask;
}
foreach (Report R in E.Owner.Reports)
{
E.Origin.Tell(String.Format("^5{0}^7->^1{1}^7: {2}", R.Origin.Name, R.Target.Name, R.Reason));
}
return Task.CompletedTask;
}
}
/// <summary>
/// Masks client from announcements and online admin list
/// </summary>
@ -1292,49 +878,6 @@ namespace SharedLibraryCore.Commands
}
}
/// <summary>
/// Lists alises of specified client
/// </summary>
public class ListAliasesCommand : Command
{
public ListAliasesCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "alias";
Description = _translationLookup["COMMANDS_ALIAS_DESC"];
Alias = "known";
Permission = EFClient.Permission.Moderator;
RequiresTarget = true;
Arguments = new[]
{
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true,
}
};
}
public override Task ExecuteAsync(GameEvent E)
{
StringBuilder message = new StringBuilder();
var names = new List<string>(E.Target.AliasLink.Children.Select(a => a.Name));
var IPs = new List<string>(E.Target.AliasLink.Children.Select(a => a.IPAddress.ConvertIPtoString()).Distinct());
E.Origin.Tell($"[^3{E.Target}^7]");
message.Append($"{_translationLookup["COMMANDS_ALIAS_ALIASES"]}: ");
message.Append(String.Join(" | ", names));
E.Origin.Tell(message.ToString());
message.Clear();
message.Append($"{_translationLookup["COMMANDS_ALIAS_IPS"]}: ");
message.Append(String.Join(" | ", IPs));
E.Origin.Tell(message.ToString());
return Task.CompletedTask;
}
}
/// <summary>
/// Executes RCon command
/// </summary>
@ -1372,33 +915,6 @@ namespace SharedLibraryCore.Commands
}
}
/// <summary>
/// Lists the loaded plugins
/// </summary>
/*public class ListPluginsCommand : Command
{
private readonly IPluginImporter _pluginImporter;
public ListPluginsCommand(CommandConfiguration config, ITranslationLookup translationLookup, IPluginImporter pluginImporter) : base(config, translationLookup)
{
Name = "plugins";
Description = _translationLookup["COMMANDS_PLUGINS_DESC"];
Alias = "p";
Permission = Permission.Administrator;
RequiresTarget = false;
_pluginImporter = pluginImporter;
}
public override Task ExecuteAsync(GameEvent E)
{
E.Origin.Tell(_translationLookup["COMMANDS_PLUGINS_LOADED"]);
foreach (var P in _pluginImporter.ActivePlugins)
{
E.Origin.Tell(string.Format("^3{0} ^7[v^3{1}^7] by ^5{2}^7", P.Name, P.Version, P.Author));
}
return Task.CompletedTask;
}
}*/
/// <summary>
/// Lists external IP
/// </summary>
@ -1558,11 +1074,11 @@ namespace SharedLibraryCore.Commands
if (E.Target == null)
{
E.Origin.Tell(_translationLookup["COMMANDS_PING_SELF"].FormatExt(E.Origin.Ping));
E.Origin.Tell(_translationLookup["COMMANDS_PING_SELF_V2"].FormatExt(E.Origin.Ping));
}
else
{
E.Origin.Tell(_translationLookup["COMMANDS_PING_TARGET"].FormatExt(E.Target.Name, E.Target.Ping));
E.Origin.Tell(_translationLookup["COMMANDS_PING_TARGET_V2"].FormatExt(E.Target.Name, E.Target.Ping));
}
return Task.CompletedTask;

View File

@ -108,7 +108,7 @@ namespace SharedLibraryCore.Configuration
public string ConnectionString { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_RCON_POLLRATE")]
public int RConPollRate { get; set; } = 5000;
public int RConPollRate { get; set; } = 8000;
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_MAX_TB")]
public TimeSpan MaximumTempBanTime { get; set; } = new TimeSpan(24 * 30, 0, 0);
@ -116,6 +116,9 @@ namespace SharedLibraryCore.Configuration
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_ENABLE_COLOR_CODES")]
public bool EnableColorCodes { get; set; }
[ConfigurationIgnore]
public string IngameAccentColorKey { get; set; } = "Cyan";
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_AUTOMESSAGE_PERIOD")]
public int AutoMessagePeriod { get; set; }

View File

@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using Microsoft.Win32;
using SharedLibraryCore.Interfaces;
namespace SharedLibraryCore.Configuration.Extensions
{
public static class ConfigurationExtensions
{
public static void TrySetIpAddress(this ServerConfiguration config)
{
try
{
var interfaces = NetworkInterface.GetAllNetworkInterfaces().Where(nic =>
nic.OperationalStatus == OperationalStatus.Up &&
(nic.NetworkInterfaceType == NetworkInterfaceType.Wireless80211 ||
nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet && nic.GetIPProperties().UnicastAddresses
.Any(addr => addr.Address.AddressFamily == AddressFamily.InterNetwork))).ToList();
var publicInterfaces = interfaces.Where(nic =>
nic.GetIPProperties().UnicastAddresses.Any(info =>
info.Address.AddressFamily == AddressFamily.InterNetwork && !info.Address.IsInternal()))
.ToList();
config.IPAddress = publicInterfaces.Any()
? publicInterfaces.First().GetIPProperties().UnicastAddresses.First().Address.ToString()
: IPAddress.Loopback.ToString();
}
catch
{
config.IPAddress = IPAddress.Loopback.ToString();
}
}
public static (string, string)[] TryGetRConPasswords(this IRConParser parser)
{
string searchPath = null;
var isRegistryKey = parser.Configuration.DefaultInstallationDirectoryHint.Contains("HKEY_");
try
{
if (isRegistryKey)
{
var result = Registry.GetValue(parser.Configuration.DefaultInstallationDirectoryHint, null, null);
if (result == null)
{
return new (string, string)[0];
}
searchPath = Path.Combine(result.ToString().Split(Path.DirectorySeparatorChar)
.Where(p => !p.Contains(".exe"))
.Select(p => p.Replace("\"", "")).ToArray());
}
else
{
var path = parser.Configuration.DefaultInstallationDirectoryHint.Replace("{LocalAppData}",
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
if (Directory.Exists(path))
{
searchPath = path;
}
}
if (string.IsNullOrEmpty(searchPath))
{
return new (string, string)[0];
}
var possibleFiles = Directory.GetFiles(searchPath, "*.cfg", SearchOption.AllDirectories);
if (!possibleFiles.Any())
{
return new (string, string)[0];
}
var possiblePasswords = possibleFiles.SelectMany(File.ReadAllLines)
.Select(line => Regex.Match(line, "^(\\/\\/)?.*rcon_password +\"?([^\\/\"\n]+)\"?"))
.Where(match => match.Success)
.Select(match =>
!string.IsNullOrEmpty(match.Groups[1].ToString())
? (match.Groups[2].ToString(),
Utilities.CurrentLocalization.LocalizationIndex["SETUP_RCON_PASSWORD_COMMENTED"])
: (match.Groups[2].ToString(), null));
return possiblePasswords.ToArray();
}
catch
{
return new (string, string)[0];
}
}
}
}

View File

@ -3,6 +3,7 @@ using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using SharedLibraryCore.Configuration.Extensions;
namespace SharedLibraryCore.Configuration
{
@ -10,96 +11,145 @@ namespace SharedLibraryCore.Configuration
{
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_IP")]
public string IPAddress { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_PORT")]
public int Port { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_PASSWORD")]
public string Password { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_RULES")]
public string[] Rules { get; set; } = new string[0];
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_AUTO_MESSAGES")]
public string[] AutoMessages { get; set; } = new string[0];
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_PATH")]
[ConfigurationOptional]
public string ManualLogPath { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_RCON_PARSER")]
public string RConParserVersion { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_EVENT_PARSER")]
public string EventParserVersion { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_RESERVED_SLOT")]
public int ReservedSlotNumber { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_GAME_LOG_SERVER")]
[ConfigurationOptional]
public Uri GameLogServerUrl { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_CUSTOM_HOSTNAME")]
[ConfigurationOptional]
public string CustomHostname { get; set; }
private readonly IList<IRConParser> rconParsers;
private readonly IList<IEventParser> eventParsers;
private readonly IList<IRConParser> _rconParsers;
private IRConParser _selectedParser;
public ServerConfiguration()
{
rconParsers = new List<IRConParser>();
eventParsers = new List<IEventParser>();
_rconParsers = new List<IRConParser>();
Rules = new string[0];
AutoMessages = new string[0];
}
public void AddRConParser(IRConParser parser)
{
rconParsers.Add(parser);
_rconParsers.Add(parser);
}
public void AddEventParser(IEventParser parser)
{
eventParsers.Add(parser);
}
public void ModifyParsers()
{
var loc = Utilities.CurrentLocalization.LocalizationIndex;
var parserVersions = rconParsers.Select(_parser => _parser.Name).ToArray();
var selection = Utilities.PromptSelection($"{loc["SETUP_SERVER_RCON_PARSER_VERSION"]} ({IPAddress}:{Port})", parserVersions[0], null, parserVersions);
var parserVersions = _rconParsers.Select(p => p.Name).ToArray();
var (index, parser) = loc["SETUP_SERVER_RCON_PARSER_VERSION"].PromptSelection(parserVersions[0],
null, parserVersions);
if (selection.Item1 >= 0)
if (index < 0)
{
RConParserVersion = rconParsers.FirstOrDefault(_parser => _parser.Name == selection.Item2)?.Version;
if (selection.Item1 > 0 && !rconParsers[selection.Item1].CanGenerateLogPath)
{
Console.WriteLine(loc["SETUP_SERVER_NO_LOG"]);
ManualLogPath = Utilities.PromptString(loc["SETUP_SERVER_LOG_PATH"]);
}
return;
}
parserVersions = eventParsers.Select(_parser => _parser.Name).ToArray();
selection = Utilities.PromptSelection($"{loc["SETUP_SERVER_EVENT_PARSER_VERSION"]} ({IPAddress}:{Port})", parserVersions[0], null, parserVersions);
_selectedParser = _rconParsers.FirstOrDefault(p => p.Name == parser);
RConParserVersion = _selectedParser?.Version;
EventParserVersion = _selectedParser?.Version;
if (selection.Item1 >= 0)
if (index <= 0 || _rconParsers[index].CanGenerateLogPath)
{
EventParserVersion = eventParsers.FirstOrDefault(_parser => _parser.Name == selection.Item2)?.Version;
return;
}
Console.WriteLine(loc["SETUP_SERVER_NO_LOG"]);
ManualLogPath = loc["SETUP_SERVER_LOG_PATH"].PromptString();
}
public IBaseConfiguration Generate()
{
ModifyParsers();
var loc = Utilities.CurrentLocalization.LocalizationIndex;
var shouldTryFindIp = loc["SETUP_SERVER_IP_AUTO"].PromptBool(defaultValue: true);
while (string.IsNullOrEmpty(IPAddress))
if (shouldTryFindIp)
{
var input = Utilities.PromptString(loc["SETUP_SERVER_IP"]);
IPAddress = input;
this.TrySetIpAddress();
Console.WriteLine(loc["SETUP_SERVER_IP_AUTO_RESULT"].FormatExt(IPAddress));
}
else
{
while (string.IsNullOrEmpty(IPAddress))
{
var input = loc["SETUP_SERVER_IP"].PromptString();
IPAddress = input;
}
}
var defaultPort = _selectedParser.Configuration.DefaultRConPort;
Port = loc["SETUP_SERVER_PORT"].PromptInt(null, 1, ushort.MaxValue, defaultValue:defaultPort);
if (!string.IsNullOrEmpty(_selectedParser.Configuration.DefaultInstallationDirectoryHint))
{
var shouldTryFindPassword = loc["SETUP_RCON_PASSWORD_AUTO"].PromptBool(defaultValue: true);
if (shouldTryFindPassword)
{
var passwords = _selectedParser.TryGetRConPasswords();
if (passwords.Length > 1)
{
var (index, value) =
loc["SETUP_RCON_PASSWORD_PROMPT"].PromptSelection(loc["SETUP_RCON_PASSWORD_MANUAL"], null,
passwords.Select(pw =>
$"{pw.Item1}{(string.IsNullOrEmpty(pw.Item2) ? "" : " " + pw.Item2)}").ToArray());
if (index > 0)
{
Password = passwords[index - 1].Item1;
}
}
else if (passwords.Length > 0)
{
Password = passwords[0].Item1;
Console.WriteLine(loc["SETUP_RCON_PASSWORD_RESULT"].FormatExt(Password));
}
}
}
if (string.IsNullOrEmpty(Password))
{
Password = loc["SETUP_SERVER_RCON"].PromptString();
}
Port = Utilities.PromptInt(loc["SETUP_SERVER_PORT"], null, 1, ushort.MaxValue);
Password = Utilities.PromptString(loc["SETUP_SERVER_RCON"]);
AutoMessages = new string[0];
Rules = new string[0];
ReservedSlotNumber = loc["SETUP_SERVER_RESERVEDSLOT"].PromptInt(null, 0, 32);
ManualLogPath = null;
ModifyParsers();
return this;
}
@ -108,4 +158,4 @@ namespace SharedLibraryCore.Configuration
return "ServerConfiguration";
}
}
}
}

View File

@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace SharedLibraryCore.Formatting
{
public enum ColorCodes
{
Black,
Red,
Green,
Yellow,
Blue,
Cyan,
Purple,
Pink,
White,
Map,
Grey,
Wildcard,
Accent
}
public class ColorCodeMapping : Dictionary<string, string>
{
}
}

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Helpers
{

View File

@ -20,6 +20,7 @@ namespace SharedLibraryCore.Interfaces
ILogger GetLogger(long serverId);
IList<Server> GetServers();
IList<IManagerCommand> GetCommands();
IReadOnlyList<IManagerCommand> Commands { get; }
IList<Helpers.MessageToken> GetMessageTokens();
IList<EFClient> GetActiveClients();
EFClient FindActiveClient(EFClient client);

View File

@ -1,5 +1,4 @@
using System;
using System.Threading.Tasks;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
@ -10,9 +9,9 @@ namespace SharedLibraryCore.Interfaces
Task OnEventAsync(GameEvent E, Server S);
Task OnTickAsync(Server S);
//for logging purposes
String Name { get; }
string Name { get; }
float Version { get; }
String Author { get; }
string Author { get; }
bool IsParser => false;
}
}

View File

@ -1,6 +1,8 @@
using SharedLibraryCore.RCon;
using System.Collections.Generic;
using System.Globalization;
using SharedLibraryCore.Formatting;
using SharedLibraryCore.Localization;
namespace SharedLibraryCore.Interfaces
{
@ -87,5 +89,17 @@ namespace SharedLibraryCore.Interfaces
/// specifies the characters used to split a line
/// </summary>
string NoticeLineSeparator { get; }
/// <summary>
/// Default port the game listens to RCon requests on
/// </summary>
int? DefaultRConPort { get; }
/// <summary>
/// Default Indicator of where the game is installed (ex file path or registry entry)
/// </summary>
string DefaultInstallationDirectoryHint { get; }
ColorCodeMapping ColorCodeMapping { get; }
}
}

View File

@ -100,8 +100,9 @@ namespace SharedLibraryCore.Database.Models
CorrelationId = CurrentServer.Manager.ProcessingEvents.Values
.FirstOrDefault(ev => ev.Type == GameEvent.EventType.Command && (ev.Origin?.ClientId == ClientId || ev.ImpersonationOrigin?.ClientId == ClientId))?.CorrelationId ?? Guid.NewGuid()
};
e.Output.Add(message.StripColors());
e.Output.Add(message.FormatMessageForEngine(CurrentServer?.RconParser.Configuration.ColorCodeMapping)
.StripColors());
CurrentServer?.Manager.AddEvent(e);
return e;

View File

@ -132,10 +132,12 @@ namespace SharedLibraryCore
/// <param name="message">Message to be sent to all players</param>
public GameEvent Broadcast(string message, EFClient sender = null)
{
string formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Say ?? "", $"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{message.FixIW4ForwardSlash()}");
ServerLogger.LogDebug("All->" + message.StripColors());
var formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Say ?? "",
$"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping)}");
ServerLogger.LogDebug("All-> {Message}",
message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping).StripColors());
var e = new GameEvent()
var e = new GameEvent
{
Type = GameEvent.EventType.Broadcast,
Data = formattedMessage,
@ -165,6 +167,8 @@ namespace SharedLibraryCore
/// <param name="targetClient">EFClient to send message to</param>
protected async Task Tell(string message, EFClient targetClient)
{
var engineMessage = message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping);
if (!Utilities.IsDevelopment)
{
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
@ -173,24 +177,25 @@ namespace SharedLibraryCore
var formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Tell,
clientNumber,
$"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{message.FixIW4ForwardSlash()}");
$"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{engineMessage}");
if (targetClient.ClientNumber > -1 && message.Length > 0 && targetClient.Level != EFClient.Permission.Console)
await this.ExecuteCommandAsync(formattedMessage);
}
else
{
ServerLogger.LogDebug("Tell[{clientNumber}]->{message}", targetClient.ClientNumber, message.StripColors());
ServerLogger.LogDebug("Tell[{ClientNumber}]->{Message}", targetClient.ClientNumber,
message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping).StripColors());
}
if (targetClient.Level == EFClient.Permission.Console)
{
Console.ForegroundColor = ConsoleColor.Green;
using (LogContext.PushProperty("Server", ToString()))
{
ServerLogger.LogInformation("Command output received: {message}", message);
ServerLogger.LogInformation("Command output received: {Message}",
engineMessage.StripColors());
}
Console.WriteLine(message.StripColors());
Console.WriteLine(engineMessage.StripColors());
Console.ForegroundColor = ConsoleColor.Gray;
}
}

View File

@ -716,12 +716,15 @@ namespace SharedLibraryCore.Services
// we want to project our results
var iqClientProjection = iqClients.OrderByDescending(_client => _client.LastConnection)
.Select(_client => new PlayerInfo()
.Select(_client => new PlayerInfo
{
Name = _client.CurrentAlias.Name,
LevelInt = (int)_client.Level,
LevelInt = (int) _client.Level,
LastConnection = _client.LastConnection,
ClientId = _client.ClientId,
IPAddress = _client.CurrentAlias.IPAddress.HasValue
? _client.CurrentAlias.IPAddress.Value.ToString()
: ""
});
var clients = await iqClientProjection.ToListAsync();

View File

@ -4,7 +4,7 @@
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
<Version>2021.8.31.1</Version>
<Version>2021.11.21.1</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Configurations>Debug;Release;Prerelease</Configurations>
@ -19,7 +19,7 @@
<IsPackable>true</IsPackable>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Description>Shared Library for IW4MAdmin</Description>
<PackageVersion>2021.8.31.1</PackageVersion>
<PackageVersion>2021.11.21.1</PackageVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
@ -44,11 +44,11 @@
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.10" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.10" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="RaidMax.IW4MAdmin.Data" Version="1.0.7" />
<PackageReference Include="RaidMax.IW4MAdmin.Data" Version="1.0.9" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="if not exist &quot;$(ProjectDir)..\BUILD&quot; (&#xD;&#xA;if $(ConfigurationName) == Debug (&#xD;&#xA;md &quot;$(ProjectDir)..\BUILD&quot;&#xD;&#xA;)&#xD;&#xA;)&#xD;&#xA;if not exist &quot;$(ProjectDir)..\BUILD\Plugins&quot; (&#xD;&#xA;if $(ConfigurationName) == Debug (&#xD;&#xA;md &quot;$(ProjectDir)..\BUILD\Plugins&quot;&#xD;&#xA;)&#xD;&#xA;)" />
</Target>

View File

@ -1,8 +1,8 @@
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.Linq;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace SharedLibraryCore
namespace SharedLibraryCore.TagHelpers
{
[HtmlTargetElement("color-code")]
public class ColorCode : TagHelper

View File

@ -21,6 +21,7 @@ using static SharedLibraryCore.Server;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using static Data.Models.Client.EFClient;
using Data.Models;
using SharedLibraryCore.Formatting;
using static Data.Models.EFPenalty;
namespace SharedLibraryCore
@ -178,6 +179,24 @@ namespace SharedLibraryCore
/// <returns></returns>
public static string FixIW4ForwardSlash(this string str) => str.Replace("//", "/ /");
public static string FormatMessageForEngine(this string str, ColorCodeMapping mapping)
{
if (mapping == null || string.IsNullOrEmpty(str))
{
return str;
}
var output = str;
var colorCodeMatches = Regex.Matches(output, @"\(Color::(.{1,16})\)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
foreach (var match in colorCodeMatches.Where(m => m.Success))
{
var key = match.Groups[1].ToString();
output = output.Replace(match.Value, mapping.TryGetValue(key, out var code) ? code : "");
}
return output.FixIW4ForwardSlash() + mapping[ColorCodes.White.ToString()];
}
private static readonly IList<string> _zmGameTypes = new[] { "zclassic", "zstandard", "zcleansed", "zgrief" };
/// <summary>
/// indicates if the given server is running a zombie game mode
@ -189,36 +208,25 @@ namespace SharedLibraryCore
public static bool IsCodGame(this Server server) => server.RconParser?.RConEngine == "COD";
/// <summary>
/// Get the IW Engine color code corresponding to an admin level
/// Get the color key corresponding to a given user level
/// </summary>
/// <param name="level">Specified player level</param>
/// <param name="localizedLevel"></param>
/// <returns></returns>
public static String ConvertLevelToColor(EFClient.Permission level, string localizedLevel)
public static string ConvertLevelToColor(Permission level, string localizedLevel)
{
char colorCode = '6';
// todo: maybe make this game independant?
switch (level)
// todo: make configurable
var colorCode = level switch
{
case EFClient.Permission.Banned:
colorCode = '1';
break;
case EFClient.Permission.Flagged:
colorCode = '9';
break;
case EFClient.Permission.Owner:
colorCode = '5';
break;
case EFClient.Permission.User:
colorCode = '2';
break;
case EFClient.Permission.Trusted:
colorCode = '3';
break;
default:
break;
}
Permission.Banned => "Red",
Permission.Flagged => "Map",
Permission.Owner => "Accent",
Permission.User => "Yellow",
Permission.Trusted => "Green",
_ => "Pink"
};
return $"^{colorCode}{localizedLevel ?? level.ToString()}";
return $"(Color::{colorCode}){localizedLevel ?? level.ToString()}";
}
public static string ToLocalizedLevelName(this Permission permission)
@ -546,7 +554,7 @@ namespace SharedLibraryCore
/// <param name="description">description of the question's value</param>
/// <param name="defaultValue">default value to set if no input is entered</param>
/// <returns></returns>
public static bool PromptBool(string question, string description = null, bool defaultValue = true)
public static bool PromptBool(this string question, string description = null, bool defaultValue = true)
{
Console.Write($"{question}?{(string.IsNullOrEmpty(description) ? " " : $" ({description}) ")}[y/n]: ");
char response = Console.ReadLine().ToLower().FirstOrDefault();
@ -562,7 +570,7 @@ namespace SharedLibraryCore
/// <param name="description">description of the question's value</param>
/// <param name="selections">array of possible selections (should be able to convert to string)</param>
/// <returns></returns>
public static Tuple<int, T> PromptSelection<T>(string question, T defaultValue, string description = null, params T[] selections)
public static Tuple<int, T> PromptSelection<T>(this string question, T defaultValue, string description = null, params T[] selections)
{
bool hasDefault = false;
@ -634,7 +642,7 @@ namespace SharedLibraryCore
/// <param name="description">description of the question's value</param>
/// <param name="defaultValue">default value to set the return value to</param>
/// <returns></returns>
public static string PromptString(string question, string description = null, string defaultValue = null)
public static string PromptString(this string question, string description = null, string defaultValue = null)
{
string inputOrDefault()
{

View File

@ -46,7 +46,8 @@ namespace WebfrontCore.Controllers
case nameof(UnbanCommand):
_unbanCommandName = cmd.Name;
break;
case nameof(SayCommand):
// todo: this should be flag driven
case "SayCommand":
_sayCommandName = cmd.Name;
break;
case nameof(KickCommand):

View File

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks;
using Data.Models;
using IW4MAdmin.Plugins.Stats.Config;
using Stats.Config;
using WebfrontCore.ViewComponents;
namespace WebfrontCore.Controllers

View File

@ -16,6 +16,7 @@ using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using Data.Abstractions;
using IW4MAdmin.Plugins.Stats.Config;
using Stats.Config;
namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
{

View File

@ -26,6 +26,7 @@ using Data.Abstractions;
using Data.Helpers;
using IW4MAdmin.Plugins.Stats.Config;
using Stats.Client.Abstractions;
using Stats.Config;
using WebfrontCore.Controllers.API.Validation;
using WebfrontCore.Middleware;

View File

@ -5,6 +5,7 @@ using IW4MAdmin.Plugins.Stats.Config;
using IW4MAdmin.Plugins.Stats.Helpers;
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore.Interfaces;
using Stats.Config;
namespace WebfrontCore.ViewComponents
{

View File

@ -50,9 +50,15 @@
{
<!-- I don't want to include the entire highlight js into the bundle for this 1 page -->
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/highlight.min.js"></script>
<script>
if (hljs !== undefined) {
hljs.highlightAll();
}
</script>
<environment include="Development">
<script type="text/javascript" src="~/js/configuration.js"></script>
</environment>
}
</div>

View File

@ -99,7 +99,7 @@ $(document).ready(function () {
$.get('https://ip2c.org/' + $(address).data('ip'), function (result) {
const countryCode = result.split(';')[1].toLowerCase();
if (countryCode !== 'zz') {
$(address).css('background-image', `url(https://www.countryflags.io/${countryCode}/flat/64.png)`);
$(address).css('background-image', `url('https://flagcdn.com/w80/${countryCode}.png')`);
}
});
});

View File

@ -73,8 +73,7 @@
return false;
});
hljs.highlightAll();
$('.edit-file' ).on('keydown .editable', function(e){
if(e.keyCode === 9) {
document.execCommand ( 'styleWithCSS', true, null )

View File

@ -26,7 +26,7 @@
$('#ip_lookup_country').text(country);
if (countryCode !== 'zz' && countryCode !== '') {
$(address).css('background-image', `url(https://www.countryflags.io/${countryCode}/flat/64.png)`);
$(address).css('background-image', `url('https://flagcdn.com/w80/${countryCode}.png')`);
}
});
});
@ -34,7 +34,6 @@
/* set the end time for initial event query */
startAt = $('.loader-data-time').last().data('time');
$('#filter_meta_container_button').click(function () {
$('#filter_meta_container').hide();
$('#filter_meta_container').removeClass('d-none');
@ -97,7 +96,7 @@
$('.ip-locate-link').click(function (e) {
e.preventDefault();
const ip = $(this).data("ip");
$.getJSON('https://extreme-ip-lookup.com/json/' + ip)
$.getJSON('https://extreme-ip-lookup.com/json/' + ip + '?key=demo')
.done(function (response) {
$('#mainModal .modal-title').text(ip);
$('#mainModal .modal-body').text('');