IW4M-Admin/SharedLibraryCore/Server.cs

458 lines
17 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Linq;
2021-07-11 18:26:30 -04:00
using System.Net;
using System.Threading;
using System.Threading.Tasks;
2022-01-26 11:32:16 -05:00
using Data.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog.Context;
2018-04-08 02:44:42 -04:00
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
2022-01-26 11:32:16 -05:00
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using ILogger = Microsoft.Extensions.Logging.ILogger;
2018-04-08 02:44:42 -04:00
namespace SharedLibraryCore
{
public abstract class Server : IGameServer
{
public enum Game
{
COD = -1,
UKN = 0,
IW3 = 1,
IW4 = 2,
IW5 = 3,
IW6 = 4,
T4 = 5,
T5 = 6,
T6 = 7,
2021-04-16 14:35:51 -04:00
T7 = 8,
2021-06-03 11:51:03 -04:00
SHG1 = 9,
CSGO = 10
}
2022-01-26 11:32:16 -05:00
// only here for performance
private readonly bool CustomSayEnabled;
protected readonly string CustomSayName;
protected readonly DefaultSettings DefaultSettings;
protected readonly ILogger ServerLogger;
protected List<string> BroadcastMessages;
protected int ConnectionErrors;
protected string FSGame;
protected IGameLogReaderFactory gameLogReaderFactory;
// Info
private string hostname;
protected TimeSpan LastMessage;
protected DateTime LastPoll;
protected int NextMessage;
protected ManualResetEventSlim OnRemoteCommandResponse;
protected IRConConnectionFactory RConConnectionFactory;
#pragma warning disable CS0612
public Server(ILogger<Server> logger, Interfaces.ILogger deprecatedLogger,
#pragma warning restore CS0612
ServerConfiguration config, IManager mgr, IRConConnectionFactory rconConnectionFactory,
IGameLogReaderFactory gameLogReaderFactory, IServiceProvider serviceProvider)
{
Password = config.Password;
IP = config.IPAddress;
Port = config.Port;
Manager = mgr;
2022-01-26 11:32:16 -05:00
#pragma warning disable CS0612
Logger = deprecatedLogger ?? throw new ArgumentNullException(nameof(deprecatedLogger));
#pragma warning restore CS0612
ServerConfig = config;
EventProcessing = new SemaphoreSlim(1, 1);
Clients = new List<EFClient>(new EFClient[64]);
Reports = new List<Report>();
ClientHistory = new Queue<PlayerHistory>();
2018-03-09 03:01:12 -05:00
ChatHistory = new List<ChatInfo>();
NextMessage = 0;
CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName;
CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName;
this.gameLogReaderFactory = gameLogReaderFactory;
2021-06-03 11:51:03 -04:00
RConConnectionFactory = rconConnectionFactory;
ServerLogger = logger;
DefaultSettings = serviceProvider.GetRequiredService<DefaultSettings>();
InitializeTokens();
InitializeAutoMessages();
}
2022-01-26 11:32:16 -05:00
public long EndPoint => IPAddress.TryParse(IP, out _)
? Convert.ToInt64($"{IP.Replace(".", "")}{Port}")
2021-07-11 18:26:30 -04:00
: $"{IP.Replace(".", "")}{Port}".GetStableHashCode();
2022-01-26 11:32:16 -05:00
// Objects
public IManager Manager { get; protected set; }
[Obsolete] public Interfaces.ILogger Logger { get; }
public ServerConfiguration ServerConfig { get; }
public List<Map> Maps { get; protected set; } = new List<Map>();
public List<Report> Reports { get; set; }
public List<ChatInfo> ChatHistory { get; protected set; }
public Queue<PlayerHistory> ClientHistory { get; }
public Game GameName { get; set; }
public string Hostname
{
get => ServerConfig.CustomHostname ?? hostname;
protected set => hostname = value;
}
public string Website { get; protected set; }
public string Gametype { get; set; }
public string GametypeName => DefaultSettings.Gametypes.FirstOrDefault(gt => gt.Game == GameName)?.Gametypes
?.FirstOrDefault(gt => gt.Name == Gametype)?.Alias ?? Gametype;
public string GamePassword { get; protected set; }
public Map CurrentMap { get; set; }
public int ClientNum
{
get { return Clients.ToArray().Count(p => p != null && !p.IsBot); }
}
public int MaxClients { get; protected set; }
public List<EFClient> Clients { get; protected set; }
public string Password { get; }
public bool Throttled { get; protected set; }
public bool CustomCallback { get; protected set; }
public string WorkingDirectory { get; protected set; }
public IRConConnection RemoteConnection { get; protected set; }
public IRConParser RconParser { get; set; }
public IEventParser EventParser { get; set; }
public string LogPath { get; protected set; }
public bool RestartRequested { get; set; }
public SemaphoreSlim EventProcessing { get; }
// Internal
/// <summary>
/// this is actually the hostname now
/// </summary>
public string IP { get; protected set; }
public IPEndPoint ResolvedIpEndPoint { get; protected set; }
public string Version { get; protected set; }
public bool IsInitialized { get; set; }
public int Port { get; }
public abstract Task Kick(string reason, EFClient target, EFClient origin, EFPenalty originalPenalty);
/// <summary>
2022-01-26 11:32:16 -05:00
/// Returns list of all current players
/// </summary>
/// <returns></returns>
public List<EFClient> GetClientsAsList()
{
return Clients.FindAll(x => x != null && x.NetworkId != 0);
}
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Add a player to the server's player list
2015-08-20 15:23:13 -04:00
/// </summary>
/// <param name="P">EFClient pulled from memory reading</param>
/// <returns>True if player added successfully, false otherwise</returns>
public abstract Task<EFClient> OnClientConnected(EFClient P);
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Remove player by client number
2015-08-20 15:23:13 -04:00
/// </summary>
/// <returns>true if removal succeeded, false otherwise</returns>
public abstract Task OnClientDisconnected(EFClient client);
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Get a player by name
/// todo: make this an extension
2015-08-20 15:23:13 -04:00
/// </summary>
/// <param name="pName">EFClient name to search for</param>
2015-08-20 15:23:13 -04:00
/// <returns>Matching player if found</returns>
2022-01-26 11:32:16 -05:00
public List<EFClient> GetClientByName(string pName)
{
if (string.IsNullOrEmpty(pName))
{
return new List<EFClient>();
}
pName = pName.Trim().StripColors();
2022-01-26 11:32:16 -05:00
var QuoteSplit = pName.Split('"');
var literal = false;
if (QuoteSplit.Length > 1)
{
pName = QuoteSplit[1];
literal = true;
}
2022-01-26 11:32:16 -05:00
if (literal)
{
return GetClientsAsList().Where(p => p.Name?.StripColors()?.ToLower() == pName.ToLower()).ToList();
}
2022-01-26 11:32:16 -05:00
return GetClientsAsList().Where(p => (p.Name?.StripColors()?.ToLower() ?? "").Contains(pName.ToLower()))
.ToList();
}
2022-01-26 11:32:16 -05:00
public virtual Task<bool> ProcessUpdatesAsync(CancellationToken cts)
{
return (Task<bool>)Task.CompletedTask;
}
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Process any server event
2015-08-20 15:23:13 -04:00
/// </summary>
/// <param name="E">Event</param>
/// <returns>True on sucess</returns>
protected abstract Task<bool> ProcessEvent(GameEvent E);
2022-01-26 11:32:16 -05:00
public abstract Task ExecuteEvent(GameEvent E);
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Send a message to all players
2015-08-20 15:23:13 -04:00
/// </summary>
/// <param name="message">Message to be sent to all players</param>
/// <param name="sender">Client that initiated the broadcast</param>
public GameEvent Broadcast(string message, EFClient sender = null)
{
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
{
Type = GameEvent.EventType.Broadcast,
Data = formattedMessage,
Owner = this,
2022-01-26 11:32:16 -05:00
Origin = sender
};
Manager.AddEvent(e);
return e;
}
2022-01-26 11:32:16 -05:00
2022-03-23 12:43:20 -04:00
[Obsolete("Use BroadcastAsync")]
public void Broadcast(IEnumerable<string> messages, EFClient sender = null)
{
foreach (var message in messages)
{
#pragma warning disable 4014
Broadcast(message, sender).WaitAsync();
#pragma warning restore 4014
}
}
2022-01-26 11:32:16 -05:00
2022-03-23 12:43:20 -04:00
public async Task BroadcastAsync(IEnumerable<string> messages, EFClient sender = null,
CancellationToken token = default)
{
foreach (var message in messages)
{
if (Manager.CancellationToken.IsCancellationRequested)
{
return;
}
await Broadcast(message, sender).WaitAsync(Utilities.DefaultCommandTimeout, Manager.CancellationToken);
}
}
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Send a message to a particular players
2015-08-20 15:23:13 -04:00
/// </summary>
2019-08-02 19:04:34 -04:00
/// <param name="message">Message to send</param>
2021-06-03 11:51:03 -04:00
/// <param name="targetClient">EFClient to send message to</param>
protected async Task Tell(string message, EFClient targetClient)
{
var engineMessage = message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping);
2022-01-26 11:32:16 -05:00
if (!Utilities.IsDevelopment)
{
2021-06-03 11:51:03 -04:00
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
var parsedClientId = string.IsNullOrEmpty(temporalClientId) ? (int?)null : int.Parse(temporalClientId);
var clientNumber = parsedClientId ?? targetClient.ClientNumber;
var formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Tell,
2021-06-03 11:51:03 -04:00
clientNumber,
$"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{engineMessage}");
2022-01-26 11:32:16 -05:00
if (targetClient.ClientNumber > -1 && message.Length > 0 &&
targetClient.Level != Data.Models.Client.EFClient.Permission.Console)
{
await this.ExecuteCommandAsync(formattedMessage);
2022-01-26 11:32:16 -05:00
}
}
else
{
ServerLogger.LogDebug("Tell[{ClientNumber}]->{Message}", targetClient.ClientNumber,
message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping).StripColors());
}
2022-01-26 11:32:16 -05:00
if (targetClient.Level == Data.Models.Client.EFClient.Permission.Console)
{
Console.ForegroundColor = ConsoleColor.Green;
using (LogContext.PushProperty("Server", ToString()))
{
ServerLogger.LogInformation("Command output received: {Message}",
engineMessage.StripColors());
}
2022-01-26 11:32:16 -05:00
Console.WriteLine(engineMessage.StripColors());
Console.ForegroundColor = ConsoleColor.Gray;
}
}
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Send a message to all admins on the server
2015-08-20 15:23:13 -04:00
/// </summary>
/// <param name="message">Message to send out</param>
2022-01-26 11:32:16 -05:00
public void ToAdmins(string message)
{
2022-01-26 11:32:16 -05:00
foreach (var client in GetClientsAsList()
.Where(c => c.Level > Data.Models.Client.EFClient.Permission.Flagged))
client.Tell(message);
}
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Kick a player from the server
2015-08-20 15:23:13 -04:00
/// </summary>
/// <param name="reason">Reason for kicking</param>
/// <param name="target">EFClient to kick</param>
/// <param name="origin">Client initating the kick</param>
public Task Kick(string reason, EFClient target, EFClient origin)
2022-01-26 11:32:16 -05:00
{
return Kick(reason, target, origin, null);
2022-01-26 11:32:16 -05:00
}
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Temporarily ban a player ( default 1 hour ) from the server
2015-08-20 15:23:13 -04:00
/// </summary>
2021-10-31 12:57:32 -04:00
/// <param name="reason">Reason for banning the player</param>
/// <param name="length">Duration of the ban</param>
/// <param name="target">The client to ban</param>
/// <param name="origin">The client performing the ban</param>
public abstract Task TempBan(string reason, TimeSpan length, EFClient target, EFClient origin);
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Perm ban a player from the server
2015-08-20 15:23:13 -04:00
/// </summary>
/// <param name="reason">The reason for the ban</param>
/// <param name="target">The person to ban</param>
/// <param name="origin">The person who banned the target</param>
/// <param name="isEvade">obsolete</param>
public abstract Task Ban(string reason, EFClient target, EFClient origin, bool isEvade = false);
2015-08-20 15:23:13 -04:00
public abstract Task Warn(string reason, EFClient target, EFClient origin);
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Unban a player by npID / GUID
2015-08-20 15:23:13 -04:00
/// </summary>
/// <param name="reason">reason for unban</param>
/// <param name="targetClient">client being unbanned</param>
/// <param name="originClient">client performing the unban</param>
2015-08-20 15:23:13 -04:00
/// <returns></returns>
2022-01-26 11:32:16 -05:00
public abstract Task Unban(string reason, EFClient targetClient, EFClient originClient);
2015-08-20 15:23:13 -04:00
/// <summary>
/// Change the current server map
2015-08-20 15:23:13 -04:00
/// </summary>
/// <param name="mapName">Non-localized map name</param>
public async Task LoadMap(string mapName)
{
await this.ExecuteCommandAsync($"map {mapName}");
}
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Initalize the macro variables
2015-08-20 15:23:13 -04:00
/// </summary>
2022-01-26 11:32:16 -05:00
public abstract void InitializeTokens();
2015-08-20 15:23:13 -04:00
/// <summary>
2022-01-26 11:32:16 -05:00
/// Initialize the messages to be broadcasted
2015-08-20 15:23:13 -04:00
/// </summary>
protected void InitializeAutoMessages()
{
2022-01-26 11:32:16 -05:00
BroadcastMessages = new List<string>();
2018-05-10 01:34:29 -04:00
if (ServerConfig.AutoMessages != null)
2022-01-26 11:32:16 -05:00
{
BroadcastMessages.AddRange(ServerConfig.AutoMessages);
2022-01-26 11:32:16 -05:00
}
BroadcastMessages.AddRange(Manager.GetApplicationSettings().Configuration().AutoMessages);
}
public override string ToString()
{
return $"{IP}:{Port}";
}
protected async Task<bool> ScriptLoaded()
{
try
{
return (await this.GetDvarAsync("sv_customcallbacks", "0", Manager.CancellationToken)).Value == "1";
}
2022-01-26 11:32:16 -05:00
catch (DvarException)
{
return false;
}
}
public abstract Task<long> GetIdForServer(Server server = null);
public string[] ExecuteServerCommand(string command, int timeoutMs = 1000)
{
2022-02-13 22:38:40 -05:00
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(timeoutMs));
2022-02-13 22:38:40 -05:00
try
{
return this.ExecuteCommandAsync(command, tokenSource.Token).GetAwaiter().GetResult();
2022-02-13 22:38:40 -05:00
}
catch
{
return null;
}
}
public string GetServerDvar(string dvarName, int timeoutMs = 1000)
{
2022-02-13 22:38:40 -05:00
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(timeoutMs));
2022-02-13 22:38:40 -05:00
try
{
return this.GetDvarAsync<string>(dvarName, token: tokenSource.Token).GetAwaiter().GetResult().Value;
2022-02-13 22:38:40 -05:00
}
catch
{
return null;
}
}
public bool SetServerDvar(string dvarName, string dvarValue, int timeoutMs = 1000)
{
2022-02-13 22:38:40 -05:00
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(timeoutMs));
2022-02-13 22:38:40 -05:00
try
{
this.SetDvarAsync(dvarName, dvarValue, tokenSource.Token).GetAwaiter().GetResult();
2022-02-13 22:38:40 -05:00
return true;
}
catch
{
return false;
}
}
public EFClient GetClientByNumber(int clientNumber) =>
GetClientsAsList().FirstOrDefault(client => client.ClientNumber == clientNumber);
}
}