more consistent/enhanced game penalty messages per issue #171
This commit is contained in:
parent
a574fb0d4b
commit
941d9cea73
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
@ -36,7 +37,7 @@ namespace IW4MAdmin.Application.Factories
|
||||
/// <returns></returns>
|
||||
public Server CreateServer(ServerConfiguration config, IManager manager)
|
||||
{
|
||||
return new IW4MServer(config, _translationLookup, _metaService, _serviceProvider);
|
||||
return new IW4MServer(config, _translationLookup, _metaService, _serviceProvider, _serviceProvider.GetRequiredService<IClientNoticeMessageFormatter>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,12 +33,14 @@ namespace IW4MAdmin
|
||||
|
||||
public int Id { get; private set; }
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IClientNoticeMessageFormatter _messageFormatter;
|
||||
|
||||
public IW4MServer(
|
||||
ServerConfiguration serverConfiguration,
|
||||
ITranslationLookup lookup,
|
||||
IMetaService metaService,
|
||||
IServiceProvider serviceProvider) : base(serviceProvider.GetRequiredService<ILogger<Server>>(),
|
||||
IServiceProvider serviceProvider,
|
||||
IClientNoticeMessageFormatter messageFormatter) : base(serviceProvider.GetRequiredService<ILogger<Server>>(),
|
||||
serviceProvider.GetRequiredService<SharedLibraryCore.Interfaces.ILogger>(),
|
||||
serverConfiguration,
|
||||
serviceProvider.GetRequiredService<IManager>(),
|
||||
@ -48,6 +50,7 @@ namespace IW4MAdmin
|
||||
_translationLookup = lookup;
|
||||
_metaService = metaService;
|
||||
_serviceProvider = serviceProvider;
|
||||
_messageFormatter = messageFormatter;
|
||||
}
|
||||
|
||||
public override async Task<EFClient> OnClientConnected(EFClient clientFromLog)
|
||||
@ -454,7 +457,6 @@ namespace IW4MAdmin
|
||||
else if (E.Type == GameEvent.EventType.TempBan)
|
||||
{
|
||||
await TempBan(E.Data, (TimeSpan) E.Extra, E.Target, E.ImpersonationOrigin ?? E.Origin);
|
||||
;
|
||||
}
|
||||
|
||||
else if (E.Type == GameEvent.EventType.Ban)
|
||||
@ -470,7 +472,7 @@ namespace IW4MAdmin
|
||||
|
||||
else if (E.Type == GameEvent.EventType.Kick)
|
||||
{
|
||||
await Kick(E.Data, E.Target, E.ImpersonationOrigin ?? E.Origin);
|
||||
await Kick(E.Data, E.Target, E.ImpersonationOrigin ?? E.Origin, E.Extra as EFPenalty);
|
||||
}
|
||||
|
||||
else if (E.Type == GameEvent.EventType.Warn)
|
||||
@ -1016,6 +1018,12 @@ namespace IW4MAdmin
|
||||
Website = loc["SERVER_WEBSITE_GENERIC"];
|
||||
}
|
||||
|
||||
// todo: remove this once _website is weaned off
|
||||
if (string.IsNullOrEmpty(Manager.GetApplicationSettings().Configuration().ContactUri))
|
||||
{
|
||||
Manager.GetApplicationSettings().Configuration().ContactUri = Website;
|
||||
}
|
||||
|
||||
InitializeMaps();
|
||||
|
||||
WorkingDirectory = basepath.Value;
|
||||
@ -1190,7 +1198,7 @@ namespace IW4MAdmin
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task Kick(string Reason, EFClient targetClient, EFClient originClient)
|
||||
public override async Task Kick(string reason, EFClient targetClient, EFClient originClient, EFPenalty previousPenalty)
|
||||
{
|
||||
targetClient = targetClient.ClientNumber < 0 ?
|
||||
Manager.GetActiveClients()
|
||||
@ -1202,7 +1210,7 @@ namespace IW4MAdmin
|
||||
Type = EFPenalty.PenaltyType.Kick,
|
||||
Expires = DateTime.UtcNow,
|
||||
Offender = targetClient,
|
||||
Offense = Reason,
|
||||
Offense = reason,
|
||||
Punisher = originClient,
|
||||
Link = targetClient.AliasLink
|
||||
};
|
||||
@ -1221,8 +1229,11 @@ namespace IW4MAdmin
|
||||
|
||||
Manager.AddEvent(e);
|
||||
|
||||
// todo: move to translation sheet
|
||||
string formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"{loc["SERVER_KICK_TEXT"]} - ^5{Reason}^7");
|
||||
var formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||
targetClient.ClientNumber,
|
||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration,
|
||||
newPenalty,
|
||||
previousPenalty));
|
||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||
}
|
||||
}
|
||||
@ -1250,14 +1261,15 @@ namespace IW4MAdmin
|
||||
|
||||
if (targetClient.IsIngame)
|
||||
{
|
||||
// todo: move to translation sheet
|
||||
string formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"^7{loc["SERVER_TB_TEXT"]}- ^5{Reason}");
|
||||
var formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||
targetClient.ClientNumber,
|
||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
|
||||
ServerLogger.LogDebug("Executing tempban kick command for {targetClient}", targetClient.ToString());
|
||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
|
||||
}
|
||||
}
|
||||
|
||||
override public async Task Ban(string reason, EFClient targetClient, EFClient originClient, bool isEvade = false)
|
||||
public override async Task Ban(string reason, EFClient targetClient, EFClient originClient, bool isEvade = false)
|
||||
{
|
||||
// ensure player gets kicked if command not performed on them in the same server
|
||||
targetClient = targetClient.ClientNumber < 0 ?
|
||||
@ -1283,8 +1295,9 @@ namespace IW4MAdmin
|
||||
if (targetClient.IsIngame)
|
||||
{
|
||||
ServerLogger.LogDebug("Attempting to kicking newly banned client {targetClient}", targetClient.ToString());
|
||||
// todo: move to translation sheet
|
||||
string formattedString = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{reason} ^7{loc["SERVER_BAN_APPEAL"].FormatExt(Website)}^7");
|
||||
var formattedString = string.Format(RconParser.Configuration.CommandPrefixes.Kick,
|
||||
targetClient.ClientNumber,
|
||||
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
|
||||
await targetClient.CurrentServer.ExecuteCommandAsync(formattedString);
|
||||
}
|
||||
}
|
||||
|
@ -350,6 +350,7 @@ namespace IW4MAdmin.Application
|
||||
.AddSingleton<IMasterCommunication, MasterCommunication>()
|
||||
.AddSingleton<IManager, ApplicationManager>()
|
||||
.AddSingleton<SharedLibraryCore.Interfaces.ILogger, Logger>()
|
||||
.AddSingleton<IClientNoticeMessageFormatter, ClientNoticeMessageFormatter>()
|
||||
.AddSingleton(translationLookup);
|
||||
|
||||
if (args.Contains("serialevents"))
|
||||
|
101
Application/Misc/ClientNoticeMessageFormatter.cs
Normal file
101
Application/Misc/ClientNoticeMessageFormatter.cs
Normal file
@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc
|
||||
{
|
||||
/// <summary>
|
||||
/// implementation of IClientNoticeMessageFormatter
|
||||
/// </summary>
|
||||
public class ClientNoticeMessageFormatter : IClientNoticeMessageFormatter
|
||||
{
|
||||
private readonly ITranslationLookup _transLookup;
|
||||
private readonly ApplicationConfiguration _appConfig;
|
||||
|
||||
public ClientNoticeMessageFormatter(ITranslationLookup transLookup, ApplicationConfiguration appConfig)
|
||||
{
|
||||
_transLookup = transLookup;
|
||||
_appConfig = appConfig;
|
||||
}
|
||||
|
||||
public string BuildFormattedMessage(IRConParserConfiguration config, EFPenalty currentPenalty, EFPenalty originalPenalty = null)
|
||||
{
|
||||
var penalty = originalPenalty ?? currentPenalty;
|
||||
var builder = new StringBuilder();
|
||||
// build the top level header
|
||||
var header = _transLookup[$"SERVER_{penalty.Type.ToString().ToUpper()}_TEXT"];
|
||||
builder.Append(header);
|
||||
builder.Append(Environment.NewLine);
|
||||
// build the reason
|
||||
var reason = _transLookup["GAME_MESSAGE_PENALTY_REASON"].FormatExt(penalty.Offense);
|
||||
foreach (var splitReason in SplitOverMaxLength(reason, config.NoticeMaxCharactersPerLine))
|
||||
{
|
||||
builder.Append(splitReason);
|
||||
builder.Append(Environment.NewLine);
|
||||
}
|
||||
|
||||
if (penalty.Type == EFPenalty.PenaltyType.TempBan)
|
||||
{
|
||||
// build the time remaining if temporary
|
||||
var timeRemainingValue = penalty.Expires.HasValue
|
||||
? (penalty.Expires - DateTime.UtcNow).Value.HumanizeForCurrentCulture()
|
||||
: "--";
|
||||
var timeRemaining = _transLookup["GAME_MESSAGE_PENALTY_TIME_REMAINING"].FormatExt(timeRemainingValue);
|
||||
|
||||
foreach (var splitReason in SplitOverMaxLength(timeRemaining, config.NoticeMaxCharactersPerLine))
|
||||
{
|
||||
builder.Append(splitReason);
|
||||
builder.Append(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
|
||||
if (penalty.Type == EFPenalty.PenaltyType.Ban)
|
||||
{
|
||||
// provide a place to appeal the ban (should always be specified but including a placeholder just incase)
|
||||
builder.Append(_transLookup["GAME_MESSAGE_PENALTY_APPEAL"].FormatExt(_appConfig.ContactUri ?? "--"));
|
||||
}
|
||||
|
||||
// final format looks something like:
|
||||
/*
|
||||
* You are permanently banned
|
||||
* Reason - toxic behavior
|
||||
* Visit example.com to appeal
|
||||
*/
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static IEnumerable<string> SplitOverMaxLength(string source, int maxCharactersPerLine)
|
||||
{
|
||||
if (source.Length <= maxCharactersPerLine)
|
||||
{
|
||||
return new[] {source};
|
||||
}
|
||||
|
||||
var segments = new List<string>();
|
||||
var currentLocation = 0;
|
||||
while (currentLocation < source.Length)
|
||||
{
|
||||
var nextLocation = currentLocation + maxCharactersPerLine;
|
||||
// there's probably a more efficient way to do this but this is readable
|
||||
segments.Add(string.Concat(
|
||||
source
|
||||
.Skip(currentLocation)
|
||||
.Take(Math.Min(maxCharactersPerLine, source.Length - currentLocation))));
|
||||
currentLocation = nextLocation;
|
||||
}
|
||||
|
||||
if (currentLocation < source.Length)
|
||||
{
|
||||
segments.Add(source.Substring(currentLocation, source.Length - currentLocation));
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,8 @@ namespace IW4MAdmin.Application.RconParsers
|
||||
public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber;
|
||||
public IDictionary<string, string> OverrideDvarNameMapping { get; set; } = new Dictionary<string, string>();
|
||||
public IDictionary<string, string> DefaultDvarValues { get; set; } = new Dictionary<string, string>();
|
||||
public int NoticeMaximumLines { get; set; } = 8;
|
||||
public int NoticeMaxCharactersPerLine { get; set; } = 50;
|
||||
|
||||
public DynamicRConParserConfiguration(IParserRegexFactory parserRegexFactory)
|
||||
{
|
||||
|
@ -47,6 +47,8 @@ namespace SharedLibraryCore.Configuration
|
||||
public string SocialLinkAddress { get; set; }
|
||||
[LocalizedDisplayName("SETUP_SOCIAL_TITLE")]
|
||||
public string SocialLinkTitle { get; set; }
|
||||
[LocalizedDisplayName("SETUP_CONTACT_URI")]
|
||||
public string ContactUri { get; set; }
|
||||
|
||||
[LocalizedDisplayName("SETUP_USE_CUSTOMENCODING")]
|
||||
[ConfigurationLinked("CustomParserEncoding")]
|
||||
|
@ -0,0 +1,16 @@
|
||||
using SharedLibraryCore.Database.Models;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
public interface IClientNoticeMessageFormatter
|
||||
{
|
||||
/// <summary>
|
||||
/// builds a game formatted notice message
|
||||
/// </summary>
|
||||
/// <param name="currentPenalty">current penalty the message is for</param>
|
||||
/// <param name="originalPenalty">previous penalty the current penalty relates to</param>
|
||||
/// <param name="config">RCon parser config</param>
|
||||
/// <returns></returns>
|
||||
string BuildFormattedMessage(IRConParserConfiguration config, EFPenalty currentPenalty, EFPenalty originalPenalty = null);
|
||||
}
|
||||
}
|
18
SharedLibraryCore/Interfaces/IGameServer.cs
Normal file
18
SharedLibraryCore/Interfaces/IGameServer.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces
|
||||
{
|
||||
public interface IGameServer
|
||||
{
|
||||
/// <summary>
|
||||
/// kicks target on behalf of origin for given reason
|
||||
/// </summary>
|
||||
/// <param name="reason">reason client is being kicked</param>
|
||||
/// <param name="target">client to kick</param>
|
||||
/// <param name="origin">source of kick action</param>
|
||||
/// <param name="previousPenalty">previous penalty the kick is occuring for (if applicable)</param>
|
||||
/// <returns></returns>
|
||||
public Task Kick(string reason, EFClient target, EFClient origin, EFPenalty previousPenalty = null);
|
||||
}
|
||||
}
|
@ -62,5 +62,9 @@ namespace SharedLibraryCore.Interfaces
|
||||
/// specifies the default dvar values for games that don't support certain dvars
|
||||
/// </summary>
|
||||
IDictionary<string, string> DefaultDvarValues { get; set; }
|
||||
|
||||
int NoticeMaximumLines { get; set; }
|
||||
|
||||
int NoticeMaxCharactersPerLine { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -320,7 +320,15 @@ namespace SharedLibraryCore.Database.Models
|
||||
/// </summary>
|
||||
/// <param name="kickReason">reason to kick for</param>
|
||||
/// <param name="sender">client performing the kick</param>
|
||||
public GameEvent Kick(string kickReason, EFClient sender)
|
||||
public GameEvent Kick(string kickReason, EFClient sender) => Kick(kickReason, sender, null);
|
||||
|
||||
/// <summary>
|
||||
/// kick a client for the given reason
|
||||
/// </summary>
|
||||
/// <param name="kickReason">reason to kick for</param>
|
||||
/// <param name="sender">client performing the kick</param>
|
||||
/// <param name="originalPenalty">original client penalty</param>
|
||||
public GameEvent Kick(string kickReason, EFClient sender, EFPenalty originalPenalty)
|
||||
{
|
||||
var e = new GameEvent()
|
||||
{
|
||||
@ -329,6 +337,7 @@ namespace SharedLibraryCore.Database.Models
|
||||
Target = this,
|
||||
Origin = sender,
|
||||
Data = kickReason,
|
||||
Extra = originalPenalty,
|
||||
Owner = sender.CurrentServer
|
||||
};
|
||||
|
||||
@ -597,7 +606,6 @@ namespace SharedLibraryCore.Database.Models
|
||||
{
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
var autoKickClient = Utilities.IW4MAdminClient(CurrentServer);
|
||||
|
||||
bool isAbleToConnectSimple = IsAbleToConnectSimple();
|
||||
|
||||
if (!isAbleToConnectSimple)
|
||||
@ -617,23 +625,18 @@ namespace SharedLibraryCore.Database.Models
|
||||
// we want to kick them if any account is banned
|
||||
if (banPenalty != null)
|
||||
{
|
||||
if (Level == Permission.Banned)
|
||||
{
|
||||
Utilities.DefaultLogger.LogInformation("Kicking {client} because they are banned", ToString());
|
||||
Kick(loc["SERVER_BAN_PREV"].FormatExt(banPenalty?.Offense), autoKickClient);
|
||||
return false;
|
||||
}
|
||||
|
||||
else
|
||||
if (Level != Permission.Banned)
|
||||
{
|
||||
Utilities.DefaultLogger.LogInformation(
|
||||
"Client {client} is banned, but using a new GUID, we we're updating their level and kicking them",
|
||||
ToString());
|
||||
await SetLevel(Permission.Banned, autoKickClient).WaitAsync(Utilities.DefaultCommandTimeout,
|
||||
CurrentServer.Manager.CancellationToken);
|
||||
Kick(loc["SERVER_BAN_PREV"].FormatExt(banPenalty?.Offense), autoKickClient);
|
||||
return false;
|
||||
}
|
||||
|
||||
Utilities.DefaultLogger.LogInformation("Kicking {client} because they are banned", ToString());
|
||||
Kick(loc["WEBFRONT_PENALTY_LIST_BANNED_REASON"], autoKickClient, banPenalty);
|
||||
return false;
|
||||
}
|
||||
|
||||
// we want to kick them if any account is tempbanned
|
||||
@ -641,9 +644,7 @@ namespace SharedLibraryCore.Database.Models
|
||||
{
|
||||
Utilities.DefaultLogger.LogInformation("Kicking {client} because their GUID is temporarily banned",
|
||||
ToString());
|
||||
Kick(
|
||||
$"{loc["SERVER_TB_REMAIN"]} ({(tempbanPenalty.Expires.Value - DateTime.UtcNow).HumanizeForCurrentCulture()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})",
|
||||
autoKickClient);
|
||||
Kick(loc["WEBFRONT_PENALTY_LIST_TEMPBANNED_REASON"], autoKickClient, tempbanPenalty);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||
|
||||
namespace SharedLibraryCore
|
||||
{
|
||||
public abstract class Server
|
||||
public abstract class Server : IGameServer
|
||||
{
|
||||
public enum Game
|
||||
{
|
||||
@ -205,9 +205,10 @@ namespace SharedLibraryCore
|
||||
/// <summary>
|
||||
/// Kick a player from the server
|
||||
/// </summary>
|
||||
/// <param name="Reason">Reason for kicking</param>
|
||||
/// <param name="reason">Reason for kicking</param>
|
||||
/// <param name="Target">EFClient to kick</param>
|
||||
abstract public Task Kick(String Reason, EFClient Target, EFClient Origin);
|
||||
public Task Kick(String reason, EFClient Target, EFClient Origin) => Kick(reason, Target, Origin, null);
|
||||
public abstract Task Kick(string reason, EFClient target, EFClient origin, EFPenalty originalPenalty);
|
||||
|
||||
/// <summary>
|
||||
/// Temporarily ban a player ( default 1 hour ) from the server
|
||||
|
Loading…
Reference in New Issue
Block a user