Fixed non player killstreak kills counting as suicide

(custom callbacks)
RCON tweaks to hopefully prevent RCON flooding
Stats reimp
added IW4x extra weapons
This commit is contained in:
RaidMax 2018-02-08 01:23:45 -06:00
parent 0b62cba52a
commit 850d9e8c1a
16 changed files with 298 additions and 45 deletions

View File

@ -34,7 +34,7 @@ namespace IW4MAdmin
AliasService AliasSvc;
PenaltyService PenaltySvc;
#if FTP_LOG
const int UPDATE_FREQUENCY = 15000;
const int UPDATE_FREQUENCY = 700;
#else
const int UPDATE_FREQUENCY = 300;
#endif

View File

@ -19,7 +19,7 @@ namespace IW4MAdmin
public override int GetHashCode()
{
return IP.GetHashCode() + Port;
return Math.Abs(IP.GetHashCode() + Port);
}
override public async Task<bool> AddPlayer(Player polledPlayer)
{
@ -405,11 +405,7 @@ namespace IW4MAdmin
if (lines != oldLines)
{
l_size = LogFile.Length();
int end;
if (lines.Length == oldLines.Length)
end = lines.Length - 1;
else
end = Math.Abs((lines.Length - oldLines.Length)) - 1;
int end = (lines.Length == oldLines.Length) ? lines.Length - 1 : Math.Abs((lines.Length - oldLines.Length)) - 1;
for (int count = 0; count < lines.Length; count++)
{
@ -434,7 +430,6 @@ namespace IW4MAdmin
await ExecuteEvent(event_);
}
}
}
}
}
@ -537,8 +532,13 @@ namespace IW4MAdmin
#endif
}
else
{
#if !DEBUG
LogFile = new IFile(logPath);
#else
}
LogFile = new RemoteFile("https://raidmax.org/IW4MAdmin/getlog.php");
#endif
Logger.WriteInfo("Log file is " + logPath);
await ExecuteEvent(new Event(Event.GType.Start, "Server started", null, null, this));
#if !DEBUG

Binary file not shown.

View File

@ -1,4 +1,8 @@
Version 1.6:
Version 1.7:
CHANGELOG:
-EntityFramework is now the main database system
Version 1.6:
CHANGELOG:
-got rid of pesky "error on character" message
-optimizations to commands

View File

@ -102,10 +102,15 @@ namespace StatsPlugin.Helpers
clientStats = ClientStatSvc.Insert(clientStats);
}
else
lock (playerStats)
{
if (playerStats.ContainsKey(pl.ClientNumber))
{
Log.WriteWarning($"Duplicate clientnumber in stats {pl.ClientId} vs {playerStats[pl.ClientNumber].ClientId}");
playerStats.Remove(pl.ClientNumber);
}
playerStats.Add(pl.ClientNumber, clientStats);
}
return clientStats;
}
@ -113,29 +118,32 @@ namespace StatsPlugin.Helpers
{
int serverId = pl.CurrentServer.GetHashCode();
var playerStats = Servers[serverId].PlayerStats;
// get individual client's stats
var clientStats = playerStats[pl.ClientNumber];
// remove the client from the stats dictionary as they're leaving
lock (playerStats)
playerStats.Remove(pl.ClientNumber);
// allow accessing certain properties
//clientStats.Client = pl;
// update skill
// clientStats = UpdateStats(clientStats);
// reset for EF cache
//clientStats.SessionDeaths = 0;
// clientStats.SessionKills = 0;
// prevent mismatched primary key
//clientStats.Client = null;
// update in database
await ClientStatSvc.SaveChangesAsync();
//await ClientStatSvc.SaveChangesAsync();
}
/// <summary>
/// Process stats for kill event
/// </summary>
/// <returns></returns>
public async Task AddKill(Player attacker, Player victim, int serverId, string map, string hitLoc, string type,
public async Task AddScriptKill(Player attacker, Player victim, int serverId, string map, string hitLoc, string type,
string damage, string weapon, string killOrigin, string deathOrigin)
{
var attackerStats = Servers[serverId].PlayerStats[attacker.ClientNumber];
attackerStats.Kills += 1;
var victimStats = Servers[serverId].PlayerStats[victim.ClientNumber];
victimStats.Deaths += 1;
AddStandardKill(attacker, victim);
var kill = new EFClientKill()
{
@ -156,10 +164,88 @@ namespace StatsPlugin.Helpers
await KillSvc.SaveChangesAsync();
}
private EFClientStatistics UpdateStats(EFClientStatistics cs)
public void AddStandardKill(Player attacker, Player victim)
{
// todo: everything
return cs;
var attackerStats = Servers[attacker.CurrentServer.GetHashCode()].PlayerStats[attacker.ClientNumber];
// set to access total time
attackerStats.Client = attacker;
var victimStats = Servers[victim.CurrentServer.GetHashCode()].PlayerStats[victim.ClientNumber];
CalculateKill(attackerStats, victimStats);
}
/// <summary>
/// Performs the incrementation of kills and deaths for client statistics
/// </summary>
/// <param name="attackerStats">Stats of the attacker</param>
/// <param name="victimStats">Stats of the victim</param>
public void CalculateKill(EFClientStatistics attackerStats, EFClientStatistics victimStats)
{
attackerStats.Kills += 1;
attackerStats.SessionKills += 1;
attackerStats.KillStreak += 1;
attackerStats.DeathStreak = 0;
victimStats.Deaths += 1;
victimStats.SessionDeaths += 1;
victimStats.DeathStreak += 1;
victimStats.KillStreak = 0;
// process the attacker's stats after the kills
UpdateStats(attackerStats);
attackerStats.Client = null;
// immediately write changes in debug
#if DEBUG
ClientStatSvc.SaveChanges();
#endif
}
/// <summary>
/// Update the client stats (skill etc)
/// </summary>
/// <param name="clientStats">Client statistics</param>
/// <returns></returns>
private EFClientStatistics UpdateStats(EFClientStatistics clientStats)
{
// if it's their first kill we need to set the last kill as the time they joined
clientStats.LastStatCalculation = (clientStats.LastStatCalculation == DateTime.MinValue) ? DateTime.UtcNow : clientStats.LastStatCalculation;
double timeSinceLastCalc = (DateTime.UtcNow - clientStats.LastStatCalculation).TotalSeconds / 60.0;
// each 'session' is one minute
if (timeSinceLastCalc >= 1)
{
Log.WriteDebug($"Updated stats for {clientStats.ClientId} ({clientStats.SessionKills})");
// calculate the players Score Per Minute for the current session
// todo: score should be based on gamemode
double killSPM = clientStats.SessionKills * 100.0;
// calculate how much the KDR should weigh
// 1.637 is a Eddie-Generated number that weights the KDR nicely
double KDRWeight = Math.Round(Math.Pow(clientStats.KDR, 1.637 / Math.E), 3);
// if no SPM, weight is 1 else the weight ishe current session's spm / lifetime average score per minute
double SPMWeightAgainstAverage = (clientStats.SPM < 1) ? 1 : killSPM / clientStats.SPM;
// calculate the weight of the new play time against last 10 hours of gameplay
int totalConnectionTime = (clientStats.Client.TotalConnectionTime == 0) ?
(int)(DateTime.UtcNow - clientStats.Client.FirstConnection).TotalSeconds :
clientStats.Client.TotalConnectionTime + (int)(DateTime.UtcNow - clientStats.Client.LastConnection).TotalSeconds;
double SPMAgainstPlayWeight = timeSinceLastCalc / Math.Min(600, (totalConnectionTime / 60.0));
// calculate the new weight against average times the weight against play time
clientStats.SPM = (killSPM * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight));
clientStats.SPM = Math.Round(clientStats.SPM, 3);
clientStats.Skill = Math.Round((clientStats.SPM * KDRWeight) / 10.0, 3);
clientStats.SessionKills = 0;
clientStats.SessionDeaths = 0;
clientStats.LastStatCalculation = DateTime.UtcNow;
}
return clientStats;
}
}
}

View File

@ -1225,7 +1225,118 @@ namespace StatsPlugin
nuke_mp = 1190,
barrel_mp = 1191,
lightstick_mp = 1192,
throwingknife_rhand_mp = 1193
throwingknife_rhand_mp = 1193,
deserteaglegold_akimbo_mp,
deserteaglegold_fmj_mp,
deserteaglegold_tactical_mp,
deserteaglegold_akimbo_fmj_mp,
deserteaglegold_fmj_tactical_mp,
ak47classic_mp,
ak47classic_acog_mp,
ak47classic_eotech_mp,
ak47classic_fmj_mp,
ak47classic_gl_mp,
gl_ak47classic_mp,
ak47classic_heartbeat_mp,
ak47classic_reflex_mp,
ak47classic_shotgun_mp,
ak47classic_shotgun_attach_mp,
ak47classic_silencer_mp,
ak47classic_thermal_mp,
ak47classic_xmags_mp,
ak47classic_acog_fmj_mp,
ak47classic_acog_gl_mp,
ak47classic_acog_heartbeat_mp,
ak47classic_acog_shotgun_mp,
ak47classic_acog_silencer_mp,
ak47classic_acog_xmags_mp,
ak47classic_eotech_fmj_mp,
ak47classic_eotech_gl_mp,
ak47classic_eotech_heartbeat_mp,
ak47classic_eotech_shotgun_mp,
ak47classic_eotech_silencer_mp,
ak47classic_eotech_xmags_mp,
ak47classic_fmj_gl_mp,
ak47classic_fmj_heartbeat_mp,
ak47classic_fmj_reflex_mp,
ak47classic_fmj_shotgun_mp,
ak47classic_fmj_silencer_mp,
ak47classic_fmj_thermal_mp,
ak47classic_fmj_xmags_mp,
ak47classic_gl_heartbeat_mp,
ak47classic_gl_reflex_mp,
ak47classic_gl_silencer_mp,
ak47classic_gl_thermal_mp,
ak47classic_gl_xmags_mp,
ak47classic_heartbeat_reflex_mp,
ak47classic_heartbeat_shotgun_mp,
ak47classic_heartbeat_silencer_mp,
ak47classic_heartbeat_thermal_mp,
ak47classic_heartbeat_xmags_mp,
ak47classic_reflex_shotgun_mp,
ak47classic_reflex_silencer_mp,
ak47classic_reflex_xmags_mp,
ak47classic_shotgun_silencer_mp,
ak47classic_shotgun_thermal_mp,
ak47classic_shotgun_xmags_mp,
ak47classic_silencer_thermal_mp,
ak47classic_silencer_xmags_mp,
ak47classic_thermal_xmags_mp,
ak74u_mp,
ak74u_acog_mp,
ak74u_eotech_mp,
ak74u_fmj_mp,
ak74u_gl_mp,
gl_ak74u_mp,
ak74u_heartbeat_mp,
ak74u_reflex_mp,
ak74u_shotgun_mp,
ak74u_shotgun_attach_mp,
ak74u_silencer_mp,
ak74u_thermal_mp,
ak74u_xmags_mp,
ak74u_acog_fmj_mp,
ak74u_acog_gl_mp,
ak74u_acog_heartbeat_mp,
ak74u_acog_shotgun_mp,
ak74u_acog_silencer_mp,
ak74u_acog_xmags_mp,
ak74u_eotech_fmj_mp,
ak74u_eotech_gl_mp,
ak74u_eotech_heartbeat_mp,
ak74u_eotech_shotgun_mp,
ak74u_eotech_silencer_mp,
ak74u_eotech_xmags_mp,
ak74u_fmj_gl_mp,
ak74u_fmj_heartbeat_mp,
ak74u_fmj_reflex_mp,
ak74u_fmj_shotgun_mp,
ak74u_fmj_silencer_mp,
ak74u_fmj_thermal_mp,
ak74u_fmj_xmags_mp,
ak74u_gl_heartbeat_mp,
ak74u_gl_reflex_mp,
ak74u_gl_silencer_mp,
ak74u_gl_thermal_mp,
ak74u_gl_xmags_mp,
ak74u_heartbeat_reflex_mp,
ak74u_heartbeat_shotgun_mp,
ak74u_heartbeat_silencer_mp,
ak74u_heartbeat_thermal_mp,
ak74u_heartbeat_xmags_mp,
ak74u_reflex_shotgun_mp,
ak74u_reflex_silencer_mp,
ak74u_reflex_xmags_mp,
ak74u_shotgun_silencer_mp,
ak74u_shotgun_thermal_mp,
ak74u_shotgun_xmags_mp,
ak74u_silencer_thermal_mp,
ak74u_silencer_xmags_mp,
ak74u_thermal_xmags_mp,
m40a3_mp = 1194,
peacekeeper_mp,
dragunov_mp,
cobra_player_minigun_mp
}
public enum MapName

View File

@ -28,11 +28,22 @@ namespace StatsPlugin.Models
[NotMapped]
public double KDR
{
get => Deaths == 0 ? 0.0 : Math.Round((float)Kills / (float)Deaths, 2);
get => Deaths == 0 ? Kills : Math.Round((float)Kills / (float)Deaths, 2);
}
[Required]
public double SPM { get; set; }
[Required]
public double Skill { get; set; }
[NotMapped]
public int SessionKills { get; set; }
[NotMapped]
public int SessionDeaths { get; set; }
[NotMapped]
public int KillStreak { get; set; }
[NotMapped]
public int DeathStreak { get; set; }
[NotMapped]
public DateTime LastStatCalculation { get; set; }
}
}

View File

@ -62,7 +62,7 @@ namespace StatsPlugin
case Event.GType.Kill:
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 9 && killInfo[0].Contains("ScriptKill"))
await Manager.AddKill(E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], killInfo[5], killInfo[6], killInfo[3], killInfo[4]);
await Manager.AddScriptKill(E.Origin, E.Target, S.GetHashCode(), S.CurrentMap.Name, killInfo[7], killInfo[8], killInfo[5], killInfo[6], killInfo[3], killInfo[4]);
break;
case Event.GType.Death:
break;

View File

@ -202,8 +202,6 @@ namespace IW4MAdmin.Plugins
Interval = DateTime.Now;
if (S.ClientNum > 0)
{
//"K;26d2f66b95184934;1;allies;egor;5c56fef676b3818d;0;axis;1_din;m21_heartbeat_mp;98;MOD_RIFLE_BULLET;torso_lower";
var victimPlayer = S.Players.Where(pl => pl != null).ToList()[rand.Next(0, S.ClientNum - 1)];
var attackerPlayer = S.Players.Where(pl => pl != null).ToList()[rand.Next(0, S.ClientNum - 1)];

View File

@ -18,6 +18,7 @@ namespace SharedLibrary.Database.Models
[Required]
public int Connections { get; set; }
[Required]
// in seconds
public int TotalConnectionTime { get; set; }
[Required]
public DateTime FirstConnection { get; set; }

View File

@ -3,19 +3,53 @@ using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Http;
namespace SharedLibrary
{
public class RemoteFile : IFile
{
string Location;
string[] FileCache;
public RemoteFile(string location) : base(string.Empty)
{
Location = location;
}
private void Retrieve()
{
using (var cl = new HttpClient())
FileCache = cl.GetStringAsync(Location).Result.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
}
public override string[] Tail(int lineCount)
{
Retrieve();
return FileCache;
}
public override long Length()
{
Retrieve();
return FileCache[0].Length;
}
}
public class IFile
{
public IFile(String fileName)
{
Name = fileName;
Handle = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
sze = Handle.BaseStream.Length;
if (fileName != string.Empty)
{
Name = fileName;
Handle = new StreamReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
sze = Handle.BaseStream.Length;
}
}
public long Length()
public virtual long Length()
{
sze = Handle.BaseStream.Length;
return sze;
@ -36,7 +70,7 @@ namespace SharedLibrary
return Handle?.ReadToEnd();
}
public String[] Tail(int lineCount)
public virtual String[] Tail(int lineCount)
{
var buffer = new List<string>(lineCount);
string line;

View File

@ -40,7 +40,7 @@ namespace SharedLibrary.Helpers
public void Update(Task<bool> T)
{
RequestedTask = T;
Console.WriteLine($"Starting Task {T.Id} ");
// Console.WriteLine($"Starting Task {T.Id} ");
RequestedTask.Start();
if (TimesRun > 25)

View File

@ -26,12 +26,12 @@ namespace SharedLibrary.Network
static string[] SendQuery(QueryType Type, Server QueryServer, string Parameters = "")
{
if ((DateTime.Now - LastQuery).TotalMilliseconds < 30)
Task.Delay(30).Wait();
if ((DateTime.Now - LastQuery).TotalMilliseconds < 100)
Task.Delay(100).Wait();
LastQuery = DateTime.Now;
var ServerOOBConnection = new UdpClient();
ServerOOBConnection.Client.SendTimeout = 5000;
ServerOOBConnection.Client.ReceiveTimeout = 5000;
ServerOOBConnection.Client.SendTimeout = 1000;
ServerOOBConnection.Client.ReceiveTimeout = 1000;
var Endpoint = new IPEndPoint(IPAddress.Parse(QueryServer.GetIP()), QueryServer.GetPort());
string QueryString = String.Empty;
@ -139,7 +139,7 @@ namespace SharedLibrary.Network
public static async Task<List<Player>> GetStatusAsync(this Server server)
{
#if DEBUG
#if DEBUG && DEBUG_PLAYERS
string[] response = await Task.Run(() => System.IO.File.ReadAllLines("players.txt"));
#else
string[] response = await Task.FromResult(SendQuery(QueryType.DVAR, server, "status"));

View File

@ -140,11 +140,13 @@ namespace SharedLibrary
/// <param name="Message">Message to be sent to all players</param>
public async Task Broadcast(String Message)
{
#if DEBUG
//return;
#endif
string sayCommand = (GameName == Game.IW4) ? "sayraw" : "say";
#if !DEBUG
await this.ExecuteCommandAsync($"{sayCommand} {Message}");
#else
Logger.WriteVerbose(Message.StripColors());
#endif
}
/// <summary>
@ -156,8 +158,12 @@ namespace SharedLibrary
{
string tellCommand = (GameName == Game.IW4) ? "tellraw" : "tell";
#if !DEBUG
if (Target.ClientNumber > -1 && Message.Length > 0 && Target.Level != Player.Permission.Console)
await this.ExecuteCommandAsync($"{tellCommand} {Target.ClientNumber} {Message}^7");
#else
Logger.WriteVerbose($"{Target.ClientNumber}->{Message.StripColors()}");
#endif
if (Target.Level == Player.Permission.Console)
{

View File

@ -86,6 +86,7 @@
<HintPath>..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http" />
<Reference Include="System.Web" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />

View File

@ -13,9 +13,10 @@ init()
Callback_PlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration )
{
victim = self;
_attacker = attacker;
if (!isDefined(attacker) || !isPlayer(attacker))
attacker = victim;
_attacker = victim;
logPrint("ScriptKill;" + attacker.guid + ";" + victim.guid + ";" + attacker.origin + ";" + victim.origin + ";" + iDamage + ";" + sWeapon + ";" + sHitLoc + ";" + sMeansOfDeath + "\n");
logPrint("ScriptKill;" + _attacker.guid + ";" + victim.guid + ";" + _attacker.origin + ";" + victim.origin + ";" + iDamage + ";" + sWeapon + ";" + sHitLoc + ";" + sMeansOfDeath + "\n");
self maps\mp\gametypes\_damage::Callback_PlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration );
}