fix bug with player not getting updated on disconnect (related to issue #24)

jint version downgraded for better stability (also locked the engine instance as it's not thread safe)
updated readme
remove vpn detection from application configuration as it's now in a seperate plugin
defaulted webfront bind URl to all interfaces
readd the custom say name
added visibility percentage to AC
This commit is contained in:
RaidMax 2018-09-04 12:40:29 -05:00
parent 672d45df7c
commit cfbacabb4a
22 changed files with 1126 additions and 313 deletions

View File

@ -22,3 +22,10 @@ xcopy /Y "%SolutionDir%BUILD\Plugins" "%SolutionDir%Publish\WindowsPrerelease\Pl
echo Copying script plugins for publish
xcopy /Y "%SolutionDir%Plugins\ScriptPlugins" "%SolutionDir%Publish\Windows\Plugins\"
xcopy /Y "%SolutionDir%Plugins\ScriptPlugins" "%SolutionDir%Publish\WindowsPrerelease\Plugins\"
echo Copying GSC files for publish
xcopy /Y "%SolutionDir%_customcallbacks.gsc" "%SolutionDir%Publish\Windows\userraw\scripts\"
xcopy /Y "%SolutionDir%_customcallbacks.gsc" "%SolutionDir%Publish\WindowsPrerelease\userraw\scripts\"
xcopy /Y "%SolutionDir%_commands.gsc" "%SolutionDir%Publish\Windows\userraw\scripts\"
xcopy /Y "%SolutionDir%_commands.gsc" "%SolutionDir%Publish\WindowsPrerelease\userraw\scripts\"

View File

@ -1,12 +1,9 @@
using SharedLibraryCore;
using SharedLibraryCore.Events;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace IW4MAdmin.Application
{
@ -22,59 +19,57 @@ namespace IW4MAdmin.Application
OutOfOrderEvents = new SortedList<long, GameEvent>();
}
public bool AddEvent(GameEvent gameEvent)
public void AddEvent(GameEvent gameEvent)
{
#if DEBUG
Manager.GetLogger().WriteDebug($"Got new event of type {gameEvent.Type} for {gameEvent.Owner} with id {gameEvent.Id}");
#endif
// GameEvent sortedEvent = null;
// lock (OutOfOrderEvents)
// {
// sortedEvent = OutOfOrderEvents.Values.FirstOrDefault();
GameEvent sortedEvent = null;
lock (OutOfOrderEvents)
{
sortedEvent = OutOfOrderEvents.Values.FirstOrDefault();
// while (sortedEvent?.Id == Interlocked.Read(ref NextEventId))
// {
// if (OutOfOrderEvents.Count > 0)
// {
// OutOfOrderEvents.RemoveAt(0);
// }
while (sortedEvent?.Id == Interlocked.Read(ref NextEventId))
{
if (OutOfOrderEvents.Count > 0)
{
OutOfOrderEvents.RemoveAt(0);
}
// AddEvent(sortedEvent);
// sortedEvent = OutOfOrderEvents.Values.FirstOrDefault();
// }
// }
AddEvent(sortedEvent);
sortedEvent = OutOfOrderEvents.Values.FirstOrDefault();
}
}
// // both the gameEvent Id and the LastEventId are thread safe because we want to synchronize when the
// // event occurs
// if (gameEvent.Id == Interlocked.Read(ref NextEventId))
// {
//#if DEBUG == true
// Manager.GetLogger().WriteDebug($"sent event with id {gameEvent.Id} to be processed");
//#endif
// both the gameEvent Id and the LastEventId are thread safe because we want to synchronize when the
// event occurs
if (gameEvent.Id == Interlocked.Read(ref NextEventId))
{
#if DEBUG == true
Manager.GetLogger().WriteDebug($"sent event with id {gameEvent.Id} to be processed");
#endif
((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent));
return true;
// Interlocked.Increment(ref NextEventId);
// }
Interlocked.Increment(ref NextEventId);
}
// // a "newer" event has been added before and "older" one has been added (due to threads and context switching)
// // so me must wait until the next expected one arrives
// else
// {
//#if DEBUG == true
// Manager.GetLogger().WriteWarning("Incoming event is out of order");
// Manager.GetLogger().WriteDebug($"Expected event Id is {Interlocked.Read(ref NextEventId)}, but got {gameEvent.Id} instead");
//#endif
// a "newer" event has been added before and "older" one has been added (due to threads and context switching)
// so me must wait until the next expected one arrives
else
{
#if DEBUG == true
Manager.GetLogger().WriteWarning("Incoming event is out of order");
Manager.GetLogger().WriteDebug($"Expected event Id is {Interlocked.Read(ref NextEventId)}, but got {gameEvent.Id} instead");
#endif
// // this prevents multiple threads from adding simultaneously
// lock (OutOfOrderEvents)
// {
// if (!OutOfOrderEvents.TryGetValue(gameEvent.Id, out GameEvent discard))
// {
// OutOfOrderEvents.Add(gameEvent.Id, gameEvent);
// }
// }
// }
// return true;
// this prevents multiple threads from adding simultaneously
lock (OutOfOrderEvents)
{
if (!OutOfOrderEvents.TryGetValue(gameEvent.Id, out GameEvent discard))
{
OutOfOrderEvents.Add(gameEvent.Id, gameEvent);
}
}
}
}
}
}

View File

@ -258,7 +258,6 @@ namespace IW4MAdmin
{
Player Leaving = Players[cNum];
Logger.WriteInfo($"Client {Leaving}, state {Leaving.State.ToString()} disconnecting...");
Leaving.State = Player.ClientState.Disconnecting;
// occurs when the player disconnects via log before being authenticated by RCon
if (Leaving.State != Player.ClientState.Connected)
@ -268,6 +267,7 @@ namespace IW4MAdmin
else
{
Leaving.State = Player.ClientState.Disconnecting;
Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds;
Leaving.LastConnection = DateTime.UtcNow;
await Manager.GetClientService().Update(Leaving);
@ -822,7 +822,7 @@ namespace IW4MAdmin
Logger.WriteInfo($"Log file is {logPath}");
Task.Run(() => LogEvent.PollForChanges());
_ = Task.Run(() => LogEvent.PollForChanges());
#if !DEBUG
await Broadcast(loc["BROADCAST_ONLINE"]);
#endif

View File

@ -1,197 +1,197 @@
//using SharedLibraryCore;
//using SharedLibraryCore.Objects;
//using System;
//using System.Collections.Generic;
//using System.Linq;
//using System.Text;
//using System.Threading.Tasks;
using SharedLibraryCore;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//namespace IW4ScriptCommands.Commands
//{
// class Balance : Command
// {
// private class TeamAssignment
// {
// public IW4MAdmin.Plugins.Stats.IW4Info.Team CurrentTeam { get; set; }
// public int Num { get; set; }
// public IW4MAdmin.Plugins.Stats.Models.EFClientStatistics Stats { get; set; }
// }
// public Balance() : base("balance", "balance teams", "bal", Player.Permission.Trusted, false, null)
// {
// }
namespace IW4ScriptCommands.Commands
{
class Balance : Command
{
private class TeamAssignment
{
public IW4MAdmin.Plugins.Stats.IW4Info.Team CurrentTeam { get; set; }
public int Num { get; set; }
public IW4MAdmin.Plugins.Stats.Models.EFClientStatistics Stats { get; set; }
}
public Balance() : base("balance", "balance teams", "bal", Player.Permission.Trusted, false, null)
{
}
// public override async Task ExecuteAsync(GameEvent E)
// {
// string teamsString = (await E.Owner.GetDvarAsync<string>("sv_iw4madmin_teams")).Value;
public override async Task ExecuteAsync(GameEvent E)
{
string teamsString = (await E.Owner.GetDvarAsync<string>("sv_iw4madmin_teams")).Value;
// var scriptClientTeams = teamsString.Split(';', StringSplitOptions.RemoveEmptyEntries)
// .Select(c => c.Split(','))
// .Select(c => new TeamAssignment()
// {
// CurrentTeam = (IW4MAdmin.Plugins.Stats.IW4Info.Team)Enum.Parse(typeof(IW4MAdmin.Plugins.Stats.IW4Info.Team), c[1]),
// Num = E.Owner.Players.FirstOrDefault(p => p?.NetworkId == c[0].ConvertLong())?.ClientNumber ?? -1,
// Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(E.Owner.Players.FirstOrDefault(p => p?.NetworkId == c[0].ConvertLong()).ClientId, E.Owner.GetHashCode())
// })
// .ToList();
var scriptClientTeams = teamsString.Split(';', StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Split(','))
.Select(c => new TeamAssignment()
{
CurrentTeam = (IW4MAdmin.Plugins.Stats.IW4Info.Team)Enum.Parse(typeof(IW4MAdmin.Plugins.Stats.IW4Info.Team), c[1]),
Num = E.Owner.Players.FirstOrDefault(p => p?.NetworkId == c[0].ConvertLong())?.ClientNumber ?? -1,
Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(E.Owner.Players.FirstOrDefault(p => p?.NetworkId == c[0].ConvertLong()).ClientId, E.Owner.GetHashCode())
})
.ToList();
// // at least one team is full so we can't balance
// if (scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) >= Math.Floor(E.Owner.MaxClients / 2.0)
// || scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) >= Math.Floor(E.Owner.MaxClients / 2.0))
// {
// await E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]);
// return;
// }
// at least one team is full so we can't balance
if (scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis) >= Math.Floor(E.Owner.MaxClients / 2.0)
|| scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) >= Math.Floor(E.Owner.MaxClients / 2.0))
{
await E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]);
return;
}
// List<string> teamAssignments = new List<string>();
List<string> teamAssignments = new List<string>();
// var activeClients = E.Owner.GetPlayersAsList().Select(c => new TeamAssignment()
// {
// Num = c.ClientNumber,
// Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()),
// CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()).Team
// })
// .Where(c => scriptClientTeams.FirstOrDefault(sc => sc.Num == c.Num)?.CurrentTeam != IW4MAdmin.Plugins.Stats.IW4Info.Team.Spectator)
// .Where(c => c.CurrentTeam != scriptClientTeams.FirstOrDefault(p => p.Num == c.Num)?.CurrentTeam)
// .OrderByDescending(c => c.Stats.Performance)
// .ToList();
var activeClients = E.Owner.GetPlayersAsList().Select(c => new TeamAssignment()
{
Num = c.ClientNumber,
Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()),
CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, E.Owner.GetHashCode()).Team
})
.Where(c => scriptClientTeams.FirstOrDefault(sc => sc.Num == c.Num)?.CurrentTeam != IW4MAdmin.Plugins.Stats.IW4Info.Team.Spectator)
.Where(c => c.CurrentTeam != scriptClientTeams.FirstOrDefault(p => p.Num == c.Num)?.CurrentTeam)
.OrderByDescending(c => c.Stats.Performance)
.ToList();
// var alliesTeam = scriptClientTeams
// .Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies)
// .Where(c => activeClients.Count(t => t.Num == c.Num) == 0)
// .ToList();
var alliesTeam = scriptClientTeams
.Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies)
.Where(c => activeClients.Count(t => t.Num == c.Num) == 0)
.ToList();
// var axisTeam = scriptClientTeams
// .Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis)
// .Where(c => activeClients.Count(t => t.Num == c.Num) == 0)
// .ToList();
var axisTeam = scriptClientTeams
.Where(c => c.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis)
.Where(c => activeClients.Count(t => t.Num == c.Num) == 0)
.ToList();
// while (activeClients.Count() > 0)
// {
// int teamSizeDifference = alliesTeam.Count - axisTeam.Count;
// double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 -
// axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0;
while (activeClients.Count() > 0)
{
int teamSizeDifference = alliesTeam.Count - axisTeam.Count;
double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 -
axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0;
// if (teamSizeDifference == 0)
// {
// if (performanceDisparity == 0)
// {
// alliesTeam.Add(activeClients.First());
// activeClients.RemoveAt(0);
// }
// else
// {
// if (performanceDisparity > 0)
// {
// axisTeam.Add(activeClients.First());
// activeClients.RemoveAt(0);
// }
// else
// {
// alliesTeam.Add(activeClients.First());
// activeClients.RemoveAt(0);
// }
// }
// }
// else if (teamSizeDifference > 0)
// {
// if (performanceDisparity > 0)
// {
// axisTeam.Add(activeClients.First());
// activeClients.RemoveAt(0);
// }
if (teamSizeDifference == 0)
{
if (performanceDisparity == 0)
{
alliesTeam.Add(activeClients.First());
activeClients.RemoveAt(0);
}
else
{
if (performanceDisparity > 0)
{
axisTeam.Add(activeClients.First());
activeClients.RemoveAt(0);
}
else
{
alliesTeam.Add(activeClients.First());
activeClients.RemoveAt(0);
}
}
}
else if (teamSizeDifference > 0)
{
if (performanceDisparity > 0)
{
axisTeam.Add(activeClients.First());
activeClients.RemoveAt(0);
}
// else
// {
// axisTeam.Add(activeClients.Last());
// activeClients.RemoveAt(activeClients.Count - 1);
// }
// }
// else
// {
// if (performanceDisparity > 0)
// {
// alliesTeam.Add(activeClients.First());
// activeClients.RemoveAt(0);
// }
else
{
axisTeam.Add(activeClients.Last());
activeClients.RemoveAt(activeClients.Count - 1);
}
}
else
{
if (performanceDisparity > 0)
{
alliesTeam.Add(activeClients.First());
activeClients.RemoveAt(0);
}
// else
// {
// alliesTeam.Add(activeClients.Last());
// activeClients.RemoveAt(activeClients.Count - 1);
// }
// }
// }
else
{
alliesTeam.Add(activeClients.Last());
activeClients.RemoveAt(activeClients.Count - 1);
}
}
}
// alliesTeam = alliesTeam.OrderByDescending(t => t.Stats.Performance)
// .ToList();
alliesTeam = alliesTeam.OrderByDescending(t => t.Stats.Performance)
.ToList();
// axisTeam = axisTeam.OrderByDescending(t => t.Stats.Performance)
// .ToList();
axisTeam = axisTeam.OrderByDescending(t => t.Stats.Performance)
.ToList();
// while (Math.Abs(alliesTeam.Count - axisTeam.Count) > 1)
// {
// int teamSizeDifference = alliesTeam.Count - axisTeam.Count;
// double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 -
// axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0;
while (Math.Abs(alliesTeam.Count - axisTeam.Count) > 1)
{
int teamSizeDifference = alliesTeam.Count - axisTeam.Count;
double performanceDisparity = alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0 -
axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0;
// if (teamSizeDifference > 0)
// {
// if (performanceDisparity > 0)
// {
// axisTeam.Add(alliesTeam.First());
// alliesTeam.RemoveAt(0);
// }
if (teamSizeDifference > 0)
{
if (performanceDisparity > 0)
{
axisTeam.Add(alliesTeam.First());
alliesTeam.RemoveAt(0);
}
// else
// {
// axisTeam.Add(alliesTeam.Last());
// alliesTeam.RemoveAt(axisTeam.Count - 1);
// }
// }
else
{
axisTeam.Add(alliesTeam.Last());
alliesTeam.RemoveAt(axisTeam.Count - 1);
}
}
// else
// {
// if (performanceDisparity > 0)
// {
// alliesTeam.Add(axisTeam.Last());
// axisTeam.RemoveAt(axisTeam.Count - 1);
// }
else
{
if (performanceDisparity > 0)
{
alliesTeam.Add(axisTeam.Last());
axisTeam.RemoveAt(axisTeam.Count - 1);
}
// else
// {
// alliesTeam.Add(axisTeam.First());
// axisTeam.RemoveAt(0);
// }
// }
// }
else
{
alliesTeam.Add(axisTeam.First());
axisTeam.RemoveAt(0);
}
}
}
// foreach (var assignment in alliesTeam)
// {
// teamAssignments.Add($"{assignment.Num},2");
// assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies;
// }
// foreach (var assignment in axisTeam)
// {
// teamAssignments.Add($"{assignment.Num},3");
// assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis;
// }
foreach (var assignment in alliesTeam)
{
teamAssignments.Add($"{assignment.Num},2");
assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies;
}
foreach (var assignment in axisTeam)
{
teamAssignments.Add($"{assignment.Num},3");
assignment.Stats.Team = IW4MAdmin.Plugins.Stats.IW4Info.Team.Axis;
}
// if (alliesTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0 &&
// axisTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0)
// {
// await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]);
// return;
// }
if (alliesTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0 &&
axisTeam.Count(ac => scriptClientTeams.First(sc => sc.Num == ac.Num).CurrentTeam != ac.CurrentTeam) == 0)
{
await E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]);
return;
}
// if (E.Origin?.Level > Player.Permission.Administrator)
// {
// await E.Origin.Tell($"Allies Elo: {(alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0)}");
// await E.Origin.Tell($"Axis Elo: {(axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0)}");
// }
if (E.Origin?.Level > Player.Permission.Administrator)
{
await E.Origin.Tell($"Allies Elo: {(alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0)}");
await E.Origin.Tell($"Axis Elo: {(axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0)}");
}
// string args = string.Join(",", teamAssignments);
// await E.Owner.ExecuteCommandAsync($"sv_iw4madmin_command \"balance:{args}\"");
// await E.Origin.Tell("Balance command sent");
// }
// }
//}
string args = string.Join(",", teamAssignments);
await E.Owner.ExecuteCommandAsync($"sv_iw4madmin_command \"balance:{args}\"");
await E.Origin.Tell("Balance command sent");
}
}
}

View File

@ -1,6 +1,7 @@
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
@ -36,6 +37,15 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
var objectionalWords = Settings.Configuration().OffensiveWords;
bool containsObjectionalWord = objectionalWords.FirstOrDefault(w => E.Origin.Name.ToLower().Contains(w)) != null;
// we want to run regex against it just incase
if (!containsObjectionalWord)
{
foreach (string word in objectionalWords)
{
containsObjectionalWord |= Regex.IsMatch(E.Origin.Name.ToLower(), word);
}
}
if (containsObjectionalWord)
{
await E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
@ -56,7 +66,18 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
if (E.Type == GameEvent.EventType.Say)
{
var objectionalWords = Settings.Configuration().OffensiveWords;
bool containsObjectionalWord = objectionalWords.FirstOrDefault(w => E.Data.ToLower().Contains(w)) != null;
bool containsObjectionalWord = false;
foreach (string word in objectionalWords)
{
containsObjectionalWord |= Regex.IsMatch(E.Origin.Name.ToLower(), word);
// break out early because there's at least one objectional word
if (containsObjectionalWord)
{
break;
}
}
if (containsObjectionalWord)
{

View File

@ -1,14 +1,14 @@
const plugin = {
var plugin = {
author: 'RaidMax',
version: 1.0,
name: 'VPN Kick Plugin',
name: 'VPN Detection Plugin',
manager: null,
logger: null,
vpnExceptionIds: [],
checkForVpn(origin) {
let exempt = false;
checkForVpn: function (origin) {
var exempt = false;
// prevent players that are exempt from being kicked
this.vpnExceptionIds.forEach(function (id) {
if (id === origin.ClientId) {
@ -21,43 +21,46 @@ const plugin = {
return;
}
let usingVPN = false;
var usingVPN = false;
try {
let cl = new System.Net.Http.HttpClient();
let re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result;
let co = re.Content;
let parsedJSON = JSON.parse(co.ReadAsStringAsync().Result);
//co.Dispose();
//re.Dispose();
//cl.Dispose();
usingVPN = parsedJSON['success'] && parsedJSON['proxy'];
var cl = new System.Net.Http.HttpClient();
var re = cl.GetAsync('https://api.xdefcon.com/proxy/check/?ip=' + origin.IPAddressString).Result;
var co = re.Content;
var parsedJSON = JSON.parse(co.ReadAsStringAsync().Result);
// todo: does this work as expected now?
co.Dispose();
re.Dispose();
cl.Dispose();
usingVPN = parsedJSON.success && parsedJSON.proxy;
} catch (e) {
this.logger.WriteError(e.message);
}
if (usingVPN) {
this.logger.WriteInfo(origin + ' is using a VPN (' + origin.IPAddressString + ')');
let library = importNamespace('SharedLibraryCore');
let kickOrigin = new library.Objects.Player();
var library = importNamespace('SharedLibraryCore');
var kickOrigin = new library.Objects.Player();
kickOrigin.ClientId = 1;
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], kickOrigin);
}
},
onEventAsync(gameEvent, server) {
onEventAsync: function (gameEvent, server) {
// connect event
if (gameEvent.Type === 3) {
this.checkForVpn(gameEvent.Origin);
}
},
onLoadAsync(manager) {
onLoadAsync: function (manager) {
this.manager = manager;
this.logger = manager.GetLogger();
},
onUnloadAsync() { },
onUnloadAsync: function () {
},
onTickAsync(server) { }
onTickAsync: function (server) {
}
};

View File

@ -43,15 +43,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
context.ChangeTracker.AutoDetectChangesEnabled = false;
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1);
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
var iqClientRatings = (from rating in context.Set<EFRating>()
#if DEBUG == false
where rating.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime
#endif
where rating.RatingHistory.Client.Level != Player.Permission.Banned
where rating.RatingHistory.Client.LastConnection > thirtyDaysAgo
where rating.Newest
where rating.ServerId == null
where rating.RatingHistory.Client.LastConnection > fifteenDaysAgo
where rating.RatingHistory.Client.Level != Player.Permission.Banned
where rating.Newest
where rating.ActivityAmount >= Plugin.Config.Configuration().TopPlayersMinPlayTime
orderby rating.Performance descending
select new
{
@ -67,7 +65,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var clientRatings = await iqClientRatings.ToListAsync();
var clientIds = clientRatings.GroupBy(r => r.ClientId).Select(r => r.First().ClientId).ToList();
var clientIds = clientRatings
.GroupBy(r => r.ClientId)
.Select(r => r.First().ClientId)
.ToList();
var iqStatsInfo = (from stat in context.Set<EFClientStatistics>()
where clientIds.Contains(stat.ClientId)
@ -324,7 +325,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
/// </summary>
/// <returns></returns>
public async Task AddScriptHit(bool isDamage, DateTime time, Player attacker, Player victim, int serverId, string map, string hitLoc, string type,
string damage, string weapon, string killOrigin, string deathOrigin, string viewAngles, string offset, string isKillstreakKill, string Ads, string fraction, string snapAngles)
string damage, string weapon, string killOrigin, string deathOrigin, string viewAngles, string offset, string isKillstreakKill, string Ads,
string fraction, string visibilityPercentage, string snapAngles)
{
var statsSvc = ContextThreads[serverId];
Vector3 vDeathOrigin = null;
@ -381,6 +383,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
IsKillstreakKill = isKillstreakKill[0] != '0',
AdsPercent = float.Parse(Ads),
Fraction = double.Parse(fraction),
VisibilityPercentage = double.Parse(visibilityPercentage),
IsKill = !isDamage,
AnglesList = snapshotAngles
};
@ -569,9 +572,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}
// update their performance
//#if !DEBUG
#if !DEBUG
if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 2.5)
//#endif
#endif
{
await UpdateStatHistory(attacker, attackerStats);
attackerStats.LastStatHistoryUpdate = DateTime.UtcNow;
@ -631,13 +634,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
ctx.Update(clientHistory);
}
var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1);
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
// get the client ranking for the current server
int individualClientRanking = await ctx.Set<EFRating>()
.Where(c => c.ServerId == clientStats.ServerId)
.Where(r => r.RatingHistory.Client.LastConnection > fifteenDaysAgo)
.Where(r => r.RatingHistory.Client.Level != Player.Permission.Banned)
.Where(r => r.ActivityAmount > Plugin.Config.Configuration().TopPlayersMinPlayTime)
.Where(r => r.RatingHistory.Client.LastConnection > thirtyDaysAgo)
.Where(c => c.RatingHistory.ClientId != client.ClientId)
.Where(r => r.Newest)
.Where(c => c.Performance > clientStats.Performance)
@ -689,11 +692,11 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}
int overallClientRanking = await ctx.Set<EFRating>()
.Where(r => r.ServerId == null)
.Where(r => r.RatingHistory.ClientId != client.ClientId)
.Where(r => r.RatingHistory.Client.LastConnection > fifteenDaysAgo)
.Where(r => r.RatingHistory.Client.Level != Player.Permission.Banned)
.Where(r => r.ActivityAmount > Plugin.Config.Configuration().TopPlayersMinPlayTime)
.Where(r => r.RatingHistory.Client.LastConnection > thirtyDaysAgo)
.Where(r => r.RatingHistory.ClientId != client.ClientId)
.Where(r => r.ServerId == null)
.Where(r => r.Newest)
.Where(r => r.Performance > performanceAverage)
.CountAsync() + 1;
@ -730,7 +733,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
await ctx.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException e)
// this can happen when the client disconnects without any stat changes
catch (DbUpdateConcurrencyException)
{
}

View File

@ -31,6 +31,7 @@ namespace IW4MAdmin.Plugins.Stats.Models
public DateTime When { get; set; }
public double Fraction { get; set; }
public bool IsKill { get; set; }
public double VisibilityPercentage { get; set; }
// http://wiki.modsrepository.com/index.php?title=Call_of_Duty_5:_Gameplay_standards for conversion to meters
[NotMapped]
public double Distance => Vector3.Distance(KillOrigin, DeathOrigin) * 0.0254;

View File

@ -76,7 +76,7 @@ namespace IW4MAdmin.Plugins.Stats
if (killInfo.Length >= 14)
{
await Manager.AddScriptHit(false, E.Time, 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], killInfo[13], killInfo[14]);
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
}
break;
case GameEvent.EventType.Kill:
@ -92,7 +92,7 @@ namespace IW4MAdmin.Plugins.Stats
if (killInfo.Length >= 14)
{
await Manager.AddScriptHit(true, E.Time, 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], killInfo[13], killInfo[14]);
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
}
break;
}

View File

@ -0,0 +1,55 @@
using IW4MAdmin.Application;
using SharedLibraryCore;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;
namespace Tests
{
[Collection("ManagerCollection")]
public class PluginTests
{
readonly ApplicationManager Manager;
public PluginTests(ManagerFixture fixture)
{
Manager = fixture.Manager;
}
[Fact]
public void ClientSayObjectionalWordShouldWarn()
{
var e = new GameEvent()
{
Type = GameEvent.EventType.Connect,
Origin = new Player()
{
Name = $"Player1",
NetworkId = 1,
ClientNumber = 1
},
Owner = Manager.GetServers()[0]
};
Manager.GetEventHandler().AddEvent(e);
e.OnProcessed.Wait();
var client = Manager.GetServers()[0].Players[0];
e = new GameEvent()
{
Type = GameEvent.EventType.Say,
Origin = client,
Data = "nigger",
Owner = client.CurrentServer
};
Manager.GetEventHandler().AddEvent(e);
e.OnProcessed.Wait();
Assert.True(client.Warnings == 1, "client wasn't warned for objectional language");
}
}
}

View File

@ -11,6 +11,9 @@ Latest binary builds are always available at https://raidmax.org/IW4MAdmin
* [.NET Core 2.1 Runtime](https://www.microsoft.com/net/download) *or newer*
1. Extract `IW4MAdmin-<version>.zip`
2. Run `StartIW4MAdmin.cmd`
### Help
Feel free to join the **IW4MAdmin** [Discord](https://discord.gg/ZZFK5p3)
If you come across an issue, bug, or feature request please post an [issue](https://github.com/RaidMax/IW4M-Admin/issues)
___
### Configuration
@ -30,10 +33,6 @@ When **IW4MAdmin** is launched for the _first time_, you will be prompted to set
* Shows a prefix to every message send by **IW4MAdmin** -- `[Admin] message`
* _This feature requires you specify a custom say name_
`Enable client VPNs`
* Allow clients to use a [VPN](https://en.wikipedia.org/wiki/Virtual_private_network)
* _This feature requires an active api key on [iphub.info](https://iphub.info/)_
`Enable social link`
* Shows a link to your community's social media/website on the webfront
@ -82,9 +81,6 @@ If you wish to further customize your experience of **IW4MAdmin**, the following
`RConPollRate`
* Specifies (in milliseconds) how often to poll each server for updates
`VpnExceptionIds`
* Specifies the list of `Client IDs` exempt from the VPN check (if enabled)
`Servers`
* Specifies the list of servers **IW4MAdmin** will monitor
* `IPAddress`
@ -228,6 +224,9 @@ ___
#### Stats
- This plugin calculates basic player performance, skill approximation, and kill/death ratio
- Skill is an number derived from an algorithmic processing of a player's Kill Death Ratio (KDR) and Score per Minute (SPM).
- Elo Rating is based off of the number of encounters a player wins.
- Performance is the average of Skill + Elo Rating
**Commands added by this plugin**
@ -238,7 +237,7 @@ ___
|topstats|ts|view the top 5 players on this server|False|!ts |User|
|mostplayed|mp|view the top 5 dedicated players on the server|False|!mp |User|
- To qualify for top stats, a client must have played for at least `1 hour` and connected within the past `30 days`.
- To qualify for top stats, a client must have played for at least `3 hours` and connected within the past `15 days`.
#### Login
- This plugin deters GUID spoofing by requiring privileged users to login with their password before executing commands
@ -254,6 +253,14 @@ ___
- This plugin warns and kicks players for using profanity
- Profane words and warning message can be specified in `ProfanityDetermentSettings.json`
- If a client's name contains a word listed in the settings, they will immediately be kicked
####IW4 Script Commands
- This plugin provides additional integration to IW4x
- In order to take advantage of it, copy the `userraw` folder into your IW4x server directory
####VPN Detection [Script Plugin]
- This plugin detects if a client is using a VPN and kicks them if they are
- To disable this plugin, delete `Plugins\VPNDetection.js`
___
### Webfront
`Home`
@ -274,34 +281,35 @@ ___
`Web Console`
* Allows logged in privileged users to execute commands as if they are in-game
---
### Extending Plugins
#### Code
IW4Madmin functionality can be extended by writing additional plugins in C#.
Each class library must implement the `IPlugin` interface.
See the existing plugins for examples.
#### JavaScript
IW4MAdmin functionality can be extended using JavaScript.
The JavaScript parser supports [some](https://github.com/sebastienros/jint/issues/343) of ECMAScript 6's new features.
The JavaScript parser supports [ECMA 5.1](https://ecma-international.org/ecma-262/5.1/) standards.
#### Plugin Object Template
In order to be properly parsed by the JavaScript engine, every plugin must conform to the following template.
```js
const plugin = {
var plugin = {
author: 'YourHandle',
version: 1.0,
name: 'Sample JavaScript Plugin',
onEventAsync(gameEvent, server) {
onEventAsync: function (gameEvent, server) {
},
onLoadAsync(manager) {
onLoadAsync: function (manager) {
},
onUnloadAsync() {
onUnloadAsync: function () {
},
onTickAsync(server) {
}
onTickAsync: function (server) {
}
};
```
#### Required Properties
- `author` &mdash; [string] Author of the plugin (usually your name or online name/alias)

View File

@ -10,19 +10,16 @@ namespace SharedLibraryCore.Configuration
public bool EnableWebFront { get; set; }
public bool EnableMultipleOwners { get; set; }
public bool EnableSteppedHierarchy { get; set; }
public bool EnableClientVPNs { get; set; }
public bool EnableSocialLink { get; set; }
public bool EnableCustomSayName { get; set; }
public string CustomSayName { get; set; }
public string SocialLinkAddress { get; set; }
public string SocialLinkTitle { get; set; }
public string IPHubAPIKey { get; set; }
public string WebfrontBindUrl { get; set; }
public string CustomParserEncoding { get; set; }
public string CustomLocale { get; set; }
public string ConnectionString { get; set; }
public int RConPollRate { get; set; } = 5000;
public List<int> VpnExceptionIds { get; set; }
public string Id { get; set; }
public List<ServerConfiguration> Servers { get; set; }
public int AutoMessagePeriod { get; set; }
@ -43,16 +40,11 @@ namespace SharedLibraryCore.Configuration
bool useCustomParserEncoding = Utilities.PromptBool(loc["SETUP_USE_CUSTOMENCODING"]);
CustomParserEncoding = useCustomParserEncoding ? Utilities.PromptString(loc["SETUP_ENCODING_STRING"]) : "windows-1252";
WebfrontBindUrl = "http://127.0.0.1:1624";
WebfrontBindUrl = "http://0.0.0.0:1624";
if (EnableCustomSayName)
CustomSayName = Utilities.PromptString(loc["SETUP_SAY_NAME"]);
EnableClientVPNs = Utilities.PromptBool(loc["SETUP_ENABLE_VPNS"]);
if (!EnableClientVPNs)
IPHubAPIKey = Utilities.PromptString(loc["SETUP_IPHUB_KEY"]);
EnableSocialLink = Utilities.PromptBool(loc["SETUP_DISPLAY_SOCIAL"]);
if (EnableSocialLink)
@ -60,7 +52,7 @@ namespace SharedLibraryCore.Configuration
SocialLinkTitle = Utilities.PromptString(loc["SETUP_SOCIAL_TITLE"]);
SocialLinkAddress = Utilities.PromptString(loc["SETUP_SOCIAL_LINK"]);
}
VpnExceptionIds = new List<int>();
RConPollRate = 5000;
return this;

View File

@ -13,6 +13,6 @@ namespace SharedLibraryCore.Interfaces
/// Add a game event event to the queue to be processed
/// </summary>
/// <param name="gameEvent">Game event</param>
bool AddEvent(GameEvent gameEvent);
void AddEvent(GameEvent gameEvent);
}
}

View File

@ -0,0 +1,665 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using SharedLibraryCore.Database;
namespace SharedLibraryCore.Migrations
{
[DbContext(typeof(DatabaseContext))]
[Migration("20180904154622_AddVisibilityPercentage")]
partial class AddVisibilityPercentage
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.2-rtm-30932");
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.Property<int>("SnapshotId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<int>("CurrentSessionLength");
b.Property<double>("CurrentStrain");
b.Property<int?>("CurrentViewAngleVector3Id");
b.Property<int>("Deaths");
b.Property<double>("Distance");
b.Property<double>("EloRating");
b.Property<int?>("HitDestinationVector3Id");
b.Property<int>("HitLocation");
b.Property<int?>("HitOriginVector3Id");
b.Property<int>("HitType");
b.Property<int>("Hits");
b.Property<int>("Kills");
b.Property<int?>("LastStrainAngleVector3Id");
b.Property<double>("SessionAngleOffset");
b.Property<double>("SessionSPM");
b.Property<int>("SessionScore");
b.Property<double>("StrainAngleBetween");
b.Property<int>("TimeSinceLastEvent");
b.Property<int>("WeaponId");
b.Property<DateTime>("When");
b.HasKey("SnapshotId");
b.HasIndex("ClientId");
b.HasIndex("CurrentViewAngleVector3Id");
b.HasIndex("HitDestinationVector3Id");
b.HasIndex("HitOriginVector3Id");
b.HasIndex("LastStrainAngleVector3Id");
b.ToTable("EFACSnapshot");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.Property<long>("KillId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AttackerId");
b.Property<int>("Damage");
b.Property<int?>("DeathOriginVector3Id");
b.Property<int>("DeathType");
b.Property<double>("Fraction");
b.Property<int>("HitLoc");
b.Property<bool>("IsKill");
b.Property<int?>("KillOriginVector3Id");
b.Property<int>("Map");
b.Property<int>("ServerId");
b.Property<int>("VictimId");
b.Property<int?>("ViewAnglesVector3Id");
b.Property<double>("VisibilityPercentage");
b.Property<int>("Weapon");
b.Property<DateTime>("When");
b.HasKey("KillId");
b.HasIndex("AttackerId");
b.HasIndex("DeathOriginVector3Id");
b.HasIndex("KillOriginVector3Id");
b.HasIndex("ServerId");
b.HasIndex("VictimId");
b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.Property<long>("MessageId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<string>("Message");
b.Property<int>("ServerId");
b.Property<DateTime>("TimeSent");
b.HasKey("MessageId");
b.HasIndex("ClientId");
b.HasIndex("ServerId");
b.ToTable("EFClientMessages");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.Property<int>("RatingHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.HasKey("RatingHistoryId");
b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.Property<int>("ClientId");
b.Property<int>("ServerId");
b.Property<bool>("Active");
b.Property<int>("Deaths");
b.Property<double>("EloRating");
b.Property<int>("Kills");
b.Property<double>("MaxStrain");
b.Property<double>("RollingWeightedKDR");
b.Property<double>("SPM");
b.Property<double>("Skill");
b.Property<int>("TimePlayed");
b.HasKey("ClientId", "ServerId");
b.HasIndex("ServerId");
b.ToTable("EFClientStatistics");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.Property<int>("HitLocationCountId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId")
.HasColumnName("EFClientStatistics_ClientId");
b.Property<int>("HitCount");
b.Property<float>("HitOffsetAverage");
b.Property<int>("Location");
b.Property<float>("MaxAngleDistance");
b.Property<int>("ServerId")
.HasColumnName("EFClientStatistics_ServerId");
b.HasKey("HitLocationCountId");
b.HasIndex("ServerId");
b.HasIndex("ClientId", "ServerId");
b.ToTable("EFHitLocationCounts");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.Property<int>("RatingId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ActivityAmount");
b.Property<bool>("Newest");
b.Property<double>("Performance");
b.Property<int>("Ranking");
b.Property<int>("RatingHistoryId");
b.Property<int?>("ServerId");
b.HasKey("RatingId");
b.HasIndex("RatingHistoryId");
b.HasIndex("ServerId");
b.ToTable("EFRating");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b =>
{
b.Property<int>("ServerId");
b.Property<bool>("Active");
b.Property<int>("Port");
b.HasKey("ServerId");
b.ToTable("EFServers");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.Property<int>("StatisticId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ServerId");
b.Property<long>("TotalKills");
b.Property<long>("TotalPlayTime");
b.HasKey("StatisticId");
b.HasIndex("ServerId");
b.ToTable("EFServerStatistics");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.Property<int>("AliasId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<DateTime>("DateAdded");
b.Property<int>("IPAddress");
b.Property<int>("LinkId");
b.Property<string>("Name")
.IsRequired();
b.HasKey("AliasId");
b.HasIndex("IPAddress");
b.HasIndex("LinkId");
b.ToTable("EFAlias");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b =>
{
b.Property<int>("AliasLinkId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFChangeHistory", b =>
{
b.Property<int>("ChangeHistoryId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("Comment")
.HasMaxLength(128);
b.Property<int>("OriginEntityId");
b.Property<int>("TargetEntityId");
b.Property<DateTime>("TimeChanged");
b.Property<int>("TypeOfChange");
b.HasKey("ChangeHistoryId");
b.ToTable("EFChangeHistory");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.Property<int>("ClientId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("AliasLinkId");
b.Property<int>("Connections");
b.Property<int>("CurrentAliasId");
b.Property<DateTime>("FirstConnection");
b.Property<DateTime>("LastConnection");
b.Property<int>("Level");
b.Property<bool>("Masked");
b.Property<long>("NetworkId");
b.Property<string>("Password");
b.Property<string>("PasswordSalt");
b.Property<int>("TotalConnectionTime");
b.HasKey("ClientId");
b.HasIndex("AliasLinkId");
b.HasIndex("CurrentAliasId");
b.HasIndex("NetworkId")
.IsUnique();
b.ToTable("EFClients");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.Property<int>("MetaId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<int>("ClientId");
b.Property<DateTime>("Created");
b.Property<string>("Extra");
b.Property<string>("Key")
.IsRequired();
b.Property<DateTime>("Updated");
b.Property<string>("Value")
.IsRequired();
b.HasKey("MetaId");
b.HasIndex("ClientId");
b.ToTable("EFMeta");
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.Property<int>("PenaltyId")
.ValueGeneratedOnAdd();
b.Property<bool>("Active");
b.Property<string>("AutomatedOffense");
b.Property<DateTime>("Expires");
b.Property<int>("LinkId");
b.Property<int>("OffenderId");
b.Property<string>("Offense")
.IsRequired();
b.Property<int>("PunisherId");
b.Property<int>("Type");
b.Property<DateTime>("When");
b.HasKey("PenaltyId");
b.HasIndex("LinkId");
b.HasIndex("OffenderId");
b.HasIndex("PunisherId");
b.ToTable("EFPenalties");
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.Property<int>("Vector3Id")
.ValueGeneratedOnAdd();
b.Property<int?>("EFACSnapshotSnapshotId");
b.Property<float>("X");
b.Property<float>("Y");
b.Property<float>("Z");
b.HasKey("Vector3Id");
b.ToTable("Vector3");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "CurrentViewAngle")
.WithMany()
.HasForeignKey("CurrentViewAngleVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitDestination")
.WithMany()
.HasForeignKey("HitDestinationVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "HitOrigin")
.WithMany()
.HasForeignKey("HitOriginVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "LastStrainAngle")
.WithMany()
.HasForeignKey("LastStrainAngleVector3Id");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker")
.WithMany()
.HasForeignKey("AttackerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin")
.WithMany()
.HasForeignKey("DeathOriginVector3Id");
b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin")
.WithMany()
.HasForeignKey("KillOriginVector3Id");
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim")
.WithMany()
.HasForeignKey("VictimId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles")
.WithMany()
.HasForeignKey("ViewAnglesVector3Id");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics")
.WithMany("HitLocations")
.HasForeignKey("ClientId", "ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientRatingHistory", "RatingHistory")
.WithMany("Ratings")
.HasForeignKey("RatingHistoryId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId");
});
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("Children")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink")
.WithMany()
.HasForeignKey("AliasLinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias")
.WithMany()
.HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFMeta", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client")
.WithMany("Meta")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b =>
{
b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties")
.HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender")
.WithMany("ReceivedPenalties")
.HasForeignKey("OffenderId")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher")
.WithMany("AdministeredPenalties")
.HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b =>
{
b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot")
.WithMany("PredictedViewAngles")
.HasForeignKey("EFACSnapshotSnapshotId");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class AddVisibilityPercentage : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<double>(
name: "VisibilityPercentage",
table: "EFClientKills",
nullable: false,
defaultValue: 0.0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "VisibilityPercentage",
table: "EFClientKills");
}
}
}

View File

@ -111,6 +111,8 @@ namespace SharedLibraryCore.Migrations
b.Property<int?>("ViewAnglesVector3Id");
b.Property<double>("VisibilityPercentage");
b.Property<int>("Weapon");
b.Property<DateTime>("When");

View File

@ -17,19 +17,14 @@ namespace SharedLibraryCore.RCon
public int BufferSize { get; private set; }
public byte[] Buffer { get; private set; }
private readonly StringBuilder sb;
public StringBuilder ResponseString
{
get => sb;
}
public StringBuilder ResponseString { get; }
public ConnectionState(Socket cl)
{
BufferSize = 8192;
Buffer = new byte[BufferSize];
Client = cl;
sb = new StringBuilder();
ResponseString = new StringBuilder();
}
}

View File

@ -13,6 +13,6 @@ namespace SharedLibraryCore.RCon
}
public static char SeperatorChar = (char)int.Parse("0a", System.Globalization.NumberStyles.AllowHexSpecifier);
public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 5);
public static readonly TimeSpan SocketTimeout = new TimeSpan(0, 0, 10);
}
}

View File

@ -81,11 +81,14 @@ namespace SharedLibraryCore
}
public Task OnEventAsync(GameEvent E, Server S)
{
lock (ScriptEngine)
{
ScriptEngine.SetValue("_gameEvent", E);
ScriptEngine.SetValue("_server", S);
return Task.FromResult(ScriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue());
}
}
public Task OnLoadAsync(IManager manager)
{

View File

@ -117,7 +117,7 @@ namespace SharedLibraryCore
public async Task Broadcast(String Message)
{
#if DEBUG == false
string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Say, Message);
string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Say, $"{(CustomSayEnabled ? $"{CustomSayName}: " : "")}{Message}");
#else
Logger.WriteVerbose(Message.StripColors());
#endif
@ -145,7 +145,7 @@ namespace SharedLibraryCore
public async Task Tell(String Message, Player Target)
{
#if !DEBUG
string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Tell, Target.ClientNumber, Message);
string formattedMessage = String.Format(RconParser.GetCommandPrefixes().Tell, Target.ClientNumber, $"{(CustomSayEnabled ? $"{CustomSayName}: " : "")}{Message}");
if (Target.ClientNumber > -1 && Message.Length > 0 && Target.Level != Player.Permission.Console)
await this.ExecuteCommandAsync(formattedMessage);
#else
@ -160,9 +160,11 @@ namespace SharedLibraryCore
Console.ForegroundColor = ConsoleColor.Gray;
}
// prevent this from queueing up too many command responses
if (CommandResult.Count > 15)
CommandResult.RemoveAt(0);
// it was a remote command so we need to add it to the command result queue
if (Target.ClientNumber < 0)
{
CommandResult.Add(new CommandResponseInfo()

View File

@ -17,7 +17,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Jint" Version="3.0.0-beta-1249" />
<PackageReference Include="Jint" Version="2.11.58" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.2" />

View File

@ -12,6 +12,26 @@ init()
level waittill("prematch_over");
level.callbackPlayerKilled = ::Callback_PlayerKilled;
level.callbackPlayerDamage = ::Callback_PlayerDamage;
level.playerTags = [];
level.playerTags[0] = "j_head";
level.playerTags[1] = "j_neck";
level.playerTags[2] = "j_spineupper";
level.playerTags[3] = "j_spinelower";
level.playerTags[4] = "j_shoulder_ri";
level.playerTags[5] = "j_shoulder_le";
level.playerTags[6] = "j_elbow_ri";
level.playerTags[7] = "j_spineupper";
level.playerTags[8] = "j_spineupper";
level.playerTags[9] = "j_elbow_le";
level.playerTags[10] = "j_wrist_ri";
level.playerTags[11] = "j_wrist_le";
level.playerTags[12] = "j_hip_ri";
level.playerTags[13] = "j_hip_le";
level.playerTags[14] = "j_knee_ri";
level.playerTags[15] = "j_knee_le";
level.playerTags[16] = "j_ankle_ri";
level.playerTags[17] = "j_ankle_le";
level.playerTags[18] = "j_helmet";
}
@ -112,6 +132,21 @@ waitForAdditionalAngles(logString)
logPrint(logString + ";" + anglesStr + "\n");
}
runVisibilityCheck(attacker, victim)
{
start = attacker getTagOrigin("tag_eye");
traceSucceedCount = 0;
for (i = 0; i < 19; i++)
{
if (sightTracePassed(start, victim getTagOrigin(level.playerTags[i]), false, attacker))
{
traceSucceedCount += 1;
}
}
return traceSucceedCount / 20;
}
vectorScale(vector, scale)
{
return (vector[0] * scale, vector[1] * scale, vector[2] * scale);
@ -134,7 +169,9 @@ Process_Hit(type, attacker, sHitLoc, sMeansOfDeath, iDamage, sWeapon)
end = location;
trace = bulletTrace(start, end, true, _attacker);
logLine = "Script" + type + ";" + _attacker.guid + ";" + victim.guid + ";" + _attacker GetTagOrigin("tag_eye") + ";" + location + ";" + iDamage + ";" + sWeapon + ";" + sHitLoc + ";" + sMeansOfDeath + ";" + _attacker getPlayerAngles() + ";" + gettime() + ";" + isKillstreakKill + ";" + _attacker playerADS() + ";" + trace["fraction"];
playerVisibilityPercentage = runVisibilityCheck(_attacker, victim);
logLine = "Script" + type + ";" + _attacker.guid + ";" + victim.guid + ";" + _attacker GetTagOrigin("tag_eye") + ";" + location + ";" + iDamage + ";" + sWeapon + ";" + sHitLoc + ";" + sMeansOfDeath + ";" + _attacker getPlayerAngles() + ";" + gettime() + ";" + isKillstreakKill + ";" + _attacker playerADS() + ";" + trace["fraction"] + ";" + playerVisibilityPercentage;
attacker thread waitForAdditionalAngles(logLine);
}