99390f1f35
view angle vector parse fail is now a handled exception change local host check to byte array to make it faster than comparing string kick command now requires moderator level or higher tempban now requires administrator level or higher hopefully fixed negative SPM bug pipelined the events and consolidated them to run through GameEventHandler uniform console colors
265 lines
11 KiB
C#
265 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Reflection;
|
|
|
|
using SharedLibraryCore;
|
|
using SharedLibraryCore.Configuration;
|
|
using SharedLibraryCore.Dtos;
|
|
using SharedLibraryCore.Helpers;
|
|
using SharedLibraryCore.Interfaces;
|
|
using SharedLibraryCore.Services;
|
|
using IW4MAdmin.Plugins.Stats.Config;
|
|
using IW4MAdmin.Plugins.Stats.Helpers;
|
|
using IW4MAdmin.Plugins.Stats.Models;
|
|
|
|
namespace IW4MAdmin.Plugins.Stats
|
|
{
|
|
class Plugin : IPlugin
|
|
{
|
|
public string Name => "Simple Stats";
|
|
|
|
public float Version => Assembly.GetExecutingAssembly().GetName().Version.Major + Assembly.GetExecutingAssembly().GetName().Version.Minor / 10.0f;
|
|
|
|
public string Author => "RaidMax";
|
|
|
|
public static StatManager Manager { get; private set; }
|
|
private IManager ServerManager;
|
|
public static BaseConfigurationHandler<StatsConfiguration> Config { get; private set; }
|
|
|
|
public async Task OnEventAsync(GameEvent E, Server S)
|
|
{
|
|
switch (E.Type)
|
|
{
|
|
case GameEvent.EventType.Start:
|
|
Manager.AddServer(S);
|
|
break;
|
|
case GameEvent.EventType.Stop:
|
|
break;
|
|
case GameEvent.EventType.Connect:
|
|
await Manager.AddPlayer(E.Origin);
|
|
break;
|
|
case GameEvent.EventType.Disconnect:
|
|
await Manager.RemovePlayer(E.Origin);
|
|
break;
|
|
case GameEvent.EventType.Say:
|
|
if (E.Data != string.Empty && E.Data.Trim().Length > 0 && E.Message.Trim()[0] != '!' && E.Origin.ClientId > 1)
|
|
await Manager.AddMessageAsync(E.Origin.ClientId, E.Owner.GetHashCode(), E.Data);
|
|
break;
|
|
case GameEvent.EventType.MapChange:
|
|
Manager.ResetKillstreaks(S.GetHashCode());
|
|
await Manager.Sync(S);
|
|
break;
|
|
case GameEvent.EventType.MapEnd:
|
|
break;
|
|
case GameEvent.EventType.Broadcast:
|
|
break;
|
|
case GameEvent.EventType.Tell:
|
|
break;
|
|
case GameEvent.EventType.Kick:
|
|
break;
|
|
case GameEvent.EventType.Ban:
|
|
break;
|
|
case GameEvent.EventType.Remote:
|
|
break;
|
|
case GameEvent.EventType.Unknown:
|
|
break;
|
|
case GameEvent.EventType.Report:
|
|
break;
|
|
case GameEvent.EventType.Flag:
|
|
break;
|
|
case GameEvent.EventType.Script:
|
|
break;
|
|
case GameEvent.EventType.Kill:
|
|
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
|
|
if (killInfo.Length >= 9 && killInfo[0].Contains("ScriptKill") && E.Owner.CustomCallback)
|
|
await Manager.AddScriptKill(E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8],
|
|
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12]);
|
|
else if (!E.Owner.CustomCallback)
|
|
await Manager.AddStandardKill(E.Origin, E.Target);
|
|
break;
|
|
case GameEvent.EventType.Death:
|
|
break;
|
|
}
|
|
}
|
|
|
|
public async Task OnLoadAsync(IManager manager)
|
|
{
|
|
// load custom configuration
|
|
Config = new BaseConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
|
|
if (Config.Configuration() == null)
|
|
{
|
|
Config.Set((StatsConfiguration)new StatsConfiguration().Generate());
|
|
await Config.Save();
|
|
}
|
|
|
|
// meta data info
|
|
async Task<List<ProfileMeta>> getStats(int clientId)
|
|
{
|
|
var statsSvc = new GenericRepository<EFClientStatistics>();
|
|
var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId);
|
|
|
|
int kills = clientStats.Sum(c => c.Kills);
|
|
int deaths = clientStats.Sum(c => c.Deaths);
|
|
double kdr = Math.Round(kills / (double)deaths, 2);
|
|
double skill = Math.Round(clientStats.Sum(c => c.Skill) / clientStats.Count, 2);
|
|
double spm = Math.Round(clientStats.Sum(c => c.SPM), 1);
|
|
|
|
return new List<ProfileMeta>()
|
|
{
|
|
new ProfileMeta()
|
|
{
|
|
Key = "Kills",
|
|
Value = kills
|
|
},
|
|
new ProfileMeta()
|
|
{
|
|
Key = "Deaths",
|
|
Value = deaths
|
|
},
|
|
new ProfileMeta()
|
|
{
|
|
Key = "KDR",
|
|
Value = kdr
|
|
},
|
|
new ProfileMeta()
|
|
{
|
|
Key = "Skill",
|
|
Value = skill
|
|
},
|
|
new ProfileMeta()
|
|
{
|
|
Key = "Score Per Minute",
|
|
Value = spm
|
|
}
|
|
};
|
|
}
|
|
|
|
async Task<List<ProfileMeta>> getAnticheatInfo(int clientId)
|
|
{
|
|
var statsSvc = new GenericRepository<EFClientStatistics>();
|
|
var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId);
|
|
|
|
double headRatio = 0;
|
|
double chestRatio = 0;
|
|
double abdomenRatio = 0;
|
|
double chestAbdomenRatio = 0;
|
|
double hitOffsetAverage = 0;
|
|
|
|
if (clientStats.Where(cs => cs.HitLocations.Count > 0).FirstOrDefault() != null)
|
|
{
|
|
chestRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
|
|
c.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_upper).HitCount) /
|
|
(double)clientStats.Where(c => c.HitLocations.Count > 0)
|
|
.Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount)), 2);
|
|
|
|
abdomenRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
|
|
c.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount) /
|
|
(double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount)), 2);
|
|
|
|
chestAbdomenRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_upper).HitCount) /
|
|
(double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount), 2);
|
|
|
|
headRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.head).HitCount) /
|
|
(double)clientStats.Where(c => c.HitLocations.Count > 0)
|
|
.Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount)), 2);
|
|
|
|
hitOffsetAverage = clientStats.Sum(c => c.AverageHitOffset) / Math.Max(1, clientStats.Where(c => c.AverageHitOffset > 0).Count());
|
|
}
|
|
|
|
return new List<ProfileMeta>()
|
|
{
|
|
new ProfileMeta()
|
|
{
|
|
Key = "Chest Ratio",
|
|
Value = chestRatio,
|
|
Sensitive = true
|
|
},
|
|
new ProfileMeta()
|
|
{
|
|
Key = "Abdomen Ratio",
|
|
Value = abdomenRatio,
|
|
Sensitive = true
|
|
},
|
|
new ProfileMeta()
|
|
{
|
|
Key = "Chest To Abdomen Ratio",
|
|
Value = chestAbdomenRatio,
|
|
Sensitive = true
|
|
},
|
|
new ProfileMeta()
|
|
{
|
|
Key = "Headshot Ratio",
|
|
Value = headRatio,
|
|
Sensitive = true
|
|
},
|
|
new ProfileMeta()
|
|
{
|
|
Key = "Hit Offset Average",
|
|
Value = $"{Math.Round(((float)hitOffsetAverage).ToDegrees(), 4)}°",
|
|
Sensitive = true
|
|
}
|
|
};
|
|
}
|
|
|
|
async Task<List<ProfileMeta>> getMessages(int clientId)
|
|
{
|
|
var messageSvc = new GenericRepository<EFClientMessage>();
|
|
var messages = await messageSvc.FindAsync(m => m.ClientId == clientId);
|
|
var messageMeta = messages.Select(m => new ProfileMeta()
|
|
{
|
|
Key = "EventMessage",
|
|
Value = m.Message,
|
|
When = m.TimeSent
|
|
}).ToList();
|
|
messageMeta.Add(new ProfileMeta()
|
|
{
|
|
Key = "Messages",
|
|
Value = messages.Count
|
|
});
|
|
|
|
return messageMeta;
|
|
}
|
|
|
|
MetaService.AddMeta(getStats);
|
|
|
|
if (Config.Configuration().EnableAntiCheat)
|
|
{
|
|
MetaService.AddMeta(getAnticheatInfo);
|
|
}
|
|
|
|
MetaService.AddMeta(getMessages);
|
|
|
|
string totalKills()
|
|
{
|
|
var serverStats = new GenericRepository<EFServerStatistics>();
|
|
return serverStats.Find(s => s.Active)
|
|
.Sum(c => c.TotalKills).ToString("#,##0");
|
|
}
|
|
|
|
string totalPlayTime()
|
|
{
|
|
var serverStats = new GenericRepository<EFServerStatistics>();
|
|
return Math.Ceiling((serverStats.GetQuery(s => s.Active)
|
|
.Sum(c => c.TotalPlayTime) / 3600.0)).ToString("#,##0");
|
|
}
|
|
|
|
manager.GetMessageTokens().Add(new MessageToken("TOTALKILLS", totalKills));
|
|
manager.GetMessageTokens().Add(new MessageToken("TOTALPLAYTIME", totalPlayTime));
|
|
|
|
ServerManager = manager;
|
|
|
|
Manager = new StatManager(manager);
|
|
}
|
|
|
|
public Task OnTickAsync(Server S) => Task.CompletedTask;
|
|
|
|
public async Task OnUnloadAsync()
|
|
{
|
|
foreach (var sv in ServerManager.GetServers())
|
|
await Manager.Sync(sv);
|
|
}
|
|
}
|
|
}
|