moved heartbeat to timer instead of manual task/thread
GameEventHandler uses ConcurrentQueue for events exception handlers for events and log reading added IW4ScriptCommands plugin fixed stats lots of little fixes
This commit is contained in:
parent
2c2c442ba7
commit
bb90a807b7
@ -8,9 +8,13 @@ using SharedLibraryCore;
|
||||
|
||||
namespace IW4MAdmin.Application.API.Master
|
||||
{
|
||||
public class HeartbeatState
|
||||
{
|
||||
public bool Connected { get; set; }
|
||||
}
|
||||
|
||||
public class Heartbeat
|
||||
{
|
||||
|
||||
public static async Task Send(ApplicationManager mgr, bool firstHeartbeat = false)
|
||||
{
|
||||
var api = Endpoint.Get();
|
||||
|
@ -1,6 +1,7 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
@ -8,12 +9,14 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
class GameEventHandler : IEventHandler
|
||||
{
|
||||
private Queue<GameEvent> EventQueue;
|
||||
private ConcurrentQueue<GameEvent> EventQueue;
|
||||
private ConcurrentQueue<GameEvent> StatusSensitiveQueue;
|
||||
private IManager Manager;
|
||||
|
||||
public GameEventHandler(IManager mgr)
|
||||
{
|
||||
EventQueue = new Queue<GameEvent>();
|
||||
EventQueue = new ConcurrentQueue<GameEvent>();
|
||||
StatusSensitiveQueue = new ConcurrentQueue<GameEvent>();
|
||||
Manager = mgr;
|
||||
}
|
||||
|
||||
@ -22,7 +25,21 @@ namespace IW4MAdmin.Application
|
||||
#if DEBUG
|
||||
Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner}");
|
||||
#endif
|
||||
EventQueue.Enqueue(gameEvent);
|
||||
// we need this to keep accurate track of the score
|
||||
if (gameEvent.Type == GameEvent.EventType.Script ||
|
||||
gameEvent.Type == GameEvent.EventType.Kill)
|
||||
{
|
||||
#if DEBUG
|
||||
Manager.GetLogger().WriteDebug($"Added sensitive event to queue");
|
||||
#endif
|
||||
StatusSensitiveQueue.Enqueue(gameEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
EventQueue.Enqueue(gameEvent);
|
||||
}
|
||||
#if DEBUG
|
||||
Manager.GetLogger().WriteDebug($"There are now {EventQueue.Count} events in queue");
|
||||
#endif
|
||||
@ -34,13 +51,43 @@ namespace IW4MAdmin.Application
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public GameEvent GetNextSensitiveEvent()
|
||||
{
|
||||
if (StatusSensitiveQueue.Count > 0)
|
||||
{
|
||||
if (!StatusSensitiveQueue.TryDequeue(out GameEvent newEvent))
|
||||
{
|
||||
Manager.GetLogger().WriteWarning("Could not dequeue time sensitive event for processing");
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return newEvent;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public GameEvent GetNextEvent()
|
||||
{
|
||||
if (EventQueue.Count > 0)
|
||||
{
|
||||
#if DEBUG
|
||||
Manager.GetLogger().WriteDebug("Getting next event to be processed");
|
||||
Manager.GetLogger().WriteDebug("Getting next event to be processed");
|
||||
#endif
|
||||
if (!EventQueue.TryDequeue(out GameEvent newEvent))
|
||||
{
|
||||
Manager.GetLogger().WriteWarning("Could not dequeue event for processing");
|
||||
}
|
||||
|
||||
return EventQueue.Count > 0 ? EventQueue.Dequeue() : null;
|
||||
else
|
||||
{
|
||||
return newEvent;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,41 +11,46 @@ namespace IW4MAdmin.Application.IO
|
||||
{
|
||||
class GameLogEvent
|
||||
{
|
||||
FileSystemWatcher LogPathWatcher;
|
||||
Server Server;
|
||||
long PreviousFileSize;
|
||||
GameLogReader Reader;
|
||||
Timer RefreshInfoTimer;
|
||||
string GameLogFile;
|
||||
|
||||
class EventState
|
||||
{
|
||||
public ILogger Log { get; set; }
|
||||
public string ServerId { get; set; }
|
||||
}
|
||||
|
||||
public GameLogEvent(Server server, string gameLogPath, string gameLogName)
|
||||
{
|
||||
GameLogFile = gameLogPath;
|
||||
Reader = new GameLogReader(gameLogPath, server.EventParser);
|
||||
Server = server;
|
||||
RefreshInfoTimer = new Timer((sender) =>
|
||||
RefreshInfoTimer = new Timer(OnEvent, new EventState()
|
||||
{
|
||||
long newLength = new FileInfo(GameLogFile).Length;
|
||||
UpdateLogEvents(newLength);
|
||||
|
||||
}, null, 0, 100);
|
||||
/*LogPathWatcher = new FileSystemWatcher()
|
||||
{
|
||||
Path = gameLogPath.Replace(gameLogName, ""),
|
||||
Filter = gameLogName,
|
||||
NotifyFilter = (NotifyFilters)383,
|
||||
InternalBufferSize = 4096
|
||||
};
|
||||
|
||||
// LogPathWatcher.Changed += LogPathWatcher_Changed;
|
||||
LogPathWatcher.EnableRaisingEvents = true;*/
|
||||
Log = server.Manager.GetLogger(),
|
||||
ServerId = server.ToString()
|
||||
}, 0, 100);
|
||||
}
|
||||
|
||||
/*
|
||||
~GameLogEvent()
|
||||
private void OnEvent(object state)
|
||||
{
|
||||
LogPathWatcher.EnableRaisingEvents = false;
|
||||
}*/
|
||||
long newLength = new FileInfo(GameLogFile).Length;
|
||||
|
||||
try
|
||||
{
|
||||
UpdateLogEvents(newLength);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
((EventState)state).Log.WriteWarning($"Failed to update log event for {((EventState)state).ServerId}");
|
||||
((EventState)state).Log.WriteDebug($"Exception: {e.Message}");
|
||||
((EventState)state).Log.WriteDebug($"StackTrace: {e.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLogEvents(long fileSize)
|
||||
{
|
||||
|
@ -127,7 +127,19 @@ namespace IW4MAdmin.Application
|
||||
|
||||
if (ServerManager.GetApplicationSettings().Configuration().EnableWebFront)
|
||||
{
|
||||
Task.Run(() => WebfrontCore.Program.Init(ServerManager));
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
WebfrontCore.Program.Init(ServerManager);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
ServerManager.Logger.WriteWarning("Webfront had unhandled exception");
|
||||
ServerManager.Logger.WriteDebug(e.Message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ using SharedLibraryCore.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Text;
|
||||
using IW4MAdmin.Application.API.Master;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
@ -43,12 +44,7 @@ namespace IW4MAdmin.Application
|
||||
EventApi Api;
|
||||
GameEventHandler Handler;
|
||||
ManualResetEventSlim OnEvent;
|
||||
Timer StatusUpdateTimer;
|
||||
#if FTP_LOG
|
||||
const int UPDATE_FREQUENCY = 700;
|
||||
#else
|
||||
const int UPDATE_FREQUENCY = 2000;
|
||||
#endif
|
||||
Timer HeartbeatTimer;
|
||||
|
||||
private ApplicationManager()
|
||||
{
|
||||
@ -90,16 +86,62 @@ namespace IW4MAdmin.Application
|
||||
return Instance ?? (Instance = new ApplicationManager());
|
||||
}
|
||||
|
||||
public void UpdateStatus(object state)
|
||||
public async Task UpdateStatus(object state)
|
||||
{
|
||||
var taskList = new List<Task>();
|
||||
|
||||
foreach (var server in Servers)
|
||||
while (Running)
|
||||
{
|
||||
taskList.Add(Task.Run(() => server.ProcessUpdatesAsync(new CancellationToken())));
|
||||
}
|
||||
taskList.Clear();
|
||||
foreach (var server in Servers)
|
||||
{
|
||||
taskList.Add(Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await server.ProcessUpdatesAsync(new CancellationToken());
|
||||
}
|
||||
|
||||
Task.WaitAll(taskList.ToArray());
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.WriteWarning($"Failed to update status for {server}");
|
||||
Logger.WriteDebug($"Exception: {e.Message}");
|
||||
Logger.WriteDebug($"StackTrace: {e.StackTrace}");
|
||||
}
|
||||
}));
|
||||
}
|
||||
#if DEBUG
|
||||
Logger.WriteDebug($"{taskList.Count} servers queued for stats updates");
|
||||
ThreadPool.GetMaxThreads(out int workerThreads, out int n);
|
||||
ThreadPool.GetAvailableThreads(out int availableThreads, out int m);
|
||||
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
|
||||
#endif
|
||||
|
||||
await Task.WhenAll(taskList.ToArray());
|
||||
|
||||
GameEvent sensitiveEvent;
|
||||
while ((sensitiveEvent = Handler.GetNextSensitiveEvent()) != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await sensitiveEvent.Owner.ExecuteEvent(sensitiveEvent);
|
||||
#if DEBUG
|
||||
Logger.WriteDebug($"Processed Sensitive Event {sensitiveEvent.Type}");
|
||||
#endif
|
||||
}
|
||||
|
||||
catch (Exception E)
|
||||
{
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationSet["SERVER_ERROR_EXCEPTION"]} {sensitiveEvent.Owner}");
|
||||
Logger.WriteDebug("Error Message: " + E.Message);
|
||||
Logger.WriteDebug("Error Trace: " + E.StackTrace);
|
||||
sensitiveEvent.OnProcessed.Set();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(5000);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Init()
|
||||
@ -286,41 +328,35 @@ namespace IW4MAdmin.Application
|
||||
}
|
||||
|
||||
await Task.WhenAll(config.Servers.Select(c => Init(c)).ToArray());
|
||||
// start polling servers
|
||||
StatusUpdateTimer = new Timer(UpdateStatus, null, 0, 10000);
|
||||
|
||||
#endregion
|
||||
|
||||
Running = true;
|
||||
}
|
||||
|
||||
private void HeartBeatThread()
|
||||
private void SendHeartbeat(object state)
|
||||
{
|
||||
bool successfulConnection = false;
|
||||
restartConnection:
|
||||
while (!successfulConnection)
|
||||
var heartbeatState = (HeartbeatState)state;
|
||||
|
||||
if (!heartbeatState.Connected)
|
||||
{
|
||||
try
|
||||
{
|
||||
API.Master.Heartbeat.Send(this, true).Wait();
|
||||
successfulConnection = true;
|
||||
Heartbeat.Send(this, true).Wait();
|
||||
heartbeatState.Connected = true;
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
successfulConnection = false;
|
||||
heartbeatState.Connected = false;
|
||||
Logger.WriteWarning($"Could not connect to heartbeat server - {e.Message}");
|
||||
}
|
||||
|
||||
Thread.Sleep(30000);
|
||||
}
|
||||
|
||||
while (Running)
|
||||
else
|
||||
{
|
||||
Logger.WriteDebug("Sending heartbeat...");
|
||||
try
|
||||
{
|
||||
API.Master.Heartbeat.Send(this).Wait();
|
||||
Heartbeat.Send(this).Wait();
|
||||
}
|
||||
catch (System.Net.Http.HttpRequestException e)
|
||||
{
|
||||
@ -336,8 +372,7 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
if (((RestEase.ApiException)ex).StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
successfulConnection = false;
|
||||
goto restartConnection;
|
||||
heartbeatState.Connected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -347,55 +382,65 @@ namespace IW4MAdmin.Application
|
||||
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
||||
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
successfulConnection = false;
|
||||
goto restartConnection;
|
||||
heartbeatState.Connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(30000);
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Task.Run(() => HeartBeatThread());
|
||||
GameEvent newEvent;
|
||||
while (Running)
|
||||
{
|
||||
// wait for new event to be added
|
||||
OnEvent.Wait();
|
||||
|
||||
// todo: sequencially or parallelize?
|
||||
while ((newEvent = Handler.GetNextEvent()) != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
newEvent.Owner.ExecuteEvent(newEvent).Wait();
|
||||
#if DEBUG
|
||||
Logger.WriteDebug("Processed Event");
|
||||
#endif
|
||||
}
|
||||
|
||||
catch (Exception E)
|
||||
{
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationSet["SERVER_ERROR_EXCEPTION"]} {newEvent.Owner}");
|
||||
Logger.WriteDebug("Error Message: " + E.Message);
|
||||
Logger.WriteDebug("Error Trace: " + E.StackTrace);
|
||||
newEvent.OnProcessed.Set();
|
||||
continue;
|
||||
}
|
||||
// tell anyone waiting for the output that we're done
|
||||
newEvent.OnProcessed.Set();
|
||||
}
|
||||
|
||||
// signal that all events have been processed
|
||||
OnEvent.Reset();
|
||||
}
|
||||
#if !DEBUG
|
||||
// start heartbeat
|
||||
HeartbeatTimer = new Timer(SendHeartbeat, new HeartbeatState(), 0, 30000);
|
||||
#endif
|
||||
// start polling servers
|
||||
// StatusUpdateTimer = new Timer(UpdateStatus, null, 0, 5000);
|
||||
Task.Run(() => UpdateStatus(null));
|
||||
GameEvent newEvent;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (Running)
|
||||
{
|
||||
// wait for new event to be added
|
||||
OnEvent.Wait();
|
||||
|
||||
// todo: sequencially or parallelize?
|
||||
while ((newEvent = Handler.GetNextEvent()) != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await newEvent.Owner.ExecuteEvent(newEvent);
|
||||
#if DEBUG
|
||||
Logger.WriteDebug("Processed Event");
|
||||
#endif
|
||||
}
|
||||
|
||||
catch (Exception E)
|
||||
{
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationSet["SERVER_ERROR_EXCEPTION"]} {newEvent.Owner}");
|
||||
Logger.WriteDebug("Error Message: " + E.Message);
|
||||
Logger.WriteDebug("Error Trace: " + E.StackTrace);
|
||||
newEvent.OnProcessed.Set();
|
||||
continue;
|
||||
}
|
||||
// tell anyone waiting for the output that we're done
|
||||
newEvent.OnProcessed.Set();
|
||||
}
|
||||
|
||||
// signal that all events have been processed
|
||||
OnEvent.Reset();
|
||||
}
|
||||
#if !DEBUG
|
||||
HeartbeatTimer.Change(0, Timeout.Infinite);
|
||||
|
||||
foreach (var S in Servers)
|
||||
S.Broadcast(Utilities.CurrentLocalization.LocalizationSet["BROADCAST_OFFLINE"]).Wait();
|
||||
#endif
|
||||
_servers.Clear();
|
||||
_servers.Clear();
|
||||
}).Wait();
|
||||
}
|
||||
|
||||
|
||||
|
@ -91,7 +91,7 @@ namespace Application.RconParsers
|
||||
int Ping = -1;
|
||||
Int32.TryParse(playerInfo[2], out Ping);
|
||||
String cName = Encoding.UTF8.GetString(Encoding.Convert(Utilities.EncodingType, Encoding.UTF8, Utilities.EncodingType.GetBytes(responseLine.Substring(46, 18).StripColors().Trim())));
|
||||
long npID = Regex.Match(responseLine, @"([a-z]|[0-9]){16}", RegexOptions.IgnoreCase).Value.ConvertLong();
|
||||
long npID = Regex.Match(responseLine, @"([a-z]|[0-9]){16}|bot[0-9]+", RegexOptions.IgnoreCase).Value.ConvertLong();
|
||||
int.TryParse(playerInfo[0], out cID);
|
||||
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
|
||||
int cIP = regex.Value.Split(':')[0].ConvertToIP();
|
||||
@ -105,8 +105,9 @@ namespace Application.RconParsers
|
||||
IPAddress = cIP,
|
||||
Ping = Ping,
|
||||
Score = score,
|
||||
IsBot = npID == 0
|
||||
IsBot = cIP == 0
|
||||
};
|
||||
|
||||
StatusPlayers.Add(P);
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ namespace Application.RconParsers
|
||||
regex = Regex.Match(responseLine, @" +(\d+ +){3}");
|
||||
int score = Int32.Parse(regex.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]);
|
||||
|
||||
StatusPlayers.Add(new Player()
|
||||
var p = new Player()
|
||||
{
|
||||
Name = name,
|
||||
NetworkId = networkId,
|
||||
@ -156,7 +156,12 @@ namespace Application.RconParsers
|
||||
Ping = Ping,
|
||||
Score = score,
|
||||
IsBot = networkId == 0
|
||||
});
|
||||
};
|
||||
|
||||
StatusPlayers.Add(p);
|
||||
|
||||
if (p.IsBot)
|
||||
p.NetworkId = -p.ClientNumber;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,8 +177,7 @@ namespace Application.RconParsers
|
||||
int score = 0;
|
||||
// todo: fix this when T6M score is valid ;)
|
||||
//int score = Int32.Parse(playerInfo[1]);
|
||||
|
||||
StatusPlayers.Add(new Player()
|
||||
var p = new Player()
|
||||
{
|
||||
Name = name,
|
||||
NetworkId = networkId,
|
||||
@ -187,7 +186,12 @@ namespace Application.RconParsers
|
||||
Ping = Ping,
|
||||
Score = score,
|
||||
IsBot = networkId == 0
|
||||
});
|
||||
};
|
||||
|
||||
if (p.IsBot)
|
||||
p.NetworkId = -p.ClientNumber;
|
||||
|
||||
StatusPlayers.Add(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ namespace IW4MAdmin
|
||||
{
|
||||
|
||||
if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot) ||
|
||||
polledPlayer.Ping < 1 || polledPlayer.ClientNumber > (MaxClients) ||
|
||||
polledPlayer.Ping < 1 ||
|
||||
polledPlayer.ClientNumber < 0)
|
||||
{
|
||||
//Logger.WriteDebug($"Skipping client not in connected state {P}");
|
||||
@ -415,7 +415,7 @@ namespace IW4MAdmin
|
||||
if (E.Type == GameEvent.EventType.Connect)
|
||||
{
|
||||
// special case for IW5 when connect is from the log
|
||||
if (E.Extra != null)
|
||||
if (E.Extra != null && GameName == Game.IW5)
|
||||
{
|
||||
var logClient = (Player)E.Extra;
|
||||
var client = (await this.GetStatusAsync())
|
||||
@ -573,10 +573,12 @@ namespace IW4MAdmin
|
||||
Logger.WriteInfo($"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms");
|
||||
#endif
|
||||
Throttled = false;
|
||||
for (int i = 0; i < Players.Count; i++)
|
||||
|
||||
var clients = GetPlayersAsList();
|
||||
foreach(var client in clients)
|
||||
{
|
||||
if (CurrentPlayers.Find(p => p.ClientNumber == i) == null && Players[i] != null)
|
||||
await RemovePlayer(i);
|
||||
if (!CurrentPlayers.Select(c => c.NetworkId).Contains(client.NetworkId))
|
||||
await RemovePlayer(client.ClientNumber);
|
||||
}
|
||||
|
||||
for (int i = 0; i < CurrentPlayers.Count; i++)
|
||||
@ -673,15 +675,15 @@ namespace IW4MAdmin
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (NetworkException)
|
||||
{
|
||||
Logger.WriteError($"{loc["SERVER_ERROR_COMMUNICATION"]} {IP}:{Port}");
|
||||
return false;
|
||||
}
|
||||
|
||||
catch (InvalidOperationException)
|
||||
// this one is ok
|
||||
catch (ServerException e)
|
||||
{
|
||||
Logger.WriteWarning("Event could not parsed properly");
|
||||
if (e is NetworkException)
|
||||
{
|
||||
Logger.WriteError($"{loc["SERVER_ERROR_COMMUNICATION"]} {IP}:{Port}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -778,7 +780,7 @@ namespace IW4MAdmin
|
||||
CustomCallback = await ScriptLoaded();
|
||||
string mainPath = EventParser.GetGameDir();
|
||||
#if DEBUG
|
||||
basepath.Value = @"\\192.168.88.253\Call of Duty 4\";
|
||||
basepath.Value = @"\\192.168.88.253\mw2";
|
||||
#endif
|
||||
string logPath;
|
||||
if (GameName == Game.IW5)
|
||||
|
@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{26E8
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C8F3945-0AEF-4949-A1F7-B18E952E50BC}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
_commands.gsc = _commands.gsc
|
||||
_customcallbacks.gsc = _customcallbacks.gsc
|
||||
README.md = README.md
|
||||
version.txt = version.txt
|
||||
@ -28,7 +29,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Login", "Plugins\Login\Logi
|
||||
EndProject
|
||||
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Master", "Master\Master.pyproj", "{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Plugins\Tests\Tests.csproj", "{B72DEBFB-9D48-4076-8FF5-1FD72A830845}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Plugins\Tests\Tests.csproj", "{B72DEBFB-9D48-4076-8FF5-1FD72A830845}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IW4ScriptCommands", "Plugins\IW4ScriptCommands\IW4ScriptCommands.csproj", "{6C706CE5-A206-4E46-8712-F8C48D526091}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -260,6 +263,30 @@ Global
|
||||
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|x64.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|x64.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|x86.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|x86.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -270,6 +297,7 @@ Global
|
||||
{958FF7EC-0226-4E85-A85B-B84EC768197D} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{D9F2ED28-6FA5-40CA-9912-E7A849147AB1} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{B72DEBFB-9D48-4076-8FF5-1FD72A830845} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}
|
||||
|
@ -7,8 +7,7 @@
|
||||
<ProjectGuid>f5051a32-6bd0-4128-abba-c202ee15fc5c</ProjectGuid>
|
||||
<ProjectHome>.</ProjectHome>
|
||||
<ProjectTypeGuids>{789894c7-04a9-4a11-a6b5-3f4435165112};{1b580a1a-fdb3-4b32-83e1-6407eb2722e6};{349c5851-65df-11da-9384-00065b846f21};{888888a0-9f3d-457c-b088-3a5042f75d52}</ProjectTypeGuids>
|
||||
<StartupFile>
|
||||
</StartupFile>
|
||||
<StartupFile>master\runserver.py</StartupFile>
|
||||
<SearchPath>
|
||||
</SearchPath>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
@ -19,6 +18,11 @@
|
||||
<Name>Master</Name>
|
||||
<RootNamespace>Master</RootNamespace>
|
||||
<InterpreterId>MSBuild|dev_env|$(MSBuildProjectFullPath)</InterpreterId>
|
||||
<IsWindowsApplication>False</IsWindowsApplication>
|
||||
<PythonRunWebServerCommand>master\runserver</PythonRunWebServerCommand>
|
||||
<PythonDebugWebServerCommand>master\runserver</PythonDebugWebServerCommand>
|
||||
<PythonRunWebServerCommandType>module</PythonRunWebServerCommandType>
|
||||
<PythonDebugWebServerCommandType>module</PythonDebugWebServerCommandType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@ -73,6 +77,9 @@
|
||||
<Compile Include="master\routes.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\runserver.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\schema\instanceschema.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
|
9
Master/master/runserver.py
Normal file
9
Master/master/runserver.py
Normal file
@ -0,0 +1,9 @@
|
||||
"""
|
||||
This script runs the Master application using a development server.
|
||||
"""
|
||||
|
||||
from os import environ
|
||||
from master import app
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=80, debug=True)
|
19
Plugins/IW4ScriptCommands/Commands/Balance.cs
Normal file
19
Plugins/IW4ScriptCommands/Commands/Balance.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4ScriptCommands.Commands
|
||||
{
|
||||
class Balance : Command
|
||||
{
|
||||
public Balance() : base("balance", "balance teams", "bal", Player.Permission.Trusted, false, null)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
await E.Owner.ExecuteCommandAsync("sv_iw4madmin_command balance");
|
||||
await E.Origin.Tell("Balance command sent");
|
||||
}
|
||||
}
|
||||
}
|
22
Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj
Normal file
22
Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj
Normal file
@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="copy "$(TargetPath)" "$(SolutionDir)BUILD\Plugins"" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
26
Plugins/IW4ScriptCommands/Plugin.cs
Normal file
26
Plugins/IW4ScriptCommands/Plugin.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4ScriptCommands
|
||||
{
|
||||
class Plugin : IPlugin
|
||||
{
|
||||
public string Name => "IW4 Script Commands";
|
||||
|
||||
public float Version => 1.0f;
|
||||
|
||||
public string Author => "RaidMax";
|
||||
|
||||
public Task OnEventAsync(GameEvent E, Server S) => Task.CompletedTask;
|
||||
|
||||
public Task OnLoadAsync(IManager manager) => Task.CompletedTask;
|
||||
|
||||
public Task OnTickAsync(Server S) => Task.CompletedTask;
|
||||
|
||||
public Task OnUnloadAsync() => Task.CompletedTask;
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
stats.Kills = 0;
|
||||
stats.SPM = 0.0;
|
||||
stats.Skill = 0.0;
|
||||
stats.TimePlayed = 0;
|
||||
|
||||
// reset the cached version
|
||||
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
|
||||
|
@ -190,15 +190,15 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
|
||||
// get individual client's stats
|
||||
var clientStats = playerStats[pl.ClientId];
|
||||
// sync their score
|
||||
clientStats.SessionScore += (pl.Score - clientStats.LastScore);
|
||||
/*// sync their score
|
||||
clientStats.SessionScore += pl.Score;*/
|
||||
|
||||
// remove the client from the stats dictionary as they're leaving
|
||||
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3);
|
||||
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4);
|
||||
|
||||
// sync their stats before they leave
|
||||
clientStats = UpdateStats(clientStats);
|
||||
/* // sync their stats before they leave
|
||||
clientStats = UpdateStats(clientStats);*/
|
||||
|
||||
// todo: should this be saved every disconnect?
|
||||
statsSvc.ClientStatSvc.Update(clientStats);
|
||||
@ -229,8 +229,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
|
||||
catch (FormatException)
|
||||
{
|
||||
Log.WriteWarning("Could not parse kill or death origin vector");
|
||||
Log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin}");
|
||||
Log.WriteWarning("Could not parse kill or death origin or viewangle vectors");
|
||||
Log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAgnel - {viewAngles}");
|
||||
await AddStandardKill(attacker, victim);
|
||||
return;
|
||||
}
|
||||
@ -342,15 +342,27 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Log.WriteDebug("Calculating standard kill");
|
||||
#endif
|
||||
|
||||
// update the total stats
|
||||
Servers[serverId].ServerStatistics.TotalKills += 1;
|
||||
|
||||
attackerStats.SessionScore += (attacker.Score - attackerStats.LastScore);
|
||||
victimStats.SessionScore += (victim.Score - victimStats.LastScore);
|
||||
// this happens when the round has changed
|
||||
if (attackerStats.SessionScore == 0)
|
||||
attackerStats.LastScore = 0;
|
||||
|
||||
if (victimStats.SessionScore == 0)
|
||||
victimStats.LastScore = 0;
|
||||
|
||||
attackerStats.SessionScore = attacker.Score;
|
||||
victimStats.SessionScore = victim.Score;
|
||||
|
||||
// calculate for the clients
|
||||
CalculateKill(attackerStats, victimStats);
|
||||
// this should fix the negative SPM
|
||||
// updates their last score after being calculated
|
||||
attackerStats.LastScore = attacker.Score;
|
||||
victimStats.LastScore = victim.Score;
|
||||
|
||||
@ -427,14 +439,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
// prevent NaN or inactive time lowering SPM
|
||||
if ((DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0 < 0.01 ||
|
||||
(DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0 > 3 ||
|
||||
clientStats.SessionScore < 1)
|
||||
clientStats.SessionScore == 0)
|
||||
{
|
||||
// prevents idle time counting
|
||||
clientStats.LastStatCalculation = DateTime.UtcNow;
|
||||
return clientStats;
|
||||
}
|
||||
|
||||
double timeSinceLastCalc = (DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0;
|
||||
double timeSinceLastActive = (DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0;
|
||||
|
||||
// calculate the players Score Per Minute for the current session
|
||||
int scoreDifference = clientStats.LastScore == 0 ? 0 : clientStats.SessionScore - clientStats.LastScore;
|
||||
int scoreDifference = clientStats.RoundScore - clientStats.LastScore;
|
||||
double killSPM = scoreDifference / timeSinceLastCalc;
|
||||
|
||||
// calculate how much the KDR should weigh
|
||||
@ -442,9 +458,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
double kdr = clientStats.Deaths == 0 ? clientStats.Kills : clientStats.KDR;
|
||||
double KDRWeight = Math.Round(Math.Pow(kdr, 1.637 / Math.E), 3);
|
||||
|
||||
// if no SPM, weight is 1 else the weight ishe current session's spm / lifetime average score per minute
|
||||
//double SPMWeightAgainstAverage = (clientStats.SPM < 1) ? 1 : killSPM / clientStats.SPM;
|
||||
|
||||
// calculate the weight of the new play time against last 10 hours of gameplay
|
||||
int totalPlayTime = (clientStats.TimePlayed == 0) ?
|
||||
(int)(DateTime.UtcNow - clientStats.LastActive).TotalSeconds :
|
||||
@ -504,10 +517,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
{
|
||||
var serverStats = Servers[serverId];
|
||||
foreach (var stat in serverStats.PlayerStats.Values)
|
||||
{
|
||||
stat.KillStreak = 0;
|
||||
stat.DeathStreak = 0;
|
||||
}
|
||||
stat.StartNewSession();
|
||||
}
|
||||
|
||||
public void ResetStats(int clientId, int serverId)
|
||||
@ -517,6 +527,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
stats.Deaths = 0;
|
||||
stats.SPM = 0;
|
||||
stats.Skill = 0;
|
||||
stats.TimePlayed = 0;
|
||||
}
|
||||
|
||||
public async Task AddMessageAsync(int clientId, int serverId, string message)
|
||||
|
@ -56,7 +56,34 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
public int LastScore { get; set; }
|
||||
[NotMapped]
|
||||
public DateTime LastActive { get; set; }
|
||||
public void StartNewSession()
|
||||
{
|
||||
KillStreak = 0;
|
||||
DeathStreak = 0;
|
||||
LastScore = 0;
|
||||
SessionScores.Add(0);
|
||||
}
|
||||
[NotMapped]
|
||||
public int SessionScore { get; set; }
|
||||
public int SessionScore
|
||||
{
|
||||
set
|
||||
{
|
||||
SessionScores[SessionScores.Count - 1] = value;
|
||||
}
|
||||
get
|
||||
{
|
||||
return SessionScores.Sum();
|
||||
}
|
||||
}
|
||||
[NotMapped]
|
||||
public int RoundScore
|
||||
{
|
||||
get
|
||||
{
|
||||
return SessionScores[SessionScores.Count - 1];
|
||||
}
|
||||
}
|
||||
[NotMapped]
|
||||
private List<int> SessionScores = new List<int>() { 0 };
|
||||
}
|
||||
}
|
||||
|
@ -262,14 +262,17 @@ namespace SharedLibraryCore.Services
|
||||
{
|
||||
p.Active = false;
|
||||
// reset the player levels
|
||||
if (p.Type == Objects.Penalty.PenaltyType.Ban)
|
||||
if (p.Type == Penalty.PenaltyType.Ban)
|
||||
{
|
||||
using (var internalContext = new DatabaseContext())
|
||||
{
|
||||
await internalContext.Clients
|
||||
.Where(c => c.AliasLinkId == p.LinkId)
|
||||
.ForEachAsync(c => c.Level = Objects.Player.Permission.User);
|
||||
await internalContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -183,6 +183,9 @@ namespace SharedLibraryCore
|
||||
{
|
||||
if (Int64.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long id))
|
||||
return id;
|
||||
var bot = Regex.Match(str, @"bot[0-9]+").Value;
|
||||
if (!string.IsNullOrEmpty(bot))
|
||||
return -Convert.ToInt64(bot.Substring(3));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
30
_commands.gsc
Normal file
30
_commands.gsc
Normal file
@ -0,0 +1,30 @@
|
||||
#include maps\mp\_utility;
|
||||
#include maps\mp\gametypes\_hud_util;
|
||||
#include common_scripts\utility;
|
||||
|
||||
init()
|
||||
{
|
||||
level thread WaitForCommand();
|
||||
}
|
||||
|
||||
WaitForCommand()
|
||||
{
|
||||
for(;;)
|
||||
{
|
||||
command = getDvar("sv_iw4madmin_command");
|
||||
switch(command)
|
||||
{
|
||||
case "balance":
|
||||
if (isRoundBased())
|
||||
{
|
||||
iPrintLnBold("Balancing Teams..");
|
||||
level maps\mp\gametypes\_teams::balanceTeams();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
setDvar("sv_iw4madmin_command", "");
|
||||
|
||||
wait(1);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user