161b27e2f2
(hopefully) fix an issue with banned players causing exception if they create events before they are kicked out fix issues with sometimes wrong error message for timeout show most recent IP address at top of alias list optimization to some sql queries
339 lines
12 KiB
C#
339 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
using SharedLibraryCore.Helpers;
|
|
using SharedLibraryCore.Dtos;
|
|
using SharedLibraryCore.Configuration;
|
|
using SharedLibraryCore.Interfaces;
|
|
using SharedLibraryCore.Database.Models;
|
|
|
|
namespace SharedLibraryCore
|
|
{
|
|
public abstract class Server
|
|
{
|
|
public enum Game
|
|
{
|
|
COD = -1,
|
|
UKN = 0,
|
|
IW3 = 1,
|
|
IW4 = 2,
|
|
IW5 = 3,
|
|
IW6 = 4,
|
|
T4 = 5,
|
|
T5 = 6,
|
|
T6 = 7,
|
|
T7 = 8
|
|
}
|
|
|
|
public Server(IManager mgr, ServerConfiguration config)
|
|
{
|
|
Password = config.Password;
|
|
IP = config.IPAddress;
|
|
Port = config.Port;
|
|
Manager = mgr;
|
|
Logger = Manager.GetLogger(this.EndPoint);
|
|
Logger.WriteInfo(this.ToString());
|
|
ServerConfig = config;
|
|
RemoteConnection = new RCon.Connection(IP, Port, Password, Logger, null);
|
|
|
|
EventProcessing = new SemaphoreSlim(1, 1);
|
|
Clients = new List<EFClient>(new EFClient[18]);
|
|
Reports = new List<Report>();
|
|
ClientHistory = new Queue<PlayerHistory>();
|
|
ChatHistory = new List<ChatInfo>();
|
|
NextMessage = 0;
|
|
CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName;
|
|
CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName;
|
|
InitializeTokens();
|
|
InitializeAutoMessages();
|
|
}
|
|
|
|
public long EndPoint => Convert.ToInt64($"{IP.Replace(".", "")}{Port}");
|
|
|
|
/// <summary>
|
|
/// Returns list of all current players
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public List<EFClient> GetClientsAsList()
|
|
{
|
|
return Clients.FindAll(x => x != null && x.NetworkId != 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a player to the server's player list
|
|
/// </summary>
|
|
/// <param name="P">EFClient pulled from memory reading</param>
|
|
/// <returns>True if player added sucessfully, false otherwise</returns>
|
|
public abstract Task<EFClient> OnClientConnected(EFClient P);
|
|
|
|
/// <summary>
|
|
/// Remove player by client number
|
|
/// </summary>
|
|
/// <param name="cNum">Client ID of player to be removed</param>
|
|
/// <returns>true if removal succeded, false otherwise</returns>
|
|
public abstract Task OnClientDisconnected(EFClient client);
|
|
|
|
/// <summary>
|
|
/// Get a player by name
|
|
/// todo: make this an extension
|
|
/// </summary>
|
|
/// <param name="pName">EFClient name to search for</param>
|
|
/// <returns>Matching player if found</returns>
|
|
public List<EFClient> GetClientByName(String pName)
|
|
{
|
|
if (string.IsNullOrEmpty(pName))
|
|
{
|
|
return new List<EFClient>();
|
|
}
|
|
|
|
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?.ToLower() == pName.ToLower()).ToList();
|
|
}
|
|
|
|
return GetClientsAsList().Where(p => (p.Name?.ToLower() ?? "").Contains(pName.ToLower())).ToList();
|
|
}
|
|
|
|
virtual public Task<bool> ProcessUpdatesAsync(CancellationToken cts) => (Task<bool>)Task.CompletedTask;
|
|
|
|
/// <summary>
|
|
/// Process any server event
|
|
/// </summary>
|
|
/// <param name="E">Event</param>
|
|
/// <returns>True on sucess</returns>
|
|
protected abstract Task<bool> ProcessEvent(GameEvent E);
|
|
public abstract Task ExecuteEvent(GameEvent E);
|
|
|
|
/// <summary>
|
|
/// Send a message to all players
|
|
/// </summary>
|
|
/// <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 ? $"{CustomSayName}: " : "")}{message.FixIW4ForwardSlash()}");
|
|
|
|
#if DEBUG == true
|
|
Logger.WriteVerbose(message.StripColors());
|
|
#endif
|
|
|
|
var e = new GameEvent()
|
|
{
|
|
Type = GameEvent.EventType.Broadcast,
|
|
Data = formattedMessage,
|
|
Owner = this,
|
|
Origin = sender,
|
|
};
|
|
|
|
Manager.GetEventHandler().AddEvent(e);
|
|
return e;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a message to a particular players
|
|
/// </summary>
|
|
/// <param name="message">Message to send</param>
|
|
/// <param name="target">EFClient to send message to</param>
|
|
protected async Task Tell(string message, EFClient target)
|
|
{
|
|
#if !DEBUG
|
|
string formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Tell, target.ClientNumber, $"{(CustomSayEnabled ? $"{CustomSayName}: " : "")}{message.FixIW4ForwardSlash()}");
|
|
if (target.ClientNumber > -1 && message.Length > 0 && target.Level != EFClient.Permission.Console)
|
|
await this.ExecuteCommandAsync(formattedMessage);
|
|
#else
|
|
Logger.WriteVerbose($"{target.ClientNumber}->{message.StripColors()}");
|
|
await Task.CompletedTask;
|
|
#endif
|
|
|
|
if (target.Level == EFClient.Permission.Console)
|
|
{
|
|
Console.ForegroundColor = ConsoleColor.Green;
|
|
Console.WriteLine(message.StripColors());
|
|
Console.ForegroundColor = ConsoleColor.Gray;
|
|
}
|
|
|
|
// prevent this from queueing up too many command responses
|
|
if (CommandResult.Count > 15)
|
|
{
|
|
CommandResult.RemoveAt(0);
|
|
}
|
|
|
|
// it was a remote command so we need to add it to the command result queue
|
|
if (target.ClientNumber < 0)
|
|
{
|
|
CommandResult.Add(new CommandResponseInfo()
|
|
{
|
|
Response = message.StripColors(),
|
|
ClientId = target.ClientId
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a message to all admins on the server
|
|
/// </summary>
|
|
/// <param name="message">Message to send out</param>
|
|
public void ToAdmins(String message)
|
|
{
|
|
foreach (var client in GetClientsAsList().Where(c => c.Level > EFClient.Permission.Flagged))
|
|
{
|
|
client.Tell(message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Kick a player from the server
|
|
/// </summary>
|
|
/// <param name="Reason">Reason for kicking</param>
|
|
/// <param name="Target">EFClient to kick</param>
|
|
abstract protected Task Kick(String Reason, EFClient Target, EFClient Origin);
|
|
|
|
/// <summary>
|
|
/// Temporarily ban a player ( default 1 hour ) from the server
|
|
/// </summary>
|
|
/// <param name="Reason">Reason for banning the player</param>
|
|
/// <param name="Target">The player to ban</param>
|
|
abstract protected Task TempBan(String Reason, TimeSpan length, EFClient Target, EFClient Origin);
|
|
|
|
/// <summary>
|
|
/// Perm ban a player from the server
|
|
/// </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>
|
|
abstract protected Task Ban(String Reason, EFClient Target, EFClient Origin, bool isEvade = false);
|
|
|
|
abstract protected Task Warn(String Reason, EFClient Target, EFClient Origin);
|
|
|
|
/// <summary>
|
|
/// Unban a player by npID / GUID
|
|
/// </summary>
|
|
/// <param name="npID">npID of the player</param>
|
|
/// <param name="Target">I don't remember what this is for</param>
|
|
/// <returns></returns>
|
|
abstract public Task Unban(string reason, EFClient Target, EFClient Origin);
|
|
|
|
/// <summary>
|
|
/// Change the current searver map
|
|
/// </summary>
|
|
/// <param name="mapName">Non-localized map name</param>
|
|
public async Task LoadMap(string mapName)
|
|
{
|
|
await this.ExecuteCommandAsync($"map {mapName}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initalize the macro variables
|
|
/// </summary>
|
|
abstract public void InitializeTokens();
|
|
|
|
/// <summary>
|
|
/// Read the map configuration
|
|
/// </summary>
|
|
protected void InitializeMaps()
|
|
{
|
|
Maps = new List<Map>();
|
|
var gameMaps = Manager.GetApplicationSettings().Configuration().Maps.FirstOrDefault(m => m.Game == GameName);
|
|
if (gameMaps != null)
|
|
Maps.AddRange(gameMaps.Maps);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize the messages to be broadcasted
|
|
/// </summary>
|
|
protected void InitializeAutoMessages()
|
|
{
|
|
BroadcastMessages = new List<String>();
|
|
|
|
if (ServerConfig.AutoMessages != null)
|
|
BroadcastMessages.AddRange(ServerConfig.AutoMessages);
|
|
BroadcastMessages.AddRange(Manager.GetApplicationSettings().Configuration().AutoMessages);
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return $"{IP}:{Port}";
|
|
}
|
|
|
|
protected async Task<bool> ScriptLoaded()
|
|
{
|
|
try
|
|
{
|
|
return (await this.GetDvarAsync<string>("sv_customcallbacks")).Value == "1";
|
|
}
|
|
|
|
catch (Exceptions.DvarException)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Objects
|
|
public IManager Manager { get; protected set; }
|
|
public ILogger Logger { get; private set; }
|
|
public ServerConfiguration ServerConfig { get; private set; }
|
|
public List<Map> Maps { get; protected set; }
|
|
public List<Report> Reports { get; set; }
|
|
public List<ChatInfo> ChatHistory { get; protected set; }
|
|
public Queue<PlayerHistory> ClientHistory { get; private set; }
|
|
public Game GameName { get; protected set; }
|
|
|
|
// Info
|
|
public string Hostname { get; protected set; }
|
|
public string Website { get; protected set; }
|
|
public string Gametype { get; set; }
|
|
public Map CurrentMap { get; set; }
|
|
public int ClientNum
|
|
{
|
|
get
|
|
{
|
|
return Clients.Where(p => p != null && !p.IsBot).Count();
|
|
}
|
|
}
|
|
public int MaxClients { get; protected set; }
|
|
public List<EFClient> 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 RCon.Connection RemoteConnection { get; protected set; }
|
|
public IRConParser RconParser { get; protected set; }
|
|
public IEventParser EventParser { get; set; }
|
|
public string LogPath { get; protected set; }
|
|
public bool RestartRequested { get; set; }
|
|
public SemaphoreSlim EventProcessing { get; private set; }
|
|
|
|
// Internal
|
|
public string IP { get; protected set; }
|
|
public string Version { get; protected set; }
|
|
public bool IsInitialized { get; set; }
|
|
|
|
public int Port { get; private set; }
|
|
protected string FSGame;
|
|
protected int NextMessage;
|
|
protected int ConnectionErrors;
|
|
protected List<string> BroadcastMessages;
|
|
protected TimeSpan LastMessage;
|
|
protected DateTime LastPoll;
|
|
protected ManualResetEventSlim OnRemoteCommandResponse;
|
|
|
|
// only here for performance
|
|
private readonly bool CustomSayEnabled;
|
|
private readonly string CustomSayName;
|
|
|
|
//Remote
|
|
public IList<CommandResponseInfo> CommandResult = new List<CommandResponseInfo>();
|
|
}
|
|
}
|