fixed misc issues and added stats page to webfront
This commit is contained in:
parent
4cddefd542
commit
9758e72f6b
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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.
@ -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:
|
||||
|
BIN
Admin/webfront/images/minimap_mp_rust.png
Normal file
BIN
Admin/webfront/images/minimap_mp_rust.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 183 KiB |
BIN
Admin/webfront/images/minimap_mp_terminal.png
Normal file
BIN
Admin/webfront/images/minimap_mp_terminal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 110 KiB |
@ -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
134
Admin/webfront/stats.html
Normal 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>
|
@ -92,9 +92,9 @@ namespace Plugin
|
||||
}
|
||||
}
|
||||
|
||||
public Task OnUnloadAsync()
|
||||
public async Task OnUnloadAsync()
|
||||
{
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
70
Plugins/SimpleStats/MinimapConfig.cs
Normal file
70
Plugins/SimpleStats/MinimapConfig.cs
Normal 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
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
67
Plugins/SimpleStats/StatsPage.cs
Normal file
67
Plugins/SimpleStats/StatsPage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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">
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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.
|
||||
|
24
SharedLibrary/Helpers/ParseEnum.cs
Normal file
24
SharedLibrary/Helpers/ParseEnum.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
SharedLibrary/Helpers/ThreadSafe.cs
Normal file
48
SharedLibrary/Helpers/ThreadSafe.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
44
SharedLibrary/Helpers/Vector3.cs
Normal file
44
SharedLibrary/Helpers/Vector3.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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" />
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user