2020-04-13 17:16:31 -04:00
using IW4MAdmin.Application.IO ;
2020-02-17 11:05:31 -05:00
using IW4MAdmin.Application.Misc ;
2018-04-08 02:44:42 -04:00
using SharedLibraryCore ;
2018-11-05 22:01:29 -05:00
using SharedLibraryCore.Configuration ;
2018-04-08 02:44:42 -04:00
using SharedLibraryCore.Database.Models ;
using SharedLibraryCore.Dtos ;
2018-04-16 16:31:14 -04:00
using SharedLibraryCore.Exceptions ;
2019-05-29 17:55:35 -04:00
using SharedLibraryCore.Helpers ;
2018-11-05 22:01:29 -05:00
using SharedLibraryCore.Interfaces ;
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Runtime.InteropServices ;
2020-05-04 17:50:02 -04:00
using System.Text ;
2018-11-05 22:01:29 -05:00
using System.Text.RegularExpressions ;
using System.Threading ;
using System.Threading.Tasks ;
2019-04-02 21:20:37 -04:00
using static SharedLibraryCore . Database . Models . EFClient ;
2018-04-02 01:25:06 -04:00
2015-03-08 17:20:10 -04:00
namespace IW4MAdmin
{
2017-05-26 18:49:27 -04:00
public class IW4MServer : Server
2015-03-08 17:20:10 -04:00
{
2020-01-26 19:06:50 -05:00
private static readonly SharedLibraryCore . Localization . TranslationLookup loc = Utilities . CurrentLocalization . LocalizationIndex ;
2020-04-01 15:11:56 -04:00
public GameLogEventDetection LogEvent ;
2020-01-26 19:06:50 -05:00
private readonly ITranslationLookup _translationLookup ;
2020-08-17 22:21:11 -04:00
private readonly IMetaService _metaService ;
2019-08-10 10:08:26 -04:00
private const int REPORT_FLAG_COUNT = 4 ;
2020-02-06 19:35:30 -05:00
private int lastGameTime = 0 ;
2019-04-23 18:27:20 -04:00
2018-10-25 09:14:39 -04:00
public int Id { get ; private set ; }
2018-04-26 02:13:04 -04:00
2020-02-11 17:44:06 -05:00
public IW4MServer ( IManager mgr , ServerConfiguration cfg , ITranslationLookup lookup ,
2020-08-17 22:21:11 -04:00
IRConConnectionFactory connectionFactory , IGameLogReaderFactory gameLogReaderFactory , IMetaService metaService ) : base ( cfg , mgr , connectionFactory , gameLogReaderFactory )
2018-06-30 21:55:16 -04:00
{
2020-01-26 19:06:50 -05:00
_translationLookup = lookup ;
2020-08-17 22:21:11 -04:00
_metaService = metaService ;
2018-06-30 21:55:16 -04:00
}
2017-05-26 18:49:27 -04:00
2019-11-15 15:50:20 -05:00
override public async Task < EFClient > OnClientConnected ( EFClient clientFromLog )
2015-03-08 17:20:10 -04:00
{
2018-11-05 22:01:29 -05:00
Logger . WriteDebug ( $"Client slot #{clientFromLog.ClientNumber} now reserved" ) ;
2019-04-12 23:25:18 -04:00
2019-05-17 10:02:09 -04:00
EFClient client = await Manager . GetClientService ( ) . GetUnique ( clientFromLog . NetworkId ) ;
2015-03-08 17:20:10 -04:00
2019-05-17 10:02:09 -04:00
// first time client is connecting to server
if ( client = = null )
{
Logger . WriteDebug ( $"Client {clientFromLog} first time connecting" ) ;
clientFromLog . CurrentServer = this ;
client = await Manager . GetClientService ( ) . Create ( clientFromLog ) ;
}
2017-05-26 18:49:27 -04:00
2019-05-17 10:02:09 -04:00
/// this is only a temporary version until the IPAddress is transmitted
client . CurrentAlias = new EFAlias ( )
{
Name = clientFromLog . Name ,
IPAddress = clientFromLog . IPAddress
} ;
2018-08-07 14:43:09 -04:00
2019-05-17 10:02:09 -04:00
Logger . WriteInfo ( $"Client {client} connected..." ) ;
2018-07-04 22:09:42 -04:00
2019-05-17 10:02:09 -04:00
// Do the player specific stuff
client . ClientNumber = clientFromLog . ClientNumber ;
client . Score = clientFromLog . Score ;
client . Ping = clientFromLog . Ping ;
client . CurrentServer = this ;
2020-02-11 17:44:06 -05:00
client . State = ClientState . Connecting ;
2015-04-10 00:02:12 -04:00
2019-05-17 10:02:09 -04:00
Clients [ client . ClientNumber ] = client ;
2018-11-07 21:30:11 -05:00
#if DEBUG = = true
2019-05-17 10:02:09 -04:00
Logger . WriteDebug ( $"End PreConnect for {client}" ) ;
2018-12-29 13:43:40 -05:00
#endif
2019-05-17 10:02:09 -04:00
var e = new GameEvent ( )
2015-03-08 17:20:10 -04:00
{
2019-05-17 10:02:09 -04:00
Origin = client ,
Owner = this ,
Type = GameEvent . EventType . Connect
} ;
2020-05-04 17:50:02 -04:00
Manager . AddEvent ( e ) ;
2019-11-15 15:50:20 -05:00
return client ;
2015-03-08 17:20:10 -04:00
}
2018-11-07 21:30:11 -05:00
override public async Task OnClientDisconnected ( EFClient client )
2015-03-08 17:20:10 -04:00
{
2019-12-27 21:37:50 -05:00
if ( ! GetClientsAsList ( ) . Any ( _client = > _client . NetworkId = = client . NetworkId ) )
{
Logger . WriteInfo ( $"{client} disconnecting, but they are not connected" ) ;
return ;
}
2018-11-07 21:30:11 -05:00
#if DEBUG = = true
2019-02-17 19:48:40 -05:00
if ( client . ClientNumber > = 0 )
2015-03-16 16:40:30 -04:00
{
2019-02-17 19:48:40 -05:00
#endif
2020-02-06 22:05:50 -05:00
Logger . WriteInfo ( $"Client {client} [{client.State.ToString().ToLower()}] disconnecting..." ) ;
Clients [ client . ClientNumber ] = null ;
await client . OnDisconnect ( ) ;
2019-04-02 21:20:37 -04:00
2020-02-06 22:05:50 -05:00
var e = new GameEvent ( )
{
Origin = client ,
Owner = this ,
Type = GameEvent . EventType . Disconnect
} ;
2015-03-08 17:20:10 -04:00
2020-05-04 17:50:02 -04:00
Manager . AddEvent ( e ) ;
2019-02-17 19:48:40 -05:00
#if DEBUG = = true
}
#endif
2015-03-08 17:20:10 -04:00
}
2018-04-13 02:32:30 -04:00
public override async Task ExecuteEvent ( GameEvent E )
2015-07-06 13:13:42 -04:00
{
2019-11-15 15:50:20 -05:00
if ( E = = null )
2018-09-23 20:45:54 -04:00
{
2019-11-15 15:50:20 -05:00
Logger . WriteError ( "Received NULL event" ) ;
2018-09-23 20:45:54 -04:00
return ;
}
2015-07-15 17:11:29 -04:00
2019-11-15 15:50:20 -05:00
if ( E . IsBlocking )
2018-05-10 01:34:29 -04:00
{
2019-11-15 15:50:20 -05:00
await E . Origin ? . Lock ( ) ;
}
2018-05-10 01:34:29 -04:00
2019-11-15 15:50:20 -05:00
bool canExecuteCommand = true ;
Exception lastException = null ;
2018-05-10 01:34:29 -04:00
2019-11-15 15:50:20 -05:00
try
{
if ( ! await ProcessEvent ( E ) )
2018-05-10 01:34:29 -04:00
{
2019-11-15 15:50:20 -05:00
return ;
2018-05-10 01:34:29 -04:00
}
2019-11-15 15:50:20 -05:00
Command C = null ;
if ( E . Type = = GameEvent . EventType . Command )
2015-08-20 15:23:13 -04:00
{
2019-11-15 15:50:20 -05:00
try
{
2020-07-31 21:40:03 -04:00
C = await SharedLibraryCore . Commands . CommandProcessing . ValidateCommand ( E , Manager . GetApplicationSettings ( ) . Configuration ( ) ) ;
2019-11-15 15:50:20 -05:00
}
catch ( CommandException e )
{
Logger . WriteInfo ( e . Message ) ;
2020-04-26 22:12:49 -04:00
E . FailReason = GameEvent . EventFailReason . Invalid ;
2019-11-15 15:50:20 -05:00
}
if ( C ! = null )
{
E . Extra = C ;
}
2017-05-26 18:49:27 -04:00
}
2019-11-15 15:50:20 -05:00
2020-04-20 11:45:58 -04:00
try
{
var loginPlugin = Manager . Plugins . FirstOrDefault ( _plugin = > _plugin . Name = = "Login" ) ;
if ( loginPlugin ! = null )
{
await loginPlugin . OnEventAsync ( E , this ) ;
}
}
catch ( AuthorizationException e )
{
E . Origin . Tell ( $"{loc[" COMMAND_NOTAUTHORIZED "]} - {e.Message}" ) ;
canExecuteCommand = false ;
}
// hack: this prevents commands from getting executing that 'shouldn't' be
if ( E . Type = = GameEvent . EventType . Command & & E . Extra is Command command & &
( canExecuteCommand | | E . Origin ? . Level = = Permission . Console ) )
{
await command . ExecuteAsync ( E ) ;
}
var pluginTasks = Manager . Plugins . Where ( _plugin = > _plugin . Name ! = "Login" ) . Select ( async _plugin = >
2018-04-26 02:13:04 -04:00
{
2019-11-15 15:50:20 -05:00
try
{
2020-02-17 11:05:31 -05:00
// we don't want to run the events on parser plugins
2020-04-20 11:45:58 -04:00
if ( _plugin is ScriptPlugin scriptPlugin & & scriptPlugin . IsParser )
2020-02-17 11:05:31 -05:00
{
2020-04-20 11:45:58 -04:00
return ;
2020-02-17 11:05:31 -05:00
}
2020-05-04 17:50:02 -04:00
using ( var tokenSource = new CancellationTokenSource ( ) )
{
tokenSource . CancelAfter ( Utilities . DefaultCommandTimeout ) ;
await ( _plugin . OnEventAsync ( E , this ) ) . WithWaitCancellation ( tokenSource . Token ) ;
}
2019-11-15 15:50:20 -05:00
}
catch ( Exception Except )
{
2020-04-20 11:45:58 -04:00
Logger . WriteError ( $"{loc[" SERVER_PLUGIN_ERROR "]} [{_plugin.Name}]" ) ;
2019-11-15 15:50:20 -05:00
Logger . WriteDebug ( Except . GetExceptionInfo ( ) ) ;
}
2020-05-17 18:01:13 -04:00
} ) . ToArray ( ) ;
2019-11-15 15:50:20 -05:00
2020-05-17 18:01:13 -04:00
if ( pluginTasks . Any ( ) )
2020-05-16 12:54:01 -04:00
{
await Task . WhenAny ( pluginTasks ) ;
}
2015-08-20 13:52:30 -04:00
}
2018-04-26 02:13:04 -04:00
2019-11-15 15:50:20 -05:00
catch ( Exception e )
{
lastException = e ;
2020-02-11 17:44:06 -05:00
2020-02-17 11:05:31 -05:00
if ( E . Origin ! = null & & E . Type = = GameEvent . EventType . Command )
2020-02-11 17:44:06 -05:00
{
E . Origin . Tell ( _translationLookup [ "SERVER_ERROR_COMMAND_INGAME" ] ) ;
}
2019-11-15 15:50:20 -05:00
}
finally
2018-04-26 02:13:04 -04:00
{
2019-11-25 13:05:12 -05:00
if ( E . IsBlocking )
{
E . Origin ? . Unlock ( ) ;
}
2019-11-15 15:50:20 -05:00
if ( lastException ! = null )
{
2020-04-25 20:01:26 -04:00
bool notifyDisconnects = ! Manager . GetApplicationSettings ( ) . Configuration ( ) . IgnoreServerConnectionLost ;
if ( notifyDisconnects | | ( ! notifyDisconnects & & lastException as NetworkException = = null ) )
{
throw lastException ;
}
2019-11-15 15:50:20 -05:00
}
2018-04-26 02:13:04 -04:00
}
2015-08-20 13:52:30 -04:00
}
2018-04-26 02:13:04 -04:00
/// <summary>
/// Perform the server specific tasks when an event occurs
/// </summary>
/// <param name="E"></param>
/// <returns></returns>
2018-09-23 20:45:54 -04:00
override protected async Task < bool > ProcessEvent ( GameEvent E )
2018-04-26 02:13:04 -04:00
{
2019-06-09 10:50:58 -04:00
#if DEBUG
Logger . WriteDebug ( $"processing event of type {E.Type}" ) ;
#endif
2019-04-23 18:27:20 -04:00
if ( E . Type = = GameEvent . EventType . ConnectionLost )
{
var exception = E . Extra as Exception ;
2020-04-22 19:46:41 -04:00
if ( ! Manager . GetApplicationSettings ( ) . Configuration ( ) . IgnoreServerConnectionLost )
2019-04-23 18:27:20 -04:00
{
2020-04-22 19:46:41 -04:00
Logger . WriteError ( exception . Message ) ;
if ( exception . Data [ "internal_exception" ] ! = null )
{
Logger . WriteDebug ( $"Internal Exception: {exception.Data[" internal_exception "]}" ) ;
}
2019-04-23 18:27:20 -04:00
}
Logger . WriteInfo ( "Connection lost to server, so we are throttling the poll rate" ) ;
Throttled = true ;
}
if ( E . Type = = GameEvent . EventType . ConnectionRestored )
{
2020-04-25 20:01:26 -04:00
if ( Throttled & & ! Manager . GetApplicationSettings ( ) . Configuration ( ) . IgnoreServerConnectionLost )
2019-04-25 14:00:54 -04:00
{
Logger . WriteVerbose ( loc [ "MANAGER_CONNECTION_REST" ] . FormatExt ( $"[{IP}:{Port}]" ) ) ;
}
2019-04-23 18:27:20 -04:00
Logger . WriteInfo ( "Connection restored to server, so we are no longer throttling the poll rate" ) ;
Throttled = false ;
}
2018-11-25 21:00:36 -05:00
if ( E . Type = = GameEvent . EventType . ChangePermission )
2018-04-28 21:11:13 -04:00
{
2019-04-08 13:29:48 -04:00
var newPermission = ( Permission ) E . Extra ;
Logger . WriteInfo ( $"{E.Origin} is setting {E.Target} to permission level {newPermission}" ) ;
await Manager . GetClientService ( ) . UpdateLevel ( newPermission , E . Target , E . Origin ) ;
2018-11-25 21:00:36 -05:00
}
2018-08-27 18:07:54 -04:00
2019-11-15 15:50:20 -05:00
else if ( E . Type = = GameEvent . EventType . Connect )
{
if ( E . Origin . State ! = ClientState . Connected )
{
E . Origin . State = ClientState . Connected ;
E . Origin . LastConnection = DateTime . UtcNow ;
E . Origin . Connections + = 1 ;
ChatHistory . Add ( new ChatInfo ( )
{
Name = E . Origin . Name ,
Message = "CONNECTED" ,
Time = DateTime . UtcNow
} ) ;
await E . Origin . OnJoin ( E . Origin . IPAddress ) ;
}
}
2018-11-25 21:00:36 -05:00
else if ( E . Type = = GameEvent . EventType . PreConnect )
{
2019-02-22 20:06:51 -05:00
// 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 ;
}
2020-04-25 20:01:26 -04:00
if ( E . Origin . CurrentServer = = null )
{
Logger . WriteWarning ( $"preconnecting client {E.Origin} did not have a current server specified" ) ;
E . Origin . CurrentServer = this ;
}
2019-04-02 21:20:37 -04:00
var existingClient = GetClientsAsList ( ) . FirstOrDefault ( _client = > _client . Equals ( E . Origin ) ) ;
2019-04-05 22:15:17 -04:00
// they're already connected
2019-06-09 10:50:58 -04:00
if ( existingClient ! = null & & existingClient . ClientNumber = = E . Origin . ClientNumber & & ! E . Origin . IsBot )
2019-04-05 22:15:17 -04:00
{
2019-04-06 22:48:49 -04:00
Logger . WriteWarning ( $"detected preconnect for {E.Origin}, but they are already connected" ) ;
2019-04-05 22:15:17 -04:00
return false ;
}
2019-06-09 10:50:58 -04:00
// this happens for some reason rarely where the client spots get out of order
// possible a connect/reconnect game event before we get to process it here
2020-04-26 13:32:41 -04:00
// it appears that new games decide to switch client slots between maps (even if the clients aren't disconnecting)
2020-05-04 17:50:02 -04:00
// bots can have duplicate names which causes conflicting GUIDs
else if ( existingClient ! = null & & existingClient . ClientNumber ! = E . Origin . ClientNumber & & ! E . Origin . IsBot )
2019-06-09 10:50:58 -04:00
{
Logger . WriteWarning ( $"client {E.Origin} is trying to connect in client slot {E.Origin.ClientNumber}, but they are already registed in client slot {existingClient.ClientNumber}, swapping..." ) ;
// we need to remove them so the client spots can swap
await OnClientDisconnected ( Clients [ existingClient . ClientNumber ] ) ;
}
2018-11-25 21:00:36 -05:00
if ( Clients [ E . Origin . ClientNumber ] = = null )
2018-04-28 21:11:13 -04:00
{
2018-11-07 21:30:11 -05:00
#if DEBUG = = true
Logger . WriteDebug ( $"Begin PreConnect for {E.Origin}" ) ;
#endif
2019-04-06 22:48:49 -04:00
// we can go ahead and put them in so that they don't get re added
Clients [ E . Origin . ClientNumber ] = E . Origin ;
2019-05-17 10:02:09 -04:00
try
{
2019-11-15 15:50:20 -05:00
E . Origin = await OnClientConnected ( E . Origin ) ;
E . Target = E . Origin ;
2019-05-17 10:02:09 -04:00
}
catch ( Exception ex )
{
Logger . WriteError ( $"{loc[" SERVER_ERROR_ADDPLAYER "]} {E.Origin}" ) ;
Logger . WriteDebug ( ex . GetExceptionInfo ( ) ) ;
Clients [ E . Origin . ClientNumber ] = null ;
return false ;
}
2018-04-28 21:11:13 -04:00
2020-04-26 22:12:49 -04:00
if ( E . Origin . Level > Permission . Moderator )
2018-11-07 21:30:11 -05:00
{
E . Origin . Tell ( string . Format ( loc [ "SERVER_REPORT_COUNT" ] , E . Owner . Reports . Count ) ) ;
}
2018-09-29 15:52:22 -04:00
}
2018-04-28 21:11:13 -04:00
2019-04-02 21:20:37 -04:00
// for some reason there's still a client in the spot
2019-04-05 22:15:17 -04:00
else
2019-04-02 21:20:37 -04:00
{
2019-05-29 17:55:35 -04:00
Logger . WriteWarning ( $"{E.Origin} is connecting but {Clients[E.Origin.ClientNumber]} is currently in that client slot" ) ;
2019-04-02 21:20:37 -04:00
}
2018-06-30 21:55:16 -04:00
}
2018-10-02 13:39:08 -04:00
else if ( E . Type = = GameEvent . EventType . Flag )
{
2019-06-25 19:01:47 -04:00
DateTime ? expires = null ;
if ( E . Extra is TimeSpan ts )
{
expires = DateTime . UtcNow + ts ;
}
2018-12-16 22:16:56 -05:00
// todo: maybe move this to a seperate function
2019-05-29 17:55:35 -04:00
var newPenalty = new EFPenalty ( )
2018-10-02 13:39:08 -04:00
{
2019-05-29 17:55:35 -04:00
Type = EFPenalty . PenaltyType . Flag ,
2019-06-25 19:01:47 -04:00
Expires = expires ,
2018-10-02 13:39:08 -04:00
Offender = E . Target ,
Offense = E . Data ,
2020-04-26 22:12:49 -04:00
Punisher = E . ImpersonationOrigin ? ? E . Origin ,
2018-10-02 13:39:08 -04:00
When = DateTime . UtcNow ,
Link = E . Target . AliasLink
} ;
var addedPenalty = await Manager . GetPenaltyService ( ) . Create ( newPenalty ) ;
2019-04-02 21:20:37 -04:00
E . Target . SetLevel ( Permission . Flagged , E . Origin ) ;
2018-10-02 13:39:08 -04:00
}
else if ( E . Type = = GameEvent . EventType . Unflag )
{
2019-05-29 17:55:35 -04:00
var unflagPenalty = new EFPenalty ( )
2019-04-05 14:34:03 -04:00
{
2019-05-29 17:55:35 -04:00
Type = EFPenalty . PenaltyType . Unflag ,
2019-04-05 14:34:03 -04:00
Expires = DateTime . UtcNow ,
Offender = E . Target ,
Offense = E . Data ,
2020-04-26 22:12:49 -04:00
Punisher = E . ImpersonationOrigin ? ? E . Origin ,
2019-04-05 14:34:03 -04:00
When = DateTime . UtcNow ,
Link = E . Target . AliasLink
} ;
2019-04-02 21:20:37 -04:00
E . Target . SetLevel ( Permission . User , E . Origin ) ;
2019-06-27 21:06:30 -04:00
await Manager . GetPenaltyService ( ) . RemoveActivePenalties ( E . Target . AliasLinkId ) ;
await Manager . GetPenaltyService ( ) . Create ( unflagPenalty ) ;
2018-10-02 13:39:08 -04:00
}
else if ( E . Type = = GameEvent . EventType . Report )
{
2019-04-02 21:20:37 -04:00
Reports . Add ( new Report ( )
2018-10-02 13:39:08 -04:00
{
Origin = E . Origin ,
Target = E . Target ,
Reason = E . Data
} ) ;
2019-05-08 21:34:17 -04:00
2019-05-29 17:55:35 -04:00
var newReport = new EFPenalty ( )
2019-05-08 21:34:17 -04:00
{
2019-05-29 17:55:35 -04:00
Type = EFPenalty . PenaltyType . Report ,
2019-05-08 21:34:17 -04:00
Expires = DateTime . UtcNow ,
Offender = E . Target ,
Offense = E . Message ,
2020-04-26 22:12:49 -04:00
Punisher = E . ImpersonationOrigin ? ? E . Origin ,
2019-05-08 21:34:17 -04:00
Active = true ,
When = DateTime . UtcNow ,
Link = E . Target . AliasLink
} ;
await Manager . GetPenaltyService ( ) . Create ( newReport ) ;
2019-08-10 10:08:26 -04:00
int reportNum = await Manager . GetClientService ( ) . GetClientReportCount ( E . Target . ClientId ) ;
bool isAutoFlagged = await Manager . GetClientService ( ) . IsAutoFlagged ( E . Target . ClientId ) ;
2019-08-30 14:31:23 -04:00
if ( ! E . Target . IsPrivileged ( ) & & reportNum > = REPORT_FLAG_COUNT & & ! isAutoFlagged )
2019-08-10 10:08:26 -04:00
{
E . Target . Flag ( Utilities . CurrentLocalization . LocalizationIndex [ "SERVER_AUTO_FLAG_REPORT" ] . FormatExt ( reportNum ) , Utilities . IW4MAdminClient ( E . Owner ) ) ;
}
2018-10-02 13:39:08 -04:00
}
else if ( E . Type = = GameEvent . EventType . TempBan )
{
2020-04-26 22:12:49 -04:00
await TempBan ( E . Data , ( TimeSpan ) E . Extra , E . Target , E . ImpersonationOrigin ? ? E . Origin ) ; ;
2018-10-02 13:39:08 -04:00
}
else if ( E . Type = = GameEvent . EventType . Ban )
{
2018-12-29 13:43:40 -05:00
bool isEvade = E . Extra ! = null ? ( bool ) E . Extra : false ;
2020-04-26 22:12:49 -04:00
await Ban ( E . Data , E . Target , E . ImpersonationOrigin ? ? E . Origin , isEvade ) ;
2018-10-02 13:39:08 -04:00
}
else if ( E . Type = = GameEvent . EventType . Unban )
{
2020-04-26 22:12:49 -04:00
await Unban ( E . Data , E . Target , E . ImpersonationOrigin ? ? E . Origin ) ;
2018-10-02 13:39:08 -04:00
}
else if ( E . Type = = GameEvent . EventType . Kick )
{
2020-04-26 22:12:49 -04:00
await Kick ( E . Data , E . Target , E . ImpersonationOrigin ? ? E . Origin ) ;
2018-10-02 13:39:08 -04:00
}
2018-10-03 22:20:49 -04:00
else if ( E . Type = = GameEvent . EventType . Warn )
{
2020-04-26 22:12:49 -04:00
await Warn ( E . Data , E . Target , E . ImpersonationOrigin ? ? E . Origin ) ;
2018-10-03 22:20:49 -04:00
}
2019-02-22 20:06:51 -05:00
else if ( E . Type = = GameEvent . EventType . Disconnect )
{
2019-04-02 21:20:37 -04:00
ChatHistory . Add ( new ChatInfo ( )
{
Name = E . Origin . Name ,
Message = "DISCONNECTED" ,
Time = DateTime . UtcNow
} ) ;
2020-08-17 22:21:11 -04:00
await _metaService . AddPersistentMeta ( "LastMapPlayed" , CurrentMap . Alias , E . Origin ) ;
await _metaService . AddPersistentMeta ( "LastServerPlayed" , E . Owner . Hostname , E . Origin ) ;
2019-02-22 20:06:51 -05:00
}
2018-11-07 21:30:11 -05:00
else if ( E . Type = = GameEvent . EventType . PreDisconnect )
2018-04-26 02:13:04 -04:00
{
2020-02-06 19:35:30 -05:00
bool isPotentialFalseQuit = E . GameTime . HasValue & & E . GameTime . Value = = lastGameTime ;
if ( isPotentialFalseQuit )
{
2020-02-07 12:15:21 -05:00
Logger . WriteInfo ( $"Receive predisconnect event for {E.Origin}, but it occured at game time {E.GameTime.Value}, which is the same last map change, so we're ignoring" ) ;
2020-02-06 19:35:30 -05:00
return false ;
}
2018-11-07 21:30:11 -05:00
// 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 ) ) ;
2020-01-06 12:04:36 -05:00
if ( client = = null )
{
Logger . WriteWarning ( $"Client {E.Origin} detected as disconnecting, but could not find them in the player list" ) ;
return false ;
}
2020-02-07 12:15:21 -05:00
else if ( client . State ! = ClientState . Unknown )
2018-04-26 02:13:04 -04:00
{
2018-11-07 21:30:11 -05:00
#if DEBUG = = true
Logger . WriteDebug ( $"Begin PreDisconnect for {client}" ) ;
#endif
await OnClientDisconnected ( client ) ;
2019-04-02 21:20:37 -04:00
#if DEBUG = = true
Logger . WriteDebug ( $"End PreDisconnect for {client}" ) ;
#endif
2020-01-06 12:04:36 -05:00
return true ;
2018-11-07 21:30:11 -05:00
}
2018-08-28 17:32:59 -04:00
2020-01-06 12:04:36 -05:00
else
2018-08-28 17:32:59 -04:00
{
2020-01-06 12:04:36 -05:00
Logger . WriteWarning ( $"Expected disconnecting client {client} to be in state {ClientState.Connected.ToString()}, but is in state {client.State}" ) ;
2018-11-07 21:30:11 -05:00
return false ;
2018-08-28 17:32:59 -04:00
}
2018-04-26 02:13:04 -04:00
}
2018-11-07 21:30:11 -05:00
else if ( E . Type = = GameEvent . EventType . Update )
{
#if DEBUG = = true
Logger . WriteDebug ( $"Begin Update for {E.Origin}" ) ;
#endif
await OnClientUpdate ( E . Origin ) ;
}
2018-05-14 13:55:10 -04:00
if ( E . Type = = GameEvent . EventType . Say )
2018-04-26 02:13:04 -04:00
{
2019-04-25 14:00:54 -04:00
if ( E . Data ? . Length > 0 )
2018-04-26 02:13:04 -04:00
{
2019-04-25 14:00:54 -04:00
string message = E . Data ;
if ( E . Data . IsQuickMessage ( ) )
2018-05-14 13:55:10 -04:00
{
2019-04-25 14:00:54 -04:00
try
2018-05-14 13:55:10 -04:00
{
2019-04-25 14:00:54 -04:00
message = Manager . GetApplicationSettings ( ) . Configuration ( )
. QuickMessages
. First ( _qm = > _qm . Game = = GameName )
. Messages [ E . Data . Substring ( 1 ) ] ;
}
2020-08-20 11:38:11 -04:00
catch
{
message = E . Data . Substring ( 1 ) ;
}
2018-05-14 13:55:10 -04:00
}
2019-04-25 14:00:54 -04:00
ChatHistory . Add ( new ChatInfo ( )
{
Name = E . Origin . Name ,
Message = message ,
2020-08-20 11:38:11 -04:00
Time = DateTime . UtcNow ,
IsHidden = ! string . IsNullOrEmpty ( GamePassword )
2019-04-25 14:00:54 -04:00
} ) ;
2018-05-14 13:55:10 -04:00
}
2018-04-26 02:13:04 -04:00
}
if ( E . Type = = GameEvent . EventType . MapChange )
{
Logger . WriteInfo ( $"New map loaded - {ClientNum} active players" ) ;
// iw4 doesn't log the game info
if ( E . Extra = = null )
{
2019-10-23 11:40:24 -04:00
var dict = await this . GetInfoAsync ( new TimeSpan ( 0 , 0 , 20 ) ) ;
2018-04-26 02:13:04 -04:00
2018-05-03 01:25:49 -04:00
if ( dict = = null )
{
Logger . WriteWarning ( "Map change event response doesn't have any data" ) ;
}
2018-04-26 02:13:04 -04:00
2018-05-03 01:25:49 -04:00
else
{
2019-08-01 10:37:33 -04:00
Gametype = dict [ "gametype" ] ;
Hostname = dict [ "hostname" ] ;
2018-05-03 01:25:49 -04:00
2019-08-01 10:37:33 -04:00
string mapname = dict [ "mapname" ] ? ? CurrentMap . Name ;
2019-11-18 15:02:35 -05:00
UpdateMap ( mapname ) ;
2018-05-03 01:25:49 -04:00
}
2018-04-26 02:13:04 -04:00
}
else
{
var dict = ( Dictionary < string , string > ) E . Extra ;
2019-08-01 10:37:33 -04:00
Gametype = dict [ "g_gametype" ] ;
Hostname = dict [ "sv_hostname" ] ;
2019-04-02 21:20:37 -04:00
MaxClients = int . Parse ( dict [ "sv_maxclients" ] ) ;
2018-04-26 02:13:04 -04:00
2019-08-01 10:37:33 -04:00
string mapname = dict [ "mapname" ] ;
2019-11-18 15:02:35 -05:00
UpdateMap ( mapname ) ;
2018-04-26 02:13:04 -04:00
}
2020-02-06 19:35:30 -05:00
if ( E . GameTime . HasValue )
{
lastGameTime = E . GameTime . Value ;
}
2018-04-26 02:13:04 -04:00
}
if ( E . Type = = GameEvent . EventType . MapEnd )
{
Logger . WriteInfo ( "Game ending..." ) ;
2020-02-06 19:35:30 -05:00
if ( E . GameTime . HasValue )
{
lastGameTime = E . GameTime . Value ;
}
2018-04-26 02:13:04 -04:00
}
2018-05-04 00:22:10 -04:00
if ( E . Type = = GameEvent . EventType . Tell )
{
await Tell ( E . Message , E . Target ) ;
}
if ( E . Type = = GameEvent . EventType . Broadcast )
{
2020-08-17 22:21:11 -04:00
if ( ! Utilities . IsDevelopment & & E . Data ! = null ) // hides broadcast when in development mode
2018-06-30 21:55:16 -04:00
{
2018-08-31 23:35:51 -04:00
await E . Owner . ExecuteCommandAsync ( E . Data ) ;
2018-06-30 21:55:16 -04:00
}
2018-05-04 00:22:10 -04:00
}
2018-12-01 13:17:53 -05:00
lock ( ChatHistory )
2018-11-05 22:01:29 -05:00
{
2018-12-01 13:17:53 -05:00
while ( ChatHistory . Count > Math . Ceiling ( ClientNum / 2.0 ) )
{
ChatHistory . RemoveAt ( 0 ) ;
}
2018-11-05 22:01:29 -05:00
}
2018-04-26 02:13:04 -04:00
// the last client hasn't fully disconnected yet
// so there will still be at least 1 client left
if ( ClientNum < 2 )
2018-11-05 22:01:29 -05:00
{
2018-04-26 02:13:04 -04:00
ChatHistory . Clear ( ) ;
2018-11-05 22:01:29 -05:00
}
2018-09-23 20:45:54 -04:00
return true ;
2018-04-26 02:13:04 -04:00
}
2019-04-06 22:48:49 -04:00
private async Task OnClientUpdate ( EFClient origin )
2018-11-07 21:30:11 -05:00
{
2019-12-27 13:10:20 -05:00
var client = GetClientsAsList ( ) . FirstOrDefault ( _client = > _client . Equals ( origin ) ) ;
if ( client = = null )
{
Logger . WriteWarning ( $"{origin} expected to exist in client list for update, but they do not" ) ;
2019-12-28 16:40:55 -05:00
return ;
2019-12-27 13:10:20 -05:00
}
2018-11-07 21:30:11 -05:00
2019-11-25 13:05:12 -05:00
client . Ping = origin . Ping ;
client . Score = origin . Score ;
2018-11-07 21:30:11 -05:00
2019-11-25 13:05:12 -05:00
// update their IP if it hasn't been set yet
if ( client . IPAddress = = null & &
! client . IsBot & &
client . State = = ClientState . Connected )
{
try
2018-11-07 21:30:11 -05:00
{
2019-11-25 13:05:12 -05:00
await client . OnJoin ( origin . IPAddress ) ;
}
2019-04-08 13:29:48 -04:00
2019-11-25 13:05:12 -05:00
catch ( Exception e )
{
2019-12-29 18:07:00 -05:00
Logger . WriteWarning ( $"Could not execute on join for {origin}" ) ;
Logger . WriteDebug ( e . GetExceptionInfo ( ) ) ;
2018-11-07 21:30:11 -05:00
}
}
2020-02-06 19:35:30 -05:00
2020-06-16 18:16:12 -04:00
else if ( ( client . IPAddress ! = null & & client . State = = ClientState . Disconnecting ) | |
2020-05-16 21:55:18 -04:00
client . Level = = Permission . Banned )
2020-02-06 19:35:30 -05:00
{
2020-02-07 12:15:21 -05:00
Logger . WriteWarning ( $"{client} state is Unknown (probably kicked), but they are still connected. trying to kick again..." ) ;
2020-02-06 19:35:30 -05:00
await client . CanConnect ( client . IPAddress ) ;
}
2018-11-07 21:30:11 -05:00
}
2018-08-27 18:07:54 -04:00
/// <summary>
/// lists the connecting and disconnecting clients via RCon response
2019-06-27 21:06:30 -04:00
/// array index 0 = connecting clients
2018-08-27 18:07:54 -04:00
/// array index 1 = disconnecting clients
2019-06-27 21:06:30 -04:00
/// array index 2 = updated clients
2018-08-27 18:07:54 -04:00
/// </summary>
/// <returns></returns>
2018-11-05 22:01:29 -05:00
async Task < IList < EFClient > [ ] > PollPlayersAsync ( )
2015-03-08 17:20:10 -04:00
{
2018-08-27 18:07:54 -04:00
#if DEBUG
2018-03-06 02:22:19 -05:00
var now = DateTime . Now ;
2018-08-27 18:07:54 -04:00
#endif
2018-11-05 22:01:29 -05:00
var currentClients = GetClientsAsList ( ) ;
2019-11-18 15:02:35 -05:00
var statusResponse = ( await this . GetStatusAsync ( ) ) ;
var polledClients = statusResponse . Item1 . AsEnumerable ( ) ;
2019-04-02 21:20:37 -04:00
2018-12-30 19:13:13 -05:00
if ( Manager . GetApplicationSettings ( ) . Configuration ( ) . IgnoreBots )
2018-10-13 19:49:08 -04:00
{
polledClients = polledClients . Where ( c = > ! c . IsBot ) ;
}
2018-03-06 02:22:19 -05:00
#if DEBUG
Logger . WriteInfo ( $"Polling players took {(DateTime.Now - now).TotalMilliseconds}ms" ) ;
#endif
2018-08-27 18:07:54 -04:00
var disconnectingClients = currentClients . Except ( polledClients ) ;
2018-11-07 21:30:11 -05:00
var connectingClients = polledClients . Except ( currentClients ) ;
2018-11-25 21:00:36 -05:00
var updatedClients = polledClients . Except ( connectingClients ) . Except ( disconnectingClients ) ;
2018-06-30 21:55:16 -04:00
2019-11-18 15:02:35 -05:00
UpdateMap ( statusResponse . Item2 ) ;
2020-08-05 10:30:02 -04:00
UpdateGametype ( statusResponse . Item3 ) ;
2019-11-18 15:02:35 -05:00
2018-11-07 21:30:11 -05:00
return new List < EFClient > [ ]
{
connectingClients . ToList ( ) ,
disconnectingClients . ToList ( ) ,
updatedClients . ToList ( )
} ;
2017-05-26 18:49:27 -04:00
}
2015-03-08 17:20:10 -04:00
2019-11-18 15:02:35 -05:00
private void UpdateMap ( string mapname )
{
if ( ! string . IsNullOrEmpty ( mapname ) )
{
CurrentMap = Maps . Find ( m = > m . Name = = mapname ) ? ? new Map ( )
{
Alias = mapname ,
Name = mapname
} ;
}
}
2020-08-05 10:30:02 -04:00
private void UpdateGametype ( string gameType )
{
if ( ! string . IsNullOrEmpty ( gameType ) )
{
Gametype = gameType ;
}
}
2019-08-24 11:02:53 -04:00
private async Task ShutdownInternal ( )
{
foreach ( var client in GetClientsAsList ( ) )
{
await client . OnDisconnect ( ) ;
var e = new GameEvent ( )
{
Type = GameEvent . EventType . Disconnect ,
Owner = this ,
Origin = client
} ;
2020-05-04 17:50:02 -04:00
Manager . AddEvent ( e ) ;
2019-08-24 11:02:53 -04:00
await e . WaitAsync ( Utilities . DefaultCommandTimeout , new CancellationTokenRegistration ( ) . Token ) ;
}
2020-02-11 17:44:06 -05:00
foreach ( var plugin in Manager . Plugins )
2019-08-24 11:02:53 -04:00
{
await plugin . OnUnloadAsync ( ) ;
}
}
2017-05-26 18:49:27 -04:00
DateTime start = DateTime . Now ;
DateTime playerCountStart = DateTime . Now ;
DateTime lastCount = DateTime . Now ;
2015-03-08 17:20:10 -04:00
2018-02-07 00:19:06 -05:00
override public async Task < bool > ProcessUpdatesAsync ( CancellationToken cts )
2017-05-26 18:49:27 -04:00
{
2020-04-22 19:46:41 -04:00
bool notifyDisconnects = ! Manager . GetApplicationSettings ( ) . Configuration ( ) . IgnoreServerConnectionLost ;
2017-05-30 17:23:31 -04:00
try
2019-10-23 11:40:24 -04:00
{
2019-08-24 11:02:53 -04:00
if ( cts . IsCancellationRequested )
2018-04-29 16:44:04 -04:00
{
2019-08-24 11:02:53 -04:00
await ShutdownInternal ( ) ;
2018-11-07 21:30:11 -05:00
return true ;
}
2017-06-12 08:28:08 -04:00
try
{
2019-06-24 12:01:34 -04:00
#if DEBUG
if ( Manager . GetApplicationSettings ( ) . Configuration ( ) . RConPollRate = = int . MaxValue )
{
return true ;
}
#endif
2018-08-27 18:07:54 -04:00
var polledClients = await PollPlayersAsync ( ) ;
foreach ( var disconnectingClient in polledClients [ 1 ] )
{
2020-04-26 13:32:41 -04:00
disconnectingClient . CurrentServer = this ;
2018-08-27 18:07:54 -04:00
var e = new GameEvent ( )
{
2018-11-07 21:30:11 -05:00
Type = GameEvent . EventType . PreDisconnect ,
2018-08-27 18:07:54 -04:00
Origin = disconnectingClient ,
2020-05-04 17:50:02 -04:00
Owner = this ,
Source = GameEvent . EventSource . Status
2018-08-27 18:07:54 -04:00
} ;
2020-05-04 17:50:02 -04:00
Manager . AddEvent ( e ) ;
2019-11-15 15:50:20 -05:00
await e . WaitAsync ( Utilities . DefaultCommandTimeout , Manager . CancellationToken ) ;
2018-08-27 18:07:54 -04:00
}
2019-09-26 17:08:49 -04:00
2018-08-27 18:07:54 -04:00
// this are our new connecting clients
foreach ( var client in polledClients [ 0 ] )
{
2019-04-05 14:34:03 -04:00
// note: this prevents players in ZMBI state from being registered with no name
2019-06-15 18:37:43 -04:00
if ( string . IsNullOrEmpty ( client . Name ) | | ( client . Ping = = 999 & & ! client . IsBot ) )
2019-04-05 14:34:03 -04:00
{
continue ;
}
2020-04-26 13:32:41 -04:00
client . CurrentServer = this ;
2018-11-07 21:30:11 -05:00
var e = new GameEvent ( )
2018-09-02 17:59:27 -04:00
{
2018-11-07 21:30:11 -05:00
Type = GameEvent . EventType . PreConnect ,
Origin = client ,
2019-10-18 14:39:21 -04:00
Owner = this ,
2020-05-04 17:50:02 -04:00
IsBlocking = true ,
Source = GameEvent . EventSource . Status
2018-11-07 21:30:11 -05:00
} ;
2020-05-04 17:50:02 -04:00
Manager . AddEvent ( e ) ;
2019-11-15 15:50:20 -05:00
await e . WaitAsync ( Utilities . DefaultCommandTimeout , Manager . CancellationToken ) ;
2019-09-26 17:08:49 -04:00
}
2018-09-02 17:59:27 -04:00
2018-11-07 21:30:11 -05:00
// these are the clients that have updated
foreach ( var client in polledClients [ 2 ] )
{
2020-04-26 13:32:41 -04:00
client . CurrentServer = this ;
2018-08-27 18:07:54 -04:00
var e = new GameEvent ( )
{
2018-11-07 21:30:11 -05:00
Type = GameEvent . EventType . Update ,
2018-08-27 18:07:54 -04:00
Origin = client ,
Owner = this
} ;
2020-05-04 17:50:02 -04:00
Manager . AddEvent ( e ) ;
2019-09-26 17:08:49 -04:00
}
2018-08-28 17:32:59 -04:00
2020-04-25 20:01:26 -04:00
if ( ConnectionErrors > 0 )
2018-04-29 16:44:04 -04:00
{
2019-03-24 22:34:20 -04:00
var _event = new GameEvent ( )
{
Type = GameEvent . EventType . ConnectionRestored ,
Owner = this ,
Origin = Utilities . IW4MAdminClient ( this ) ,
Target = Utilities . IW4MAdminClient ( this )
} ;
2020-05-04 17:50:02 -04:00
Manager . AddEvent ( _event ) ;
2017-08-09 00:35:23 -04:00
}
2018-11-07 21:30:11 -05:00
2018-04-29 16:44:04 -04:00
ConnectionErrors = 0 ;
LastPoll = DateTime . Now ;
2017-06-12 08:28:08 -04:00
}
2018-04-14 00:51:38 -04:00
catch ( NetworkException e )
2017-06-12 08:28:08 -04:00
{
ConnectionErrors + + ;
2020-04-25 20:01:26 -04:00
if ( ConnectionErrors = = 3 )
2017-08-09 00:35:23 -04:00
{
2019-03-24 22:34:20 -04:00
var _event = new GameEvent ( )
{
Type = GameEvent . EventType . ConnectionLost ,
Owner = this ,
Origin = Utilities . IW4MAdminClient ( this ) ,
Target = Utilities . IW4MAdminClient ( this ) ,
Extra = e ,
Data = ConnectionErrors . ToString ( )
} ;
2020-05-04 17:50:02 -04:00
Manager . AddEvent ( _event ) ;
2017-08-09 00:35:23 -04:00
}
2018-02-07 00:19:06 -05:00
return true ;
2017-06-12 08:28:08 -04:00
}
2017-05-26 18:49:27 -04:00
2017-06-19 13:58:01 -04:00
LastMessage = DateTime . Now - start ;
2017-05-26 18:49:27 -04:00
lastCount = DateTime . Now ;
2018-04-29 16:44:04 -04:00
// update the player history
2019-06-11 09:00:14 -04:00
if ( ( lastCount - playerCountStart ) . TotalMinutes > = PlayerHistory . UpdateInterval )
2017-05-26 18:49:27 -04:00
{
2019-06-11 09:00:14 -04:00
while ( ClientHistory . Count > ( ( 60 / PlayerHistory . UpdateInterval ) * 12 ) ) // 12 times a hour for 12 hours
2018-11-05 22:01:29 -05:00
{
ClientHistory . Dequeue ( ) ;
}
2019-06-11 09:00:14 -04:00
ClientHistory . Enqueue ( new PlayerHistory ( ClientNum ) ) ;
2017-05-26 18:49:27 -04:00
playerCountStart = DateTime . Now ;
}
2015-08-20 01:06:44 -04:00
2018-04-29 16:44:04 -04:00
// send out broadcast messages
2018-03-27 00:54:20 -04:00
if ( LastMessage . TotalSeconds > Manager . GetApplicationSettings ( ) . Configuration ( ) . AutoMessagePeriod
& & BroadcastMessages . Count > 0
2018-03-18 22:25:11 -04:00
& & ClientNum > 0 )
2017-05-26 18:49:27 -04:00
{
2019-02-18 20:30:38 -05:00
string [ ] messages = ( await this . ProcessMessageToken ( Manager . GetMessageTokens ( ) , BroadcastMessages [ NextMessage ] ) ) . Split ( Environment . NewLine ) ;
2018-05-05 16:36:26 -04:00
2018-05-10 01:34:29 -04:00
foreach ( string message in messages )
2018-09-29 15:52:22 -04:00
{
Broadcast ( message ) ;
}
2018-05-05 16:36:26 -04:00
2017-08-17 19:28:08 -04:00
NextMessage = NextMessage = = ( BroadcastMessages . Count - 1 ) ? 0 : NextMessage + 1 ;
2017-05-26 18:49:27 -04:00
start = DateTime . Now ;
}
2017-05-30 17:23:31 -04:00
2018-02-07 00:19:06 -05:00
return true ;
2017-05-26 18:49:27 -04:00
}
2017-05-30 17:23:31 -04:00
2019-08-24 11:02:53 -04:00
catch ( TaskCanceledException )
{
await ShutdownInternal ( ) ;
return true ;
}
2018-04-28 01:22:18 -04:00
// this one is ok
catch ( ServerException e )
2018-04-15 21:27:43 -04:00
{
2020-04-22 21:51:04 -04:00
if ( e is NetworkException & & ! Throttled & & notifyDisconnects )
2018-04-28 01:22:18 -04:00
{
2019-04-23 18:27:20 -04:00
Logger . WriteError ( loc [ "SERVER_ERROR_COMMUNICATION" ] . FormatExt ( $"{IP}:{Port}" ) ) ;
2018-09-23 20:45:54 -04:00
Logger . WriteDebug ( e . GetExceptionInfo ( ) ) ;
2018-04-28 01:22:18 -04:00
}
2020-04-22 21:51:04 -04:00
else
{
Logger . WriteError ( e . Message ) ;
}
2018-04-15 21:27:43 -04:00
return false ;
}
2017-05-30 17:23:31 -04:00
catch ( Exception E )
{
2019-04-21 17:28:13 -04:00
Logger . WriteError ( loc [ "SERVER_ERROR_EXCEPTION" ] . FormatExt ( $"[{IP}:{Port}]" ) ) ;
2018-09-23 20:45:54 -04:00
Logger . WriteDebug ( E . GetExceptionInfo ( ) ) ;
2018-02-07 00:19:06 -05:00
return false ;
2015-03-08 17:20:10 -04:00
}
}
2015-04-19 14:14:30 -04:00
2017-05-26 18:49:27 -04:00
public async Task Initialize ( )
2015-03-08 17:20:10 -04:00
{
2019-02-02 21:19:24 -05:00
RconParser = Manager . AdditionalRConParsers
2019-02-05 12:14:43 -05:00
. FirstOrDefault ( _parser = > _parser . Version = = ServerConfig . RConParserVersion ) ;
2019-02-01 20:49:25 -05:00
2019-02-02 21:19:24 -05:00
EventParser = Manager . AdditionalEventParsers
2019-02-05 12:14:43 -05:00
. FirstOrDefault ( _parser = > _parser . Version = = ServerConfig . EventParserVersion ) ;
2019-02-01 20:49:25 -05:00
2020-04-01 15:11:56 -04:00
RconParser = RconParser ? ? Manager . AdditionalRConParsers [ 0 ] ;
EventParser = EventParser ? ? Manager . AdditionalEventParsers [ 0 ] ;
2019-02-02 19:54:30 -05:00
2019-02-02 21:19:24 -05:00
RemoteConnection . SetConfiguration ( RconParser . Configuration ) ;
2018-06-05 17:31:36 -04:00
2020-06-16 18:16:12 -04:00
var version = await this . GetMappedDvarValueOrDefaultAsync < string > ( "version" ) ;
2018-12-16 22:16:56 -05:00
Version = version . Value ;
2019-02-05 19:02:45 -05:00
GameName = Utilities . GetGame ( version ? . Value ? ? RconParser . Version ) ;
if ( GameName = = Game . UKN )
{
GameName = RconParser . GameName ;
}
2017-08-08 22:44:52 -04:00
2019-02-03 21:47:05 -05:00
if ( version ? . Value ? . Length ! = 0 )
2019-02-02 21:19:24 -05:00
{
2019-06-24 12:01:34 -04:00
var matchedRconParser = Manager . AdditionalRConParsers . FirstOrDefault ( _parser = > _parser . Version = = version . Value ) ;
RconParser . Configuration = matchedRconParser ! = null ? matchedRconParser . Configuration : RconParser . Configuration ;
2019-02-03 21:47:05 -05:00
EventParser = Manager . AdditionalEventParsers . FirstOrDefault ( _parser = > _parser . Version = = version . Value ) ? ? EventParser ;
2019-02-12 21:34:29 -05:00
Version = RconParser . Version ;
2019-02-02 21:19:24 -05:00
}
2018-04-26 20:19:42 -04:00
2020-06-16 18:16:12 -04:00
var svRunning = await this . GetMappedDvarValueOrDefaultAsync < string > ( "sv_running" ) ;
2020-01-13 17:51:16 -05:00
2020-02-01 13:27:14 -05:00
if ( ! string . IsNullOrEmpty ( svRunning . Value ) & & svRunning . Value ! = "1" )
2020-01-13 17:51:16 -05:00
{
throw new ServerException ( loc [ "SERVER_ERROR_NOT_RUNNING" ] ) ;
}
2019-02-01 20:49:25 -05:00
var infoResponse = RconParser . Configuration . CommandPrefixes . RConGetInfo ! = null ? await this . GetInfoAsync ( ) : null ;
2020-06-16 18:16:12 -04:00
string hostname = ( await this . GetMappedDvarValueOrDefaultAsync < string > ( "sv_hostname" , "hostname" , infoResponse ) ) . Value ;
string mapname = ( await this . GetMappedDvarValueOrDefaultAsync < string > ( "mapname" , infoResponse : infoResponse ) ) . Value ;
int maxplayers = ( await this . GetMappedDvarValueOrDefaultAsync < int > ( "sv_maxclients" , infoResponse : infoResponse ) ) . Value ;
string gametype = ( await this . GetMappedDvarValueOrDefaultAsync < string > ( "g_gametype" , "gametype" , infoResponse ) ) . Value ;
var basepath = ( await this . GetMappedDvarValueOrDefaultAsync < string > ( "fs_basepath" ) ) ;
var basegame = ( await this . GetMappedDvarValueOrDefaultAsync < string > ( "fs_basegame" ) ) ;
var game = ( await this . GetMappedDvarValueOrDefaultAsync < string > ( "fs_game" , infoResponse : infoResponse ) ) ;
var logfile = await this . GetMappedDvarValueOrDefaultAsync < string > ( "g_log" ) ;
var logsync = await this . GetMappedDvarValueOrDefaultAsync < int > ( "g_logsync" ) ;
var ip = await this . GetMappedDvarValueOrDefaultAsync < string > ( "net_ip" ) ;
2020-08-20 11:38:11 -04:00
var gamePassword = await this . GetMappedDvarValueOrDefaultAsync ( "g_password" , overrideDefault : "" ) ;
2017-08-08 22:44:52 -04:00
2020-04-20 11:45:58 -04:00
if ( Manager . GetApplicationSettings ( ) . Configuration ( ) . EnableCustomSayName )
{
await this . SetDvarAsync ( "sv_sayname" , Manager . GetApplicationSettings ( ) . Configuration ( ) . CustomSayName ) ;
}
2015-08-22 02:04:30 -04:00
try
{
2020-06-16 18:16:12 -04:00
var website = await this . GetMappedDvarValueOrDefaultAsync < string > ( "_website" ) ;
2020-04-13 19:15:46 -04:00
2020-04-01 15:11:56 -04:00
// this occurs for games that don't give us anything back when
// the dvar is not set
if ( string . IsNullOrWhiteSpace ( website . Value ) )
{
throw new DvarException ( "value is empty" ) ;
}
2017-05-26 18:49:27 -04:00
Website = website . Value ;
}
2015-10-10 22:32:12 -04:00
2018-04-15 00:26:27 -04:00
catch ( DvarException )
2017-05-26 18:49:27 -04:00
{
2018-04-22 16:04:18 -04:00
Website = loc [ "SERVER_WEBSITE_GENERIC" ] ;
2017-05-26 18:49:27 -04:00
}
2015-08-23 17:58:48 -04:00
2018-03-14 14:22:04 -04:00
InitializeMaps ( ) ;
2020-04-13 17:16:31 -04:00
WorkingDirectory = basepath . Value ;
2019-08-01 10:37:33 -04:00
this . Hostname = hostname ;
2018-04-26 20:19:42 -04:00
this . MaxClients = maxplayers ;
2020-06-16 18:16:12 -04:00
this . FSGame = game . Value ;
2018-04-26 20:19:42 -04:00
this . Gametype = gametype ;
2019-02-02 19:54:30 -05:00
this . IP = ip . Value = = "localhost" ? ServerConfig . IPAddress : ip . Value ? ? ServerConfig . IPAddress ;
2020-08-20 11:38:11 -04:00
this . GamePassword = gamePassword . Value ;
2019-11-18 15:02:35 -05:00
UpdateMap ( mapname ) ;
2018-11-25 21:00:36 -05:00
2019-06-27 21:06:30 -04:00
if ( RconParser . CanGenerateLogPath )
2017-05-26 18:49:27 -04:00
{
2019-06-27 21:06:30 -04:00
bool needsRestart = false ;
if ( logsync . Value = = 0 )
{
await this . SetDvarAsync ( "g_logsync" , 2 ) ; // set to 2 for continous in other games, clamps to 1 for IW4
needsRestart = true ;
}
if ( string . IsNullOrWhiteSpace ( logfile . Value ) )
{
logfile . Value = "games_mp.log" ;
await this . SetDvarAsync ( "g_log" , logfile . Value ) ;
needsRestart = true ;
}
if ( needsRestart )
{
Logger . WriteWarning ( "Game log file not properly initialized, restarting map..." ) ;
await this . ExecuteCommandAsync ( "map_restart" ) ;
}
2017-05-26 18:49:27 -04:00
// this DVAR isn't set until the a map is loaded
2017-08-08 22:44:52 -04:00
await this . SetDvarAsync ( "logfile" , 2 ) ;
2018-09-29 15:52:22 -04:00
}
2017-11-02 12:49:45 -04:00
2018-09-08 18:29:30 -04:00
CustomCallback = await ScriptLoaded ( ) ;
2019-02-17 19:48:40 -05:00
2019-02-09 16:35:13 -05:00
// they've manually specified the log path
2020-04-17 16:05:16 -04:00
if ( ! string . IsNullOrEmpty ( ServerConfig . ManualLogPath ) | | ! RconParser . CanGenerateLogPath )
2018-04-23 01:43:48 -04:00
{
2019-02-09 16:35:13 -05:00
LogPath = ServerConfig . ManualLogPath ;
2020-04-17 16:05:16 -04:00
if ( string . IsNullOrEmpty ( LogPath ) & & ! RconParser . CanGenerateLogPath )
{
throw new ServerException ( loc [ "SERVER_ERROR_REQUIRES_PATH" ] . FormatExt ( GameName . ToString ( ) ) ) ;
}
2018-04-23 01:43:48 -04:00
}
2019-02-09 16:35:13 -05:00
2018-04-23 01:43:48 -04:00
else
{
2020-04-14 16:46:14 -04:00
var logInfo = new LogPathGeneratorInfo ( )
{
BaseGameDirectory = basegame . Value ,
BasePathDirectory = basepath . Value ,
GameDirectory = EventParser . Configuration . GameDirectory ? ? "" ,
2020-06-16 18:16:12 -04:00
ModDirectory = game . Value ? ? "" ,
2020-04-14 16:46:14 -04:00
LogFile = logfile . Value ,
IsWindows = RuntimeInformation . IsOSPlatform ( OSPlatform . Windows )
} ;
LogPath = GenerateLogPath ( logInfo ) ;
2018-12-16 22:16:56 -05:00
2019-02-09 22:24:54 -05:00
if ( ! File . Exists ( LogPath ) & & ServerConfig . GameLogServerUrl = = null )
2018-12-16 22:16:56 -05:00
{
2019-04-09 16:02:49 -04:00
Logger . WriteError ( loc [ "SERVER_ERROR_DNE" ] . FormatExt ( LogPath ) ) ;
throw new ServerException ( loc [ "SERVER_ERROR_DNE" ] . FormatExt ( LogPath ) ) ;
2018-12-16 22:16:56 -05:00
}
2018-04-26 02:13:04 -04:00
}
2018-04-28 21:11:13 -04:00
2020-05-04 17:50:02 -04:00
LogEvent = new GameLogEventDetection ( this , GenerateUriForLog ( LogPath , ServerConfig . GameLogServerUrl ? . AbsoluteUri ) , gameLogReaderFactory ) ;
2019-02-09 16:35:13 -05:00
Logger . WriteInfo ( $"Log file is {LogPath}" ) ;
2018-08-30 21:53:00 -04:00
2018-09-04 13:40:29 -04:00
_ = Task . Run ( ( ) = > LogEvent . PollForChanges ( ) ) ;
2020-08-17 22:21:11 -04:00
if ( ! Utilities . IsDevelopment )
{
Broadcast ( loc [ "BROADCAST_ONLINE" ] ) ;
}
2015-03-08 17:20:10 -04:00
}
2015-07-06 13:13:42 -04:00
2020-05-04 17:50:02 -04:00
public Uri [ ] GenerateUriForLog ( string logPath , string gameLogServerUrl )
{
var logUri = new Uri ( logPath ) ;
if ( string . IsNullOrEmpty ( gameLogServerUrl ) )
{
return new [ ] { logUri } ;
}
else
{
return new [ ] { new Uri ( gameLogServerUrl ) , logUri } ;
}
}
2020-04-14 16:46:14 -04:00
public static string GenerateLogPath ( LogPathGeneratorInfo logInfo )
2020-04-13 17:16:31 -04:00
{
string logPath ;
2020-04-14 16:46:14 -04:00
string workingDirectory = logInfo . BasePathDirectory ;
2020-04-13 17:16:31 -04:00
2020-04-14 16:46:14 -04:00
bool baseGameIsDirectory = ! string . IsNullOrWhiteSpace ( logInfo . BaseGameDirectory ) & &
logInfo . BaseGameDirectory . IndexOfAny ( Utilities . DirectorySeparatorChars ) ! = - 1 ;
2020-04-13 19:15:46 -04:00
2020-04-14 16:46:14 -04:00
bool baseGameIsRelative = logInfo . BaseGameDirectory . FixDirectoryCharacters ( )
. Equals ( logInfo . GameDirectory . FixDirectoryCharacters ( ) , StringComparison . InvariantCultureIgnoreCase ) ;
2020-04-13 19:15:46 -04:00
2020-04-13 17:16:31 -04:00
// we want to see if base game is provided and it 'looks' like a directory
2020-04-13 19:15:46 -04:00
if ( baseGameIsDirectory & & ! baseGameIsRelative )
2020-04-13 17:16:31 -04:00
{
2020-04-14 16:46:14 -04:00
workingDirectory = logInfo . BaseGameDirectory ;
2020-04-13 17:16:31 -04:00
}
2020-04-14 16:46:14 -04:00
if ( string . IsNullOrWhiteSpace ( logInfo . ModDirectory ) )
2020-04-13 17:16:31 -04:00
{
2020-04-14 16:46:14 -04:00
logPath = Path . Combine ( workingDirectory , logInfo . GameDirectory , logInfo . LogFile ) ;
2020-04-13 17:16:31 -04:00
}
else
{
2020-04-14 16:46:14 -04:00
logPath = Path . Combine ( workingDirectory , logInfo . ModDirectory , logInfo . LogFile ) ;
2020-04-13 17:16:31 -04:00
}
// fix wine drive name mangling
2020-04-14 16:46:14 -04:00
if ( ! logInfo . IsWindows )
2020-04-13 17:16:31 -04:00
{
2020-04-14 16:46:14 -04:00
logPath = $"{Path.DirectorySeparatorChar}{Regex.Replace(logPath, @" [ A - Z ] : ( \ / | \ \ ) ", " ")}" ;
2020-04-13 17:16:31 -04:00
}
return logPath . FixDirectoryCharacters ( ) ;
}
2020-04-21 18:34:00 -04:00
public override async Task Warn ( string reason , EFClient targetClient , EFClient targetOrigin )
2016-01-16 17:58:24 -05:00
{
2017-11-29 19:35:50 -05:00
// ensure player gets warned if command not performed on them in game
2020-04-21 18:34:00 -04:00
targetClient = targetClient . ClientNumber < 0 ?
Manager . GetActiveClients ( )
. FirstOrDefault ( c = > c . ClientId = = targetClient ? . ClientId ) ? ? targetClient :
targetClient ;
2017-11-29 19:35:50 -05:00
2019-05-29 17:55:35 -04:00
var newPenalty = new EFPenalty ( )
2017-11-29 19:35:50 -05:00
{
2019-05-29 17:55:35 -04:00
Type = EFPenalty . PenaltyType . Warning ,
2017-11-29 19:35:50 -05:00
Expires = DateTime . UtcNow ,
2020-04-21 18:34:00 -04:00
Offender = targetClient ,
Punisher = targetOrigin ,
Offense = reason ,
Link = targetClient . AliasLink
2017-11-29 19:35:50 -05:00
} ;
2020-04-21 18:34:00 -04:00
Logger . WriteDebug ( $"Creating warn penalty for {targetClient}" ) ;
await newPenalty . TryCreatePenalty ( Manager . GetPenaltyService ( ) , Manager . GetLogger ( 0 ) ) ;
2016-01-16 17:58:24 -05:00
2020-04-21 18:34:00 -04:00
if ( targetClient . IsIngame )
2016-01-15 17:15:39 -05:00
{
2020-04-21 18:34:00 -04:00
if ( targetClient . Warnings > = 4 )
2017-11-25 20:29:58 -05:00
{
2020-04-21 18:34:00 -04:00
targetClient . Kick ( loc [ "SERVER_WARNLIMT_REACHED" ] , Utilities . IW4MAdminClient ( this ) ) ;
2017-11-29 19:35:50 -05:00
return ;
}
2020-04-21 18:34:00 -04:00
string message = $"^1{loc[" SERVER_WARNING "]} ^7[^3{targetClient.Warnings}^7]: ^3{targetClient.Name}^7, {reason}" ;
targetClient . CurrentServer . Broadcast ( message ) ;
}
}
2020-01-06 12:04:36 -05:00
2020-04-21 18:34:00 -04:00
public override async Task Kick ( string Reason , EFClient targetClient , EFClient originClient )
{
targetClient = targetClient . ClientNumber < 0 ?
Manager . GetActiveClients ( )
. FirstOrDefault ( c = > c . ClientId = = targetClient ? . ClientId ) ? ? targetClient :
targetClient ;
2017-11-29 19:35:50 -05:00
2019-05-29 17:55:35 -04:00
var newPenalty = new EFPenalty ( )
2017-11-29 19:35:50 -05:00
{
2019-05-29 17:55:35 -04:00
Type = EFPenalty . PenaltyType . Kick ,
2017-11-29 19:35:50 -05:00
Expires = DateTime . UtcNow ,
2020-04-21 18:34:00 -04:00
Offender = targetClient ,
2017-11-29 19:35:50 -05:00
Offense = Reason ,
2020-04-21 18:34:00 -04:00
Punisher = originClient ,
Link = targetClient . AliasLink
2017-11-29 19:35:50 -05:00
} ;
2020-04-21 18:34:00 -04:00
Logger . WriteDebug ( $"Creating kick penalty for {targetClient}" ) ;
await newPenalty . TryCreatePenalty ( Manager . GetPenaltyService ( ) , Manager . GetLogger ( 0 ) ) ;
2016-01-15 17:15:39 -05:00
2020-04-21 18:34:00 -04:00
if ( targetClient . IsIngame )
2017-11-29 19:35:50 -05:00
{
2020-04-21 18:34:00 -04:00
var e = new GameEvent ( )
2017-11-29 19:35:50 -05:00
{
2020-04-21 18:34:00 -04:00
Type = GameEvent . EventType . PreDisconnect ,
Origin = targetClient ,
Owner = this
} ;
2020-05-04 17:50:02 -04:00
Manager . AddEvent ( e ) ;
2020-04-21 18:34:00 -04:00
string formattedKick = string . Format ( RconParser . Configuration . CommandPrefixes . Kick , targetClient . ClientNumber , $"{loc[" SERVER_KICK_TEXT "]} - ^5{Reason}^7" ) ;
await targetClient . CurrentServer . ExecuteCommandAsync ( formattedKick ) ;
2018-04-13 02:32:30 -04:00
}
2020-04-21 18:34:00 -04:00
}
public override async Task TempBan ( string Reason , TimeSpan length , EFClient targetClient , EFClient originClient )
{
// ensure player gets kicked if command not performed on them in the same server
targetClient = targetClient . ClientNumber < 0 ?
Manager . GetActiveClients ( )
. FirstOrDefault ( c = > c . ClientId = = targetClient ? . ClientId ) ? ? targetClient :
targetClient ;
2017-11-29 19:35:50 -05:00
2019-05-29 17:55:35 -04:00
var newPenalty = new EFPenalty ( )
2017-11-25 20:29:58 -05:00
{
2019-05-29 17:55:35 -04:00
Type = EFPenalty . PenaltyType . TempBan ,
2017-11-25 20:29:58 -05:00
Expires = DateTime . UtcNow + length ,
2020-04-21 18:34:00 -04:00
Offender = targetClient ,
2017-11-25 20:29:58 -05:00
Offense = Reason ,
2020-04-21 18:34:00 -04:00
Punisher = originClient ,
Link = targetClient . AliasLink
2017-11-25 20:29:58 -05:00
} ;
2020-04-21 18:34:00 -04:00
Logger . WriteDebug ( $"Creating tempban penalty for {targetClient}" ) ;
await newPenalty . TryCreatePenalty ( Manager . GetPenaltyService ( ) , Manager . GetLogger ( 0 ) ) ;
2017-05-26 18:49:27 -04:00
2020-04-21 18:34:00 -04:00
if ( targetClient . IsIngame )
2017-11-29 19:35:50 -05:00
{
2020-04-21 18:34:00 -04:00
string formattedKick = string . Format ( RconParser . Configuration . CommandPrefixes . Kick , targetClient . ClientNumber , $"^7{loc[" SERVER_TB_TEXT "]}- ^5{Reason}" ) ;
2020-08-17 22:21:11 -04:00
Logger . WriteDebug ( $"Executing tempban kick command for {targetClient}" ) ;
2020-04-21 18:34:00 -04:00
await targetClient . CurrentServer . ExecuteCommandAsync ( formattedKick ) ;
2017-11-29 19:35:50 -05:00
}
2020-04-21 18:34:00 -04:00
}
2017-11-29 19:35:50 -05:00
2020-04-21 18:34:00 -04:00
override public async Task Ban ( string reason , EFClient targetClient , EFClient originClient , bool isEvade = false )
{
// ensure player gets kicked if command not performed on them in the same server
targetClient = targetClient . ClientNumber < 0 ?
Manager . GetActiveClients ( )
. FirstOrDefault ( c = > c . ClientId = = targetClient ? . ClientId ) ? ? targetClient :
targetClient ;
2017-11-29 19:35:50 -05:00
2019-05-29 17:55:35 -04:00
EFPenalty newPenalty = new EFPenalty ( )
2017-11-25 20:29:58 -05:00
{
2019-05-29 17:55:35 -04:00
Type = EFPenalty . PenaltyType . Ban ,
2018-10-15 20:51:04 -04:00
Expires = null ,
2018-12-16 22:16:56 -05:00
Offender = targetClient ,
Offense = reason ,
Punisher = originClient ,
Link = targetClient . AliasLink ,
IsEvadedOffense = isEvade
2017-11-25 20:29:58 -05:00
} ;
2020-04-21 18:34:00 -04:00
Logger . WriteDebug ( $"Creating ban penalty for {targetClient}" ) ;
2019-04-02 21:20:37 -04:00
targetClient . SetLevel ( Permission . Banned , originClient ) ;
2020-04-21 18:34:00 -04:00
await newPenalty . TryCreatePenalty ( Manager . GetPenaltyService ( ) , Manager . GetLogger ( 0 ) ) ;
if ( targetClient . IsIngame )
{
Logger . WriteDebug ( $"Attempting to kicking newly banned client {targetClient}" ) ;
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 ) ;
}
2015-03-08 17:20:10 -04:00
}
2018-11-05 22:01:29 -05:00
override public async Task Unban ( string reason , EFClient Target , EFClient Origin )
2015-03-08 17:20:10 -04:00
{
2019-05-29 17:55:35 -04:00
var unbanPenalty = new EFPenalty ( )
2018-02-17 01:13:38 -05:00
{
2019-05-29 17:55:35 -04:00
Type = EFPenalty . PenaltyType . Unban ,
2019-06-27 21:06:30 -04:00
Expires = DateTime . Now ,
2018-02-17 01:13:38 -05:00
Offender = Target ,
Offense = reason ,
Punisher = Origin ,
When = DateTime . UtcNow ,
Active = true ,
Link = Target . AliasLink
} ;
2019-04-02 21:20:37 -04:00
Target . SetLevel ( Permission . User , Origin ) ;
2019-06-27 21:06:30 -04:00
await Manager . GetPenaltyService ( ) . RemoveActivePenalties ( Target . AliasLink . AliasLinkId ) ;
await Manager . GetPenaltyService ( ) . Create ( unbanPenalty ) ;
2017-05-26 18:49:27 -04:00
}
2017-06-12 13:50:00 -04:00
override public void InitializeTokens ( )
2015-03-13 19:40:16 -04:00
{
2019-06-11 09:00:14 -04:00
Manager . GetMessageTokens ( ) . Add ( new MessageToken ( "TOTALPLAYERS" , ( Server s ) = > Task . Run ( async ( ) = > ( await Manager . GetClientService ( ) . GetTotalClientsAsync ( ) ) . ToString ( ) ) ) ) ;
Manager . GetMessageTokens ( ) . Add ( new MessageToken ( "VERSION" , ( Server s ) = > Task . FromResult ( Application . Program . Version . ToString ( ) ) ) ) ;
2020-01-26 19:06:50 -05:00
Manager . GetMessageTokens ( ) . Add ( new MessageToken ( "NEXTMAP" , ( Server s ) = > SharedLibraryCore . Commands . NextMapCommand . GetNextMap ( s , _translationLookup ) ) ) ;
Manager . GetMessageTokens ( ) . Add ( new MessageToken ( "ADMINS" , ( Server s ) = > Task . FromResult ( SharedLibraryCore . Commands . ListAdminsCommand . OnlineAdmins ( s , _translationLookup ) ) ) ) ;
2017-05-26 18:49:27 -04:00
}
2015-03-08 17:20:10 -04:00
}
}