using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Serilog.Context; using SharedLibraryCore.Helpers; using SharedLibraryCore.Dtos; using SharedLibraryCore.Configuration; using SharedLibraryCore.Interfaces; using SharedLibraryCore.Database.Models; using ILogger = Microsoft.Extensions.Logging.ILogger; using Data.Models; 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, T7 = 8, SHG1 = 9, CSGO = 10 } public Server(ILogger logger, SharedLibraryCore.Interfaces.ILogger deprecatedLogger, ServerConfiguration config, IManager mgr, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory) { Password = config.Password; IP = config.IPAddress; Port = config.Port; Manager = mgr; Logger = deprecatedLogger; ServerConfig = config; EventProcessing = new SemaphoreSlim(1, 1); Clients = new List(new EFClient[64]); Reports = new List(); ClientHistory = new Queue(); ChatHistory = new List(); NextMessage = 0; CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName; CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName; this.gameLogReaderFactory = gameLogReaderFactory; RConConnectionFactory = rconConnectionFactory; ServerLogger = logger; InitializeTokens(); InitializeAutoMessages(); } public long EndPoint => IPAddress.TryParse(IP, out _) ? Convert.ToInt64($"{IP.Replace(".", "")}{Port}") : $"{IP.Replace(".", "")}{Port}".GetStableHashCode(); /// /// Returns list of all current players /// /// public List GetClientsAsList() { return Clients.FindAll(x => x != null && x.NetworkId != 0); } /// /// Add a player to the server's player list /// /// EFClient pulled from memory reading /// True if player added sucessfully, false otherwise public abstract Task OnClientConnected(EFClient P); /// /// Remove player by client number /// /// Client ID of player to be removed /// true if removal succeded, false otherwise public abstract Task OnClientDisconnected(EFClient client); /// /// Get a player by name /// todo: make this an extension /// /// EFClient name to search for /// Matching player if found public List GetClientByName(String pName) { if (string.IsNullOrEmpty(pName)) { return new List(); } pName = pName.Trim().StripColors(); string[] QuoteSplit = pName.Split('"'); bool literal = false; if (QuoteSplit.Length > 1) { pName = QuoteSplit[1]; literal = true; } if (literal) { return GetClientsAsList().Where(p => p.Name?.StripColors()?.ToLower() == pName.ToLower()).ToList(); } return GetClientsAsList().Where(p => (p.Name?.StripColors()?.ToLower() ?? "").Contains(pName.ToLower())).ToList(); } virtual public Task ProcessUpdatesAsync(CancellationToken cts) => (Task)Task.CompletedTask; /// /// Process any server event /// /// Event /// True on sucess protected abstract Task ProcessEvent(GameEvent E); public abstract Task ExecuteEvent(GameEvent E); /// /// Send a message to all players /// /// Message to be sent to all players 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 e = new GameEvent() { Type = GameEvent.EventType.Broadcast, Data = formattedMessage, Owner = this, Origin = sender, }; Manager.AddEvent(e); return e; } public void Broadcast(IEnumerable messages, EFClient sender = null) { foreach (var message in messages) { #pragma warning disable 4014 Broadcast(message, sender).WaitAsync(); #pragma warning restore 4014 } } /// /// Send a message to a particular players /// /// Message to send /// EFClient to send message to protected async Task Tell(string message, EFClient targetClient) { if (!Utilities.IsDevelopment) { var temporalClientId = targetClient.GetAdditionalProperty("ConnectionClientId"); var parsedClientId = string.IsNullOrEmpty(temporalClientId) ? (int?)null : int.Parse(temporalClientId); var clientNumber = parsedClientId ?? targetClient.ClientNumber; var formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Tell, clientNumber, $"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{message.FixIW4ForwardSlash()}"); 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()); } if (targetClient.Level == EFClient.Permission.Console) { Console.ForegroundColor = ConsoleColor.Green; using (LogContext.PushProperty("Server", ToString())) { ServerLogger.LogInformation("Command output received: {message}", message); } Console.WriteLine(message.StripColors()); Console.ForegroundColor = ConsoleColor.Gray; } } /// /// Send a message to all admins on the server /// /// Message to send out public void ToAdmins(String message) { foreach (var client in GetClientsAsList().Where(c => c.Level > EFClient.Permission.Flagged)) { client.Tell(message); } } /// /// Kick a player from the server /// /// Reason for kicking /// EFClient to kick 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); /// /// Temporarily ban a player ( default 1 hour ) from the server /// /// Reason for banning the player /// The player to ban abstract public Task TempBan(String Reason, TimeSpan length, EFClient Target, EFClient Origin); /// /// Perm ban a player from the server /// /// The reason for the ban /// The person to ban /// The person who banned the target abstract public Task Ban(String Reason, EFClient Target, EFClient Origin, bool isEvade = false); abstract public Task Warn(String Reason, EFClient Target, EFClient Origin); /// /// Unban a player by npID / GUID /// /// npID of the player /// I don't remember what this is for /// abstract public Task Unban(string reason, EFClient Target, EFClient Origin); /// /// Change the current searver map /// /// Non-localized map name public async Task LoadMap(string mapName) { await this.ExecuteCommandAsync($"map {mapName}"); } /// /// Initalize the macro variables /// abstract public void InitializeTokens(); /// /// Initialize the messages to be broadcasted /// protected void InitializeAutoMessages() { BroadcastMessages = new List(); if (ServerConfig.AutoMessages != null) BroadcastMessages.AddRange(ServerConfig.AutoMessages); BroadcastMessages.AddRange(Manager.GetApplicationSettings().Configuration().AutoMessages); } public override string ToString() { return $"{IP}:{Port}"; } protected async Task ScriptLoaded() { try { return (await this.GetDvarAsync("sv_customcallbacks", "0")).Value == "1"; } catch (Exceptions.DvarException) { return false; } } // Objects public IManager Manager { get; protected set; } [Obsolete] public SharedLibraryCore.Interfaces.ILogger Logger { get; private set; } public ServerConfiguration ServerConfig { get; private set; } public List Maps { get; protected set; } = new List(); public List Reports { get; set; } public List ChatHistory { get; protected set; } public Queue ClientHistory { get; private set; } public Game GameName { get; set; } // Info private string hostname; public string Hostname { get => ServerConfig.CustomHostname ?? hostname; protected set => hostname = value; } public string Website { get; protected set; } public string Gametype { get; set; } 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 Clients { get; protected set; } public string Password { get; private set; } 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; private set; } // Internal /// /// this is actually the hostname now /// public string IP { get; protected set; } public IPEndPoint ResolvedIpEndPoint { get; protected set; } public string Version { get; protected set; } public bool IsInitialized { get; set; } protected readonly ILogger ServerLogger; public int Port { get; private set; } protected string FSGame; protected int NextMessage; protected int ConnectionErrors; protected List BroadcastMessages; protected TimeSpan LastMessage; protected DateTime LastPoll; protected ManualResetEventSlim OnRemoteCommandResponse; protected IGameLogReaderFactory gameLogReaderFactory; protected IRConConnectionFactory RConConnectionFactory; // only here for performance private readonly bool CustomSayEnabled; private readonly string CustomSayName; } }