public override async Task ExecuteEvent(GameEvent E) { //if (Throttled) // return; await ProcessEvent(E); Manager.GetEventApi().OnServerEvent(this, E); foreach (IPlugin P in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) { #if !DEBUG try #endif { if (cts.IsCancellationRequested) { break; } await P.OnEventAsync(E, this); } #if !DEBUG catch (Exception Except) { Logger.WriteError(String.Format("The plugin \"{0}\" generated an error. ( see log )", P.Name)); Logger.WriteDebug(String.Format("Error Message: {0}", Except.Message)); Logger.WriteDebug(String.Format("Error Trace: {0}", Except.StackTrace)); while (Except.InnerException != null) { Except = Except.InnerException; Logger.WriteDebug($"Inner exception: {Except.Message}"); } continue; } #endif } }
//Another version of client from line, written for the line created by a kill or death event override public Player ParseClientFromString(String[] L, int cIDPos) { if (L.Length < cIDPos) { Logger.WriteError("Line sent for client creation is not long enough!"); return null; } int pID = -2; // apparently falling = -1 cID so i can't use it now int.TryParse(L[cIDPos].Trim(), out pID); if (pID == -1) // special case similar to mod_suicide int.TryParse(L[2], out pID); if (pID < 0 || pID > 17) { Logger.WriteError("Event player index " + pID + " is out of bounds!"); Logger.WriteDebug("Offending line -- " + String.Join(";", L)); return null; } return Players[pID]; }
//Process any server event override protected async Task ProcessEvent(GameEvent E) { if (E.Type == GameEvent.EventType.Connect) { // special case for IW5 when connect is from the log if (E.Extra != null) { var logClient = (Player)E.Extra; var client = (await this.GetStatusAsync()) .Single(c => c.ClientNumber == logClient.ClientNumber && c.Name == logClient.Name); client.NetworkId = logClient.NetworkId; await AddPlayer(client); // hack: to prevent plugins from registering it as a real connect E.Type = GameEvent.EventType.Unknown; } else { ChatHistory.Add(new ChatInfo() { Name = E.Origin.Name, Message = "CONNECTED", Time = DateTime.UtcNow }); if (E.Origin.Level > Player.Permission.Moderator) { await E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count)); } } } else if (E.Type == GameEvent.EventType.Disconnect) { ChatHistory.Add(new ChatInfo() { Name = E.Origin.Name, Message = "DISCONNECTED", Time = DateTime.UtcNow }); } else if (E.Type == GameEvent.EventType.Script) { await ExecuteEvent(new GameEvent(GameEvent.EventType.Kill, E.Data, E.Origin, E.Target, this)); } if (E.Type == GameEvent.EventType.Say && E.Data.Length >= 2) { if (E.Data.Substring(0, 1) == "!" || E.Data.Substring(0, 1) == "@" || E.Origin.Level == Player.Permission.Console) { Command C = null; try { C = await ValidateCommand(E); } catch (CommandException e) { Logger.WriteInfo(e.Message); } if (C != null) { if (C.RequiresTarget && E.Target == null) { Logger.WriteWarning("Requested event (command) requiring target does not have a target!"); } try { if (!E.Remote && E.Origin.Level != Player.Permission.Console) { await ExecuteEvent(new GameEvent() { Type = GameEvent.EventType.Command, Data = string.Empty, Origin = E.Origin, Target = E.Target, Owner = this, Extra = C, Remote = E.Remote }); } await C.ExecuteAsync(E); } catch (AuthorizationException e) { await E.Origin.Tell($"{loc["COMMAND_NOTAUTHORIZED"]} - {e.Message}"); } catch (Exception Except) { Logger.WriteError(String.Format("A command request \"{0}\" generated an error.", C.Name)); Logger.WriteDebug(String.Format("Error Message: {0}", Except.Message)); Logger.WriteDebug(String.Format("Error Trace: {0}", Except.StackTrace)); await E.Origin.Tell("^1An internal error occured while processing your command^7"); #if DEBUG await E.Origin.Tell(Except.Message); #endif } } } else // Not a command { E.Data = E.Data.StripColors(); ChatHistory.Add(new ChatInfo() { Name = E.Origin.Name, Message = E.Data, Time = DateTime.UtcNow }); } } if (E.Type == GameEvent.EventType.MapChange) { Logger.WriteInfo($"New map loaded - {ClientNum} active players"); var dict = (Dictionary <string, string>)E.Extra; Gametype = dict["g_gametype"].StripColors(); Hostname = dict["sv_hostname"].StripColors(); string mapname = dict["mapname"].StripColors(); CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname }; } if (E.Type == GameEvent.EventType.MapEnd) { Logger.WriteInfo("Game ending..."); } //todo: move while (ChatHistory.Count > Math.Ceiling((double)ClientNum / 2)) { ChatHistory.RemoveAt(0); } // the last client hasn't fully disconnected yet // so there will still be at least 1 client left if (ClientNum < 2) { ChatHistory.Clear(); } }
override public async Task <bool> AddPlayer(Player polledPlayer) { if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot) || polledPlayer.Ping < 1 || polledPlayer.ClientNumber > (MaxClients) || polledPlayer.ClientNumber < 0) { //Logger.WriteDebug($"Skipping client not in connected state {P}"); return(true); } if (Players[polledPlayer.ClientNumber] != null && Players[polledPlayer.ClientNumber].NetworkId == polledPlayer.NetworkId) { // update their ping & score Players[polledPlayer.ClientNumber].Ping = polledPlayer.Ping; Players[polledPlayer.ClientNumber].Score = polledPlayer.Score; return(true); } #if !DEBUG if (polledPlayer.Name.Length < 3) { Logger.WriteDebug($"Kicking {polledPlayer} because their name is too short"); string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, loc["SERVER_KICK_MINNAME"]); await this.ExecuteCommandAsync(formattedKick); return(false); } if (Players.FirstOrDefault(p => p != null && p.Name == polledPlayer.Name) != null) { Logger.WriteDebug($"Kicking {polledPlayer} because their name is already in use"); string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, loc["SERVER_KICK_NAME_INUSE"]); await this.ExecuteCommandAsync(formattedKick); return(false); } if (polledPlayer.Name == "Unknown Soldier" || polledPlayer.Name == "UnknownSoldier" || polledPlayer.Name == "CHEATER") { Logger.WriteDebug($"Kicking {polledPlayer} because their name is generic"); string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, loc["SERVER_KICK_GENERICNAME"]); await this.ExecuteCommandAsync(formattedKick); return(false); } if (polledPlayer.Name.Where(c => Char.IsControl(c)).Count() > 0) { Logger.WriteDebug($"Kicking {polledPlayer} because their name contains control characters"); string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, loc["SERVER_KICK_CONTROLCHARS"]); await this.ExecuteCommandAsync(formattedKick); return(false); } #endif Logger.WriteDebug($"Client slot #{polledPlayer.ClientNumber} now reserved"); try { Player player = null; var client = await Manager.GetClientService().GetUnique(polledPlayer.NetworkId); // first time client is connecting to server if (client == null) { Logger.WriteDebug($"Client {polledPlayer} first time connecting"); player = (await Manager.GetClientService().Create(polledPlayer)).AsPlayer(); } // client has connected in the past else { client.LastConnection = DateTime.UtcNow; client.Connections += 1; var existingAlias = client.AliasLink.Children .FirstOrDefault(a => a.Name == polledPlayer.Name && a.IPAddress == polledPlayer.IPAddress); if (existingAlias == null) { Logger.WriteDebug($"Client {polledPlayer} has connected previously under a different ip/name"); client.CurrentAlias = new EFAlias() { IPAddress = polledPlayer.IPAddress, Name = polledPlayer.Name, }; // we need to update their new ip and name to the virtual property client.Name = polledPlayer.Name; client.IPAddress = polledPlayer.IPAddress; await Manager.GetClientService().Update(client); } else if (existingAlias.Name == polledPlayer.Name) { client.CurrentAlias = existingAlias; client.CurrentAliasId = existingAlias.AliasId; await Manager.GetClientService().Update(client); } player = client.AsPlayer(); } // Do the player specific stuff player.ClientNumber = polledPlayer.ClientNumber; player.IsBot = polledPlayer.IsBot; player.Score = polledPlayer.Score; player.CurrentServer = this; Players[player.ClientNumber] = player; var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(player.AliasLinkId, player.IPAddress); var currentBan = activePenalties.FirstOrDefault(b => b.Expires > DateTime.UtcNow); if (currentBan != null) { Logger.WriteInfo($"Banned client {player} trying to connect..."); var autoKickClient = (await Manager.GetClientService().Get(1)).AsPlayer(); autoKickClient.CurrentServer = this; if (currentBan.Type == Penalty.PenaltyType.TempBan) { string formattedKick = String.Format(RconParser.GetCommandPrefixes().Kick, polledPlayer.ClientNumber, $"{loc["SERVER_TB_REMAIN"]} ({(currentBan.Expires - DateTime.UtcNow).TimeSpanText()} left)"); await this.ExecuteCommandAsync(formattedKick); } else { await player.Kick($"{loc["SERVER_BAN_PREV"]} {currentBan.Offense}", autoKickClient); } if (player.Level != Player.Permission.Banned && currentBan.Type == Penalty.PenaltyType.Ban) { await player.Ban($"{loc["SERVER_BAN_PREV"]} {currentBan.Offense}", autoKickClient); } return(true); } Logger.WriteInfo($"Client {player} connecting..."); await ExecuteEvent(new GameEvent(GameEvent.EventType.Connect, "", player, null, this)); if (!Manager.GetApplicationSettings().Configuration().EnableClientVPNs&& await VPNCheck.UsingVPN(player.IPAddressString, Manager.GetApplicationSettings().Configuration().IPHubAPIKey)) { await player.Kick(Utilities.CurrentLocalization.LocalizationSet["SERVER_KICK_VPNS_NOTALLOWED"], new Player() { ClientId = 1 }); } return(true); } catch (Exception E) { Manager.GetLogger().WriteError($"Unable to add player {polledPlayer.Name}::{polledPlayer.NetworkId}"); Manager.GetLogger().WriteDebug(E.StackTrace); return(false); } }
override public async Task <bool> ProcessUpdatesAsync(CancellationToken cts) { this.cts = cts; //#if DEBUG == false try //#endif { // first start if (firstRun) { await ExecuteEvent(new GameEvent(GameEvent.EventType.Start, "Server started", null, null, this)); firstRun = false; } if ((DateTime.Now - LastPoll).TotalMinutes < 2 && ConnectionErrors >= 1) { return(true); } try { // trying to reduce the polling rate as every 450ms is unnecessary if ((DateTime.Now - LastPoll).TotalSeconds >= 10) { int polledPlayerCount = await PollPlayersAsync(); if (ConnectionErrors > 0) { Logger.WriteVerbose($"Connection has been reestablished with {IP}:{Port}"); Throttled = false; } ConnectionErrors = 0; LastPoll = DateTime.Now; } } catch (NetworkException e) { ConnectionErrors++; if (ConnectionErrors == 1) { Logger.WriteError($"{e.Message} {IP}:{Port}, reducing polling rate"); Logger.WriteDebug($"Internal Exception: {e.Data["internal_exception"]}"); Throttled = true; } return(true); } LastMessage = DateTime.Now - start; lastCount = DateTime.Now; if ((DateTime.Now - tickTime).TotalMilliseconds >= 1000) { foreach (var Plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) { if (cts.IsCancellationRequested) { break; } await Plugin.OnTickAsync(this); } tickTime = DateTime.Now; } if ((lastCount - playerCountStart).TotalMinutes >= SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval) { while (PlayerHistory.Count > ((60 / SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval) * 12)) // 12 times a hour for 12 hours { PlayerHistory.Dequeue(); } PlayerHistory.Enqueue(new SharedLibraryCore.Helpers.PlayerHistory(ClientNum)); playerCountStart = DateTime.Now; } if (LastMessage.TotalSeconds > Manager.GetApplicationSettings().Configuration().AutoMessagePeriod && BroadcastMessages.Count > 0 && ClientNum > 0) { await Broadcast(Utilities.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage])); NextMessage = NextMessage == (BroadcastMessages.Count - 1) ? 0 : NextMessage + 1; start = DateTime.Now; } if (LogFile == null) { return(true); } if (l_size != LogFile.Length()) { lines = l_size != -1 ? await LogFile.Tail(12) : lines; if (lines != oldLines) { l_size = LogFile.Length(); int end = (lines.Length == oldLines.Length) ? lines.Length - 1 : Math.Abs((lines.Length - oldLines.Length)) - 1; for (count = 0; count < lines.Length; count++) { if (lines.Length < 1 && oldLines.Length < 1) { continue; } if (lines[count] == oldLines[oldLines.Length - 1]) { continue; } if (lines[count].Length < 10) // it's not a needed line { continue; } else { GameEvent event_ = EventParser.GetEvent(this, lines[count]); if (event_ != null) { if (event_.Origin == null) { continue; } await ExecuteEvent(event_); } } } } } oldLines = lines; l_size = LogFile.Length(); if (Manager.ShutdownRequested()) { foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) { await plugin.OnUnloadAsync(); } for (int i = 0; i < Players.Count; i++) { await RemovePlayer(i); } } return(true); } //#if !DEBUG catch (NetworkException) { Logger.WriteError($"Could not communicate with {IP}:{Port}"); return(false); } catch (InvalidOperationException) { Logger.WriteWarning("Event could not parsed properly"); Logger.WriteDebug($"Log Line: {lines[count]}"); return(false); } catch (Exception E) { Logger.WriteError($"Encountered error on {IP}:{Port}"); Logger.WriteDebug("Error Message: " + E.Message); Logger.WriteDebug("Error Trace: " + E.StackTrace); return(false); } //#endif }
override public async Task <bool> ProcessUpdatesAsync(CancellationToken cts) { try { #region SHUTDOWN if (Manager.ShutdownRequested()) { foreach (var client in GetClientsAsList()) { var e = new GameEvent() { Type = GameEvent.EventType.PreDisconnect, Origin = client, Owner = this, }; Manager.GetEventHandler().AddEvent(e); await e.WaitAsync(); } foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) { await plugin.OnUnloadAsync(); } return(true); } #endregion try { var polledClients = await PollPlayersAsync(); var waiterList = new List <GameEvent>(); foreach (var disconnectingClient in polledClients[1]) { if (disconnectingClient.State == ClientState.Disconnecting) { continue; } var e = new GameEvent() { Type = GameEvent.EventType.PreDisconnect, Origin = disconnectingClient, Owner = this }; Manager.GetEventHandler().AddEvent(e); // wait until the disconnect event is complete // because we don't want to try to fill up a slot that's not empty yet waiterList.Add(e); } // wait for all the disconnect tasks to finish await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000))); waiterList.Clear(); // 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, Origin = client, Owner = this }; Manager.GetEventHandler().AddEvent(e); waiterList.Add(e); } // wait for all the connect tasks to finish await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000))); waiterList.Clear(); // these are the clients that have updated foreach (var client in polledClients[2]) { var e = new GameEvent() { Type = GameEvent.EventType.Update, Origin = client, Owner = this }; Manager.GetEventHandler().AddEvent(e); waiterList.Add(e); } await Task.WhenAll(waiterList.Select(e => e.WaitAsync(10 * 1000))); if (ConnectionErrors > 0) { var _event = new GameEvent() { Type = GameEvent.EventType.ConnectionRestored, Owner = this, Origin = Utilities.IW4MAdminClient(this), Target = Utilities.IW4MAdminClient(this) }; Manager.GetEventHandler().AddEvent(_event); } ConnectionErrors = 0; LastPoll = DateTime.Now; } catch (NetworkException e) { ConnectionErrors++; if (ConnectionErrors == 3) { 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); } LastMessage = DateTime.Now - start; lastCount = DateTime.Now; // update the player history if ((lastCount - playerCountStart).TotalMinutes >= SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval) { while (ClientHistory.Count > ((60 / SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval) * 12)) // 12 times a hour for 12 hours { ClientHistory.Dequeue(); } ClientHistory.Enqueue(new SharedLibraryCore.Helpers.PlayerHistory(ClientNum)); playerCountStart = DateTime.Now; } // send out broadcast messages if (LastMessage.TotalSeconds > Manager.GetApplicationSettings().Configuration().AutoMessagePeriod && BroadcastMessages.Count > 0 && ClientNum > 0) { string[] messages = (await this.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage])).Split(Environment.NewLine); foreach (string message in messages) { Broadcast(message); } NextMessage = NextMessage == (BroadcastMessages.Count - 1) ? 0 : NextMessage + 1; start = DateTime.Now; } return(true); } // this one is ok catch (ServerException e) { if (e is NetworkException && !Throttled) { Logger.WriteError(loc["SERVER_ERROR_COMMUNICATION"].FormatExt($"{IP}:{Port}")); Logger.WriteDebug(e.GetExceptionInfo()); } return(false); } catch (Exception E) { Logger.WriteError(loc["SERVER_ERROR_EXCEPTION"].FormatExt($"[{IP}:{Port}]")); Logger.WriteDebug(E.GetExceptionInfo()); return(false); } }
/// <summary> /// Perform the server specific tasks when an event occurs /// </summary> /// <param name="E"></param> /// <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) { var newPermission = (Permission)E.Extra; if (newPermission < Permission.Moderator) { // remove banned or demoted privileged user Manager.GetPrivilegedClients().Remove(E.Target.ClientId); } else { 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() { Name = E.Origin.Name, Message = "CONNECTED", Time = DateTime.UtcNow }); if (E.Origin.Level > EFClient.Permission.Moderator) { E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count)); } } // for some reason there's still a client in the spot else { 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; } } else if (E.Type == GameEvent.EventType.Flag) { // todo: maybe move this to a seperate function Penalty newPenalty = new Penalty() { Type = Penalty.PenaltyType.Flag, Expires = DateTime.UtcNow, Offender = E.Target, Offense = E.Data, Punisher = E.Origin, When = DateTime.UtcNow, Link = E.Target.AliasLink }; var addedPenalty = await Manager.GetPenaltyService().Create(newPenalty); E.Target.SetLevel(Permission.Flagged, E.Origin); } else if (E.Type == GameEvent.EventType.Unflag) { 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) { Reports.Add(new Report() { Origin = E.Origin, Target = E.Target, Reason = E.Data }); } else if (E.Type == GameEvent.EventType.TempBan) { await TempBan(E.Data, (TimeSpan)E.Extra, E.Target, E.Origin);; } else if (E.Type == GameEvent.EventType.Ban) { bool isEvade = E.Extra != null ? (bool)E.Extra : false; await Ban(E.Data, E.Target, E.Origin, isEvade); } else if (E.Type == GameEvent.EventType.Unban) { await Unban(E.Data, E.Target, E.Origin); } else if (E.Type == GameEvent.EventType.Kick) { await Kick(E.Data, E.Target, E.Origin); } else if (E.Type == GameEvent.EventType.Warn) { await Warn(E.Data, E.Target, E.Origin); } else if (E.Type == GameEvent.EventType.Disconnect) { ChatHistory.Add(new ChatInfo() { Name = E.Origin.Name, Message = "DISCONNECTED", Time = DateTime.UtcNow }); 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) { // 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)); if (client != null) { #if DEBUG == true Logger.WriteDebug($"Begin PreDisconnect for {client}"); #endif await OnClientDisconnected(client); #if DEBUG == true Logger.WriteDebug($"End PreDisconnect for {client}"); #endif } 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); } } else if (E.Type == GameEvent.EventType.Update) { #if DEBUG == true Logger.WriteDebug($"Begin Update for {E.Origin}"); #endif await OnClientUpdate(E.Origin); } if (E.Type == GameEvent.EventType.Say) { E.Data = E.Data.StripColors(); if (E.Data?.Length > 0) { string message = E.Data; if (E.Data.IsQuickMessage()) { try { 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 }); } } 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) { var dict = await this.GetInfoAsync(); if (dict == null) { Logger.WriteWarning("Map change event response doesn't have any data"); } else { Gametype = dict["gametype"].StripColors(); Hostname = dict["hostname"]?.StripColors(); string mapname = dict["mapname"]?.StripColors() ?? CurrentMap.Name; CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname }; } } else { var dict = (Dictionary <string, string>)E.Extra; Gametype = dict["g_gametype"].StripColors(); Hostname = dict["sv_hostname"].StripColors(); MaxClients = int.Parse(dict["sv_maxclients"]); string mapname = dict["mapname"].StripColors(); CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname }; } } if (E.Type == GameEvent.EventType.MapEnd) { Logger.WriteInfo("Game ending..."); SessionStart = DateTime.UtcNow; } if (E.Type == GameEvent.EventType.Tell) { await Tell(E.Message, E.Target); } if (E.Type == GameEvent.EventType.Broadcast) { #if DEBUG == false // this is a little ugly but I don't want to change the abstract class if (E.Data != null) { await E.Owner.ExecuteCommandAsync(E.Data); } #endif } lock (ChatHistory) { while (ChatHistory.Count > Math.Ceiling(ClientNum / 2.0)) { ChatHistory.RemoveAt(0); } } // the last client hasn't fully disconnected yet // so there will still be at least 1 client left if (ClientNum < 2) { ChatHistory.Clear(); } return(true); }
public void Init() { #region WEBSERVICE // SharedLibrary.WebService.Init(); //WebSvc = new WebService(); //WebSvc.StartScheduler(); #endregion #region PLUGINS SharedLibrary.Plugins.PluginImporter.Load(this); foreach (var Plugin in SharedLibrary.Plugins.PluginImporter.ActivePlugins) { try { Plugin.OnLoadAsync(this); } catch (Exception e) { Logger.WriteError($"An error occured loading plugin {Plugin.Name}"); Logger.WriteDebug($"Exception: {e.Message}"); Logger.WriteDebug($"Stack Trace: {e.StackTrace}"); } } #endregion #region CONFIG var Configs = Directory.EnumerateFiles($"{Program.OperatingDirectory}config/servers").Where(x => x.Contains(".cfg")); if (Configs.Count() == 0) { ServerConfigurationGenerator.Generate(); } foreach (var file in Configs) { var Conf = ServerConfiguration.Read(file); Task.Run(async() => { try { var ServerInstance = new IW4MServer(this, Conf); await ServerInstance.Initialize(); lock (_servers) { _servers.Add(ServerInstance); } Logger.WriteVerbose($"Now monitoring {ServerInstance.Hostname}"); // this way we can keep track of execution time and see if problems arise. var Status = new AsyncStatus(ServerInstance, UPDATE_FREQUENCY); lock (TaskStatuses) { TaskStatuses.Add(Status); } } catch (ServerException e) { Logger.WriteError($"Not monitoring server {Conf.IP}:{Conf.Port} due to uncorrectable errors"); if (e.GetType() == typeof(DvarException)) { Logger.WriteDebug($"Could not get the dvar value for {(e as DvarException).Data["dvar_name"]} (ensure the server has a map loaded)"); } else if (e.GetType() == typeof(NetworkException)) { Logger.WriteDebug(e.Message); Logger.WriteDebug($"Internal Exception: {e.Data["internal_exception"]}"); } } }); } #endregion #region COMMANDS if (ClientSvc.GetOwners().Result.Count == 0) { Commands.Add(new COwner()); } Commands.Add(new CQuit()); Commands.Add(new CKick()); Commands.Add(new CSay()); Commands.Add(new CTempBan()); Commands.Add(new CBan()); Commands.Add(new CWhoAmI()); Commands.Add(new CList()); Commands.Add(new CHelp()); Commands.Add(new CFastRestart()); Commands.Add(new CMapRotate()); Commands.Add(new CSetLevel()); Commands.Add(new CUsage()); Commands.Add(new CUptime()); Commands.Add(new CWarn()); Commands.Add(new CWarnClear()); Commands.Add(new CUnban()); Commands.Add(new CListAdmins()); Commands.Add(new CLoadMap()); Commands.Add(new CFindPlayer()); Commands.Add(new CListRules()); Commands.Add(new CPrivateMessage()); Commands.Add(new CReload()); Commands.Add(new CFlag()); Commands.Add(new CReport()); Commands.Add(new CListReports()); Commands.Add(new CListBanInfo()); Commands.Add(new CListAlias()); Commands.Add(new CExecuteRCON()); Commands.Add(new CPlugins()); Commands.Add(new CIP()); Commands.Add(new CMask()); Commands.Add(new CPruneAdmins()); foreach (Command C in SharedLibrary.Plugins.PluginImporter.ActiveCommands) { Commands.Add(C); } #endregion Running = true; }
override public async Task<bool> ProcessUpdatesAsync(CancellationToken cts) { this.cts = cts; #if DEBUG == false try #endif { // first start if (firstRun) { await ExecuteEvent(new Event(Event.GType.Start, "Server started", null, null, this)); firstRun = false; } if ((DateTime.Now - LastPoll).TotalMinutes < 2 && ConnectionErrors >= 1) return true; try { int polledPlayerCount = await PollPlayersAsync(); if (ConnectionErrors > 0) { Logger.WriteVerbose($"Connection has been reestablished with {IP}:{Port}"); Throttled = false; } ConnectionErrors = 0; LastPoll = DateTime.Now; } catch (SharedLibrary.Exceptions.NetworkException e) { ConnectionErrors++; if (ConnectionErrors == 1) { Logger.WriteError($"{e.Message} {IP}:{Port}, reducing polling rate"); Logger.WriteDebug($"Internal Exception: {e.Data["internal_exception"]}"); Throttled = true; } return true; } LastMessage = DateTime.Now - start; lastCount = DateTime.Now; if ((DateTime.Now - tickTime).TotalMilliseconds >= 1000) { foreach (var Plugin in SharedLibrary.Plugins.PluginImporter.ActivePlugins) { if (cts.IsCancellationRequested) break; await Plugin.OnTickAsync(this); } tickTime = DateTime.Now; } if ((lastCount - playerCountStart).TotalMinutes >= SharedLibrary.Helpers.PlayerHistory.UpdateInterval) { while (PlayerHistory.Count > ((60 / SharedLibrary.Helpers.PlayerHistory.UpdateInterval) * 12)) // 12 times a hour for 12 hours PlayerHistory.Dequeue(); PlayerHistory.Enqueue(new SharedLibrary.Helpers.PlayerHistory(ClientNum)); playerCountStart = DateTime.Now; } if (LastMessage.TotalSeconds > MessageTime && BroadcastMessages.Count > 0 && ClientNum > 0) { await Broadcast(Utilities.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage])); NextMessage = NextMessage == (BroadcastMessages.Count - 1) ? 0 : NextMessage + 1; start = DateTime.Now; } if (LogFile == null) return true; if (l_size != LogFile.Length()) { // this should be the longest running task await Task.FromResult(lines = LogFile.Tail(12)); if (lines != oldLines) { l_size = LogFile.Length(); int end = (lines.Length == oldLines.Length) ? lines.Length - 1 : Math.Abs((lines.Length - oldLines.Length)) - 1; for (int count = 0; count < lines.Length; count++) { if (lines.Length < 1 && oldLines.Length < 1) continue; if (lines[count] == oldLines[oldLines.Length - 1]) continue; if (lines[count].Length < 10) // it's not a needed line continue; else { string[] game_event = lines[count].Split(';'); Event event_ = Event.ParseEventString(game_event, this); if (event_ != null) { if (event_.Origin == null) continue; await ExecuteEvent(event_); } } } } } oldLines = lines; l_size = LogFile.Length(); if (!((ApplicationManager)Manager).Running) { foreach (var plugin in SharedLibrary.Plugins.PluginImporter.ActivePlugins) await plugin.OnUnloadAsync(); for (int i = 0; i < Players.Count; i++) await RemovePlayer(i); } return true; } #if DEBUG == false catch (SharedLibrary.Exceptions.NetworkException) { Logger.WriteError($"Could not communicate with {IP}:{Port}"); return false; } catch (Exception E) { Logger.WriteError($"Encountered error on {IP}:{Port}"); Logger.WriteDebug("Error Message: " + E.Message); Logger.WriteDebug("Error Trace: " + E.StackTrace); return false; } #endif }
override public async Task<bool> AddPlayer(Player polledPlayer) { if (polledPlayer.Ping == 999 || polledPlayer.Ping < 1 || polledPlayer.ClientNumber > (MaxClients) || polledPlayer.ClientNumber < 0) { //Logger.WriteDebug($"Skipping client not in connected state {P}"); return true; } if (Players[polledPlayer.ClientNumber] != null && Players[polledPlayer.ClientNumber].NetworkId == polledPlayer.NetworkId) { // update their ping & score Players[polledPlayer.ClientNumber].Ping = polledPlayer.Ping; Players[polledPlayer.ClientNumber].Score = polledPlayer.Score; return true; } if (polledPlayer.Name.Length < 3) { Logger.WriteDebug($"Kicking {polledPlayer} because their name is too short"); await this.ExecuteCommandAsync($"clientkick {polledPlayer.ClientNumber} \"Your name must contain atleast 3 characters.\""); return false; } if (Players.FirstOrDefault(p => p != null && p.Name == polledPlayer.Name) != null) { Logger.WriteDebug($"Kicking {polledPlayer} because their name is already in use"); await this.ExecuteCommandAsync($"clientkick {polledPlayer.ClientNumber} \"Your name is being used by someone else.\""); return false; } if (polledPlayer.Name == "Unknown Soldier" || polledPlayer.Name == "UnknownSoldier") { Logger.WriteDebug($"Kicking {polledPlayer} because their name is generic"); await this.ExecuteCommandAsync($"clientkick {polledPlayer.ClientNumber} \"Please change your name using /name.\""); return false; } Logger.WriteDebug($"Client slot #{polledPlayer.ClientNumber} now reserved"); try { Player player = null; var client = await Manager.GetClientService().GetUnique(polledPlayer.NetworkId); // first time client is connecting to server if (client == null) { Logger.WriteDebug($"Client {polledPlayer} first time connecting"); player = (await Manager.GetClientService().Create(polledPlayer)).AsPlayer(); } // client has connected in the past else { client.Connections += 1; var existingAlias = client.AliasLink.Children .FirstOrDefault(a => a.Name == polledPlayer.Name && a.IPAddress == polledPlayer.IPAddress); if (existingAlias == null) { Logger.WriteDebug($"Client {polledPlayer} has connected previously under a different ip/name"); client.CurrentAlias = new SharedLibrary.Database.Models.EFAlias() { IPAddress = polledPlayer.IPAddress, Name = polledPlayer.Name, }; // we need to update their new ip and name to the virtual property client.Name = polledPlayer.Name; client.IPAddress = polledPlayer.IPAddress; await Manager.GetClientService().Update(client); } else if (existingAlias.Name == polledPlayer.Name) { client.CurrentAlias = existingAlias; client.CurrentAliasId = existingAlias.AliasId; await Manager.GetClientService().Update(client); } player = client.AsPlayer(); } // Do the player specific stuff player.ClientNumber = polledPlayer.ClientNumber; player.Score = polledPlayer.Score; player.CurrentServer = this; Players[player.ClientNumber] = player; var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(player.AliasLinkId); var currentBan = activePenalties.FirstOrDefault(b => b.Expires > DateTime.UtcNow); if (currentBan != null) { Logger.WriteInfo($"Banned client {player} trying to connect..."); var autoKickClient = (await Manager.GetClientService().Get(1)).AsPlayer(); autoKickClient.CurrentServer = this; if (currentBan.Type == Penalty.PenaltyType.TempBan) await this.ExecuteCommandAsync($"clientkick {player.ClientNumber} \"You are temporarily banned. ({(currentBan.Expires - DateTime.UtcNow).TimeSpanText()} left)\""); else await player.Kick($"Previously banned for {currentBan.Offense}", autoKickClient); if (player.Level != Player.Permission.Banned && currentBan.Type == Penalty.PenaltyType.Ban) await player.Ban($"Previously banned for {currentBan.Offense}", autoKickClient); return true; } Logger.WriteInfo($"Client {player} connecting..."); await ExecuteEvent(new Event(Event.GType.Connect, "", player, null, this)); return true; } catch (Exception E) { Manager.GetLogger().WriteError($"Unable to add player {polledPlayer.Name}::{polledPlayer.NetworkId}"); Manager.GetLogger().WriteDebug(E.StackTrace); return false; } }