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
|
namespace IW4MAdmin.Application.API.Master
|
||||||
{
|
{
|
||||||
|
public class HeartbeatState
|
||||||
|
{
|
||||||
|
public bool Connected { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class Heartbeat
|
public class Heartbeat
|
||||||
{
|
{
|
||||||
|
|
||||||
public static async Task Send(ApplicationManager mgr, bool firstHeartbeat = false)
|
public static async Task Send(ApplicationManager mgr, bool firstHeartbeat = false)
|
||||||
{
|
{
|
||||||
var api = Endpoint.Get();
|
var api = Endpoint.Get();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@ -8,12 +9,14 @@ namespace IW4MAdmin.Application
|
|||||||
{
|
{
|
||||||
class GameEventHandler : IEventHandler
|
class GameEventHandler : IEventHandler
|
||||||
{
|
{
|
||||||
private Queue<GameEvent> EventQueue;
|
private ConcurrentQueue<GameEvent> EventQueue;
|
||||||
|
private ConcurrentQueue<GameEvent> StatusSensitiveQueue;
|
||||||
private IManager Manager;
|
private IManager Manager;
|
||||||
|
|
||||||
public GameEventHandler(IManager mgr)
|
public GameEventHandler(IManager mgr)
|
||||||
{
|
{
|
||||||
EventQueue = new Queue<GameEvent>();
|
EventQueue = new ConcurrentQueue<GameEvent>();
|
||||||
|
StatusSensitiveQueue = new ConcurrentQueue<GameEvent>();
|
||||||
Manager = mgr;
|
Manager = mgr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +25,21 @@ namespace IW4MAdmin.Application
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner}");
|
Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner}");
|
||||||
#endif
|
#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
|
#if DEBUG
|
||||||
Manager.GetLogger().WriteDebug($"There are now {EventQueue.Count} events in queue");
|
Manager.GetLogger().WriteDebug($"There are now {EventQueue.Count} events in queue");
|
||||||
#endif
|
#endif
|
||||||
@ -34,13 +51,43 @@ namespace IW4MAdmin.Application
|
|||||||
throw new NotImplementedException();
|
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()
|
public GameEvent GetNextEvent()
|
||||||
{
|
{
|
||||||
|
if (EventQueue.Count > 0)
|
||||||
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Manager.GetLogger().WriteDebug("Getting next event to be processed");
|
Manager.GetLogger().WriteDebug("Getting next event to be processed");
|
||||||
#endif
|
#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
|
class GameLogEvent
|
||||||
{
|
{
|
||||||
FileSystemWatcher LogPathWatcher;
|
|
||||||
Server Server;
|
Server Server;
|
||||||
long PreviousFileSize;
|
long PreviousFileSize;
|
||||||
GameLogReader Reader;
|
GameLogReader Reader;
|
||||||
Timer RefreshInfoTimer;
|
Timer RefreshInfoTimer;
|
||||||
string GameLogFile;
|
string GameLogFile;
|
||||||
|
|
||||||
|
class EventState
|
||||||
|
{
|
||||||
|
public ILogger Log { get; set; }
|
||||||
|
public string ServerId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public GameLogEvent(Server server, string gameLogPath, string gameLogName)
|
public GameLogEvent(Server server, string gameLogPath, string gameLogName)
|
||||||
{
|
{
|
||||||
GameLogFile = gameLogPath;
|
GameLogFile = gameLogPath;
|
||||||
Reader = new GameLogReader(gameLogPath, server.EventParser);
|
Reader = new GameLogReader(gameLogPath, server.EventParser);
|
||||||
Server = server;
|
Server = server;
|
||||||
RefreshInfoTimer = new Timer((sender) =>
|
RefreshInfoTimer = new Timer(OnEvent, new EventState()
|
||||||
{
|
{
|
||||||
long newLength = new FileInfo(GameLogFile).Length;
|
Log = server.Manager.GetLogger(),
|
||||||
UpdateLogEvents(newLength);
|
ServerId = server.ToString()
|
||||||
|
}, 0, 100);
|
||||||
}, null, 0, 100);
|
|
||||||
/*LogPathWatcher = new FileSystemWatcher()
|
|
||||||
{
|
|
||||||
Path = gameLogPath.Replace(gameLogName, ""),
|
|
||||||
Filter = gameLogName,
|
|
||||||
NotifyFilter = (NotifyFilters)383,
|
|
||||||
InternalBufferSize = 4096
|
|
||||||
};
|
|
||||||
|
|
||||||
// LogPathWatcher.Changed += LogPathWatcher_Changed;
|
|
||||||
LogPathWatcher.EnableRaisingEvents = true;*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
private void OnEvent(object state)
|
||||||
~GameLogEvent()
|
|
||||||
{
|
{
|
||||||
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)
|
private void UpdateLogEvents(long fileSize)
|
||||||
{
|
{
|
||||||
|
@ -127,7 +127,19 @@ namespace IW4MAdmin.Application
|
|||||||
|
|
||||||
if (ServerManager.GetApplicationSettings().Configuration().EnableWebFront)
|
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;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using IW4MAdmin.Application.API.Master;
|
||||||
|
|
||||||
namespace IW4MAdmin.Application
|
namespace IW4MAdmin.Application
|
||||||
{
|
{
|
||||||
@ -43,12 +44,7 @@ namespace IW4MAdmin.Application
|
|||||||
EventApi Api;
|
EventApi Api;
|
||||||
GameEventHandler Handler;
|
GameEventHandler Handler;
|
||||||
ManualResetEventSlim OnEvent;
|
ManualResetEventSlim OnEvent;
|
||||||
Timer StatusUpdateTimer;
|
Timer HeartbeatTimer;
|
||||||
#if FTP_LOG
|
|
||||||
const int UPDATE_FREQUENCY = 700;
|
|
||||||
#else
|
|
||||||
const int UPDATE_FREQUENCY = 2000;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private ApplicationManager()
|
private ApplicationManager()
|
||||||
{
|
{
|
||||||
@ -90,16 +86,62 @@ namespace IW4MAdmin.Application
|
|||||||
return Instance ?? (Instance = new ApplicationManager());
|
return Instance ?? (Instance = new ApplicationManager());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStatus(object state)
|
public async Task UpdateStatus(object state)
|
||||||
{
|
{
|
||||||
var taskList = new List<Task>();
|
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()
|
public async Task Init()
|
||||||
@ -286,41 +328,35 @@ 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
|
||||||
|
|
||||||
Running = true;
|
Running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HeartBeatThread()
|
private void SendHeartbeat(object state)
|
||||||
{
|
{
|
||||||
bool successfulConnection = false;
|
var heartbeatState = (HeartbeatState)state;
|
||||||
restartConnection:
|
|
||||||
while (!successfulConnection)
|
if (!heartbeatState.Connected)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
API.Master.Heartbeat.Send(this, true).Wait();
|
Heartbeat.Send(this, true).Wait();
|
||||||
successfulConnection = true;
|
heartbeatState.Connected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
successfulConnection = false;
|
heartbeatState.Connected = false;
|
||||||
Logger.WriteWarning($"Could not connect to heartbeat server - {e.Message}");
|
Logger.WriteWarning($"Could not connect to heartbeat server - {e.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.Sleep(30000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (Running)
|
else
|
||||||
{
|
{
|
||||||
Logger.WriteDebug("Sending heartbeat...");
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
API.Master.Heartbeat.Send(this).Wait();
|
Heartbeat.Send(this).Wait();
|
||||||
}
|
}
|
||||||
catch (System.Net.Http.HttpRequestException e)
|
catch (System.Net.Http.HttpRequestException e)
|
||||||
{
|
{
|
||||||
@ -336,8 +372,7 @@ namespace IW4MAdmin.Application
|
|||||||
{
|
{
|
||||||
if (((RestEase.ApiException)ex).StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
if (((RestEase.ApiException)ex).StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
{
|
{
|
||||||
successfulConnection = false;
|
heartbeatState.Connected = false;
|
||||||
goto restartConnection;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -347,55 +382,65 @@ namespace IW4MAdmin.Application
|
|||||||
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
||||||
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
{
|
{
|
||||||
successfulConnection = false;
|
heartbeatState.Connected = false;
|
||||||
goto restartConnection;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.Sleep(30000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
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
|
#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)
|
foreach (var S in Servers)
|
||||||
S.Broadcast(Utilities.CurrentLocalization.LocalizationSet["BROADCAST_OFFLINE"]).Wait();
|
S.Broadcast(Utilities.CurrentLocalization.LocalizationSet["BROADCAST_OFFLINE"]).Wait();
|
||||||
#endif
|
#endif
|
||||||
_servers.Clear();
|
_servers.Clear();
|
||||||
|
}).Wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ namespace Application.RconParsers
|
|||||||
int Ping = -1;
|
int Ping = -1;
|
||||||
Int32.TryParse(playerInfo[2], out Ping);
|
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())));
|
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);
|
int.TryParse(playerInfo[0], out cID);
|
||||||
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
|
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
|
||||||
int cIP = regex.Value.Split(':')[0].ConvertToIP();
|
int cIP = regex.Value.Split(':')[0].ConvertToIP();
|
||||||
@ -105,8 +105,9 @@ namespace Application.RconParsers
|
|||||||
IPAddress = cIP,
|
IPAddress = cIP,
|
||||||
Ping = Ping,
|
Ping = Ping,
|
||||||
Score = score,
|
Score = score,
|
||||||
IsBot = npID == 0
|
IsBot = cIP == 0
|
||||||
};
|
};
|
||||||
|
|
||||||
StatusPlayers.Add(P);
|
StatusPlayers.Add(P);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ namespace Application.RconParsers
|
|||||||
regex = Regex.Match(responseLine, @" +(\d+ +){3}");
|
regex = Regex.Match(responseLine, @" +(\d+ +){3}");
|
||||||
int score = Int32.Parse(regex.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]);
|
int score = Int32.Parse(regex.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]);
|
||||||
|
|
||||||
StatusPlayers.Add(new Player()
|
var p = new Player()
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
NetworkId = networkId,
|
NetworkId = networkId,
|
||||||
@ -156,7 +156,12 @@ namespace Application.RconParsers
|
|||||||
Ping = Ping,
|
Ping = Ping,
|
||||||
Score = score,
|
Score = score,
|
||||||
IsBot = networkId == 0
|
IsBot = networkId == 0
|
||||||
});
|
};
|
||||||
|
|
||||||
|
StatusPlayers.Add(p);
|
||||||
|
|
||||||
|
if (p.IsBot)
|
||||||
|
p.NetworkId = -p.ClientNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,8 +177,7 @@ namespace Application.RconParsers
|
|||||||
int score = 0;
|
int score = 0;
|
||||||
// todo: fix this when T6M score is valid ;)
|
// todo: fix this when T6M score is valid ;)
|
||||||
//int score = Int32.Parse(playerInfo[1]);
|
//int score = Int32.Parse(playerInfo[1]);
|
||||||
|
var p = new Player()
|
||||||
StatusPlayers.Add(new Player()
|
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
NetworkId = networkId,
|
NetworkId = networkId,
|
||||||
@ -187,7 +186,12 @@ namespace Application.RconParsers
|
|||||||
Ping = Ping,
|
Ping = Ping,
|
||||||
Score = score,
|
Score = score,
|
||||||
IsBot = networkId == 0
|
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) ||
|
if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot) ||
|
||||||
polledPlayer.Ping < 1 || polledPlayer.ClientNumber > (MaxClients) ||
|
polledPlayer.Ping < 1 ||
|
||||||
polledPlayer.ClientNumber < 0)
|
polledPlayer.ClientNumber < 0)
|
||||||
{
|
{
|
||||||
//Logger.WriteDebug($"Skipping client not in connected state {P}");
|
//Logger.WriteDebug($"Skipping client not in connected state {P}");
|
||||||
@ -415,7 +415,7 @@ namespace IW4MAdmin
|
|||||||
if (E.Type == GameEvent.EventType.Connect)
|
if (E.Type == GameEvent.EventType.Connect)
|
||||||
{
|
{
|
||||||
// special case for IW5 when connect is from the log
|
// 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 logClient = (Player)E.Extra;
|
||||||
var client = (await this.GetStatusAsync())
|
var client = (await this.GetStatusAsync())
|
||||||
@ -573,10 +573,12 @@ namespace IW4MAdmin
|
|||||||
Logger.WriteInfo($"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms");
|
Logger.WriteInfo($"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms");
|
||||||
#endif
|
#endif
|
||||||
Throttled = false;
|
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)
|
if (!CurrentPlayers.Select(c => c.NetworkId).Contains(client.NetworkId))
|
||||||
await RemovePlayer(i);
|
await RemovePlayer(client.ClientNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < CurrentPlayers.Count; i++)
|
for (int i = 0; i < CurrentPlayers.Count; i++)
|
||||||
@ -673,15 +675,15 @@ namespace IW4MAdmin
|
|||||||
}
|
}
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -778,7 +780,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)
|
||||||
|
@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{26E8
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C8F3945-0AEF-4949-A1F7-B18E952E50BC}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C8F3945-0AEF-4949-A1F7-B18E952E50BC}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
_commands.gsc = _commands.gsc
|
||||||
_customcallbacks.gsc = _customcallbacks.gsc
|
_customcallbacks.gsc = _customcallbacks.gsc
|
||||||
README.md = README.md
|
README.md = README.md
|
||||||
version.txt = version.txt
|
version.txt = version.txt
|
||||||
@ -28,7 +29,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Login", "Plugins\Login\Logi
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Master", "Master\Master.pyproj", "{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}"
|
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Master", "Master\Master.pyproj", "{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}"
|
||||||
EndProject
|
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
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
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|x64.Build.0 = Release|Any CPU
|
||||||
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x86.ActiveCfg = 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
|
{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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -270,6 +297,7 @@ Global
|
|||||||
{958FF7EC-0226-4E85-A85B-B84EC768197D} = {26E8B310-269E-46D4-A612-24601F16065F}
|
{958FF7EC-0226-4E85-A85B-B84EC768197D} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||||
{D9F2ED28-6FA5-40CA-9912-E7A849147AB1} = {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}
|
{B72DEBFB-9D48-4076-8FF5-1FD72A830845} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||||
|
{6C706CE5-A206-4E46-8712-F8C48D526091} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}
|
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}
|
||||||
|
@ -7,8 +7,7 @@
|
|||||||
<ProjectGuid>f5051a32-6bd0-4128-abba-c202ee15fc5c</ProjectGuid>
|
<ProjectGuid>f5051a32-6bd0-4128-abba-c202ee15fc5c</ProjectGuid>
|
||||||
<ProjectHome>.</ProjectHome>
|
<ProjectHome>.</ProjectHome>
|
||||||
<ProjectTypeGuids>{789894c7-04a9-4a11-a6b5-3f4435165112};{1b580a1a-fdb3-4b32-83e1-6407eb2722e6};{349c5851-65df-11da-9384-00065b846f21};{888888a0-9f3d-457c-b088-3a5042f75d52}</ProjectTypeGuids>
|
<ProjectTypeGuids>{789894c7-04a9-4a11-a6b5-3f4435165112};{1b580a1a-fdb3-4b32-83e1-6407eb2722e6};{349c5851-65df-11da-9384-00065b846f21};{888888a0-9f3d-457c-b088-3a5042f75d52}</ProjectTypeGuids>
|
||||||
<StartupFile>
|
<StartupFile>master\runserver.py</StartupFile>
|
||||||
</StartupFile>
|
|
||||||
<SearchPath>
|
<SearchPath>
|
||||||
</SearchPath>
|
</SearchPath>
|
||||||
<WorkingDirectory>.</WorkingDirectory>
|
<WorkingDirectory>.</WorkingDirectory>
|
||||||
@ -19,6 +18,11 @@
|
|||||||
<Name>Master</Name>
|
<Name>Master</Name>
|
||||||
<RootNamespace>Master</RootNamespace>
|
<RootNamespace>Master</RootNamespace>
|
||||||
<InterpreterId>MSBuild|dev_env|$(MSBuildProjectFullPath)</InterpreterId>
|
<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>
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
@ -73,6 +77,9 @@
|
|||||||
<Compile Include="master\routes.py">
|
<Compile Include="master\routes.py">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="master\runserver.py">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
<Compile Include="master\schema\instanceschema.py">
|
<Compile Include="master\schema\instanceschema.py">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</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.Kills = 0;
|
||||||
stats.SPM = 0.0;
|
stats.SPM = 0.0;
|
||||||
stats.Skill = 0.0;
|
stats.Skill = 0.0;
|
||||||
|
stats.TimePlayed = 0;
|
||||||
|
|
||||||
// reset the cached version
|
// reset the cached version
|
||||||
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
|
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
|
||||||
|
@ -190,15 +190,15 @@ 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.LastScore);
|
clientStats.SessionScore += pl.Score;*/
|
||||||
|
|
||||||
// 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 removedValue3);
|
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue3);
|
||||||
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4);
|
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4);
|
||||||
|
|
||||||
// sync their stats before they leave
|
/* // sync their stats before they leave
|
||||||
clientStats = UpdateStats(clientStats);
|
clientStats = UpdateStats(clientStats);*/
|
||||||
|
|
||||||
// todo: should this be saved every disconnect?
|
// todo: should this be saved every disconnect?
|
||||||
statsSvc.ClientStatSvc.Update(clientStats);
|
statsSvc.ClientStatSvc.Update(clientStats);
|
||||||
@ -229,8 +229,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
catch (FormatException)
|
catch (FormatException)
|
||||||
{
|
{
|
||||||
Log.WriteWarning("Could not parse kill or death origin vector");
|
Log.WriteWarning("Could not parse kill or death origin or viewangle vectors");
|
||||||
Log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin}");
|
Log.WriteDebug($"Kill - {killOrigin} Death - {deathOrigin} ViewAgnel - {viewAngles}");
|
||||||
await AddStandardKill(attacker, victim);
|
await AddStandardKill(attacker, victim);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -342,15 +342,27 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
Log.WriteDebug("Calculating standard kill");
|
||||||
|
#endif
|
||||||
|
|
||||||
// update the total stats
|
// update the total stats
|
||||||
Servers[serverId].ServerStatistics.TotalKills += 1;
|
Servers[serverId].ServerStatistics.TotalKills += 1;
|
||||||
|
|
||||||
attackerStats.SessionScore += (attacker.Score - attackerStats.LastScore);
|
// this happens when the round has changed
|
||||||
victimStats.SessionScore += (victim.Score - victimStats.LastScore);
|
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
|
// calculate for the clients
|
||||||
CalculateKill(attackerStats, victimStats);
|
CalculateKill(attackerStats, victimStats);
|
||||||
// this should fix the negative SPM
|
// this should fix the negative SPM
|
||||||
|
// updates their last score after being calculated
|
||||||
attackerStats.LastScore = attacker.Score;
|
attackerStats.LastScore = attacker.Score;
|
||||||
victimStats.LastScore = victim.Score;
|
victimStats.LastScore = victim.Score;
|
||||||
|
|
||||||
@ -427,14 +439,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
// prevent NaN or inactive time lowering SPM
|
// prevent NaN or inactive time lowering SPM
|
||||||
if ((DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0 < 0.01 ||
|
if ((DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0 < 0.01 ||
|
||||||
(DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0 > 3 ||
|
(DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0 > 3 ||
|
||||||
clientStats.SessionScore < 1)
|
clientStats.SessionScore == 0)
|
||||||
|
{
|
||||||
|
// prevents idle time counting
|
||||||
|
clientStats.LastStatCalculation = DateTime.UtcNow;
|
||||||
return clientStats;
|
return clientStats;
|
||||||
|
}
|
||||||
|
|
||||||
double timeSinceLastCalc = (DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0;
|
double timeSinceLastCalc = (DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0;
|
||||||
double timeSinceLastActive = (DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0;
|
double timeSinceLastActive = (DateTime.UtcNow - clientStats.LastActive).TotalSeconds / 60.0;
|
||||||
|
|
||||||
// calculate the players Score Per Minute for the current session
|
// 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;
|
double killSPM = scoreDifference / timeSinceLastCalc;
|
||||||
|
|
||||||
// calculate how much the KDR should weigh
|
// 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 kdr = clientStats.Deaths == 0 ? clientStats.Kills : clientStats.KDR;
|
||||||
double KDRWeight = Math.Round(Math.Pow(kdr, 1.637 / Math.E), 3);
|
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
|
// calculate the weight of the new play time against last 10 hours of gameplay
|
||||||
int totalPlayTime = (clientStats.TimePlayed == 0) ?
|
int totalPlayTime = (clientStats.TimePlayed == 0) ?
|
||||||
(int)(DateTime.UtcNow - clientStats.LastActive).TotalSeconds :
|
(int)(DateTime.UtcNow - clientStats.LastActive).TotalSeconds :
|
||||||
@ -504,10 +517,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
{
|
{
|
||||||
var serverStats = Servers[serverId];
|
var serverStats = Servers[serverId];
|
||||||
foreach (var stat in serverStats.PlayerStats.Values)
|
foreach (var stat in serverStats.PlayerStats.Values)
|
||||||
{
|
stat.StartNewSession();
|
||||||
stat.KillStreak = 0;
|
|
||||||
stat.DeathStreak = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetStats(int clientId, int serverId)
|
public void ResetStats(int clientId, int serverId)
|
||||||
@ -517,6 +527,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
stats.Deaths = 0;
|
stats.Deaths = 0;
|
||||||
stats.SPM = 0;
|
stats.SPM = 0;
|
||||||
stats.Skill = 0;
|
stats.Skill = 0;
|
||||||
|
stats.TimePlayed = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddMessageAsync(int clientId, int serverId, string message)
|
public async Task AddMessageAsync(int clientId, int serverId, string message)
|
||||||
|
@ -56,7 +56,34 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
|||||||
public int LastScore { get; set; }
|
public int LastScore { get; set; }
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public DateTime LastActive { get; set; }
|
public DateTime LastActive { get; set; }
|
||||||
|
public void StartNewSession()
|
||||||
|
{
|
||||||
|
KillStreak = 0;
|
||||||
|
DeathStreak = 0;
|
||||||
|
LastScore = 0;
|
||||||
|
SessionScores.Add(0);
|
||||||
|
}
|
||||||
[NotMapped]
|
[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;
|
p.Active = false;
|
||||||
// reset the player levels
|
// reset the player levels
|
||||||
if (p.Type == Objects.Penalty.PenaltyType.Ban)
|
if (p.Type == Penalty.PenaltyType.Ban)
|
||||||
{
|
{
|
||||||
using (var internalContext = new DatabaseContext())
|
using (var internalContext = new DatabaseContext())
|
||||||
{
|
{
|
||||||
await internalContext.Clients
|
await internalContext.Clients
|
||||||
.Where(c => c.AliasLinkId == p.LinkId)
|
.Where(c => c.AliasLinkId == p.LinkId)
|
||||||
.ForEachAsync(c => c.Level = Objects.Player.Permission.User);
|
.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))
|
if (Int64.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long id))
|
||||||
return id;
|
return id;
|
||||||
|
var bot = Regex.Match(str, @"bot[0-9]+").Value;
|
||||||
|
if (!string.IsNullOrEmpty(bot))
|
||||||
|
return -Convert.ToInt64(bot.Substring(3));
|
||||||
return 0;
|
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…
x
Reference in New Issue
Block a user