fixed misc issues and added stats page to webfront

This commit is contained in:
RaidMax 2017-10-03 18:17:35 -05:00
parent 4cddefd542
commit 9758e72f6b
26 changed files with 677 additions and 69 deletions

View File

@ -156,6 +156,15 @@
<Compile Include="WebService.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="webfront\images\minimap_mp_rust.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="webfront\images\minimap_mp_terminal.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="webfront\stats.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="app.manifest" />
<Content Include="IW4AdminIcon.ico" />
<Content Include="lib\Kayak.dll">
@ -351,11 +360,11 @@ copy /Y "$(ProjectDir)lib\Kayak.dll" "$(SolutionDir)BUILD\lib"
xcopy /Y /I /E "$(ProjectDir)webfront\*" "$(SolutionDir)BUILD\Webfront"
if $(ConfigurationName) == Release powershell.exe -file "$(SolutionDir)DEPLOY\publish_nightly.ps1" 1.4
if $(ConfigurationName) == Release-Nightly powershell.exe -file "$(SolutionDir)DEPLOY\publish_nightly.ps1" 1.4
if $(ConfigurationName) == Release-Stable powershell.exe -file "$(SolutionDir)DEPLOY\publish_stable.ps1" 1.4</PostBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PreBuildEvent>xcopy "$(SolutionDir)BUILD\plugins" "$(TargetDir)plugins" /Y</PreBuildEvent>
<PreBuildEvent>xcopy /Y "$(SolutionDir)BUILD\Plugins" "$(TargetDir)Plugins\"</PreBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -50,21 +50,27 @@ namespace IW4MAdmin
{
SharedLibrary.HttpResponse requestedPage = WebService.GetPage(request.Path, querySet, request.Headers);
bool binaryContent = requestedPage.BinaryContent != null;
if (requestedPage.content != null && requestedPage.content.GetType() != typeof(string))
requestedPage.content = Newtonsoft.Json.JsonConvert.SerializeObject(requestedPage.content);
var headers = new HttpResponseHead()
{
Status = "200 OK",
Headers = new Dictionary<string, string>()
{
{ "Content-Type", requestedPage.contentType },
{ "Content-Length", requestedPage.content.Length.ToString() },
{ "Content-Length", binaryContent ? requestedPage.BinaryContent.Length.ToString() : requestedPage.content.ToString().Length.ToString() },
{ "Access-Control-Allow-Origin", "*" },
}
};
foreach (var key in requestedPage.additionalHeaders.Keys)
headers.Headers.Add(key, requestedPage.additionalHeaders[key]);
response.OnResponse(headers, new BufferedProducer(requestedPage.content));
if (!binaryContent)
response.OnResponse(headers, new BufferedProducer((string)requestedPage.content));
else
response.OnResponse(headers, new BufferedProducer(requestedPage.BinaryContent));
}
catch (Exception e)

View File

@ -491,17 +491,17 @@ namespace IW4MAdmin
logfile = await this.GetDvarAsync<string>("g_log");
}
#if DEBUG
if (Environment.OSVersion.VersionString != "Microsoft Windows NT 6.2.9200.0")
//if (Environment.OSVersion.VersionString != "Microsoft Windows NT 6.2.9200.0")
{
basepath.Value = (GameName == Game.IW4) ?
@"\\tsclient\J\WIN7_10.25\MW2" :
@"\\tsclient\G\Program Files (x86)\Steam\SteamApps\common\Call of Duty 4";
}
else
{
basepath.Value = @"C:\MW2";
}
// else
// {
// basepath.Value = @"C:\MW2";
// }
#endif
string mainPath = (GameName == Game.IW4) ? "userraw" : "main";

View File

@ -3,6 +3,7 @@ using Kayak.Http;
using SharedLibrary;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
@ -85,7 +86,7 @@ namespace IW4MAdmin
{
if (System.IO.File.Exists(path.Replace("/", "\\").Substring(1)))
{
IFile f = new IFile(path.Replace("/", "\\").Substring(1));
var f = File.ReadAllBytes(path.Replace("/", "\\").Substring(1));
if (path.Contains(".css"))
@ -93,10 +94,9 @@ namespace IW4MAdmin
HttpResponse css = new HttpResponse()
{
additionalHeaders = new Dictionary<string, string>(),
content = f.GetText(),
content = Encoding.ASCII.GetString(f),
contentType = "text/css"
};
f.Close();
return css;
}
@ -106,13 +106,23 @@ namespace IW4MAdmin
HttpResponse css = new HttpResponse()
{
additionalHeaders = new Dictionary<string, string>(),
content = f.GetText(),
content = Encoding.ASCII.GetString(f),
contentType = "application/javascript"
};
f.Close();
return css;
}
f.Close();
else if (path.Contains(".png"))
{
HttpResponse png = new HttpResponse()
{
additionalHeaders = new Dictionary<string, string>(),
BinaryContent = f,
contentType = "image/png"
};
return png;
}
}

Binary file not shown.

View File

@ -1,4 +1,4 @@
Versino 1.5
Version 1.5
CHANGELOG:
-added back player history graphs (past 12 hours every 15 minutes)
-fixed issue with configurationmanager files and threading
@ -6,6 +6,7 @@ CHANGELOG:
-fixed resolution of tempban times from console feedback
-reconfigured solution and projects to be correct debug/release and files copy properly
-started working on more advanced statistics
-fixed misc issues
VERSION 1.4
CHANGELOG:

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@ -1,5 +1,6 @@
* { margin: 0; padding: 0; }
html { background-color: rgb(34, 34, 34); color: rgb(204, 204, 204); font-family: 'Open Sans', serif; min-width: 720px; }
body { background-color: rgb(34,34,34); color: rgb(204, 204, 204); font-family: 'Open Sans'; }
a:link, a:visited, input[type="submit"], input[type="submit"]:hover { -webkit-transition: color 100ms ease-out; -moz-transition: color 100ms ease-out; -o-transition: color 100ms ease-out; transition: color 100ms ease-out; text-decoration: none; color: rgb(204, 204, 204); }
a:link:hover, a:visited:hover { color: rgb(0, 122, 204); }
@ -202,4 +203,39 @@ div#footer { position: fixed; bottom: 0.5em; right: 0.5em; opacity: 0.5; }
.admin-name a:hover { color: #fff !important; }
.clients { margin: 0.5em; }
.canvasjs-chart-credit { display: none; }
.player-history { margin-top: -100px; height: 100px; }
.player-history { margin-top: -100px; height: 100px; }
.stats-minimap-image { height: 512px; width: 512px; }
.stats-minimap-container { max-width: 512px; margin: 2em 0; float: left;}
#stats { width: 100%; }
.stats-serverinfo { float: left;margin: 2em 1em; font-size: 16pt; }
#KillEventCount { margin-bottom: 1em; }
.slider {
-webkit-appearance: none;
width: 100%;
height: 15px;
background: rgb(24,24,24);
outline: 1px rgba(255, 255, 255, 0.1) solid;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
border-radius: 50%;
background: #007ACC;
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 25px;
height: 25px;
border-radius: 50%;
background: #007ACC;
cursor: pointer;
}

134
Admin/webfront/stats.html Normal file
View File

@ -0,0 +1,134 @@
<div id="stats">
<div id="stats-info">
<input type="range" min="1" max="1000" value="100" class="slider" id="KillEventCount">
<label for="KillEventCount">Showing 100 Kills</label>
</div>
</div>
<script>
var killsList = [];
function drawCircle(context, x, y, color) {
context.beginPath();
context.arc(x, y, 3.5, 0, 2 * Math.PI, false);
context.fillStyle = color;
context.fill();
context.lineWidth = 0.5;
context.strokeStyle = 'rgba(255, 255, 255, 0.5)';
context.stroke();
}
function drawLine(context, x1, y1, x2, y2, color) {
context.beginPath();
context.lineWidth = '1';
var grad = context.createLinearGradient(x1, y1, x2, y2);
grad.addColorStop(0, 'rgba(0, 255, 0, 0.75)');
grad.addColorStop(0.75, 'rgba(223, 66, 244, 0.8)');
context.strokeStyle = grad;
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
}
function clearCanvas(canvas) {
var context = canvas[0].getContext("2d");
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
}
function checkCanvasSize(canvas, context, minimap, minimapInfo) {
var height = minimap.height() - minimapInfo.Top - minimapInfo.Bottom;
var width = minimap.width() - minimapInfo.Left - minimapInfo.Right;
if (context.canvas.height != height || context.canvas.width != width) {
context.canvas.height = height;
context.canvas.width = width;
canvas.css('position', 'absolute');
canvas.css('left', minimapInfo.Left + minimap.offset().left);
canvas.css('top', minimapInfo.Top + minimap.offset().top);
}
}
function drawKill(opacity, canvas, server, kill) {
var context = canvas[0].getContext("2d");
var minimapInfo = server.Minimap[0];
var calcX1 = (minimapInfo.MaxLeft - kill.KillOrigin.Y) / (minimapInfo.Width / canvas.width());
var calcY1 = (minimapInfo.MaxTop - kill.KillOrigin.X) / (minimapInfo.Height / canvas.height());
var calcX2 = (minimapInfo.MaxLeft - kill.DeathOrigin.Y) / (minimapInfo.Width / canvas.width());
var calcY2 = (minimapInfo.MaxTop - kill.DeathOrigin.X) / (minimapInfo.Height / canvas.height());
drawCircle(context, calcX1, calcY1, 'rgba(0, 122, 204, ' + opacity + ')');
drawLine(context, calcX1, calcY1, calcX2, calcY2, 'rgba(0, 255, 0, 0.1)');
drawCircle(context, calcX2, calcY2, 'rgba(255, 0, 0, ' + opacity + ')');
}
function loadKills() {
$.getJSON("/_killstats?count=" + $('#KillEventCount').val(), function (result) {
$.each(result.Servers, function (i, server) {
if (server.Minimap.length == 0) {
//console.log("missing minimap data for " + server.ServerName);
return true;
}
if ($('#minimap-overlay-' + i).length == 0) {
var html = '<div style="clear: both;"></div><div id="stats-container-' + i + '"><div class="stats-minimap-container" id="stats-minimap-' + i + '"><h2 class="datThing">' + server.ServerName + '</h2><img class="stats-minimap-image" src="/webfront/images/minimap_' + server.Minimap[0].MapName + '.png" /> </div>';
html += '<div class="stats-serverinfo"></div><canvas id="minimap-overlay-' + i + '"></canvas></div>';
$('#stats').append(html);
}
server.MapKills = server.MapKills.reverse();
var newKills = [];
var oldKills = [];
if (killsList[i] != undefined) {
newKills = server.MapKills.filter(x => killsList[i].filter(x2 => x2.ID == x.ID).length == 0);
oldKills = server.MapKills.filter(x => newKills.findIndex(x2 => x2.ID == x.ID) === -1);
}
killsList[i] = server.MapKills;
var canvas = $('#minimap-overlay-' + i);
var minimap = $('#stats-minimap-' + i + ' img');
clearCanvas(canvas);
checkCanvasSize(canvas, canvas[0].getContext("2d"), minimap, server.Minimap[0]);
var furthestKill = 0;
$.each(newKills, function (i, kill) {
drawKill('1', canvas, server, kill);
});
$.each(oldKills, function (i, kill) {
if (kill.Distance > furthestKill)
furthestKill = kill.Distance;
drawKill('0.35', canvas, server, kill);
});
var html = '<span>' + server.ServerInfo.Uptime + ' of uptime</span><br/><span>Round started ' + server.ServerInfo.ElapsedRoundTime + '</span><br/>';
html += '<span>Furthest kill from ' + Math.round(furthestKill * 10) / 10 + ' meters</span><br/>';
if (newKills.length > 0)
html += '<span class="last-kill">' + newKills[0].KillerPlayer + ' killed ' + newKills[0].VictimPlayer + '</span><br/>';
else
html += '<span class="last-kill">' + $('#stats-container-' + i).find('.last-kill').text() + '</span><br/>';
$('#stats-container-' + i + ' .stats-serverinfo').html(html);
});
});
}
$(document).ready(function () {
$('#KillEventCount').on('input change', function () {
$(this).next().text('Showing ' + $(this).val() + ' Kills');
});
loadKills();
setInterval(loadKills, 1000);
});
</script>

View File

@ -92,9 +92,9 @@ namespace Plugin
}
}
public Task OnUnloadAsync()
public async Task OnUnloadAsync()
{
return null;
}
}
}

View File

@ -255,7 +255,7 @@ namespace MessageBoard.Forum
public ErrorCode authorizeUser(string username, string password, string sessionID)
{
User toAuth = database.getUser(username.ToLower());
User toAuth = database.getUser(username);
if (toAuth == null)
return ErrorCode.USER_BADCREDENTIALS;

View File

@ -32,7 +32,8 @@ namespace StatsPlugin
MOD_HIT_BY_OBJECT,
MOD_DROWN,
MOD_GAS,
MOD_NUM
MOD_NUM,
MOD_EXPLOSIVE_BULLET
}
public enum HitLocation
@ -1226,5 +1227,12 @@ namespace StatsPlugin
lightstick_mp = 1192,
throwingknife_rhand_mp = 1193
}
public enum MapName
{
mp_unknown,
mp_terminal,
mp_rust
}
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibrary.Helpers;
using SharedLibrary.Interfaces;
namespace StatsPlugin
{
public class MinimapInfo
{
public string MapName { get; set; }
// distance from the edge of the minimap image
// to the "playable" area
public int Top { get; set; }
public int Bottom { get; set; }
public int Left { get; set; }
public int Right { get; set; }
// maximum coordinate values for the map
public int MaxTop { get; set; }
public int MaxBottom { get; set; }
public int MaxLeft { get; set; }
public int MaxRight { get; set; }
public int Width => MaxLeft - MaxRight;
public int Height => MaxTop - MaxBottom;
}
public class MinimapConfig : Serialize<MinimapConfig>
{
public List<MinimapInfo> MapInfo;
public static MinimapConfig IW4Minimaps()
{
return new MinimapConfig()
{
MapInfo = new List<MinimapInfo>()
{
new MinimapInfo()
{
MapName = "mp_terminal",
Top = 85,
Bottom = 89,
Left = 7,
Right = 6,
MaxTop = 2929,
MaxBottom = -513,
MaxLeft = 7520,
MaxRight = 2447
},
new MinimapInfo()
{
MapName = "mp_rust",
Top = 122,
Bottom = 104,
Left = 155,
Right = 82,
MaxRight = -225,
MaxLeft = 1809,
MaxTop = 1641,
MaxBottom = -469
}
}
};
}
}
}

View File

@ -1,12 +1,12 @@
using System;
using System.Text;
using System.IO;
using SharedLibrary;
using SharedLibrary.Helpers;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using SharedLibrary;
namespace StatsPlugin
{
public class CViewStats : Command
@ -96,30 +96,57 @@ namespace StatsPlugin
/// </summary>
public class Stats : SharedLibrary.Interfaces.IPlugin
{
private class KillEvent
public static SharedLibrary.Interfaces.IManager ManagerInstance;
public static int MAX_KILLEVENTS = 1000;
public static Dictionary<int, ServerStatInfo> ServerStats { get; private set; }
public class ServerStatInfo
{
public ServerStatInfo()
{
KillQueue = new Queue<KillInfo>();
ServerStartTime = DateTime.Now;
}
public DateTime ServerStartTime { get; private set; }
public DateTime RoundStartTime { get; set; }
public string Uptime => Utilities.GetTimePassed(ServerStartTime, false);
public string ElapsedRoundTime => Utilities.GetTimePassed(RoundStartTime);
private Queue<KillInfo> KillQueue { get; set; }
public Queue<KillInfo> GetKillQueue() { return KillQueue; }
}
public class KillInfo
{
public IW4Info.HitLocation HitLoc { get; set; }
public string HitLocString => HitLoc.ToString();
public IW4Info.MeansOfDeath DeathType { get; set; }
public string DeathTypeString => DeathType.ToString();
public int Damage { get; set; }
public IW4Info.WeaponName Weapon { get; set; }
public string WeaponString => Weapon.ToString();
public Vector3 KillOrigin { get; set; }
public Vector3 DeathOrigin { get; set; }
// http://wiki.modsrepository.com/index.php?title=Call_of_Duty_5:_Gameplay_standards for conversion to meters
public double Distance => Vector3.Distance(KillOrigin, DeathOrigin) * 0.0254;
public string KillerPlayer { get; set; }
public int KillerPlayerID { get; set; }
public string VictimPlayer { get; set; }
public int VictimPlayerID { get; set; }
public IW4Info.MapName Map { get; set; }
public int ID => GetHashCode();
public KillEvent(string hit, string type, string damage, string weapon, string kOrigin, string dOrigin)
public KillInfo() { }
public KillInfo(int killer, int victim, string map, string hit, string type, string damage, string weapon, string kOrigin, string dOrigin)
{
HitLoc = (IW4Info.HitLocation)Enum.Parse(typeof(IW4Info.HitLocation), hit);
DeathType = (IW4Info.MeansOfDeath)Enum.Parse(typeof(IW4Info.MeansOfDeath), type);
KillerPlayerID = killer;
VictimPlayerID = victim;
Map = ParseEnum<IW4Info.MapName>.Get(map, typeof(IW4Info.MapName));
HitLoc = ParseEnum<IW4Info.HitLocation>.Get(hit, typeof(IW4Info.HitLocation));
DeathType = ParseEnum<IW4Info.MeansOfDeath>.Get(type, typeof(IW4Info.MeansOfDeath));
Damage = Int32.Parse(damage);
try
{
Weapon = (IW4Info.WeaponName)Enum.Parse(typeof(IW4Info.WeaponName), weapon);
}
catch (Exception)
{
Weapon = IW4Info.WeaponName.defaultweapon_mp;
}
Weapon = ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName));
KillOrigin = Vector3.Parse(kOrigin);
DeathOrigin = Vector3.Parse(dOrigin);
}
@ -165,6 +192,17 @@ namespace StatsPlugin
public async Task OnLoadAsync()
{
statLists = new List<StatTracking>();
ServerStats = new Dictionary<int, ServerStatInfo>();
try
{
var minimapConfig = MinimapConfig.Read("Config/minimaps.cfg");
}
catch (SharedLibrary.Exceptions.SerializeException e)
{
MinimapConfig.Write("Config/minimaps.cfg", MinimapConfig.IW4Minimaps());
}
}
public async Task OnUnloadAsync()
@ -181,17 +219,28 @@ namespace StatsPlugin
{
if (E.Type == Event.GType.Start)
{
if (ManagerInstance == null)
{
ManagerInstance = S.Manager;
WebService.PageList.Add(new StatsPage());
WebService.PageList.Add(new KillStatsJSON());
}
statLists.Add(new StatTracking(S.GetPort()));
if (statLists.Count == 1)
{
S.Manager.GetMessageTokens().Add(new SharedLibrary.Helpers.MessageToken("TOTALKILLS", GetTotalKills));
S.Manager.GetMessageTokens().Add(new SharedLibrary.Helpers.MessageToken("TOTALPLAYTIME", GetTotalPlaytime));
S.Manager.GetMessageTokens().Add(new MessageToken("TOTALKILLS", GetTotalKills));
S.Manager.GetMessageTokens().Add(new MessageToken("TOTALPLAYTIME", GetTotalPlaytime));
}
ServerStats.Add(S.GetPort(), new ServerStatInfo());
}
if (E.Type == Event.GType.Stop)
{
statLists.RemoveAll(x => x.Port == S.GetPort());
statLists.RemoveAll(s => s.Port == S.GetPort());
ServerStats.Remove(S.GetPort());
}
if (E.Type == Event.GType.Connect)
@ -224,6 +273,12 @@ namespace StatsPlugin
}
}
if (E.Type == Event.GType.MapChange)
{
ServerStats[S.GetPort()].GetKillQueue().Clear();
ServerStats[S.GetPort()].RoundStartTime = DateTime.Now;
}
if (E.Type == Event.GType.Disconnect)
{
CalculateAndSaveSkill(E.Origin, statLists.Find(x => x.Port == S.GetPort()));
@ -238,15 +293,27 @@ namespace StatsPlugin
string[] killInfo = E.Data.Split(';');
var killEvent = new KillEvent(killInfo[7], killInfo[8], killInfo[5], killInfo[6], killInfo[3], killInfo[4]);
S.Logger.WriteInfo($"{E.Origin.Name} killed {E.Target.Name} with a {killEvent.Weapon} from a distance of {Vector3.Distance(killEvent.KillOrigin, killEvent.DeathOrigin)} with {killEvent.Damage} damage, at {killEvent.HitLoc}");
Player Killer = E.Origin;
StatTracking curServer = statLists.Find(x => x.Port == S.GetPort());
PlayerStats killerStats = curServer.playerStats.GetStats(Killer);
if (killInfo.Length >= 9)
{
var killEvent = new KillInfo(E.Origin.DatabaseID, E.Target.DatabaseID, S.CurrentMap.Name, killInfo[7], killInfo[8], killInfo[5], killInfo[6], killInfo[3], killInfo[4])
{
KillerPlayer = E.Origin.Name,
VictimPlayer = E.Target.Name,
};
if (ServerStats[S.GetPort()].GetKillQueue().Count > MAX_KILLEVENTS - 1)
ServerStats[S.GetPort()].GetKillQueue().Dequeue();
ServerStats[S.GetPort()].GetKillQueue().Enqueue(killEvent);
//S.Logger.WriteInfo($"{E.Origin.Name} killed {E.Target.Name} with a {killEvent.Weapon} from a distance of {Vector3.Distance(killEvent.KillOrigin, killEvent.DeathOrigin)} with {killEvent.Damage} damage, at {killEvent.HitLoc}");
curServer.playerStats.AddKill(killEvent);
return;
}
curServer.lastKill[E.Origin.ClientID] = DateTime.Now;
curServer.Kills[E.Origin.ClientID]++;
@ -395,6 +462,7 @@ namespace StatsPlugin
String createKillsTable = @"CREATE TABLE `KILLS` (
`KillerID` INTEGER NOT NULL,
`VictimID` INTEGER NOT NULL,
`MapID` INTEGER NOT NULL,
`DeathOrigin` TEXT NOT NULL,
`MeansOfDeath` INTEGER NOT NULL,
`Weapon` INTEGER NOT NULL,
@ -407,6 +475,81 @@ namespace StatsPlugin
}
}
public void AddKill(Stats.KillInfo info)
{
var kill = new Dictionary<string, object>
{
{ "KillerID", info.KillerPlayerID },
{ "VictimID", info.VictimPlayerID },
{ "MapID", (int)info.Map },
{ "KillOrigin", info.KillOrigin.ToString() },
{ "DeathOrigin", info.DeathOrigin.ToString() },
{ "MeansOfDeath", (int)info.DeathType },
{ "Weapon", (int)info.Weapon },
{ "HitLocation", (int)info.HitLoc },
{ "Damage", info.Damage }
};
Insert("KILLS", kill);
}
public List<Stats.KillInfo> GetKillsByPlayer(int databaseID)
{
var queryResult = GetDataTable("KILLS", new KeyValuePair<string, object>("KillerID", databaseID));
var resultList = new List<Stats.KillInfo>();
if (queryResult?.Rows.Count > 0)
{
foreach (DataRow resultRow in queryResult.Rows)
{
resultList.Add(new Stats.KillInfo()
{
KillerPlayerID = Convert.ToInt32(resultRow["KillerID"]),
VictimPlayerID = Convert.ToInt32(resultRow["VictimID"]),
Map = (IW4Info.MapName)resultRow["MapID"],
HitLoc = (IW4Info.HitLocation)resultRow["HitLocation"],
DeathType = (IW4Info.MeansOfDeath)resultRow["MeansOfDeath"],
Damage = (int)resultRow["Damage"],
Weapon = (IW4Info.WeaponName)resultRow["Weapon"],
KillOrigin = Vector3.Parse(resultRow["KillOrigin"].ToString()),
DeathOrigin = Vector3.Parse(resultRow["DeathOrigin"].ToString())
});
}
}
return resultList;
}
public List<Stats.KillInfo> GetKillsByMap(Map map)
{
var mapID = ParseEnum<IW4Info.MapName>.Get(map.Name, typeof(IW4Info.MapName));
var queryResult = GetDataTable($"select * from KILLS where MapID == {(int)mapID} LIMIT 500 OFFSET (SELECT COUNT(*) FROM KILLS)-500"); //GetDataTable("KILLS", new KeyValuePair<string, object>("MapID", mapID));
var resultList = new List<Stats.KillInfo>();
if (queryResult?.Rows.Count > 0)
{
foreach (DataRow resultRow in queryResult.Rows)
{
resultList.Add(new Stats.KillInfo()
{
KillerPlayerID = Convert.ToInt32(resultRow["KillerID"]),
VictimPlayerID = Convert.ToInt32(resultRow["VictimID"]),
Map = ParseEnum<IW4Info.MapName>.Get(resultRow["MapID"].ToString(), typeof(IW4Info.MapName)),
HitLoc = ParseEnum<IW4Info.HitLocation>.Get(resultRow["HitLocation"].ToString(), typeof(IW4Info.HitLocation)),
DeathType = ParseEnum<IW4Info.MeansOfDeath>.Get(resultRow["MeansOfDeath"].ToString(), typeof(IW4Info.MeansOfDeath)),
Damage = Convert.ToInt32(resultRow["Damage"]),
Weapon = ParseEnum<IW4Info.WeaponName>.Get(resultRow["Weapon"].ToString(), typeof(IW4Info.WeaponName)),
KillOrigin = Vector3.Parse(resultRow["KillOrigin"].ToString()),
DeathOrigin = Vector3.Parse(resultRow["DeathOrigin"].ToString())
});
}
}
return resultList;
}
public void AddPlayer(Player P)
{
Dictionary<String, object> newPlayer = new Dictionary<String, object>

View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibrary;
namespace StatsPlugin
{
public class StatsPage : HTMLPage
{
public override string GetContent(NameValueCollection querySet, IDictionary<string, string> headers)
{
StringBuilder S = new StringBuilder();
S.Append(LoadHeader());
IFile stats = new IFile("webfront\\stats.html");
S.Append(stats.GetText());
stats.Close();
S.Append(LoadFooter());
return S.ToString();
}
public override string GetName() => "Stats";
public override string GetPath() => "/stats";
}
class KillStatsJSON : IPage
{
public string GetName() => "Kill Stats JSON";
public string GetPath() => "/_killstats";
public string GetContentType() => "application/json";
public bool Visible() => false;
public HttpResponse GetPage(NameValueCollection querySet, IDictionary<string, string> headers)
{
int selectCount = Stats.MAX_KILLEVENTS;
if (querySet.Get("count") != null)
selectCount = Int32.Parse(querySet.Get("count"));
HttpResponse resp = new HttpResponse()
{
contentType = GetContentType(),
content = new
{
Servers = Stats.ManagerInstance.GetServers().Select(s => new
{
ServerName = s.Hostname,
ServerMap = s.CurrentMap.Alias,
ServerInfo = Stats.ServerStats[s.GetPort()],
Minimap = MinimapConfig.Read(@"Config\minimaps.cfg").MapInfo.Where(m => m.MapName == s.CurrentMap.Name),
MapKills = Stats.ServerStats[s.GetPort()].GetKillQueue().ToArray()
.Skip(Math.Min(Stats.MAX_KILLEVENTS - selectCount, Stats.ServerStats[s.GetPort()].GetKillQueue().Count - selectCount))
})
},
additionalHeaders = new Dictionary<string, string>()
};
return resp;
}
}
}

View File

@ -70,9 +70,10 @@
</ItemGroup>
<ItemGroup>
<Compile Include="IW4Info.cs" />
<Compile Include="MinimapConfig.cs" />
<Compile Include="Plugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Vector3.cs" />
<Compile Include="StatsPage.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\SharedLibrary\SharedLibrary.csproj">

View File

@ -77,13 +77,16 @@ namespace IW4MAdmin.Plugins
if (S.GameName == Server.Game.IW4)
{
// attackerID ; victimID ; attackerOrigin ; victimOrigin ; Damage ; Weapon ; hitLocation ; meansOfDeath
var minimapInfo = StatsPlugin.MinimapConfig.IW4Minimaps().MapInfo.FirstOrDefault(m => m.MapName == S.CurrentMap.Name);
if (minimapInfo == null)
return;
eventLine = new string[]
{
"ScriptKill",
attackerPlayer.NetworkID,
victimPlayer.NetworkID,
new StatsPlugin.Vector3(rand.Next(0, 5000), rand.Next(0, 100), rand.Next(0, 5000)).ToString(),
new StatsPlugin.Vector3(rand.Next(0, 5000), rand.Next(0, 100), rand.Next(0, 5000)).ToString(),
new Vector3(rand.Next(minimapInfo.MaxRight, minimapInfo.MaxLeft), rand.Next(minimapInfo.MaxBottom, minimapInfo.MaxTop), rand.Next(0, 100)).ToString(),
new Vector3(rand.Next(minimapInfo.MaxRight, minimapInfo.MaxLeft), rand.Next(minimapInfo.MaxBottom, minimapInfo.MaxTop), rand.Next(0, 100)).ToString(),
rand.Next(50, 105).ToString(),
((StatsPlugin.IW4Info.WeaponName)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.WeaponName)).Length - 1)).ToString(),
((StatsPlugin.IW4Info.HitLocation)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.HitLocation)).Length - 1)).ToString(),
@ -105,7 +108,7 @@ namespace IW4MAdmin.Plugins
rand.Next(0, 1) == 0 ? "allies" : "axis",
attackerPlayer.Name.ToString(),
((StatsPlugin.IW4Info.WeaponName)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.WeaponName)).Length - 1)).ToString(), // Weapon
rand.Next(50, 105).ToString(), // Damange
rand.Next(50, 105).ToString(), // Damage
((StatsPlugin.IW4Info.MeansOfDeath)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.MeansOfDeath)).Length - 1)).ToString(), // Means of Death
((StatsPlugin.IW4Info.HitLocation)rand.Next(0, Enum.GetValues(typeof(StatsPlugin.IW4Info.HitLocation)).Length - 1)).ToString(), // Hit Location
};

View File

@ -86,8 +86,8 @@
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\plugins\"
copy /Y "$(ProjectDir)GeoIP.dat" "$(SolutionDir)Admin\bin\$(ConfigurationName)\GeoIP.dat"</PostBuildEvent>
<PostBuildEvent>copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\plugins\$(TargetName).dll"
copy /Y "$(ProjectDir)GeoIP.dat" "$(SolutionDir)BUILD\plugins\GeoIP.dat"</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibrary.Helpers
{
public class ParseEnum<T>
{
public static T Get(string e, Type type)
{
try
{
return (T)Enum.Parse(type, e);
}
catch (Exception)
{
return (T)(Enum.GetValues(type).GetValue(0));
}
}
}
}

View File

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibrary.Helpers
{
/// <summary>
/// Excuse this monstrosity
/// </summary>
/// <typeparam name="T"></typeparam>
public class ThreadSafe<T>
{
private bool _lock;
private T instance;
public ThreadSafe(T instance)
{
this.instance = instance;
_lock = true;
}
public T Value
{
get
{
// shush
if (_lock)
return Value;
_lock = true;
return instance;
}
set
{
if (_lock)
{
Value = Value;
return;
}
instance = Value;
}
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace SharedLibrary.Helpers
{
public class Vector3
{
public float X { get; private set; }
public float Y { get; private set; }
public float Z { get; private set; }
public Vector3(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
public override string ToString()
{
return $"({X}, {Y}, {Z})";
}
public static Vector3 Parse(string s)
{
bool valid = Regex.Match(s, @"\(-?[0-9]+.?[0-9]*,\ -?[0-9]+.?[0-9]*,\ -?[0-9]+.?[0-9]*\)").Success;
if (!valid)
throw new FormatException("Vector3 is not in correct format");
string removeParenthesis = s.Substring(1, s.Length - 2);
string[] eachPoint = removeParenthesis.Split(',');
return new Vector3(float.Parse(eachPoint[0]), float.Parse(eachPoint[1]), float.Parse(eachPoint[2]));
}
public static double Distance(Vector3 a, Vector3 b)
{
return Math.Round(Math.Sqrt(Math.Pow(b.X - a.X, 2) + Math.Pow(b.Y - a.Y, 2) + Math.Pow(b.Z - a.Z, 2)), 2);
}
}
}

View File

@ -35,11 +35,6 @@ namespace SharedLibrary
Console = 8,
}
public override bool Equals(object obj)
{
return ((Player)obj).NetworkID == NetworkID;
}
public override int GetHashCode()
{
return base.GetHashCode();

View File

@ -90,6 +90,8 @@
<Compile Include="Exceptions\SerializationException.cs" />
<Compile Include="Exceptions\ServerException.cs" />
<Compile Include="Helpers\ConfigurationManager.cs" />
<Compile Include="Helpers\ParseEnum.cs" />
<Compile Include="Helpers\Vector3.cs" />
<Compile Include="Interfaces\ILogger.cs" />
<Compile Include="Interfaces\IManager.cs" />
<Compile Include="Interfaces\IPenaltyList.cs" />

View File

@ -222,30 +222,36 @@ namespace SharedLibrary
public static String GetTimePassed(DateTime start)
{
TimeSpan Elapsed = DateTime.Now - start;
return GetTimePassed(start, true);
}
if (Elapsed.TotalSeconds < 30)
public static String GetTimePassed(DateTime start, bool includeAgo)
{
TimeSpan Elapsed = DateTime.Now - start;
string ago = includeAgo ? " ago" : "";
if (Elapsed.TotalSeconds < 30 && includeAgo)
return "just now";
if (Elapsed.TotalMinutes < 120)
{
if (Elapsed.TotalMinutes < 1.5)
return "1 minute ago";
return Math.Round(Elapsed.TotalMinutes, 0) + " minutes ago";
return $"1 minute{ago}";
return Math.Round(Elapsed.TotalMinutes, 0) + $" minutes{ago}";
}
if (Elapsed.TotalHours <= 24)
{
if (Elapsed.TotalHours < 1.5)
return "1 hour ago";
return Math.Round(Elapsed.TotalHours, 0) + " hours ago";
return $"1 hour{ago}";
return Math.Round(Elapsed.TotalHours, 0) + $" hours{ago}";
}
if (Elapsed.TotalDays <= 365)
{
if (Elapsed.TotalDays < 1.5)
return "1 day ago";
return Math.Round(Elapsed.TotalDays, 0) + " days ago";
return $"1 day{ago}";
return Math.Round(Elapsed.TotalDays, 0) + $" days{ago}";
}
else
return "a very long time ago";
return $"a very long time{ago}";
}
public static Game GetGame(string gameName)

View File

@ -18,7 +18,8 @@ namespace SharedLibrary
public struct HttpResponse
{
public string contentType;
public string content;
public object content;
public byte[] BinaryContent;
public Dictionary<string, string> additionalHeaders;
}