Compare commits
98 Commits
2.0
...
2.3-prerel
Author | SHA1 | Date | |
---|---|---|---|
8aa0d204f4 | |||
12cf2e8247 | |||
b77bdbe793 | |||
4522992c0e | |||
9d6cbee69c | |||
abf0609e2e | |||
5ac8a55c72 | |||
9bdd7d1b8a | |||
ed83c4c011 | |||
d9d548ea18 | |||
1779bf821d | |||
d50e6c8030 | |||
a58726d872 | |||
dded60a6ef | |||
305817d00c | |||
65cf3566db | |||
e91d076b41 | |||
b5e9519f0c | |||
7caee55a7e | |||
b289917319 | |||
c8366a22e5 | |||
de902a58ac | |||
c7547f1ad5 | |||
9d946d1bad | |||
f4ac815d07 | |||
7fa0b52543 | |||
d45729d7e1 | |||
5d93e7ac57 | |||
0f9d2e92e1 | |||
4a46abc46d | |||
7c708f06f3 | |||
98adfb12d2 | |||
a786541484 | |||
b9086fd145 | |||
3d8108f339 | |||
39596db56e | |||
ba5b1e19a6 | |||
385879618d | |||
0c90d02e44 | |||
cfbacabb4a | |||
672d45df7c | |||
20d4ab27d3 | |||
e77ef69ee8 | |||
cc7628d058 | |||
46bdc2ac33 | |||
bbefd53db4 | |||
56cb8c50e7 | |||
0538d9f479 | |||
1343d4959e | |||
ac64d8d3c1 | |||
b5939bbdaf | |||
a0fafe5797 | |||
bbade07646 | |||
3c0e101f14 | |||
d0be08629d | |||
9d00d5a16a | |||
396e5c9215 | |||
4ec16d3aa2 | |||
f40bcce44f | |||
6071ad8653 | |||
16d7ccd590 | |||
87541c4a5a | |||
af6361144e | |||
454238a192 | |||
e7c7145da1 | |||
5be6b75ccf | |||
e60f612f95 | |||
ba023ceeb5 | |||
e3dba96d72 | |||
6d0f859a93 | |||
696e2d12c9 | |||
bf68e5672f | |||
2204686b08 | |||
897ec0d0c1 | |||
d9a601328c | |||
36d493f05b | |||
be68335f70 | |||
4d585e6ab2 | |||
4006c09045 | |||
699c19cd4b | |||
6e5501b32d | |||
e964013700 | |||
9ff7f39e8d | |||
a54ea3913d | |||
e8dff01c41 | |||
3092a529e9 | |||
f442f251f6 | |||
3a463be7f8 | |||
35e7f57156 | |||
8071fb37bc | |||
bb90a807b7 | |||
2c2c442ba7 | |||
b6c979beba | |||
99390f1f35 | |||
ece519251a | |||
0e3d280595 | |||
5dfaa4ebd6 | |||
02ef5a0bf8 |
13
.gitignore
vendored
13
.gitignore
vendored
@ -220,7 +220,16 @@ Thumbs.db
|
||||
DEPLOY
|
||||
global.min.css
|
||||
global.min.js
|
||||
bootstrap-custom.css
|
||||
bootstrap-custom.min.css
|
||||
**/Master/static
|
||||
**/Master/dev_env
|
||||
**/Master/dev_env
|
||||
/WebfrontCore/Views/Plugins/Stats
|
||||
/WebfrontCore/wwwroot/**/dds
|
||||
|
||||
/DiscordWebhook/env
|
||||
/DiscordWebhook/config.dev.json
|
||||
/GameLogServer/env
|
||||
launchSettings.json
|
||||
/VpnDetectionPrivate.js
|
||||
/Plugins/ScriptPlugins/VpnDetectionPrivate.js
|
||||
**/Master/env_master
|
@ -1,75 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Dtos;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
|
||||
namespace IW4MAdmin.Application.API
|
||||
{
|
||||
class EventApi : IEventApi
|
||||
{
|
||||
Queue<EventInfo> Events = new Queue<EventInfo>();
|
||||
DateTime LastFlagEvent;
|
||||
static string[] FlaggedMessageContains =
|
||||
{
|
||||
" wh ",
|
||||
"hax",
|
||||
"cheat",
|
||||
" hack ",
|
||||
"aim",
|
||||
"wall",
|
||||
"cheto",
|
||||
"hak",
|
||||
"bot"
|
||||
};
|
||||
int FlaggedMessageCount;
|
||||
|
||||
public Queue<EventInfo> GetEvents() => Events;
|
||||
|
||||
public void OnServerEvent(object sender, GameEvent E)
|
||||
{
|
||||
if (E.Type == GameEvent.EventType.Say && E.Origin.Level < Player.Permission.Trusted)
|
||||
{
|
||||
bool flaggedMessage = false;
|
||||
foreach (string msg in FlaggedMessageContains)
|
||||
flaggedMessage = flaggedMessage ? flaggedMessage : E.Data.ToLower().Contains(msg);
|
||||
|
||||
if (flaggedMessage)
|
||||
FlaggedMessageCount++;
|
||||
|
||||
if (FlaggedMessageCount > 3)
|
||||
{
|
||||
if (Events.Count > 20)
|
||||
Events.Dequeue();
|
||||
|
||||
FlaggedMessageCount = 0;
|
||||
|
||||
E.Owner.Broadcast("If you suspect someone of ^5CHEATING ^7use the ^5!report ^7command").Wait();
|
||||
Events.Enqueue(new EventInfo(
|
||||
EventInfo.EventType.ALERT,
|
||||
EventInfo.EventVersion.IW4MAdmin,
|
||||
"Chat indicates there may be a cheater",
|
||||
"Alert",
|
||||
E.Owner.Hostname, ""));
|
||||
}
|
||||
|
||||
if ((DateTime.UtcNow - LastFlagEvent).Minutes >= 3)
|
||||
{
|
||||
FlaggedMessageCount = 0;
|
||||
LastFlagEvent = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Report)
|
||||
{
|
||||
Events.Enqueue(new EventInfo(
|
||||
EventInfo.EventType.ALERT,
|
||||
EventInfo.EventVersion.IW4MAdmin,
|
||||
$"**{E.Origin.Name}** has reported **{E.Target.Name}** for: {E.Data.Trim()}",
|
||||
E.Target.Name, E.Origin.Name, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
Application/API/GameLogServer/IGameLogServer.cs
Normal file
12
Application/API/GameLogServer/IGameLogServer.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
using RestEase;
|
||||
|
||||
namespace IW4MAdmin.Application.API.GameLogServer
|
||||
{
|
||||
[Header("User-Agent", "IW4MAdmin-RestEase")]
|
||||
public interface IGameLogServer
|
||||
{
|
||||
[Get("log/{path}")]
|
||||
Task<LogInfo> Log([Path] string path);
|
||||
}
|
||||
}
|
17
Application/API/GameLogServer/LogInfo.cs
Normal file
17
Application/API/GameLogServer/LogInfo.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace IW4MAdmin.Application.API.GameLogServer
|
||||
{
|
||||
public class LogInfo
|
||||
{
|
||||
[JsonProperty("success")]
|
||||
public bool Success { get; set; }
|
||||
[JsonProperty("length")]
|
||||
public int Length { get; set; }
|
||||
[JsonProperty("data")]
|
||||
public string Data { get; set; }
|
||||
}
|
||||
}
|
@ -8,9 +8,13 @@ namespace IW4MAdmin.Application.API.Master
|
||||
public class ApiServer
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public int Id { get; set; }
|
||||
public long Id { get; set; }
|
||||
[JsonProperty("ip")]
|
||||
public string IPAddress { get; set; }
|
||||
[JsonProperty("port")]
|
||||
public short Port { get; set; }
|
||||
[JsonProperty("version")]
|
||||
public string Version { get; set; }
|
||||
[JsonProperty("gametype")]
|
||||
public string Gametype { get; set; }
|
||||
[JsonProperty("map")]
|
||||
|
@ -8,9 +8,13 @@ using SharedLibraryCore;
|
||||
|
||||
namespace IW4MAdmin.Application.API.Master
|
||||
{
|
||||
public class HeartbeatState
|
||||
{
|
||||
public bool Connected { get; set; }
|
||||
}
|
||||
|
||||
public class Heartbeat
|
||||
{
|
||||
|
||||
public static async Task Send(ApplicationManager mgr, bool firstHeartbeat = false)
|
||||
{
|
||||
var api = Endpoint.Get();
|
||||
@ -35,12 +39,14 @@ namespace IW4MAdmin.Application.API.Master
|
||||
{
|
||||
ClientNum = s.ClientNum,
|
||||
Game = s.GameName.ToString(),
|
||||
Version = s.Version,
|
||||
Gametype = s.Gametype,
|
||||
Hostname = s.Hostname,
|
||||
Map = s.CurrentMap.Name,
|
||||
MaxClientNum = s.MaxClients,
|
||||
Id = s.GetHashCode(),
|
||||
Port = (short)s.GetPort()
|
||||
Id = s.EndPoint,
|
||||
Port = (short)s.GetPort(),
|
||||
IPAddress = s.IP
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
|
@ -8,7 +8,7 @@ using RestEase;
|
||||
namespace IW4MAdmin.Application.API.Master
|
||||
{
|
||||
public class AuthenticationId
|
||||
{
|
||||
{
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
@ -36,7 +36,7 @@ namespace IW4MAdmin.Application.API.Master
|
||||
public class Endpoint
|
||||
{
|
||||
#if !DEBUG
|
||||
private static IMasterApi api = RestClient.For<IMasterApi>("http://api.raidmax.org:5000");
|
||||
private static readonly IMasterApi api = RestClient.For<IMasterApi>("http://api.raidmax.org:5000");
|
||||
#else
|
||||
private static IMasterApi api = RestClient.For<IMasterApi>("http://127.0.0.1");
|
||||
#endif
|
||||
@ -60,5 +60,11 @@ namespace IW4MAdmin.Application.API.Master
|
||||
|
||||
[Get("version")]
|
||||
Task<VersionInfo> GetVersion();
|
||||
|
||||
[Get("localization")]
|
||||
Task<List<SharedLibraryCore.Localization.Layout>> GetLocalization();
|
||||
|
||||
[Get("localization/{languageTag}")]
|
||||
Task<SharedLibraryCore.Localization.Layout> GetLocalization([Path("languageTag")] string languageTag);
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,11 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
|
||||
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
|
||||
<Version>2.1.0</Version>
|
||||
<Version>2.2.2.3</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Product>IW4MAdmin</Product>
|
||||
@ -19,13 +20,21 @@
|
||||
<AssemblyName>IW4MAdmin</AssemblyName>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
<Win32Resource />
|
||||
<RootNamespace>IW4MAdmin.Application</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RestEase" Version="1.4.5" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" />
|
||||
<PackageReference Include="RestEase" Version="1.4.7" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<TieredCompilation>true</TieredCompilation>
|
||||
<AssemblyVersion>2.2.2.3</AssemblyVersion>
|
||||
<FileVersion>2.2.2.3</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SharedLibraryCore\SharedLibraryCore.csproj">
|
||||
<Private>true</Private>
|
||||
@ -58,18 +67,33 @@
|
||||
<None Update="Localization\IW4MAdmin.en-US.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Localization\IW4MAdmin.es-EC.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Localization\IW4MAdmin.pt-BR.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Localization\IW4MAdmin.ru-RU.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="call $(ProjectDir)BuildScripts\PreBuild.bat $(SolutionDir) $(ProjectDir) $(TargetDir) $(OutDir)" />
|
||||
<Exec Command="call $(ProjectDir)BuildScripts\PreBuild.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(OutDir)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="call $(ProjectDir)BuildScripts\PostBuild.bat $(SolutionDir) $(ProjectDir) $(TargetDir) $(OutDir)" />
|
||||
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
|
||||
<Output TaskParameter="Assemblies" ItemName="CurrentAssembly" />
|
||||
</GetAssemblyIdentity>
|
||||
<Exec Command="call $(ProjectDir)BuildScripts\PostBuild.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(OutDir) %(CurrentAssembly.Version)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PostPublish" AfterTargets="Publish">
|
||||
<Exec Command="call $(ProjectDir)BuildScripts\PostPublish.bat $(SolutionDir) $(ProjectDir) $(TargetDir) $(OutDir)" />
|
||||
<Exec Command="call $(ProjectDir)BuildScripts\PostPublish.bat $(ProjectDir)..\ $(ProjectDir) $(TargetDir) $(ConfigurationName)" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
@ -2,6 +2,9 @@ set SolutionDir=%1
|
||||
set ProjectDir=%2
|
||||
set TargetDir=%3
|
||||
set OutDir=%4
|
||||
set Version=%5
|
||||
|
||||
echo %Version% > "%SolutionDir%DEPLOY\version.txt"
|
||||
|
||||
echo Copying dependency configs
|
||||
copy "%SolutionDir%WebfrontCore\%OutDir%*.deps.json" "%TargetDir%"
|
||||
@ -15,4 +18,17 @@ if not exist "%TargetDir%Plugins" (
|
||||
xcopy /y "%SolutionDir%Build\Plugins" "%TargetDir%Plugins\"
|
||||
|
||||
echo Copying plugins for publish
|
||||
xcopy /Y "%SolutionDir%BUILD\Plugins" "%SolutionDir%Publish\Windows\Plugins\"
|
||||
del %SolutionDir%BUILD\Plugins\Tests.dll
|
||||
xcopy /Y "%SolutionDir%BUILD\Plugins" "%SolutionDir%Publish\Windows\Plugins\"
|
||||
xcopy /Y "%SolutionDir%BUILD\Plugins" "%SolutionDir%Publish\WindowsPrerelease\Plugins\"
|
||||
|
||||
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\"
|
@ -1,8 +1,12 @@
|
||||
set SolutionDir=%1
|
||||
set ProjectDir=%2
|
||||
set TargetDir=%3
|
||||
set CurrentConfiguration=%4
|
||||
SET COPYCMD=/Y
|
||||
|
||||
echo Deleting extra language files
|
||||
|
||||
if exist "%SolutionDir%Publish\Windows\en-US\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\Windows\en-US'
|
||||
if exist "%SolutionDir%Publish\Windows\de\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\Windows\de'
|
||||
if exist "%SolutionDir%Publish\Windows\es\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\Windows\es'
|
||||
if exist "%SolutionDir%Publish\Windows\fr\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\Windows\fr'
|
||||
@ -13,6 +17,17 @@ if exist "%SolutionDir%Publish\Windows\ru\" powershell Remove-Item -Force -Recur
|
||||
if exist "%SolutionDir%Publish\Windows\zh-Hans\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\Windows\zh-Hans'
|
||||
if exist "%SolutionDir%Publish\Windows\zh-Hant\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\Windows\zh-Hant'
|
||||
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\en-US\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\en-US'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\de\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\de'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\es\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\es'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\fr\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\fr'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\it\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\it'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\ja\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\ja'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\ko\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\ko'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\ru\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\ru'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\zh-Hans\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\zh-Hans'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\zh-Hant\" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\zh-Hant'
|
||||
|
||||
echo Deleting extra runtime files
|
||||
if exist "%SolutionDir%Publish\Windows\runtimes\linux-arm" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\Windows\runtimes\linux-arm'
|
||||
if exist "%SolutionDir%Publish\Windows\runtimes\linux-arm64" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\Windows\runtimes\linux-arm64'
|
||||
@ -24,6 +39,69 @@ if exist "%SolutionDir%Publish\Windows\runtimes\osx-x64" powershell Remove-Item
|
||||
if exist "%SolutionDir%Publish\Windows\runtimes\win-arm" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\Windows\runtimes\win-arm'
|
||||
if exist "%SolutionDir%Publish\Windows\runtimes\win-arm64" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\Windows\runtimes\win-arm64'
|
||||
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\runtimes\linux-arm" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\runtimes\linux-arm'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\runtimes\linux-arm64" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\runtimes\linux-arm64'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\runtimes\linux-armel" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\runtimes\linux-armel'
|
||||
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\runtimes\osx" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\runtimes\osx'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\runtimes\osx-x64" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\runtimes\osx-x64'
|
||||
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\runtimes\win-arm" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\runtimes\win-arm'
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\runtimes\win-arm64" powershell Remove-Item -Force -Recurse '%SolutionDir%Publish\WindowsPrerelease\runtimes\win-arm64'
|
||||
|
||||
echo Deleting misc files
|
||||
if exist "%SolutionDir%Publish\Windows\web.config" del "%SolutionDir%Publish\Windows\web.config"
|
||||
del "%SolutionDir%Publish\Windows\*pdb"
|
||||
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\web.config" del "%SolutionDir%Publish\WindowsPrerelease\web.config"
|
||||
del "%SolutionDir%Publish\WindowsPrerelease\*pdb"
|
||||
|
||||
echo setting up library folders
|
||||
|
||||
if "%CurrentConfiguration%" == "Prerelease" (
|
||||
echo PR-Config
|
||||
if not exist "%SolutionDir%Publish\WindowsPrerelease\Configuration" md "%SolutionDir%Publish\WindowsPrerelease\Configuration"
|
||||
move "%SolutionDir%Publish\WindowsPrerelease\DefaultSettings.json" "%SolutionDir%Publish\WindowsPrerelease\Configuration\"
|
||||
)
|
||||
|
||||
if "%CurrentConfiguration%" == "Release" (
|
||||
echo R-Config
|
||||
if not exist "%SolutionDir%Publish\Windows\Configuration" md "%SolutionDir%Publish\Windows\Configuration"
|
||||
if exist "%SolutionDir%Publish\Windows\DefaultSettings.json" move "%SolutionDir%Publish\Windows\DefaultSettings.json" "%SolutionDir%Publish\Windows\Configuration\DefaultSettings.json"
|
||||
)
|
||||
|
||||
if "%CurrentConfiguration%" == "Prerelease" (
|
||||
echo PR-LIB
|
||||
if not exist "%SolutionDir%Publish\WindowsPrerelease\Lib\" md "%SolutionDir%Publish\WindowsPrerelease\Lib\"
|
||||
move "%SolutionDir%Publish\WindowsPrerelease\*.dll" "%SolutionDir%Publish\WindowsPrerelease\Lib\"
|
||||
move "%SolutionDir%Publish\WindowsPrerelease\*.json" "%SolutionDir%Publish\WindowsPrerelease\Lib\"
|
||||
)
|
||||
|
||||
if "%CurrentConfiguration%" == "Release" (
|
||||
echo R-LIB
|
||||
if not exist "%SolutionDir%Publish\Windows\Lib\" md "%SolutionDir%Publish\Windows\Lib\"
|
||||
move "%SolutionDir%Publish\Windows\*.dll" "%SolutionDir%Publish\Windows\Lib\"
|
||||
move "%SolutionDir%Publish\Windows\*.json" "%SolutionDir%Publish\Windows\Lib\"
|
||||
)
|
||||
|
||||
if "%CurrentConfiguration%" == "Prerelease" (
|
||||
echo PR-RT
|
||||
move "%SolutionDir%Publish\WindowsPrerelease\runtimes" "%SolutionDir%Publish\WindowsPrerelease\Lib\runtimes"
|
||||
if exist "%SolutionDir%Publish\WindowsPrerelease\refs" move "%SolutionDir%Publish\WindowsPrerelease\refs" "%SolutionDir%Publish\WindowsPrerelease\Lib\refs"
|
||||
)
|
||||
|
||||
|
||||
if "%CurrentConfiguration%" == "Release" (
|
||||
echo R-RT
|
||||
move "%SolutionDir%Publish\Windows\runtimes" "%SolutionDir%Publish\Windows\Lib\runtimes"
|
||||
if exist "%SolutionDir%Publish\Windows\refs" move "%SolutionDir%Publish\Windows\refs" "%SolutionDir%Publish\Windows\Lib\refs"
|
||||
)
|
||||
|
||||
echo making start scripts
|
||||
@(echo dotnet Lib/IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd"
|
||||
@(echo dotnet Lib/IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.cmd"
|
||||
|
||||
@(echo #!/bin/bash && echo dotnet Lib\IW4MAdmin.dll) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.sh"
|
||||
@(echo #!/bin/bash && echo dotnet Lib\IW4MAdmin.dll) > "%SolutionDir%Publish\Windows\StartIW4MAdmin.sh"
|
||||
|
||||
eCHO "%CurrentConfiguration%"
|
||||
|
@ -1,62 +0,0 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
class ConfigurationGenerator
|
||||
{
|
||||
public static List<ServerConfiguration> GenerateServerConfig(List<ServerConfiguration> configList)
|
||||
{
|
||||
|
||||
var loc = Utilities.CurrentLocalization.LocalizationSet;
|
||||
var newConfig = new ServerConfiguration();
|
||||
|
||||
while (string.IsNullOrEmpty(newConfig.IPAddress))
|
||||
{
|
||||
try
|
||||
{
|
||||
string input = Utilities.PromptString(loc["SETUP_SERVER_IP"]);
|
||||
IPAddress.Parse(input);
|
||||
newConfig.IPAddress = input;
|
||||
}
|
||||
|
||||
catch (Exception)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
while (newConfig.Port == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
newConfig.Port = Int16.Parse(Utilities.PromptString(loc["SETUP_SERVER_PORT"]));
|
||||
}
|
||||
|
||||
catch (Exception)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
newConfig.Password = Utilities.PromptString(loc["SETUP_SERVER_RCON"]);
|
||||
newConfig.AutoMessages = new List<string>();
|
||||
newConfig.Rules = new List<string>();
|
||||
|
||||
newConfig.UseT6MParser = Utilities.PromptBool(loc["SETUP_SERVER_USET6M"]);
|
||||
|
||||
configList.Add(newConfig);
|
||||
|
||||
if (Utilities.PromptBool(loc["SETUP_SERVER_SAVE"]))
|
||||
GenerateServerConfig(configList);
|
||||
|
||||
return configList;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
"AutoMessages": [
|
||||
"This server uses ^5IW4M Admin v{{VERSION}} ^7get it at ^5raidmax.org/IW4MAdmin",
|
||||
"^5IW4M Admin ^7sees ^5YOU!",
|
||||
"{{TOPSTATS}}",
|
||||
"This server has seen a total of ^5{{TOTALPLAYERS}} ^7players!",
|
||||
"Cheaters are ^1unwelcome ^7 on this server",
|
||||
"Did you know 8/10 people agree with unverified statistics?"
|
||||
@ -24,11 +25,6 @@
|
||||
"Name": "mp_rust"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Highrise",
|
||||
"Name": "mp_highrise"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Terminal",
|
||||
"Name": "mp_terminal"
|
||||
@ -39,16 +35,6 @@
|
||||
"Name": "mp_crash"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Skidrow",
|
||||
"Name": "mp_nightshift"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Quarry",
|
||||
"Name": "mp_quarry"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Afghan",
|
||||
"Name": "mp_afghan"
|
||||
@ -170,7 +156,7 @@
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "IW4 Credits",
|
||||
"Alias": "Test map",
|
||||
"Name": "iw4_credits"
|
||||
},
|
||||
|
||||
@ -189,6 +175,11 @@
|
||||
"Name": "mp_cargoship_sh"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Cargoship",
|
||||
"Name": "mp_cargoship"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Shipment",
|
||||
"Name": "mp_shipment"
|
||||
@ -215,23 +206,43 @@
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Favela - Tropical",
|
||||
"Alias": "Tropical Favela",
|
||||
"Name": "mp_fav_tropical"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Estate - Tropical",
|
||||
"Alias": "Tropical Estate",
|
||||
"Name": "mp_estate_tropical"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Crash - Tropical",
|
||||
"Alias": "Tropical Crash",
|
||||
"Name": "mp_crash_tropical"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Forgotten City",
|
||||
"Name": "mp_bloc_sh"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Crossfire",
|
||||
"Name": "mp_cross_fire"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Bloc",
|
||||
"Name": "mp_bloc"
|
||||
},
|
||||
|
||||
{
|
||||
"Alias": "Oilrig",
|
||||
"Name": "oilrig"
|
||||
},
|
||||
|
||||
{
|
||||
"Name": "Village",
|
||||
"Alias": "co_hunted"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
11
Application/EventParsers/IW3EventParser.cs
Normal file
11
Application/EventParsers/IW3EventParser.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
class IW3EventParser : IW4EventParser
|
||||
{
|
||||
public override string GetGameDir() => "main";
|
||||
}
|
||||
}
|
@ -1,110 +1,231 @@
|
||||
using System;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
|
||||
namespace Application.EventParsers
|
||||
namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
class IW4EventParser : IEventParser
|
||||
{
|
||||
public GameEvent GetEvent(Server server, string logLine)
|
||||
{
|
||||
string[] lineSplit = logLine.Split(';');
|
||||
string cleanedEventLine = Regex.Replace(lineSplit[0], @"[0-9]+:[0-9]+\ ", "").Trim();
|
||||
private const string SayRegex = @"(say|sayteam);(.{1,32});([0-9]+)(.*);(.*)";
|
||||
|
||||
if (cleanedEventLine[0] == 'K')
|
||||
public virtual GameEvent GetEvent(Server server, string logLine)
|
||||
{
|
||||
logLine = Regex.Replace(logLine, @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim();
|
||||
string[] lineSplit = logLine.Split(';');
|
||||
string eventType = lineSplit[0];
|
||||
|
||||
if (eventType == "JoinTeam")
|
||||
{
|
||||
var origin = server.GetClientsAsList().FirstOrDefault(c => c.NetworkId == lineSplit[1].ConvertLong());
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.JoinTeam,
|
||||
Data = eventType,
|
||||
Origin = origin,
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
|
||||
if (eventType == "say" || eventType == "sayteam")
|
||||
{
|
||||
var matchResult = Regex.Match(logLine, SayRegex);
|
||||
|
||||
if (matchResult.Success)
|
||||
{
|
||||
string message = matchResult.Groups[5].ToString()
|
||||
.Replace("\x15", "")
|
||||
.Trim();
|
||||
|
||||
var origin = server.GetClientsAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2));
|
||||
|
||||
if (message[0] == '!' || message[0] == '@')
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Command,
|
||||
Data = message,
|
||||
Origin = origin,
|
||||
Owner = server,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Say,
|
||||
Data = message,
|
||||
Origin = origin,
|
||||
Owner = server,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType == "K")
|
||||
{
|
||||
if (!server.CustomCallback)
|
||||
{
|
||||
var origin = server.GetClientsAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6));
|
||||
var target = server.GetClientsAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2));
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Script,
|
||||
Type = GameEvent.EventType.Kill,
|
||||
Data = logLine,
|
||||
Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6)),
|
||||
Target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)),
|
||||
Origin = origin,
|
||||
Target = target,
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanedEventLine == "say" || cleanedEventLine == "sayteam")
|
||||
if (eventType == "ScriptKill")
|
||||
{
|
||||
var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
|
||||
var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Say,
|
||||
Data = lineSplit[4].Replace("\x15", ""),
|
||||
Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)),
|
||||
Owner = server,
|
||||
Message = lineSplit[4].Replace("\x15", "")
|
||||
};
|
||||
}
|
||||
|
||||
if (cleanedEventLine.Contains("ScriptKill"))
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Script,
|
||||
Type = GameEvent.EventType.ScriptKill,
|
||||
Data = logLine,
|
||||
Origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()),
|
||||
Target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong()),
|
||||
Origin = origin,
|
||||
Target = target,
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
|
||||
if (cleanedEventLine.Contains("ExitLevel"))
|
||||
if (eventType == "ScriptDamage")
|
||||
{
|
||||
var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
|
||||
var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[2].ConvertLong());
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.ScriptDamage,
|
||||
Data = logLine,
|
||||
Origin = origin,
|
||||
Target = target,
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
|
||||
// damage
|
||||
if (eventType == "D")
|
||||
{
|
||||
if (!server.CustomCallback)
|
||||
{
|
||||
if (Regex.Match(eventType, @"^(D);((?:bot[0-9]+)|(?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$").Success)
|
||||
{
|
||||
var origin = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong());
|
||||
var target = server.GetClientsAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong());
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Damage,
|
||||
Data = eventType,
|
||||
Origin = origin,
|
||||
Target = target,
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// join
|
||||
if (eventType == "J")
|
||||
{
|
||||
var regexMatch = Regex.Match(logLine, @"^(J;)(.{1,32});([0-9]+);(.*)$");
|
||||
if (regexMatch.Success)
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.PreConnect,
|
||||
Data = logLine,
|
||||
Owner = server,
|
||||
Origin = new EFClient()
|
||||
{
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Active = false,
|
||||
Name = regexMatch.Groups[4].ToString().StripColors(),
|
||||
},
|
||||
NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
|
||||
ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
|
||||
State = EFClient.ClientState.Connecting,
|
||||
CurrentServer = server
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType == "Q")
|
||||
{
|
||||
var regexMatch = Regex.Match(logLine, @"^(Q;)(.{1,32});([0-9]+);(.*)$");
|
||||
if (regexMatch.Success)
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.PreDisconnect,
|
||||
Data = logLine,
|
||||
Owner = server,
|
||||
Origin = new EFClient()
|
||||
{
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Active = false,
|
||||
Name = regexMatch.Groups[4].ToString().StripColors()
|
||||
},
|
||||
NetworkId = regexMatch.Groups[2].ToString().ConvertLong(),
|
||||
ClientNumber = Convert.ToInt32(regexMatch.Groups[3].ToString()),
|
||||
State = EFClient.ClientState.Disconnecting
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (eventType.Contains("ExitLevel"))
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.MapEnd,
|
||||
Data = lineSplit[0],
|
||||
Origin = new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Target = new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Origin = Utilities.IW4MAdminClient(server),
|
||||
Target = Utilities.IW4MAdminClient(server),
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
|
||||
if (cleanedEventLine.Contains("InitGame"))
|
||||
if (eventType.Contains("InitGame"))
|
||||
{
|
||||
string dump = eventType.Replace("InitGame: ", "");
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.MapChange,
|
||||
Data = lineSplit[0],
|
||||
Origin = new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Target = new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Owner = server
|
||||
Origin = Utilities.IW4MAdminClient(server),
|
||||
Target = Utilities.IW4MAdminClient(server),
|
||||
Owner = server,
|
||||
Extra = dump.DictionaryFromKeyValue()
|
||||
};
|
||||
}
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Unknown,
|
||||
Origin = new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Target = new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Origin = Utilities.IW4MAdminClient(server),
|
||||
Target = Utilities.IW4MAdminClient(server),
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
|
||||
// other parsers can derive from this parser so we make it virtual
|
||||
public virtual string GetGameDir() => "userraw";
|
||||
public virtual string GetGameDir()
|
||||
{
|
||||
return "userraw";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,58 @@
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Application.EventParsers
|
||||
namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
class IW5EventParser : IW4EventParser
|
||||
{
|
||||
public override string GetGameDir() => "rzodemo";
|
||||
public override string GetGameDir()
|
||||
{
|
||||
return "logs";
|
||||
}
|
||||
|
||||
public override GameEvent GetEvent(Server server, string logLine)
|
||||
{
|
||||
string cleanedEventLine = Regex.Replace(logLine, @"[0-9]+:[0-9]+\ ", "").Trim();
|
||||
|
||||
if (cleanedEventLine.Contains("J;"))
|
||||
{
|
||||
string[] lineSplit = cleanedEventLine.Split(';');
|
||||
|
||||
int clientNum = Int32.Parse(lineSplit[2]);
|
||||
|
||||
var player = new EFClient()
|
||||
{
|
||||
NetworkId = lineSplit[1].ConvertLong(),
|
||||
ClientNumber = clientNum,
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Active = false,
|
||||
Name = lineSplit[3]
|
||||
}
|
||||
};
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.PreConnect,
|
||||
Origin = new EFClient()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Target = new EFClient()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Owner = server,
|
||||
Extra = player
|
||||
};
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return base.GetEvent(server, logLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
Application/EventParsers/T5MEventParser.cs
Normal file
11
Application/EventParsers/T5MEventParser.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
class T5MEventParser : IW4EventParser
|
||||
{
|
||||
public override string GetGameDir() => "v2";
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
@ -6,107 +7,10 @@ using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
|
||||
namespace Application.EventParsers
|
||||
namespace IW4MAdmin.Application.EventParsers
|
||||
{
|
||||
class T6MEventParser : IEventParser
|
||||
class T6MEventParser : IW4EventParser
|
||||
{
|
||||
public GameEvent GetEvent(Server server, string logLine)
|
||||
{
|
||||
string cleanedLogLine = Regex.Replace(logLine, @"^ *[0-9]+:[0-9]+ *", "");
|
||||
string[] lineSplit = cleanedLogLine.Split(';');
|
||||
|
||||
if (lineSplit[0][0] == 'K')
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Script,
|
||||
Data = cleanedLogLine,
|
||||
Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6)),
|
||||
Target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)),
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
|
||||
if (lineSplit[0][0] == 'D')
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Damage,
|
||||
Data = cleanedLogLine,
|
||||
Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 6)),
|
||||
Target = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)),
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
|
||||
if (lineSplit[0] == "say" || lineSplit[0] == "sayteam")
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Say,
|
||||
Data = lineSplit[4],
|
||||
Origin = server.GetPlayersAsList().First(c => c.ClientNumber == Utilities.ClientIdFromString(lineSplit, 2)),
|
||||
Owner = server,
|
||||
Message = lineSplit[4]
|
||||
};
|
||||
}
|
||||
|
||||
if (lineSplit[0].Contains("ExitLevel"))
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.MapEnd,
|
||||
Data = lineSplit[0],
|
||||
Origin = new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Target = new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
|
||||
/*if (lineSplit[0].Contains("ShutdownGame"))
|
||||
{
|
||||
|
||||
}*/
|
||||
|
||||
if (lineSplit[0].Contains("InitGame"))
|
||||
{
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.MapChange,
|
||||
Data = lineSplit[0],
|
||||
Origin = new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Target = new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
|
||||
return new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Unknown,
|
||||
Origin = new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Target = new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
},
|
||||
Owner = server
|
||||
};
|
||||
}
|
||||
|
||||
public string GetGameDir() => $"t6r{Path.DirectorySeparatorChar}data";
|
||||
public override string GetGameDir() => $"t6r{Path.DirectorySeparatorChar}data";
|
||||
}
|
||||
}
|
||||
|
23
Application/GameEventHandler.cs
Normal file
23
Application/GameEventHandler.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Events;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
class GameEventHandler : IEventHandler
|
||||
{
|
||||
readonly IManager Manager;
|
||||
public GameEventHandler(IManager mgr)
|
||||
{
|
||||
Manager = mgr;
|
||||
}
|
||||
|
||||
public void AddEvent(GameEvent gameEvent)
|
||||
{
|
||||
((Manager as ApplicationManager).OnServerEvent)(this, new GameEventArgs(null, false, gameEvent));
|
||||
}
|
||||
}
|
||||
}
|
88
Application/IO/GameLogEventDetection.cs
Normal file
88
Application/IO/GameLogEventDetection.cs
Normal file
@ -0,0 +1,88 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Application.IO
|
||||
{
|
||||
class GameLogEventDetection
|
||||
{
|
||||
Server Server;
|
||||
long PreviousFileSize;
|
||||
IGameLogReader Reader;
|
||||
readonly string GameLogFile;
|
||||
|
||||
class EventState
|
||||
{
|
||||
public ILogger Log { get; set; }
|
||||
public string ServerId { get; set; }
|
||||
}
|
||||
|
||||
public GameLogEventDetection(Server server, string gameLogPath, string gameLogName)
|
||||
{
|
||||
GameLogFile = gameLogPath;
|
||||
// todo: abtract this more
|
||||
if (gameLogPath.StartsWith("http"))
|
||||
{
|
||||
Reader = new GameLogReaderHttp(gameLogPath, server.EventParser);
|
||||
}
|
||||
else
|
||||
{
|
||||
Reader = new GameLogReader(gameLogPath, server.EventParser);
|
||||
}
|
||||
|
||||
Server = server;
|
||||
}
|
||||
|
||||
public async Task PollForChanges()
|
||||
{
|
||||
while (!Server.Manager.ShutdownRequested())
|
||||
{
|
||||
if ((Server.Manager as ApplicationManager).IsInitialized)
|
||||
{
|
||||
try
|
||||
{
|
||||
await UpdateLogEvents();
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
Server.Logger.WriteWarning($"Failed to update log event for {Server.EndPoint}");
|
||||
Server.Logger.WriteDebug($"Exception: {e.Message}");
|
||||
Server.Logger.WriteDebug($"StackTrace: {e.StackTrace}");
|
||||
}
|
||||
}
|
||||
Thread.Sleep(Reader.UpdateInterval);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateLogEvents()
|
||||
{
|
||||
long fileSize = Reader.Length;
|
||||
|
||||
if (PreviousFileSize == 0)
|
||||
PreviousFileSize = fileSize;
|
||||
|
||||
long fileDiff = fileSize - PreviousFileSize;
|
||||
|
||||
// this makes the http log get pulled
|
||||
if (fileDiff < 1 && fileSize != -1)
|
||||
return;
|
||||
|
||||
PreviousFileSize = fileSize;
|
||||
|
||||
var events = await Reader.ReadEventsFromLog(Server, fileDiff, 0);
|
||||
|
||||
foreach (var ev in events)
|
||||
{
|
||||
Server.Manager.GetEventHandler().AddEvent(ev);
|
||||
await ev.WaitAsync();
|
||||
}
|
||||
|
||||
PreviousFileSize = fileSize;
|
||||
}
|
||||
}
|
||||
}
|
70
Application/IO/GameLogReader.cs
Normal file
70
Application/IO/GameLogReader.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Application.IO
|
||||
{
|
||||
class GameLogReader : IGameLogReader
|
||||
{
|
||||
IEventParser Parser;
|
||||
readonly string LogFile;
|
||||
|
||||
public long Length => new FileInfo(LogFile).Length;
|
||||
|
||||
public int UpdateInterval => 300;
|
||||
|
||||
public GameLogReader(string logFile, IEventParser parser)
|
||||
{
|
||||
LogFile = logFile;
|
||||
Parser = parser;
|
||||
}
|
||||
|
||||
public async Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
|
||||
{
|
||||
// allocate the bytes for the new log lines
|
||||
List<string> logLines = new List<string>();
|
||||
|
||||
// open the file as a stream
|
||||
using (var rd = new StreamReader(new FileStream(LogFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Utilities.EncodingType))
|
||||
{
|
||||
// todo: max async
|
||||
// take the old start position and go back the number of new characters
|
||||
rd.BaseStream.Seek(-fileSizeDiff, SeekOrigin.End);
|
||||
// the difference should be in the range of a int :P
|
||||
string newLine;
|
||||
while (!String.IsNullOrEmpty(newLine = rd.ReadLine()))
|
||||
{
|
||||
logLines.Add(newLine);
|
||||
}
|
||||
}
|
||||
|
||||
List<GameEvent> events = new List<GameEvent>();
|
||||
|
||||
// parse each line
|
||||
foreach (string eventLine in logLines)
|
||||
{
|
||||
if (eventLine.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
// todo: catch elsewhere
|
||||
events.Add(Parser.GetEvent(server, eventLine));
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
server.Logger.WriteWarning("Could not properly parse event line");
|
||||
server.Logger.WriteDebug(e.Message);
|
||||
server.Logger.WriteDebug(eventLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
}
|
||||
}
|
74
Application/IO/GameLogReaderHttp.cs
Normal file
74
Application/IO/GameLogReaderHttp.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using IW4MAdmin.Application.API.GameLogServer;
|
||||
using RestEase;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using static SharedLibraryCore.Utilities;
|
||||
|
||||
namespace IW4MAdmin.Application.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// provides capibility of reading log files over HTTP
|
||||
/// </summary>
|
||||
class GameLogReaderHttp : IGameLogReader
|
||||
{
|
||||
readonly IEventParser Parser;
|
||||
readonly IGameLogServer Api;
|
||||
readonly string LogFile;
|
||||
|
||||
public GameLogReaderHttp(string logFile, IEventParser parser)
|
||||
{
|
||||
LogFile = logFile;
|
||||
Parser = parser;
|
||||
Api = RestClient.For<IGameLogServer>(logFile);
|
||||
}
|
||||
|
||||
public long Length => -1;
|
||||
|
||||
public int UpdateInterval => 350;
|
||||
|
||||
public async Task<ICollection<GameEvent>> ReadEventsFromLog(Server server, long fileSizeDiff, long startPosition)
|
||||
{
|
||||
#if DEBUG == true
|
||||
server.Logger.WriteDebug($"Begin reading from http log");
|
||||
#endif
|
||||
var events = new List<GameEvent>();
|
||||
string b64Path = server.LogPath.ToBase64UrlSafeString();
|
||||
var response = await Api.Log(b64Path);
|
||||
|
||||
if (!response.Success)
|
||||
{
|
||||
server.Logger.WriteError($"Could not get log server info of {LogFile}/{b64Path} ({server.LogPath})");
|
||||
return events;
|
||||
}
|
||||
|
||||
// parse each line
|
||||
foreach (string eventLine in response.Data.Split(Environment.NewLine))
|
||||
{
|
||||
if (eventLine.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var e = Parser.GetEvent(server, eventLine);
|
||||
#if DEBUG == true
|
||||
server.Logger.WriteDebug($"Parsed event with id {e.Id} from http");
|
||||
#endif
|
||||
events.Add(e);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
server.Logger.WriteWarning("Could not properly parse event line");
|
||||
server.Logger.WriteDebug(e.Message);
|
||||
server.Logger.WriteDebug(eventLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
}
|
||||
}
|
1018
Application/IW4MServer.cs
Normal file
1018
Application/IW4MServer.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,33 +1,75 @@
|
||||
using SharedLibraryCore;
|
||||
using IW4MAdmin.Application.API.Master;
|
||||
using SharedLibraryCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Application.Localization
|
||||
{
|
||||
public class Configure
|
||||
{
|
||||
public static void Initialize()
|
||||
public static void Initialize(string customLocale)
|
||||
{
|
||||
string currentLocal = CultureInfo.CurrentCulture.Name;
|
||||
string localizationFile = $"Localization{Path.DirectorySeparatorChar}IW4MAdmin.{currentLocal}.json";
|
||||
string localizationContents;
|
||||
string currentLocale = string.IsNullOrEmpty(customLocale) ? CultureInfo.CurrentCulture.Name : customLocale;
|
||||
string[] localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale}.json");
|
||||
|
||||
if (File.Exists(localizationFile))
|
||||
try
|
||||
{
|
||||
localizationContents = File.ReadAllText(localizationFile);
|
||||
|
||||
var api = Endpoint.Get();
|
||||
var localization = api.GetLocalization(currentLocale).Result;
|
||||
Utilities.CurrentLocalization = localization;
|
||||
return;
|
||||
}
|
||||
|
||||
else
|
||||
catch (Exception)
|
||||
{
|
||||
localizationFile = $"Localization{Path.DirectorySeparatorChar}IW4MAdmin.en-US.json";
|
||||
localizationContents = File.ReadAllText(localizationFile);
|
||||
// the online localization failed so will default to local files
|
||||
}
|
||||
|
||||
Utilities.CurrentLocalization = Newtonsoft.Json.JsonConvert.DeserializeObject<SharedLibraryCore.Localization.Layout>(localizationContents);
|
||||
// culture doesn't exist so we just want language
|
||||
if (localizationFiles.Length == 0)
|
||||
{
|
||||
localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale.Substring(0, 2)}*.json");
|
||||
}
|
||||
|
||||
// language doesn't exist either so defaulting to english
|
||||
if (localizationFiles.Length == 0)
|
||||
{
|
||||
localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), "*.en-US.json");
|
||||
}
|
||||
|
||||
// this should never happen unless the localization folder is empty
|
||||
if (localizationFiles.Length == 0)
|
||||
{
|
||||
throw new Exception("No localization files were found");
|
||||
}
|
||||
|
||||
var localizationDict = new Dictionary<string, string>();
|
||||
|
||||
foreach (string filePath in localizationFiles)
|
||||
{
|
||||
var localizationContents = File.ReadAllText(filePath, Encoding.UTF8);
|
||||
var eachLocalizationFile = Newtonsoft.Json.JsonConvert.DeserializeObject<SharedLibraryCore.Localization.Layout>(localizationContents);
|
||||
|
||||
foreach (var item in eachLocalizationFile.LocalizationIndex.Set)
|
||||
{
|
||||
if (!localizationDict.TryAdd(item.Key, item.Value))
|
||||
{
|
||||
Program.ServerManager.GetLogger(0).WriteError($"Could not add locale string {item.Key} to localization");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string localizationFile = $"{Path.Join(Utilities.OperatingDirectory, "Localization")}{Path.DirectorySeparatorChar}IW4MAdmin.{currentLocale}-{currentLocale.ToUpper()}.json";
|
||||
|
||||
Utilities.CurrentLocalization = new SharedLibraryCore.Localization.Layout(localizationDict)
|
||||
{
|
||||
LocalizationName = currentLocale,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,109 +1,269 @@
|
||||
{
|
||||
"LocalizationName": "en-US",
|
||||
"LocalizationSet": {
|
||||
"MANAGER_VERSION_FAIL": "Could not get latest IW4MAdmin version",
|
||||
"MANAGER_VERSION_UPDATE": "has an update. Latest version is",
|
||||
"MANAGER_VERSION_CURRENT": "Your version is",
|
||||
"MANAGER_VERSION_SUCCESS": "IW4MAdmin is up to date",
|
||||
"MANAGER_INIT_FAIL": "Fatal error during initialization",
|
||||
"MANAGER_EXIT": "Press any key to exit...",
|
||||
"SETUP_ENABLE_WEBFRONT": "Enable webfront",
|
||||
"SETUP_ENABLE_MULTIOWN": "Enable multiple owners",
|
||||
"SETUP_ENABLE_STEPPEDPRIV": "Enable stepped privilege hierarchy",
|
||||
"SETUP_ENABLE_CUSTOMSAY": "Enable custom say name",
|
||||
"SETUP_SAY_NAME": "Enter custom say name",
|
||||
"SETUP_USE_CUSTOMENCODING": "Use custom encoding parser",
|
||||
"SETUP_ENCODING_STRING": "Enter encoding string",
|
||||
"SETUP_ENABLE_VPNS": "Enable client VPNs",
|
||||
"SETUP_IPHUB_KEY": "Enter iphub.info api key",
|
||||
"SETUP_DISPLAY_DISCORD": "Display discord link on webfront",
|
||||
"SETUP_DISCORD_INVITE": "Enter discord invite link",
|
||||
"SETUP_SERVER_USET6M": "Use T6M parser",
|
||||
"SETUP_SERVER_IP": "Enter server IP Address",
|
||||
"SETUP_SERVER_PORT": "Enter server port",
|
||||
"SETUP_SERVER_RCON": "Enter server RCon password",
|
||||
"SETUP_SERVER_SAVE": "Configuration saved, add another",
|
||||
"SERVER_KICK_VPNS_NOTALLOWED": "VPNs are not allowed on this server",
|
||||
"SERVER_KICK_TEXT": "You were kicked",
|
||||
"SERVER_KICK_MINNAME": "Your name must contain at least 3 characters",
|
||||
"SERVER_KICK_NAME_INUSE": "Your name is being used by someone else",
|
||||
"SERVER_KICK_GENERICNAME": "Please change your name using /name",
|
||||
"SERVER_KICK_CONTROLCHARS": "Your name cannot contain control characters",
|
||||
"SERVER_TB_TEXT": "You're temporarily banned",
|
||||
"SERVER_TB_REMAIN": "You are temporarily banned",
|
||||
"SERVER_BAN_TEXT": "You're banned",
|
||||
"SERVER_BAN_PREV": "Previously banned for",
|
||||
"SERVER_BAN_APPEAL": "appeal at",
|
||||
"SERVER_REPORT_COUNT": "There are ^5{0} ^7recent reports",
|
||||
"SERVER_WARNLIMT_REACHED": "Too many warnings",
|
||||
"SERVER_WARNING": "Warning",
|
||||
"SERVER_WEBSITE_GENERIC": "this server's website",
|
||||
"BROADCAST_ONLINE": "^5IW4MADMIN ^7is now ^2ONLINE",
|
||||
"BROADCAST_OFFLINE": "IW4MAdmin is going offline",
|
||||
"COMMAND_HELP_SYNTAX": "syntax:",
|
||||
"COMMAND_HELP_OPTIONAL": "optional",
|
||||
"COMMAND_UNKNOWN": "You entered an unknown command",
|
||||
"COMMAND_NOACCESS": "You do not have access to that command",
|
||||
"COMMAND_NOTAUTHORIZED": "You are not authorized to execute that command",
|
||||
"COMMAND_MISSINGARGS": "Not enough arguments supplied",
|
||||
"COMMAND_TARGET_MULTI": "Multiple players match that name",
|
||||
"COMMAND_TARGET_NOTFOUND": "Unable to find specified player",
|
||||
"PLUGIN_IMPORTER_NOTFOUND": "No plugins found to load",
|
||||
"PLUGIN_IMPORTER_REGISTERCMD": "Registered command",
|
||||
"COMMANDS_OWNER_SUCCESS": "Congratulations, you have claimed ownership of this server!",
|
||||
"COMMANDS_OWNER_FAIL": "This server already has an owner",
|
||||
"COMMANDS_WARN_FAIL": "You do not have the required privileges to warn",
|
||||
"COMMANDS_WARNCLEAR_SUCCESS": "All warning cleared for",
|
||||
"COMMANDS_KICK_SUCCESS": "has been kicked",
|
||||
"COMMANDS_KICK_FAIL": "You do not have the required privileges to kick",
|
||||
"COMMANDS_TEMPBAN_SUCCESS": "has been temporarily banned for",
|
||||
"COMMANDS_TEMPBAN_FAIL": "You cannot temporarily ban",
|
||||
"COMMANDS_BAN_SUCCESS": "has been permanently banned",
|
||||
"COMMANDS_BAN_FAIL": "You cannot ban",
|
||||
"COMMANDS_UNBAN_SUCCESS": "Successfully unbanned",
|
||||
"COMMANDS_UNBAN_FAIL": "is not banned",
|
||||
"COMMANDS_HELP_NOTFOUND": "Could not find that command",
|
||||
"COMMANDS_HELP_MOREINFO": "Type !help <command name> to get command usage syntax",
|
||||
"COMMANDS_FASTRESTART_UNMASKED": "fast restarted the map",
|
||||
"COMMANDS_FASTRESTART_MASKED": "The map has been fast restarted",
|
||||
"COMMANDS_MAPROTATE": "Map rotating in ^55 ^7seconds",
|
||||
"COMMANDS_SETLEVEL_SELF": "You cannot change your own level",
|
||||
"COMMANDS_SETLEVEL_OWNER": "There can only be 1 owner. Modify your settings if multiple owners are required",
|
||||
"COMMANDS_SETLEVEL_STEPPEDDISABLED": "This server does not allow you to promote",
|
||||
"COMMANDS_SETLEVEL_LEVELTOOHIGH": "You can only promote ^5{0} ^7to ^5{1} ^7or lower privilege",
|
||||
"COMMANDS_SETLEVEL_SUCCESS_TARGET": "Congratulations! You have been promoted to",
|
||||
"COMMANDS_SETLEVEL_SUCCESS": "was successfully promoted",
|
||||
"COMMANDS_SETLEVEL_FAIL": "Invalid group specified",
|
||||
"COMMANDS_ADMINS_NONE": "No visible administrators online",
|
||||
"COMMANDS_MAP_SUCCESS": "Changing to map",
|
||||
"COMMANDS_MAP_UKN": "Attempting to change to unknown map",
|
||||
"COMMANDS_FIND_MIN": "Please enter at least 3 characters",
|
||||
"COMMANDS_FIND_EMPTY": "No players found",
|
||||
"COMMANDS_RULES_NONE": "The server owner has not set any rules",
|
||||
"COMMANDS_FLAG_SUCCESS": "You have flagged",
|
||||
"COMMANDS_FLAG_UNFLAG": "You have unflagged",
|
||||
"COMMANDS_FLAG_FAIL": "You cannot flag",
|
||||
"COMMANDS_REPORT_FAIL_CAMP": "You cannot report an player for camping",
|
||||
"COMMANDS_REPORT_FAIL_DUPLICATE": "You have already reported this player",
|
||||
"COMMANDS_REPORT_FAIL_SELF": "You cannot report yourself",
|
||||
"COMMANDS_REPORT_FAIL": "You cannot report",
|
||||
"COMMANDS_REPORT_SUCCESS": "Thank you for your report, an administrator has been notified",
|
||||
"COMMANDS_REPORTS_CLEAR_SUCCESS": "Reports successfully cleared",
|
||||
"COMMANDS_REPORTS_NONE": "No players reported yet",
|
||||
"COMMANDS_MASK_ON": "You are now masked",
|
||||
"COMMANDS_MASK_OFF": "You are now unmasked",
|
||||
"COMMANDS_BANINFO_NONE": "No active ban was found for that player",
|
||||
"COMMANDS_BANINO_SUCCESS": "was banned by ^5{0} ^7for:",
|
||||
"COMMANDS_ALIAS_ALIASES": "Aliases",
|
||||
"COMMANDS_ALIAS_IPS": "IPs",
|
||||
"COMMANDS_RCON_SUCCESS": "Successfully sent RCon command",
|
||||
"COMMANDS_PLUGINS_LOADED": "Loaded Plugins",
|
||||
"COMMANDS_IP_SUCCESS": "Your external IP is",
|
||||
"COMMANDS_PRUNE_FAIL": "Invalid number of inactive days",
|
||||
"COMMANDS_PRUNE_SUCCESS": "inactive privileged users were pruned",
|
||||
"COMMANDS_PASSWORD_FAIL": "Your password must be at least 5 characters long",
|
||||
"COMMANDS_PASSWORD_SUCCESS": "Your password has been set successfully",
|
||||
"COMMANDS_PING_TARGET": "ping is",
|
||||
"COMMANDS_PING_SELF": "Your ping is"
|
||||
}
|
||||
"LocalizationName": "en-US",
|
||||
"LocalizationIndex": {
|
||||
"Set": {
|
||||
"BROADCAST_OFFLINE": "^5IW4MAdmin ^7is going ^1OFFLINE",
|
||||
"BROADCAST_ONLINE": "^5IW4MADMIN ^7is now ^2ONLINE",
|
||||
"COMMAND_HELP_OPTIONAL": "optional",
|
||||
"COMMAND_HELP_SYNTAX": "syntax:",
|
||||
"COMMAND_MISSINGARGS": "Not enough arguments supplied",
|
||||
"COMMAND_NOACCESS": "You do not have access to that command",
|
||||
"COMMAND_NOTAUTHORIZED": "You are not authorized to execute that command",
|
||||
"COMMAND_TARGET_MULTI": "Multiple players match that name",
|
||||
"COMMAND_TARGET_NOTFOUND": "Unable to find specified player",
|
||||
"COMMAND_UNKNOWN": "You entered an unknown command",
|
||||
"COMMANDS_ADMINS_DESC": "list currently connected privileged clients",
|
||||
"COMMANDS_ADMINS_NONE": "No visible administrators online",
|
||||
"COMMANDS_ALIAS_ALIASES": "Aliases",
|
||||
"COMMANDS_ALIAS_DESC": "get past aliases and ips of a client",
|
||||
"COMMANDS_ALIAS_IPS": "IPs",
|
||||
"COMMANDS_ARGS_CLEAR": "clear",
|
||||
"COMMANDS_ARGS_CLIENTID": "client id",
|
||||
"COMMANDS_ARGS_COMMANDS": "commands",
|
||||
"COMMANDS_ARGS_DURATION": "duration (m|h|d|w|y)",
|
||||
"COMMANDS_ARGS_INACTIVE": "inactive days",
|
||||
"COMMANDS_ARGS_LEVEL": "level",
|
||||
"COMMANDS_ARGS_MAP": "map",
|
||||
"COMMANDS_ARGS_MESSAGE": "message",
|
||||
"COMMANDS_ARGS_PASSWORD": "password",
|
||||
"COMMANDS_ARGS_PLAYER": "player",
|
||||
"COMMANDS_ARGS_REASON": "reason",
|
||||
"COMMANDS_BAN_DESC": "permanently ban a client from the server",
|
||||
"COMMANDS_BAN_FAIL": "You cannot ban",
|
||||
"COMMANDS_BAN_SUCCESS": "has been permanently banned",
|
||||
"COMMANDS_BANINFO_DESC": "get information about a ban for a client",
|
||||
"COMMANDS_BANINFO_NONE": "No active ban was found for that player",
|
||||
"COMMANDS_BANINO_SUCCESS": "was banned by ^5{0} ^7for:",
|
||||
"COMMANDS_FASTRESTART_DESC": "fast restart current map",
|
||||
"COMMANDS_FASTRESTART_MASKED": "The map has been fast restarted",
|
||||
"COMMANDS_FASTRESTART_UNMASKED": "fast restarted the map",
|
||||
"COMMANDS_FIND_DESC": "find client in database",
|
||||
"COMMANDS_FIND_EMPTY": "No players found",
|
||||
"COMMANDS_FIND_MIN": "Please enter at least 3 characters",
|
||||
"COMMANDS_FLAG_DESC": "flag a suspicious client and announce to admins on join",
|
||||
"COMMANDS_FLAG_FAIL": "You cannot flag",
|
||||
"COMMANDS_FLAG_SUCCESS": "You have flagged",
|
||||
"COMMANDS_FLAG_UNFLAG": "You have unflagged",
|
||||
"COMMANDS_HELP_DESC": "list all available commands",
|
||||
"COMMANDS_HELP_MOREINFO": "Type !help <command name> to get command usage syntax",
|
||||
"COMMANDS_HELP_NOTFOUND": "Could not find that command",
|
||||
"COMMANDS_IP_DESC": "view your external IP address",
|
||||
"COMMANDS_IP_SUCCESS": "Your external IP is",
|
||||
"COMMANDS_KICK_DESC": "kick a client by name",
|
||||
"COMMANDS_KICK_FAIL": "You do not have the required privileges to kick",
|
||||
"COMMANDS_KICK_SUCCESS": "has been kicked",
|
||||
"COMMANDS_LIST_DESC": "list active clients",
|
||||
"COMMANDS_MAP_DESC": "change to specified map",
|
||||
"COMMANDS_MAP_SUCCESS": "Changing to map",
|
||||
"COMMANDS_MAP_UKN": "Attempting to change to unknown map",
|
||||
"COMMANDS_MAPROTATE": "Map rotating in ^55 ^7seconds",
|
||||
"COMMANDS_MAPROTATE_DESC": "cycle to the next map in rotation",
|
||||
"COMMANDS_MASK_DESC": "hide your presence as a privileged client",
|
||||
"COMMANDS_MASK_OFF": "You are now unmasked",
|
||||
"COMMANDS_MASK_ON": "You are now masked",
|
||||
"COMMANDS_OWNER_DESC": "claim ownership of the server",
|
||||
"COMMANDS_OWNER_FAIL": "This server already has an owner",
|
||||
"COMMANDS_OWNER_SUCCESS": "Congratulations, you have claimed ownership of this server!",
|
||||
"COMMANDS_PASSWORD_FAIL": "Your password must be at least 5 characters long",
|
||||
"COMMANDS_PASSWORD_SUCCESS": "Your password has been set successfully",
|
||||
"COMMANDS_PING_DESC": "get client's latency",
|
||||
"COMMANDS_PING_SELF": "Your latency is",
|
||||
"COMMANDS_PING_TARGET": "latency is",
|
||||
"COMMANDS_PLUGINS_DESC": "view all loaded plugins",
|
||||
"COMMANDS_PLUGINS_LOADED": "Loaded Plugins",
|
||||
"COMMANDS_PM_DESC": "send message to other client",
|
||||
"COMMANDS_PRUNE_DESC": "demote any privileged clients that have not connected recently (defaults to 30 days)",
|
||||
"COMMANDS_PRUNE_FAIL": "Invalid number of inactive days",
|
||||
"COMMANDS_PRUNE_SUCCESS": "inactive privileged users were pruned",
|
||||
"COMMANDS_QUIT_DESC": "quit IW4MAdmin",
|
||||
"COMMANDS_RCON_DESC": "send rcon command to server",
|
||||
"COMMANDS_RCON_SUCCESS": "Successfully sent RCon command",
|
||||
"COMMANDS_REPORT_DESC": "report a client for suspicious behavior",
|
||||
"COMMANDS_REPORT_FAIL": "You cannot report",
|
||||
"COMMANDS_REPORT_FAIL_CAMP": "You cannot report an player for camping",
|
||||
"COMMANDS_REPORT_FAIL_DUPLICATE": "You have already reported this player",
|
||||
"COMMANDS_REPORT_FAIL_SELF": "You cannot report yourself",
|
||||
"COMMANDS_REPORT_SUCCESS": "Thank you for your report, an administrator has been notified",
|
||||
"COMMANDS_REPORTS_CLEAR_SUCCESS": "Reports successfully cleared",
|
||||
"COMMANDS_REPORTS_DESC": "get or clear recent reports",
|
||||
"COMMANDS_REPORTS_NONE": "No players reported yet",
|
||||
"COMMANDS_RULES_DESC": "list server rules",
|
||||
"COMMANDS_RULES_NONE": "The server owner has not set any rules",
|
||||
"COMMANDS_SAY_DESC": "broadcast message to all clients",
|
||||
"COMMANDS_SETLEVEL_DESC": "set client to specified privilege level",
|
||||
"COMMANDS_SETLEVEL_FAIL": "Invalid group specified",
|
||||
"COMMANDS_SETLEVEL_LEVELTOOHIGH": "You can only promote ^5{0} ^7to ^5{1} ^7or lower privilege",
|
||||
"COMMANDS_SETLEVEL_OWNER": "There can only be 1 owner. Modify your settings if multiple owners are required",
|
||||
"COMMANDS_SETLEVEL_SELF": "You cannot change your own level",
|
||||
"COMMANDS_SETLEVEL_STEPPEDDISABLED": "This server does not allow you to promote",
|
||||
"COMMANDS_SETLEVEL_SUCCESS": "was successfully promoted",
|
||||
"COMMANDS_SETLEVEL_SUCCESS_TARGET": "Congratulations! You have been promoted to",
|
||||
"COMMANDS_SETPASSWORD_DESC": "set your authentication password",
|
||||
"COMMANDS_TEMPBAN_DESC": "temporarily ban a client for specified time (defaults to 1 hour)",
|
||||
"COMMANDS_TEMPBAN_FAIL": "You cannot temporarily ban",
|
||||
"COMMANDS_TEMPBAN_SUCCESS": "has been temporarily banned for",
|
||||
"COMMANDS_UNBAN_DESC": "unban client by client id",
|
||||
"COMMANDS_UNBAN_FAIL": "is not banned",
|
||||
"COMMANDS_UNBAN_SUCCESS": "Successfully unbanned",
|
||||
"COMMANDS_UPTIME_DESC": "get current application running time",
|
||||
"COMMANDS_UPTIME_TEXT": "has been online for",
|
||||
"COMMANDS_USAGE_DESC": "get application memory usage",
|
||||
"COMMANDS_USAGE_TEXT": "is using",
|
||||
"COMMANDS_WARN_DESC": "warn client for infringing rules",
|
||||
"COMMANDS_WARN_FAIL": "You do not have the required privileges to warn",
|
||||
"COMMANDS_WARNCLEAR_DESC": "remove all warnings for a client",
|
||||
"COMMANDS_WARNCLEAR_SUCCESS": "All warning cleared for",
|
||||
"COMMANDS_WHO_DESC": "give information about yourself",
|
||||
"GLOBAL_TIME_DAYS": "days",
|
||||
"GLOBAL_ERROR": "Error",
|
||||
"GLOBAL_TIME_HOURS": "hours",
|
||||
"GLOBAL_INFO": "Info",
|
||||
"GLOBAL_TIME_MINUTES": "minutes",
|
||||
"GLOBAL_REPORT": "If you suspect someone of ^5CHEATING ^7use the ^5!report ^7command",
|
||||
"GLOBAL_VERBOSE": "Verbose",
|
||||
"GLOBAL_WARNING": "Warning",
|
||||
"MANAGER_CONNECTION_REST": "Connection has been reestablished with",
|
||||
"MANAGER_CONSOLE_NOSERV": "No servers are currently being monitored",
|
||||
"MANAGER_EXIT": "Press any key to exit...",
|
||||
"MANAGER_INIT_FAIL": "Fatal error during initialization",
|
||||
"MANAGER_MONITORING_TEXT": "Now monitoring",
|
||||
"MANAGER_SHUTDOWN_SUCCESS": "Shutdown complete",
|
||||
"MANAGER_VERSION_CURRENT": "Your version is",
|
||||
"MANAGER_VERSION_FAIL": "Could not get latest IW4MAdmin version",
|
||||
"MANAGER_VERSION_SUCCESS": "IW4MAdmin is up to date",
|
||||
"MANAGER_VERSION_UPDATE": "has an update. Latest version is",
|
||||
"PLUGIN_IMPORTER_NOTFOUND": "No plugins found to load",
|
||||
"PLUGIN_IMPORTER_REGISTERCMD": "Registered command",
|
||||
"PLUGINS_LOGIN_COMMANDS_LOGIN_DESC": "login using password",
|
||||
"PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL": "Your password is incorrect",
|
||||
"PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS": "You are now logged in",
|
||||
"PLUGINS_STATS_COMMANDS_RESET_DESC": "reset your stats to factory-new",
|
||||
"PLUGINS_STATS_COMMANDS_RESET_FAIL": "You must be connected to a server to reset your stats",
|
||||
"PLUGINS_STATS_COMMANDS_RESET_SUCCESS": "Your stats for this server have been reset",
|
||||
"PLUGINS_STATS_COMMANDS_TOP_DESC": "view the top 5 players in this server",
|
||||
"PLUGINS_STATS_COMMANDS_TOP_TEXT": "Top Players",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_DESC": "view your stats",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_FAIL": "Cannot find the player you specified",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_FAIL_INGAME": "The specified player must be ingame",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_FAIL_INGAME_SELF": "You must be ingame to view your stats",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_SUCCESS": "Stats for",
|
||||
"PLUGINS_STATS_TEXT_DEATHS": "DEATHS",
|
||||
"PLUGINS_STATS_TEXT_KILLS": "KILLS",
|
||||
"PLUGINS_STATS_TEXT_NOQUALIFY": "No players qualify for top stats yet",
|
||||
"PLUGINS_STATS_TEXT_SKILL": "SKILL",
|
||||
"SERVER_BAN_APPEAL": "appeal at",
|
||||
"SERVER_BAN_PREV": "Previously banned for",
|
||||
"SERVER_BAN_TEXT": "You're banned",
|
||||
"SERVER_ERROR_ADDPLAYER": "Unable to add player",
|
||||
"SERVER_ERROR_COMMAND_INGAME": "An internal error occured while processing your command",
|
||||
"SERVER_ERROR_COMMAND_LOG": "command generated an error",
|
||||
"SERVER_ERROR_COMMUNICATION": "Could not communicate with",
|
||||
"SERVER_ERROR_DNE": "does not exist",
|
||||
"SERVER_ERROR_DVAR": "Could not get the dvar value for",
|
||||
"SERVER_ERROR_DVAR_HELP": "ensure the server has a map loaded",
|
||||
"SERVER_ERROR_EXCEPTION": "Unexpected exception on",
|
||||
"SERVER_ERROR_LOG": "Invalid game log file",
|
||||
"SERVER_ERROR_PLUGIN": "An error occured loading plugin",
|
||||
"SERVER_ERROR_POLLING": "reducing polling rate",
|
||||
"SERVER_ERROR_UNFIXABLE": "Not monitoring server due to uncorrectable errors",
|
||||
"SERVER_KICK_CONTROLCHARS": "Your name cannot contain control characters",
|
||||
"SERVER_KICK_GENERICNAME": "Please change your name using /name",
|
||||
"SERVER_KICK_MINNAME": "Your name must contain at least 3 characters",
|
||||
"SERVER_KICK_NAME_INUSE": "Your name is being used by someone else",
|
||||
"SERVER_KICK_TEXT": "You were kicked",
|
||||
"SERVER_KICK_VPNS_NOTALLOWED": "VPNs are not allowed on this server",
|
||||
"SERVER_PLUGIN_ERROR": "A plugin generated an error",
|
||||
"SERVER_REPORT_COUNT": "There are ^5{0} ^7recent reports",
|
||||
"SERVER_TB_REMAIN": "You are temporarily banned",
|
||||
"SERVER_TB_TEXT": "You're temporarily banned",
|
||||
"SERVER_WARNING": "WARNING",
|
||||
"SERVER_WARNLIMT_REACHED": "Too many warnings",
|
||||
"SERVER_WEBSITE_GENERIC": "this server's website",
|
||||
"SETUP_DISPLAY_SOCIAL": "Display social media link on webfront (discord, website, VK, etc..)",
|
||||
"SETUP_ENABLE_CUSTOMSAY": "Enable custom say name",
|
||||
"SETUP_ENABLE_MULTIOWN": "Enable multiple owners",
|
||||
"SETUP_ENABLE_STEPPEDPRIV": "Enable stepped privilege hierarchy",
|
||||
"SETUP_ENABLE_VPNS": "Enable client VPNs",
|
||||
"SETUP_ENABLE_WEBFRONT": "Enable webfront",
|
||||
"SETUP_ENCODING_STRING": "Enter encoding string",
|
||||
"SETUP_IPHUB_KEY": "Enter iphub.info api key",
|
||||
"SETUP_SAY_NAME": "Enter custom say name",
|
||||
"SETUP_SERVER_IP": "Enter server IP Address",
|
||||
"SETUP_SERVER_MANUALLOG": "Enter manual log file path",
|
||||
"SETUP_SERVER_PORT": "Enter server port",
|
||||
"SETUP_SERVER_RCON": "Enter server RCon password",
|
||||
"SETUP_SERVER_SAVE": "Configuration saved, add another",
|
||||
"SETUP_SERVER_USEIW5M": "Use Pluto IW5 Parser",
|
||||
"SETUP_SERVER_USET6M": "Use Pluto T6 parser",
|
||||
"SETUP_SOCIAL_LINK": "Enter social media link",
|
||||
"SETUP_SOCIAL_TITLE": "Enter social media name",
|
||||
"SETUP_USE_CUSTOMENCODING": "Use custom encoding parser",
|
||||
"WEBFRONT_ACTION_BAN_NAME": "Ban",
|
||||
"WEBFRONT_ACTION_LABEL_ID": "Client ID",
|
||||
"WEBFRONT_ACTION_LABEL_PASSWORD": "Password",
|
||||
"WEBFRONT_ACTION_LABEL_REASON": "Reason",
|
||||
"WEBFRONT_ACTION_LOGIN_NAME": "Login",
|
||||
"WEBFRONT_ACTION_UNBAN_NAME": "Unban",
|
||||
"WEBFRONT_CLIENT_META_FALSE": "Is not",
|
||||
"WEBFRONT_CLIENT_META_JOINED": "Joined with alias",
|
||||
"WEBFRONT_CLIENT_META_MASKED": "Masked",
|
||||
"WEBFRONT_CLIENT_META_TRUE": "Is",
|
||||
"WEBFRONT_CLIENT_PRIVILEGED_TITLE": "Privileged Clients",
|
||||
"WEBFRONT_CLIENT_PROFILE_TITLE": "Profile",
|
||||
"WEBFRONT_CLIENT_SEARCH_MATCHING": "Clients Matching",
|
||||
"WEBFRONT_CONSOLE_EXECUTE": "Execute",
|
||||
"WEBFRONT_CONSOLE_TITLE": "Web Console",
|
||||
"WEBFRONT_ERROR_DESC": "IW4MAdmin encountered an error",
|
||||
"WEBFRONT_ERROR_GENERIC_DESC": "An error occurred while processing your request",
|
||||
"WEBFRONT_ERROR_GENERIC_TITLE": "Sorry!",
|
||||
"WEBFRONT_ERROR_TITLE": "Error!",
|
||||
"WEBFRONT_HOME_TITLE": "Server Overview",
|
||||
"WEBFRONT_NAV_CONSOLE": "Console",
|
||||
"WEBFRONT_NAV_DISCORD": "Discord",
|
||||
"WEBFRONT_NAV_HOME": "Home",
|
||||
"WEBFRONT_NAV_LOGOUT": "Logout",
|
||||
"WEBFRONT_NAV_PENALTIES": "Penalties",
|
||||
"WEBFRONT_NAV_PRIVILEGED": "Admins",
|
||||
"WEBFRONT_NAV_PROFILE": "Client Profile",
|
||||
"WEBFRONT_NAV_SEARCH": "Find Client",
|
||||
"WEBFRONT_NAV_SOCIAL": "Social",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_ADMIN": "Admin",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_AGO": "ago",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_NAME": "Name",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_OFFENSE": "Offense",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_REMAINING": "left",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_SHOW": "Show",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_SHOWONLY": "Show only",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_TIME": "Time/Left",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_TYPE": "Type",
|
||||
"WEBFRONT_PENALTY_TITLE": "Client Penalties",
|
||||
"WEBFRONT_PROFILE_FSEEN": "First seen",
|
||||
"WEBFRONT_PROFILE_LEVEL": "Level",
|
||||
"WEBFRONT_PROFILE_LSEEN": "Last seen",
|
||||
"WEBFRONT_PROFILE_PLAYER": "Played",
|
||||
"PLUGIN_STATS_SETUP_ENABLEAC": "Enable server-side anti-cheat (IW4 only)",
|
||||
"PLUGIN_STATS_ERROR_ADD": "Could not add server to server stats",
|
||||
"PLUGIN_STATS_CHEAT_DETECTED": "You appear to be cheating",
|
||||
"PLUGINS_STATS_TEXT_KDR": "KDR",
|
||||
"PLUGINS_STATS_META_SPM": "Score per Minute",
|
||||
"PLUGINS_WELCOME_USERANNOUNCE": "^5{{ClientName}} ^7hails from ^5{{ClientLocation}}",
|
||||
"PLUGINS_WELCOME_USERWELCOME": "Welcome ^5{{ClientName}}^7, this is your ^5{{TimesConnected}} ^7time connecting!",
|
||||
"PLUGINS_WELCOME_PRIVANNOUNCE": "{{ClientLevel}} {{ClientName}} has joined the server",
|
||||
"PLUGINS_LOGIN_AUTH": "not logged in",
|
||||
"PLUGINS_PROFANITY_SETUP_ENABLE": "Enable profanity deterring",
|
||||
"PLUGINS_PROFANITY_WARNMSG": "Please do not use profanity on this server",
|
||||
"PLUGINS_PROFANITY_KICKMSG": "Excessive use of profanity",
|
||||
"GLOBAL_DEBUG": "Debug",
|
||||
"COMMANDS_UNFLAG_DESC": "Remove flag for client",
|
||||
"COMMANDS_UNFLAG_FAIL": "You cannot unflag",
|
||||
"COMMANDS_UNFLAG_NOTFLAGGED": "Client is not flagged",
|
||||
"COMMANDS_FLAG_ALREADYFLAGGED": "Client is already flagged",
|
||||
"PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT": "Most Played",
|
||||
"PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC": "view the top 5 dedicated players on the server",
|
||||
"WEBFRONT_PROFILE_MESSAGES": "Messages",
|
||||
"WEBFRONT_CLIENT_META_CONNECTIONS": "Connections",
|
||||
"PLUGINS_STATS_COMMANDS_TOPSTATS_RATING": "Rating",
|
||||
"PLUGINS_STATS_COMMANDS_PERFORMANCE": "Performance"
|
||||
}
|
||||
}
|
||||
}
|
269
Application/Localization/IW4MAdmin.es-EC.json
Normal file
269
Application/Localization/IW4MAdmin.es-EC.json
Normal file
@ -0,0 +1,269 @@
|
||||
{
|
||||
"LocalizationName": "es-EC",
|
||||
"LocalizationIndex": {
|
||||
"Set": {
|
||||
"BROADCAST_OFFLINE": "^5IW4MAdmin ^7está ^1DESCONECTANDOSE",
|
||||
"BROADCAST_ONLINE": "^5IW4MADMIN ^7está ahora ^2en línea",
|
||||
"COMMAND_HELP_OPTIONAL": "opcional",
|
||||
"COMMAND_HELP_SYNTAX": "sintaxis:",
|
||||
"COMMAND_MISSINGARGS": "No se han proporcionado suficientes argumentos",
|
||||
"COMMAND_NOACCESS": "Tú no tienes acceso a ese comando",
|
||||
"COMMAND_NOTAUTHORIZED": "Tú no estás autorizado para ejecutar ese comando",
|
||||
"COMMAND_TARGET_MULTI": "Múltiples jugadores coinciden con ese nombre",
|
||||
"COMMAND_TARGET_NOTFOUND": "No se puede encontrar el jugador especificado",
|
||||
"COMMAND_UNKNOWN": "Has ingresado un comando desconocido",
|
||||
"COMMANDS_ADMINS_DESC": "enlistar clientes privilegiados actualmente conectados",
|
||||
"COMMANDS_ADMINS_NONE": "No hay administradores visibles en línea",
|
||||
"COMMANDS_ALIAS_ALIASES": "Aliases",
|
||||
"COMMANDS_ALIAS_DESC": "obtener alias e ips anteriores de un cliente",
|
||||
"COMMANDS_ALIAS_IPS": "IPs",
|
||||
"COMMANDS_ARGS_CLEAR": "borrar",
|
||||
"COMMANDS_ARGS_CLIENTID": "id del cliente",
|
||||
"COMMANDS_ARGS_COMMANDS": "comandos",
|
||||
"COMMANDS_ARGS_DURATION": "duración (m|h|d|w|y)",
|
||||
"COMMANDS_ARGS_INACTIVE": "días inactivo",
|
||||
"COMMANDS_ARGS_LEVEL": "nivel",
|
||||
"COMMANDS_ARGS_MAP": "mapa",
|
||||
"COMMANDS_ARGS_MESSAGE": "mensaje",
|
||||
"COMMANDS_ARGS_PASSWORD": "contraseña",
|
||||
"COMMANDS_ARGS_PLAYER": "jugador",
|
||||
"COMMANDS_ARGS_REASON": "razón",
|
||||
"COMMANDS_BAN_DESC": "banear permanentemente un cliente del servidor",
|
||||
"COMMANDS_BAN_FAIL": "Tú no puedes banear",
|
||||
"COMMANDS_BAN_SUCCESS": "ha sido baneado permanentemente",
|
||||
"COMMANDS_BANINFO_DESC": "obtener información sobre el ban de un cliente",
|
||||
"COMMANDS_BANINFO_NONE": "No se encontró ban activo para ese jugador",
|
||||
"COMMANDS_BANINO_SUCCESS": "fue baneado por ^5{0} ^7debido a:",
|
||||
"COMMANDS_FASTRESTART_DESC": "dar reinicio rápido al mapa actial",
|
||||
"COMMANDS_FASTRESTART_MASKED": "Al mapa se le ha dado un reinicio rápido",
|
||||
"COMMANDS_FASTRESTART_UNMASKED": "ha dado rápido reinicio al mapa",
|
||||
"COMMANDS_FIND_DESC": "encontrar cliente en la base de datos",
|
||||
"COMMANDS_FIND_EMPTY": "No se encontraron jugadores",
|
||||
"COMMANDS_FIND_MIN": "Por Favor introduzca al menos 3 caracteres",
|
||||
"COMMANDS_FLAG_DESC": "marcar un cliente sospechoso y anunciar a los administradores al unirse",
|
||||
"COMMANDS_FLAG_FAIL": "Tú no puedes marcar",
|
||||
"COMMANDS_FLAG_SUCCESS": "Has marcado a",
|
||||
"COMMANDS_FLAG_UNFLAG": "Has desmarcado a",
|
||||
"COMMANDS_HELP_DESC": "enlistar todos los comandos disponibles",
|
||||
"COMMANDS_HELP_MOREINFO": "Escribe !help <nombre del comando> para obtener la sintaxis de uso del comando",
|
||||
"COMMANDS_HELP_NOTFOUND": "No se ha podido encontrar ese comando",
|
||||
"COMMANDS_IP_DESC": "ver tu dirección IP externa",
|
||||
"COMMANDS_IP_SUCCESS": "Tu IP externa es",
|
||||
"COMMANDS_KICK_DESC": "expulsar a un cliente por su nombre",
|
||||
"COMMANDS_KICK_FAIL": "No tienes los privilegios necesarios para expulsar a",
|
||||
"COMMANDS_KICK_SUCCESS": "ha sido expulsado",
|
||||
"COMMANDS_LIST_DESC": "enlistar clientes activos",
|
||||
"COMMANDS_MAP_DESC": "cambiar al mapa especificado",
|
||||
"COMMANDS_MAP_SUCCESS": "Cambiando al mapa",
|
||||
"COMMANDS_MAP_UKN": "Intentando cambiar a un mapa desconocido",
|
||||
"COMMANDS_MAPROTATE": "Rotación de mapa en ^55 ^7segundos",
|
||||
"COMMANDS_MAPROTATE_DESC": "pasar al siguiente mapa en rotación",
|
||||
"COMMANDS_MASK_DESC": "esconde tu presencia como un cliente privilegiado",
|
||||
"COMMANDS_MASK_OFF": "Ahora estás desenmascarado",
|
||||
"COMMANDS_MASK_ON": "Ahora estás enmascarado",
|
||||
"COMMANDS_OWNER_DESC": "reclamar la propiedad del servidor",
|
||||
"COMMANDS_OWNER_FAIL": "Este servidor ya tiene un propietario",
|
||||
"COMMANDS_OWNER_SUCCESS": "¡Felicidades, has reclamado la propiedad de este servidor!",
|
||||
"COMMANDS_PASSWORD_FAIL": "Tu contraseña debe tener al menos 5 caracteres de largo",
|
||||
"COMMANDS_PASSWORD_SUCCESS": "Su contraseña ha sido establecida con éxito",
|
||||
"COMMANDS_PING_DESC": "obtener ping del cliente",
|
||||
"COMMANDS_PING_SELF": "Tu ping es",
|
||||
"COMMANDS_PING_TARGET": "ping es",
|
||||
"COMMANDS_PLUGINS_DESC": "ver todos los complementos cargados",
|
||||
"COMMANDS_PLUGINS_LOADED": "Complementos cargados",
|
||||
"COMMANDS_PM_DESC": "enviar mensaje a otro cliente",
|
||||
"COMMANDS_PRUNE_DESC": "degradar a los clientes con privilegios que no se hayan conectado recientemente (el valor predeterminado es 30 días)",
|
||||
"COMMANDS_PRUNE_FAIL": "Número inválido de días inactivos",
|
||||
"COMMANDS_PRUNE_SUCCESS": "los usuarios privilegiados inactivos fueron podados",
|
||||
"COMMANDS_QUIT_DESC": "salir de IW4MAdmin",
|
||||
"COMMANDS_RCON_DESC": "enviar el comando rcon al servidor",
|
||||
"COMMANDS_RCON_SUCCESS": "Exitosamente enviado el comando RCon",
|
||||
"COMMANDS_REPORT_DESC": "reportar un cliente por comportamiento sospechoso",
|
||||
"COMMANDS_REPORT_FAIL": "Tú no puedes reportar",
|
||||
"COMMANDS_REPORT_FAIL_CAMP": "No puedes reportar a un jugador por campear",
|
||||
"COMMANDS_REPORT_FAIL_DUPLICATE": "Ya has reportado a este jugador",
|
||||
"COMMANDS_REPORT_FAIL_SELF": "No puedes reportarte a ti mismo",
|
||||
"COMMANDS_REPORT_SUCCESS": "Gracias por su reporte, un administrador ha sido notificado",
|
||||
"COMMANDS_REPORTS_CLEAR_SUCCESS": "Reportes borrados con éxito",
|
||||
"COMMANDS_REPORTS_DESC": "obtener o borrar informes recientes",
|
||||
"COMMANDS_REPORTS_NONE": "No hay jugadores reportados aun",
|
||||
"COMMANDS_RULES_DESC": "enlistar reglas del servidor",
|
||||
"COMMANDS_RULES_NONE": "El propietario del servidor no ha establecido ninguna regla",
|
||||
"COMMANDS_SAY_DESC": "transmitir el mensaje a todos los clientes",
|
||||
"COMMANDS_SETLEVEL_DESC": "establecer el cliente al nivel de privilegio especificado",
|
||||
"COMMANDS_SETLEVEL_FAIL": "Grupo inválido especificado",
|
||||
"COMMANDS_SETLEVEL_LEVELTOOHIGH": "Tú solo puedes promover ^5{0} ^7a ^5{1} ^7o menor privilegio",
|
||||
"COMMANDS_SETLEVEL_OWNER": "Solo puede haber un propietario. Modifica tu configuración si múltiples propietarios son requeridos",
|
||||
"COMMANDS_SETLEVEL_SELF": "No puedes cambiar tu propio nivel",
|
||||
"COMMANDS_SETLEVEL_STEPPEDDISABLED": "Este servidor no te permite promover",
|
||||
"COMMANDS_SETLEVEL_SUCCESS": "fue promovido con éxito",
|
||||
"COMMANDS_SETLEVEL_SUCCESS_TARGET": "¡Felicitaciones! has ha sido promovido a",
|
||||
"COMMANDS_SETPASSWORD_DESC": "configura tu contraseña de autenticación",
|
||||
"COMMANDS_TEMPBAN_DESC": "banear temporalmente a un cliente por el tiempo especificado (predeterminado en 1 hora)",
|
||||
"COMMANDS_TEMPBAN_FAIL": "Tú no puedes banear temporalmente",
|
||||
"COMMANDS_TEMPBAN_SUCCESS": "ha sido baneado temporalmente por",
|
||||
"COMMANDS_UNBAN_DESC": "desbanear al cliente por ID",
|
||||
"COMMANDS_UNBAN_FAIL": "no está baneado",
|
||||
"COMMANDS_UNBAN_SUCCESS": "Exitosamente desbaneado",
|
||||
"COMMANDS_UPTIME_DESC": "obtener el tiempo de ejecución de la aplicación actual",
|
||||
"COMMANDS_UPTIME_TEXT": "ha estado en línea por",
|
||||
"COMMANDS_USAGE_DESC": "obtener uso de la memoria de la aplicación",
|
||||
"COMMANDS_USAGE_TEXT": "está usando",
|
||||
"COMMANDS_WARN_DESC": "advertir al cliente por infringir las reglas",
|
||||
"COMMANDS_WARN_FAIL": "No tiene los privilegios necesarios para advertir a",
|
||||
"COMMANDS_WARNCLEAR_DESC": "eliminar todas las advertencias de un cliente",
|
||||
"COMMANDS_WARNCLEAR_SUCCESS": "Todas las advertencias borradas para",
|
||||
"COMMANDS_WHO_DESC": "da información sobre ti",
|
||||
"GLOBAL_TIME_DAYS": "días",
|
||||
"GLOBAL_ERROR": "Error",
|
||||
"GLOBAL_TIME_HOURS": "horas",
|
||||
"GLOBAL_INFO": "Información",
|
||||
"GLOBAL_TIME_MINUTES": "minutos",
|
||||
"GLOBAL_REPORT": "Si sospechas que alguien ^5usa cheats ^7usa el comando ^5!report",
|
||||
"GLOBAL_VERBOSE": "Detallado",
|
||||
"GLOBAL_WARNING": "Advertencia",
|
||||
"MANAGER_CONNECTION_REST": "La conexión ha sido restablecida con",
|
||||
"MANAGER_CONSOLE_NOSERV": "No hay servidores que estén siendo monitoreados en este momento",
|
||||
"MANAGER_EXIT": "Presione cualquier tecla para salir...",
|
||||
"MANAGER_INIT_FAIL": "Error fatal durante la inicialización",
|
||||
"MANAGER_MONITORING_TEXT": "Ahora monitoreando",
|
||||
"MANAGER_SHUTDOWN_SUCCESS": "Apagado completo",
|
||||
"MANAGER_VERSION_CURRENT": "Tu versión es",
|
||||
"MANAGER_VERSION_FAIL": "No se ha podido conseguir la última versión de IW4MAdmin",
|
||||
"MANAGER_VERSION_SUCCESS": "IW4MAdmin está actualizado",
|
||||
"MANAGER_VERSION_UPDATE": "tiene una actualización. La última versión es",
|
||||
"PLUGIN_IMPORTER_NOTFOUND": "No se encontraron complementos para cargar",
|
||||
"PLUGIN_IMPORTER_REGISTERCMD": "Comando registrado",
|
||||
"PLUGINS_LOGIN_COMMANDS_LOGIN_DESC": "iniciar sesión usando la contraseña",
|
||||
"PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL": "tu contraseña es incorrecta",
|
||||
"PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS": "Ahora está conectado",
|
||||
"PLUGINS_STATS_COMMANDS_RESET_DESC": "restablece tus estadísticas a las nuevas de fábrica",
|
||||
"PLUGINS_STATS_COMMANDS_RESET_FAIL": "Debes estar conectado a un servidor para restablecer tus estadísticas",
|
||||
"PLUGINS_STATS_COMMANDS_RESET_SUCCESS": "Tus estadísticas para este servidor se han restablecido",
|
||||
"PLUGINS_STATS_COMMANDS_TOP_DESC": "ver los 5 mejores jugadores en este servidor",
|
||||
"PLUGINS_STATS_COMMANDS_TOP_TEXT": "Mejores Jugadores",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_DESC": "ver tus estadísticas",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_FAIL": "No se puede encontrar el jugador que especificó",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_FAIL_INGAME": "El jugador especificado debe estar dentro del juego",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_FAIL_INGAME_SELF": "Debes estar dentro del juego para ver tus estadísticas",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_SUCCESS": "Estadísticas para",
|
||||
"PLUGINS_STATS_TEXT_DEATHS": "Muertes",
|
||||
"PLUGINS_STATS_TEXT_KILLS": "Asesinatos",
|
||||
"PLUGINS_STATS_TEXT_NOQUALIFY": "No hay jugadores que califiquen para los primeros lugares aun",
|
||||
"PLUGINS_STATS_TEXT_SKILL": "Habilidad",
|
||||
"SERVER_BAN_APPEAL": "apela en",
|
||||
"SERVER_BAN_PREV": "Baneado anteriormente por",
|
||||
"SERVER_BAN_TEXT": "Estás baneado",
|
||||
"SERVER_ERROR_ADDPLAYER": "Incapaz de añadir al jugador",
|
||||
"SERVER_ERROR_COMMAND_INGAME": "Un error interno ocurrió mientras se procesaba tu comando",
|
||||
"SERVER_ERROR_COMMAND_LOG": "Comando generó error",
|
||||
"SERVER_ERROR_COMMUNICATION": "No se ha podido comunicar con",
|
||||
"SERVER_ERROR_DNE": "No existe",
|
||||
"SERVER_ERROR_DVAR": "No se pudo obtener el valor dvar",
|
||||
"SERVER_ERROR_DVAR_HELP": "asegúrate de que el servidor tenga un mapa cargado",
|
||||
"SERVER_ERROR_EXCEPTION": "Excepción inesperada en",
|
||||
"SERVER_ERROR_LOG": "Archivo de registro del juego invalido",
|
||||
"SERVER_ERROR_PLUGIN": "Un error ocurrió mientras se cargaba el complemente",
|
||||
"SERVER_ERROR_POLLING": "reduciendo la tasa de sondeo",
|
||||
"SERVER_ERROR_UNFIXABLE": "No se está supervisando el servidor debido a errores incorregibles",
|
||||
"SERVER_KICK_CONTROLCHARS": "Tu nombre no puede contener caracteres de control",
|
||||
"SERVER_KICK_GENERICNAME": "Por favor cambia tu nombre usando /name",
|
||||
"SERVER_KICK_MINNAME": "Tu nombre debe contener al menos 3 caracteres",
|
||||
"SERVER_KICK_NAME_INUSE": "Tu nombre está siendo usado por alguien más",
|
||||
"SERVER_KICK_TEXT": "Fuiste expulsado",
|
||||
"SERVER_KICK_VPNS_NOTALLOWED": "Las VPNs no están permitidas en este servidor",
|
||||
"SERVER_PLUGIN_ERROR": "Un complemento generó un error",
|
||||
"SERVER_REPORT_COUNT": "Hay ^5{0} ^7reportes recientes",
|
||||
"SERVER_TB_REMAIN": "Tú estás temporalmente baneado",
|
||||
"SERVER_TB_TEXT": "Estás temporalmente baneado",
|
||||
"SERVER_WARNING": "ADVERTENCIA",
|
||||
"SERVER_WARNLIMT_REACHED": "Muchas advertencias",
|
||||
"SERVER_WEBSITE_GENERIC": "el sitio web de este servidor",
|
||||
"SETUP_DISPLAY_SOCIAL": "Mostrar el link del medio de comunicación en la parte frontal de la web. (discord, website, VK, etc..)",
|
||||
"SETUP_ENABLE_CUSTOMSAY": "Habilitar nombre a decir personalizado",
|
||||
"SETUP_ENABLE_MULTIOWN": "Habilitar múltiples propietarios",
|
||||
"SETUP_ENABLE_STEPPEDPRIV": "Habilitar jerarquía de privilegios por escalones",
|
||||
"SETUP_ENABLE_VPNS": "Habilitar VPNs clientes",
|
||||
"SETUP_ENABLE_WEBFRONT": "Habilitar frente de la web",
|
||||
"SETUP_ENCODING_STRING": "Ingresar cadena de codificación",
|
||||
"SETUP_IPHUB_KEY": "Ingresar clave api de iphub.info",
|
||||
"SETUP_SAY_NAME": "Ingresar nombre a decir personalizado",
|
||||
"SETUP_SERVER_IP": "Ingresar Dirección IP del servidor",
|
||||
"SETUP_SERVER_MANUALLOG": "Ingresar manualmente la ruta del archivo de registro",
|
||||
"SETUP_SERVER_PORT": "Ingresar puerto del servidor",
|
||||
"SETUP_SERVER_RCON": "Ingresar contraseña RCon del servidor",
|
||||
"SETUP_SERVER_SAVE": "Configuración guardada, añadir otra",
|
||||
"SETUP_SERVER_USEIW5M": "Usar analizador Pluto IW5",
|
||||
"SETUP_SERVER_USET6M": "Usar analizador Pluto T6",
|
||||
"SETUP_SOCIAL_LINK": "Ingresar link del medio de comunicación",
|
||||
"SETUP_SOCIAL_TITLE": "Ingresa el nombre de la red de comunicación",
|
||||
"SETUP_USE_CUSTOMENCODING": "Usar analizador de codificación personalizado",
|
||||
"WEBFRONT_ACTION_BAN_NAME": "Ban",
|
||||
"WEBFRONT_ACTION_LABEL_ID": "ID del Cliente",
|
||||
"WEBFRONT_ACTION_LABEL_PASSWORD": "Contraseña",
|
||||
"WEBFRONT_ACTION_LABEL_REASON": "Razón",
|
||||
"WEBFRONT_ACTION_LOGIN_NAME": "Inicio de sesión",
|
||||
"WEBFRONT_ACTION_UNBAN_NAME": "Desban",
|
||||
"WEBFRONT_CLIENT_META_FALSE": "No está",
|
||||
"WEBFRONT_CLIENT_META_JOINED": "Se unió con el alias",
|
||||
"WEBFRONT_CLIENT_META_MASKED": "Enmascarado",
|
||||
"WEBFRONT_CLIENT_META_TRUE": "Está",
|
||||
"WEBFRONT_CLIENT_PRIVILEGED_TITLE": "Clientes privilegiados",
|
||||
"WEBFRONT_CLIENT_PROFILE_TITLE": "Perfil",
|
||||
"WEBFRONT_CLIENT_SEARCH_MATCHING": "Clientes que concuerdan",
|
||||
"WEBFRONT_CONSOLE_EXECUTE": "Ejecutar",
|
||||
"WEBFRONT_CONSOLE_TITLE": "Consola Web",
|
||||
"WEBFRONT_ERROR_DESC": "IW4MAdmin encontró un error",
|
||||
"WEBFRONT_ERROR_GENERIC_DESC": "Un error ha ocurrido mientras se procesaba tu solicitud",
|
||||
"WEBFRONT_ERROR_GENERIC_TITLE": "¡Lo lamento!",
|
||||
"WEBFRONT_ERROR_TITLE": "¡Error!",
|
||||
"WEBFRONT_HOME_TITLE": "Vista general del servidor",
|
||||
"WEBFRONT_NAV_CONSOLE": "Consola",
|
||||
"WEBFRONT_NAV_DISCORD": "Discord",
|
||||
"WEBFRONT_NAV_HOME": "Inicio",
|
||||
"WEBFRONT_NAV_LOGOUT": "Cerrar sesión",
|
||||
"WEBFRONT_NAV_PENALTIES": "Sanciones",
|
||||
"WEBFRONT_NAV_PRIVILEGED": "Administradores",
|
||||
"WEBFRONT_NAV_PROFILE": "Perfil del cliente",
|
||||
"WEBFRONT_NAV_SEARCH": "Encontrar cliente",
|
||||
"WEBFRONT_NAV_SOCIAL": "Social",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_ADMIN": "Administrador",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_AGO": "atrás",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_NAME": "Nombre",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_OFFENSE": "Ofensa",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_REMAINING": "restante",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_SHOW": "Mostrar",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_SHOWONLY": "Mostrar solamente",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_TIME": "Tiempo/Restante",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_TYPE": "Tipo",
|
||||
"WEBFRONT_PENALTY_TITLE": "Faltas del cliente",
|
||||
"WEBFRONT_PROFILE_FSEEN": "Primera vez visto hace",
|
||||
"WEBFRONT_PROFILE_LEVEL": "Nivel",
|
||||
"WEBFRONT_PROFILE_LSEEN": "Última vez visto hace",
|
||||
"WEBFRONT_PROFILE_PLAYER": "Jugadas",
|
||||
"PLUGIN_STATS_SETUP_ENABLEAC": "Habilitar anti-trampas junto al servidor (solo IW4)",
|
||||
"PLUGIN_STATS_ERROR_ADD": "No se puedo añadir servidor a los estados del servidor",
|
||||
"PLUGIN_STATS_CHEAT_DETECTED": "Pareces estar haciendo trampa",
|
||||
"PLUGINS_STATS_TEXT_KDR": "KDR",
|
||||
"PLUGINS_STATS_META_SPM": "Puntaje por minuto",
|
||||
"PLUGINS_WELCOME_USERANNOUNCE": "^5{{ClientName}} ^7llega desde ^5{{ClientLocation}}",
|
||||
"PLUGINS_WELCOME_USERWELCOME": "¡Bienvenido ^5{{ClientName}}^7, esta es tu visita numero ^5{{TimesConnected}} ^7 en el servidor!",
|
||||
"PLUGINS_WELCOME_PRIVANNOUNCE": "{{ClientLevel}} {{ClientName}} Se ha unido al servidor",
|
||||
"PLUGINS_LOGIN_AUTH": "No registrado",
|
||||
"PLUGINS_PROFANITY_SETUP_ENABLE": "Habilitar la disuasión de blasfemias",
|
||||
"PLUGINS_PROFANITY_WARNMSG": "Por favor no uses blasfemias en este servidor",
|
||||
"PLUGINS_PROFANITY_KICKMSG": "Excesivo uso de blasfemias",
|
||||
"GLOBAL_DEBUG": "Depurar",
|
||||
"COMMANDS_UNFLAG_DESC": "Remover marca del cliente",
|
||||
"COMMANDS_UNFLAG_FAIL": "Tu no puedes desmarcar",
|
||||
"COMMANDS_UNFLAG_NOTFLAGGED": "El cliente no está marcado",
|
||||
"COMMANDS_FLAG_ALREADYFLAGGED": "El cliente yá se encuentra marcado",
|
||||
"PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT": "Más jugado",
|
||||
"PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC": "ver el Top 5 de jugadores dedicados en el servidor",
|
||||
"WEBFRONT_PROFILE_MESSAGES": "Mensajes",
|
||||
"WEBFRONT_CLIENT_META_CONNECTIONS": "Conexiones",
|
||||
"PLUGINS_STATS_COMMANDS_TOPSTATS_RATING": "Clasificación",
|
||||
"PLUGINS_STATS_COMMANDS_PERFORMANCE": "Desempeño"
|
||||
}
|
||||
}
|
||||
}
|
269
Application/Localization/IW4MAdmin.pt-BR.json
Normal file
269
Application/Localization/IW4MAdmin.pt-BR.json
Normal file
@ -0,0 +1,269 @@
|
||||
{
|
||||
"LocalizationName": "pt-BR",
|
||||
"LocalizationIndex": {
|
||||
"Set": {
|
||||
"BROADCAST_OFFLINE": "IW4MAdmin ficou offline",
|
||||
"BROADCAST_ONLINE": "^5IW4MADMIN ^7agora está ^2ONLINE",
|
||||
"COMMAND_HELP_OPTIONAL": "opcional",
|
||||
"COMMAND_HELP_SYNTAX": "sintaxe:",
|
||||
"COMMAND_MISSINGARGS": "Não foram oferecidos argumentos suficientes",
|
||||
"COMMAND_NOACCESS": "Você não tem acesso a este comando",
|
||||
"COMMAND_NOTAUTHORIZED": "Você não está autorizado a executar este comando",
|
||||
"COMMAND_TARGET_MULTI": "Vários jogadores correspondem a esse nome",
|
||||
"COMMAND_TARGET_NOTFOUND": "Não é possível encontrar o jogador especificado",
|
||||
"COMMAND_UNKNOWN": "Você digitou um comando desconhecido",
|
||||
"COMMANDS_ADMINS_DESC": "lista os clientes privilegiados conectados no momento",
|
||||
"COMMANDS_ADMINS_NONE": "Não há administradores visíveis online",
|
||||
"COMMANDS_ALIAS_ALIASES": "Nomes registrados",
|
||||
"COMMANDS_ALIAS_DESC": "obtém a lista de histórico de nomes que o jogador usou no servidor",
|
||||
"COMMANDS_ALIAS_IPS": "IPs",
|
||||
"COMMANDS_ARGS_CLEAR": "apagar",
|
||||
"COMMANDS_ARGS_CLIENTID": "id do jogador",
|
||||
"COMMANDS_ARGS_COMMANDS": "comandos",
|
||||
"COMMANDS_ARGS_DURATION": "duração (m|h|d|w|y)",
|
||||
"COMMANDS_ARGS_INACTIVE": "dias inativos",
|
||||
"COMMANDS_ARGS_LEVEL": "nível",
|
||||
"COMMANDS_ARGS_MAP": "mapa",
|
||||
"COMMANDS_ARGS_MESSAGE": "mensagem",
|
||||
"COMMANDS_ARGS_PASSWORD": "senha",
|
||||
"COMMANDS_ARGS_PLAYER": "jogador",
|
||||
"COMMANDS_ARGS_REASON": "razão",
|
||||
"COMMANDS_BAN_DESC": "banir permanentemente um cliente do servidor",
|
||||
"COMMANDS_BAN_FAIL": "Você não pode banir permanentemente",
|
||||
"COMMANDS_BAN_SUCCESS": "foi banido permanentemente",
|
||||
"COMMANDS_BANINFO_DESC": "obtém informações sobre um banimento para um jogador",
|
||||
"COMMANDS_BANINFO_NONE": "Nenhum banimento ativo foi encontrado para esse jogador",
|
||||
"COMMANDS_BANINO_SUCCESS": "foi banido por ^5{0} ^7por:",
|
||||
"COMMANDS_FASTRESTART_DESC": "reinicializa rapidamente o mapa atual, não recomendável o uso várias vezes seguidas",
|
||||
"COMMANDS_FASTRESTART_MASKED": "O mapa foi reiniciado rapidamente",
|
||||
"COMMANDS_FASTRESTART_UNMASKED": "reiniciou rapidamente o mapa",
|
||||
"COMMANDS_FIND_DESC": "acha o jogador na base de dados",
|
||||
"COMMANDS_FIND_EMPTY": "Nenhum jogador foi encontrado",
|
||||
"COMMANDS_FIND_MIN": "Por favor, insira pelo menos 3 caracteres",
|
||||
"COMMANDS_FLAG_DESC": "sinaliza um cliente suspeito e anuncia aos administradores ao entrar no servidor",
|
||||
"COMMANDS_FLAG_FAIL": "Você não pode sinalizar",
|
||||
"COMMANDS_FLAG_SUCCESS": "Você sinalizou",
|
||||
"COMMANDS_FLAG_UNFLAG": "Você tirou a sinalização de",
|
||||
"COMMANDS_HELP_DESC": "lista todos os comandos disponíveis",
|
||||
"COMMANDS_HELP_MOREINFO": "Digite !help <comando> para saber como usar o comando",
|
||||
"COMMANDS_HELP_NOTFOUND": "Não foi possível encontrar esse comando",
|
||||
"COMMANDS_IP_DESC": "mostrar o seu endereço IP externo",
|
||||
"COMMANDS_IP_SUCCESS": "Seu endereço IP externo é",
|
||||
"COMMANDS_KICK_DESC": "expulsa o jogador pelo nome",
|
||||
"COMMANDS_KICK_FAIL": "Você não tem os privilégios necessários para expulsar",
|
||||
"COMMANDS_KICK_SUCCESS": "foi expulso",
|
||||
"COMMANDS_LIST_DESC": "lista os jogadores ativos na partida",
|
||||
"COMMANDS_MAP_DESC": "muda para o mapa especificado",
|
||||
"COMMANDS_MAP_SUCCESS": "Mudando o mapa para",
|
||||
"COMMANDS_MAP_UKN": "Tentando mudar para o mapa desconhecido",
|
||||
"COMMANDS_MAPROTATE": "Rotacionando o mapa em ^55 ^7segundos",
|
||||
"COMMANDS_MAPROTATE_DESC": "avança para o próximo mapa da rotação",
|
||||
"COMMANDS_MASK_DESC": "esconde a sua presença como um jogador privilegiado",
|
||||
"COMMANDS_MASK_OFF": "Você foi desmascarado",
|
||||
"COMMANDS_MASK_ON": "Você agora está mascarado",
|
||||
"COMMANDS_OWNER_DESC": "reivindica a propriedade do servidor",
|
||||
"COMMANDS_OWNER_FAIL": "Este servidor já tem um dono",
|
||||
"COMMANDS_OWNER_SUCCESS": "Parabéns, você reivindicou a propriedade deste servidor!",
|
||||
"COMMANDS_PASSWORD_FAIL": "Sua senha deve ter pelo menos 5 caracteres",
|
||||
"COMMANDS_PASSWORD_SUCCESS": "Sua senha foi configurada com sucesso",
|
||||
"COMMANDS_PING_DESC": "mostra o quanto de latência tem o jogador",
|
||||
"COMMANDS_PING_SELF": "Sua latência é",
|
||||
"COMMANDS_PING_TARGET": "latência é",
|
||||
"COMMANDS_PLUGINS_DESC": "mostra todos os plugins que estão carregados",
|
||||
"COMMANDS_PLUGINS_LOADED": "Plugins carregados",
|
||||
"COMMANDS_PM_DESC": "envia a mensagem para o outro jogador de maneira privada, use /!pm para ter efeito, se possível",
|
||||
"COMMANDS_PRUNE_DESC": "rebaixa qualquer jogador privilegiado que não tenha se conectado recentemente (o padrão é 30 dias)",
|
||||
"COMMANDS_PRUNE_FAIL": "Número inválido de dias ativo",
|
||||
"COMMANDS_PRUNE_SUCCESS": "usuários privilegiados inativos foram removidos",
|
||||
"COMMANDS_QUIT_DESC": "sair do IW4MAdmin",
|
||||
"COMMANDS_RCON_DESC": "envia o comando Rcon para o servidor",
|
||||
"COMMANDS_RCON_SUCCESS": "O comando para o RCon foi enviado com sucesso!",
|
||||
"COMMANDS_REPORT_DESC": "denuncia o jogador por comportamento suspeito",
|
||||
"COMMANDS_REPORT_FAIL": "Você não pode reportar",
|
||||
"COMMANDS_REPORT_FAIL_CAMP": "Você não pode denunciar o jogador por camperar",
|
||||
"COMMANDS_REPORT_FAIL_DUPLICATE": "Você já denunciou o jogador",
|
||||
"COMMANDS_REPORT_FAIL_SELF": "Você não pode reportar a si mesmo",
|
||||
"COMMANDS_REPORT_SUCCESS": "Obrigado pela sua denúncia, um administrador foi notificado",
|
||||
"COMMANDS_REPORTS_CLEAR_SUCCESS": "Lista de denúncias limpa com sucesso",
|
||||
"COMMANDS_REPORTS_DESC": "obtém ou limpa as denúncias recentes",
|
||||
"COMMANDS_REPORTS_NONE": "Ninguém foi denunciado ainda",
|
||||
"COMMANDS_RULES_DESC": "lista as regras do servidor",
|
||||
"COMMANDS_RULES_NONE": "O proprietário do servidor não definiu nenhuma regra, sinta-se livre",
|
||||
"COMMANDS_SAY_DESC": "transmite mensagem para todos os jogadores",
|
||||
"COMMANDS_SETLEVEL_DESC": "define o jogador para o nível de privilégio especificado",
|
||||
"COMMANDS_SETLEVEL_FAIL": "grupo especificado inválido",
|
||||
"COMMANDS_SETLEVEL_LEVELTOOHIGH": "Você só pode promover do ^5{0} ^7para ^5{1} ^7ou um nível menor",
|
||||
"COMMANDS_SETLEVEL_OWNER": "Só pode haver 1 dono. Modifique suas configurações se vários proprietários forem necessários",
|
||||
"COMMANDS_SETLEVEL_SELF": "Você não pode mudar seu próprio nível",
|
||||
"COMMANDS_SETLEVEL_STEPPEDDISABLED": "Este servidor não permite que você promova",
|
||||
"COMMANDS_SETLEVEL_SUCCESS": "foi promovido com sucesso",
|
||||
"COMMANDS_SETLEVEL_SUCCESS_TARGET": "Parabéns! Você foi promovido para",
|
||||
"COMMANDS_SETPASSWORD_DESC": "define sua senha de autenticação",
|
||||
"COMMANDS_TEMPBAN_DESC": "bane temporariamente um jogador por tempo especificado (o padrão é 1 hora)",
|
||||
"COMMANDS_TEMPBAN_FAIL": "Você não pode banir temporariamente",
|
||||
"COMMANDS_TEMPBAN_SUCCESS": "foi banido temporariamente por",
|
||||
"COMMANDS_UNBAN_DESC": "retira o banimento de um jogador pelo seu ID",
|
||||
"COMMANDS_UNBAN_FAIL": "não está banido",
|
||||
"COMMANDS_UNBAN_SUCCESS": "Foi retirado o banimento com sucesso",
|
||||
"COMMANDS_UPTIME_DESC": "obtém o tempo de execução do aplicativo a quando aberto",
|
||||
"COMMANDS_UPTIME_TEXT": "está online por",
|
||||
"COMMANDS_USAGE_DESC": "vê quanto o aplicativo está usando de memória RAM do seu computador",
|
||||
"COMMANDS_USAGE_TEXT": "está usando",
|
||||
"COMMANDS_WARN_DESC": "adverte o cliente por infringir as regras",
|
||||
"COMMANDS_WARN_FAIL": "Você não tem os privilégios necessários para advertir",
|
||||
"COMMANDS_WARNCLEAR_DESC": "remove todos os avisos para um cliente",
|
||||
"COMMANDS_WARNCLEAR_SUCCESS": "Todos as advertências foram apagados para",
|
||||
"COMMANDS_WHO_DESC": "dá informações sobre você",
|
||||
"GLOBAL_TIME_DAYS": "dias",
|
||||
"GLOBAL_ERROR": "Erro",
|
||||
"GLOBAL_TIME_HOURS": "horas",
|
||||
"GLOBAL_INFO": "Informação",
|
||||
"GLOBAL_TIME_MINUTES": "minutos",
|
||||
"GLOBAL_REPORT": "Se você está suspeitando alguém de alguma ^5TRAPAÇA ^7use o comando ^5!report",
|
||||
"GLOBAL_VERBOSE": "Detalhe",
|
||||
"GLOBAL_WARNING": "AVISO",
|
||||
"MANAGER_CONNECTION_REST": "A conexão foi reestabelecida com",
|
||||
"MANAGER_CONSOLE_NOSERV": "Não há servidores sendo monitorados neste momento",
|
||||
"MANAGER_EXIT": "Pressione qualquer tecla para sair...",
|
||||
"MANAGER_INIT_FAIL": "Erro fatal durante a inicialização",
|
||||
"MANAGER_MONITORING_TEXT": "Agora monitorando",
|
||||
"MANAGER_SHUTDOWN_SUCCESS": "Desligamento concluído",
|
||||
"MANAGER_VERSION_CURRENT": "Está é a sua versão",
|
||||
"MANAGER_VERSION_FAIL": "Não foi possível obter a versão mais recente do IW4MAdmin",
|
||||
"MANAGER_VERSION_SUCCESS": "O IW4MAdmin está atualizado",
|
||||
"MANAGER_VERSION_UPDATE": "Há uma atualização disponível. A versão mais recente é",
|
||||
"PLUGIN_IMPORTER_NOTFOUND": "Não foram encontrados plugins para carregar",
|
||||
"PLUGIN_IMPORTER_REGISTERCMD": "Comando registrado",
|
||||
"PLUGINS_LOGIN_COMMANDS_LOGIN_DESC": "Inicie a sua sessão usando a senha",
|
||||
"PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL": "Sua senha está errada",
|
||||
"PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS": "Você agora está conectado",
|
||||
"PLUGINS_STATS_COMMANDS_RESET_DESC": "reinicia suas estatísticas para uma nova",
|
||||
"PLUGINS_STATS_COMMANDS_RESET_FAIL": "Você deve estar conectado a um servidor para reiniciar as suas estatísticas",
|
||||
"PLUGINS_STATS_COMMANDS_RESET_SUCCESS": "Suas estatísticas nesse servidor foram reiniciadas",
|
||||
"PLUGINS_STATS_COMMANDS_TOP_DESC": "visualiza os 5 melhores jogadores do servidor",
|
||||
"PLUGINS_STATS_COMMANDS_TOP_TEXT": "Top Jogadores",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_DESC": "mostra suas estatísticas",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_FAIL": "Não foi encontrado o jogador que você especificou",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_FAIL_INGAME": "o jogador especificado deve estar dentro do jogo",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_FAIL_INGAME_SELF": "Você deve estar no jogo para ver suas estatísticas",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_SUCCESS": "Estatísticas para",
|
||||
"PLUGINS_STATS_TEXT_DEATHS": "MORTES",
|
||||
"PLUGINS_STATS_TEXT_KILLS": "BAIXAS",
|
||||
"PLUGINS_STATS_TEXT_NOQUALIFY": "Não há ainda jogadores qualificados para os primeiros lugares",
|
||||
"PLUGINS_STATS_TEXT_SKILL": "HABILIDADE",
|
||||
"SERVER_BAN_APPEAL": "apele em",
|
||||
"SERVER_BAN_PREV": "Banido preventivamente por",
|
||||
"SERVER_BAN_TEXT": "Você está banido",
|
||||
"SERVER_ERROR_ADDPLAYER": "Não foi possível adicionar o jogador",
|
||||
"SERVER_ERROR_COMMAND_INGAME": "Ocorreu um erro interno ao processar seu comando",
|
||||
"SERVER_ERROR_COMMAND_LOG": "o comando gerou um erro",
|
||||
"SERVER_ERROR_COMMUNICATION": "Não foi possível fazer a comunicação com",
|
||||
"SERVER_ERROR_DNE": "não existe",
|
||||
"SERVER_ERROR_DVAR": "Não foi possível obter o valor de dvar para",
|
||||
"SERVER_ERROR_DVAR_HELP": "garanta que o servidor tenha um mapa carregado",
|
||||
"SERVER_ERROR_EXCEPTION": "Exceção inesperada em",
|
||||
"SERVER_ERROR_LOG": "Log do jogo inválido",
|
||||
"SERVER_ERROR_PLUGIN": "Ocorreu um erro ao carregar o plug-in",
|
||||
"SERVER_ERROR_POLLING": "reduzir a taxa de sondagem do server",
|
||||
"SERVER_ERROR_UNFIXABLE": "Não monitorando o servidor devido a erros incorrigíveis",
|
||||
"SERVER_KICK_CONTROLCHARS": "Seu nome não pode conter caracteres de controle",
|
||||
"SERVER_KICK_GENERICNAME": "Por favor, mude o seu nome usando o comando /name no console",
|
||||
"SERVER_KICK_MINNAME": "Seu nome deve conter no mínimo três caracteres",
|
||||
"SERVER_KICK_NAME_INUSE": "Seu nome já está sendo usado por outra pessoa",
|
||||
"SERVER_KICK_TEXT": "Você foi expulso",
|
||||
"SERVER_KICK_VPNS_NOTALLOWED": "VPNs não são permitidas neste servidor",
|
||||
"SERVER_PLUGIN_ERROR": "Um plugin gerou erro",
|
||||
"SERVER_REPORT_COUNT": "Você tem ^5{0} ^7denúncias recentes",
|
||||
"SERVER_TB_REMAIN": "Você está banido temporariamente",
|
||||
"SERVER_TB_TEXT": "Você está banido temporariamente",
|
||||
"SERVER_WARNING": "AVISO",
|
||||
"SERVER_WARNLIMT_REACHED": "Avisos demais! Leia o chat da próxima vez",
|
||||
"SERVER_WEBSITE_GENERIC": "este é o site do servidor",
|
||||
"SETUP_DISPLAY_SOCIAL": "Digitar link do convite do seu site no módulo da web (Discord, YouTube, etc.)",
|
||||
"SETUP_ENABLE_CUSTOMSAY": "Habilitar a customização do nome do comando say",
|
||||
"SETUP_ENABLE_MULTIOWN": "Habilitar vários proprietários",
|
||||
"SETUP_ENABLE_STEPPEDPRIV": "Ativar hierarquia de privilégios escalonada",
|
||||
"SETUP_ENABLE_VPNS": "Habilitar que os usuários usem VPN",
|
||||
"SETUP_ENABLE_WEBFRONT": "Habilitar o módulo da web do IW4MAdmin",
|
||||
"SETUP_ENCODING_STRING": "Digite sequência de codificação",
|
||||
"SETUP_IPHUB_KEY": "Digite iphub.info api key",
|
||||
"SETUP_SAY_NAME": "Habilitar a customização do nome do comando say",
|
||||
"SETUP_SERVER_IP": "Digite o endereço IP do servidor",
|
||||
"SETUP_SERVER_MANUALLOG": "Insira o caminho do arquivo de log manualmente",
|
||||
"SETUP_SERVER_PORT": "Digite a porta do servidor",
|
||||
"SETUP_SERVER_RCON": "Digite a senha do RCon do servidor",
|
||||
"SETUP_SERVER_SAVE": "Configuração salva, adicionar outra",
|
||||
"SETUP_SERVER_USEIW5M": "Usar analisador Pluto IW5 ",
|
||||
"SETUP_SERVER_USET6M": "Usar analisador Pluto T6 ",
|
||||
"SETUP_SOCIAL_LINK": "Digite o link da Rede Social",
|
||||
"SETUP_SOCIAL_TITLE": "Digite o nome da rede social",
|
||||
"SETUP_USE_CUSTOMENCODING": "Usar o analisador de codificação customizado",
|
||||
"WEBFRONT_ACTION_BAN_NAME": "Banir",
|
||||
"WEBFRONT_ACTION_LABEL_ID": "ID do cliente",
|
||||
"WEBFRONT_ACTION_LABEL_PASSWORD": "Senha",
|
||||
"WEBFRONT_ACTION_LABEL_REASON": "Razão",
|
||||
"WEBFRONT_ACTION_LOGIN_NAME": "Iniciar a sessão",
|
||||
"WEBFRONT_ACTION_UNBAN_NAME": "Retirar o banimento",
|
||||
"WEBFRONT_CLIENT_META_FALSE": "Não está",
|
||||
"WEBFRONT_CLIENT_META_JOINED": "Entrou com o nome",
|
||||
"WEBFRONT_CLIENT_META_MASKED": "Mascarado",
|
||||
"WEBFRONT_CLIENT_META_TRUE": "Está",
|
||||
"WEBFRONT_CLIENT_PRIVILEGED_TITLE": "Jogadores Privilegiados",
|
||||
"WEBFRONT_CLIENT_PROFILE_TITLE": "Pefil",
|
||||
"WEBFRONT_CLIENT_SEARCH_MATCHING": "Jogadores correspondidos",
|
||||
"WEBFRONT_CONSOLE_EXECUTE": "Executar",
|
||||
"WEBFRONT_CONSOLE_TITLE": "Console da Web",
|
||||
"WEBFRONT_ERROR_DESC": "O IW4MAdmin encontrou um erro",
|
||||
"WEBFRONT_ERROR_GENERIC_DESC": "Ocorreu um erro ao processar seu pedido",
|
||||
"WEBFRONT_ERROR_GENERIC_TITLE": "Desculpe!",
|
||||
"WEBFRONT_ERROR_TITLE": "Erro!",
|
||||
"WEBFRONT_HOME_TITLE": "Visão geral do servidor",
|
||||
"WEBFRONT_NAV_CONSOLE": "Console",
|
||||
"WEBFRONT_NAV_DISCORD": "Discord",
|
||||
"WEBFRONT_NAV_HOME": "Início",
|
||||
"WEBFRONT_NAV_LOGOUT": "Encerrar a sessão",
|
||||
"WEBFRONT_NAV_PENALTIES": "Penalidades",
|
||||
"WEBFRONT_NAV_PRIVILEGED": "Administradores",
|
||||
"WEBFRONT_NAV_PROFILE": "Perfil do Jogador",
|
||||
"WEBFRONT_NAV_SEARCH": "Achar jogador",
|
||||
"WEBFRONT_NAV_SOCIAL": "Rede Social",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_ADMIN": "Administrador",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_AGO": "atrás",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_NAME": "Nome",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_OFFENSE": "Ofensa",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_REMAINING": "restantes",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_SHOW": "Mostrar",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_SHOWONLY": "Mostrar somente",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_TIME": "Tempo/Restante",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_TYPE": "Tipo",
|
||||
"WEBFRONT_PENALTY_TITLE": "Penalidades dos jogadores",
|
||||
"WEBFRONT_PROFILE_FSEEN": "Visto primeiro em",
|
||||
"WEBFRONT_PROFILE_LEVEL": "Nível",
|
||||
"WEBFRONT_PROFILE_LSEEN": "Visto por último em",
|
||||
"WEBFRONT_PROFILE_PLAYER": "Jogou",
|
||||
"PLUGIN_STATS_SETUP_ENABLEAC": "Habilitar a anti-trapaça no servidor (Somente IW4/MW2)",
|
||||
"PLUGIN_STATS_ERROR_ADD": "Não foi possível adicionar o servidor para as estatísticas do servidor",
|
||||
"PLUGIN_STATS_CHEAT_DETECTED": "Aparentemente você está trapaceando",
|
||||
"PLUGINS_STATS_TEXT_KDR": "KDR",
|
||||
"PLUGINS_STATS_META_SPM": "Pontuação por minuto",
|
||||
"PLUGINS_WELCOME_USERANNOUNCE": "^5{{ClientName}} ^7 vem de ^5{{ClientLocation}}",
|
||||
"PLUGINS_WELCOME_USERWELCOME": "Bem-vindo ^5{{ClientName}}^7, esta é a sua visita de número ^5{{TimesConnected}} ^7 no servidor!",
|
||||
"PLUGINS_WELCOME_PRIVANNOUNCE": "{{ClientLevel}} {{ClientName}} entrou no servidor",
|
||||
"PLUGINS_LOGIN_AUTH": "não está registrado",
|
||||
"PLUGINS_PROFANITY_SETUP_ENABLE": "Habilitar o plugin de anti-palavrão",
|
||||
"PLUGINS_PROFANITY_WARNMSG": "Por favor, não use palavras ofensivas neste servidor",
|
||||
"PLUGINS_PROFANITY_KICKMSG": "Uso excessivo de palavrão, lave a boca da próxima vez",
|
||||
"GLOBAL_DEBUG": "Depuração",
|
||||
"COMMANDS_UNFLAG_DESC": "Remover a sinalização do jogador",
|
||||
"COMMANDS_UNFLAG_FAIL": "Você não pode retirar a sinalização do jogador",
|
||||
"COMMANDS_UNFLAG_NOTFLAGGED": "O jogador não está sinalizado",
|
||||
"COMMANDS_FLAG_ALREADYFLAGGED": "O jogador já está sinalizado",
|
||||
"PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT": "Mais jogado",
|
||||
"PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC": "ver o top 5 de jogadores mais dedicados no servidor",
|
||||
"WEBFRONT_PROFILE_MESSAGES": "Mensagens",
|
||||
"WEBFRONT_CLIENT_META_CONNECTIONS": "Conexões",
|
||||
"PLUGINS_STATS_COMMANDS_TOPSTATS_RATING": "Classificação",
|
||||
"PLUGINS_STATS_COMMANDS_PERFORMANCE": "Desempenho"
|
||||
}
|
||||
}
|
||||
}
|
269
Application/Localization/IW4MAdmin.ru-RU.json
Normal file
269
Application/Localization/IW4MAdmin.ru-RU.json
Normal file
@ -0,0 +1,269 @@
|
||||
{
|
||||
"LocalizationName": "ru-RU",
|
||||
"LocalizationIndex": {
|
||||
"Set": {
|
||||
"BROADCAST_OFFLINE": "^5IW4MAdmin ^1ВЫКЛЮЧАЕТСЯ",
|
||||
"BROADCAST_ONLINE": "^5IW4MADMIN ^7сейчас В СЕТИ",
|
||||
"COMMAND_HELP_OPTIONAL": "опционально",
|
||||
"COMMAND_HELP_SYNTAX": "Проблема с выражением мысли ( пересмотри слова)",
|
||||
"COMMAND_MISSINGARGS": "Приведено недостаточно аргументов",
|
||||
"COMMAND_NOACCESS": "У вас нет доступа к этой команде",
|
||||
"COMMAND_NOTAUTHORIZED": "Вы не авторизованы для исполнения этой команды",
|
||||
"COMMAND_TARGET_MULTI": "Несколько игроков соответствуют этому имени",
|
||||
"COMMAND_TARGET_NOTFOUND": "Невозможно найти указанного игрока",
|
||||
"COMMAND_UNKNOWN": "Вы ввели неизвестную команду",
|
||||
"COMMANDS_ADMINS_DESC": "перечислить присоединенных на данный момент игроков с правами",
|
||||
"COMMANDS_ADMINS_NONE": "Нет видимых администраторов в сети",
|
||||
"COMMANDS_ALIAS_ALIASES": "Имена",
|
||||
"COMMANDS_ALIAS_DESC": "получить прошлые имена и IP игрока",
|
||||
"COMMANDS_ALIAS_IPS": "IP",
|
||||
"COMMANDS_ARGS_CLEAR": "очистить",
|
||||
"COMMANDS_ARGS_CLIENTID": "ID игрока",
|
||||
"COMMANDS_ARGS_COMMANDS": "команды",
|
||||
"COMMANDS_ARGS_DURATION": "длительность (m|h|d|w|y)",
|
||||
"COMMANDS_ARGS_INACTIVE": "дни бездействия",
|
||||
"COMMANDS_ARGS_LEVEL": "уровень",
|
||||
"COMMANDS_ARGS_MAP": "карта",
|
||||
"COMMANDS_ARGS_MESSAGE": "сообщение",
|
||||
"COMMANDS_ARGS_PASSWORD": "пароль",
|
||||
"COMMANDS_ARGS_PLAYER": "игрок",
|
||||
"COMMANDS_ARGS_REASON": "причина",
|
||||
"COMMANDS_BAN_DESC": "навсегда забанить игрока на сервере",
|
||||
"COMMANDS_BAN_FAIL": "Вы не можете выдавать бан",
|
||||
"COMMANDS_BAN_SUCCESS": "был забанен навсегда",
|
||||
"COMMANDS_BANINFO_DESC": "получить информацию о бане игрока",
|
||||
"COMMANDS_BANINFO_NONE": "Не найдено действующего бана для этого игрока",
|
||||
"COMMANDS_BANINO_SUCCESS": "был забанен игроком ^5{0} ^7на:",
|
||||
"COMMANDS_FASTRESTART_DESC": "перезапустить нынешнюю карту",
|
||||
"COMMANDS_FASTRESTART_MASKED": "Карта была перезапущена",
|
||||
"COMMANDS_FASTRESTART_UNMASKED": "перезапустил карту",
|
||||
"COMMANDS_FIND_DESC": "найти игрока в базе данных",
|
||||
"COMMANDS_FIND_EMPTY": "Не найдено игроков",
|
||||
"COMMANDS_FIND_MIN": "Пожалуйста, введите хотя бы 3 символа",
|
||||
"COMMANDS_FLAG_DESC": "отметить подозрительного игрока и сообщить администраторам, чтобы присоединились",
|
||||
"COMMANDS_FLAG_FAIL": "Вы не можете ставить отметки",
|
||||
"COMMANDS_FLAG_SUCCESS": "Вы отметили",
|
||||
"COMMANDS_FLAG_UNFLAG": "Вы сняли отметку",
|
||||
"COMMANDS_HELP_DESC": "перечислить все доступные команды",
|
||||
"COMMANDS_HELP_MOREINFO": "Введите !help <имя команды>, чтобы узнать синтаксис для использования команды",
|
||||
"COMMANDS_HELP_NOTFOUND": "Не удалось найти эту команду",
|
||||
"COMMANDS_IP_DESC": "просмотреть ваш внешний IP-адрес",
|
||||
"COMMANDS_IP_SUCCESS": "Ваш внешний IP:",
|
||||
"COMMANDS_KICK_DESC": "исключить игрока по имени",
|
||||
"COMMANDS_KICK_FAIL": "У вас нет достаточных прав, чтобы исключать",
|
||||
"COMMANDS_KICK_SUCCESS": "был исключен",
|
||||
"COMMANDS_LIST_DESC": "перечислить действующих игроков",
|
||||
"COMMANDS_MAP_DESC": "сменить на определенную карту",
|
||||
"COMMANDS_MAP_SUCCESS": "Смена карты на",
|
||||
"COMMANDS_MAP_UKN": "Попытка сменить на неизвестную карту",
|
||||
"COMMANDS_MAPROTATE": "Смена карты через ^55 ^7секунд",
|
||||
"COMMANDS_MAPROTATE_DESC": "переключиться на следующую карту в ротации",
|
||||
"COMMANDS_MASK_DESC": "скрыть свое присутствие как игрока с правами",
|
||||
"COMMANDS_MASK_OFF": "Вы теперь демаскированы",
|
||||
"COMMANDS_MASK_ON": "Вы теперь замаскированы",
|
||||
"COMMANDS_OWNER_DESC": "утверить владение сервером",
|
||||
"COMMANDS_OWNER_FAIL": "Этот сервер уже имеет владельца",
|
||||
"COMMANDS_OWNER_SUCCESS": "Поздравляю, вы утвердили владение этим сервером!",
|
||||
"COMMANDS_PASSWORD_FAIL": "Ваш пароль должен быть хотя бы 5 символов в длину",
|
||||
"COMMANDS_PASSWORD_SUCCESS": "Ваш пароль был успешно установлен",
|
||||
"COMMANDS_PING_DESC": "получить пинг игрока",
|
||||
"COMMANDS_PING_SELF": "Ваш пинг:",
|
||||
"COMMANDS_PING_TARGET": "пинг:",
|
||||
"COMMANDS_PLUGINS_DESC": "просмотреть все загруженные плагины",
|
||||
"COMMANDS_PLUGINS_LOADED": "Загруженные плагины",
|
||||
"COMMANDS_PM_DESC": "отправить сообщение другому игроку",
|
||||
"COMMANDS_PRUNE_DESC": "понизить любых игроков с правами, которые не подключались за последнее время (по умолчанию: 30 дней)",
|
||||
"COMMANDS_PRUNE_FAIL": "Неверное количество дней бездействия",
|
||||
"COMMANDS_PRUNE_SUCCESS": "бездействующих пользователей с правами было сокращено",
|
||||
"COMMANDS_QUIT_DESC": "покинуть IW4MAdmin",
|
||||
"COMMANDS_RCON_DESC": "отправить RCon команду на сервер",
|
||||
"COMMANDS_RCON_SUCCESS": "Успешно отправлена команда RCon",
|
||||
"COMMANDS_REPORT_DESC": "пожаловаться на игрока за подозрительное поведение",
|
||||
"COMMANDS_REPORT_FAIL": "Вы не можете пожаловаться",
|
||||
"COMMANDS_REPORT_FAIL_CAMP": "Вы не можете пожаловаться на игрока за кемперство",
|
||||
"COMMANDS_REPORT_FAIL_DUPLICATE": "Вы уже пожаловались на этого игрока",
|
||||
"COMMANDS_REPORT_FAIL_SELF": "Вы не можете пожаловаться на самого себя",
|
||||
"COMMANDS_REPORT_SUCCESS": "Спасибо за вашу жалобу, администратор оповещен",
|
||||
"COMMANDS_REPORTS_CLEAR_SUCCESS": "Жалобы успешно очищены",
|
||||
"COMMANDS_REPORTS_DESC": "получить или очистить последние жалобы",
|
||||
"COMMANDS_REPORTS_NONE": "Пока нет жалоб на игроков",
|
||||
"COMMANDS_RULES_DESC": "перечислить правила сервера",
|
||||
"COMMANDS_RULES_NONE": "Владелец сервера не установил никаких правил",
|
||||
"COMMANDS_SAY_DESC": "транслировать сообщения всем игрокам",
|
||||
"COMMANDS_SETLEVEL_DESC": "установить особый уровень прав игроку",
|
||||
"COMMANDS_SETLEVEL_FAIL": "Указана неверная группа",
|
||||
"COMMANDS_SETLEVEL_LEVELTOOHIGH": "Вы только можете повысить ^5{0} ^7до ^5{1} ^7или понизить в правах",
|
||||
"COMMANDS_SETLEVEL_OWNER": "Может быть только 1 владелец. Измените настройки, если требуется несколько владельцов",
|
||||
"COMMANDS_SETLEVEL_SELF": "Вы не можете изменить свой уровень",
|
||||
"COMMANDS_SETLEVEL_STEPPEDDISABLED": "Этот сервер не разрешает вам повыситься",
|
||||
"COMMANDS_SETLEVEL_SUCCESS": "был успешно повышен",
|
||||
"COMMANDS_SETLEVEL_SUCCESS_TARGET": "Поздравляю! Вы были повышены до",
|
||||
"COMMANDS_SETPASSWORD_DESC": "установить свой пароль аутентификации",
|
||||
"COMMANDS_TEMPBAN_DESC": "временно забанить игрока на определенное время (по умолчанию: 1 час)",
|
||||
"COMMANDS_TEMPBAN_FAIL": "Вы не можете выдавать временный бан",
|
||||
"COMMANDS_TEMPBAN_SUCCESS": "был временно забанен за",
|
||||
"COMMANDS_UNBAN_DESC": "разбанить игрока по ID игрока",
|
||||
"COMMANDS_UNBAN_FAIL": "не забанен",
|
||||
"COMMANDS_UNBAN_SUCCESS": "Успешно разбанен",
|
||||
"COMMANDS_UPTIME_DESC": "получить время с начала запуска текущего приложения",
|
||||
"COMMANDS_UPTIME_TEXT": "был в сети",
|
||||
"COMMANDS_USAGE_DESC": "узнать о потреблении памяти приложением",
|
||||
"COMMANDS_USAGE_TEXT": "используется",
|
||||
"COMMANDS_WARN_DESC": "предупредить игрока за нарушение правил",
|
||||
"COMMANDS_WARN_FAIL": "У вас недостаточно прав, чтобы выносить предупреждения",
|
||||
"COMMANDS_WARNCLEAR_DESC": "удалить все предупреждения у игрока",
|
||||
"COMMANDS_WARNCLEAR_SUCCESS": "Все предупреждения очищены у",
|
||||
"COMMANDS_WHO_DESC": "предоставить информацию о себе",
|
||||
"GLOBAL_TIME_DAYS": "дней",
|
||||
"GLOBAL_ERROR": "Ошибка",
|
||||
"GLOBAL_TIME_HOURS": "часов",
|
||||
"GLOBAL_INFO": "Информация",
|
||||
"GLOBAL_TIME_MINUTES": "минут",
|
||||
"GLOBAL_REPORT": "Если вы подозреваете кого-то в ^5ЧИТЕРСТВЕ^7, используйте команду ^5!report",
|
||||
"GLOBAL_VERBOSE": "Подробно",
|
||||
"GLOBAL_WARNING": "Предупреждение",
|
||||
"MANAGER_CONNECTION_REST": "Соединение было восстановлено с помощью",
|
||||
"MANAGER_CONSOLE_NOSERV": "На данный момент нет серверов под мониторингом",
|
||||
"MANAGER_EXIT": "Нажмите любую клавишу, чтобы выйти...",
|
||||
"MANAGER_INIT_FAIL": "Критическая ошибка во время инициализации",
|
||||
"MANAGER_MONITORING_TEXT": "Идет мониторинг",
|
||||
"MANAGER_SHUTDOWN_SUCCESS": "Выключение завершено",
|
||||
"MANAGER_VERSION_CURRENT": "Ваша версия:",
|
||||
"MANAGER_VERSION_FAIL": "Не удалось получить последнюю версию IW4MAdmin",
|
||||
"MANAGER_VERSION_SUCCESS": "IW4MAdmin обновлен",
|
||||
"MANAGER_VERSION_UPDATE": "- есть обновление. Последняя версия:",
|
||||
"PLUGIN_IMPORTER_NOTFOUND": "Не найдено плагинов для загрузки",
|
||||
"PLUGIN_IMPORTER_REGISTERCMD": "Зарегистрированная команда",
|
||||
"PLUGINS_LOGIN_COMMANDS_LOGIN_DESC": "войти, используя пароль",
|
||||
"PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL": "Ваш пароль неверный",
|
||||
"PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS": "Вы теперь вошли",
|
||||
"PLUGINS_STATS_COMMANDS_RESET_DESC": "сбросить вашу статистику под ноль",
|
||||
"PLUGINS_STATS_COMMANDS_RESET_FAIL": "Вы должны быть подключены к серверу, чтобы сбросить свою статистику",
|
||||
"PLUGINS_STATS_COMMANDS_RESET_SUCCESS": "Ваша статистика на этом сервере была сброшена",
|
||||
"PLUGINS_STATS_COMMANDS_TOP_DESC": "показать топ-5 лучших игроков на этом сервере",
|
||||
"PLUGINS_STATS_COMMANDS_TOP_TEXT": "Лучшие игроки",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_DESC": "просмотреть свою статистику",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_FAIL": "Не удается найти игрока, которого вы указали.",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_FAIL_INGAME": "Указанный игрок должен быть в игре",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_FAIL_INGAME_SELF": "Вы должны быть в игре, чтобы просмотреть свою статистику",
|
||||
"PLUGINS_STATS_COMMANDS_VIEW_SUCCESS": "Статистика",
|
||||
"PLUGINS_STATS_TEXT_DEATHS": "СМЕРТЕЙ",
|
||||
"PLUGINS_STATS_TEXT_KILLS": "УБИЙСТВ",
|
||||
"PLUGINS_STATS_TEXT_NOQUALIFY": "Ещё нет совернующихся игроков за лучшую статистику",
|
||||
"PLUGINS_STATS_TEXT_SKILL": "МАСТЕРСТВО",
|
||||
"SERVER_BAN_APPEAL": "оспорить:",
|
||||
"SERVER_BAN_PREV": "Ранее забанены за",
|
||||
"SERVER_BAN_TEXT": "Вы забанены",
|
||||
"SERVER_ERROR_ADDPLAYER": "Не удалось добавить игрока",
|
||||
"SERVER_ERROR_COMMAND_INGAME": "Произошла внутренняя ошибка при обработке вашей команды",
|
||||
"SERVER_ERROR_COMMAND_LOG": "команда сгенерировала ошибку",
|
||||
"SERVER_ERROR_COMMUNICATION": "Не удалось связаться с",
|
||||
"SERVER_ERROR_DNE": "не существует",
|
||||
"SERVER_ERROR_DVAR": "Не удалось получить значение dvar:",
|
||||
"SERVER_ERROR_DVAR_HELP": "убедитесь, что на сервере загружена карта",
|
||||
"SERVER_ERROR_EXCEPTION": "Неожиданное исключение на",
|
||||
"SERVER_ERROR_LOG": "Неверный игровой лог-файл",
|
||||
"SERVER_ERROR_PLUGIN": "Произошла ошибка загрузки плагина",
|
||||
"SERVER_ERROR_POLLING": "снижение частоты обновления данных",
|
||||
"SERVER_ERROR_UNFIXABLE": "Мониторинг сервера выключен из-за неисправимых ошибок",
|
||||
"SERVER_KICK_CONTROLCHARS": "Ваше имя не должно содержать спецсимволы",
|
||||
"SERVER_KICK_GENERICNAME": "Пожалуйста, смените ваше имя, используя /name",
|
||||
"SERVER_KICK_MINNAME": "Ваше имя должно содержать хотя бы 3 символа",
|
||||
"SERVER_KICK_NAME_INUSE": "Ваше имя используется кем-то другим",
|
||||
"SERVER_KICK_TEXT": "Вы были исключены",
|
||||
"SERVER_KICK_VPNS_NOTALLOWED": "Использование VPN не разрешено на этом сервере",
|
||||
"SERVER_PLUGIN_ERROR": "Плагин образовал ошибку",
|
||||
"SERVER_REPORT_COUNT": "Имеется ^5{0} ^7жалоб за последнее время",
|
||||
"SERVER_TB_REMAIN": "Вы временно забанены",
|
||||
"SERVER_TB_TEXT": "Вы временно забанены",
|
||||
"SERVER_WARNING": "ПРЕДУПРЕЖДЕНИЕ",
|
||||
"SERVER_WARNLIMT_REACHED": "Слишком много предупреждений",
|
||||
"SERVER_WEBSITE_GENERIC": "веб-сайт этого сервера",
|
||||
"SETUP_DISPLAY_SOCIAL": "Отображать ссылку на социальную сеть в веб-интерфейсе (Discord, веб-сайт, ВК, и т.д.)",
|
||||
"SETUP_ENABLE_CUSTOMSAY": "Включить кастомное имя для чата",
|
||||
"SETUP_ENABLE_MULTIOWN": "Включить поддержку нескольких владельцев",
|
||||
"SETUP_ENABLE_STEPPEDPRIV": "Включить последовательную иерархию прав",
|
||||
"SETUP_ENABLE_VPNS": "Включить поддержку VPN у игроков",
|
||||
"SETUP_ENABLE_WEBFRONT": "Включить веб-интерфейс",
|
||||
"SETUP_ENCODING_STRING": "Введите кодировку",
|
||||
"SETUP_IPHUB_KEY": "Введите iphub.info api-ключ",
|
||||
"SETUP_SAY_NAME": "Введите кастомное имя для чата",
|
||||
"SETUP_SERVER_IP": "Введите IP-адрес сервера",
|
||||
"SETUP_SERVER_MANUALLOG": "Введите путь для лог-файла",
|
||||
"SETUP_SERVER_PORT": "Введите порт сервера",
|
||||
"SETUP_SERVER_RCON": "Введите RCon пароль сервера",
|
||||
"SETUP_SERVER_SAVE": "Настройки сохранены, добавить",
|
||||
"SETUP_SERVER_USEIW5M": "Использовать парсер Pluto IW5",
|
||||
"SETUP_SERVER_USET6M": "Использовать парсер Pluto T6",
|
||||
"SETUP_SOCIAL_LINK": "Ввести ссылку на социальную сеть",
|
||||
"SETUP_SOCIAL_TITLE": "Ввести имя социальной сети",
|
||||
"SETUP_USE_CUSTOMENCODING": "Использовать кастомную кодировку парсера",
|
||||
"WEBFRONT_ACTION_BAN_NAME": "Забанить",
|
||||
"WEBFRONT_ACTION_LABEL_ID": "ID игрока",
|
||||
"WEBFRONT_ACTION_LABEL_PASSWORD": "Пароль",
|
||||
"WEBFRONT_ACTION_LABEL_REASON": "Причина",
|
||||
"WEBFRONT_ACTION_LOGIN_NAME": "Войти",
|
||||
"WEBFRONT_ACTION_UNBAN_NAME": "Разбанить",
|
||||
"WEBFRONT_CLIENT_META_FALSE": "не",
|
||||
"WEBFRONT_CLIENT_META_JOINED": "Присоединился с именем",
|
||||
"WEBFRONT_CLIENT_META_MASKED": "Замаскирован",
|
||||
"WEBFRONT_CLIENT_META_TRUE": "Это",
|
||||
"WEBFRONT_CLIENT_PRIVILEGED_TITLE": "Игроки с правами",
|
||||
"WEBFRONT_CLIENT_PROFILE_TITLE": "Профиль",
|
||||
"WEBFRONT_CLIENT_SEARCH_MATCHING": "Подходящие игроки",
|
||||
"WEBFRONT_CONSOLE_EXECUTE": "Выполнить",
|
||||
"WEBFRONT_CONSOLE_TITLE": "Веб-консоль",
|
||||
"WEBFRONT_ERROR_DESC": "IW4MAdmin столкнулся с ошибкой",
|
||||
"WEBFRONT_ERROR_GENERIC_DESC": "Произошла ошибка во время обработки вашего запроса",
|
||||
"WEBFRONT_ERROR_GENERIC_TITLE": "Извините!",
|
||||
"WEBFRONT_ERROR_TITLE": "Ошибка!",
|
||||
"WEBFRONT_HOME_TITLE": "Обзор сервера",
|
||||
"WEBFRONT_NAV_CONSOLE": "Консоль",
|
||||
"WEBFRONT_NAV_DISCORD": "Дискорд ",
|
||||
"WEBFRONT_NAV_HOME": "Обзор Серверов ",
|
||||
"WEBFRONT_NAV_LOGOUT": "Выйти",
|
||||
"WEBFRONT_NAV_PENALTIES": "Наказания",
|
||||
"WEBFRONT_NAV_PRIVILEGED": "Админы",
|
||||
"WEBFRONT_NAV_PROFILE": "Профиль игрока",
|
||||
"WEBFRONT_NAV_SEARCH": "Найти игрока",
|
||||
"WEBFRONT_NAV_SOCIAL": "Соц. сети",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_ADMIN": "Админ",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_AGO": "назад",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_NAME": "Имя",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_OFFENSE": "Нарушение",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_REMAINING": "осталось",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_SHOW": "Показывать",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_SHOWONLY": "Показывать только",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_TIME": "Время/Осталось",
|
||||
"WEBFRONT_PENALTY_TEMPLATE_TYPE": "Тип",
|
||||
"WEBFRONT_PENALTY_TITLE": "Наказания игроков",
|
||||
"WEBFRONT_PROFILE_FSEEN": "Впервые заходил",
|
||||
"WEBFRONT_PROFILE_LEVEL": "Уровень",
|
||||
"WEBFRONT_PROFILE_LSEEN": "Последний раз заходил",
|
||||
"WEBFRONT_PROFILE_PLAYER": "Наиграл",
|
||||
"PLUGIN_STATS_SETUP_ENABLEAC": "Включить серверный античит (только IW4)",
|
||||
"PLUGIN_STATS_ERROR_ADD": "Не удалось добавить сервер в статистику серверов",
|
||||
"PLUGIN_STATS_CHEAT_DETECTED": "Кажется, вы читерите",
|
||||
"PLUGINS_STATS_TEXT_KDR": "Вот так ..",
|
||||
"PLUGINS_STATS_META_SPM": "Счёт за минуту",
|
||||
"PLUGINS_WELCOME_USERANNOUNCE": "^5{{ClientName}} ^7из ^5{{ClientLocation}}",
|
||||
"PLUGINS_WELCOME_USERWELCOME": "Добро пожаловать, ^5{{ClientName}}^7. Это ваше ^5{{TimesConnected}} ^7подключение по счёту!",
|
||||
"PLUGINS_WELCOME_PRIVANNOUNCE": "{{ClientLevel}} {{ClientName}} присоединился к серверу",
|
||||
"PLUGINS_LOGIN_AUTH": "Сперва Подключись",
|
||||
"PLUGINS_PROFANITY_SETUP_ENABLE": "Включить сдерживание ненормативной лексики",
|
||||
"PLUGINS_PROFANITY_WARNMSG": "Пожалуйта, не ругайтесь на этом сервере",
|
||||
"PLUGINS_PROFANITY_KICKMSG": "Чрезмерное употребление ненормативной лексики",
|
||||
"GLOBAL_DEBUG": "Отлаживание ",
|
||||
"COMMANDS_UNFLAG_DESC": "Снять все подозрение с игрока !",
|
||||
"COMMANDS_UNFLAG_FAIL": "Вы не можете снять подозрения..",
|
||||
"COMMANDS_UNFLAG_NOTFLAGGED": "Игрок без подозрения !",
|
||||
"COMMANDS_FLAG_ALREADYFLAGGED": "Игрок помечен ! ",
|
||||
"PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT": "Самые популярные",
|
||||
"PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC": "просмотр 5 лучших игроков на сервере",
|
||||
"WEBFRONT_PROFILE_MESSAGES": "Сообщения",
|
||||
"WEBFRONT_CLIENT_META_CONNECTIONS": "Подключения",
|
||||
"PLUGINS_STATS_COMMANDS_TOPSTATS_RATING": "Рейтинг",
|
||||
"PLUGINS_STATS_COMMANDS_PERFORMANCE": "Эффективность"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
using System;
|
||||
using SharedLibraryCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
class Logger : SharedLibraryCore.Interfaces.ILogger
|
||||
{
|
||||
enum LogType
|
||||
{
|
||||
{
|
||||
Verbose,
|
||||
Info,
|
||||
Debug,
|
||||
@ -15,27 +18,61 @@ namespace IW4MAdmin.Application
|
||||
Assert
|
||||
}
|
||||
|
||||
string FileName;
|
||||
object ThreadLock;
|
||||
readonly string FileName;
|
||||
readonly SemaphoreSlim OnLogWriting;
|
||||
static readonly short MAX_LOG_FILES = 10;
|
||||
|
||||
public Logger(string fn)
|
||||
{
|
||||
FileName = fn;
|
||||
ThreadLock = new object();
|
||||
if (File.Exists(fn))
|
||||
File.Delete(fn);
|
||||
FileName = Path.Join(Utilities.OperatingDirectory, "Log", $"{fn}.log");
|
||||
OnLogWriting = new SemaphoreSlim(1, 1);
|
||||
RotateLogs();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// rotates logs when log is initialized
|
||||
/// </summary>
|
||||
private void RotateLogs()
|
||||
{
|
||||
string maxLog = FileName + MAX_LOG_FILES;
|
||||
|
||||
if (File.Exists(maxLog))
|
||||
{
|
||||
File.Delete(maxLog);
|
||||
}
|
||||
|
||||
for (int i = MAX_LOG_FILES - 1; i >= 0; i--)
|
||||
{
|
||||
string logToMove = i == 0 ? FileName : FileName + i;
|
||||
string movedLogName = FileName + (i + 1);
|
||||
|
||||
if (File.Exists(logToMove))
|
||||
{
|
||||
File.Move(logToMove, movedLogName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Write(string msg, LogType type)
|
||||
{
|
||||
string LogLine = $"[{DateTime.Now.ToString("HH:mm:ss")}] - {type}: {msg}";
|
||||
lock (ThreadLock)
|
||||
OnLogWriting.Wait();
|
||||
|
||||
string stringType = type.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
stringType = Utilities.CurrentLocalization.LocalizationIndex[$"GLOBAL_{type.ToString().ToUpper()}"];
|
||||
}
|
||||
|
||||
catch (Exception) { }
|
||||
|
||||
string LogLine = $"[{DateTime.Now.ToString("MM.dd.yyy HH:mm:ss.fff")}] - {stringType}: {msg}";
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
// lets keep it simple and dispose of everything quickly as logging wont be that much (relatively)
|
||||
|
||||
Console.WriteLine(LogLine);
|
||||
File.AppendAllText(FileName, LogLine + Environment.NewLine);
|
||||
// lets keep it simple and dispose of everything quickly as logging wont be that much (relatively)
|
||||
Console.WriteLine(LogLine);
|
||||
File.AppendAllText(FileName, LogLine + Environment.NewLine);
|
||||
#else
|
||||
if (type == LogType.Error || type == LogType.Verbose)
|
||||
Console.WriteLine(LogLine);
|
||||
@ -43,6 +80,14 @@ namespace IW4MAdmin.Application
|
||||
File.AppendAllText(FileName, $"{LogLine}{Environment.NewLine}");
|
||||
#endif
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Well.. It looks like your machine can't event write to the log file. That's something else...");
|
||||
Console.WriteLine(ex.GetExceptionInfo());
|
||||
}
|
||||
|
||||
OnLogWriting.Release(1);
|
||||
}
|
||||
|
||||
public void WriteVerbose(string msg)
|
||||
|
@ -6,40 +6,53 @@ using System.Reflection;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore.Database;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using SharedLibraryCore.Localization;
|
||||
using IW4MAdmin.Application.Migration;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
static public double Version { get; private set; }
|
||||
static public ApplicationManager ServerManager = ApplicationManager.GetInstance();
|
||||
public static string OperatingDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
|
||||
static public ApplicationManager ServerManager;
|
||||
private static ManualResetEventSlim OnShutdownComplete = new ManualResetEventSlim();
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
AppDomain.CurrentDomain.SetData("DataDirectory", OperatingDirectory);
|
||||
System.Diagnostics.Process.GetCurrentProcess().PriorityClass = System.Diagnostics.ProcessPriorityClass.BelowNormal;
|
||||
Localization.Configure.Initialize();
|
||||
var loc = Utilities.CurrentLocalization.LocalizationSet;
|
||||
AppDomain.CurrentDomain.SetData("DataDirectory", Utilities.OperatingDirectory);
|
||||
|
||||
Version = Assembly.GetExecutingAssembly().GetName().Version.Major + Assembly.GetExecutingAssembly().GetName().Version.Minor / 10.0f;
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
|
||||
Version = Utilities.GetVersionAsDouble();
|
||||
|
||||
Console.WriteLine("=====================================================");
|
||||
Console.WriteLine(" IW4M ADMIN");
|
||||
Console.WriteLine(" by RaidMax ");
|
||||
Console.WriteLine($" Version {Version.ToString("0.0")}");
|
||||
Console.WriteLine($" Version {Utilities.GetVersionAsString()}");
|
||||
Console.WriteLine("=====================================================");
|
||||
|
||||
Index loc = null;
|
||||
|
||||
try
|
||||
{
|
||||
using (var db = new DatabaseContext())
|
||||
new ContextSeed(db).Seed().Wait();
|
||||
ServerManager = ApplicationManager.GetInstance();
|
||||
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration()?.CustomLocale);
|
||||
loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
|
||||
|
||||
CheckDirectories();
|
||||
// do any needed migrations
|
||||
// todo: move out
|
||||
ConfigurationMigration.MoveConfigFolder10518(null);
|
||||
|
||||
ServerManager = ApplicationManager.GetInstance();
|
||||
ServerManager.Logger.WriteInfo($"Version is {Version}");
|
||||
|
||||
var api = API.Master.Endpoint.Get();
|
||||
|
||||
var version = new API.Master.VersionInfo()
|
||||
{
|
||||
CurrentVersionStable = 99.99f
|
||||
@ -65,7 +78,7 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(loc["MANAGER_VERSION_FAIL"]);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
|
||||
#if !PRERELEASE
|
||||
@ -74,7 +87,7 @@ namespace IW4MAdmin.Application
|
||||
Console.ForegroundColor = ConsoleColor.DarkYellow;
|
||||
Console.WriteLine($"IW4MAdmin {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionStable.ToString("0.0")}]");
|
||||
Console.WriteLine($"{loc["MANAGER_VERSION_CURRENT"]} [v{Version.ToString("0.0")}]");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
#else
|
||||
else if (version.CurrentVersionPrerelease > Version)
|
||||
@ -82,22 +95,22 @@ namespace IW4MAdmin.Application
|
||||
Console.ForegroundColor = ConsoleColor.DarkYellow;
|
||||
Console.WriteLine($"IW4MAdmin-Prerelease {loc["MANAGER_VERSION_UPDATE"]} [v{version.CurrentVersionPrerelease.ToString("0.0")}-pr]");
|
||||
Console.WriteLine($"{loc["MANAGER_VERSION_CURRENT"]} [v{Version.ToString("0.0")}-pr]");
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine(loc["MANAGER_VERSION_SUCCESS"]);
|
||||
Console.ForegroundColor = ConsoleColor.White;
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
|
||||
ServerManager.Init().Wait();
|
||||
|
||||
var consoleTask = Task.Run(() =>
|
||||
var consoleTask = Task.Run(async () =>
|
||||
{
|
||||
String userInput;
|
||||
Player Origin = ServerManager.GetClientService().Get(1).Result.AsPlayer();
|
||||
var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
|
||||
|
||||
do
|
||||
{
|
||||
@ -108,26 +121,27 @@ namespace IW4MAdmin.Application
|
||||
|
||||
if (ServerManager.Servers.Count == 0)
|
||||
{
|
||||
Console.WriteLine("No servers are currently being monitored");
|
||||
Console.WriteLine(loc["MANAGER_CONSOLE_NOSERV"]);
|
||||
continue;
|
||||
}
|
||||
|
||||
Origin.CurrentServer = ServerManager.Servers[0];
|
||||
GameEvent E = new GameEvent(GameEvent.EventType.Say, userInput, Origin, null, ServerManager.Servers[0]);
|
||||
ServerManager.Servers[0].ExecuteEvent(E);
|
||||
if (userInput?.Length > 0)
|
||||
{
|
||||
GameEvent E = new GameEvent()
|
||||
{
|
||||
Type = GameEvent.EventType.Command,
|
||||
Data = userInput,
|
||||
Origin = Origin,
|
||||
Owner = ServerManager.Servers[0]
|
||||
};
|
||||
|
||||
ServerManager.GetEventHandler().AddEvent(E);
|
||||
await E.WaitAsync(30 * 1000);
|
||||
}
|
||||
Console.Write('>');
|
||||
|
||||
} while (ServerManager.Running);
|
||||
});
|
||||
|
||||
if (ServerManager.GetApplicationSettings().Configuration().EnableWebFront)
|
||||
{
|
||||
Task.Run(() => WebfrontCore.Program.Init(ServerManager));
|
||||
}
|
||||
|
||||
ServerManager.Start();
|
||||
ServerManager.Logger.WriteVerbose("Shutdown complete");
|
||||
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
@ -137,18 +151,45 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
e = e.InnerException;
|
||||
}
|
||||
Console.WriteLine($"Exception: {e.Message}");
|
||||
Console.WriteLine(e.Message);
|
||||
Console.WriteLine(loc["MANAGER_EXIT"]);
|
||||
Console.ReadKey();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ServerManager.GetApplicationSettings().Configuration().EnableWebFront)
|
||||
{
|
||||
Task.Run(() => WebfrontCore.Program.Init(ServerManager));
|
||||
}
|
||||
|
||||
OnShutdownComplete.Reset();
|
||||
ServerManager.Start();
|
||||
ServerManager.Logger.WriteVerbose(loc["MANAGER_SHUTDOWN_SUCCESS"]);
|
||||
OnShutdownComplete.Set();
|
||||
}
|
||||
|
||||
private static void OnCancelKey(object sender, ConsoleCancelEventArgs e)
|
||||
{
|
||||
ServerManager.Stop();
|
||||
OnShutdownComplete.Wait();
|
||||
}
|
||||
|
||||
static void CheckDirectories()
|
||||
{
|
||||
string curDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + Path.DirectorySeparatorChar;
|
||||
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Plugins")))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Plugins"));
|
||||
}
|
||||
|
||||
if (!Directory.Exists($"{curDirectory}Plugins"))
|
||||
Directory.CreateDirectory($"{curDirectory}Plugins");
|
||||
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Database")))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Database"));
|
||||
}
|
||||
|
||||
if (!Directory.Exists(Path.Join(Utilities.OperatingDirectory, "Log")))
|
||||
{
|
||||
Directory.CreateDirectory(Path.Join(Utilities.OperatingDirectory, "Log"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,21 @@
|
||||
using System;
|
||||
using IW4MAdmin.Application.API.Master;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Events;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Services;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore.Services;
|
||||
using IW4MAdmin.Application.API;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using WebfrontCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
@ -26,30 +23,33 @@ namespace IW4MAdmin.Application
|
||||
{
|
||||
private List<Server> _servers;
|
||||
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
|
||||
public Dictionary<int, Player> PrivilegedClients { get; set; }
|
||||
public ILogger Logger { get; private set; }
|
||||
public Dictionary<int, EFClient> PrivilegedClients { get; set; }
|
||||
public ILogger Logger => GetLogger(0);
|
||||
public bool Running { get; private set; }
|
||||
public EventHandler<GameEvent> ServerEventOccurred { get; private set; }
|
||||
public bool IsInitialized { get; private set; }
|
||||
// define what the delagate function looks like
|
||||
public delegate void OnServerEventEventHandler(object sender, GameEventArgs e);
|
||||
// expose the event handler so we can execute the events
|
||||
public OnServerEventEventHandler OnServerEvent { get; set; }
|
||||
public DateTime StartTime { get; private set; }
|
||||
public string Version => Assembly.GetEntryAssembly().GetName().Version.ToString();
|
||||
|
||||
static ApplicationManager Instance;
|
||||
List<AsyncStatus> TaskStatuses;
|
||||
readonly List<AsyncStatus> TaskStatuses;
|
||||
List<Command> Commands;
|
||||
List<MessageToken> MessageTokens;
|
||||
readonly List<MessageToken> MessageTokens;
|
||||
ClientService ClientSvc;
|
||||
AliasService AliasSvc;
|
||||
PenaltyService PenaltySvc;
|
||||
BaseConfigurationHandler<ApplicationConfiguration> ConfigHandler;
|
||||
EventApi Api;
|
||||
#if FTP_LOG
|
||||
const int UPDATE_FREQUENCY = 700;
|
||||
#else
|
||||
const int UPDATE_FREQUENCY = 450;
|
||||
#endif
|
||||
readonly AliasService AliasSvc;
|
||||
readonly PenaltyService PenaltySvc;
|
||||
public BaseConfigurationHandler<ApplicationConfiguration> ConfigHandler;
|
||||
GameEventHandler Handler;
|
||||
ManualResetEventSlim OnQuit;
|
||||
readonly IPageList PageList;
|
||||
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
|
||||
readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
|
||||
|
||||
private ApplicationManager()
|
||||
{
|
||||
Logger = new Logger($@"{Utilities.OperatingDirectory}IW4MAdmin.log");
|
||||
_servers = new List<Server>();
|
||||
Commands = new List<Command>();
|
||||
TaskStatuses = new List<AsyncStatus>();
|
||||
@ -57,19 +57,73 @@ namespace IW4MAdmin.Application
|
||||
ClientSvc = new ClientService();
|
||||
AliasSvc = new AliasService();
|
||||
PenaltySvc = new PenaltyService();
|
||||
PrivilegedClients = new Dictionary<int, Player>();
|
||||
Api = new EventApi();
|
||||
ServerEventOccurred += Api.OnServerEvent;
|
||||
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
|
||||
Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
|
||||
StartTime = DateTime.UtcNow;
|
||||
OnQuit = new ManualResetEventSlim();
|
||||
PageList = new PageList();
|
||||
OnServerEvent += OnGameEvent;
|
||||
OnServerEvent += EventApi.OnGameEvent;
|
||||
}
|
||||
|
||||
private void OnCancelKey(object sender, ConsoleCancelEventArgs args)
|
||||
private async void OnGameEvent(object sender, GameEventArgs args)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
#if DEBUG == true
|
||||
Logger.WriteDebug($"Entering event process for {args.Event.Id}");
|
||||
#endif
|
||||
|
||||
var newEvent = args.Event;
|
||||
|
||||
// the event has failed already
|
||||
if (newEvent.Failed)
|
||||
{
|
||||
goto skip;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await newEvent.Owner.ExecuteEvent(newEvent);
|
||||
|
||||
// save the event info to the database
|
||||
var changeHistorySvc = new ChangeHistoryService();
|
||||
await changeHistorySvc.Add(args.Event);
|
||||
|
||||
#if DEBUG
|
||||
Logger.WriteDebug($"Processed event with id {newEvent.Id}");
|
||||
#endif
|
||||
}
|
||||
|
||||
// this happens if a plugin requires login
|
||||
catch (AuthorizationException ex)
|
||||
{
|
||||
newEvent.FailReason = GameEvent.EventFailReason.Permission;
|
||||
newEvent.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMAND_NOTAUTHORIZED"]} - {ex.Message}");
|
||||
}
|
||||
|
||||
catch (NetworkException ex)
|
||||
{
|
||||
newEvent.FailReason = GameEvent.EventFailReason.Exception;
|
||||
Logger.WriteError(ex.Message);
|
||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
||||
}
|
||||
|
||||
catch (ServerException ex)
|
||||
{
|
||||
newEvent.FailReason = GameEvent.EventFailReason.Exception;
|
||||
Logger.WriteWarning(ex.Message);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
newEvent.FailReason = GameEvent.EventFailReason.Exception;
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EXCEPTION"]} {newEvent.Owner}");
|
||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
||||
}
|
||||
|
||||
skip:
|
||||
|
||||
// tell anyone waiting for the output that we're done
|
||||
newEvent.OnProcessed.Set();
|
||||
}
|
||||
|
||||
public IList<Server> GetServers()
|
||||
{
|
||||
@ -86,38 +140,76 @@ namespace IW4MAdmin.Application
|
||||
return Instance ?? (Instance = new ApplicationManager());
|
||||
}
|
||||
|
||||
public async Task UpdateServerStates()
|
||||
{
|
||||
// store the server hash code and task for it
|
||||
var runningUpdateTasks = new Dictionary<long, Task>();
|
||||
|
||||
while (Running)
|
||||
{
|
||||
// select the server ids that have completed the update task
|
||||
var serverTasksToRemove = runningUpdateTasks
|
||||
.Where(ut => ut.Value.Status == TaskStatus.RanToCompletion ||
|
||||
ut.Value.Status == TaskStatus.Canceled ||
|
||||
ut.Value.Status == TaskStatus.Faulted)
|
||||
.Select(ut => ut.Key)
|
||||
.ToList();
|
||||
|
||||
// this is to prevent the log reader from starting before the initial
|
||||
// query of players on the server
|
||||
if (serverTasksToRemove.Count > 0)
|
||||
{
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
// remove the update tasks as they have completd
|
||||
foreach (long serverId in serverTasksToRemove)
|
||||
{
|
||||
runningUpdateTasks.Remove(serverId);
|
||||
}
|
||||
|
||||
// select the servers where the tasks have completed
|
||||
var serverIds = Servers.Select(s => s.EndPoint).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
|
||||
foreach (var server in Servers.Where(s => serverIds.Contains(s.EndPoint)))
|
||||
{
|
||||
runningUpdateTasks.Add(server.EndPoint, Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await server.ProcessUpdatesAsync(new CancellationToken());
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.WriteWarning($"Failed to update status for {server}");
|
||||
Logger.WriteDebug(e.GetExceptionInfo());
|
||||
}
|
||||
}));
|
||||
}
|
||||
#if DEBUG
|
||||
Logger.WriteDebug($"{runningUpdateTasks.Count} servers queued for stats updates");
|
||||
ThreadPool.GetMaxThreads(out int workerThreads, out int n);
|
||||
ThreadPool.GetAvailableThreads(out int availableThreads, out int m);
|
||||
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
|
||||
#endif
|
||||
await Task.Delay(ConfigHandler.Configuration().RConPollRate);
|
||||
}
|
||||
|
||||
// trigger the event processing loop to end
|
||||
SetHasEvent();
|
||||
}
|
||||
|
||||
public async Task Init()
|
||||
{
|
||||
Running = true;
|
||||
|
||||
#region DATABASE
|
||||
var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
|
||||
.Select(c => new
|
||||
{
|
||||
c.Password,
|
||||
c.PasswordSalt,
|
||||
c.ClientId,
|
||||
c.Level,
|
||||
c.Name
|
||||
});
|
||||
|
||||
foreach (var a in ipList)
|
||||
using (var db = new DatabaseContext(GetApplicationSettings().Configuration()?.ConnectionString, GetApplicationSettings().Configuration()?.DatabaseProvider))
|
||||
{
|
||||
try
|
||||
{
|
||||
PrivilegedClients.Add(a.ClientId, new Player()
|
||||
{
|
||||
Name = a.Name,
|
||||
ClientId = a.ClientId,
|
||||
Level = a.Level,
|
||||
PasswordSalt = a.PasswordSalt,
|
||||
Password = a.Password
|
||||
});
|
||||
}
|
||||
|
||||
catch (ArgumentException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
await new ContextSeed(db).Seed();
|
||||
}
|
||||
|
||||
PrivilegedClients = (await ClientSvc.GetPrivilegedClients()).ToDictionary(_client => _client.ClientId);
|
||||
#endregion
|
||||
|
||||
#region CONFIG
|
||||
@ -138,7 +230,13 @@ namespace IW4MAdmin.Application
|
||||
if (newConfig.Servers == null)
|
||||
{
|
||||
ConfigHandler.Set(newConfig);
|
||||
newConfig.Servers = ConfigurationGenerator.GenerateServerConfig(new List<ServerConfiguration>());
|
||||
newConfig.Servers = new List<ServerConfiguration>();
|
||||
|
||||
do
|
||||
{
|
||||
newConfig.Servers.Add((ServerConfiguration)new ServerConfiguration().Generate());
|
||||
} while (Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["SETUP_SERVER_SAVE"]));
|
||||
|
||||
config = newConfig;
|
||||
await ConfigHandler.Save();
|
||||
}
|
||||
@ -154,13 +252,15 @@ namespace IW4MAdmin.Application
|
||||
|
||||
if (string.IsNullOrEmpty(config.WebfrontBindUrl))
|
||||
{
|
||||
config.WebfrontBindUrl = "http://127.0.0.1:1624";
|
||||
config.WebfrontBindUrl = "http://0.0.0.0:1624";
|
||||
await ConfigHandler.Save();
|
||||
}
|
||||
}
|
||||
|
||||
else if (config.Servers.Count == 0)
|
||||
{
|
||||
throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid");
|
||||
}
|
||||
|
||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||
Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(config.CustomParserEncoding) ? config.CustomParserEncoding : "windows-1252");
|
||||
@ -176,18 +276,19 @@ namespace IW4MAdmin.Application
|
||||
await Plugin.OnLoadAsync(this);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.WriteError($"An error occured loading plugin {Plugin.Name}");
|
||||
Logger.WriteDebug($"Exception: {e.Message}");
|
||||
Logger.WriteDebug($"Stack Trace: {e.StackTrace}");
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_PLUGIN"]} {Plugin.Name}");
|
||||
Logger.WriteDebug(ex.GetExceptionInfo());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region COMMANDS
|
||||
if (ClientSvc.GetOwners().Result.Count == 0)
|
||||
{
|
||||
Commands.Add(new COwner());
|
||||
}
|
||||
|
||||
Commands.Add(new CQuit());
|
||||
Commands.Add(new CKick());
|
||||
@ -211,6 +312,7 @@ namespace IW4MAdmin.Application
|
||||
Commands.Add(new CListRules());
|
||||
Commands.Add(new CPrivateMessage());
|
||||
Commands.Add(new CFlag());
|
||||
Commands.Add(new CUnflag());
|
||||
Commands.Add(new CReport());
|
||||
Commands.Add(new CListReports());
|
||||
Commands.Add(new CListBanInfo());
|
||||
@ -223,14 +325,20 @@ namespace IW4MAdmin.Application
|
||||
Commands.Add(new CKillServer());
|
||||
Commands.Add(new CSetPassword());
|
||||
Commands.Add(new CPing());
|
||||
Commands.Add(new CSetGravatar());
|
||||
Commands.Add(new CNextMap());
|
||||
|
||||
foreach (Command C in SharedLibraryCore.Plugins.PluginImporter.ActiveCommands)
|
||||
{
|
||||
Commands.Add(C);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region INIT
|
||||
async Task Init(ServerConfiguration Conf)
|
||||
{
|
||||
// setup the event handler after the class is initialized
|
||||
Handler = new GameEventHandler(this);
|
||||
try
|
||||
{
|
||||
var ServerInstance = new IW4MServer(this, Conf);
|
||||
@ -241,21 +349,26 @@ namespace IW4MAdmin.Application
|
||||
_servers.Add(ServerInstance);
|
||||
}
|
||||
|
||||
Logger.WriteVerbose($"Now monitoring {ServerInstance.Hostname}");
|
||||
Logger.WriteVerbose($"{Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"]} {ServerInstance.Hostname}");
|
||||
// add the start event for this server
|
||||
|
||||
// this way we can keep track of execution time and see if problems arise.
|
||||
var Status = new AsyncStatus(ServerInstance, UPDATE_FREQUENCY);
|
||||
lock (TaskStatuses)
|
||||
var e = new GameEvent()
|
||||
{
|
||||
TaskStatuses.Add(Status);
|
||||
}
|
||||
Type = GameEvent.EventType.Start,
|
||||
Data = $"{ServerInstance.GameName} started",
|
||||
Owner = ServerInstance
|
||||
};
|
||||
|
||||
Handler.AddEvent(e);
|
||||
}
|
||||
|
||||
catch (ServerException e)
|
||||
{
|
||||
Logger.WriteError($"Not monitoring server {Conf.IPAddress}:{Conf.Port} due to uncorrectable errors");
|
||||
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"]} [{Conf.IPAddress}:{Conf.Port}]");
|
||||
if (e.GetType() == typeof(DvarException))
|
||||
Logger.WriteDebug($"Could not get the dvar value for {(e as DvarException).Data["dvar_name"]} (ensure the server has a map loaded)");
|
||||
{
|
||||
Logger.WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"]} {(e as DvarException).Data["dvar_name"]} ({Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR_HELP"]})");
|
||||
}
|
||||
else if (e.GetType() == typeof(NetworkException))
|
||||
{
|
||||
Logger.WriteDebug(e.Message);
|
||||
@ -267,126 +380,116 @@ namespace IW4MAdmin.Application
|
||||
}
|
||||
|
||||
await Task.WhenAll(config.Servers.Select(c => Init(c)).ToArray());
|
||||
|
||||
#endregion
|
||||
|
||||
Running = true;
|
||||
}
|
||||
|
||||
private void HeartBeatThread()
|
||||
private async Task SendHeartbeat(object state)
|
||||
{
|
||||
bool successfulConnection = false;
|
||||
restartConnection:
|
||||
while (!successfulConnection)
|
||||
{
|
||||
try
|
||||
{
|
||||
API.Master.Heartbeat.Send(this, true).Wait();
|
||||
successfulConnection = true;
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
successfulConnection = false;
|
||||
Logger.WriteWarning($"Could not connect to heartbeat server - {e.Message}");
|
||||
}
|
||||
|
||||
Thread.Sleep(30000);
|
||||
}
|
||||
var heartbeatState = (HeartbeatState)state;
|
||||
|
||||
while (Running)
|
||||
{
|
||||
Logger.WriteDebug("Sending heartbeat...");
|
||||
try
|
||||
if (!heartbeatState.Connected)
|
||||
{
|
||||
API.Master.Heartbeat.Send(this).Wait();
|
||||
}
|
||||
catch (System.Net.Http.HttpRequestException e)
|
||||
{
|
||||
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
||||
}
|
||||
|
||||
catch (AggregateException e)
|
||||
{
|
||||
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
||||
var exceptions = e.InnerExceptions.Where(ex => ex.GetType() == typeof(RestEase.ApiException));
|
||||
|
||||
foreach (var ex in exceptions)
|
||||
try
|
||||
{
|
||||
if (((RestEase.ApiException)ex).StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
await Heartbeat.Send(this, true);
|
||||
heartbeatState.Connected = true;
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
heartbeatState.Connected = false;
|
||||
Logger.WriteWarning($"Could not connect to heartbeat server - {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
await Heartbeat.Send(this);
|
||||
}
|
||||
|
||||
catch (System.Net.Http.HttpRequestException e)
|
||||
{
|
||||
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
||||
}
|
||||
|
||||
catch (AggregateException e)
|
||||
{
|
||||
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
||||
var exceptions = e.InnerExceptions.Where(ex => ex.GetType() == typeof(RestEase.ApiException));
|
||||
|
||||
foreach (var ex in exceptions)
|
||||
{
|
||||
successfulConnection = false;
|
||||
goto restartConnection;
|
||||
if (((RestEase.ApiException)ex).StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
heartbeatState.Connected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (RestEase.ApiException e)
|
||||
{
|
||||
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
||||
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
catch (RestEase.ApiException e)
|
||||
{
|
||||
successfulConnection = false;
|
||||
goto restartConnection;
|
||||
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
||||
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||
{
|
||||
heartbeatState.Connected = false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.WriteWarning($"Could not send heartbeat - {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(30000);
|
||||
}
|
||||
await Task.Delay(30000);
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
Task.Run(() => HeartBeatThread());
|
||||
while (Running || TaskStatuses.Count > 0)
|
||||
// this needs to be run seperately from the main thread
|
||||
var _ = Task.Run(() => SendHeartbeat(new HeartbeatState()));
|
||||
_ = Task.Run(() => UpdateServerStates());
|
||||
|
||||
while (Running)
|
||||
{
|
||||
for (int i = 0; i < TaskStatuses.Count; i++)
|
||||
{
|
||||
var Status = TaskStatuses[i];
|
||||
|
||||
// task is read to be rerun
|
||||
if (Status.RequestedTask == null || Status.RequestedTask.Status == TaskStatus.RanToCompletion)
|
||||
{
|
||||
// remove the task when we want to quit and last run has finished
|
||||
if (!Running)
|
||||
{
|
||||
TaskStatuses.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
// normal operation
|
||||
else
|
||||
{
|
||||
Status.Update(new Task<bool>(() => { return (Status.Dependant as Server).ProcessUpdatesAsync(Status.GetToken()).Result; }));
|
||||
if (Status.RunAverage > 1000 + UPDATE_FREQUENCY && !(Status.Dependant as Server).Throttled)
|
||||
Logger.WriteWarning($"Update task average execution is longer than desired for {(Status.Dependant as Server)} [{Status.RunAverage}ms]");
|
||||
}
|
||||
}
|
||||
|
||||
if (Status.RequestedTask.Status == TaskStatus.Faulted)
|
||||
{
|
||||
Logger.WriteWarning($"Update task for {(Status.Dependant as Server)} faulted, restarting");
|
||||
Status.Abort();
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(UPDATE_FREQUENCY);
|
||||
OnQuit.Wait();
|
||||
OnQuit.Reset();
|
||||
}
|
||||
#if !DEBUG
|
||||
foreach (var S in Servers)
|
||||
S.Broadcast(Utilities.CurrentLocalization.LocalizationSet["BROADCAST_OFFLINE"]).Wait();
|
||||
#endif
|
||||
_servers.Clear();
|
||||
}
|
||||
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
Running = false;
|
||||
}
|
||||
|
||||
public ILogger GetLogger()
|
||||
public ILogger GetLogger(long serverId)
|
||||
{
|
||||
return Logger;
|
||||
if (Loggers.ContainsKey(serverId))
|
||||
{
|
||||
return Loggers[serverId];
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Logger newLogger;
|
||||
|
||||
if (serverId == 0)
|
||||
{
|
||||
newLogger = new Logger("IW4MAdmin-Manager");
|
||||
}
|
||||
else
|
||||
{
|
||||
newLogger = new Logger($"IW4MAdmin-Server-{serverId}");
|
||||
}
|
||||
|
||||
Loggers.Add(serverId, newLogger);
|
||||
return newLogger;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<MessageToken> GetMessageTokens()
|
||||
@ -394,22 +497,59 @@ namespace IW4MAdmin.Application
|
||||
return MessageTokens;
|
||||
}
|
||||
|
||||
public IList<Player> GetActiveClients()
|
||||
public IList<EFClient> GetActiveClients()
|
||||
{
|
||||
var ActiveClients = new List<Player>();
|
||||
|
||||
foreach (var server in _servers)
|
||||
ActiveClients.AddRange(server.Players.Where(p => p != null));
|
||||
|
||||
return ActiveClients;
|
||||
return _servers.SelectMany(s => s.Clients).Where(p => p != null).ToList();
|
||||
}
|
||||
|
||||
public ClientService GetClientService() => ClientSvc;
|
||||
public AliasService GetAliasService() => AliasSvc;
|
||||
public PenaltyService GetPenaltyService() => PenaltySvc;
|
||||
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings() => ConfigHandler;
|
||||
public IDictionary<int, Player> GetPrivilegedClients() => PrivilegedClients;
|
||||
public IEventApi GetEventApi() => Api;
|
||||
public bool ShutdownRequested() => !Running;
|
||||
public ClientService GetClientService()
|
||||
{
|
||||
return ClientSvc;
|
||||
}
|
||||
|
||||
public AliasService GetAliasService()
|
||||
{
|
||||
return AliasSvc;
|
||||
}
|
||||
|
||||
public PenaltyService GetPenaltyService()
|
||||
{
|
||||
return PenaltySvc;
|
||||
}
|
||||
|
||||
public IConfigurationHandler<ApplicationConfiguration> GetApplicationSettings()
|
||||
{
|
||||
return ConfigHandler;
|
||||
}
|
||||
|
||||
public IDictionary<int, EFClient> GetPrivilegedClients()
|
||||
{
|
||||
return PrivilegedClients;
|
||||
}
|
||||
|
||||
public bool ShutdownRequested()
|
||||
{
|
||||
return !Running;
|
||||
}
|
||||
|
||||
public IEventHandler GetEventHandler()
|
||||
{
|
||||
return Handler;
|
||||
}
|
||||
|
||||
public void SetHasEvent()
|
||||
{
|
||||
OnQuit.Set();
|
||||
}
|
||||
|
||||
public IList<Assembly> GetPluginAssemblies()
|
||||
{
|
||||
return SharedLibraryCore.Plugins.PluginImporter.PluginAssemblies;
|
||||
}
|
||||
|
||||
public IPageList GetPageList()
|
||||
{
|
||||
return PageList;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
60
Application/Migration/ConfigurationMigration.cs
Normal file
60
Application/Migration/ConfigurationMigration.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace IW4MAdmin.Application.Migration
|
||||
{
|
||||
/// <summary>
|
||||
/// helps facilitate the migration of configs from one version and or location
|
||||
/// to another
|
||||
/// </summary>
|
||||
class ConfigurationMigration
|
||||
{
|
||||
/// <summary>
|
||||
/// moves existing configs from the root folder into a configs folder
|
||||
/// </summary>
|
||||
public static void MoveConfigFolder10518(ILogger log)
|
||||
{
|
||||
string currentDirectory = Utilities.OperatingDirectory;
|
||||
|
||||
// we don't want to do this for migrations or tests where the
|
||||
// property isn't initialized or it's wrong
|
||||
if (currentDirectory != null)
|
||||
{
|
||||
string configDirectory = Path.Join(currentDirectory, "Configuration");
|
||||
|
||||
if (!Directory.Exists(configDirectory))
|
||||
{
|
||||
log?.WriteDebug($"Creating directory for configs {configDirectory}");
|
||||
Directory.CreateDirectory(configDirectory);
|
||||
}
|
||||
|
||||
var configurationFiles = Directory.EnumerateFiles(currentDirectory, "*.json")
|
||||
.Select(f => f.Split(Path.DirectorySeparatorChar).Last())
|
||||
.Where(f => f.Count(c => c == '.') == 1);
|
||||
|
||||
foreach (var configFile in configurationFiles)
|
||||
{
|
||||
log?.WriteDebug($"Moving config file {configFile}");
|
||||
string destinationPath = Path.Join("Configuration", configFile);
|
||||
if (!File.Exists(destinationPath))
|
||||
{
|
||||
File.Move(configFile, destinationPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!File.Exists(Path.Join("Database", "Database.db")) &&
|
||||
File.Exists("Database.db"))
|
||||
{
|
||||
log?.WriteDebug("Moving database file");
|
||||
File.Move("Database.db", Path.Join("Database", "Database.db"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Application.Misc
|
||||
{
|
||||
public class VPNCheck
|
||||
{
|
||||
public static async Task<bool> UsingVPN(string ip, string apiKey)
|
||||
{
|
||||
#if DEBUG
|
||||
return await Task.FromResult(false);
|
||||
|
||||
#else
|
||||
try
|
||||
{
|
||||
using (var RequestClient = new System.Net.Http.HttpClient())
|
||||
{
|
||||
RequestClient.DefaultRequestHeaders.Add("X-Key", apiKey);
|
||||
string response = await RequestClient.GetStringAsync($"http://v2.api.iphub.info/ip/{ip}");
|
||||
var responseJson = JsonConvert.DeserializeObject<JObject>(response);
|
||||
int blockType = Convert.ToInt32(responseJson["block"]);
|
||||
return blockType == 1;
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
26
Application/PageList.cs
Normal file
26
Application/PageList.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace IW4MAdmin.Application
|
||||
{
|
||||
/// <summary>
|
||||
/// implementatin of IPageList that supports basic
|
||||
/// pages title and page location for webfront
|
||||
/// </summary>
|
||||
class PageList : IPageList
|
||||
{
|
||||
/// <summary>
|
||||
/// Pages dictionary
|
||||
/// Key = page name
|
||||
/// Value = page location (url)
|
||||
/// </summary>
|
||||
public IDictionary<string, string> Pages { get; set; }
|
||||
|
||||
public PageList()
|
||||
{
|
||||
Pages = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Configuration>Release</Configuration>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<PublishDir>C:\Projects\IW4M-Admin\Publish\Windows</PublishDir>
|
||||
</PropertyGroup>
|
||||
</Project>
|
21
Application/RconParsers/IW3RConParser.cs
Normal file
21
Application/RconParsers/IW3RConParser.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using SharedLibraryCore.RCon;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
{
|
||||
class IW3RConParser : IW4RConParser
|
||||
{
|
||||
private static readonly CommandPrefix Prefixes = new CommandPrefix()
|
||||
{
|
||||
Tell = "tell {0} {1}",
|
||||
Say = "say {0}",
|
||||
Kick = "clientkick {0} \"{1}\"",
|
||||
Ban = "clientkick {0} \"{1}\"",
|
||||
TempBan = "tempbanclient {0} \"{1}\""
|
||||
};
|
||||
|
||||
public override CommandPrefix GetCommandPrefixes() => Prefixes;
|
||||
}
|
||||
}
|
@ -1,20 +1,19 @@
|
||||
using System;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.RCon;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.RCon;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
|
||||
namespace Application.RconParsers
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
{
|
||||
class IW4RConParser : IRConParser
|
||||
{
|
||||
private static CommandPrefix Prefixes = new CommandPrefix()
|
||||
private static readonly CommandPrefix Prefixes = new CommandPrefix()
|
||||
{
|
||||
Tell = "tellraw {0} {1}",
|
||||
Say = "sayraw {0}",
|
||||
@ -22,10 +21,13 @@ namespace Application.RconParsers
|
||||
Ban = "clientkick {0} \"{1}\"",
|
||||
TempBan = "tempbanclient {0} \"{1}\""
|
||||
};
|
||||
|
||||
|
||||
private static readonly string StatusRegex = @"^( *[0-9]+) +-*([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|(?:[a-z]|[0-9]){32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$";
|
||||
|
||||
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
|
||||
{
|
||||
return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command)).Skip(1).ToArray();
|
||||
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
|
||||
return response.Skip(1).ToArray();
|
||||
}
|
||||
|
||||
public async Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName)
|
||||
@ -58,7 +60,7 @@ namespace Application.RconParsers
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<Player>> GetStatusAsync(Connection connection)
|
||||
public async Task<List<EFClient>> GetStatusAsync(Connection connection)
|
||||
{
|
||||
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
|
||||
return ClientsFromStatus(response);
|
||||
@ -69,46 +71,75 @@ namespace Application.RconParsers
|
||||
return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"set {dvarName} {dvarValue}")).Length > 0;
|
||||
}
|
||||
|
||||
public CommandPrefix GetCommandPrefixes() => Prefixes;
|
||||
|
||||
private List<Player> ClientsFromStatus(string[] Status)
|
||||
public virtual CommandPrefix GetCommandPrefixes()
|
||||
{
|
||||
List<Player> StatusPlayers = new List<Player>();
|
||||
return Prefixes;
|
||||
}
|
||||
|
||||
private List<EFClient> ClientsFromStatus(string[] Status)
|
||||
{
|
||||
List<EFClient> StatusPlayers = new List<EFClient>();
|
||||
|
||||
if (Status.Length < 4)
|
||||
{
|
||||
throw new ServerException("Unexpected status response received");
|
||||
}
|
||||
|
||||
int validMatches = 0;
|
||||
foreach (String S in Status)
|
||||
{
|
||||
String responseLine = S.Trim();
|
||||
|
||||
if (Regex.Matches(responseLine, @" *^\d+", RegexOptions.IgnoreCase).Count > 0)
|
||||
var regex = Regex.Match(responseLine, StatusRegex, RegexOptions.IgnoreCase);
|
||||
|
||||
if (regex.Success)
|
||||
{
|
||||
String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
int cID = -1;
|
||||
int Ping = -1;
|
||||
Int32.TryParse(playerInfo[2], out Ping);
|
||||
String cName = Encoding.UTF8.GetString(Encoding.Convert(Utilities.EncodingType, Encoding.UTF8, Utilities.EncodingType.GetBytes(responseLine.Substring(46, 18).StripColors().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()
|
||||
validMatches++;
|
||||
int clientNumber = int.Parse(regex.Groups[1].Value);
|
||||
int score = int.Parse(regex.Groups[2].Value);
|
||||
|
||||
int ping = 999;
|
||||
|
||||
// their state can be CNCT, ZMBI etc
|
||||
if (regex.Groups[3].Value.Length <= 3)
|
||||
{
|
||||
Name = cName,
|
||||
NetworkId = npID,
|
||||
ClientNumber = cID,
|
||||
IPAddress = cIP,
|
||||
Ping = Ping,
|
||||
ping = int.Parse(regex.Groups[3].Value);
|
||||
}
|
||||
|
||||
long networkId = regex.Groups[4].Value.ConvertLong();
|
||||
string name = regex.Groups[5].Value.StripColors().Trim();
|
||||
int? ip = regex.Groups[7].Value.Split(':')[0].ConvertToIP();
|
||||
|
||||
var client = new EFClient()
|
||||
{
|
||||
CurrentAlias = new EFAlias()
|
||||
{
|
||||
Name = name
|
||||
},
|
||||
NetworkId = networkId,
|
||||
ClientNumber = clientNumber,
|
||||
IPAddress = ip,
|
||||
Ping = ping,
|
||||
Score = score,
|
||||
IsBot = npID == -1
|
||||
IsBot = ip == null,
|
||||
State = EFClient.ClientState.Connecting
|
||||
};
|
||||
StatusPlayers.Add(P);
|
||||
StatusPlayers.Add(client);
|
||||
|
||||
// they've not fully connected yet
|
||||
if (!client.IsBot && ping == 999)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this happens if status is requested while map is rotating
|
||||
if (Status.Length > 5 && validMatches == 0)
|
||||
{
|
||||
throw new ServerException("Server is rotating map");
|
||||
}
|
||||
|
||||
return StatusPlayers;
|
||||
}
|
||||
}
|
||||
|
171
Application/RconParsers/IW5MRConParser.cs
Normal file
171
Application/RconParsers/IW5MRConParser.cs
Normal file
@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text;
|
||||
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore.RCon;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
{
|
||||
public class IW5MRConParser : IRConParser
|
||||
{
|
||||
private static readonly CommandPrefix Prefixes = new CommandPrefix()
|
||||
{
|
||||
Tell = "tell {0} {1}",
|
||||
Say = "say {0}",
|
||||
Kick = "dropClient {0} \"{1}\"",
|
||||
Ban = "dropClient {0} \"{1}\"",
|
||||
TempBan = "dropClient {0} \"{1}\""
|
||||
};
|
||||
|
||||
public CommandPrefix GetCommandPrefixes() => Prefixes;
|
||||
|
||||
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
|
||||
{
|
||||
await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, false);
|
||||
return new string[] { "Command Executed" };
|
||||
}
|
||||
|
||||
public async Task<Dvar<T>> GetDvarAsync<T>(Connection connection, string dvarName)
|
||||
{
|
||||
// why can't this be real :(
|
||||
if (dvarName == "version")
|
||||
return new Dvar<T>(dvarName)
|
||||
{
|
||||
Value = (T)Convert.ChangeType("IW5 MP 1.9 build 461 Fri Sep 14 00:04:28 2012 win-x86", typeof(T))
|
||||
};
|
||||
|
||||
if (dvarName == "shortversion")
|
||||
return new Dvar<T>(dvarName)
|
||||
{
|
||||
Value = (T)Convert.ChangeType("1.9", typeof(T))
|
||||
};
|
||||
|
||||
if (dvarName == "mapname")
|
||||
return new Dvar<T>(dvarName)
|
||||
{
|
||||
Value = (T)Convert.ChangeType("Unknown", typeof(T))
|
||||
};
|
||||
|
||||
if (dvarName == "g_gametype")
|
||||
return new Dvar<T>(dvarName)
|
||||
{
|
||||
Value = (T)Convert.ChangeType("Unknown", typeof(T))
|
||||
};
|
||||
|
||||
if (dvarName == "fs_game")
|
||||
return new Dvar<T>(dvarName)
|
||||
{
|
||||
Value = (T)Convert.ChangeType("", typeof(T))
|
||||
};
|
||||
|
||||
if (dvarName == "g_logsync")
|
||||
return new Dvar<T>(dvarName)
|
||||
{
|
||||
Value = (T)Convert.ChangeType(1, typeof(T))
|
||||
};
|
||||
|
||||
if (dvarName == "fs_basepath")
|
||||
return new Dvar<T>(dvarName)
|
||||
{
|
||||
Value = (T)Convert.ChangeType("", typeof(T))
|
||||
};
|
||||
|
||||
|
||||
|
||||
string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, dvarName);
|
||||
|
||||
if (LineSplit.Length < 4)
|
||||
{
|
||||
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
|
||||
e.Data["dvar_name"] = dvarName;
|
||||
throw e;
|
||||
}
|
||||
|
||||
string[] ValueSplit = LineSplit[1].Split(new char[] { '"' });
|
||||
|
||||
if (ValueSplit.Length == 0)
|
||||
{
|
||||
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
|
||||
e.Data["dvar_name"] = dvarName;
|
||||
throw e;
|
||||
}
|
||||
|
||||
string DvarName = dvarName;
|
||||
string DvarCurrentValue = Regex.Replace(ValueSplit[3].StripColors(), @"\^[0-9]", "");
|
||||
|
||||
return new Dvar<T>(DvarName)
|
||||
{
|
||||
Value = (T)Convert.ChangeType(DvarCurrentValue, typeof(T))
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<EFClient>> GetStatusAsync(Connection connection)
|
||||
{
|
||||
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
|
||||
return ClientsFromStatus(response);
|
||||
}
|
||||
|
||||
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
|
||||
{
|
||||
// T6M doesn't respond with anything when a value is set, so we can only hope for the best :c
|
||||
await connection.SendQueryAsync(StaticHelpers.QueryType.DVAR, $"set {dvarName} {dvarValue}", false);
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<EFClient> ClientsFromStatus(string[] status)
|
||||
{
|
||||
List<EFClient> StatusPlayers = new List<EFClient>();
|
||||
|
||||
foreach (string statusLine in status)
|
||||
{
|
||||
String responseLine = statusLine;
|
||||
|
||||
if (Regex.Matches(responseLine, @"^ *\d+", RegexOptions.IgnoreCase).Count > 0) // its a client line!
|
||||
{
|
||||
String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// this happens when the client is in a zombie state
|
||||
if (playerInfo.Length < 5)
|
||||
continue;
|
||||
int clientId = -1;
|
||||
int Ping = -1;
|
||||
|
||||
Int32.TryParse(playerInfo[2], out Ping);
|
||||
string name = Encoding.UTF8.GetString(Encoding.Convert(Utilities.EncodingType, Encoding.UTF8, Utilities.EncodingType.GetBytes(responseLine.Substring(23, 15).StripColors().Trim())));
|
||||
long networkId = 0;//playerInfo[4].ConvertLong();
|
||||
int.TryParse(playerInfo[0], out clientId);
|
||||
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}");
|
||||
int? ipAddress = regex.Value.Split(':')[0].ConvertToIP();
|
||||
regex = Regex.Match(responseLine, @" +(\d+ +){3}");
|
||||
int score = Int32.Parse(regex.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]);
|
||||
|
||||
var p = new EFClient()
|
||||
{
|
||||
Name = name,
|
||||
NetworkId = networkId,
|
||||
ClientNumber = clientId,
|
||||
IPAddress = ipAddress,
|
||||
Ping = Ping,
|
||||
Score = score,
|
||||
IsBot = false,
|
||||
State = EFClient.ClientState.Connecting
|
||||
};
|
||||
|
||||
StatusPlayers.Add(p);
|
||||
|
||||
if (p.IsBot)
|
||||
p.NetworkId = -p.ClientNumber;
|
||||
}
|
||||
}
|
||||
|
||||
return StatusPlayers;
|
||||
}
|
||||
}
|
||||
}
|
@ -9,56 +9,19 @@ using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore.RCon;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
|
||||
namespace Application.RconParsers
|
||||
namespace IW4MAdmin.Application.RconParsers
|
||||
{
|
||||
public class T6MRConParser : IRConParser
|
||||
{
|
||||
class T6MResponse
|
||||
{
|
||||
public class SInfo
|
||||
{
|
||||
public short Com_maxclients { get; set; }
|
||||
public string Game { get; set; }
|
||||
public string Gametype { get; set; }
|
||||
public string Mapname { get; set; }
|
||||
public short NumBots { get; set; }
|
||||
public short NumClients { get; set; }
|
||||
public short Round { get; set; }
|
||||
public string Sv_hostname { get; set; }
|
||||
}
|
||||
|
||||
public class PInfo
|
||||
{
|
||||
public short Assists { get; set; }
|
||||
public string Clan { get; set; }
|
||||
public short Deaths { get; set; }
|
||||
public short Downs { get; set; }
|
||||
public short Headshots { get; set; }
|
||||
public short Id { get; set; }
|
||||
public bool IsBot { get; set; }
|
||||
public short Kills { get; set; }
|
||||
public string Name { get; set; }
|
||||
public short Ping { get; set; }
|
||||
public short Revives { get; set; }
|
||||
public int Score { get; set; }
|
||||
public long Xuid { get; set; }
|
||||
public string Ip { get; set; }
|
||||
}
|
||||
|
||||
public SInfo Info { get; set; }
|
||||
public PInfo[] Players { get; set; }
|
||||
}
|
||||
|
||||
private static CommandPrefix Prefixes = new CommandPrefix()
|
||||
private static readonly CommandPrefix Prefixes = new CommandPrefix()
|
||||
{
|
||||
Tell = "tell {0} {1}",
|
||||
Say = "say {0}",
|
||||
Kick = "clientKick {0}",
|
||||
Ban = "clientKick {0}",
|
||||
TempBan = "clientKick {0}"
|
||||
Kick = "clientkick_for_reason {0} \"{1}\"",
|
||||
Ban = "clientkick_for_reason {0} \"{1}\"",
|
||||
TempBan = "clientkick_for_reason {0} \"{1}\""
|
||||
};
|
||||
|
||||
public CommandPrefix GetCommandPrefixes() => Prefixes;
|
||||
@ -73,7 +36,6 @@ namespace Application.RconParsers
|
||||
{
|
||||
string[] LineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, $"get {dvarName}");
|
||||
|
||||
|
||||
if (LineSplit.Length < 2)
|
||||
{
|
||||
var e = new DvarException($"DVAR \"{dvarName}\" does not exist");
|
||||
@ -99,12 +61,10 @@ namespace Application.RconParsers
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<Player>> GetStatusAsync(Connection connection)
|
||||
public async Task<List<EFClient>> GetStatusAsync(Connection connection)
|
||||
{
|
||||
string[] response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, "status");
|
||||
return ClientsFromStatus(response);
|
||||
|
||||
//return ClientsFromResponse(connection);
|
||||
}
|
||||
|
||||
public async Task<bool> SetDvarAsync(Connection connection, string dvarName, object dvarValue)
|
||||
@ -114,45 +74,9 @@ namespace Application.RconParsers
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<List<Player>> ClientsFromResponse(Connection conn)
|
||||
private List<EFClient> ClientsFromStatus(string[] status)
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.BaseAddress = new Uri($"http://{conn.Endpoint.Address}:{conn.Endpoint.Port}/");
|
||||
|
||||
try
|
||||
{
|
||||
var parameters = new FormUrlEncodedContent(new[]
|
||||
{
|
||||
new KeyValuePair<string, string>("rcon_password", conn.RConPassword)
|
||||
});
|
||||
|
||||
var serverResponse = await client.PostAsync("/info", parameters);
|
||||
var serverResponseObject = Newtonsoft.Json.JsonConvert.DeserializeObject<T6MResponse>(await serverResponse.Content.ReadAsStringAsync());
|
||||
|
||||
return serverResponseObject.Players.Select(p => new Player()
|
||||
{
|
||||
Name = p.Name,
|
||||
NetworkId = p.Xuid,
|
||||
ClientNumber = p.Id,
|
||||
IPAddress = p.Ip.Split(':')[0].ConvertToIP(),
|
||||
Ping = p.Ping,
|
||||
Score = p.Score,
|
||||
IsBot = p.IsBot,
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
throw new NetworkException(e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private List<Player> ClientsFromStatus(string[] status)
|
||||
{
|
||||
List<Player> StatusPlayers = new List<Player>();
|
||||
List<EFClient> StatusPlayers = new List<EFClient>();
|
||||
|
||||
foreach (string statusLine in status)
|
||||
{
|
||||
@ -173,22 +97,24 @@ namespace Application.RconParsers
|
||||
#if DEBUG
|
||||
Ping = 1;
|
||||
#endif
|
||||
int ipAddress = regex.Value.Split(':')[0].ConvertToIP();
|
||||
int? ipAddress = regex.Value.Split(':')[0].ConvertToIP();
|
||||
regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+");
|
||||
int score = 0;
|
||||
// todo: fix this when T6M score is valid ;)
|
||||
//int score = Int32.Parse(playerInfo[1]);
|
||||
|
||||
StatusPlayers.Add(new Player()
|
||||
var p = new EFClient()
|
||||
{
|
||||
Name = name,
|
||||
NetworkId = networkId,
|
||||
ClientNumber = clientId,
|
||||
IPAddress = ipAddress,
|
||||
Ping = Ping,
|
||||
Score = score,
|
||||
IsBot = networkId < 1
|
||||
});
|
||||
Score = 0,
|
||||
State = EFClient.ClientState.Connecting,
|
||||
IsBot = ipAddress == null
|
||||
};
|
||||
|
||||
if (p.IsBot)
|
||||
p.NetworkId = -p.ClientNumber;
|
||||
|
||||
StatusPlayers.Add(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
31
Application/SharedGUIDKick.js
Normal file
31
Application/SharedGUIDKick.js
Normal file
@ -0,0 +1,31 @@
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 1.1,
|
||||
name: 'Shared GUID Kicker Plugin',
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
// make sure we only check for IW4(x)
|
||||
if (server.GameName !== 2) {
|
||||
return false;
|
||||
|
||||
|
||||
}
|
||||
|
||||
// connect or join event
|
||||
if (gameEvent.Type === 3) {
|
||||
// this GUID seems to have been packed in a IW4 torrent and results in an unreasonable amount of people using the same GUID
|
||||
if (gameEvent.Origin.NetworkId === -805366929435212061) {
|
||||
gameEvent.Origin.Kick('Your GUID is generic. Delete players/guids.dat and rejoin', _IW4MAdminClient);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
62
Application/VPNDetection.js
Normal file
62
Application/VPNDetection.js
Normal file
@ -0,0 +1,62 @@
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 1.0,
|
||||
name: 'VPN Detection Plugin',
|
||||
|
||||
manager: null,
|
||||
logger: null,
|
||||
vpnExceptionIds: [],
|
||||
|
||||
checkForVpn: function (origin) {
|
||||
var exempt = false;
|
||||
// prevent players that are exempt from being kicked
|
||||
this.vpnExceptionIds.forEach(function (id) {
|
||||
if (id === origin.ClientId) {
|
||||
exempt = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (exempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
var usingVPN = false;
|
||||
|
||||
try {
|
||||
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);
|
||||
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 + ')');
|
||||
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], _IW4MAdminClient);
|
||||
}
|
||||
},
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
// connect event
|
||||
if (gameEvent.Type === 3) {
|
||||
this.checkForVpn(gameEvent.Origin);
|
||||
}
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
this.manager = manager;
|
||||
this.logger = manager.GetLogger(0);
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
214
DiscordWebhook/DiscordWebhook.py
Normal file
214
DiscordWebhook/DiscordWebhook.py
Normal file
@ -0,0 +1,214 @@
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
import collections
|
||||
import os
|
||||
|
||||
# the following classes model the discord webhook api parameters
|
||||
class WebhookAuthor():
|
||||
def __init__(self, name=None, url=None, icon_url=None):
|
||||
if name:
|
||||
self.name = name
|
||||
if url:
|
||||
self.url = url
|
||||
if icon_url:
|
||||
self.icon_url = icon_url
|
||||
|
||||
class WebhookField():
|
||||
def __init__(self, name=None, value=None, inline=False):
|
||||
if name:
|
||||
self.name = name
|
||||
if value:
|
||||
self.value = value
|
||||
if inline:
|
||||
self.inline = inline
|
||||
|
||||
class WebhookEmbed():
|
||||
def __init__(self):
|
||||
self.author = ''
|
||||
self.title = ''
|
||||
self.url = ''
|
||||
self.description = ''
|
||||
self.color = 0
|
||||
self.fields = []
|
||||
self.thumbnail = {}
|
||||
|
||||
class WebhookParams():
|
||||
def __init__(self, username=None, avatar_url=None, content=None):
|
||||
self.username = ''
|
||||
self.avatar_url = ''
|
||||
self.content = ''
|
||||
self.embeds = []
|
||||
|
||||
# quick way to convert all the objects to a nice json object
|
||||
def to_json(self):
|
||||
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True)
|
||||
|
||||
# gets the relative link to a user's profile
|
||||
def get_client_profile(profile_id):
|
||||
return u'{}/Client/ProfileAsync/{}'.format(base_url, profile_id)
|
||||
|
||||
def get_client_profile_markdown(client_name, profile_id):
|
||||
return u'[{}]({})'.format(client_name, get_client_profile(profile_id))
|
||||
|
||||
#todo: exception handling for opening the file
|
||||
if os.getenv("DEBUG"):
|
||||
config_file_name = 'config.dev.json'
|
||||
else:
|
||||
config_file_name = 'config.json'
|
||||
|
||||
with open(config_file_name) as json_config_file:
|
||||
json_config = json.load(json_config_file)
|
||||
|
||||
# this should be an URL to an IP or FQN to an IW4MAdmin instance
|
||||
# ie http://127.0.0.1 or http://IW4MAdmin.com
|
||||
base_url = json_config['IW4MAdminUrl']
|
||||
end_point = '/api/event'
|
||||
request_url = base_url + end_point
|
||||
# this should be the full discord webhook url
|
||||
# ie https://discordapp.com/api/webhooks/<id>/<token>
|
||||
discord_webhook_notification_url = json_config['DiscordWebhookNotificationUrl']
|
||||
discord_webhook_information_url = json_config['DiscordWebhookInformationUrl']
|
||||
# this should be the numerical id of the discord group
|
||||
# 12345678912345678
|
||||
notify_role_ids = json_config['NotifyRoleIds']
|
||||
|
||||
def get_new_events():
|
||||
events = []
|
||||
response = requests.get(request_url)
|
||||
data = response.json()
|
||||
should_notify = False
|
||||
|
||||
for event in data:
|
||||
# commonly used event info items
|
||||
event_type = event['eventType']['name']
|
||||
server_name = event['ownerEntity']['name']
|
||||
|
||||
if event['originEntity']:
|
||||
origin_client_name = event['originEntity']['name']
|
||||
origin_client_id = int(event['originEntity']['id'])
|
||||
|
||||
if event['targetEntity']:
|
||||
target_client_name = event['targetEntity']['name'] or ''
|
||||
target_client_id = int(event['targetEntity']['id']) or 0
|
||||
|
||||
webhook_item = WebhookParams()
|
||||
webhook_item_embed = WebhookEmbed()
|
||||
|
||||
#todo: the following don't need to be generated every time, as it says the same
|
||||
webhook_item.username = 'IW4MAdmin'
|
||||
webhook_item.avatar_url = 'https://raidmax.org/IW4MAdmin/img/iw4adminicon-3.png'
|
||||
webhook_item_embed.color = 31436
|
||||
webhook_item_embed.url = base_url
|
||||
webhook_item_embed.thumbnail = { 'url' : 'https://raidmax.org/IW4MAdmin/img/iw4adminicon-3.png' }
|
||||
webhook_item.embeds.append(webhook_item_embed)
|
||||
|
||||
# the server should be visible on all event types
|
||||
server_field = WebhookField('Server', server_name)
|
||||
webhook_item_embed.fields.append(server_field)
|
||||
|
||||
role_ids_string = ''
|
||||
for id in notify_role_ids:
|
||||
role_ids_string += '\r\n<@&{}>\r\n'.format(id)
|
||||
|
||||
if event_type == 'Report':
|
||||
report_reason = event['extraInfo']
|
||||
|
||||
report_reason_field = WebhookField('Reason', report_reason)
|
||||
reported_by_field = WebhookField('By', get_client_profile_markdown(origin_client_name, origin_client_id))
|
||||
reported_field = WebhookField('Reported Player',get_client_profile_markdown(target_client_name, target_client_id))
|
||||
|
||||
# add each fields to the embed
|
||||
webhook_item_embed.title = 'Player Reported'
|
||||
webhook_item_embed.fields.append(reported_field)
|
||||
webhook_item_embed.fields.append(reported_by_field)
|
||||
webhook_item_embed.fields.append(report_reason_field)
|
||||
|
||||
should_notify = True
|
||||
|
||||
elif event_type == 'Ban':
|
||||
ban_reason = event['extraInfo']
|
||||
ban_reason_field = WebhookField('Reason', ban_reason)
|
||||
banned_by_field = WebhookField('By', get_client_profile_markdown(origin_client_name, origin_client_id))
|
||||
banned_field = WebhookField('Banned Player', get_client_profile_markdown(target_client_name, target_client_id))
|
||||
|
||||
# add each fields to the embed
|
||||
webhook_item_embed.title = 'Player Banned'
|
||||
webhook_item_embed.fields.append(banned_field)
|
||||
webhook_item_embed.fields.append(banned_by_field)
|
||||
webhook_item_embed.fields.append(ban_reason_field)
|
||||
|
||||
should_notify = True
|
||||
|
||||
elif event_type == 'Connect':
|
||||
connected_field = WebhookField('Connected Player', get_client_profile_markdown(origin_client_name, origin_client_id))
|
||||
webhook_item_embed.title = 'Player Connected'
|
||||
webhook_item_embed.fields.append(connected_field)
|
||||
|
||||
elif event_type == 'Disconnect':
|
||||
disconnected_field = WebhookField('Disconnected Player', get_client_profile_markdown(origin_client_name, origin_client_id))
|
||||
webhook_item_embed.title = 'Player Disconnected'
|
||||
webhook_item_embed.fields.append(disconnected_field)
|
||||
|
||||
elif event_type == 'Say':
|
||||
say_client_field = WebhookField('Player', get_client_profile_markdown(origin_client_name, origin_client_id))
|
||||
message_field = WebhookField('Message', event['extraInfo'])
|
||||
|
||||
webhook_item_embed.title = 'Message From Player'
|
||||
webhook_item_embed.fields.append(say_client_field)
|
||||
webhook_item_embed.fields.append(message_field)
|
||||
|
||||
#if event_type == 'ScriptKill' or event_type == 'Kill':
|
||||
# kill_str = '{} killed {}'.format(get_client_profile_markdown(origin_client_name, origin_client_id),
|
||||
# get_client_profile_markdown(target_client_name, target_client_id))
|
||||
# killed_field = WebhookField('Kill Information', kill_str)
|
||||
# webhook_item_embed.title = 'Player Killed'
|
||||
# webhook_item_embed.fields.append(killed_field)
|
||||
|
||||
#todo: handle other events
|
||||
else:
|
||||
continue
|
||||
|
||||
#make sure there's at least one group to notify
|
||||
if len(notify_role_ids) > 0:
|
||||
# unfortunately only the content can be used to to notify members in groups
|
||||
#embed content shows the role but doesn't notify
|
||||
webhook_item.content = role_ids_string
|
||||
|
||||
events.append({'item' : webhook_item, 'notify' : should_notify})
|
||||
|
||||
return events
|
||||
|
||||
# sends the data to the webhook location
|
||||
def execute_webhook(data):
|
||||
for event in data:
|
||||
event_json = event['item'].to_json()
|
||||
url = None
|
||||
|
||||
if event['notify']:
|
||||
url = discord_webhook_notification_url
|
||||
else:
|
||||
if len(discord_webhook_information_url) > 0:
|
||||
url = discord_webhook_information_url
|
||||
|
||||
if url :
|
||||
response = requests.post(url,
|
||||
data=event_json,
|
||||
headers={'Content-type' : 'application/json'})
|
||||
|
||||
# grabs new events and executes the webhook fo each valid event
|
||||
def run():
|
||||
failed_count = 1
|
||||
print('starting polling for events')
|
||||
while True:
|
||||
try:
|
||||
new_events = get_new_events()
|
||||
execute_webhook(new_events)
|
||||
except Exception as e:
|
||||
print('failed to get new events ({})'.format(failed_count))
|
||||
print(e)
|
||||
failed_count += 1
|
||||
time.sleep(5)
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
99
DiscordWebhook/DiscordWebhook.pyproj
Normal file
99
DiscordWebhook/DiscordWebhook.pyproj
Normal file
@ -0,0 +1,99 @@
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>15a81d6e-7502-46ce-8530-0647a380b5f4</ProjectGuid>
|
||||
<ProjectHome>.</ProjectHome>
|
||||
<StartupFile>DiscordWebhook.py</StartupFile>
|
||||
<SearchPath>
|
||||
</SearchPath>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
<OutputPath>.</OutputPath>
|
||||
<Name>DiscordWebhook</Name>
|
||||
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
|
||||
<RootNamespace>DiscordWebhook</RootNamespace>
|
||||
<InterpreterId>MSBuild|env|$(MSBuildProjectFullPath)</InterpreterId>
|
||||
<IsWindowsApplication>False</IsWindowsApplication>
|
||||
<LaunchProvider>Standard Python launcher</LaunchProvider>
|
||||
<EnableNativeCodeDebugging>False</EnableNativeCodeDebugging>
|
||||
<Environment>DEBUG=True</Environment>
|
||||
<PublishUrl>C:\Projects\IW4M-Admin\Publish\WindowsPrerelease\DiscordWebhook</PublishUrl>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
<OutputPath>bin\Prerelease\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DiscordWebhook.py" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Interpreter Include="env\">
|
||||
<Id>env</Id>
|
||||
<Version>3.6</Version>
|
||||
<Description>env (Python 3.6 (64-bit))</Description>
|
||||
<InterpreterPath>Scripts\python.exe</InterpreterPath>
|
||||
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
|
||||
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
|
||||
<Architecture>X64</Architecture>
|
||||
</Interpreter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="config.json">
|
||||
<Publish>True</Publish>
|
||||
</Content>
|
||||
<Content Include="requirements.txt">
|
||||
<Publish>True</Publish>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" />
|
||||
<!-- Uncomment the CoreCompile target to enable the Build command in
|
||||
Visual Studio and specify your pre- and post-build commands in
|
||||
the BeforeBuild and AfterBuild targets below. -->
|
||||
<!--<Target Name="CoreCompile" />-->
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||
<WebProjectProperties>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<UseCustomServer>True</UseCustomServer>
|
||||
<CustomServerUrl>http://localhost</CustomServerUrl>
|
||||
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}" User="">
|
||||
<WebProjectProperties>
|
||||
<StartPageUrl>
|
||||
</StartPageUrl>
|
||||
<StartAction>CurrentPage</StartAction>
|
||||
<AspNetDebugging>True</AspNetDebugging>
|
||||
<SilverlightDebugging>False</SilverlightDebugging>
|
||||
<NativeDebugging>False</NativeDebugging>
|
||||
<SQLDebugging>False</SQLDebugging>
|
||||
<ExternalProgram>
|
||||
</ExternalProgram>
|
||||
<StartExternalURL>
|
||||
</StartExternalURL>
|
||||
<StartCmdLineArguments>
|
||||
</StartCmdLineArguments>
|
||||
<StartWorkingDirectory>
|
||||
</StartWorkingDirectory>
|
||||
<EnableENC>False</EnableENC>
|
||||
<AlwaysStartWebServerOnDebug>False</AlwaysStartWebServerOnDebug>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
6
DiscordWebhook/config.json
Normal file
6
DiscordWebhook/config.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"IW4MAdminUrl": "",
|
||||
"DiscordWebhookNotificationUrl": "",
|
||||
"DiscordWebhookInformationUrl": "",
|
||||
"NotifyRoleIds": []
|
||||
}
|
7
DiscordWebhook/requirements.txt
Normal file
7
DiscordWebhook/requirements.txt
Normal file
@ -0,0 +1,7 @@
|
||||
certifi>=2018.4.16
|
||||
chardet>=3.0.4
|
||||
idna>=2.7
|
||||
pip>=18.0
|
||||
requests>=2.19.1
|
||||
setuptools>=39.0.1
|
||||
urllib3>=1.23
|
105
GameLogServer/GameLogServer.pyproj
Normal file
105
GameLogServer/GameLogServer.pyproj
Normal file
@ -0,0 +1,105 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>42efda12-10d3-4c40-a210-9483520116bc</ProjectGuid>
|
||||
<ProjectHome>.</ProjectHome>
|
||||
<ProjectTypeGuids>{789894c7-04a9-4a11-a6b5-3f4435165112};{1b580a1a-fdb3-4b32-83e1-6407eb2722e6};{349c5851-65df-11da-9384-00065b846f21};{888888a0-9f3d-457c-b088-3a5042f75d52}</ProjectTypeGuids>
|
||||
<StartupFile>runserver.py</StartupFile>
|
||||
<SearchPath>
|
||||
</SearchPath>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
<LaunchProvider>Web launcher</LaunchProvider>
|
||||
<WebBrowserUrl>http://localhost</WebBrowserUrl>
|
||||
<OutputPath>.</OutputPath>
|
||||
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
|
||||
<Name>GameLogServer</Name>
|
||||
<RootNamespace>GameLogServer</RootNamespace>
|
||||
<InterpreterId>MSBuild|env|$(MSBuildProjectFullPath)</InterpreterId>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Prerelease' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||
<OutputPath>bin\Prerelease\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="GameLogServer\log_reader.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="GameLogServer\restart_resource.py" />
|
||||
<Compile Include="GameLogServer\server.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="runserver.py" />
|
||||
<Compile Include="GameLogServer\__init__.py" />
|
||||
<Compile Include="GameLogServer\log_resource.py" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="GameLogServer\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="requirements.txt" />
|
||||
<None Include="Stable.pubxml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Interpreter Include="env\">
|
||||
<Id>env</Id>
|
||||
<Version>3.6</Version>
|
||||
<Description>env (Python 3.6 (64-bit))</Description>
|
||||
<InterpreterPath>Scripts\python.exe</InterpreterPath>
|
||||
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
|
||||
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
|
||||
<Architecture>X64</Architecture>
|
||||
</Interpreter>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" />
|
||||
<!-- Specify pre- and post-build commands in the BeforeBuild and
|
||||
AfterBuild targets below. -->
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||
<WebProjectProperties>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<UseCustomServer>True</UseCustomServer>
|
||||
<CustomServerUrl>http://localhost</CustomServerUrl>
|
||||
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}" User="">
|
||||
<WebProjectProperties>
|
||||
<StartPageUrl>
|
||||
</StartPageUrl>
|
||||
<StartAction>CurrentPage</StartAction>
|
||||
<AspNetDebugging>True</AspNetDebugging>
|
||||
<SilverlightDebugging>False</SilverlightDebugging>
|
||||
<NativeDebugging>False</NativeDebugging>
|
||||
<SQLDebugging>False</SQLDebugging>
|
||||
<ExternalProgram>
|
||||
</ExternalProgram>
|
||||
<StartExternalURL>
|
||||
</StartExternalURL>
|
||||
<StartCmdLineArguments>
|
||||
</StartCmdLineArguments>
|
||||
<StartWorkingDirectory>
|
||||
</StartWorkingDirectory>
|
||||
<EnableENC>False</EnableENC>
|
||||
<AlwaysStartWebServerOnDebug>False</AlwaysStartWebServerOnDebug>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
9
GameLogServer/GameLogServer/__init__.py
Normal file
9
GameLogServer/GameLogServer/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
"""
|
||||
The flask application package.
|
||||
"""
|
||||
|
||||
from flask import Flask
|
||||
from flask_restful import Api
|
||||
|
||||
app = Flask(__name__)
|
||||
api = Api(app)
|
76
GameLogServer/GameLogServer/log_reader.py
Normal file
76
GameLogServer/GameLogServer/log_reader.py
Normal file
@ -0,0 +1,76 @@
|
||||
import re
|
||||
import os
|
||||
import time
|
||||
|
||||
class LogReader(object):
|
||||
def __init__(self):
|
||||
self.log_file_sizes = {}
|
||||
# (if the file changes more than this, ignore ) - 0.125 MB
|
||||
self.max_file_size_change = 125000
|
||||
# (if the time between checks is greater, ignore ) - 5 minutes
|
||||
self.max_file_time_change = 60
|
||||
|
||||
def read_file(self, path):
|
||||
# prevent traversing directories
|
||||
if re.search('r^.+\.\.\\.+$', path):
|
||||
return False
|
||||
# must be a valid log path and log file
|
||||
if not re.search(r'^.+[\\|\/](userraw|mods|main)[\\|\/].+.log$', path):
|
||||
return False
|
||||
# set the initialze size to the current file size
|
||||
file_size = 0
|
||||
|
||||
if path not in self.log_file_sizes:
|
||||
self.log_file_sizes[path] = {
|
||||
'length' : self.file_length(path),
|
||||
'read': time.time()
|
||||
}
|
||||
return True
|
||||
|
||||
# grab the previous values
|
||||
last_length = self.log_file_sizes[path]['length']
|
||||
last_read = self.log_file_sizes[path]['read']
|
||||
|
||||
# the file is being tracked already
|
||||
new_file_size = self.file_length(path)
|
||||
|
||||
# the log size was unable to be read (probably the wrong path)
|
||||
if new_file_size < 0:
|
||||
return False
|
||||
|
||||
now = time.time()
|
||||
|
||||
file_size_difference = new_file_size - last_length
|
||||
time_difference = now - last_read
|
||||
|
||||
# update the new size and actually read the data
|
||||
self.log_file_sizes[path] = {
|
||||
'length': new_file_size,
|
||||
'read': now
|
||||
}
|
||||
|
||||
# if it's been too long since we read and the amount changed is too great, discard it
|
||||
# todo: do we really want old events? maybe make this an "or"
|
||||
if file_size_difference > self.max_file_size_change or time_difference > self.max_file_time_change:
|
||||
return True
|
||||
|
||||
new_log_info = self.get_file_lines(path, file_size_difference)
|
||||
return new_log_info
|
||||
|
||||
def get_file_lines(self, path, length):
|
||||
try:
|
||||
file_handle = open(path, 'rb')
|
||||
file_handle.seek(-length, 2)
|
||||
file_data = file_handle.read(length)
|
||||
file_handle.close()
|
||||
return file_data.decode('utf-8')
|
||||
except:
|
||||
return False
|
||||
|
||||
def file_length(self, path):
|
||||
try:
|
||||
return os.stat(path).st_size
|
||||
except:
|
||||
return -1
|
||||
|
||||
reader = LogReader()
|
19
GameLogServer/GameLogServer/log_resource.py
Normal file
19
GameLogServer/GameLogServer/log_resource.py
Normal file
@ -0,0 +1,19 @@
|
||||
from flask_restful import Resource
|
||||
from GameLogServer.log_reader import reader
|
||||
from base64 import urlsafe_b64decode
|
||||
|
||||
class LogResource(Resource):
|
||||
def get(self, path):
|
||||
path = urlsafe_b64decode(path).decode('utf-8')
|
||||
log_info = reader.read_file(path)
|
||||
|
||||
if log_info is False:
|
||||
print('could not read log file ' + path)
|
||||
|
||||
empty_read = (log_info == False) or (log_info == True)
|
||||
|
||||
return {
|
||||
'success' : log_info is not False,
|
||||
'length': -1 if empty_read else len(log_info),
|
||||
'data': log_info
|
||||
}
|
29
GameLogServer/GameLogServer/restart_resource.py
Normal file
29
GameLogServer/GameLogServer/restart_resource.py
Normal file
@ -0,0 +1,29 @@
|
||||
from flask_restful import Resource
|
||||
from flask import request
|
||||
import requests
|
||||
import os
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
def get_pid_of_server_windows(port):
|
||||
process = subprocess.Popen('netstat -aon', shell=True, stdout=subprocess.PIPE)
|
||||
output = process.communicate()[0]
|
||||
matches = re.search(' *(UDP) +([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}):'+ str(port) + ' +[^\w]*([0-9]+)', output.decode('utf-8'))
|
||||
if matches is not None:
|
||||
return matches.group(3)
|
||||
else:
|
||||
return 0
|
||||
|
||||
class RestartResource(Resource):
|
||||
def get(self):
|
||||
try:
|
||||
response = requests.get('http://' + request.remote_addr + ':1624/api/restartapproved')
|
||||
if response.status_code == 200:
|
||||
pid = get_pid_of_server_windows(response.json()['port'])
|
||||
subprocess.check_output("Taskkill /PID %s /F" % pid)
|
||||
else:
|
||||
return {}, 400
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return {}, 500
|
||||
return {}, 200
|
11
GameLogServer/GameLogServer/server.py
Normal file
11
GameLogServer/GameLogServer/server.py
Normal file
@ -0,0 +1,11 @@
|
||||
from flask import Flask
|
||||
from flask_restful import Api
|
||||
from .log_resource import LogResource
|
||||
from .restart_resource import RestartResource
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
def init():
|
||||
api = Api(app)
|
||||
api.add_resource(LogResource, '/log/<string:path>')
|
||||
api.add_resource(RestartResource, '/restart')
|
26
GameLogServer/requirements.txt
Normal file
26
GameLogServer/requirements.txt
Normal file
@ -0,0 +1,26 @@
|
||||
aniso8601==3.0.2
|
||||
APScheduler==3.5.3
|
||||
certifi==2018.10.15
|
||||
chardet==3.0.4
|
||||
click==6.7
|
||||
Flask==1.0.2
|
||||
Flask-JWT==0.3.2
|
||||
Flask-JWT-Extended==3.8.1
|
||||
Flask-RESTful==0.3.6
|
||||
idna==2.7
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.10
|
||||
MarkupSafe==1.0
|
||||
marshmallow==3.0.0b8
|
||||
pip==9.0.3
|
||||
psutil==5.4.8
|
||||
pygal==2.4.0
|
||||
PyJWT==1.4.2
|
||||
pytz==2018.7
|
||||
requests==2.20.0
|
||||
setuptools==40.5.0
|
||||
six==1.11.0
|
||||
timeago==1.0.8
|
||||
tzlocal==1.5.1
|
||||
urllib3==1.24
|
||||
Werkzeug==0.14.1
|
15
GameLogServer/runserver.py
Normal file
15
GameLogServer/runserver.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
This script runs the GameLogServer application using a development server.
|
||||
"""
|
||||
|
||||
from os import environ
|
||||
from GameLogServer.server import app, init
|
||||
|
||||
if __name__ == '__main__':
|
||||
HOST = environ.get('SERVER_HOST', '0.0.0.0')
|
||||
try:
|
||||
PORT = int(environ.get('SERVER_PORT', '1625'))
|
||||
except ValueError:
|
||||
PORT = 5555
|
||||
init()
|
||||
app.run(HOST, PORT, debug=False)
|
@ -1,4 +1,3 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26730.16
|
||||
@ -7,8 +6,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{26E8
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8C8F3945-0AEF-4949-A1F7-B18E952E50BC}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
_commands.gsc = _commands.gsc
|
||||
_customcallbacks.gsc = _customcallbacks.gsc
|
||||
README.md = README.md
|
||||
RunPublishPre.cmd = RunPublishPre.cmd
|
||||
RunPublishRelease.cmd = RunPublishRelease.cmd
|
||||
version.txt = version.txt
|
||||
EndProjectSection
|
||||
EndProject
|
||||
@ -28,7 +30,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Login", "Plugins\Login\Logi
|
||||
EndProject
|
||||
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Master", "Master\Master.pyproj", "{F5051A32-6BD0-4128-ABBA-C202EE15FC5C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Plugins\Tests\Tests.csproj", "{B72DEBFB-9D48-4076-8FF5-1FD72A830845}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Plugins\Tests\Tests.csproj", "{B72DEBFB-9D48-4076-8FF5-1FD72A830845}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IW4ScriptCommands", "Plugins\IW4ScriptCommands\IW4ScriptCommands.csproj", "{6C706CE5-A206-4E46-8712-F8C48D526091}"
|
||||
EndProject
|
||||
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "DiscordWebhook", "DiscordWebhook\DiscordWebhook.pyproj", "{15A81D6E-7502-46CE-8530-0647A380B5F4}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlugins", "{3F9ACC27-26DB-49FA-BCD2-50C54A49C9FA}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Plugins\ScriptPlugins\SharedGUIDKick.js = Plugins\ScriptPlugins\SharedGUIDKick.js
|
||||
Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "GameLogServer", "GameLogServer\GameLogServer.pyproj", "{42EFDA12-10D3-4C40-A210-9483520116BC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -260,6 +274,66 @@ Global
|
||||
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B72DEBFB-9D48-4076-8FF5-1FD72A830845}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|x64.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|x64.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|x86.ActiveCfg = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|x86.Build.0 = Debug|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091}.Release|x86.Build.0 = Release|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|x64.ActiveCfg = Release|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Prerelease|x86.ActiveCfg = Release|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{15A81D6E-7502-46CE-8530-0647A380B5F4}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Any CPU.Build.0 = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|x64.ActiveCfg = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|x64.Build.0 = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|x86.ActiveCfg = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Prerelease|x86.Build.0 = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Release|Mixed Platforms.Build.0 = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x64.Build.0 = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{42EFDA12-10D3-4C40-A210-9483520116BC}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@ -270,6 +344,8 @@ Global
|
||||
{958FF7EC-0226-4E85-A85B-B84EC768197D} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{D9F2ED28-6FA5-40CA-9912-E7A849147AB1} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{B72DEBFB-9D48-4076-8FF5-1FD72A830845} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{6C706CE5-A206-4E46-8712-F8C48D526091} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
{3F9ACC27-26DB-49FA-BCD2-50C54A49C9FA} = {26E8B310-269E-46D4-A612-24601F16065F}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<ProjectGuid>f5051a32-6bd0-4128-abba-c202ee15fc5c</ProjectGuid>
|
||||
<ProjectHome>.</ProjectHome>
|
||||
<ProjectTypeGuids>{789894c7-04a9-4a11-a6b5-3f4435165112};{1b580a1a-fdb3-4b32-83e1-6407eb2722e6};{349c5851-65df-11da-9384-00065b846f21};{888888a0-9f3d-457c-b088-3a5042f75d52}</ProjectTypeGuids>
|
||||
<StartupFile>runserver.py</StartupFile>
|
||||
<StartupFile>master\runserver.py</StartupFile>
|
||||
<SearchPath>
|
||||
</SearchPath>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
@ -17,7 +17,14 @@
|
||||
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
|
||||
<Name>Master</Name>
|
||||
<RootNamespace>Master</RootNamespace>
|
||||
<InterpreterId>MSBuild|dev_env|$(MSBuildProjectFullPath)</InterpreterId>
|
||||
<InterpreterId>MSBuild|env_master|$(MSBuildProjectFullPath)</InterpreterId>
|
||||
<IsWindowsApplication>False</IsWindowsApplication>
|
||||
<PythonRunWebServerCommand>
|
||||
</PythonRunWebServerCommand>
|
||||
<PythonDebugWebServerCommand>
|
||||
</PythonDebugWebServerCommand>
|
||||
<PythonRunWebServerCommandType>script</PythonRunWebServerCommandType>
|
||||
<PythonDebugWebServerCommandType>script</PythonDebugWebServerCommandType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@ -51,27 +58,36 @@
|
||||
<Compile Include="master\models\__init__.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Master\resources\authenticate.py">
|
||||
<Compile Include="master\resources\authenticate.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\resources\history_graph.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Master\resources\instance.py">
|
||||
<Compile Include="master\resources\instance.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Master\resources\null.py">
|
||||
<Compile Include="master\resources\localization.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Master\resources\version.py">
|
||||
<Compile Include="master\resources\null.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Master\resources\__init__.py">
|
||||
<Compile Include="Master\resources\server.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\resources\version.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\resources\__init__.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\routes.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\runserver.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="master\schema\instanceschema.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
@ -81,33 +97,32 @@
|
||||
<Compile Include="master\schema\__init__.py">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="runserver.py" />
|
||||
<Compile Include="master\__init__.py" />
|
||||
<Compile Include="master\views.py" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="C:\Projects\IW4M-Admin\Master\master\" />
|
||||
<Folder Include="master\" />
|
||||
<Folder Include="master\context\" />
|
||||
<Folder Include="master\models\" />
|
||||
<Folder Include="master\config\" />
|
||||
<Folder Include="master\schema\" />
|
||||
<Folder Include="Master\resources\" />
|
||||
<Folder Include="Master\static\" />
|
||||
<Folder Include="Master\templates\" />
|
||||
<Folder Include="master\static\" />
|
||||
<Folder Include="master\templates\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="FolderProfile.pubxml" />
|
||||
<Content Include="master\config\master.json" />
|
||||
<Content Include="master\templates\serverlist.html" />
|
||||
<None Include="Release.pubxml" />
|
||||
<Content Include="requirements.txt" />
|
||||
<Content Include="Master\templates\index.html" />
|
||||
<Content Include="Master\templates\layout.html" />
|
||||
<Content Include="master\templates\index.html" />
|
||||
<Content Include="master\templates\layout.html" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Interpreter Include="dev_env\">
|
||||
<Id>dev_env</Id>
|
||||
<Interpreter Include="env_master\">
|
||||
<Id>env_master</Id>
|
||||
<Version>3.6</Version>
|
||||
<Description>dev_env (Python 3.6 (64-bit))</Description>
|
||||
<Description>env_master (Python 3.6 (64-bit))</Description>
|
||||
<InterpreterPath>Scripts\python.exe</InterpreterPath>
|
||||
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
|
||||
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
|
||||
|
@ -6,7 +6,6 @@ from flask import Flask
|
||||
from flask_restful import Resource, Api
|
||||
from flask_jwt_extended import JWTManager
|
||||
from master.context.base import Base
|
||||
import json
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['JWT_SECRET_KEY'] = 'my key!'
|
||||
@ -14,7 +13,7 @@ app.config['PROPAGATE_EXCEPTIONS'] = True
|
||||
jwt = JWTManager(app)
|
||||
api = Api(app)
|
||||
ctx = Base()
|
||||
config = json.load(open('./master/config/master.json'))
|
||||
#config = json.load(open('./master/config/master.json'))
|
||||
|
||||
import master.routes
|
||||
import master.views
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"current-version-stable": 2.0,
|
||||
"current-version-prerelease": 2.0
|
||||
"current-version-stable": 2.1,
|
||||
"current-version-prerelease": 2.1
|
||||
}
|
@ -15,9 +15,9 @@ class Base():
|
||||
self.scheduler.start()
|
||||
self.scheduler.add_job(
|
||||
func=self._remove_staleinstances,
|
||||
trigger=IntervalTrigger(seconds=120),
|
||||
trigger=IntervalTrigger(seconds=60),
|
||||
id='stale_instance_remover',
|
||||
name='Remove stale instances if no heartbeat in 120 seconds',
|
||||
name='Remove stale instances if no heartbeat in 60 seconds',
|
||||
replace_existing=True
|
||||
)
|
||||
self.scheduler.add_job(
|
||||
@ -37,10 +37,11 @@ class Base():
|
||||
client_num += server.clientnum
|
||||
self.history.add_client_history(client_num)
|
||||
self.history.add_instance_history(len(self.instance_list))
|
||||
self.history.add_server_history(len(servers))
|
||||
|
||||
def _remove_staleinstances(self):
|
||||
for key, value in list(self.instance_list.items()):
|
||||
if int(time.time()) - value.last_heartbeat > 120:
|
||||
if int(time.time()) - value.last_heartbeat > 60:
|
||||
print('[_remove_staleinstances] removing stale instance {id}'.format(id=key))
|
||||
del self.instance_list[key]
|
||||
del self.token_list[key]
|
||||
|
@ -5,17 +5,26 @@ class History():
|
||||
def __init__(self):
|
||||
self.client_history = list()
|
||||
self.instance_history = list()
|
||||
self.server_history = list()
|
||||
|
||||
def add_client_history(self, client_num):
|
||||
if len(self.client_history) > 1440:
|
||||
if len(self.client_history) > 2880:
|
||||
self.client_history = self.client_history[1:]
|
||||
self.client_history.append({
|
||||
'count' : client_num,
|
||||
'time' : int(time.time())
|
||||
})
|
||||
|
||||
def add_server_history(self, server_num):
|
||||
if len(self.server_history) > 2880:
|
||||
self.server_history = self.server_history[1:]
|
||||
self.server_history.append({
|
||||
'count' : server_num,
|
||||
'time' : int(time.time())
|
||||
})
|
||||
|
||||
def add_instance_history(self, instance_num):
|
||||
if len(self.instance_history) > 1440:
|
||||
if len(self.instance_history) > 2880:
|
||||
self.instance_history = self.instance_history[1:]
|
||||
self.instance_history.append({
|
||||
'count' : instance_num,
|
||||
|
@ -1,14 +1,16 @@
|
||||
|
||||
class ServerModel(object):
|
||||
def __init__(self, id, port, game, hostname, clientnum, maxclientnum, map, gametype):
|
||||
def __init__(self, id, port, game, hostname, clientnum, maxclientnum, map, gametype, ip, version):
|
||||
self.id = id
|
||||
self.port = port
|
||||
self.version = version
|
||||
self.game = game
|
||||
self.hostname = hostname
|
||||
self.clientnum = clientnum
|
||||
self.maxclientnum = maxclientnum
|
||||
self.map = map
|
||||
self.gametype = gametype
|
||||
self.ip = ip
|
||||
|
||||
def __repr__(self):
|
||||
return '<ServerModel(id={id})>'.format(id=self.id)
|
||||
|
@ -8,10 +8,11 @@ import datetime
|
||||
class Authenticate(Resource):
|
||||
def post(self):
|
||||
instance_id = request.json['id']
|
||||
if ctx.get_token(instance_id) is not False:
|
||||
return { 'message' : 'that id already has a token'}, 401
|
||||
else:
|
||||
expires = datetime.timedelta(days=1)
|
||||
token = create_access_token(instance_id, expires_delta=expires)
|
||||
ctx.add_token(instance_id, token)
|
||||
return { 'access_token' : token }, 200
|
||||
#todo: see why this is failing
|
||||
#if ctx.get_token(instance_id) is not False:
|
||||
# return { 'message' : 'that id already has a token'}, 401
|
||||
#else:
|
||||
expires = datetime.timedelta(days=30)
|
||||
token = create_access_token(instance_id, expires_delta=expires)
|
||||
ctx.add_token(instance_id, token)
|
||||
return { 'access_token' : token }, 200
|
||||
|
@ -11,22 +11,18 @@ class HistoryGraph(Resource):
|
||||
custom_style = Style(
|
||||
background='transparent',
|
||||
plot_background='transparent',
|
||||
foreground='rgba(109, 118, 126, 0.3)',
|
||||
foreground_strong='rgba(109, 118, 126, 0.3)',
|
||||
foreground_subtle='rgba(109, 118, 126, 0.3)',
|
||||
foreground='#6c757d',
|
||||
foreground_strong='#6c757d',
|
||||
foreground_subtle='#6c757d',
|
||||
opacity='0.1',
|
||||
opacity_hover='0.2',
|
||||
transition='100ms ease-in',
|
||||
colors=('#007acc', '#749363')
|
||||
transition='0ms',
|
||||
colors=('#749363','#007acc'),
|
||||
)
|
||||
|
||||
graph = pygal.StackedLine(
|
||||
interpolate='cubic',
|
||||
interpolation_precision=3,
|
||||
#x_labels_major_every=100,
|
||||
#x_labels_major_count=500,
|
||||
graph = pygal.Line(
|
||||
stroke_style={'width': 0.4},
|
||||
show_dots=False,
|
||||
#show_dots=False,
|
||||
show_legend=False,
|
||||
fill=True,
|
||||
style=custom_style,
|
||||
@ -37,10 +33,17 @@ class HistoryGraph(Resource):
|
||||
if len(instance_count) > 0:
|
||||
graph.x_labels = [ timeago.format(instance_count[0])]
|
||||
|
||||
graph.add('Instance Count', [history['count'] for history in ctx.history.instance_history][-history_count:])
|
||||
graph.add('Client Count', [history['count'] for history in ctx.history.client_history][-history_count:])
|
||||
return { 'message' : graph.render(),
|
||||
'data_points' : len(instance_count)
|
||||
instance_counts = [history['count'] for history in ctx.history.instance_history][-history_count:]
|
||||
client_counts = [history['count'] for history in ctx.history.client_history][-history_count:]
|
||||
server_counts = [history['count'] for history in ctx.history.server_history][-history_count:]
|
||||
|
||||
graph.add('Client Count', client_counts)
|
||||
graph.add('Instance Count', instance_counts)
|
||||
return { 'message' : graph.render().replace("<title>Pygal</title>", ""),
|
||||
'data_points' : len(instance_count),
|
||||
'instance_count' : 0 if len(instance_counts) is 0 else instance_counts[-1],
|
||||
'client_count' : 0 if len(client_counts) is 0 else client_counts[-1],
|
||||
'server_count' : 0 if len(server_counts) is 0 else server_counts[-1]
|
||||
}, 200
|
||||
except Exception as e:
|
||||
return { 'message' : str(e) }, 500
|
||||
|
@ -4,6 +4,7 @@ from flask_jwt_extended import jwt_required
|
||||
from marshmallow import ValidationError
|
||||
from master.schema.instanceschema import InstanceSchema
|
||||
from master import ctx
|
||||
import json
|
||||
|
||||
class Instance(Resource):
|
||||
def get(self, id=None):
|
||||
@ -21,6 +22,11 @@ class Instance(Resource):
|
||||
@jwt_required
|
||||
def put(self, id):
|
||||
try:
|
||||
for server in request.json['servers']:
|
||||
if 'ip' not in server or server['ip'] == 'localhost':
|
||||
server['ip'] = request.remote_addr
|
||||
if 'version' not in server:
|
||||
server['version'] = 'Unknown'
|
||||
instance = InstanceSchema().load(request.json)
|
||||
except ValidationError as err:
|
||||
return {'message' : err.messages }, 400
|
||||
@ -30,6 +36,11 @@ class Instance(Resource):
|
||||
@jwt_required
|
||||
def post(self):
|
||||
try:
|
||||
for server in request.json['servers']:
|
||||
if 'ip' not in server or server['ip'] == 'localhost':
|
||||
server['ip'] = request.remote_addr
|
||||
if 'version' not in server:
|
||||
server['version'] = 'Unknown'
|
||||
instance = InstanceSchema().load(request.json)
|
||||
except ValidationError as err:
|
||||
return {'message' : err.messages }, 400
|
||||
|
59
Master/master/resources/localization.py
Normal file
59
Master/master/resources/localization.py
Normal file
@ -0,0 +1,59 @@
|
||||
from flask_restful import Resource
|
||||
from flask import request, jsonify
|
||||
from flask_jwt_extended import create_access_token
|
||||
from master import app, ctx
|
||||
import datetime
|
||||
import urllib.request
|
||||
import csv
|
||||
from io import StringIO
|
||||
|
||||
class Localization(Resource):
|
||||
def list(self):
|
||||
response = urllib.request.urlopen('https://docs.google.com/spreadsheets/d/e/2PACX-1vRQjCqPvd0Xqcn86WqpFqp_lx4KKpel9O4OV13NycmV8rmqycorgJQm-8qXMfw37QJHun3pqVZFUKG-/pub?gid=0&single=true&output=csv')
|
||||
data = response.read().decode('utf-8')
|
||||
|
||||
localization = []
|
||||
csv_data = csv.DictReader(StringIO(data))
|
||||
|
||||
for language in csv_data.fieldnames[1:]:
|
||||
localization.append({
|
||||
'LocalizationName' : language,
|
||||
'LocalizationIndex' : {
|
||||
'Set' : {}
|
||||
}
|
||||
})
|
||||
|
||||
for row in csv_data:
|
||||
localization_string = row['STRING']
|
||||
count = 0
|
||||
for language in csv_data.fieldnames[1:]:
|
||||
localization[count]['LocalizationIndex']['Set'][localization_string] = row[language]
|
||||
count += 1
|
||||
|
||||
return localization, 200
|
||||
|
||||
def get(self, language_tag=None):
|
||||
response = urllib.request.urlopen('https://docs.google.com/spreadsheets/d/e/2PACX-1vRQjCqPvd0Xqcn86WqpFqp_lx4KKpel9O4OV13NycmV8rmqycorgJQm-8qXMfw37QJHun3pqVZFUKG-/pub?gid=0&single=true&output=csv')
|
||||
data = response.read().decode('utf-8')
|
||||
|
||||
csv_data = csv.DictReader(StringIO(data))
|
||||
|
||||
|
||||
if language_tag != None:
|
||||
valid_language_tag = next((l for l in csv_data.fieldnames[1:] if l == language_tag), None)
|
||||
if valid_language_tag is None:
|
||||
valid_language_tag = next((l for l in csv_data.fieldnames[1:] if l.startswith(language_tag[:2])), None)
|
||||
if valid_language_tag is None:
|
||||
valid_language_tag = 'en-US'
|
||||
localization = {
|
||||
'LocalizationName' : valid_language_tag,
|
||||
'LocalizationIndex' : {
|
||||
'Set' : {}
|
||||
}
|
||||
}
|
||||
for row in csv_data:
|
||||
localization_string = row['STRING']
|
||||
localization['LocalizationIndex']['Set'][localization_string] = row[valid_language_tag]
|
||||
return localization, 200
|
||||
else:
|
||||
return self.list()[0][0], 200
|
6
Master/master/resources/server.py
Normal file
6
Master/master/resources/server.py
Normal file
@ -0,0 +1,6 @@
|
||||
from flask_restful import Resource
|
||||
|
||||
class Server(Resource):
|
||||
"""description of class"""
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
from flask_restful import Resource
|
||||
from master import config
|
||||
import json
|
||||
|
||||
class Version(Resource):
|
||||
def get(self):
|
||||
config = json.load(open('./master/config/master.json'))
|
||||
return {
|
||||
'current-version-stable' : config['current-version-stable'],
|
||||
'current-version-prerelease' : config['current-version-prerelease']
|
||||
|
@ -5,9 +5,13 @@ from master.resources.instance import Instance
|
||||
from master.resources.authenticate import Authenticate
|
||||
from master.resources.version import Version
|
||||
from master.resources.history_graph import HistoryGraph
|
||||
from master.resources.localization import Localization
|
||||
from master.resources.server import Server
|
||||
|
||||
api.add_resource(Null, '/null')
|
||||
api.add_resource(Instance, '/instance/', '/instance/<string:id>')
|
||||
api.add_resource(Version, '/version')
|
||||
api.add_resource(Authenticate, '/authenticate')
|
||||
api.add_resource(HistoryGraph, '/history/', '/history/<int:history_count>')
|
||||
api.add_resource(HistoryGraph, '/history/', '/history/<int:history_count>')
|
||||
api.add_resource(Localization, '/localization/', '/localization/<string:language_tag>')
|
||||
api.add_resource(Server, '/server')
|
@ -6,4 +6,4 @@ from os import environ
|
||||
from master import app
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=80, debug=True)
|
||||
app.run(host='0.0.0.0', port=80, debug=True)
|
@ -4,19 +4,26 @@ from master.models.servermodel import ServerModel
|
||||
class ServerSchema(Schema):
|
||||
id = fields.Int(
|
||||
required=True,
|
||||
validate=validate.Range(1, 2147483647, 'invalid id')
|
||||
validate=validate.Range(1, 25525525525565535, 'invalid id')
|
||||
)
|
||||
ip = fields.Str(
|
||||
required=True
|
||||
)
|
||||
port = fields.Int(
|
||||
required=True,
|
||||
validate=validate.Range(1, 665535, 'invalid port')
|
||||
validate=validate.Range(1, 65535, 'invalid port')
|
||||
)
|
||||
version = fields.String(
|
||||
required=False,
|
||||
validate=validate.Length(0, 128, 'invalid server version')
|
||||
)
|
||||
game = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(1, 8, 'invalid game name')
|
||||
validate=validate.Length(1, 5, 'invalid game name')
|
||||
)
|
||||
hostname = fields.String(
|
||||
required=True,
|
||||
validate=validate.Length(1, 48, 'invalid hostname')
|
||||
validate=validate.Length(1, 64, 'invalid hostname')
|
||||
)
|
||||
clientnum = fields.Int(
|
||||
required=True,
|
||||
|
@ -5,43 +5,60 @@
|
||||
<div class="col-12">
|
||||
<figure>
|
||||
<div id="history_graph">{{history_graph|safe}}</div>
|
||||
<figcaption class="float-right pr-3 mr-4">
|
||||
<figcaption class="float-right">
|
||||
<span id="history_graph_zoom_out" class="h4 oi oi-zoom-out text-muted" style="cursor:pointer;"></span>
|
||||
<span id="history_graph_zoom_in" class="h4 oi oi-zoom-in text-muted" style="cursor:pointer;"></span>
|
||||
</figcaption>
|
||||
<figcaption class="float-left">
|
||||
<span class="h4 text-muted">{{instance_count}} instances</span>
|
||||
<span class="h4 text-muted">— {{client_count}} clients</span>
|
||||
<span class="h4 text-muted">— {{server_count}} servers</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-12">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="text/javascript" src="http://kozea.github.com/pygal.js/latest/pygal-tooltips.min.js"></script>
|
||||
<script>
|
||||
let dataPoints = {{data_points}};
|
||||
let zoomLevel = Math.ceil(dataPoints / 2);
|
||||
//console.log(dataPoints);
|
||||
<script type="text/javascript" src="http://kozea.github.com/pygal.js/latest/pygal-tooltips.min.js"></script>
|
||||
<script>
|
||||
let dataPoints = {{data_points}};
|
||||
let maxPoints = 2880;
|
||||
maxPoints = Math.min(maxPoints, dataPoints);
|
||||
let zoomLevel = Math.floor(maxPoints);
|
||||
let performingZoom = false;
|
||||
|
||||
function updateHistoryGraph() {
|
||||
$.get('/history/' + zoomLevel)
|
||||
.done(function (content) {
|
||||
$('#history_graph').html(content.message);
|
||||
dataPoints = content.data_points
|
||||
});
|
||||
function updateHistoryGraph() {
|
||||
perfomingZoom = true;
|
||||
$.get('/history/' + zoomLevel)
|
||||
.done(function (content) {
|
||||
$('#history_graph').html(content.message);
|
||||
//maxPoints = Math.min(maxPoints, dataPoints);
|
||||
perfomingZoom = false;
|
||||
});
|
||||
}
|
||||
|
||||
//setInterval(updateHistoryGraph, 30000);
|
||||
|
||||
$('#history_graph_zoom_out').click(function () {
|
||||
if (performingZoom === true) {
|
||||
return false;
|
||||
}
|
||||
setInterval(updateHistoryGraph, 30000);
|
||||
zoomLevel = Math.floor(zoomLevel * 2) <= maxPoints ? Math.floor(zoomLevel * 2) : maxPoints;
|
||||
updateHistoryGraph();
|
||||
});
|
||||
|
||||
$('#history_graph_zoom_out').click(function () {
|
||||
// console.log(zoomLevel);
|
||||
zoomLevel = zoomLevel * 2 <= 1440 ? Math.ceil(zoomLevel * 2) : dataPoints;
|
||||
updateHistoryGraph();
|
||||
});
|
||||
$('#history_graph_zoom_in').click(function () {
|
||||
if (performingZoom === true) {
|
||||
return false;
|
||||
}
|
||||
zoomLevel = zoomLevel / 2 > 2 ? Math.ceil(zoomLevel / 2) : 2;
|
||||
updateHistoryGraph();
|
||||
});
|
||||
|
||||
$('#history_graph_zoom_in').click(function () {
|
||||
// console.log(zoomLevel);
|
||||
zoomLevel = zoomLevel / 2 > 2 ? Math.ceil(zoomLevel / 2) : 2;
|
||||
updateHistoryGraph();
|
||||
});
|
||||
|
||||
</script>
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -14,6 +14,18 @@
|
||||
.oi:hover {
|
||||
color: #fff !important;
|
||||
}
|
||||
.dot {
|
||||
opacity: 0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.dot:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tooltip-box {
|
||||
fill: #343a40 !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@ -27,6 +39,7 @@
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
|
||||
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
113
Master/master/templates/serverlist.html
Normal file
113
Master/master/templates/serverlist.html
Normal file
@ -0,0 +1,113 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
<!-- todo: move this! -->
|
||||
<style>
|
||||
.server-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-content, .nav-item {
|
||||
background-color: #212529;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.modal-header, .modal-footer {
|
||||
border-color: #32383e !important;
|
||||
}
|
||||
|
||||
.modal-dark button.close, a.nav-link {
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="modal modal-dark" id="serverModalCenter" tabindex="-1" role="dialog" aria-labelledby="serverModalCenterTitle" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="serverModalTitle">Modal title</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="h5" id="server_socket"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="connect_button" type="button" class="btn btn-dark">Connect</button>
|
||||
<button type="button" class="btn btn-dark" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav>
|
||||
<div class="nav nav-tabs" id="server_game_tabs" role="tablist">
|
||||
{% for game in games %}
|
||||
<a class="nav-item nav-link {{'active' if loop.first else ''}}" id="{{game}}_servers_tab" data-toggle="tab" href="#{{game}}_servers" role="tab" aria-controls="{{game}}_servers" aria-selected="{{'true' if loop.first else 'false' }}">{{game}}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tab-content" id="server_game_tabs_content">
|
||||
{% for game, servers in games.items() %}
|
||||
|
||||
<div class="tab-pane {{'show active' if loop.first else ''}}" id="{{game}}_servers" role="tabpanel" aria-labelledby="{{game}}_servers_tab">
|
||||
|
||||
<table class="table table-dark table-striped table-hover table-responsive-lg">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Server Name</th>
|
||||
<th>Map Name</th>
|
||||
<th>Players</th>
|
||||
<th>Mode</th>
|
||||
<th class="text-center">Connect</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{% for server in servers %}
|
||||
|
||||
<tr class="server-row" data-toggle="modal" data-target="#serverModalCenter"
|
||||
data-ip="{{server.ip}}" data-port="{{server.port}}">
|
||||
<td data-hostname="{{server.hostname}}" class="server-hostname">{{server.hostname}}</td>
|
||||
<td data-map="{{server.map}} " class="server-map">{{server.map}}</td>
|
||||
<td data-clientnum="{{server.clientnum}}" data-maxclientnum="{{server.maxclientnum}}"
|
||||
class="server-clientnum">
|
||||
{{server.clientnum}}/{{server.maxclientnum}}
|
||||
</td>
|
||||
<td data-gametype="{{server.gametype}}" class="server-gametype">{{server.gametype}}</td>
|
||||
<td class="text-center"><span class="oi oi-play-circle"></span></td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="w-100 small text-right text-muted">
|
||||
<span>Developed by RaidMax</span><br />
|
||||
<span>PRERELEASE</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(document).ready(() => {
|
||||
$('.server-row').off('click');
|
||||
$('.server-row').on('click', function (e) {
|
||||
$('#serverModalTitle').text($(this).find('.server-hostname').text());
|
||||
$('#server_socket').text(`/connect ${$(this).data('ip')}:${$(this).data('port')}`);
|
||||
});
|
||||
|
||||
$('#connect_button').off('click');
|
||||
$('#connect_button').on('click', (e) => alert('soon...'));
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@ -4,15 +4,32 @@ Routes and views for the flask application.
|
||||
|
||||
from datetime import datetime
|
||||
from flask import render_template
|
||||
from master import app
|
||||
from master import app, ctx
|
||||
from master.resources.history_graph import HistoryGraph
|
||||
from collections import defaultdict
|
||||
|
||||
@app.route('/')
|
||||
def home():
|
||||
_history_graph = HistoryGraph().get(500)
|
||||
_history_graph = HistoryGraph().get(2880)
|
||||
return render_template(
|
||||
'index.html',
|
||||
title='API Overview',
|
||||
history_graph = _history_graph[0]['message'],
|
||||
data_points = _history_graph[0]['data_points']
|
||||
data_points = _history_graph[0]['data_points'],
|
||||
instance_count = _history_graph[0]['instance_count'],
|
||||
client_count = _history_graph[0]['client_count'],
|
||||
server_count = _history_graph[0]['server_count']
|
||||
)
|
||||
|
||||
@app.route('/servers')
|
||||
def servers():
|
||||
servers = defaultdict(list)
|
||||
if len(ctx.instance_list.values()) > 0:
|
||||
ungrouped_servers = [server for instance in ctx.instance_list.values() for server in instance.servers]
|
||||
for server in ungrouped_servers:
|
||||
servers[server.game].append(server)
|
||||
return render_template(
|
||||
'serverlist.html',
|
||||
title = 'Server List',
|
||||
games = servers
|
||||
)
|
||||
|
@ -1,16 +1,26 @@
|
||||
aniso8601==3.0.0
|
||||
aniso8601==3.0.2
|
||||
APScheduler==3.5.3
|
||||
certifi==2018.10.15
|
||||
chardet==3.0.4
|
||||
click==6.7
|
||||
Flask==0.12.2
|
||||
Flask==1.0.2
|
||||
Flask-JWT==0.3.2
|
||||
Flask-JWT-Extended==3.8.1
|
||||
Flask-RESTful==0.3.6
|
||||
idna==2.7
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.10
|
||||
MarkupSafe==1.0
|
||||
marshmallow==3.0.0b8
|
||||
pip==9.0.1
|
||||
pip==9.0.3
|
||||
psutil==5.4.8
|
||||
pygal==2.4.0
|
||||
PyJWT==1.4.2
|
||||
pytz==2018.4
|
||||
setuptools==39.0.1
|
||||
pytz==2018.7
|
||||
requests==2.20.0
|
||||
setuptools==40.5.0
|
||||
six==1.11.0
|
||||
timeago==1.0.8
|
||||
tzlocal==1.5.1
|
||||
urllib3==1.24
|
||||
Werkzeug==0.14.1
|
||||
|
14
Plugins/IW4ScriptCommands/CommandInfo.cs
Normal file
14
Plugins/IW4ScriptCommands/CommandInfo.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace IW4ScriptCommands
|
||||
{
|
||||
class CommandInfo
|
||||
{
|
||||
public string Command { get; set; }
|
||||
public int ClientNumber { get; set; }
|
||||
public List<string> CommandArguments { get; set; } = new List<string>();
|
||||
public override string ToString() => $"{Command};{ClientNumber},{string.Join(',', CommandArguments)}";
|
||||
}
|
||||
}
|
200
Plugins/IW4ScriptCommands/Commands/Balance.cs
Normal file
200
Plugins/IW4ScriptCommands/Commands/Balance.cs
Normal file
@ -0,0 +1,200 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4ScriptCommands.Commands
|
||||
{
|
||||
class Balance
|
||||
{
|
||||
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 static string GetTeamAssignments(EFClient client, bool isDisconnect, Server server, string teamsString = "")
|
||||
{
|
||||
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 = server.GetClientsAsList().FirstOrDefault(p => p.ClientNumber == Int32.Parse(c[0]))?.ClientNumber ?? -1,
|
||||
Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(server.Clients.FirstOrDefault(p => p.ClientNumber == Int32.Parse(c[0])).ClientId, server.EndPoint)
|
||||
})
|
||||
.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(server.MaxClients / 2.0)
|
||||
|| scriptClientTeams.Count(ct => ct.CurrentTeam == IW4MAdmin.Plugins.Stats.IW4Info.Team.Allies) >= Math.Floor(server.MaxClients / 2.0))
|
||||
{
|
||||
// E.Origin?.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL"]);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
List<string> teamAssignments = new List<string>();
|
||||
|
||||
var _c = server.GetClientsAsList();
|
||||
if (isDisconnect && client != null)
|
||||
{
|
||||
_c = _c.Where(c => c.ClientNumber != client.ClientNumber).ToList();
|
||||
}
|
||||
|
||||
var activeClients = _c.Select(c => new TeamAssignment()
|
||||
{
|
||||
Num = c.ClientNumber,
|
||||
Stats = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.EndPoint),
|
||||
CurrentTeam = IW4MAdmin.Plugins.Stats.Plugin.Manager.GetClientStats(c.ClientId, server.EndPoint).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 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;
|
||||
|
||||
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
|
||||
{
|
||||
alliesTeam.Add(activeClients.Last());
|
||||
activeClients.RemoveAt(activeClients.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alliesTeam = alliesTeam.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;
|
||||
|
||||
if (teamSizeDifference > 0)
|
||||
{
|
||||
if (performanceDisparity > 0)
|
||||
{
|
||||
axisTeam.Add(alliesTeam.First());
|
||||
alliesTeam.RemoveAt(0);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
axisTeam.Add(alliesTeam.Last());
|
||||
alliesTeam.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
//{
|
||||
// //E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BALANCE_FAIL_BALANCED"]);
|
||||
// return string.Empty;
|
||||
//}
|
||||
|
||||
//if (E.Origin?.Level > Player.Permission.Administrator)
|
||||
//{
|
||||
// E.Origin.Tell($"Allies Elo: {(alliesTeam.Count > 0 ? alliesTeam.Average(t => t.Stats.Performance) : 0)}");
|
||||
// E.Origin.Tell($"Axis Elo: {(axisTeam.Count > 0 ? axisTeam.Average(t => t.Stats.Performance) : 0)}");
|
||||
//}
|
||||
|
||||
//E.Origin.Tell("Balance command sent");
|
||||
string args = string.Join(",", teamAssignments);
|
||||
return args;
|
||||
}
|
||||
}
|
||||
}
|
54
Plugins/IW4ScriptCommands/GscApiController.cs
Normal file
54
Plugins/IW4ScriptCommands/GscApiController.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using IW4ScriptCommands.Commands;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebfrontCore.Controllers.API
|
||||
{
|
||||
[Route("api/gsc/[action]")]
|
||||
public class GscApiController : ApiController
|
||||
{
|
||||
[HttpGet("{networkId}")]
|
||||
public IActionResult ClientInfo(string networkId)
|
||||
{
|
||||
var clientInfo = Manager.GetActiveClients()
|
||||
.FirstOrDefault(c => c.NetworkId == networkId.ConvertLong());
|
||||
|
||||
if (clientInfo != null)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"admin={clientInfo.IsPrivileged()}");
|
||||
sb.AppendLine($"level={(int)clientInfo.Level}");
|
||||
sb.AppendLine($"levelstring={clientInfo.Level.ToLocalizedLevelName()}");
|
||||
sb.AppendLine($"connections={clientInfo.Connections}");
|
||||
sb.AppendLine($"authenticated={clientInfo.GetAdditionalProperty<bool>("IsLoggedIn") == true}");
|
||||
|
||||
return Content(sb.ToString());
|
||||
}
|
||||
|
||||
return Content("");
|
||||
}
|
||||
|
||||
[HttpGet("{networkId}")]
|
||||
public IActionResult GetTeamAssignments(string networkId, int serverId, string teams = "", bool isDisconnect = false)
|
||||
{
|
||||
return Unauthorized();
|
||||
|
||||
var client = Manager.GetActiveClients()
|
||||
.FirstOrDefault(c => c.NetworkId == networkId.ConvertLong());
|
||||
|
||||
var server = Manager.GetServers().First(c => c.EndPoint == serverId);
|
||||
|
||||
teams = teams ?? string.Empty;
|
||||
|
||||
string assignments = Balance.GetTeamAssignments(client, isDisconnect, server, teams);
|
||||
|
||||
return Content(assignments);
|
||||
}
|
||||
}
|
||||
}
|
25
Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj
Normal file
25
Plugins/IW4ScriptCommands/IW4ScriptCommands.csproj
Normal file
@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="copy "$(TargetPath)" "$(SolutionDir)BUILD\Plugins"" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||
<ProjectReference Include="..\Stats\Stats.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
49
Plugins/IW4ScriptCommands/Plugin.cs
Normal file
49
Plugins/IW4ScriptCommands/Plugin.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4ScriptCommands
|
||||
{
|
||||
class Plugin : IPlugin
|
||||
{
|
||||
public string Name => "IW4 Script Commands";
|
||||
|
||||
public float Version => 1.0f;
|
||||
|
||||
public string Author => "RaidMax";
|
||||
|
||||
public Task OnEventAsync(GameEvent E, Server S)
|
||||
{
|
||||
if (E.Type == GameEvent.EventType.Start)
|
||||
{
|
||||
return S.SetDvarAsync("sv_iw4madmin_serverid", S.EndPoint);
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Warn)
|
||||
{
|
||||
return S.SetDvarAsync("sv_iw4madmin_command", new CommandInfo()
|
||||
{
|
||||
ClientNumber = E.Target.ClientNumber,
|
||||
Command = "alert",
|
||||
CommandArguments = new List<string>()
|
||||
{
|
||||
"Warning",
|
||||
"ui_mp_nukebomb_timer",
|
||||
E.Data
|
||||
}
|
||||
}.ToString());
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnLoadAsync(IManager manager) => Task.CompletedTask;
|
||||
|
||||
public Task OnTickAsync(Server S) => Task.CompletedTask;
|
||||
|
||||
public Task OnUnloadAsync() => Task.CompletedTask;
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -8,14 +9,15 @@ namespace IW4MAdmin.Plugins.Login.Commands
|
||||
{
|
||||
public class CLogin : Command
|
||||
{
|
||||
public CLogin() : base("login", "login using password", "l", Player.Permission.Trusted, false, new CommandArgument[]
|
||||
public CLogin() : base("login", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_DESC"], "li", EFClient.Permission.Trusted, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
Name = "password",
|
||||
Name = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ARGS_PASSWORD"],
|
||||
Required = true
|
||||
}
|
||||
}){ }
|
||||
})
|
||||
{ }
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
@ -25,12 +27,12 @@ namespace IW4MAdmin.Plugins.Login.Commands
|
||||
if (hashedPassword[0] == client.Password)
|
||||
{
|
||||
Plugin.AuthorizedClients[E.Origin.ClientId] = true;
|
||||
await E.Origin.Tell("You are now logged in");
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_SUCCESS"]);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
await E.Origin.Tell("Your password is incorrect");
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_COMMANDS_LOGIN_FAIL"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.Login</PackageId>
|
||||
@ -20,6 +21,10 @@
|
||||
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="copy "$(TargetPath)" "$(SolutionDir)BUILD\Plugins"" />
|
||||
</Target>
|
||||
|
@ -3,8 +3,10 @@ using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Exceptions;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Login
|
||||
{
|
||||
@ -27,6 +29,7 @@ namespace IW4MAdmin.Plugins.Login
|
||||
if (E.Type == GameEvent.EventType.Connect)
|
||||
{
|
||||
AuthorizedClients.TryAdd(E.Origin.ClientId, false);
|
||||
E.Origin.SetAdditionalProperty("IsLoggedIn", false);
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Disconnect)
|
||||
@ -36,18 +39,28 @@ namespace IW4MAdmin.Plugins.Login
|
||||
|
||||
if (E.Type == GameEvent.EventType.Command)
|
||||
{
|
||||
if (E.Origin.Level < SharedLibraryCore.Objects.Player.Permission.Moderator)
|
||||
if (E.Origin.Level < EFClient.Permission.Moderator ||
|
||||
E.Origin.Level == EFClient.Permission.Console)
|
||||
return Task.CompletedTask;
|
||||
|
||||
E.Owner.Manager.GetPrivilegedClients().TryGetValue(E.Origin.ClientId, out EFClient client);
|
||||
|
||||
if (((Command)E.Extra).Name == new SharedLibraryCore.Commands.CSetPassword().Name &&
|
||||
E.Owner.Manager.GetPrivilegedClients()[E.Origin.ClientId].Password == null)
|
||||
client?.Password == null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (((Command)E.Extra).Name == new Commands.CLogin().Name)
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (!AuthorizedClients[E.Origin.ClientId])
|
||||
throw new AuthorizationException("not logged in");
|
||||
{
|
||||
throw new AuthorizationException(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_LOGIN_AUTH"]);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
E.Origin.SetAdditionalProperty("IsLoggedIn", true);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
@ -67,12 +80,8 @@ namespace IW4MAdmin.Plugins.Login
|
||||
Config = cfg.Configuration();
|
||||
}
|
||||
|
||||
public Task OnTickAsync(Server S) => Utilities.CompletedTask;
|
||||
public Task OnTickAsync(Server S) => Task.CompletedTask;
|
||||
|
||||
public Task OnUnloadAsync()
|
||||
{
|
||||
AuthorizedClients.Clear();
|
||||
return Utilities.CompletedTask;
|
||||
}
|
||||
public Task OnUnloadAsync() => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
@ -16,14 +16,16 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
{
|
||||
OffensiveWords = new List<string>()
|
||||
{
|
||||
"nigger",
|
||||
"nigga",
|
||||
"fuck"
|
||||
@"\s*n+.*i+.*g+.*e+.*r+\s*",
|
||||
@"\s*n+.*i+.*g+.*a+\s*",
|
||||
@"\s*f+u+.*c+.*k+.*\s*"
|
||||
};
|
||||
|
||||
EnableProfanityDeterment = Utilities.PromptBool("Enable profanity deterring");
|
||||
ProfanityWarningMessage = "Please do not use profanity on this server";
|
||||
ProfanityKickMessage = "Excessive use of profanity";
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
|
||||
EnableProfanityDeterment = Utilities.PromptBool(loc["PLUGINS_PROFANITY_SETUP_ENABLE"]);
|
||||
ProfanityWarningMessage = loc["PLUGINS_PROFANITY_WARNMSG"];
|
||||
ProfanityKickMessage = loc["PLUGINS_PROFANITY_KICKMSG"];
|
||||
KickAfterInfringementCount = 2;
|
||||
|
||||
return this;
|
||||
|
@ -1,9 +1,11 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
|
||||
@ -20,12 +22,11 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
BaseConfigurationHandler<Configuration> Settings;
|
||||
ConcurrentDictionary<int, Tracking> ProfanityCounts;
|
||||
IManager Manager;
|
||||
Task CompletedTask = Task.FromResult(false);
|
||||
|
||||
public async Task OnEventAsync(GameEvent E, Server S)
|
||||
public Task OnEventAsync(GameEvent E, Server S)
|
||||
{
|
||||
if (!Settings.Configuration().EnableProfanityDeterment)
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (E.Type == GameEvent.EventType.Connect)
|
||||
{
|
||||
@ -34,6 +35,26 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
S.Logger.WriteWarning("Could not add client to profanity tracking");
|
||||
}
|
||||
|
||||
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, RegexOptions.IgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
if (containsObjectionalWord)
|
||||
{
|
||||
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new EFClient()
|
||||
{
|
||||
ClientId = 1,
|
||||
CurrentServer = E.Owner
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
if (E.Type == GameEvent.EventType.Disconnect)
|
||||
@ -47,30 +68,36 @@ 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.Data.ToLower(), word, RegexOptions.IgnoreCase);
|
||||
|
||||
// break out early because there's at least one objectional word
|
||||
if (containsObjectionalWord)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (containsObjectionalWord)
|
||||
{
|
||||
var clientProfanity = ProfanityCounts[E.Origin.ClientId];
|
||||
if (clientProfanity.Infringements >= Settings.Configuration().KickAfterInfringementCount)
|
||||
{
|
||||
await clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
});
|
||||
clientProfanity.Client.Kick(Settings.Configuration().ProfanityKickMessage, Utilities.IW4MAdminClient(E.Owner));
|
||||
}
|
||||
|
||||
else if (clientProfanity.Infringements < Settings.Configuration().KickAfterInfringementCount)
|
||||
{
|
||||
clientProfanity.Infringements++;
|
||||
|
||||
await clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, new Player()
|
||||
{
|
||||
ClientId = 1
|
||||
});
|
||||
clientProfanity.Client.Warn(Settings.Configuration().ProfanityWarningMessage, Utilities.IW4MAdminClient(E.Owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task OnLoadAsync(IManager manager)
|
||||
@ -87,8 +114,8 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
Manager = manager;
|
||||
}
|
||||
|
||||
public Task OnTickAsync(Server S) => CompletedTask;
|
||||
public Task OnTickAsync(Server S) => Task.CompletedTask;
|
||||
|
||||
public Task OnUnloadAsync() => CompletedTask;
|
||||
public Task OnUnloadAsync() => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<RuntimeFrameworkVersion>2.1.5</RuntimeFrameworkVersion>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.Plugins.ProfanityDeterment</PackageId>
|
||||
@ -18,6 +19,10 @@
|
||||
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.NETCore.App" Version="2.1.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="copy "$(TargetPath)" "$(SolutionDir)BUILD\Plugins"" />
|
||||
</Target>
|
||||
|
@ -1,16 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using SharedLibraryCore.Objects;
|
||||
|
||||
namespace IW4MAdmin.Plugins.ProfanityDeterment
|
||||
{
|
||||
class Tracking
|
||||
{
|
||||
public Player Client { get; private set; }
|
||||
public EFClient Client { get; private set; }
|
||||
public int Infringements { get; set; }
|
||||
|
||||
public Tracking(Player client)
|
||||
public Tracking(EFClient client)
|
||||
{
|
||||
Client = client;
|
||||
Infringements = 0;
|
||||
|
29
Plugins/ScriptPlugins/SharedGUIDKick.js
Normal file
29
Plugins/ScriptPlugins/SharedGUIDKick.js
Normal file
@ -0,0 +1,29 @@
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 1.1,
|
||||
name: 'Shared GUID Kicker Plugin',
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
// make sure we only check for IW4(x)
|
||||
if (server.GameName !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// connect or join event
|
||||
if (gameEvent.Type === 3) {
|
||||
// this GUID seems to have been packed in a IW4 torrent and results in an unreasonable amount of people using the same GUID
|
||||
if (gameEvent.Origin.NetworkId === -805366929435212061) {
|
||||
gameEvent.Origin.Kick('Your GUID is generic. Delete players/guids.dat and rejoin', _IW4MAdminClient);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
62
Plugins/ScriptPlugins/VPNDetection.js
Normal file
62
Plugins/ScriptPlugins/VPNDetection.js
Normal file
@ -0,0 +1,62 @@
|
||||
var plugin = {
|
||||
author: 'RaidMax',
|
||||
version: 1.0,
|
||||
name: 'VPN Detection Plugin',
|
||||
|
||||
manager: null,
|
||||
logger: null,
|
||||
vpnExceptionIds: [],
|
||||
|
||||
checkForVpn: function (origin) {
|
||||
var exempt = false;
|
||||
// prevent players that are exempt from being kicked
|
||||
this.vpnExceptionIds.forEach(function (id) {
|
||||
if (id === origin.ClientId) {
|
||||
exempt = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (exempt) {
|
||||
return;
|
||||
}
|
||||
|
||||
var usingVPN = false;
|
||||
|
||||
try {
|
||||
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);
|
||||
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 + ')');
|
||||
origin.Kick(_localization.LocalizationIndex["SERVER_KICK_VPNS_NOTALLOWED"], _IW4MAdminClient);
|
||||
}
|
||||
},
|
||||
|
||||
onEventAsync: function (gameEvent, server) {
|
||||
// connect event
|
||||
if (gameEvent.Type === 3) {
|
||||
this.checkForVpn(gameEvent.Origin);
|
||||
}
|
||||
},
|
||||
|
||||
onLoadAsync: function (manager) {
|
||||
this.manager = manager;
|
||||
this.logger = manager.GetLogger(0);
|
||||
},
|
||||
|
||||
onUnloadAsync: function () {
|
||||
},
|
||||
|
||||
onTickAsync: function (server) {
|
||||
}
|
||||
};
|
@ -1,8 +1,7 @@
|
||||
using SharedLibraryCore.Helpers;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using SharedLibraryCore.Objects;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -12,89 +11,187 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
{
|
||||
class Detection
|
||||
{
|
||||
public enum DetectionType
|
||||
{
|
||||
Bone,
|
||||
Chest,
|
||||
Offset,
|
||||
Strain
|
||||
};
|
||||
|
||||
public ChangeTracking<EFACSnapshot> Tracker { get; private set; }
|
||||
public const int QUEUE_COUNT = 10;
|
||||
|
||||
public List<EFClientKill> QueuedHits { get; set; }
|
||||
int Kills;
|
||||
int AboveThresholdCount;
|
||||
double AverageKillTime;
|
||||
int HitCount;
|
||||
Dictionary<IW4Info.HitLocation, int> HitLocationCount;
|
||||
double AngleDifferenceAverage;
|
||||
EFClientStatistics ClientStats;
|
||||
DateTime LastKill;
|
||||
long LastOffset;
|
||||
IW4Info.WeaponName LastWeapon;
|
||||
ILogger Log;
|
||||
Strain Strain;
|
||||
readonly DateTime ConnectionTime = DateTime.UtcNow;
|
||||
|
||||
public Detection(ILogger log, EFClientStatistics clientStats)
|
||||
{
|
||||
Log = log;
|
||||
HitLocationCount = new Dictionary<IW4Info.HitLocation, int>();
|
||||
foreach (var loc in Enum.GetValues(typeof(IW4Info.HitLocation)))
|
||||
{
|
||||
HitLocationCount.Add((IW4Info.HitLocation)loc, 0);
|
||||
LastKill = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
ClientStats = clientStats;
|
||||
Strain = new Strain();
|
||||
Tracker = new ChangeTracking<EFACSnapshot>();
|
||||
QueuedHits = new List<EFClientKill>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyze kill and see if performed by a cheater
|
||||
/// </summary>
|
||||
/// <param name="kill">kill performed by the player</param>
|
||||
/// <param name="hit">kill performed by the player</param>
|
||||
/// <returns>true if detection reached thresholds, false otherwise</returns>
|
||||
public DetectionPenaltyResult ProcessKill(EFClientKill kill)
|
||||
public DetectionPenaltyResult ProcessHit(EFClientKill hit, bool isDamage)
|
||||
{
|
||||
if ((kill.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
|
||||
kill.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
|
||||
kill.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
|
||||
kill.HitLoc == IW4Info.HitLocation.none)
|
||||
if ((hit.DeathType != IW4Info.MeansOfDeath.MOD_PISTOL_BULLET &&
|
||||
hit.DeathType != IW4Info.MeansOfDeath.MOD_RIFLE_BULLET &&
|
||||
hit.DeathType != IW4Info.MeansOfDeath.MOD_HEAD_SHOT) ||
|
||||
hit.HitLoc == IW4Info.HitLocation.none || hit.TimeOffset - LastOffset < 0 ||
|
||||
// hack: prevents false positives
|
||||
(LastWeapon != hit.Weapon && (hit.TimeOffset - LastOffset) == 50))
|
||||
{
|
||||
return new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Any,
|
||||
RatioAmount = 0
|
||||
};
|
||||
|
||||
HitLocationCount[kill.HitLoc]++;
|
||||
Kills++;
|
||||
AverageKillTime = (AverageKillTime + (DateTime.UtcNow - LastKill).TotalSeconds) / Kills;
|
||||
|
||||
#region VIEWANGLES
|
||||
double distance = Vector3.Distance(kill.KillOrigin, kill.DeathOrigin);
|
||||
double x = kill.KillOrigin.X + distance * Math.Cos(kill.ViewAngles.X.ToRadians()) * Math.Cos(kill.ViewAngles.Y.ToRadians());
|
||||
double y = kill.KillOrigin.Y + (distance * Math.Sin(kill.ViewAngles.X.ToRadians()) * Math.Cos(kill.ViewAngles.Y.ToRadians()));
|
||||
double z = kill.KillOrigin.Z + distance * Math.Sin((360.0f - kill.ViewAngles.Y).ToRadians());
|
||||
var trueVector = Vector3.Subtract(kill.KillOrigin, kill.DeathOrigin);
|
||||
var calculatedVector = Vector3.Subtract(kill.KillOrigin, new Vector3((float)x, (float)y, (float)z));
|
||||
double angle = trueVector.AngleBetween(calculatedVector);
|
||||
|
||||
if (kill.AdsPercent > 0.5 && kill.Distance > 3)
|
||||
{
|
||||
var hitLoc = ClientStats.HitLocations
|
||||
.First(hl => hl.Location == kill.HitLoc);
|
||||
float previousAverage = hitLoc.HitOffsetAverage;
|
||||
double newAverage = (previousAverage * (hitLoc.HitCount - 1) + angle) / hitLoc.HitCount;
|
||||
hitLoc.HitOffsetAverage = (float)newAverage;
|
||||
|
||||
if (double.IsNaN(hitLoc.HitOffsetAverage))
|
||||
{
|
||||
Log.WriteWarning("[Detection::ProcessKill] HitOffsetAvgerage NaN");
|
||||
Log.WriteDebug($"{previousAverage}-{hitLoc.HitCount}-{hitLoc}-{newAverage}");
|
||||
hitLoc.HitOffsetAverage = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
DetectionPenaltyResult result = null;
|
||||
LastWeapon = hit.Weapon;
|
||||
|
||||
HitLocationCount[hit.HitLoc]++;
|
||||
HitCount++;
|
||||
|
||||
if (!isDamage)
|
||||
{
|
||||
Kills++;
|
||||
}
|
||||
|
||||
#region VIEWANGLES
|
||||
if (hit.AnglesList.Count >= 2)
|
||||
{
|
||||
double realAgainstPredict = Vector3.ViewAngleDistance(hit.AnglesList[0], hit.AnglesList[1], hit.ViewAngles);
|
||||
|
||||
// LIFETIME
|
||||
var hitLoc = ClientStats.HitLocations
|
||||
.First(hl => hl.Location == hit.HitLoc);
|
||||
|
||||
float previousAverage = hitLoc.HitOffsetAverage;
|
||||
double newAverage = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount;
|
||||
hitLoc.HitOffsetAverage = (float)newAverage;
|
||||
|
||||
if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset(hitLoc.HitCount) &&
|
||||
hitLoc.HitCount > 100)
|
||||
{
|
||||
//Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***");
|
||||
//Log.WriteDebug($"Lifetime Average = {newAverage}");
|
||||
//Log.WriteDebug($"Bone = {hitLoc.Location}");
|
||||
//Log.WriteDebug($"HitCount = {hitLoc.HitCount}");
|
||||
//Log.WriteDebug($"ID = {hit.AttackerId}");
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Ban,
|
||||
Value = hitLoc.HitOffsetAverage,
|
||||
HitCount = hitLoc.HitCount,
|
||||
Type = DetectionType.Offset
|
||||
};
|
||||
}
|
||||
|
||||
// SESSION
|
||||
double sessAverage = (AngleDifferenceAverage * (HitCount - 1) + realAgainstPredict) / HitCount;
|
||||
AngleDifferenceAverage = sessAverage;
|
||||
|
||||
if (sessAverage > Thresholds.MaxOffset(HitCount) &&
|
||||
HitCount > 30)
|
||||
{
|
||||
//Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
|
||||
//Log.WriteDebug($"Session Average = {sessAverage}");
|
||||
//Log.WriteDebug($"HitCount = {HitCount}");
|
||||
//Log.WriteDebug($"ID = {hit.AttackerId}");
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Ban,
|
||||
Value = sessAverage,
|
||||
HitCount = HitCount,
|
||||
Type = DetectionType.Offset,
|
||||
Location = hitLoc.Location
|
||||
};
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Log.WriteDebug($"PredictVsReal={realAgainstPredict}");
|
||||
#endif
|
||||
}
|
||||
|
||||
double currentStrain = Strain.GetStrain(isDamage, hit.Damage, hit.Distance / 0.0254, hit.ViewAngles, Math.Max(50, hit.TimeOffset - LastOffset));
|
||||
#if DEBUG == true
|
||||
Log.WriteDebug($"Current Strain: {currentStrain}");
|
||||
#endif
|
||||
LastOffset = hit.TimeOffset;
|
||||
|
||||
if (currentStrain > ClientStats.MaxStrain)
|
||||
{
|
||||
ClientStats.MaxStrain = currentStrain;
|
||||
}
|
||||
|
||||
// flag
|
||||
if (currentStrain > Thresholds.MaxStrainFlag)
|
||||
{
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Flag,
|
||||
Value = currentStrain,
|
||||
HitCount = HitCount,
|
||||
Type = DetectionType.Strain
|
||||
};
|
||||
}
|
||||
|
||||
// ban
|
||||
if (currentStrain > Thresholds.MaxStrainBan &&
|
||||
HitCount >= 5)
|
||||
{
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Ban,
|
||||
Value = currentStrain,
|
||||
HitCount = HitCount,
|
||||
Type = DetectionType.Strain
|
||||
};
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SESSION_RATIOS
|
||||
if (Kills >= Thresholds.LowSampleMinKills)
|
||||
{
|
||||
double marginOfError = Thresholds.GetMarginOfError(Kills);
|
||||
double marginOfError = Thresholds.GetMarginOfError(HitCount);
|
||||
// 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 lerpAmount = Math.Min(1.0, (HitCount - Thresholds.LowSampleMinKills) / (double)(/*Thresholds.HighSampleMinKills*/ 60 - Thresholds.LowSampleMinKills));
|
||||
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;
|
||||
double maxHeadshotLerpValueForBan = Thresholds.Lerp(Thresholds.HeadshotRatioThresholdLowSample(3.5), Thresholds.HeadshotRatioThresholdHighSample(3.5), lerpAmount) + marginOfError;
|
||||
// determine what the max bone percentage can be for current number of kills
|
||||
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 currentHeadshotRatio = ((HitLocationCount[IW4Info.HitLocation.head] + HitLocationCount[IW4Info.HitLocation.helmet]) / (double)Kills);
|
||||
double currentHeadshotRatio = ((HitLocationCount[IW4Info.HitLocation.head] + HitLocationCount[IW4Info.HitLocation.helmet] + HitLocationCount[IW4Info.HitLocation.neck]) / (double)HitCount);
|
||||
|
||||
// calculate maximum bone
|
||||
double currentMaxBoneRatio = (HitLocationCount.Values.Select(v => v / (double)Kills).Max());
|
||||
double currentMaxBoneRatio = (HitLocationCount.Values.Select(v => v / (double)HitCount).Max());
|
||||
var bone = HitLocationCount.FirstOrDefault(b => b.Value == HitLocationCount.Values.Max()).Key;
|
||||
|
||||
#region HEADSHOT_RATIO
|
||||
@ -102,48 +199,52 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
if (currentHeadshotRatio > maxHeadshotLerpValueForFlag)
|
||||
{
|
||||
// ban on headshot
|
||||
if (currentHeadshotRatio > maxHeadshotLerpValueForFlag)
|
||||
if (currentHeadshotRatio > maxHeadshotLerpValueForBan)
|
||||
{
|
||||
AboveThresholdCount++;
|
||||
Log.WriteDebug("**Maximum Headshot Ratio Reached For Ban**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"**Kills: {Kills}");
|
||||
Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
Log.WriteDebug($"**HitCount: {HitCount}");
|
||||
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()
|
||||
Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Ban,
|
||||
RatioAmount = currentHeadshotRatio,
|
||||
Bone = IW4Info.HitLocation.head,
|
||||
KillCount = Kills
|
||||
Value = currentHeadshotRatio,
|
||||
Location = IW4Info.HitLocation.head,
|
||||
HitCount = HitCount,
|
||||
Type = DetectionType.Bone
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
AboveThresholdCount++;
|
||||
Log.WriteDebug("**Maximum Headshot Ratio Reached For Flag**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"**Kills: {Kills}");
|
||||
Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
Log.WriteDebug($"**HitCount: {HitCount}");
|
||||
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()
|
||||
Log.WriteDebug(sb.ToString());
|
||||
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Flag,
|
||||
RatioAmount = currentHeadshotRatio,
|
||||
Bone = IW4Info.HitLocation.head,
|
||||
KillCount = Kills
|
||||
Value = currentHeadshotRatio,
|
||||
Location = IW4Info.HitLocation.head,
|
||||
HitCount = HitCount,
|
||||
Type = DetectionType.Bone
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -157,41 +258,49 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
if (currentMaxBoneRatio > maxBoneRatioLerpValueForBan)
|
||||
{
|
||||
Log.WriteDebug("**Maximum Bone Ratio Reached For Ban**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"**Kills: {Kills}");
|
||||
Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
Log.WriteDebug($"**HitCount: {HitCount}");
|
||||
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()
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Ban,
|
||||
RatioAmount = currentMaxBoneRatio,
|
||||
Bone = bone,
|
||||
KillCount = Kills
|
||||
Value = currentMaxBoneRatio,
|
||||
Location = bone,
|
||||
HitCount = HitCount,
|
||||
Type = DetectionType.Bone
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.WriteDebug("**Maximum Bone Ratio Reached For Flag**");
|
||||
Log.WriteDebug($"ClientId: {kill.AttackerId}");
|
||||
Log.WriteDebug($"**Kills: {Kills}");
|
||||
Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
Log.WriteDebug($"**HitCount: {HitCount}");
|
||||
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()
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Flag,
|
||||
RatioAmount = currentMaxBoneRatio,
|
||||
Bone = bone,
|
||||
KillCount = Kills
|
||||
Value = currentMaxBoneRatio,
|
||||
Location = bone,
|
||||
HitCount = HitCount,
|
||||
Type = DetectionType.Bone
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -199,12 +308,12 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
}
|
||||
|
||||
#region CHEST_ABDOMEN_RATIO_SESSION
|
||||
int chestKills = HitLocationCount[IW4Info.HitLocation.torso_upper];
|
||||
int chestHits = HitLocationCount[IW4Info.HitLocation.torso_upper];
|
||||
|
||||
if (chestKills >= Thresholds.MediumSampleMinKills)
|
||||
if (chestHits >= Thresholds.MediumSampleMinKills)
|
||||
{
|
||||
double marginOfError = Thresholds.GetMarginOfError(chestKills);
|
||||
double lerpAmount = Math.Min(1.0, (chestKills - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
|
||||
double marginOfError = Thresholds.GetMarginOfError(chestHits);
|
||||
double lerpAmount = Math.Min(1.0, (chestHits - Thresholds.MediumSampleMinKills) / (double)(Thresholds.HighSampleMinKills - Thresholds.LowSampleMinKills));
|
||||
// determine max acceptable ratio of chest to abdomen kills
|
||||
double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(3), Thresholds.ChestAbdomenRatioThresholdHighSample(3), lerpAmount) + marginOfError;
|
||||
double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdLowSample(4), Thresholds.ChestAbdomenRatioThresholdHighSample(4), lerpAmount) + marginOfError;
|
||||
@ -214,72 +323,101 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag)
|
||||
{
|
||||
|
||||
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestKills >= Thresholds.MediumSampleMinKills + 30)
|
||||
if (currentChestAbdomenRatio > chestAbdomenLerpValueForBan && chestHits >= 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}");
|
||||
//Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Ban**");
|
||||
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
//Log.WriteDebug($"**Chest Hits: {chestHits}");
|
||||
//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());
|
||||
|
||||
return new DetectionPenaltyResult()
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Ban,
|
||||
RatioAmount = currentChestAbdomenRatio,
|
||||
Bone = 0,
|
||||
KillCount = chestKills
|
||||
Value = currentChestAbdomenRatio,
|
||||
Location = IW4Info.HitLocation.torso_upper,
|
||||
Type = DetectionType.Chest,
|
||||
HitCount = chestHits
|
||||
};
|
||||
}
|
||||
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}");
|
||||
//Log.WriteDebug("**Maximum Chest/Abdomen Ratio Reached For Flag**");
|
||||
//Log.WriteDebug($"ClientId: {hit.AttackerId}");
|
||||
//Log.WriteDebug($"**Chest Hits: {chestHits}");
|
||||
//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());
|
||||
|
||||
return new DetectionPenaltyResult()
|
||||
result = new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Flag,
|
||||
RatioAmount = currentChestAbdomenRatio,
|
||||
Bone = 0,
|
||||
KillCount = chestKills
|
||||
Value = currentChestAbdomenRatio,
|
||||
Location = IW4Info.HitLocation.torso_upper,
|
||||
Type = DetectionType.Chest,
|
||||
HitCount = chestHits
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
return new DetectionPenaltyResult()
|
||||
|
||||
Tracker.OnChange(new EFACSnapshot()
|
||||
{
|
||||
When = hit.When,
|
||||
ClientId = ClientStats.ClientId,
|
||||
SessionAngleOffset = AngleDifferenceAverage,
|
||||
CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds,
|
||||
CurrentStrain = currentStrain,
|
||||
CurrentViewAngle = hit.ViewAngles,
|
||||
Hits = HitCount,
|
||||
Kills = Kills,
|
||||
Deaths = ClientStats.SessionDeaths,
|
||||
HitDestinationId = hit.DeathOrigin.Vector3Id,
|
||||
HitDestination = hit.DeathOrigin,
|
||||
HitOriginId = hit.KillOrigin.Vector3Id,
|
||||
HitOrigin = hit.KillOrigin,
|
||||
EloRating = ClientStats.EloRating,
|
||||
HitLocation = hit.HitLoc,
|
||||
LastStrainAngle = Strain.LastAngle,
|
||||
PredictedViewAngles = hit.AnglesList,
|
||||
// this is in "meters"
|
||||
Distance = hit.Distance,
|
||||
SessionScore = ClientStats.SessionScore,
|
||||
HitType = hit.DeathType,
|
||||
SessionSPM = ClientStats.SessionSPM,
|
||||
StrainAngleBetween = Strain.LastDistance,
|
||||
TimeSinceLastEvent = (int)Strain.LastDeltaTime,
|
||||
WeaponId = hit.Weapon
|
||||
});
|
||||
|
||||
return result ?? new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Any,
|
||||
RatioAmount = 0
|
||||
};
|
||||
}
|
||||
|
||||
public DetectionPenaltyResult ProcessTotalRatio(EFClientStatistics stats)
|
||||
{
|
||||
int totalChestKills = stats.HitLocations.Single(c => c.Location == IW4Info.HitLocation.torso_upper).HitCount;
|
||||
int totalChestHits = stats.HitLocations.Single(c => c.Location == IW4Info.HitLocation.torso_upper).HitCount;
|
||||
|
||||
if (totalChestKills >= 60)
|
||||
if (totalChestHits >= 60)
|
||||
{
|
||||
double marginOfError = Thresholds.GetMarginOfError(totalChestKills);
|
||||
double lerpAmount = Math.Min(1.0, (totalChestKills - 60) / 250.0);
|
||||
double marginOfError = Thresholds.GetMarginOfError(totalChestHits);
|
||||
double lerpAmount = Math.Min(1.0, (totalChestHits - 60) / 250.0);
|
||||
// determine max acceptable ratio of chest to abdomen kills
|
||||
double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdHighSample(3.0), Thresholds.ChestAbdomenRatioThresholdHighSample(2), lerpAmount) + marginOfError;
|
||||
double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdHighSample(4.0), Thresholds.ChestAbdomenRatioThresholdHighSample(4.0), lerpAmount) + marginOfError;
|
||||
double chestAbdomenRatioLerpValueForFlag = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdHighSample(3.0), Thresholds.ChestAbdomenRatioThresholdHighSample(2.0), lerpAmount) + marginOfError;
|
||||
double chestAbdomenLerpValueForBan = Thresholds.Lerp(Thresholds.ChestAbdomenRatioThresholdHighSample(4.0), Thresholds.ChestAbdomenRatioThresholdHighSample(3.0), lerpAmount) + marginOfError;
|
||||
|
||||
double currentChestAbdomenRatio = totalChestKills /
|
||||
double currentChestAbdomenRatio = totalChestHits /
|
||||
stats.HitLocations.Single(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount;
|
||||
|
||||
if (currentChestAbdomenRatio > chestAbdomenRatioLerpValueForFlag)
|
||||
@ -287,44 +425,44 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
|
||||
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 location in stats.HitLocations)
|
||||
sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
|
||||
Log.WriteDebug(sb.ToString());
|
||||
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
|
||||
//Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Ban**");
|
||||
//Log.WriteDebug($"ClientId: {stats.ClientId}");
|
||||
//Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
|
||||
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
//Log.WriteDebug($"**MaxRatio {chestAbdomenLerpValueForBan}");
|
||||
//var sb = new StringBuilder();
|
||||
//foreach (var location in stats.HitLocations)
|
||||
// sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
|
||||
//Log.WriteDebug(sb.ToString());
|
||||
|
||||
return new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Ban,
|
||||
RatioAmount = currentChestAbdomenRatio,
|
||||
Bone = IW4Info.HitLocation.torso_upper,
|
||||
KillCount = totalChestKills
|
||||
Value = currentChestAbdomenRatio,
|
||||
Location = IW4Info.HitLocation.torso_upper,
|
||||
HitCount = totalChestHits,
|
||||
Type = DetectionType.Chest
|
||||
};
|
||||
}
|
||||
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 location in stats.HitLocations)
|
||||
sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
|
||||
Log.WriteDebug(sb.ToString());
|
||||
// Log.WriteDebug($"ThresholdReached: {AboveThresholdCount}");
|
||||
//Log.WriteDebug("**Maximum Lifetime Chest/Abdomen Ratio Reached For Flag**");
|
||||
//Log.WriteDebug($"ClientId: {stats.ClientId}");
|
||||
//Log.WriteDebug($"**Total Chest Hits: {totalChestHits}");
|
||||
//Log.WriteDebug($"**Ratio {currentChestAbdomenRatio}");
|
||||
//Log.WriteDebug($"**MaxRatio {chestAbdomenRatioLerpValueForFlag}");
|
||||
//var sb = new StringBuilder();
|
||||
//foreach (var location in stats.HitLocations)
|
||||
// sb.Append($"HitLocation: {location.Location} -> {location.HitCount}\r\n");
|
||||
//Log.WriteDebug(sb.ToString());
|
||||
|
||||
return new DetectionPenaltyResult()
|
||||
{
|
||||
ClientPenalty = Penalty.PenaltyType.Flag,
|
||||
RatioAmount = currentChestAbdomenRatio,
|
||||
Bone = IW4Info.HitLocation.torso_upper,
|
||||
KillCount = totalChestKills
|
||||
Value = currentChestAbdomenRatio,
|
||||
Location = IW4Info.HitLocation.torso_upper,
|
||||
HitCount = totalChestHits,
|
||||
Type = DetectionType.Chest
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -332,7 +470,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
|
||||
return new DetectionPenaltyResult()
|
||||
{
|
||||
Bone = IW4Info.HitLocation.none,
|
||||
ClientPenalty = Penalty.PenaltyType.Any
|
||||
};
|
||||
}
|
||||
|
@ -1,17 +1,13 @@
|
||||
using SharedLibraryCore.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
{
|
||||
class DetectionPenaltyResult
|
||||
{
|
||||
public Detection.DetectionType Type { get; set; }
|
||||
public Penalty.PenaltyType ClientPenalty { get; set; }
|
||||
public double RatioAmount { get; set; }
|
||||
public IW4Info.HitLocation Bone { get; set; }
|
||||
public int KillCount { get; set; }
|
||||
public double Value { get; set; }
|
||||
public IW4Info.HitLocation Location { get; set; }
|
||||
public int HitCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
54
Plugins/Stats/Cheat/Strain.cs
Normal file
54
Plugins/Stats/Cheat/Strain.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using SharedLibraryCore.Helpers;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
{
|
||||
class Strain
|
||||
{
|
||||
private const double StrainDecayBase = 0.9;
|
||||
private double CurrentStrain;
|
||||
public double LastDistance { get; private set; }
|
||||
public Vector3 LastAngle { get; private set; }
|
||||
public double LastDeltaTime { get; private set; }
|
||||
|
||||
public double GetStrain(bool isDamage, int damage, double killDistance, Vector3 newAngle, double deltaTime)
|
||||
{
|
||||
if (LastAngle == null)
|
||||
LastAngle = newAngle;
|
||||
|
||||
LastDeltaTime = deltaTime;
|
||||
|
||||
double decayFactor = GetDecay(deltaTime);
|
||||
CurrentStrain *= decayFactor;
|
||||
double[] distance = Helpers.Extensions.AngleStuff(newAngle, LastAngle);
|
||||
LastDistance = distance[0] + distance[1];
|
||||
|
||||
#if DEBUG == true
|
||||
Console.WriteLine($"Angle Between = {LastDistance}");
|
||||
Console.WriteLine($"Distance From Target = {killDistance}");
|
||||
Console.WriteLine($"Time Offset = {deltaTime}");
|
||||
Console.WriteLine($"Decay Factor = {decayFactor} ");
|
||||
#endif
|
||||
// this happens on first kill
|
||||
if ((distance[0] == 0 && distance[1] == 0) ||
|
||||
deltaTime == 0 ||
|
||||
double.IsNaN(CurrentStrain))
|
||||
{
|
||||
return CurrentStrain;
|
||||
}
|
||||
|
||||
double newStrain = Math.Pow(LastDistance, 0.99) / deltaTime;
|
||||
newStrain *= killDistance / 1000.0;
|
||||
|
||||
CurrentStrain += newStrain;
|
||||
|
||||
LastAngle = newAngle;
|
||||
return CurrentStrain;
|
||||
}
|
||||
|
||||
private double GetDecay(double deltaTime) => Math.Pow(StrainDecayBase, Math.Pow(2.0, deltaTime / 250.0) / 1000.0);
|
||||
}
|
||||
}
|
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
{
|
||||
@ -31,6 +27,11 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
|
||||
public const int HighSampleMinKills = 100;
|
||||
public const double KillTimeThreshold = 0.2;
|
||||
|
||||
public const double MaxStrainBan = 0.9;
|
||||
|
||||
public static double MaxOffset(int sampleSize) => Math.Exp(Math.Max(-3.07 + (-3.07 / Math.Sqrt(sampleSize)), -3.07 - (-3.07 / Math.Sqrt(sampleSize))) + 4 * (0.869));
|
||||
public const double MaxStrainFlag = 0.36;
|
||||
|
||||
public static double GetMarginOfError(int numKills) => 1.6455 / Math.Sqrt(numKills);
|
||||
|
||||
public static double Lerp(double v1, double v2, double amount)
|
||||
|
82
Plugins/Stats/Commands/MostPlayed.cs
Normal file
82
Plugins/Stats/Commands/MostPlayed.cs
Normal file
@ -0,0 +1,82 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Objects;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using SharedLibraryCore.Database;
|
||||
using System.Collections.Generic;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
class MostPlayed : Command
|
||||
{
|
||||
public static async Task<List<string>> GetMostPlayed(Server s)
|
||||
{
|
||||
long serverId = await StatManager.GetIdForServer(s);
|
||||
|
||||
List<string> mostPlayed = new List<string>()
|
||||
{
|
||||
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
|
||||
};
|
||||
|
||||
using (var db = new DatabaseContext())
|
||||
{
|
||||
db.ChangeTracker.AutoDetectChangesEnabled = false;
|
||||
db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
|
||||
var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1);
|
||||
|
||||
var iqStats = (from stats in db.Set<EFClientStatistics>()
|
||||
join client in db.Clients
|
||||
on stats.ClientId equals client.ClientId
|
||||
join alias in db.Aliases
|
||||
on client.CurrentAliasId equals alias.AliasId
|
||||
where stats.ServerId == serverId
|
||||
where client.Level != EFClient.Permission.Banned
|
||||
where client.LastConnection >= thirtyDaysAgo
|
||||
orderby stats.Kills descending
|
||||
select new
|
||||
{
|
||||
alias.Name,
|
||||
client.TotalConnectionTime,
|
||||
stats.Kills
|
||||
})
|
||||
.Take(5);
|
||||
|
||||
var iqList = await iqStats.ToListAsync();
|
||||
|
||||
mostPlayed.AddRange(iqList.Select(stats =>
|
||||
$"^3{stats.Name}^7 - ^5{stats.Kills} ^7{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KILLS"]} | ^5{Utilities.GetTimePassed(DateTime.UtcNow.AddSeconds(-stats.TotalConnectionTime), false)} ^7{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_PLAYER"].ToLower()}"));
|
||||
}
|
||||
|
||||
|
||||
return mostPlayed;
|
||||
}
|
||||
|
||||
public MostPlayed() : base("mostplayed", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_DESC"], "mp", EFClient.Permission.User, false) { }
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
var topStats = await GetMostPlayed(E.Owner);
|
||||
if (!E.Message.IsBroadcastCommand())
|
||||
{
|
||||
foreach (var stat in topStats)
|
||||
{
|
||||
E.Origin.Tell(stat);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var stat in topStats)
|
||||
{
|
||||
E.Owner.Broadcast(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,42 +1,52 @@
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Objects;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Database;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
public class ResetStats : Command
|
||||
{
|
||||
public ResetStats() : base("resetstats", "reset your stats to factory-new", "rs", Player.Permission.User, false) { }
|
||||
public ResetStats() : base("resetstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_DESC"], "rs", EFClient.Permission.User, false) { }
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
if (E.Origin.ClientNumber >= 0)
|
||||
{
|
||||
var svc = new SharedLibraryCore.Services.GenericRepository<EFClientStatistics>();
|
||||
int serverId = E.Owner.GetHashCode();
|
||||
var stats = svc.Find(s => s.ClientId == E.Origin.ClientId && s.ServerId == serverId).First();
|
||||
|
||||
stats.Deaths = 0;
|
||||
stats.Kills = 0;
|
||||
stats.SPM = 0.0;
|
||||
stats.Skill = 0.0;
|
||||
long serverId = await Helpers.StatManager.GetIdForServer(E.Owner);
|
||||
|
||||
// reset the cached version
|
||||
Plugin.Manager.ResetStats(E.Origin.ClientId, E.Owner.GetHashCode());
|
||||
EFClientStatistics clientStats;
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
clientStats = await ctx.Set<EFClientStatistics>()
|
||||
.Where(s => s.ClientId == E.Origin.ClientId)
|
||||
.Where(s => s.ServerId == serverId)
|
||||
.FirstAsync();
|
||||
|
||||
// fixme: this doesn't work properly when another context exists
|
||||
await svc.SaveChangesAsync();
|
||||
await E.Origin.Tell("Your stats for this server have been reset");
|
||||
clientStats.Deaths = 0;
|
||||
clientStats.Kills = 0;
|
||||
clientStats.SPM = 0.0;
|
||||
clientStats.Skill = 0.0;
|
||||
clientStats.TimePlayed = 0;
|
||||
// todo: make this more dynamic
|
||||
clientStats.EloRating = 200.0;
|
||||
|
||||
// reset the cached version
|
||||
Plugin.Manager.ResetStats(E.Origin.ClientId, serverId);
|
||||
|
||||
// fixme: this doesn't work properly when another context exists
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_SUCCESS"]);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
await E.Origin.Tell("You must be connected to a server to reset your stats");
|
||||
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_RESET_FAIL"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,52 +8,84 @@ using SharedLibraryCore.Objects;
|
||||
using SharedLibraryCore.Services;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using SharedLibraryCore.Database;
|
||||
using System.Collections.Generic;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
class TopStats : Command
|
||||
{
|
||||
public TopStats() : base("topstats", "view the top 5 players on this server", "ts", Player.Permission.User, false) { }
|
||||
public static async Task<List<string>> GetTopStats(Server s)
|
||||
{
|
||||
long serverId = await StatManager.GetIdForServer(s);
|
||||
List<string> topStatsText = new List<string>()
|
||||
{
|
||||
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_TEXT"]}--"
|
||||
};
|
||||
|
||||
using (var db = new DatabaseContext(true))
|
||||
{
|
||||
var fifteenDaysAgo = DateTime.UtcNow.AddDays(-15);
|
||||
|
||||
var iqStats = (from stats in db.Set<EFClientStatistics>()
|
||||
join client in db.Clients
|
||||
on stats.ClientId equals client.ClientId
|
||||
join alias in db.Aliases
|
||||
on client.CurrentAliasId equals alias.AliasId
|
||||
where stats.ServerId == serverId
|
||||
where stats.TimePlayed >= Plugin.Config.Configuration().TopPlayersMinPlayTime
|
||||
where client.Level != EFClient.Permission.Banned
|
||||
where client.LastConnection >= fifteenDaysAgo
|
||||
orderby stats.Performance descending
|
||||
select new
|
||||
{
|
||||
stats.KDR,
|
||||
stats.Performance,
|
||||
alias.Name
|
||||
})
|
||||
.Take(5);
|
||||
|
||||
#if DEBUG == true
|
||||
var statsSql = iqStats.ToSql();
|
||||
#endif
|
||||
|
||||
var statsList = (await iqStats.ToListAsync())
|
||||
.Select(stats => $"^3{stats.Name}^7 - ^5{stats.KDR} ^7{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"]} | ^5{stats.Performance} ^7{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_PERFORMANCE"]}");
|
||||
|
||||
topStatsText.AddRange(statsList);
|
||||
}
|
||||
|
||||
// no one qualified
|
||||
if (topStatsText.Count == 1)
|
||||
{
|
||||
topStatsText = new List<string>()
|
||||
{
|
||||
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_NOQUALIFY"]
|
||||
};
|
||||
}
|
||||
|
||||
return topStatsText;
|
||||
}
|
||||
|
||||
public TopStats() : base("topstats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_DESC"], "ts", EFClient.Permission.User, false) { }
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
var statsSvc = new GenericRepository<EFClientStatistics>();
|
||||
int serverId = E.Owner.GetHashCode();
|
||||
|
||||
using (var db = new DatabaseContext())
|
||||
var topStats = await GetTopStats(E.Owner);
|
||||
if (!E.Message.IsBroadcastCommand())
|
||||
{
|
||||
var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1);
|
||||
var topStats = await (from stats in db.Set<EFClientStatistics>()
|
||||
join client in db.Clients
|
||||
on stats.ClientId equals client.ClientId
|
||||
join alias in db.Aliases
|
||||
on client.CurrentAliasId equals alias.AliasId
|
||||
where stats.TimePlayed >= 3600
|
||||
where client.Level != Player.Permission.Banned
|
||||
where client.LastConnection >= thirtyDaysAgo
|
||||
orderby stats.Skill descending
|
||||
select new
|
||||
{
|
||||
alias.Name,
|
||||
stats.KDR,
|
||||
stats.Skill
|
||||
})
|
||||
.Take(5)
|
||||
.ToListAsync();
|
||||
|
||||
|
||||
if (!E.Message.IsBroadcastCommand())
|
||||
foreach (var stat in topStats)
|
||||
{
|
||||
await E.Origin.Tell("^5--Top Players--");
|
||||
E.Origin.Tell(stat);
|
||||
|
||||
foreach (var stat in topStats)
|
||||
await E.Origin.Tell($"^3{stat.Name}^7 - ^5{stat.KDR} ^7KDR | ^5{stat.Skill} ^7SKILL");
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var stat in topStats)
|
||||
{
|
||||
await E.Owner.Broadcast("^5--Top Players--");
|
||||
|
||||
foreach (var stat in topStats)
|
||||
await E.Owner.Broadcast($"^3{stat.Name}^7 - ^5{stat.KDR} ^7KDR | ^5{stat.Skill} ^7SKILL");
|
||||
E.Owner.Broadcast(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,12 +7,16 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharedLibraryCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using IW4MAdmin.Plugins.Stats.Helpers;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
{
|
||||
public class CViewStats : Command
|
||||
{
|
||||
public CViewStats() : base("stats", "view your stats", "xlrstats", Player.Permission.User, false, new CommandArgument[]
|
||||
public CViewStats() : base("stats", Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_VIEW_DESC"], "xlrstats", EFClient.Permission.User, false, new CommandArgument[]
|
||||
{
|
||||
new CommandArgument()
|
||||
{
|
||||
@ -24,54 +28,59 @@ namespace IW4MAdmin.Plugins.Stats.Commands
|
||||
|
||||
public override async Task ExecuteAsync(GameEvent E)
|
||||
{
|
||||
if (E.Target?.ClientNumber < 0)
|
||||
{
|
||||
await E.Origin.Tell("The specified player must be ingame");
|
||||
return;
|
||||
}
|
||||
|
||||
if (E.Origin.ClientNumber < 0 && E.Target == null)
|
||||
{
|
||||
await E.Origin.Tell("You must be ingame to view your stats");
|
||||
return;
|
||||
}
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
|
||||
String statLine;
|
||||
EFClientStatistics pStats;
|
||||
|
||||
if (E.Data.Length > 0 && E.Target == null)
|
||||
{
|
||||
await E.Origin.Tell("Cannot find the player you specified");
|
||||
return;
|
||||
E.Target = E.Owner.GetClientByName(E.Data).FirstOrDefault();
|
||||
|
||||
if (E.Target == null)
|
||||
{
|
||||
E.Origin.Tell(loc["PLUGINS_STATS_COMMANDS_VIEW_FAIL"]);
|
||||
}
|
||||
}
|
||||
|
||||
var clientStats = new GenericRepository<EFClientStatistics>();
|
||||
int serverId = E.Owner.GetHashCode();
|
||||
long serverId = await StatManager.GetIdForServer(E.Owner);
|
||||
|
||||
if (E.Target != null)
|
||||
using (var ctx = new DatabaseContext(disableTracking: true))
|
||||
{
|
||||
pStats = clientStats.Find(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId).First();
|
||||
statLine = String.Format("^5{0} ^7KILLS | ^5{1} ^7DEATHS | ^5{2} ^7KDR | ^5{3} ^7SKILL", pStats.Kills, pStats.Deaths, pStats.KDR, pStats.Skill);
|
||||
}
|
||||
if (E.Target != null)
|
||||
{
|
||||
int performanceRanking = await StatManager.GetClientOverallRanking(E.Target.ClientId);
|
||||
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
|
||||
|
||||
else
|
||||
{
|
||||
pStats = pStats = clientStats.Find(c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId).First();
|
||||
statLine = String.Format("^5{0} ^7KILLS | ^5{1} ^7DEATHS | ^5{2} ^7KDR | ^5{3} ^7SKILL", pStats.Kills, pStats.Deaths, pStats.KDR, pStats.Skill);
|
||||
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync(c => c.ServerId == serverId && c.ClientId == E.Target.ClientId));
|
||||
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
int performanceRanking = await StatManager.GetClientOverallRanking(E.Origin.ClientId);
|
||||
string performanceRankingString = performanceRanking == 0 ? loc["WEBFRONT_STATS_INDEX_UNRANKED"] : $"{loc["WEBFRONT_STATS_INDEX_RANKED"]} #{performanceRanking}";
|
||||
|
||||
pStats = (await ctx.Set<EFClientStatistics>().FirstAsync((c => c.ServerId == serverId && c.ClientId == E.Origin.ClientId)));
|
||||
statLine = $"^5{pStats.Kills} ^7{loc["PLUGINS_STATS_TEXT_KILLS"]} | ^5{pStats.Deaths} ^7{loc["PLUGINS_STATS_TEXT_DEATHS"]} | ^5{pStats.KDR} ^7KDR | ^5{pStats.Performance} ^7{loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"].ToUpper()} | {performanceRankingString}";
|
||||
}
|
||||
}
|
||||
|
||||
if (E.Message.IsBroadcastCommand())
|
||||
{
|
||||
string name = E.Target == null ? E.Origin.Name : E.Target.Name;
|
||||
await E.Owner.Broadcast($"Stats for ^5{name}^7");
|
||||
await E.Owner.Broadcast(statLine);
|
||||
E.Owner.Broadcast($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{name}^7");
|
||||
E.Owner.Broadcast(statLine);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (E.Target != null)
|
||||
await E.Origin.Tell($"Stats for ^5{E.Target.Name}^7");
|
||||
await E.Origin.Tell(statLine);
|
||||
{
|
||||
E.Origin.Tell($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{E.Target.Name}^7");
|
||||
}
|
||||
|
||||
E.Origin.Tell(statLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,21 @@
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Config
|
||||
{
|
||||
class StatsConfiguration : IBaseConfiguration
|
||||
public class StatsConfiguration : IBaseConfiguration
|
||||
{
|
||||
public bool EnableAntiCheat { get; set; }
|
||||
public List<StreakMessageConfiguration> KillstreakMessages { get; set; }
|
||||
public List<StreakMessageConfiguration> DeathstreakMessages { get; set; }
|
||||
public int TopPlayersMinPlayTime { get; set; }
|
||||
public bool StoreClientKills { get; set; }
|
||||
public string Name() => "Stats";
|
||||
public IBaseConfiguration Generate()
|
||||
{
|
||||
var config = new StatsConfiguration();
|
||||
|
||||
Console.Write("Enable server-side anti-cheat? [y/n]: ");
|
||||
config.EnableAntiCheat = (Console.ReadLine().ToLower().FirstOrDefault() as char?) == 'y';
|
||||
|
||||
config.KillstreakMessages = new List<StreakMessageConfiguration>()
|
||||
EnableAntiCheat = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_SETUP_ENABLEAC"]);
|
||||
KillstreakMessages = new List<StreakMessageConfiguration>()
|
||||
{
|
||||
new StreakMessageConfiguration(){
|
||||
Count = -1,
|
||||
@ -42,7 +36,7 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
||||
}
|
||||
};
|
||||
|
||||
config.DeathstreakMessages = new List<StreakMessageConfiguration>()
|
||||
DeathstreakMessages = new List<StreakMessageConfiguration>()
|
||||
{
|
||||
new StreakMessageConfiguration()
|
||||
{
|
||||
@ -55,7 +49,10 @@ namespace IW4MAdmin.Plugins.Stats.Config
|
||||
},
|
||||
};
|
||||
|
||||
return config;
|
||||
TopPlayersMinPlayTime = 3600 * 3;
|
||||
StoreClientKills = false;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,5 +22,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
public static float ToRadians(this float value) => (float)Math.PI * value / 180.0f;
|
||||
|
||||
public static float ToDegrees(this float value) => value * 180.0f / (float)Math.PI;
|
||||
|
||||
public static double[] AngleStuff(Vector3 a, Vector3 b)
|
||||
{
|
||||
double deltaX = 180.0 - Math.Abs(Math.Abs(a.X - b.X) - 180.0);
|
||||
double deltaY = 180.0 - Math.Abs(Math.Abs(a.Y - b.Y) - 180.0);
|
||||
|
||||
return new[] { deltaX, deltaY };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,8 @@
|
||||
using SharedLibraryCore;
|
||||
using IW4MAdmin.Plugins.Stats.Cheat;
|
||||
using IW4MAdmin.Plugins.Stats.Cheat;
|
||||
using IW4MAdmin.Plugins.Stats.Models;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
{
|
||||
@ -15,6 +11,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
public ConcurrentDictionary<int, Detection> PlayerDetections { get; set; }
|
||||
public EFServerStatistics ServerStatistics { get; private set; }
|
||||
public EFServer Server { get; private set; }
|
||||
public bool IsTeamBased { get; set; }
|
||||
|
||||
public ServerStats(EFServer sv, EFServerStatistics st)
|
||||
{
|
||||
@ -23,5 +20,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
||||
ServerStatistics = st;
|
||||
Server = sv;
|
||||
}
|
||||
|
||||
public int TeamCount(IW4Info.Team teamName)
|
||||
{
|
||||
if (PlayerStats.Count(p => p.Value.Team == IW4Info.Team.None) / (double)PlayerStats.Count <= 0.25)
|
||||
{
|
||||
return IsTeamBased ? Math.Max(PlayerStats.Count(p => p.Value.Team == teamName), 1) : Math.Max(PlayerStats.Count - 1, 1);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return IsTeamBased ? (int)Math.Max(Math.Floor(PlayerStats.Count / 2.0), 1) : Math.Max(PlayerStats.Count - 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user