fixed issue with status response erroring when incorrect length

view angle vector parse fail is now a handled exception
change local host check to byte array to make it faster than comparing string
kick command now requires moderator level or higher
tempban now requires administrator level or higher
hopefully fixed negative SPM bug
pipelined the events and consolidated them to run through GameEventHandler
uniform console colors
This commit is contained in:
RaidMax 2018-04-26 01:13:04 -05:00
parent ece519251a
commit 99390f1f35
26 changed files with 526 additions and 355 deletions

View File

@ -19,6 +19,7 @@
<AssemblyName>IW4MAdmin</AssemblyName> <AssemblyName>IW4MAdmin</AssemblyName>
<Configurations>Debug;Release;Prerelease</Configurations> <Configurations>Debug;Release;Prerelease</Configurations>
<Win32Resource /> <Win32Resource />
<RootNamespace>IW4MAdmin.Application</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -0,0 +1,46 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace IW4MAdmin.Application
{
class GameEventHandler : IEventHandler
{
private Queue<GameEvent> EventQueue;
private IManager Manager;
public GameEventHandler(IManager mgr)
{
EventQueue = new Queue<GameEvent>();
Manager = mgr;
}
public void AddEvent(GameEvent gameEvent)
{
#if DEBUG
Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner}");
#endif
EventQueue.Enqueue(gameEvent);
#if DEBUG
Manager.GetLogger().WriteDebug($"There are now {EventQueue.Count} events in queue");
#endif
Manager.SetHasEvent();
}
public string[] GetEventOutput()
{
throw new NotImplementedException();
}
public GameEvent GetNextEvent()
{
#if DEBUG
Manager.GetLogger().WriteDebug("Getting next event to be processed");
#endif
return EventQueue.Count > 0 ? EventQueue.Dequeue() : null;
}
}
}

View File

@ -0,0 +1,72 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application.IO
{
class GameLogEvent
{
FileSystemWatcher LogPathWatcher;
Server Server;
long PreviousFileSize;
GameLogReader Reader;
Timer RefreshInfoTimer;
string GameLogFile;
FileInfo Info;
public GameLogEvent(Server server, string gameLogPath, string gameLogName)
{
GameLogFile = gameLogPath;
Reader = new GameLogReader(gameLogPath, server.EventParser);
Server = server;
RefreshInfoTimer = new Timer((sender) =>
{
var newInfo = new FileInfo(GameLogFile);
if (newInfo.Length - Info?.Length > 0)
LogPathWatcher_Changed(this, new FileSystemEventArgs(WatcherChangeTypes.Changed, "", ""));
Info = newInfo;
}, null, 0, 100);
LogPathWatcher = new FileSystemWatcher()
{
Path = gameLogPath.Replace(gameLogName, ""),
Filter = gameLogName,
NotifyFilter = (NotifyFilters)383,
InternalBufferSize = 4096
};
LogPathWatcher.Changed += LogPathWatcher_Changed;
LogPathWatcher.EnableRaisingEvents = true;
}
~GameLogEvent()
{
LogPathWatcher.EnableRaisingEvents = false;
}
private void LogPathWatcher_Changed(object sender, FileSystemEventArgs e)
{
// retrieve the new file size
long newFileSize = new FileInfo(GameLogFile).Length;
if (PreviousFileSize == 0)
PreviousFileSize = newFileSize;
long fileDiff = newFileSize - PreviousFileSize;
if (fileDiff < 1)
return;
var events = Reader.EventsFromLog(Server, fileDiff);
foreach (var ev in events)
Server.Manager.GetEventHandler().AddEvent(ev);
PreviousFileSize = newFileSize;
}
}
}

View File

@ -0,0 +1,49 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace IW4MAdmin.Application.IO
{
class GameLogReader
{
IEventParser Parser;
string LogFile;
public GameLogReader(string logFile, IEventParser parser)
{
LogFile = logFile;
Parser = parser;
}
public ICollection<GameEvent> EventsFromLog(Server server, long fileSizeDiff)
{
// allocate the bytes for the new log lines
byte[] fileBytes = new byte[fileSizeDiff];
// open the file as a stream
using (var rd = new BinaryReader(new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Utilities.EncodingType))
{
rd.BaseStream.Seek(rd.BaseStream.Length - fileSizeDiff - 1, SeekOrigin.Begin);
// the difference should be in the range of a int :P
rd.Read(fileBytes, 0, (int)fileSizeDiff);
}
// convert to event line list
string[] logLines = Utilities.EncodingType.GetString(fileBytes).Replace("\r", "").Split('\n');
List<GameEvent> events = new List<GameEvent>();
// parse each line
foreach (string eventLine in logLines)
{
if (eventLine.Length > 0)
events.Add(Parser.GetEvent(server, eventLine));
}
return events;
}
}
}

View File

@ -30,17 +30,8 @@ namespace IW4MAdmin.Application
void Write(string msg, LogType type) void Write(string msg, LogType type)
{ {
string stringType; if (!Utilities.CurrentLocalization.LocalizationSet.TryGetValue($"GLOBAL_{type.ToString().ToUpper()}", out string stringType))
try
{
stringType = Utilities.CurrentLocalization.LocalizationSet[$"GLOBAL_{type.ToString().ToUpper()}"];
}
catch(KeyNotFoundException)
{
stringType = type.ToString(); stringType = type.ToString();
}
string LogLine = $"[{DateTime.Now.ToString("HH:mm:ss")}] - {stringType}: {msg}"; string LogLine = $"[{DateTime.Now.ToString("HH:mm:ss")}] - {stringType}: {msg}";
lock (ThreadLock) lock (ThreadLock)

View File

@ -24,6 +24,7 @@ namespace IW4MAdmin.Application
Localization.Configure.Initialize(); Localization.Configure.Initialize();
var loc = Utilities.CurrentLocalization.LocalizationSet; var loc = Utilities.CurrentLocalization.LocalizationSet;
Console.OutputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8;
Console.ForegroundColor = ConsoleColor.Gray;
Version = Assembly.GetExecutingAssembly().GetName().Version.Major + Assembly.GetExecutingAssembly().GetName().Version.Minor / 10.0f; Version = Assembly.GetExecutingAssembly().GetName().Version.Major + Assembly.GetExecutingAssembly().GetName().Version.Minor / 10.0f;
Version = Math.Round(Version, 2); Version = Math.Round(Version, 2);
@ -40,7 +41,7 @@ namespace IW4MAdmin.Application
ServerManager = ApplicationManager.GetInstance(); ServerManager = ApplicationManager.GetInstance();
using (var db = new DatabaseContext(ServerManager.GetApplicationSettings().Configuration().ConnectionString)) using (var db = new DatabaseContext(ServerManager.GetApplicationSettings().Configuration().ConnectionString))
new ContextSeed(db).Seed().Wait(); new ContextSeed(db).Seed().Wait();
var api = API.Master.Endpoint.Get(); var api = API.Master.Endpoint.Get();
@ -69,7 +70,7 @@ namespace IW4MAdmin.Application
{ {
Console.ForegroundColor = ConsoleColor.Red; Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(loc["MANAGER_VERSION_FAIL"]); Console.WriteLine(loc["MANAGER_VERSION_FAIL"]);
Console.ForegroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.Gray;
} }
#if !PRERELEASE #if !PRERELEASE
@ -78,7 +79,7 @@ namespace IW4MAdmin.Application
Console.ForegroundColor = ConsoleColor.DarkYellow; Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($"IW4MAdmin {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionStable.ToString("0.0")}]"); Console.WriteLine($"IW4MAdmin {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionStable.ToString("0.0")}]");
Console.WriteLine($"{loc["MANAGER_VERSION_CURRENT"]} [v{Version.ToString("0.0")}]"); Console.WriteLine($"{loc["MANAGER_VERSION_CURRENT"]} [v{Version.ToString("0.0")}]");
Console.ForegroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.Gray;
} }
#else #else
else if (version.CurrentVersionPrerelease > Version) else if (version.CurrentVersionPrerelease > Version)
@ -86,14 +87,14 @@ namespace IW4MAdmin.Application
Console.ForegroundColor = ConsoleColor.DarkYellow; Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($"IW4MAdmin-Prerelease {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionPrerelease.ToString("0.0")}-pr]"); Console.WriteLine($"IW4MAdmin-Prerelease {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionPrerelease.ToString("0.0")}-pr]");
Console.WriteLine($"{loc["MANAGER_VERSION_CURRENT"]} [v{Version.ToString("0.0")}-pr]"); Console.WriteLine($"{loc["MANAGER_VERSION_CURRENT"]} [v{Version.ToString("0.0")}-pr]");
Console.ForegroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.Gray;
} }
#endif #endif
else else
{ {
Console.ForegroundColor = ConsoleColor.Green; Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(loc["MANAGER_VERSION_SUCCESS"]); Console.WriteLine(loc["MANAGER_VERSION_SUCCESS"]);
Console.ForegroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.Gray;
} }
ServerManager.Init().Wait(); ServerManager.Init().Wait();
@ -112,13 +113,13 @@ namespace IW4MAdmin.Application
if (ServerManager.Servers.Count == 0) if (ServerManager.Servers.Count == 0)
{ {
Console.WriteLine("No servers are currently being monitored"); Console.WriteLine(loc["MANAGER_CONSOLE_NOSERV"]);
continue; continue;
} }
Origin.CurrentServer = ServerManager.Servers[0]; Origin.CurrentServer = ServerManager.Servers[0];
GameEvent E = new GameEvent(GameEvent.EventType.Say, userInput, Origin, null, ServerManager.Servers[0]); GameEvent E = new GameEvent(GameEvent.EventType.Say, userInput, Origin, null, ServerManager.Servers[0]);
ServerManager.Servers[0].ExecuteEvent(E); ServerManager.GetEventHandler().AddEvent(E);
Console.Write('>'); Console.Write('>');
} while (ServerManager.Running); } while (ServerManager.Running);
@ -128,10 +129,6 @@ namespace IW4MAdmin.Application
{ {
Task.Run(() => WebfrontCore.Program.Init(ServerManager)); Task.Run(() => WebfrontCore.Program.Init(ServerManager));
} }
ServerManager.Start();
ServerManager.Logger.WriteVerbose(loc["MANAGER_SHUTDOWN_SUCCESS"]);
} }
catch (Exception e) catch (Exception e)
@ -145,6 +142,10 @@ namespace IW4MAdmin.Application
Console.WriteLine(loc["MANAGER_EXIT"]); Console.WriteLine(loc["MANAGER_EXIT"]);
Console.ReadKey(); Console.ReadKey();
} }
ServerManager.Start();
ServerManager.Logger.WriteVerbose(loc["MANAGER_SHUTDOWN_SUCCESS"]);
} }
static void CheckDirectories() static void CheckDirectories()

View File

@ -41,10 +41,13 @@ namespace IW4MAdmin.Application
PenaltyService PenaltySvc; PenaltyService PenaltySvc;
BaseConfigurationHandler<ApplicationConfiguration> ConfigHandler; BaseConfigurationHandler<ApplicationConfiguration> ConfigHandler;
EventApi Api; EventApi Api;
GameEventHandler Handler;
ManualResetEventSlim OnEvent;
Timer StatusUpdateTimer;
#if FTP_LOG #if FTP_LOG
const int UPDATE_FREQUENCY = 700; const int UPDATE_FREQUENCY = 700;
#else #else
const int UPDATE_FREQUENCY = 450; const int UPDATE_FREQUENCY = 2000;
#endif #endif
private ApplicationManager() private ApplicationManager()
@ -63,6 +66,7 @@ namespace IW4MAdmin.Application
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings"); ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey); Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
StartTime = DateTime.UtcNow; StartTime = DateTime.UtcNow;
OnEvent = new ManualResetEventSlim();
} }
private void OnCancelKey(object sender, ConsoleCancelEventArgs args) private void OnCancelKey(object sender, ConsoleCancelEventArgs args)
@ -86,8 +90,18 @@ namespace IW4MAdmin.Application
return Instance ?? (Instance = new ApplicationManager()); return Instance ?? (Instance = new ApplicationManager());
} }
public void UpdateStatus(object state)
{
foreach (var server in Servers)
{
Task.Run(() => server.ProcessUpdatesAsync(new CancellationToken()));
}
}
public async Task Init() public async Task Init()
{ {
// setup the event handler after the class is initialized
Handler = new GameEventHandler(this);
#region DATABASE #region DATABASE
var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted)) var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
.Select(c => new .Select(c => new
@ -248,13 +262,8 @@ namespace IW4MAdmin.Application
} }
Logger.WriteVerbose($"{Utilities.CurrentLocalization.LocalizationSet["MANAGER_MONITORING_TEXT"]} {ServerInstance.Hostname}"); Logger.WriteVerbose($"{Utilities.CurrentLocalization.LocalizationSet["MANAGER_MONITORING_TEXT"]} {ServerInstance.Hostname}");
// add the start event for this server
// this way we can keep track of execution time and see if problems arise. Handler.AddEvent(new GameEvent(GameEvent.EventType.Start, "Server started", null, null, ServerInstance));
var Status = new AsyncStatus(ServerInstance, UPDATE_FREQUENCY);
lock (TaskStatuses)
{
TaskStatuses.Add(Status);
}
} }
catch (ServerException e) catch (ServerException e)
@ -273,6 +282,8 @@ namespace IW4MAdmin.Application
} }
await Task.WhenAll(config.Servers.Select(c => Init(c)).ToArray()); await Task.WhenAll(config.Servers.Select(c => Init(c)).ToArray());
// start polling servers
StatusUpdateTimer = new Timer(UpdateStatus, null, 0, 10000);
#endregion #endregion
@ -344,38 +355,32 @@ namespace IW4MAdmin.Application
public void Start() public void Start()
{ {
Task.Run(() => HeartBeatThread()); Task.Run(() => HeartBeatThread());
while (Running || TaskStatuses.Count > 0) GameEvent newEvent;
while (Running)
{ {
for (int i = 0; i < TaskStatuses.Count; i++) // wait for new event to be added
OnEvent.Wait();
// todo: sequencially or parallelize?
while ((newEvent = Handler.GetNextEvent()) != null)
{ {
var Status = TaskStatuses[i]; try
// task is read to be rerun
if (Status.RequestedTask == null || Status.RequestedTask.Status == TaskStatus.RanToCompletion)
{ {
// remove the task when we want to quit and last run has finished Task.WaitAll(newEvent.Owner.ExecuteEvent(newEvent));
if (!Running)
{
TaskStatuses.RemoveAt(i);
continue;
}
// normal operation
else
{
Status.Update(new Task<bool>(() => { return (Status.Dependant as Server).ProcessUpdatesAsync(Status.GetToken()).Result; }));
if (Status.RunAverage > 1000 + UPDATE_FREQUENCY && !(Status.Dependant as Server).Throttled)
Logger.WriteWarning($"Update task average execution is longer than desired for {(Status.Dependant as Server)} [{Status.RunAverage}ms]");
}
} }
if (Status.RequestedTask.Status == TaskStatus.Faulted) catch (Exception E)
{ {
Logger.WriteWarning($"Update task for {(Status.Dependant as Server)} faulted, restarting"); Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationSet["SERVER_ERROR_EXCEPTION"]} {newEvent.Owner}");
Status.Abort(); Logger.WriteDebug("Error Message: " + E.Message);
Logger.WriteDebug("Error Trace: " + E.StackTrace);
} }
// tell anyone waiting for the output that we're done
newEvent.OnProcessed.Set();
} }
Thread.Sleep(UPDATE_FREQUENCY); // signal that all events have been processed
OnEvent.Reset();
} }
#if !DEBUG #if !DEBUG
foreach (var S in Servers) foreach (var S in Servers)
@ -388,6 +393,9 @@ namespace IW4MAdmin.Application
public void Stop() public void Stop()
{ {
Running = false; Running = false;
// trigger the event processing loop to end
SetHasEvent();
} }
public ILogger GetLogger() public ILogger GetLogger()
@ -417,5 +425,11 @@ namespace IW4MAdmin.Application
public IDictionary<int, Player> GetPrivilegedClients() => PrivilegedClients; public IDictionary<int, Player> GetPrivilegedClients() => PrivilegedClients;
public IEventApi GetEventApi() => Api; public IEventApi GetEventApi() => Api;
public bool ShutdownRequested() => !Running; public bool ShutdownRequested() => !Running;
public IEventHandler GetEventHandler() => Handler;
public void SetHasEvent()
{
OnEvent.Set();
}
} }
} }

View File

@ -85,6 +85,8 @@ namespace Application.RconParsers
if (Regex.Matches(responseLine, @" *^\d+", RegexOptions.IgnoreCase).Count > 0) if (Regex.Matches(responseLine, @" *^\d+", RegexOptions.IgnoreCase).Count > 0)
{ {
String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (playerInfo.Length < 4)
continue;
int cID = -1; int cID = -1;
int Ping = -1; int Ping = -1;
Int32.TryParse(playerInfo[2], out Ping); Int32.TryParse(playerInfo[2], out Ping);

View File

@ -18,6 +18,7 @@ using SharedLibraryCore.Exceptions;
using Application.Misc; using Application.Misc;
using Application.RconParsers; using Application.RconParsers;
using IW4MAdmin.Application.EventParsers; using IW4MAdmin.Application.EventParsers;
using IW4MAdmin.Application.IO;
namespace IW4MAdmin namespace IW4MAdmin
{ {
@ -25,6 +26,8 @@ namespace IW4MAdmin
{ {
private CancellationToken cts; private CancellationToken cts;
private static Dictionary<string, string> loc = Utilities.CurrentLocalization.LocalizationSet; private static Dictionary<string, string> loc = Utilities.CurrentLocalization.LocalizationSet;
private GameLogEvent LogEvent;
public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg) { } public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg) { }
@ -180,7 +183,8 @@ namespace IW4MAdmin
Logger.WriteInfo($"Client {player} connecting..."); Logger.WriteInfo($"Client {player} connecting...");
await ExecuteEvent(new GameEvent(GameEvent.EventType.Connect, "", player, null, this));
Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Connect, "", player, null, this));
if (!Manager.GetApplicationSettings().Configuration().EnableClientVPNs && if (!Manager.GetApplicationSettings().Configuration().EnableClientVPNs &&
@ -208,7 +212,7 @@ namespace IW4MAdmin
Player Leaving = Players[cNum]; Player Leaving = Players[cNum];
Logger.WriteInfo($"Client {Leaving} disconnecting..."); Logger.WriteInfo($"Client {Leaving} disconnecting...");
await ExecuteEvent(new GameEvent(GameEvent.EventType.Disconnect, "", Leaving, null, this)); Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Disconnect, "", Leaving, null, this));
Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds; Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds;
Leaving.LastConnection = DateTime.UtcNow; Leaving.LastConnection = DateTime.UtcNow;
@ -255,16 +259,8 @@ namespace IW4MAdmin
if (C.RequiresTarget || Args.Length > 0) if (C.RequiresTarget || Args.Length > 0)
{ {
int cNum = -1; if (!Int32.TryParse(Args[0], out int cNum))
try cNum = -1;
{
cNum = Convert.ToInt32(Args[0]);
}
catch (FormatException)
{
}
if (Args[0][0] == '@') // user specifying target by database ID if (Args[0][0] == '@') // user specifying target by database ID
{ {
@ -359,24 +355,27 @@ namespace IW4MAdmin
public override async Task ExecuteEvent(GameEvent E) public override async Task ExecuteEvent(GameEvent E)
{ {
//if (Throttled) bool canExecuteCommand = true;
// return;
await ProcessEvent(E); await ProcessEvent(E);
Manager.GetEventApi().OnServerEvent(this, E); Manager.GetEventApi().OnServerEvent(this, E);
foreach (IPlugin P in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) foreach (IPlugin P in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
{ {
#if !DEBUG
try try
#endif
{ {
if (cts.IsCancellationRequested) if (cts.IsCancellationRequested)
break; break;
await P.OnEventAsync(E, this); await P.OnEventAsync(E, this);
} }
#if !DEBUG
// this happens if a plugin (login) wants to stop commands from executing
catch (AuthorizationException e)
{
await E.Origin.Tell($"{loc["COMMAND_NOTAUTHORIZED"]} - {e.Message}");
canExecuteCommand = false;
}
catch (Exception Except) catch (Exception Except)
{ {
Logger.WriteError(String.Format("The plugin \"{0}\" generated an error. ( see log )", P.Name)); Logger.WriteError(String.Format("The plugin \"{0}\" generated an error. ( see log )", P.Name));
@ -389,10 +388,168 @@ namespace IW4MAdmin
} }
continue; continue;
} }
#endif
} }
// hack: this prevents commands from getting executing that 'shouldn't' be
if (E.Type == GameEvent.EventType.Command &&
E.Extra != null &&
(canExecuteCommand ||
E.Origin?.Level == Player.Permission.Console))
{
await (((Command)E.Extra).ExecuteAsync(E));
}
} }
/// <summary>
/// Perform the server specific tasks when an event occurs
/// </summary>
/// <param name="E"></param>
/// <returns></returns>
override protected async Task ProcessEvent(GameEvent E)
{
if (E.Type == GameEvent.EventType.Connect)
{
// special case for IW5 when connect is from the log
if (E.Extra != null)
{
var logClient = (Player)E.Extra;
var client = (await this.GetStatusAsync())
.Single(c => c.ClientNumber == logClient.ClientNumber &&
c.Name == logClient.Name);
client.NetworkId = logClient.NetworkId;
await AddPlayer(client);
// hack: to prevent plugins from registering it as a real connect
E.Type = GameEvent.EventType.Unknown;
}
else
{
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin.Name,
Message = "CONNECTED",
Time = DateTime.UtcNow
});
if (E.Origin.Level > Player.Permission.Moderator)
await E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count));
}
}
else if (E.Type == GameEvent.EventType.Disconnect)
{
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin.Name,
Message = "DISCONNECTED",
Time = DateTime.UtcNow
});
}
else if (E.Type == GameEvent.EventType.Script)
{
Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Kill, E.Data, E.Origin, E.Target, this));
}
if (E.Type == GameEvent.EventType.Say && E.Data.Length >= 2)
{
if (E.Data.Substring(0, 1) == "!" ||
E.Data.Substring(0, 1) == "@" ||
E.Origin.Level == Player.Permission.Console)
{
Command C = null;
try
{
C = await ValidateCommand(E);
}
catch (CommandException e)
{
Logger.WriteInfo(e.Message);
}
if (C != null)
{
if (C.RequiresTarget && E.Target == null)
{
Logger.WriteWarning("Requested event (command) requiring target does not have a target!");
}
Manager.GetEventHandler().AddEvent(new GameEvent()
{
Type = GameEvent.EventType.Command,
Data = E.Data,
Origin = E.Origin,
Target = E.Target,
Owner = this,
Extra = C,
Remote = E.Remote,
Message = E.Message
});
}
}
else // Not a command
{
E.Data = E.Data.StripColors();
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin.Name,
Message = E.Data,
Time = DateTime.UtcNow
});
}
}
if (E.Type == GameEvent.EventType.MapChange)
{
Logger.WriteInfo($"New map loaded - {ClientNum} active players");
// iw4 doesn't log the game info
if (E.Extra == null)
{
var dict = await this.GetInfoAsync();
Gametype = dict["gametype"].StripColors();
Hostname = dict["hostname"].StripColors();
string mapname = dict["mapname"].StripColors();
CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname };
}
else
{
var dict = (Dictionary<string, string>)E.Extra;
Gametype = dict["g_gametype"].StripColors();
Hostname = dict["sv_hostname"].StripColors();
string mapname = dict["mapname"].StripColors();
CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname };
}
}
if (E.Type == GameEvent.EventType.MapEnd)
{
Logger.WriteInfo("Game ending...");
}
//todo: move
while (ChatHistory.Count > Math.Ceiling((double)ClientNum / 2))
ChatHistory.RemoveAt(0);
// the last client hasn't fully disconnected yet
// so there will still be at least 1 client left
if (ClientNum < 2)
ChatHistory.Clear();
}
async Task<int> PollPlayersAsync() async Task<int> PollPlayersAsync()
{ {
var now = DateTime.Now; var now = DateTime.Now;
@ -429,30 +586,16 @@ namespace IW4MAdmin
return CurrentPlayers.Count; return CurrentPlayers.Count;
} }
long l_size = -1;
String[] lines = new String[8];
String[] oldLines = new String[8];
DateTime start = DateTime.Now; DateTime start = DateTime.Now;
DateTime playerCountStart = DateTime.Now; DateTime playerCountStart = DateTime.Now;
DateTime lastCount = DateTime.Now; DateTime lastCount = DateTime.Now;
DateTime tickTime = DateTime.Now; DateTime tickTime = DateTime.Now;
bool firstRun = true;
int count = 0;
override public async Task<bool> ProcessUpdatesAsync(CancellationToken cts) override public async Task<bool> ProcessUpdatesAsync(CancellationToken cts)
{ {
this.cts = cts; this.cts = cts;
//#if DEBUG == false
try try
//#endif
{ {
// first start
if (firstRun)
{
await ExecuteEvent(new GameEvent(GameEvent.EventType.Start, "Server started", null, null, this));
firstRun = false;
}
if ((DateTime.Now - LastPoll).TotalMinutes < 2 && ConnectionErrors >= 1) if ((DateTime.Now - LastPoll).TotalMinutes < 2 && ConnectionErrors >= 1)
return true; return true;
@ -517,44 +660,6 @@ namespace IW4MAdmin
start = DateTime.Now; start = DateTime.Now;
} }
if (LogFile == null)
return true;
if (l_size != LogFile.Length())
{
lines = l_size != -1 ? await LogFile.Tail(12) : lines;
if (lines != oldLines)
{
l_size = LogFile.Length();
int end = (lines.Length == oldLines.Length) ? lines.Length - 1 : Math.Abs((lines.Length - oldLines.Length)) - 1;
for (count = 0; count < lines.Length; count++)
{
if (lines.Length < 1 && oldLines.Length < 1)
continue;
if (lines[count] == oldLines[oldLines.Length - 1])
continue;
if (lines[count].Length < 10) // it's not a needed line
continue;
else
{
GameEvent event_ = EventParser.GetEvent(this, lines[count]);
if (event_ != null)
{
if (event_.Origin == null)
continue;
await ExecuteEvent(event_);
}
}
}
}
}
oldLines = lines;
l_size = LogFile.Length();
if (Manager.ShutdownRequested()) if (Manager.ShutdownRequested())
{ {
foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
@ -565,7 +670,6 @@ namespace IW4MAdmin
} }
return true; return true;
} }
//#if !DEBUG
catch (NetworkException) catch (NetworkException)
{ {
Logger.WriteError($"{loc["SERVER_ERROR_COMMUNICATION"]} {IP}:{Port}"); Logger.WriteError($"{loc["SERVER_ERROR_COMMUNICATION"]} {IP}:{Port}");
@ -575,7 +679,6 @@ namespace IW4MAdmin
catch (InvalidOperationException) catch (InvalidOperationException)
{ {
Logger.WriteWarning("Event could not parsed properly"); Logger.WriteWarning("Event could not parsed properly");
Logger.WriteDebug($"Log Line: {lines[count]}");
return false; return false;
} }
@ -586,7 +689,6 @@ namespace IW4MAdmin
Logger.WriteDebug("Error Trace: " + E.StackTrace); Logger.WriteDebug("Error Trace: " + E.StackTrace);
return false; return false;
} }
//#endif
} }
public async Task Initialize() public async Task Initialize()
@ -657,7 +759,7 @@ namespace IW4MAdmin
CustomCallback = await ScriptLoaded(); CustomCallback = await ScriptLoaded();
string mainPath = EventParser.GetGameDir(); string mainPath = EventParser.GetGameDir();
#if DEBUG #if DEBUG
basepath.Value = @"\\192.168.88.253\Call of Duty 4"; basepath.Value = @"\\192.168.88.253\mw2\";
#endif #endif
string logPath; string logPath;
if (GameName == Game.IW5) if (GameName == Game.IW5)
@ -671,6 +773,7 @@ namespace IW4MAdmin
$"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{game.Value.Replace('/', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{logfile.Value}"; $"{basepath.Value.Replace('\\', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{game.Value.Replace('/', Path.DirectorySeparatorChar)}{Path.DirectorySeparatorChar}{logfile.Value}";
} }
// hopefully fix wine drive name mangling // hopefully fix wine drive name mangling
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
@ -686,183 +789,18 @@ namespace IW4MAdmin
} }
else else
{ {
LogFile = new IFile(logPath); // LogFile = new IFile(logPath);
}
}
LogEvent = new GameLogEvent(this, logPath, logfile.Value);
Logger.WriteInfo($"Log file is {logPath}"); Logger.WriteInfo($"Log file is {logPath}");
#if DEBUG #if DEBUG
// LogFile = new RemoteFile("https://raidmax.org/IW4MAdmin/getlog.php"); // LogFile = new RemoteFile("https://raidmax.org/IW4MAdmin/getlog.php");
#else #else
await Broadcast(loc["BROADCAST_ONLINE"]); await Broadcast(loc["BROADCAST_ONLINE"]);
#endif #endif
} }
//Process any server event
override protected async Task ProcessEvent(GameEvent E)
{
if (E.Type == GameEvent.EventType.Connect)
{
// special case for IW5 when connect is from the log
if (E.Extra != null)
{
var logClient = (Player)E.Extra;
var client = (await this.GetStatusAsync())
.Single(c => c.ClientNumber == logClient.ClientNumber &&
c.Name == logClient.Name);
client.NetworkId = logClient.NetworkId;
await AddPlayer(client);
// hack: to prevent plugins from registering it as a real connect
E.Type = GameEvent.EventType.Unknown;
}
else
{
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin.Name,
Message = "CONNECTED",
Time = DateTime.UtcNow
});
if (E.Origin.Level > Player.Permission.Moderator)
await E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count));
}
}
else if (E.Type == GameEvent.EventType.Disconnect)
{
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin.Name,
Message = "DISCONNECTED",
Time = DateTime.UtcNow
});
}
else if (E.Type == GameEvent.EventType.Script)
{
await ExecuteEvent(new GameEvent(GameEvent.EventType.Kill, E.Data, E.Origin, E.Target, this));
}
if (E.Type == GameEvent.EventType.Say && E.Data.Length >= 2)
{
if (E.Data.Substring(0, 1) == "!" || E.Data.Substring(0, 1) == "@" || E.Origin.Level == Player.Permission.Console)
{
Command C = null;
try
{
C = await ValidateCommand(E);
}
catch (CommandException e)
{
Logger.WriteInfo(e.Message);
}
if (C != null)
{
if (C.RequiresTarget && E.Target == null)
{
Logger.WriteWarning("Requested event (command) requiring target does not have a target!");
}
try
{
if (!E.Remote && E.Origin.Level != Player.Permission.Console)
{
await ExecuteEvent(new GameEvent()
{
Type = GameEvent.EventType.Command,
Data = string.Empty,
Origin = E.Origin,
Target = E.Target,
Owner = this,
Extra = C,
Remote = E.Remote
});
}
await C.ExecuteAsync(E);
}
catch (AuthorizationException e)
{
await E.Origin.Tell($"{loc["COMMAND_NOTAUTHORIZED"]} - {e.Message}");
}
catch (Exception Except)
{
Logger.WriteError(String.Format($"\"{0}\" {loc["SERVER_ERROR_COMMAND_LOG"]}", C.Name));
Logger.WriteDebug(String.Format("Error Message: {0}", Except.Message));
Logger.WriteDebug(String.Format("Error Trace: {0}", Except.StackTrace));
await E.Origin.Tell($"^1{loc["SERVER_ERROR_COMMAND_INGAME"]}");
#if DEBUG
await E.Origin.Tell(Except.Message);
#endif
}
}
}
else // Not a command
{
E.Data = E.Data.StripColors();
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin.Name,
Message = E.Data,
Time = DateTime.UtcNow
});
}
}
if (E.Type == GameEvent.EventType.MapChange)
{
Logger.WriteInfo($"New map loaded - {ClientNum} active players");
// iw4 doesn't log the game info
if (E.Extra == null)
{
var dict = await this.GetInfoAsync();
Gametype = dict["gametype"].StripColors();
Hostname = dict["hostname"].StripColors();
string mapname = dict["mapname"].StripColors();
CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname };
}
else
{
var dict = (Dictionary<string, string>)E.Extra;
Gametype = dict["g_gametype"].StripColors();
Hostname = dict["sv_hostname"].StripColors();
string mapname = dict["mapname"].StripColors();
CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname };
}
}
if (E.Type == GameEvent.EventType.MapEnd)
{
Logger.WriteInfo("Game ending...");
}
//todo: move
while (ChatHistory.Count > Math.Ceiling((double)ClientNum / 2))
ChatHistory.RemoveAt(0);
// the last client hasn't fully disconnected yet
// so there will still be at least 1 client left
if (ClientNum < 2)
ChatHistory.Clear();
}
public override async Task Warn(String Reason, Player Target, Player Origin) public override async Task Warn(String Reason, Player Target, Player Origin)
{ {
// ensure player gets warned if command not performed on them in game // ensure player gets warned if command not performed on them in game

View File

@ -70,12 +70,8 @@ namespace IW4MAdmin.Plugins.Login
Config = cfg.Configuration(); Config = cfg.Configuration();
} }
public Task OnTickAsync(Server S) => Utilities.CompletedTask; public Task OnTickAsync(Server S) => Task.CompletedTask;
public Task OnUnloadAsync() public Task OnUnloadAsync() => Task.CompletedTask;
{
AuthorizedClients.Clear();
return Utilities.CompletedTask;
}
} }
} }

View File

@ -20,7 +20,6 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
BaseConfigurationHandler<Configuration> Settings; BaseConfigurationHandler<Configuration> Settings;
ConcurrentDictionary<int, Tracking> ProfanityCounts; ConcurrentDictionary<int, Tracking> ProfanityCounts;
IManager Manager; IManager Manager;
Task CompletedTask = Task.FromResult(false);
public async Task OnEventAsync(GameEvent E, Server S) public async Task OnEventAsync(GameEvent E, Server S)
{ {
@ -87,8 +86,8 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
Manager = manager; Manager = manager;
} }
public Task OnTickAsync(Server S) => CompletedTask; public Task OnTickAsync(Server S) => Task.CompletedTask;
public Task OnUnloadAsync() => CompletedTask; public Task OnUnloadAsync() => Task.CompletedTask;
} }
} }

View File

@ -187,7 +187,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// get individual client's stats // get individual client's stats
var clientStats = playerStats[pl.ClientId]; var clientStats = playerStats[pl.ClientId];
// sync their score // sync their score
clientStats.SessionScore = pl.Score; clientStats.SessionScore += (pl.Score - clientStats.LastScore);
// remove the client from the stats dictionary as they're leaving // remove the client from the stats dictionary as they're leaving
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue); playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue);
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2); detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2);
@ -213,11 +213,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var statsSvc = ContextThreads[serverId]; var statsSvc = ContextThreads[serverId];
Vector3 vDeathOrigin = null; Vector3 vDeathOrigin = null;
Vector3 vKillOrigin = null; Vector3 vKillOrigin = null;
Vector3 vViewAngles = null;
try try
{ {
vDeathOrigin = Vector3.Parse(deathOrigin); vDeathOrigin = Vector3.Parse(deathOrigin);
vKillOrigin = Vector3.Parse(killOrigin); vKillOrigin = Vector3.Parse(killOrigin);
vViewAngles = Vector3.Parse(viewAngles).FixIW4Angles();
} }
catch (FormatException) catch (FormatException)
@ -241,7 +243,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Damage = Int32.Parse(damage), Damage = Int32.Parse(damage),
HitLoc = ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)), HitLoc = ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)),
Weapon = ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName)), Weapon = ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName)),
ViewAngles = Vector3.Parse(viewAngles).FixIW4Angles(), ViewAngles = vViewAngles,
TimeOffset = Int64.Parse(offset), TimeOffset = Int64.Parse(offset),
When = DateTime.UtcNow, When = DateTime.UtcNow,
IsKillstreakKill = isKillstreakKill[0] != '0', IsKillstreakKill = isKillstreakKill[0] != '0',
@ -338,11 +340,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// update the total stats // update the total stats
Servers[serverId].ServerStatistics.TotalKills += 1; Servers[serverId].ServerStatistics.TotalKills += 1;
attackerStats.SessionScore = attacker.Score; attackerStats.SessionScore += (attacker.Score - attackerStats.LastScore);
victimStats.SessionScore = victim.Score; victimStats.SessionScore += (victim.Score - victimStats.LastScore);
// calculate for the clients // calculate for the clients
CalculateKill(attackerStats, victimStats); CalculateKill(attackerStats, victimStats);
// this should fix the negative SPM
attackerStats.LastScore = attacker.Score;
victimStats.LastScore = victim.Score;
// show encouragement/discouragement // show encouragement/discouragement
string streakMessage = (attackerStats.ClientId != victimStats.ClientId) ? string streakMessage = (attackerStats.ClientId != victimStats.ClientId) ?
@ -457,7 +462,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
} }
clientStats.LastStatCalculation = DateTime.UtcNow; clientStats.LastStatCalculation = DateTime.UtcNow;
clientStats.LastScore = clientStats.SessionScore; //clientStats.LastScore = clientStats.SessionScore;
return clientStats; return clientStats;
} }

View File

@ -253,7 +253,7 @@ namespace IW4MAdmin.Plugins.Stats
Manager = new StatManager(manager); Manager = new StatManager(manager);
} }
public Task OnTickAsync(Server S) => Utilities.CompletedTask; public Task OnTickAsync(Server S) => Task.CompletedTask;
public async Task OnUnloadAsync() public async Task OnUnloadAsync()
{ {

View File

@ -51,8 +51,9 @@ namespace IW4MAdmin.Plugins
public Task OnLoadAsync(IManager manager) => Task.CompletedTask; public Task OnLoadAsync(IManager manager) => Task.CompletedTask;
public async Task OnTickAsync(Server S) public Task OnTickAsync(Server S)
{ {
return Task.CompletedTask;
/* /*
if ((DateTime.Now - Interval).TotalSeconds > 1) if ((DateTime.Now - Interval).TotalSeconds > 1)
{ {

View File

@ -72,9 +72,9 @@ namespace IW4MAdmin.Plugins.Welcome
} }
} }
public Task OnUnloadAsync() => Utilities.CompletedTask; public Task OnUnloadAsync() => Task.CompletedTask;
public Task OnTickAsync(Server S) => Utilities.CompletedTask; public Task OnTickAsync(Server S) => Task.CompletedTask;
public async Task OnEventAsync(GameEvent E, Server S) public async Task OnEventAsync(GameEvent E, Server S)
{ {

View File

@ -32,7 +32,7 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
{ {
if ((await (E.Owner.Manager.GetClientService() as Services.ClientService).GetOwners()).Count == 0) if ((await (E.Owner.Manager.GetClientService() as ClientService).GetOwners()).Count == 0)
{ {
E.Origin.Level = Player.Permission.Owner; E.Origin.Level = Player.Permission.Owner;
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationSet["COMMANDS_OWNER_SUCCESS"]); await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationSet["COMMANDS_OWNER_SUCCESS"]);
@ -94,7 +94,7 @@ namespace SharedLibraryCore.Commands
public class CKick : Command public class CKick : Command
{ {
public CKick() : public CKick() :
base("kick", Utilities.CurrentLocalization.LocalizationSet["COMMANDS_KICK_DESC"], "k", Player.Permission.Trusted, true, new CommandArgument[] base("kick", Utilities.CurrentLocalization.LocalizationSet["COMMANDS_KICK_DESC"], "k", Player.Permission.Moderator, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -113,7 +113,7 @@ namespace SharedLibraryCore.Commands
{ {
if (E.Origin.Level > E.Target.Level) if (E.Origin.Level > E.Target.Level)
{ {
await E.Owner.ExecuteEvent(new GameEvent(GameEvent.EventType.Kick, E.Data, E.Origin, E.Target, E.Owner)); E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Kick, E.Data, E.Origin, E.Target, E.Owner));
await E.Target.Kick(E.Data, E.Origin); await E.Target.Kick(E.Data, E.Origin);
await E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationSet["COMMANDS_KICK_SUCCESS"]}"); await E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationSet["COMMANDS_KICK_SUCCESS"]}");
} }
@ -144,7 +144,7 @@ namespace SharedLibraryCore.Commands
public class CTempBan : Command public class CTempBan : Command
{ {
public CTempBan() : public CTempBan() :
base("tempban", Utilities.CurrentLocalization.LocalizationSet["COMMANDS_TEMPBAN_DESC"], "tb", Player.Permission.Moderator, true, new CommandArgument[] base("tempban", Utilities.CurrentLocalization.LocalizationSet["COMMANDS_TEMPBAN_DESC"], "tb", Player.Permission.Administrator, true, new CommandArgument[]
{ {
new CommandArgument() new CommandArgument()
{ {
@ -716,7 +716,7 @@ namespace SharedLibraryCore.Commands
}; };
await E.Owner.Manager.GetPenaltyService().Create(newPenalty); await E.Owner.Manager.GetPenaltyService().Create(newPenalty);
await E.Owner.ExecuteEvent(new GameEvent(GameEvent.EventType.Flag, E.Data, E.Origin, E.Target, E.Owner)); E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Flag, E.Data, E.Origin, E.Target, E.Owner));
await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationSet["COMMANDS_FLAG_SUCCESS"]} ^5{E.Target.Name}"); await E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationSet["COMMANDS_FLAG_SUCCESS"]} ^5{E.Target.Name}");
} }
@ -770,7 +770,7 @@ namespace SharedLibraryCore.Commands
E.Owner.Reports.Add(new Report(E.Target, E.Origin, E.Data)); E.Owner.Reports.Add(new Report(E.Target, E.Origin, E.Data));
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationSet["COMMANDS_REPORT_SUCCESS"]); await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationSet["COMMANDS_REPORT_SUCCESS"]);
await E.Owner.ExecuteEvent(new GameEvent(GameEvent.EventType.Report, E.Data, E.Origin, E.Target, E.Owner)); E.Owner.Manager.GetEventHandler().AddEvent(new GameEvent(GameEvent.EventType.Report, E.Data, E.Origin, E.Target, E.Owner));
await E.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", E.Origin.Name, E.Target.Name, E.Data)); await E.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", E.Origin.Name, E.Target.Name, E.Data));
} }
} }
@ -1023,7 +1023,8 @@ namespace SharedLibraryCore.Commands
E.Origin.PasswordSalt = hashedPassword[1]; E.Origin.PasswordSalt = hashedPassword[1];
// update the password for the client in privileged // update the password for the client in privileged
E.Owner.Manager.GetPrivilegedClients()[E.Origin.ClientId] = E.Origin; E.Owner.Manager.GetPrivilegedClients()[E.Origin.ClientId].Password = hashedPassword[0];
E.Owner.Manager.GetPrivilegedClients()[E.Origin.ClientId].PasswordSalt = hashedPassword[1];
await E.Owner.Manager.GetClientService().Update(E.Origin); await E.Owner.Manager.GetClientService().Update(E.Origin);
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationSet["COMMANDS_PASSWORD_SUCCESS"]); await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationSet["COMMANDS_PASSWORD_SUCCESS"]);

View File

@ -2,7 +2,7 @@
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using SharedLibraryCore.Objects; using SharedLibraryCore.Objects;
namespace SharedLibraryCore namespace SharedLibraryCore
@ -47,10 +47,13 @@ namespace SharedLibraryCore
Origin = O; Origin = O;
Target = T; Target = T;
Owner = S; Owner = S;
OnProcessed = new ManualResetEventSlim();
} }
public GameEvent() { } public GameEvent()
{
OnProcessed = new ManualResetEventSlim();
}
public EventType Type; public EventType Type;
public string Data; // Data is usually the message sent by player public string Data; // Data is usually the message sent by player
@ -60,5 +63,6 @@ namespace SharedLibraryCore
public Server Owner; public Server Owner;
public Boolean Remote = false; public Boolean Remote = false;
public object Extra { get; set; } public object Extra { get; set; }
public ManualResetEventSlim OnProcessed { get; private set; }
} }
} }

View File

@ -5,6 +5,7 @@ using System.IO;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Linq;
namespace SharedLibraryCore namespace SharedLibraryCore
{ {
@ -27,7 +28,12 @@ namespace SharedLibraryCore
public override long Length() public override long Length()
{ {
Retrieve(); Retrieve();
return FileCache[0].Length; return FileCache.Sum(l => l.Length);
}
public override Task<string[]> Tail(int lineCount)
{
return Task.FromResult(FileCache);
} }
} }

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// This class handle games events (from log, manual events, etc)
/// </summary>
public interface IEventHandler
{
/// <summary>
/// Add a game event event to the queue to be processed
/// </summary>
/// <param name="gameEvent">Game event</param>
void AddEvent(GameEvent gameEvent);
/// <summary>
/// Get the next event to be processed
/// </summary>
/// <returns>Game event that needs to be processed</returns>
GameEvent GetNextEvent();
/// <summary>
/// If an event has output. Like executing a command wait until it's available
/// </summary>
/// <returns>List of output strings</returns>
string[] GetEventOutput();
}
}

View File

@ -12,6 +12,7 @@ namespace SharedLibraryCore.Interfaces
/// <param name="server">server the event occurred on</param> /// <param name="server">server the event occurred on</param>
/// <param name="logLine">single log line string</param> /// <param name="logLine">single log line string</param>
/// <returns></returns> /// <returns></returns>
/// todo: make this integrate without needing the server
GameEvent GetEvent(Server server, string logLine); GameEvent GetEvent(Server server, string logLine);
/// <summary> /// <summary>
/// Get game specific folder prefix for log files /// Get game specific folder prefix for log files

View File

@ -23,6 +23,15 @@ namespace SharedLibraryCore.Interfaces
PenaltyService GetPenaltyService(); PenaltyService GetPenaltyService();
IDictionary<int, Player> GetPrivilegedClients(); IDictionary<int, Player> GetPrivilegedClients();
IEventApi GetEventApi(); IEventApi GetEventApi();
/// <summary>
/// Get the event handlers
/// </summary>
/// <returns>EventHandler for the manager</returns>
IEventHandler GetEventHandler();
/// <summary>
/// Signal to the manager that event(s) needs to be processed
/// </summary>
void SetHasEvent();
bool ShutdownRequested(); bool ShutdownRequested();
} }
} }

View File

@ -41,6 +41,7 @@ namespace SharedLibraryCore
PlayerHistory = new Queue<PlayerHistory>(); PlayerHistory = new Queue<PlayerHistory>();
ChatHistory = new List<ChatInfo>(); ChatHistory = new List<ChatInfo>();
NextMessage = 0; NextMessage = 0;
OnEvent = new ManualResetEventSlim();
CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName; CustomSayEnabled = Manager.GetApplicationSettings().Configuration().EnableCustomSayName;
CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName; CustomSayName = Manager.GetApplicationSettings().Configuration().CustomSayName;
InitializeTokens(); InitializeTokens();
@ -132,7 +133,7 @@ namespace SharedLibraryCore
await this.ExecuteCommandAsync(formattedMessage); await this.ExecuteCommandAsync(formattedMessage);
#else #else
Logger.WriteVerbose(Message.StripColors()); Logger.WriteVerbose(Message.StripColors());
await Utilities.CompletedTask; await Task.CompletedTask;
#endif #endif
} }
@ -150,7 +151,7 @@ namespace SharedLibraryCore
await this.ExecuteCommandAsync(formattedMessage); await this.ExecuteCommandAsync(formattedMessage);
#else #else
Logger.WriteVerbose($"{Target.ClientNumber}->{Message.StripColors()}"); Logger.WriteVerbose($"{Target.ClientNumber}->{Message.StripColors()}");
await Utilities.CompletedTask; await Task.CompletedTask;
#endif #endif
if (Target.Level == Player.Permission.Console) if (Target.Level == Player.Permission.Console)
@ -308,6 +309,7 @@ namespace SharedLibraryCore
public RCon.Connection RemoteConnection { get; protected set; } public RCon.Connection RemoteConnection { get; protected set; }
public IRConParser RconParser { get; protected set; } public IRConParser RconParser { get; protected set; }
public IEventParser EventParser { get; set; } public IEventParser EventParser { get; set; }
public ManualResetEventSlim OnEvent { get; private set; }
// Internal // Internal
protected string IP; protected string IP;

View File

@ -16,7 +16,6 @@ namespace SharedLibraryCore
public static class Utilities public static class Utilities
{ {
public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar; public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
public static readonly Task CompletedTask = Task.FromResult(false);
public static Encoding EncodingType; public static Encoding EncodingType;
public static Localization.Layout CurrentLocalization; public static Localization.Layout CurrentLocalization;

View File

@ -16,24 +16,37 @@ namespace WebfrontCore.Controllers
protected IManager Manager; protected IManager Manager;
protected readonly DatabaseContext Context; protected readonly DatabaseContext Context;
protected bool Authorized { get; private set; } protected bool Authorized { get; private set; }
protected EFClient User { get; private set; } protected EFClient Client { get; private set; }
private static byte[] LocalHost = { 127, 0, 0, 1 };
private static string DiscordLink;
public BaseController()
{
var Manager = Program.Manager;
if (Manager.GetApplicationSettings().Configuration().EnableDiscordLink)
{
string inviteLink = Manager.GetApplicationSettings().Configuration().DiscordInviteCode;
if (inviteLink != null)
DiscordLink = inviteLink.Contains("https") ? inviteLink : $"https://discordapp.com/invite/{inviteLink}";
else
DiscordLink = "";
}
}
public override void OnActionExecuting(ActionExecutingContext context) public override void OnActionExecuting(ActionExecutingContext context)
{ {
Manager = Program.Manager; Client = Client ?? new EFClient()
User = User ?? new EFClient()
{ {
ClientId = -1 ClientId = -1
}; };
if (HttpContext.Connection.RemoteIpAddress.ToString() != "127.0.0.1") if (!HttpContext.Connection.RemoteIpAddress.GetAddressBytes().SequenceEqual(LocalHost))
{ {
try try
{ {
User.ClientId = Convert.ToInt32(base.User.Claims.First(c => c.Type == ClaimTypes.Sid).Value); Client.ClientId = Convert.ToInt32(base.User.Claims.First(c => c.Type == ClaimTypes.Sid).Value);
User.Level = (Player.Permission)Enum.Parse(typeof(Player.Permission), base.User.Claims.First(c => c.Type == ClaimTypes.Role).Value); Client.Level = (Player.Permission)Enum.Parse(typeof(Player.Permission), User.Claims.First(c => c.Type == ClaimTypes.Role).Value);
User.CurrentAlias = new EFAlias() { Name = base.User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value }; Client.CurrentAlias = new EFAlias() { Name = User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value };
} }
catch (InvalidOperationException) catch (InvalidOperationException)
@ -44,24 +57,17 @@ namespace WebfrontCore.Controllers
else else
{ {
User.ClientId = 1; Client.ClientId = 1;
User.Level = Player.Permission.Console; Client.Level = Player.Permission.Console;
User.CurrentAlias = new EFAlias() { Name = "IW4MAdmin" }; Client.CurrentAlias = new EFAlias() { Name = "IW4MAdmin" };
} }
Authorized = User.ClientId >= 0; Authorized = Client.ClientId >= 0;
ViewBag.Authorized = Authorized; ViewBag.Authorized = Authorized;
ViewBag.Url = Startup.Configuration["Web:Address"]; ViewBag.Url = Startup.Configuration["Web:Address"];
ViewBag.User = User; ViewBag.User = Client;
ViewBag.DiscordLink = DiscordLink;
if (Manager.GetApplicationSettings().Configuration().EnableDiscordLink)
{
string inviteLink = Manager.GetApplicationSettings().Configuration().DiscordInviteCode;
if (inviteLink != null)
ViewBag.DiscordLink = inviteLink.Contains("https") ? inviteLink : $"https://discordapp.com/invite/{inviteLink}";
else
ViewBag.DiscordLink = "";
}
base.OnActionExecuting(context); base.OnActionExecuting(context);
} }
} }

View File

@ -32,10 +32,10 @@ namespace WebfrontCore.Controllers
var server = Manager.GetServers().First(s => s.GetHashCode() == serverId); var server = Manager.GetServers().First(s => s.GetHashCode() == serverId);
var client = new Player() var client = new Player()
{ {
ClientId = User.ClientId, ClientId = Client.ClientId,
Level = User.Level, Level = Client.Level,
CurrentServer = server, CurrentServer = server,
CurrentAlias = new Alias() { Name = User.Name } CurrentAlias = new Alias() { Name = Client.Name }
}; };
var remoteEvent = new GameEvent() var remoteEvent = new GameEvent()
{ {
@ -46,8 +46,9 @@ namespace WebfrontCore.Controllers
Remote = true Remote = true
}; };
await server.ExecuteEvent(remoteEvent); Manager.GetEventHandler().AddEvent(remoteEvent);
// wait for the event to process
remoteEvent.OnProcessed.Wait();
var response = server.CommandResult.Where(c => c.ClientId == client.ClientId).ToList(); var response = server.CommandResult.Where(c => c.ClientId == client.ClientId).ToList();
// remove the added command response // remove the added command response

View File

@ -33,7 +33,6 @@ a.nav-link {
} }
.server-history, .server-history,
.server-header,
.server-activity, .server-activity,
#mobile_seperator, #mobile_seperator,
.border-bottom { .border-bottom {