Compare commits

..

45 Commits

Author SHA1 Message Date
02622ea7de update application version
ignore stat events of bots if they are ignored
limit max number of bot profiles to 18, greater than 18 wraps
prevent anti cheat from running on bot events
create localization folder on publish so copying over doesn't fail
include quick message mapping on webfront server history chat
make gravatar on profile not repeat
2019-04-25 13:00:54 -05:00
03ae3b5822 deleted localization files as they're now generated during release
reintroduce throttling for servers that are unreachable (defaults to 60 seconds between polls)
small revert to the RektT5M parser contell -> tell
add migration to introduce gamename to server
impllement quickmessage mapping
2019-04-23 17:27:20 -05:00
0711249a46 add parser for RektT5M
update base event parser to include "short" GUIDs
updated some localization
add tooltip to anti cheat metrics on profile for more information about what they mean
2019-04-21 16:28:13 -05:00
18f4ffa9ff fix extra prompt for server port
change vpn detection failure to warning instead of error
2019-04-18 14:19:50 -05:00
934fead5c2 fix issue with login 2019-04-17 17:50:53 -05:00
02cad10d77 prevent starting if no servers can be connected to
fix nextmap issue on t6
fix bug with kicking client for profane name
2019-04-16 11:32:42 -05:00
b134cd4728 fix gravatars not showing
fix web config not saving Uri
fix issue with token login
2019-04-14 10:55:05 -05:00
b9c4a1b5f6 finish initial implementing application configuration editing through webfront
todo: server configuration
2019-04-12 22:25:18 -05:00
f0fd4c66e9 add configuration option to force local translations
fix silly bug with no being able to claim ownership
continue work on configuration via webfront
2019-04-11 20:43:05 -05:00
52fe8fc847 !setgravatar uses meta service now
update certain prompts to use interpolated strings from translation
update application version
2019-04-09 15:02:49 -05:00
9f8c35dbed fix bug with chat context timestamps not parsing is different machine locales
add disallowed client names to default config
fix ping not working for targets
2019-04-08 20:31:32 -05:00
9d9be7f8af few more small fixes
complete join button on webfront
update for 2.2.6.0
2019-04-08 12:29:48 -05:00
dd82a5e3fa start add of join button (still need to grab the external IP address)
finish up fixes for alias stuff
2019-04-07 20:14:59 -05:00
8667532d24 remove create proxy as it's not even used anymore
more fixes for alias stuff
hopefully fix rare bug where client activity cshtml loop goes oob
add URLProtocol format to event parsers to allow connecting through webfront
2019-04-06 21:48:49 -05:00
863ba8b096 strip drive letter on gamelog server if running on linux
strip undecodable chacters from gamelog server log file
finish re work on alias add/update ( I think)
2019-04-05 21:15:17 -05:00
8ab89e113d clean up log reader/make it output more useful message if things go wrong
add unflag as a penalty
show bans/tempbans even after they've expired on penalty list
continue making alias links great again
2019-04-05 13:34:03 -05:00
00634780d4 use "world client" when recieving fall damage/damage
fix rare bug with GetClientByName
refactor some alias/ef stuff. still more to do
2019-04-02 20:20:37 -05:00
6f80f1edbb refine webfront pages
finish refactor of penalty information/profile
optimize pull penalty query
start impl of quick message mapping
2019-03-31 19:56:31 -05:00
9393b35c39 start implementation of configuration via webfront 2019-03-30 22:04:15 -05:00
37d3f4f90d changes for latest release 2019-03-30 17:21:01 -05:00
8521df85f5 finish initial rework of profile page with meta pagination 2019-03-29 21:56:56 -05:00
7b8126d57e continue rework of profile
start moving profile info out of javascript into componentview
rework meta data to include count and offset
2019-03-27 19:40:26 -05:00
1e2e2218e3 finish initial rework of profile page 2019-03-26 21:02:11 -05:00
11e3235d5d fix issue with not loading last connection for admins
continue work on fixing profile
2019-03-25 21:12:16 -05:00
cae6d8389e fix bug with privileged users not always showing the most recent profile
temporary bans are now applied to all linked accounts instead of a per-guid basis
rework set level flow
add guid and ip address (if logged in) to public async endpoint
2019-03-24 21:34:20 -05:00
1056c7c335 fix for issue #70
update start script for windows
2019-03-18 10:36:31 -05:00
95e4ee672e Merge branch 'master' of https://github.com/RaidMax/IW4M-Admin 2019-03-17 17:38:17 -05:00
bf0a0befc6 fix top players row not fill full width
make stats web use view imports
add more games/maps to default settings (thanks to FryTechTiddys
#7622)
replace ConfigurationBuilder Config Handler implementation with Newtonsoft.Json as it wasn't deserializing "complex" classes properly
2019-03-17 17:37:50 -05:00
53c3ff6ce3 2 typos fixed
Plutoniun -> Plutonium
IW3 -> IW4
2019-03-17 08:06:15 -05:00
c8ec0eefa9 game log reader reads async now.
should have done that a long time ago
update profile page to have a bit better space usage
2019-03-09 10:28:04 -06:00
82bae772f0 most played command now ordered by play time
issue #68
2019-03-02 17:29:09 -06:00
af3aea77bc finish UI tweaks for issue #39 2019-03-02 14:29:09 -06:00
b3e5f468a1 finish implementation of per server top stats page 2019-02-27 20:13:15 -06:00
c21bf2ebf1 continue working on per servver topstats 2019-02-26 21:25:27 -06:00
d318a57830 start implementation for per server top stats
issue #39
2019-02-25 19:36:10 -06:00
61f1436faf prompt user to continue if not all servers can be connected
issue #58
2019-02-24 19:35:59 -06:00
0c527a5f65 Add lock menu icon for tempbanned players
issue #62
2019-02-24 19:07:56 -06:00
1457b843e2 one more small fix for meta service after testing live 2019-02-22 19:37:53 -06:00
de69bed792 small migration fix for MySql 2019-02-22 19:35:03 -06:00
4b1f44cc2a re-enable login to webfront with password
update cookie to last 3 months
add configuration option to limit # of rss feed items
prevent database tracking of bots if ignore bots requested
add last map and last server played to profile
2019-02-22 19:06:51 -06:00
74cdf8e885 Fix bug introduced with auto messages 2019-02-19 19:39:09 -06:00
5d41059641 accidentally copied a file to the wrong project 2019-02-18 19:48:28 -06:00
2e6889d9bb implement RSS feed in auto messages for issue #53
modified automessages to use async mesthods instead of synchronous
2019-02-18 19:30:38 -06:00
9c4d23f0b4 enhancement for issue #63 2019-02-17 18:48:40 -06:00
b4e3e8526a fix for issue #66 2019-02-17 13:16:48 -06:00
134 changed files with 5334 additions and 2991 deletions

5
.gitignore vendored
View File

@ -223,7 +223,7 @@ global.min.js
bootstrap-custom.min.css
**/Master/static
**/Master/dev_env
/WebfrontCore/Views/Plugins/Stats
/WebfrontCore/Views/Plugins/*
/WebfrontCore/wwwroot/**/dds
/DiscordWebhook/env
@ -232,4 +232,5 @@ bootstrap-custom.min.css
launchSettings.json
/VpnDetectionPrivate.js
/Plugins/ScriptPlugins/VpnDetectionPrivate.js
**/Master/env_master
**/Master/env_master
/GameLogServer/log_env

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using RestEase;
using SharedLibraryCore;
@ -11,6 +12,7 @@ namespace IW4MAdmin.Application.API.Master
public class HeartbeatState
{
public bool Connected { get; set; }
public CancellationToken Token { get; set; }
}
public class Heartbeat

View File

@ -6,7 +6,7 @@
<RuntimeFrameworkVersion>2.2.2</RuntimeFrameworkVersion>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2.2.5.2</Version>
<Version>2.2.6.5</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Product>IW4MAdmin</Product>
@ -31,8 +31,8 @@
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
<TieredCompilation>true</TieredCompilation>
<AssemblyVersion>2.2.5.2</AssemblyVersion>
<FileVersion>2.2.5.2</FileVersion>
<AssemblyVersion>2.2.6.5</AssemblyVersion>
<FileVersion>2.2.6.5</FileVersion>
</PropertyGroup>
<ItemGroup>
@ -49,18 +49,6 @@
<None Update="DefaultSettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<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>

View File

@ -7,10 +7,12 @@ using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Events;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Services;
using System;
using System.Collections.Generic;
@ -39,10 +41,9 @@ namespace IW4MAdmin.Application
public IList<IRConParser> AdditionalRConParsers { get; }
public IList<IEventParser> AdditionalEventParsers { get; }
public ITokenAuthentication TokenAuthenticator => Authenticator;
public ITokenAuthentication Authenticator => _authenticator;
public string ExternalIPAddress { get; private set; }
static ApplicationManager Instance;
readonly List<AsyncStatus> TaskStatuses;
@ -58,6 +59,8 @@ namespace IW4MAdmin.Application
readonly SemaphoreSlim ProcessingEvent = new SemaphoreSlim(1, 1);
readonly Dictionary<long, ILogger> Loggers = new Dictionary<long, ILogger>();
readonly ITokenAuthentication _authenticator;
private readonly MetaService _metaService;
private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0);
private ApplicationManager()
{
@ -77,6 +80,7 @@ namespace IW4MAdmin.Application
OnServerEvent += OnGameEvent;
OnServerEvent += EventApi.OnGameEvent;
_authenticator = new TokenAuthentication();
_metaService = new MetaService();
}
private async void OnGameEvent(object sender, GameEventArgs args)
@ -129,7 +133,7 @@ namespace IW4MAdmin.Application
catch (Exception ex)
{
newEvent.FailReason = GameEvent.EventFailReason.Exception;
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EXCEPTION"]} {newEvent.Owner}");
Logger.WriteError(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_EXCEPTION"].FormatExt(newEvent.Owner));
Logger.WriteDebug(ex.GetExceptionInfo());
}
@ -154,7 +158,7 @@ namespace IW4MAdmin.Application
return Instance ?? (Instance = new ApplicationManager());
}
public async Task UpdateServerStates()
public async Task UpdateServerStates(CancellationToken token)
{
// store the server hash code and task for it
var runningUpdateTasks = new Dictionary<long, Task>();
@ -190,7 +194,11 @@ namespace IW4MAdmin.Application
{
try
{
await server.ProcessUpdatesAsync(new CancellationToken());
await server.ProcessUpdatesAsync(token);
if (server.Throttled)
{
await Task.Delay((int)_throttleTimeout.TotalMilliseconds);
}
}
catch (Exception e)
@ -198,6 +206,11 @@ namespace IW4MAdmin.Application
Logger.WriteWarning($"Failed to update status for {server}");
Logger.WriteDebug(e.GetExceptionInfo());
}
finally
{
server.IsInitialized = true;
}
}));
}
#if DEBUG
@ -206,17 +219,19 @@ namespace IW4MAdmin.Application
ThreadPool.GetAvailableThreads(out int availableThreads, out int m);
Logger.WriteDebug($"There are {workerThreads - availableThreads} active threading tasks");
#endif
await Task.Delay(ConfigHandler.Configuration().RConPollRate);
try
{
await Task.Delay(ConfigHandler.Configuration().RConPollRate, token);
}
// if a cancellation is received, we want to return immediately
catch { break; }
}
// trigger the event processing loop to end
SetHasEvent();
}
public async Task Init()
{
Running = true;
ExternalIPAddress = await Utilities.GetExternalIP();
#region PLUGINS
SharedLibraryCore.Plugins.PluginImporter.Load(this);
@ -246,10 +261,11 @@ namespace IW4MAdmin.Application
ConfigHandler.Set((ApplicationConfiguration)new ApplicationConfiguration().Generate());
var newConfig = ConfigHandler.Configuration();
newConfig.AutoMessagePeriod = defaultConfig.AutoMessagePeriod;
newConfig.AutoMessages = defaultConfig.AutoMessages;
newConfig.GlobalRules = defaultConfig.GlobalRules;
newConfig.Maps = defaultConfig.Maps;
newConfig.DisallowedClientNames = defaultConfig.DisallowedClientNames;
newConfig.QuickMessages = defaultConfig.QuickMessages;
if (newConfig.Servers == null)
{
@ -277,7 +293,7 @@ namespace IW4MAdmin.Application
}
}
else if (config != null)
else
{
if (string.IsNullOrEmpty(config.Id))
{
@ -313,7 +329,7 @@ namespace IW4MAdmin.Application
}
}
else if (config.Servers.Count == 0)
if (config.Servers.Count == 0)
{
throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid");
}
@ -384,11 +400,139 @@ namespace IW4MAdmin.Application
}
#endregion
#region META
async Task<List<ProfileMeta>> getProfileMeta(int clientId, int offset, int count, DateTime? startAt)
{
var metaList = new List<ProfileMeta>();
// we don't want to return anything because it means we're trying to retrieve paged meta data
if (count > 1)
{
return metaList;
}
var lastMapMeta = await _metaService.GetPersistentMeta("LastMapPlayed", new EFClient() { ClientId = clientId });
if (lastMapMeta != null)
{
metaList.Add(new ProfileMeta()
{
Id = lastMapMeta.MetaId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_MAP"],
Value = lastMapMeta.Value,
Show = true,
Type = ProfileMeta.MetaType.Information,
});
}
var lastServerMeta = await _metaService.GetPersistentMeta("LastServerPlayed", new EFClient() { ClientId = clientId });
if (lastServerMeta != null)
{
metaList.Add(new ProfileMeta()
{
Id = lastServerMeta.MetaId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_SERVER"],
Value = lastServerMeta.Value,
Show = true,
Type = ProfileMeta.MetaType.Information
});
}
var client = await GetClientService().Get(clientId);
metaList.Add(new ProfileMeta()
{
Id = client.ClientId,
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]} {Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_PLAYER"]}",
Value = Math.Round(client.TotalConnectionTime / 3600.0, 1).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Show = true,
Column = 1,
Order = 0,
Type = ProfileMeta.MetaType.Information
});
metaList.Add(new ProfileMeta()
{
Id = client.ClientId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_FSEEN"],
Value = Utilities.GetTimePassed(client.FirstConnection, false),
Show = true,
Column = 1,
Order = 1,
Type = ProfileMeta.MetaType.Information
});
metaList.Add(new ProfileMeta()
{
Id = client.ClientId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_LSEEN"],
Value = Utilities.GetTimePassed(client.LastConnection, false),
Show = true,
Column = 1,
Order = 2,
Type = ProfileMeta.MetaType.Information
});
metaList.Add(new ProfileMeta()
{
Id = client.ClientId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_CONNECTIONS"],
Value = client.Connections.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Show = true,
Column = 1,
Order = 3,
Type = ProfileMeta.MetaType.Information
});
metaList.Add(new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_MASKED"],
Value = client.Masked ? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"] : Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"],
Sensitive = true,
Column = 1,
Order = 4,
Type = ProfileMeta.MetaType.Information
});
return metaList;
}
async Task<List<ProfileMeta>> getPenaltyMeta(int clientId, int offset, int count, DateTime? startAt)
{
if (count <= 1)
{
return new List<ProfileMeta>();
}
var penalties = await GetPenaltyService().GetClientPenaltyForMetaAsync(clientId, count, offset, startAt);
return penalties.Select(_penalty => new ProfileMeta()
{
Id = _penalty.Id,
Type = _penalty.PunisherId == clientId ? ProfileMeta.MetaType.Penalized : ProfileMeta.MetaType.ReceivedPenalty,
Value = _penalty,
When = _penalty.TimePunished,
Sensitive = _penalty.Sensitive
})
.ToList();
}
MetaService.AddRuntimeMeta(getProfileMeta);
MetaService.AddRuntimeMeta(getPenaltyMeta);
#endregion
#region INIT
int failedServers = 0;
int successServers = 0;
Exception lastException = null;
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);
@ -399,7 +543,7 @@ namespace IW4MAdmin.Application
_servers.Add(ServerInstance);
}
Logger.WriteVerbose($"{Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"]} {ServerInstance.Hostname}");
Logger.WriteVerbose(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"].FormatExt(ServerInstance.Hostname));
// add the start event for this server
var e = new GameEvent()
@ -410,26 +554,37 @@ namespace IW4MAdmin.Application
};
Handler.AddEvent(e);
successServers++;
}
catch (ServerException e)
{
Logger.WriteError($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"]} [{Conf.IPAddress}:{Conf.Port}]");
Logger.WriteError(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_UNFIXABLE"].FormatExt($"[{Conf.IPAddress}:{Conf.Port}]"));
if (e.GetType() == typeof(DvarException))
{
Logger.WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"]} {(e as DvarException).Data["dvar_name"]} ({Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR_HELP"]})");
Logger.WriteDebug($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"].FormatExt((e as DvarException).Data["dvar_name"])} ({Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR_HELP"]})");
}
else if (e.GetType() == typeof(NetworkException))
{
Logger.WriteDebug(e.Message);
}
// throw the exception to the main method to stop before instantly exiting
throw e;
failedServers++;
lastException = e;
}
}
await Task.WhenAll(config.Servers.Select(c => Init(c)).ToArray());
if (successServers - failedServers <= 0)
{
if (!Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_START_WITH_ERRORS"]))
{
throw lastException;
}
}
#endregion
}
@ -494,27 +649,34 @@ namespace IW4MAdmin.Application
}
}
await Task.Delay(30000);
try
{
await Task.Delay(30000, heartbeatState.Token);
}
catch { break; }
}
}
public void Start()
{
var tokenSource = new CancellationTokenSource();
// this needs to be run seperately from the main thread
var _ = Task.Run(() => SendHeartbeat(new HeartbeatState()));
_ = Task.Run(() => UpdateServerStates());
_ = Task.Run(() => SendHeartbeat(new HeartbeatState() { Token = tokenSource.Token }));
_ = Task.Run(() => UpdateServerStates(tokenSource.Token));
while (Running)
{
OnQuit.Wait();
tokenSource.Cancel();
OnQuit.Reset();
}
_servers.Clear();
}
public void Stop()
{
Running = false;
OnQuit.Set();
}
public ILogger GetLogger(long serverId)
@ -589,7 +751,7 @@ namespace IW4MAdmin.Application
public void SetHasEvent()
{
OnQuit.Set();
}
public IList<Assembly> GetPluginAssemblies()

View File

@ -97,11 +97,19 @@ if "%CurrentConfiguration%" == "Release" (
if exist "%SolutionDir%Publish\Windows\refs" move "%SolutionDir%Publish\Windows\refs" "%SolutionDir%Publish\Windows\Lib\refs"
)
if "%CurrentConfiguration%" == "Prerelease" (
echo PR-LOC
if not exist "%SolutionDir%Publish\WindowsPrerelease\Localization" md "%SolutionDir%Publish\WindowsPrerelease\Localization"
)
if "%CurrentConfiguration%" == "Release" (
echo R-LOC
if not exist "%SolutionDir%Publish\Windows\Localization" md "%SolutionDir%Publish\Windows\Localization"
)
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 @echo off && echo @title IW4MAdmin && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%SolutionDir%Publish\WindowsPrerelease\StartIW4MAdmin.cmd"
@(echo @echo off && echo @title IW4MAdmin && 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%"
@(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"

View File

@ -16,7 +16,231 @@
"Keep grenade launcher use to a minimum",
"Balance teams at ALL times"
],
"DisallowedClientNames": ["Unknown Soldier", "VickNet", "UnknownSoldier", "CHEATER"],
"QuickMessages": [
{
"Game": "IW4",
"Messages": {
"QUICKMESSAGE_AREA_SECURE": "Area secure!",
"QUICKMESSAGE_ARE_YOU_CRAZY": "Are you crazy?",
"QUICKMESSAGE_ATTACK_LEFT_FLANK": "Attack left flank!",
"QUICKMESSAGE_ATTACK_RIGHT_FLANK": "Attack right flank!",
"QUICKMESSAGE_COME_ON": "Come on.",
"QUICKMESSAGE_ENEMIES_SPOTTED": "Multiple contacts!",
"QUICKMESSAGE_ENEMY_DOWN": "Enemy down!",
"QUICKMESSAGE_ENEMY_GRENADE": "Enemy grenade!",
"QUICKMESSAGE_ENEMY_SPOTTED": "Contact!",
"QUICKMESSAGE_FALL_BACK": "Fall back!",
"QUICKMESSAGE_FOLLOW_ME": "On me!",
"QUICKMESSAGE_GREAT_SHOT": "Nice shot!",
"QUICKMESSAGE_GRENADE": "Grenade!",
"QUICKMESSAGE_HOLD_THIS_POSITION": "Hold this position!",
"QUICKMESSAGE_HOLD_YOUR_FIRE": "Hold your fire!",
"QUICKMESSAGE_IM_IN_POSITION": "In position.",
"QUICKMESSAGE_IM_ON_MY_WAY": "Moving.",
"QUICKMESSAGE_MOVE_IN": "Move in!",
"QUICKMESSAGE_NEED_REINFORCEMENTS": "Need reinforcements!",
"QUICKMESSAGE_NO_SIR": "Negative.",
"QUICKMESSAGE_ON_MY_WAY": "On my way.",
"QUICKMESSAGE_REGROUP": "Regroup!",
"QUICKMESSAGE_SNIPER": "Sniper!",
"QUICKMESSAGE_SORRY": "Sorry.",
"QUICKMESSAGE_SQUAD_ATTACK_LEFT_FLANK": "Squad, attack left flank!",
"QUICKMESSAGE_SQUAD_ATTACK_RIGHT_FLANK": "Squad, attack right flank!",
"QUICKMESSAGE_SQUAD_HOLD_THIS_POSITION": "Squad, hold this position!",
"QUICKMESSAGE_SQUAD_REGROUP": "Squad, regroup!",
"QUICKMESSAGE_SQUAD_STICK_TOGETHER": "Squad, stick together!",
"QUICKMESSAGE_STICK_TOGETHER": "Stick together!",
"QUICKMESSAGE_SUPPRESSING_FIRE": "Base of fire!",
"QUICKMESSAGE_TOOK_LONG_ENOUGH": "Took long enough!",
"QUICKMESSAGE_TOOK_YOU_LONG_ENOUGH": "Took you long enough!",
"QUICKMESSAGE_WATCH_SIX": "Watch your six!",
"QUICKMESSAGE_YES_SIR": "Roger.",
"QUICKMESSAGE_YOURE_CRAZY": "You're crazy!",
"QUICKMESSAGE_YOURE_NUTS": "You're nuts!",
"QUICKMESSAGE_YOU_OUTTA_YOUR_MIND": "You outta your mind?"
}
}
],
"Maps": [
{
"Game": "IW3",
"Maps": [
{
"Alias": "Ambush",
"Name": "mp_convoy"
},
{
"Alias": "Backlot",
"Name": "mp_backlot"
},
{
"Alias": "Bloc",
"Name": "mp_bloc"
},
{
"Alias": "Bog",
"Name": "mp_bog"
},
{
"Alias": "Countdown",
"Name": "mp_countdown"
},
{
"Alias": "Crash",
"Name": "mp_crash"
},
{
"Alias": "Crossfire",
"Name": "mp_crossfire"
},
{
"Alias": "District",
"Name": "mp_citystreets"
},
{
"Alias": "Downpour",
"Name": "mp_farm"
},
{
"Alias": "Overgrown",
"Name": "mp_overgrown"
},
{
"Alias": "Pipeline",
"Name": "mp_pipeline"
},
{
"Alias": "Shipment",
"Name": "mp_shipment"
},
{
"Alias": "Showdown",
"Name": "mp_showdown"
},
{
"Alias": "Strike",
"Name": "mp_strike"
},
{
"Alias": "Vacant",
"Name": "mp_vacant"
},
{
"Alias": "Wet Work",
"Name": "mp_cargoship"
},
{
"Alias": "Winter Crash",
"Name": "mp_crash_snow"
},
{
"Alias": "Broadcast",
"Name": "mp_broadcast"
},
{
"Alias": "Creek",
"Name": "mp_creek"
},
{
"Alias": "Chinatown",
"Name": "mp_carentan"
},
{
"Alias": "Killhouse",
"Name": "mp_killhouse"
}
]
},
{
"Game": "T4",
"Maps": [
{
"Alias": "Airfield",
"Name": "mp_airfield"
},
{
"Alias": "Asylum",
"Name": "mp_asylum"
},
{
"Alias": "Castle",
"Name": "mp_castle"
},
{
"Alias": "Cliffside",
"Name": "mp_shrine"
},
{
"Alias": "Courtyard",
"Name": "mp_courtyard"
},
{
"Alias": "Dome",
"Name": "mp_dome"
},
{
"Alias": "Downfall",
"Name": "mp_downfall"
},
{
"Alias": "Hanger",
"Name": "mp_hangar"
},
{
"Alias": "Makin",
"Name": "mp_makin"
},
{
"Alias": "Outskirts",
"Name": "mp_outskirts"
},
{
"Alias": "Roundhouse",
"Name": "mp_roundhouse"
},
{
"Alias": "Upheaval",
"Name": "mp_suburban"
},
{
"Alias": "Knee Deep",
"Name": "mp_kneedeep"
},
{
"Alias": "Nightfire",
"Name": "mp_nachtfeuer"
},
{
"Alias": "Station",
"Name": "mp_subway"
},
{
"Alias": "Banzai",
"Name": "mp_kwai"
},
{
"Alias": "Corrosion",
"Name": "mp_stalingrad"
},
{
"Alias": "Sub Pens",
"Name": "mp_docks"
},
{
"Alias": "Battery",
"Name": "mp_drum"
},
{
"Alias": "Breach",
"Name": "mp_bgate"
},
{
"Alias": "Revolution",
"Name": "mp_vodka"
}
]
},
{
"Game": "IW4",
"Maps": [
@ -246,6 +470,260 @@
}
]
},
{
"Game": "T5",
"Maps": [
{
"Alias": "Array",
"Name": "mp_array"
},
{
"Alias": "Berlin Wall",
"Name": "mp_berlinwall2"
},
{
"Alias": "Convoy",
"Name": "mp_gridlock"
},
{
"Alias": "Cracked",
"Name": "mp_cracked"
},
{
"Alias": "Crisis",
"Name": "mp_crisis"
},
{
"Alias": "Discovery",
"Name": "mp_discovery"
},
{
"Alias": "Drive-In",
"Name": "mp_drivein"
},
{
"Alias": "Firing Range",
"Name": "mp_firingrange"
},
{
"Alias": "Grid",
"Name": "mp_duga"
},
{
"Alias": "Hangar 18",
"Name": "mp_area51"
},
{
"Alias": "Hanoi",
"Name": "mp_hanoi"
},
{
"Alias": "Hazard",
"Name": "mp_golfcourse"
},
{
"Alias": "Hotel",
"Name": "mp_hotel"
},
{
"Alias": "Jungle",
"Name": "mp_havoc"
},
{
"Alias": "Kowloon",
"Name": "mp_kowloon"
},
{
"Alias": "Launch",
"Name": "mp_cosmodrome"
},
{
"Alias": "Nuketown",
"Name": "mp_nuked"
},
{
"Alias": "Radiation",
"Name": "mp_radiation"
},
{
"Alias": "Silo",
"Name": "mp_silo"
},
{
"Alias": "Stadium",
"Name": "mp_stadium"
},
{
"Alias": "Stockpile",
"Name": "mp_outskirts"
},
{
"Alias": "Summit",
"Name": "mp_mountain"
},
{
"Alias": "Villa",
"Name": "mp_villa"
},
{
"Alias": "WMD",
"Name": "mp_russianbase"
},
{
"Alias": "Zoo",
"Name": "mp_zoo"
}
]
},
{
"Game": "IW5",
"Maps": [
{
"Alias": "Seatown",
"Name": "mp_seatown"
},
{
"Alias": "Lockdown",
"Name": "mp_alpha"
},
{
"Alias": "Mission",
"Name": "mp_bravo"
},
{
"Alias": "Carbon",
"Name": "mp_carbon"
},
{
"Alias": "Dome",
"Name": "mp_dome"
},
{
"Alias": "Arkaden",
"Name": "mp_plaza2"
},
{
"Alias": "Downturn",
"Name": "mp_exchange"
},
{
"Alias": "Bootleg",
"Name": "mp_bootleg"
},
{
"Alias": "Hardhat",
"Name": "mp_hardhat"
},
{
"Alias": "Interchange",
"Name": "mp_interchange"
},
{
"Alias": "Fallen",
"Name": "mp_lambeth"
},
{
"Alias": "Outpost",
"Name": "mp_radar"
},
{
"Alias": "Bakaara",
"Name": "mp_mogadishu"
},
{
"Alias": "Resistance",
"Name": "mp_paris"
},
{
"Alias": "Underground",
"Name": "mp_underground"
},
{
"Alias": "Village",
"Name": "mp_village"
},
{
"Alias": "Aground",
"Name": "mp_aground_ss"
},
{
"Alias": "Boardwalk",
"Name": "mp_boardwalk"
},
{
"Alias": "U-turn",
"Name": "mp_burn_ss"
},
{
"Alias": "Foundation",
"Name": "mp_cement"
},
{
"Alias": "Erosion",
"Name": "mp_courtyard_ss"
},
{
"Alias": "Intersection",
"Name": "mp_crosswalk_ss"
},
{
"Alias": "Getaway",
"Name": "mp_hillside_ss"
},
{
"Alias": "Piazza",
"Name": "mp_italy"
},
{
"Alias": "Sanctuary",
"Name": "mp_meteora"
},
{
"Alias": "Gulch",
"Name": "mp_moab"
},
{
"Alias": "Black Box",
"Name": "mp_morningwood"
},
{
"Alias": "Parish",
"Name": "mp_nola"
},
{
"Alias": "Overwatch",
"Name": "mp_overwatch"
},
{
"Alias": "Liberation",
"Name": "mp_park"
},
{
"Alias": "Oasis",
"Name": "mp_qadeem"
},
{
"Alias": "Lookout",
"Name": "mp_restrepo_ss"
},
{
"Alias": "Off Shore",
"Name": "mp_roughneck"
},
{
"Alias": "Decommission",
"Name": "mp_shipbreaker"
},
{
"Alias": "Vortex",
"Name": "mp_six_ss"
},
{
"Alias": "Terminal",
"Name": "mp_terminal_cls"
}
]
},
{
"Game": "T6",
"Maps": [
@ -402,95 +880,6 @@
"Name": "zm_transit"
}
]
},
{
"Game": "IW3",
"Maps": [
{
"Alias": "Ambush",
"Name": "mp_convoy"
},
{
"Alias": "Backlot",
"Name": "mp_backlot"
},
{
"Alias": "Bloc",
"Name": "mp_bloc"
},
{
"Alias": "Bog",
"Name": "mp_bog"
},
{
"Alias": "Countdown",
"Name": "mp_countdown"
},
{
"Alias": "Crash",
"Name": "mp_crash"
},
{
"Alias": "Crossfire",
"Name": "mp_crossfire"
},
{
"Alias": "District",
"Name": "mp_citystreets"
},
{
"Alias": "Downpour",
"Name": "mp_farm"
},
{
"Alias": "Overgrown",
"Name": "mp_overgrown"
},
{
"Alias": "Pipeline",
"Name": "mp_pipeline"
},
{
"Alias": "Shipment",
"Name": "mp_shipment"
},
{
"Alias": "Showdown",
"Name": "mp_showdown"
},
{
"Alias": "Strike",
"Name": "mp_strike"
},
{
"Alias": "Vacant",
"Name": "mp_vacant"
},
{
"Alias": "Wet Work",
"Name": "mp_cargoship"
},
{
"Alias": "Winter Crash",
"Name": "mp_crash_snow"
},
{
"Alias": "Broadcast",
"Name": "mp_broadcast"
},
{
"Alias": "Creek",
"Name": "mp_creek"
},
{
"Alias": "Chinatown",
"Name": "mp_carentan"
},
{
"Alias": "Killhouse",
"Name": "mp_killhouse"
}
]
}
]
}

View File

@ -17,26 +17,26 @@ namespace IW4MAdmin.Application.EventParsers
GameDirectory = "main",
};
Configuration.Say.Pattern = @"^(say|sayteam);(-?[A-Fa-f0-9_]{8,32});([0-9]+);(.+);(.*)$";
Configuration.Say.Pattern = @"^(say|sayteam);(-?[A-Fa-f0-9_]{1,32});([0-9]+);(.+);(.*)$";
Configuration.Say.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Say.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.Say.AddMapping(ParserRegex.GroupType.Message, 5);
Configuration.Quit.Pattern = @"^(Q);(-?[A-Fa-f0-9_]{8,32}|bot[0-9]+);([0-9]+);(.*)$";
Configuration.Quit.Pattern = @"^(Q);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+);([0-9]+);(.*)$";
Configuration.Quit.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Quit.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.Join.Pattern = @"^(J);(-?[A-Fa-f0-9_]{8,32}|bot[0-9]+);([0-9]+);(.*)$";
Configuration.Join.Pattern = @"^(J);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+);([0-9]+);(.*)$";
Configuration.Join.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{8,32}|bot[0-9]+);(-?[0-9]+);(axis|allies|world);(.{1,24});(-?[A-Fa-f0-9_]{8,32}|bot[0-9]+)?;-?([0-9]+);(axis|allies|world);(.{1,24})?;((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+);(-?[0-9]+);(axis|allies|world);(.{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+)?;-?([0-9]+);(axis|allies|world);(.{1,24})?;((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Damage.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
@ -51,7 +51,7 @@ namespace IW4MAdmin.Application.EventParsers
Configuration.Damage.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12);
Configuration.Damage.AddMapping(ParserRegex.GroupType.HitLocation, 13);
Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{8,32}|bot[0-9]+);(-?[0-9]+);(axis|allies|world);(.{1,24});(-?[A-Fa-f0-9_]{8,32}|bot[0-9]+)?;-?([0-9]+);(axis|allies|world);(.{1,24})?;((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+);(-?[0-9]+);(axis|allies|world);(.{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+)?;-?([0-9]+);(axis|allies|world);(.{1,24})?;((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Kill.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
@ -73,6 +73,8 @@ namespace IW4MAdmin.Application.EventParsers
public Game GameName { get; set; } = Game.COD;
public string URLProtocolFormat { get; set; } = "CoD://{{ip}}:{{port}}";
public virtual GameEvent GetEvent(Server server, string logLine)
{
logLine = Regex.Replace(logLine, @"([0-9]+:[0-9]+ |^[0-9]+ )", "").Trim();
@ -139,11 +141,16 @@ namespace IW4MAdmin.Application.EventParsers
if (match.Success)
{
var origin = server.GetClientsAsList()
.First(c => c.NetworkId == match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong());
var target = server.GetClientsAsList()
.First(c => c.NetworkId == match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertLong());
string originId = match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].Value.ToString();
string targetId = match.Groups[Configuration.Kill.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].Value.ToString();
var origin = !string.IsNullOrEmpty(originId) ? server.GetClientsAsList()
.First(c => c.NetworkId == originId.ConvertLong()) :
Utilities.IW4MAdminClient(server);
var target = !string.IsNullOrEmpty(targetId) ? server.GetClientsAsList()
.First(c => c.NetworkId == targetId.ConvertLong()) :
Utilities.IW4MAdminClient(server);
return new GameEvent()
{
@ -159,8 +166,14 @@ namespace IW4MAdmin.Application.EventParsers
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());
long originId = lineSplit[1].ConvertLong();
long targetId = lineSplit[2].ConvertLong();
var origin = originId == long.MinValue ? Utilities.IW4MAdminClient(server) :
server.GetClientsAsList().First(c => c.NetworkId == originId);
var target = targetId == long.MinValue ? Utilities.IW4MAdminClient(server) :
server.GetClientsAsList().FirstOrDefault(c => c.NetworkId == targetId) ?? Utilities.IW4MAdminClient(server);
return new GameEvent()
{
Type = GameEvent.EventType.ScriptKill,
@ -173,8 +186,13 @@ namespace IW4MAdmin.Application.EventParsers
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());
long originId = lineSplit[1].ConvertLong();
long targetId = lineSplit[2].ConvertLong();
var origin = originId == long.MinValue ? Utilities.IW4MAdminClient(server) :
server.GetClientsAsList().First(c => c.NetworkId == originId);
var target = targetId == long.MinValue ? Utilities.IW4MAdminClient(server) :
server.GetClientsAsList().FirstOrDefault(c => c.NetworkId == targetId) ?? Utilities.IW4MAdminClient(server);
return new GameEvent()
{
@ -195,10 +213,16 @@ namespace IW4MAdmin.Application.EventParsers
if (regexMatch.Success)
{
var origin = server.GetClientsAsList()
.First(c => c.NetworkId == regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong());
var target = server.GetClientsAsList()
.First(c => c.NetworkId == regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString().ConvertLong());
string originId = regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString();
string targetId = regexMatch.Groups[Configuration.Damage.GroupMapping[ParserRegex.GroupType.TargetNetworkId]].ToString();
var origin = !string.IsNullOrEmpty(originId) ? server.GetClientsAsList()
.First(c => c.NetworkId == originId.ConvertLong()) :
Utilities.IW4MAdminClient(server);
var target = !string.IsNullOrEmpty(targetId) ? server.GetClientsAsList()
.First(c => c.NetworkId == targetId.ConvertLong()) :
Utilities.IW4MAdminClient(server);
return new GameEvent()
{
@ -229,7 +253,6 @@ namespace IW4MAdmin.Application.EventParsers
{
CurrentAlias = new EFAlias()
{
Active = false,
Name = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors(),
},
NetworkId = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(),
@ -256,7 +279,6 @@ namespace IW4MAdmin.Application.EventParsers
{
CurrentAlias = new EFAlias()
{
Active = false,
Name = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors()
},
NetworkId = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(),

View File

@ -30,7 +30,7 @@ namespace IW4MAdmin.Application.IO
{
while (!Server.Manager.ShutdownRequested())
{
if ((Server.Manager as ApplicationManager).IsInitialized)
if (Server.IsInitialized)
{
try
{

View File

@ -34,9 +34,9 @@ namespace IW4MAdmin.Application.IO
// 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()))
while (!string.IsNullOrEmpty(newLine = await rd.ReadLineAsync()))
{
logLines.Add(newLine);
}

View File

@ -9,6 +9,7 @@ using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Localization;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Services;
using System;
using System.Collections.Generic;
using System.IO;
@ -17,6 +18,7 @@ using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
namespace IW4MAdmin
{
@ -25,6 +27,7 @@ namespace IW4MAdmin
private static readonly Index loc = Utilities.CurrentLocalization.LocalizationIndex;
private GameLogEventDetection LogEvent;
private DateTime SessionStart;
public int Id { get; private set; }
public IW4MServer(IManager mgr, ServerConfiguration cfg) : base(mgr, cfg)
@ -34,7 +37,6 @@ namespace IW4MAdmin
override public async Task OnClientConnected(EFClient clientFromLog)
{
Logger.WriteDebug($"Client slot #{clientFromLog.ClientNumber} now reserved");
Clients[clientFromLog.ClientNumber] = new EFClient();
try
{
@ -44,19 +46,16 @@ namespace IW4MAdmin
if (client == null)
{
Logger.WriteDebug($"Client {clientFromLog} first time connecting");
clientFromLog.CurrentServer = this;
client = await Manager.GetClientService().Create(clientFromLog);
}
// client has connected in the past
else
/// this is only a temporary version until the IPAddress is transmitted
client.CurrentAlias = new EFAlias()
{
// this is only a temporary version until the IPAddress is transmitted
client.CurrentAlias = new EFAlias
{
Name = clientFromLog.Name,
IPAddress = clientFromLog.IPAddress
};
}
Name = clientFromLog.Name,
IPAddress = clientFromLog.IPAddress
};
Logger.WriteInfo($"Client {client} connected...");
@ -68,8 +67,6 @@ namespace IW4MAdmin
client.CurrentServer = this;
Clients[client.ClientNumber] = client;
client.State = EFClient.ClientState.Connected;
#if DEBUG == true
Logger.WriteDebug($"End PreConnect for {client}");
#endif
@ -81,11 +78,8 @@ namespace IW4MAdmin
};
Manager.GetEventHandler().AddEvent(e);
if (client.IPAddress != null)
{
await client.OnJoin(client.IPAddress);
}
await client.OnJoin(client.IPAddress);
client.State = ClientState.Connected;
}
catch (Exception ex)
@ -97,12 +91,14 @@ namespace IW4MAdmin
override public async Task OnClientDisconnected(EFClient client)
{
Logger.WriteInfo($"Client {client} [{client.State.ToString().ToLower()}] disconnecting...");
await client.OnDisconnect();
Clients[client.ClientNumber] = null;
#if DEBUG == true
Logger.WriteDebug($"End PreDisconnect for {client}");
if (client.ClientNumber >= 0)
{
#endif
Logger.WriteInfo($"Client {client} [{client.State.ToString().ToLower()}] disconnecting...");
Clients[client.ClientNumber] = null;
await client.OnDisconnect();
var e = new GameEvent()
{
Origin = client,
@ -111,6 +107,9 @@ namespace IW4MAdmin
};
Manager.GetEventHandler().AddEvent(e);
#if DEBUG == true
}
#endif
}
public override async Task ExecuteEvent(GameEvent E)
@ -176,9 +175,33 @@ namespace IW4MAdmin
/// <returns></returns>
override protected async Task<bool> ProcessEvent(GameEvent E)
{
if (E.Type == GameEvent.EventType.ConnectionLost)
{
var exception = E.Extra as Exception;
Logger.WriteError(exception.Message);
if (exception.Data["internal_exception"] != null)
{
Logger.WriteDebug($"Internal Exception: {exception.Data["internal_exception"]}");
}
Logger.WriteInfo("Connection lost to server, so we are throttling the poll rate");
Throttled = true;
}
if (E.Type == GameEvent.EventType.ConnectionRestored)
{
if (Throttled)
{
Logger.WriteVerbose(loc["MANAGER_CONNECTION_REST"].FormatExt($"[{IP}:{Port}]"));
}
Logger.WriteInfo("Connection restored to server, so we are no longer throttling the poll rate");
Throttled = false;
}
if (E.Type == GameEvent.EventType.ChangePermission)
{
if (!E.Target.IsPrivileged())
var newPermission = (Permission)E.Extra;
if (newPermission < Permission.Moderator)
{
// remove banned or demoted privileged user
Manager.GetPrivilegedClients().Remove(E.Target.ClientId);
@ -186,17 +209,46 @@ namespace IW4MAdmin
else
{
Manager.GetPrivilegedClients()[E.Target.ClientId] = E.Target;
if (Manager.GetPrivilegedClients().ContainsKey(E.Target.ClientId))
{
Manager.GetPrivilegedClients()[E.Target.ClientId] = E.Target;
}
else
{
Manager.GetPrivilegedClients().Add(E.Target.ClientId, E.Target);
}
}
Logger.WriteInfo($"{E.Origin} is setting {E.Target} to permission level {newPermission}");
await Manager.GetClientService().UpdateLevel(newPermission, E.Target, E.Origin);
}
else if (E.Type == GameEvent.EventType.PreConnect)
{
// we don't want to track bots in the database at all if ignore bots is requested
if (E.Origin.IsBot && Manager.GetApplicationSettings().Configuration().IgnoreBots)
{
return false;
}
var existingClient = GetClientsAsList().FirstOrDefault(_client => _client.Equals(E.Origin));
// they're already connected
if (existingClient != null)
{
Logger.WriteWarning($"detected preconnect for {E.Origin}, but they are already connected");
return false;
}
CONNECT:
if (Clients[E.Origin.ClientNumber] == null)
{
#if DEBUG == true
Logger.WriteDebug($"Begin PreConnect for {E.Origin}");
#endif
// we can go ahead and put them in so that they don't get re added
Clients[E.Origin.ClientNumber] = E.Origin;
await OnClientConnected(E.Origin);
ChatHistory.Add(new ChatInfo()
@ -212,9 +264,12 @@ namespace IW4MAdmin
}
}
// for some reason there's still a client in the spot
else
{
return false;
Logger.WriteWarning($"{E.Origin} is connecteding but {Clients[E.Origin.ClientNumber]} is currently in that client slot");
await OnClientDisconnected(Clients[E.Origin.ClientNumber]);
goto CONNECT;
}
}
@ -233,17 +288,29 @@ namespace IW4MAdmin
};
var addedPenalty = await Manager.GetPenaltyService().Create(newPenalty);
await Manager.GetClientService().Update(E.Target);
E.Target.SetLevel(Permission.Flagged, E.Origin);
}
else if (E.Type == GameEvent.EventType.Unflag)
{
await Manager.GetClientService().Update(E.Target);
var unflagPenalty = new Penalty()
{
Type = Penalty.PenaltyType.Unflag,
Expires = DateTime.UtcNow,
Offender = E.Target,
Offense = E.Data,
Punisher = E.Origin,
When = DateTime.UtcNow,
Link = E.Target.AliasLink
};
await Manager.GetPenaltyService().Create(unflagPenalty);
E.Target.SetLevel(Permission.User, E.Origin);
}
else if (E.Type == GameEvent.EventType.Report)
{
this.Reports.Add(new Report()
Reports.Add(new Report()
{
Origin = E.Origin,
Target = E.Target,
@ -277,37 +344,21 @@ namespace IW4MAdmin
await Warn(E.Data, E.Target, E.Origin);
}
else if (E.Type == GameEvent.EventType.Quit)
else if (E.Type == GameEvent.EventType.Disconnect)
{
var origin = GetClientsAsList().FirstOrDefault(_client => _client.NetworkId.Equals(E.Origin));
if (origin != null)
ChatHistory.Add(new ChatInfo()
{
var e = new GameEvent()
{
Type = GameEvent.EventType.Disconnect,
Origin = origin,
Owner = this
};
Name = E.Origin.Name,
Message = "DISCONNECTED",
Time = DateTime.UtcNow
});
Manager.GetEventHandler().AddEvent(e);
}
else
{
return false;
}
await new MetaService().AddPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin);
await new MetaService().AddPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin);
}
else if (E.Type == GameEvent.EventType.PreDisconnect)
{
if ((DateTime.UtcNow - SessionStart).TotalSeconds < 30)
{
Logger.WriteInfo($"Cancelling pre disconnect for {E.Origin} as it occured too close to map end");
E.FailReason = GameEvent.EventFailReason.Invalid;
return false;
}
// predisconnect comes from minimal rcon polled players and minimal log players
// so we need to disconnect the "full" version of the client
var client = GetClientsAsList().FirstOrDefault(_client => _client.Equals(E.Origin));
@ -317,18 +368,16 @@ namespace IW4MAdmin
#if DEBUG == true
Logger.WriteDebug($"Begin PreDisconnect for {client}");
#endif
ChatHistory.Add(new ChatInfo()
{
Name = client.Name,
Message = "DISCONNECTED",
Time = DateTime.UtcNow
});
await OnClientDisconnected(client);
#if DEBUG == true
Logger.WriteDebug($"End PreDisconnect for {client}");
#endif
}
else
else if (client?.State != ClientState.Disconnecting)
{
Logger.WriteWarning($"Client {E.Origin} detected as disconnecting, but could not find them in the player list");
Logger.WriteDebug($"Expected {E.Origin} but found {GetClientsAsList().FirstOrDefault(_client => _client.ClientNumber == E.Origin.ClientNumber)}");
return false;
}
}
@ -345,18 +394,27 @@ namespace IW4MAdmin
{
E.Data = E.Data.StripColors();
if (E.Data.Length > 0)
if (E.Data?.Length > 0)
{
// this may be a fix for a hard to reproduce null exception error
lock (ChatHistory)
string message = E.Data;
if (E.Data.IsQuickMessage())
{
ChatHistory.Add(new ChatInfo()
try
{
Name = E.Origin.Name,
Message = E.Data ?? "NULL",
Time = DateTime.UtcNow
});
message = Manager.GetApplicationSettings().Configuration()
.QuickMessages
.First(_qm => _qm.Game == GameName)
.Messages[E.Data.Substring(1)];
}
catch { }
}
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin.Name,
Message = message,
Time = DateTime.UtcNow
});
}
}
@ -389,7 +447,7 @@ namespace IW4MAdmin
var dict = (Dictionary<string, string>)E.Extra;
Gametype = dict["g_gametype"].StripColors();
Hostname = dict["sv_hostname"].StripColors();
MaxClients = Int32.Parse(dict["sv_maxclients"]);
MaxClients = int.Parse(dict["sv_maxclients"]);
string mapname = dict["mapname"].StripColors();
CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map()
@ -440,9 +498,9 @@ namespace IW4MAdmin
return true;
}
private Task OnClientUpdate(EFClient origin)
private async Task OnClientUpdate(EFClient origin)
{
var client = Clients[origin.ClientNumber];
var client = GetClientsAsList().FirstOrDefault(_client => _client.Equals(origin));
if (client != null)
{
@ -450,13 +508,22 @@ namespace IW4MAdmin
client.Score = origin.Score;
// update their IP if it hasn't been set yet
if (client.IPAddress == null && !client.IsBot)
if (client.IPAddress == null &&
!client.IsBot &&
client.State == ClientState.Connected)
{
return client.OnJoin(origin.IPAddress);
try
{
await client.OnJoin(origin.IPAddress);
}
catch (Exception e)
{
origin.CurrentServer.Logger.WriteWarning($"Could not execute on join for {origin}");
origin.CurrentServer.Logger.WriteDebug(e.GetExceptionInfo());
}
}
}
return Task.CompletedTask;
}
/// <summary>
@ -472,6 +539,7 @@ namespace IW4MAdmin
#endif
var currentClients = GetClientsAsList();
var polledClients = (await this.GetStatusAsync()).AsEnumerable();
if (Manager.GetApplicationSettings().Configuration().IgnoreBots)
{
polledClients = polledClients.Where(c => !c.IsBot);
@ -479,8 +547,6 @@ namespace IW4MAdmin
#if DEBUG
Logger.WriteInfo($"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms");
#endif
Throttled = false;
var disconnectingClients = currentClients.Except(polledClients);
var connectingClients = polledClients.Except(currentClients);
var updatedClients = polledClients.Except(connectingClients).Except(disconnectingClients);
@ -533,7 +599,7 @@ namespace IW4MAdmin
foreach (var disconnectingClient in polledClients[1])
{
if (disconnectingClient.State == EFClient.ClientState.Disconnecting)
if (disconnectingClient.State == ClientState.Disconnecting)
{
continue;
}
@ -557,6 +623,12 @@ namespace IW4MAdmin
// this are our new connecting clients
foreach (var client in polledClients[0])
{
// note: this prevents players in ZMBI state from being registered with no name
if (string.IsNullOrEmpty(client.Name))
{
continue;
}
var e = new GameEvent()
{
Type = GameEvent.EventType.PreConnect,
@ -590,8 +662,15 @@ namespace IW4MAdmin
if (ConnectionErrors > 0)
{
Logger.WriteVerbose($"{loc["MANAGER_CONNECTION_REST"]} {IP}:{Port}");
Throttled = false;
var _event = new GameEvent()
{
Type = GameEvent.EventType.ConnectionRestored,
Owner = this,
Origin = Utilities.IW4MAdminClient(this),
Target = Utilities.IW4MAdminClient(this)
};
Manager.GetEventHandler().AddEvent(_event);
}
ConnectionErrors = 0;
@ -603,9 +682,17 @@ namespace IW4MAdmin
ConnectionErrors++;
if (ConnectionErrors == 3)
{
Logger.WriteError($"{e.Message} {IP}:{Port}, {loc["SERVER_ERROR_POLLING"]}");
Logger.WriteDebug($"Internal Exception: {e.Data["internal_exception"]}");
Throttled = true;
var _event = new GameEvent()
{
Type = GameEvent.EventType.ConnectionLost,
Owner = this,
Origin = Utilities.IW4MAdminClient(this),
Target = Utilities.IW4MAdminClient(this),
Extra = e,
Data = ConnectionErrors.ToString()
};
Manager.GetEventHandler().AddEvent(_event);
}
return true;
}
@ -630,7 +717,7 @@ namespace IW4MAdmin
&& BroadcastMessages.Count > 0
&& ClientNum > 0)
{
string[] messages = this.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage]).Split(Environment.NewLine);
string[] messages = (await this.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage])).Split(Environment.NewLine);
foreach (string message in messages)
{
@ -647,9 +734,9 @@ namespace IW4MAdmin
// this one is ok
catch (ServerException e)
{
if (e is NetworkException)
if (e is NetworkException && !Throttled)
{
Logger.WriteError($"{loc["SERVER_ERROR_COMMUNICATION"]} {IP}:{Port}");
Logger.WriteError(loc["SERVER_ERROR_COMMUNICATION"].FormatExt($"{IP}:{Port}"));
Logger.WriteDebug(e.GetExceptionInfo());
}
@ -658,7 +745,7 @@ namespace IW4MAdmin
catch (Exception E)
{
Logger.WriteError($"{loc["SERVER_ERROR_EXCEPTION"]} {IP}:{Port}");
Logger.WriteError(loc["SERVER_ERROR_EXCEPTION"].FormatExt($"[{IP}:{Port}]"));
Logger.WriteDebug(E.GetExceptionInfo());
return false;
}
@ -751,7 +838,7 @@ namespace IW4MAdmin
}
CustomCallback = await ScriptLoaded();
// they've manually specified the log path
if (!string.IsNullOrEmpty(ServerConfig.ManualLogPath))
{
@ -774,8 +861,8 @@ namespace IW4MAdmin
if (!File.Exists(LogPath) && ServerConfig.GameLogServerUrl == null)
{
Logger.WriteError($"{LogPath} {loc["SERVER_ERROR_DNE"]}");
throw new ServerException($"{loc["SERVER_ERROR_LOG"]} {LogPath}");
Logger.WriteError(loc["SERVER_ERROR_DNE"].FormatExt(LogPath));
throw new ServerException(loc["SERVER_ERROR_DNE"].FormatExt(LogPath));
}
}
@ -845,7 +932,7 @@ namespace IW4MAdmin
#if !DEBUG
else
{
string formattedKick = String.Format(RconParser.Configuration.CommandPrefixes.Kick, Target.ClientNumber, $"{loc["SERVER_KICK_TEXT"]} - ^5{Reason}^7");
string formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick, Target.ClientNumber, $"{loc["SERVER_KICK_TEXT"]} - ^5{Reason}^7");
await Target.CurrentServer.ExecuteCommandAsync(formattedKick);
}
#endif
@ -925,11 +1012,9 @@ namespace IW4MAdmin
else
{
// this is set only because they're still in the server.
targetClient.Level = EFClient.Permission.Banned;
#if !DEBUG
string formattedString = String.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{reason} ^7({loc["SERVER_BAN_APPEAL"]} {Website})^7");
string formattedString = String.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"{loc["SERVER_BAN_TEXT"]} - ^5{reason} ^7{loc["SERVER_BAN_APPEAL"].FormatExt(Website)}^7");
await targetClient.CurrentServer.ExecuteCommandAsync(formattedString);
#else
await targetClient.CurrentServer.OnClientDisconnected(targetClient);
@ -946,8 +1031,10 @@ namespace IW4MAdmin
Link = targetClient.AliasLink,
AutomatedOffense = originClient.AdministeredPenalties?.FirstOrDefault()?.AutomatedOffense,
IsEvadedOffense = isEvade
};
targetClient.SetLevel(Permission.Banned, originClient);
await Manager.GetPenaltyService().Create(newPenalty);
}
@ -965,16 +1052,17 @@ namespace IW4MAdmin
Link = Target.AliasLink
};
await Manager.GetPenaltyService().RemoveActivePenalties(Target.AliasLink.AliasLinkId);
await Manager.GetPenaltyService().RemoveActivePenalties(Target.AliasLink.AliasLinkId, Origin);
await Manager.GetPenaltyService().Create(unbanPenalty);
Target.SetLevel(Permission.User, Origin);
}
override public void InitializeTokens()
{
Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("TOTALPLAYERS", (Server s) => Manager.GetClientService().GetTotalClientsAsync().Result.ToString()));
Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("VERSION", (Server s) => Application.Program.Version.ToString()));
Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("NEXTMAP", (Server s) => SharedLibraryCore.Commands.CNextMap.GetNextMap(s).Result));
Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("ADMINS", (Server s) => SharedLibraryCore.Commands.CListAdmins.OnlineAdmins(s)));
Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("TOTALPLAYERS", (Server s) => Task.Run(async () => (await Manager.GetClientService().GetTotalClientsAsync()).ToString())));
Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("VERSION", (Server s) => Task.FromResult(Application.Program.Version.ToString())));
Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("NEXTMAP", (Server s) => SharedLibraryCore.Commands.CNextMap.GetNextMap(s)));
Manager.GetMessageTokens().Add(new SharedLibraryCore.Helpers.MessageToken("ADMINS", (Server s) => Task.FromResult(SharedLibraryCore.Commands.CListAdmins.OnlineAdmins(s))));
}
}
}

View File

@ -17,17 +17,20 @@ namespace IW4MAdmin.Application.Localization
string currentLocale = string.IsNullOrEmpty(customLocale) ? CultureInfo.CurrentCulture.Name : customLocale;
string[] localizationFiles = Directory.GetFiles(Path.Join(Utilities.OperatingDirectory, "Localization"), $"*.{currentLocale}.json");
try
if (!Program.ServerManager.GetApplicationSettings()?.Configuration()?.UseLocalTranslations ?? false)
{
var api = Endpoint.Get();
var localization = api.GetLocalization(currentLocale).Result;
Utilities.CurrentLocalization = localization;
return;
}
try
{
var api = Endpoint.Get();
var localization = api.GetLocalization(currentLocale).Result;
Utilities.CurrentLocalization = localization;
return;
}
catch (Exception)
{
// the online localization failed so will default to local files
catch (Exception)
{
// the online localization failed so will default to local files
}
}
// culture doesn't exist so we just want language

View File

@ -1,269 +0,0 @@
{
"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"
}
}
}

View File

@ -1,269 +0,0 @@
{
"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"
}
}
}

View File

@ -1,269 +0,0 @@
{
"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"
}
}
}

View File

@ -1,269 +0,0 @@
{
"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": "Эффективность"
}
}
}

View File

@ -34,10 +34,11 @@ namespace IW4MAdmin.Application
try
{
ServerManager = ApplicationManager.GetInstance();
var configuration = ServerManager.GetApplicationSettings().Configuration();
if (ServerManager.GetApplicationSettings().Configuration() != null)
if (configuration != null)
{
Localization.Configure.Initialize(ServerManager.GetApplicationSettings().Configuration().CustomLocale);
Localization.Configure.Initialize(configuration.EnableCustomLocale ? (configuration.CustomLocale ?? "windows-1252") : "windows-1252");
}
else
@ -90,7 +91,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.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString("0.0")}]"));
Console.ForegroundColor = ConsoleColor.Gray;
}
#else
@ -98,7 +99,7 @@ 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.WriteLine(loc["MANAGER_VERSION_CURRENT"].FormatExt($"[v{Version.ToString("0.0")}-pr]"));
Console.ForegroundColor = ConsoleColor.Gray;
}
#endif

View File

@ -119,9 +119,9 @@ namespace IW4MAdmin.Application.RconParsers
}
int validMatches = 0;
foreach (string S in Status)
foreach (string statusLine in Status)
{
string responseLine = S.Trim();
string responseLine = statusLine.Trim();
var regex = Regex.Match(responseLine, Configuration.Status.Pattern, RegexOptions.IgnoreCase);
@ -158,11 +158,11 @@ namespace IW4MAdmin.Application.RconParsers
State = EFClient.ClientState.Connecting
};
// they've not fully connected yet
if (!client.IsBot && ping == 999)
{
continue;
}
//// they've not fully connected yet
//if (!client.IsBot && ping == 999)
//{
// continue;
//}
StatusPlayers.Add(client);
}

View File

@ -17,7 +17,7 @@
<SuppressCollectPythonCloudServiceFiles>true</SuppressCollectPythonCloudServiceFiles>
<Name>GameLogServer</Name>
<RootNamespace>GameLogServer</RootNamespace>
<InterpreterId>MSBuild|env|$(MSBuildProjectFullPath)</InterpreterId>
<InterpreterId>MSBuild|log_env|$(MSBuildProjectFullPath)</InterpreterId>
<EnableNativeCodeDebugging>False</EnableNativeCodeDebugging>
<Environment>DEBUG=True</Environment>
</PropertyGroup>
@ -50,7 +50,6 @@
<Folder Include="GameLogServer\" />
</ItemGroup>
<ItemGroup>
<Content Include="requirements.txt" />
<None Include="Stable.pubxml" />
</ItemGroup>
<ItemGroup>
@ -63,6 +62,18 @@
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
<Architecture>X64</Architecture>
</Interpreter>
<Interpreter Include="log_env\">
<Id>log_env</Id>
<Version>3.6</Version>
<Description>log_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="requirements.txt" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.Web.targets" />
<!-- Specify pre- and post-build commands in the BeforeBuild and

View File

@ -5,55 +5,53 @@ 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
# (if the time between checks is greater, ignore ) - in seconds
self.max_file_time_change = 30
def read_file(self, path):
# this removes old entries that are no longer valid
try:
self._clear_old_logs()
except Exception as e:
print('could not clear old logs')
print(e)
if os.name != 'nt':
path = re.sub(r'^[A-Z]\:', '', path)
path = re.sub(r'\\+', '/', 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'^.+[\\|\/](.+)[\\|\/].+.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
# get the new file size
new_file_size = self.file_length(path)
# the log size was unable to be read (probably the wrong path)
# the log size was unable to be read (probably the wrong path)
if new_file_size < 0:
return False
now = time.time()
# this is the first time the log has been requested
if path not in self.log_file_sizes:
self.log_file_sizes[path] = {
'length' : new_file_size,
'read': time.time()
}
return ''
# grab the previous values
last_length = self.log_file_sizes[path]['length']
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
'read': time.time()
}
# 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
@ -63,14 +61,25 @@ class LogReader(object):
file_handle.seek(-length, 2)
file_data = file_handle.read(length)
file_handle.close()
return file_data.decode('utf-8')
except:
# using ignore errors omits the pesky 0xb2 bytes we're reading in for some reason
return file_data.decode('utf-8', errors='ignore')
except Exception as e:
print('could not read the log file at {0}, wanted to read {1} bytes'.format(path, length))
print(e)
return False
def _clear_old_logs(self):
expired_logs = [path for path in self.log_file_sizes if int(time.time() - self.log_file_sizes[path]['read']) > self.max_file_time_change]
for log in expired_logs:
print('removing expired log {0}'.format(log))
del self.log_file_sizes[log]
def file_length(self, path):
try:
return os.stat(path).st_size
except:
except Exception as e:
print('could not get the size of the log file at {0}'.format(path))
print(e)
return -1
reader = LogReader()

View File

@ -7,13 +7,8 @@ class LogResource(Resource):
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),
'length': 0 if log_info is False else len(log_info),
'data': log_info
}

View File

@ -1,29 +1,29 @@
from flask_restful import Resource
from flask import request
import requests
import os
import subprocess
import re
#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
#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
#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

View File

@ -1,11 +1,14 @@
from flask import Flask
from flask_restful import Api
from .log_resource import LogResource
from .restart_resource import RestartResource
#from .restart_resource import RestartResource
import logging
app = Flask(__name__)
def init():
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
api = Api(app)
api.add_resource(LogResource, '/log/<string:path>')
api.add_resource(RestartResource, '/restart')
#api.add_resource(RestartResource, '/restart')

View File

@ -1,26 +1,12 @@
aniso8601==3.0.2
APScheduler==3.5.3
certifi==2018.10.15
chardet==3.0.4
click==6.7
aniso8601==6.0.0
Click==7.0
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
Flask-RESTful==0.3.7
itsdangerous==1.1.0
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
MarkupSafe==1.1.1
pip==10.0.1
pytz==2018.9
setuptools==39.0.1
six==1.12.0
Werkzeug==0.15.2

View File

@ -41,6 +41,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
Plugins\ScriptPlugins\ParserCoD4x.js = Plugins\ScriptPlugins\ParserCoD4x.js
Plugins\ScriptPlugins\ParserIW4x.js = Plugins\ScriptPlugins\ParserIW4x.js
Plugins\ScriptPlugins\ParserPT6.js = Plugins\ScriptPlugins\ParserPT6.js
Plugins\ScriptPlugins\ParserRektT5M.js = Plugins\ScriptPlugins\ParserRektT5M.js
Plugins\ScriptPlugins\ParserTeknoMW3.js = Plugins\ScriptPlugins\ParserTeknoMW3.js
Plugins\ScriptPlugins\SharedGUIDKick.js = Plugins\ScriptPlugins\SharedGUIDKick.js
Plugins\ScriptPlugins\VPNDetection.js = Plugins\ScriptPlugins\VPNDetection.js
@ -52,6 +53,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{A848FCF1-852
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StatsWeb", "Plugins\Web\StatsWeb\StatsWeb.csproj", "{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutomessageFeed", "Plugins\AutomessageFeed\AutomessageFeed.csproj", "{F5815359-CFC7-44B4-9A3B-C04BACAD5836}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -290,8 +293,8 @@ Global
{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|Any CPU.ActiveCfg = Prerelease|Any CPU
{6C706CE5-A206-4E46-8712-F8C48D526091}.Prerelease|Any CPU.Build.0 = Prerelease|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
@ -310,7 +313,7 @@ Global
{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|Any CPU.ActiveCfg = Prerelease|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
@ -319,15 +322,13 @@ Global
{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|Any CPU.ActiveCfg = Prerelease|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
@ -350,8 +351,8 @@ Global
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Debug|x64.Build.0 = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Debug|x86.ActiveCfg = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Debug|x86.Build.0 = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|Any CPU.Build.0 = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Prerelease|x64.ActiveCfg = Debug|Any CPU
@ -366,6 +367,30 @@ Global
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Release|x64.Build.0 = Release|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Release|x86.ActiveCfg = Release|Any CPU
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B}.Release|x86.Build.0 = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|x64.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|x64.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|x86.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Debug|x86.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|Any CPU.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|Any CPU.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|Mixed Platforms.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|Mixed Platforms.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|x64.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|x64.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|x86.ActiveCfg = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Prerelease|x86.Build.0 = Debug|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|Any CPU.Build.0 = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|x64.ActiveCfg = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|x64.Build.0 = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|x86.ActiveCfg = Release|Any CPU
{F5815359-CFC7-44B4-9A3B-C04BACAD5836}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -380,6 +405,7 @@ Global
{3F9ACC27-26DB-49FA-BCD2-50C54A49C9FA} = {26E8B310-269E-46D4-A612-24601F16065F}
{A848FCF1-8527-4AA8-A1AA-50D29695C678} = {26E8B310-269E-46D4-A612-24601F16065F}
{776B348B-F818-4A0F-A625-D0AF8BAD3E9B} = {A848FCF1-8527-4AA8-A1AA-50D29695C678}
{F5815359-CFC7-44B4-9A3B-C04BACAD5836} = {26E8B310-269E-46D4-A612-24601F16065F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {84F8F8E0-1F73-41E0-BD8D-BB6676E2EE87}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\SharedLibraryCore\SharedLibraryCore.csproj" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="copy &quot;$(TargetPath)&quot; &quot;$(SolutionDir)BUILD\Plugins&quot;"/>
<Exec Command="copy &quot;$(TargetDir)Microsoft.SyndicationFeed.ReaderWriter.dll&quot; &quot;$(SolutionDir)BUILD\Plugins&quot;"/>
</Target>
</Project>

View File

@ -0,0 +1,29 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
namespace AutomessageFeed
{
class Configuration : IBaseConfiguration
{
public bool EnableFeed { get; set; }
public string FeedUrl { get; set; }
public int MaxFeedItems { get; set; }
public IBaseConfiguration Generate()
{
EnableFeed = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_PROMPT_ENABLE"]);
if (EnableFeed)
{
FeedUrl = Utilities.PromptString(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_URL"]);
MaxFeedItems = Utilities.PromptInt(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_PROMPT_MAXITEMS"],
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_PROMPT_MAXITEMS_DESC"],
0, int.MaxValue, 0);
}
return this;
}
public string Name() => "AutomessageFeedConfiguration";
}
}

View File

@ -0,0 +1,85 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Threading.Tasks;
using Microsoft.SyndicationFeed.Rss;
using SharedLibraryCore.Configuration;
using System.Xml;
using Microsoft.SyndicationFeed;
using System.Collections.Generic;
using SharedLibraryCore.Helpers;
using System.Text.RegularExpressions;
namespace AutomessageFeed
{
public class Plugin : IPlugin
{
public string Name => "Automessage Feed";
public float Version => (float)Utilities.GetVersionAsDouble();
public string Author => "RaidMax";
private Configuration _configuration;
private int _currentFeedItem;
private async Task<string> GetNextFeedItem(Server server)
{
var items = new List<string>();
using (var reader = XmlReader.Create(_configuration.FeedUrl, new XmlReaderSettings() { Async = true }))
{
var feedReader = new RssFeedReader(reader);
while (await feedReader.Read())
{
switch (feedReader.ElementType)
{
case SyndicationElementType.Item:
var item = await feedReader.ReadItem();
items.Add(Regex.Replace(item.Title, @"\<.+\>.*\</.+\>", ""));
break;
}
}
}
if (_currentFeedItem < items.Count && (_configuration.MaxFeedItems == 0 || _currentFeedItem < _configuration.MaxFeedItems))
{
_currentFeedItem++;
return items[_currentFeedItem - 1];
}
_currentFeedItem = 0;
return Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_NO_ITEMS"];
}
public Task OnEventAsync(GameEvent E, Server S)
{
return Task.CompletedTask;
}
public async Task OnLoadAsync(IManager manager)
{
var cfg = new BaseConfigurationHandler<Configuration>("AutomessageFeedPluginSettings");
if (cfg.Configuration() == null)
{
cfg.Set((Configuration)new Configuration().Generate());
await cfg.Save();
}
_configuration = cfg.Configuration();
manager.GetMessageTokens().Add(new MessageToken("FEED", GetNextFeedItem));
}
public Task OnTickAsync(Server S)
{
throw new NotImplementedException();
}
public Task OnUnloadAsync()
{
throw new NotImplementedException();
}
}
}

View File

@ -24,12 +24,12 @@ namespace IW4MAdmin.Plugins.Login.Commands
if (!success)
{
string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(E.Data, client.PasswordSalt));
success = hashedPassword[0] == client.Password;
}
if (hashedPassword[0] == client.Password)
{
success = true;
Plugin.AuthorizedClients[E.Origin.ClientId] = true;
}
if (success)
{
Plugin.AuthorizedClients[E.Origin.ClientId] = true;
}
_ = success ?

View File

@ -7,7 +7,6 @@ using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Objects;
namespace IW4MAdmin.Plugins.ProfanityDeterment
{
@ -49,11 +48,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
if (containsObjectionalWord)
{
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new EFClient()
{
ClientId = 1,
CurrentServer = E.Owner
});
E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, Utilities.IW4MAdminClient(E.Owner));
};
}

View File

@ -3,7 +3,7 @@ var eventParser;
var plugin = {
author: 'FrenchFry, RaidMax',
version: 0.2,
version: 0.3,
name: 'CoD4x Parser',
isParser: true,
@ -27,6 +27,7 @@ var plugin = {
eventParser.Configuration.GameDirectory = 'main';
eventParser.Version = 'CoD4 X 1.8 win_mingw-x86 build 2055 May 2 2017';
eventParser.GameName = 1; // IW3
eventParser.URLProtocolFormat = 'cod4://{{ip}}:{{port}}';
},
onUnloadAsync: function () {

View File

@ -3,8 +3,8 @@ var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.2,
name: 'IW3 Parser',
version: 0.3,
name: 'IW4 Parser',
isParser: true,
onEventAsync: function (gameEvent, server) {
@ -25,6 +25,7 @@ var plugin = {
rconParser.GameName = 2; // IW4x
eventParser.Version = 'IW4x (v0.6.0)';
eventParser.GameName = 2; // IW4x
eventParser.URLProtocolFormat = 'iw4x://{{ip}}:{{port}}';
},
onUnloadAsync: function () {

View File

@ -4,7 +4,7 @@ var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.2,
name: 'Plutoniun T6 Parser',
name: 'Plutonium T6 Parser',
isParser: true,
onEventAsync: function (gameEvent, server) {

View File

@ -0,0 +1,34 @@
var rconParser;
var eventParser;
var plugin = {
author: 'RaidMax',
version: 0.1,
name: 'RektT5m Parser',
isParser: true,
onEventAsync: function (gameEvent, server) {
},
onLoadAsync: function (manager) {
rconParser = manager.GenerateDynamicRConParser();
eventParser = manager.GenerateDynamicEventParser();
eventParser.Configuration.GameDirectory = 'data';
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xff\1print';
rconParser.Configuration.CommandPrefixes.Tell = 'tell {0} {1}';
rconParser.Configuration.CommandPrefixes.RConGetInfo = undefined;
rconParser.Version = 'Call of Duty Multiplayer - Ship COD_T5_S MP build 7.0.189 CL(1022875) CODPCAB-V64 CEG Wed Nov 02 18:02:23 2011 win-x86';
rconParser.GameName = 6; // T5
eventParser.Version = 'Call of Duty Multiplayer - Ship COD_T5_S MP build 7.0.189 CL(1022875) CODPCAB-V64 CEG Wed Nov 02 18:02:23 2011 win-x86';
eventParser.GameName = 6; // T5
},
onUnloadAsync: function () {
},
onTickAsync: function (server) {
}
};

View File

@ -1,6 +1,6 @@
var plugin = {
author: 'RaidMax',
version: 1.0,
version: 1.1,
name: 'VPN Detection Plugin',
manager: null,
@ -33,7 +33,7 @@ var plugin = {
cl.Dispose();
usingVPN = parsedJSON.success && parsedJSON.proxy;
} catch (e) {
this.logger.WriteError(e.message);
this.logger.WriteWarning('There was a problem checking client IP for VPN ' + e.message);
}
if (usingVPN) {

View File

@ -39,7 +39,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
where stats.ServerId == serverId
where client.Level != EFClient.Permission.Banned
where client.LastConnection >= thirtyDaysAgo
orderby stats.Kills descending
orderby stats.TimePlayed descending
select new
{
alias.Name,

View File

@ -69,7 +69,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
if (E.Message.IsBroadcastCommand())
{
string name = E.Target == null ? E.Origin.Name : E.Target.Name;
E.Owner.Broadcast($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{name}^7");
E.Owner.Broadcast(loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"].FormatExt(name));
E.Owner.Broadcast(statLine);
}
@ -77,7 +77,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
{
if (E.Target != null)
{
E.Origin.Tell($"{loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"]} ^5{E.Target.Name}^7");
E.Origin.Tell(loc["PLUGINS_STATS_COMMANDS_VIEW_SUCCESS"].FormatExt(E.Target.Name));
}
E.Origin.Tell(statLine);

View File

@ -1,7 +1,9 @@
using Microsoft.AspNetCore.Authorization;
using IW4MAdmin.Plugins.Stats.Helpers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Dtos;
using System;
using System.Linq;
using System.Threading.Tasks;
@ -12,25 +14,51 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
public class StatsController : BaseController
{
[HttpGet]
public async Task<IActionResult> TopPlayersAsync()
public IActionResult TopPlayersAsync()
{
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex.Set["WEBFRONT_STATS_INDEX_TITLE"];
ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex.Set["WEBFRONT_STATS_INDEX_DESC"];
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_TITLE"];
ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_DESC"];
ViewBag.Servers = Manager.GetServers().Select(_server => new ServerInfo() { Name = _server.Hostname, ID = _server.EndPoint });
return View("Index", await Plugin.Manager.GetTopStats(0, 50));
return View("Index");
}
[HttpGet]
public async Task<IActionResult> GetTopPlayersAsync(int count, int offset)
public async Task<IActionResult> GetTopPlayersAsync(int count, int offset, long? serverId = null)
{
return View("_List", await Plugin.Manager.GetTopStats(offset, count));
// this prevents empty results when we really want aggregate
if (serverId == 0)
{
serverId = null;
}
var server = Manager.GetServers().FirstOrDefault(_server => _server.EndPoint == serverId);
if (server != null)
{
serverId = await StatManager.GetIdForServer(server);
}
var results = await Plugin.Manager.GetTopStats(offset, count, serverId);
// this returns an empty result so we know to stale the loader
if (results.Count == 0 && offset > 0)
{
return Ok();
}
else
{
return View("Components/TopPlayers/_List", results);
}
}
[HttpGet]
public async Task<IActionResult> GetMessageAsync(int serverId, DateTime when)
public async Task<IActionResult> GetMessageAsync(int serverId, long when)
{
var whenUpper = when.AddMinutes(5);
var whenLower = when.AddMinutes(-5);
var whenTime = DateTime.FromFileTimeUtc(when);
var whenUpper = whenTime.AddMinutes(5);
var whenLower = whenTime.AddMinutes(-5);
using (var ctx = new SharedLibraryCore.Database.DatabaseContext(true))
{
@ -38,20 +66,36 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
where message.ServerId == serverId
where message.TimeSent >= whenLower
where message.TimeSent <= whenUpper
select new SharedLibraryCore.Dtos.ChatInfo()
select new ChatInfo()
{
ClientId = message.ClientId,
Message = message.Message,
Name = message.Client.CurrentAlias.Name,
Time = message.TimeSent
Time = message.TimeSent,
ServerGame = message.Server.GameName ?? Server.Game.IW4
};
#if DEBUG == true
var messagesSql = iqMessages.ToSql();
#endif
var messages = await iqMessages.ToListAsync();
foreach (var message in messages)
{
if (message.Message.IsQuickMessage())
{
try
{
var quickMessages = Manager.GetApplicationSettings().Configuration()
.QuickMessages
.First(_qm => _qm.Game == message.ServerGame);
message.Message = quickMessages.Messages[message.Message.Substring(1)];
message.IsQuickMessage = true;
}
catch { }
}
}
return View("_MessageContext", messages);
}
}
@ -62,16 +106,30 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers
{
using (var ctx = new SharedLibraryCore.Database.DatabaseContext(true))
{
var penaltyInfo = await ctx.Set<Models.EFACSnapshot>()
.Where(s => s.ClientId == clientId)
int linkId = await ctx.Clients
.Where(_client => _client.ClientId == clientId)
.Select(_client => _client.AliasLinkId)
.FirstOrDefaultAsync();
var clientIds = await ctx.Clients.Where(_client => _client.AliasLinkId == linkId)
.Select(_client => _client.ClientId)
.ToListAsync();
var iqPenaltyInfo = ctx.Set<Models.EFACSnapshot>()
.Where(s => clientIds.Contains(s.ClientId))
.Include(s => s.LastStrainAngle)
.Include(s => s.HitOrigin)
.Include(s => s.HitDestination)
.Include(s => s.CurrentViewAngle)
.Include(s => s.PredictedViewAngles)
.OrderBy(s => s.When)
.ThenBy(s => s.Hits)
.ToListAsync();
.ThenBy(s => s.Hits);
#if DEBUG == true
var sql = iqPenaltyInfo.ToSql();
#endif
var penaltyInfo = await iqPenaltyInfo.ToListAsync();
return View("_PenaltyInfo", penaltyInfo);
}

View File

@ -13,7 +13,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@ -82,13 +81,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}
}
public async Task<List<TopStatsInfo>> GetTopStats(int start, int count)
public async Task<List<TopStatsInfo>> GetTopStats(int start, int count, long? serverId = null)
{
using (var context = new DatabaseContext(true))
{
// setup the query for the clients within the given rating range
var iqClientRatings = (from rating in context.Set<EFRating>()
.Where(GetRankingFunc())
.Where(GetRankingFunc(serverId))
select new
{
rating.RatingHistory.ClientId,
@ -113,7 +112,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var iqRatingInfo = from rating in context.Set<EFRating>()
where clientIds.Contains(rating.RatingHistory.ClientId)
where rating.ServerId == null
where rating.ServerId == serverId
select new
{
rating.Ranking,
@ -155,6 +154,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var finished = topPlayers.Select(s => new TopStatsInfo()
{
ClientId = s.ClientId,
Id = (int?)serverId ?? 0,
Deaths = s.Deaths,
Kills = s.Kills,
KDR = Math.Round(s.KDR, 2),
@ -221,13 +221,22 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
Port = sv.GetPort(),
EndPoint = sv.ToString(),
ServerId = serverId
ServerId = serverId,
GameName = sv.GameName
};
server = serverSet.Add(server).Entity;
// this doesn't need to be async as it's during initialization
ctx.SaveChanges();
}
// we want to set the gamename up if it's never been set, or it changed
else if (!server.GameName.HasValue || server.GameName.HasValue && server.GameName.Value != sv.GameName)
{
server.GameName = sv.GameName;
ctx.Entry(server).Property(_prop => _prop.GameName).IsModified = true;
ctx.SaveChanges();
}
}
// check to see if the stats have ever been initialized
@ -527,7 +536,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
return;
}
// incase the add palyer event get delayed
// incase the add player event get delayed
if (!Servers[serverId].PlayerStats.ContainsKey(attacker.ClientId))
{
await AddPlayer(attacker);
@ -561,7 +570,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
ctx.Set<EFClientKill>().Add(hit);
}
if (Plugin.Config.Configuration().EnableAntiCheat)
if (Plugin.Config.Configuration().EnableAntiCheat && !attacker.IsBot)
{
if (clientDetection.QueuedHits.Count > Detection.QUEUE_COUNT)
{
@ -570,7 +579,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
clientDetection.QueuedHits = clientDetection.QueuedHits.OrderBy(_hits => _hits.TimeOffset).ToList();
var oldestHit = clientDetection.QueuedHits.First();
clientDetection.QueuedHits.RemoveAt(0);
ApplyPenalty(clientDetection.ProcessHit(oldestHit, isDamage), clientDetection, attacker, ctx);
await ApplyPenalty(clientDetection.ProcessHit(oldestHit, isDamage), clientDetection, attacker, ctx);
}
}
@ -579,7 +588,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
clientDetection.QueuedHits.Add(hit);
}
ApplyPenalty(clientDetection.ProcessTotalRatio(clientStats), clientDetection, attacker, ctx);
await ApplyPenalty(clientDetection.ProcessTotalRatio(clientStats), clientDetection, attacker, ctx);
}
ctx.Set<EFHitLocationCount>().UpdateRange(clientStats.HitLocations);
@ -597,8 +606,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}
}
void ApplyPenalty(DetectionPenaltyResult penalty, Detection clientDetection, EFClient attacker, DatabaseContext ctx)
async Task ApplyPenalty(DetectionPenaltyResult penalty, Detection clientDetection, EFClient attacker, DatabaseContext ctx)
{
var penaltyClient = Utilities.IW4MAdminClient(attacker.CurrentServer);
switch (penalty.ClientPenalty)
{
case Penalty.PenaltyType.Ban:
@ -606,22 +616,19 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
break;
}
attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], new EFClient()
{
ClientId = 1,
AdministeredPenalties = new List<EFPenalty>()
{
new EFPenalty()
{
AutomatedOffense = penalty.Type == Cheat.Detection.DetectionType.Bone ?
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
}
},
Level = EFClient.Permission.Console,
CurrentServer = attacker.CurrentServer,
}, false);
penaltyClient.AdministeredPenalties = new List<EFPenalty>()
{
new EFPenalty()
{
AutomatedOffense = penalty.Type == Detection.DetectionType.Bone ?
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
}
};
await attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], penaltyClient, false).WaitAsync();
if (clientDetection.Tracker.HasChanges)
{
SaveTrackedSnapshots(clientDetection, ctx);
@ -637,23 +644,17 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}";
attacker.Flag(flagReason, new EFClient()
{
ClientId = 1,
Level = EFClient.Permission.Console,
CurrentServer = attacker.CurrentServer,
});
await attacker.Flag(flagReason, penaltyClient).WaitAsync();
if (clientDetection.Tracker.HasChanges)
{
SaveTrackedSnapshots(clientDetection, ctx);
}
break;
}
}
void SaveTrackedSnapshots(Cheat.Detection clientDetection, DatabaseContext ctx)
void SaveTrackedSnapshots(Detection clientDetection, DatabaseContext ctx)
{
// todo: why does this cause duplicate primary key
var change = clientDetection.Tracker.GetNextChange();

View File

@ -2,6 +2,7 @@
using SharedLibraryCore.Database.Models;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static SharedLibraryCore.Server;
namespace IW4MAdmin.Plugins.Stats.Models
{
@ -13,5 +14,6 @@ namespace IW4MAdmin.Plugins.Stats.Models
[Required]
public int Port { get; set; }
public string EndPoint { get; set; }
public Game? GameName { get; set; }
}
}

View File

@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
@ -26,7 +27,7 @@ namespace IW4MAdmin.Plugins.Stats
public string Author => "RaidMax";
public static StatManager Manager { get; private set; }
private IManager ServerManager;
public static IManager ServerManager;
public static BaseConfigurationHandler<StatsConfiguration> Config { get; private set; }
public async Task OnEventAsync(GameEvent E, Server S)
@ -51,7 +52,6 @@ namespace IW4MAdmin.Plugins.Stats
{
await Manager.AddMessageAsync(E.Origin.ClientId, await StatManager.GetIdForServer(E.Owner), E.Data);
}
break;
case GameEvent.EventType.MapChange:
Manager.SetTeamBased(await StatManager.GetIdForServer(E.Owner), E.Owner.Gametype != "dm");
@ -77,28 +77,72 @@ namespace IW4MAdmin.Plugins.Stats
break;
case GameEvent.EventType.ScriptKill:
string[] killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 14)
if (killInfo.Length >= 14 && !ShouldIgnoreEvent(E.Origin, E.Target))
{
// this treats "world" damage as self damage
if (E.Origin.ClientId <= 1)
{
E.Origin = E.Target;
}
if (E.Target.ClientId <= 1)
{
E.Target = E.Origin;
}
await Manager.AddScriptHit(false, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
}
break;
case GameEvent.EventType.Kill:
if (!E.Owner.CustomCallback)
if (!E.Owner.CustomCallback && !ShouldIgnoreEvent(E.Origin, E.Target))
{
// this treats "world" damage as self damage
if (E.Origin.ClientId <= 1)
{
E.Origin = E.Target;
}
if (E.Target.ClientId <= 1)
{
E.Target = E.Origin;
}
await Manager.AddStandardKill(E.Origin, E.Target);
}
break;
case GameEvent.EventType.Damage:
if (!E.Owner.CustomCallback)
if (!E.Owner.CustomCallback && !ShouldIgnoreEvent(E.Origin, E.Target))
{
// this treats "world" damage as self damage
if (E.Origin.ClientId <= 1)
{
E.Origin = E.Target;
}
if (E.Target.ClientId <= 1)
{
E.Target = E.Origin;
}
Manager.AddDamageEvent(E.Data, E.Origin.ClientId, E.Target.ClientId, await StatManager.GetIdForServer(E.Owner));
}
break;
case GameEvent.EventType.ScriptDamage:
killInfo = (E.Data != null) ? E.Data.Split(';') : new string[0];
if (killInfo.Length >= 14)
if (killInfo.Length >= 14 && !ShouldIgnoreEvent(E.Origin, E.Target))
{
// this treats "world" damage as self damage
if (E.Origin.ClientId <= 1)
{
E.Origin = E.Target;
}
if (E.Target.ClientId <= 1)
{
E.Target = E.Origin;
}
await Manager.AddScriptHit(true, E.Time, E.Origin, E.Target, await StatManager.GetIdForServer(E.Owner), S.CurrentMap.Name, killInfo[7], killInfo[8],
killInfo[5], killInfo[6], killInfo[3], killInfo[4], killInfo[9], killInfo[10], killInfo[11], killInfo[12], killInfo[13], killInfo[14], killInfo[15]);
}
@ -124,8 +168,13 @@ namespace IW4MAdmin.Plugins.Stats
"/Stats/TopPlayersAsync");
// meta data info
async Task<List<ProfileMeta>> getStats(int clientId)
async Task<List<ProfileMeta>> getStats(int clientId, int offset, int count, DateTime? startAt)
{
if (count > 1)
{
return new List<ProfileMeta>();
}
IList<EFClientStatistics> clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
{
@ -145,39 +194,63 @@ namespace IW4MAdmin.Plugins.Stats
new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
Value = "#" + await StatManager.GetClientOverallRanking(clientId),
Value = "#" + (await StatManager.GetClientOverallRanking(clientId)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 0,
Type = ProfileMeta.MetaType.Information
},
new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KILLS"],
Value = kills
Value = kills.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 1,
Type = ProfileMeta.MetaType.Information
},
new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_DEATHS"],
Value = deaths
Value = deaths.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 2,
Type = ProfileMeta.MetaType.Information
},
new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"],
Value = kdr
Value = kdr.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 3,
Type = ProfileMeta.MetaType.Information
},
new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_PERFORMANCE"],
Value = performance
Value = performance.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 4,
Type = ProfileMeta.MetaType.Information
},
new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_META_SPM"],
Value = spm
Value = spm.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 5,
Type = ProfileMeta.MetaType.Information
}
};
}
async Task<List<ProfileMeta>> getAnticheatInfo(int clientId)
async Task<List<ProfileMeta>> getAnticheatInfo(int clientId, int offset, int count, DateTime? startAt)
{
if (count > 1)
{
return new List<ProfileMeta>();
}
IList<EFClientStatistics> clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
{
clientStats = await ctx.Set<EFClientStatistics>()
@ -195,21 +268,21 @@ namespace IW4MAdmin.Plugins.Stats
if (clientStats.Where(cs => cs.HitLocations.Count > 0).FirstOrDefault() != null)
{
chestRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
chestRatio = Math.Round((clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
c.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_upper).HitCount) /
(double)clientStats.Where(c => c.HitLocations.Count > 0)
.Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount)), 2);
.Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount))) * 100.0, 0);
abdomenRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
abdomenRatio = Math.Round((clientStats.Where(c => c.HitLocations.Count > 0).Sum(c =>
c.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount) /
(double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount)), 2);
(double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount))) * 100.0, 0);
chestAbdomenRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_upper).HitCount) /
(double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount), 2);
chestAbdomenRatio = Math.Round((clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_upper).HitCount) /
(double)clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.torso_lower).HitCount)) * 100.0, 0);
headRatio = Math.Round(clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.head).HitCount) /
headRatio = Math.Round((clientStats.Where(c => c.HitLocations.Count > 0).Sum(cs => cs.HitLocations.First(hl => hl.Location == IW4Info.HitLocation.head).HitCount) /
(double)clientStats.Where(c => c.HitLocations.Count > 0)
.Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount)), 2);
.Sum(c => c.HitLocations.Where(hl => hl.Location != IW4Info.HitLocation.none).Sum(f => f.HitCount))) * 100.0, 0);
var validOffsets = clientStats.Where(c => c.HitLocations.Count(hl => hl.HitCount > 0) > 0).SelectMany(hl => hl.HitLocations);
hitOffsetAverage = validOffsets.Sum(o => o.HitCount * o.HitOffsetAverage) / (double)validOffsets.Sum(o => o.HitCount);
@ -219,104 +292,172 @@ namespace IW4MAdmin.Plugins.Stats
{
new ProfileMeta()
{
Key = "Chest Ratio",
Value = chestRatio,
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 1",
Value = chestRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
Type = ProfileMeta.MetaType.Information,
Column = 2,
Order = 0,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM1"],
Sensitive = true
},
new ProfileMeta()
{
Key = "Abdomen Ratio",
Value = abdomenRatio,
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 2",
Value = abdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
Type = ProfileMeta.MetaType.Information,
Column = 2,
Order = 1,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM2"],
Sensitive = true
},
new ProfileMeta()
{
Key = "Chest To Abdomen Ratio",
Value = chestAbdomenRatio,
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 3",
Value = chestAbdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
Type = ProfileMeta.MetaType.Information,
Column = 2,
Order = 2,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM3"],
Sensitive = true
},
new ProfileMeta()
{
Key = "Headshot Ratio",
Value = headRatio,
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 4",
Value = headRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
Type = ProfileMeta.MetaType.Information,
Column = 2,
Order = 3,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM4"],
Sensitive = true
},
new ProfileMeta()
{
Key = "Hit Offset Average",
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 5",
// todo: make sure this is wrapped somewhere else
Value = $"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°",
Type = ProfileMeta.MetaType.Information,
Column = 2,
Order = 4,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM5"],
Sensitive = true
},
new ProfileMeta()
{
Key = "Max Strain",
Value = Math.Round(maxStrain, 3),
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 6",
Value = Math.Round(maxStrain, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Type = ProfileMeta.MetaType.Information,
Column = 2,
Order = 5,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM6"],
Sensitive = true
},
};
}
async Task<List<ProfileMeta>> getMessages(int clientId)
async Task<List<ProfileMeta>> getMessages(int clientId, int offset, int count, DateTime? startAt)
{
if (count <= 1)
{
using (var ctx = new DatabaseContext(true))
{
return new List<ProfileMeta>
{
new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"],
Value = (await ctx.Set<EFClientMessage>()
.CountAsync(_message => _message.ClientId == clientId))
.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 1,
Order= 4,
Type = ProfileMeta.MetaType.Information
}
};
}
}
List<ProfileMeta> messageMeta;
using (var ctx = new DatabaseContext(disableTracking: true))
{
var messages = ctx.Set<EFClientMessage>().Where(m => m.ClientId == clientId);
var messages = ctx.Set<EFClientMessage>()
.Where(m => m.ClientId == clientId)
.Where(_message => _message.TimeSent < startAt)
.OrderByDescending(_message => _message.TimeSent)
.Skip(offset)
.Take(count);
messageMeta = await messages.Select(m => new ProfileMeta()
{
Key = "EventMessage",
Value = m.Message,
Key = null,
Value = new { m.Message, m.Server.GameName },
When = m.TimeSent,
Extra = m.ServerId.ToString()
Extra = m.ServerId.ToString(),
Type = ProfileMeta.MetaType.ChatMessage
}).ToListAsync();
}
messageMeta.Add(new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"],
Value = messageMeta.Count
});
foreach (var message in messageMeta)
{
if ((message.Value.Message as string).IsQuickMessage())
{
try
{
var quickMessages = ServerManager.GetApplicationSettings().Configuration()
.QuickMessages
.First(_qm => _qm.Game == message.Value.GameName);
message.Value = quickMessages.Messages[(message.Value.Message as string).Substring(1)];
message.Type = ProfileMeta.MetaType.QuickMessage;
}
catch
{
message.Value = message.Value.Message;
}
}
else
{
message.Value = message.Value.Message;
}
}
}
return messageMeta;
}
MetaService.AddMeta(getStats);
if (Config.Configuration().EnableAntiCheat)
{
MetaService.AddMeta(getAnticheatInfo);
MetaService.AddRuntimeMeta(getAnticheatInfo);
}
MetaService.AddMeta(getMessages);
MetaService.AddRuntimeMeta(getStats);
MetaService.AddRuntimeMeta(getMessages);
string totalKills(Server server)
async Task<string> totalKills(Server server)
{
using (var ctx = new DatabaseContext(disableTracking: true))
{
long kills = ctx.Set<EFServerStatistics>().Where(s => s.Active).Sum(s => s.TotalKills);
return kills.ToString("#,##0");
long kills = await ctx.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalKills);
return kills.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName));
}
}
string totalPlayTime(Server server)
async Task<string> totalPlayTime(Server server)
{
using (var ctx = new DatabaseContext(disableTracking: true))
{
long playTime = ctx.Set<EFServerStatistics>().Where(s => s.Active).Sum(s => s.TotalPlayTime);
return (playTime / 3600.0).ToString("#,##0");
long playTime = await ctx.Set<EFServerStatistics>().Where(s => s.Active).SumAsync(s => s.TotalPlayTime);
return (playTime / 3600.0).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName));
}
}
string topStats(Server s)
async Task<string> topStats(Server s)
{
return String.Join(Environment.NewLine, Commands.TopStats.GetTopStats(s).Result);
return string.Join(Environment.NewLine, await Commands.TopStats.GetTopStats(s));
}
string mostPlayed(Server s)
async Task<string> mostPlayed(Server s)
{
return String.Join(Environment.NewLine, Commands.MostPlayed.GetMostPlayed(s).Result);
return string.Join(Environment.NewLine, await Commands.MostPlayed.GetMostPlayed(s));
}
manager.GetMessageTokens().Add(new MessageToken("TOTALKILLS", totalKills));
@ -325,7 +466,6 @@ namespace IW4MAdmin.Plugins.Stats
manager.GetMessageTokens().Add(new MessageToken("MOSTPLAYED", mostPlayed));
ServerManager = manager;
Manager = new StatManager(manager);
}
@ -341,5 +481,17 @@ namespace IW4MAdmin.Plugins.Stats
await Manager.Sync(sv);
}
}
/// <summary>
/// Indicates if the event should be ignored
/// (If the client id or target id is not a real client or the target/origin is a bot and ignore bots is turned on)
/// </summary>
/// <param name="origin"></param>
/// <param name="target"></param>
/// <returns></returns>
private bool ShouldIgnoreEvent(EFClient origin, EFClient target)
{
return ((origin.ClientId <= 1 && target.ClientId <= 1) || (target.IsBot || origin.IsBot) && ServerManager.GetApplicationSettings().Configuration().IgnoreBots);
}
}
}

View File

@ -0,0 +1,28 @@
using IW4MAdmin.Plugins.Stats;
using IW4MAdmin.Plugins.Stats.Helpers;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using System.Threading.Tasks;
namespace Stats.ViewComponents
{
public class TopPlayersViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(int count, int offset, long? serverId = null)
{
if (serverId == 0)
{
serverId = null;
}
var server = Plugin.ServerManager.GetServers().FirstOrDefault(_server => _server.EndPoint == serverId);
if (server != null)
{
serverId = await StatManager.GetIdForServer(server);
}
return View("_List", await Plugin.Manager.GetTopStats(offset, count, serverId));
}
}
}

View File

@ -56,10 +56,10 @@ namespace Tests
var warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait();
Assert.True((client.Warnings == 1 ||
warnEvent.Failed) &&
Manager.GetPenaltyService().GetClientPenaltiesAsync(client.ClientId).Result.Count(p => p.Type == Penalty.PenaltyType.Warning) == 1,
"warning did not get applied");
//Assert.True((client.Warnings == 1 ||
// warnEvent.Failed) &&
// Manager.GetPenaltyService().GetClientPenaltiesAsync(client.ClientId).Result.Count(p => p.Type == Penalty.PenaltyType.Warning) == 1,
// "warning did not get applied");
warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait();

View File

@ -18,7 +18,7 @@ namespace Tests
{
File.WriteAllText("test_mp.log", "test_log_file");
IW4MAdmin.Application.Localization.Configure.Initialize("en-US");
//IW4MAdmin.Application.Localization.Configure.Initialize("en-US");
Manager = ApplicationManager.GetInstance();
@ -41,7 +41,7 @@ namespace Tests
Maps = new List<MapConfiguration>(),
RConPollRate = 10000
};
Manager.ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("test.json");
Manager.ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("test");
Manager.ConfigHandler.Set(config);
Manager.Init().Wait();

View File

@ -34,7 +34,7 @@ namespace Tests
[Fact]
public void AreCommandAliasesUnique()
{
var mgr = IW4MAdmin.Application.Program.ServerManager;
var mgr = Program.ServerManager;
bool test = mgr.GetCommands().Count == mgr.GetCommands().Select(c => c.Alias).Distinct().Count();
Assert.True(test, "command aliases are not unique");

View File

@ -13,8 +13,12 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.2" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RazorCompileOnBuild>true</RazorCompileOnBuild>
<ResolvedRazorCompileToolset>RazorSdk</ResolvedRazorCompileToolset>
<RazorCompileOnBuild Condition="'$(CONFIG)'!='Debug'">true</RazorCompileOnBuild>
<RazorCompiledOnPublish Condition="'$(CONFIG)'!='Debug'">true</RazorCompiledOnPublish>
<Configurations>Debug;Release;Prerelease</Configurations>
</PropertyGroup>
<ItemGroup>
@ -14,11 +15,9 @@
</ItemGroup>
<ItemGroup>
</ItemGroup>
<ItemGroup>
<Content Update="Views\_ViewImports.cshtml">
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">

View File

@ -25,6 +25,11 @@
return "0_no-place/menu_div_no_place.png";
}
}
@if (Model.Count == 0)
{
<div class="p-2 text-center">@SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_NOQUALIFY"]</div>
}
@foreach (var stat in Model)
{
<div class="row ml-0 mr-0 pt-2 pb-2">
@ -57,7 +62,7 @@
<span class="text-muted">@loc["WEBFRONT_PROFILE_LSEEN"]</span><span class="text-primary"> @stat.LastSeen </span><span class="text-muted">@loc["WEBFRONT_PENALTY_TEMPLATE_AGO"]</span>
</div>
<div class="col-md-6 client-rating-graph" id="rating_history_@stat.ClientId" data-history="@Html.Raw(Json.Serialize(stat.PerformanceHistory))">
<div class="col-md-6 client-rating-graph" id="rating_history_@(stat.ClientId + "_" + stat.Id)" data-history="@Html.Raw(Json.Serialize(stat.PerformanceHistory))">
</div>

View File

@ -1,14 +1,32 @@
@model List<IW4MAdmin.Plugins.Stats.Web.Dtos.TopStatsInfo>
<h4 class="pb-2 text-center ">@ViewBag.Title</h4>
<ul class="nav nav-tabs border-top border-bottom nav-fill row" role="tablist" id="stats_top_players">
<li class="nav-item">
<a class="nav-link active top-players-link" href="#server_0" role="tab" data-toggle="tab" aria-selected="true" data-serverid="0">All Servers</a>
</li>
<div id="stats_top_players" class="striped border-top border-bottom">
@await Html.PartialAsync("_List", Model)
@foreach (var server in ViewBag.Servers)
{
<li class="nav-item ">
<a class="nav-link top-players-link" href="#server_@server.ID" role="tab" data-toggle="tab" aria-selected="false" data-serverid="@server.ID">@server.Name</a>
</li>
}
</ul>
<div class="tab-content border-bottom row">
<div role="tabpanel" class="tab-pane active striped flex-fill" id="server_0">
@await Component.InvokeAsync("TopPlayers", new { count = 10, offset = 0 })
</div>
@foreach (var server in ViewBag.Servers)
{
<div role="tabpanel" class="tab-pane striped flex-fill" id="server_@server.ID">
</div>
}
</div>
@section scripts {
@section scripts
{
<environment include="Development">
<script type="text/javascript" src="~/js/loader.js"></script>
<script type="text/javascript" src="~/js/stats.js"></script>
</environment>
<script>initLoader('/Stats/GetTopPlayersAsync', '#stats_top_players', 50);</script>
<script>initLoader('/Stats/GetTopPlayersAsync', '#server_0', 10);</script>
}

View File

@ -3,10 +3,12 @@
Layout = null;
}
<div class="client-message-context bg-dark p-2 mt-2 mb-2 border-top border-bottom">
<h5>@Model.First().Time.ToString()</h5>
@foreach (var message in Model)
{
<span class="text-white">@Html.ActionLink(@message.Name, "ProfileAsync", "Client", new { id = message.ClientId})</span><span> &mdash; @message.Message</span><br />
}
<div class="client-message-context">
<h5 class="bg-primary pt-2 pb-2 pl-3 mb-0 mt-2 text-white">@Model.First().Time.ToString()</h5>
<div class="bg-dark p-3 mb-2 border-bottom">
@foreach (var message in Model)
{
<span class="text-white">@message.Name</span><span> &mdash; <span class="@(message.IsQuickMessage ? "font-italic" : "")">@message.Message</span></span><br />
}
</div>
</div>

View File

@ -0,0 +1 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -1,6 +1,5 @@
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
@ -23,7 +22,9 @@ namespace SharedLibraryCore.Commands
foreach (Command cmd in Manager.GetCommands())
{
if (cmd.Name == CommandString.ToLower() || cmd.Alias == CommandString.ToLower())
{
C = cmd;
}
}
if (C == null)
@ -34,6 +35,7 @@ namespace SharedLibraryCore.Commands
E.Data = E.Data.RemoveWords(1);
String[] Args = E.Data.Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
// todo: the code below can be cleaned up
if (E.Origin.Level < C.Permission)
{
@ -48,31 +50,36 @@ namespace SharedLibraryCore.Commands
throw new CommandException($"{E.Origin} did not supply enough arguments for \"{C.Name}\"");
}
if (C.RequiresTarget || Args.Length > 0)
if (C.RequiresTarget)
{
if (!Int32.TryParse(Args[0], out int cNum))
cNum = -1;
if (Args[0][0] == '@') // user specifying target by database ID
if (Args.Length > 0)
{
int dbID = -1;
int.TryParse(Args[0].Substring(1, Args[0].Length - 1), out dbID);
var found = await Manager.GetClientService().Get(dbID);
if (found != null)
if (!Int32.TryParse(Args[0], out int cNum))
{
E.Target = found;
E.Target.CurrentServer = E.Owner;
E.Data = String.Join(" ", Args.Skip(1));
cNum = -1;
}
}
else if (Args[0].Length < 3 && cNum > -1 && cNum < E.Owner.MaxClients) // user specifying target by client num
{
if (E.Owner.Clients[cNum] != null)
if (Args[0][0] == '@') // user specifying target by database ID
{
E.Target = E.Owner.Clients[cNum];
E.Data = String.Join(" ", Args.Skip(1));
int dbID = -1;
int.TryParse(Args[0].Substring(1, Args[0].Length - 1), out dbID);
var found = await Manager.GetClientService().Get(dbID);
if (found != null)
{
E.Target = found;
E.Target.CurrentServer = E.Owner;
E.Data = String.Join(" ", Args.Skip(1));
}
}
else if (Args[0].Length < 3 && cNum > -1 && cNum < E.Owner.MaxClients) // user specifying target by client num
{
if (E.Owner.Clients[cNum] != null)
{
E.Target = E.Owner.Clients[cNum];
E.Data = String.Join(" ", Args.Skip(1));
}
}
}
@ -103,7 +110,7 @@ namespace SharedLibraryCore.Commands
}
}
if (E.Target == null && C.RequiresTarget) // Find active player as single word
if (E.Target == null && C.RequiresTarget && Args.Length > 0) // Find active player as single word
{
matchingPlayers = E.Owner.GetClientByName(Args[0]);
if (matchingPlayers.Count > 1)
@ -115,6 +122,7 @@ namespace SharedLibraryCore.Commands
}
throw new CommandException($"{E.Origin} had multiple players found for {C.Name}");
}
else if (matchingPlayers.Count == 1)
{
E.Target = matchingPlayers.First();
@ -141,6 +149,7 @@ namespace SharedLibraryCore.Commands
throw new CommandException($"{E.Origin} specified invalid player for \"{C.Name}\"");
}
}
E.Data = E.Data.Trim();
return C;
}

View File

@ -1,7 +1,6 @@
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Events;
using SharedLibraryCore.Objects;
using SharedLibraryCore.Services;
using System;
@ -18,7 +17,7 @@ namespace SharedLibraryCore.Commands
public class CQuit : Command
{
public CQuit() :
base("quit", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_QUIT_DESC"], "q", EFClient.Permission.Owner, false)
base("quit", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_QUIT_DESC"], "q", EFClient.Permission.Owner, false)
{ }
public override Task ExecuteAsync(GameEvent E)
@ -30,32 +29,15 @@ namespace SharedLibraryCore.Commands
public class COwner : Command
{
public COwner() :
base("owner", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_DESC"], "iamgod", EFClient.Permission.User, false)
base("owner", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_DESC"], "iamgod", EFClient.Permission.User, false)
{ }
public override async Task ExecuteAsync(GameEvent E)
{
if ((await (E.Owner.Manager.GetClientService() as ClientService).GetOwners()).Count == 0)
if (await (E.Owner.Manager.GetClientService() as ClientService).GetOwnerCount() == 0 &&
!E.Origin.SetLevel(EFClient.Permission.Owner, Utilities.IW4MAdminClient(E.Owner)).Failed)
{
var oldPermission = E.Origin.Level;
E.Origin.Level = EFClient.Permission.Owner;
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_OWNER_SUCCESS"]);
await E.Owner.Manager.GetClientService().Update(E.Origin);
var e = new GameEvent()
{
Type = GameEvent.EventType.ChangePermission,
Origin = E.Origin,
Target = E.Origin,
Owner = E.Owner,
Extra = new Change()
{
PreviousValue = oldPermission.ToString(),
NewValue = E.Origin.Level.ToString()
}
};
E.Owner.Manager.GetEventHandler().AddEvent(e);
}
else
{
@ -67,7 +49,7 @@ namespace SharedLibraryCore.Commands
public class CWarn : Command
{
public CWarn() :
base("warn", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARN_DESC"], "w", EFClient.Permission.Trusted, true, new CommandArgument[]
base("warn", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARN_DESC"], "w", EFClient.Permission.Trusted, true, new CommandArgument[]
{
new CommandArgument()
{
@ -86,7 +68,7 @@ namespace SharedLibraryCore.Commands
{
if (E.Target.Warn(E.Data, E.Origin).Failed)
{
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARN_FAIL"]} {E.Target.Name}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARN_FAIL"].FormatExt(E.Target.Name));
}
return Task.CompletedTask;
@ -96,7 +78,7 @@ namespace SharedLibraryCore.Commands
public class CWarnClear : Command
{
public CWarnClear() :
base("warnclear", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARNCLEAR_DESC"], "wc", EFClient.Permission.Trusted, true, new CommandArgument[]
base("warnclear", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARNCLEAR_DESC"], "wc", EFClient.Permission.Trusted, true, new CommandArgument[]
{
new CommandArgument()
{
@ -110,7 +92,7 @@ namespace SharedLibraryCore.Commands
{
if (!E.Target.WarnClear(E.Origin).Failed)
{
E.Owner.Broadcast($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARNCLEAR_SUCCESS"]} {E.Target.Name}");
E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WARNCLEAR_SUCCESS"].FormatExt(E.Target.Name));
}
return Task.CompletedTask;
@ -120,7 +102,7 @@ namespace SharedLibraryCore.Commands
public class CKick : Command
{
public CKick() :
base("kick", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_DESC"], "k", EFClient.Permission.Moderator, true, new CommandArgument[]
base("kick", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_DESC"], "k", EFClient.Permission.Moderator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -138,15 +120,15 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent E)
{
var _ = !(await E.Target.Kick(E.Data, E.Origin).WaitAsync()).Failed ?
E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_SUCCESS"]}") :
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_FAIL"]} {E.Target.Name}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_SUCCESS"].FormatExt(E.Target.Name)) :
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_KICK_FAIL"].FormatExt(E.Target.Name));
}
}
public class CSay : Command
{
public CSay() :
base("say", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SAY_DESC"], "s", EFClient.Permission.Moderator, false, new CommandArgument[]
base("say", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SAY_DESC"], "s", EFClient.Permission.Moderator, false, new CommandArgument[]
{
new CommandArgument()
{
@ -166,7 +148,7 @@ namespace SharedLibraryCore.Commands
public class CTempBan : Command
{
public CTempBan() :
base("tempban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_DESC"], "tb", EFClient.Permission.Administrator, true, new CommandArgument[]
base("tempban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_DESC"], "tb", EFClient.Permission.Administrator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -204,8 +186,8 @@ namespace SharedLibraryCore.Commands
else
{
var _ = !(await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync()).Failed ?
E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"]} ^5{length.TimeSpanText()}") :
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"]} {E.Target.Name}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_SUCCESS"].FormatExt(E.Target, length.TimeSpanText())) :
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_TEMPBAN_FAIL"].FormatExt(E.Target.Name));
}
}
}
@ -214,7 +196,7 @@ namespace SharedLibraryCore.Commands
public class CBan : Command
{
public CBan() :
base("ban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_DESC"], "b", EFClient.Permission.SeniorAdmin, true, new CommandArgument[]
base("ban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_DESC"], "b", EFClient.Permission.SeniorAdmin, true, new CommandArgument[]
{
new CommandArgument()
{
@ -232,15 +214,15 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent E)
{
var _ = !(await E.Target.Ban(E.Data, E.Origin, false).WaitAsync()).Failed ?
E.Origin.Tell($"^5{E.Target} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_SUCCESS"]}") :
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_FAIL"]} {E.Target.Name}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_SUCCESS"].FormatExt(E.Target.Name)) :
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BAN_FAIL"].FormatExt(E.Target.Name));
}
}
public class CUnban : Command
{
public CUnban() :
base("unban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_DESC"], "ub", EFClient.Permission.SeniorAdmin, true, new CommandArgument[]
base("unban", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_DESC"], "ub", EFClient.Permission.SeniorAdmin, true, new CommandArgument[]
{
new CommandArgument()
{
@ -261,11 +243,11 @@ namespace SharedLibraryCore.Commands
if (penalties.Where(p => p.Type == Penalty.PenaltyType.Ban || p.Type == Penalty.PenaltyType.TempBan).FirstOrDefault() != null)
{
await E.Target.Unban(E.Data, E.Origin).WaitAsync();
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_SUCCESS"]} {E.Target}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_SUCCESS"].FormatExt(E.Target));
}
else
{
E.Origin.Tell($"{E.Target} {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_FAIL"]}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNBAN_FAIL"].FormatExt(E.Target));
}
}
}
@ -273,7 +255,7 @@ namespace SharedLibraryCore.Commands
public class CWhoAmI : Command
{
public CWhoAmI() :
base("whoami", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WHO_DESC"], "who", EFClient.Permission.User, false)
base("whoami", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_WHO_DESC"], "who", EFClient.Permission.User, false)
{ }
public override Task ExecuteAsync(GameEvent E)
@ -288,7 +270,7 @@ namespace SharedLibraryCore.Commands
public class CList : Command
{
public CList() :
base("list", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_LIST_DESC"], "l", EFClient.Permission.Moderator, false)
base("list", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_LIST_DESC"], "l", EFClient.Permission.Moderator, false)
{ }
public override Task ExecuteAsync(GameEvent E)
@ -307,11 +289,11 @@ namespace SharedLibraryCore.Commands
// todo: make this better :)
if (P.Masked)
{
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor( EFClient.Permission.User, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces( EFClient.Permission.SeniorAdmin.ToString().Length - EFClient.Permission.User.ToString().Length));
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(EFClient.Permission.User, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces(EFClient.Permission.SeniorAdmin.ToString().Length - EFClient.Permission.User.ToString().Length));
}
else
{
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(P.Level, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces( EFClient.Permission.SeniorAdmin.ToString().Length - P.Level.ToString().Length));
playerList.AppendFormat("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.ConvertLevelToColor(P.Level, P.ClientPermission.Name), P.ClientNumber, P.Name, Utilities.GetSpaces(EFClient.Permission.SeniorAdmin.ToString().Length - P.Level.ToString().Length));
}
if (count == 2 || E.Owner.GetClientsAsList().Count == 1)
@ -339,7 +321,7 @@ namespace SharedLibraryCore.Commands
public class CHelp : Command
{
public CHelp() :
base("help", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_HELP_DESC"], "h", EFClient.Permission.User, false, new CommandArgument[]
base("help", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_HELP_DESC"], "h", EFClient.Permission.User, false, new CommandArgument[]
{
new CommandArgument()
{
@ -404,7 +386,7 @@ namespace SharedLibraryCore.Commands
public class CFastRestart : Command
{
public CFastRestart() :
base("fastrestart", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FASTRESTART_DESC"], "fr", EFClient.Permission.Moderator, false)
base("fastrestart", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FASTRESTART_DESC"], "fr", EFClient.Permission.Moderator, false)
{ }
public override async Task ExecuteAsync(GameEvent E)
@ -420,7 +402,7 @@ namespace SharedLibraryCore.Commands
public class CMapRotate : Command
{
public CMapRotate() :
base("maprotate", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAPROTATE_DESC"], "mr", EFClient.Permission.Administrator, false)
base("maprotate", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAPROTATE_DESC"], "mr", EFClient.Permission.Administrator, false)
{ }
public override async Task ExecuteAsync(GameEvent E)
@ -437,7 +419,7 @@ namespace SharedLibraryCore.Commands
public class CSetLevel : Command
{
public CSetLevel() :
base("setlevel", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DESC"], "sl", EFClient.Permission.Moderator, true, new CommandArgument[]
base("setlevel", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DESC"], "sl", EFClient.Permission.Moderator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -452,98 +434,89 @@ namespace SharedLibraryCore.Commands
})
{ }
public override async Task ExecuteAsync(GameEvent E)
public override Task ExecuteAsync(GameEvent E)
{
EFClient.Permission oldPerm = E.Target.Level;
EFClient.Permission newPerm = Utilities.MatchPermission(E.Data);
if (E.Target == E.Origin)
{
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SELF"]);
return;
return Task.CompletedTask;
}
EFClient.Permission oldPerm = E.Target.Level;
EFClient.Permission newPerm = Utilities.MatchPermission(E.Data);
if (newPerm == EFClient.Permission.Owner &&
else if (newPerm == EFClient.Permission.Owner &&
!E.Owner.Manager.GetApplicationSettings().Configuration().EnableMultipleOwners)
{
// only one owner is allowed
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_OWNER"]);
return;
return Task.CompletedTask;
}
if (E.Origin.Level < EFClient.Permission.Owner &&
else if (E.Origin.Level < EFClient.Permission.Owner &&
!E.Owner.Manager.GetApplicationSettings().Configuration().EnableSteppedHierarchy)
{
// only the owner is allowed to set levels
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_STEPPEDDISABLED"]} ^5{E.Target.Name}");
return;
return Task.CompletedTask;
}
if (newPerm >= E.Origin.Level)
else if (E.Origin.Level <= newPerm &&
E.Origin.Level < EFClient.Permission.Owner)
{
if (E.Origin.Level < EFClient.Permission.Owner)
{
E.Origin.Tell(string.Format(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_LEVELTOOHIGH"], E.Target.Name, (E.Origin.Level - 1).ToString()));
return;
}
// can't promote a client to higher than your current perms
E.Origin.Tell(string.Format(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_LEVELTOOHIGH"], E.Target.Name, (E.Origin.Level - 1).ToString()));
return Task.CompletedTask;
}
else if (newPerm > EFClient.Permission.Banned)
else if (newPerm > EFClient.Permission.Banned)
{
var ActiveClient = E.Owner.Manager.GetActiveClients()
.FirstOrDefault(p => p.NetworkId == E.Target.NetworkId);
if (ActiveClient != null)
{
ActiveClient.Level = newPerm;
await E.Owner.Manager.GetClientService().Update(ActiveClient);
ActiveClient.SetLevel(newPerm, E.Origin);
// inform the client that they were promoted
// we don't really want to tell them if they're demoted haha
if (newPerm > oldPerm)
{
ActiveClient.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS_TARGET"]} {newPerm}");
ActiveClient.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS_TARGET"].FormatExt(newPerm));
}
}
else
{
E.Target.Level = newPerm;
await E.Owner.Manager.GetClientService().Update(E.Target);
E.Target.SetLevel(newPerm, E.Origin);
}
var e = new GameEvent()
{
Origin = E.Origin,
Target = E.Target,
Owner = E.Owner,
Type = GameEvent.EventType.ChangePermission,
Extra = new Change()
{
PreviousValue = oldPerm.ToString(),
NewValue = newPerm.ToString()
}
};
E.Owner.Manager.GetEventHandler().AddEvent(e);
var _ = newPerm < oldPerm ?
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DEMOTE_SUCCESS"]} {E.Target.Name}") :
E.Origin.Tell($"{E.Target.Name} {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS"]}");
// inform the origin that the client has been updated
_ = newPerm < oldPerm ?
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_DEMOTE_SUCCESS"].FormatExt(E.Target.Name)) :
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_SUCCESS"].FormatExt(E.Target.Name));
}
else
{
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETLEVEL_FAIL"]);
}
return Task.CompletedTask;
}
}
public class CUsage : Command
{
public CUsage() :
base("usage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_USAGE_DESC"], "us", EFClient.Permission.Moderator, false)
base("usage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_USAGE_DESC"], "us", EFClient.Permission.Moderator, false)
{ }
public override Task ExecuteAsync(GameEvent E)
{
E.Origin.Tell($"IW4MAdmin {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_USAGE_TEXT"]} " + Math.Round(((System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / 2048f) / 1200f), 1) + "MB");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_USAGE_TEXT"].FormatExt(Math.Round(((System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / 2048f) / 1200f), 1)));
return Task.CompletedTask;
}
}
@ -551,14 +524,14 @@ namespace SharedLibraryCore.Commands
public class CUptime : Command
{
public CUptime() :
base("uptime", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UPTIME_DESC"], "up", EFClient.Permission.Moderator, false)
base("uptime", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UPTIME_DESC"], "up", EFClient.Permission.Moderator, false)
{ }
public override Task ExecuteAsync(GameEvent E)
{
TimeSpan uptime = DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime;
var loc = Utilities.CurrentLocalization.LocalizationIndex;
E.Origin.Tell($"IW4M Admin {loc["COMMANDS_UPTIME_TEXT"]} {uptime.Days} {loc["GLOBAL_TIME_DAYS"]}, {uptime.Hours} {loc["GLOBAL_TIME_HOURS"]}, {uptime.Minutes} {loc["GLOBAL_TIME_MINUTES"]}");
E.Origin.Tell(loc["COMMANDS_UPTIME_TEXT"].FormatExt(uptime.Days, uptime.Hours, uptime.Minutes));
return Task.CompletedTask;
}
}
@ -566,13 +539,13 @@ namespace SharedLibraryCore.Commands
public class CListAdmins : Command
{
public CListAdmins() :
base("admins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ADMINS_DESC"], "a", EFClient.Permission.User, false)
base("admins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ADMINS_DESC"], "a", EFClient.Permission.User, false)
{ }
public static string OnlineAdmins(Server S)
{
var onlineAdmins = S.GetClientsAsList()
.Where(p => p.Level > EFClient.Permission.Flagged)
.Where(p => p.Level > EFClient.Permission.Flagged)
.Where(p => !p.Masked)
.Select(p => $"[^3{Utilities.ConvertLevelToColor(p.Level, p.ClientPermission.Name)}^7] {p.Name}");
@ -595,7 +568,7 @@ namespace SharedLibraryCore.Commands
public class CLoadMap : Command
{
public CLoadMap() :
base("map", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_DESC"], "m", EFClient.Permission.Administrator, false, new CommandArgument[]
base("map", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_DESC"], "m", EFClient.Permission.Administrator, false, new CommandArgument[]
{
new CommandArgument()
{
@ -612,14 +585,14 @@ namespace SharedLibraryCore.Commands
{
if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap)
{
E.Owner.Broadcast($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_SUCCESS"]} ^5{m.Alias}");
E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_SUCCESS"].FormatExt(m.Alias));
await Task.Delay(5000);
await E.Owner.LoadMap(m.Name);
return;
}
}
E.Owner.Broadcast($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_UKN"]} ^5{newMap}");
E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_UKN"].FormatExt(newMap));
await Task.Delay(5000);
await E.Owner.LoadMap(newMap);
}
@ -628,7 +601,7 @@ namespace SharedLibraryCore.Commands
public class CFindPlayer : Command
{
public CFindPlayer() :
base("find", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FIND_DESC"], "f", EFClient.Permission.Administrator, false, new CommandArgument[]
base("find", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FIND_DESC"], "f", EFClient.Permission.Administrator, false, new CommandArgument[]
{
new CommandArgument()
{
@ -672,7 +645,7 @@ namespace SharedLibraryCore.Commands
public class CListRules : Command
{
public CListRules() :
base("rules", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RULES_DESC"], "r", EFClient.Permission.User, false)
base("rules", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RULES_DESC"], "r", EFClient.Permission.User, false)
{ }
public override Task ExecuteAsync(GameEvent E)
@ -707,7 +680,7 @@ namespace SharedLibraryCore.Commands
public class CPrivateMessage : Command
{
public CPrivateMessage() :
base("privatemessage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PM_DESC"], "pm", EFClient.Permission.User, true, new CommandArgument[]
base("privatemessage", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PM_DESC"], "pm", EFClient.Permission.User, true, new CommandArgument[]
{
new CommandArgument()
{
@ -734,7 +707,7 @@ namespace SharedLibraryCore.Commands
public class CFlag : Command
{
public CFlag() :
base("flag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_DESC"], "fp", EFClient.Permission.Moderator, true, new CommandArgument[]
base("flag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_DESC"], "fp", EFClient.Permission.Moderator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -755,7 +728,7 @@ namespace SharedLibraryCore.Commands
if (E.FailReason == GameEvent.EventFailReason.Permission)
{
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_FAIL"]} ^5{E.Target.Name}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_FAIL"].FormatExt(E.Target.Name));
}
else if (E.FailReason == GameEvent.EventFailReason.Invalid)
@ -765,7 +738,7 @@ namespace SharedLibraryCore.Commands
else
{
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_SUCCESS"]} ^5{E.Target.Name}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_SUCCESS"].FormatExt(E.Target.Name));
}
return Task.CompletedTask;
@ -776,7 +749,7 @@ namespace SharedLibraryCore.Commands
public class CUnflag : Command
{
public CUnflag() :
base("unflag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_DESC"], "uf", EFClient.Permission.Moderator, true, new CommandArgument[]
base("unflag", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_DESC"], "uf", EFClient.Permission.Moderator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -792,7 +765,7 @@ namespace SharedLibraryCore.Commands
if (unflagEvent.FailReason == GameEvent.EventFailReason.Permission)
{
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_FAIL"]} ^5{E.Target.Name}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_UNFLAG_FAIL"].FormatExt(E.Target.Name));
}
else if (unflagEvent.FailReason == GameEvent.EventFailReason.Invalid)
@ -802,7 +775,7 @@ namespace SharedLibraryCore.Commands
else
{
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"]} ^5{E.Target.Name}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_FLAG_UNFLAG"].FormatExt(E.Target.Name));
}
return Task.CompletedTask;
@ -814,7 +787,7 @@ namespace SharedLibraryCore.Commands
public class CReport : Command
{
public CReport() :
base("report", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_DESC"], "rep", EFClient.Permission.User, true, new CommandArgument[]
base("report", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_DESC"], "rep", EFClient.Permission.User, true, new CommandArgument[]
{
new CommandArgument()
{
@ -841,7 +814,7 @@ namespace SharedLibraryCore.Commands
if (reportEvent.FailReason == GameEvent.EventFailReason.Permission)
{
commandEvent.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL"]} {commandEvent.Target.Name}");
commandEvent.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORT_FAIL"].FormatExt(commandEvent.Target.Name));
}
else if (reportEvent.FailReason == GameEvent.EventFailReason.Invalid)
@ -885,7 +858,7 @@ namespace SharedLibraryCore.Commands
public class CListReports : Command
{
public CListReports() :
base("reports", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORTS_DESC"], "reps", EFClient.Permission.Moderator, false, new CommandArgument[]
base("reports", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_REPORTS_DESC"], "reps", EFClient.Permission.Moderator, false, new CommandArgument[]
{
new CommandArgument()
{
@ -922,7 +895,7 @@ namespace SharedLibraryCore.Commands
public class CMask : Command
{
public CMask() :
base("mask", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MASK_DESC"], "hide", EFClient.Permission.Moderator, false)
base("mask", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MASK_DESC"], "hide", EFClient.Permission.Moderator, false)
{ }
public override async Task ExecuteAsync(GameEvent E)
@ -945,7 +918,7 @@ namespace SharedLibraryCore.Commands
public class CListBanInfo : Command
{
public CListBanInfo() :
base("baninfo", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_DESC"], "bi", EFClient.Permission.Moderator, true, new CommandArgument[]
base("baninfo", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_DESC"], "bi", EFClient.Permission.Moderator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -957,10 +930,8 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent E)
{
var B = await E.Owner.Manager.GetPenaltyService().GetClientPenaltiesAsync(E.Target.ClientId);
var penalty = B.FirstOrDefault(b => b.Type > Penalty.PenaltyType.Kick &&
(b.Expires == null || b.Expires > DateTime.UtcNow));
var existingPenalties = await E.Owner.Manager.GetPenaltyService().GetActivePenaltiesAsync(E.Target.AliasLinkId, E.Target.IPAddress);
var penalty = existingPenalties.FirstOrDefault(b => b.Type > Penalty.PenaltyType.Kick);
if (penalty == null)
{
@ -968,17 +939,23 @@ namespace SharedLibraryCore.Commands
return;
}
string timeRemaining = penalty.Type == Penalty.PenaltyType.TempBan ? $"({(penalty.Expires.Value - DateTime.UtcNow).TimeSpanText()} remaining)" : "";
string success = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_SUCCESS"];
if (penalty.Type == Penalty.PenaltyType.Ban)
{
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_SUCCESS"].FormatExt(E.Target.Name, penalty.Offense));
}
E.Origin.Tell($"^1{E.Target.Name} ^7{string.Format(success, penalty.Punisher.Name)} {penalty.Punisher.Name} {timeRemaining}");
else
{
string remainingTime = (penalty.Expires.Value - DateTime.UtcNow).TimeSpanText();
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_TB_SUCCESS"].FormatExt(E.Target.Name, penalty.Offense, remainingTime));
}
}
}
public class CListAlias : Command
{
public CListAlias() :
base("alias", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ALIAS_DESC"], "known", EFClient.Permission.Moderator, true, new CommandArgument[]
base("alias", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_ALIAS_DESC"], "known", EFClient.Permission.Moderator, true, new CommandArgument[]
{
new CommandArgument()
{
@ -1012,7 +989,7 @@ namespace SharedLibraryCore.Commands
public class CExecuteRCON : Command
{
public CExecuteRCON() :
base("rcon", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_DESC"], "rcon", EFClient.Permission.Owner, false, new CommandArgument[]
base("rcon", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RCON_DESC"], "rcon", EFClient.Permission.Owner, false, new CommandArgument[]
{
new CommandArgument()
{
@ -1040,7 +1017,7 @@ namespace SharedLibraryCore.Commands
public class CPlugins : Command
{
public CPlugins() :
base("plugins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PLUGINS_DESC"], "p", EFClient.Permission.Administrator, false)
base("plugins", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PLUGINS_DESC"], "p", EFClient.Permission.Administrator, false)
{ }
public override Task ExecuteAsync(GameEvent E)
@ -1057,19 +1034,19 @@ namespace SharedLibraryCore.Commands
public class CIP : Command
{
public CIP() :
base("getexternalip", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_IP_DESC"], "ip", EFClient.Permission.User, false)
base("getexternalip", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_IP_DESC"], "ip", EFClient.Permission.User, false)
{ }
public override Task ExecuteAsync(GameEvent E)
{
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_IP_SUCCESS"]} ^5{E.Origin.IPAddressString}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_IP_SUCCESS"].FormatExt(E.Origin.IPAddressString));
return Task.CompletedTask;
}
}
public class CPruneAdmins : Command
{
public CPruneAdmins() : base("prune", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_DESC"], "pa", EFClient.Permission.Owner, false, new CommandArgument[]
public CPruneAdmins() : base("prune", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_DESC"], "pa", EFClient.Permission.Owner, false, new CommandArgument[]
{
new CommandArgument()
{
@ -1108,19 +1085,19 @@ namespace SharedLibraryCore.Commands
{
var lastActive = DateTime.UtcNow.AddDays(-inactiveDays);
inactiveUsers = await context.Clients
.Where(c => c.Level > EFClient.Permission.Flagged && c.Level <= EFClient.Permission.Moderator)
.Where(c => c.Level > EFClient.Permission.Flagged && c.Level <= EFClient.Permission.Moderator)
.Where(c => c.LastConnection < lastActive)
.ToListAsync();
inactiveUsers.ForEach(c => c.Level = EFClient.Permission.User);
inactiveUsers.ForEach(c => c.SetLevel(EFClient.Permission.User, E.Origin));
await context.SaveChangesAsync();
}
E.Origin.Tell($"^5{inactiveUsers.Count} ^7{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_SUCCESS"]}");
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PRUNE_SUCCESS"].FormatExt(inactiveUsers.Count));
}
}
public class CSetPassword : Command
{
public CSetPassword() : base("setpassword", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETPASSWORD_DESC"], "sp", EFClient.Permission.Moderator, false, new CommandArgument[]
public CSetPassword() : base("setpassword", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_SETPASSWORD_DESC"], "sp", EFClient.Permission.Moderator, false, new CommandArgument[]
{
new CommandArgument()
{
@ -1154,7 +1131,7 @@ namespace SharedLibraryCore.Commands
public class CKillServer : Command
{
public CKillServer() : base("killserver", "kill the game server", "kill", EFClient.Permission.Administrator, false)
public CKillServer() : base("killserver", "kill the game server", "kill", EFClient.Permission.Administrator, false)
{
}
@ -1201,7 +1178,7 @@ namespace SharedLibraryCore.Commands
if (!E.Owner.Throttled)
{
#if !DEBUG
await E.Owner.ExecuteCommandAsync("quit");
await E.Owner.ExecuteCommandAsync("quit");
#endif
}
}
@ -1236,7 +1213,7 @@ namespace SharedLibraryCore.Commands
public class CPing : Command
{
public CPing() : base("ping", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_DESC"], "pi", EFClient.Permission.User, false, new CommandArgument[]
public CPing() : base("ping", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_DESC"], "pi", EFClient.Permission.User, false, new CommandArgument[]
{
new CommandArgument()
{
@ -1248,27 +1225,18 @@ namespace SharedLibraryCore.Commands
public override Task ExecuteAsync(GameEvent E)
{
if (E.Message.IsBroadcastCommand())
if (E.Target == null)
{
if (E.Target == null)
{
E.Owner.Broadcast($"{E.Origin.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Origin.Ping}^7ms");
}
else
{
E.Owner.Broadcast($"{E.Target.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Target.Ping}^7ms");
}
E.Target = E.Owner.GetClientByName(E.Data).FirstOrDefault();
}
if (E.Target == null)
{
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_SELF"].FormatExt(E.Origin.Ping));
}
else
{
if (E.Target == null)
{
E.Origin.Tell($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_SELF"]} ^5{E.Origin.Ping}^7ms");
}
else
{
E.Origin.Tell($"{E.Target.Name}'s {Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"]} ^5{E.Target.Ping}^7ms");
}
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_PING_TARGET"].FormatExt(E.Target.Name, E.Target.Ping));
}
return Task.CompletedTask;
@ -1277,7 +1245,7 @@ namespace SharedLibraryCore.Commands
public class CSetGravatar : Command
{
public CSetGravatar() : base("setgravatar", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GRAVATAR_DESC"], "sg", EFClient.Permission.User, false, new CommandArgument[]
public CSetGravatar() : base("setgravatar", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GRAVATAR_DESC"], "sg", EFClient.Permission.User, false, new CommandArgument[]
{
new CommandArgument()
{
@ -1289,50 +1257,16 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent E)
{
using (var ctx = new DatabaseContext())
var metaSvc = new MetaService();
using (var md5 = MD5.Create())
{
var iqMeta = from meta in ctx.EFMeta
where meta.ClientId == E.Origin.ClientId
where meta.Key == "GravatarEmail"
select meta;
var gravatarMeta = await iqMeta.FirstOrDefaultAsync();
// gravatar meta has never been added
if (gravatarMeta == null)
{
using (var md5 = MD5.Create())
{
gravatarMeta = new EFMeta()
{
Active = true,
ClientId = E.Origin.ClientId,
Key = "GravatarEmail",
Value = string.Concat(md5.ComputeHash(E.Data.ToLower().Select(d => Convert.ToByte(d)).ToArray())
.Select(h => h.ToString("x2"))),
};
ctx.EFMeta.Add(gravatarMeta);
await ctx.SaveChangesAsync();
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GRAVATAR_SUCCESS_NEW"]);
return;
}
}
else
{
ctx.EFMeta.Update(gravatarMeta);
using (var md5 = MD5.Create())
{
gravatarMeta.Value = string.Concat(md5.ComputeHash(E.Data.ToLower().Select(d => Convert.ToByte(d)).ToArray())
string gravatarEmail = string.Concat(md5.ComputeHash(E.Data.ToLower().Select(d => Convert.ToByte(d)).ToArray())
.Select(h => h.ToString("x2")));
gravatarMeta.Updated = DateTime.UtcNow;
}
await ctx.SaveChangesAsync();
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GRAVATAR_SUCCESS_UPDATE"]);
}
await metaSvc.AddPersistentMeta("GravatarEmail", gravatarEmail, E.Origin);
}
E.Origin.Tell(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_GRAVATAR_SUCCESS_NEW"]);
}
}
@ -1341,11 +1275,11 @@ namespace SharedLibraryCore.Commands
/// </summary>
public class CNextMap : Command
{
public CNextMap() : base("nextmap", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_NEXTMAP_DESC"], "nm", EFClient.Permission.User, false) { }
public CNextMap() : base("nextmap", Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_NEXTMAP_DESC"], "nm", EFClient.Permission.User, false) { }
public static async Task<string> GetNextMap(Server s)
{
string mapRotation = (await s.GetDvarAsync<string>("sv_mapRotation")).Value.ToLower();
var regexMatches = Regex.Matches(mapRotation, @"(gametype +([a-z]{1,4}))? *map ([a-z|_]+)", RegexOptions.IgnoreCase).ToList();
string mapRotation = (await s.GetDvarAsync<string>("sv_mapRotation")).Value?.ToLower() ?? "";
var regexMatches = Regex.Matches(mapRotation, @"((?:gametype|exec) +(?:([a-z]{1,4})(?:.cfg)?))? *map ([a-z|_|\d]+)", RegexOptions.IgnoreCase).ToList();
// find the current map in the rotation
var currentMap = regexMatches.Where(m => m.Groups[3].ToString() == s.CurrentMap.Name);
@ -1355,7 +1289,7 @@ namespace SharedLibraryCore.Commands
// no maprotation at all
if (regexMatches.Count() == 0)
{
return $"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_NEXTMAP_SUCCESS"]} ^5{s.CurrentMap.Alias}/{Utilities.GetLocalizedGametype(s.Gametype)}";
return Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_NEXTMAP_SUCCESS"].FormatExt(s.CurrentMap.Alias, Utilities.GetLocalizedGametype(s.Gametype));
}
// the current map is not in rotation
@ -1384,12 +1318,14 @@ namespace SharedLibraryCore.Commands
regexMatches[regexMatches.IndexOf(currentMap.First()) + 1] :
regexMatches.First();
nextMap = s.Maps.FirstOrDefault(m => m.Name == nextMapMatch.Groups[3].ToString()) ?? nextMap;
string nextMapName = nextMapMatch.Groups[3].ToString();
nextMap = s.Maps.FirstOrDefault(m => m.Name == nextMapMatch.Groups[3].ToString()) ?? new Map() { Alias = nextMapName, Name = nextMapName };
string nextGametype = nextMapMatch.Groups[2].ToString().Length == 0 ?
Utilities.GetLocalizedGametype(s.Gametype) :
Utilities.GetLocalizedGametype(nextMapMatch.Groups[2].ToString());
return $"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_NEXTMAP_SUCCESS"]} ^5{nextMap.Alias}/{nextGametype}";
return Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_NEXTMAP_SUCCESS"].FormatExt(nextMap.Alias, nextGametype);
}
public override async Task ExecuteAsync(GameEvent E)

View File

@ -1,38 +1,95 @@
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Configuration.Attributes;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
namespace SharedLibraryCore.Configuration
{
public class ApplicationConfiguration : IBaseConfiguration
{
[LocalizedDisplayName("SETUP_ENABLE_WEBFRONT")]
[ConfiguratinLinked("WebfrontBindUrl", "ManualWebfrontUrl")]
public bool EnableWebFront { get; set; }
public bool EnableMultipleOwners { get; set; }
public bool EnableSteppedHierarchy { get; set; }
public bool EnableSocialLink { get; set; }
public bool EnableCustomSayName { get; set; }
public string CustomSayName { get; set; }
public string SocialLinkAddress { get; set; }
public string SocialLinkTitle { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_BIND_URL")]
public string WebfrontBindUrl { get; set; }
[ConfigurationOptional]
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_MANUAL_URL")]
public string ManualWebfrontUrl { get; set; }
public string WebfrontUrl => string.IsNullOrEmpty(ManualWebfrontUrl) ? WebfrontBindUrl.Replace("0.0.0.0", "127.0.0.1") : ManualWebfrontUrl;
public string CustomParserEncoding { get; set; }
public string CustomLocale { get; set; }
public string DatabaseProvider { get; set; } = "sqlite";
public string ConnectionString { get; set; }
public int RConPollRate { get; set; } = 5000;
[LocalizedDisplayName("SETUP_ENABLE_MULTIOWN")]
public bool EnableMultipleOwners { get; set; }
[LocalizedDisplayName("SETUP_ENABLE_STEPPEDPRIV")]
public bool EnableSteppedHierarchy { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_USE_LOCAL_TRANSLATIONS")]
public bool UseLocalTranslations { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_IGNORE_BOTS")]
public bool IgnoreBots { get; set; }
public TimeSpan MaximumTempBanTime { get; set; } = new TimeSpan(24 * 30, 0, 0);
[ConfiguratinLinked("CustomSayName")]
[LocalizedDisplayName("SETUP_ENABLE_CUSTOMSAY")]
public bool EnableCustomSayName { get; set; }
[LocalizedDisplayName("SETUP_SAY_NAME")]
public string CustomSayName { get; set; }
[LocalizedDisplayName("SETUP_DISPLAY_SOCIAL")]
[ConfiguratinLinked("SocialLinkAddress", "SocialLinkTitle")]
public bool EnableSocialLink { get; set; }
[LocalizedDisplayName("SETUP_SOCIAL_LINK")]
public string SocialLinkAddress { get; set; }
[LocalizedDisplayName("SETUP_SOCIAL_TITLE")]
public string SocialLinkTitle { get; set; }
[LocalizedDisplayName("SETUP_USE_CUSTOMENCODING")]
[ConfiguratinLinked("CustomParserEncoding")]
public bool EnableCustomParserEncoding { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_ENCODING")]
public string CustomParserEncoding { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_ENABLE_WHITELIST")]
[ConfiguratinLinked("WebfrontConnectionWhitelist")]
public bool EnableWebfrontConnectionWhitelist { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_WHITELIST_LIST")]
public List<string> WebfrontConnectionWhitelist { get; set; }
public string Id { get; set; }
public List<ServerConfiguration> Servers { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_CUSTOM_LOCALE")]
[ConfiguratinLinked("CustomLocale")]
public bool EnableCustomLocale { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_CUSTOM_LOCALE")]
public string CustomLocale { get; set; }
[ConfigurationOptional]
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_DB_PROVIDER")]
public string DatabaseProvider { get; set; } = "sqlite";
[ConfigurationOptional]
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_CONNECTION_STRING")]
public string ConnectionString { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_RCON_POLLRATE")]
public int RConPollRate { get; set; } = 5000;
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_MAX_TB")]
public TimeSpan MaximumTempBanTime { get; set; } = new TimeSpan(24 * 30, 0, 0);
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_AUTOMESSAGE_PERIOD")]
public int AutoMessagePeriod { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_AUTOMESSAGES")]
public List<string> AutoMessages { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_GLOBAL_RULES")]
public List<string> GlobalRules { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_DISALLOWED_NAMES")]
public List<string> DisallowedClientNames { get; set; }
[UIHint("ServerConfiguration")]
public List<ServerConfiguration> Servers { get; set; }
[ConfigurationIgnore]
public string Id { get; set; }
[ConfigurationIgnore]
public List<MapConfiguration> Maps { get; set; }
[ConfigurationIgnore]
public List<QuickMessageConfiguration> QuickMessages { get; set; }
[ConfigurationIgnore]
public string WebfrontUrl => string.IsNullOrEmpty(ManualWebfrontUrl) ? WebfrontBindUrl?.Replace("0.0.0.0", "127.0.0.1") : ManualWebfrontUrl;
public IBaseConfiguration Generate()
{
@ -45,12 +102,17 @@ namespace SharedLibraryCore.Configuration
EnableCustomSayName = Utilities.PromptBool(loc["SETUP_ENABLE_CUSTOMSAY"]);
bool useCustomParserEncoding = Utilities.PromptBool(loc["SETUP_USE_CUSTOMENCODING"]);
CustomParserEncoding = useCustomParserEncoding ? Utilities.PromptString(loc["SETUP_ENCODING_STRING"]) : "windows-1252";
if (useCustomParserEncoding)
{
CustomParserEncoding = Utilities.PromptString(loc["SETUP_ENCODING_STRING"]);
}
WebfrontBindUrl = "http://0.0.0.0:1624";
if (EnableCustomSayName)
{
CustomSayName = Utilities.PromptString(loc["SETUP_SAY_NAME"]);
}
EnableSocialLink = Utilities.PromptBool(loc["SETUP_DISPLAY_SOCIAL"]);
@ -61,10 +123,13 @@ namespace SharedLibraryCore.Configuration
}
RConPollRate = 5000;
AutoMessagePeriod = 60;
return this;
}
public string Name() => "ApplicationConfiguration";
public string Name()
{
return "ApplicationConfiguration";
}
}
}

View File

@ -0,0 +1,15 @@
using System;
namespace SharedLibraryCore.Configuration.Attributes
{
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class ConfiguratinLinked : Attribute
{
public string[] LinkedPropertyNames { get; set; }
public ConfiguratinLinked(params string[] linkedPropertyNames)
{
LinkedPropertyNames = linkedPropertyNames;
}
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Configuration.Attributes
{
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class ConfigurationIgnore : Attribute
{
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Configuration.Attributes
{
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class ConfigurationOptional : Attribute
{
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
namespace SharedLibraryCore.Configuration.Attributes
{
class LocalizedDisplayName : DisplayNameAttribute
{
private readonly string _localizationKey;
public LocalizedDisplayName(string localizationKey)
{
_localizationKey = localizationKey;
}
public override string DisplayName => Utilities.CurrentLocalization.LocalizationIndex[_localizationKey];
}
}

View File

@ -7,10 +7,11 @@ namespace SharedLibraryCore.Configuration
{
public class DefaultConfiguration : IBaseConfiguration
{
public int AutoMessagePeriod { get; set; }
public List<string> AutoMessages { get; set; }
public List<string> GlobalRules { get; set; }
public List<MapConfiguration> Maps { get; set; }
public List<QuickMessageConfiguration> QuickMessages {get; set;}
public List<string> DisallowedClientNames { get; set; }
public IBaseConfiguration Generate() => this;

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
using static SharedLibraryCore.Server;
namespace SharedLibraryCore.Configuration
{
public class QuickMessageConfiguration
{
public Game Game { get; set; }
public Dictionary<string, string> Messages { get; set; }
}
}

View File

@ -1,4 +1,5 @@
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Configuration.Attributes;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
@ -7,15 +8,27 @@ namespace SharedLibraryCore.Configuration
{
public class ServerConfiguration : IBaseConfiguration
{
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_IP")]
public string IPAddress { get; set; }
public ushort Port { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_PORT")]
public int Port { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_PASSWORD")]
public string Password { get; set; }
public IList<string> Rules { get; set; }
public IList<string> AutoMessages { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_RULES")]
public List<string> Rules { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_AUTO_MESSAGES")]
public List<string> AutoMessages { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_PATH")]
[ConfigurationOptional]
public string ManualLogPath { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_RCON_PARSER")]
public string RConParserVersion { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_EVENT_PARSER")]
public string EventParserVersion { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_RESERVED_SLOT")]
public int ReservedSlotNumber { get; set; }
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_GAME_LOG_SERVER")]
[ConfigurationOptional]
public Uri GameLogServerUrl { get; set; }
private readonly IList<IRConParser> rconParsers;
@ -25,10 +38,19 @@ namespace SharedLibraryCore.Configuration
{
rconParsers = new List<IRConParser>();
eventParsers = new List<IEventParser>();
Rules = new List<string>();
AutoMessages = new List<string>();
}
public void AddRConParser(IRConParser parser) => rconParsers.Add(parser);
public void AddEventParser(IEventParser parser) => eventParsers.Add(parser);
public void AddRConParser(IRConParser parser)
{
rconParsers.Add(parser);
}
public void AddEventParser(IEventParser parser)
{
eventParsers.Add(parser);
}
public void ModifyParsers()
{
@ -65,26 +87,26 @@ namespace SharedLibraryCore.Configuration
string input = Utilities.PromptString(loc["SETUP_SERVER_IP"]);
if (System.Net.IPAddress.TryParse(input, out System.Net.IPAddress ip))
{
IPAddress = input;
}
}
while(Port < 1)
{
string input = Utilities.PromptString(loc["SETUP_SERVER_PORT"]);
if (UInt16.TryParse(input, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.CurrentCulture, out ushort port))
Port = port;
}
Port = Utilities.PromptInt(loc["SETUP_SERVER_PORT"], null, 1, ushort.MaxValue);
Password = Utilities.PromptString(loc["SETUP_SERVER_RCON"]);
AutoMessages = new List<string>();
Rules = new List<string>();
ReservedSlotNumber = loc["SETUP_SERVER_RESERVEDSLOT"].PromptInt(null, 0, 32);
ManualLogPath = null;
ModifyParsers();
return this;
}
public string Name() => "ServerConfiguration";
public string Name()
{
return "ServerConfiguration";
}
}
}

View File

@ -34,7 +34,6 @@ namespace SharedLibraryCore.Database
AliasId = 1,
Active = true,
DateAdded = DateTime.UtcNow,
IPAddress = 0,
Name = "IW4MAdmin",
LinkId = 1
};

View File

@ -22,13 +22,35 @@ namespace SharedLibraryCore.Database
static string _ConnectionString;
static string _provider;
private static readonly string _migrationPluginDirectory = @"X:\IW4MAdmin\BUILD\Plugins\";
private static readonly string _migrationPluginDirectory = @"X:\IW4MAdmin\BUILD\Plugins";
private static int activeContextCount;
public DatabaseContext(DbContextOptions<DatabaseContext> opt) : base(opt) { }
public DatabaseContext(DbContextOptions<DatabaseContext> opt) : base(opt)
{
#if DEBUG == true
activeContextCount++;
Console.WriteLine($"Initialized DB Context #{activeContextCount}");
#endif
}
public DatabaseContext() { }
public DatabaseContext()
{
#if DEBUG == true
activeContextCount++;
Console.WriteLine($"Initialized DB Context #{activeContextCount}");
#endif
}
public DatabaseContext(bool disableTracking)
public override void Dispose()
{
#if DEBUG == true
Console.WriteLine($"Disposed DB Context #{activeContextCount}");
activeContextCount--;
#endif
}
public DatabaseContext(bool disableTracking) : this()
{
if (disableTracking)
{
@ -45,7 +67,7 @@ namespace SharedLibraryCore.Database
}
}
public DatabaseContext(string connStr, string provider)
public DatabaseContext(string connStr, string provider) : this()
{
_ConnectionString = connStr;
_provider = provider;
@ -123,6 +145,11 @@ namespace SharedLibraryCore.Database
ent.HasIndex(a => a.Name);
});
modelBuilder.Entity<EFMeta>(ent =>
{
ent.HasIndex(_meta => _meta.Key);
});
// force full name for database conversion
modelBuilder.Entity<EFClient>().ToTable("EFClients");
modelBuilder.Entity<EFAlias>().ToTable("EFAlias");
@ -138,7 +165,6 @@ namespace SharedLibraryCore.Database
#endif
IEnumerable<string> directoryFiles = Directory.GetFiles(pluginDir).Where(f => f.EndsWith(".dll"));
foreach (string dllPath in directoryFiles)
{
Assembly library;

View File

@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace SharedLibraryCore.Database.Models
{
public class EFAlias : SharedEntity
public partial class EFAlias : SharedEntity
{
[Key]
public int AliasId { get; set; }

View File

@ -40,8 +40,8 @@ namespace SharedLibraryCore.Database.Models
[NotMapped]
public virtual string Name
{
get { return CurrentAlias.Name; }
set { CurrentAlias.Name = value; }
get { return CurrentAlias?.Name ?? "--"; }
set { if (CurrentAlias != null) CurrentAlias.Name = value; }
}
[NotMapped]
public virtual int? IPAddress

View File

@ -23,6 +23,8 @@ namespace SharedLibraryCore.Database.Models
public virtual EFClient Client { get; set; }
[Required]
[MinLength(3)]
[StringLength(32)]
[MaxLength(32)]
public string Key { get; set; }
[Required]
public string Value { get; set; }

View File

@ -1,4 +1,5 @@
using System;
using static SharedLibraryCore.Server;
namespace SharedLibraryCore.Dtos
{
@ -8,5 +9,7 @@ namespace SharedLibraryCore.Dtos
public string Message { get; set; }
public DateTime Time { get; set; }
public string Name { get; set; }
public Game ServerGame { get; set; }
public bool IsQuickMessage { get; set; }
}
}

View File

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
using static SharedLibraryCore.Objects.Penalty;
namespace SharedLibraryCore.Dtos
{
@ -10,14 +8,25 @@ namespace SharedLibraryCore.Dtos
{
public string OffenderName { get; set; }
public int OffenderId { get; set; }
public ulong OffenderNetworkId { get; set; }
public string OffenderIPAddress { get; set; }
public string PunisherName { get; set; }
public int PunisherId { get; set; }
public string PunisherLevel { get; set; }
public int PunisherLevelId { get; set; }
public ulong PunisherNetworkId { get; set; }
public string PunisherIPAddress { get; set; }
public Permission PunisherLevel { get; set; }
public string PunisherLevelText => PunisherLevel.ToLocalizedLevelName();
public string Offense { get; set; }
public string AutomatedOffense { get; set; }
public string Type { get; set; }
public string TimePunished { get; set; }
public string TimeRemaining { get; set; }
public PenaltyType PenaltyType { get; set; }
public string PenaltyTypeText => PenaltyType.ToString();
public DateTime TimePunished { get; set; }
public string TimePunishedString => Utilities.GetTimePassed(TimePunished, true);
public string TimeRemaining => DateTime.UtcNow > Expires ? "" : $"{((Expires ?? DateTime.MaxValue).Year == DateTime.MaxValue.Year ? Utilities.GetTimePassed(TimePunished, true) : Utilities.TimeSpanText((Expires ?? DateTime.MaxValue) - DateTime.UtcNow))}";
public bool Expired => Expires.HasValue && Expires <= DateTime.UtcNow;
public DateTime? Expires { get; set; }
public override bool Sensitive => PenaltyType == PenaltyType.Flag || PenaltyType == PenaltyType.Unflag;
public bool IsEvade { get; set; }
public string AdditionalPenaltyInformation => $"{(!string.IsNullOrEmpty(AutomatedOffense) ? $" ({AutomatedOffense})" : "")}{(IsEvade ? $" ({Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PENALTY_EVADE"]})" : "")}";
}
}

View File

@ -17,14 +17,13 @@ namespace SharedLibraryCore.Dtos
public List<string> Aliases { get; set; }
public List<string> IPs { get; set; }
public bool HasActivePenalty { get; set; }
public int ConnectionCount { get; set; }
public string LastSeen { get; set; }
public string FirstSeen { get; set; }
public string TimePlayed { get; set; }
public string ActivePenaltyType { get; set; }
public bool Authenticated { get; set; }
public List<ProfileMeta> Meta { get; set; }
public bool Online { get; set; }
public string TimeOnline { get; set; }
public DateTime LastConnection { get; set; }
public string LastConnectionText => Utilities.GetTimePassed(LastConnection, true);
public IDictionary<int, long> LinkedAccounts { get; set; }
}
}

View File

@ -8,11 +8,25 @@ namespace SharedLibraryCore.Dtos
{
public class ProfileMeta : SharedInfo
{
public enum MetaType
{
Other,
Information,
AliasUpdate,
ChatMessage,
Penalized,
ReceivedPenalty,
QuickMessage
}
public DateTime When { get; set; }
public string WhenString => Utilities.GetTimePassed(When, false);
public string Key { get; set; }
public dynamic Value { get; set; }
public string Extra { get; set; }
public virtual string Class => Value.GetType().ToString();
public MetaType Type { get; set; }
public int? Column { get; set; }
public int? Order { get; set; }
}
}

View File

@ -19,5 +19,7 @@ namespace SharedLibraryCore.Dtos
public Helpers.PlayerHistory[] PlayerHistory { get; set; }
public long ID { get; set; }
public bool Online { get; set; }
public string ConnectProtocolUrl { get; set; }
public string IPAddress { get; set; }
}
}

View File

@ -3,8 +3,8 @@ namespace SharedLibraryCore.Dtos
{
public class SharedInfo
{
public bool Sensitive { get; set; }
public virtual bool Sensitive { get; set; }
public bool Show { get; set; } = true;
public int Id {get;set;}
}
public int Id { get; set; }
}
}

View File

@ -85,6 +85,14 @@ namespace SharedLibraryCore
/// a client's information was updated
/// </summary>
Update,
/// <summary>
/// connection was lost to a server (the server has not responded after a number of attempts)
/// </summary>
ConnectionLost,
/// <summary>
/// connection was restored to a server (the server began responding again)
/// </summary>
ConnectionRestored,
// events "generated" by clients
/// <summary>
@ -167,6 +175,10 @@ namespace SharedLibraryCore
/// team info printed out by game script
/// </summary>
JoinTeam = 304,
/// <summary>
/// used for community generated plugin events
/// </summary>
Other
}
static long NextEventId;

View File

@ -1,47 +1,45 @@
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using SharedLibraryCore.Exceptions;
using Newtonsoft.Json;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Configuration
{
public class BaseConfigurationHandler<T> : IConfigurationHandler<T> where T : IBaseConfiguration
{
readonly string Filename;
IConfigurationRoot ConfigurationRoot { get; set; }
readonly string _configurationPath;
T _configuration;
public BaseConfigurationHandler(string fn)
{
Filename = fn;
_configurationPath = Path.Join(Utilities.OperatingDirectory, "Configuration", $"{fn}.json");
Build();
}
public void Build()
{
ConfigurationRoot = new ConfigurationBuilder()
.AddJsonFile(Path.Join(Utilities.OperatingDirectory, "Configuration", $"{Filename}.json"), true)
.Build();
_configuration = ConfigurationRoot.Get<T>();
if (_configuration == null)
try
{
var configContent = File.ReadAllText(_configurationPath);
_configuration = JsonConvert.DeserializeObject<T>(configContent);
}
catch
{
_configuration = default(T);
}
}
public Task Save()
{
var appConfigJSON = JsonConvert.SerializeObject(_configuration, Formatting.Indented);
return File.WriteAllTextAsync(Path.Join(Utilities.OperatingDirectory, "Configuration", $"{Filename}.json"), appConfigJSON);
return File.WriteAllTextAsync(_configurationPath, appConfigJSON);
}
public T Configuration() => _configuration;
public T Configuration()
{
return _configuration;
}
public void Set(T config)
{

View File

@ -1,20 +1,25 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace SharedLibraryCore.Helpers
{
public class MessageToken
{
public string Name { get; private set; }
Func<Server, string> Value;
public MessageToken(string Name, Func<Server, string> Value)
private readonly Func<Server, Task<string>> _asyncValue;
public MessageToken(string Name, Func<Server, Task<string>> Value)
{
this.Name = Name;
this.Value = Value;
_asyncValue = Value;
}
public string Process(Server server)
public async Task<string> ProcessAsync(Server server)
{
return this.Value(server);
string result = await _asyncValue(server);
return result;
}
}
}

View File

@ -11,19 +11,10 @@ namespace SharedLibraryCore.Helpers
{
DateTime t = DateTime.UtcNow;
When = new DateTime(t.Year, t.Month, t.Day, t.Hour, Math.Min(59, UpdateInterval * (int)Math.Round(t.Minute / (float)UpdateInterval)), 0);
PlayerCount = cNum;
y = cNum;
}
#if DEBUG
public PlayerHistory(DateTime t, int cNum)
{
When = new DateTime(t.Year, t.Month, t.Day, t.Hour, Math.Min(59, UpdateInterval * (int)Math.Round(t.Minute / (float)UpdateInterval)), 0);
PlayerCount = cNum;
}
#endif
private DateTime When;
private int PlayerCount;
/// <summary>
/// Used by CanvasJS as a point on the x axis
@ -36,16 +27,9 @@ namespace SharedLibraryCore.Helpers
}
}
/// <summary>
/// Used by CanvasJS as a point on the y axis
/// </summary>
public int y
{
get
{
return PlayerCount;
}
}
public int y { get; }
}
}

View File

@ -27,5 +27,10 @@ namespace SharedLibraryCore.Interfaces
/// specifies the game name (usually the internal studio iteration ie: IW4, T5 etc...)
/// </summary>
Game GameName { get; set; }
/// <summary>
/// specifies the connect URI used to join game servers via web browser
/// </summary>
string URLProtocolFormat { get; set; }
}
}

View File

@ -46,5 +46,6 @@ namespace SharedLibraryCore.Interfaces
IEventParser GenerateDynamicEventParser();
string Version { get;}
ITokenAuthentication TokenAuthenticator { get; }
string ExternalIPAddress { get; }
}
}

View File

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

View File

@ -0,0 +1,30 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class AddIndexToEFMetaKeyAndClientId : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder.ActiveProvider == "Pomelo.EntityFrameworkCore.MySql")
{
migrationBuilder.Sql("CREATE FULLTEXT INDEX IX_EFMeta_Key ON EFMeta ( `Key` );");
}
else
{
migrationBuilder.CreateIndex(
name: "IX_EFMeta_Key",
table: "EFMeta",
column: "Key");
}
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFMeta_Key",
table: "EFMeta");
}
}
}

View File

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

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace SharedLibraryCore.Migrations
{
public partial class AddGameNameToEFServer : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "GameName",
table: "EFServers",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GameName",
table: "EFServers");
}
}
}

View File

@ -14,7 +14,7 @@ namespace SharedLibraryCore.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.4-rtm-31024");
.HasAnnotation("ProductVersion", "2.2.2-servicing-10034");
modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFACSnapshot", b =>
{
@ -283,6 +283,8 @@ namespace SharedLibraryCore.Migrations
b.Property<string>("EndPoint");
b.Property<int?>("GameName");
b.Property<int>("Port");
b.HasKey("ServerId");
@ -432,7 +434,8 @@ namespace SharedLibraryCore.Migrations
b.Property<string>("Extra");
b.Property<string>("Key")
.IsRequired();
.IsRequired()
.HasMaxLength(32);
b.Property<DateTime>("Updated");
@ -443,6 +446,8 @@ namespace SharedLibraryCore.Migrations
b.HasIndex("ClientId");
b.HasIndex("Key");
b.ToTable("EFMeta");
});

View File

@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Objects
{
public class Alias : Database.Models.EFAlias
{
}
}

View File

@ -0,0 +1,7 @@
namespace SharedLibraryCore.Database.Models
{
public partial class EFAlias
{
}
}

View File

@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace SharedLibraryCore.Database.Models
@ -79,12 +80,12 @@ namespace SharedLibraryCore.Database.Models
{
{ "_reportCount", 0 }
};
CurrentAlias = new EFAlias();
ReceivedPenalties = new List<EFPenalty>();
}
public override string ToString()
{
return $"{Name}::{NetworkId}";
return $"{CurrentAlias?.Name ?? "--"}::{NetworkId}";
}
/// <summary>
@ -241,11 +242,6 @@ namespace SharedLibraryCore.Database.Models
e.FailReason = GameEvent.EventFailReason.Invalid;
}
else
{
this.Level = Permission.Flagged;
}
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
return e;
}
@ -273,16 +269,11 @@ namespace SharedLibraryCore.Database.Models
e.FailReason = GameEvent.EventFailReason.Permission;
}
else if (this.Level != EFClient.Permission.Flagged)
else if (this.Level != Permission.Flagged)
{
e.FailReason = GameEvent.EventFailReason.Invalid;
}
else
{
this.Level = Permission.User;
}
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
return e;
}
@ -377,7 +368,7 @@ namespace SharedLibraryCore.Database.Models
/// <param name="unbanReason">reason for the unban</param>
/// <param name="sender">client performing the unban</param>
/// <returns></returns>
public GameEvent Unban(String unbanReason, EFClient sender)
public GameEvent Unban(string unbanReason, EFClient sender)
{
var e = new GameEvent()
{
@ -399,6 +390,42 @@ namespace SharedLibraryCore.Database.Models
return e;
}
/// <summary>
/// sets the level of the client
/// </summary>
/// <param name="newPermission">new permission to set client to</param>
/// <param name="sender">user performing the set level</param>
/// <returns></returns>
public GameEvent SetLevel(Permission newPermission, EFClient sender)
{
var e = new GameEvent()
{
Type = GameEvent.EventType.ChangePermission,
Extra = newPermission,
Origin = sender,
Target = this,
Owner = sender.CurrentServer
};
if (this.Level > sender.Level)
{
e.FailReason = GameEvent.EventFailReason.Permission;
}
else if (Level == newPermission)
{
e.FailReason = GameEvent.EventFailReason.Invalid;
}
else
{
Level = newPermission;
}
sender.CurrentServer.Manager.GetEventHandler().AddEvent(e);
return e;
}
/// <summary>
/// Handles any client related logic on connection
/// </summary>
@ -416,9 +443,9 @@ namespace SharedLibraryCore.Database.Models
return;
}
if (Name == "Unknown Soldier" ||
Name == "UnknownSoldier" ||
Name == "CHEATER")
if (CurrentServer.Manager.GetApplicationSettings().Configuration()
.DisallowedClientNames
?.Any(_name => Regex.IsMatch(Name, _name)) ?? false)
{
CurrentServer.Logger.WriteDebug($"Kicking {this} because their name is generic");
Kick(loc["SERVER_KICK_GENERICNAME"], Utilities.IW4MAdminClient(CurrentServer));
@ -450,95 +477,141 @@ namespace SharedLibraryCore.Database.Models
State = ClientState.Disconnecting;
TotalConnectionTime += ConnectionLength;
LastConnection = DateTime.UtcNow;
await CurrentServer.Manager.GetClientService().Update(this);
try
{
await CurrentServer.Manager.GetClientService().Update(this);
}
catch (Exception e)
{
CurrentServer.Logger.WriteWarning($"Could not update disconnected player {this}");
CurrentServer.Logger.WriteDebug(e.GetExceptionInfo());
}
}
public async Task<bool> OnJoin(int? ipAddress)
public async Task OnJoin(int? ipAddress)
{
CurrentServer.Logger.WriteDebug($"Start join for {this}::{ipAddress}::{Level.ToString()}");
IPAddress = ipAddress;
var loc = Utilities.CurrentLocalization.LocalizationIndex;
var autoKickClient = Utilities.IW4MAdminClient(CurrentServer);
if (ipAddress != null)
{
// todo: remove this in a few weeks because it's just temporary for server forwarding
if (IPAddressString == "66.150.121.184" || IPAddressString == "62.210.178.177")
{
Kick($"Your favorite servers are outdated. Please remove and re-add this server. ({CurrentServer.Hostname})", autoKickClient);
return false;
}
IPAddress = ipAddress;
await CurrentServer.Manager.GetClientService().UpdateAlias(this);
}
// we want to run any non GUID based logic here
OnConnect();
await CurrentServer.Manager.GetClientService().Update(this);
CurrentServer.Logger.WriteDebug($"OnConnect finished for {this}");
if (await CanConnect(ipAddress))
{
if (IPAddress != null)
{
await CurrentServer.Manager.GetClientService().Update(this);
#region CLIENT_BAN
var e = new GameEvent()
{
Type = GameEvent.EventType.Join,
Origin = this,
Target = this,
Owner = CurrentServer
};
CurrentServer.Manager.GetEventHandler().AddEvent(e);
}
}
else
{
CurrentServer.Logger.WriteDebug($"Client {this} is not allowed to join the server");
}
CurrentServer.Logger.WriteDebug($"OnJoin finished for {this}");
}
private async Task<bool> CanConnect(int? ipAddress)
{
var loc = Utilities.CurrentLocalization.LocalizationIndex;
var autoKickClient = Utilities.IW4MAdminClient(CurrentServer);
#region CLIENT_GUID_BAN
// kick them as their level is banned
if (Level == Permission.Banned)
{
CurrentServer.Logger.WriteDebug($"Kicking {this} because they are banned");
var ban = ReceivedPenalties.FirstOrDefault(_penalty => _penalty.Expires == null && _penalty.Active);
var profileBan = ReceivedPenalties.FirstOrDefault(_penalty => _penalty.Expires == null && _penalty.Active);
if (ban == null)
if (profileBan == null)
{
// this is from the old system before bans were applied to all accounts
ban = (await CurrentServer.Manager
profileBan = (await CurrentServer.Manager
.GetPenaltyService()
.GetActivePenaltiesAsync(AliasLinkId))
.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.Ban);
CurrentServer.Logger.WriteError($"Client {this} is banned, but no penalty exists for their ban");
CurrentServer.Logger.WriteWarning($"Client {this} is GUID banned, but no previous penalty exists for their ban");
if (profileBan == null)
{
profileBan = new EFPenalty() { Offense = loc["SERVER_BAN_UNKNOWN"] };
CurrentServer.Logger.WriteWarning($"Client {this} is GUID banned, but we could not find the penalty on any linked accounts");
}
// hack: re apply the automated offense to the reban
if (ban.AutomatedOffense != null)
if (profileBan.AutomatedOffense != null)
{
autoKickClient.AdministeredPenalties?.Add(new EFPenalty()
{
AutomatedOffense = ban.AutomatedOffense
AutomatedOffense = profileBan.AutomatedOffense
});
}
// this is a reban of the new GUID and IP
Ban($"{ban.Offense}", autoKickClient, false);
Ban($"{profileBan.Offense}", autoKickClient, false);
return false;
}
Kick($"{loc["SERVER_BAN_PREV"]} {ban?.Offense}", autoKickClient);
CurrentServer.Logger.WriteDebug($"Kicking {this} because they are banned");
Kick(loc["SERVER_BAN_PREV"].FormatExt(profileBan?.Offense), autoKickClient);
return false;
}
#endregion
var tempBan = ReceivedPenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan && _penalty.Expires > DateTime.UtcNow && _penalty.Active);
// they have an active tempban tied to their GUID
if (tempBan != null)
#region CLIENT_GUID_TEMPBAN
else
{
CurrentServer.Logger.WriteDebug($"Kicking {this} because they are temporarily banned");
Kick($"{loc["SERVER_TB_REMAIN"]} ({(tempBan.Expires.Value - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient);
return false;
var profileTempBan = ReceivedPenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan &&
_penalty.Active &&
_penalty.Expires > DateTime.UtcNow);
// they have an active tempban tied to their GUID
if (profileTempBan != null)
{
CurrentServer.Logger.WriteDebug($"Kicking {this} because their GUID is temporarily banned");
Kick($"{loc["SERVER_TB_REMAIN"]} ({(profileTempBan.Expires.Value - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient);
return false;
}
}
#endregion
// we want to get any penalties that are tied to their IP or AliasLink (but not necessarily their GUID)
var activePenalties = await CurrentServer.Manager.GetPenaltyService().GetActivePenaltiesAsync(AliasLinkId, ipAddress);
#region CLIENT_LINKED_TEMPBAN
var tempBan = activePenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan);
// they have an active tempban tied to their AliasLink
if (tempBan != null)
{
CurrentServer.Logger.WriteDebug($"Tempbanning {this} because their AliasLink is temporarily banned, but they are not");
TempBan(tempBan.Offense, DateTime.UtcNow - (tempBan.Expires ?? DateTime.UtcNow), autoKickClient);
return false;
}
#endregion
#region CLIENT_LINKED_BAN
var currentBan = activePenalties.FirstOrDefault(p => p.Type == Penalty.PenaltyType.Ban);
var currentAutoFlag = activePenalties.Where(p => p.Type == Penalty.PenaltyType.Flag && p.PunisherId == 1)
.Where(p => p.Active)
.OrderByDescending(p => p.When)
.FirstOrDefault();
// remove their auto flag status after a week
if (Level == Permission.Flagged &&
currentAutoFlag != null &&
(DateTime.UtcNow - currentAutoFlag.When).TotalDays > 7)
{
Level = Permission.User;
}
// they have a perm ban tied to their AliasLink/profile
if (currentBan != null)
{
CurrentServer.Logger.WriteInfo($"Banned client {this} trying to evade...");
@ -567,20 +640,38 @@ namespace SharedLibraryCore.Database.Models
return false;
}
#endregion
else
#region CLIENT_LINKED_FLAG
if (Level != Permission.Flagged)
{
var e = new GameEvent()
{
Type = GameEvent.EventType.Join,
Origin = this,
Target = this,
Owner = CurrentServer
};
var currentFlag = activePenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.Flag);
CurrentServer.Manager.GetEventHandler().AddEvent(e);
return true;
if (currentFlag != null)
{
CurrentServer.Logger.WriteDebug($"Flagging {this} because their AliasLink is flagged, but they are not");
Flag(currentFlag.Offense, autoKickClient);
}
}
#endregion
if (Level == Permission.Flagged)
{
var currentAutoFlag = activePenalties
.Where(p => p.Type == Penalty.PenaltyType.Flag && p.PunisherId == 1)
.OrderByDescending(p => p.When)
.FirstOrDefault();
// remove their auto flag status after a week
if (currentAutoFlag != null &&
(DateTime.UtcNow - currentAutoFlag.When).TotalDays > 7)
{
CurrentServer.Logger.WriteInfo($"Unflagging {this} because the auto flag time has expired");
Unflag(Utilities.CurrentLocalization.LocalizationIndex["SERVER_AUTOFLAG_UNFLAG"], autoKickClient);
}
}
return true;
}
[NotMapped]

View File

@ -15,11 +15,7 @@ namespace SharedLibraryCore.Objects
Ban,
Unban,
Any,
}
public String GetWhenFormatted()
{
return When.ToString("MM/dd/yy HH:mm:ss"); ;
Unflag
}
}
}

View File

@ -88,9 +88,10 @@ namespace SharedLibraryCore.Plugins
}
}
catch (Exception E)
catch (Exception e)
{
Manager.GetLogger(0).WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"]} {Plugin.Location} - {E.Message}");
Manager.GetLogger(0).WriteWarning(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"].FormatExt(Plugin.Location));
Manager.GetLogger(0).WriteDebug(e.GetExceptionInfo());
}
}
}

View File

@ -120,7 +120,7 @@ namespace SharedLibraryCore.RCon
retrySend:
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
{
//DontFragment = true,
DontFragment = true,
Ttl = 100,
ExclusiveAddressUse = true,
})
@ -138,7 +138,7 @@ namespace SharedLibraryCore.RCon
if (response.Length == 0 && waitForResponse)
{
throw new Exception();
throw new NetworkException("Expected response but got 0 bytes back");
}
connectionState.OnComplete.Release(1);
@ -149,18 +149,16 @@ namespace SharedLibraryCore.RCon
{
if (connectionState.ConnectionAttempts < StaticHelpers.AllowedConnectionFails)
{
// Log.WriteWarning($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}] ({connectionState.ConnectionAttempts}/{StaticHelpers.AllowedConnectionFails})");
await Task.Delay(StaticHelpers.FloodProtectionInterval);
goto retrySend;
}
connectionState.OnComplete.Release(1);
//Log.WriteDebug(ex.GetExceptionInfo());
throw new NetworkException($"{Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"]} [{this.Endpoint}]");
throw new NetworkException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMUNICATION"].FormatExt(Endpoint));
}
}
string responseString = Encoding.GetEncoding("windows-1252").GetString(response, 0, response.Length) + '\n';
string responseString = defaultEncoding.GetString(response, 0, response.Length) + '\n';
if (responseString.Contains("Invalid password"))
{
@ -229,6 +227,8 @@ namespace SharedLibraryCore.RCon
}
}
rconSocket.Close();
byte[] response = connectionState.ReceiveBuffer
.Take(connectionState.ReceiveEventArgs.BytesTransferred)
.ToArray();

View File

@ -87,6 +87,11 @@ namespace SharedLibraryCore
/// <returns>Matching player if found</returns>
public List<EFClient> GetClientByName(String pName)
{
if (string.IsNullOrEmpty(pName))
{
return new List<EFClient>();
}
string[] QuoteSplit = pName.Split('"');
bool literal = false;
if (QuoteSplit.Length > 1)
@ -95,9 +100,11 @@ namespace SharedLibraryCore
literal = true;
}
if (literal)
return Clients.Where(p => p != null && p.Name.ToLower().Equals(pName.ToLower())).ToList();
{
return GetClientsAsList().Where(p => p.Name?.ToLower() == pName.ToLower()).ToList();
}
return Clients.Where(p => p != null && p.Name.ToLower().Contains(pName.ToLower())).ToList();
return GetClientsAsList().Where(p => (p.Name?.ToLower() ?? "").Contains(pName.ToLower())).ToList();
}
virtual public Task<bool> ProcessUpdatesAsync(CancellationToken cts) => (Task<bool>)Task.CompletedTask;
@ -260,7 +267,7 @@ namespace SharedLibraryCore
public override string ToString()
{
return $"{IP}-{Port}";
return $"{IP}:{Port}";
}
protected async Task<bool> ScriptLoaded()
@ -313,6 +320,7 @@ namespace SharedLibraryCore
// Internal
public string IP { get; protected set; }
public string Version { get; protected set; }
public bool IsInitialized { get; set; }
protected int Port;
protected string FSGame;

View File

@ -17,27 +17,6 @@ namespace SharedLibraryCore.Services
public async Task<EFAlias> Create(EFAlias entity)
{
throw await Task.FromResult(new Exception());
/*using (var context = new DatabaseContext())
{
var alias = new EFAlias()
{
Active = true,
DateAdded = DateTime.UtcNow,
IPAddress = entity.IPAddress,
Name = entity.Name
};
entity.Link = await context.AliasLinks
.FirstAsync(a => a.AliasLinkId == entity.Link.AliasLinkId);
context.Aliases.Add(entity);
await context.SaveChangesAsync();
return entity;
}*/
}
public Task<EFAlias> CreateProxy()
{
return null;
}
public async Task<EFAlias> Delete(EFAlias entity)

View File

@ -1,10 +1,9 @@
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Events;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
namespace SharedLibraryCore.Services
@ -16,7 +15,7 @@ namespace SharedLibraryCore.Services
throw new NotImplementedException();
}
public async Task<EFChangeHistory> Add(GameEvent e)
public async Task<EFChangeHistory> Add(GameEvent e, DatabaseContext ctx = null)
{
EFChangeHistory change = null;
@ -32,12 +31,19 @@ namespace SharedLibraryCore.Services
};
break;
case GameEvent.EventType.Command:
// this prevents passwords/tokens being logged into the database in plain text
if (e.Extra is Command cmd)
{
if (cmd.Name == "login" || cmd.Name == "setpassword")
{
e.Message = string.Join(' ', e.Message.Split(" ").Select((arg, index) => index > 0 ? "*****" : arg));
}
}
change = new EFChangeHistory()
{
OriginEntityId = e.Origin.ClientId,
TargetEntityId = e.Target?.ClientId ?? 0,
Comment = "Executed command",
PreviousValue = "",
CurrentValue = e.Message,
TypeOfChange = EFChangeHistory.ChangeType.Command
};
@ -47,9 +53,9 @@ namespace SharedLibraryCore.Services
{
OriginEntityId = e.Origin.ClientId,
TargetEntityId = e.Target.ClientId,
Comment = "Changed permission level",
TypeOfChange = EFChangeHistory.ChangeType.Permission,
PreviousValue = ((Change)e.Extra).PreviousValue,
CurrentValue = ((Change)e.Extra).NewValue
CurrentValue = ((EFClient.Permission)e.Extra).ToString()
};
break;
default:
@ -58,18 +64,27 @@ namespace SharedLibraryCore.Services
if (change != null)
{
using (var ctx = new DatabaseContext(true))
{
ctx.EFChangeHistory.Add(change);
try
{
await ctx.SaveChangesAsync();
}
bool existingCtx = ctx != null;
ctx = ctx ?? new DatabaseContext(true);
catch (Exception ex)
ctx.EFChangeHistory.Add(change);
try
{
await ctx.SaveChangesAsync();
}
catch (Exception ex)
{
e.Owner.Logger.WriteWarning(ex.Message);
e.Owner.Logger.WriteDebug(ex.GetExceptionInfo());
}
finally
{
if (!existingCtx)
{
e.Owner.Logger.WriteWarning(ex.Message);
e.Owner.Logger.WriteDebug(ex.GetExceptionInfo());
ctx.Dispose();
}
}
}

View File

@ -17,33 +17,79 @@ namespace SharedLibraryCore.Services
{
using (var context = new DatabaseContext())
{
int? linkId = null;
int? aliasId = null;
if (entity.IPAddress != null)
{
var existingAlias = await context.Aliases
.Select(_alias => new { _alias.AliasId, _alias.LinkId, _alias.IPAddress, _alias.Name })
.FirstOrDefaultAsync(_alias => _alias.IPAddress == entity.IPAddress);
if (existingAlias != null)
{
entity.CurrentServer.Logger.WriteDebug($"[create] client with new GUID {entity} has existing link {existingAlias.LinkId}");
linkId = existingAlias.LinkId;
if (existingAlias.Name == entity.Name)
{
entity.CurrentServer.Logger.WriteDebug($"[create] client with new GUID {entity} has existing alias {existingAlias.AliasId}");
aliasId = existingAlias.AliasId;
}
}
}
var client = new EFClient()
{
Level = Permission.User,
FirstConnection = DateTime.UtcNow,
Connections = 1,
LastConnection = DateTime.UtcNow,
Masked = false,
NetworkId = entity.NetworkId,
AliasLink = new EFAliasLink()
{
Active = false
},
ReceivedPenalties = new List<EFPenalty>()
};
client.CurrentAlias = new Alias()
{
Name = entity.Name,
Link = client.AliasLink,
DateAdded = DateTime.UtcNow,
IPAddress = entity.IPAddress,
// the first time a client is created, we may not have their ip,
// so we create a temporary alias
Active = false
NetworkId = entity.NetworkId
};
context.Clients.Add(client);
// they're just using a new GUID
if (aliasId.HasValue)
{
entity.CurrentServer.Logger.WriteDebug($"[create] setting {entity}'s alias id and linkid to ({aliasId.Value}, {linkId.Value})");
client.CurrentAliasId = aliasId.Value;
client.AliasLinkId = linkId.Value;
}
// link was found but they don't have an exact alias
else if (!aliasId.HasValue && linkId.HasValue)
{
entity.CurrentServer.Logger.WriteDebug($"[create] setting {entity}'s linkid to {linkId.Value}, but creating new alias");
client.AliasLinkId = linkId.Value;
client.CurrentAlias = new EFAlias()
{
Name = entity.Name,
DateAdded = DateTime.UtcNow,
IPAddress = entity.IPAddress,
LinkId = linkId.Value
};
}
// brand new players (supposedly)
else
{
entity.CurrentServer.Logger.WriteDebug($"[create] creating new Link and Alias for {entity}");
var link = new EFAliasLink();
var alias = new EFAlias()
{
Name = entity.Name,
DateAdded = DateTime.UtcNow,
IPAddress = entity.IPAddress,
Link = link
};
link.Children.Add(alias);
client.AliasLink = link;
client.CurrentAlias = alias;
}
await context.SaveChangesAsync();
return client;
@ -56,137 +102,161 @@ namespace SharedLibraryCore.Services
// get all aliases by IP address and LinkId
var iqAliases = context.Aliases
.Include(a => a.Link)
.Where(a => (a.IPAddress == ip) ||
a.LinkId == entity.AliasLinkId);
// we only want alias that have the same IP address or share a link
.Where(_alias => _alias.IPAddress == ip || (_alias.LinkId == entity.AliasLinkId));
#if DEBUG == true
var aliasSql = iqAliases.ToSql();
#endif
var aliases = await iqAliases.ToListAsync();
// update each of the aliases where this is no IP but the name is identical
foreach (var alias in aliases.Where(_alias => (_alias.IPAddress == null || _alias.IPAddress == 0)))
{
alias.IPAddress = ip;
}
await context.SaveChangesAsync();
// see if they have a matching IP + Name but new NetworkId
var existingExactAlias = aliases.FirstOrDefault(a => a.Name == name && a.IPAddress == ip);
bool exactAliasMatch = existingExactAlias != null;
bool hasExactAliasMatch = existingExactAlias != null;
// if existing alias matches link them
EFAliasLink aliasLink = existingExactAlias?.Link;
// if no exact matches find the first IP that matches
aliasLink = aliasLink ?? aliases.FirstOrDefault()?.Link;
// if no matches are found, use our current one
aliasLink = aliasLink ?? entity.AliasLink;
var newAliasLink = existingExactAlias?.Link;
// if no exact matches find the first IP or LinkId that matches
newAliasLink = newAliasLink ?? aliases.FirstOrDefault()?.Link;
// if no matches are found, use our current one ( it will become permanent )
newAliasLink = newAliasLink ?? entity.AliasLink;
bool hasExistingAlias = aliases.Count > 0;
bool isAliasLinkUpdated = newAliasLink.AliasLinkId != entity.AliasLink.AliasLinkId;
// this happens when an alias exists but the current link is a temporary one
if ((exactAliasMatch || hasExistingAlias) &&
(!entity.AliasLink.Active && entity.AliasLinkId != aliasLink.AliasLinkId))
// this happens when the link we found is different than the one we create before adding an IP
if (isAliasLinkUpdated)
{
entity.AliasLinkId = aliasLink.AliasLinkId;
entity.AliasLink = aliasLink;
entity.CurrentServer.Logger.WriteDebug($"[updatealias] found a link for {entity} so we are updating link from {entity.AliasLink.AliasLinkId} to {newAliasLink.AliasLinkId}");
var oldAliasLink = entity.AliasLink;
// update all the clients that have the old alias link
await context.Clients
.Where(_client => _client.AliasLinkId == oldAliasLink.AliasLinkId)
.ForEachAsync(_client => _client.AliasLinkId = newAliasLink.AliasLinkId);
entity.AliasLink = newAliasLink;
entity.AliasLinkId = newAliasLink.AliasLinkId;
// update all previous aliases
await context.Aliases
.Where(_alias => _alias.LinkId == oldAliasLink.AliasLinkId)
.ForEachAsync(_alias => _alias.LinkId = newAliasLink.AliasLinkId);
//entity.CurrentServer.Logger.WriteDebug($"Updating alias link for {entity}");
await context.SaveChangesAsync();
foreach (var alias in aliases.Union(new List<EFAlias>() { entity.CurrentAlias })
.Where(_alias => !_alias.Active ||
_alias.LinkId != aliasLink.AliasLinkId))
{
entity.CurrentServer.Logger.WriteDebug($"{entity} updating alias-link id is {alias.LinkId}");
alias.Active = true;
alias.LinkId = aliasLink.AliasLinkId;
}
//entity.CurrentServer.Logger.WriteDebug($"Saving updated aliases for {entity}");
// we want to delete the now inactive alias
context.AliasLinks.Remove(oldAliasLink);
await context.SaveChangesAsync();
// todo: fix this
/*context.AliasLinks.Remove(entity.AliasLink);
entity.AliasLink = null;
//entity.CurrentServer.Logger.WriteDebug($"Removing temporary link for {entity}");
try
{
await context.SaveChangesAsync();
}
catch
{
// entity.CurrentServer.Logger.WriteDebug($"Failed to remove link for {entity}");
}*/
}
// the existing alias matches ip and name, so we can just ignore the temporary one
if (exactAliasMatch)
if (hasExactAliasMatch)
{
entity.CurrentServer.Logger.WriteDebug($"{entity} has exact alias match");
entity.CurrentServer.Logger.WriteDebug($"[updatealias] {entity} has exact alias match");
var oldAlias = entity.CurrentAlias;
entity.CurrentAliasId = existingExactAlias.AliasId;
entity.CurrentAlias = existingExactAlias;
await context.SaveChangesAsync();
// the alias is the same so we can just remove it
if (oldAlias.AliasId != existingExactAlias.AliasId && oldAlias.AliasId > 0)
{
await context.Clients
.Where(_client => _client.CurrentAliasId == oldAlias.AliasId)
.ForEachAsync(_client => _client.CurrentAliasId = existingExactAlias.AliasId);
await context.SaveChangesAsync();
entity.CurrentServer.Logger.WriteDebug($"[updatealias] {entity} has exact alias match, so we're going to try to remove aliasId {oldAlias.AliasId} with linkId {oldAlias.AliasId}");
context.Aliases.Remove(oldAlias);
await context.SaveChangesAsync();
}
}
// theres no exact match, but they've played before with the GUID or IP
else if (hasExistingAlias)
{
//entity.CurrentServer.Logger.WriteDebug($"Connecting player is using a new alias {entity}");
// this happens when a temporary alias gets updated
if (entity.CurrentAlias.Name == name && entity.CurrentAlias.IPAddress == null)
{
entity.CurrentAlias.IPAddress = ip;
entity.CurrentAlias.Active = true;
//entity.CurrentServer.Logger.WriteDebug($"Updating temporary alias for {entity}");
await context.SaveChangesAsync();
}
else
{
var newAlias = new EFAlias()
{
DateAdded = DateTime.UtcNow,
IPAddress = ip,
LinkId = aliasLink.AliasLinkId,
Name = name,
Active = true,
};
entity.CurrentAlias = newAlias;
//entity.CurrentServer.Logger.WriteDebug($"Saving new alias for {entity}");
await context.SaveChangesAsync();
}
}
// no record of them playing
else
{
//entity.CurrentServer.Logger.WriteDebug($"{entity} has not be seen before");
entity.CurrentServer.Logger.WriteDebug($"[updatealias] {entity} is using a new alias");
entity.AliasLink.Active = true;
entity.CurrentAlias.Active = true;
entity.CurrentAlias.IPAddress = ip;
entity.CurrentAlias.Name = name;
var newAlias = new EFAlias()
{
DateAdded = DateTime.UtcNow,
IPAddress = ip,
LinkId = newAliasLink.AliasLinkId,
Name = name,
Active = true,
};
//entity.CurrentServer.Logger.WriteDebug($"updating new alias for {entity}");
entity.CurrentAlias = newAlias;
entity.CurrentAliasId = 0;
await context.SaveChangesAsync();
}
}
var linkIds = aliases.Select(a => a.LinkId);
if (linkIds.Count() > 0 &&
aliases.Count(_alias => _alias.Name == name && _alias.IPAddress == ip) > 0)
/// <summary>
/// updates the permission level of the given target to the given permission level
/// </summary>
/// <param name="newPermission"></param>
/// <param name="temporalClient"></param>
/// <param name="origin"></param>
/// <param name="ctx"></param>
/// <returns></returns>
public async Task UpdateLevel(Permission newPermission, EFClient temporalClient, EFClient origin)
{
using (var ctx = new DatabaseContext())
{
var highestLevel = await context.Clients
.Where(c => linkIds.Contains(c.AliasLinkId))
.MaxAsync(c => c.Level);
var entity = await ctx.Clients
.Where(_client => _client.AliasLinkId == temporalClient.AliasLinkId)
.FirstAsync();
if (entity.Level != highestLevel)
var oldPermission = entity.Level;
entity.Level = newPermission;
await ctx.SaveChangesAsync();
#if DEBUG == true
temporalClient.CurrentServer.Logger.WriteDebug($"Updated {temporalClient.ClientId} to {newPermission}");
#endif
// if their permission level has been changed to level that needs to be updated on all accounts
if ((oldPermission != newPermission) &&
(newPermission == Permission.Banned ||
newPermission == Permission.Flagged ||
newPermission == Permission.User))
{
entity.CurrentServer.Logger.WriteDebug($"{entity} updating user level");
// todo: log level changes here
context.Update(entity);
entity.Level = highestLevel;
await context.SaveChangesAsync();
var changeSvc = new ChangeHistoryService();
//get all clients that have the same linkId
var iqMatchingClients = ctx.Clients
.Where(_client => _client.AliasLinkId == entity.AliasLinkId);
// make sure we don't select ourselves twice
//.Where(_client => _client.ClientId != temporalClient.ClientId);
// this updates the level for all the clients with the same LinkId
// only if their new level is flagged or banned
await iqMatchingClients.ForEachAsync(_client =>
{
_client.Level = newPermission;
#if DEBUG == true
temporalClient.CurrentServer.Logger.WriteDebug($"Updated linked {_client.ClientId} to {newPermission}");
#endif
});
await ctx.SaveChangesAsync();
}
}
temporalClient.Level = newPermission;
}
public async Task<EFClient> Delete(EFClient entity)
@ -275,73 +345,45 @@ namespace SharedLibraryCore.Services
}
}
public async Task UpdateAlias(EFClient entity)
public async Task UpdateAlias(EFClient temporalClient)
{
using (var context = new DatabaseContext())
{
var client = context.Clients
var entity = context.Clients
.Include(c => c.AliasLink)
.Include(c => c.CurrentAlias)
.First(e => e.ClientId == entity.ClientId);
.First(e => e.ClientId == temporalClient.ClientId);
client.CurrentServer = entity.CurrentServer;
entity.CurrentServer = temporalClient.CurrentServer;
await UpdateAlias(entity.Name, entity.IPAddress, client, context);
await UpdateAlias(temporalClient.Name, temporalClient.IPAddress, entity, context);
entity.CurrentAlias = client.CurrentAlias;
entity.CurrentAliasId = client.CurrentAliasId;
entity.AliasLink = client.AliasLink;
entity.AliasLinkId = client.AliasLinkId;
temporalClient.CurrentAlias = entity.CurrentAlias;
temporalClient.CurrentAliasId = entity.CurrentAliasId;
temporalClient.AliasLink = entity.AliasLink;
temporalClient.AliasLinkId = entity.AliasLinkId;
}
}
public async Task<EFClient> Update(EFClient entity)
public async Task<EFClient> Update(EFClient temporalClient)
{
using (var context = new DatabaseContext())
{
// grab the context version of the entity
var client = context.Clients
.First(e => e.ClientId == entity.ClientId);
var entity = context.Clients
.First(client => client.ClientId == temporalClient.ClientId);
client.CurrentServer = entity.CurrentServer;
// if their level has been changed
if (entity.Level != client.Level)
{
// get all clients that use the same aliasId
var matchingClients = context.Clients
.Where(c => c.CurrentAliasId == client.CurrentAliasId)
// make sure we don't select ourselves twice
.Where(c => c.ClientId != entity.ClientId);
// update all related clients level
await matchingClients.ForEachAsync(c =>
{
// todo: log that it has changed here
c.Level = entity.Level;
});
}
// set remaining non-navigation properties that may have been updated
client.Level = entity.Level;
client.LastConnection = entity.LastConnection;
client.Connections = entity.Connections;
client.FirstConnection = entity.FirstConnection;
client.Masked = entity.Masked;
client.TotalConnectionTime = entity.TotalConnectionTime;
client.Password = entity.Password;
client.PasswordSalt = entity.PasswordSalt;
entity.LastConnection = temporalClient.LastConnection;
entity.Connections = temporalClient.Connections;
entity.FirstConnection = temporalClient.FirstConnection;
entity.Masked = temporalClient.Masked;
entity.TotalConnectionTime = temporalClient.TotalConnectionTime;
entity.Password = temporalClient.Password;
entity.PasswordSalt = temporalClient.PasswordSalt;
// update in database
await context.SaveChangesAsync();
// this is set so future updates don't trigger a new alias add
if (entity.CurrentAlias.AliasId == 0)
{
entity.CurrentAlias.AliasId = client.CurrentAlias.AliasId;
}
return client;
return entity;
}
}
@ -356,11 +398,25 @@ namespace SharedLibraryCore.Services
}
}
/// <summary>
/// retrieves the number of owners
/// (client level is owner)
/// </summary>
/// <returns></returns>
public async Task<int> GetOwnerCount()
{
using (var ctx = new DatabaseContext(true))
{
return await ctx.Clients.AsNoTracking()
.CountAsync(_client => _client.Level == Permission.Owner);
}
}
public async Task<List<EFClient>> GetPrivilegedClients()
{
using (var context = new DatabaseContext(disableTracking: true))
{
var iqClients = from client in context.Clients
var iqClients = from client in context.Clients.AsNoTracking()
where client.Level >= Permission.Trusted
where client.Active
select new EFClient()
@ -371,7 +427,8 @@ namespace SharedLibraryCore.Services
Level = client.Level,
Password = client.Password,
PasswordSalt = client.PasswordSalt,
NetworkId = client.NetworkId
NetworkId = client.NetworkId,
LastConnection = client.LastConnection
};
#if DEBUG == true
@ -384,7 +441,7 @@ namespace SharedLibraryCore.Services
public async Task<IList<EFClient>> FindClientsByIdentifier(string identifier)
{
if (identifier.Length < 3)
if (identifier?.Length < 3)
{
return new List<EFClient>();
}
@ -401,7 +458,7 @@ namespace SharedLibraryCore.Services
alias.Name.ToLower().Contains(identifier)
select alias.LinkId).Distinct();
var linkIds = iqLinkIds.ToList();
var linkIds = await iqLinkIds.ToListAsync();
var iqClients = context.Clients
.Where(c => linkIds.Contains(c.AliasLinkId) ||
@ -425,11 +482,6 @@ namespace SharedLibraryCore.Services
.CountAsync();
}
}
public Task<EFClient> CreateProxy()
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@ -1,25 +1,157 @@
using SharedLibraryCore.Dtos;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SharedLibraryCore.Services
{
public class MetaService
{
private static List<Func<int, Task<List<ProfileMeta>>>> MetaActions = new List<Func<int, Task<List<ProfileMeta>>>>();
private static List<Func<int, int, int, DateTime?, Task<List<ProfileMeta>>>> _metaActions = new List<Func<int, int, int, DateTime?, Task<List<ProfileMeta>>>>();
public static void AddMeta(Func<int, Task<List<ProfileMeta>>> metaAction)
/// <summary>
/// adds or updates meta key and value to the database
/// </summary>
/// <param name="metaKey">key of meta data</param>
/// <param name="metaValue">value of the meta data</param>
/// <param name="client">client to save the meta for</param>
/// <returns></returns>
public async Task AddPersistentMeta(string metaKey, string metaValue, EFClient client)
{
MetaActions.Add(metaAction);
// this seems to happen if the client disconnects before they've had time to authenticate and be added
if (client.ClientId < 1)
{
return;
}
using (var ctx = new DatabaseContext())
{
var existingMeta = await ctx.EFMeta
.Where(_meta => _meta.Key == metaKey)
.Where(_meta => _meta.ClientId == client.ClientId)
.FirstOrDefaultAsync();
if (existingMeta != null)
{
existingMeta.Value = metaValue;
existingMeta.Updated = DateTime.UtcNow;
}
else
{
ctx.EFMeta.Add(new EFMeta()
{
ClientId = client.ClientId,
Created = DateTime.UtcNow,
Key = metaKey,
Value = metaValue
});
}
await ctx.SaveChangesAsync();
}
}
public static async Task<List<ProfileMeta>> GetMeta(int clientId)
/// <summary>
/// retrieves meta data for given client and key
/// </summary>
/// <param name="metaKey">key to retrieve value for</param>
/// <param name="client">client to retrieve meta for</param>
/// <returns></returns>
public async Task<EFMeta> GetPersistentMeta(string metaKey, EFClient client)
{
using (var ctx = new DatabaseContext(disableTracking: true))
{
return await ctx.EFMeta
.Where(_meta => _meta.Key == metaKey)
.Where(_meta => _meta.ClientId == client.ClientId)
.FirstOrDefaultAsync();
}
}
/// <summary>
/// aads a meta task to the runtime meta list
/// </summary>
/// <param name="metaAction"></param>
public static void AddRuntimeMeta(Func<int, int, int, DateTime?, Task<List<ProfileMeta>>> metaAction)
{
_metaActions.Add(metaAction);
}
/// <summary>
/// retrieves all the runtime meta information for given client idea
/// </summary>
/// <param name="clientId">id of the client</param>
/// <param name="count">number of meta items to retrieve</param>
/// <param name="offset">offset from the first item</param>
/// <returns></returns>
public static async Task<List<ProfileMeta>> GetRuntimeMeta(int clientId, int offset = 0, int count = int.MaxValue, DateTime? startAt = null)
{
var meta = new List<ProfileMeta>();
foreach (var action in MetaActions)
meta.AddRange(await action(clientId));
return meta;
foreach (var action in _metaActions)
{
var metaItems = await action(clientId, offset, count, startAt);
meta.AddRange(metaItems);
}
if (count == 1)
{
var table = new List<List<ProfileMeta>>();
var metaWithColumn = meta
.Where(_meta => _meta.Column != null);
var columnGrouping = metaWithColumn
.GroupBy(_meta => _meta.Column);
var metaToSort = meta.Except(metaWithColumn).ToList();
foreach (var metaItem in columnGrouping)
{
table.Add(new List<ProfileMeta>(metaItem));
}
while (metaToSort.Count > 0)
{
var sortingMeta = metaToSort.First();
int indexOfSmallestColumn()
{
int index = 0;
int smallestColumnSize = int.MaxValue;
for (int i = 0; i < table.Count; i++)
{
if (table[i].Count < smallestColumnSize)
{
smallestColumnSize = table[i].Count;
index = i;
}
}
return index;
}
int columnIndex = indexOfSmallestColumn();
sortingMeta.Column = columnIndex;
sortingMeta.Order = columnGrouping
.First(_group => _group.Key == columnIndex)
.Count();
table[columnIndex].Add(sortingMeta);
metaToSort.Remove(sortingMeta);
}
return meta;
}
return meta.OrderByDescending(_meta => _meta.When)
.Take(count)
.ToList();
}
}
}

View File

@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
namespace SharedLibraryCore.Services
{
@ -18,91 +17,56 @@ namespace SharedLibraryCore.Services
{
using (var context = new DatabaseContext())
{
// make bans propogate to all aliases
if (newEntity.Type == Penalty.PenaltyType.Ban)
var penalty = new EFPenalty()
{
await context.Clients
.Include(c => c.ReceivedPenalties)
.Where(c => c.AliasLinkId == newEntity.Link.AliasLinkId)
.ForEachAsync(c =>
{
if (c.Level != Permission.Banned)
{
c.Level = Permission.Banned;
c.ReceivedPenalties.Add(new EFPenalty()
{
Active = true,
OffenderId = c.ClientId,
PunisherId = newEntity.Punisher.ClientId,
LinkId = c.AliasLinkId,
Type = newEntity.Type,
Expires = newEntity.Expires,
Offense = newEntity.Offense,
When = DateTime.UtcNow,
AutomatedOffense = newEntity.AutomatedOffense,
IsEvadedOffense = newEntity.IsEvadedOffense
});
}
});
}
// make flags propogate to all aliases
else if (newEntity.Type == Penalty.PenaltyType.Flag)
{
await context.Clients
.Include(c => c.ReceivedPenalties)
.Where(c => c.AliasLinkId == newEntity.Link.AliasLinkId)
.ForEachAsync(c =>
{
if (c.Level != Permission.Flagged)
{
c.Level = Permission.Flagged;
c.ReceivedPenalties.Add(new EFPenalty()
{
Active = true,
OffenderId = c.ClientId,
PunisherId = newEntity.Punisher.ClientId,
LinkId = c.AliasLinkId,
Type = newEntity.Type,
Expires = newEntity.Expires,
Offense = newEntity.Offense,
When = DateTime.UtcNow,
AutomatedOffense = newEntity.AutomatedOffense,
IsEvadedOffense = newEntity.IsEvadedOffense
});
}
});
}
// we just want to add it to the database
else
{
var penalty = new EFPenalty()
{
Active = true,
OffenderId = newEntity.Offender.ClientId,
PunisherId = newEntity.Punisher.ClientId,
LinkId = newEntity.Link.AliasLinkId,
Type = newEntity.Type,
Expires = newEntity.Expires,
Offense = newEntity.Offense,
When = DateTime.UtcNow,
AutomatedOffense = newEntity.AutomatedOffense,
IsEvadedOffense = newEntity.IsEvadedOffense
};
newEntity.Offender.ReceivedPenalties?.Add(penalty);
context.Penalties.Add(penalty);
}
Active = true,
OffenderId = newEntity.Offender.ClientId,
PunisherId = newEntity.Punisher.ClientId,
LinkId = newEntity.Link.AliasLinkId,
Type = newEntity.Type,
Expires = newEntity.Expires,
Offense = newEntity.Offense,
When = DateTime.UtcNow,
AutomatedOffense = newEntity.AutomatedOffense,
IsEvadedOffense = newEntity.IsEvadedOffense
};
context.Penalties.Add(penalty);
await context.SaveChangesAsync();
return newEntity;
}
}
public Task<EFPenalty> CreateProxy()
{
throw new NotImplementedException();
// certain penalties we want to save across all profiles
if (penalty.Type.ShouldPenaltyApplyToAllProfiles())
{
var iqLinkedProfiles = context.Clients
.Where(_client => _client.AliasLinkId == newEntity.Link.AliasLinkId)
.Where(_client => _client.Level != EFClient.Permission.Banned)
// prevent adding the penalty twice to the same profile
.Where(_client => _client.ClientId != penalty.OffenderId);
await iqLinkedProfiles.ForEachAsync(_client =>
{
var linkedPenalty = new EFPenalty()
{
OffenderId = _client.ClientId,
PunisherId = newEntity.Punisher.ClientId,
LinkId = newEntity.Link.AliasLinkId,
Type = newEntity.Type,
Expires = newEntity.Expires,
Offense = newEntity.Offense,
When = DateTime.UtcNow,
AutomatedOffense = newEntity.AutomatedOffense,
IsEvadedOffense = newEntity.IsEvadedOffense
};
context.Penalties.Add(linkedPenalty);
});
await context.SaveChangesAsync();
}
}
return newEntity;
}
public Task<EFPenalty> Delete(EFPenalty entity)
@ -130,145 +94,82 @@ namespace SharedLibraryCore.Services
throw new NotImplementedException();
}
public async Task<IList<EFPenalty>> GetRecentPenalties(int count, int offset, Penalty.PenaltyType showOnly = Penalty.PenaltyType.Any)
public async Task<IList<PenaltyInfo>> GetRecentPenalties(int count, int offset, Penalty.PenaltyType showOnly = Penalty.PenaltyType.Any)
{
using (var context = new DatabaseContext(true))
{
return await context.Penalties
.Include(p => p.Offender.CurrentAlias)
.Include(p => p.Punisher.CurrentAlias)
.Where(p => showOnly == Penalty.PenaltyType.Any ? p.Type != Penalty.PenaltyType.Any : p.Type == showOnly)
.Where(p => p.Active)
.OrderByDescending(p => p.When)
.Skip(offset)
.Take(count)
.ToListAsync();
}
}
var iqPenalties = context.Penalties
.Where(p => showOnly == Penalty.PenaltyType.Any ? p.Type != Penalty.PenaltyType.Any : p.Type == showOnly)
.OrderByDescending(p => p.When)
.Skip(offset)
.Take(count)
.Select(_penalty => new PenaltyInfo()
{
Id = _penalty.PenaltyId,
Offense = _penalty.Offense,
AutomatedOffense = _penalty.AutomatedOffense,
OffenderId = _penalty.OffenderId,
OffenderName = _penalty.Offender.CurrentAlias.Name,
PunisherId = _penalty.PunisherId,
PunisherName = _penalty.Punisher.CurrentAlias.Name,
PunisherLevel = _penalty.Punisher.Level,
PenaltyType = _penalty.Type,
Expires = _penalty.Expires,
TimePunished = _penalty.When,
IsEvade = _penalty.IsEvadedOffense
});
public async Task<IList<EFPenalty>> GetClientPenaltiesAsync(int clientId)
{
using (var context = new DatabaseContext(true))
{
return await context.Penalties
.Where(p => p.OffenderId == clientId)
.Where(p => p.Active)
.Include(p => p.Offender.CurrentAlias)
.Include(p => p.Punisher.CurrentAlias)
.ToListAsync();
#if DEBUG == true
var querySql = iqPenalties.ToSql();
#endif
return await iqPenalties.ToListAsync();
}
}
/// <summary>
/// Get a read-only copy of client penalties
/// retrieves penalty information for meta service
/// </summary>
/// <param name="clientId"></param>
/// <param name="victim">Retreive penalties for clients receiving penalties, other wise given</param>
/// <param name="clientId">database id of the client</param>
/// <param name="count">how many items to retrieve</param>
/// <param name="offset">not used</param>
/// <param name="startAt">retreive penalties older than this</param>
/// <returns></returns>
public async Task<List<ProfileMeta>> ReadGetClientPenaltiesAsync(int clientId, bool victim = true)
public async Task<IList<PenaltyInfo>> GetClientPenaltyForMetaAsync(int clientId, int count, int offset, DateTime? startAt)
{
using (var context = new DatabaseContext(true))
using (var ctx = new DatabaseContext(true))
{
// todo: clean this up
if (victim)
{
var now = DateTime.UtcNow;
var iqPenalties = from penalty in context.Penalties.AsNoTracking()
where penalty.OffenderId == clientId
join victimClient in context.Clients.AsNoTracking()
on penalty.OffenderId equals victimClient.ClientId
join victimAlias in context.Aliases.AsNoTracking()
on victimClient.CurrentAliasId equals victimAlias.AliasId
join punisherClient in context.Clients.AsNoTracking()
on penalty.PunisherId equals punisherClient.ClientId
join punisherAlias in context.Aliases.AsNoTracking()
on punisherClient.CurrentAliasId equals punisherAlias.AliasId
//orderby penalty.When descending
select new ProfileMeta()
{
Key = "Event.Penalty",
Value = new PenaltyInfo
{
Id = penalty.PenaltyId,
OffenderName = victimAlias.Name,
OffenderId = victimClient.ClientId,
PunisherName = punisherAlias.Name,
PunisherId = penalty.PunisherId,
Offense = penalty.Offense,
Type = penalty.Type.ToString(),
TimeRemaining = penalty.Expires.HasValue ? (now > penalty.Expires ? "" : penalty.Expires.ToString()) : DateTime.MaxValue.ToString(),
AutomatedOffense = penalty.AutomatedOffense
},
When = penalty.When,
Sensitive = penalty.Type == Penalty.PenaltyType.Flag
};
// fixme: is this good and fast?
var list = await iqPenalties.ToListAsync();
list.ForEach(p =>
var iqPenalties = ctx.Penalties.AsNoTracking()
//.Where(_penalty => _penalty.Active)
.Where(_penalty => _penalty.OffenderId == clientId || _penalty.PunisherId == clientId)
.Where(_penalty => _penalty.When < startAt)
.OrderByDescending(_penalty => _penalty.When)
.Skip(offset)
.Take(count)
.Select(_penalty => new PenaltyInfo()
{
// todo: why does this have to be done?
if (((PenaltyInfo)p.Value).Type.Length < 2)
{
((PenaltyInfo)p.Value).Type = ((Penalty.PenaltyType)Convert.ToInt32(((PenaltyInfo)p.Value).Type)).ToString();
}
var pi = ((PenaltyInfo)p.Value);
if (pi.TimeRemaining?.Length > 0)
{
pi.TimeRemaining = (DateTime.Parse(((PenaltyInfo)p.Value).TimeRemaining) - now).TimeSpanText();
}
});
return list;
}
else
{
var iqPenalties = from penalty in context.Penalties.AsNoTracking()
where penalty.PunisherId == clientId
join victimClient in context.Clients.AsNoTracking()
on penalty.OffenderId equals victimClient.ClientId
join victimAlias in context.Aliases
on victimClient.CurrentAliasId equals victimAlias.AliasId
join punisherClient in context.Clients
on penalty.PunisherId equals punisherClient.ClientId
join punisherAlias in context.Aliases
on punisherClient.CurrentAliasId equals punisherAlias.AliasId
//orderby penalty.When descending
select new ProfileMeta()
{
Key = "Event.Penalty",
Value = new PenaltyInfo
{
Id = penalty.PenaltyId,
OffenderName = victimAlias.Name,
OffenderId = victimClient.ClientId,
PunisherName = punisherAlias.Name,
PunisherId = penalty.PunisherId,
Offense = penalty.Offense,
Type = penalty.Type.ToString(),
AutomatedOffense = penalty.AutomatedOffense
},
When = penalty.When,
Sensitive = penalty.Type == Penalty.PenaltyType.Flag
};
// fixme: is this good and fast?
var list = await iqPenalties.ToListAsync();
list.ForEach(p =>
{
// todo: why does this have to be done?
if (((PenaltyInfo)p.Value).Type.Length < 2)
{
((PenaltyInfo)p.Value).Type = ((Penalty.PenaltyType)Convert.ToInt32(((PenaltyInfo)p.Value).Type)).ToString();
}
Id = _penalty.PenaltyId,
Offense = _penalty.Offense,
AutomatedOffense = _penalty.AutomatedOffense,
OffenderId = _penalty.OffenderId,
OffenderName = _penalty.Offender.CurrentAlias.Name,
PunisherId = _penalty.PunisherId,
PunisherName = _penalty.Punisher.CurrentAlias.Name,
PunisherLevel = _penalty.Punisher.Level,
PenaltyType = _penalty.Type,
Expires = _penalty.Expires,
TimePunished = _penalty.When,
IsEvade = _penalty.IsEvadedOffense
});
return list;
}
#if DEBUG == true
var querySql = iqPenalties.ToSql();
#endif
return await iqPenalties.ToListAsync();
}
}
public async Task<List<EFPenalty>> GetActivePenaltiesAsync(int linkId, int? ip = null)
public async Task<List<EFPenalty>> GetActivePenaltiesAsync(int linkId, int? ip = null, bool includePunisherName = false)
{
var now = DateTime.UtcNow;
@ -306,31 +207,20 @@ namespace SharedLibraryCore.Services
}
}
public async Task RemoveActivePenalties(int aliasLinkId)
public async Task RemoveActivePenalties(int aliasLinkId, EFClient origin)
{
using (var context = new DatabaseContext())
{
var now = DateTime.UtcNow;
var penalties = await context.Penalties
var penalties = context.Penalties
.Include(p => p.Link.Children)
.Where(p => p.LinkId == aliasLinkId)
.Where(p => p.Expires > now || p.Expires == null)
.ToListAsync();
.Where(p => p.Expires > now || p.Expires == null);
penalties.ForEach(async p =>
await penalties.ForEachAsync(p =>
{
p.Active = false;
// reset the player levels
if (p.Type == Penalty.PenaltyType.Ban)
{
using (var internalContext = new DatabaseContext())
{
await internalContext.Clients
.Where(c => c.AliasLinkId == p.LinkId)
.ForEachAsync(c => c.Level = EFClient.Permission.User);
await internalContext.SaveChangesAsync();
}
}
});
await context.SaveChangesAsync();

View File

@ -17,6 +17,8 @@
<Compile Remove="Migrations\20181126232438_AddEndpointToEFServer.cs" />
<Compile Remove="Migrations\20181126233300_AddEndpointToEFServer.cs" />
<Compile Remove="Migrations\20181127143920_AddEndpointToEFServerUpdateServerIdType.cs" />
<Compile Remove="Migrations\20190222234606_AddIndexToEFMeta-KeyAndClientId.cs" />
<Compile Remove="Migrations\20190223012312_SetMaxLengthForMetaKey.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -10,10 +10,12 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using static SharedLibraryCore.Objects.Penalty;
using static SharedLibraryCore.Server;
namespace SharedLibraryCore
@ -21,7 +23,8 @@ namespace SharedLibraryCore
public static class Utilities
{
#if DEBUG == true
public static string OperatingDirectory => $"{Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)}{Path.DirectorySeparatorChar}";
public static string OperatingDirectory => @"X:\IW4MAdmin\Application\bin\Debug\netcoreapp2.2\";
//public static string OperatingDirectory => $"{Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)}{Path.DirectorySeparatorChar}";
#else
public static string OperatingDirectory => $"{Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)}{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}";
#endif
@ -183,7 +186,7 @@ namespace SharedLibraryCore
return CurrentLocalization.LocalizationIndex[$"GLOBAL_PERMISSION_{perm.ToString().ToUpper()}"];
}
public static String ProcessMessageToken(this Server server, IList<Helpers.MessageToken> tokens, String str)
public async static Task<string> ProcessMessageToken(this Server server, IList<Helpers.MessageToken> tokens, String str)
{
MatchCollection RegexMatches = Regex.Matches(str, @"\{\{[A-Z]+\}\}", RegexOptions.IgnoreCase);
foreach (Match M in RegexMatches)
@ -195,7 +198,7 @@ namespace SharedLibraryCore
if (found != null)
{
str = str.Replace(Match, found.Process(server));
str = str.Replace(Match, await found.ProcessAsync(server));
}
}
@ -266,6 +269,7 @@ namespace SharedLibraryCore
public static long ConvertLong(this string str)
{
str = str.Substring(0, Math.Min(str.Length, 16));
int maxBots = 18;
long id;
if (str.Length <= 11) // 10 numeric characters + signed character
@ -285,7 +289,7 @@ namespace SharedLibraryCore
if (!string.IsNullOrEmpty(bot))
{
// should set their GUID to the negation of their 1 based index (-1 - -18)
return -(Convert.ToInt64(bot.Substring(3)) + 1);
return -(Convert.ToInt64(bot.Substring(3)) + 1) % maxBots;
}
return long.MinValue;
@ -478,9 +482,23 @@ namespace SharedLibraryCore
return "unknown";
}
public static bool ShouldPenaltyApplyToAllProfiles(this PenaltyType penaltyType)
{
return penaltyType == PenaltyType.Ban ||
penaltyType == PenaltyType.Unban ||
penaltyType == PenaltyType.Flag ||
penaltyType == PenaltyType.Unflag ||
penaltyType == PenaltyType.TempBan;
}
/// <summary>
/// Helper extension that determines if a user is a privileged client
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
public static bool IsPrivileged(this EFClient p)
{
return p.Level > EFClient.Permission.User;
return p.Level > EFClient.Permission.Flagged;
}
/// <summary>
@ -596,19 +614,6 @@ namespace SharedLibraryCore
return response;
}
public static int ClientIdFromString(String[] lineSplit, int cIDPos)
{
int pID = -2; // apparently falling = -1 cID so i can't use it now
int.TryParse(lineSplit[cIDPos].Trim(), out pID);
if (pID == -1) // special case similar to mod_suicide
{
int.TryParse(lineSplit[2], out pID);
}
return pID;
}
public static Dictionary<string, string> DictionaryFromKeyValue(this string eventLine)
{
string[] values = eventLine.Substring(1).Split('\\');
@ -713,6 +718,76 @@ namespace SharedLibraryCore
return Assembly.GetCallingAssembly().GetName().Version.ToString();
}
public static string FormatExt(this string input, params object[] values)
{
var matches = Regex.Matches(Regex.Unescape(input), @"{{\w+}}");
string output = input;
int index = 0;
foreach (Match match in matches)
{
output = output.Replace(match.Value.ToString(), $"{{{index.ToString()}}}");
index++;
}
try
{
return string.Format(output, values);
}
catch { return input; }
}
/// <summary>
/// https://stackoverflow.com/questions/8113546/how-to-determine-whether-an-ip-address-in-private/39120248
/// An extension method to determine if an IP address is internal, as specified in RFC1918
/// </summary>
/// <param name="toTest">The IP address that will be tested</param>
/// <returns>Returns true if the IP is internal, false if it is external</returns>
public static bool IsInternal(this IPAddress toTest)
{
if (toTest.ToString().StartsWith("127.0.0"))
{
return true;
}
byte[] bytes = toTest.GetAddressBytes();
switch (bytes[0])
{
case 10:
return true;
case 172:
return bytes[1] < 32 && bytes[1] >= 16;
case 192:
return bytes[1] == 168;
default:
return false;
}
}
/// <summary>
/// retrieves the external IP address of the current running machine
/// </summary>
/// <returns></returns>
public static async Task<string> GetExternalIP()
{
try
{
using (var wc = new WebClient())
{
return await wc.DownloadStringTaskAsync("https://api.ipify.org");
}
}
catch
{
return null;
}
}
public static bool IsQuickMessage(this string message)
{
return Regex.IsMatch(message, @"^\u0014(?:[A-Z]|_)+$");
}
#if DEBUG == true
private static readonly TypeInfo QueryCompilerTypeInfo = typeof(QueryCompiler).GetTypeInfo();

View File

@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore;
using System;
using System.Security.Claims;
using System.Threading.Tasks;
@ -10,6 +10,11 @@ namespace WebfrontCore.Controllers
{
public class AccountController : BaseController
{
/// <summary>
/// life span in months
/// </summary>
private const int COOKIE_LIFESPAN = 3;
[HttpGet]
public async Task<IActionResult> LoginAsync(int clientId, string password)
{
@ -20,19 +25,23 @@ namespace WebfrontCore.Controllers
try
{
var client = Manager.GetPrivilegedClients()[clientId];
//#if DEBUG == true
// var client = Utilities.IW4MAdminClient();
// bool loginSuccess = true;
//#else
var privilegedClient = Manager.GetPrivilegedClients()[clientId];
bool loginSuccess = Manager.TokenAuthenticator.AuthorizeToken(privilegedClient.NetworkId, password) ||
(await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(password, privilegedClient.PasswordSalt)))[0] == privilegedClient.Password;
//#endif
// string[] hashedPassword = await Task.FromResult(SharedLibraryCore.Helpers.Hashing.Hash(password, client.PasswordSalt));
//if (hashedPassword[0] == client.Password)
if (Manager.TokenAuthenticator.AuthorizeToken(client.NetworkId, password))
if (loginSuccess)
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, client.Name),
new Claim(ClaimTypes.Role, client.Level.ToString()),
new Claim(ClaimTypes.Sid, client.ClientId.ToString()),
new Claim(ClaimTypes.PrimarySid, client.NetworkId.ToString())
new Claim(ClaimTypes.NameIdentifier, privilegedClient.Name),
new Claim(ClaimTypes.Role, privilegedClient.Level.ToString()),
new Claim(ClaimTypes.Sid, privilegedClient.ClientId.ToString()),
new Claim(ClaimTypes.PrimarySid, privilegedClient.NetworkId.ToString("X"))
};
var claimsIdentity = new ClaimsIdentity(claims, "login");
@ -40,7 +49,7 @@ namespace WebfrontCore.Controllers
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrinciple, new AuthenticationProperties()
{
AllowRefresh = true,
ExpiresUtc = DateTime.UtcNow.AddDays(30),
ExpiresUtc = DateTime.UtcNow.AddMonths(COOKIE_LIFESPAN),
IsPersistent = true,
IssuedUtc = DateTime.UtcNow
});

View File

@ -184,7 +184,7 @@ namespace WebfrontCore.Controllers
}));
}
public async Task<IActionResult> GenerateLoginTokenForm()
public IActionResult GenerateLoginTokenForm()
{
var info = new ActionInfo()
{

View File

@ -59,17 +59,23 @@ namespace WebfrontCore.Controllers
{
ClientId = -1,
Level = EFClient.Permission.User,
CurrentAlias = new EFAlias() { Name = "Web Console Guest" }
CurrentAlias = new EFAlias() { Name = "Webfront Guest" }
};
if (!HttpContext.Connection.RemoteIpAddress.GetAddressBytes().SequenceEqual(LocalHost))
{
try
{
Client.ClientId = Convert.ToInt32(base.User.Claims.First(c => c.Type == ClaimTypes.Sid).Value);
Client.NetworkId = User.Claims.First(_claim => _claim.Type == ClaimTypes.PrimarySid).Value.ConvertLong();
Client.Level = (EFClient.Permission)Enum.Parse(typeof(EFClient.Permission), User.Claims.First(c => c.Type == ClaimTypes.Role).Value);
Client.CurrentAlias = new EFAlias() { Name = User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value };
int clientId = Convert.ToInt32(User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Sid)?.Value ?? "-1");
if (clientId > 0)
{
Client.ClientId = clientId;
Client.NetworkId = User.Claims.First(_claim => _claim.Type == ClaimTypes.PrimarySid).Value.ConvertLong();
Client.Level = (EFClient.Permission)Enum.Parse(typeof(EFClient.Permission), User.Claims.First(c => c.Type == ClaimTypes.Role).Value);
Client.CurrentAlias = new EFAlias() { Name = User.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value };
Authorized = Client.ClientId >= 0;
}
}
catch (InvalidOperationException)
@ -77,7 +83,7 @@ namespace WebfrontCore.Controllers
}
catch (System.Collections.Generic.KeyNotFoundException)
catch (KeyNotFoundException)
{
// force the "banned" client to be signed out
HttpContext.SignOutAsync().Wait(5000);
@ -93,7 +99,6 @@ namespace WebfrontCore.Controllers
Authorized = true;
}
Authorized = Client.ClientId >= 0;
ViewBag.Authorized = Authorized;
ViewBag.Url = Manager.GetApplicationSettings().Configuration().WebfrontUrl;
ViewBag.User = Client;

View File

@ -15,6 +15,7 @@ namespace WebfrontCore.Controllers
public async Task<IActionResult> ProfileAsync(int id)
{
var client = await Manager.GetClientService().Get(id);
if (client == null)
{
return NotFound();
@ -22,10 +23,6 @@ namespace WebfrontCore.Controllers
var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId, client.IPAddress);
#if DEBUG
Authorized = true;
#endif
var clientDto = new PlayerInfo()
{
Name = client.Name,
@ -34,10 +31,6 @@ namespace WebfrontCore.Controllers
ClientId = client.ClientId,
IPAddress = client.IPAddressString,
NetworkId = client.NetworkId,
ConnectionCount = client.Connections,
FirstSeen = Utilities.GetTimePassed(client.FirstConnection, false),
LastSeen = Utilities.GetTimePassed(client.LastConnection, false),
TimePlayed = Math.Round(client.TotalConnectionTime / 3600.0, 1).ToString("#,##0"),
Meta = new List<ProfileMeta>(),
Aliases = client.AliasLink.Children
.Where(a => a.Name != client.Name)
@ -53,61 +46,37 @@ namespace WebfrontCore.Controllers
.OrderBy(i => i)
.ToList(),
HasActivePenalty = activePenalties.Count > 0,
ActivePenaltyType = activePenalties.Count > 0 ? activePenalties.First().Type.ToString() : null,
Online = Manager.GetActiveClients().FirstOrDefault(c => c.ClientId == client.ClientId) != null,
TimeOnline = (DateTime.UtcNow - client.LastConnection).TimeSpanText(),
LinkedAccounts = client.LinkedAccounts
};
var meta = await MetaService.GetMeta(client.ClientId);
var penaltyMeta = await Manager.GetPenaltyService()
.ReadGetClientPenaltiesAsync(client.ClientId);
var administeredPenaltiesMeta = await Manager.GetPenaltyService()
.ReadGetClientPenaltiesAsync(client.ClientId, false);
if (Authorized && client.Level > EFClient.Permission.Trusted)
var meta = await MetaService.GetRuntimeMeta(client.ClientId, 0, 1, DateTime.UtcNow);
var gravatar = await new MetaService().GetPersistentMeta("GravatarEmail", client);
if (gravatar != null)
{
clientDto.Meta.Add(new ProfileMeta()
{
Key = Localization["WEBFRONT_CLIENT_META_MASKED"],
Value = client.Masked ? Localization["WEBFRONT_CLIENT_META_TRUE"] : Localization["WEBFRONT_CLIENT_META_FALSE"],
Sensitive = true,
When = DateTime.MinValue
Key = "GravatarEmail",
Type = ProfileMeta.MetaType.Other,
Value = gravatar.Value
});
}
if (Authorized)
{
clientDto.Meta.AddRange(client.AliasLink.Children
.GroupBy(a => a.Name)
.Select(a => a.First())
.Select(a => new ProfileMeta()
{
Key = "AliasEvent",
Value = $"{Localization["WEBFRONT_CLIENT_META_JOINED"]} {a.Name}",
Sensitive = true,
When = a.DateAdded
}));
}
var currentPenalty = activePenalties.FirstOrDefault();
if (Authorized)
if (currentPenalty != null && currentPenalty.Type == SharedLibraryCore.Objects.Penalty.PenaltyType.TempBan)
{
penaltyMeta.ForEach(p => p.Value.Offense = p.Value.AutomatedOffense ?? p.Value.Offense);
administeredPenaltiesMeta.ForEach(p => p.Value.Offense = p.Value.AutomatedOffense ?? p.Value.Offense);
clientDto.Meta.Add(new ProfileMeta()
{
Key = Localization["WEBFRONT_CLIENT_META_REMAINING_BAN"],
Value = ((currentPenalty.Expires - DateTime.UtcNow) ?? new TimeSpan()).TimeSpanText(),
When = currentPenalty.When
});
}
clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.Sensitive));
clientDto.Meta.AddRange(Authorized ? penaltyMeta : penaltyMeta.Where(m => !m.Sensitive));
clientDto.Meta.AddRange(Authorized ? administeredPenaltiesMeta : administeredPenaltiesMeta.Where(m => !m.Sensitive));
clientDto.Meta.AddRange(client.Meta.Select(m => new ProfileMeta()
{
When = m.Created,
Key = m.Key,
Value = m.Value,
Show = false,
}));
clientDto.Meta = clientDto.Meta
.OrderByDescending(m => m.When)
.ToList();
ViewBag.Title = clientDto.Name.Substring(clientDto.Name.Length - 1).ToLower()[0] == 's' ?
clientDto.Name + "'" :
@ -122,8 +91,8 @@ namespace WebfrontCore.Controllers
public async Task<IActionResult> PrivilegedAsync()
{
var admins = (await Manager.GetClientService().GetPrivilegedClients())
.GroupBy(a => a.AliasLinkId).Where(_clients => _clients.FirstOrDefault(_client => _client.LastConnection == _clients.Max(c => c.LastConnection)) != null)
.Select(_client => _client.First())
.GroupBy(a => a.AliasLinkId)
.Select(_client => _client.OrderByDescending(_c => _c.LastConnection).First())
.OrderByDescending(_client => _client.Level);
var adminsDict = new Dictionary<EFClient.Permission, IList<ClientInfo>>();
@ -160,13 +129,30 @@ namespace WebfrontCore.Controllers
Name = c.Name,
Level = c.Level.ToLocalizedLevelName(),
LevelInt = (int)c.Level,
ClientId = c.ClientId,
LastSeen = Utilities.GetTimePassed(c.LastConnection, false)
LastConnection = c.LastConnection,
ClientId = c.ClientId
})
.ToList();
ViewBag.Title = $"{clientsDto.Count} {Localization["WEBFRONT_CLIENT_SEARCH_MATCHING"]} \"{clientName}\"";
return View("Find/Index", clientsDto);
}
public async Task<IActionResult> Meta(int id, int count, int offset, DateTime? startAt)
{
IEnumerable<ProfileMeta> meta = await MetaService.GetRuntimeMeta(id, startAt == null ? offset : 0, count, startAt ?? DateTime.UtcNow);
if (!Authorized)
{
meta = meta.Where(_meta => !_meta.Sensitive);
}
if (meta.Count() == 0)
{
return Ok();
}
return View("Components/ProfileMetaList/_List", meta);
}
}
}

Some files were not shown because too many files have changed in this diff Show More