the meats

This commit is contained in:
RaidMax 2018-03-06 01:22:19 -06:00
parent 6fa466fdf8
commit 1adf3ceb3c
56 changed files with 1107 additions and 719 deletions

4
.gitignore vendored
View File

@ -1,6 +1,10 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
Detection.cs
DetectionPenaltyResult.cs
Thresholds.cs
# User-specific files
*.suo
*.user

View File

@ -1,90 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{D076ABC9-DDD6-4E30-9584-E45273950902}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Database</RootNamespace>
<AssemblyName>Database</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release-Nightly|AnyCPU'">
<OutputPath>bin\Release-Nightly\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release-Stable|AnyCPU'">
<OutputPath>bin\Release-Stable\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll</HintPath>
</Reference>
<Reference Include="EntityFramework.SqlServerCompact, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.SqlServerCompact.6.2.0\lib\net45\EntityFramework.SqlServerCompact.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="IW4MAdminDatabaseContext.cs" />
<Compile Include="IW4MAdminDatabase.cs" />
<Compile Include="Models\Alias.cs" />
<Compile Include="Models\Client.cs" />
<Compile Include="Models\Penalty.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SharedLibrary\SharedLibrary.csproj">
<Project>{d51eeceb-438a-47da-870f-7d7b41bc24d6}</Project>
<Name>SharedLibrary</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>
if not exist "$(TargetDir)x86" md "$(TargetDir)x86"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\x86\*.*" "$(TargetDir)x86"
if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"</PostBuildEvent>
</PropertyGroup>
</Project>

View File

@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Database.Models;
namespace Database
{
public class IW4MAdminDatabase : SharedLibrary.Interfaces.IDatabase
{
private IW4MAdminDatabaseContext _context;
public IW4MAdminDatabase()
{
_context = new IW4MAdminDatabaseContext();
}
public SharedLibrary.Interfaces.IDatabaseContext GetContext() => _context;
public async Task<Client> AddClient(Client newClient)
{
var client = _context.Clients.Add(newClient);
await _context.SaveChangesAsync();
return client;
}
public Client GetClient(int clientID) => _context.Clients.SingleOrDefault(c => c.ClientId == clientID);
public Client GetClient(string networkID) => _context.Clients.SingleOrDefault(c => c.NetworkId == networkID);
public IList<Client> GetOwners() => _context.Clients.Where(c => c.Level == SharedLibrary.Player.Permission.Owner).ToList();
public IList<SharedLibrary.Player> GetPlayers(IList<string> networkIDs) => _context.Clients.Where(c => networkIDs.Contains(c.NetworkId)).Select(c => c.ToPlayer()).ToList();
public IList<Penalty> GetPenalties (int clientID) => _context.Penalties.Where(p => p.OffenderId == clientID).ToList();
public IList<SharedLibrary.Player> GetAdmins() => _context.Clients.Where(c => c.Level > SharedLibrary.Player.Permission.Flagged).Select(c => c.ToPlayer()).ToList();
}
}

View File

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Database.Models;
using System.Data.SqlServerCe;
namespace Database
{
public class IW4MAdminDatabaseContext : DbContext, SharedLibrary.Interfaces.IDatabaseContext
{
public DbSet<Client> Clients { get; set; }
public DbSet<Alias> Aliases { get; set; }
public DbSet<Penalty> Penalties { get; set; }
public IW4MAdminDatabaseContext() : base("DefaultConnection") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var instance = System.Data.Entity.SqlServerCompact.SqlCeProviderServices.Instance;
// todo custom load DBSets from plugins
// https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/
base.OnModelCreating(modelBuilder);
}
}
}

View File

@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Database.Models
{
public class Alias
{
[Key]
public int AliasId { get; set; }
public int ClientId { get; set; }
public string Name { get; set; }
public string IP { get; set; }
}
}

View File

@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Database.Models
{
public class Client
{
[Key]
public int ClientId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string NetworkId { get; set; }
[Required]
public SharedLibrary.Player.Permission Level { get; set; }
public int Connections { get; set; }
[Required]
public string IPAddress { get; set; }
[Required]
public DateTime LastConnection { get; set; }
public bool Masked { get; set; }
public SharedLibrary.Player ToPlayer()
{
return new SharedLibrary.Player()
{
Name = Name,
Connections = Connections,
DatabaseID = ClientId,
NetworkID = NetworkId,
Level = Level,
IP = IPAddress,
LastConnection = LastConnection,
Masked = Masked
};
}
}
}

View File

@ -1,23 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Database.Models
{
public class Penalty
{
[Key]
public int PenaltyId { get; set; }
public int OffenderId { get; set; }
public Client Offender { get; set; }
public int PunisherId { get; set; }
public Client Punisher { get; set; }
public DateTime When { get; set; }
public DateTime Expires { get; set; }
public SharedLibrary.Penalty.Type Type { get; set; }
}
}

View File

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Database")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Database")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d076abc9-ddd6-4e30-9584-e45273950902")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="6.2.0" targetFramework="net45" />
<package id="EntityFramework.SqlServerCompact" version="6.2.0" targetFramework="net45" />
<package id="Microsoft.SqlServer.Compact" version="4.0.8876.1" targetFramework="net45" />
</packages>

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2036
VisualStudioVersion = 15.0.27004.2006
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatsPlugin", "Plugins\SimpleStats\StatsPlugin.csproj", "{4785AB75-66F3-4391-985D-63A5A049A0FA}"
ProjectSection(ProjectDependencies) = postProject
@ -320,16 +320,16 @@ Global
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|Mixed Platforms.Build.0 = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x64.ActiveCfg = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x64.Build.0 = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x86.ActiveCfg = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x86.Build.0 = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x86.ActiveCfg = Release|x86
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Nightly|x86.Build.0 = Release|x86
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Any CPU.ActiveCfg = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Any CPU.Build.0 = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Mixed Platforms.ActiveCfg = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|Mixed Platforms.Build.0 = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x64.ActiveCfg = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x64.Build.0 = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x86.ActiveCfg = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x86.Build.0 = Release|Any CPU
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x86.ActiveCfg = Release|x86
{65340D7D-5831-406C-ACAD-B13BA634BDE2}.Release-Stable|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -1,4 +1,5 @@
using SharedLibrary.Interfaces;
using SharedLibrary.Objects;
using StatsPlugin.Models;
using System;
using System.Collections.Generic;
@ -30,62 +31,276 @@ namespace StatsPlugin.Cheat
/// </summary>
/// <param name="kill">kill performed by the player</param>
/// <returns>true if detection reached thresholds, false otherwise</returns>
public bool ProcessKill(EFClientKill kill)
public DetectionPenaltyResult ProcessKill(EFClientKill kill)
{
if (kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET && kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET)
return false;
bool thresholdReached = false;
if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET) ||
kill.HitLoc == IW4Info.HitLocation.none)
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Any,
RatioAmount = 0
};
HitLocationCount[kill.HitLoc]++;
Kills++;
AverageKillTime = (AverageKillTime + (DateTime.UtcNow - LastKill).TotalSeconds) / Kills;
if (Kills > Thresholds.LowSampleMinKills)
#region SESSION_RATIOS
if (Kills >= Thresholds.LowSampleMinKills)
{
double marginOfError = Thresholds.GetMarginOfError(Kills);
// determine what the max headshot percentage can be for current number of kills
double lerpAmount = Math.Min(1.0, (Kills - Thresholds.LowSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
double maxHeadshotLerpValue = Thresholds.Lerp( Thresholds.HeadshotRatioThresholdLowSample, Thresholds.HeadshotRatioThresholdHighSample, lerpAmount);
double maxHeadshotLerpValueForFlag = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(2.0), Thresholds.HeadshotRatioThresholdHighSample(2.0), lerpAmount) + marginOfError;
double maxHeadshotLerpValueForBan = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(3.0), Thresholds.HeadshotRatioThresholdHighSample(3.0), lerpAmount) + marginOfError;
// determine what the max bone percentage can be for current number of kills
double maxBoneRatioLerpValue = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample, Thresholds.BoneRatioThresholdHighSample, lerpAmount);
double maxBoneRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(2.25), Thresholds.BoneRatioThresholdHighSample(2.25), lerpAmount) + marginOfError;
double maxBoneRatioLerpValueForBan = Thresholds.Lerp(Thresholds.BoneRatioThresholdLowSample(3.25), Thresholds.BoneRatioThresholdHighSample(3.25), lerpAmount) + marginOfError;
// calculate headshot ratio
double headshotRatio = ((HitLocationCount[IW4Info.HitLocation.head] + HitLocationCount[IW4Info.HitLocation.helmet]) / (double)Kills) - marginOfError;
double currentHeadshotRatio = ((HitLocationCount[IW4Info.HitLocation.head] + HitLocationCount[IW4Info.HitLocation.helmet]) / (double)Kills);
// calculate maximum bone
double maximumBoneRatio = (HitLocationCount.Values.Select(v => v / (double)Kills).Max()) - marginOfError;
double currentMaxBoneRatio = (HitLocationCount.Values.Select(v => v / (double)Kills).Max());
if (headshotRatio > maxHeadshotLerpValue)
var bone = HitLocationCount.FirstOrDefault(b => b.Value == HitLocationCount.Values.Max()).Key;
#region HEADSHOT_RATIO
// flag on headshot
if (currentHeadshotRatio > maxHeadshotLerpValueForFlag)
{
AboveThresholdCount++;
Log.WriteDebug("**Maximum Headshot Ratio Reached**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Kills: {Kills}");
Log.WriteDebug($"**Ratio {headshotRatio}");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValue}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} Count: {kvp.Value}");
Log.WriteDebug(sb.ToString());
Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
thresholdReached = true;
// ban on headshot
if (currentHeadshotRatio > maxHeadshotLerpValueForFlag)
{
AboveThresholdCount++;
Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Kills: {Kills}");
Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = currentHeadshotRatio,
Bone = IW4Info.HitLocation.head,
KillCount = Kills
};
}
else
{
AboveThresholdCount++;
Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Kills: {Kills}");
Log.WriteDebug($"**Ratio {currentHeadshotRatio}");
Log.WriteDebug($"**MaxRatio {maxHeadshotLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = currentHeadshotRatio,
Bone = IW4Info.HitLocation.head,
KillCount = Kills
};
}
}
#endregion
else if (maximumBoneRatio > maxBoneRatioLerpValue)
#region BONE_RATIO
// flag on bone ratio
else if (currentMaxBoneRatio > maxBoneRatioLerpValueForFlag)
{
Log.WriteDebug("**Maximum Bone Ratio Reached**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Kills: {Kills}");
Log.WriteDebug($"**Ratio {maximumBoneRatio}");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValue}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} Count: {kvp.Value}");
Log.WriteDebug(sb.ToString());
thresholdReached = true;
// ban on bone ratio
if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan)
{
Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Kills: {Kills}");
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForBan}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = currentMaxBoneRatio,
Bone = bone,
KillCount = Kills
};
}
else
{
Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Kills: {Kills}");
Log.WriteDebug($"**Ratio {currentMaxBoneRatio}");
Log.WriteDebug($"**MaxRatio {maxBoneRatioLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = currentMaxBoneRatio,
Bone = bone,
KillCount = Kills
};
}
}
#endregion
}
#region CHEST_ABDOMEN_RATIO_SESSION
int chestKills = HitLocationCount[IW4Info.HitLocation.torso_upper];
if (chestKills >= Thresholds.MediumSampleMinKills)
{
double marginOfError = Thresholds.GetMarginOfError(chestKills);
double lerpAmount = Math.Min(1.0, (chestKills - Thresholds.LowSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
// determine max acceptable ratio of chest to abdomen kills
double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(2.25), Thresholds.ChestAbdomenRatioThresholdHighSample(2.25), lerpAmount) + marginOfError;
double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3.25), Thresholds.ChestAbdomenRatioThresholdHighSample(3.25), lerpAmount) + marginOfError;
double currentChestAbdomenRatio = HitLocationCount[IW4Info.HitLocation.torso_upper] / (double)HitLocationCount[IW4Info.HitLocation.torso_lower];
if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag)
{
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestKills >= Thresholds.MediumSampleMinKills + 30)
{
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Chest Kills: {chestKills}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = currentChestAbdomenRatio,
Bone = IW4Info.HitLocation.torso_upper,
KillCount = chestKills
};
}
else
{
Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {kill.AttackerId}");
Log.WriteDebug($"**Chest Kills: {chestKills}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = currentChestAbdomenRatio,
Bone = IW4Info.HitLocation.torso_upper,
KillCount = chestKills
};
}
}
}
#endregion
#endregion
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Any,
RatioAmount = 0
};
}
public DetectionPenaltyResult ProcessTotalRatio(EFClientStatistics stats)
{
int totalChestKills = stats.HitLocations.Single(c => c.Location == IW4Info.HitLocation.left_arm_upper).HitCount;
if (totalChestKills >= 250)
{
double marginOfError = Thresholds.GetMarginOfError(totalChestKills);
double lerpAmount = Math.Min(1.0, (totalChestKills - Thresholds.LowSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
// determine max acceptable ratio of chest to abdomen kills
double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(2.25), Thresholds.ChestAbdomenRatioThresholdHighSample(2.25), lerpAmount) + marginOfError;
double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3.0), Thresholds.ChestAbdomenRatioThresholdHighSample(3.0), lerpAmount) + marginOfError;
double currentChestAbdomenRatio = HitLocationCount[IW4Info.HitLocation.torso_upper] / (double)HitLocationCount[IW4Info.HitLocation.torso_lower];
if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag)
{
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan)
{
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**");
Log.WriteDebug($"ClientId: {stats.ClientId}");
Log.WriteDebug($"**Total Chest Kills: {totalChestKills}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Ban,
RatioAmount = currentChestAbdomenRatio,
Bone = IW4Info.HitLocation.torso_upper,
KillCount = totalChestKills
};
}
else
{
Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**");
Log.WriteDebug($"ClientId: {stats.ClientId}");
Log.WriteDebug($"**Total Chest Kills: {totalChestKills}");
Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
var sb = new StringBuilder();
foreach (var kvp in HitLocationCount)
sb.Append($"HitLocation: {kvp.Key} -> {kvp.Value}\r\n");
Log.WriteDebug(sb.ToString());
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Flag,
RatioAmount = currentChestAbdomenRatio,
Bone = IW4Info.HitLocation.torso_upper,
KillCount = totalChestKills
};
}
}
}
return thresholdReached;
return new DetectionPenaltyResult()
{
Bone = IW4Info.HitLocation.none,
ClientPenalty = Penalty.PenaltyType.Any
};
}
}
}

View File

@ -8,26 +8,30 @@ namespace StatsPlugin.Cheat
{
class Thresholds
{
private const double Deviations = 3.33;
public const double HeadshotRatioThresholdLowSample = HeadshotRatioStandardDeviationLowSample * Deviations + HeadshotRatioMean;
public const double HeadshotRatioThresholdHighSample = HeadshotRatioStandardDeviationHighSample * Deviations + HeadshotRatioMean;
public static double HeadshotRatioThresholdLowSample(double deviations) => HeadshotRatioStandardDeviationLowSample * deviations + HeadshotRatioMean;
public static double HeadshotRatioThresholdHighSample(double deviations) => HeadshotRatioStandardDeviationHighSample * deviations + HeadshotRatioMean;
public const double HeadshotRatioStandardDeviationLowSample = 0.1769994181;
public const double HeadshotRatioStandardDeviationHighSample = 0.03924263235;
//public const double HeadshotRatioMean = 0.09587712258;
public const double HeadshotRatioMean = 0.222;
public const double BoneRatioThresholdLowSample = BoneRatioStandardDeviationLowSample * Deviations + BoneRatioMean;
public const double BoneRatioThresholdHighSample = BoneRatioStandardDeviationHighSample * Deviations + BoneRatioMean;
public static double BoneRatioThresholdLowSample(double deviations) => BoneRatioStandardDeviationLowSample * deviations + BoneRatioMean;
public static double BoneRatioThresholdHighSample(double deviations) => BoneRatioStandardDeviationHighSample * deviations + BoneRatioMean;
public const double BoneRatioStandardDeviationLowSample = 0.1324612879;
public const double BoneRatioStandardDeviationHighSample = 0.0515753935;
public const double BoneRatioMean = 0.3982907516;
public const double BoneRatioMean = 0.4593110238;
public static double ChestAbdomenRatioThresholdLowSample(double deviations) => ChestAbdomenStandardDeviationLowSample * deviations + ChestAbdomenRatioMean;
public static double ChestAbdomenRatioThresholdHighSample(double deviations) => ChestAbdomenStandardDeviationHighSample * deviations + ChestAbdomenRatioMean;
public const double ChestAbdomenStandardDeviationLowSample = 0.2859234644;
public const double ChestAbdomenStandardDeviationHighSample = 0.2195212861;
public const double ChestAbdomenRatioMean = 0.3925617500;
public const int LowSampleMinKills = 15;
public const int MediumSampleMinKills = 30;
public const int HighSampleMinKills = 100;
public const double KillTimeThreshold = 0.2;
public static double GetMarginOfError(int numKills) => 1.645 /(2 * Math.Sqrt(numKills));
public static double GetMarginOfError(int numKills) => 0.98 / Math.Sqrt(numKills);
public static double Lerp(double v1, double v2, double amount)
{

View File

@ -27,6 +27,9 @@ namespace StatsPlugin.Commands
stats.SPM = 0;
stats.Skill = 0;
// reset the cached version
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
// fixme: this doesn't work properly when another context exists
await svc.SaveChangesAsync();
await E.Origin.Tell("Your stats have been reset");

View File

@ -2,6 +2,7 @@
using StatsPlugin.Cheat;
using StatsPlugin.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -10,15 +11,15 @@ using System.Threading.Tasks;
namespace StatsPlugin.Helpers
{
class ServerStats {
public Dictionary<int, EFClientStatistics> PlayerStats { get; set; }
public Dictionary<int, Detection> PlayerDetections { get; set; }
public ConcurrentDictionary<int, EFClientStatistics> PlayerStats { get; set; }
public ConcurrentDictionary<int, Detection> PlayerDetections { get; set; }
public EFServerStatistics ServerStatistics { get; private set; }
public EFServer Server { get; private set; }
public ServerStats(EFServer sv, EFServerStatistics st)
{
PlayerStats = new Dictionary<int, EFClientStatistics>();
PlayerDetections = new Dictionary<int, Detection>();
PlayerStats = new ConcurrentDictionary<int, EFClientStatistics>();
PlayerDetections = new ConcurrentDictionary<int, Detection>();
ServerStatistics = st;
Server = sv;
}

View File

@ -9,6 +9,7 @@ using SharedLibrary.Interfaces;
using SharedLibrary.Objects;
using SharedLibrary.Services;
using StatsPlugin.Models;
using SharedLibrary.Commands;
namespace StatsPlugin.Helpers
{
@ -75,7 +76,7 @@ namespace StatsPlugin.Helpers
catch (Exception e)
{
Log.WriteWarning($"Could not add server to ServerStats - {e.Message}");
Log.WriteError($"Could not add server to ServerStats - {e.Message}");
}
}
@ -84,9 +85,17 @@ namespace StatsPlugin.Helpers
/// </summary>
/// <param name="pl">Player to add/retrieve stats for</param>
/// <returns>EFClientStatistic of specified player</returns>
public EFClientStatistics AddPlayer(Player pl)
public async Task<EFClientStatistics> AddPlayer(Player pl)
{
Log.WriteInfo($"Adding {pl} to stats");
int serverId = pl.CurrentServer.GetHashCode();
if (!Servers.ContainsKey(serverId))
{
Log.WriteError($"[Stats::AddPlayer] Server with id {serverId} could not be found");
return null;
}
var playerStats = Servers[serverId].PlayerStats;
var statsSvc = ContextThreads[serverId];
@ -105,33 +114,50 @@ namespace StatsPlugin.Helpers
ServerId = serverId,
Skill = 0.0,
SPM = 0.0,
HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount()
{
Active = true,
HitCount = 0,
Location = hl
})
.ToList()
};
clientStats = statsSvc.ClientStatSvc.Insert(clientStats);
await statsSvc.ClientStatSvc.SaveChangesAsync();
}
// migration for previous existing stats
else if (clientStats.HitLocations.Count == 0)
{
clientStats.HitLocations = Enum.GetValues(typeof(IW4Info.HitLocation)).OfType<IW4Info.HitLocation>().Select(hl => new EFHitLocationCount()
{
Active = true,
HitCount = 0,
Location = hl
})
.ToList();
await statsSvc.ClientStatSvc.SaveChangesAsync();
}
// set these on connecting
clientStats.LastActive = DateTime.UtcNow;
clientStats.LastStatCalculation = DateTime.UtcNow;
clientStats.SessionScore = pl.Score;
lock (playerStats)
if (playerStats.ContainsKey(pl.ClientId))
{
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);
Log.WriteWarning($"Duplicate ClientId in stats {pl.ClientId} vs {playerStats[pl.ClientId].ClientId}");
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue);
}
playerStats.TryAdd(pl.ClientId, clientStats);
var detectionStats = Servers[serverId].PlayerDetections;
lock (detectionStats)
{
if (detectionStats.ContainsKey(pl.ClientNumber))
detectionStats.Remove(pl.ClientNumber);
detectionStats.Add(pl.ClientNumber, new Cheat.Detection(Log));
}
if (detectionStats.ContainsKey(pl.ClientId))
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue);
detectionStats.TryAdd(pl.ClientId, new Cheat.Detection(Log));
return clientStats;
}
@ -143,19 +169,27 @@ namespace StatsPlugin.Helpers
/// <returns></returns>
public async Task RemovePlayer(Player pl)
{
Log.WriteInfo($"Removing {pl} from stats");
int serverId = pl.CurrentServer.GetHashCode();
var playerStats = Servers[serverId].PlayerStats;
var detectionStats = Servers[serverId].PlayerDetections;
var serverStats = Servers[serverId].ServerStatistics;
var statsSvc = ContextThreads[serverId];
if (!playerStats.ContainsKey(pl.ClientId))
{
Log.WriteWarning($"Client disconnecting not in stats {pl}");
return;
}
// get individual client's stats
var clientStats = playerStats[pl.ClientNumber];
var clientStats = playerStats[pl.ClientId];
// sync their score
clientStats.SessionScore = pl.Score;
// remove the client from the stats dictionary as they're leaving
lock (playerStats)
playerStats.Remove(pl.ClientNumber);
lock (detectionStats)
detectionStats.Remove(pl.ClientNumber);
playerStats.TryRemove(pl.ClientId, out EFClientStatistics removedValue);
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue2);
// sync their stats before they leave
UpdateStats(clientStats);
@ -174,16 +208,7 @@ namespace StatsPlugin.Helpers
public async Task AddScriptKill(Player attacker, Player victim, int serverId, string map, string hitLoc, string type,
string damage, string weapon, string killOrigin, string deathOrigin)
{
await AddStandardKill(attacker, victim);
if (victim == null)
{
Log.WriteError($"[AddScriptKill] Victim is null");
return;
}
var statsSvc = ContextThreads[serverId];
var playerDetection = Servers[serverId].PlayerDetections[attacker.ClientNumber];
var kill = new EFClientKill()
{
@ -200,30 +225,88 @@ namespace StatsPlugin.Helpers
Weapon = ParseEnum<IW4Info.WeaponName>.Get(weapon, typeof(IW4Info.WeaponName))
};
playerDetection.ProcessKill(kill);
if (kill.DeathType == IW4Info.MeansOfDeath.MOD_SUICIDE &&
kill.Damage == 10000)
{
// suicide by switching teams so let's not count it against them
return;
}
return;
await AddStandardKill(attacker, victim);
statsSvc.KillStatsSvc.Insert(kill);
await statsSvc.KillStatsSvc.SaveChangesAsync();
var playerDetection = Servers[serverId].PlayerDetections[attacker.ClientId];
var playerStats = Servers[serverId].PlayerStats[attacker.ClientId];
// increment their hit count
if (kill.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET ||
kill.DeathType == IW4Info.MeansOfDeath.MOD_RIFLE_BULLET)
{
playerStats.HitLocations.Single(hl => hl.Location == kill.HitLoc).HitCount += 1;
await statsSvc.ClientStatSvc.SaveChangesAsync();
}
//statsSvc.KillStatsSvc.Insert(kill);
//await statsSvc.KillStatsSvc.SaveChangesAsync();
async Task executePenalty(Cheat.DetectionPenaltyResult penalty)
{
switch (penalty.ClientPenalty)
{
case Penalty.PenaltyType.Ban:
await attacker.Ban("You appear to be cheating", new Player() { ClientId = 1 });
break;
case Penalty.PenaltyType.Flag:
if (attacker.Level != Player.Permission.User)
break;
var flagCmd = new CFlag();
await flagCmd.ExecuteAsync(new Event(Event.GType.Flag, $"{(int)penalty.Bone}-{Math.Round(penalty.RatioAmount, 2).ToString()}@{penalty.KillCount}", new Player()
{
ClientId = 1,
Level = Player.Permission.Console,
ClientNumber = -1,
CurrentServer = attacker.CurrentServer
}, attacker, attacker.CurrentServer));
break;
}
}
await executePenalty(playerDetection.ProcessKill(kill));
await executePenalty(playerDetection.ProcessTotalRatio(playerStats));
}
public async Task AddStandardKill(Player attacker, Player victim)
{
int serverId = attacker.CurrentServer.GetHashCode();
var attackerStats = Servers[serverId].PlayerStats[attacker.ClientNumber];
if (victim == null)
EFClientStatistics attackerStats = null;
try
{
Log.WriteError($"[AddStandardKill] Victim is null");
attackerStats = Servers[serverId].PlayerStats[attacker.ClientId];
}
catch (KeyNotFoundException)
{
Log.WriteError($"[Stats::AddStandardKill] kill attacker ClientId is invalid {attacker.ClientId}-{attacker}");
return;
}
var victimStats = Servers[serverId].PlayerStats[victim.ClientNumber];
EFClientStatistics victimStats = null;
try
{
victimStats = Servers[serverId].PlayerStats[victim.ClientId];
}
catch (KeyNotFoundException)
{
Log.WriteError($"[Stats::AddStandardKill] kill victim ClientId is invalid {victim.ClientId}-{victim}");
return;
}
// update the total stats
Servers[serverId].ServerStatistics.TotalKills += 1;
attackerStats.SessionScore = attacker.Score;
victimStats.SessionScore = victim.Score;
// calculate for the clients
CalculateKill(attackerStats, victimStats);
@ -292,9 +375,7 @@ namespace StatsPlugin.Helpers
return clientStats;
// calculate the players Score Per Minute for the current session
int currentScore = Manager.GetActiveClients()
.First(c => c.ClientId == clientStats.ClientId)
.Score;
int currentScore = clientStats.SessionScore;
double killSPM = currentScore / (timeSinceLastCalc * 60.0);
// calculate how much the KDR should weigh
@ -350,6 +431,25 @@ namespace StatsPlugin.Helpers
}
}
public void ResetKillstreaks(int serverId)
{
var serverStats = Servers[serverId];
foreach (var stat in serverStats.PlayerStats.Values)
{
stat.KillStreak = 0;
stat.DeathStreak = 0;
}
}
public void ResetStats(int clientId, int serverId)
{
var stats = Servers[serverId].PlayerStats[clientId];
stats.Kills = 0;
stats.Deaths = 0;
stats.SPM = 0;
stats.Skill = 0;
}
public async Task AddMessageAsync(int clientId, int serverId, string message)
{
// the web users can have no account

View File

@ -10,7 +10,6 @@ namespace StatsPlugin.Helpers
{
public class ThreadSafeStatsService
{
public GenericRepository<EFClientStatistics> ClientStatSvc { get; private set; }
public GenericRepository<EFServer> ServerSvc { get; private set; }
public GenericRepository<EFClientKill> KillStatsSvc { get; private set; }

View File

@ -1358,7 +1358,9 @@ namespace StatsPlugin
m40a3_mp,
peacekeeper_mp,
dragunov_mp,
cobra_player_minigun_mp
cobra_player_minigun_mp,
destructible_car,
sentry_minigun_mp
}
public enum MapName

View File

@ -24,7 +24,9 @@ namespace StatsPlugin.Models
public int Kills { get; set; }
[Required]
public int Deaths { get; set; }
[Required]
public virtual ICollection<EFHitLocationCount> HitLocations { get; set; }
[NotMapped]
public double KDR
{
@ -51,5 +53,7 @@ namespace StatsPlugin.Models
public int LastScore { get; set; }
[NotMapped]
public DateTime LastActive { get; set; }
[NotMapped]
public int SessionScore { get; set; }
}
}

View File

@ -0,0 +1,21 @@
using SharedLibrary.Database.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StatsPlugin.Models
{
public class EFHitLocationCount : SharedEntity
{
[Key]
public int HitLocationCountId { get; set; }
[Required]
public IW4Info.HitLocation Location { get; set; }
[Required]
public int HitCount { get; set; }
}
}

View File

@ -22,7 +22,7 @@ namespace StatsPlugin
public string Author => "RaidMax";
private StatManager Manager;
public static StatManager Manager { get; private set; }
private IManager ServerManager;
public async Task OnEventAsync(Event E, Server S)
@ -35,19 +35,20 @@ namespace StatsPlugin
case Event.GType.Stop:
break;
case Event.GType.Connect:
Manager.AddPlayer(E.Origin);
await Manager.AddPlayer(E.Origin);
break;
case Event.GType.Disconnect:
await Manager.RemovePlayer(E.Origin);
break;
case Event.GType.Say:
if (E.Data != string.Empty && E.Data.Trim().Length > 0 && E.Data.Trim()[0] != '!')
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 Event.GType.MapChange:
Manager.ResetKillstreaks(S.GetHashCode());
await Manager.Sync(S);
break;
case Event.GType.MapEnd:
await Manager.Sync(S);
break;
case Event.GType.Broadcast:
break;
@ -92,6 +93,25 @@ namespace StatsPlugin
double kdr = Math.Round(kills / (double)deaths, 2);
double skill = Math.Round(clientStats.Sum(c => c.Skill) / clientStats.Count, 2);
double chestRatio = 0;
double abdomenRatio = 0;
double chestAbdomenRatio = 0;
if (clientStats.FirstOrDefault()?.HitLocations.Count > 0)
{
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);
}
return new List<ProfileMeta>()
{
new ProfileMeta()
@ -113,6 +133,24 @@ namespace StatsPlugin
{
Key = "Skill",
Value = skill
},
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
}
};
}

View File

@ -119,6 +119,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Cheat\Detection.cs" />
<Compile Include="Cheat\DetectionPenaltyResult.cs" />
<Compile Include="Cheat\Thresholds.cs" />
<Compile Include="Commands\ResetStats.cs" />
<Compile Include="Commands\TopStats.cs" />
@ -131,6 +132,7 @@
<Compile Include="MinimapConfig.cs" />
<Compile Include="Models\EFClientKill.cs" />
<Compile Include="Models\EFClientMessage.cs" />
<Compile Include="Models\EFHitLocationCount.cs" />
<Compile Include="Models\EFServer.cs" />
<Compile Include="Models\EFClientStatistics.cs" />
<Compile Include="Models\EFServerStatistics.cs" />
@ -146,7 +148,6 @@
<ProjectReference Include="..\..\SharedLibrary\SharedLibrary.csproj">
<Project>{d51eeceb-438a-47da-870f-7d7b41bc24d6}</Project>
<Name>SharedLibrary</Name>
<Private>False</Private>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@ -126,7 +126,7 @@ namespace Welcome_Plugin
msg = msg.Replace("{{ClientLevel}}", Utilities.ConvertLevelToColor(joining.Level));
try
{
CountryLookupProj.CountryLookup CLT = new CountryLookupProj.CountryLookup("Plugins/GeoIP.dat");
CountryLookupProj.CountryLookup CLT = new CountryLookupProj.CountryLookup($"{Utilities.OperatingDirectory}Plugins{System.IO.Path.DirectorySeparatorChar}GeoIP.dat");
msg = msg.Replace("{{ClientLocation}}", CLT.LookupCountryName(joining.IPAddressString));
}

View File

@ -1,2 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration />
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2"/>
</startup>
<runtime>
<assemblyBinding>
<!-- <probing privatePath="lib"/>-->
</assemblyBinding>
</runtime>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlCeConnectionFactory, EntityFramework">
<parameters>
<parameter value="System.Data.SqlServerCe.4.0" />
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="System.Data.SqlServerCe.4.0" type="System.Data.Entity.SqlServerCompact.SqlCeProviderServices, EntityFramework.SqlServerCompact" />
</providers>
</entityFramework>
<system.data>
<DbProviderFactories>
<remove invariant="System.Data.SqlServerCe.4.0" />
<add name="Microsoft SQL Server Compact Data Provider 4.0" invariant="System.Data.SqlServerCe.4.0" description=".NET Framework Data Provider for Microsoft SQL Server Compact" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" />
</DbProviderFactories>
</system.data>
<connectionStrings>
<add name="DefaultConnection"
providerName="System.Data.SqlServerCe.4.0"
connectionString="Data Source=|DataDirectory|\Database.sdf"/>
</connectionStrings>
</configuration>

View File

@ -11,6 +11,7 @@ using SharedLibrary.Database;
using System.Data.Entity;
using SharedLibrary.Database.Models;
using SharedLibrary.Services;
using SharedLibrary.Exceptions;
namespace SharedLibrary.Commands
{
@ -170,6 +171,9 @@ namespace SharedLibrary.Commands
{
String Message = Utilities.RemoveWords(E.Data, 1).Trim();
var length = E.Data.Split(' ')[0].ToLower().ParseTimespan();
if (length.TotalHours >= 1 && length.TotalHours < 2)
Message = E.Data;
if (E.Origin.Level > E.Target.Level)
{
@ -428,8 +432,8 @@ namespace SharedLibrary.Commands
if (newPerm > Player.Permission.Banned)
{
var ActiveClient = E.Owner.Manager.GetActiveClients().FirstOrDefault(p => p.NetworkId == E.Target.NetworkId);
var ActiveClient = E.Owner.Manager.GetActiveClients()
.FirstOrDefault(p => p.NetworkId == E.Target.NetworkId);
if (ActiveClient != null)
{
@ -444,7 +448,6 @@ namespace SharedLibrary.Commands
}
await E.Origin.Tell($"{E.Target.Name} was successfully promoted!");
}
else
@ -485,17 +488,22 @@ namespace SharedLibrary.Commands
public override async Task ExecuteAsync(Event E)
{
int numOnline = 0;
for (int i = 0; i < E.Owner.Players.Count; i++)
{
var P = E.Owner.Players[i];
if (P != null && P.Level > Player.Permission.Flagged && !P.Masked)
{
numOnline++;
if (E.Message[0] == '@')
await E.Owner.Broadcast(String.Format("[^3{0}^7] {1}", Utilities.ConvertLevelToColor(P.Level), P.Name));
else
await E.Origin.Tell(String.Format("[^3{0}^7] {1}", Utilities.ConvertLevelToColor(P.Level), P.Name));
}
}
if (numOnline == 0)
await E.Origin.Tell("No visible administrators online");
}
}
@ -519,14 +527,14 @@ namespace SharedLibrary.Commands
{
if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap)
{
await E.Owner.Broadcast("Changing to map ^2" + m.Alias);
await E.Owner.Broadcast($"Changing to map ^5{m.Alias}");
Task.Delay(5000).Wait();
await E.Owner.LoadMap(m.Name);
return;
}
}
await E.Owner.Broadcast("Attempting to change to unknown map ^1" + newMap);
await E.Owner.Broadcast($"Attempting to change to unknown map ^5{newMap}");
Task.Delay(5000).Wait();
await E.Owner.LoadMap(newMap);
}
@ -927,7 +935,7 @@ namespace SharedLibrary.Commands
}
})
{ }
public override async Task ExecuteAsync(Event E)
{
int inactiveDays = 30;
@ -961,7 +969,89 @@ namespace SharedLibrary.Commands
inactiveUsers.ForEach(c => c.Level = Player.Permission.User);
await context.SaveChangesAsync();
}
await E.Origin.Tell($"Pruned {inactiveUsers.Count} inactive privileged users");
await E.Origin.Tell($"Pruned {inactiveUsers.Count} inactive privileged users");
}
}
public class CRestartServer : Command
{
public CRestartServer() : base("restartserver", "restart the server", "restart", Player.Permission.Administrator, false)
{
}
public override async Task ExecuteAsync(Event E)
{
var gameserverProcesses = System.Diagnostics.Process.GetProcessesByName("iw4x");
var currentProcess = gameserverProcesses.FirstOrDefault(g => g.GetCommandLine().Contains($"+set net_port {E.Owner.GetPort()}"));
if (currentProcess == null)
{
await E.Origin.Tell("Could not find running/stalled instance of IW4x");
}
else
{
var commandLine = currentProcess.GetCommandLine();
// attempt to kill it natively
try
{
if (!E.Owner.Throttled)
{
// await E.Owner.ExecuteCommandAsync("quit");
}
}
catch (NetworkException)
{
await E.Origin.Tell("Unable to cleanly shutdown server, forcing");
}
if (!currentProcess.HasExited)
{
try
{
currentProcess.Kill();
}
catch (Exception e)
{
await E.Origin.Tell("Could not kill IW4x process");
E.Owner.Logger.WriteDebug("Unable to kill process");
E.Owner.Logger.WriteDebug($"Exception: {e.Message}");
return;
}
try
{
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.UseShellExecute = false;
#if !DEBUG
process.StartInfo.WorkingDirectory = E.Owner.WorkingDirectory;
#else
process.StartInfo.WorkingDirectory = @"C:\Users\User\Desktop\MW2";
#endif
process.StartInfo.FileName = $"{process.StartInfo.WorkingDirectory}\\iw4x.exe";
process.StartInfo.Arguments = commandLine.Substring(6);
process.StartInfo.UserName = E.Owner.Config.RestartUsername;
var pw = new System.Security.SecureString();
foreach (char c in E.Owner.Config.RestartPassword)
pw.AppendChar(c);
process.StartInfo.Password = pw;
process.Start();
}
catch (Exception e)
{
await E.Origin.Tell("Could not start the IW4x process");
E.Owner.Logger.WriteDebug("Unable to start process");
E.Owner.Logger.WriteDebug($"Exception: {e.Message}");
}
}
}
}
}

View File

@ -10,6 +10,7 @@ using System.Data.Entity.ModelConfiguration.Conventions;
using System.Reflection;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.SqlServerCompact;
using System.IO;
namespace SharedLibrary.Database
{
@ -23,7 +24,8 @@ namespace SharedLibrary.Database
public DatabaseContext() : base("DefaultConnection")
{
System.Data.Entity.Database.SetInitializer(new Initializer());
System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<DatabaseContext, Migrations.Configuration>());
//Database.CreateIfNotExists();
Configuration.LazyLoadingEnabled = true;
}
@ -50,7 +52,12 @@ namespace SharedLibrary.Database
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// https://aleemkhan.wordpress.com/2013/02/28/dynamically-adding-dbset-properties-in-dbcontext-for-entity-framework-code-first/
//string dir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
#if !DEBUG
foreach (string dllPath in System.IO.Directory.GetFiles($"{Utilities.OperatingDirectory}Plugins"))
#else
foreach (string dllPath in System.IO.Directory.GetFiles(@"C:\Users\User\Desktop\stuff\IW4M-Admin\IW4M-Admin\WebfrontCore\bin\x86\Debug\Plugins"))
#endif
{
Assembly library;
try
@ -64,7 +71,7 @@ namespace SharedLibrary.Database
continue;
}
foreach(var type in library.ExportedTypes)
foreach (var type in library.ExportedTypes)
{
if (type.IsClass && type.IsSubclassOf(typeof(SharedEntity)))
{

View File

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibrary.Database
{
public class Initializer : DropCreateDatabaseIfModelChanges<DatabaseContext>
{
protected override void Seed(DatabaseContext context)
{
var aliasLink = new Models.EFAliasLink();
var currentAlias = new Models.EFAlias()
{
Active = true,
DateAdded = DateTime.UtcNow,
IPAddress = 0,
Name = "IW4MAdmin",
Link = aliasLink
};
context.Clients.Add(new Models.EFClient()
{
Active = false,
Connections = 0,
FirstConnection = DateTime.UtcNow,
LastConnection = DateTime.UtcNow,
Level = Objects.Player.Permission.Console,
Masked = true,
NetworkId = 0,
AliasLink = aliasLink,
CurrentAlias = currentAlias
});
base.Seed(context);
}
}
}

View File

@ -0,0 +1,26 @@
using SharedLibrary.Interfaces;
using System;
using System.Data.SqlServerCe;
namespace SharedLibrary.Database
{
public class Repair
{
public static void Run(ILogger log)
{
SqlCeEngine engine = new SqlCeEngine(@"Data Source=|DataDirectory|\Database.sdf");
if (false == engine.Verify())
{
log.WriteWarning("Database is corrupted.");
try
{
engine.Repair(null, RepairOption.DeleteCorruptedRows);
}
catch (SqlCeException ex)
{
log.WriteError(ex.Message);
}
}
}
}
}

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace SharedLibrary.Dtos
{
public class PenaltyInfo
public class PenaltyInfo : SharedInfo
{
public string OffenderName { get; set; }
public int OffenderId { get; set; }

View File

@ -7,9 +7,10 @@ using System.Threading.Tasks;
namespace SharedLibrary.Dtos
{
public class ProfileMeta
public class ProfileMeta : SharedInfo
{
public DateTime When { get; set; }
public bool Sensitive { get; set; }
public string WhenString => Utilities.GetTimePassed(When, false);
public string Key { get; set; }
public dynamic Value { get; set; }

View File

@ -0,0 +1,8 @@

namespace SharedLibrary.Dtos
{
public class SharedInfo
{
public bool Sensitive { get; set; }
}
}

View File

@ -2,12 +2,13 @@
using SharedLibrary.Objects;
using SharedLibrary.Database.Models;
using SharedLibrary.Services;
using System.Threading.Tasks;
namespace SharedLibrary.Interfaces
{
public interface IManager
{
void Init();
Task Init();
void Start();
void Stop();
ILogger GetLogger();
@ -15,7 +16,7 @@ namespace SharedLibrary.Interfaces
IList<Command> GetCommands();
IList<Helpers.MessageToken> GetMessageTokens();
IList<Player> GetActiveClients();
ClientService GetClientService();
ClientService GetClientService();
AliasService GetAliasService();
PenaltyService GetPenaltyService();
}

View File

@ -0,0 +1,29 @@
// <auto-generated />
namespace SharedLibrary.Migrations
{
using System.CodeDom.Compiler;
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
[GeneratedCode("EntityFramework.Migrations", "6.2.0-61023")]
public sealed partial class Intial : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(Intial));
string IMigrationMetadata.Id
{
get { return "201803030146021_Intial"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}

View File

@ -0,0 +1,16 @@
namespace SharedLibrary.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class Intial : DbMigration
{
public override void Up()
{
}
public override void Down()
{
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,52 @@
namespace SharedLibrary.Migrations
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<SharedLibrary.Database.DatabaseContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(SharedLibrary.Database.DatabaseContext context)
{
context.AliasLinks.AddOrUpdate(new SharedLibrary.Database.Models.EFAliasLink()
{
AliasLinkId = 1
});
var currentAlias = new SharedLibrary.Database.Models.EFAlias()
{
AliasId = 1,
Active = true,
DateAdded = DateTime.UtcNow,
IPAddress = 0,
Name = "IW4MAdmin",
LinkId = 1
};
context.Aliases.AddOrUpdate(currentAlias);
context.Clients.AddOrUpdate(new SharedLibrary.Database.Models.EFClient()
{
ClientId = 1,
Active = false,
Connections = 0,
FirstConnection = DateTime.UtcNow,
LastConnection = DateTime.UtcNow,
Level = Objects.Player.Permission.Console,
Masked = true,
NetworkId = 0,
AliasLinkId = 1,
CurrentAliasId = 1,
});
base.Seed(context);
}
}
}

View File

@ -66,7 +66,7 @@ namespace SharedLibrary.Network
do
{
ReceiveBuffer = ServerOOBConnection.Receive(ref Endpoint);
QueryResponseString.Append(Encoding.ASCII.GetString(ReceiveBuffer).TrimEnd('\0'));
QueryResponseString.Append(Encoding.UTF7.GetString(ReceiveBuffer).TrimEnd('\0'));
} while (ServerOOBConnection.Available > 0 && ServerOOBConnection.Client.Connected);
if (QueryResponseString.ToString().Contains("Invalid password"))

View File

@ -385,6 +385,7 @@ namespace SharedLibrary
public string Password { get; private set; }
public bool Throttled { get; protected set; }
public bool CustomCallback { get; protected set; }
public string WorkingDirectory { get; protected set; }
// Internal
protected string IP;

View File

@ -10,6 +10,8 @@ namespace SharedLibrary
public string FtpPrefix;
public bool AllowMultipleOwners;
public bool AllowTrustedRank;
public string RestartUsername;
public string RestartPassword;
public override string Filename()
{

View File

@ -168,6 +168,11 @@ namespace SharedLibrary.Services
};
}
else
{
client.CurrentAliasId = entity.CurrentAliasId;
}
// set remaining non-navigation properties that may have been updated
client.Level = entity.Level;
client.LastConnection = entity.LastConnection;

View File

@ -15,6 +15,21 @@
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -118,22 +133,10 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll</HintPath>
</Reference>
<Reference Include="EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll</HintPath>
</Reference>
<Reference Include="EntityFramework.SqlServerCompact, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.SqlServerCompact.6.2.0\lib\net45\EntityFramework.SqlServerCompact.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Management" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Web" />
<Reference Include="System.Xml.Linq" />
@ -144,19 +147,20 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Database\Importer.cs" />
<Compile Include="Database\Initializer.cs" />
<Compile Include="Database\DatabaseContext.cs" />
<Compile Include="Database\Models\EFAlias.cs" />
<Compile Include="Database\Models\EFAliasLink.cs" />
<Compile Include="Database\Models\EFClient.cs" />
<Compile Include="Database\Models\EFPenalty.cs" />
<Compile Include="Database\Models\SharedEntity.cs" />
<Compile Include="Database\Repair.cs" />
<Compile Include="Dtos\CommandResponseInfo.cs" />
<Compile Include="Dtos\PlayerInfo.cs" />
<Compile Include="Dtos\ClientInfo.cs" />
<Compile Include="Dtos\ProfileMeta.cs" />
<Compile Include="Dtos\PenaltyInfo.cs" />
<Compile Include="Dtos\ServerInfo.cs" />
<Compile Include="Dtos\SharedInfo.cs" />
<Compile Include="Exceptions\DatabaseException.cs" />
<Compile Include="Helpers\AsyncStatus.cs" />
<Compile Include="Commands\NativeCommands.cs" />
@ -173,6 +177,11 @@
<Compile Include="Interfaces\IManager.cs" />
<Compile Include="Interfaces\ISerializable.cs" />
<Compile Include="Helpers\MessageToken.cs" />
<Compile Include="Migrations\201803030146021_Intial.cs" />
<Compile Include="Migrations\201803030146021_Intial.Designer.cs">
<DependentUpon>201803030146021_Intial.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\Configuration.cs" />
<Compile Include="Objects\Alias.cs" />
<Compile Include="Objects\Penalty.cs" />
<Compile Include="Command.cs" />
@ -197,13 +206,37 @@
<Compile Include="Utilities.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="EntityFramework">
<Version>6.2.0</Version>
</PackageReference>
<PackageReference Include="EntityFramework.SqlServerCompact">
<Version>6.2.0</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>11.0.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.5.2">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.5.2 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Migrations\201803030146021_Intial.resx">
<DependentUpon>201803030146021_Intial.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>copy /Y "$(TargetDir)$(TargetName).dll" "$(SolutionDir)BUILD\lib"
@ -218,9 +251,7 @@ if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
xcopy /Y /I /E "$(TargetDir)*" "$(SolutionDir)BUILD\Lib"</PostBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PreBuildEvent>if exist "$(SolutionDir)BUILD\Plugins" rmdir /Q /S "$(SolutionDir)BUILD\Plugins"
mkdir "$(SolutionDir)BUILD\Plugins"
<PreBuildEvent>
if not exist "$(SolutionDir)BUILD" mkdir "$(SolutionDir)BUILD"
if not exist "$(SolutionDir)BUILD\Lib" mkdir "$(SolutionDir)BUILD\Lib"
if not exist "$(SolutionDir)BUILD\userraw\scripts" mkdir "$(SolutionDir)BUILD\userraw\scripts"</PreBuildEvent>

View File

@ -4,11 +4,13 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Linq;
using System.Collections.Generic;
using System.Management;
using SharedLibrary.Objects;
using static SharedLibrary.Server;
using System.Reflection;
using System.IO;
using System.Diagnostics;
namespace SharedLibrary
{
@ -61,14 +63,14 @@ namespace SharedLibrary
int cID = -1;
int Ping = -1;
Int32.TryParse(playerInfo[2], out Ping);
String cName = Utilities.StripColors(responseLine.Substring(46, 18)).Trim();
String cName = Encoding.UTF8.GetString(Encoding.Convert(Encoding.UTF7, Encoding.UTF8, Encoding.UTF7.GetBytes(StripColors(responseLine.Substring(46, 18)).Trim())));
long npID = Regex.Match(responseLine, @"([a-z]|[0-9]){16}", RegexOptions.IgnoreCase).Value.ConvertLong();
int.TryParse(playerInfo[0], out cID);
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
int cIP = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+");
int score = Int32.Parse(regex.Value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[1]);
Player P = new Player() { Name = cName, NetworkId = npID, ClientNumber = cID, IPAddress = cIP, Ping = Ping, Score = score};
Player P = new Player() { Name = cName, NetworkId = npID, ClientNumber = cID, IPAddress = cIP, Ping = Ping, Score = score };
StatusPlayers.Add(P);
}
}
@ -96,8 +98,11 @@ namespace SharedLibrary
{
if (str == null)
return "";
return Regex.Replace(str, @"(\^+((?![a-z]|[A-Z]).){0,1})+", "")
str = Regex.Replace(str, @"(\^+((?![a-z]|[A-Z]).){0,1})+", "");
string str2 = Regex.Match(str, @"(^\/+.*$)|(^.*\/+$)")
.Value
.Replace("/", " /");
return str2.Length > 0 ? str2 : str;
}
/// <summary>
@ -334,11 +339,6 @@ namespace SharedLibrary
return "1 hour";
}
public static string EscapeMarkdown(this string markdownString)
{
return markdownString.Replace("<", "\\<").Replace(">", "\\>").Replace("|", "\\|");
}
public static Player AsPlayer(this Database.Models.EFClient client)
{
return client == null ? null : new Player()
@ -361,5 +361,35 @@ namespace SharedLibrary
CurrentAliasId = client.CurrentAlias.AliasId
};
}
/*https://stackoverflow.com/questions/2633628/can-i-get-command-line-arguments-of-other-processes-from-net-c*/
// Define an extension method for type System.Process that returns the command
// line via WMI.
public static string GetCommandLine(this Process process)
{
string cmdLine = null;
using (var searcher = new ManagementObjectSearcher(
$"SELECT CommandLine FROM Win32_Process WHERE ProcessId = {process.Id}"))
{
// By definition, the query returns at most 1 match, because the process
// is looked up by ID (which is unique by definition).
var matchEnum = searcher.Get().GetEnumerator();
if (matchEnum.MoveNext()) // Move to the 1st item.
{
cmdLine = matchEnum.Current["CommandLine"]?.ToString();
}
}
if (cmdLine == null)
{
// Not having found a command line implies 1 of 2 exceptions, which the
// WMI query masked:
// An "Access denied" exception due to lack of privileges.
// A "Cannot process request because the process (<pid>) has exited."
// exception due to the process having terminated.
// We provoke the same exception again simply by accessing process.MainModule.
var dummy = process.MainModule; // Provoke exception.
}
return cmdLine;
}
}
}

View File

@ -1,191 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using Kayak.Http;
using Kayak;
namespace IW4MAdmin
{
class Scheduler : ISchedulerDelegate
{
public void OnException(IScheduler scheduler, Exception e)
{
// it looks like there's a library error in
// Kayak.Http.HttpServerTransactionDelegate.OnError
if ((uint)e.HResult == 0x80004003 || (uint)e.InnerException?.HResult == 0x80004003)
return;
ApplicationManager.GetInstance().Logger.WriteWarning("Web service has encountered an error - " + e.Message);
ApplicationManager.GetInstance().Logger.WriteDebug($"Stack Trace: {e.StackTrace}");
if (e.InnerException != null)
{
ApplicationManager.GetInstance().Logger.WriteDebug($"Inner Exception: {e.InnerException.Message}");
ApplicationManager.GetInstance().Logger.WriteDebug($"Inner Stack Trace: {e.InnerException.StackTrace}");
}
}
public void OnStop(IScheduler scheduler)
{
ApplicationManager.GetInstance().Logger.WriteInfo("Web service has been stopped...");
}
}
class Request : IHttpRequestDelegate
{
public void OnRequest(HttpRequestHead request, IDataProducer requestBody, IHttpResponseDelegate response)
{
#if DEBUG
var logger = ApplicationManager.GetInstance().GetLogger();
logger.WriteDebug($"HTTP request {request.Path}");
logger.WriteDebug($"QueryString: {request.QueryString}");
logger.WriteDebug($"IP: {request.IPAddress}");
#endif
NameValueCollection querySet = new NameValueCollection();
if (request.QueryString != null)
querySet = System.Web.HttpUtility.ParseQueryString(request.QueryString);
querySet.Set("IP", request.IPAddress);
try
{
request.Path = String.IsNullOrEmpty(request.Path) ? "/" : request.Path;
SharedLibrary.HttpResponse requestedPage = WebService.GetPage(request.Path, querySet, request.Headers);
bool binaryContent = requestedPage.BinaryContent != null;
if (requestedPage.content != null && requestedPage.content.GetType() != typeof(string))
#if !DEBUG
requestedPage.content = Newtonsoft.Json.JsonConvert.SerializeObject(requestedPage.content);
#else
requestedPage.content = Newtonsoft.Json.JsonConvert.SerializeObject(requestedPage.content, Newtonsoft.Json.Formatting.Indented);
#endif
string maxAge = requestedPage.contentType == "application/json" ? "0" : "21600";
var headers = new HttpResponseHead()
{
Status = "200 OK",
Headers = new Dictionary<string, string>()
{
{ "Content-Type", requestedPage.contentType },
{ "Content-Length", binaryContent ? requestedPage.BinaryContent.Length.ToString() : requestedPage.content.ToString().Length.ToString() },
{ "Access-Control-Allow-Origin", "*" },
{ "Cache-Control", $"public,max-age={maxAge}"}
}
};
foreach (var key in requestedPage.additionalHeaders.Keys)
headers.Headers.Add(key, requestedPage.additionalHeaders[key]);
if (!binaryContent)
response.OnResponse(headers, new BufferedProducer((string)requestedPage.content));
else
response.OnResponse(headers, new BufferedProducer(requestedPage.BinaryContent));
}
catch (Exception e)
{
if (e.GetType() == typeof(FormatException))
{
ApplicationManager.GetInstance().Logger.WriteWarning("Request parameter data format was incorrect");
ApplicationManager.GetInstance().Logger.WriteDebug($"Request Path {request.Path}");
ApplicationManager.GetInstance().Logger.WriteDebug($"Request Query String {request.QueryString}");
response.OnResponse(new HttpResponseHead()
{
Status = "400 Bad Request",
Headers = new Dictionary<string, string>()
{
{ "Content-Type", "text/html" },
{ "Content-Length", "0"},
}
}, new BufferedProducer(""));
}
else
{
ApplicationManager.GetInstance().Logger.WriteError($"Webfront error during request");
ApplicationManager.GetInstance().Logger.WriteDebug($"Message: {e.Message}");
ApplicationManager.GetInstance().Logger.WriteDebug($"Stack Trace: {e.StackTrace}");
response.OnResponse(new HttpResponseHead()
{
Status = "500 Internal Server Error",
Headers = new Dictionary<string, string>()
{
{ "Content-Type", "text/html" },
{ "Content-Length", "0"},
}
}, new BufferedProducer(""));
}
}
}
}
class BufferedProducer : IDataProducer
{
ArraySegment<byte> data;
public BufferedProducer(string data) : this(data, Encoding.ASCII) { }
public BufferedProducer(string data, Encoding encoding) : this(encoding.GetBytes(data)) { }
public BufferedProducer(byte[] data) : this(new ArraySegment<byte>(data)) { }
public BufferedProducer(ArraySegment<byte> data)
{
this.data = data;
}
public IDisposable Connect(IDataConsumer channel)
{
try
{
channel?.OnData(data, null);
channel?.OnEnd();
}
catch (Exception)
{
}
return null;
}
}
class BufferedConsumer : IDataConsumer
{
List<ArraySegment<byte>> buffer = new List<ArraySegment<byte>>();
Action<string> resultCallback;
Action<Exception> errorCallback;
public BufferedConsumer(Action<string> resultCallback, Action<Exception> errorCallback)
{
this.resultCallback = resultCallback;
this.errorCallback = errorCallback;
}
public bool OnData(ArraySegment<byte> data, Action continuation)
{
// this should hopefully clean the non ascii characters out.
buffer?.Add(new ArraySegment<byte>(Encoding.ASCII.GetBytes(Encoding.ASCII.GetString(data.ToArray()))));
return false;
}
public void OnError(Exception error)
{
// errorCallback?.Invoke(error);
}
public void OnEnd()
{
var str = buffer
.Select(b => Encoding.ASCII.GetString(b.Array, b.Offset, b.Count))
.Aggregate((result, next) => result + next);
resultCallback(str);
}
}
}

View File

@ -6,11 +6,7 @@ using System.Threading.Tasks;
using System.IO;
using SharedLibrary.Objects;
using System.Reflection;
#if DEBUG
using SharedLibrary.Database;
#endif
using System.Linq;
namespace IW4MAdmin
{
@ -20,7 +16,7 @@ namespace IW4MAdmin
public static extern bool AllocConsole();
static public double Version { get; private set; }
static public ApplicationManager ServerManager = ApplicationManager.GetInstance();
public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
public static void Start()
{
@ -29,7 +25,6 @@ namespace IW4MAdmin
Version = 1.6;
//double.TryParse(CheckUpdate(), out double latestVersion);
Console.WriteLine("=====================================================");
Console.WriteLine(" IW4M ADMIN");
Console.WriteLine(" by RaidMax ");
@ -40,13 +35,16 @@ namespace IW4MAdmin
{
CheckDirectories();
ServerManager = ApplicationManager.GetInstance();
ServerManager.Init();
Task.Run(async () =>
{
ServerManager = ApplicationManager.GetInstance();
SharedLibrary.Database.Repair.Run(ServerManager.Logger);
await ServerManager.Init();
ServerManager.Start();
});
Task.Run(() =>
{
ServerManager.Start();
String userInput;
Player Origin = ServerManager.GetClientService().Get(1).Result.AsPlayer();
@ -66,6 +64,8 @@ namespace IW4MAdmin
Console.Write('>');
} while (ServerManager.Running);
Console.WriteLine("Shutdown complete");
});
}

View File

@ -11,8 +11,6 @@ using SharedLibrary.Commands;
using SharedLibrary.Helpers;
using SharedLibrary.Exceptions;
using SharedLibrary.Objects;
using SharedLibrary.Database;
using SharedLibrary.Database.Models;
using SharedLibrary.Services;
namespace IW4MAdmin
@ -21,6 +19,7 @@ namespace IW4MAdmin
{
private List<Server> _servers;
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
public List<int> AdministratorIPs { get; set; }
public ILogger Logger { get; private set; }
public bool Running { get; private set; }
@ -34,7 +33,7 @@ namespace IW4MAdmin
#if FTP_LOG
const int UPDATE_FREQUENCY = 700;
#else
const int UPDATE_FREQUENCY = 300;
const int UPDATE_FREQUENCY = 750;
#endif
private ApplicationManager()
@ -47,6 +46,7 @@ namespace IW4MAdmin
ClientSvc = new ClientService();
AliasSvc = new AliasService();
PenaltySvc = new PenaltyService();
AdministratorIPs = new List<int>();
}
public IList<Server> GetServers()
@ -64,12 +64,12 @@ namespace IW4MAdmin
return Instance ?? (Instance = new ApplicationManager());
}
public void Init()
public async Task Init()
{
#region WEBSERVICE
// SharedLibrary.WebService.Init();
//WebSvc = new WebService();
//WebSvc.StartScheduler();
#region DATABASE
AdministratorIPs = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
.Select(c => c.IPAddress)
.ToList();
#endregion
#region PLUGINS
@ -79,7 +79,7 @@ namespace IW4MAdmin
{
try
{
Plugin.OnLoadAsync(this);
await Plugin.OnLoadAsync(this);
}
catch (Exception e)
@ -174,6 +174,7 @@ namespace IW4MAdmin
Commands.Add(new CIP());
Commands.Add(new CMask());
Commands.Add(new CPruneAdmins());
Commands.Add(new CRestartServer());
foreach (Command C in SharedLibrary.Plugins.PluginImporter.ActiveCommands)
Commands.Add(C);

View File

@ -10,6 +10,8 @@ using SharedLibrary.Network;
using SharedLibrary.Interfaces;
using SharedLibrary.Objects;
using System.Text.RegularExpressions;
using SharedLibrary.Services;
using SharedLibrary.Database.Models;
namespace IW4MAdmin
{
@ -99,7 +101,7 @@ namespace IW4MAdmin
await Manager.GetClientService().Update(client);
}
else if (existingAlias.Name == polledPlayer.Name)
else if (existingAlias.Name == polledPlayer.Name)
{
client.CurrentAlias = existingAlias;
client.CurrentAliasId = existingAlias.AliasId;
@ -154,12 +156,13 @@ namespace IW4MAdmin
if (cNum >= 0 && Players[cNum] != null)
{
Player Leaving = Players[cNum];
Logger.WriteInfo($"Client {Leaving} disconnecting...");
await ExecuteEvent(new Event(Event.GType.Disconnect, "", Leaving, null, this));
Leaving.TotalConnectionTime += (int)(DateTime.UtcNow - Leaving.ConnectionTime).TotalSeconds;
Leaving.LastConnection = DateTime.UtcNow;
await Manager.GetClientService().Update(Leaving);
Logger.WriteInfo($"Client {Leaving} disconnecting...");
await ExecuteEvent(new Event(Event.GType.Disconnect, "", Leaving, null, this));
Players[cNum] = null;
}
}
@ -357,7 +360,11 @@ namespace IW4MAdmin
async Task<int> PollPlayersAsync()
{
var now = DateTime.Now;
var CurrentPlayers = await this.GetStatusAsync();
#if DEBUG
Logger.WriteInfo($"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms");
#endif
for (int i = 0; i < Players.Count; i++)
{
@ -385,9 +392,9 @@ namespace IW4MAdmin
override public async Task<bool> ProcessUpdatesAsync(CancellationToken cts)
{
this.cts = cts;
#if DEBUG == false
//#if DEBUG == false
try
#endif
//#endif
{
// first start
if (firstRun)
@ -504,7 +511,7 @@ namespace IW4MAdmin
}
return true;
}
#if DEBUG == false
//#if !DEBUG
catch (SharedLibrary.Exceptions.NetworkException)
{
Logger.WriteError($"Could not communicate with {IP}:{Port}");
@ -518,7 +525,7 @@ namespace IW4MAdmin
Logger.WriteDebug("Error Trace: " + E.StackTrace);
return false;
}
#endif
//#endif
}
public async Task Initialize()
@ -537,6 +544,7 @@ namespace IW4MAdmin
await this.GetDvarAsync<int>("sv_maxclients");
var gametype = await this.GetDvarAsync<string>("g_gametype");
var basepath = await this.GetDvarAsync<string>("fs_basepath");
WorkingDirectory = basepath.Value;
var game = await this.GetDvarAsync<string>("fs_game");
var logfile = await this.GetDvarAsync<string>("g_log");
var logsync = await this.GetDvarAsync<int>("g_logsync");
@ -705,6 +713,11 @@ namespace IW4MAdmin
string mapname = this.GetDvarAsync<string>("mapname").Result.Value;
CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map(mapname, mapname);
// todo: make this more efficient
((ApplicationManager)(Manager)).AdministratorIPs = (await new GenericRepository<EFClient>().FindAsync(c => c.Level > Player.Permission.Trusted))
.Select(c => c.IPAddress)
.ToList();
}
if (E.Type == Event.GType.MapEnd)

View File

@ -0,0 +1,25 @@
using IW4MAdmin;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using SharedLibrary;
using SharedLibrary.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebfrontCore.Controllers
{
public class BaseController : Controller
{
protected ApplicationManager Manager;
protected bool Authorized { get; private set; }
public override void OnActionExecuting(ActionExecutingContext context)
{
Manager = IW4MAdmin.Program.ServerManager;
Authorized = Manager.AdministratorIPs.Contains(context.HttpContext.Connection.RemoteIpAddress.ToString().ConvertToIP());
base.OnActionExecuting(context);
}
}
}

View File

@ -9,11 +9,11 @@ using System.Threading.Tasks;
namespace WebfrontCore.Controllers
{
public class ClientController : Controller
public class ClientController : BaseController
{
public async Task<IActionResult> ProfileAsync(int id)
{
var client = await IW4MAdmin.ApplicationManager.GetInstance().GetClientService().Get(id);
var client = await Manager.GetClientService().Get(id);
var clientDto = new PlayerInfo()
{
Name = client.Name,
@ -38,11 +38,15 @@ namespace WebfrontCore.Controllers
.OrderBy(i => i)
.ToList(),
};
clientDto.Meta.AddRange(await MetaService.GetMeta(client.ClientId));
clientDto.Meta.AddRange(await IW4MAdmin.ApplicationManager.GetInstance().GetPenaltyService().ReadGetClientPenaltiesAsync(client.ClientId));
clientDto.Meta.AddRange(await IW4MAdmin.ApplicationManager.GetInstance().GetPenaltyService().ReadGetClientPenaltiesAsync(client.ClientId, false));
clientDto.Meta = clientDto.Meta.OrderByDescending(m => m.When).ToList();
var meta = await MetaService.GetMeta(client.ClientId);
clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.Sensitive));
clientDto.Meta.AddRange(await Manager.GetPenaltyService()
.ReadGetClientPenaltiesAsync(client.ClientId));
clientDto.Meta.AddRange(await Manager.GetPenaltyService()
.ReadGetClientPenaltiesAsync(client.ClientId, false));
clientDto.Meta = clientDto.Meta
.OrderByDescending(m => m.When)
.ToList();
ViewBag.Title = clientDto.Name;
@ -51,7 +55,7 @@ namespace WebfrontCore.Controllers
public async Task<IActionResult> PrivilegedAsync()
{
var admins = (await IW4MAdmin.ApplicationManager.GetInstance().GetClientService().GetPrivilegedClients())
var admins = (await Manager.GetClientService().GetPrivilegedClients())
.Where(a => a.Active)
.OrderByDescending(a => a.Level);
var adminsDict = new Dictionary<SharedLibrary.Objects.Player.Permission, IList<ClientInfo>>();
@ -74,7 +78,7 @@ namespace WebfrontCore.Controllers
public async Task<IActionResult> FindAsync(string clientName)
{
var clients = (await IW4MAdmin.ApplicationManager.GetInstance().GetClientService().GetClientByName(clientName))
var clients = (await Manager.GetClientService().GetClientByName(clientName))
.OrderByDescending(c => c.LastConnection);
var clientsDto = clients.Select(c => new PlayerInfo()
{

View File

@ -9,11 +9,11 @@ using System.Threading.Tasks;
namespace WebfrontCore.Controllers
{
public class ConsoleController : Controller
public class ConsoleController : BaseController
{
public IActionResult Index()
{
var activeServers = IW4MAdmin.ApplicationManager.GetInstance().Servers.Select(s => new ServerInfo()
var activeServers = Manager.Servers.Select(s => new ServerInfo()
{
Name = s.Hostname,
ID = s.GetHashCode(),
@ -38,10 +38,10 @@ namespace WebfrontCore.Controllers
IPAddress = intIP
};
#else
var origin = (await IW4MAdmin.ApplicationManager.GetInstance().GetClientService().GetUnique(0)).AsPlayer();
var origin = (await Manager.GetClientService().GetUnique(0)).AsPlayer();
#endif
var server = IW4MAdmin.ApplicationManager.GetInstance().Servers.First(s => s.GetHashCode() == serverId);
var server = Manager.Servers.First(s => s.GetHashCode() == serverId);
origin.CurrentServer = server;
var remoteEvent = new Event(Event.GType.Say, command, origin, null, server);

View File

@ -8,7 +8,7 @@ using SharedLibrary.Dtos;
namespace WebfrontCore.Controllers
{
public class HomeController : Controller
public class HomeController : BaseController
{
public IActionResult Index()
{

View File

@ -9,9 +9,9 @@ using WebfrontCore.ViewComponents;
namespace WebfrontCore.Controllers
{
public class PenaltyController : Controller
public class PenaltyController : BaseController
{
public IActionResult List()
public IActionResult List()
{
ViewBag.Title = "Penalty List";
return View();

View File

@ -7,15 +7,14 @@ using System.Threading.Tasks;
namespace WebfrontCore.Controllers
{
public class ServerController : Controller
public class ServerController : BaseController
{
[HttpGet]
[ResponseCache(NoStore = true, Duration = 0)]
public IActionResult ClientActivity(int id)
{
var s = IW4MAdmin.Program.ServerManager.GetServers().FirstOrDefault(s2 => s2.GetHashCode() == id);
var s = Manager.GetServers().FirstOrDefault(s2 => s2.GetHashCode() == id);
if (s == null)
return View("Error", "Invalid server!");

View File

@ -12,6 +12,12 @@ namespace WebfrontCore.ViewComponents
{
public async Task<IViewComponentResult> InvokeAsync(int offset)
{
int ip = HttpContext.Connection.RemoteIpAddress
.ToString().ConvertToIP();
bool authed = IW4MAdmin.ApplicationManager.GetInstance()
.AdministratorIPs.Contains(ip);
var penalties = await IW4MAdmin.ApplicationManager.GetInstance().GetPenaltyService().GetRecentPenalties(15, offset);
var penaltiesDto = penalties.Select(p => new PenaltyInfo()
{
@ -23,8 +29,11 @@ namespace WebfrontCore.ViewComponents
Offense = p.Offense,
Type = p.Type.ToString(),
TimePunished = Utilities.GetTimePassed(p.When, false),
TimeRemaining = DateTime.UtcNow > p.Expires ? "" : Utilities.TimeSpanText(p.Expires - DateTime.UtcNow)
}).ToList();
TimeRemaining = DateTime.UtcNow > p.Expires ? "" : Utilities.TimeSpanText(p.Expires - DateTime.UtcNow),
Sensitive = p.Type == SharedLibrary.Objects.Penalty.PenaltyType.Flag
});
penaltiesDto = authed ? penaltiesDto.ToList() : penaltiesDto.Where(p => !p.Sensitive).ToList();
return View("_List", penaltiesDto);
}

View File

@ -8,7 +8,10 @@
</div>
<div id="profile_info" class="text-center text-sm-left pr-3 pl-3">
<div id="profile_name">
<h1><span class="client-name mr-4">@Model.Name<span id="profile_aliases_btn" class="oi oi-caret-bottom pl-2"></span></span></h1>
@{
string displayAliasButton = Model.Aliases.Count > 0 ? "" : "display: none;";
}
<h1><span class="client-name mr-4">@Model.Name<span id="profile_aliases_btn" class="oi oi-caret-bottom pl-2 @displayAliasButton"></span></span></h1>
<div id="profile_aliases" class="pr-0 pr-sm-4 pb-2 mb-2 text-muted">
@{
foreach (string alias in Model.Aliases)

View File

@ -8,19 +8,22 @@
<PackageId>WebfrontCore</PackageId>
<Platforms>AnyCPU;x86</Platforms>
<ApplicationIcon>wwwroot\favicon.ico</ApplicationIcon>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<OutputPath>bin\x86\Debug\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Application\Kayak.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="Application\Kayak.cs" />
<None Update="wwwroot\**\*">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None>
@ -60,12 +63,6 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Update="app.config">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="xcopy /Y &quot;$(SolutionDir)BUILD\Plugins&quot; &quot;$(TargetDir)Plugins\&quot;&#xD;&#xA;xcopy /Y /I /E &quot;$(SolutionDir)BUILD\Lib&quot; &quot;$(TargetDir)&quot; &#xD;&#xA;&#xD;&#xA;xcopy /Y /I /E &quot;$(ProjectDir)Application\Config&quot; &quot;$(TargetDir)Config&quot;" />
</Target>

View File

@ -31,6 +31,6 @@
<connectionStrings>
<add name="DefaultConnection"
providerName="System.Data.SqlServerCe.4.0"
connectionString="Data Source=|DataDirectory|\Database\Database.sdf"/>
connectionString="Data Source=|DataDirectory|\Database.sdf"/>
</connectionStrings>
</configuration>

View File

@ -2,9 +2,9 @@
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"Default": "Trace",
"System": "Information",
"Microsoft": "Information"
"Microsoft": "None"
}
}
}